A flat chain of <xs:group> references is not bounded by the parser's maxDepth (the definitions are siblings, not nested), so the compositional UPA determinism check's lazy per-reference recursion overflowed the native 8 MB stack while compiling a legal schema. Group summaries form an acyclic graph, so they are now precomputed bottom-up in dependency order (GroupDependencyOrder): the walk's group lookup is a cache hit and recursion is bounded by a single group's own model nesting. The precomputed reachable set is identical to the lazy walk's, so the verdict is unchanged. Verified verdict-equivalent over the full W3C XSTS corpus, the libxml2 determinism differential (0 FP, same 3 FN), the full suite, and an adversarial critic. Residual, tracked: the particle compiler that inlines a group reference still overflows on a far longer chain; closing it needs a content-model nesting-depth limit with a located error.
48 lines
2.6 KiB
Swift
48 lines
2.6 KiB
Swift
/// The overlap tests for a compositional-determinism decision set: a UPA violation
|
|
/// is two DISTINCT particles in one decision set that can match the same element
|
|
/// (same element name, or a wildcard admitting the other's name/namespace). Pure
|
|
/// functions over ``CompItem`` sets, factored out of ``CompWalk`` so the walk file
|
|
/// stays within its length budget; the walk accumulates decision sets and hands
|
|
/// each to ``overlap(_:)``.
|
|
enum CompConflict {
|
|
static func overlap(_ set: [CompItem]) -> String? {
|
|
var nameOwner: [PureXML.Model.QualifiedName: ObjectIdentifier] = [:]
|
|
var wildcards: [(card: PureXML.Schema.Wildcard, particle: ObjectIdentifier)] = []
|
|
for item in set {
|
|
switch item.label {
|
|
case let .name(qname):
|
|
if let owner = nameOwner[qname], owner != item.particle { return qname.localName }
|
|
if nameOwner[qname] == nil { nameOwner[qname] = item.particle }
|
|
case let .wildcard(card):
|
|
wildcards.append((card, item.particle))
|
|
}
|
|
}
|
|
for (index, wildcard) in wildcards.enumerated() {
|
|
// A wildcard conflicts with any name it admits, from a distinct particle,
|
|
// and with any other wildcard it intersects, from a distinct particle.
|
|
if nameOwner.contains(where: { wildcard.card.admits($0.key) && $0.value != wildcard.particle }) { return "any" }
|
|
for other in wildcards[(index + 1)...] where other.particle != wildcard.particle && wildcardsOverlap(wildcard.card, other.card) {
|
|
return "any"
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
static func wildcardsOverlap(_ lhs: PureXML.Schema.Wildcard, _ rhs: PureXML.Schema.Wildcard) -> Bool {
|
|
// Two wildcards overlap when some namespace name is admitted by both. Any
|
|
// two `not` forms always share infinitely many named namespaces; a `not`
|
|
// form overlaps a set when the set holds a named namespace it admits.
|
|
switch (lhs.namespace, rhs.namespace) {
|
|
case (.any, _), (_, .any): true
|
|
case let (.enumerated(lhsSet), .enumerated(rhsSet)): !lhsSet.isDisjoint(with: rhsSet)
|
|
case let (.enumerated(set), .notNamespace(name)), let (.notNamespace(name), .enumerated(set)):
|
|
set.contains { !$0.isEmpty && $0 != name }
|
|
case let (.enumerated(set), .notAbsent), let (.notAbsent, .enumerated(set)):
|
|
set.contains { !$0.isEmpty }
|
|
case (.notNamespace, .notNamespace), (.notNamespace, .notAbsent),
|
|
(.notAbsent, .notNamespace), (.notAbsent, .notAbsent):
|
|
true
|
|
}
|
|
}
|
|
}
|