PureXML/Sources/Schema/SchemaSimpleTypeVariety.swift
Mihaela Mihaljevic 4024801171
perf(schema): resolve a simple-type restriction chain in linear, not cubic, time (#373)
A long <xs:simpleType> restriction chain is flat in the document, so the same base was resolved repeatedly: the named simple types were resolved by an N-pass fixpoint, and the facet-applicability variety check re-scanned the schema's children to find each base at every link of every type's chain (compiling a 600-link chain took ~4.4s). Named simple types are now resolved once in dependency order (XSDSimpleTypeOrder), and the variety check uses an O(1) name map and memoizes each type's variety (SchemaSimpleTypeVariety); the same chain now compiles in ~30ms. Behaviour-preserving, verified conformance-neutral over the full W3C XSTS corpus, the determinism differential, and the full suite.
2026-06-29 02:55:02 +02:00

62 lines
3.6 KiB
Swift

extension PureXML.Schema.XSDParser {
/// The lookups a simple-type variety walk needs, bundled so the walk's base resolution
/// is O(1) (the name map) and threadable without a long parameter list.
struct VarietyLookup {
let byName: [String: XSDTree]
let bindings: [String: String]
let target: String?
/// Built from a schema element: its global `simpleType` definitions keyed by name
/// (first declaration wins, matching the prior first-match scan), so the variety
/// walk looks a base up in O(1) instead of re-scanning the schema's children at
/// every link of a `restriction` chain (the former O(n^3) on a long, hostile chain).
init(schema: XSDTree, bindings: [String: String], target: String?) {
var byName: [String: XSDTree] = [:]
for child in PureXML.Schema.XSDNode.elementChildren(schema) where PureXML.Schema.XSDNode.localName(child) == "simpleType" {
if let name = PureXML.Schema.XSDNode.attribute(child, "name"), byName[name] == nil { byName[name] = child }
}
self.byName = byName
self.bindings = bindings
self.target = target
}
}
/// The variety (`"list"`, `"union"`, or nil for atomic/unknown) a `restriction`
/// derives from: an inline `simpleType` base wins, otherwise the `base` QName.
/// `cache` memoizes each named type's variety, so a chain is walked once, not per type.
static func restrictionBaseVariety(_ restriction: XSDTree, _ lookup: VarietyLookup, _ visited: Set<String>, _ cache: inout [String: String?]) -> String? {
if let inlineBase = PureXML.Schema.XSDNode.firstChild(restriction, named: "simpleType") {
return simpleTypeVariety(inlineBase, lookup, visited, &cache)
}
guard let base = PureXML.Schema.XSDNode.attribute(restriction, "base") else { return nil }
return namedTypeVariety(base, lookup, visited, &cache)
}
private static func simpleTypeVariety(_ simpleType: XSDTree, _ lookup: VarietyLookup, _ visited: Set<String>, _ cache: inout [String: String?]) -> String? {
if PureXML.Schema.XSDNode.firstChild(simpleType, named: "list") != nil { return "list" }
if PureXML.Schema.XSDNode.firstChild(simpleType, named: "union") != nil { return "union" }
if let restriction = PureXML.Schema.XSDNode.firstChild(simpleType, named: "restriction") {
return restrictionBaseVariety(restriction, lookup, visited, &cache)
}
return nil
}
/// The variety of a named base, resolved namespace-aware so a same-local-name
/// collision never flips the variety: a base in the XSD namespace is a built-in
/// (only the list datatypes are list-variety; the rest atomic, nil); a base in
/// the schema's own target namespace resolves to a declared global `simpleType`
/// (cycle-guarded); a base in any other namespace is external and skipped (nil).
private static func namedTypeVariety(_ base: String, _ lookup: VarietyLookup, _ visited: Set<String>, _ cache: inout [String: String?]) -> String? {
let (prefix, local) = splitQName(base)
let uri = prefix.map { lookup.bindings[$0] } ?? lookup.bindings[""]
if uri == xsdNamespace {
return builtinListTypes.contains(local) ? "list" : nil
}
if let cached = cache[local] { return cached }
guard uri == lookup.target, !visited.contains(local), let declared = lookup.byName[local] else { return nil }
let variety = simpleTypeVariety(declared, lookup, visited.union([local]), &cache)
cache[local] = variety
return variety
}
}