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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +3 -2
- data/.github/workflows/release.yml +6 -0
- data/.rubocop_todo.yml +98 -89
- data/Gemfile +1 -1
- data/README.adoc +372 -1
- data/docs/_guides/formatter/formatter-architecture.adoc +401 -0
- data/docs/_guides/ruby-api/parsing-files.adoc +1 -1
- data/docs/_pages/parsers.adoc +31 -5
- data/docs/lychee.toml +3 -0
- data/expressir.gemspec +2 -2
- data/lib/expressir/benchmark.rb +6 -6
- data/lib/expressir/cli.rb +9 -0
- data/lib/expressir/commands/format.rb +28 -0
- data/lib/expressir/coverage.rb +15 -11
- data/lib/expressir/express/builder.rb +350 -0
- data/lib/expressir/express/builders/attribute_decl_builder.rb +38 -0
- data/lib/expressir/express/builders/built_in_builder.rb +88 -0
- data/lib/expressir/express/builders/constant_builder.rb +115 -0
- data/lib/expressir/express/builders/declaration_builder.rb +24 -0
- data/lib/expressir/express/builders/derive_clause_builder.rb +16 -0
- data/lib/expressir/express/builders/derived_attr_builder.rb +28 -0
- data/lib/expressir/express/builders/domain_rule_builder.rb +21 -0
- data/lib/expressir/express/builders/entity_decl_builder.rb +108 -0
- data/lib/expressir/express/builders/explicit_attr_builder.rb +52 -0
- data/lib/expressir/express/builders/expression_builder.rb +453 -0
- data/lib/expressir/express/builders/function_decl_builder.rb +84 -0
- data/lib/expressir/express/builders/helpers.rb +148 -0
- data/lib/expressir/express/builders/interface_builder.rb +171 -0
- data/lib/expressir/express/builders/inverse_attr_builder.rb +45 -0
- data/lib/expressir/express/builders/inverse_attr_type_builder.rb +36 -0
- data/lib/expressir/express/builders/inverse_clause_builder.rb +16 -0
- data/lib/expressir/express/builders/literal_builder.rb +107 -0
- data/lib/expressir/express/builders/procedure_decl_builder.rb +80 -0
- data/lib/expressir/express/builders/qualifier_builder.rb +128 -0
- data/lib/expressir/express/builders/reference_builder.rb +27 -0
- data/lib/expressir/express/builders/rule_decl_builder.rb +95 -0
- data/lib/expressir/express/builders/schema_body_decl_builder.rb +22 -0
- data/lib/expressir/express/builders/schema_decl_builder.rb +62 -0
- data/lib/expressir/express/builders/schema_version_builder.rb +40 -0
- data/lib/expressir/express/builders/simple_id_builder.rb +26 -0
- data/lib/expressir/express/builders/statement_builder.rb +250 -0
- data/lib/expressir/express/builders/subtype_constraint_builder.rb +188 -0
- data/lib/expressir/express/builders/syntax_builder.rb +19 -0
- data/lib/expressir/express/builders/token_builder.rb +15 -0
- data/lib/expressir/express/builders/type_builder.rb +264 -0
- data/lib/expressir/express/builders/type_decl_builder.rb +32 -0
- data/lib/expressir/express/builders/unique_clause_builder.rb +22 -0
- data/lib/expressir/express/builders/unique_rule_builder.rb +36 -0
- data/lib/expressir/express/builders/where_clause_builder.rb +22 -0
- data/lib/expressir/express/builders.rb +43 -0
- data/lib/expressir/express/error.rb +18 -2
- data/lib/expressir/express/formatter.rb +18 -1508
- data/lib/expressir/express/formatters/data_types_formatter.rb +317 -0
- data/lib/expressir/express/formatters/declarations_formatter.rb +689 -0
- data/lib/expressir/express/formatters/expressions_formatter.rb +160 -0
- data/lib/expressir/express/formatters/literals_formatter.rb +46 -0
- data/lib/expressir/express/formatters/references_formatter.rb +42 -0
- data/lib/expressir/express/formatters/remark_formatter.rb +296 -0
- data/lib/expressir/express/formatters/statements_formatter.rb +224 -0
- data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +48 -0
- data/lib/expressir/express/parser.rb +129 -14
- data/lib/expressir/express/pretty_formatter.rb +624 -0
- data/lib/expressir/express/remark_attacher.rb +1155 -0
- data/lib/expressir/express/resolve_references_model_visitor.rb +1 -0
- data/lib/expressir/express/streaming_builder.rb +467 -0
- data/lib/expressir/express/transformer/remark_handling.rb +196 -0
- data/lib/expressir/model/identifier.rb +1 -1
- data/lib/expressir/model/model_element.rb +30 -2
- data/lib/expressir/model/remark_info.rb +51 -0
- data/lib/expressir/model/search_engine.rb +58 -9
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +5 -1
- metadata +56 -7
- 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)
|