diff --git a/Sources/FoundationEssentials/Predicate/Predicate.swift b/Sources/FoundationEssentials/Predicate/Predicate.swift index 3a8c2a5f8..fadfc6884 100644 --- a/Sources/FoundationEssentials/Predicate/Predicate.swift +++ b/Sources/FoundationEssentials/Predicate/Predicate.swift @@ -49,6 +49,52 @@ extension Predicate { } } +@available(FoundationPreview 6.4, *) +extension Predicate { + public init(all subpredicates: some BidirectionalCollection) { + var iterator = subpredicates.reversed().makeIterator() + + if let guarded = iterator.next() { + self.init({ (input: repeat PredicateExpressions.Variable) in + var pieces: any StandardPredicateExpression = PredicateExpressions.build_evaluate(PredicateExpressions.build_Arg(guarded), repeat each input) + + while let next = iterator.next() { + pieces = Self.meet(PredicateExpressions.build_evaluate(PredicateExpressions.build_Arg(next), repeat each input), pieces) + } + + return pieces + }) + } else { + self.init(value: true) + } + } + + public init(any subpredicates: some BidirectionalCollection) { + var iterator = subpredicates.reversed().makeIterator() + + if let guarded = iterator.next() { + self.init({ (input: repeat PredicateExpressions.Variable) in + var pieces: any StandardPredicateExpression = PredicateExpressions.build_evaluate(PredicateExpressions.build_Arg(guarded), repeat each input) + + while let next = iterator.next() { + pieces = Self.join(PredicateExpressions.build_evaluate(PredicateExpressions.build_Arg(next), repeat each input), pieces) + } + + return pieces + }) + } else { + self.init(value: false) + } + } + + fileprivate static func meet, U: StandardPredicateExpression>(_ lhs: T, _ rhs: U) -> any StandardPredicateExpression { + PredicateExpressions.build_Conjunction(lhs: lhs, rhs: rhs) + } + + fileprivate static func join, U: StandardPredicateExpression>(_ lhs: T, _ rhs: U) -> any StandardPredicateExpression { + PredicateExpressions.build_Disjunction(lhs: lhs, rhs: rhs) + } +} // Namespace for operator expressions @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) diff --git a/Tests/FoundationEssentialsTests/PredicateTests.swift b/Tests/FoundationEssentialsTests/PredicateTests.swift index b16223c3e..9c619d881 100644 --- a/Tests/FoundationEssentialsTests/PredicateTests.swift +++ b/Tests/FoundationEssentialsTests/PredicateTests.swift @@ -541,4 +541,42 @@ private struct PredicateTests { #expect(try expression.evaluate(i) == i + 1) } } + + @Test func all() throws { + #expect(try Predicate(all: []).evaluate(42)) + #expect(try !Predicate(all: [Predicate.true, Predicate.false]).evaluate(42)) + let input = Object(a: 6, b: "xyz", c: 0.27, d: 97, e: "u", f: false, g: [7, 42, 1, 0, 25]) + var predicates: [Predicate] = [ + #Predicate { $0.a == 6 }, + #Predicate { $0.b == "xyz" }, + #Predicate { $0.c < 0.3 }, + #Predicate { $0.d == 97 }, + #Predicate { $0.d % 2 != 0 }, + #Predicate { !$0.f }, + #Predicate { $0.g.contains(42) }, + #Predicate { $0.g.min() == 0 } + ] + #expect(try Predicate(all: predicates).evaluate(input)) + predicates.append(#Predicate { $0.g.max() == 7 }) + #expect(try !Predicate(all: predicates).evaluate(input)) + predicates.append(#Predicate { $0.c < 0.0 }) + #expect(try !Predicate(all: predicates).evaluate(input)) + } + + @Test func any() throws { + #expect(try !Predicate(any: []).evaluate(42)) + #expect(try Predicate(any: [Predicate.true, Predicate.false]).evaluate(42)) + let input = Object(a: 6, b: "xyz", c: 0.27, d: 97, e: "u", f: false, g: [7, 42, 1, 0, 25]) + let predicates: [Predicate] = [ + #Predicate { $0.g.max() == 7 }, + #Predicate { $0.c < 0.0 }, + #Predicate { $0.a == 6 }, + #Predicate { $0.b == "xyz" }, + #Predicate { $0.c < 0.3 } + ] + #expect(try Predicate(any: predicates.dropLast()).evaluate(input)) + #expect(try Predicate(any: predicates.dropLast(2)).evaluate(input)) + #expect(try !Predicate(any: predicates.dropLast(3)).evaluate(input)) + #expect(try !Predicate(any: predicates.dropLast(4)).evaluate(input)) + } }