expressir 2.4.29-x86_64-linux → 2.4.35-x86_64-linux
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/.github/scripts/test_installed_gem.rb +18 -4
- data/.github/workflows/native-gems.yml +4 -1
- data/TODO.eengine/00-overview.md +39 -0
- data/TODO.eengine/01-resolution-diagnostics.md +30 -0
- data/TODO.eengine/02-check-catalog-completion.md +53 -0
- data/TODO.eengine/03-entity-computations.md +22 -0
- data/TODO.eengine/04-shtolo-rule-audit.md +32 -0
- data/TODO.eengine/05-shtolo-longform-flatten.md +12 -0
- data/TODO.eengine/06-population-p21.md +34 -0
- data/TODO.eengine/07-schema-compare-patch.md +25 -0
- data/TODO.eengine/08-official-corpus-gate.md +7 -0
- data/TODO.eengine/09-mim-mapping-parser.md +9 -0
- data/TODO.parity-ee/00-overview.md +1 -1
- data/TODO.suma-improvements/07-part28-xml.md +10 -13
- data/expressir.gemspec +5 -5
- data/lib/expressir/3.2/expressir_core.so +0 -0
- data/lib/expressir/3.3/expressir_core.so +0 -0
- data/lib/expressir/3.4/expressir_core.so +0 -0
- data/lib/expressir/4.0/expressir_core.so +0 -0
- data/lib/expressir/cli.rb +7 -0
- data/lib/expressir/commands/parity_inputs.rb +6 -1
- data/lib/expressir/commands/smrl_xml_index.rb +25 -0
- data/lib/expressir/commands.rb +2 -1
- data/lib/expressir/express/builder.rb +17 -5
- data/lib/expressir/express/builders/entity_decl_builder.rb +17 -5
- data/lib/expressir/express/checker.rb +191 -3
- data/lib/expressir/express/core.rb +1 -0
- data/lib/expressir/express/formatters/declarations_formatter.rb +21 -14
- data/lib/expressir/express/grammar/parser.rb +29 -1
- data/lib/expressir/express/pretty_formatter.rb +9 -14
- data/lib/expressir/express/schema_compare.rb +194 -0
- data/lib/expressir/express/shtolo.rb +194 -63
- data/lib/expressir/express.rb +2 -0
- data/lib/expressir/model/declarations/entity.rb +132 -0
- data/lib/expressir/model/declarations/schema.rb +37 -6
- data/lib/expressir/p21/builder.rb +92 -0
- data/lib/expressir/p21/grammar.rb +107 -0
- data/lib/expressir/p21/parser.rb +16 -0
- data/lib/expressir/p21/population.rb +35 -0
- data/lib/expressir/p21/validator.rb +213 -0
- data/lib/expressir/p21.rb +17 -0
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +1 -0
- metadata +37 -19
- /data/lib/expressir/commands/{version.rb → version_command.rb} +0 -0
|
@@ -130,16 +130,20 @@ module Expressir
|
|
|
130
130
|
schema.interfaces.each do |iface|
|
|
131
131
|
next unless iface.kind == Model::Declarations::Interface::REFERENCE
|
|
132
132
|
|
|
133
|
+
iface_schema = iface.schema.is_a?(String) ? iface.schema : iface.schema&.id
|
|
134
|
+
target = closure[iface_schema&.safe_downcase]
|
|
133
135
|
iface.items.each do |item|
|
|
134
136
|
original = original_name(item, schema)
|
|
135
|
-
next unless
|
|
137
|
+
next unless target&.entities&.find do |e|
|
|
138
|
+
e.id.safe_downcase == original.safe_downcase
|
|
139
|
+
end
|
|
136
140
|
|
|
137
141
|
@reference_entities << original.safe_downcase
|
|
138
142
|
end
|
|
139
143
|
end
|
|
140
144
|
end
|
|
141
145
|
|
|
142
|
-
declarations
|
|
146
|
+
declarations.concat(build_dependent_declarations) if @reference_entities.any?
|
|
143
147
|
|
|
144
148
|
Model::Declarations::Schema.new(
|
|
145
149
|
id: @longform_name,
|
|
@@ -166,7 +170,18 @@ module Expressir
|
|
|
166
170
|
# OWN schema's id, and the global map redirects the bare name so
|
|
167
171
|
# declarations from third schemas resolve to the first occurrence.
|
|
168
172
|
def plan_ids(schemas)
|
|
169
|
-
|
|
173
|
+
# G.NM.1: EVERY clashing declaration is prefixed (the annex
|
|
174
|
+
# example munges farming_dot_dog AND pet_shows_dot_dog), so the
|
|
175
|
+
# clash set is computed over the whole population first.
|
|
176
|
+
counts = Hash.new(0)
|
|
177
|
+
schemas.each do |schema|
|
|
178
|
+
all_decls(schema).each do |decl|
|
|
179
|
+
next unless decl.respond_to?(:id) && decl.id
|
|
180
|
+
|
|
181
|
+
counts[decl.id.safe_downcase] += 1
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
170
185
|
global = {} # downcase id => munged final
|
|
171
186
|
own_by_schema = {} # schema => {downcase id => final id}
|
|
172
187
|
schemas.each do |schema|
|
|
@@ -175,14 +190,11 @@ module Expressir
|
|
|
175
190
|
next unless decl.respond_to?(:id) && decl.id
|
|
176
191
|
|
|
177
192
|
key = decl.id.safe_downcase
|
|
178
|
-
if
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
seen_first[key] = schema
|
|
184
|
-
own[key] = decl.id
|
|
185
|
-
end
|
|
193
|
+
own[key] = if counts[key] > 1
|
|
194
|
+
global[key] = "#{schema.id.safe_downcase}_dot_#{decl.id}"
|
|
195
|
+
else
|
|
196
|
+
decl.id
|
|
197
|
+
end
|
|
186
198
|
end
|
|
187
199
|
own_by_schema[schema] = own
|
|
188
200
|
end
|
|
@@ -253,11 +265,20 @@ module Expressir
|
|
|
253
265
|
current == current.upcase ? new.upcase : new
|
|
254
266
|
end
|
|
255
267
|
|
|
256
|
-
# G.1.6: parse-templates keep the annex rule/function verbatim
|
|
257
|
-
|
|
268
|
+
# G.1.6: parse-templates keep the annex rule/function verbatim;
|
|
269
|
+
# the pair is emitted together (the rule's WHERE calls the
|
|
270
|
+
# function, and the prune pass would drop an uncalled function).
|
|
271
|
+
def build_dependent_declarations
|
|
272
|
+
snippet = parse_schema_snippet(dependent_template_src)
|
|
273
|
+
rule = snippet.schemas.first.rules.first
|
|
274
|
+
fn = snippet.schemas.first.functions.first
|
|
275
|
+
[rule, fn].compact
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def dependent_template_src
|
|
258
279
|
list = @reference_entities.map(&:upcase).join(", ")
|
|
259
280
|
union = @reference_entities.join("+")
|
|
260
|
-
|
|
281
|
+
<<~EXP
|
|
261
282
|
SCHEMA __dependent__;
|
|
262
283
|
RULE #{DEPENDENT_RULE_NAME} FOR (#{list});
|
|
263
284
|
LOCAL
|
|
@@ -270,9 +291,62 @@ module Expressir
|
|
|
270
291
|
WHERE
|
|
271
292
|
wr1: dependently_instantiated(all_instances, set_of_input_types, previous_in_chain);
|
|
272
293
|
END_RULE;
|
|
294
|
+
|
|
295
|
+
FUNCTION dependently_instantiated(
|
|
296
|
+
set_of_input_instances : SET OF GENERIC:igen;
|
|
297
|
+
set_of_input_types : SET OF STRING;
|
|
298
|
+
previous_in_chain : LIST OF GENERIC:cgen): BOOLEAN;
|
|
299
|
+
LOCAL
|
|
300
|
+
number_of_input_instances : INTEGER;
|
|
301
|
+
number_of_referring_instances : INTEGER;
|
|
302
|
+
bag_of_referring_instances : BAG OF GENERIC:igen := [];
|
|
303
|
+
dependently_instantiated_flag : BOOLEAN;
|
|
304
|
+
previous_in_chain_plus : LIST OF GENERIC:cgen := [];
|
|
305
|
+
result : BOOLEAN := true;
|
|
306
|
+
set_of_types : SET OF STRING := [];
|
|
307
|
+
END_LOCAL;
|
|
308
|
+
IF EXISTS(set_of_input_instances) THEN
|
|
309
|
+
number_of_input_instances := SIZEOF(set_of_input_instances);
|
|
310
|
+
set_of_input_types := set_of_input_types + 'GENERIC';
|
|
311
|
+
REPEAT i := 1 TO number_of_input_instances;
|
|
312
|
+
bag_of_referring_instances := USEDIN(set_of_input_instances[i], '');
|
|
313
|
+
IF EXISTS(bag_of_referring_instances) THEN
|
|
314
|
+
number_of_referring_instances := SIZEOF(bag_of_referring_instances);
|
|
315
|
+
dependently_instantiated_flag := false;
|
|
316
|
+
REPEAT j := 1 TO number_of_referring_instances;
|
|
317
|
+
set_of_types := TYPEOF(bag_of_referring_instances[j]);
|
|
318
|
+
IF set_of_types <= set_of_input_types THEN
|
|
319
|
+
IF NOT (bag_of_referring_instances[j] IN previous_in_chain) THEN
|
|
320
|
+
previous_in_chain_plus := previous_in_chain +
|
|
321
|
+
set_of_input_instances[i];
|
|
322
|
+
IF dependently_instantiated([bag_of_referring_instances[j]],
|
|
323
|
+
set_of_input_types,
|
|
324
|
+
previous_in_chain_plus) THEN
|
|
325
|
+
dependently_instantiated_flag := true;
|
|
326
|
+
ESCAPE;
|
|
327
|
+
ELSE
|
|
328
|
+
SKIP;
|
|
329
|
+
END_IF;
|
|
330
|
+
END_IF;
|
|
331
|
+
ELSE
|
|
332
|
+
dependently_instantiated_flag := true;
|
|
333
|
+
ESCAPE;
|
|
334
|
+
END_IF;
|
|
335
|
+
END_REPEAT;
|
|
336
|
+
IF NOT dependently_instantiated_flag THEN
|
|
337
|
+
RETURN(false);
|
|
338
|
+
END_IF;
|
|
339
|
+
ELSE
|
|
340
|
+
RETURN(false);
|
|
341
|
+
END_IF;
|
|
342
|
+
END_REPEAT;
|
|
343
|
+
ELSE
|
|
344
|
+
RETURN(false);
|
|
345
|
+
END_IF;
|
|
346
|
+
RETURN(true);
|
|
347
|
+
END_FUNCTION;
|
|
273
348
|
END_SCHEMA;
|
|
274
349
|
EXP
|
|
275
|
-
parse_schema_snippet(src).schemas.first.rules.first
|
|
276
350
|
end
|
|
277
351
|
|
|
278
352
|
def parse_schema_snippet(src)
|
|
@@ -295,10 +369,32 @@ module Expressir
|
|
|
295
369
|
convert_generic_entities!(artifact)
|
|
296
370
|
convert_renamed_attributes!(artifact)
|
|
297
371
|
rewrite_qualified_strings!(artifact)
|
|
372
|
+
add_provenance_remark!(artifact)
|
|
298
373
|
|
|
299
374
|
artifact
|
|
300
375
|
end
|
|
301
376
|
|
|
377
|
+
# G.1.10: optional embedded provenance remark — if any schema
|
|
378
|
+
# name is kept in the longform, ALL contributing schemas are
|
|
379
|
+
# recorded (`schema = <id> [schema_version_id = '<v>'];`, order
|
|
380
|
+
# insignificant).
|
|
381
|
+
def add_provenance_remark!(schema)
|
|
382
|
+
names = closure.values.map(&:id).sort
|
|
383
|
+
return if names.empty?
|
|
384
|
+
|
|
385
|
+
remarks = ["Original 2003 schemas:"] + names.map do |id|
|
|
386
|
+
contributing = closure.values.find { |s| s.id == id }
|
|
387
|
+
version = contributing&.version&.id
|
|
388
|
+
line = "schema = #{id};"
|
|
389
|
+
line = "schema = #{id} schema_version_id = '#{version}';" if version
|
|
390
|
+
line
|
|
391
|
+
end
|
|
392
|
+
schema.remark_items = [
|
|
393
|
+
Model::Declarations::RemarkItem.new(id: "provenance", remarks: remarks),
|
|
394
|
+
*Array(schema.remark_items),
|
|
395
|
+
]
|
|
396
|
+
end
|
|
397
|
+
|
|
302
398
|
# G.1.9 prune pass: the artifact keeps only what stays visible
|
|
303
399
|
# and reachable. Runs before the stage-2 rewrites so they never
|
|
304
400
|
# see declarations the longform would drop.
|
|
@@ -477,57 +573,95 @@ module Expressir
|
|
|
477
573
|
end
|
|
478
574
|
end
|
|
479
575
|
|
|
576
|
+
# G.2.2/G.2.4: every extensible/extending select becomes a
|
|
577
|
+
# non-extensible select whose items are the full domain in
|
|
578
|
+
# longform scope. Chains fold in dependency order (a node whose
|
|
579
|
+
# own BASED_ON is still pending is deferred), so an extending
|
|
580
|
+
# type that is itself extensible resolves against its base's
|
|
581
|
+
# folded domain.
|
|
582
|
+
# G.2.2/G.2.4: every extensible/extending select becomes a
|
|
583
|
+
# non-extensible select whose items are the full domain in
|
|
584
|
+
# longform scope. Chains fold in dependency order (a node whose
|
|
585
|
+
# own BASED_ON is still pending is deferred), so an extending
|
|
586
|
+
# type that is itself extensible resolves against its base's
|
|
587
|
+
# folded domain.
|
|
480
588
|
def resolve_extensible_selects!(schema)
|
|
481
|
-
schema.types.select do |
|
|
482
|
-
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
# every entity the artifact carries, so the longform select is
|
|
489
|
-
# complete against the closure. Flags stay as declared: the
|
|
490
|
-
# extensible/generic-entity markers are part of the shortform's
|
|
491
|
-
# semantics and are not expressir's to drop.
|
|
492
|
-
if sel.generic_entity
|
|
493
|
-
sel.items = schema.entities.map do |e|
|
|
494
|
-
Model::References::SimpleReference.new(id: e.id)
|
|
495
|
-
end
|
|
496
|
-
next
|
|
497
|
-
end
|
|
589
|
+
select_types = schema.types.select do |ty|
|
|
590
|
+
ty.underlying_type.is_a?(Model::DataTypes::Select)
|
|
591
|
+
end
|
|
592
|
+
select_types.select { |ty| sel_pending?(ty) }.each do |base|
|
|
593
|
+
fold_select_node(schema, select_types, base)
|
|
594
|
+
end
|
|
595
|
+
end
|
|
498
596
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
597
|
+
def sel_pending?(select_type)
|
|
598
|
+
sel = select_type.underlying_type
|
|
599
|
+
sel.extensible || sel.based_on
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def fold_select_node(schema, select_types, base_type)
|
|
603
|
+
sel = base_type.underlying_type
|
|
604
|
+
return unless sel_pending?(base_type)
|
|
605
|
+
|
|
606
|
+
# all-extenders for GENERIC_ENTITY selects (WG12 requirement,
|
|
607
|
+
# #32): the extenders are the entity types themselves — fold in
|
|
608
|
+
# every entity the artifact carries, so the longform select is
|
|
609
|
+
# complete against the closure. Flags stay as declared: the
|
|
610
|
+
# extensible/generic-entity markers are part of the shortform's
|
|
611
|
+
# semantics and are not expressir's to drop.
|
|
612
|
+
if sel.generic_entity && !sel.based_on
|
|
613
|
+
sel.items = schema.entities.map do |e|
|
|
614
|
+
Model::References::SimpleReference.new(id: e.id)
|
|
503
615
|
end
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
616
|
+
return
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
own_items = Array(sel.items)
|
|
620
|
+
inherited = []
|
|
621
|
+
if sel.based_on
|
|
622
|
+
base_ty = select_types.find do |ty|
|
|
623
|
+
ty.id&.safe_downcase == sel.based_on&.id&.safe_downcase
|
|
512
624
|
end
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
625
|
+
fold_select_node(schema, select_types, base_ty) if base_ty && sel_pending?(base_ty)
|
|
626
|
+
inherited = base_ty ? Array(base_ty.underlying_type.items) : []
|
|
627
|
+
end
|
|
516
628
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
629
|
+
extenders = select_types.select do |ty|
|
|
630
|
+
!ty.equal?(base_type) &&
|
|
631
|
+
ty.underlying_type.based_on&.id&.safe_downcase == base_type.id&.safe_downcase
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
merged = inherited + own_items +
|
|
635
|
+
extenders.flat_map { |ty| Array(ty.underlying_type.items) }
|
|
636
|
+
seen = {}
|
|
637
|
+
uniq = merged.select do |item|
|
|
638
|
+
key = item.id.safe_downcase
|
|
639
|
+
next false if key.empty? || seen.key?(key)
|
|
640
|
+
|
|
641
|
+
seen[key] = true
|
|
642
|
+
end
|
|
643
|
+
sel.items = uniq
|
|
644
|
+
sel.extensible = false
|
|
645
|
+
sel.based_on = nil
|
|
646
|
+
|
|
647
|
+
extenders.each do |ty|
|
|
648
|
+
ext_sel = ty.underlying_type
|
|
649
|
+
keep = Array(ext_sel.items).map { |i| i.id.upcase }
|
|
650
|
+
base_ref = "#{@longform_name.upcase}.#{base_type.id.upcase}"
|
|
651
|
+
ty.underlying_type = Model::DataTypes::Select.new(
|
|
652
|
+
items: uniq.map { |i| Model::References::SimpleReference.new(id: i.id) },
|
|
653
|
+
)
|
|
654
|
+
ext_sel_items = ty.underlying_type.items
|
|
655
|
+
exclusions = uniq.reject { |i| keep.include?(i.id.upcase) }
|
|
656
|
+
ty.where_rules = exclusions.each_with_index.map do |item, i|
|
|
657
|
+
Model::Declarations::WhereRule.new(
|
|
658
|
+
id: "wr#{i + 1}",
|
|
659
|
+
expression: parse_expression(
|
|
660
|
+
"NOT ('#{base_ref}.#{item.id.upcase}' IN TYPEOF(SELF));",
|
|
661
|
+
),
|
|
523
662
|
)
|
|
524
|
-
ext.where_rules = exclusions.each_with_index.map do |item, i|
|
|
525
|
-
Model::Declarations::WhereRule.new(
|
|
526
|
-
id: "wr#{i + 1}",
|
|
527
|
-
expression: parse_expression("NOT ('#{base_ref}.#{item.id.upcase}' IN TYPEOF(SELF));"),
|
|
528
|
-
)
|
|
529
|
-
end
|
|
530
663
|
end
|
|
664
|
+
ext_sel_items
|
|
531
665
|
end
|
|
532
666
|
end
|
|
533
667
|
|
|
@@ -543,10 +677,7 @@ module Expressir
|
|
|
543
677
|
entity.supertype_expression =
|
|
544
678
|
entity.supertype_expression ? combine_andor(entity.supertype_expression, expr) : expr
|
|
545
679
|
end
|
|
546
|
-
if
|
|
547
|
-
# G.2.6: abstract entity ⇒ ABSTRACT SUPERTYPE (formatter
|
|
548
|
-
# renders from abstract + supertype status).
|
|
549
|
-
end
|
|
680
|
+
entity.abstract = true if sc.abstract
|
|
550
681
|
next if Array(sc.total_over).empty?
|
|
551
682
|
|
|
552
683
|
rule_name = "total_over_#{sc.id}"
|
data/lib/expressir/express.rb
CHANGED
|
@@ -43,6 +43,8 @@ module Expressir
|
|
|
43
43
|
autoload :SchemaPlainSourceFormatter,
|
|
44
44
|
"#{__dir__}/express/schema_plain_source_formatter"
|
|
45
45
|
autoload :SchemaSourceFormatter, "#{__dir__}/express/schema_source_formatter"
|
|
46
|
+
autoload :SchemaCompare, "#{__dir__}/express/schema_compare"
|
|
47
|
+
autoload :SchemaPatch, "#{__dir__}/express/schema_compare"
|
|
46
48
|
autoload :ScopeResolver, "#{__dir__}/express/scope_resolver"
|
|
47
49
|
autoload :LazyRepository, "#{__dir__}/express/lazy_repository"
|
|
48
50
|
autoload :SmrlXml, "#{__dir__}/express/smrl_xml"
|
|
@@ -56,6 +56,138 @@ module Expressir
|
|
|
56
56
|
*remark_items,
|
|
57
57
|
]
|
|
58
58
|
end
|
|
59
|
+
|
|
60
|
+
# eeng compute-inheritance: the full supertype closure, nearest
|
|
61
|
+
# supertype first, cycle-safe. Cross-schema supertypes resolve
|
|
62
|
+
# through the enclosing repository.
|
|
63
|
+
# @return [Array<Entity>]
|
|
64
|
+
def supertype_closure
|
|
65
|
+
closure = []
|
|
66
|
+
visited = { id&.safe_downcase => true }
|
|
67
|
+
queue = Array(subtype_of).map { |ref| ref.respond_to?(:id) ? ref.id : ref }
|
|
68
|
+
while (name = queue.shift)
|
|
69
|
+
key = name&.safe_downcase
|
|
70
|
+
next if key.nil? || visited.key?(key)
|
|
71
|
+
|
|
72
|
+
supertype = find_supertype_entity(key)
|
|
73
|
+
next unless supertype
|
|
74
|
+
|
|
75
|
+
visited[key] = true
|
|
76
|
+
closure << supertype
|
|
77
|
+
queue.concat(Array(supertype.subtype_of).map do |ref|
|
|
78
|
+
ref.respond_to?(:id) ? ref.id : ref
|
|
79
|
+
end)
|
|
80
|
+
end
|
|
81
|
+
closure
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Explicit attributes inherited from the supertype closure,
|
|
85
|
+
# nearest supertype first, skipping ids this entity (or a
|
|
86
|
+
# nearer ancestor) redeclares.
|
|
87
|
+
# @return [Array<ExplicitAttribute>]
|
|
88
|
+
def inherited_explicit_attributes
|
|
89
|
+
claimed = explicit_only(Array(attributes)).filter_map(&:id).map(&:safe_downcase)
|
|
90
|
+
closure = supertype_closure
|
|
91
|
+
closure.flat_map do |supertype|
|
|
92
|
+
explicit_only(Array(supertype.attributes)).filter_map do |attr|
|
|
93
|
+
next nil unless attr.id
|
|
94
|
+
|
|
95
|
+
key = attr.id.safe_downcase
|
|
96
|
+
next nil if claimed.include?(key)
|
|
97
|
+
|
|
98
|
+
claimed << key
|
|
99
|
+
attr
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Derived attributes accumulated from the supertype closure.
|
|
105
|
+
# @return [Array<DerivedAttribute>]
|
|
106
|
+
def inherited_derived_attributes
|
|
107
|
+
closure = supertype_closure
|
|
108
|
+
seen = {}
|
|
109
|
+
closure.flat_map do |supertype|
|
|
110
|
+
derived_attribute_items(supertype).filter_map do |attr|
|
|
111
|
+
next nil unless attr.id
|
|
112
|
+
|
|
113
|
+
key = attr.id.safe_downcase
|
|
114
|
+
next nil if seen.key?(key)
|
|
115
|
+
|
|
116
|
+
seen[key] = true
|
|
117
|
+
attr
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Inverse attributes accumulated from the supertype closure.
|
|
123
|
+
# @return [Array<InverseAttribute>]
|
|
124
|
+
def inherited_inverse_attributes
|
|
125
|
+
closure = supertype_closure
|
|
126
|
+
seen = {}
|
|
127
|
+
closure.flat_map do |supertype|
|
|
128
|
+
inverse_attribute_items(supertype).filter_map do |attr|
|
|
129
|
+
next nil unless attr.id
|
|
130
|
+
|
|
131
|
+
key = attr.id.safe_downcase
|
|
132
|
+
next nil if seen.key?(key)
|
|
133
|
+
|
|
134
|
+
seen[key] = true
|
|
135
|
+
attr
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# All explicit attributes: own + inherited (eeng
|
|
141
|
+
# explicit-attrs materialization).
|
|
142
|
+
# @return [Array<ExplicitAttribute>]
|
|
143
|
+
def all_explicit_attributes
|
|
144
|
+
[*explicit_only(Array(attributes)), *inherited_explicit_attributes]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# Entity#attributes carries every attribute kind; the explicit
|
|
150
|
+
# kind is Expressir::Model::Declarations::Attribute.
|
|
151
|
+
# DerivedAttribute/InverseAttribute subclass Attribute, so the
|
|
152
|
+
# explicit kind is the exact base class.
|
|
153
|
+
def explicit_only(attrs)
|
|
154
|
+
attrs.select { |a| a.instance_of?(Expressir::Model::Declarations::Attribute) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# The Ruby parse path parks every attribute kind in `attributes`
|
|
158
|
+
# (typed by class); the core path fills derived_attributes/
|
|
159
|
+
# inverse_attributes instead. Both layouts resolve to the same
|
|
160
|
+
# items here.
|
|
161
|
+
def derived_attribute_items(entity)
|
|
162
|
+
own = Array(entity.derived_attributes)
|
|
163
|
+
return own if own.any?
|
|
164
|
+
|
|
165
|
+
Array(entity.attributes).grep(Expressir::Model::Declarations::DerivedAttribute)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def inverse_attribute_items(entity)
|
|
169
|
+
own = Array(entity.inverse_attributes)
|
|
170
|
+
return own if own.any?
|
|
171
|
+
|
|
172
|
+
Array(entity.attributes).grep(Expressir::Model::Declarations::InverseAttribute)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def find_supertype_entity(key)
|
|
176
|
+
container = parent
|
|
177
|
+
container = container.parent while container &&
|
|
178
|
+
!container.is_a?(Model::Repository) &&
|
|
179
|
+
!container.is_a?(Model::ExpFile)
|
|
180
|
+
return nil unless container.respond_to?(:schemas)
|
|
181
|
+
|
|
182
|
+
container.schemas.each do |foreign|
|
|
183
|
+
next unless foreign.respond_to?(:entities)
|
|
184
|
+
|
|
185
|
+
foreign.entities.each do |candidate|
|
|
186
|
+
return candidate if candidate.id&.safe_downcase == key
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
nil
|
|
190
|
+
end
|
|
59
191
|
end
|
|
60
192
|
end
|
|
61
193
|
end
|
|
@@ -127,6 +127,19 @@ module Expressir
|
|
|
127
127
|
@formatted_hyperlinked_adoc ||= Expressir::Express::AdocSourceFormatter.format(self)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
+
USE_ITEM_KINDS = [
|
|
131
|
+
Expressir::Model::Declarations::Entity,
|
|
132
|
+
Expressir::Model::Declarations::Type,
|
|
133
|
+
].freeze
|
|
134
|
+
|
|
135
|
+
REFERENCE_ITEM_KINDS = [
|
|
136
|
+
Expressir::Model::Declarations::Constant,
|
|
137
|
+
Expressir::Model::Declarations::Entity,
|
|
138
|
+
Expressir::Model::Declarations::Function,
|
|
139
|
+
Expressir::Model::Declarations::Procedure,
|
|
140
|
+
Expressir::Model::Declarations::Type,
|
|
141
|
+
].freeze
|
|
142
|
+
|
|
130
143
|
private
|
|
131
144
|
|
|
132
145
|
# @param [String] id
|
|
@@ -142,6 +155,7 @@ module Expressir
|
|
|
142
155
|
end
|
|
143
156
|
|
|
144
157
|
# @return [Array<InterfacedItem>]
|
|
158
|
+
|
|
145
159
|
def interfaced_items
|
|
146
160
|
return [] unless parent
|
|
147
161
|
|
|
@@ -151,6 +165,11 @@ module Expressir
|
|
|
151
165
|
end.compact
|
|
152
166
|
end
|
|
153
167
|
|
|
168
|
+
# ISO 10303-11 implicit interfacing: USE exposes entities and
|
|
169
|
+
# types only; REFERENCE has no implicit interfacing. Listed
|
|
170
|
+
# items follow eeng resolve-interface: use-from resolves via
|
|
171
|
+
# find-type/entity, reference-from via the 5-kind or-chain.
|
|
172
|
+
|
|
154
173
|
# Direct items of one interface plus the items visible through
|
|
155
174
|
# the foreign schema's own USE interfaces (transitive USE
|
|
156
175
|
# visibility, eeng :use-only → :use-from recursion).
|
|
@@ -171,16 +190,23 @@ module Expressir
|
|
|
171
190
|
end
|
|
172
191
|
|
|
173
192
|
own = if interface.items.empty?
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
if interface.kind == Interface::REFERENCE
|
|
194
|
+
[]
|
|
195
|
+
else
|
|
196
|
+
safe_children.filter_map do |base_item|
|
|
197
|
+
next nil unless USE_ITEM_KINDS.any? { |k| base_item.is_a?(k) }
|
|
198
|
+
|
|
199
|
+
create_interfaced_item(base_item.id, base_item)
|
|
200
|
+
end
|
|
176
201
|
end
|
|
177
202
|
else
|
|
178
203
|
interface.items.filter_map do |item|
|
|
179
204
|
base_item = children_by_id[item.ref.id.safe_downcase]
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
205
|
+
next nil unless base_item
|
|
206
|
+
next nil unless item_kind_interfacable?(interface.kind, base_item)
|
|
207
|
+
|
|
208
|
+
create_interfaced_item(item.id || base_item.id,
|
|
209
|
+
base_item)
|
|
184
210
|
end
|
|
185
211
|
end
|
|
186
212
|
|
|
@@ -197,6 +223,11 @@ module Expressir
|
|
|
197
223
|
end.flatten
|
|
198
224
|
end
|
|
199
225
|
|
|
226
|
+
def item_kind_interfacable?(kind, node)
|
|
227
|
+
kinds = kind == Interface::REFERENCE ? REFERENCE_ITEM_KINDS : USE_ITEM_KINDS
|
|
228
|
+
kinds.any? { |k| node.is_a?(k) }
|
|
229
|
+
end
|
|
230
|
+
|
|
200
231
|
# Find a foreign schema by name: same file first, then the
|
|
201
232
|
# enclosing repository (expressir's compiled/batch repository).
|
|
202
233
|
def foreign_schema(name)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module P21
|
|
5
|
+
# Turns a {Grammar} capture tree into a {Population}.
|
|
6
|
+
module Builder
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def build(tree)
|
|
10
|
+
header = build_header(tree[:header_section])
|
|
11
|
+
instances = build_instances(tree[:data_section])
|
|
12
|
+
Population.new(header: header, instances: instances)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def build_header(section)
|
|
16
|
+
Array(section[:header_entity]).each_with_object({}) do |entry, hash|
|
|
17
|
+
record = entry[:simple_record]
|
|
18
|
+
hash[record_keyword(record)] = record_params(record)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build_instances(section)
|
|
23
|
+
list = section[:instance_list]
|
|
24
|
+
entries = list.is_a?(Array) ? list : [list]
|
|
25
|
+
entries.each_with_object({}) do |entry, hash|
|
|
26
|
+
if (complex = entry[:complex_entity_instance])
|
|
27
|
+
hash[instance_id(complex)] = build_complex(complex)
|
|
28
|
+
elsif (simple = entry[:simple_entity_instance])
|
|
29
|
+
record = simple[:simple_record]
|
|
30
|
+
hash[instance_id(simple)] = Record.new(record_keyword(record),
|
|
31
|
+
record_params(record))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def instance_id(body)
|
|
37
|
+
"##{body[:entity_instance_name].to_s.delete_prefix('#')}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_complex(body)
|
|
41
|
+
subs = body[:subsuper_record]
|
|
42
|
+
subs = [subs] unless subs.is_a?(Array)
|
|
43
|
+
records = subs.map do |sub|
|
|
44
|
+
record = sub[:simple_record]
|
|
45
|
+
Record.new(record_keyword(record), record_params(record))
|
|
46
|
+
end
|
|
47
|
+
ComplexRecord.new(records)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def record_keyword(record)
|
|
51
|
+
record[:keyword].to_s.delete_prefix("!")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# A parameter_list capture is a hash when there is a single parameter
|
|
55
|
+
# and an array of {parameter: ...} entries otherwise.
|
|
56
|
+
def parameter_list_values(capture)
|
|
57
|
+
return [] if capture.nil?
|
|
58
|
+
|
|
59
|
+
entries = capture.is_a?(Array) ? capture : [capture]
|
|
60
|
+
entries.map { |entry| build_value(entry[:parameter]) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def record_params(record)
|
|
64
|
+
parameter_list_values(record[:parameter_list])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_value(param)
|
|
68
|
+
if (typed = param[:typed_parameter])
|
|
69
|
+
TypedValue.new(typed[:keyword].to_s.delete_prefix("!"),
|
|
70
|
+
parameter_list_values(typed[:parameter_list]))
|
|
71
|
+
elsif param[:parameter_list]
|
|
72
|
+
parameter_list_values(param[:parameter_list])
|
|
73
|
+
elsif param[:entity_instance_name]
|
|
74
|
+
"##{param[:entity_instance_name].to_s.delete_prefix('#')}"
|
|
75
|
+
elsif param[:number_literal]
|
|
76
|
+
text = param[:number_literal].to_s
|
|
77
|
+
text.match?(/[.Ee]/) ? text.to_f : text.to_i
|
|
78
|
+
elsif param[:enumeration]
|
|
79
|
+
Enumeration.new(param[:enumeration].to_s.delete_prefix(".").delete_suffix("."))
|
|
80
|
+
elsif param[:binary_literal]
|
|
81
|
+
Binary.new(param[:binary_literal].to_s.delete_prefix('"').delete_suffix('"'))
|
|
82
|
+
elsif param[:string_literal]
|
|
83
|
+
param[:string_literal].to_s[1..-2].gsub("''", "'")
|
|
84
|
+
elsif param[:unset]
|
|
85
|
+
:unset
|
|
86
|
+
elsif param[:derived]
|
|
87
|
+
:derived
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|