expressir 2.4.31-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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.eengine/00-overview.md +39 -0
  3. data/TODO.eengine/01-resolution-diagnostics.md +30 -0
  4. data/TODO.eengine/02-check-catalog-completion.md +53 -0
  5. data/TODO.eengine/03-entity-computations.md +22 -0
  6. data/TODO.eengine/04-shtolo-rule-audit.md +32 -0
  7. data/TODO.eengine/05-shtolo-longform-flatten.md +12 -0
  8. data/TODO.eengine/06-population-p21.md +34 -0
  9. data/TODO.eengine/07-schema-compare-patch.md +25 -0
  10. data/TODO.eengine/08-official-corpus-gate.md +7 -0
  11. data/TODO.eengine/09-mim-mapping-parser.md +9 -0
  12. data/TODO.parity-ee/00-overview.md +1 -1
  13. data/TODO.suma-improvements/07-part28-xml.md +10 -13
  14. data/lib/expressir/3.2/expressir_core.so +0 -0
  15. data/lib/expressir/3.3/expressir_core.so +0 -0
  16. data/lib/expressir/3.4/expressir_core.so +0 -0
  17. data/lib/expressir/4.0/expressir_core.so +0 -0
  18. data/lib/expressir/cli.rb +7 -0
  19. data/lib/expressir/commands/smrl_xml_index.rb +25 -0
  20. data/lib/expressir/commands.rb +1 -0
  21. data/lib/expressir/express/builder.rb +17 -5
  22. data/lib/expressir/express/builders/entity_decl_builder.rb +17 -5
  23. data/lib/expressir/express/checker.rb +191 -3
  24. data/lib/expressir/express/formatters/declarations_formatter.rb +21 -14
  25. data/lib/expressir/express/pretty_formatter.rb +9 -14
  26. data/lib/expressir/express/schema_compare.rb +194 -0
  27. data/lib/expressir/express/shtolo.rb +194 -63
  28. data/lib/expressir/express.rb +2 -0
  29. data/lib/expressir/model/declarations/entity.rb +132 -0
  30. data/lib/expressir/model/declarations/schema.rb +37 -6
  31. data/lib/expressir/p21/builder.rb +92 -0
  32. data/lib/expressir/p21/grammar.rb +107 -0
  33. data/lib/expressir/p21/parser.rb +16 -0
  34. data/lib/expressir/p21/population.rb +35 -0
  35. data/lib/expressir/p21/validator.rb +213 -0
  36. data/lib/expressir/p21.rb +17 -0
  37. data/lib/expressir/version.rb +1 -1
  38. data/lib/expressir.rb +1 -0
  39. metadata +20 -2
@@ -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 closure[original]&.entities&.find { |e| e.id == original }
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 << build_dependent_rule if @reference_entities.any?
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
- seen_first = {} # downcase id => owning schema (first occurrence)
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 seen_first.key?(key)
179
- final = "#{schema.id.safe_downcase}_dot_#{decl.id}"
180
- own[key] = final
181
- global[key] = final
182
- else
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
- def build_dependent_rule
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
- src = <<~EXP
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 |t|
482
- t.underlying_type.is_a?(Model::DataTypes::Select) && t.underlying_type.extensible
483
- end.each do |base|
484
- sel = base.underlying_type
485
-
486
- # all-extenders for GENERIC_ENTITY selects (WG12 requirement,
487
- # #32): the extenders are the entity types themselves — fold in
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
- extensions = schema.types.select do |t|
500
- t.underlying_type.is_a?(Model::DataTypes::Select) &&
501
- !t.underlying_type.extensible &&
502
- t.underlying_type.based_on&.id&.safe_downcase == base.id.safe_downcase
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
- merged_items = sel.items +
505
- extensions.flat_map { |x| x.underlying_type.items }
506
- seen_ids = {}
507
- uniq = merged_items.select do |item|
508
- key = item.id.safe_downcase
509
- next false if seen_ids.key?(key) || key.empty?
510
-
511
- seen_ids[key] = true
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
- sel.items = uniq
514
- sel.extensible = false
515
- sel.based_on = nil
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
- extensions.each do |ext|
518
- keep = ext.underlying_type.items.map { |i| i.id.upcase }
519
- exclusions = uniq.reject { |i| keep.include?(i.id.upcase) }
520
- base_ref = "#{@longform_name.upcase}.#{base.id.upcase}"
521
- ext.underlying_type = Model::DataTypes::Select.new(
522
- items: sel.items.map { |i| Model::References::SimpleReference.new(id: i.id) },
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 entity.abstract
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}"
@@ -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
- safe_children.map do |base_item|
175
- create_interfaced_item(base_item.id, base_item)
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
- if base_item
181
- create_interfaced_item(item.id || base_item.id,
182
- base_item)
183
- end
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