kumi 0.0.9 → 0.0.10

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +28 -44
  3. data/README.md +187 -120
  4. data/docs/AST.md +1 -1
  5. data/docs/FUNCTIONS.md +52 -8
  6. data/docs/compiler_design_principles.md +86 -0
  7. data/docs/features/README.md +15 -2
  8. data/docs/features/hierarchical-broadcasting.md +349 -0
  9. data/docs/features/javascript-transpiler.md +148 -0
  10. data/docs/features/performance.md +1 -3
  11. data/docs/schema_metadata.md +7 -7
  12. data/examples/game_of_life.rb +2 -4
  13. data/lib/kumi/analyzer.rb +0 -2
  14. data/lib/kumi/compiler.rb +6 -275
  15. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +600 -42
  16. data/lib/kumi/core/analyzer/passes/input_collector.rb +4 -2
  17. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +27 -0
  18. data/lib/kumi/core/analyzer/passes/type_checker.rb +6 -2
  19. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +90 -46
  20. data/lib/kumi/core/cascade_executor_builder.rb +132 -0
  21. data/lib/kumi/core/compiler/expression_compiler.rb +146 -0
  22. data/lib/kumi/core/compiler/function_invoker.rb +55 -0
  23. data/lib/kumi/core/compiler/path_traversal_compiler.rb +158 -0
  24. data/lib/kumi/core/compiler/reference_compiler.rb +46 -0
  25. data/lib/kumi/core/compiler_base.rb +137 -0
  26. data/lib/kumi/core/explain.rb +2 -2
  27. data/lib/kumi/core/function_registry/collection_functions.rb +86 -3
  28. data/lib/kumi/core/function_registry/function_builder.rb +5 -3
  29. data/lib/kumi/core/function_registry/logical_functions.rb +171 -1
  30. data/lib/kumi/core/function_registry/stat_functions.rb +156 -0
  31. data/lib/kumi/core/function_registry.rb +32 -10
  32. data/lib/kumi/core/nested_structure_utils.rb +78 -0
  33. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +2 -2
  34. data/lib/kumi/core/ruby_parser/input_builder.rb +61 -8
  35. data/lib/kumi/core/schema_instance.rb +4 -0
  36. data/lib/kumi/core/vectorized_function_builder.rb +88 -0
  37. data/lib/kumi/errors.rb +2 -0
  38. data/lib/kumi/js/compiler.rb +878 -0
  39. data/lib/kumi/js/function_registry.rb +333 -0
  40. data/lib/kumi/js.rb +23 -0
  41. data/lib/kumi/registry.rb +61 -1
  42. data/lib/kumi/schema.rb +1 -1
  43. data/lib/kumi/support/s_expression_printer.rb +16 -15
  44. data/lib/kumi/syntax/array_expression.rb +6 -6
  45. data/lib/kumi/syntax/call_expression.rb +4 -4
  46. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  47. data/lib/kumi/syntax/case_expression.rb +4 -4
  48. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  49. data/lib/kumi/syntax/hash_expression.rb +4 -4
  50. data/lib/kumi/syntax/input_declaration.rb +6 -5
  51. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  52. data/lib/kumi/syntax/input_reference.rb +5 -5
  53. data/lib/kumi/syntax/literal.rb +4 -4
  54. data/lib/kumi/syntax/node.rb +34 -34
  55. data/lib/kumi/syntax/root.rb +6 -6
  56. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  57. data/lib/kumi/syntax/value_declaration.rb +4 -4
  58. data/lib/kumi/version.rb +1 -1
  59. data/lib/kumi.rb +1 -1
  60. data/scripts/analyze_broadcast_methods.rb +68 -0
  61. data/scripts/analyze_cascade_methods.rb +74 -0
  62. data/scripts/check_broadcasting_coverage.rb +51 -0
  63. data/scripts/find_dead_code.rb +114 -0
  64. metadata +20 -4
  65. data/docs/features/array-broadcasting.md +0 -170
  66. data/lib/kumi/cli.rb +0 -449
  67. data/lib/kumi/core/vectorization_metadata.rb +0 -110
@@ -0,0 +1,333 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Js
5
+ # Maps Ruby function registry to JavaScript implementations
6
+ # Each function maintains the same signature and behavior as the Ruby version
7
+ module FunctionRegistry
8
+ # Generate complete JavaScript function registry
9
+ def self.generate_js_registry
10
+ {
11
+ # Mathematical functions
12
+ **math_functions,
13
+
14
+ # Comparison functions
15
+ **comparison_functions,
16
+
17
+ # Logical functions
18
+ **logical_functions,
19
+
20
+ # String functions
21
+ **string_functions,
22
+
23
+ # Collection functions
24
+ **collection_functions,
25
+
26
+ # Conditional functions
27
+ **conditional_functions,
28
+
29
+ # Type functions
30
+ **type_functions,
31
+
32
+ # Statistical functions
33
+ **stat_functions
34
+ }
35
+ end
36
+
37
+ # Generate JavaScript code for the function registry
38
+ def self.generate_js_code(functions_required: nil)
39
+ registry = generate_js_registry
40
+
41
+ # Filter registry to only include required functions if specified
42
+ registry = registry.slice(*functions_required) if functions_required && !functions_required.empty?
43
+
44
+ functions_js = registry.map do |name, js_code|
45
+ # Handle symbol names that need quoting in JS
46
+ js_name = name.to_s.match?(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/) ? name : "\"#{name}\""
47
+ " #{js_name}: #{js_code}"
48
+ end.join(",\n")
49
+
50
+ <<~JAVASCRIPT
51
+ const kumiRegistry = {
52
+ #{functions_js}
53
+ };
54
+ JAVASCRIPT
55
+ end
56
+
57
+ def self.math_functions
58
+ {
59
+ # Basic arithmetic
60
+ add: "(a, b) => a + b",
61
+ subtract: "(a, b) => a - b",
62
+ multiply: "(a, b) => a * b",
63
+ divide: "(a, b) => a / b",
64
+ modulo: "(a, b) => a % b",
65
+ power: "(a, b) => Math.pow(a, b)",
66
+
67
+ # Unary operations
68
+ abs: "(a) => Math.abs(a)",
69
+ floor: "(a) => Math.floor(a)",
70
+ ceil: "(a) => Math.ceil(a)",
71
+
72
+ # Special operations
73
+ round: "(a, precision = 0) => Number(a.toFixed(precision))",
74
+ clamp: "(value, min, max) => Math.min(max, Math.max(min, value))",
75
+
76
+ # Complex mathematical operations
77
+ piecewise_sum: <<~JS.strip
78
+ (value, breaks, rates) => {
79
+ if (breaks.length !== rates.length) {
80
+ throw new Error('breaks & rates size mismatch');
81
+ }
82
+ #{' '}
83
+ let acc = 0.0;
84
+ let previous = 0.0;
85
+ let marginal = rates[rates.length - 1];
86
+ #{' '}
87
+ for (let i = 0; i < breaks.length; i++) {
88
+ const upper = breaks[i];
89
+ const rate = rates[i];
90
+ #{' '}
91
+ if (value <= upper) {
92
+ marginal = rate;
93
+ acc += (value - previous) * rate;
94
+ break;
95
+ } else {
96
+ acc += (upper - previous) * rate;
97
+ previous = upper;
98
+ }
99
+ }
100
+ #{' '}
101
+ return [acc, marginal];
102
+ }
103
+ JS
104
+ }
105
+ end
106
+
107
+ def self.comparison_functions
108
+ {
109
+ # Equality operators (using strict equality)
110
+ "==": "(a, b) => a === b",
111
+ "!=": "(a, b) => a !== b",
112
+
113
+ # Comparison operators
114
+ ">": "(a, b) => a > b",
115
+ "<": "(a, b) => a < b",
116
+ ">=": "(a, b) => a >= b",
117
+ "<=": "(a, b) => a <= b",
118
+
119
+ # Range comparison
120
+ between?: "(value, min, max) => value >= min && value <= max"
121
+ }
122
+ end
123
+
124
+ def self.logical_functions
125
+ {
126
+ # Basic logical operations
127
+ and: "(...conditions) => conditions.every(x => x)",
128
+ or: "(...conditions) => conditions.some(x => x)",
129
+ not: "(a) => !a",
130
+
131
+ # Collection logical operations
132
+ all?: "(collection) => collection.every(x => x)",
133
+ any?: "(collection) => collection.some(x => x)",
134
+ none?: "(collection) => !collection.some(x => x)",
135
+
136
+ # Element-wise AND for cascades - works on arrays with hierarchical broadcasting
137
+ cascade_and: <<~JS.strip
138
+ (...conditions) => {
139
+ if (conditions.length === 0) return false;
140
+ if (conditions.length === 1) return conditions[0];
141
+ #{' '}
142
+ // Start with first condition
143
+ let result = conditions[0];
144
+ #{' '}
145
+ // Apply element-wise AND with remaining conditions using hierarchical broadcasting
146
+ for (let i = 1; i < conditions.length; i++) {
147
+ result = kumiRuntime.elementWiseAnd(result, conditions[i]);
148
+ }
149
+ #{' '}
150
+ return result;
151
+ }
152
+ JS
153
+ }
154
+ end
155
+
156
+ def self.string_functions
157
+ {
158
+ # String transformations
159
+ upcase: "(str) => str.toString().toUpperCase()",
160
+ downcase: "(str) => str.toString().toLowerCase()",
161
+ capitalize: "(str) => { const s = str.toString(); return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); }",
162
+ strip: "(str) => str.toString().trim()",
163
+
164
+ # String queries
165
+ string_length: "(str) => str.toString().length",
166
+ length: "(str) => str.toString().length",
167
+
168
+ # String inclusion checks
169
+ string_include?: "(str, substr) => str.toString().includes(substr.toString())",
170
+ includes?: "(str, substr) => str.toString().includes(substr.toString())",
171
+ contains?: "(str, substr) => str.toString().includes(substr.toString())",
172
+ start_with?: "(str, prefix) => str.toString().startsWith(prefix.toString())",
173
+ end_with?: "(str, suffix) => str.toString().endsWith(suffix.toString())",
174
+
175
+ # String building
176
+ concat: "(...strings) => strings.map(s => s.toString()).join('')"
177
+ }
178
+ end
179
+
180
+ def self.collection_functions
181
+ {
182
+ # Collection queries (reducers)
183
+ empty?: "(collection) => collection.length === 0",
184
+ size: "(collection) => collection.length",
185
+
186
+ # Element access
187
+ first: "(collection) => collection[0]",
188
+ last: "(collection) => collection[collection.length - 1]",
189
+
190
+ # Mathematical operations on collections
191
+ sum: "(collection) => collection.reduce((a, b) => a + b, 0)",
192
+ min: "(collection) => Math.min(...collection)",
193
+ max: "(collection) => Math.max(...collection)",
194
+
195
+ # Collection operations
196
+ include?: "(collection, element) => collection.includes(element)",
197
+ reverse: "(collection) => [...collection].reverse()",
198
+ sort: "(collection) => [...collection].sort()",
199
+ unique: "(collection) => [...new Set(collection)]",
200
+ flatten: "(collection) => collection.flat(Infinity)",
201
+ flatten_one: "(collection) => collection.flat(1)",
202
+ flatten_deep: "(collection) => collection.flat(Infinity)",
203
+
204
+ # Array transformation functions
205
+ map_multiply: "(collection, factor) => collection.map(x => x * factor)",
206
+ map_add: "(collection, value) => collection.map(x => x + value)",
207
+ map_conditional: "(collection, condition_value, true_value, false_value) => collection.map(x => x === condition_value ? true_value : false_value)",
208
+
209
+ # Range/index functions
210
+ build_array: "(size) => Array.from({length: size}, (_, i) => i)",
211
+ range: "(start, finish) => Array.from({length: finish - start}, (_, i) => start + i)",
212
+
213
+ # Array slicing and grouping
214
+ each_slice: <<~JS.strip,
215
+ (array, size) => {
216
+ const result = [];
217
+ for (let i = 0; i < array.length; i += size) {
218
+ result.push(array.slice(i, i + size));
219
+ }
220
+ return result;
221
+ }
222
+ JS
223
+
224
+ join: "(array, separator = '') => array.map(x => x.toString()).join(separator.toString())",
225
+
226
+ map_join_rows: "(array_of_arrays, row_separator = '', column_separator = '\\n') => array_of_arrays.map(row => row.join(row_separator.toString())).join(column_separator.toString())",
227
+
228
+ # Higher-order collection functions
229
+ map_with_index: "(collection) => collection.map((item, index) => [item, index])",
230
+ indices: "(collection) => Array.from({length: collection.length}, (_, i) => i)",
231
+
232
+ # Conditional aggregation functions
233
+ count_if: "(condition_array) => condition_array.filter(x => x === true).length",
234
+ sum_if: "(value_array, condition_array) => value_array.reduce((sum, value, i) => sum + (condition_array[i] ? value : 0), 0)",
235
+ avg_if: <<~JS.strip,
236
+ (value_array, condition_array) => {
237
+ const pairs = value_array.map((value, i) => [value, condition_array[i]]);
238
+ const true_values = pairs.filter(([_, condition]) => condition).map(([value, _]) => value);
239
+ return true_values.length === 0 ? 0.0 : true_values.reduce((a, b) => a + b, 0) / true_values.length;
240
+ }
241
+ JS
242
+
243
+ # Flattening utilities for hierarchical data
244
+ any_across: "(nested_array) => nested_array.flat(Infinity).some(x => x)",
245
+ all_across: "(nested_array) => nested_array.flat(Infinity).every(x => x)",
246
+ count_across: "(nested_array) => nested_array.flat(Infinity).length"
247
+ }
248
+ end
249
+
250
+ def self.conditional_functions
251
+ {
252
+ conditional: "(condition, true_value, false_value) => condition ? true_value : false_value",
253
+ if: "(condition, true_value, false_value = null) => condition ? true_value : false_value",
254
+ coalesce: "(...values) => values.find(v => v != null)"
255
+ }
256
+ end
257
+
258
+ def self.type_functions
259
+ {
260
+ # Hash/object operations - assuming TypeFunctions exists
261
+ fetch: "(hash, key, default_value = null) => hash.hasOwnProperty(key) ? hash[key] : default_value",
262
+ has_key?: "(hash, key) => hash.hasOwnProperty(key)",
263
+ keys: "(hash) => Object.keys(hash)",
264
+ values: "(hash) => Object.values(hash)",
265
+ at: "(collection, index) => collection[index]"
266
+ }
267
+ end
268
+
269
+ def self.stat_functions
270
+ {
271
+ # Statistical functions - mirror Ruby StatFunctions behavior
272
+ avg: "(array) => array.length === 0 ? 0 : array.reduce((sum, val) => sum + val, 0) / array.length",
273
+ mean: "(array) => array.length === 0 ? 0 : array.reduce((sum, val) => sum + val, 0) / array.length",
274
+ median: <<~JS.strip,
275
+ (array) => {
276
+ if (array.length === 0) return 0;
277
+ const sorted = [...array].sort((a, b) => a - b);
278
+ const mid = Math.floor(sorted.length / 2);
279
+ return sorted.length % 2 === 0#{' '}
280
+ ? (sorted[mid - 1] + sorted[mid]) / 2
281
+ : sorted[mid];
282
+ }
283
+ JS
284
+ variance: <<~JS.strip,
285
+ (array) => {
286
+ if (array.length === 0) return 0;
287
+ const mean = array.reduce((sum, val) => sum + val, 0) / array.length;
288
+ const squaredDiffs = array.map(val => Math.pow(val - mean, 2));
289
+ return squaredDiffs.reduce((sum, val) => sum + val, 0) / array.length;
290
+ }
291
+ JS
292
+ stdev: <<~JS.strip,
293
+ (array) => {
294
+ if (array.length === 0) return 0;
295
+ const mean = array.reduce((sum, val) => sum + val, 0) / array.length;
296
+ const squaredDiffs = array.map(val => Math.pow(val - mean, 2));
297
+ const variance = squaredDiffs.reduce((sum, val) => sum + val, 0) / array.length;
298
+ return Math.sqrt(variance);
299
+ }
300
+ JS
301
+ sample_variance: <<~JS.strip,
302
+ (array) => {
303
+ if (array.length <= 1) return 0;
304
+ const mean = array.reduce((sum, val) => sum + val, 0) / array.length;
305
+ const squaredDiffs = array.map(val => Math.pow(val - mean, 2));
306
+ return squaredDiffs.reduce((sum, val) => sum + val, 0) / (array.length - 1);
307
+ }
308
+ JS
309
+ sample_stdev: <<~JS.strip,
310
+ (array) => {
311
+ if (array.length <= 1) return 0;
312
+ const mean = array.reduce((sum, val) => sum + val, 0) / array.length;
313
+ const squaredDiffs = array.map(val => Math.pow(val - mean, 2));
314
+ const variance = squaredDiffs.reduce((sum, val) => sum + val, 0) / (array.length - 1);
315
+ return Math.sqrt(variance);
316
+ }
317
+ JS
318
+ # Flattened statistics functions
319
+ flat_size: "(nestedArray) => nestedArray.flat(Infinity).length",
320
+ flat_sum: "(nestedArray) => nestedArray.flat(Infinity).reduce((sum, val) => sum + val, 0)",
321
+ flat_avg: <<~JS.strip,
322
+ (nestedArray) => {
323
+ const flattened = nestedArray.flat(Infinity);
324
+ return flattened.length === 0 ? 0 : flattened.reduce((sum, val) => sum + val, 0) / flattened.length;
325
+ }
326
+ JS
327
+ flat_max: "(nestedArray) => Math.max(...nestedArray.flat(Infinity))",
328
+ flat_min: "(nestedArray) => Math.min(...nestedArray.flat(Infinity))"
329
+ }
330
+ end
331
+ end
332
+ end
333
+ end
data/lib/kumi/js.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Js
5
+ # JavaScript transpiler for Kumi schemas
6
+ # Extends the existing compiler architecture to output JavaScript instead of Ruby lambdas
7
+
8
+ # Export a compiled schema to JavaScript
9
+ def self.compile(schema_class, **options)
10
+ syntax_tree = schema_class.__syntax_tree__
11
+ analyzer_result = schema_class.__analyzer_result__
12
+
13
+ compiler = Compiler.new(syntax_tree, analyzer_result)
14
+ compiler.compile(**options)
15
+ end
16
+
17
+ # Export to JavaScript file
18
+ def self.export_to_file(schema_class, filename, **options)
19
+ js_code = compile(schema_class, **options)
20
+ File.write(filename, js_code)
21
+ end
22
+ end
23
+ end
data/lib/kumi/registry.rb CHANGED
@@ -1,6 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kumi
4
+ # Public interface for registering custom functions in Kumi schemas
5
+ #
6
+ # Usage:
7
+ # Kumi::Registry.register(:my_function) do |x|
8
+ # x * 2
9
+ # end
2
10
  module Registry
3
11
  extend Core::FunctionRegistry
12
+
4
13
  Entry = Core::FunctionRegistry::FunctionBuilder::Entry
5
14
 
6
15
  @functions = Core::FunctionRegistry::CORE_FUNCTIONS.transform_values(&:dup)
@@ -17,11 +26,62 @@ module Kumi
17
26
  end
18
27
  end
19
28
 
29
+ # Register a custom function with the Kumi function registry
30
+ #
31
+ # Example:
32
+ # Kumi::Registry.register(:double) do |x|
33
+ # x * 2
34
+ # end
35
+ #
36
+ # # Use in schema:
37
+ # value :doubled, fn(:double, input.number)
20
38
  def register(name, &block)
21
39
  @lock.synchronize do
22
40
  raise FrozenError, "registry is frozen" if @frozen
41
+ raise ArgumentError, "Function #{name.inspect} already registered" if @functions.key?(name)
42
+
43
+ fn_lambda = block.is_a?(Proc) ? block : ->(*args) { yield(*args) }
44
+ @functions[name] = Entry.new(
45
+ fn: fn_lambda,
46
+ arity: fn_lambda.arity,
47
+ param_types: [:any],
48
+ return_type: :any,
49
+ description: nil,
50
+ inverse: nil,
51
+ reducer: false
52
+ )
53
+ end
54
+ end
55
+
56
+ # Register a custom function with detailed metadata for type and domain validation
57
+ #
58
+ # Example:
59
+ # Kumi::Registry.register_with_metadata(
60
+ # :add_tax,
61
+ # ->(amount, rate) { amount * (1 + rate) },
62
+ # arity: 2,
63
+ # param_types: [:float, :float],
64
+ # return_type: :float,
65
+ # description: "Adds tax to an amount",
66
+ # )
67
+ #
68
+ # # Use in schema:
69
+ # value :total, fn(:add_tax, input.price, input.tax_rate)
70
+ def register_with_metadata(name, fn_lambda, arity:, param_types: [:any], return_type: :any, description: nil, inverse: nil,
71
+ reducer: false)
72
+ @lock.synchronize do
73
+ raise FrozenError, "registry is frozen" if @frozen
74
+ raise ArgumentError, "Function #{name.inspect} already registered" if @functions.key?(name)
23
75
 
24
- super
76
+ @functions[name] = Entry.new(
77
+ fn: fn_lambda,
78
+ arity: arity,
79
+ param_types: param_types,
80
+ return_type: return_type,
81
+ description: description,
82
+ inverse: inverse,
83
+ reducer: reducer
84
+ )
25
85
  end
26
86
  end
27
87
 
data/lib/kumi/schema.rb CHANGED
@@ -8,7 +8,7 @@ module Kumi
8
8
 
9
9
  Inspector = Struct.new(:syntax_tree, :analyzer_result, :compiled_schema) do
10
10
  def inspect
11
- "#<#{self.class} syntax_tree: #{syntax_tree.inspect}, analyzer_result: #{analyzer_result.inspect}, schema: #{schema.inspect}>"
11
+ "#<#{self.class} syntax_tree: #{syntax_tree.inspect}, analyzer_result: #{analyzer_result.inspect}, compiled_schema: #{compiled_schema.inspect}>"
12
12
  end
13
13
  end
14
14
 
@@ -9,7 +9,7 @@ module Kumi
9
9
 
10
10
  def visit(node)
11
11
  return node.inspect unless node.respond_to?(:class)
12
-
12
+
13
13
  case node
14
14
  when nil then "nil"
15
15
  when Array then visit_array(node)
@@ -38,7 +38,7 @@ module Kumi
38
38
 
39
39
  def visit_array(node)
40
40
  return "[]" if node.empty?
41
-
41
+
42
42
  elements = node.map { |child| child_printer.visit(child) }
43
43
  "[\n#{indent_str(2)}#{elements.join("\n#{indent_str(2)}")}\n#{indent_str}]"
44
44
  end
@@ -48,7 +48,7 @@ module Kumi
48
48
  value = node.public_send(field)
49
49
  "#{field}: #{child_printer.visit(value)}"
50
50
  end.join("\n#{indent_str(2)}")
51
-
51
+
52
52
  "(Root\n#{indent_str(2)}#{fields}\n#{indent_str})"
53
53
  end
54
54
 
@@ -64,7 +64,8 @@ module Kumi
64
64
  fields = [":#{node.name}"]
65
65
  fields << ":#{node.type}" if node.respond_to?(:type) && node.type
66
66
  fields << "domain: #{node.domain.inspect}" if node.respond_to?(:domain) && node.domain
67
-
67
+ fields << "access_mode: #{node.access_mode.inspect}" if node.respond_to?(:access_mode) && node.access_mode
68
+
68
69
  if node.respond_to?(:children) && !node.children.empty?
69
70
  children_str = child_printer.visit(node.children)
70
71
  "(InputDeclaration #{fields.join(' ')}\n#{child_indent}#{children_str}\n#{indent_str})"
@@ -75,14 +76,14 @@ module Kumi
75
76
 
76
77
  def visit_call_expression(node)
77
78
  return "(CallExpression :#{node.fn_name})" if node.args.empty?
78
-
79
+
79
80
  args = node.args.map { |arg| child_printer.visit(arg) }
80
81
  "(CallExpression :#{node.fn_name}\n#{indent_str(2)}#{args.join("\n#{indent_str(2)}")}\n#{indent_str})"
81
82
  end
82
83
 
83
84
  def visit_array_expression(node)
84
85
  return "(ArrayExpression)" if node.elements.empty?
85
-
86
+
86
87
  elements = node.elements.map { |elem| child_printer.visit(elem) }
87
88
  "(ArrayExpression\n#{indent_str(2)}#{elements.join("\n#{indent_str(2)}")}\n#{indent_str})"
88
89
  end
@@ -91,7 +92,7 @@ module Kumi
91
92
  cases = node.cases.map do |case_expr|
92
93
  "(#{visit(case_expr.condition)} #{visit(case_expr.result)})"
93
94
  end.join("\n#{indent_str(2)}")
94
-
95
+
95
96
  "(CascadeExpression\n#{indent_str(2)}#{cases}\n#{indent_str})"
96
97
  end
97
98
 
@@ -117,17 +118,17 @@ module Kumi
117
118
 
118
119
  def visit_hash_expression(node)
119
120
  return "(HashExpression)" if node.pairs.empty?
120
-
121
+
121
122
  pairs = node.pairs.map do |pair|
122
123
  "(#{visit(pair.key)} #{visit(pair.value)})"
123
124
  end.join("\n#{indent_str(2)}")
124
-
125
+
125
126
  "(HashExpression\n#{indent_str(2)}#{pairs}\n#{indent_str})"
126
127
  end
127
128
 
128
129
  def visit_generic(node)
129
- class_name = node.class.name&.split('::')&.last || node.class.to_s
130
-
130
+ class_name = node.class.name&.split("::")&.last || node.class.to_s
131
+
131
132
  if node.respond_to?(:children) && !node.children.empty?
132
133
  children = node.children.map { |child| child_printer.visit(child) }
133
134
  "(#{class_name}\n#{indent_str(2)}#{children.join("\n#{indent_str(2)}")}\n#{indent_str})"
@@ -136,9 +137,9 @@ module Kumi
136
137
  value = node[member]
137
138
  "#{member}: #{child_printer.visit(value)}"
138
139
  end
139
-
140
+
140
141
  return "(#{class_name})" if fields.empty?
141
-
142
+
142
143
  "(#{class_name}\n#{indent_str(2)}#{fields.join("\n#{indent_str(2)}")}\n#{indent_str})"
143
144
  else
144
145
  "(#{class_name} #{node.inspect})"
@@ -150,7 +151,7 @@ module Kumi
150
151
  end
151
152
 
152
153
  def indent_str(extra = 0)
153
- ' ' * (@indent + extra)
154
+ " " * (@indent + extra)
154
155
  end
155
156
 
156
157
  def child_indent
@@ -158,4 +159,4 @@ module Kumi
158
159
  end
159
160
  end
160
161
  end
161
- end
162
+ end
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Kumi
4
4
  module Syntax
5
- ArrayExpression = Struct.new(:elements) do
6
- include Node
5
+ ArrayExpression = Struct.new(:elements) do
6
+ include Node
7
7
 
8
- def children = elements
8
+ def children = elements
9
9
 
10
- def size
11
- elements.size
12
- end
10
+ def size
11
+ elements.size
13
12
  end
13
+ end
14
14
  end
15
15
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Kumi
4
4
  module Syntax
5
- CallExpression = Struct.new(:fn_name, :args) do
6
- include Node
5
+ CallExpression = Struct.new(:fn_name, :args) do
6
+ include Node
7
7
 
8
- def children = args
9
- end
8
+ def children = args
9
+ end
10
10
  end
11
11
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Kumi
4
4
  module Syntax
5
- CascadeExpression = Struct.new(:cases) do
6
- include Node
5
+ CascadeExpression = Struct.new(:cases) do
6
+ include Node
7
7
 
8
- def children = cases
9
- end
8
+ def children = cases
9
+ end
10
10
  end
11
11
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Kumi
4
4
  module Syntax
5
- CaseExpression = Struct.new(:condition, :result) do
6
- include Node
5
+ CaseExpression = Struct.new(:condition, :result) do
6
+ include Node
7
7
 
8
- def children = [condition, result]
9
- end
8
+ def children = [condition, result]
9
+ end
10
10
  end
11
11
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Kumi
4
4
  module Syntax
5
- DeclarationReference = Struct.new(:name) do
6
- include Node
5
+ DeclarationReference = Struct.new(:name) do
6
+ include Node
7
7
 
8
- def children = []
9
- end
8
+ def children = []
9
+ end
10
10
  end
11
11
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Kumi
4
4
  module Syntax
5
- HashExpression = Struct.new(:pairs) do
6
- include Node
5
+ HashExpression = Struct.new(:pairs) do
6
+ include Node
7
7
 
8
- def children = pairs.flatten
9
- end
8
+ def children = pairs.flatten
9
+ end
10
10
  end
11
11
  end
@@ -2,11 +2,12 @@
2
2
 
3
3
  module Kumi
4
4
  module Syntax
5
- # For field metadata declarations inside input blocks
6
- InputDeclaration = Struct.new(:name, :domain, :type, :children) do
7
- include Node
5
+ # For field metadata declarations inside input blocks
6
+ InputDeclaration = Struct.new(:name, :domain, :type, :children, :access_mode) do
7
+ include Node
8
8
 
9
- def children = self[:children] || []
10
- end
9
+ def children = self[:children] || []
10
+ def access_mode = self[:access_mode]
11
+ end
11
12
  end
12
13
  end