expressir 2.4.33 → 2.4.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 791c09b1ddc6f0ed583b4608c59ec327b7126c7c5b9d12c3a02acbaf236ae4c6
4
- data.tar.gz: edb97cb53754e74511c9c28926ce4d9235afcd42d3d56fb4cd127017bff58590
3
+ metadata.gz: 2fef762a5f189a2fa5e19af4e03b508ceecb9c51f4f70a805af7b0cfaa955c66
4
+ data.tar.gz: f4bfae05b286248e59f3bb54c83369c1c1bba957edeb5dc1e9e0aa8e7d0bc147
5
5
  SHA512:
6
- metadata.gz: ed1e988f03f3e4f5af1f77872495088c87c49a5247b055850b498b235f5fd1693055fb734441ad9d357cd71548a2692b5a17dceacea10fe9d77d21bb580766a4
7
- data.tar.gz: f63d8a48e336d00996b90efcc7797877c48a74a049aafcf52ae4e7f417dddbbf189a3d7452fd1daba527d38b17ae8d7a25b054338b64e41efb4ebd484aef669d
6
+ metadata.gz: 75be497abf1fc565aa7098271d933715ab0e2ccaa2eed76eda589d37fe511cb8b52a2c08ffa537e23011cb847d14c9a9647c99e814473cdb5820577e61b31af1
7
+ data.tar.gz: 5420b2a0edfd9f4135515c75cf163b68dbb147e6b60fd7900528671862672f3695936884ea919692036c8e09eabb60f85c13cc939c61fdc67c9ec8ad2e1571cd
@@ -186,17 +186,29 @@ module Expressir
186
186
 
187
187
  case item
188
188
  when Array
189
- item.each do |sub|
190
- result << build(sub) unless sub.nil?
191
- end
189
+ item.each { |sub| append_built(result, build(sub)) unless sub.nil? }
192
190
  else
193
- built = build(item)
194
- result << built if built
191
+ append_built(result, build(item))
195
192
  end
196
193
  end
197
194
  result
198
195
  end
199
196
 
197
+ # Builders for list productions (ExplicitAttrBuilder and friends)
198
+ # return Arrays of nodes. Splice those items into the parent list
199
+ # rather than nesting them — nested Arrays break every consumer
200
+ # that expects ModelElements (`x.kind`, tree walks, formatters).
201
+ def append_built(result, built)
202
+ case built
203
+ when nil
204
+ nil
205
+ when Array
206
+ built.each { |b| append_built(result, b) }
207
+ else
208
+ result << built
209
+ end
210
+ end
211
+
200
212
  # Fast build methods - call registered builder directly without hash wrapping
201
213
  # These are optimized for hot paths in expression building
202
214
  # NOTE: These assume data is already snake_case (no key conversion needed)
@@ -73,17 +73,17 @@ module Expressir
73
73
  if entity_body.is_a?(Hash)
74
74
  if entity_body[:explicit_attr]
75
75
  explicit_attrs = Builder.build_children(entity_body[:explicit_attr])
76
- attributes.concat(explicit_attrs)
76
+ attributes.concat(flatten_items(explicit_attrs))
77
77
  end
78
78
 
79
79
  if entity_body[:derive_clause]
80
80
  derived = Builder.build({ derive_clause: entity_body[:derive_clause] })
81
- attributes.push(derived) if derived
81
+ attributes.concat(flatten_items(derived))
82
82
  end
83
83
 
84
84
  if entity_body[:inverse_clause]
85
85
  inverse = Builder.build({ inverse_clause: entity_body[:inverse_clause] })
86
- attributes.push(inverse) if inverse
86
+ attributes.concat(flatten_items(inverse))
87
87
  end
88
88
 
89
89
  if entity_body[:unique_clause]
@@ -103,13 +103,25 @@ module Expressir
103
103
  supertype_expression: supertype_expression,
104
104
  subtype_of: subtype_of,
105
105
  attributes: attributes,
106
- unique_rules: [unique_rules].flatten,
107
- where_rules: [where_rules].flatten,
106
+ unique_rules: flatten_items(unique_rules),
107
+ where_rules: flatten_items(where_rules),
108
108
  }
109
109
  entity_params[:abstract] = true if abstract
110
110
 
111
111
  Expressir::Model::Declarations::Entity.new(**entity_params)
112
112
  end
113
+
114
+ private
115
+
116
+ # ExplicitAttrBuilder / DeriveClauseBuilder / InverseClauseBuilder
117
+ # return Arrays (one node per declaration). Call sites must splice
118
+ # those items into the parent list — nesting them leaves
119
+ # `entity.attributes` as Array-of-Array, which formatters then
120
+ # crash on (`undefined method 'kind' for an instance of Array`).
121
+ # lutaml-model 0.8.107 no longer flattens collection assignments.
122
+ def flatten_items(value)
123
+ Array(value).flatten.compact
124
+ end
113
125
  end
114
126
  end
115
127
  end
@@ -110,20 +110,15 @@ module Expressir
110
110
  end
111
111
 
112
112
  def format_declarations_entity(node)
113
- if node.attributes.nil?
114
- explicit_attributes = []
115
- derived_attributes = []
116
- inverse_attributes = []
117
- else
118
- explicit_attributes = node.attributes.select do |x|
119
- x.kind == Model::Declarations::Attribute::EXPLICIT
120
- end
121
- derived_attributes = node.attributes.select do |x|
122
- x.kind == Model::Declarations::Attribute::DERIVED
123
- end
124
- inverse_attributes = node.attributes.select do |x|
125
- x.kind == Model::Declarations::Attribute::INVERSE
126
- end
113
+ attrs = entity_attribute_items(node)
114
+ explicit_attributes = attrs.select do |x|
115
+ x.kind == Model::Declarations::Attribute::EXPLICIT
116
+ end
117
+ derived_attributes = attrs.select do |x|
118
+ x.kind == Model::Declarations::Attribute::DERIVED
119
+ end
120
+ inverse_attributes = attrs.select do |x|
121
+ x.kind == Model::Declarations::Attribute::INVERSE
127
122
  end
128
123
 
129
124
  indent_const = self.class.const_get(:INDENT)
@@ -231,6 +226,18 @@ module Expressir
231
226
  ].join("\n")
232
227
  end
233
228
 
229
+ # Entity#attributes is the Ruby-parse layout (every attribute kind
230
+ # in one list). The core path parks derived/inverse in their own
231
+ # collections instead. Both may also arrive as Array-of-Array when
232
+ # a builder nested list productions. Normalize to a flat list of
233
+ # Attribute-like nodes before selecting on `kind`.
234
+ def entity_attribute_items(node)
235
+ items = Array(node.attributes)
236
+ items += Array(node.derived_attributes) if node.respond_to?(:derived_attributes)
237
+ items += Array(node.inverse_attributes) if node.respond_to?(:inverse_attributes)
238
+ items.flatten.compact.uniq(&:object_id)
239
+ end
240
+
234
241
  def format_declarations_function(node)
235
242
  indent_char = self.class.const_get(:INDENT_CHAR)
236
243
  [
@@ -112,20 +112,15 @@ module Expressir
112
112
  # @param node [Model::Declarations::Entity] Entity node
113
113
  # @return [String] Formatted entity
114
114
  def format_declarations_entity(node)
115
- if node.attributes.nil?
116
- explicit_attributes = []
117
- derived_attributes = []
118
- inverse_attributes = []
119
- else
120
- explicit_attributes = node.attributes.select do |x|
121
- x.kind == Model::Declarations::Attribute::EXPLICIT
122
- end
123
- derived_attributes = node.attributes.select do |x|
124
- x.kind == Model::Declarations::Attribute::DERIVED
125
- end
126
- inverse_attributes = node.attributes.select do |x|
127
- x.kind == Model::Declarations::Attribute::INVERSE
128
- end
115
+ attrs = entity_attribute_items(node)
116
+ explicit_attributes = attrs.select do |x|
117
+ x.kind == Model::Declarations::Attribute::EXPLICIT
118
+ end
119
+ derived_attributes = attrs.select do |x|
120
+ x.kind == Model::Declarations::Attribute::DERIVED
121
+ end
122
+ inverse_attributes = attrs.select do |x|
123
+ x.kind == Model::Declarations::Attribute::INVERSE
129
124
  end
130
125
 
131
126
  indent_str = " " * @config.indent
@@ -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)
@@ -499,57 +573,95 @@ module Expressir
499
573
  end
500
574
  end
501
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.
502
588
  def resolve_extensible_selects!(schema)
503
- schema.types.select do |t|
504
- t.underlying_type.is_a?(Model::DataTypes::Select) && t.underlying_type.extensible
505
- end.each do |base|
506
- sel = base.underlying_type
507
-
508
- # all-extenders for GENERIC_ENTITY selects (WG12 requirement,
509
- # #32): the extenders are the entity types themselves — fold in
510
- # every entity the artifact carries, so the longform select is
511
- # complete against the closure. Flags stay as declared: the
512
- # extensible/generic-entity markers are part of the shortform's
513
- # semantics and are not expressir's to drop.
514
- if sel.generic_entity
515
- sel.items = schema.entities.map do |e|
516
- Model::References::SimpleReference.new(id: e.id)
517
- end
518
- next
519
- 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
520
596
 
521
- extensions = schema.types.select do |t|
522
- t.underlying_type.is_a?(Model::DataTypes::Select) &&
523
- !t.underlying_type.extensible &&
524
- 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)
525
615
  end
526
- merged_items = sel.items +
527
- extensions.flat_map { |x| x.underlying_type.items }
528
- seen_ids = {}
529
- uniq = merged_items.select do |item|
530
- key = item.id.safe_downcase
531
- next false if seen_ids.key?(key) || key.empty?
532
-
533
- 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
534
624
  end
535
- sel.items = uniq
536
- sel.extensible = false
537
- 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
538
628
 
539
- extensions.each do |ext|
540
- keep = ext.underlying_type.items.map { |i| i.id.upcase }
541
- exclusions = uniq.reject { |i| keep.include?(i.id.upcase) }
542
- base_ref = "#{@longform_name.upcase}.#{base.id.upcase}"
543
- ext.underlying_type = Model::DataTypes::Select.new(
544
- 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
+ ),
545
662
  )
546
- ext.where_rules = exclusions.each_with_index.map do |item, i|
547
- Model::Declarations::WhereRule.new(
548
- id: "wr#{i + 1}",
549
- expression: parse_expression("NOT ('#{base_ref}.#{item.id.upcase}' IN TYPEOF(SELF));"),
550
- )
551
- end
552
663
  end
664
+ ext_sel_items
553
665
  end
554
666
  end
555
667
 
@@ -565,10 +677,7 @@ module Expressir
565
677
  entity.supertype_expression =
566
678
  entity.supertype_expression ? combine_andor(entity.supertype_expression, expr) : expr
567
679
  end
568
- if entity.abstract
569
- # G.2.6: abstract entity ⇒ ABSTRACT SUPERTYPE (formatter
570
- # renders from abstract + supertype status).
571
- end
680
+ entity.abstract = true if sc.abstract
572
681
  next if Array(sc.total_over).empty?
573
682
 
574
683
  rule_name = "total_over_#{sc.id}"
@@ -3,6 +3,6 @@ module Expressir
3
3
  # autoload it (Ruby autoload only works for module/class constants, not
4
4
  # for string constants like `Expressir::VERSION`).
5
5
  module Version
6
- VERSION = "2.4.33".freeze
6
+ VERSION = "2.4.35".freeze
7
7
  end
8
8
  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.33
4
+ version: 2.4.35
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-10-15 00:00:00.000000000 Z
11
+ date: 2026-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys