expressir 2.4.65 → 2.4.66

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: b99726a3a4b9e12cd568a109ae7b690649c13668449729b1b7fc528183efdc20
4
- data.tar.gz: 6b39aa8bffd2204669c98627a204ec0a973202d34ef32509a245deb71995f819
3
+ metadata.gz: 181fe5fd66409895fe025e9c6ac010ce82af48f8ba9f51a697ab8c8910e8834f
4
+ data.tar.gz: 987e054bdf5ee0c6f9f2571286f518babb40ee01aa96d1c30450cf42b27e7ec2
5
5
  SHA512:
6
- metadata.gz: 617c1e1f3271128cd2c7eac0dd8f26f2a9c49964af7fdb1f3fe06f63592d5174c42861044b310aeee10db4a67a9b9495349520e3f742f374a6cdaa07dbf364e5
7
- data.tar.gz: 8d423850c4c866f1c389c7f01eb9bc733e630e1f87ae8925a75fd14ffae4d6f56666e6d7f7b8577e89f85de7f1c76a43012aa57805e1e5a683fd1f790d5239d9
6
+ metadata.gz: b9a09e37cf413fadbe54a5194cbfb902eb8e8ef5c60ece71edd7f4c1cf782b3eb7e2ebee86d3b64b7593d2629c5a8fc23734d886c62f48ffc5e49e52f1a4851e
7
+ data.tar.gz: bb9027b0e4b5bc568e00edcdbfd4a5f2f91d176ac32ddf5d43122b23601a2929ecfaf163dd27a80f9133f07ec9c15d0d503b85c601d2e106c02ca9c7af201af7
data/.gitattributes ADDED
@@ -0,0 +1,4 @@
1
+ # Byte-exact fixture: the P21 artifact's checksums attest the
2
+ # producer's exact bytes, so checkout line-ending conversion must
3
+ # never touch it (windows CRLF checkouts would break the seal).
4
+ lib/expressir/p21/iso-10303-21.artifact.json -text
@@ -0,0 +1,30 @@
1
+ name: engine-compat
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ engine-compat:
15
+ runs-on: ubuntu-24.04
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: "3.4"
22
+
23
+ # The checker is stdlib-only (json/open3/rubygems) — no bundler.
24
+ - name: Verify ENGINE-COMPAT.json against the ext pin and rubygems
25
+ run: |
26
+ ruby -Ilib -rexpressir/tools/check_engine_compat -e '
27
+ failures = Expressir::Tools::CheckEngineCompat.failures(root: Dir.pwd)
28
+ abort(failures.join("\n")) unless failures.empty?
29
+ puts "engine-compat OK"
30
+ '
@@ -0,0 +1,49 @@
1
+ # The differential fuzz (issue #624, mirroring parsanol's fuzz.yml):
2
+ # date-seeded mutations over the corpus, parsed + attached + formatted
3
+ # on both lanes (native extension, pure-Ruby fallback) with byte-exact
4
+ # comparison. Drift is a release blocker.
5
+ name: differential-fuzz
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ on:
11
+ schedule:
12
+ - cron: "0 5 * * *"
13
+ workflow_dispatch:
14
+ inputs:
15
+ seed:
16
+ description: "Generator seed (default: today, YYYYMMDD)"
17
+ required: false
18
+ cases:
19
+ description: "Number of mutated cases"
20
+ required: false
21
+ default: "120"
22
+
23
+ jobs:
24
+ fuzz:
25
+ runs-on: ubuntu-latest
26
+ env:
27
+ FUZZ_SEED: ${{ github.event.inputs.seed }}
28
+ FUZZ_CASES: ${{ github.event.inputs.cases }}
29
+ FUZZ_EXPORT_DIR: ${{ github.workspace }}/fuzz-artifacts
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - uses: ruby/setup-ruby@v1
33
+ with:
34
+ ruby-version: "3.4"
35
+ bundler-cache: true
36
+ - name: Default the seed to today
37
+ run: |
38
+ if [ -z "$FUZZ_SEED" ]; then
39
+ echo "FUZZ_SEED=$(date +%Y%m%d)" >> "$GITHUB_ENV"
40
+ fi
41
+ - name: Differential fuzz across the lanes
42
+ run: |
43
+ bundle exec rake fuzz:run 2>&1 | tee fuzz.log
44
+ {
45
+ echo '### Differential fuzz'
46
+ echo '```'
47
+ grep 'fuzz stats' fuzz.log || true
48
+ echo '```'
49
+ } >> "$GITHUB_STEP_SUMMARY"
@@ -0,0 +1,38 @@
1
+ # The lane-parity gate (issue #623): the pure-Ruby fallback is a
2
+ # supported configuration, not a degraded one — the suite runs on
3
+ # both lanes and the fallback lane must stay green. Drift between
4
+ # the native lane and the fallback lane is a release blocker.
5
+ name: lane-parity
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ on:
11
+ push:
12
+ branches: [master, main]
13
+ pull_request:
14
+
15
+ jobs:
16
+ native-lane:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: "3.4"
23
+ bundler-cache: true
24
+ - name: Suite on the native lane
25
+ run: bundle exec rspec spec/expressir/express spec/expressir/p21 -f progress
26
+
27
+ fallback-lane:
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: ruby/setup-ruby@v1
32
+ with:
33
+ ruby-version: "3.4"
34
+ bundler-cache: true
35
+ - name: Suite on the pure-Ruby fallback lane
36
+ run: bundle exec rspec spec/expressir/express spec/expressir/p21 -f progress
37
+ env:
38
+ EXPRESSIR_DISABLE_NATIVE: "1"
@@ -0,0 +1,10 @@
1
+ {
2
+ "engine_commit": "7a3fb54319a47b96ec07b9324066f98099bb2f8a",
3
+ "rubygems": "2.4.65",
4
+ "ext": "v0.1.35",
5
+ "crates": null,
6
+ "npm": null,
7
+ "pypi": null,
8
+ "released_at": "2026-11-11T02:02:03Z",
9
+ "_comment": "expressir#618 — the engine-compat manifest, mirroring parsanol-rs#423. Maps the expressir gem train to the expressir-rs ext tag and its engine commit; the null fields fill as the crates/npm/pypi tiers land (expressir#619/#622/#621). tools/check_engine_compat.rb verifies the pairings in CI; every release train updates this file."
10
+ }
@@ -106,7 +106,7 @@ module Expressir
106
106
  # build so recursive build calls can read source/include_source via
107
107
  # `Builder.source` / `Builder.include_source`. The previous context
108
108
  # (if any) is restored on exit so nested builds are reentrant.
109
- def build_with_remarks(ast, source: nil, include_source: nil)
109
+ def build_with_remarks(ast, source: nil, include_source: nil, annotations: false)
110
110
  # Trigger BuilderRegistry autoload so all AST node type handlers
111
111
  # are registered before build() runs. The reference to
112
112
  # BuilderRegistry resolves the autoload defined in express.rb.
@@ -123,7 +123,7 @@ module Expressir
123
123
  # Only attach remarks if include_source is explicitly true
124
124
  # (nil means use default behavior - attach remarks)
125
125
  if source && result && include_source != false
126
- attacher = RemarkAttacher.new(source)
126
+ attacher = RemarkAttacher.new(source, annotations: annotations)
127
127
  attacher.attach(result)
128
128
  end
129
129
 
@@ -9,7 +9,11 @@ module Expressir
9
9
  # callers fall back to the pure-Ruby parser otherwise.
10
10
  #
11
11
  module Core
12
- NATIVE_AVAILABLE = begin
12
+ # EXPRESSIR_DISABLE_NATIVE=1 forces the pure-Ruby fallback lanes
13
+ # even when the extension is present: the no-native lane is a
14
+ # supported configuration (issue #623) — CI runs the suite on
15
+ # both lanes so fallback drift cannot land.
16
+ NATIVE_AVAILABLE = ENV["EXPRESSIR_DISABLE_NATIVE"] != "1" && begin
13
17
  require "expressir/expressir_core"
14
18
  true
15
19
  rescue LoadError
@@ -68,6 +72,8 @@ module Expressir
68
72
  # RemarkScanner::Remark's fields (position, end_position, line,
69
73
  # text, tag, format), straight from the Rust scanner.
70
74
  def self.scan_remarks(source, annotations: false)
75
+ raise NotImplementedError, "the native extension is unavailable" unless NATIVE_AVAILABLE
76
+
71
77
  ::Expressir::Core.scan_remarks(source, annotations)
72
78
  end
73
79
 
@@ -94,7 +94,8 @@ module Expressir
94
94
  end
95
95
 
96
96
  exp_file = ::Expressir::Express::Builder.build_with_remarks(ast, source: source,
97
- include_source: include_source)
97
+ include_source: include_source,
98
+ annotations: annotations)
98
99
 
99
100
  # issue #537 part 2: a remark-only fragment (no SCHEMA
100
101
  # wrapper — e.g. conversion artifacts) parses to an empty
@@ -0,0 +1 @@
1
+ {"bindings":null,"version":"1.0.0","grammar":"Iso1030321","shape":"parsanol-tree/v2","binding_version":1,"entries":{"exchange_structure":{"root":"exchange_structure","grammar":{"atoms":[{"Entity":{"atom":15}},{"Re":{"pattern":"[ ]"}},{"Re":{"pattern":"[\\x09]"}},{"Re":{"pattern":"[\\x0d]"}},{"Re":{"pattern":"[\\x0a]"}},{"Entity":{"atom":13}},{"Str":{"pattern":"/*"}},{"Str":{"pattern":"*/"}},{"Lookahead":{"atom":7,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[8,9]}},{"Repetition":{"atom":10,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"*/"}},{"Sequence":{"atoms":[6,11,12]}},{"Alternative":{"atoms":[1,2,3,4,5]}},{"Repetition":{"atom":14,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"ISO-10303-21;"}},{"Entity":{"atom":15}},{"Entity":{"atom":183}},{"Str":{"pattern":"HEADER;"}},{"Entity":{"atom":15}},{"Entity":{"atom":25}},{"Str":{"pattern":".."}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[22,23]}},{"Repetition":{"atom":24,"min":0,"max":null,"tag":"Repetition"}},{"Entity":{"atom":178}},{"Entity":{"atom":174}},{"Entity":{"atom":37}},{"Str":{"pattern":"!"}},{"Repetition":{"atom":29,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[_]"}},{"Alternative":{"atoms":[31,32,33,34]}},{"Repetition":{"atom":35,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[30,36]}},{"Named":{"name":"keyword","atom":28}},{"Entity":{"atom":15}},{"Entity":{"atom":172}},{"Str":{"pattern":"("}},{"Entity":{"atom":15}},{"Entity":{"atom":168}},{"Entity":{"atom":166}},{"Entity":{"atom":37}},{"Named":{"name":"keyword","atom":45}},{"Entity":{"atom":15}},{"Entity":{"atom":164}},{"Named":{"name":"parameter","atom":43}},{"Str":{"pattern":","}},{"Entity":{"atom":15}},{"Entity":{"atom":158}},{"Named":{"name":"typed_parameter","atom":44}},{"Entity":{"atom":62}},{"Named":{"name":"parameter","atom":52}},{"Sequence":{"atoms":[50,51,55]}},{"Repetition":{"atom":56,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[49,57]}},{"Repetition":{"atom":58,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":")"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[41,42,59,60,61]}},{"Named":{"name":"parameter_list","atom":54}},{"Entity":{"atom":68}},{"Str":{"pattern":"#"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":66,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[65,67]}},{"Named":{"name":"entity_instance_name","atom":64}},{"Entity":{"atom":82}},{"Str":{"pattern":"#"}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[_]"}},{"Alternative":{"atoms":[72,73,74]}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[_]"}},{"Alternative":{"atoms":[76,77,78,79]}},{"Repetition":{"atom":80,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[71,75,81]}},{"Named":{"name":"constant_reference","atom":70}},{"Entity":{"atom":92}},{"Str":{"pattern":"<"}},{"Str":{"pattern":">"}},{"Lookahead":{"atom":86,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[87,88]}},{"Repetition":{"atom":89,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":">"}},{"Sequence":{"atoms":[85,90,91]}},{"Named":{"name":"external_anchor","atom":84}},{"Entity":{"atom":117}},{"Re":{"pattern":"[+]"}},{"Re":{"pattern":"[\\-]"}},{"Alternative":{"atoms":[95,96]}},{"Repetition":{"atom":97,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":99,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":102,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[101,103]}},{"Repetition":{"atom":104,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[E]"}},{"Re":{"pattern":"[e]"}},{"Alternative":{"atoms":[106,107]}},{"Re":{"pattern":"[+]"}},{"Re":{"pattern":"[\\-]"}},{"Alternative":{"atoms":[109,110]}},{"Repetition":{"atom":111,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":113,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[108,112,114]}},{"Repetition":{"atom":115,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[98,100,105,116]}},{"Named":{"name":"number_literal","atom":94}},{"Entity":{"atom":128}},{"Str":{"pattern":"."}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[_]"}},{"Alternative":{"atoms":[121,122,123,124]}},{"Repetition":{"atom":125,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Sequence":{"atoms":[120,126,127]}},{"Named":{"name":"enumeration","atom":119}},{"Entity":{"atom":138}},{"Re":{"pattern":"[\"]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[\\x41-\\x46]"}},{"Re":{"pattern":"[\\x61-\\x66]"}},{"Alternative":{"atoms":[132,133,134]}},{"Repetition":{"atom":135,"min":0,"max":null,"tag":"Repetition"}},{"Re":{"pattern":"[\"]"}},{"Sequence":{"atoms":[131,136,137]}},{"Named":{"name":"binary_literal","atom":130}},{"Entity":{"atom":150}},{"Str":{"pattern":"'"}},{"Str":{"pattern":"'"}},{"Lookahead":{"atom":142,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[143,144]}},{"Str":{"pattern":"''"}},{"Alternative":{"atoms":[145,146]}},{"Repetition":{"atom":147,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"'"}},{"Sequence":{"atoms":[141,148,149]}},{"Named":{"name":"string_literal","atom":140}},{"Entity":{"atom":153}},{"Str":{"pattern":"$"}},{"Named":{"name":"unset","atom":152}},{"Entity":{"atom":156}},{"Str":{"pattern":"*"}},{"Named":{"name":"derived","atom":155}},{"Alternative":{"atoms":[53,63,69,83,93,118,129,139,151,154,157]}},{"Named":{"name":"parameter","atom":52}},{"Sequence":{"atoms":[50,51,159]}},{"Repetition":{"atom":160,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[49,161]}},{"Repetition":{"atom":162,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[41,42,163,60,61]}},{"Named":{"name":"parameter_list","atom":48}},{"Sequence":{"atoms":[46,47,165]}},{"Named":{"name":"typed_parameter","atom":44}},{"Alternative":{"atoms":[167,63,69,83,93,118,129,139,151,154,157]}},{"Named":{"name":"parameter","atom":43}},{"Sequence":{"atoms":[169,161]}},{"Repetition":{"atom":170,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[41,42,171,60,61]}},{"Named":{"name":"parameter_list","atom":40}},{"Sequence":{"atoms":[38,39,173]}},{"Named":{"name":"simple_record","atom":27}},{"Str":{"pattern":";"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[175,176,177]}},{"Repetition":{"atom":26,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"header_entity","atom":179}},{"Str":{"pattern":"ENDSEC;"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[19,20,21,180,181,182]}},{"Named":{"name":"header_section","atom":18}},{"Entity":{"atom":225}},{"Str":{"pattern":"ANCHOR;"}},{"Entity":{"atom":15}},{"Entity":{"atom":219}},{"Str":{"pattern":"<"}},{"Str":{"pattern":">"}},{"Lookahead":{"atom":190,"positive":false}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[_]"}},{"Re":{"pattern":"[.]"}},{"Re":{"pattern":"[/]"}},{"Re":{"pattern":"[:]"}},{"Re":{"pattern":"[?]"}},{"Re":{"pattern":"[=]"}},{"Re":{"pattern":"[&]"}},{"Re":{"pattern":"[%]"}},{"Re":{"pattern":"[\\-]"}},{"Alternative":{"atoms":[192,193,194,195,196,197,198,199,200,201,202,203]}},{"Repetition":{"atom":204,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"anchor_label","atom":205}},{"Str":{"pattern":">"}},{"Entity":{"atom":15}},{"Str":{"pattern":"="}},{"Entity":{"atom":15}},{"Entity":{"atom":68}},{"Named":{"name":"entity_instance_name","atom":211}},{"Entity":{"atom":153}},{"Named":{"name":"unset","atom":213}},{"Alternative":{"atoms":[212,214]}},{"Str":{"pattern":";"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[189,191,206,207,208,209,210,215,216,217]}},{"Named":{"name":"anchor_entry","atom":218}},{"Repetition":{"atom":188,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"anchor_entry","atom":220}},{"Str":{"pattern":"ENDSEC;"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[186,187,221,222,223]}},{"Named":{"name":"anchor_section","atom":224}},{"Repetition":{"atom":185,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":281}},{"Str":{"pattern":"REFERENCE;"}},{"Entity":{"atom":15}},{"Entity":{"atom":275}},{"Entity":{"atom":250}},{"Entity":{"atom":68}},{"Named":{"name":"entity_instance_name","atom":232}},{"Entity":{"atom":15}},{"Str":{"pattern":"="}},{"Entity":{"atom":15}},{"Entity":{"atom":245}},{"Str":{"pattern":"("}},{"Entity":{"atom":15}},{"Entity":{"atom":174}},{"Named":{"name":"simple_record","atom":240}},{"Repetition":{"atom":241,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":")"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[238,239,242,243,244]}},{"Named":{"name":"subsuper_record","atom":237}},{"Str":{"pattern":";"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[233,234,235,236,246,247,248]}},{"Named":{"name":"complex_entity_instance","atom":249}},{"Entity":{"atom":262}},{"Entity":{"atom":68}},{"Named":{"name":"entity_instance_name","atom":252}},{"Entity":{"atom":15}},{"Str":{"pattern":"="}},{"Entity":{"atom":15}},{"Entity":{"atom":174}},{"Named":{"name":"simple_record","atom":257}},{"Str":{"pattern":";"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[253,254,255,256,258,259,260]}},{"Named":{"name":"simple_entity_instance","atom":261}},{"Entity":{"atom":274}},{"Entity":{"atom":68}},{"Named":{"name":"entity_instance_name","atom":264}},{"Entity":{"atom":15}},{"Str":{"pattern":"="}},{"Entity":{"atom":15}},{"Entity":{"atom":92}},{"Named":{"name":"external_anchor","atom":269}},{"Str":{"pattern":";"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[265,266,267,268,270,271,272]}},{"Named":{"name":"external_reference_instance","atom":273}},{"Alternative":{"atoms":[231,251,263]}},{"Repetition":{"atom":230,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"instance_list","atom":276}},{"Str":{"pattern":"ENDSEC;"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[228,229,277,278,279]}},{"Named":{"name":"reference_section","atom":280}},{"Repetition":{"atom":227,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":297}},{"Str":{"pattern":"DATA"}},{"Entity":{"atom":15}},{"Entity":{"atom":172}},{"Named":{"name":"section_tag","atom":286}},{"Repetition":{"atom":287,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":";"}},{"Entity":{"atom":15}},{"Entity":{"atom":275}},{"Repetition":{"atom":291,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"instance_list","atom":292}},{"Str":{"pattern":"ENDSEC;"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[284,285,288,289,290,293,294,295]}},{"Named":{"name":"data_section","atom":296}},{"Repetition":{"atom":283,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"data_section","atom":298}},{"Str":{"pattern":"END-ISO-10303-21;"}},{"Entity":{"atom":15}},{"Sequence":{"atoms":[0,16,17,184,226,282,299,300,301]}}],"root":302},"bindings":[]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule parameter: branches 1 and 6 are order-dependent (shared first bytes; ordered choice is decisive)","rule parameter: branches 3 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule entity_instance: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule entity_instance: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule entity_instance: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"exchange_structure","render":{},"derive":{},"tests":[{"entry":"exchange_structure","kind":"accept","input":"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION(('a'),'b');\nFILE_NAME('n');\nFILE_SCHEMA(('S'));\nENDSEC;\nDATA;\n#1=E('v');\n#2=(A(1)B(2));\nENDSEC;\nEND-ISO-10303-21;\n","expect":{}},{"entry":"exchange_structure","kind":"accept","input":"ISO-10303-21;\nHEADER;\nFILE_NAME('n');\nENDSEC;\nANCHOR;\n<lbl>=#1;\nENDSEC;\nDATA;\n#1=E('v');\nENDSEC;\nEND-ISO-10303-21;\n","expect":{}},{"entry":"exchange_structure","kind":"reject","input":"ISO-10303-21;\nHEADER;\nFILE_NAME('n');\nENDSEC;\nDATA;\n#1=E('v');\n","expect":{}}],"docs":{"ws":"Whitespace and /* */ comments (clause 5.2).","string_literal":"ISO 10303-21: doubled quotes are escaped quotes.","binary_literal":"The double-quote terminal is %x22: a bare \"'\" breaks the lexer's\nquote pairing.","signature_lines":"ISO 10303-21:2016 clause 14: two signature lines open the header\nof a signed exchange structure.","anchor_entry":"ISO 10303-21:2016 clause 9: the anchor section binds labels to\nlocal instances (a promissory anchor binds the unset value).","reference_section":"ISO 10303-21:2016 clause 10: the reference section binds a local\ninstance name to an external anchor.","exchange_structure":"The section rules carry their own capture (mirroring the DSL),\nso the call sites stay bare — an extra `as` here would\ndouble-wrap the subtree."},"source":"## ISO 10303-21 exchange structure (expressir#625).\n##\n## Single source of truth for P21 parsing across engines: the rust\n## core loads the compiled artifact (assets/p21/iso-10303-21.artifact.json),\n## and the ruby `Expressir::P21` pipeline consumes the same parse tree\n## in place of its DSL grammar (which this file mirrors 1:1 — the\n## capture names are the Builder's contract).\n##\n## Reference: ISO 10303-21 (the exchange structure; clauses 8-14,\n## annexes E/H/I/J). Supported subset: the header FILE_* entities,\n## ANCHOR and REFERENCE sections, named data sections, and\n## simple/complex/external-reference instances with lists, typed\n## parameters, references, enumerations, binaries, strings, unset ($)\n## and derived (*).\n\ngrammar Iso1030321 version \"1.0.0\" {\n\n ## Whitespace and /* */ comments (clause 5.2).\n ws = *( %x20 / %x09 / %x0D / %x0A / comment )\n\n comment = {\n \"/*\" *( !\"*/\" ANY ) \"*/\"\n }\n\n keyword = {\n [ \"!\" ] 1*( %x41-5A / %x61-7A / %x30-39 / %x5F )\n }\n\n entity_instance_name = {\n \"#\" 1*%x30-39\n }\n\n constant_reference = {\n \"#\" ( %x41-5A / %x61-7A / %x5F ) *( %x41-5A / %x61-7A / %x30-39 / %x5F )\n }\n\n external_anchor = {\n \"<\" *( !\">\" ANY ) \">\"\n }\n\n ## ISO 10303-21: doubled quotes are escaped quotes.\n string_literal = {\n \"'\" *( ( !\"'\" ANY ) / \"''\" ) \"'\"\n }\n\n ## The double-quote terminal is %x22: a bare \"'\" breaks the lexer's\n ## quote pairing.\n binary_literal = {\n %x22 *( %x30-39 / %x41-46 / %x61-66 ) %x22\n }\n\n enumeration = {\n \".\" 1*( %x41-5A / %x61-7A / %x30-39 / %x5F ) \".\"\n }\n\n unset = {\n \"$\"\n }\n\n derived = {\n \"*\"\n }\n\n number_literal = {\n [ ( %x2B / %x2D ) ] 1*%x30-39\n [ \".\" 1*%x30-39 ]\n [ ( %x45 / %x65 ) [ ( %x2B / %x2D ) ] 1*%x30-39 ]\n }\n\n parameter = {\n ( typed_parameter as typed_parameter ) /\n ( parameter_list as parameter_list ) /\n ( entity_instance_name as entity_instance_name ) /\n ( constant_reference as constant_reference ) /\n ( external_anchor as external_anchor ) /\n ( number_literal as number_literal ) /\n ( enumeration as enumeration ) /\n ( binary_literal as binary_literal ) /\n ( string_literal as string_literal ) /\n ( unset as unset ) /\n ( derived as derived )\n }\n\n parameter_list = {\n \"(\" ws\n [ parameter as parameter\n *( \",\" ws parameter as parameter )\n ]\n \")\" ws\n }\n\n typed_parameter = {\n keyword as keyword ws parameter_list as parameter_list\n }\n\n simple_record = {\n keyword as keyword ws parameter_list as parameter_list\n }\n\n subsuper_record = {\n \"(\" ws 1*( simple_record as simple_record ) \")\" ws\n }\n\n simple_entity_instance = {\n ( entity_instance_name as entity_instance_name ws\n \"=\" ws simple_record as simple_record\n \";\" ws ) as simple_entity_instance\n }\n\n complex_entity_instance = {\n ( entity_instance_name as entity_instance_name ws\n \"=\" ws subsuper_record as subsuper_record\n \";\" ws ) as complex_entity_instance\n }\n\n external_reference_instance = {\n ( entity_instance_name as entity_instance_name ws\n \"=\" ws external_anchor as external_anchor\n \";\" ws ) as external_reference_instance\n }\n\n entity_instance = {\n complex_entity_instance / simple_entity_instance / external_reference_instance\n }\n\n header_entity = {\n simple_record as simple_record \";\" ws\n }\n\n ## ISO 10303-21:2016 clause 14: two signature lines open the header\n ## of a signed exchange structure.\n signature_lines = *( \"..\" ws )\n\n header_section = {\n \"HEADER;\" ws signature_lines\n 1*( header_entity ) as header_entity\n \"ENDSEC;\" ws\n }\n\n data_section = {\n ( \"DATA\" ws\n [ parameter_list as section_tag ] \";\" ws\n *( entity_instance ) as instance_list\n \"ENDSEC;\" ws ) as data_section\n }\n\n ## ISO 10303-21:2016 clause 9: the anchor section binds labels to\n ## local instances (a promissory anchor binds the unset value).\n anchor_entry = {\n ( \"<\" !\">\" *( %x41-5A / %x61-7A / %x30-39 / %x5F / %x2E / %x2F / %x3A / %x3F / %x3D / %x26 / %x25 / %x2D ) as anchor_label\n \">\" ws \"=\" ws\n ( ( entity_instance_name as entity_instance_name ) / ( unset as unset ) )\n \";\" ws ) as anchor_entry\n }\n\n anchor_section = {\n ( \"ANCHOR;\" ws *( anchor_entry ) as anchor_entry \"ENDSEC;\" ws ) as anchor_section\n }\n\n ## ISO 10303-21:2016 clause 10: the reference section binds a local\n ## instance name to an external anchor.\n reference_section = {\n ( \"REFERENCE;\" ws *( entity_instance ) as instance_list \"ENDSEC;\" ws ) as reference_section\n }\n\n entry exchange_structure: exchange_structure\n\n ## The section rules carry their own capture (mirroring the DSL),\n ## so the call sites stay bare — an extra `as` here would\n ## double-wrap the subtree.\n exchange_structure = {\n ws \"ISO-10303-21;\" ws\n header_section as header_section\n [ anchor_section ]\n [ reference_section ]\n 1*( data_section ) as data_section\n \"END-ISO-10303-21;\" ws\n }\n\n test exchange_structure {\n accept \"ISO-10303-21;\\nHEADER;\\nFILE_DESCRIPTION(('a'),'b');\\nFILE_NAME('n');\\nFILE_SCHEMA(('S'));\\nENDSEC;\\nDATA;\\n#1=E('v');\\n#2=(A(1)B(2));\\nENDSEC;\\nEND-ISO-10303-21;\\n\"\n accept \"ISO-10303-21;\\nHEADER;\\nFILE_NAME('n');\\nENDSEC;\\nANCHOR;\\n<lbl>=#1;\\nENDSEC;\\nDATA;\\n#1=E('v');\\nENDSEC;\\nEND-ISO-10303-21;\\n\"\n reject \"ISO-10303-21;\\nHEADER;\\nFILE_NAME('n');\\nENDSEC;\\nDATA;\\n#1=E('v');\\n\"\n }\n}\n","source_hash":"70b966720a8c5918e203d607f35cb699068ffb95ab14e7323b3d40b76db3dc01","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.130","edition":2,"semantics_hash":"sha256:98a96f64d8b9013f71ec9dcb852a2739fdc77cd1d073dcb4db0c95b18a8e1063","checksum":"sha256:8c748f7a0d992894441f995cd5f9e2db821aab0ed5a6f29ef40ac9fa89b28245"}
@@ -4,13 +4,39 @@ module Expressir
4
4
  module P21
5
5
  # Facade: parse an ISO 10303-21 exchange structure into a
6
6
  # {Population} (TODO.eengine/06 step 1).
7
+ #
8
+ # The grammar ships as a compiled PARG artifact
9
+ # (+iso-10303-21.artifact.json+, the checksummed single source of
10
+ # truth shared with expressir-rs; issue #625): parsing runs through
11
+ # the artifact when it loads, with the DSL grammar as the fallback
12
+ # lane. The parity spec pins the two lanes byte-identically on the
13
+ # annex corpus.
7
14
  module Parser
8
15
  module_function
9
16
 
10
17
  def parse(source)
11
- tree = Grammar.new.parse(source)
18
+ tree = artifact_tree(source) || Grammar.new.parse(source)
12
19
  Builder.build(tree)
13
20
  end
21
+
22
+ def artifact_tree(source)
23
+ artifact.parse("exchange_structure", source)
24
+ rescue Parsanol::PARG::ArtifactError
25
+ # The artifact itself is unusable (shape/edition/binding drift):
26
+ # fall back to the DSL grammar lane. A genuine ParseFailed from
27
+ # a valid artifact propagates — malformed input fails loudly on
28
+ # both lanes.
29
+ nil
30
+ end
31
+
32
+ def artifact
33
+ @artifact ||= begin
34
+ require "parsanol"
35
+ Parsanol::PARG::Artifact.load(
36
+ Expressir.root_path.join("lib/expressir/p21/iso-10303-21.artifact.json").to_s,
37
+ )
38
+ end
39
+ end
14
40
  end
15
41
  end
16
42
  end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open3"
5
+ require "rubygems/version"
6
+
7
+ module Expressir
8
+ module Tools
9
+ # The engine-compat manifest checker (expressir#618, mirroring
10
+ # parsanol-rs#423's tools/check_engine_compat.py): ENGINE-COMPAT.json
11
+ # maps the gem train to the expressir-rs ext tag and its engine
12
+ # commit; this verifies the pairings so the train cannot ship with a
13
+ # stale manifest.
14
+ #
15
+ # Checks:
16
+ # 1. The ext pin in ext/expressir_core/Cargo.toml names
17
+ # manifest["ext"].
18
+ # 2. The tag exists on origin and points at manifest["engine_commit"].
19
+ # 3. rubygems.org serves a version >= manifest["rubygems"] (>= because
20
+ # CI runs on the bump commit before the tag publish catches up).
21
+ module CheckEngineCompat
22
+ MANIFEST = "ENGINE-COMPAT.json"
23
+ PIN_RE = /expressir-rs = \{ git = "([^"]+)", tag = "([^"]+)" \}/
24
+
25
+ module_function
26
+
27
+ def failures(root: Dir.pwd)
28
+ manifest = JSON.parse(File.read(File.join(root, MANIFEST)))
29
+ failures = []
30
+
31
+ failures.concat(pin_failures(root, manifest))
32
+ failures.concat(tag_failures(root, manifest))
33
+ failures.concat(registry_failures(manifest)) unless manifest["rubygems"].nil?
34
+ failures
35
+ rescue StandardError => e
36
+ ["#{MANIFEST}: #{e.message}"]
37
+ end
38
+
39
+ # [git url, tag] straight from the ext pin — the tag lives on the
40
+ # expressir-rs repository, not on expressir's origin.
41
+ def ext_pin(root)
42
+ m = File.read(File.join(root, "ext/expressir_core/Cargo.toml")).match(PIN_RE)
43
+ return unless m
44
+
45
+ # ssh and https forms both resolve via ls-remote; normalize to https.
46
+ [m[1].sub(%r{\Agit@github\.com:}, "https://github.com/"), m[2]]
47
+ end
48
+
49
+ def pin_failures(root, manifest)
50
+ pin = ext_pin(root)
51
+ return ["ext pin missing from ext/expressir_core/Cargo.toml"] unless pin
52
+ return [] if pin[1] == manifest["ext"]
53
+
54
+ ["ext pin #{pin[1].inspect} != manifest ext #{manifest['ext'].inspect}"]
55
+ end
56
+
57
+ def tag_failures(root, manifest)
58
+ pin = ext_pin(root)
59
+ return ["ext pin missing from ext/expressir_core/Cargo.toml"] unless pin
60
+
61
+ url, tag = pin
62
+ out, status = Open3.capture2("git", "ls-remote", url, "refs/tags/#{tag}")
63
+ return ["git ls-remote failed for #{url}"] unless status.success?
64
+
65
+ sha = out[/^([0-9a-f]{40})/, 1]
66
+ return ["tag #{tag.inspect} not found on #{url}"] unless sha
67
+
68
+ # annotated (release) tags carry a ^{} peeled entry; either line
69
+ # matching the engine commit passes.
70
+ return [] if out.include?(manifest["engine_commit"])
71
+
72
+ ["tag #{tag.inspect} points at #{sha[0, 7]}, " \
73
+ "not engine_commit #{manifest['engine_commit'][0, 7]}"]
74
+ end
75
+
76
+ def registry_failures(manifest)
77
+ url = "https://rubygems.org/api/v1/versions/expressir/latest.json"
78
+ out, status = Open3.capture2("curl", "-sf", "--retry", "3", url)
79
+ return ["rubygems query failed"] unless status.success?
80
+
81
+ served = JSON.parse(out)["version"]
82
+ return [] if Gem::Version.new(served) >= Gem::Version.new(manifest["rubygems"])
83
+
84
+ ["rubygems serves #{served.inspect}, manifest wants >= " \
85
+ "#{manifest['rubygems'].inspect}"]
86
+ end
87
+ end
88
+ end
89
+ 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.65".freeze
6
+ VERSION = "2.4.66".freeze
7
7
  end
8
8
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The contract matrix (issue #624): the canonical lane seals one
4
+ # artifact per wire shape; every lane re-derives the shape from the
5
+ # same source and must be byte-identical. Drift here is a release
6
+ # blocker — canonicalization changes are invisible to parse-level
7
+ # differentials (the parsanol rs#397 lesson).
8
+ #
9
+ # No file-level constants (rake double-loads rakelib); helpers stay
10
+ # inside the task body.
11
+ namespace :contract_matrix do
12
+ require "json"
13
+ require "digest"
14
+ require "tmpdir"
15
+
16
+ desc "Seal the contract-matrix artifacts from the canonical lane"
17
+ task :seal do
18
+ require "expressir"
19
+ matrix = "spec/fixtures/contract-matrix"
20
+ source_path = File.join(matrix, "source.exp")
21
+
22
+ seal = lambda do |shape, text|
23
+ File.write(File.join(matrix, shape), text)
24
+ Digest::SHA256.hexdigest(text)
25
+ end
26
+
27
+ Dir.mktmpdir do |dir|
28
+ file = File.join(dir, "source.exp")
29
+ FileUtils.cp(source_path, file)
30
+
31
+ source = File.read(file)
32
+ wire = Expressir::Express::Core.parse_to_model_hash(source, "source.exp")
33
+ attached_model = Expressir::Express::Core.parse_to_model_attached_with_wire(
34
+ source, "source.exp"
35
+ )
36
+ attached = JSON.parse(attached_model.core_wire)
37
+ formatted = Expressir::Express::Core.format_model_hash(attached)
38
+ stripped = Expressir::Express::Core.format_model_hash(attached, no_remarks: true)
39
+
40
+ checksums = {
41
+ "model-hash wire" => seal.call("wire.json", "#{JSON.pretty_generate(wire)}\n"),
42
+ "attached wire" => seal.call("attached.json", "#{JSON.pretty_generate(attached)}\n"),
43
+ "formatted text" => seal.call("formatted.exp", formatted),
44
+ "stripped text" => seal.call("stripped.exp", stripped),
45
+ }
46
+
47
+ manifest = {
48
+ "contract" => "what the canonical lane seals, every lane re-derives byte-identically",
49
+ "source" => "source.exp",
50
+ "canonical_lane" => "Expressir::Express::Core (the native extension)",
51
+ "artifacts" => checksums.transform_values { |digest| "sha256:#{digest}" },
52
+ "shapes" => %w[
53
+ model-hash wire attached wire formatted text stripped text
54
+ ],
55
+ }
56
+ File.write(File.join(matrix, "manifest.yml"), YAML.dump(manifest))
57
+ puts "sealed #{checksums.size} artifacts + manifest.yml"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+
5
+ namespace :lint do
6
+ desc "Verify ENGINE-COMPAT.json against the ext pin and rubygems (#618)"
7
+ task :engine_compat do
8
+ require_relative "../lib/expressir/tools/check_engine_compat"
9
+ failures = Expressir::Tools::CheckEngineCompat.failures(root: Dir.pwd)
10
+ if failures.empty?
11
+ puts "lint:engine_compat OK"
12
+ else
13
+ warn failures.join("\n")
14
+ raise "lint:engine_compat found #{failures.length} problem(s)"
15
+ end
16
+ end
17
+ end
data/rakelib/fuzz.rake ADDED
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The differential fuzz (issue #624, mirroring parsanol's fuzz.yml):
4
+ # date-seeded mutations over the corpus, each source parsed + attached
5
+ # + formatted on BOTH lanes — the native extension and the pure-Ruby
6
+ # fallback (use_core: false, use_native: false) — with byte-exact
7
+ # comparison. Fallback drift cannot land.
8
+ #
9
+ # rake fuzz # today's seed, 40 cases
10
+ # rake fuzz SEED=20261107 CASES=200
11
+ #
12
+ # No file-level constants (rake double-loads rakelib); helpers stay
13
+ # inside the task body.
14
+ namespace :fuzz do
15
+ require "tmpdir"
16
+ require "fileutils"
17
+
18
+ desc "Date-seeded differential fuzz across the native and fallback lanes"
19
+ task :run do
20
+ require "expressir"
21
+
22
+ mutators = %i[
23
+ squeeze_whitespace reword_remark rename_identifier
24
+ splice_comment duplicate_line
25
+ ].freeze
26
+
27
+ corpus_sources = lambda do
28
+ glob = ENV.fetch("FUZZ_CORPUS", "spec/fixtures/contract-matrix/source.exp")
29
+ files = Dir[glob]
30
+ files.empty? ? ["spec/fixtures/contract-matrix/source.exp"] : files
31
+ end
32
+
33
+ mutate = lambda do |source, rng|
34
+ lines = source.lines
35
+ rng.rand(1..3).times do
36
+ case mutators[rng.rand(mutators.size)]
37
+ when :squeeze_whitespace then lines.each { |l| l.gsub!(/[ \t]+/, " ") }
38
+ when :reword_remark
39
+ lines.each_with_index do |l, i|
40
+ lines[i] = l.sub(/\(\*"(.*?)"\s*(.*?)\s*\*\)/) do
41
+ "(\"#{Regexp.last_match(1)}\" reworded fuzz note #{i} *)"
42
+ end
43
+ end
44
+ when :rename_identifier
45
+ lines.map! { |l| l.sub(/\b(part|compatible)\b/) { "#{Regexp.last_match(0)}_#{rng.rand(100)}" } }
46
+ when :splice_comment then lines.insert(rng.rand(lines.size + 1), "(* a spliced fuzz comment *)\n")
47
+ when :duplicate_line then lines.insert(rng.rand(lines.size + 1), lines[rng.rand(lines.size)].to_s)
48
+ end
49
+ end
50
+ lines.join
51
+ end
52
+
53
+ lane_outputs = lambda do |source, lane_options|
54
+ Dir.mktmpdir do |dir|
55
+ file = File.join(dir, "fuzz.exp")
56
+ File.write(file, source)
57
+ model = Expressir::Express::Parser.from_file(file, **lane_options)
58
+ {
59
+ "format" => Expressir::Express::Formatter.new.format(model),
60
+ "stripped" => Expressir::Express::Formatter.new(no_remarks: true).format(model),
61
+ }
62
+ end
63
+ end
64
+
65
+ seed = Integer(ENV.fetch("FUZZ_SEED", Time.now.strftime("%Y%m%d")))
66
+ cases = Integer(ENV.fetch("FUZZ_CASES", 40))
67
+ rng = Random.new(seed)
68
+ sources = corpus_sources.call
69
+ export = ENV.fetch("FUZZ_EXPORT_DIR", "tmp/fuzz")
70
+
71
+ failures = 0
72
+ compared = 0
73
+
74
+ cases.times do |i|
75
+ base = sources[rng.rand(sources.size)]
76
+ mutated = mutate.call(File.read(base), rng)
77
+ begin
78
+ native_outputs = lane_outputs.call(mutated, {})
79
+ fallback_outputs = lane_outputs.call(mutated, { use_core: false, use_native: false })
80
+ compared += 1
81
+ native_outputs.each do |key, native_text|
82
+ next if native_text == fallback_outputs[key]
83
+
84
+ failures += 1
85
+ FileUtils.mkdir_p(export)
86
+ File.write(File.join(export, "case-#{i}-native.#{key}"), native_text)
87
+ File.write(File.join(export, "case-#{i}-fallback.#{key}"), fallback_outputs[key])
88
+ puts "DIVERGENT case #{i} (#{File.basename(base)}, seed #{seed}): #{key} — artifacts in #{export}"
89
+ end
90
+ rescue Expressir::Express::Error::SchemaParseFailure, Parsanol::ParseFailed
91
+ # A mutation neither lane accepts is fine; a lane DIVERGENCE
92
+ # (one parses, the other refuses) is what this must catch.
93
+ end
94
+ end
95
+
96
+ puts "fuzz stats: seed=#{seed} cases=#{cases} compared=#{compared} divergent=#{failures}"
97
+ abort "differential fuzz found #{failures} divergences" if failures.positive?
98
+ end
99
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expressir
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.65
4
+ version: 2.4.66
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -232,11 +232,15 @@ extensions:
232
232
  extra_rdoc_files: []
233
233
  files:
234
234
  - ".cargo/config.toml"
235
+ - ".gitattributes"
235
236
  - ".github/scripts/test_installed_gem.rb"
236
237
  - ".github/workflows/codeql.yml"
237
238
  - ".github/workflows/conformance.yml"
238
239
  - ".github/workflows/docs.yml"
240
+ - ".github/workflows/engine-compat.yml"
241
+ - ".github/workflows/fuzz.yml"
239
242
  - ".github/workflows/gemspec-floor.yml"
243
+ - ".github/workflows/lane-parity.yml"
240
244
  - ".github/workflows/links.yml"
241
245
  - ".github/workflows/native-gems.yml"
242
246
  - ".github/workflows/performance-release.yml"
@@ -253,6 +257,7 @@ files:
253
257
  - ".rubocop_todo.yml"
254
258
  - CHANGELOG.md
255
259
  - Cargo.toml
260
+ - ENGINE-COMPAT.json
256
261
  - Gemfile
257
262
  - README.adoc
258
263
  - Rakefile
@@ -765,6 +770,7 @@ files:
765
770
  - lib/expressir/p21/dataset_manifest.rb
766
771
  - lib/expressir/p21/diff.rb
767
772
  - lib/expressir/p21/grammar.rb
773
+ - lib/expressir/p21/iso-10303-21.artifact.json
768
774
  - lib/expressir/p21/json.rb
769
775
  - lib/expressir/p21/parser.rb
770
776
  - lib/expressir/p21/population.rb
@@ -789,12 +795,16 @@ files:
789
795
  - lib/expressir/schema_manifest.rb
790
796
  - lib/expressir/schema_manifest_entry.rb
791
797
  - lib/expressir/scoreboard.rb
798
+ - lib/expressir/tools/check_engine_compat.rb
792
799
  - lib/expressir/version.rb
793
800
  - lib/tasks/conformance.rake
794
801
  - lib/tasks/instance.rake
795
802
  - lib/tasks/reference.rake
796
803
  - lib/tasks/scoreboard.rake
797
804
  - lib/tasks/verify_remarks.rake
805
+ - rakelib/contract_matrix.rake
806
+ - rakelib/engine_compat.rake
807
+ - rakelib/fuzz.rake
798
808
  - rakelib/native.rake
799
809
  - rakelib/oracle.rake
800
810
  - rakelib/verify_remarks.rake