PureXML/Sources/XPath/EvaluatorOperators.swift
Mihaela Mihaljevic 380160cc2b refactor(validation): decompose XSD instance validation into composable rules
The two monolithic XSD content/identity rules only satisfied the OpenAPIKit
validation idiom at the wrong granularity: no per-constraint removability,
no isolable failing/near-miss tests. Split them into 32 named rules, each
finding tagged at its origin and cached per XSDContext so the walk still
runs once per document (the #101 decision against duplicating XSD's
recursive type resolution still holds). Wired the streaming path's ID/IDREF
and identity checks through the same framework instead of calling the
validators imperatively, and added the unwrapAndLookup combinator the rule
already named as required.

Also includes the mandated repo-wide swiftformat/swiftlint pass.
2026-07-12 14:12:17 +02:00

118 lines
4.1 KiB
Swift

extension PureXML.XPath.Evaluator {
typealias BinaryOperator = PureXML.XPath.BinaryOperator
static func evalBinary(
_ oper: BinaryOperator,
_ left: Expression,
_ right: Expression,
_ context: EvaluationContext,
) throws -> Value {
switch oper {
case .logicalOr:
try .boolean(eval(left, context).boolean || eval(right, context).boolean)
case .logicalAnd:
try .boolean(eval(left, context).boolean && eval(right, context).boolean)
case .add, .subtract, .multiply, .divide, .modulo:
try .number(arithmetic(oper, eval(left, context).number, eval(right, context).number))
case .equal, .notEqual:
try .boolean(equality(eval(left, context), eval(right, context), negate: oper == .notEqual))
case .lessThan, .lessOrEqual, .greaterThan, .greaterOrEqual:
try .boolean(relational(eval(left, context), eval(right, context), oper))
}
}
private static func arithmetic(_ oper: BinaryOperator, _ lhs: Double, _ rhs: Double) -> Double {
switch oper {
case .add: lhs + rhs
case .subtract: lhs - rhs
case .multiply: lhs * rhs
case .divide: lhs / rhs
case .modulo: lhs.truncatingRemainder(dividingBy: rhs)
default: .nan
}
}
// MARK: Equality
private static func equality(_ lhs: Value, _ rhs: Value, negate: Bool) -> Bool {
switch (lhs, rhs) {
case let (.nodeSet(left), .nodeSet(right)):
nodeSetEquality(left, right, negate: negate)
case let (.nodeSet(nodes), scalar):
nodeSetScalarEquality(nodes, scalar, negate: negate)
case let (scalar, .nodeSet(nodes)):
nodeSetScalarEquality(nodes, scalar, negate: negate)
default:
scalarEquality(lhs, rhs, negate: negate)
}
}
private static func nodeSetEquality(_ left: [Node], _ right: [Node], negate: Bool) -> Bool {
let rightStrings = right.map(\.stringValue)
for value in left.map(\.stringValue) {
for other in rightStrings where (value == other) != negate {
return true
}
}
return false
}
private static func nodeSetScalarEquality(_ nodes: [Node], _ scalar: Value, negate: Bool) -> Bool {
switch scalar {
case let .boolean(flag):
(!nodes.isEmpty == flag) != negate
case let .number(number):
nodes.contains { (PureXML.XPath.Value.parseNumber($0.stringValue) == number) != negate }
case let .string(text):
nodes.contains { ($0.stringValue == text) != negate }
case .nodeSet:
false
}
}
private static func scalarEquality(_ lhs: Value, _ rhs: Value, negate: Bool) -> Bool {
let equal: Bool = if case .boolean = lhs {
lhs.boolean == rhs.boolean
} else if case .boolean = rhs {
lhs.boolean == rhs.boolean
} else if case .number = lhs {
lhs.number == rhs.number
} else if case .number = rhs {
lhs.number == rhs.number
} else {
lhs.string == rhs.string
}
return equal != negate
}
// MARK: Relational
private static func relational(_ lhs: Value, _ rhs: Value, _ oper: BinaryOperator) -> Bool {
let lefts = numbers(of: lhs)
let rights = numbers(of: rhs)
for left in lefts {
for right in rights where numericCompare(left, right, oper) {
return true
}
}
return false
}
private static func numbers(of value: Value) -> [Double] {
if case let .nodeSet(nodes) = value {
return nodes.map { PureXML.XPath.Value.parseNumber($0.stringValue) }
}
return [value.number]
}
private static func numericCompare(_ lhs: Double, _ rhs: Double, _ oper: BinaryOperator) -> Bool {
switch oper {
case .lessThan: lhs < rhs
case .lessOrEqual: lhs <= rhs
case .greaterThan: lhs > rhs
case .greaterOrEqual: lhs >= rhs
default: false
}
}
}