expressir 2.4.15 → 2.4.17
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 +4 -4
- data/TODO.parity-ee/18-mim-mapping.md +47 -0
- data/lib/expressir/commands/parity_inputs.rb +14 -5
- data/lib/expressir/express/lazy_repository.rb +211 -0
- data/lib/expressir/express.rb +1 -0
- data/lib/expressir/version.rb +1 -1
- data/rakelib/oracle.rake +52 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6fb8953f2f2e9d76be3693148c8951ae36f562bbaf8214a8bc510b68888700d2
|
|
4
|
+
data.tar.gz: dfbcc336cc1a580501ca69632d6e6bb348e196be470cbc712a637131ffe1993c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e5259fb143ac5419ab85b8a97e8ab50a3a809826f799b8cd74242a673599c5cac0bd21cfe1dc33af562c16a806afc95f9f43f75f8eba43e18b68377104d5406
|
|
7
|
+
data.tar.gz: 0d2520a7a817f1c8d72a6f768887e803c91a6070f3cf8d4230bde9e66dcb6a1580ff7ee39ac6e9c886106cde64db14e175db57e81be0b798ea8f45af57bfaaa7
|
|
@@ -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.
|
|
@@ -61,15 +61,18 @@ module Expressir
|
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
# 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.
|
|
65
68
|
def manifest_index(manifest_path)
|
|
66
69
|
manifest = Expressir::SchemaManifest.from_file(manifest_path)
|
|
67
70
|
manifest.schemas.to_h do |entry|
|
|
68
71
|
[entry.id.downcase, entry.path]
|
|
69
72
|
end
|
|
70
73
|
rescue StandardError => e
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
raise Thor::Error,
|
|
75
|
+
"expressir: could not load schema manifest #{manifest_path}: #{e.message}"
|
|
73
76
|
end
|
|
74
77
|
|
|
75
78
|
# Schema-name → path index over a STEPmod checkout root (the
|
|
@@ -94,10 +97,16 @@ module Expressir
|
|
|
94
97
|
|
|
95
98
|
# Default: the file's own directory plus, when the root sits
|
|
96
99
|
# inside a STEPmod checkout, its enclosing `schemas/` tree —
|
|
97
|
-
# the layout wg12-step modules live in.
|
|
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).
|
|
98
103
|
index = same_dir_index(root_path)
|
|
99
104
|
ancestor = ancestor_schemas_dir(root_path)
|
|
100
|
-
|
|
105
|
+
if ancestor
|
|
106
|
+
tree = stepmod_index(File.dirname(ancestor))
|
|
107
|
+
tree.update(index)
|
|
108
|
+
index = tree
|
|
109
|
+
end
|
|
101
110
|
index
|
|
102
111
|
end
|
|
103
112
|
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "liquid"
|
|
4
|
+
|
|
5
|
+
module Expressir
|
|
6
|
+
module Express
|
|
7
|
+
# Lazy read path over a compiled-set artifact (M2 slice 2,
|
|
8
|
+
# TODO.parity-ee/19): schemas hydrate individually, on first touch,
|
|
9
|
+
# instead of the whole repository at once. Metanorma schema-doc
|
|
10
|
+
# rendering touches one schema per page — the lazy repository makes
|
|
11
|
+
# that pay: one hydration per page, not one per schema in the set.
|
|
12
|
+
#
|
|
13
|
+
# Duck-types the read surface of Model::Repository used by
|
|
14
|
+
# rendering (schemas, files, each, size, children). Anything beyond
|
|
15
|
+
# the read surface should wait for eager hydration via
|
|
16
|
+
# #to_eager (returns a real Model::Repository).
|
|
17
|
+
class LazyRepository
|
|
18
|
+
attr_reader :set_path
|
|
19
|
+
|
|
20
|
+
# @param set_path [String] compiled-set artifact path
|
|
21
|
+
# @param read_paths [Hash{String => String}] wire path => source path
|
|
22
|
+
def initialize(set_path, read_paths = {})
|
|
23
|
+
@set_path = set_path
|
|
24
|
+
@set = Core.lazy_set(set_path)
|
|
25
|
+
@read_paths = read_paths
|
|
26
|
+
@wire_paths = @set.wire_paths
|
|
27
|
+
@mutex = Mutex.new
|
|
28
|
+
@file_cache = {}
|
|
29
|
+
@hydration_count = 0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Wire paths in artifact order — no hydration.
|
|
33
|
+
# @return [Array<String>]
|
|
34
|
+
def wire_paths
|
|
35
|
+
@wire_paths.dup
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Files in artifact order, each wrapped so it hydrates on first
|
|
39
|
+
# use. The file is the hydration grain: an artifact stores wire
|
|
40
|
+
# JSON per source file.
|
|
41
|
+
# @return [Array<LazyFile>]
|
|
42
|
+
def files
|
|
43
|
+
@wire_paths.map { |wire_path| file_by_wire_path(wire_path) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Hydrated (or hydrating) file for one wire path.
|
|
47
|
+
def file_by_wire_path(wire_path)
|
|
48
|
+
@mutex.synchronize do
|
|
49
|
+
@file_cache[wire_path] ||= LazyFile.new(wire_path) do
|
|
50
|
+
hydrate(wire_path)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Schemas across all files. EAGER by definition: it hydrates
|
|
56
|
+
# every file. Lazy callers iterate #files, or render through
|
|
57
|
+
# #to_liquid, which keeps per-file hydration lazy.
|
|
58
|
+
# @return [Array<Declarations::Schema>]
|
|
59
|
+
def schemas
|
|
60
|
+
files.map(&:__target__).flat_map(&:schemas).compact
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Liquid binding for metanorma (M2): returns a Drop whose
|
|
64
|
+
# `schemas` is a lazy per-file list — each entry hydrates only
|
|
65
|
+
# its own file when the template touches it. Schema count per
|
|
66
|
+
# file is unknown until hydration, so the Drop presents one
|
|
67
|
+
# entry per FILE; single-schema files (the corpus norm) render
|
|
68
|
+
# identically to the eager path.
|
|
69
|
+
def to_liquid
|
|
70
|
+
LazyRepositoryDrop.new(self)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# How many files have actually been hydrated (observability for
|
|
74
|
+
# the laziness contract).
|
|
75
|
+
def hydration_count
|
|
76
|
+
@hydration_count
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Hydrate everything and return a real Model::Repository — the
|
|
80
|
+
# escape hatch for callers needing the full read surface.
|
|
81
|
+
def to_eager
|
|
82
|
+
repo = Model::Repository.new
|
|
83
|
+
repo.files = files.map(&:__target__)
|
|
84
|
+
repo
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def each(&)
|
|
88
|
+
files.each(&)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def size
|
|
92
|
+
@wire_paths.size
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def children
|
|
96
|
+
schemas
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The read path on disk for a wire path (may be nil when the
|
|
100
|
+
# caller did not supply source paths).
|
|
101
|
+
def read_path_for(wire_path)
|
|
102
|
+
@read_paths[wire_path]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def matches_sources?
|
|
106
|
+
@set.matches_sources(@read_paths.dup) == true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def hydrate(wire_path)
|
|
112
|
+
@mutex.synchronize do
|
|
113
|
+
_wire, model = @set.hydrate_one(wire_path)
|
|
114
|
+
@hydration_count += 1
|
|
115
|
+
model
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Liquid::Drop over a LazyRepository: `schemas` enumerates lazy
|
|
121
|
+
# per-file entries; touching an entry hydrates that file and
|
|
122
|
+
# presents its schemas (single-schema files present the schema
|
|
123
|
+
# itself, matching the eager Drop's element contract).
|
|
124
|
+
class LazyRepositoryDrop < ::Liquid::Drop
|
|
125
|
+
def initialize(repo)
|
|
126
|
+
@repo = repo
|
|
127
|
+
super()
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def schemas
|
|
131
|
+
@repo.files.map do |file|
|
|
132
|
+
LazyFileDrop.new(file)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def size
|
|
137
|
+
@repo.size
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Per-file lazy drop: hydrates on first access, then presents the
|
|
142
|
+
# file's single schema directly (or the schema list for
|
|
143
|
+
# multi-schema files).
|
|
144
|
+
class LazyFileDrop < ::Liquid::Drop
|
|
145
|
+
def initialize(file)
|
|
146
|
+
@file = file
|
|
147
|
+
super()
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def __file__
|
|
151
|
+
@file.__target__
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def method_missing(name, *, &)
|
|
155
|
+
schemas = __file__.schemas
|
|
156
|
+
if schemas.size == 1
|
|
157
|
+
schemas.first.public_send(name, *, &)
|
|
158
|
+
else
|
|
159
|
+
__file__.public_send(name, *, &)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def respond_to_missing?(name, include_private = false)
|
|
164
|
+
__file__.respond_to?(name, include_private) ||
|
|
165
|
+
__file__.schemas.first.respond_to?(name, include_private)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Stands in for a Model::ExpFile until first method access, then
|
|
170
|
+
# hydrates through the block and forwards everything.
|
|
171
|
+
class LazyFile
|
|
172
|
+
def initialize(wire_path, &hydrator)
|
|
173
|
+
@wire_path = wire_path
|
|
174
|
+
@hydrator = hydrator
|
|
175
|
+
@mutex = Mutex.new
|
|
176
|
+
@target = nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
attr_reader :wire_path
|
|
180
|
+
|
|
181
|
+
# The real Schema, hydrating on first call.
|
|
182
|
+
def __target__
|
|
183
|
+
@mutex.synchronize do
|
|
184
|
+
@__target__ ||= @hydrator.call
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def method_missing(name, *, &)
|
|
189
|
+
__target__.public_send(name, *, &)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def respond_to_missing?(name, include_private = false)
|
|
193
|
+
__target__.respond_to?(name, include_private)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Identity through the wrapper: rendering compares and groups by
|
|
197
|
+
# class/is_a?, which the real file answers.
|
|
198
|
+
def is_a?(klass)
|
|
199
|
+
super || __target__.is_a?(klass)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def instance_of?(klass)
|
|
203
|
+
__target__.instance_of?(klass)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def class
|
|
207
|
+
__target__.class
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
data/lib/expressir/express.rb
CHANGED
|
@@ -44,6 +44,7 @@ module Expressir
|
|
|
44
44
|
"#{__dir__}/express/schema_plain_source_formatter"
|
|
45
45
|
autoload :SchemaSourceFormatter, "#{__dir__}/express/schema_source_formatter"
|
|
46
46
|
autoload :ScopeResolver, "#{__dir__}/express/scope_resolver"
|
|
47
|
+
autoload :LazyRepository, "#{__dir__}/express/lazy_repository"
|
|
47
48
|
autoload :SmrlXml, "#{__dir__}/express/smrl_xml"
|
|
48
49
|
autoload :Xsd, "#{__dir__}/express/xsd"
|
|
49
50
|
autoload :SourceFormatter, "#{__dir__}/express/source_formatter"
|
data/lib/expressir/version.rb
CHANGED
data/rakelib/oracle.rake
ADDED
|
@@ -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.
|
|
4
|
+
version: 2.4.17
|
|
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-
|
|
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
|
|
@@ -505,6 +506,7 @@ files:
|
|
|
505
506
|
- lib/expressir/express/grammar/parser.rb
|
|
506
507
|
- lib/expressir/express/hyperlink_formatter.rb
|
|
507
508
|
- lib/expressir/express/interface_dot.rb
|
|
509
|
+
- lib/expressir/express/lazy_repository.rb
|
|
508
510
|
- lib/expressir/express/line_map.rb
|
|
509
511
|
- lib/expressir/express/listing.rb
|
|
510
512
|
- lib/expressir/express/model_traversal.rb
|
|
@@ -639,6 +641,7 @@ files:
|
|
|
639
641
|
- lib/expressir/schema_manifest_entry.rb
|
|
640
642
|
- lib/expressir/version.rb
|
|
641
643
|
- lib/tasks/verify_remarks.rake
|
|
644
|
+
- rakelib/oracle.rake
|
|
642
645
|
- rakelib/verify_remarks.rake
|
|
643
646
|
homepage: https://github.com/lutaml/expressir
|
|
644
647
|
licenses:
|