expressir 2.4.3 → 2.4.4

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/.cargo/config.toml +3 -0
  3. data/.github/workflows/rust-ext.yml +41 -0
  4. data/.github/workflows/verify_remarks.yml +33 -0
  5. data/.gitignore +6 -0
  6. data/.rubocop.yml +4 -0
  7. data/.rubocop_todo.yml +1 -0
  8. data/Cargo.toml +7 -0
  9. data/README.adoc +65 -0
  10. data/Rakefile +12 -0
  11. data/TODO.parity-ee/00-overview.md +58 -0
  12. data/TODO.parity-ee/01-annex-g-rule-extraction.md +41 -0
  13. data/TODO.parity-ee/02-eeng-algorithm-inventory.md +8 -0
  14. data/TODO.parity-ee/03-eeng-oracle-harness.md +33 -0
  15. data/TODO.parity-ee/04-shtolo-concatenate.md +44 -0
  16. data/TODO.parity-ee/05-shtolo-longform-flatten.md +53 -0
  17. data/TODO.parity-ee/06-interface-scheduling-parity.md +47 -0
  18. data/TODO.parity-ee/07-semantic-checks-port.md +35 -0
  19. data/TODO.parity-ee/08-pretty-roundtrip-gate.md +27 -0
  20. data/TODO.parity-ee/09-smrl-index-and-listing.md +22 -0
  21. data/TODO.parity-ee/10-interface-dot-graph.md +21 -0
  22. data/TODO.parity-ee/11-import-eeng-tests.md +79 -0
  23. data/TODO.parity-ee/parity-matrix.md +108 -0
  24. data/exe/expressir +6 -0
  25. data/ext/expressir_core/Cargo.toml +24 -0
  26. data/ext/expressir_core/extconf.rb +30 -0
  27. data/ext/expressir_core/src/lib.rs +446 -0
  28. data/lib/expressir/cli.rb +49 -4
  29. data/lib/expressir/commands/check.rb +56 -0
  30. data/lib/expressir/commands/expand.rb +26 -0
  31. data/lib/expressir/commands/fix.rb +33 -0
  32. data/lib/expressir/commands/flatten.rb +29 -0
  33. data/lib/expressir/commands/parity_inputs.rb +85 -0
  34. data/lib/expressir/commands/validate.rb +22 -0
  35. data/lib/expressir/commands.rb +5 -0
  36. data/lib/expressir/express/builder_registry.rb +11 -2
  37. data/lib/expressir/express/builders/entity_decl_builder.rb +17 -7
  38. data/lib/expressir/express/builders/subtype_constraint_builder.rb +3 -1
  39. data/lib/expressir/express/builders/type_builder.rb +8 -0
  40. data/lib/expressir/express/builders/unique_rule_builder.rb +8 -1
  41. data/lib/expressir/express/checker.rb +458 -0
  42. data/lib/expressir/express/concatenator.rb +79 -0
  43. data/lib/expressir/express/core.rb +66 -0
  44. data/lib/expressir/express/formatter.rb +25 -3
  45. data/lib/expressir/express/formatters/data_types_formatter.rb +2 -2
  46. data/lib/expressir/express/formatters/declarations_formatter.rb +1 -0
  47. data/lib/expressir/express/formatters/remark_formatter.rb +30 -6
  48. data/lib/expressir/express/formatters/statements_formatter.rb +7 -1
  49. data/lib/expressir/express/interface_dot.rb +105 -0
  50. data/lib/expressir/express/listing.rb +174 -0
  51. data/lib/expressir/express/model_traversal.rb +42 -0
  52. data/lib/expressir/express/node_position_index.rb +18 -4
  53. data/lib/expressir/express/parser.rb +242 -4
  54. data/lib/expressir/express/pretty_gate.rb +61 -0
  55. data/lib/expressir/express/refs_overlay.rb +47 -0
  56. data/lib/expressir/express/remark_attacher.rb +122 -10
  57. data/lib/expressir/express/remark_overlay.rb +86 -0
  58. data/lib/expressir/express/schema_plain_source_formatter.rb +11 -0
  59. data/lib/expressir/express/schema_source_formatter.rb +5 -5
  60. data/lib/expressir/express/self_schema_reference.rb +126 -0
  61. data/lib/expressir/express/shtolo.rb +522 -0
  62. data/lib/expressir/express.rb +13 -0
  63. data/lib/expressir/model/concerns.rb +15 -1
  64. data/lib/expressir/model/declarations/entity.rb +6 -1
  65. data/lib/expressir/model/declarations/interface.rb +13 -0
  66. data/lib/expressir/model/declarations/schema.rb +74 -23
  67. data/lib/expressir/model/indexes/item_graph.rb +135 -0
  68. data/lib/expressir/model/indexes.rb +1 -0
  69. data/lib/expressir/model/model_element.rb +30 -1
  70. data/lib/expressir/model/remark_info.rb +20 -1
  71. data/lib/expressir/model/remark_placement.rb +15 -2
  72. data/lib/expressir/model/repository.rb +8 -2
  73. data/lib/expressir/model.rb +2 -1
  74. data/lib/expressir/version.rb +1 -1
  75. data/lib/tasks/verify_remarks.rake +9 -0
  76. data/rakelib/verify_remarks.rake +16 -0
  77. metadata +42 -2
@@ -0,0 +1,85 @@
1
+ require "find"
2
+
3
+ module Expressir
4
+ module Commands
5
+ # Common parser/discovery helpers shared by parity CLI commands
6
+ # (expand, flatten, check) — they all need: list of files in the
7
+ # interface closure of a root file, and the root schema parsed.
8
+ module ParityInputs
9
+ module_function
10
+
11
+ # Resolve the transitive interface closure of +root_path+. Deps are
12
+ # looked up by schema name: first in the file's own directory, then
13
+ # across the enclosing STEPmod checkout (the nearest ancestor holding
14
+ # a `schemas/` directory, e.g. resources/ and modules/ trees).
15
+ def closure_paths(root_path, manifest: nil, stepmod: nil)
16
+ index = schema_index(root_path)
17
+ seen = { File.basename(root_path, ".exp").downcase => root_path }
18
+ queue = [File.basename(root_path, ".exp").downcase]
19
+ until queue.empty?
20
+ name = queue.shift
21
+ path = seen[name] || index[name]
22
+ next unless path
23
+ next if seen.key?(name) && seen[name] != root_path
24
+
25
+ seen[name] = path
26
+ closure_names(File.read(path)).each { |d| queue << d.downcase }
27
+ end
28
+ seen.values.uniq
29
+ end
30
+
31
+ # Schema-name → path map for the STEPmod checkout around +root_path+,
32
+ # built once per invocation. The index root is the nearest ancestor
33
+ # directory NAMED `schemas` (wg12-step layout:
34
+ # `schemas/modules/<module>/mim.exp`), and entries key on the schema
35
+ # name declared inside each file — module files are `arm.exp` /
36
+ # `mim.exp`, so basenames cannot be the key. Falls back to the file's
37
+ # own directory when there is no schemas/ ancestor.
38
+ def schema_index(root_path)
39
+ dir = File.dirname(File.expand_path(root_path))
40
+ until dir == "/" || File.basename(dir) == "schemas"
41
+ parent = File.dirname(dir)
42
+ break if parent == dir
43
+
44
+ dir = parent
45
+ end
46
+ return same_dir_index(root_path) unless File.basename(dir) == "schemas"
47
+
48
+ @@indexes ||= {}
49
+ @@indexes[dir] ||= begin
50
+ h = {}
51
+ Find.find(dir) do |path|
52
+ next unless path.end_with?(".exp")
53
+
54
+ declared = File.read(path)[/\bSCHEMA\s+(\w+)/i, 1]
55
+ h[declared.downcase] = path if declared
56
+ h[File.basename(path, ".exp").downcase] ||= path
57
+ end
58
+ h
59
+ end
60
+ end
61
+
62
+ def same_dir_index(root_path)
63
+ dir = File.dirname(File.expand_path(root_path))
64
+ Dir.glob(File.join(dir, "*.exp")).to_h do |p|
65
+ [File.basename(p, ".exp").downcase, p]
66
+ end
67
+ end
68
+
69
+ def closure_names(source)
70
+ source.scan(/\bUSE\s+FROM\s+(\w+)/i).flatten.uniq +
71
+ source.scan(/\bREFERENCE\s+FROM\s+(\w+)/i).flatten.uniq
72
+ end
73
+
74
+ # manifest:/stepmod: are accepted for the ELF-manifest resolver
75
+ # contract; the directory-index fallback ignores them until that
76
+ # resolver lands.
77
+ def root_schema(root_path, manifest: nil, stepmod: nil)
78
+ name = File.read(root_path)[/\bSCHEMA\s+(\w+)/, 1]
79
+ cp = closure_paths(root_path)
80
+ repo = Expressir::Express::Parser.from_files(cp, max_processes: 1)
81
+ [repo.schemas.find { |s| s.id == name }, repo]
82
+ end
83
+ end
84
+ end
85
+ end
@@ -79,6 +79,28 @@ module Expressir
79
79
  def ascii(path)
80
80
  ValidateAscii.new(options).run(path)
81
81
  end
82
+
83
+ desc "check *PATHS", "Run eeng check-p11 semantic checks"
84
+ long_desc <<~DESC
85
+ Walks one or more EXPRESS files (or directories of .exp) and
86
+ reports the eeng check-p11 note subset covered by Checker:
87
+ redundant interfaces, duplicate resources, unresolved interface
88
+ schema/ref, SUBTYPE OF targets, SELECT/ENUM BASED_ON, and
89
+ WHERE/UNIQUE label patterns.
90
+
91
+ Exits 1 when any error-severity note fires.
92
+ DESC
93
+ method_option :json, type: :boolean, default: false,
94
+ desc: "Emit the report as JSON (errors + warnings)"
95
+ method_option :manifest, type: :string,
96
+ desc: "ELF schema manifest YAML defining where schemas live " \
97
+ "(primary resolution mode)"
98
+ method_option :stepmod, type: :string,
99
+ desc: "STEPmod checkout root — explicit opt-in to directory-" \
100
+ "convention resolution as the fallback"
101
+ def check(*paths)
102
+ Check.new(options).run(*paths)
103
+ end
82
104
  end
83
105
  end
84
106
  end
@@ -4,9 +4,14 @@ module Expressir
4
4
  autoload :Base, "#{__dir__}/commands/base"
5
5
  autoload :Benchmark, "#{__dir__}/commands/benchmark"
6
6
  autoload :BenchmarkCache, "#{__dir__}/commands/benchmark_cache"
7
+ autoload :Check, "#{__dir__}/commands/check"
7
8
  autoload :Clean, "#{__dir__}/commands/clean"
8
9
  autoload :Coverage, "#{__dir__}/commands/coverage"
10
+ autoload :Expand, "#{__dir__}/commands/expand"
11
+ autoload :Fix, "#{__dir__}/commands/fix"
12
+ autoload :Flatten, "#{__dir__}/commands/flatten"
9
13
  autoload :Format, "#{__dir__}/commands/format"
14
+ autoload :ParityInputs, "#{__dir__}/commands/parity_inputs"
10
15
  autoload :Validate, "#{__dir__}/commands/validate"
11
16
  autoload :ValidateLoad, "#{__dir__}/commands/validate_load"
12
17
  autoload :ValidateAscii, "#{__dir__}/commands/validate_ascii"
@@ -325,13 +325,22 @@ module Expressir
325
325
 
326
326
  # Simple types
327
327
  Builder.register(:boolean_type) { |d| type_builder.build_boolean_type(d) }
328
+ # GENERIC[: label] / GENERIC_ENTITY[: label] — ISO 10303-11
329
+ # 9.5.3.2 type label (GH-339).
330
+ def self.type_label_id(ast_data)
331
+ label = ast_data.is_a?(Hash) ? ast_data[:type_label] : nil
332
+ return nil unless label.is_a?(Hash)
333
+
334
+ Builder.build_optional({ simple_id: label[:type_label_id][:simple_id] })
335
+ end
336
+
328
337
  Builder.register(:integer_type) { |d| type_builder.build_integer_type(d) }
329
338
  Builder.register(:logical_type) { |d| type_builder.build_logical_type(d) }
330
339
  Builder.register(:number_type) { |d| type_builder.build_number_type(d) }
331
340
 
332
341
  # Type constructors
333
- Builder.register(:generic_type) { |_d| Expressir::Model::DataTypes::Generic.new }
334
- Builder.register(:generic_entity_type) { |_d| Expressir::Model::DataTypes::GenericEntity.new }
342
+ Builder.register(:generic_type) { |d| Expressir::Model::DataTypes::Generic.new(id: BuilderRegistry.type_label_id(d)) }
343
+ Builder.register(:generic_entity_type) { |d| Expressir::Model::DataTypes::GenericEntity.new(id: BuilderRegistry.type_label_id(d)) }
335
344
  Builder.register(:aggregate_type) do |d|
336
345
  type_builder.build_aggregate_type(d)
337
346
  end
@@ -46,13 +46,22 @@ module Expressir
46
46
 
47
47
  if subtype_declaration.is_a?(Hash)
48
48
  list_ref = subtype_declaration[:list_of_entity_ref]
49
- if list_ref.is_a?(Hash)
50
- entity_ref_data = list_ref[:entity_ref]
51
- if entity_ref_data
52
- subtype_of = Builder.build_children(Builder.ensure_array(entity_ref_data).map do |d|
53
- { entity_ref: d }
54
- end)
49
+ # SUBTYPE OF (a) flattens to a Hash; SUBTYPE OF (a, b) to an
50
+ # Array of single-key fragments (entity_ref possibly merged
51
+ # with its op_comma). Collect entity_ref from either shape
52
+ # (lutaml/expressir#341).
53
+ raw_entity_refs =
54
+ if list_ref.is_a?(Hash)
55
+ ref = list_ref[:entity_ref]
56
+ ref.is_a?(Array) ? ref : [ref]
57
+ elsif list_ref.is_a?(Array)
58
+ list_ref.filter_map { |el| el[:entity_ref] if el.is_a?(Hash) }
59
+ .flat_map { |d| d.is_a?(Array) ? d : [d] }
55
60
  end
61
+ if raw_entity_refs && !raw_entity_refs.empty?
62
+ subtype_of = Builder.build_children(raw_entity_refs.map do |d|
63
+ { entity_ref: d }
64
+ end)
56
65
  end
57
66
  end
58
67
  end
@@ -78,7 +87,8 @@ module Expressir
78
87
  end
79
88
 
80
89
  if entity_body[:unique_clause]
81
- unique_rules = Builder.build({ unique_clause: entity_body[:unique_clause] })
90
+ built = Builder.build({ unique_clause: entity_body[:unique_clause] })
91
+ unique_rules = Array(built).compact
82
92
  end
83
93
  unique_rules ||= []
84
94
 
@@ -21,7 +21,9 @@ module Expressir
21
21
  if body.is_a?(Hash)
22
22
  abstract = !body[:abstract_supertype].nil?
23
23
  if body[:total_over].is_a?(Hash)
24
- total_over = Builder.build_children(Builder.ensure_array(body[:total_over][:entity_ref]))
24
+ refs = body[:total_over][:list_of_entity_ref] ||
25
+ body[:total_over][:entity_ref]
26
+ total_over = Builder.build_children(Builder.ensure_array(refs))
25
27
  end
26
28
  # Wrap supertype_expression in its node type so Builder.build dispatches correctly
27
29
  supertype_expression = if body[:supertype_expression]
@@ -114,10 +114,18 @@ module Expressir
114
114
  bound1 = Builder.build_optional(bound_spec[:bound1])
115
115
  bound2 = Builder.build_optional(bound_spec[:bound2])
116
116
  base_type = Builder.build_optional(ast_data[:parameter_type])
117
+ # Element modifiers are legal on general aggregations too
118
+ # (attributes and parameters); dropping them silently changed
119
+ # element constraints (GH-338). ARRAY carries both flags,
120
+ # LIST only UNIQUE, per the model.
121
+ optional = !ast_data[:t_optional].nil? if type_class.method_defined?(:optional)
122
+ unique = !ast_data[:t_unique].nil? if type_class.method_defined?(:unique)
117
123
 
118
124
  type_class.new(
119
125
  bound1: bound1,
120
126
  bound2: bound2,
127
+ optional: optional,
128
+ unique: unique,
121
129
  base_type: base_type,
122
130
  )
123
131
  end
@@ -25,8 +25,15 @@ module Expressir
25
25
  attributes = Builder.build_children(inner_data[:referenced_attribute])
26
26
  end
27
27
 
28
+ attributes = attributes.compact
29
+
30
+ # The grammar's separator repetition can yield a phantom rule
31
+ # fragment (no label, no referenced attributes) between real
32
+ # rules; drop it so clean never emits a bare ";" (GH-340).
33
+ return nil if id.nil? && attributes.empty?
34
+
28
35
  Expressir::Model::Declarations::UniqueRule.new(id: id,
29
- attributes: attributes.compact)
36
+ attributes: attributes)
30
37
  end
31
38
  end
32
39
  end