expressir 2.4.14 → 2.4.16

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: ad3043a3dd1ac401e841249e3e081a7b3b6b3c3da99f6a9dbc136a127619ba49
4
- data.tar.gz: '085e25b6e9b97d53fc5207aa651b37b080557626c6971b60029b98749ff4db82'
3
+ metadata.gz: ca7a970fe2d30edcb570639ddd4c178b2edf7bdee839844df7946fdb1e842d1b
4
+ data.tar.gz: a5a5b1c703de8027892d77792fbc957155101b8875fe1aa080feefec9d278ac7
5
5
  SHA512:
6
- metadata.gz: e92cca737def4e8432f30b98162262f83df7d81c719c7a1dd916173f65ff788c1145843e46131bfe7da5ef97e76c00a155215f7c4893befe7748601711438d93
7
- data.tar.gz: 88d3f1008222838323a843a1b9a7117bf1b09465fa453de4bfd21868137b16439a680165e527555099ae67884cb8a77c8a2068e92b113f34408444c314482583
6
+ metadata.gz: '0950891e9ddb51792aaf69c1c82af9224ae6126b5ff77dd481e48105b2e39215dd23a1d1c3e30d060be4ef2988132b4aec47cf306903dc9081a05e73c5e6f42d'
7
+ data.tar.gz: 976131ab90fedb6e659129ca1f0aa6a3e8a8d5df40cb00950dc8b6b237789dcc4d896df03c35532e3afac6483e2f3674983bb0bc092adbf0534ed355d155a507
@@ -0,0 +1,47 @@
1
+ # 18 — EXPRESS MIM mapping parser (expressir#88)
2
+
3
+ ## Goal
4
+
5
+ Parse and validate MIM "reference path" mappings — the application ↔
6
+ interpretation mapping every STEP module carries — into a typed model,
7
+ with the mapping paths validated against the ARM/MIM schemas.
8
+
9
+ ## Sources
10
+
11
+ - This checkout's canonical form: `schemas/modules/*/mapping.yaml` —
12
+ structured ae/aimelt/aa records whose values use the
13
+ `<<express:SCHEMA.ITEM,ITEM>>` link markup (the same markup
14
+ `Suma::LinkValidation` validates today). Generated upstream from
15
+ module.xml.
16
+ - Upstream SMRL module.xml: `mapping`/`ae`/`aimelt` elements with
17
+ `reference_path` bodies — the ISO 10303-1x mapping methodology
18
+ grammar: entity chains `A -> B -> C`, qualifiers `[attr => val]`,
19
+ `=>` derived references, `->>` subtype traversal, `<>` select
20
+ member. Port the grammar from the Part 1x mapping annex text
21
+ (detached docs) + existing module corpus as the oracle.
22
+ - eeng has no counterpart — this is an expressir-first capability.
23
+
24
+ ## Design
25
+
26
+ 1. `Expressir::Model::Mapping` — MappingDocument →
27
+ ApplicationElements (entity, aimelt, attributes) and
28
+ ReferencePath nodes (path-expression AST: steps, qualifiers,
29
+ subtype traversals).
30
+ 2. `mapping.yaml` reader (lutaml-model typed) — yaml → MappingDocument.
31
+ 3. Reference-path expression parser — parsanol grammar over the path
32
+ syntax; AST reuses Model::References where shapes coincide.
33
+ 4. Validation: every path resolves against the resolved ARM+MIM
34
+ repository (rides SchemaIndex / ItemGraph); unknown step = error
35
+ note (Checker id `check-mapping-path`).
36
+ 5. CLI: `expressir mapping validate DIR|FILE` over module dirs.
37
+
38
+ ## Acceptance
39
+
40
+ - All module mapping.yaml files in the SMRL parse; path validation
41
+ reports zero unknown steps (or a classified, explained residue).
42
+ - Round-trip: MappingDocument → yaml byte-stable.
43
+
44
+ ## Test cases
45
+
46
+ - module corpus (603 modules); hand fixtures for each path grammar
47
+ production; invalid-path fixtures.
@@ -1,4 +1,5 @@
1
1
  require "find"
2
+ require "thor"
2
3
 
3
4
  module Expressir
4
5
  module Commands
@@ -42,8 +43,9 @@ module Expressir
42
43
  end
43
44
  end
44
45
  unless missing.empty?
45
- warn "expressir: schema(s) not found: #{missing.uniq.join(', ')}" \
46
- "#{resolver_hint(manifest, stepmod)}"
46
+ raise Thor::Error,
47
+ "expressir: schema(s) not found: #{missing.uniq.join(', ')}" \
48
+ "#{resolver_hint(manifest, stepmod)}"
47
49
  end
48
50
  seen.values.compact.uniq
49
51
  end
@@ -59,15 +61,18 @@ module Expressir
59
61
  end
60
62
  end
61
63
 
62
- # Schema-name → path index from the ELF schema manifest.
64
+ # Schema-name → path index from the ELF schema manifest. An
65
+ # explicitly given manifest that cannot be loaded is fatal (#455):
66
+ # when the root has no interface dependencies the lookup would
67
+ # never consult the index, so a typo'd --manifest would exit 0.
63
68
  def manifest_index(manifest_path)
64
69
  manifest = Expressir::SchemaManifest.from_file(manifest_path)
65
70
  manifest.schemas.to_h do |entry|
66
71
  [entry.id.downcase, entry.path]
67
72
  end
68
73
  rescue StandardError => e
69
- warn "expressir: could not load schema manifest #{manifest_path}: #{e.message}"
70
- nil
74
+ raise Thor::Error,
75
+ "expressir: could not load schema manifest #{manifest_path}: #{e.message}"
71
76
  end
72
77
 
73
78
  # Schema-name → path index over a STEPmod checkout root (the
@@ -90,14 +95,43 @@ module Expressir
90
95
  return manifest_index(manifest) if manifest
91
96
  return stepmod_index(stepmod) if stepmod
92
97
 
93
- same_dir_index(root_path)
98
+ # Default: the file's own directory plus, when the root sits
99
+ # inside a STEPmod checkout, its enclosing `schemas/` tree —
100
+ # the layout wg12-step modules live in. Same-directory
101
+ # declarations win over tree hits for the same schema name:
102
+ # the file next to the root is the intentional one (#455).
103
+ index = same_dir_index(root_path)
104
+ ancestor = ancestor_schemas_dir(root_path)
105
+ if ancestor
106
+ tree = stepmod_index(File.dirname(ancestor))
107
+ tree.update(index)
108
+ index = tree
109
+ end
110
+ index
111
+ end
112
+
113
+ # Nearest ancestor directory NAMED `schemas`, or nil.
114
+ def ancestor_schemas_dir(root_path)
115
+ dir = File.dirname(File.expand_path(root_path))
116
+ until dir == "/"
117
+ return dir if File.basename(dir) == "schemas"
118
+
119
+ dir = File.dirname(dir)
120
+ end
121
+ nil
94
122
  end
95
123
 
96
124
  def same_dir_index(root_path)
97
125
  dir = File.dirname(File.expand_path(root_path))
98
- Dir.glob(File.join(dir, "*.exp")).to_h do |p|
99
- [File.basename(p, ".exp").downcase, p]
126
+ h = {}
127
+ Dir.glob(File.join(dir, "*.exp")).each do |path|
128
+ # index by DECLARED schema name first (a file named arm.exp
129
+ # may declare schema m_arm), basename as fallback
130
+ declared = File.read(path)[/\bSCHEMA\s+(\w+)/i, 1]
131
+ h[declared.downcase] = path if declared
132
+ h[File.basename(path, ".exp").downcase] ||= path
100
133
  end
134
+ h
101
135
  end
102
136
 
103
137
  # Interface names declared by +source+. Remark text is stripped
@@ -290,7 +290,7 @@ module Expressir
290
290
  end
291
291
  end
292
292
 
293
- def each_reference(node, &)
293
+ def each_reference(node, &block)
294
294
  case node
295
295
  when Model::References::SimpleReference then yield node
296
296
  when Model::ModelElement
@@ -299,8 +299,8 @@ module Expressir
299
299
 
300
300
  value = node.public_send(attr)
301
301
  case value
302
- when Array then value.each { |item| each_reference(item, &) }
303
- when Model::ModelElement then each_reference(value, &)
302
+ when Array then value.each { |item| each_reference(item, &block) }
303
+ when Model::ModelElement then each_reference(value, &block)
304
304
  end
305
305
  end
306
306
  end
@@ -407,7 +407,7 @@ module Expressir
407
407
  end
408
408
  end
409
409
 
410
- def each_string(node, &)
410
+ def each_string(node, &block)
411
411
  case node
412
412
  when Model::Literals::String then yield node, node.value
413
413
  when Model::ModelElement
@@ -416,8 +416,8 @@ module Expressir
416
416
 
417
417
  value = node.public_send(attr)
418
418
  case value
419
- when Array then value.each { |item| each_string(item, &) }
420
- when Model::ModelElement then each_string(value, &)
419
+ when Array then value.each { |item| each_string(item, &block) }
420
+ when Model::ModelElement then each_string(value, &block)
421
421
  end
422
422
  end
423
423
  end
@@ -351,13 +351,17 @@ module Expressir
351
351
  end
352
352
  end
353
353
 
354
- # Every identifier a call site names, across the artifact's
355
- # surviving declarations: FunctionCall for functions and
356
- # ProcedureCall statements for procedures. (A zero-argument
357
- # function reference written bare is not represented as a
358
- # FunctionCall by the parser and is counted via any SimpleReference
359
- # miss — accepted v1 blind spot, see tranche notes.)
354
+ # Every identifier a call site or reference names, across the
355
+ # artifact's surviving declarations: FunctionCall for functions,
356
+ # ProcedureCall statements for procedures, and ANY SimpleReference
357
+ # naming a schema function/procedure — a zero-argument function
358
+ # used bare (`v < limit_value`) parses as a plain reference, not
359
+ # a FunctionCall, and dropping it would leave the longform with a
360
+ # dangling call. Over-retaining is safe: prune only decides what
361
+ # may be dropped.
360
362
  def called_ids(schema)
363
+ invocable = (schema.functions.to_a + schema.procedures.to_a)
364
+ .map { |d| d.id.safe_downcase }
361
365
  called = {}
362
366
  each_node(schema) do |node|
363
367
  id = nil
@@ -366,8 +370,10 @@ module Expressir
366
370
  id = node.function.is_a?(String) ? node.function : node.function&.id
367
371
  when Expressir::Model::Statements::ProcedureCall
368
372
  id = node.procedure.is_a?(String) ? node.procedure : node.procedure&.id
373
+ when Model::References::SimpleReference
374
+ id = node.id
369
375
  end
370
- called[id.safe_downcase] = true if id
376
+ called[id.safe_downcase] = true if id && invocable.include?(id.safe_downcase)
371
377
  end
372
378
  called
373
379
  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.14".freeze
6
+ VERSION = "2.4.16".freeze
7
7
  end
8
8
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "fileutils"
5
+
6
+ ORACLE_TAG = "eeng-5.2.8"
7
+ ORACLE_BASE =
8
+ "https://github.com/expresslang/eengine-releases/releases/download/#{ORACLE_TAG}".freeze
9
+ # Release-asset naming convention for the pinned tag: platform prefix
10
+ # and CPU tag used by expresslang/eengine-releases.
11
+ ORACLE_ASSET_NAMES = {
12
+ %w[darwin arm64] => "mac-arm64",
13
+ %w[darwin x86_64] => "mac-x86-64",
14
+ %w[linux x86_64] => "lnx-x86-64",
15
+ %w[linux arm64] => "lnx-arm64",
16
+ %w[mingw x86_64] => "win-x86-64",
17
+ }.freeze
18
+
19
+ namespace :oracle do
20
+ desc "Fetch the eengine oracle (#{ORACLE_TAG}) into tmp/oracle/eengine " \
21
+ "for the SHTOLO corpus differential"
22
+ task :fetch do
23
+ platform = [Gem::Platform.local.os, Gem::Platform.local.cpu]
24
+ tag = ORACLE_ASSET_NAMES[platform]
25
+ abort "no #{ORACLE_TAG} asset for #{platform.join('-')}" unless tag
26
+
27
+ version = ORACLE_TAG.delete_prefix("eeng-")
28
+ asset = "eengine-#{version}-#{tag}-sbcl#{'.exe' if platform.first == 'mingw'}"
29
+ target = File.expand_path("../tmp/oracle/eengine", __dir__)
30
+ digest = "#{target}.sha256"
31
+
32
+ if File.executable?(target)
33
+ expected = File.read(digest).strip.split(/\s+/).first if File.exist?(digest)
34
+ if expected && Digest::SHA256.file(target).hexdigest == expected
35
+ warn "oracle already present: #{target}"
36
+ exit 0
37
+ end
38
+ warn "cached oracle is missing or corrupt; refetching"
39
+ rm_f target
40
+ end
41
+
42
+ dir = File.dirname(target)
43
+ mkdir_p dir
44
+ download = File.join(dir, "#{asset}.part")
45
+ sh "curl", "-fSL", "-o", download, "#{ORACLE_BASE}/#{asset}"
46
+ sha = Digest::SHA256.file(download).hexdigest
47
+ mv download, target
48
+ chmod 0o755, target
49
+ File.write(digest, "#{sha} eengine\n")
50
+ puts "oracle ready: #{target} (#{sha})"
51
+ end
52
+ 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.14
4
+ version: 2.4.16
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-23 00:00:00.000000000 Z
11
+ date: 2026-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -318,6 +318,7 @@ files:
318
318
  - TODO.parity-ee/15-part28-xml.md
319
319
  - TODO.parity-ee/16-compare-patch.md
320
320
  - TODO.parity-ee/17-official-tests.md
321
+ - TODO.parity-ee/18-mim-mapping.md
321
322
  - TODO.parity-ee/19-architecture.md
322
323
  - TODO.parity-ee/parity-matrix.md
323
324
  - benchmark/srl_benchmark.rb
@@ -639,6 +640,7 @@ files:
639
640
  - lib/expressir/schema_manifest_entry.rb
640
641
  - lib/expressir/version.rb
641
642
  - lib/tasks/verify_remarks.rake
643
+ - rakelib/oracle.rake
642
644
  - rakelib/verify_remarks.rake
643
645
  homepage: https://github.com/lutaml/expressir
644
646
  licenses: