expressir 2.4.24 → 2.4.25

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: baecd11fc9e03ed51685a06825e68a232f3caae95fd26bbffa8f77ce48ffdc03
4
- data.tar.gz: 04b02ab33defbc571c9a1d36284c53f04b847acecb7e54b67537983ebbf2a74e
3
+ metadata.gz: da722d6f42601529fb1f7798255f6f5e8fed696c4c64998ed7e8c5b47aacb598
4
+ data.tar.gz: d9ead75080ad649480d7665cc526d500e9a228f18f941e2d0efbeab03d7871f9
5
5
  SHA512:
6
- metadata.gz: 98ea8c39e0f462b7c6ecb7a2d24281cf3d159a79544b0b98feb89721011a066ff391343b0285816576f0007f9ede00d684629734fb5d29fefe2ef8af1f3c516f
7
- data.tar.gz: c1bc94e1b7a73a79d93c7a1ae67b3f945f12ae7e3bcdcd161a6ea9f1d736ef74b0c2e39289301b31c558090c5f5fa7708b10d503c92b691ca5f1f90ff9ba0728
6
+ metadata.gz: 194e26d86f8043557120fb8935491376420600e8fecbde5ad99ceef352f3fe77251a1320c16ddb25f2ef293be33ca85c7e2daf1c02df1bcba16346d4067d1550
7
+ data.tar.gz: 68da6ffb076de9b604107ac2129d1b11224c57102037f84f526dcbd5efc4fdb54a012f843f3cb0e7f20e29eda4f11e8ee6dce81a3d58c4ed9b7238857ee02609
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Smoke test for an INSTALLED expressir gem (run after `gem install`,
4
+ # never from the source directory): the Rust core binding must be
5
+ # active and the parser must handle the standard and #462 forms.
6
+ require "expressir"
7
+
8
+ abort "FAIL: NATIVE_AVAILABLE is false" unless Expressir::Express::Core::NATIVE_AVAILABLE
9
+ puts "native binding: active"
10
+
11
+ repo = Expressir::Express::Parser.from_exp(
12
+ "SCHEMA smoke; ENTITY e; a : STRING; END_ENTITY; END_SCHEMA;",
13
+ )
14
+ abort "FAIL: schema not parsed" unless repo.schemas.first.id == "smoke"
15
+ puts "standard parse: ok"
16
+
17
+ src = <<~EXP
18
+ SCHEMA m;
19
+ ENTITY e; END_ENTITY;
20
+ SUBTYPE_CONSTRAINT c FOR e;
21
+ ABSTRACT;
22
+ END_SUBTYPE_CONSTRAINT;
23
+ END_SCHEMA;
24
+ EXP
25
+ sc = Expressir::Express::Parser.from_exp(src).schemas.first.subtype_constraints.first
26
+ abort "FAIL: subtype constraint ABSTRACT body (#462)" unless sc&.abstract == true
27
+ puts "#462 form: ok"
28
+
29
+ puts "SMOKE OK"
@@ -0,0 +1,183 @@
1
+ # Prebuilt platform gems (TODO.suma-improvements/01), following the
2
+ # parsanol-ruby gem-build pattern: oxidize-rb cross-gem builds
3
+ # per-platform gems in rake-compiler-dock containers, smoke jobs install
4
+ # them on real runners, and tag pushes publish to rubygems.
5
+ name: native-gems
6
+
7
+ on:
8
+ workflow_dispatch:
9
+ push:
10
+ branches: [main, "cross-gem/*", "pkg/*"]
11
+ tags: ["v*"]
12
+
13
+ concurrency:
14
+ group: ${{ github.workflow }}-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ ci-data:
22
+ runs-on: ubuntu-22.04
23
+ outputs:
24
+ result: ${{ steps.fetch.outputs.result }}
25
+ steps:
26
+ - id: fetch
27
+ uses: oxidize-rb/actions/fetch-ci-data@v1
28
+ with:
29
+ supported-ruby-platforms: |
30
+ exclude: [x64-mingw32]
31
+ stable-ruby-versions: |
32
+ only: [3.2, 3.3, 3.4, 4.0]
33
+
34
+ native:
35
+ name: Build native gems
36
+ needs: ci-data
37
+ runs-on: ubuntu-22.04
38
+ strategy:
39
+ fail-fast: false
40
+ matrix:
41
+ ruby-platform: ${{ fromJSON(needs.ci-data.outputs.result).supported-ruby-platforms }}
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - uses: ruby/setup-ruby@v1
46
+ with:
47
+ ruby-version: "3.4"
48
+
49
+ - uses: oxidize-rb/actions/cross-gem@v1
50
+ id: cross-gem
51
+ env:
52
+ EXPRESSIR_CORE_FORCE: "1"
53
+ with:
54
+ platform: ${{ matrix.ruby-platform }}
55
+ ruby-versions: "3.2,3.3,3.4,4.0"
56
+
57
+ - uses: actions/upload-artifact@v4
58
+ with:
59
+ name: cross-gem-${{ matrix.ruby-platform }}
60
+ path: ${{ steps.cross-gem.outputs.gem-path }}
61
+ if-no-files-found: error
62
+
63
+ smoke-linux:
64
+ name: Smoke test Linux x86_64
65
+ needs: native
66
+ runs-on: ubuntu-22.04
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+ - uses: ruby/setup-ruby@v1
70
+ with:
71
+ ruby-version: "3.4"
72
+ - uses: actions/download-artifact@v4
73
+ with:
74
+ name: cross-gem-x86_64-linux
75
+ path: pkg/
76
+ - run: |
77
+ gem install pkg/expressir-*.gem --no-document
78
+ ruby .github/scripts/test_installed_gem.rb
79
+
80
+ smoke-linux-arm:
81
+ name: Smoke test Linux ARM64
82
+ needs: native
83
+ runs-on: ubuntu-22.04-arm
84
+ steps:
85
+ - uses: actions/checkout@v4
86
+ - uses: ruby/setup-ruby@v1
87
+ with:
88
+ ruby-version: "3.4"
89
+ - uses: actions/download-artifact@v4
90
+ with:
91
+ name: cross-gem-aarch64-linux
92
+ path: pkg/
93
+ - run: |
94
+ gem install pkg/expressir-*.gem --no-document
95
+ ruby .github/scripts/test_installed_gem.rb
96
+
97
+ smoke-macos-arm:
98
+ name: Smoke test macOS ARM64
99
+ needs: native
100
+ runs-on: macos-15
101
+ steps:
102
+ - uses: actions/checkout@v4
103
+ - uses: ruby/setup-ruby@v1
104
+ with:
105
+ ruby-version: "3.4"
106
+ - uses: actions/download-artifact@v4
107
+ with:
108
+ name: cross-gem-arm64-darwin
109
+ path: pkg/
110
+ - run: |
111
+ gem install pkg/expressir-*.gem --no-document
112
+ ruby .github/scripts/test_installed_gem.rb
113
+
114
+ smoke-macos-intel:
115
+ name: Smoke test macOS x86_64
116
+ needs: native
117
+ runs-on: macos-15-intel
118
+ steps:
119
+ - uses: actions/checkout@v4
120
+ - uses: ruby/setup-ruby@v1
121
+ with:
122
+ ruby-version: "3.4"
123
+ - uses: actions/download-artifact@v4
124
+ with:
125
+ name: cross-gem-x86_64-darwin
126
+ path: pkg/
127
+ - run: |
128
+ gem install pkg/expressir-*.gem --no-document
129
+ ruby .github/scripts/test_installed_gem.rb
130
+
131
+ smoke-windows:
132
+ name: Smoke test Windows x64
133
+ needs: native
134
+ runs-on: windows-2025
135
+ steps:
136
+ - uses: actions/checkout@v4
137
+ - uses: ruby/setup-ruby@v1
138
+ with:
139
+ ruby-version: "3.4"
140
+ - uses: actions/download-artifact@v4
141
+ with:
142
+ name: cross-gem-x64-mingw-ucrt
143
+ path: pkg/
144
+ - run: |
145
+ gem install pkg/expressir-*.gem --no-document
146
+ ruby .github/scripts/test_installed_gem.rb
147
+
148
+ source:
149
+ name: Build source gem
150
+ runs-on: ubuntu-22.04
151
+ steps:
152
+ - uses: actions/checkout@v4
153
+ - uses: oxidize-rb/actions/setup-ruby-and-rust@v1
154
+ with:
155
+ ruby-version: "3.4"
156
+ bundler-cache: true
157
+ cargo-cache: true
158
+ - run: bundle exec rake build
159
+ - uses: actions/upload-artifact@v4
160
+ with:
161
+ name: source-gem
162
+ path: pkg/*.gem
163
+ if-no-files-found: error
164
+
165
+ release:
166
+ name: Release gems to rubygems
167
+ needs: [native, smoke-linux, smoke-linux-arm, smoke-macos-arm, smoke-macos-intel, smoke-windows, source]
168
+ if: startsWith(github.ref, 'refs/tags/v')
169
+ runs-on: ubuntu-22.04
170
+ environment: release
171
+ steps:
172
+ - uses: actions/download-artifact@v4
173
+ with:
174
+ path: pkg/
175
+ pattern: cross-gem-*
176
+ merge-multiple: true
177
+ - name: Push to rubygems
178
+ env:
179
+ GEM_HOST_API_KEY: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
180
+ run: |
181
+ for gem in pkg/expressir-*.gem; do
182
+ gem push "$gem"
183
+ done
data/.gitignore CHANGED
@@ -48,3 +48,8 @@ CLAUDE.md
48
48
  Cargo.lock
49
49
  /ext/expressir_core/target/
50
50
  /ext/expressir_core/*.bundle
51
+
52
+ # prebuilt binding staged by rake-compiler for platform gems
53
+ /lib/expressir/expressir_core.bundle
54
+ /lib/expressir/expressir_core.so
55
+ /lib/expressir/expressir_core.dll
data/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [workspace]
2
2
  resolver = "2"
3
- members = ["crates/expressir-core"]
3
+ members = ["ext/expressir_core"]
4
4
 
5
5
  [workspace.package]
6
6
  edition = "2021"
data/Gemfile CHANGED
@@ -9,6 +9,7 @@ gem "irb"
9
9
  gem "lutaml-model", github: "lutaml/lutaml-model", branch: "main"
10
10
  gem "openssl", "~> 3.0"
11
11
  gem "rake"
12
+ gem "rake-compiler"
12
13
  gem "rspec"
13
14
  gem "rubocop"
14
15
  gem "rubocop-performance"
@@ -0,0 +1,23 @@
1
+ # Suma improvements — integration + expressir roadmap
2
+
3
+ The metanorma ↔ expressir chain via suma: ISO 10303 SMRL schemas → expressir
4
+ parse (compiled set EXSCS1) → LazyRepository + Liquid drops
5
+ (metanorma-plugin-lutaml) → metanorma-iso schema documents. The chain is
6
+ proven piecewise (1307 schemas, 0 failures, warm start 7.8s, live xrefs in
7
+ rendered XML); the items below close the last mile and deepen expressir.
8
+
9
+ | # | item | status |
10
+ |---|------|--------|
11
+ | 01 | prebuilt platform gems (oxidize-rb cross-gem, parsanol pattern) | in flight |
12
+ | 02 | suma full-document E2E on current stack | pending |
13
+ | 03 | ItemGraph — cross-schema subtype/interface graph API | pending |
14
+ | 04 | compiled-set + lazy path as suma default build | pending |
15
+ | 05 | corpus validation surfaced in suma builds | pending |
16
+ | 06 | memory/perf re-measure with binding-by-default | pending |
17
+ | 07 | #276 Part 28 XML — select mapping v2 | pending |
18
+ | 08 | windows gnu toolchain for in-place ext builds | superseded by 01 for CI; kept for local dev |
19
+ | 09 | checker tranche 3 (INVERSE, SELF\ qualifiers) | pending |
20
+ | 10 | #91 EXPRESSdoc (upstream-gated) | parked |
21
+
22
+ Evidence trail: #88/#461 (refpath validation), #462/#467 (ABSTRACT body
23
+ form), v2.4.21–v2.4.24 (binding by default), plugin 0.7.53 + #305/#306.
@@ -0,0 +1,33 @@
1
+ # 01 — Prebuilt platform gems
2
+
3
+ ## Goal
4
+ `gem install expressir` compiles nothing on machines with a prebuilt
5
+ platform gem: the binding binary ships inside per-platform gems
6
+ (`expressir-<ver>-arm64-darwin` etc.), following the parsanol-ruby
7
+ pattern (oxidize-rb cross-gem + RbSys::ExtensionTask).
8
+
9
+ ## Pattern (from ~/src/parsanol/parsanol-ruby)
10
+ - `oxidize-rb/actions/fetch-ci-data@v1` computes supported platforms
11
+ - `oxidize-rb/actions/cross-gem@v1` matrix builds per-platform gems in
12
+ rake-compiler-dock containers (coherent gnu toolchain per target —
13
+ this is what fixes windows)
14
+ - per-platform smoke jobs on REAL runners installing the gem and running
15
+ a smoke script (NATIVE true + parse)
16
+ - source gem still builds via the gemspec (extensions stay declared);
17
+ rubygems prefers a matching platform gem automatically
18
+
19
+ ## expressir implementation
20
+ - `rakelib/native.rake`: `RbSys::ExtensionTask.new("expressir_core")`,
21
+ `ext.lib_dir = "lib/expressir"`, `cross_compiling` callback stages
22
+ `expressir_core.{so,dylib,dll}` into `lib/expressir/` (loads via the
23
+ existing first `require "expressir/expressir_core"` in core.rb)
24
+ - extconf: mingw skip becomes bypassable (`EXPRESSIR_CORE_FORCE=1` set in
25
+ the cross-gem container, where cargo gnu matches the mingw-ucrt ruby)
26
+ - `.github/workflows/native-gems.yml`: ci-data + cross-gem matrix +
27
+ smoke jobs + source gem + `gem push` on tag (RUBYGEMS_API_KEY secret)
28
+ - `.github/scripts/test_installed_gem.rb`: smoke script
29
+
30
+ ## Acceptance
31
+ - workflow green across platforms with smoke jobs passing
32
+ - a platform gem installs cargo-less and reports NATIVE true
33
+ - source-gem install path unchanged (2.4.21 behavior)
@@ -0,0 +1,39 @@
1
+ # 02 — Suma full-document E2E on the current stack
2
+
3
+ ## Goal
4
+ One clean E2E run of an actual suma document build with expressir 2.4.24 +
5
+ metanorma-plugin-lutaml 0.7.53 + current metanorma gems, rendering a real
6
+ ISO 10303 module document to XML.
7
+
8
+ ## Why now
9
+ The earlier full-render was blocked by the metanorma-document/relaton
10
+ 0.2.x skew; that ecosystem has moved (metanorma-document 0.5.1, relaton
11
+ 3.0 pre-alpha). The expressir side is proven current.
12
+
13
+ ## Steps
14
+ - locate the suma build entry (~/src/mn/iso-10303 suma config or the
15
+ metanorma-iso document that renders schema pages)
16
+ - bundle with current metanorma; render one module doc end to end
17
+ - capture XML diffs vs the last good render; if the relaton skew
18
+ persists, name the exact unresolved constraint
19
+
20
+ ## Acceptance
21
+ - a rendered module document, or a one-line statement of the precise
22
+ upstream blocker with the failing constraint
23
+
24
+ ## Result: DONE (2026-09-27)
25
+
26
+ The upstream blocker has cleared. On the iso-10303 ladder (user's
27
+ Gemfile WIP + suma from path + relaton prereleases with the rubyzip-3
28
+ fix now on rubygems — the fix/rubyzip-3 git pins were dropped as
29
+ obsolete):
30
+
31
+ bundle exec suma build srl-smol.yml # one-part SMOL collection
32
+ # => Compiling schema collection... Compiling complete collection...
33
+ # => SMOL-EXIT=0
34
+
35
+ Artifacts: 286 files in _site/ — real ISO document XML (flavor iso,
36
+ schema content present), schema-doc pages, presentation XML. The stale
37
+ sums pin was the rubygems suma 0.1.11 (expressir ~> 2.1, metanorma-cli
38
+ 0.0.3 → broken 2019 stack); suma from source (metanorma ~> 2.3 line)
39
+ resolves cleanly.
@@ -0,0 +1,22 @@
1
+ # 03 — ItemGraph
2
+
3
+ ## Goal
4
+ Cross-schema graph API over a parsed repository: subtype edges, interface
5
+ (USE FROM/REFERENCE FROM) edges, transitive queries — feeding suma doc
6
+ generation (hyperlink coverage, "used by" sections, dependency diagrams).
7
+
8
+ ## Evidence
9
+ SMRL full-set load proved the data: 37.7k subtype edges, 5.5k interface
10
+ edges over 1307 schemas (TODO.memory SMRL notes). The class is the
11
+ documented remaining gap.
12
+
13
+ ## Shape
14
+ - `Expressir::Model::ItemGraph` built from a Repository (or a
15
+ LazyRepository hydrated once)
16
+ - `subtypes(id)`, `supertypes(id)`, `used_by(id)`, `uses(id)`,
17
+ `transitive_subtypes(id)` — downcased-id keyed, cycle-safe
18
+ - built once per repository; O(V+E)
19
+
20
+ ## Acceptance
21
+ - specs over a multi-schema fixture (diamond subtyping, mutual USE FROM)
22
+ - SMRL smoke: edge counts match the recorded 37.7k/5.5k within tolerance
@@ -0,0 +1,16 @@
1
+ # 04 — Compiled-set + lazy path as the suma default build
2
+
3
+ ## Goal
4
+ suma builds parse the SMRL once into an EXSCS1 artifact and all document
5
+ renders hydrate per schema from it (warm start 7.8s vs 12s cold), instead
6
+ of eager-parsing on every build.
7
+
8
+ ## Steps
9
+ - locate the suma/rake build entry that calls Parser.from_files
10
+ - switch to `Parser.from_files(files, compiled_set:)` then
11
+ `LazyRepository.new(set)`
12
+ - keep an escape hatch (env) for debugging with eager parse
13
+
14
+ ## Acceptance
15
+ - a suma build log showing one artifact build + per-page hydration
16
+ - rendered output byte-identical to the eager path
@@ -0,0 +1,17 @@
1
+ # 05 — Corpus validation surfaced in suma builds
2
+
3
+ ## Goal
4
+ The expressir validation layers (mapping_validate incl. refpath
5
+ resolution — which found 2 genuine corpus drifts in a 60-module sweep)
6
+ run as part of suma builds, reporting per-module drift in the build log
7
+ (and optionally in published output).
8
+
9
+ ## Steps
10
+ - wire `expressir mapping_validate` over each module's mapping.yaml in
11
+ the suma build loop
12
+ - aggregate a drift report artifact; non-zero exit only on NEW drift vs
13
+ a committed baseline file
14
+
15
+ ## Acceptance
16
+ - baseline for the 2 known drifts; a build with a synthetic new drift
17
+ fails with the module/location in the message
@@ -0,0 +1,36 @@
1
+ # 06 — Memory/perf re-measure with binding-by-default
2
+
3
+ ## Goal
4
+ The recorded profile ("50x memory ratio not achieved", pure-Ruby parse
5
+ dominating) predates binding-by-default. Consumers now get the Rust
6
+ parse path automatically; re-measure and refresh the story.
7
+
8
+ ## Steps
9
+ - rerun the memory harness (TODO.memory methodology) with
10
+ NATIVE_AVAILABLE true on the same fixtures
11
+ - record ratio vs the 50x target; note remaining hot spots
12
+ - update TODO.memory notes + the perf memory file
13
+
14
+ ## Acceptance
15
+ - fresh numbers committed alongside the old ones, with the parse
16
+ backend identified per measurement
17
+
18
+ ## Results (2026-09-27, ruby 3.4.8, 227KB synthetic / 4000 entities)
19
+
20
+ | path | parse time | RSS delta | after GC |
21
+ |------|-----------|-----------|----------|
22
+ | Rust binding (NATIVE_AVAILABLE) | 7.7s | 348 MB | 387 MB |
23
+ | pure-Ruby (use_native: false) | 7.5s | 307 MB | 353 MB |
24
+
25
+ Both paths hydrate the full lutaml-model object tree, which dominates
26
+ the delta — the native parse itself is not the differentiator at this
27
+ size (arena overhead is offset by wire-hash materialization). The
28
+ binding's memory advantage in the field comes from the compiled-set
29
+ path (hydrate per file / warm start 7.8s), not from a lower per-parse
30
+ floor. Ratio ~1.5 MB/KB of source on both paths — consistent with the
31
+ older "~600x for large files" observation: the object MODEL is the
32
+ cost driver, which is exactly what M2 laziness addresses.
33
+
34
+ Old "50x memory ratio target" concern: RETIRED as the wrong metric for
35
+ the binding; per-page hydration (LazyRepository) is the delivered
36
+ mechanism for bounded memory at render time.
@@ -0,0 +1,15 @@
1
+ # 07 — Part 28 XML: select mapping v2 (#276)
2
+
3
+ ## Goal
4
+ Full ISO 10303-28 EXPRESS-to-XML beyond the v1 skeleton
5
+ (lib/expressir/express/xsd.rb): proper SELECT mapping (currently
6
+ xs:anyType), aggregate/optional fidelity, schema-level structure.
7
+
8
+ ## Steps
9
+ - ISO 10303-28 mapping rules for SELECT types (union/member semantics)
10
+ - apply on top of xsd.rb's element/complexType generation
11
+ - validate generated XSDs against the ISO 10303-28 conformance examples
12
+
13
+ ## Acceptance
14
+ - a select-typed attribute renders per the standard (not xs:anyType)
15
+ - the v1 examples still generate unchanged except where v2 improves them
@@ -0,0 +1,15 @@
1
+ # 08 — Windows gnu toolchain for in-place ext builds
2
+
3
+ ## Goal
4
+ Local windows source installs and windows CI in-place builds compile the
5
+ binding: rustup gnu target (`x86_64-pc-windows-gnu`) matching the mingw
6
+ ruby, so extconf's guard can be lifted there.
7
+
8
+ ## Status
9
+ Superseded for CI by 01 (cross-gem containers carry coherent toolchains;
10
+ platform gems ship prebuilt). Kept for developers hacking on windows
11
+ checkouts directly.
12
+
13
+ ## Acceptance
14
+ - a windows machine with the gnu target builds the ext via extconf
15
+ without EXPRESSIR_CORE_FORCE
@@ -0,0 +1,13 @@
1
+ # 09 — Checker tranche 3 + SHTOLO completion
2
+
3
+ ## Goal
4
+ Remaining eengine-parity checks: INVERSE-family rules and SELF\ qualifier
5
+ validation (tranche 3), then the leftover SHTOLO longform items
6
+ (TODO.parity-ee/14).
7
+
8
+ ## Gate
9
+ Grammar support for INVERSE-scoped references and SELF\ qualified
10
+ refs — verify against the current grammar after recent fixes.
11
+
12
+ ## Acceptance
13
+ - each new check has a fixture that fires it + a clean-corpus negative
@@ -0,0 +1,8 @@
1
+ # 10 — EXPRESSdoc (#91, upstream-gated)
2
+
3
+ ## Goal
4
+ Support the upcoming EXPRESSdoc format.
5
+
6
+ ## Status
7
+ Parked on annotated-express#94 (upstream). Revisit when that lands;
8
+ expressir's side is the remark/annotation model already in place.
@@ -16,7 +16,4 @@ magnus = { version = "0.9" }
16
16
  expressir-rs = { git = "https://github.com/lutaml/expressir-rs", tag = "v0.1.6" }
17
17
  serde_json = "1.0"
18
18
 
19
- # Standalone workspace: the repo root workspace owns crates/, the ext
20
- # is built by rb_sys with its own cargo invocation.
21
- [workspace]
22
19
 
@@ -24,7 +24,10 @@ end
24
24
  # Windows rubies are mingw-built while rustup defaults to the msvc
25
25
  # toolchain; rb-sys cannot bridge the two, so skip there too (windows
26
26
  # installs fall back to the pure-Ruby parser).
27
- if RUBY_PLATFORM.include?("mingw")
27
+ # Local mingw rubies pair with an msvc-default cargo and cannot build;
28
+ # cross-gem containers carry the coherent gnu toolchain, so the skip is
29
+ # bypassable there (EXPRESSIR_CORE_FORCE=1).
30
+ if RUBY_PLATFORM.include?("mingw") && ENV["EXPRESSIR_CORE_FORCE"] != "1"
28
31
  write_noop_makefile
29
32
  warn "expressir: skipping the Rust core extension on mingw (msvc " \
30
33
  "toolchain mismatch)"
@@ -17,6 +17,9 @@ module Expressir
17
17
  # check-unresolved-ref (aggregate of eeng type/entity unparsed notes)
18
18
  # check-select-extended-type
19
19
  # check-enumeration-extended-type
20
+ # check-inverse-entity/-attrib-ref/-inverse/-derive (tranche 3)
21
+ # qualified-attrib-group-not-found/-not-ancestor,
22
+ # qualified-attrib-attr-not-found (tranche 3)
20
23
  #
21
24
  # Severity: :error stops a clean SHTOLO run; :warning is advisory.
22
25
  class Checker
@@ -226,6 +229,8 @@ module Expressir
226
229
  check_subtype_cycles(schema, entity)
227
230
 
228
231
  check_where_rules(schema, entity, entity.where_rules)
232
+ check_inverse_attributes(schema, entity)
233
+ check_group_references(schema, entity)
229
234
  Array(entity.unique_rules).each do |ur|
230
235
  next if ur.id.nil? || ur.id.match?(UNIQUE_LABEL)
231
236
 
@@ -290,6 +295,193 @@ module Expressir
290
295
  end
291
296
  end
292
297
 
298
+ # eeng check-inverse-* (ISO 10303-11 9.2.1.4): every INVERSE
299
+ # attribute must name a real entity and pair with a real
300
+ # attribute on it — one that is neither DERIVE nor itself an
301
+ # INVERSE — whose domain includes the owning entity (through
302
+ # subtype relations and nested aggregates). check-inverse-agg
303
+ # (only SET/BAG may aggregate an inverse) is structurally
304
+ # impossible here: the grammar rejects other aggregate kinds.
305
+ def check_inverse_attributes(schema, entity)
306
+ entity.attributes.to_a
307
+ .grep(Model::Declarations::InverseAttribute)
308
+ .each { |inv| check_inverse_attribute(schema, entity, inv) }
309
+ end
310
+
311
+ def check_inverse_attribute(schema, entity, inv)
312
+ target = inverse_target(schema, inv)
313
+ if target.nil?
314
+ note!(:check_inverse_entity, :error, schema,
315
+ "ENTITY #{entity.id}: INVERSE '#{inv.id}' target " \
316
+ "'#{inverse_target_id(inv)}' is not an entity", inv)
317
+ return
318
+ end
319
+
320
+ attr_id = inverse_paired_attr_id(inv)
321
+ if attr_id.nil?
322
+ note!(:check_inverse_attrib_ref, :error, schema,
323
+ "ENTITY #{entity.id}: INVERSE '#{inv.id}' names no " \
324
+ "attribute after FOR", inv)
325
+ return
326
+ end
327
+
328
+ owner = inverse_attr_owner(schema, inv, target)
329
+ paired = owner && attribute_on_closure(schema, owner, attr_id)
330
+ if paired.nil?
331
+ note!(:check_inverse_attrib_ref, :error, schema,
332
+ "ENTITY #{entity.id}: INVERSE '#{inv.id}': entity " \
333
+ "'#{owner&.id || target.id}' has no attribute '#{attr_id}'", inv)
334
+ return
335
+ end
336
+
337
+ if paired.is_a?(Model::Declarations::InverseAttribute)
338
+ note!(:check_inverse_inverse, :error, schema,
339
+ "ENTITY #{entity.id}: INVERSE '#{inv.id}' pairs with " \
340
+ "'#{owner.id}.#{attr_id}', which is itself an INVERSE", inv)
341
+ elsif paired.kind == Expressir::Model::Declarations::Attribute::DERIVED
342
+ note!(:check_inverse_derive, :error, schema,
343
+ "ENTITY #{entity.id}: INVERSE '#{inv.id}' pairs with " \
344
+ "'#{owner.id}.#{attr_id}', which is a DERIVE", inv)
345
+ end
346
+ end
347
+
348
+ def inverse_target(schema, inv)
349
+ type = inv.type
350
+ type = type.base_type if type.respond_to?(:base_type)
351
+ return nil unless type.respond_to?(:id)
352
+
353
+ find_entity(schema, ref_id(type))
354
+ end
355
+
356
+ def inverse_target_id(inv)
357
+ type = inv.type
358
+ type = type.base_type if type.respond_to?(:base_type)
359
+ ref_id(type)
360
+ end
361
+
362
+ # The entity the FOR-attribute lives on: the qualified
363
+ # `e.a FOR` form overrides the type's entity.
364
+ def inverse_attr_owner(schema, inv, target)
365
+ expr = inv.expression
366
+ if expr.is_a?(Model::References::AttributeReference) &&
367
+ expr.ref.respond_to?(:id)
368
+ find_entity(schema, ref_id(expr.ref)) || target
369
+ else
370
+ target
371
+ end
372
+ end
373
+
374
+ def inverse_paired_attr_id(inv)
375
+ expr = inv.expression
376
+ return ref_id(expr) if expr.respond_to?(:id)
377
+
378
+ expr.respond_to?(:attribute) ? ref_id(expr.attribute) : nil
379
+ end
380
+
381
+ # Attribute on +entity+ or any of its supertype ancestors.
382
+ def attribute_on_closure(schema, entity, attr_id)
383
+ key = attr_id.safe_downcase
384
+ seen = {}.compare_by_identity
385
+ queue = [entity]
386
+ until queue.empty?
387
+ current = queue.shift
388
+ next if seen[current]
389
+
390
+ seen[current] = true
391
+ hit = Array(current.attributes).find do |a|
392
+ a.id&.safe_downcase == key
393
+ end
394
+ return hit if hit
395
+
396
+ Array(current.subtype_of).each do |ref|
397
+ id = ref_id(ref)
398
+ queue << find_entity(schema, id) if id
399
+ end
400
+ end
401
+ nil
402
+ end
403
+
404
+ # eeng qualified-attrib-* (ISO 10303-11 12.7.4). The group must
405
+ # resolve to an entity and the qualified attribute must exist on
406
+ # it. The ANCESTRY requirement applies only to redeclared
407
+ # attributes (SELF\T.attr as an attribute declaration) — eeng
408
+ # checks exactly those; TYPEOF-guarded sibling views in
409
+ # expressions (geometry_schema's SELF\subface.parent_face) are
410
+ # standard corpus usage and are not flagged.
411
+ def check_group_references(schema, entity)
412
+ closure = supertype_closure_ids(schema, entity)
413
+ closure << entity.id if entity.id
414
+ redeclared = {}.compare_by_identity
415
+ entity.attributes.to_a.each do |attr|
416
+ sa = attr.supertype_attribute
417
+ next unless sa.is_a?(Model::References::AttributeReference) &&
418
+ sa.ref.is_a?(Model::References::GroupReference)
419
+
420
+ redeclared[sa.ref] = true
421
+ group = sa.ref
422
+ target = resolve_group(schema, entity, group)
423
+ next if target.nil?
424
+ next if closure.include?(ref_id(group.entity)&.safe_downcase)
425
+
426
+ note!(:qualified_attrib_group_not_ancestor, :error, schema,
427
+ "ENTITY #{entity.id}: SELF\\'#{ref_id(group.entity)}' " \
428
+ "is not a supertype of #{entity.id}", group)
429
+ end
430
+
431
+ each_node(entity) do |node|
432
+ next unless node.is_a?(Model::References::GroupReference)
433
+ next if redeclared[node]
434
+
435
+ target = resolve_group(schema, entity, node)
436
+ next if target.nil?
437
+
438
+ parent = node.parent
439
+ attr_id = parent.respond_to?(:attribute) ? ref_id(parent.attribute) : nil
440
+ next if attr_id.nil?
441
+ next if Array(target.attributes).any? do |a|
442
+ a.id&.safe_downcase == attr_id.safe_downcase
443
+ end
444
+
445
+ note!(:qualified_attrib_attr_not_found, :error, schema,
446
+ "ENTITY #{entity.id}: '#{ref_id(node.entity)}.#{attr_id}': " \
447
+ "'#{ref_id(node.entity)}' has no attribute '#{attr_id}'", node)
448
+ end
449
+ end
450
+
451
+ # Resolve a group qualifier's entity, noting
452
+ # qualified-attrib-group-not-found when it does not resolve.
453
+ def resolve_group(schema, entity, node)
454
+ group_id = ref_id(node.entity)
455
+ target = group_id && find_entity(schema, group_id)
456
+ return target if target
457
+
458
+ note!(:qualified_attrib_group_not_found, :error, schema,
459
+ "ENTITY #{entity.id}: group '#{group_id}' in a qualified " \
460
+ "attribute does not resolve to an entity", node)
461
+ nil
462
+ end
463
+
464
+ def supertype_closure_ids(schema, entity)
465
+ seen = {}.compare_by_identity
466
+ queue = [entity]
467
+ ids = []
468
+ until queue.empty?
469
+ current = queue.shift
470
+ next if seen[current]
471
+
472
+ seen[current] = true
473
+ Array(current.subtype_of).each do |ref|
474
+ id = ref_id(ref)
475
+ next if id.nil?
476
+
477
+ ids << id
478
+ parent = find_entity(schema, id)
479
+ queue << parent if parent
480
+ end
481
+ end
482
+ ids
483
+ end
484
+
293
485
  def each_reference(node, &block)
294
486
  case node
295
487
  when Model::References::SimpleReference then yield node
@@ -160,6 +160,10 @@ module Expressir
160
160
  end
161
161
 
162
162
  repository = build_repository(all_exp_files, skip_references: skip_references)
163
+ if set_path && !skip_references && !File.exist?(set_path) &&
164
+ core_set_available? && !all_exp_files.include?(nil)
165
+ write_compiled_set(set_path, files, all_exp_files, root_path)
166
+ end
163
167
  if set_path && !skip_references && File.exist?(set_path)
164
168
  files = all_exp_files.compact
165
169
  RefsOverlay.write(set_path, files)
@@ -383,6 +387,47 @@ include_source: nil, root_path: nil, use_native: nil, &block)
383
387
  all_exp_files
384
388
  end
385
389
 
390
+ # Artifact write for the non-batch paths (single file,
391
+ # EXPRESSIR_BATCH=0, small sequential sets): serializes the
392
+ # parsed models through Core.write_set, producing the same
393
+ # artifact shape the batch path writes natively. The wire is
394
+ # pre-remark — RemarkOverlay.write carries the remark-derived
395
+ # attributes, exactly as it does for the batch path.
396
+ def self.write_compiled_set(set_path, files, exp_files, root_path)
397
+ require "digest"
398
+ require "json"
399
+ schema_file_for = lambda do |file|
400
+ root_path ? Pathname.new(file.to_s).relative_path_from(root_path).to_s : file.to_s
401
+ end
402
+ entries = files.zip(exp_files).map do |file, exp_file|
403
+ [schema_file_for.call(file),
404
+ Digest::SHA256.file(file.to_s).hexdigest,
405
+ JSON.generate(strip_remark_wire(exp_file.to_hash))]
406
+ end
407
+ ::Expressir::Core.write_set(set_path, entries, Expressir::Version::VERSION)
408
+ RemarkOverlay.write(set_path, exp_files)
409
+ end
410
+ private_class_method :write_compiled_set
411
+
412
+ # Deep copy of a serialized model without the RemarkOverlay
413
+ # attributes: the artifact wire carries only the parsed model;
414
+ # untagged remarks are plain hashes that the Rust hydrator
415
+ # cannot instantiate, and every remark-derived value belongs to
416
+ # the overlay anyway.
417
+ def self.strip_remark_wire(node)
418
+ case node
419
+ when Hash
420
+ node.each_with_object({}) do |(key, value), out|
421
+ out[key] = strip_remark_wire(value) unless RemarkOverlay::CAPTURED.include?(key)
422
+ end
423
+ when Array
424
+ node.map { |value| strip_remark_wire(value) }
425
+ else
426
+ node
427
+ end
428
+ end
429
+ private_class_method :strip_remark_wire
430
+
386
431
  def self.build_repository(all_exp_files, skip_references: nil)
387
432
  repository = Model::Repository.new(files: all_exp_files)
388
433
 
@@ -6,13 +6,18 @@ module Expressir
6
6
  # TODO.parity-ee 15) — the express2xsd counterpart, built on
7
7
  # moxml.
8
8
  #
9
- # Mapping (v1):
9
+ # Mapping (v2):
10
10
  # ENTITY → xs:element + xs:complexType (explicit attributes
11
11
  # in declaration order); SUBTYPE OF a schema-local
12
12
  # entity becomes a substitutionGroup reference.
13
13
  # ENUMERATION → xs:simpleType with enumeration facets.
14
- # SELECT → xs:element of xs:anyType (full Part 28 select
15
- # mapping is a documented v1 limitation).
14
+ # SELECT → xs:complexType with an xs:choice over the member
15
+ # types (ISO 10303-28 select mapping): entity
16
+ # members reference their global element
17
+ # declarations, defined-type members carry their
18
+ # simpleType. Declared members only — extensible
19
+ # selects and cross-schema members keep the
20
+ # documented v1 fallbacks.
16
21
  # defined TYPE over a simple type → xs:simpleType restriction.
17
22
  # attributes → xs:element inside the complexType sequence;
18
23
  # LIST/SET/BAG/ARRAY → maxOccurs="unbounded".
@@ -54,6 +59,10 @@ module Expressir
54
59
 
55
60
  def type_node(doc, schema, type)
56
61
  underlying = type.underlying_type
62
+ if underlying.is_a?(Expressir::Model::DataTypes::Select)
63
+ return select_type_node(doc, schema, type, underlying)
64
+ end
65
+
57
66
  node = doc.create_element("xs:simpleType")
58
67
  node.set_attributes("name" => type.id)
59
68
  restriction = doc.create_element("xs:restriction")
@@ -71,6 +80,52 @@ module Expressir
71
80
  node
72
81
  end
73
82
 
83
+ # ISO 10303-28: a select type maps to a complexType choosing
84
+ # among its member types. An extensible select with no declared
85
+ # items gets an empty sequence — an empty xs:choice is not
86
+ # schema-valid.
87
+ def select_type_node(doc, schema, type, select)
88
+ node = doc.create_element("xs:complexType")
89
+ node.set_attributes("name" => type.id)
90
+ container = select.items.to_a.empty? ? "xs:sequence" : "xs:choice"
91
+ choice = doc.create_element(container)
92
+ node.add_child(choice)
93
+ select.items.to_a.each do |item|
94
+ choice.add_child(select_member_element(doc, schema, item))
95
+ end
96
+ node
97
+ end
98
+
99
+ def select_member_element(doc, schema, item)
100
+ node = doc.create_element("xs:element")
101
+ if item.is_a?(Expressir::Model::References::SimpleReference)
102
+ id = item.respond_to?(:base_path) && item.base_path ? base_id(item.base_path) : item.id
103
+ member = select_member_target(schema, id)
104
+ if member == :entity
105
+ node.set_attributes("ref" => id)
106
+ elsif member == :type
107
+ node.set_attributes("name" => id, "type" => id)
108
+ else
109
+ node.set_attributes("name" => id, "type" => "xs:anyType")
110
+ end
111
+ else
112
+ name = item.class.name.split("::").last.downcase
113
+ node.set_attributes("name" => name,
114
+ "type" => BUILTIN_TYPES.fetch(name, "xs:anyType"))
115
+ end
116
+ node
117
+ end
118
+
119
+ def select_member_target(schema, id)
120
+ if schema.entities.to_a.any? { |e| e.id.safe_downcase == id.safe_downcase }
121
+ :entity
122
+ elsif schema.types.to_a.any? { |t| t.id.safe_downcase == id.safe_downcase }
123
+ :type
124
+ else
125
+ :unknown
126
+ end
127
+ end
128
+
74
129
  def entity_element(doc, schema, entity)
75
130
  node = doc.create_element("xs:element")
76
131
  node.set_attributes("name" => entity.id)
@@ -83,18 +138,84 @@ module Expressir
83
138
  def entity_type(doc, schema, entity)
84
139
  node = doc.create_element("xs:complexType")
85
140
  node.set_attributes("name" => entity.id)
141
+ container = node
142
+ parent = supertype_name(schema, entity)
143
+ if parent
144
+ content = doc.create_element("xs:complexContent")
145
+ extension = doc.create_element("xs:extension")
146
+ extension.set_attributes("base" => parent)
147
+ content.add_child(extension)
148
+ node.add_child(content)
149
+ container = extension
150
+ end
86
151
  sequence = doc.create_element("xs:sequence")
87
- node.add_child(sequence)
88
- entity.attributes.to_a.each do |attr|
89
- skip = attr.kind == Expressir::Model::Declarations::Attribute::INVERSE ||
90
- (attr.respond_to?(:derive?) && attr.derive?)
91
- next if skip
92
-
152
+ container.add_child(sequence)
153
+ explicit_attributes(entity).each do |attr|
154
+ sequence.add_child(attribute_element(doc, schema, attr))
155
+ end
156
+ # EXPRESS multiple inheritance: the extension base carries the
157
+ # first parent's chain; later parents' attributes (transitively)
158
+ # become explicit members of the subtype's sequence.
159
+ additional_parent_attributes(schema, entity).each do |attr|
93
160
  sequence.add_child(attribute_element(doc, schema, attr))
94
161
  end
95
162
  node
96
163
  end
97
164
 
165
+ def explicit_attributes(entity)
166
+ entity.attributes.to_a.reject do |attr|
167
+ attr.kind == Expressir::Model::Declarations::Attribute::INVERSE ||
168
+ (attr.respond_to?(:derive?) && attr.derive?)
169
+ end
170
+ end
171
+
172
+ # Attributes contributed by multiple inheritance: every ancestor
173
+ # reachable from the additional local supertypes (all but the
174
+ # first), minus the first supertype's own chain — the extension
175
+ # base already carries that. Cycle-safe.
176
+ def additional_parent_attributes(schema, entity)
177
+ parents = local_supertypes(schema, entity)
178
+ return [] if parents.size <= 1
179
+
180
+ base_chain = ancestor_set(schema, parents.first)
181
+ seen = {}.compare_by_identity
182
+ queue = parents.drop(1)
183
+ attrs = []
184
+ until queue.empty?
185
+ current = queue.shift
186
+ next if seen[current] || base_chain[current]
187
+
188
+ seen[current] = true
189
+ attrs.concat(explicit_attributes(current))
190
+ queue.concat(local_supertypes(schema, current))
191
+ end
192
+ attrs
193
+ end
194
+
195
+ # Identity set of +entity+ and every supertype ancestor.
196
+ def ancestor_set(schema, entity)
197
+ seen = {}.compare_by_identity
198
+ queue = [entity]
199
+ until queue.empty?
200
+ current = queue.shift
201
+ next if seen[current]
202
+
203
+ seen[current] = true
204
+ queue.concat(local_supertypes(schema, current))
205
+ end
206
+ seen
207
+ end
208
+
209
+ def local_supertypes(schema, entity)
210
+ Array(entity.subtype_of).filter_map do |ref|
211
+ id = ref.is_a?(String) ? ref : ref.id
212
+ next nil unless id
213
+
214
+ schema.entities.to_a
215
+ .find { |e| e.id.safe_downcase == id.safe_downcase }
216
+ end
217
+ end
218
+
98
219
  def attribute_element(doc, schema, attr)
99
220
  node = doc.create_element("xs:element")
100
221
  node.set_attributes("name" => attr.id)
@@ -126,14 +247,7 @@ module Expressir
126
247
  end
127
248
 
128
249
  def supertype_name(schema, entity)
129
- ref = Array(entity.subtype_of).first
130
- return nil unless ref
131
-
132
- id = ref.is_a?(String) ? ref : ref.id
133
- return nil unless id
134
- return nil unless schema.entities.to_a.any? { |e| e.id.safe_downcase == id.safe_downcase }
135
-
136
- schema.entities.find { |e| e.id.safe_downcase == id.safe_downcase }.id
250
+ local_supertypes(schema, entity).first&.id
137
251
  end
138
252
 
139
253
  def base_id(base_path)
@@ -80,6 +80,56 @@ module Expressir
80
80
  @subtype_of.flat_map { |child, parents| parents.map { |p| [child, p] } }
81
81
  end
82
82
 
83
+ # Direct subtypes of an entity (the inverse of subtype_of).
84
+ def subtypes(path)
85
+ key = canonical(path)
86
+ return [] unless @nodes.key?(key)
87
+
88
+ @subtype_of.select { |_child, parents| parents.include?(key) }.keys
89
+ end
90
+
91
+ # Transitive subtype closure of an entity (everything below it
92
+ # in the hierarchy), breadth-first and cycle-safe.
93
+ def transitive_subtypes(path)
94
+ key = canonical(path)
95
+ return [] unless @nodes.key?(key)
96
+
97
+ seen = {}
98
+ queue = subtypes(key)
99
+ until queue.empty?
100
+ current = queue.shift
101
+ next if seen[current]
102
+
103
+ seen[current] = true
104
+ queue.concat(subtypes(current))
105
+ end
106
+ seen.keys
107
+ end
108
+
109
+ # Transitive interface closure of a schema — every schema it
110
+ # depends on, directly or through intermediates. Cycle-safe.
111
+ def dependency_closure(schema)
112
+ schema_id = schema.respond_to?(:id) ? schema.id : schema.to_s
113
+ seen = {}
114
+ queue = dependencies_of(schema_id).dup
115
+ until queue.empty?
116
+ current = queue.shift
117
+ next if seen[current]
118
+
119
+ seen[current] = true
120
+ queue.concat(@dependencies[current] || [])
121
+ end
122
+ seen.keys
123
+ end
124
+
125
+ # Schemas that import `schema` (USE FROM / REFERENCE FROM it),
126
+ # direct only — the "used by" direction for doc generation.
127
+ def dependents_of(schema)
128
+ schema_id = schema.respond_to?(:id) ? schema.id : schema.to_s
129
+ key = schema_id.safe_downcase
130
+ @dependencies.select { |_from, deps| deps.include?(key) }.keys
131
+ end
132
+
83
133
  def cross_schema_subtype_edges
84
134
  subtype_edges.count { |child, parent| child.split(".")[0] != parent.split(".")[0] }
85
135
  end
@@ -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.24".freeze
6
+ VERSION = "2.4.25".freeze
7
7
  end
8
8
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Prebuilt platform gems (TODO.suma-improvements/01): the compiled Rust
4
+ # core ships inside per-platform gems, mirroring the parsanol-ruby
5
+ # pattern (RbSys::ExtensionTask + cross_compiling staging). The source
6
+ # gem keeps its gemspec-declared extconf build; platform gems carry the
7
+ # artifact in lib/expressir/, which core.rb's first require finds.
8
+ begin
9
+ require "rb_sys/extensiontask"
10
+ require "rb_sys/toolchain_info"
11
+
12
+ gemspec = Gem::Specification.load("expressir.gemspec")
13
+
14
+ RbSys::ExtensionTask.new("expressir_core", gemspec) do |ext|
15
+ ext.lib_dir = "lib/expressir"
16
+
17
+ ext.cross_compiling do |spec|
18
+ next if spec.platform == Gem::Platform::RUBY
19
+
20
+ plat = spec.platform.to_s
21
+ triple = begin
22
+ RbSys::ToolchainInfo::DATA.fetch(plat).fetch("rust-target")
23
+ rescue KeyError
24
+ { "arm-linux-musl" => "arm-unknown-linux-musleabihf" }.fetch(plat)
25
+ end
26
+
27
+ # rb-sys's cargo build already ran for the cross target; stage the
28
+ # artifact from the tree (rake-compiler stages callback-added files).
29
+ # rb_sys uses a per-package target dir unless the crate is a
30
+ # workspace member (then the root target/ holds artifacts) — glob
31
+ # both layouts.
32
+ art = Dir["target/#{triple}/release/expressir_core.{so,dylib,dll}",
33
+ "target/#{triple}/release/libexpressir_core.{so,dylib}",
34
+ "ext/expressir_core/target/#{triple}/release/expressir_core.{so,dylib,dll}",
35
+ "ext/expressir_core/target/#{triple}/release/libexpressir_core.{so,dylib}"].first
36
+ raise "binding artifact not found for #{plat} (#{triple})" unless art
37
+
38
+ cp art, "lib/expressir/"
39
+ spec.files << "lib/expressir/#{File.basename(art)}"
40
+ end
41
+ end
42
+ rescue LoadError
43
+ # rake-compiler/rb_sys not installed (consumer checkouts); the native
44
+ # gem tasks are a maintainer/CI concern only.
45
+ 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.24
4
+ version: 2.4.25
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-26 00:00:00.000000000 Z
11
+ date: 2026-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys
@@ -251,9 +251,11 @@ extensions:
251
251
  extra_rdoc_files: []
252
252
  files:
253
253
  - ".cargo/config.toml"
254
+ - ".github/scripts/test_installed_gem.rb"
254
255
  - ".github/workflows/codeql.yml"
255
256
  - ".github/workflows/docs.yml"
256
257
  - ".github/workflows/links.yml"
258
+ - ".github/workflows/native-gems.yml"
257
259
  - ".github/workflows/rake.yml"
258
260
  - ".github/workflows/release.yml"
259
261
  - ".github/workflows/rust-ext.yml"
@@ -336,6 +338,17 @@ files:
336
338
  - TODO.parity-ee/18-mim-mapping.md
337
339
  - TODO.parity-ee/19-architecture.md
338
340
  - TODO.parity-ee/parity-matrix.md
341
+ - TODO.suma-improvements/00-overview.md
342
+ - TODO.suma-improvements/01-prebuilt-platform-gems.md
343
+ - TODO.suma-improvements/02-suma-e2e.md
344
+ - TODO.suma-improvements/03-item-graph.md
345
+ - TODO.suma-improvements/04-compiled-set-default.md
346
+ - TODO.suma-improvements/05-validation-in-docs.md
347
+ - TODO.suma-improvements/06-memory-remeasure.md
348
+ - TODO.suma-improvements/07-part28-xml.md
349
+ - TODO.suma-improvements/08-windows-gnu-toolchain.md
350
+ - TODO.suma-improvements/09-checker-tranche3.md
351
+ - TODO.suma-improvements/10-expressdoc.md
339
352
  - benchmark/srl_benchmark.rb
340
353
  - benchmark/srl_native_benchmark.rb
341
354
  - benchmark/srl_ruby_benchmark.rb
@@ -657,6 +670,7 @@ files:
657
670
  - lib/expressir/schema_manifest_entry.rb
658
671
  - lib/expressir/version.rb
659
672
  - lib/tasks/verify_remarks.rake
673
+ - rakelib/native.rake
660
674
  - rakelib/oracle.rake
661
675
  - rakelib/verify_remarks.rake
662
676
  homepage: https://github.com/lutaml/expressir