expressir 2.1.31 → 2.2.0

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +3 -2
  3. data/.github/workflows/release.yml +6 -0
  4. data/.rubocop_todo.yml +98 -89
  5. data/Gemfile +1 -1
  6. data/README.adoc +372 -1
  7. data/docs/_guides/formatter/formatter-architecture.adoc +401 -0
  8. data/docs/_guides/ruby-api/parsing-files.adoc +1 -1
  9. data/docs/_pages/parsers.adoc +31 -5
  10. data/docs/lychee.toml +3 -0
  11. data/expressir.gemspec +2 -2
  12. data/lib/expressir/benchmark.rb +6 -6
  13. data/lib/expressir/cli.rb +9 -0
  14. data/lib/expressir/commands/format.rb +28 -0
  15. data/lib/expressir/coverage.rb +15 -11
  16. data/lib/expressir/express/builder.rb +350 -0
  17. data/lib/expressir/express/builders/attribute_decl_builder.rb +38 -0
  18. data/lib/expressir/express/builders/built_in_builder.rb +88 -0
  19. data/lib/expressir/express/builders/constant_builder.rb +115 -0
  20. data/lib/expressir/express/builders/declaration_builder.rb +24 -0
  21. data/lib/expressir/express/builders/derive_clause_builder.rb +16 -0
  22. data/lib/expressir/express/builders/derived_attr_builder.rb +28 -0
  23. data/lib/expressir/express/builders/domain_rule_builder.rb +21 -0
  24. data/lib/expressir/express/builders/entity_decl_builder.rb +108 -0
  25. data/lib/expressir/express/builders/explicit_attr_builder.rb +52 -0
  26. data/lib/expressir/express/builders/expression_builder.rb +453 -0
  27. data/lib/expressir/express/builders/function_decl_builder.rb +84 -0
  28. data/lib/expressir/express/builders/helpers.rb +148 -0
  29. data/lib/expressir/express/builders/interface_builder.rb +171 -0
  30. data/lib/expressir/express/builders/inverse_attr_builder.rb +45 -0
  31. data/lib/expressir/express/builders/inverse_attr_type_builder.rb +36 -0
  32. data/lib/expressir/express/builders/inverse_clause_builder.rb +16 -0
  33. data/lib/expressir/express/builders/literal_builder.rb +107 -0
  34. data/lib/expressir/express/builders/procedure_decl_builder.rb +80 -0
  35. data/lib/expressir/express/builders/qualifier_builder.rb +128 -0
  36. data/lib/expressir/express/builders/reference_builder.rb +27 -0
  37. data/lib/expressir/express/builders/rule_decl_builder.rb +95 -0
  38. data/lib/expressir/express/builders/schema_body_decl_builder.rb +22 -0
  39. data/lib/expressir/express/builders/schema_decl_builder.rb +62 -0
  40. data/lib/expressir/express/builders/schema_version_builder.rb +40 -0
  41. data/lib/expressir/express/builders/simple_id_builder.rb +26 -0
  42. data/lib/expressir/express/builders/statement_builder.rb +250 -0
  43. data/lib/expressir/express/builders/subtype_constraint_builder.rb +188 -0
  44. data/lib/expressir/express/builders/syntax_builder.rb +19 -0
  45. data/lib/expressir/express/builders/token_builder.rb +15 -0
  46. data/lib/expressir/express/builders/type_builder.rb +264 -0
  47. data/lib/expressir/express/builders/type_decl_builder.rb +32 -0
  48. data/lib/expressir/express/builders/unique_clause_builder.rb +22 -0
  49. data/lib/expressir/express/builders/unique_rule_builder.rb +36 -0
  50. data/lib/expressir/express/builders/where_clause_builder.rb +22 -0
  51. data/lib/expressir/express/builders.rb +43 -0
  52. data/lib/expressir/express/error.rb +18 -2
  53. data/lib/expressir/express/formatter.rb +18 -1508
  54. data/lib/expressir/express/formatters/data_types_formatter.rb +317 -0
  55. data/lib/expressir/express/formatters/declarations_formatter.rb +689 -0
  56. data/lib/expressir/express/formatters/expressions_formatter.rb +160 -0
  57. data/lib/expressir/express/formatters/literals_formatter.rb +46 -0
  58. data/lib/expressir/express/formatters/references_formatter.rb +42 -0
  59. data/lib/expressir/express/formatters/remark_formatter.rb +296 -0
  60. data/lib/expressir/express/formatters/statements_formatter.rb +224 -0
  61. data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +48 -0
  62. data/lib/expressir/express/parser.rb +129 -14
  63. data/lib/expressir/express/pretty_formatter.rb +624 -0
  64. data/lib/expressir/express/remark_attacher.rb +1155 -0
  65. data/lib/expressir/express/resolve_references_model_visitor.rb +1 -0
  66. data/lib/expressir/express/streaming_builder.rb +467 -0
  67. data/lib/expressir/express/transformer/remark_handling.rb +196 -0
  68. data/lib/expressir/model/identifier.rb +1 -1
  69. data/lib/expressir/model/model_element.rb +30 -2
  70. data/lib/expressir/model/remark_info.rb +51 -0
  71. data/lib/expressir/model/search_engine.rb +58 -9
  72. data/lib/expressir/version.rb +1 -1
  73. data/lib/expressir.rb +5 -1
  74. metadata +56 -7
  75. data/lib/expressir/express/visitor.rb +0 -2815
@@ -0,0 +1,350 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "error"
4
+
5
+ module Expressir
6
+ module Express
7
+ # Builder registry for AST node type handlers.
8
+ # Each builder is a callable object that transforms AST data into Model objects.
9
+ # This is the ONLY way to build models from AST - no Transformer fallback.
10
+ module Builder
11
+ class << self
12
+ attr_reader :source, :include_source
13
+
14
+ # Cache for snake_case conversions
15
+ SNAKE_CASE_CACHE = {} # rubocop:disable Style/MutableConstant
16
+
17
+ # Register a builder for a node type.
18
+ # @param node_type [Symbol] The AST node type
19
+ # @param builder [#call] Optional callable that takes (ast_data)
20
+ # @yield Block that takes (ast_data) if builder not provided
21
+ def register(node_type, builder = nil, &block)
22
+ @register ||= {}
23
+ @register[node_type] = builder || block
24
+ end
25
+
26
+ # Build a Model object from AST data.
27
+ # @param ast [Hash] The AST with node type as key
28
+ # @param source [String, nil] The original source code
29
+ # @param include_source [Boolean, nil] Whether to include source
30
+ # @return [Model::ModelElement] The built model object
31
+ def build(ast, source: nil, include_source: nil)
32
+ return nil unless ast
33
+
34
+ # Only set instance variables on first call (when they're provided)
35
+ # Recursive calls pass nil which shouldn't override the saved values
36
+ @source = source unless source.nil?
37
+ @include_source = include_source unless include_source.nil?
38
+
39
+ # Optimized: Hash is 90%+ of cases, check it first
40
+ case ast
41
+ when Hash
42
+ node_type = ast.keys.first
43
+ node_data = ast[node_type]
44
+
45
+ handler_key = cached_snake_case(node_type)
46
+ snake_data = fast_convert_keys(node_data)
47
+
48
+ builder = @register[handler_key]
49
+ raise Error::UnknownNodeTypeError, node_type unless builder
50
+
51
+ result = builder.call(snake_data)
52
+
53
+ # Always store source_offset for remark attachment (if source is available)
54
+ # Only store source text when include_source is requested
55
+ if @source && result.is_a?(Model::ModelElement)
56
+ source_info = extract_source_info(node_data)
57
+ if source_info
58
+ # Store offset for remark attachment (always needed)
59
+ result.source_offset = source_info[:offset]
60
+ # Store source text only when explicitly requested
61
+ if @include_source
62
+ result.source = source_info[:text]
63
+ end
64
+ end
65
+ end
66
+
67
+ result
68
+ when Array
69
+ ast.map do |item|
70
+ build(item)
71
+ end
72
+ when Parsanol::Slice
73
+ ast.to_s
74
+ else
75
+ ast
76
+ end
77
+ end
78
+
79
+ # Build with remark attachment
80
+ def build_with_remarks(ast, source: nil, include_source: nil)
81
+ # Reset instance variables at the start of a top-level build
82
+ # This ensures state from previous parses is cleared
83
+ @source = source
84
+ @include_source = include_source
85
+
86
+ result = build(ast)
87
+
88
+ if source && result
89
+ attacher = RemarkAttacher.new(source)
90
+ attacher.attach(result)
91
+ end
92
+
93
+ result
94
+ end
95
+
96
+ # Check if a builder is registered for a node type.
97
+ def registered?(node_type)
98
+ @register&.key?(node_type)
99
+ end
100
+
101
+ # Get all registered node types.
102
+ def registered_types
103
+ @register&.keys || []
104
+ end
105
+
106
+ # Build optional (returns nil if ast is nil)
107
+ def build_optional(ast)
108
+ return nil unless ast
109
+
110
+ build(ast)
111
+ end
112
+
113
+ # Build children (array of AST nodes)
114
+ # Optimized to avoid intermediate array allocations
115
+ def build_children(ast_array)
116
+ return [] unless ast_array
117
+
118
+ # Handle single element (common case)
119
+ unless ast_array.is_a?(Array)
120
+ return ast_array.nil? ? [] : [build(ast_array)].compact
121
+ end
122
+
123
+ # Build result in single pass, avoiding flatten/compact/map chain
124
+ result = []
125
+ ast_array.each do |item|
126
+ next if item.nil?
127
+
128
+ case item
129
+ when Array
130
+ item.each do |sub|
131
+ result << build(sub) unless sub.nil?
132
+ end
133
+ else
134
+ built = build(item)
135
+ result << built if built
136
+ end
137
+ end
138
+ result
139
+ end
140
+
141
+ # Fast build methods - call registered builder directly without hash wrapping
142
+ # These are optimized for hot paths in expression building
143
+ # NOTE: These assume data is already snake_case (no key conversion needed)
144
+
145
+ # Call a registered builder directly with data (avoids hash wrapper allocation)
146
+ # @param node_type [Symbol] The node type key
147
+ # @param data The data to pass to the builder (already snake_case)
148
+ def build_node(node_type, data)
149
+ builder = @register&.[](node_type)
150
+ raise Error::UnknownNodeTypeError, node_type unless builder
151
+
152
+ builder.call(data)
153
+ end
154
+
155
+ # Fast path for term nodes
156
+ def build_term(data)
157
+ build_node(:term, data)
158
+ end
159
+
160
+ # Fast path for factor nodes
161
+ def build_factor(data)
162
+ build_node(:factor, data)
163
+ end
164
+
165
+ # Fast path for simple_factor nodes
166
+ def build_simple_factor(data)
167
+ build_node(:simple_factor, data)
168
+ end
169
+
170
+ # Fast path for primary nodes
171
+ def build_primary(data)
172
+ build_node(:primary, data)
173
+ end
174
+
175
+ # Fast path for expression nodes
176
+ def build_expression(data)
177
+ build_node(:expression, data)
178
+ end
179
+
180
+ # Fast path for simple_expression nodes
181
+ def build_simple_expression(data)
182
+ build_node(:simple_expression, data)
183
+ end
184
+
185
+ private
186
+
187
+ # Cached snake_case conversion
188
+ def cached_snake_case(name)
189
+ SNAKE_CASE_CACHE[name] ||= begin
190
+ str = name.to_s
191
+ # Check if already snake_case
192
+ if /^[a-z_]+$/.match?(str)
193
+ str.to_sym
194
+ else
195
+ str
196
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
197
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
198
+ .downcase
199
+ .to_sym
200
+ end
201
+ end
202
+ end
203
+
204
+ # Optimized key conversion - returns original object when no conversion needed
205
+ # This avoids unnecessary allocations for AST nodes that don't need key conversion
206
+ def fast_convert_keys(obj)
207
+ case obj
208
+ when Hash
209
+ return obj if obj.empty?
210
+
211
+ # First pass: check if any conversion is needed
212
+ keys = obj.keys
213
+ needs_conversion = false
214
+ converted_values = nil
215
+
216
+ keys.each do |k|
217
+ key_str = k.to_s
218
+ # Check if key needs conversion (has uppercase)
219
+ if key_str.match?(/[A-Z]/)
220
+ needs_conversion = true
221
+ end
222
+
223
+ # Check if value needs conversion
224
+ val = obj[k]
225
+ case val
226
+ when Hash
227
+ next if val.empty?
228
+
229
+ converted_val = fast_convert_keys(val)
230
+ if !converted_val.equal?(val) # Identity check - same object?
231
+ needs_conversion = true
232
+ converted_values ||= {}
233
+ converted_values[k] = converted_val
234
+ end
235
+ when Array
236
+ next if val.empty?
237
+
238
+ converted_val = fast_convert_keys(val)
239
+ if !converted_val.equal?(val)
240
+ needs_conversion = true
241
+ converted_values ||= {}
242
+ converted_values[k] = converted_val
243
+ end
244
+ end
245
+ end
246
+
247
+ # Return original if no conversion needed (zero allocation!)
248
+ return obj unless needs_conversion
249
+
250
+ # Build result only when necessary
251
+ result = {}
252
+ keys.each do |k|
253
+ key_str = k.to_s
254
+ new_key = key_str.match?(/[A-Z]/) ? cached_snake_case(k) : k
255
+ new_val = converted_values&.key?(k) ? converted_values[k] : obj[k]
256
+ result[new_key] = new_val
257
+ end
258
+ result
259
+ when Array
260
+ return obj if obj.empty?
261
+
262
+ # Check if any element needs conversion
263
+ needs_conversion = false
264
+ result = []
265
+
266
+ obj.each do |item|
267
+ case item
268
+ when Hash
269
+ next if item.empty?
270
+
271
+ converted = fast_convert_keys(item)
272
+ result << converted
273
+ needs_conversion = true unless converted.equal?(item)
274
+ when Array
275
+ next if item.empty?
276
+
277
+ converted = fast_convert_keys(item)
278
+ result << converted
279
+ needs_conversion = true unless converted.equal?(item)
280
+ else
281
+ result << item
282
+ end
283
+ end
284
+
285
+ # Return original if no conversion needed
286
+ needs_conversion ? result : obj
287
+ else
288
+ obj
289
+ end
290
+ end
291
+
292
+ def to_snake_case(name)
293
+ cached_snake_case(name)
294
+ end
295
+
296
+ def convert_keys_to_snake_case(obj)
297
+ fast_convert_keys(obj)
298
+ end
299
+
300
+ def extract_source_info(data)
301
+ return nil unless data
302
+ return nil unless @source
303
+
304
+ slice = find_slice(data)
305
+ return nil unless slice
306
+
307
+ {
308
+ text: @source[slice.offset...(slice.offset + slice.length)]&.strip,
309
+ offset: slice.offset,
310
+ }
311
+ end
312
+
313
+ def find_slice(data, depth = 0)
314
+ return nil if depth > 10
315
+
316
+ case data
317
+ when Parsanol::Slice
318
+ data
319
+ when Hash
320
+ # Skip 'spaces' key which contains whitespace/comments before content
321
+ # Look for 'str' key first as it usually contains the actual content
322
+ if data.key?(:str) && data[:str].is_a?(Parsanol::Slice)
323
+ return data[:str]
324
+ end
325
+
326
+ # Then look in other keys, skipping 'spaces'
327
+ data.each do |key, value|
328
+ next if key == :spaces
329
+
330
+ return value if value.is_a?(Parsanol::Slice)
331
+
332
+ result = find_slice(value, depth + 1)
333
+ return result if result
334
+ end
335
+ nil
336
+ when Array
337
+ data.each do |item|
338
+ result = find_slice(item, depth + 1)
339
+ return result if result
340
+ end
341
+ nil
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ # Top-level alias for convenience in builder files
350
+ Builder = Expressir::Express::Builder
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helpers"
4
+
5
+ module Expressir
6
+ module Express
7
+ module Builders
8
+ # Builds attribute_decl nodes.
9
+ class AttributeDeclBuilder
10
+ include Helpers
11
+
12
+ def call(ast_data)
13
+ id = Builder.build_optional(ast_data[:attribute_id])
14
+ supertype_attribute = nil
15
+
16
+ if ast_data[:redeclared_attribute].is_a?(Hash)
17
+ redeclared = ast_data[:redeclared_attribute]
18
+ if redeclared[:qualified_attribute].is_a?(Hash)
19
+ supertype_attribute = Builder.build({ qualified_attribute: redeclared[:qualified_attribute] })
20
+
21
+ if supertype_attribute.is_a?(Expressir::Model::References::AttributeReference)
22
+ id ||= supertype_attribute.attribute&.id
23
+ end
24
+ end
25
+ id = Builder.build_optional(redeclared[:attribute_id]) if redeclared[:attribute_id]
26
+ end
27
+
28
+ Expressir::Model::Declarations::Attribute.new(
29
+ id: id,
30
+ supertype_attribute: supertype_attribute,
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ Builder.register(:attribute_decl, Expressir::Express::Builders::AttributeDeclBuilder.new)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helpers"
4
+
5
+ module Expressir
6
+ module Express
7
+ module Builders
8
+ # Builds built-in constant, function, and procedure reference nodes.
9
+ class BuiltInBuilder
10
+ include Helpers
11
+
12
+ def build_built_in_constant(ast_data)
13
+ id = extract_nested_text(ast_data)
14
+ Expressir::Model::References::SimpleReference.new(id: id)
15
+ end
16
+
17
+ def build_built_in_function(ast_data)
18
+ id = extract_text(ast_data[:str])
19
+ Expressir::Model::References::SimpleReference.new(id: id)
20
+ end
21
+
22
+ def build_built_in_procedure(ast_data)
23
+ id = extract_text(ast_data[:str])
24
+ Expressir::Model::References::SimpleReference.new(id: id)
25
+ end
26
+
27
+ def build_general_ref(ast_data)
28
+ if ast_data[:constant_ref]
29
+ Builder.build({ constant_ref: ast_data[:constant_ref] })
30
+ elsif ast_data[:function_ref]
31
+ Builder.build({ function_ref: ast_data[:function_ref] })
32
+ elsif ast_data[:parameter_ref]
33
+ Builder.build({ parameter_ref: ast_data[:parameter_ref] })
34
+ elsif ast_data[:variable_ref]
35
+ Builder.build({ variable_ref: ast_data[:variable_ref] })
36
+ elsif ast_data[:entity_ref]
37
+ Builder.build({ entity_ref: ast_data[:entity_ref] })
38
+ elsif ast_data[:type_ref]
39
+ Builder.build({ type_ref: ast_data[:type_ref] })
40
+ elsif ast_data[:attribute_ref]
41
+ Builder.build({ attribute_ref: ast_data[:attribute_ref] })
42
+ elsif ast_data[:enumeration_ref]
43
+ Builder.build({ enumeration_ref: ast_data[:enumeration_ref] })
44
+ end
45
+ end
46
+
47
+ def build_named_types(ast_data)
48
+ if ast_data[:entity_ref]
49
+ Builder.build({ entity_ref: ast_data[:entity_ref] })
50
+ elsif ast_data[:type_ref]
51
+ Builder.build({ type_ref: ast_data[:type_ref] })
52
+ end
53
+ end
54
+
55
+ def build_item(ast_data)
56
+ if ast_data[:entity_ref]
57
+ Builder.build({ entity_ref: ast_data[:entity_ref] })
58
+ elsif ast_data[:type_ref]
59
+ Builder.build({ type_ref: ast_data[:type_ref] })
60
+ end
61
+ end
62
+
63
+ def build_procedure_ref(ast_data)
64
+ id = extract_id_ref(ast_data)
65
+ Expressir::Model::References::SimpleReference.new(id: id)
66
+ end
67
+
68
+ def build_schema_ref(ast_data)
69
+ id = extract_id_ref(ast_data)
70
+ Expressir::Model::References::SimpleReference.new(id: id)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ builder = Expressir::Express::Builders::BuiltInBuilder.new
78
+
79
+ Builder.register(:built_in_constant) { |d| builder.build_built_in_constant(d) }
80
+ Builder.register(:built_in_function) { |d| builder.build_built_in_function(d) }
81
+ Builder.register(:built_in_procedure) do |d|
82
+ builder.build_built_in_procedure(d)
83
+ end
84
+ Builder.register(:general_ref) { |d| builder.build_general_ref(d) }
85
+ Builder.register(:named_types) { |d| builder.build_named_types(d) }
86
+ Builder.register(:item) { |d| builder.build_item(d) }
87
+ Builder.register(:procedure_ref) { |d| builder.build_procedure_ref(d) }
88
+ Builder.register(:schema_ref) { |d| builder.build_schema_ref(d) }
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helpers"
4
+
5
+ module Expressir
6
+ module Express
7
+ module Builders
8
+ # Builds constant and local variable nodes.
9
+ class ConstantBuilder
10
+ include Helpers
11
+
12
+ def build_constant_decl(ast_data)
13
+ Builder.build_children(ast_data[:constant_body])
14
+ end
15
+
16
+ def build_constant_body(ast_data)
17
+ id = Builder.build_optional(ast_data[:constant_id])
18
+ type = Builder.build_optional(ast_data[:instantiable_type])
19
+ expression = Builder.build_optional(ast_data[:expression])
20
+
21
+ Expressir::Model::Declarations::Constant.new(id: id, type: type,
22
+ expression: expression)
23
+ end
24
+
25
+ def build_local_decl(ast_data)
26
+ Builder.build_children(ast_data[:local_variable]).flatten.compact
27
+ end
28
+
29
+ def build_local_variable(ast_data)
30
+ var_id_data = ast_data[:list_of_variable_id] || ast_data
31
+
32
+ ids_data = if var_id_data.is_a?(Hash) && var_id_data[:variable_id]
33
+ var_id_data[:variable_id]
34
+ else
35
+ var_id_data
36
+ end
37
+
38
+ ids = if ids_data.is_a?(Hash)
39
+ [Builder.build({ variable_id: ids_data })]
40
+ elsif ids_data.is_a?(Array)
41
+ ids_data.map { |id| Builder.build({ variable_id: id }) }
42
+ else
43
+ []
44
+ end
45
+
46
+ type = Builder.build_optional(ast_data[:parameter_type])
47
+ expression = if ast_data[:expression]
48
+ Builder.build({ expression: ast_data[:expression] })
49
+ end
50
+
51
+ ids.flatten.compact.map do |id|
52
+ Expressir::Model::Declarations::Variable.new(id: id, type: type,
53
+ expression: expression)
54
+ end
55
+ end
56
+
57
+ def build_formal_parameter(ast_data)
58
+ param_id_data = ast_data[:list_of_parameter_id] || ast_data
59
+
60
+ ids_data = if param_id_data.is_a?(Hash) && param_id_data[:parameter_id]
61
+ param_id_data[:parameter_id]
62
+ else
63
+ param_id_data
64
+ end
65
+
66
+ ids = if ids_data.is_a?(Hash)
67
+ [Builder.build({ parameter_id: ids_data })]
68
+ elsif ids_data.is_a?(Array)
69
+ ids_data.map { |id| Builder.build({ parameter_id: id }) }
70
+ else
71
+ []
72
+ end
73
+
74
+ type = Builder.build_optional(ast_data[:parameter_type])
75
+ ids.flatten.compact.map { |id| Expressir::Model::Declarations::Parameter.new(id: id, type: type) }
76
+ end
77
+
78
+ def build_procedure_head_parameter(ast_data)
79
+ formal_param_data = ast_data[:formal_parameter]
80
+ params = if formal_param_data.is_a?(Hash)
81
+ result = Builder.build({ formal_parameter: formal_param_data })
82
+ [result].flatten.compact
83
+ elsif formal_param_data.is_a?(Array)
84
+ formal_param_data.map do |fp|
85
+ Builder.build({ formal_parameter: fp })
86
+ end.flatten.compact
87
+ else
88
+ []
89
+ end
90
+ is_var = !ast_data[:t_var].nil?
91
+
92
+ if is_var
93
+ params.map do |p|
94
+ Expressir::Model::Declarations::Parameter.new(id: p.id,
95
+ var: true, type: p.type)
96
+ end
97
+ else
98
+ params
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ builder = Expressir::Express::Builders::ConstantBuilder.new
107
+
108
+ Builder.register(:constant_decl) { |d| builder.build_constant_decl(d) }
109
+ Builder.register(:constant_body) { |d| builder.build_constant_body(d) }
110
+ Builder.register(:local_decl) { |d| builder.build_local_decl(d) }
111
+ Builder.register(:local_variable) { |d| builder.build_local_variable(d) }
112
+ Builder.register(:formal_parameter) { |d| builder.build_formal_parameter(d) }
113
+ Builder.register(:procedure_head_parameter) do |d|
114
+ builder.build_procedure_head_parameter(d)
115
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helpers"
4
+
5
+ module Expressir
6
+ module Express
7
+ module Builders
8
+ # Builds declaration nodes - dispatches to specific declaration type.
9
+ class DeclarationBuilder
10
+ def call(ast_data)
11
+ %i[entity_decl function_decl procedure_decl subtype_constraint_decl
12
+ type_decl].each do |key|
13
+ if ast_data[key]
14
+ return Builder.build({ key => ast_data[key] })
15
+ end
16
+ end
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ Builder.register(:declaration, Expressir::Express::Builders::DeclarationBuilder.new)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Express
5
+ module Builders
6
+ # Builds derive_clause nodes.
7
+ class DeriveClauseBuilder
8
+ def call(ast_data)
9
+ Builder.build_children(ast_data[:derived_attr])
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ Builder.register(:derive_clause, Expressir::Express::Builders::DeriveClauseBuilder.new)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Express
5
+ module Builders
6
+ # Builds derived_attr nodes.
7
+ class DerivedAttrBuilder
8
+ def call(ast_data)
9
+ attr_decl_data = ast_data[:attribute_decl]
10
+ attr = if attr_decl_data.is_a?(Hash)
11
+ Builder.build({ attribute_decl: attr_decl_data })
12
+ end
13
+ type = Builder.build_optional(ast_data[:parameter_type])
14
+ expression = Builder.build_optional(ast_data[:expression])
15
+
16
+ Expressir::Model::Declarations::DerivedAttribute.new(
17
+ id: attr&.id,
18
+ supertype_attribute: attr&.supertype_attribute,
19
+ type: type,
20
+ expression: expression,
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ Builder.register(:derived_attr, Expressir::Express::Builders::DerivedAttrBuilder.new)
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Express
5
+ module Builders
6
+ # Builds domain_rule nodes.
7
+ class DomainRuleBuilder
8
+ def call(ast_data)
9
+ inner_data = ast_data[:domain_rule] || ast_data
10
+
11
+ id = Builder.build_optional(inner_data[:rule_label_id])
12
+ expression = Builder.build_optional(inner_data[:expression])
13
+ Expressir::Model::Declarations::WhereRule.new(id: id,
14
+ expression: expression)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Builder.register(:domain_rule, Expressir::Express::Builders::DomainRuleBuilder.new)