expressir 2.4.9 → 2.4.10
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/19-architecture.md +97 -0
- data/lib/expressir/express/checker.rb +69 -0
- data/lib/expressir/express/xsd.rb +145 -0
- data/lib/expressir/express.rb +1 -0
- data/lib/expressir/mapping.rb +141 -0
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +1 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a8a349971d0d91880df982145a16571130609ca66be9082aa512dad577e2156
|
|
4
|
+
data.tar.gz: 19c5b335acdf93eb742096e0405424f7a45fa271aeaa9fd6e7f000787e0574e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 431c803fc5c99f82c513734225fb7cf37f039c7dae5da6f85b130e778cf1b2f2b34bd37a5288b86ad2fd8a2c6861441263e6313501dc484d88ca15b80ece156f
|
|
7
|
+
data.tar.gz: 873c8f40bc8c08970258106903cccc04e18b758a5af3a3cd48bf685c909f9cefe3231c4570c64fe87f96bb4e74aced28883e5be2174d3f50683905a731459549
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# 19 — End-state architecture: Expressir in Rust, bound to metanorma via lutaml-model
|
|
2
|
+
|
|
3
|
+
**Question (2026-09-23): should Expressir ultimately be in Rust, bound to
|
|
4
|
+
metanorma via lutaml-model?**
|
|
5
|
+
|
|
6
|
+
**Answer: yes as a direction, no as a rewrite.** The end state is
|
|
7
|
+
*Rust engine, Ruby presentation* — reached in three milestones, not by
|
|
8
|
+
porting everything at once. The binding to metanorma goes through
|
|
9
|
+
lutaml-model's Liquid Drop layer, which is exactly the seam that
|
|
10
|
+
already works (expressir#255, #319, #421, #426).
|
|
11
|
+
|
|
12
|
+
## Current state (v2.4.9, measured)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
.exp ──► parsanol-rs 0.8.8 (parse, wire tree)
|
|
16
|
+
└─► expressir_core ext (batch, EXSCS1 artifact, warm start)
|
|
17
|
+
└─► instantiate → Expressir::Model (lutaml-model, Ruby)
|
|
18
|
+
└─► Checker / Shtolo / formatters (Ruby semantics)
|
|
19
|
+
└─► Liquid Drops ──► metanorma / suma
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- Parse is Rust and fast (SRL cold 36 s CPU, SMRL 558 s CPU / 1448
|
|
23
|
+
schemas); parsanol 0.8.8's selective memoization is adopted.
|
|
24
|
+
- The measured floor is now the **wire→Ruby-object hydration**
|
|
25
|
+
(71 % of warm CPU) and the overlays (29 %). Rendering (metanorma)
|
|
26
|
+
touches a small fraction of the model.
|
|
27
|
+
- Semantics (Checker subset, SHTOLO with the 50-module eengine
|
|
28
|
+
differential, PrettyFormatter, SMRL XML) live in Ruby, eeng-parity
|
|
29
|
+
validated, with Ruby CI harnesses.
|
|
30
|
+
|
|
31
|
+
## Options considered
|
|
32
|
+
|
|
33
|
+
**A — Status quo+.** Rust keeps parse+artifact; all new semantics
|
|
34
|
+
(Checker catalog, mapping validation) land in Ruby. No rewrite risk,
|
|
35
|
+
parity harnesses stay, but the hydration floor remains and the Rust
|
|
36
|
+
surface stays private.
|
|
37
|
+
|
|
38
|
+
**B — Lazy binding (the recommended milestone).** Keep the Ruby model
|
|
39
|
+
as the canonical object model for feature work, and add a READ path
|
|
40
|
+
that serves metanorma directly from Rust-held compiled-set data:
|
|
41
|
+
expressir_core exposes accessors over the loaded set (schema by id,
|
|
42
|
+
kind listings, field reads), and a lutaml-model Drop proxies them on
|
|
43
|
+
demand — rendering never instantiates the full object graph.
|
|
44
|
+
lutaml-model keeps ownership of mappings/serialization/Drop
|
|
45
|
+
integration; the Drop becomes the literal "Expressir in Rust bound to
|
|
46
|
+
metanorma via lutaml-model".
|
|
47
|
+
|
|
48
|
+
**C — Full Rust Expressir.** Model + semantics in Rust; Ruby keeps
|
|
49
|
+
only Drops and CLI glue; publish expressir-rs on crates.io; unlock
|
|
50
|
+
non-Ruby consumers (standalone binary, WASM). Rejected for now: it
|
|
51
|
+
re-implements eeng-validated semantics with parity risk, doubles CI,
|
|
52
|
+
and metanorma still needs a Drop/FFI layer — the boundary cost moves,
|
|
53
|
+
it does not disappear.
|
|
54
|
+
|
|
55
|
+
**Decision: B now; C deferred behind explicit triggers**
|
|
56
|
+
(a non-Ruby consumer materializes; the Ruby semantic layers become a
|
|
57
|
+
measured bottleneck — they are not today; or crate publishing is
|
|
58
|
+
wanted for supply-chain reasons independent of consumers).
|
|
59
|
+
|
|
60
|
+
## Milestones
|
|
61
|
+
|
|
62
|
+
### M1 — rev-pinned core dependency (supply chain, one line)
|
|
63
|
+
The ext pins `expressir-rs = { git …, branch = "main" }`; a released
|
|
64
|
+
gem can pick up a shifted core. Pin `rev = "<sha>"` per expressir
|
|
65
|
+
release (2.4.8 ↔ today's expressir-rs main), bump per release.
|
|
66
|
+
|
|
67
|
+
### M2 — lazy Drops for the metanorma hot path
|
|
68
|
+
1. expressir-rs: read accessors over the loaded compiled set
|
|
69
|
+
(`schema(id)`, kind listings, per-declaration field reads,
|
|
70
|
+
remark/refs overlay application in place).
|
|
71
|
+
2. expressir_core: magnus wrappers returning primitives.
|
|
72
|
+
3. Ruby: `LazySchemaDrop` (lutaml-model #319 custom Drop, the same
|
|
73
|
+
seam as #255/#426) implementing id / declaration listings / source
|
|
74
|
+
faces via the accessors.
|
|
75
|
+
4. Gates: suma-style schema-doc pages render output-comparable with
|
|
76
|
+
the eager path on a fixture corpus; warm page-render benchmark;
|
|
77
|
+
SMRL warm-start CPU target ≤ 30 % of today's 128 s for the
|
|
78
|
+
read-only path.
|
|
79
|
+
|
|
80
|
+
### M3 — semantics placement rule (standing)
|
|
81
|
+
New semantic features land in Ruby **unless** they are
|
|
82
|
+
parse-adjacent or graph-scale (then Rust in expressir-rs). The
|
|
83
|
+
Checker catalog (12) and mapping validation (18) stay Ruby; SHTOLO
|
|
84
|
+
stays Ruby with its eengine differential as the gate.
|
|
85
|
+
|
|
86
|
+
### M4 — trigger review for option C
|
|
87
|
+
Reassess on any of: a non-Ruby consumer request; hydrate/overlay CPU
|
|
88
|
+
re-emerging as the top cost after M2; a crates.io publication ask.
|
|
89
|
+
M2's accessors are exactly the seed of C's public API — nothing in B
|
|
90
|
+
is throwaway.
|
|
91
|
+
|
|
92
|
+
## What lutaml-model owns in every option
|
|
93
|
+
|
|
94
|
+
The mapping DSL (yaml/json faces — incl. the #88 `mapping.yaml`
|
|
95
|
+
reader), Liquid integration (#319 Drops), artifact serialization —
|
|
96
|
+
the binding contract between the engine and metanorma. Rust never
|
|
97
|
+
duplicates it.
|
|
@@ -93,6 +93,7 @@ module Expressir
|
|
|
93
93
|
schema.entities.each { |e| check_entity(schema, e) }
|
|
94
94
|
schema.types.each do |t|
|
|
95
95
|
check_type(schema, t)
|
|
96
|
+
check_select_items(schema, t)
|
|
96
97
|
check_where_rules(schema, t, t.where_rules)
|
|
97
98
|
end
|
|
98
99
|
schema.rules.each { |r| check_where_rules(schema, r, r.where_rules) }
|
|
@@ -208,7 +209,15 @@ module Expressir
|
|
|
208
209
|
|
|
209
210
|
note!(:check_subtype_ref, :error, schema,
|
|
210
211
|
"ENTITY #{entity.id}: subtype '#{id}' not found", entity)
|
|
212
|
+
next unless declared_kind(schema, id) == :type
|
|
213
|
+
|
|
214
|
+
# eeng check-subtypeof-invalid: the name exists but is not
|
|
215
|
+
# an entity, so it cannot be a supertype.
|
|
216
|
+
note!(:check_subtypeof_invalid, :error, schema,
|
|
217
|
+
"ENTITY #{entity.id}: subtype '#{id}' is a TYPE, " \
|
|
218
|
+
"not an entity", entity)
|
|
211
219
|
end
|
|
220
|
+
check_supertype_refs(schema, entity)
|
|
212
221
|
check_subtype_cycles(schema, entity)
|
|
213
222
|
|
|
214
223
|
check_where_rules(schema, entity, entity.where_rules)
|
|
@@ -261,6 +270,66 @@ module Expressir
|
|
|
261
270
|
cyclic
|
|
262
271
|
end
|
|
263
272
|
|
|
273
|
+
# eeng check-supertype-ref: every entity named in the SUPERTYPE
|
|
274
|
+
# expression must resolve to an entity of this schema.
|
|
275
|
+
def check_supertype_refs(schema, entity)
|
|
276
|
+
expr = entity.supertype_expression
|
|
277
|
+
return unless expr
|
|
278
|
+
|
|
279
|
+
each_reference(expr) do |ref|
|
|
280
|
+
id = ref.id
|
|
281
|
+
next if id.nil? || find_entity(schema, id)
|
|
282
|
+
|
|
283
|
+
note!(:check_supertype_ref, :error, schema,
|
|
284
|
+
"ENTITY #{entity.id}: supertype '#{id}' not found", entity)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def each_reference(node, &)
|
|
289
|
+
case node
|
|
290
|
+
when Model::References::SimpleReference then yield node
|
|
291
|
+
when Model::ModelElement
|
|
292
|
+
node.class.attributes.each_key do |attr|
|
|
293
|
+
next if Model::ModelElement::SKIP_ATTRIBUTES.include?(attr) || attr == :parent
|
|
294
|
+
|
|
295
|
+
value = node.public_send(attr)
|
|
296
|
+
case value
|
|
297
|
+
when Array then value.each { |item| each_reference(item, &) }
|
|
298
|
+
when Model::ModelElement then each_reference(value, &)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# eeng check-select-named-type: SELECT items must name types.
|
|
305
|
+
def check_select_items(schema, type)
|
|
306
|
+
return unless type.underlying_type.is_a?(Model::DataTypes::Select)
|
|
307
|
+
|
|
308
|
+
Array(type.underlying_type.items).each do |item|
|
|
309
|
+
next if item.id.nil?
|
|
310
|
+
next if find_type(schema, item.id)
|
|
311
|
+
next if find_entity(schema, item.id).nil? # unresolved handled elsewhere
|
|
312
|
+
|
|
313
|
+
note!(:check_select_named_type, :error, schema,
|
|
314
|
+
"TYPE #{type.id}: select item '#{item.id}' is an entity, " \
|
|
315
|
+
"not a named type", type)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def find_type(schema, id)
|
|
320
|
+
key = id.safe_downcase
|
|
321
|
+
return schema.types.find { |t| t.id.safe_downcase == key } if schema
|
|
322
|
+
|
|
323
|
+
nil
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# eeng declared-kind probe: :type when a TYPE of that name is
|
|
327
|
+
# visible in the schema (used to separate not-found from
|
|
328
|
+
# not-an-entity in subtype diagnostics).
|
|
329
|
+
def declared_kind(schema, id)
|
|
330
|
+
:type if find_type(schema, id)
|
|
331
|
+
end
|
|
332
|
+
|
|
264
333
|
def check_where_rules(schema, owner, rules)
|
|
265
334
|
Array(rules).each do |wr|
|
|
266
335
|
next if wr.id.nil? || wr.id.match?(WHERE_LABEL)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Express
|
|
5
|
+
# EXPRESS → XML Schema writer (expressir #276 step 2,
|
|
6
|
+
# TODO.parity-ee 15) — the express2xsd counterpart, built on
|
|
7
|
+
# moxml.
|
|
8
|
+
#
|
|
9
|
+
# Mapping (v1):
|
|
10
|
+
# ENTITY → xs:element + xs:complexType (explicit attributes
|
|
11
|
+
# in declaration order); SUBTYPE OF a schema-local
|
|
12
|
+
# entity becomes a substitutionGroup reference.
|
|
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).
|
|
16
|
+
# defined TYPE over a simple type → xs:simpleType restriction.
|
|
17
|
+
# attributes → xs:element inside the complexType sequence;
|
|
18
|
+
# LIST/SET/BAG/ARRAY → maxOccurs="unbounded".
|
|
19
|
+
#
|
|
20
|
+
# Built-in EXPRESS types map to XML Schema built-ins (STRING→
|
|
21
|
+
# xs:string, INTEGER→xs:integer, REAL/NUMBER→xs:double,
|
|
22
|
+
# BOOLEAN→xs:boolean, LOGICAL→string restriction, BINARY→
|
|
23
|
+
# xs:hexBinary).
|
|
24
|
+
module Xsd
|
|
25
|
+
BUILTIN_TYPES = {
|
|
26
|
+
"string" => "xs:string",
|
|
27
|
+
"integer" => "xs:integer",
|
|
28
|
+
"real" => "xs:double",
|
|
29
|
+
"number" => "xs:double",
|
|
30
|
+
"boolean" => "xs:boolean",
|
|
31
|
+
"binary" => "xs:hexBinary",
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
AGGREGATES = %w[array list set bag].freeze
|
|
35
|
+
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
# @param schema [Model::Declarations::Schema]
|
|
39
|
+
# @return [String] the serialized xs:schema document
|
|
40
|
+
def format(schema)
|
|
41
|
+
ctx = Moxml::Context.new
|
|
42
|
+
doc = ctx.create_document
|
|
43
|
+
root = doc.create_element("xs:schema")
|
|
44
|
+
root.set_attributes("xmlns:xs" => "http://www.w3.org/2001/XMLSchema",
|
|
45
|
+
"elementFormDefault" => "qualified")
|
|
46
|
+
doc.add_child(root)
|
|
47
|
+
schema.types.to_a.each { |type| root.add_child(type_node(doc, schema, type)) }
|
|
48
|
+
schema.entities.to_a.each do |entity|
|
|
49
|
+
root.add_child(entity_element(doc, schema, entity))
|
|
50
|
+
root.add_child(entity_type(doc, schema, entity))
|
|
51
|
+
end
|
|
52
|
+
doc.to_xml
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def type_node(doc, schema, type)
|
|
56
|
+
underlying = type.underlying_type
|
|
57
|
+
node = doc.create_element("xs:simpleType")
|
|
58
|
+
node.set_attributes("name" => type.id)
|
|
59
|
+
restriction = doc.create_element("xs:restriction")
|
|
60
|
+
node.add_child(restriction)
|
|
61
|
+
if underlying.is_a?(Expressir::Model::DataTypes::Enumeration)
|
|
62
|
+
restriction.set_attributes("base" => "xs:string")
|
|
63
|
+
underlying.items.to_a.each do |item|
|
|
64
|
+
facet = doc.create_element("xs:enumeration")
|
|
65
|
+
facet.set_attributes("value" => item.id)
|
|
66
|
+
restriction.add_child(facet)
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
restriction.set_attributes("base" => xs_type(underlying, schema))
|
|
70
|
+
end
|
|
71
|
+
node
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def entity_element(doc, schema, entity)
|
|
75
|
+
node = doc.create_element("xs:element")
|
|
76
|
+
node.set_attributes("name" => entity.id)
|
|
77
|
+
node.set_attributes("type" => entity.id)
|
|
78
|
+
parent = supertype_name(schema, entity)
|
|
79
|
+
node.set_attributes("substitutionGroup" => parent) if parent
|
|
80
|
+
node
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def entity_type(doc, schema, entity)
|
|
84
|
+
node = doc.create_element("xs:complexType")
|
|
85
|
+
node.set_attributes("name" => entity.id)
|
|
86
|
+
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
|
+
|
|
93
|
+
sequence.add_child(attribute_element(doc, schema, attr))
|
|
94
|
+
end
|
|
95
|
+
node
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def attribute_element(doc, schema, attr)
|
|
99
|
+
node = doc.create_element("xs:element")
|
|
100
|
+
node.set_attributes("name" => attr.id)
|
|
101
|
+
node.set_attributes("type" => xs_type(attr.type, schema))
|
|
102
|
+
kind = attr.type.class.name.split("::").last.downcase
|
|
103
|
+
node.set_attributes("maxOccurs" => "unbounded") if AGGREGATES.include?(kind)
|
|
104
|
+
node
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def xs_type(type, schema)
|
|
108
|
+
if type.is_a?(Expressir::Model::References::SimpleReference)
|
|
109
|
+
id = type.respond_to?(:base_path) && type.base_path ? base_id(type.base_path) : type.id
|
|
110
|
+
return schema.types.to_a.any? { |t| t.id.safe_downcase == id.safe_downcase } ? id : "xs:anyType"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if type.is_a?(Expressir::Model::DataTypes::Enumeration)
|
|
114
|
+
return type.id || "xs:string"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if type.respond_to?(:base_type)
|
|
118
|
+
inner = type.base_type
|
|
119
|
+
return xs_type(inner, schema) if inner
|
|
120
|
+
|
|
121
|
+
return "xs:anyType"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
name = type.class.name.split("::").last.downcase
|
|
125
|
+
BUILTIN_TYPES.fetch(name, "xs:anyType")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
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
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def base_id(base_path)
|
|
140
|
+
parts = base_path.split(".")
|
|
141
|
+
parts[-1]
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
data/lib/expressir/express.rb
CHANGED
|
@@ -45,6 +45,7 @@ module Expressir
|
|
|
45
45
|
autoload :SchemaSourceFormatter, "#{__dir__}/express/schema_source_formatter"
|
|
46
46
|
autoload :ScopeResolver, "#{__dir__}/express/scope_resolver"
|
|
47
47
|
autoload :SmrlXml, "#{__dir__}/express/smrl_xml"
|
|
48
|
+
autoload :Xsd, "#{__dir__}/express/xsd"
|
|
48
49
|
autoload :SourceFormatter, "#{__dir__}/express/source_formatter"
|
|
49
50
|
autoload :StreamingBuilder, "#{__dir__}/express/streaming_builder"
|
|
50
51
|
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
# MIM application-element mapping model (expressir#88,
|
|
5
|
+
# TODO.parity-ee 18): the typed reader for the `mapping.yaml` files
|
|
6
|
+
# each STEP module carries, and validation of the
|
|
7
|
+
# `<<express:SCHEMA.ITEM,ITEM>>` links they contain against a
|
|
8
|
+
# resolved ARM/MIM repository.
|
|
9
|
+
#
|
|
10
|
+
# The model is lutaml-model Serializable — the yaml face is the
|
|
11
|
+
# module corpus format (top-level `ae` + `sc` keys); fields the
|
|
12
|
+
# corpus carries that v1 does not model (alt_map, descriptions)
|
|
13
|
+
# are ignored by the mapping and survive round-trips untouched in
|
|
14
|
+
# the source files.
|
|
15
|
+
module Mapping
|
|
16
|
+
EXPRESS_LINK = /<<express:([^,>]+)(?:,([^>]+))?>>/
|
|
17
|
+
|
|
18
|
+
class ReferencePath < Lutaml::Model::Serializable
|
|
19
|
+
attribute :content, :string
|
|
20
|
+
|
|
21
|
+
key_value do
|
|
22
|
+
map "content", to: :content
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class AttributeMapping < Lutaml::Model::Serializable
|
|
27
|
+
attribute :attribute, :string
|
|
28
|
+
attribute :aimelt, :string
|
|
29
|
+
attribute :inherited_from_entity, :string
|
|
30
|
+
attribute :assertion_to, :string
|
|
31
|
+
attribute :refpath, ReferencePath
|
|
32
|
+
|
|
33
|
+
key_value do
|
|
34
|
+
map "attribute", to: :attribute
|
|
35
|
+
map "aimelt", to: :aimelt
|
|
36
|
+
map "inherited_from_entity", to: :inherited_from_entity
|
|
37
|
+
map "assertion_to", to: :assertion_to
|
|
38
|
+
map "refpath", to: :refpath
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class ApplicationElement < Lutaml::Model::Serializable
|
|
43
|
+
attribute :entity, :string
|
|
44
|
+
attribute :aimelt, :string
|
|
45
|
+
attribute :extensible, :string
|
|
46
|
+
attribute :original_module, :string
|
|
47
|
+
attribute :refpath, ReferencePath
|
|
48
|
+
attribute :aa, AttributeMapping, collection: true
|
|
49
|
+
|
|
50
|
+
key_value do
|
|
51
|
+
map "entity", to: :entity
|
|
52
|
+
map "aimelt", to: :aimelt
|
|
53
|
+
map "extensible", to: :extensible
|
|
54
|
+
map "original_module", to: :original_module
|
|
55
|
+
map "refpath", to: :refpath
|
|
56
|
+
map "aa", to: :aa
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class SubtypeConstraintMapping < Lutaml::Model::Serializable
|
|
61
|
+
attribute :constraint, :string
|
|
62
|
+
attribute :entity, :string
|
|
63
|
+
|
|
64
|
+
key_value do
|
|
65
|
+
map "constraint", to: :constraint
|
|
66
|
+
map "entity", to: :entity
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Document < Lutaml::Model::Serializable
|
|
71
|
+
attribute :ae, ApplicationElement, collection: true
|
|
72
|
+
attribute :sc, SubtypeConstraintMapping, collection: true
|
|
73
|
+
|
|
74
|
+
key_value do
|
|
75
|
+
map "ae", to: :ae
|
|
76
|
+
map "sc", to: :sc
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
UnknownLink = Struct.new(:text, :schema, :item, keyword_init: true)
|
|
81
|
+
|
|
82
|
+
module_function
|
|
83
|
+
|
|
84
|
+
# @param path [String] path to a module mapping.yaml
|
|
85
|
+
# @return [Document]
|
|
86
|
+
def load_file(path)
|
|
87
|
+
Document.from_yaml(File.read(path))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Every `<<express:...>>` link in the document, as
|
|
91
|
+
# [schema_id, item_id] pairs (duplicates preserved).
|
|
92
|
+
def links(document)
|
|
93
|
+
values(document).flat_map do |value|
|
|
94
|
+
value.to_s.scan(EXPRESS_LINK).map do |path, _id|
|
|
95
|
+
parts = path.split(".")
|
|
96
|
+
[parts[-2], parts[-1]]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Links whose schema or item cannot be found in +repository+.
|
|
102
|
+
# A link with no item part resolves against schema presence only.
|
|
103
|
+
def unknown_links(document, repository)
|
|
104
|
+
known = repository.schemas.to_h do |schema|
|
|
105
|
+
[schema.id.safe_downcase, schema]
|
|
106
|
+
end
|
|
107
|
+
links(document).filter_map do |schema_id, item_id|
|
|
108
|
+
schema = known[schema_id&.safe_downcase]
|
|
109
|
+
if schema.nil?
|
|
110
|
+
next UnknownLink.new(text: "#{schema_id}.#{item_id}",
|
|
111
|
+
schema: schema_id, item: item_id)
|
|
112
|
+
end
|
|
113
|
+
next if item_id.nil?
|
|
114
|
+
|
|
115
|
+
unless item_declared?(schema, item_id)
|
|
116
|
+
next UnknownLink.new(text: "#{schema_id}.#{item_id}",
|
|
117
|
+
schema: schema_id, item: item_id)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def item_declared?(schema, item_id)
|
|
125
|
+
key = item_id.safe_downcase
|
|
126
|
+
%i[types entities functions procedures rules constants
|
|
127
|
+
subtype_constraints].any? do |coll|
|
|
128
|
+
Array(schema.public_send(coll)).any? do |decl|
|
|
129
|
+
decl.respond_to?(:id) && decl.id&.safe_downcase == key
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def values(document)
|
|
135
|
+
document.ae.to_a.flat_map do |element|
|
|
136
|
+
[element.entity, element.aimelt] +
|
|
137
|
+
element.aa.to_a.flat_map { |aa| [aa.attribute, aa.aimelt] }
|
|
138
|
+
end.compact
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
data/lib/expressir/version.rb
CHANGED
data/lib/expressir.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Expressir
|
|
|
29
29
|
extend Config
|
|
30
30
|
|
|
31
31
|
autoload :Version, "#{__dir__}/expressir/version"
|
|
32
|
+
autoload :Mapping, "#{__dir__}/expressir/mapping"
|
|
32
33
|
|
|
33
34
|
# Error classes — autoload the same errors.rb file from each name.
|
|
34
35
|
# The first reference triggers the load; the file defines all 21 classes.
|
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.
|
|
4
|
+
version: 2.4.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -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/19-architecture.md
|
|
321
322
|
- TODO.parity-ee/parity-matrix.md
|
|
322
323
|
- benchmark/srl_benchmark.rb
|
|
323
324
|
- benchmark/srl_native_benchmark.rb
|
|
@@ -526,10 +527,12 @@ files:
|
|
|
526
527
|
- lib/expressir/express/smrl_xml.rb
|
|
527
528
|
- lib/expressir/express/source_formatter.rb
|
|
528
529
|
- lib/expressir/express/streaming_builder.rb
|
|
530
|
+
- lib/expressir/express/xsd.rb
|
|
529
531
|
- lib/expressir/liquid.rb
|
|
530
532
|
- lib/expressir/manifest.rb
|
|
531
533
|
- lib/expressir/manifest/resolver.rb
|
|
532
534
|
- lib/expressir/manifest/validator.rb
|
|
535
|
+
- lib/expressir/mapping.rb
|
|
533
536
|
- lib/expressir/model.rb
|
|
534
537
|
- lib/expressir/model/cache.rb
|
|
535
538
|
- lib/expressir/model/concerns.rb
|