kumi 0.0.10 → 0.0.11

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/CLAUDE.md +7 -231
  4. data/README.md +1 -1
  5. data/docs/VECTOR_SEMANTICS.md +286 -0
  6. data/docs/features/hierarchical-broadcasting.md +1 -1
  7. data/docs/features/s-expression-printer.md +2 -2
  8. data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +21 -15
  9. data/lib/kumi/analyzer.rb +34 -12
  10. data/lib/kumi/compiler.rb +2 -12
  11. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +157 -64
  12. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +1 -1
  13. data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +47 -0
  14. data/lib/kumi/core/analyzer/passes/input_collector.rb +118 -101
  15. data/lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb +293 -0
  16. data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +993 -0
  17. data/lib/kumi/core/analyzer/passes/pass_base.rb +2 -2
  18. data/lib/kumi/core/analyzer/passes/scope_resolution_pass.rb +346 -0
  19. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +2 -1
  20. data/lib/kumi/core/analyzer/passes/toposorter.rb +9 -3
  21. data/lib/kumi/core/analyzer/passes/type_checker.rb +3 -3
  22. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +2 -2
  23. data/lib/kumi/core/analyzer/passes/{type_inferencer.rb → type_inferencer_pass.rb} +4 -4
  24. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +2 -2
  25. data/lib/kumi/core/analyzer/plans.rb +52 -0
  26. data/lib/kumi/core/analyzer/structs/access_plan.rb +20 -0
  27. data/lib/kumi/core/analyzer/structs/input_meta.rb +29 -0
  28. data/lib/kumi/core/compiler/access_builder.rb +36 -0
  29. data/lib/kumi/core/compiler/access_planner.rb +219 -0
  30. data/lib/kumi/core/compiler/accessors/base.rb +69 -0
  31. data/lib/kumi/core/compiler/accessors/each_indexed_accessor.rb +84 -0
  32. data/lib/kumi/core/compiler/accessors/materialize_accessor.rb +55 -0
  33. data/lib/kumi/core/compiler/accessors/ravel_accessor.rb +73 -0
  34. data/lib/kumi/core/compiler/accessors/read_accessor.rb +41 -0
  35. data/lib/kumi/core/compiler_base.rb +2 -2
  36. data/lib/kumi/core/error_reporter.rb +6 -5
  37. data/lib/kumi/core/errors.rb +4 -0
  38. data/lib/kumi/core/explain.rb +157 -205
  39. data/lib/kumi/core/export/node_builders.rb +2 -2
  40. data/lib/kumi/core/export/node_serializers.rb +1 -1
  41. data/lib/kumi/core/function_registry/collection_functions.rb +21 -10
  42. data/lib/kumi/core/function_registry/conditional_functions.rb +14 -4
  43. data/lib/kumi/core/function_registry/function_builder.rb +142 -55
  44. data/lib/kumi/core/function_registry/logical_functions.rb +5 -5
  45. data/lib/kumi/core/function_registry/stat_functions.rb +2 -2
  46. data/lib/kumi/core/function_registry.rb +126 -108
  47. data/lib/kumi/core/ir/execution_engine/combinators.rb +117 -0
  48. data/lib/kumi/core/ir/execution_engine/interpreter.rb +336 -0
  49. data/lib/kumi/core/ir/execution_engine/values.rb +46 -0
  50. data/lib/kumi/core/ir/execution_engine.rb +50 -0
  51. data/lib/kumi/core/ir.rb +58 -0
  52. data/lib/kumi/core/ruby_parser/build_context.rb +2 -2
  53. data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +0 -12
  54. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +36 -15
  55. data/lib/kumi/core/ruby_parser/input_builder.rb +5 -5
  56. data/lib/kumi/core/ruby_parser/parser.rb +1 -1
  57. data/lib/kumi/core/ruby_parser/schema_builder.rb +2 -2
  58. data/lib/kumi/core/ruby_parser/sugar.rb +7 -0
  59. data/lib/kumi/registry.rb +14 -79
  60. data/lib/kumi/runtime/executable.rb +213 -0
  61. data/lib/kumi/schema.rb +14 -3
  62. data/lib/kumi/schema_metadata.rb +2 -2
  63. data/lib/kumi/support/ir_dump.rb +491 -0
  64. data/lib/kumi/support/s_expression_printer.rb +1 -1
  65. data/lib/kumi/syntax/location.rb +5 -0
  66. data/lib/kumi/syntax/node.rb +0 -1
  67. data/lib/kumi/syntax/root.rb +2 -2
  68. data/lib/kumi/version.rb +1 -1
  69. data/lib/kumi.rb +6 -15
  70. metadata +26 -15
  71. data/lib/kumi/core/cascade_executor_builder.rb +0 -132
  72. data/lib/kumi/core/compiled_schema.rb +0 -43
  73. data/lib/kumi/core/compiler/expression_compiler.rb +0 -146
  74. data/lib/kumi/core/compiler/function_invoker.rb +0 -55
  75. data/lib/kumi/core/compiler/path_traversal_compiler.rb +0 -158
  76. data/lib/kumi/core/compiler/reference_compiler.rb +0 -46
  77. data/lib/kumi/core/evaluation_wrapper.rb +0 -40
  78. data/lib/kumi/core/nested_structure_utils.rb +0 -78
  79. data/lib/kumi/core/schema_instance.rb +0 -115
  80. data/lib/kumi/core/vectorized_function_builder.rb +0 -88
  81. data/lib/kumi/js/compiler.rb +0 -878
  82. data/lib/kumi/js/function_registry.rb +0 -333
  83. data/migrate_to_core_iterative.rb +0 -938
@@ -1,878 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Js
5
- # JavaScript compiler that extends the base Kumi compiler
6
- # Outputs JavaScript code instead of Ruby lambdas
7
- class Compiler < Kumi::Core::CompilerBase
8
- # JavaScript expression compilers - same logic, different output format
9
- module JSExprCompilers
10
- def compile_literal(expr)
11
- value_js = case expr.value
12
- when String
13
- expr.value.inspect # Use inspect for proper escaping
14
- when Numeric
15
- expr.value.to_s
16
- when TrueClass, FalseClass
17
- expr.value.to_s
18
- when NilClass
19
- "null"
20
- when Array
21
- "[#{expr.value.map(&:inspect).join(', ')}]"
22
- else
23
- expr.value.inspect
24
- end
25
- "(ctx) => #{value_js}"
26
- end
27
-
28
- def compile_field_node(expr)
29
- compile_field(expr)
30
- end
31
-
32
- def compile_element_field_reference(expr)
33
- path = expr.path
34
-
35
- # Check if we have nested paths metadata for this path
36
- nested_paths = @analysis.state[:broadcasts]&.dig(:nested_paths)
37
- unless nested_paths && nested_paths[path]
38
- raise Errors::CompilationError, "Missing nested path metadata for #{path.inspect}. This indicates an analyzer bug."
39
- end
40
-
41
- # Determine operation mode based on context
42
- operation_mode = determine_operation_mode_for_path(path)
43
- generate_js_nested_path_traversal(path, operation_mode)
44
-
45
- # ERROR: All nested paths should have metadata from the analyzer
46
- # If we reach here, it means the BroadcastDetector didn't process this path
47
- end
48
-
49
- def compile_binding_node(expr)
50
- name = expr.name
51
- # Reference to other compiled bindings
52
- "(ctx) => bindings.#{name}(ctx)"
53
- end
54
-
55
- def compile_list(expr)
56
- element_fns = expr.elements.map { |e| compile_expr(e) }
57
-
58
- # Generate JavaScript array creation
59
- elements_js = element_fns.map.with_index { |_, i| " fn#{i}(ctx)" }.join(",\n")
60
-
61
- # Create function declarations for each element
62
- fn_declarations = element_fns.map.with_index do |fn_code, i|
63
- " const fn#{i} = #{fn_code};"
64
- end.join("\n")
65
-
66
- "(ctx) => {\n#{fn_declarations}\n return [\n#{elements_js}\n ];\n}"
67
- end
68
-
69
- def compile_call(expr)
70
- fn_name = expr.fn_name
71
- arg_exprs = expr.args.map { |a| compile_expr(a) }
72
-
73
- # Get compilation metadata once
74
- compilation_meta = @analysis.state[:broadcasts]&.dig(:compilation_metadata, @current_declaration)
75
-
76
- # Check if this is a vectorized operation
77
- if vectorized_operation?(expr)
78
- # Build vectorized executor JavaScript code
79
- compile_js_vectorized_call(fn_name, arg_exprs, compilation_meta)
80
- else
81
- # Use pre-computed function call strategy
82
- function_strategy = compilation_meta&.dig(:function_call_strategy) || {}
83
-
84
- if function_strategy[:flattening_required]
85
- compile_js_call_with_flattening(fn_name, arg_exprs, function_strategy)
86
- else
87
- compile_regular_call(fn_name, arg_exprs, expr)
88
- end
89
- end
90
- end
91
-
92
- def compile_cascade(expr)
93
- # Use metadata to determine if this cascade is vectorized
94
- if is_cascade_vectorized?(expr)
95
- compile_js_vectorized_cascade(expr)
96
- else
97
- compile_js_regular_cascade(expr)
98
- end
99
- end
100
-
101
- private
102
-
103
- def generate_js_nested_path_traversal(path, operation_mode)
104
- # Get path metadata to determine access mode (element vs object)
105
- nested_paths = @analysis.state[:broadcasts]&.dig(:nested_paths)
106
- path_metadata = nested_paths && nested_paths[path]
107
- access_mode = path_metadata&.dig(:access_mode) || :object
108
-
109
- if access_mode == :element
110
- # For element access, generate direct flattening based on path depth
111
- root_field = path.first
112
- flatten_depth = path.length - 1 # Flatten (path_depth - 1) levels
113
-
114
- if flatten_depth.positive?
115
- # Generate direct flat() call
116
- "(ctx) => ctx.#{root_field}.flat(#{flatten_depth})"
117
- else
118
- # No flattening needed
119
- "(ctx) => ctx.#{root_field}"
120
- end
121
- elsif path.length == 1
122
- # For object access, generate proper nested mapping
123
- # Simple field access
124
- "(ctx) => ctx.#{path.first}"
125
- elsif path.length == 2
126
- # Common case: array.field -> array.map(item => item.field)
127
- root_field, nested_field = path
128
- "(ctx) => ctx.#{root_field}.map(item => item.#{nested_field})"
129
- else
130
- # Complex nested object access - use the traversal
131
- path_js = "[#{path.map { |p| "'#{p}'" }.join(', ')}]"
132
- case operation_mode
133
- when :flatten
134
- "(ctx) => kumiRuntime.traverseNestedPath(ctx, #{path_js}, 'flatten', 'object')"
135
- else
136
- "(ctx) => kumiRuntime.traverseNestedPath(ctx, #{path_js}, 'broadcast', 'object')"
137
- end
138
- end
139
- end
140
-
141
- def compile_js_vectorized_call(fn_name, arg_exprs, compilation_meta)
142
- # Generate vectorized function call using metadata
143
- args_js = arg_exprs.map.with_index { |_, i| "arg#{i}(ctx)" }.join(", ")
144
-
145
- arg_declarations = arg_exprs.map.with_index do |arg_code, i|
146
- " const arg#{i} = #{arg_code};"
147
- end.join("\n")
148
-
149
- # Convert compilation metadata to JSON string, handling nil case
150
- meta_json = compilation_meta ? compilation_meta.to_json : "null"
151
-
152
- "(ctx) => {\n#{arg_declarations}\n return kumiRuntime.vectorizedFunctionCall('#{fn_name}', [#{args_js}], #{meta_json});\n}"
153
- end
154
-
155
- def compile_js_call_with_flattening(fn_name, arg_exprs, function_strategy)
156
- # Generate function call with selective argument flattening
157
- flatten_indices = function_strategy[:flatten_argument_indices] || []
158
-
159
- args_js = arg_exprs.map.with_index do |_, i|
160
- if flatten_indices.include?(i)
161
- "kumiRuntime.flattenCompletely(arg#{i}(ctx))"
162
- else
163
- "arg#{i}(ctx)"
164
- end
165
- end.join(", ")
166
-
167
- arg_declarations = arg_exprs.map.with_index do |arg_code, i|
168
- " const arg#{i} = #{arg_code};"
169
- end.join("\n")
170
-
171
- fn_accessor = if fn_name.to_s.match?(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)
172
- "kumiRegistry.#{fn_name}"
173
- else
174
- "kumiRegistry[\"#{fn_name}\"]"
175
- end
176
-
177
- "(ctx) => {\n#{arg_declarations}\n return #{fn_accessor}(#{args_js});\n}"
178
- end
179
-
180
- def compile_js_vectorized_cascade(expr)
181
- # Generate JavaScript vectorized cascade logic
182
-
183
- # Get pre-computed cascade strategy
184
- get_cascade_compilation_metadata
185
- strategy = get_cascade_strategy
186
-
187
- # Separate conditional cases from base case
188
- conditional_cases = expr.cases.select(&:condition)
189
- base_case = expr.cases.find { |c| c.condition.nil? }
190
-
191
- # Compile conditional pairs with vectorized condition handling
192
- condition_compilations = conditional_cases.map.with_index do |c, i|
193
- condition_fn = if is_cascade_vectorized?(expr)
194
- compile_js_vectorized_condition(c.condition)
195
- else
196
- compile_expr(c.condition)
197
- end
198
- " const condition#{i} = #{condition_fn};"
199
- end.join("\n")
200
-
201
- result_compilations = conditional_cases.map.with_index do |c, i|
202
- result_fn = compile_expr(c.result)
203
- " const result#{i} = #{result_fn};"
204
- end.join("\n")
205
-
206
- base_compilation = base_case ? " const baseResult = #{compile_expr(base_case.result)};" : " const baseResult = () => null;"
207
-
208
- # Convert strategy to JSON, handling symbols and complex objects
209
- strategy_json = if strategy
210
- strategy.to_json
211
- else
212
- "null"
213
- end
214
-
215
- <<~JAVASCRIPT
216
- (ctx) => {
217
- #{condition_compilations}
218
- #{result_compilations}
219
- #{base_compilation}
220
- #{' '}
221
- const condResults = [#{conditional_cases.map.with_index { |_, i| "condition#{i}(ctx)" }.join(', ')}];
222
- const resResults = [#{conditional_cases.map.with_index { |_, i| "result#{i}(ctx)" }.join(', ')}];
223
- const baseRes = baseResult(ctx);
224
- #{' '}
225
- return kumiRuntime.executeVectorizedCascade(condResults, resResults, baseRes, #{strategy_json});
226
- }
227
- JAVASCRIPT
228
- end
229
-
230
- def compile_js_regular_cascade(expr)
231
- # Generate standard JavaScript cascade logic
232
- cases_js = expr.cases.map.with_index do |case_expr, i|
233
- if case_expr.condition
234
- condition_fn = compile_expr(case_expr.condition)
235
- result_fn = compile_expr(case_expr.result)
236
- " const condition#{i} = #{condition_fn};\n " \
237
- "const result#{i} = #{result_fn};\n " \
238
- "if (condition#{i}(ctx)) return result#{i}(ctx);"
239
- else
240
- # Base case
241
- result_fn = compile_expr(case_expr.result)
242
- " const baseResult = #{result_fn};\n " \
243
- "return baseResult(ctx);"
244
- end
245
- end.join("\n")
246
-
247
- "(ctx) => {\n#{cases_js}\n return null;\n}"
248
- end
249
-
250
- def compile_js_vectorized_condition(condition_expr)
251
- if condition_expr.is_a?(Kumi::Syntax::CallExpression) &&
252
- condition_expr.fn_name == :cascade_and
253
- # For cascade_and in vectorized contexts, use hierarchical broadcasting
254
- compile_js_cascade_and_for_hierarchical_broadcasting(condition_expr)
255
- else
256
- # Otherwise compile normally
257
- compile_expr(condition_expr)
258
- end
259
- end
260
-
261
- def compile_js_cascade_and_for_hierarchical_broadcasting(condition_expr)
262
- # Compile individual trait references
263
- trait_compilations = condition_expr.args.map.with_index do |arg, i|
264
- trait_fn = compile_expr(arg)
265
- " const trait#{i} = #{trait_fn};"
266
- end.join("\n")
267
-
268
- trait_args = condition_expr.args.map.with_index { |_, i| "trait#{i}(ctx)" }.join(", ")
269
-
270
- <<~JAVASCRIPT
271
- (ctx) => {
272
- #{trait_compilations}
273
- const traitValues = [#{trait_args}];
274
- return kumiRuntime.cascadeAndHierarchicalBroadcasting(traitValues);
275
- }
276
- JAVASCRIPT
277
- end
278
-
279
- def compile_regular_call(fn_name, arg_exprs, _expr)
280
- # Functions that expect spread arrays instead of array arguments
281
- spread_functions = %w[concat]
282
-
283
- # Generate function call with arguments
284
- args_js = if spread_functions.include?(fn_name.to_s) && arg_exprs.length == 1
285
- # For spread functions with single array argument, spread the array
286
- "...arg0(ctx)"
287
- else
288
- arg_exprs.map.with_index { |_, i| "arg#{i}(ctx)" }.join(", ")
289
- end
290
-
291
- arg_declarations = arg_exprs.map.with_index do |arg_code, i|
292
- " const arg#{i} = #{arg_code};"
293
- end.join("\n")
294
-
295
- # Handle function names that need bracket notation (operators with special chars)
296
- fn_accessor = if fn_name.to_s.match?(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)
297
- "kumiRegistry.#{fn_name}"
298
- else
299
- "kumiRegistry[\"#{fn_name}\"]"
300
- end
301
-
302
- "(ctx) => {\n#{arg_declarations}\n return #{fn_accessor}(#{args_js});\n}"
303
- end
304
-
305
- def compile_vectorized_call(fn_name, arg_exprs, _expr)
306
- # Generate vectorized function call (broadcasting)
307
- args_js = arg_exprs.map.with_index { |_, i| "arg#{i}(ctx)" }.join(", ")
308
-
309
- arg_declarations = arg_exprs.map.with_index do |arg_code, i|
310
- " const arg#{i} = #{arg_code};"
311
- end.join("\n")
312
-
313
- "(ctx) => {\n#{arg_declarations}\n return kumiRuntime.vectorizedCall('#{fn_name}', [#{args_js}]);\n}"
314
- end
315
-
316
- def compile_field(node)
317
- name = node.name
318
- "(ctx) => {\n " \
319
- "if (ctx.hasOwnProperty('#{name}')) return ctx.#{name};\n " \
320
- "throw new Error(`Key '#{name}' not found. Available: ${Object.keys(ctx).join(', ')}`);\n" \
321
- "}"
322
- end
323
- end
324
-
325
- include JSExprCompilers
326
-
327
- def initialize(syntax_tree, analyzer_result)
328
- super
329
- @js_bindings = {}
330
- end
331
-
332
- def compile(**options)
333
- build_index
334
-
335
- # Compile each declaration to JavaScript
336
- @analysis.topo_order.each do |name|
337
- decl = @index[name] or raise("Unknown binding #{name}")
338
- compile_js_declaration(decl)
339
- end
340
-
341
- # Generate complete JavaScript module
342
- generate_js_module(@js_bindings, **options)
343
- end
344
-
345
- private
346
-
347
- def compile_js_declaration(decl)
348
- @current_declaration = decl.name
349
- kind = decl.is_a?(Kumi::Syntax::TraitDeclaration) ? :trait : :attr
350
- js_fn = compile_expr(decl.expression)
351
- @js_bindings[decl.name] = { type: kind, function: js_fn }
352
- @current_declaration = nil
353
- end
354
-
355
- def generate_js_module(bindings, format: :standalone, **options)
356
- case format
357
- when :standalone
358
- generate_standalone_js(bindings, **options)
359
- else
360
- raise ArgumentError, "Unknown format: #{format}"
361
- end
362
- end
363
-
364
- def generate_standalone_js(bindings, **options)
365
- # Generate a complete standalone JavaScript file
366
- functions_js = bindings.map do |name, meta|
367
- " #{name}: #{meta[:function]}"
368
- end.join(",\n")
369
-
370
- <<~JAVASCRIPT
371
- // Generated by Kumi JavaScript Transpiler
372
-
373
- // Kumi Runtime and Function Registry
374
- #{js_runtime_code}
375
-
376
- // Compiled Schema Bindings
377
- const bindings = {
378
- #{functions_js}
379
- };
380
-
381
- // Schema Runner
382
- class KumiRunner {
383
- constructor(input) {
384
- this.input = input;
385
- this.cache = new Map();
386
- this.functionsUsed = [#{(@analysis.state[:functions_required] || Set.new).to_a.sort.map { |f| "\"#{f}\"" }.join(', ')}];
387
- }
388
- #{' '}
389
- fetch(key) {
390
- if (this.cache.has(key)) {
391
- return this.cache.get(key);
392
- }
393
- #{' '}
394
- if (!bindings[key]) {
395
- throw new Error(`Unknown binding: ${key}`);
396
- }
397
- #{' '}
398
- const value = bindings[key](this.input);
399
- this.cache.set(key, value);
400
- return value;
401
- }
402
- #{' '}
403
- slice(...keys) {
404
- const result = {};
405
- keys.forEach(key => {
406
- result[key] = this.fetch(key);
407
- });
408
- return result;
409
- }
410
- }
411
-
412
- // Export interface
413
- const schema = {
414
- from: (input) => new KumiRunner(input)
415
- };
416
-
417
- // CommonJS export
418
- if (typeof module !== 'undefined' && module.exports) {
419
- module.exports = { schema };
420
- }
421
-
422
- // Browser global
423
- if (typeof window !== 'undefined') {
424
- window.schema = schema;
425
- #{"window.#{options[:export_name]} = schema;" if options[:export_name]}
426
- }
427
- JAVASCRIPT
428
- end
429
-
430
- def js_runtime_code
431
- # JavaScript runtime with sophisticated broadcasting support
432
- functions_required = @analysis.state[:functions_required] || Set.new
433
- <<~JAVASCRIPT
434
- // Function Registry
435
- #{FunctionRegistry.generate_js_code(functions_required: functions_required)}
436
-
437
- // Enhanced Kumi Runtime for sophisticated vectorized operations
438
- const kumiRuntime = {
439
- // Nested path traversal matching Ruby implementation exactly
440
- traverseNestedPath: function(data, path, operationMode, accessMode = 'object') {
441
- let result;
442
- #{' '}
443
- // Use specialized traversal for element access mode
444
- if (accessMode === 'element') {
445
- result = this.traverseElementPath(data, path, operationMode);
446
- } else {
447
- result = this.traversePathRecursive(data, path, operationMode, accessMode);
448
- }
449
- #{' '}
450
- // Post-process result based on operation mode
451
- if (operationMode === 'flatten') {
452
- return this.flattenCompletely(result);
453
- }
454
- return result;
455
- },
456
- #{' '}
457
- // Specialized traversal for element access mode (matches Ruby exactly)
458
- traverseElementPath: function(data, path, operationMode) {
459
- // Handle context wrapper by extracting the specific field
460
- if (data && typeof data === 'object' && !Array.isArray(data)) {
461
- const fieldName = path[0];
462
- const arrayData = data[fieldName];
463
- #{' '}
464
- // Always apply progressive traversal based on path depth
465
- // This gives us the structure at the correct nesting level for both
466
- // broadcast operations and structure operations
467
- if (Array.isArray(arrayData) && path.length > 1) {
468
- // Flatten exactly (path_depth - 1) levels to get the desired nesting level
469
- return this.flattenToDepth(arrayData, path.length - 1);
470
- } else {
471
- return arrayData;
472
- }
473
- } else {
474
- return data;
475
- }
476
- },
477
- #{' '}
478
- // Flatten array to specific depth (matches Ruby array.flatten(n))
479
- flattenToDepth: function(arr, depth) {
480
- if (depth <= 0 || !Array.isArray(arr)) {
481
- return arr;
482
- }
483
- #{' '}
484
- let result = arr.slice(); // Copy array
485
- #{' '}
486
- for (let i = 0; i < depth; i++) {
487
- let hasNestedArrays = false;
488
- const newResult = [];
489
- #{' '}
490
- for (const item of result) {
491
- if (Array.isArray(item)) {
492
- newResult.push(...item);
493
- hasNestedArrays = true;
494
- } else {
495
- newResult.push(item);
496
- }
497
- }
498
- #{' '}
499
- result = newResult;
500
- #{' '}
501
- // If no nested arrays found, no need to continue flattening
502
- if (!hasNestedArrays) {
503
- break;
504
- }
505
- }
506
- #{' '}
507
- return result;
508
- },
509
- #{' '}
510
- traversePathRecursive: function(data, path, operationMode, accessMode = 'object', originalPathLength = null) {
511
- // Track original path length to determine traversal depth
512
- originalPathLength = originalPathLength || path.length;
513
- const currentDepth = originalPathLength - path.length;
514
- #{' '}
515
- if (path.length === 0) return data;
516
- #{' '}
517
- const field = path[0];
518
- const remainingPath = path.slice(1);
519
- #{' '}
520
- if (remainingPath.length === 0) {
521
- // Final field - extract based on operation mode
522
- if (operationMode === 'broadcast' || operationMode === 'flatten') {
523
- // Extract field preserving array structure
524
- return this.extractFieldPreservingStructure(data, field, accessMode, currentDepth);
525
- } else {
526
- // Simple field access
527
- return Array.isArray(data) ?#{' '}
528
- data.map(item => this.accessField(item, field, accessMode, currentDepth)) :#{' '}
529
- this.accessField(data, field, accessMode, currentDepth);
530
- }
531
- } else if (Array.isArray(data)) {
532
- // Intermediate step - traverse deeper
533
- // Array of items - traverse each item
534
- return data.map(item =>#{' '}
535
- this.traversePathRecursive(
536
- this.accessField(item, field, accessMode, currentDepth),#{' '}
537
- remainingPath,#{' '}
538
- operationMode,#{' '}
539
- accessMode,#{' '}
540
- originalPathLength
541
- )
542
- );
543
- } else {
544
- // Single item - traverse directly
545
- return this.traversePathRecursive(
546
- this.accessField(data, field, accessMode, currentDepth),#{' '}
547
- remainingPath,#{' '}
548
- operationMode,#{' '}
549
- accessMode,#{' '}
550
- originalPathLength
551
- );
552
- }
553
- },
554
- #{' '}
555
- extractFieldPreservingStructure: function(data, field, accessMode = 'object', depth = 0) {
556
- if (Array.isArray(data)) {
557
- return data.map(item => this.extractFieldPreservingStructure(item, field, accessMode, depth));
558
- } else {
559
- return this.accessField(data, field, accessMode, depth);
560
- }
561
- },
562
- #{' '}
563
- accessField: function(data, field, accessMode, depth = 0) {
564
- if (accessMode === 'element') {
565
- // Element access mode - for nested arrays, we need to traverse one level deeper
566
- // This enables progressive path traversal like input.cube.layer.row.value
567
- if (data && typeof data === 'object' && !Array.isArray(data)) {
568
- return data[field];
569
- } else if (Array.isArray(data)) {
570
- // For element access, flatten one level to traverse deeper into nested structure
571
- return this.flattenToDepth(data, 1);
572
- } else {
573
- // If not an array, return as-is (leaf level)
574
- return data;
575
- }
576
- } else {
577
- // Object access mode - normal hash/object field access
578
- return data[field];
579
- }
580
- },
581
- #{' '}
582
- flattenCompletely: function(data) {
583
- const result = [];
584
- this.flattenRecursive(data, result);
585
- return result;
586
- },
587
- #{' '}
588
- flattenRecursive: function(data, result) {
589
- if (Array.isArray(data)) {
590
- data.forEach(item => this.flattenRecursive(item, result));
591
- } else {
592
- result.push(data);
593
- }
594
- },
595
- #{' '}
596
- // Sophisticated vectorized function call with metadata support
597
- vectorizedFunctionCall: function(fnName, args, compilationMeta) {
598
- const fn = kumiRegistry[fnName];
599
- if (!fn) throw new Error(`Unknown function: ${fnName}`);
600
- #{' '}
601
- // Check if any argument is vectorized (array)
602
- const hasVectorizedArgs = args.some(Array.isArray);
603
- #{' '}
604
- if (hasVectorizedArgs) {
605
- return this.vectorizedBroadcastingCall(fn, args, compilationMeta);
606
- } else {
607
- // All arguments are scalars - regular function call
608
- return fn(...args);
609
- }
610
- },
611
- #{' '}
612
- vectorizedBroadcastingCall: function(fn, values, compilationMeta) {
613
- // Find array dimensions for broadcasting
614
- const arrayValues = values.filter(v => Array.isArray(v));
615
- if (arrayValues.length === 0) return fn(...values);
616
- #{' '}
617
- // Check if we have deeply nested arrays (arrays containing arrays)
618
- const hasNestedArrays = arrayValues.some(arr =>#{' '}
619
- arr.some(item => Array.isArray(item))
620
- );
621
- #{' '}
622
- if (hasNestedArrays) {
623
- return this.hierarchicalBroadcasting(fn, values);
624
- } else {
625
- return this.simpleBroadcasting(fn, values);
626
- }
627
- },
628
- #{' '}
629
- simpleBroadcasting: function(fn, values) {
630
- const arrayValues = values.filter(v => Array.isArray(v));
631
- const arrayLength = arrayValues[0].length;
632
- const result = [];
633
- #{' '}
634
- for (let i = 0; i < arrayLength; i++) {
635
- const elementArgs = values.map(arg =>
636
- Array.isArray(arg) ? arg[i] : arg
637
- );
638
- result.push(fn(...elementArgs));
639
- }
640
- #{' '}
641
- return result;
642
- },
643
- #{' '}
644
- hierarchicalBroadcasting: function(fn, values) {
645
- // Handle hierarchical broadcasting for nested arrays
646
- const arrayValues = values.filter(v => Array.isArray(v));
647
- const maxDepthArray = arrayValues.reduce((max, arr) =>#{' '}
648
- this.calculateArrayDepth(arr) > this.calculateArrayDepth(max) ? arr : max
649
- );
650
- #{' '}
651
- return this.mapNestedStructure(maxDepthArray, (indices) => {
652
- const elementArgs = values.map(arg =>#{' '}
653
- this.navigateNestedIndices(arg, indices)
654
- );
655
- return fn(...elementArgs);
656
- });
657
- },
658
- #{' '}
659
- calculateArrayDepth: function(arr) {
660
- if (!Array.isArray(arr)) return 0;
661
- return 1 + Math.max(0, ...arr.map(item => this.calculateArrayDepth(item)));
662
- },
663
- #{' '}
664
- mapNestedStructure: function(structure, fn) {
665
- if (!Array.isArray(structure)) {
666
- return fn([]);
667
- }
668
- #{' '}
669
- return structure.map((item, index) => {
670
- if (Array.isArray(item)) {
671
- return this.mapNestedStructure(item, (innerIndices) =>#{' '}
672
- fn([index, ...innerIndices])
673
- );
674
- } else {
675
- return fn([index]);
676
- }
677
- });
678
- },
679
- #{' '}
680
- navigateNestedIndices: function(data, indices) {
681
- let current = data;
682
- for (const index of indices) {
683
- if (Array.isArray(current)) {
684
- current = current[index];
685
- } else {
686
- return current; // Scalar value - return as is
687
- }
688
- }
689
- return current;
690
- },
691
- #{' '}
692
- // Vectorized cascade execution with strategy support
693
- executeVectorizedCascade: function(condResults, resResults, baseResult, strategy) {
694
- if (!strategy) {
695
- // Fallback to simple cascade evaluation
696
- for (let i = 0; i < condResults.length; i++) {
697
- if (condResults[i]) return resResults[i];
698
- }
699
- return baseResult;
700
- }
701
- #{' '}
702
- switch (strategy.mode) {
703
- case 'hierarchical':
704
- return this.executeHierarchicalCascade(condResults, resResults, baseResult);
705
- case 'nested_array':
706
- case 'deep_nested_array':
707
- return this.executeNestedArrayCascade(condResults, resResults, baseResult);
708
- case 'simple_array':
709
- return this.executeSimpleArrayCascade(condResults, resResults, baseResult);
710
- default:
711
- return this.executeScalarCascade(condResults, resResults, baseResult);
712
- }
713
- },
714
- #{' '}
715
- executeHierarchicalCascade: function(condResults, resResults, baseResult) {
716
- // Find the result structure to use as template (deepest structure)
717
- const allValues = [...resResults, ...condResults, baseResult].filter(v => Array.isArray(v));
718
- const resultTemplate = allValues.reduce((max, v) =>#{' '}
719
- this.calculateArrayDepth(v) > this.calculateArrayDepth(max) ? v : max,#{' '}
720
- allValues[0] || []
721
- );
722
- #{' '}
723
- if (!resultTemplate || !Array.isArray(resultTemplate)) {
724
- return this.executeScalarCascade(condResults, resResults, baseResult);
725
- }
726
- #{' '}
727
- // Apply hierarchical cascade logic using the result structure as template
728
- return this.mapNestedStructure(resultTemplate, (indices) => {
729
- // Check conditional cases first with hierarchical broadcasting for conditions
730
- for (let i = 0; i < condResults.length; i++) {
731
- const condVal = this.navigateWithHierarchicalBroadcasting(condResults[i], indices, resultTemplate);
732
- if (condVal) {
733
- const resVal = this.navigateNestedIndices(resResults[i], indices);
734
- return resVal;
735
- }
736
- }
737
- #{' '}
738
- // If no conditions matched, use base result
739
- return this.navigateNestedIndices(baseResult, indices);
740
- });
741
- },
742
- #{' '}
743
- navigateWithHierarchicalBroadcasting: function(value, indices, template) {
744
- // Navigate through value with hierarchical broadcasting to match template structure
745
- const valueDepth = this.calculateArrayDepth(value);
746
- const templateDepth = this.calculateArrayDepth(template);
747
- #{' '}
748
- if (valueDepth < templateDepth) {
749
- // Value is at parent level - broadcast to child level by using fewer indices
750
- const parentIndices = indices.slice(0, valueDepth);
751
- return this.navigateNestedIndices(value, parentIndices);
752
- } else {
753
- // Same or deeper level - navigate normally
754
- return this.navigateNestedIndices(value, indices);
755
- }
756
- },
757
- #{' '}
758
- executeNestedArrayCascade: function(condResults, resResults, baseResult) {
759
- // Handle nested array cascades with structure preservation
760
- const firstArrayResult = resResults.find(r => Array.isArray(r)) ||#{' '}
761
- condResults.find(c => Array.isArray(c)) ||#{' '}
762
- (Array.isArray(baseResult) ? baseResult : []);
763
- #{' '}
764
- return this.mapNestedStructure(firstArrayResult, (indices) => {
765
- for (let i = 0; i < condResults.length; i++) {
766
- const condVal = this.navigateNestedIndices(condResults[i], indices);
767
- if (condVal) {
768
- return this.navigateNestedIndices(resResults[i], indices);
769
- }
770
- }
771
- return this.navigateNestedIndices(baseResult, indices);
772
- });
773
- },
774
- #{' '}
775
- executeSimpleArrayCascade: function(condResults, resResults, baseResult) {
776
- // Handle simple array cascades (flat arrays)
777
- const arrayLength = Math.max(
778
- ...condResults.filter(Array.isArray).map(arr => arr.length),
779
- ...resResults.filter(Array.isArray).map(arr => arr.length),
780
- Array.isArray(baseResult) ? baseResult.length : 0
781
- );
782
- #{' '}
783
- const result = [];
784
- for (let i = 0; i < arrayLength; i++) {
785
- let matched = false;
786
- for (let j = 0; j < condResults.length; j++) {
787
- const condVal = Array.isArray(condResults[j]) ? condResults[j][i] : condResults[j];
788
- if (condVal) {
789
- const resVal = Array.isArray(resResults[j]) ? resResults[j][i] : resResults[j];
790
- result[i] = resVal;
791
- matched = true;
792
- break;
793
- }
794
- }
795
- if (!matched) {
796
- result[i] = Array.isArray(baseResult) ? baseResult[i] : baseResult;
797
- }
798
- }
799
- return result;
800
- },
801
- #{' '}
802
- executeScalarCascade: function(condResults, resResults, baseResult) {
803
- // Handle scalar cascades (no arrays involved)
804
- for (let i = 0; i < condResults.length; i++) {
805
- if (condResults[i]) return resResults[i];
806
- }
807
- return baseResult;
808
- },
809
- #{' '}
810
- // Cascade AND with hierarchical broadcasting support
811
- cascadeAndHierarchicalBroadcasting: function(traitValues) {
812
- if (traitValues.length === 0) return true;
813
- if (traitValues.length === 1) return traitValues[0];
814
- #{' '}
815
- // Use the cascade_and function directly on the array structures
816
- return kumiRegistry.cascade_and(...traitValues);
817
- },
818
- #{' '}
819
- // Element-wise AND with hierarchical broadcasting (matches Ruby implementation)
820
- elementWiseAnd: function(a, b) {
821
- // Handle different type combinations
822
- if (Array.isArray(a) && Array.isArray(b)) {
823
- // Both are arrays - handle hierarchical broadcasting
824
- if (this.hierarchicalBroadcastingNeeded(a, b)) {
825
- return this.performHierarchicalAnd(a, b);
826
- } else {
827
- // Same structure - use zip for element-wise operations
828
- return a.map((elemA, idx) => this.elementWiseAnd(elemA, b[idx]));
829
- }
830
- } else if (Array.isArray(a)) {
831
- // Broadcast scalar b to array a
832
- return a.map(elem => this.elementWiseAnd(elem, b));
833
- } else if (Array.isArray(b)) {
834
- // Broadcast scalar a to array b
835
- return b.map(elem => this.elementWiseAnd(a, elem));
836
- } else {
837
- // Both are scalars - simple AND
838
- return a && b;
839
- }
840
- },
841
- #{' '}
842
- hierarchicalBroadcastingNeeded: function(a, b) {
843
- // Check if arrays have different nesting depths
844
- const depthA = this.calculateArrayDepth(a);
845
- const depthB = this.calculateArrayDepth(b);
846
- return depthA !== depthB;
847
- },
848
- #{' '}
849
- performHierarchicalAnd: function(a, b) {
850
- // Determine which is parent (lower depth) and which is child (higher depth)
851
- const depthA = this.calculateArrayDepth(a);
852
- const depthB = this.calculateArrayDepth(b);
853
- #{' '}
854
- if (depthA < depthB) {
855
- // a is parent, b is child
856
- return this.broadcastParentToChild(a, b);
857
- } else {
858
- // b is parent, a is child
859
- return this.broadcastParentToChild(b, a);
860
- }
861
- },
862
- #{' '}
863
- broadcastParentToChild: function(parent, child) {
864
- // Map over child structure, broadcasting parent values appropriately
865
- return this.mapNestedStructure(child, (indices) => {
866
- // Navigate to appropriate parent element using fewer indices
867
- const parentIndices = indices.slice(0, this.calculateArrayDepth(parent));
868
- const parentValue = this.navigateNestedIndices(parent, parentIndices);
869
- const childValue = this.navigateNestedIndices(child, indices);
870
- return parentValue && childValue;
871
- });
872
- }
873
- };
874
- JAVASCRIPT
875
- end
876
- end
877
- end
878
- end