feat(schema): carry a stable group-definition identity on shared expansions (#363)
Step 1 of the GSS content-matcher build (docs/design/counted-content-automaton.md). A multiply-referenced `<xs:group>` expands to one copy-on-write-shared `Particle` (GroupParticleMemo), but `Group`/`Term` are value types with no exposed identity, so a content-model compiler cannot tell two references apart and inlines the group per reference (the 2^K build the GSS matcher will close). Group now carries a `definitionID: Int?`, stamped from a per-compile counter on GroupParticleMemo when an expansion is first compiled, so every reference sharing the memo key reuses the same stamped term and distinct definitions differ. It is metadata only: Group is not Equatable/Hashable, so it perturbs no determinism or restriction comparison, and it is nil for an inline group. FP-neutral by construction (no consumer yet). Full in-repo suite green; the XSTS buckets are unchanged (valid-schemas-rejected 0, invalid-schemas-accepted 8, valid-instances-rejected 0, invalid-instances-accepted 14); swiftformat/swiftlint clean. Step 2 (a call/return content-model build keyed on this id, alongside the inlining oracle) consumes it.
This commit is contained in:
parent
116b260149
commit
e81e4b4df6
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changed
|
||||
|
||||
- A model `Group` now carries an internal `definitionID`, a stable identity stamped on each `<xs:group>` expansion (from the existing per-compile memo key) and shared by every reference to it. It is metadata only (`Group` is not `Equatable`/`Hashable`, so it perturbs no comparison) and has no consumer yet: it is the first step of replacing the per-instance content-model build's per-reference group inlining (the `2^K` doubling-chain hang) with the graph-structured-stack matcher designed in `docs/design/counted-content-automaton.md`. No behaviour change; the XSTS buckets are unchanged.
|
||||
|
||||
- The schema-validity differential harness (`XSTSDifferentialTests`, the opt-in libxml2 characterisation for production-readiness stopper 3) now reports each disagreeing schema by its path relative to the corpus root, not its bare basename. The same basename recurs across subdirectories (`Facets/Schemas/gMonth_maxInclusive001.xsd`, which carries `value=""` and is correctly rejected, versus `Facets/gMonth/gMonth_maxInclusive001.xsd`, which carries the contested 2001-REC `--MM--` form), so a basename-only entry hid which file actually disagrees. The header now also records the triage of the "libxml2 stricter" queue: it is dominated by libxml2 over-strictness (a `maxOccurs` like `999999999999999` is a valid unbounded-precision integer libxml2 rejects on its machine-int limit; an unresolvable `include`/`import` it fails to load) and contested datatype-version readings (`gMonth --MM--`, accepted to keep valid XSTS instances; `anyURI` strictness), not clean false negatives. Characterisation only; no shipped behaviour changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -250,10 +250,20 @@ public extension PureXML.Schema {
|
||||
struct Group: Sendable {
|
||||
public var compositor: Compositor
|
||||
public var particles: [Particle]
|
||||
/// A stable identity shared by every reference that expands to this same
|
||||
/// `<xs:group>` definition (in the same scope), assigned when the expansion
|
||||
/// is first compiled. It lets a content-model compiler emit a multiply
|
||||
/// referenced group once instead of inlining it per reference (the `2^K`
|
||||
/// build the design doc closes with a graph-structured-stack matcher). It
|
||||
/// is metadata only: `Group` is not `Equatable`/`Hashable`, so carrying it
|
||||
/// changes no comparison, and it is `nil` for an inline (non-referenced)
|
||||
/// group.
|
||||
public var definitionID: Int?
|
||||
|
||||
public init(compositor: Compositor, particles: [Particle]) {
|
||||
public init(compositor: Compositor, particles: [Particle], definitionID: Int? = nil) {
|
||||
self.compositor = compositor
|
||||
self.particles = particles
|
||||
self.definitionID = definitionID
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,5 +18,14 @@ extension PureXML.Schema {
|
||||
/// instance per compile.
|
||||
final class GroupParticleMemo {
|
||||
var store: [GroupParticleMemoKey: PureXML.Schema.Particle] = [:]
|
||||
/// Source of the stable `Group.definitionID` stamped on each distinct
|
||||
/// expansion as it is first compiled, so every reference sharing the
|
||||
/// expansion carries the same id and distinct definitions differ.
|
||||
private var nextID = 0
|
||||
|
||||
func allocateDefinitionID() -> Int {
|
||||
defer { nextID += 1 }
|
||||
return nextID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,8 +110,22 @@ extension PureXML.Schema.XSDParser {
|
||||
guard let inner = modelGroup(in: definition, scoped.visiting(name)) else {
|
||||
return nil
|
||||
}
|
||||
context.groupMemo.store[key] = inner
|
||||
return Particle(occurrenceRange: occurrence, term: inner.term)
|
||||
// Stamp the expansion with a stable identity so a content-model compiler
|
||||
// can emit this group once rather than inline it per reference; every
|
||||
// reference sharing this memo key reuses the stamped term.
|
||||
let stamped = withDefinitionID(context.groupMemo.allocateDefinitionID(), on: inner)
|
||||
context.groupMemo.store[key] = stamped
|
||||
return Particle(occurrenceRange: occurrence, term: stamped.term)
|
||||
}
|
||||
|
||||
/// `particle` with its model-group term carrying `id`, or unchanged when the
|
||||
/// term is not a group (a group reference always expands to a model group, so
|
||||
/// this only no-ops defensively).
|
||||
private static func withDefinitionID(_ id: Int, on particle: Particle) -> Particle {
|
||||
guard case let .group(group) = particle.term else { return particle }
|
||||
var stamped = group
|
||||
stamped.definitionID = id
|
||||
return Particle(occurrenceRange: particle.occurrenceRange, term: .group(stamped))
|
||||
}
|
||||
|
||||
/// The particle for an `<xs:element ref="...">`. An abstract head may not appear
|
||||
|
||||
Loading…
Reference in New Issue
Block a user