expressir 2.3.7 → 2.4.1

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.bugs/01-stale-transformer-autoload.md +39 -0
  3. data/TODO.bugs/02-parser-class-instance-vars.md +36 -0
  4. data/TODO.bugs/03-builder-mutable-state.md +43 -0
  5. data/TODO.bugs/04-formatter-public-send-dispatch.md +53 -0
  6. data/TODO.bugs/05-anonymous-formatter-subclass.md +45 -0
  7. data/TODO.bugs/06-collection-registry-single-source.md +53 -0
  8. data/TODO.bugs/07-require-relative-cleanup.md +42 -0
  9. data/TODO.bugs/08-require-expressir-in-commands.md +34 -0
  10. data/TODO.bugs/09-parser-split.md +53 -0
  11. data/TODO.bugs/10-to-s-override.md +42 -0
  12. data/TODO.bugs/11-parser-class-variables.md +39 -0
  13. data/TODO.bugs/12-marker-modules-vs-registry.md +64 -0
  14. data/TODO.bugs/13-string-literal-scanner-limitation.md +52 -0
  15. data/TODO.bugs/14-model-formatting-leak.md +30 -0
  16. data/TODO.bugs/15-expression-children-macro.md +27 -0
  17. data/TODO.bugs/16-pretty-formatter-duplication.md +28 -0
  18. data/TODO.bugs/17-snake-case-cache-mutable-constant.md +28 -0
  19. data/TODO.bugs/18-const-get-private-constants.md +30 -0
  20. data/TODO.bugs/19-format-methods-public.md +22 -0
  21. data/TODO.bugs/20-coverage-nested-entities-dedup.md +20 -0
  22. data/TODO.bugs/21-operator-tokens-secondary-dispatch.md +21 -0
  23. data/TODO.bugs/22-builder-fast-path-wrappers.md +32 -0
  24. data/TODO.bugs/23-coverage-inverse-maps.md +21 -0
  25. data/TODO.bugs/24-streaming-builder-complexity.md +19 -0
  26. data/TODO.bugs/25-debug-puts-in-production.md +21 -0
  27. data/TODO.bugs/26-generic-entity-children-misplaced.md +21 -0
  28. data/TODO.bugs/27-package-build-god-method.md +19 -0
  29. data/TODO.bugs/28-package-god-class.md +30 -0
  30. data/TODO.bugs/29-validate-ascii-god-class.md +24 -0
  31. data/TODO.bugs/30-unicode-map-extraction.md +19 -0
  32. data/TODO.bugs/README.md +43 -0
  33. data/docs/_tutorials/formatting-schemas.adoc +89 -0
  34. data/docs/_tutorials/index.adoc +10 -0
  35. data/docs/lychee.toml +3 -0
  36. data/expressir.gemspec +4 -2
  37. data/lib/expressir/commands/changes_import_eengine.rb +0 -6
  38. data/lib/expressir/commands/changes_validate.rb +0 -2
  39. data/lib/expressir/commands/clean.rb +1 -1
  40. data/lib/expressir/commands/file_violations.rb +70 -0
  41. data/lib/expressir/commands/format.rb +1 -1
  42. data/lib/expressir/commands/non_ascii_character.rb +49 -0
  43. data/lib/expressir/commands/non_ascii_violation_collection.rb +301 -0
  44. data/lib/expressir/commands/package.rb +138 -185
  45. data/lib/expressir/commands/validate_ascii.rb +0 -510
  46. data/lib/expressir/commands/version.rb +1 -1
  47. data/lib/expressir/commands.rb +3 -0
  48. data/lib/expressir/coverage.rb +49 -117
  49. data/lib/expressir/express/builder.rb +74 -86
  50. data/lib/expressir/express/builder_context.rb +22 -0
  51. data/lib/expressir/express/builders/expression_builder.rb +16 -16
  52. data/lib/expressir/express/cache.rb +35 -8
  53. data/lib/expressir/express/error.rb +7 -0
  54. data/lib/expressir/express/formatter.rb +33 -18
  55. data/lib/expressir/express/formatters/declarations_formatter.rb +24 -2
  56. data/lib/expressir/express/formatters/remark_formatter.rb +75 -2
  57. data/lib/expressir/express/formatters/statements_formatter.rb +20 -3
  58. data/lib/expressir/express/grammar/parser.rb +705 -0
  59. data/lib/expressir/express/grammar.rb +11 -0
  60. data/lib/expressir/express/line_map.rb +48 -0
  61. data/lib/expressir/express/node_position_index.rb +215 -0
  62. data/lib/expressir/express/parser.rb +114 -884
  63. data/lib/expressir/express/pretty_formatter.rb +23 -5
  64. data/lib/expressir/express/remark_attacher.rb +483 -669
  65. data/lib/expressir/express/remark_scanner.rb +245 -0
  66. data/lib/expressir/express/schema_block_scanner.rb +136 -0
  67. data/lib/expressir/express/schema_source_formatter.rb +15 -0
  68. data/lib/expressir/express/scope_resolver.rb +194 -0
  69. data/lib/expressir/express/source_formatter.rb +15 -0
  70. data/lib/expressir/express/streaming_builder.rb +147 -176
  71. data/lib/expressir/express.rb +10 -6
  72. data/lib/expressir/model/concerns.rb +3 -0
  73. data/lib/expressir/model/data_types/generic_entity.rb +6 -6
  74. data/lib/expressir/model/declarations/entity.rb +5 -0
  75. data/lib/expressir/model/declarations/function.rb +5 -0
  76. data/lib/expressir/model/declarations/procedure.rb +5 -0
  77. data/lib/expressir/model/declarations/rule.rb +6 -0
  78. data/lib/expressir/model/declarations/schema.rb +21 -8
  79. data/lib/expressir/model/declarations/type.rb +3 -0
  80. data/lib/expressir/model/exp_file.rb +2 -0
  81. data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -0
  82. data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -0
  83. data/lib/expressir/model/expressions/binary_expression.rb +1 -0
  84. data/lib/expressir/model/expressions/entity_constructor.rb +1 -0
  85. data/lib/expressir/model/expressions/function_call.rb +1 -0
  86. data/lib/expressir/model/expressions/interval.rb +1 -0
  87. data/lib/expressir/model/expressions/query_expression.rb +2 -0
  88. data/lib/expressir/model/expressions/unary_expression.rb +1 -0
  89. data/lib/expressir/model/model_element.rb +128 -14
  90. data/lib/expressir/model/remark_format.rb +17 -0
  91. data/lib/expressir/model/remark_info.rb +31 -3
  92. data/lib/expressir/model/remark_placement.rb +29 -0
  93. data/lib/expressir/model/statements/alias.rb +2 -0
  94. data/lib/expressir/model/statements/assignment.rb +4 -0
  95. data/lib/expressir/model/statements/case.rb +21 -0
  96. data/lib/expressir/model/statements/case_action.rb +1 -0
  97. data/lib/expressir/model/statements/compound.rb +4 -0
  98. data/lib/expressir/model/statements/escape.rb +3 -0
  99. data/lib/expressir/model/statements/if.rb +5 -0
  100. data/lib/expressir/model/statements/null.rb +3 -0
  101. data/lib/expressir/model/statements/procedure_call.rb +3 -0
  102. data/lib/expressir/model/statements/repeat.rb +3 -0
  103. data/lib/expressir/model/statements/return.rb +3 -0
  104. data/lib/expressir/model/statements/skip.rb +3 -0
  105. data/lib/expressir/model.rb +12 -3
  106. data/lib/expressir/package/builder.rb +2 -2
  107. data/lib/expressir/version.rb +6 -1
  108. data/lib/expressir.rb +38 -3
  109. metadata +65 -5
  110. data/lib/expressir/express/transformer/remark_handling.rb +0 -194
@@ -112,15 +112,13 @@ module Expressir
112
112
  # @param key [String] The hash key name
113
113
  def on_hash_key(key)
114
114
  # Convert string key to symbol to match existing parser AST format
115
- puts "DEBUG on_hash_key: #{key.inspect} (#{key.class})" if ENV["DEBUG_STREAMING"]
116
115
  @pending_key = key.to_sym
117
116
  end
118
117
 
119
118
  # Called when a hash value is about to be parsed
120
119
  #
121
120
  # @param key [String] The hash key name
122
- def on_hash_value(key)
123
- puts "DEBUG on_hash_value: #{key.inspect} (#{key.class})" if ENV["DEBUG_STREAMING"]
121
+ def on_hash_value(_key)
124
122
  @pending_key = nil
125
123
  end
126
124
 
@@ -241,191 +239,165 @@ module Expressir
241
239
  def convert_ast_format(obj)
242
240
  case obj
243
241
  when Hash
244
- return obj if obj.empty?
245
-
246
- # Process each key-value pair
247
- result = {}
248
- obj.each do |key, value|
249
- # Check if this is a str or spaces key with an array value
250
- result[key] = if %i[str spaces].include?(key) && value.is_a?(Array)
251
- # Concatenate character arrays
252
- value.compact.join
253
- else
254
- # Recursively convert the value
255
- convert_ast_format(value)
256
- end
257
- end
258
- result
242
+ convert_hash_node(obj)
259
243
  when Array
260
- return obj if obj.empty?
261
-
262
- # First, convert each element
263
- converted_items = obj.map { |item| convert_ast_format(item) }
264
-
265
- # Flatten nested arrays that come from grammar repetition (.repeat)
266
- # These appear as arrays containing arrays (which may contain hashes)
267
- # This handles the case where (op_comma >> item).repeat produces [[{...}, {...}], [{...}, {...}]]
268
- # After inner conversion, this becomes [{multi-key hash}, {multi-key hash}]
269
- # which should be flattened into the parent array
270
- flattened_items = []
271
- converted_items.each do |item|
272
- if item.is_a?(Array)
273
- # Flatten this array into the parent
274
- flattened_items.concat(item)
275
- else
276
- flattened_items << item
277
- end
244
+ convert_array_node(obj)
245
+ else
246
+ obj
247
+ end
248
+ end
249
+
250
+ def convert_hash_node(hash)
251
+ return hash if hash.empty?
252
+
253
+ result = {}
254
+ hash.each do |key, value|
255
+ result[key] = if %i[str spaces].include?(key) && value.is_a?(Array)
256
+ value.compact.join
257
+ else
258
+ convert_ast_format(value)
259
+ end
260
+ end
261
+ result
262
+ end
263
+
264
+ def convert_array_node(array)
265
+ return array if array.empty?
266
+
267
+ converted_items = array.map { |item| convert_ast_format(item) }
268
+
269
+ # Flatten nested arrays from grammar repetition (.repeat)
270
+ flattened = flatten_nested_arrays(converted_items)
271
+
272
+ non_empty = flattened.reject { |item| item.nil? || (item.is_a?(Array) && item.empty?) }
273
+
274
+ if non_empty.empty?
275
+ {}
276
+ elsif non_empty.all?(Hash)
277
+ merge_or_preserve_sequence(non_empty, flattened)
278
+ else
279
+ flattened
280
+ end
281
+ end
282
+
283
+ def flatten_nested_arrays(items)
284
+ result = []
285
+ items.each do |item|
286
+ if item.is_a?(Array)
287
+ result.concat(item)
288
+ else
289
+ result << item
278
290
  end
279
- converted_items = flattened_items
291
+ end
292
+ result
293
+ end
294
+
295
+ def merge_or_preserve_sequence(non_empty, converted)
296
+ # Check for repetition patterns that should stay as arrays
297
+ return converted if same_key_repetition?(non_empty)
298
+ return converted if duplicate_key_repetition?(non_empty)
299
+
300
+ # Check for grouped repetition that needs unzipping
301
+ unzipped = try_unzip_repetition(non_empty)
302
+ return unzipped if unzipped
303
+
304
+ # Default: merge all single-key hashes into one hash
305
+ merge_sequence_hashes(non_empty)
306
+ end
280
307
 
281
- # Check if this is an array of hashes (Parsanol sequence format)
282
- # that should be merged into a single hash.
283
- # Empty arrays and nil values are treated as nil/absent optional elements
284
- non_empty_items = converted_items.reject do |item|
285
- item.nil? || (item.is_a?(Array) && item.empty?)
308
+ def same_key_repetition?(items)
309
+ first_keys = items.first.keys
310
+ first_keys.length == 1 &&
311
+ items.length > 1 &&
312
+ items.all? { |item| item.keys.length == 1 && item.keys.first == first_keys.first }
313
+ end
314
+
315
+ def duplicate_key_repetition?(items)
316
+ all_keys = items.flat_map(&:keys)
317
+ key_counts = all_keys.tally
318
+ key_counts.any? { |_, count| count > 1 }
319
+ end
320
+
321
+ def try_unzip_repetition(items)
322
+ return nil unless items.length >= 2
323
+
324
+ first_item = items[0]
325
+ later_items = items[1..]
326
+ return nil unless first_item.keys.length == 1 && !first_item.values.first.is_a?(Array)
327
+
328
+ first_key = first_item.keys.first
329
+ repetition_item = later_items.find do |item|
330
+ item.key?(first_key) &&
331
+ item[first_key].is_a?(Array) &&
332
+ item[first_key].length > 1 &&
333
+ item.keys.length >= 2
334
+ end
335
+ return nil unless repetition_item
336
+
337
+ unzip_grouped_repetition(first_item, later_items)
338
+ end
339
+
340
+ def unzip_grouped_repetition(first_item, later_items)
341
+ result = [first_item]
342
+
343
+ later_items.each do |item|
344
+ array_keys = item.select { |_, v| v.is_a?(Array) && v.length > 1 }.keys
345
+
346
+ if array_keys.empty?
347
+ result << item
348
+ elsif array_keys.length == 1
349
+ expand_single_array_key(item, array_keys.first, result)
350
+ else
351
+ expand_multiple_array_keys(item, array_keys, result)
286
352
  end
353
+ end
287
354
 
288
- if non_empty_items.empty?
289
- # All items were nil or empty - return empty hash (sequence with all optionals absent)
290
- {}
291
- elsif non_empty_items.all?(Hash)
292
- # Check if this is a repetition pattern where all hashes have the SAME single key
293
- first_keys = non_empty_items.first.keys
294
-
295
- if first_keys.length == 1 &&
296
- non_empty_items.length > 1 &&
297
- non_empty_items.all? do |item|
298
- item.keys.length == 1 && item.keys.first == first_keys.first
299
- end
300
- # Multiple items with the same single key - this is a repetition pattern
301
- # Keep as array (each element is already a single-key hash)
302
- return converted_items
303
- end
355
+ result
356
+ end
304
357
 
305
- # Check if any key appears in MULTIPLE hashes (repetition with separators pattern)
306
- # Example: [{formalParameter: {...}}, {op_delim: {...}}, {formalParameter: {...}}]
307
- # In this case, formalParameter appears in two different hashes
308
- all_keys = non_empty_items.flat_map(&:keys)
309
- key_counts = all_keys.tally
310
- if key_counts.any? { |_, count| count > 1 }
311
- # At least one key appears multiple times - this is a repetition pattern
312
- # Keep as array
313
- return converted_items
314
- end
358
+ def expand_single_array_key(item, key, result)
359
+ values = item[key]
360
+ other_keys = item.keys - [key]
315
361
 
316
- # Check if we have a repetition pattern that needs unzipping
317
- # Pattern: [{item: first}, {separator: [...], item: [rest...]}]
318
- # This happens when Parsanol groups repeated elements
319
-
320
- # More specific detection:
321
- # 1. First item has a single key with a non-array value
322
- # 2. A later item has the SAME key with an array value (the grouped repetitions)
323
- # 3. The later item also has a "separator" key (like op_comma)
324
-
325
- if non_empty_items.length >= 2
326
- first_item = non_empty_items[0]
327
- later_items = non_empty_items[1..]
328
-
329
- # Check if first item is simple (single key, non-array value)
330
- if first_item.keys.length == 1 && !first_item.values.first.is_a?(Array)
331
- first_key = first_item.keys.first
332
-
333
- # Look for a later item that:
334
- # 1. Has the same key as the first item
335
- # 2. That key has an array value with length > 1
336
- # 3. Also has at least one other key (the separator)
337
- repetition_item = later_items.find do |item|
338
- item.key?(first_key) &&
339
- item[first_key].is_a?(Array) &&
340
- item[first_key].length > 1 &&
341
- item.keys.length >= 2
342
- end
343
-
344
- if repetition_item
345
- # This is a repetition pattern - unzip it
346
- # Build interleaved result
347
- result = [first_item]
348
-
349
- later_items.each do |item|
350
- # Find keys with array values
351
- array_keys = item.select do |_, v|
352
- v.is_a?(Array) && v.length > 1
353
- end.keys
354
-
355
- if array_keys.empty?
356
- # No array values - add as-is
357
- result << item
358
- elsif array_keys.length == 1
359
- # Single array key - expand it
360
- key = array_keys.first
361
- values = item[key]
362
- other_keys = item.keys - [key]
363
-
364
- values.each_with_index do |val, _idx|
365
- new_item = { key => val }
366
- other_keys.each do |ok|
367
- new_item[ok] = item[ok]
368
- end
369
- result << new_item
370
- end
371
- else
372
- # Multiple array keys - interleave them
373
- # All arrays should have the same length
374
- len = array_keys.map { |k| item[k].length }.max
375
- other_keys = item.keys - array_keys
376
-
377
- len.times do |idx|
378
- new_item = {}
379
- array_keys.each do |k|
380
- values = item[k]
381
- new_item[k] = values[idx] if idx < values.length
382
- end
383
- other_keys.each do |ok|
384
- new_item[ok] = item[ok]
385
- end
386
- result << new_item
387
- end
388
- end
389
- end
362
+ values.each do |val|
363
+ new_item = { key => val }
364
+ other_keys.each { |ok| new_item[ok] = item[ok] }
365
+ result << new_item
366
+ end
367
+ end
390
368
 
391
- return result
392
- end
393
- end
394
- end
369
+ def expand_multiple_array_keys(item, array_keys, result)
370
+ len = array_keys.map { |k| item[k].length }.max
371
+ other_keys = item.keys - array_keys
395
372
 
396
- # No repetition pattern detected - merge all hashes into one hash
397
- # This handles sequences like: tENUMERATION >> tOF >> enumerationItems
398
- # which produce: [{tENUMERATION: ...}, {tOF: ..., enumerationItems: ...}]
399
- merged = {}
400
- non_empty_items.each do |item|
401
- item.each do |key, value|
402
- # If key already exists, convert to array or append
403
- if merged.key?(key)
404
- existing = merged[key]
405
- if existing.is_a?(Array)
406
- if value.is_a?(Array)
407
- existing.concat(value)
408
- else
409
- existing << value
410
- end
411
- else
412
- merged[key] = [existing, value]
413
- end
414
- else
415
- merged[key] = value
416
- end
373
+ len.times do |idx|
374
+ new_item = {}
375
+ array_keys.each do |k|
376
+ values = item[k]
377
+ new_item[k] = values[idx] if idx < values.length
378
+ end
379
+ other_keys.each { |ok| new_item[ok] = item[ok] }
380
+ result << new_item
381
+ end
382
+ end
383
+
384
+ def merge_sequence_hashes(items)
385
+ merged = {}
386
+ items.each do |item|
387
+ item.each do |key, value|
388
+ if merged.key?(key)
389
+ existing = merged[key]
390
+ if existing.is_a?(Array)
391
+ existing.is_a?(Array) && value.is_a?(Array) ? existing.concat(value) : existing << value
392
+ else
393
+ merged[key] = [existing, value]
417
394
  end
395
+ else
396
+ merged[key] = value
418
397
  end
419
-
420
- # Process the merged hash for :str/:spaces concatenation
421
- process_str_and_spaces(merged)
422
- else
423
- # Regular array - return the converted items
424
- converted_items
425
398
  end
426
- else
427
- obj
428
399
  end
400
+ process_str_and_spaces(merged)
429
401
  end
430
402
 
431
403
  # Add a value to the current container on top of the stack
@@ -445,7 +417,6 @@ module Expressir
445
417
  else
446
418
  # This handles unexpected container types (debugging)
447
419
  if ENV["DEBUG_STREAMING"]
448
- puts "DEBUG: Unknown container type: #{container.class}"
449
420
  puts " Container value: #{container.inspect[0..200]}"
450
421
  puts " Pending key: #{@pending_key.inspect}"
451
422
  puts " Stack depth: #{@stack.length}"
@@ -3,27 +3,31 @@
3
3
  module Expressir
4
4
  module Express
5
5
  autoload :Builder, "#{__dir__}/express/builder"
6
+ autoload :BuilderContext, "#{__dir__}/express/builder_context"
7
+ autoload :BuilderRegistry, "#{__dir__}/express/builder_registry"
6
8
  autoload :Builders, "#{__dir__}/express/builders"
7
- # autoload :BuilderRegistry, "#{__dir__}/express/builder_registry"
8
9
 
9
10
  # Core classes (autoloaded for lazy loading)
10
11
  autoload :Cache, "#{__dir__}/express/cache"
11
12
  autoload :Error, "#{__dir__}/express/error"
12
13
  autoload :Formatter, "#{__dir__}/express/formatter"
13
14
  autoload :Formatters, "#{__dir__}/express/formatters"
15
+ autoload :Grammar, "#{__dir__}/express/grammar"
14
16
  autoload :HyperlinkFormatter, "#{__dir__}/express/hyperlink_formatter"
17
+ autoload :LineMap, "#{__dir__}/express/line_map"
15
18
  autoload :ModelVisitor, "#{__dir__}/express/model_visitor"
19
+ autoload :NodePositionIndex, "#{__dir__}/express/node_position_index"
16
20
  autoload :Parser, "#{__dir__}/express/parser"
17
21
  autoload :PrettyFormatter, "#{__dir__}/express/pretty_formatter"
18
22
  autoload :RemarkAttacher, "#{__dir__}/express/remark_attacher"
23
+ autoload :RemarkScanner, "#{__dir__}/express/remark_scanner"
19
24
  autoload :ResolveReferencesModelVisitor,
20
25
  "#{__dir__}/express/resolve_references_model_visitor"
26
+ autoload :SchemaBlockScanner, "#{__dir__}/express/schema_block_scanner"
21
27
  autoload :SchemaHeadFormatter, "#{__dir__}/express/schema_head_formatter"
28
+ autoload :SchemaSourceFormatter, "#{__dir__}/express/schema_source_formatter"
29
+ autoload :ScopeResolver, "#{__dir__}/express/scope_resolver"
30
+ autoload :SourceFormatter, "#{__dir__}/express/source_formatter"
22
31
  autoload :StreamingBuilder, "#{__dir__}/express/streaming_builder"
23
-
24
- autoload :Transformer, "#{__dir__}/express/transformer"
25
32
  end
26
33
  end
27
-
28
- # Eagerly load builders (they register themselves at load time)
29
- require_relative "express/builder_registry"
@@ -21,5 +21,8 @@ module Expressir
21
21
 
22
22
  # Marker for types supporting where rules
23
23
  module HasWhereRules; end
24
+
25
+ # Marker for executable statements (ISO 10303-11 section 13)
26
+ module Statement; end
24
27
  end
25
28
  end
@@ -11,13 +11,13 @@ module Expressir
11
11
  key_value do
12
12
  map "_class", to: :_class, render_default: true
13
13
  end
14
- end
15
14
 
16
- # @return [Array<Declaration>]
17
- def children
18
- [
19
- *remark_items,
20
- ]
15
+ # @return [Array<Declaration>]
16
+ def children
17
+ [
18
+ *remark_items,
19
+ ]
20
+ end
21
21
  end
22
22
  end
23
23
  end
@@ -9,6 +9,11 @@ module Expressir
9
9
  include HasInformalPropositions
10
10
  include HasWhereRules
11
11
 
12
+ collection_attributes :attributes, :derived_attributes,
13
+ :inverse_attributes, :unique_rules,
14
+ :where_rules, :informal_propositions,
15
+ :remark_items
16
+
12
17
  attribute :abstract, :boolean
13
18
  attribute :supertype_expression, ModelElement
14
19
  attribute :subtype_of, ModelElement, collection: true
@@ -7,6 +7,11 @@ module Expressir
7
7
  include Identifier
8
8
  include ScopeContainer
9
9
 
10
+ collection_attributes :parameters, :types, :entities,
11
+ :subtype_constraints, :functions, :procedures,
12
+ :constants, :variables, :statements,
13
+ :remark_items
14
+
10
15
  attribute :parameters, Parameter, collection: true
11
16
  attribute :return_type, ModelElement
12
17
  attribute :types, ModelElement, collection: true
@@ -7,6 +7,11 @@ module Expressir
7
7
  include Identifier
8
8
  include ScopeContainer
9
9
 
10
+ collection_attributes :parameters, :types, :entities,
11
+ :subtype_constraints, :functions, :procedures,
12
+ :constants, :variables, :statements,
13
+ :remark_items
14
+
10
15
  attribute :parameters, Parameter, collection: true
11
16
  attribute :types, ModelElement, collection: true
12
17
  attribute :entities, Entity, collection: true
@@ -9,6 +9,12 @@ module Expressir
9
9
  include HasInformalPropositions
10
10
  include HasWhereRules
11
11
 
12
+ collection_attributes :applies_to, :types, :entities,
13
+ :subtype_constraints, :functions, :procedures,
14
+ :constants, :variables, :statements,
15
+ :where_rules, :informal_propositions,
16
+ :remark_items
17
+
12
18
  attribute :applies_to, ModelElement, collection: true
13
19
  attribute :types, ModelElement, collection: true
14
20
  attribute :entities, Entity, collection: true
@@ -7,6 +7,22 @@ module Expressir
7
7
  include Identifier
8
8
  include ScopeContainer
9
9
 
10
+ # Tree-walker collection attributes (TODO.bugs/12 — single source
11
+ # of truth; NodePositionIndex derives COLLECTION_REGISTRY from this).
12
+ collection_attributes :constants, :types, :entities,
13
+ :subtype_constraints, :functions, :rules,
14
+ :procedures, :remark_items
15
+
16
+ # Subset of this schema's collection attributes that hold named
17
+ # scope declarations (Function, Procedure, Rule, Entity, Type).
18
+ # Used by ScopeResolver to enumerate scope-capable children without
19
+ # duplicating the list. Single source of truth: this is the subset
20
+ # of NodePositionIndex::COLLECTION_REGISTRY[Schema] that holds
21
+ # scopes rather than remark_items / constants / subtype_constraints.
22
+ SCOPE_DECL_COLLECTIONS = %i[
23
+ functions procedures rules entities types
24
+ ].freeze
25
+
10
26
  attribute :file, :string
11
27
  attribute :version, SchemaVersion
12
28
  attribute :interfaces, Interface, collection: true
@@ -17,6 +33,7 @@ module Expressir
17
33
  attribute :functions, Function, collection: true
18
34
  attribute :rules, Rule, collection: true
19
35
  attribute :procedures, Procedure, collection: true
36
+ attribute :header, :string
20
37
  attribute :_class, :string, default: -> { self.class.name }
21
38
  attribute :selected, :boolean, default: false
22
39
  attribute :file_basename, :string
@@ -25,6 +42,7 @@ module Expressir
25
42
  map "_class", to: :_class, render_default: true
26
43
  map "id", to: :id
27
44
  map "file", to: :file
45
+ map "header", to: :header
28
46
  map "remarks", to: :remarks
29
47
  map "remark_items", to: :remark_items
30
48
  map "version", to: :version
@@ -39,6 +57,7 @@ module Expressir
39
57
  end
40
58
 
41
59
  liquid do
60
+ map :header, to: :header
42
61
  map :formatted, to: :formatted
43
62
  map :source, to: :source
44
63
  map :full_source, to: :full_source
@@ -74,17 +93,11 @@ module Expressir
74
93
  end
75
94
 
76
95
  def formatted
77
- @formatted ||= to_s(no_remarks: true)
96
+ @formatted ||= format(no_remarks: false)
78
97
  end
79
98
 
80
99
  def source
81
- @source ||= begin
82
- formatter = Class.new(Expressir::Express::Formatter) do
83
- include Expressir::Express::SchemaHeadFormatter
84
- include Expressir::Express::HyperlinkFormatter
85
- end
86
- formatter.format(self)
87
- end
100
+ @source ||= Expressir::Express::SchemaSourceFormatter.format(self)
88
101
  end
89
102
 
90
103
  private
@@ -9,6 +9,9 @@ module Expressir
9
9
  include HasInformalPropositions
10
10
  include HasWhereRules
11
11
 
12
+ collection_attributes :where_rules, :informal_propositions,
13
+ :remark_items
14
+
12
15
  attribute :underlying_type, ModelElement
13
16
  attribute :where_rules, WhereRule, collection: true
14
17
  attribute :informal_propositions, InformalPropositionRule,
@@ -14,6 +14,8 @@ module Expressir
14
14
  include HasId
15
15
  include ScopeContainer
16
16
 
17
+ collection_attributes :schemas
18
+
17
19
  attribute :path, :string
18
20
  attribute :schemas, Declarations::Schema, collection: true,
19
21
  initialize_empty: true
@@ -4,6 +4,7 @@ module Expressir
4
4
  # Specified in ISO 10303-11:2004
5
5
  # - section 12.9 Aggregate initializer
6
6
  class AggregateInitializer < ModelElement
7
+ child_attributes :items
7
8
  attribute :items, ModelElement, collection: true
8
9
  attribute :_class, :string, default: -> { self.class.name }
9
10
 
@@ -4,6 +4,7 @@ module Expressir
4
4
  # Specified in ISO 10303-11:2004
5
5
  # - section 12.9 Aggregate initializer
6
6
  class AggregateInitializerItem < ModelElement
7
+ child_attributes :expression, :repetition
7
8
  attribute :expression, ModelElement
8
9
  attribute :repetition, ModelElement
9
10
  attribute :_class, :string, default: -> { self.class.name }
@@ -10,6 +10,7 @@ module Expressir
10
10
  # - section 12.6 Aggregate operators
11
11
  # - section 12.10 Complex entity instance construction operator
12
12
  class BinaryExpression < ModelElement
13
+ child_attributes :operand1, :operand2
13
14
  ADDITION = "ADDITION".freeze
14
15
  AND = "AND".freeze
15
16
  COMBINE = "COMBINE".freeze
@@ -4,6 +4,7 @@ module Expressir
4
4
  # Specified in ISO 10303-11:2004
5
5
  # - section 9.2.6 Implicit declarations
6
6
  class EntityConstructor < ModelElement
7
+ child_attributes :parameters
7
8
  attribute :entity, ModelElement
8
9
  attribute :parameters, ModelElement, collection: true
9
10
  attribute :_class, :string, default: -> { self.class.name }
@@ -4,6 +4,7 @@ module Expressir
4
4
  # Specified in ISO 10303-11:2004
5
5
  # - section 12.8 Function call
6
6
  class FunctionCall < ModelElement
7
+ child_attributes :parameters
7
8
  attribute :function, ModelElement
8
9
  attribute :parameters, ModelElement, collection: true
9
10
  attribute :_class, :string, default: -> { self.class.name }
@@ -4,6 +4,7 @@ module Expressir
4
4
  # Specified in ISO 10303-11:2004
5
5
  # - section 12.2.4 Interval expressions
6
6
  class Interval < ModelElement
7
+ child_attributes :low, :item, :high
7
8
  LESS_THAN = "LESS_THAN".freeze
8
9
  LESS_THAN_OR_EQUAL = "LESS_THAN_OR_EQUAL".freeze
9
10
 
@@ -6,6 +6,8 @@ module Expressir
6
6
  class QueryExpression < ModelElement
7
7
  include Identifier
8
8
 
9
+ child_attributes :expression, :aggregate_source
10
+
9
11
  attribute :aggregate_source, ModelElement
10
12
  attribute :expression, ModelElement
11
13
  attribute :_class, :string, default: -> { self.class.name }
@@ -5,6 +5,7 @@ module Expressir
5
5
  # - section 12.1 Arithmetic operators
6
6
  # - section 12.4.1 NOT operator
7
7
  class UnaryExpression < ModelElement
8
+ child_attributes :operand
8
9
  MINUS = "MINUS".freeze
9
10
  NOT = "NOT".freeze
10
11
  PLUS = "PLUS".freeze