expressir 2.4.4 → 2.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: faa2eff1343ffba0a34111f1b9c48f2ea65fc8585da64b15844e39cd413b067f
4
- data.tar.gz: 34ad23a65dabd3806206cf741c4e19d468bad9f6c566b89c0af10c594e0ca99b
3
+ metadata.gz: d4cb3a63d16dd31f57a8050366d6b9d9d4e442d65e7df44c01f5edad8f330565
4
+ data.tar.gz: f234902848350d1abbca9aef98c0ca2bd562fe19463a51e395ec53688ef4e72b
5
5
  SHA512:
6
- metadata.gz: 05d55fe19517184f73d6f97815be45fef28a7c8d84360ee8a83288e2d0de63671f8fd5715bd4e14c0154cc731d3531871ebeccf83a52c41d6dc1f6dc412e433f
7
- data.tar.gz: fc4a6d27fde05257a4b8023ed7507caecdbbb6f79a28f32085ce17c79d1f30428bae762d70b80906f3b6e1e8a46237e7fe5a6ab2d67f407b8fc5cf0c86bf8864
6
+ metadata.gz: f25c7bf185356ceaea4a9ac257683ef56db6f24923baa6f00e3a566b8e923de2b18500fa8e9b20173b4a83d90a94aa92a2f3fda2dc239fc0eba900bd713ea23d
7
+ data.tar.gz: 0d310df7b81ada4f9a0a9c73a1e962c100af3082b9911d7c88430de01016a2566a2ff42e30f1b94b6a256c6bd50a5d05263c52e1ff667e0f3798d0958546ac0e
data/README.adoc CHANGED
@@ -213,6 +213,7 @@ Commands:
213
213
  expressir clean PATH # Strip remarks and prettify EXPRESS schema at PATH
214
214
  expressir expand PATH # Concatenate the interface closure into one .exp
215
215
  expressir flatten PATH # Flatten the interface closure into an ISO 10303-11:1994 longform schema
216
+ expressir fix PATH # Rewrite self-schema-qualified references (#125)
216
217
  expressir format PATH # pretty print EXPRESS schema located at PATH
217
218
  expressir help [COMMAND] # Describe available commands or one specific command
218
219
  expressir validate load *PATH # validate EXPRESS schema located at PATH
@@ -527,10 +528,18 @@ expressir expand schemas/resources/action_schema/action_schema.exp \
527
528
  |===
528
529
  | Option | Description
529
530
  | `--output PATH`, `-o PATH` | Write the artifact to PATH (defaults to stdout)
531
+ | `--manifest PATH` | ELF schema manifest YAML defining where schemas live (primary
532
+ resolution mode)
533
+ | `--stepmod DIR` | STEPmod checkout root — explicit opt-in to directory-convention
534
+ resolution as the fallback
530
535
  |===
531
536
 
532
537
  The closure is resolved by scanning `USE FROM` / `REFERENCE FROM` names in the
533
- root source and looking them up in the same directory.
538
+ root source. Schema resolution order: `--manifest PATH` (an ELF schema
539
+ manifest — explicit, layout-independent) first; `--stepmod DIR` (the STEPmod
540
+ directory convention) as an explicit fallback; otherwise the file's own
541
+ directory, with a failed lookup warning and pointing at the two explicit
542
+ modes.
534
543
 
535
544
  === Flatten schema (SHTOLO longform)
536
545
 
@@ -551,8 +560,35 @@ expressir flatten schemas/resources/action_schema/action_schema.exp \
551
560
  | Option | Description
552
561
  | `--output PATH`, `-o PATH` | Write the longform schema to PATH (defaults to stdout)
553
562
  | `--longform_name ID` | Identifier for the resulting SCHEMA (default: `<root>_lf`)
563
+ | `--extenders all|none` | `all` (default, the WG12 requirement): fold every extensible
564
+ SELECT/ENUMERATION extension in the closure into its base type — for
565
+ `EXTENSIBLE GENERIC_ENTITY SELECT` that means every entity the closure
566
+ carries. `none`: leave extensible types exactly as declared.
567
+ | `--manifest PATH` | ELF schema manifest YAML defining where schemas live (primary
568
+ resolution mode)
569
+ | `--stepmod DIR` | STEPmod checkout root — explicit opt-in to directory-convention
570
+ resolution as the fallback
554
571
  |===
555
572
 
573
+ The longform output is verified against Express Engine: for
574
+ `description_assignment`, `eengine --compare` of our longform against
575
+ eengine's own reference artifact reports *No differences detected*
576
+ (`spec/expressir/express/shtolo_eengine_oracle_differential_spec.rb`).
577
+
578
+ === Fix schema (self-schema references)
579
+
580
+ The `fix` command rewrites string literals that qualify an item with the
581
+ CURRENT schema's name — `'THIS_SCHEMA.ITEM'` inside THIS_SCHEMA is
582
+ technically incorrect: the item usually comes from another schema,
583
+ imported implicitly through a USEd supertype. Local items drop the
584
+ prefix; foreign items gain their true defining schema
585
+ (`'GEOMETRY_SCHEMA.B_SPLINE_SURFACE'`); unknown items are left untouched.
586
+
587
+ [source, sh]
588
+ ----
589
+ expressir fix schemas/aic_topologically_bounded_surface.exp --output fixed.exp
590
+ ----
591
+
556
592
  === Validate schema
557
593
 
558
594
  The `validate load` command performs validation checks on EXPRESS schema files.
@@ -597,6 +633,10 @@ expressir validate check schemas/resources/action_schema/action_schema.exp
597
633
  # Check all .exp files under a directory
598
634
  expressir validate check schemas/resources/
599
635
 
636
+ # Resolve the closure through an ELF schema manifest (or --stepmod DIR)
637
+ expressir validate check modules/description_assignment/mim.exp \
638
+ --manifest schemas-srl.yaml
639
+
600
640
  # Exit 1 when any :error note fires
601
641
  expressir validate check schemas/resources/action_schema/action_schema.exp
602
642
  ----
@@ -56,3 +56,21 @@ SHTOLO paper, BNFs, DTDs)
56
56
  `docs/issues/` — bug corpus incl. a shtolo old-vs-new differential;
57
57
  `docs/` — SHTOLO paper PDF, Part 11/14/21 BNFs, XML DTDs,
58
58
  interface/qualify notes; `00check.txt` — check design notes.
59
+
60
+ ## Phase 2 — full eengine parity (2026-09-22, user-directed)
61
+
62
+ | # | plan | status |
63
+ |---|---|---|
64
+ | 12 | full check-p11 catalog port | planned |
65
+ | 13 | population / Part 21 validation | planned |
66
+ | 14 | SHTOLO completion (Annex G + corpus differential) | in progress |
67
+ | 15 | Part 28 XML + SMRL index | planned |
68
+ | 16 | schema compare & patch | planned |
69
+ | 17 | official test-case incorporation | standing structure |
70
+
71
+ Execution order: 14 (finish Shtolo) → 12 (checks) → 15 (XML index)
72
+ → 16 (compare) → 13 (population, biggest). Each slice ships a PR and
73
+ a patch release as it lands ("release as we go"). Reference repos:
74
+ `~/src/external/exp-engine-engine` (oracle), `~/src/mn/iso-10303-11`
75
+ (normative text), `~/src/mn/iso-10303-detached-docs` (Parts 21/28
76
+ sources and fixtures).
@@ -0,0 +1,67 @@
1
+ # 12 — Full check-p11 catalog port (eeng `kernel/check.lisp`)
2
+
3
+ ## Goal
4
+
5
+ Bring `Expressir::Express::Checker` from the current documented subset
6
+ to the full eeng check-p11 catalog: every note eeng 5.1.0 emits over
7
+ the SRL/SMRL corpora, with note ids and severities aligned so oracle
8
+ differentials stay cross-referenceable.
9
+
10
+ ## Current subset (shipped)
11
+
12
+ check-schema-interface-redundant, check-schema-iface-resource-duplicate,
13
+ check-subtype-ref, check-subtype-cycle (path-tracked DFS),
14
+ check-duplicate-declaration, check-where-name-pattern,
15
+ check-unique-name-pattern, check-unresolved-ref,
16
+ check-select-extended-type, check-enumeration-extended-type,
17
+ check-self-schema-reference (#125), parse-failure → nonzero exit.
18
+
19
+ ## Sources
20
+
21
+ - `~/src/external/exp-engine-engine/kernel/check.lisp` (835 lines,
22
+ `check-p11` generic + per-node-type methods) and
23
+ `kernel/check-notes.lisp` (note catalog).
24
+ - ISO 10303-11:2004 clause text — `~/src/mn/iso-10303-11/sources/sections/`
25
+ (each ported check cites its clause).
26
+ - Existing plan: `07-semantic-checks-port.md`; current impl
27
+ `lib/expressir/express/checker.rb`.
28
+
29
+ ## Design
30
+
31
+ 1. **Catalog extraction**: enumerate every `post-message` site in
32
+ check.lisp/check-notes.lisp → (note-id, severity, node type,
33
+ message shape). Commit as `spec/fixtures/eeng/check-catalog.md`
34
+ with our coverage column; CI gate diffs the catalog against the
35
+ oracle so new eeng notes surface.
36
+ 2. **Port order** (by eeng's own walk): schema-level (interfaces,
37
+ constants) → entity (supertype/subtype-of, attributes, derives,
38
+ inverses, uniques, wheres) → type (select/enumeration/aggregate
39
+ bounds) → function/procedure/rule bodies (expression typing lite:
40
+ arity/name resolution of called functions, built-ins from BUILTINS,
41
+ variable scoping via ScopeResolver) → attribute-level
42
+ redeclaration consistency (redispatches, `SELF\` qualifiers).
43
+ 3. **Differential gate**: run eeng `--validate` and our `validate
44
+ check` over the SRL corpus + module arm/mim set; every corpus file
45
+ must produce the SAME note-id multiset (message text may differ).
46
+ Land as `checker_eeng_full_catalog_differential_spec`
47
+ (production_scale-tagged for the big corpus; small fixtures in the
48
+ default suite).
49
+ 4. **Severity mapping** table: eeng `:error/:warning` → ours verbatim.
50
+
51
+ ## Acceptance
52
+
53
+ - Catalog file lists 100% of eeng note ids with status columns.
54
+ - SRL differential: zero unexplained note-id mismatches.
55
+ - `validate check` docs updated (README table).
56
+
57
+ ## Test cases
58
+
59
+ - eeng `docs/issues/` corpus inputs; qualify loops
60
+ (`scripts/qual-*.sh`) over resources.lst/modules.lst.
61
+ - Per-clause unit fixtures citing ISO 10303-11 clause numbers.
62
+
63
+ ## Release checkpoint
64
+
65
+ PR + expressir patch release once the entity/type/schema tiers land
66
+ (expression-tier notes may follow in a second PR — keep the catalog
67
+ honest about which tier each note belongs to).
@@ -0,0 +1,54 @@
1
+ # 13 — Population / Part 21 instance validation
2
+
3
+ ## Goal
4
+
5
+ Validate STEP Part 21 instance data (.stp/.p21) against a loaded
6
+ schema — eeng's core strength (`--validate` with population), absent
7
+ from expressir today.
8
+
9
+ ## Sources
10
+
11
+ - ISO 10303-21: exchange structure grammar — BNF in
12
+ `~/src/mn/iso-10303-detached-docs/sources/iso-10303-21*/` (and
13
+ eeng's copy under `plugins/p21/`, incl. its parser + validation).
14
+ - eeng: `plugins/p21/` (p21 reader, population), `--validate`
15
+ (`run-op` population path), `wo-xml.lisp` instance-side references.
16
+ - eeng banner: population validation "not fully supported" upstream —
17
+ our target is the supported subset, documented precisely.
18
+
19
+ ## Design (scope ladder; each step ships)
20
+
21
+ 1. **P21 reader**: parsanol grammar for the exchange structure
22
+ (header entities + DATA section: complex entity `(`name(...)`)`,
23
+ entity instances, list/set values, typed refs `#1`, enums `.T.`,
24
+ binary `\"..."`, redefines `/`). Emit a Population model
25
+ (`Model::Population`): instances with entity name + attribute
26
+ values keyed by explicit attribute position.
27
+ 2. **Structural validation**: every instance maps to a known entity
28
+ in the schema closure (complex entities → all parts); attribute
29
+ arity matches; value kinds match declared types (aggregate bounds,
30
+ select members, string/binary widths, REAL precision) — the
31
+ aggregate of eeng's population notes.
32
+ 3. **Constraint validation**: UNIQUE rules (per-entity + inherited),
33
+ WHERE rules — evaluation needs an EXPRESS expression interpreter;
34
+ reuse/extend the `benchmark`/expression-walk machinery, evaluate
35
+ over instance bindings (scope: no INVERSE/derives first).
36
+ 4. **CLI**: `expressir validate population SCHEMA -p DATA.p21`,
37
+ JSON/plain reports aligned with Checker notes.
38
+
39
+ ## Acceptance
40
+
41
+ - Official Part 21 test corpora parse + validate (see tests).
42
+ - Differential vs eeng `--validate` on shared fixtures: same
43
+ accept/reject decision and note ids.
44
+
45
+ ## Test cases
46
+
47
+ - ISO 10303-21 validation suite files from detached-docs where
48
+ available; else eeng `tests/` + `scripts/test-tt*.sh` fixtures.
49
+ - Hand fixtures: arity mismatch, bad enum, UNIQUE collision, WHERE
50
+ failure, complex-entity mismatch.
51
+
52
+ ## Release checkpoint
53
+
54
+ Steps 1–2 = first release (structural validator); step 3 second.
@@ -0,0 +1,44 @@
1
+ # 14 — SHTOLO completion (expressir#32, Annex G)
2
+
3
+ ## Goal
4
+
5
+ Finish `Expressir::Express::Shtolo` to full Annex G conformance and
6
+ close the eeng differential over the whole corpus. Current state:
7
+ G.1 + G.2 stages implemented (522 lines), suite green (10 examples),
8
+ `extenders:` :all/:none matches the eeng-default split.
9
+
10
+ ## Remaining work
11
+
12
+ 1. **Rule audit**: walk `spec/fixtures/shtolo/annex-g-rules.md`
13
+ (G.NM.1-2, G.1.1-10, G.2.1-7) and mark every rule PASS/partial/✖
14
+ in the file; a fixture per rule. Known-thin areas from the audit:
15
+ - G.1.6 dependently-instantiable rule output (function + WHERE
16
+ wiring) — verify against Annex G text, not eeng (eeng ✖).
17
+ - name-munging policy (`_dot_` and friends) — eeng ✖; standard wins.
18
+ - prune pass reachability (unreachable subtype branches).
19
+ 2. **Corpus differential**: for every module arm/mim in modules.lst,
20
+ compare our flatten output to `eengine --flat` (eeng default =
21
+ `extenders: :none` shape); classify divergences
22
+ standard-conformant vs bug. Feed the mismatch corpus into specs.
23
+ 3. **Scale**: SMRL full set flatten over the compiled-set warm model;
24
+ memory ceiling documented.
25
+ 4. **CLI**: `expressir shtolo MANIFEST -o longform.exp` (root schema
26
+ + closure resolution reuses ParityInputs).
27
+
28
+ ## Acceptance
29
+
30
+ - Every Annex G rule has a green fixture.
31
+ - Corpus differential: zero unexplained diffs; every explained
32
+ divergence documented in `parity-matrix.md`.
33
+ - SMRL flatten completes; output parses under the 1994 profile.
34
+
35
+ ## Test cases
36
+
37
+ - annex-g-rules.md fixtures; eeng `docs/issues/bug39` shtolo outputs
38
+ (old-vs-new vs stepcode exppp); `scripts/qual-*.sh` loops;
39
+ `pretty-test.sh` ap209/210/242 × arm/mim shape checks.
40
+
41
+ ## Release checkpoint
42
+
43
+ One PR per coherent slice (rule audit + fixtures; differential gate;
44
+ CLI), patch releases as each merges.
@@ -0,0 +1,51 @@
1
+ # 15 — EXPRESS → XML (ISO 10303-28 / eeng --xml)
2
+
3
+ ## Goal
4
+
5
+ Generate XML Schema output for a schema (expressir#276): eeng's
6
+ `--xml` (`wo-xml-p11.lisp`) plus the SMRL index listing
7
+ (`wo-smrl-xml.lisp`, feeds the SMRL document set).
8
+
9
+ ## Sources
10
+
11
+ - ISO 10303-28:2007 ( EXPRESS-driven XML representation) — detached
12
+ docs `~/src/mn/iso-10303-detached-docs/sources/iso-10303-28*/`;
13
+ eeng `docs/` carries `express_model.dtd` + module.dtd (legacy
14
+ forms).
15
+ - eeng: `plugins/p11/wo-xml-p11.lisp` (schema → XSD),
16
+ `wo-smrl-xml.lisp` (SMRL index XML), `--xml` run-op.
17
+ - Consumer: iso-10303 repo's `schemas/data/*.xml` SMRL pages
18
+ (rendered by metanorma today) — our output must be drop-in for
19
+ that pipeline.
20
+
21
+ ## Design
22
+
23
+ 1. **SMRL index XML first** (small, high value for iso-10303): the
24
+ index of all schemas in a repository (name, file, interfaces,
25
+ counts) as the repo's data-layer source — replaces the
26
+ hand-maintained entries.
27
+ 2. **express2xsd**: schema → XSD via a declarative mapping table
28
+ (entity → element+complexType, type → simpleType/complexType,
29
+ select → choice, explicit attrs in order, supertype substitution
30
+ groups for SUBTYPE OF, aggregates → minOccurs/maxOccurs). Emit
31
+ through a Nokogiri-free XML writer (ROXML via lutaml-model where
32
+ it fits; hand-rolled tree otherwise — the mapping is the product,
33
+ not the serialization).
34
+ 3. **CLI**: `expressir xml SCHEMA -o schema.xsd`;
35
+ `expressir xml-index REPO -o smrl.xml`.
36
+
37
+ ## Acceptance
38
+
39
+ - Output validates against the Part 28 reference XSDs (detached
40
+ docs) for the fixture set; xsd diff vs eeng --xml on SRL corpus
41
+ classified standard-vs-eeng.
42
+ - iso-10303 SMRL pages render from our generated index unchanged.
43
+
44
+ ## Test cases
45
+
46
+ - eeng `scripts/build-xml.sh` full-corpus reference outputs;
47
+ `test-tt{,1}.sh` (XML + Saxon HTM chain) as end-to-end smoke.
48
+
49
+ ## Release checkpoint
50
+
51
+ SMRL index first (unblocks iso-10303 pipeline), express2xsd second.
@@ -0,0 +1,48 @@
1
+ # 16 — Schema compare & patch (eeng --compare / --patch)
2
+
3
+ ## Goal
4
+
5
+ Structural diff of two schemata with a machine-applicable patch
6
+ output — eeng `--compare` (+ `--concat_compare`, `--patch`,
7
+ `--patch_schema_name`), used by WG12 to track schema revisions.
8
+
9
+ ## Sources
10
+
11
+ - eeng `plugins/p11/wo-*.lisp` compare entry (`run-op (:compare)`
12
+ in top-level.lisp) and patch application paths; mode args:
13
+ `:trial_schema :reference_schema :xml-output :patch-output`.
14
+ - Semantics baseline: declaration-level matching by id within the
15
+ flattened/concatenated form (eeng compares post-concat shapes for
16
+ shortforms).
17
+
18
+ ## Design
19
+
20
+ 1. **Model diff** on the resolved repositories: declarations matched
21
+ by canonical path (`schema.item`); kinds of delta — added,
22
+ removed, changed (per-attribute structural diff: types,
23
+ attribute lists, where/unique bodies via formatted-text
24
+ comparison first, structural second).
25
+ 2. **Outputs**: unified-ish text report (human), XML
26
+ (`--xml-output` parity), and patch script (the eeng patch
27
+ format: rename/redefine vocabulary it defines — ported from
28
+ wo-patch output shapes).
29
+ 3. **CLI**: `expressir compare TRIAL.exp REFERENCE.exp [--xml|--patch]`;
30
+ `concat-compare` rides the Concatenator for shortform pairs.
31
+ 4. **Patch application**: apply a patch file to a model (rename
32
+ declarations, remap interfaces) — the write-back side eeng uses
33
+ for round-trip patching.
34
+
35
+ ## Acceptance
36
+
37
+ - SRL corpus self-compare = empty diff; mutated fixtures (rename,
38
+ add/remove attr, retype) produce the expected delta kinds.
39
+ - Differential vs eeng `--compare` XML on the same pairs.
40
+
41
+ ## Test cases
42
+
43
+ - eeng `docs/issues/` pairs; hand-mutated fixture ladder; the
44
+ `--concat_compare` module-vs-module pairs from scripts.
45
+
46
+ ## Release checkpoint
47
+
48
+ Text+XML diff first release; patch apply second.
@@ -0,0 +1,42 @@
1
+ # 17 — Official test-case incorporation
2
+
3
+ ## Goal
4
+
5
+ One place that maps every OFFICIAL/reference test corpus to our
6
+ specs, and CI gates that run the tractable tier on every push.
7
+
8
+ ## Corpora
9
+
10
+ 1. **eeng suite** (already imported, `spec/fixtures/eeng/`):
11
+ `scripts/*.sh` 14 drivers (qual-arm/mod/res loops, pretty-test.sh
12
+ ap209/210/242 × arm/mim × shortform/longform, ap210 giant file,
13
+ test-tt{,1}.sh XML chain, build-xml.sh), `docs/issues/` bug
14
+ corpus (bug39 shtolo old-vs-new vs stepcode exppp; bug40 pretty
15
+ expressions), `qualify.txt`, `interfaces.txt`, `00check.txt`.
16
+ 2. **ISO 10303-11** grammar/conformance sources:
17
+ `~/src/mn/iso-10303-11/sources/sections/` — Annex G rules
18
+ (fixtures live in `spec/fixtures/shtolo/annex-g-rules.md`),
19
+ syntax annexes for negative/positive grammar tests.
20
+ 3. **ISO detached docs** `~/src/mn/iso-10303-detached-docs/sources/
21
+ iso-10303-*`: Part 21 exchange-structure fixtures (population),
22
+ Part 28 reference XSDs (XML), Part 22 (SDAI vocabulary, later).
23
+ 4. **SRL/SMRL corpora** (`resources.lst`/`modules.lst` names match
24
+ our checkout): the differential tier.
25
+
26
+ ## Structure
27
+
28
+ - `spec/expressir/eeng_parity/` — one spec per capability matrix
29
+ row; each example tags its source corpus file in a comment.
30
+ - Tiering: `:fast` (fixtures, every push) / `:corpus`
31
+ (SRL differential, nightly or manual tag `production_scale`) /
32
+ `:smoke_eeng` (runs the local eeng oracle when the binary exists —
33
+ skipped otherwise).
34
+ - `rake parity:status` prints the capability × corpus matrix with
35
+ green/gray cells — the single progress view.
36
+
37
+ ## Rules
38
+
39
+ - No test is added without its provenance row in the fixture dir's
40
+ PROVENANCE.md (established practice, keep it).
41
+ - Oracle outputs are regenerated, never hand-edited; the regeneration
42
+ command lives next to each golden.
data/expressir.gemspec CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency "benchmark-ips"
37
37
  spec.add_dependency "csv"
38
38
  spec.add_dependency "liquid"
39
- spec.add_dependency "lutaml-model", "~> 0.8.0"
39
+ spec.add_dependency "lutaml-model", "~> 0.8.50"
40
40
  spec.add_dependency "moxml"
41
41
  spec.add_dependency "nokogiri"
42
42
  spec.add_dependency "paint"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Express
5
+ # HyperlinkFormatter variant emitting AsciiDoc xref macros instead
6
+ # of the legacy stepmod +{{{<<express:...>>}}}+ markup: a reference
7
+ # to schema.item renders as +<<schema.item,item>>+, the anchor
8
+ # namespace schema document pages emit (expressir #255 / iso-10303
9
+ # #41). With +subs="+macros"+ source blocks the links go live.
10
+ module AdocHyperlinkFormatter
11
+ # @!visibility private
12
+ def self.included(mod)
13
+ unless mod.superclass <= Expressir::Express::Formatter
14
+ raise Error::FormatterMethodMissingError.new("AdocHyperlinkFormatter",
15
+ "format_references_simple_reference")
16
+ end
17
+ end
18
+
19
+ def format_references_simple_reference(node)
20
+ return node.id unless node.base_path
21
+
22
+ parts = node.base_path.split(".")
23
+ return node.id if parts.size < 2
24
+
25
+ anchor = parts[-2, 2].join(".")
26
+ "<<#{anchor},#{node.id}>>"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Express
5
+ # Pre-built Formatter subclass rendering the FULL schema with
6
+ # cross-references as AsciiDoc xref macros — the document-page
7
+ # counterpart of SourceFormatter (Schema#formatted_hyperlinked_adoc).
8
+ class AdocSourceFormatter < Formatter
9
+ include AdocHyperlinkFormatter
10
+ end
11
+ end
12
+ end
@@ -33,12 +33,21 @@ module Expressir
33
33
  # closure into its base type (Annex G.2.3/G.2.4);
34
34
  # :none leaves extensible types exactly as declared —
35
35
  # the shape eengine's default --flat emits.
36
+ # `prune` — true (default) runs the G.1.9 prune pass;
37
+ # false keeps the artifact's full declaration set.
38
+ # `stage` — :longform (default) runs the G.2 rewrites;
39
+ # :artifact stops after stage 1 — the annex's
40
+ # intermediate schema, the shape eengine's
41
+ # --concat_schema output corresponds to (copy
42
+ # pass, no prune, no 1994 rewrite).
36
43
  def initialize(root_schema, repository, longform_name: nil,
37
- extenders: :all)
44
+ extenders: :all, prune: true, stage: :longform)
38
45
  @root = root_schema
39
46
  @repository = repository
40
47
  @longform_name = longform_name || @root.id
41
48
  @extenders = extenders
49
+ @prune = prune
50
+ @stage = stage
42
51
  @reference_entities = [] # G.1.6 entities
43
52
  end
44
53
 
@@ -274,6 +283,8 @@ module Expressir
274
283
 
275
284
  def build_longform
276
285
  artifact = build_artifact
286
+ prune!(artifact) if @prune
287
+ return artifact if @stage == :artifact
277
288
 
278
289
  if @extenders == :all
279
290
  resolve_extensible_enumerations!(artifact)
@@ -288,6 +299,123 @@ module Expressir
288
299
  artifact
289
300
  end
290
301
 
302
+ # G.1.9 prune pass: the artifact keeps only what stays visible
303
+ # and reachable. Runs before the stage-2 rewrites so they never
304
+ # see declarations the longform would drop.
305
+ def prune!(schema)
306
+ visible_ids = visible_declaration_ids(schema)
307
+
308
+ # rules whose parameter entities are not all visible
309
+ schema.rules = schema.rules.reject do |rule|
310
+ Array(rule.applies_to).any? do |ref|
311
+ id = ref.is_a?(String) ? ref : ref.id
312
+ id && !visible_ids.key?(id.safe_downcase)
313
+ end
314
+ end
315
+
316
+ # select items pointing at declarations the artifact lacks
317
+ schema.types.each do |type|
318
+ next unless type.underlying_type.is_a?(Model::DataTypes::Select)
319
+
320
+ items = Array(type.underlying_type.items)
321
+ kept = items.select do |item|
322
+ item.id.nil? || visible_ids.key?(item.id.safe_downcase)
323
+ end
324
+ # An emptied select disappears from the representation
325
+ # (annex G.1.9 s1/s2/s3 example).
326
+ type.underlying_type.items = kept unless kept.empty? && items.empty?
327
+ if kept.empty? && !items.empty?
328
+ type.underlying_type = nil
329
+ end
330
+ end
331
+ schema.types = schema.types.reject { |t| t.underlying_type.nil? }
332
+
333
+ # functions/procedures nothing calls, to fixpoint (a helper only
334
+ # a dropped function called is itself unreachable)
335
+ loop do
336
+ called = called_ids(schema)
337
+ drop = schema.functions.any? { |f| !called[f.id.safe_downcase] } ||
338
+ schema.procedures.any? { |p| !called[p.id.safe_downcase] }
339
+ break unless drop
340
+
341
+ schema.functions = schema.functions.select { |f| called[f.id.safe_downcase] }
342
+ schema.procedures = schema.procedures.select { |p| called[p.id.safe_downcase] }
343
+ end
344
+
345
+ prune_supertype_expressions!(schema, visible_ids)
346
+ end
347
+
348
+ def visible_declaration_ids(schema)
349
+ all_decls(schema).each_with_object({}) do |decl, hash|
350
+ hash[decl.id.safe_downcase] = true if decl.respond_to?(:id) && decl.id
351
+ end
352
+ end
353
+
354
+ # Every identifier a FunctionCall site names, across the
355
+ # artifact's surviving declarations.
356
+ def called_ids(schema)
357
+ called = {}
358
+ each_node(schema) do |node|
359
+ next unless node.is_a?(Model::Expressions::FunctionCall)
360
+
361
+ ref = node.function
362
+ id = ref.is_a?(String) ? ref : ref&.id
363
+ called[id.safe_downcase] = true if id
364
+ end
365
+ called
366
+ end
367
+
368
+ # Annex C reductions over supertype expressions: references to
369
+ # invisible entities leave ONEOF; ONEOF(a) => a; AND/OR sides
370
+ # that become empty collapse; a vacuous expression deletes the
371
+ # constraint.
372
+ def prune_supertype_expressions!(schema, visible_ids)
373
+ keep = []
374
+ schema.subtype_constraints.each do |constraint|
375
+ expr = constraint.supertype_expression
376
+ if expr.nil?
377
+ # total-over-only constraints carry no expression; stage 2
378
+ # turns their TOTAL_OVER into a rule.
379
+ keep << constraint
380
+ next
381
+ end
382
+ reduced = reduce_supertype_expression(expr, visible_ids)
383
+ # Annex C: a vacuous expression deletes the constraint.
384
+ next if reduced.nil?
385
+
386
+ constraint.supertype_expression = reduced
387
+ keep << constraint
388
+ end
389
+ schema.subtype_constraints = keep
390
+ end
391
+
392
+ def reduce_supertype_expression(expr, visible_ids)
393
+ case expr
394
+ when Model::SupertypeExpressions::OneofSupertypeExpression
395
+ refs = Array(expr.operands).select do |ref|
396
+ id = ref.is_a?(String) ? ref : ref.id
397
+ id.nil? || visible_ids.key?(id.safe_downcase)
398
+ end
399
+ return nil if refs.empty?
400
+
401
+ refs.one? ? refs.first : reset_oneof(expr, refs)
402
+ when Model::SupertypeExpressions::BinarySupertypeExpression
403
+ left = reduce_supertype_expression(expr.operands[0], visible_ids)
404
+ right = reduce_supertype_expression(expr.operands[1], visible_ids)
405
+ return nil if left.nil? && right.nil?
406
+
407
+ left || right
408
+ else
409
+ expr
410
+ end
411
+ end
412
+
413
+ def reset_oneof(expr, refs)
414
+ copy = deep_copy(expr)
415
+ copy.operands = refs
416
+ copy
417
+ end
418
+
291
419
  def resolve_extensible_enumerations!(schema)
292
420
  schema.types.select do |t|
293
421
  t.underlying_type.is_a?(Model::DataTypes::Enumeration) && t.underlying_type.extensible
@@ -10,6 +10,8 @@ module Expressir
10
10
 
11
11
  # Core classes (autoloaded for lazy loading)
12
12
  autoload :Cache, "#{__dir__}/express/cache"
13
+ autoload :AdocHyperlinkFormatter, "#{__dir__}/express/adoc_hyperlink_formatter"
14
+ autoload :AdocSourceFormatter, "#{__dir__}/express/adoc_source_formatter"
13
15
  autoload :Checker, "#{__dir__}/express/checker"
14
16
  autoload :Concatenator, "#{__dir__}/express/concatenator"
15
17
  autoload :Core, "#{__dir__}/express/core"
@@ -59,6 +59,8 @@ module Expressir
59
59
  liquid do
60
60
  map :header, to: :header
61
61
  map :formatted, to: :formatted
62
+ map :formatted_hyperlinked, to: :formatted_hyperlinked
63
+ map :formatted_hyperlinked_adoc, to: :formatted_hyperlinked_adoc
62
64
  map :source, to: :source
63
65
  map :source_hyperlinked, to: :source_hyperlinked
64
66
  map :full_source, to: :full_source
@@ -111,6 +113,20 @@ module Expressir
111
113
  Expressir::Express::SchemaSourceFormatter.format(self)
112
114
  end
113
115
 
116
+ # FULL schema text with cross-references rendered as hyperlinks
117
+ # — what a schema document page renders (#255): every line of
118
+ # the schema, live links included.
119
+ def formatted_hyperlinked
120
+ @formatted_hyperlinked ||= Expressir::Express::SourceFormatter.format(self)
121
+ end
122
+
123
+ # FULL schema text with cross-references as AsciiDoc xref macros
124
+ # (+<<schema.item,item>>+) — drop-in for a schema document page
125
+ # rendered through a +subs="+macros"+ source block.
126
+ def formatted_hyperlinked_adoc
127
+ @formatted_hyperlinked_adoc ||= Expressir::Express::AdocSourceFormatter.format(self)
128
+ end
129
+
114
130
  private
115
131
 
116
132
  # @param [String] id
@@ -3,6 +3,6 @@ module Expressir
3
3
  # autoload it (Ruby autoload only works for module/class constants, not
4
4
  # for string constants like `Expressir::VERSION`).
5
5
  module Version
6
- VERSION = "2.4.4".freeze
6
+ VERSION = "2.4.6".freeze
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expressir
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.4
4
+ version: 2.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-22 00:00:00.000000000 Z
11
+ date: 2026-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.8.0
89
+ version: 0.8.50
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.8.0
96
+ version: 0.8.50
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: moxml
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -312,6 +312,12 @@ files:
312
312
  - TODO.parity-ee/09-smrl-index-and-listing.md
313
313
  - TODO.parity-ee/10-interface-dot-graph.md
314
314
  - TODO.parity-ee/11-import-eeng-tests.md
315
+ - TODO.parity-ee/12-full-check-catalog.md
316
+ - TODO.parity-ee/13-population-p21.md
317
+ - TODO.parity-ee/14-shtolo-completion.md
318
+ - TODO.parity-ee/15-part28-xml.md
319
+ - TODO.parity-ee/16-compare-patch.md
320
+ - TODO.parity-ee/17-official-tests.md
315
321
  - TODO.parity-ee/parity-matrix.md
316
322
  - benchmark/srl_benchmark.rb
317
323
  - benchmark/srl_native_benchmark.rb
@@ -436,6 +442,8 @@ files:
436
442
  - lib/expressir/eengine/modified_object.rb
437
443
  - lib/expressir/errors.rb
438
444
  - lib/expressir/express.rb
445
+ - lib/expressir/express/adoc_hyperlink_formatter.rb
446
+ - lib/expressir/express/adoc_source_formatter.rb
439
447
  - lib/expressir/express/ast_key_converter.rb
440
448
  - lib/expressir/express/builder.rb
441
449
  - lib/expressir/express/builder_context.rb