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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - André Muta
@@ -31,6 +31,7 @@ extra_rdoc_files: []
31
31
  files:
32
32
  - ".rspec"
33
33
  - ".rubocop.yml"
34
+ - CHANGELOG.md
34
35
  - CLAUDE.md
35
36
  - LICENSE.txt
36
37
  - README.md
@@ -39,6 +40,7 @@ files:
39
40
  - docs/DSL.md
40
41
  - docs/FUNCTIONS.md
41
42
  - docs/SYNTAX.md
43
+ - docs/VECTOR_SEMANTICS.md
42
44
  - docs/compiler_design_principles.md
43
45
  - docs/development/README.md
44
46
  - docs/development/error-reporting.md
@@ -74,23 +76,31 @@ files:
74
76
  - lib/kumi/core/analyzer/passes/broadcast_detector.rb
75
77
  - lib/kumi/core/analyzer/passes/declaration_validator.rb
76
78
  - lib/kumi/core/analyzer/passes/dependency_resolver.rb
79
+ - lib/kumi/core/analyzer/passes/input_access_planner_pass.rb
77
80
  - lib/kumi/core/analyzer/passes/input_collector.rb
81
+ - lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb
82
+ - lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb
78
83
  - lib/kumi/core/analyzer/passes/name_indexer.rb
79
84
  - lib/kumi/core/analyzer/passes/pass_base.rb
85
+ - lib/kumi/core/analyzer/passes/scope_resolution_pass.rb
80
86
  - lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb
81
87
  - lib/kumi/core/analyzer/passes/toposorter.rb
82
88
  - lib/kumi/core/analyzer/passes/type_checker.rb
83
89
  - lib/kumi/core/analyzer/passes/type_consistency_checker.rb
84
- - lib/kumi/core/analyzer/passes/type_inferencer.rb
90
+ - lib/kumi/core/analyzer/passes/type_inferencer_pass.rb
85
91
  - lib/kumi/core/analyzer/passes/unsat_detector.rb
86
92
  - lib/kumi/core/analyzer/passes/visitor_pass.rb
93
+ - lib/kumi/core/analyzer/plans.rb
94
+ - lib/kumi/core/analyzer/structs/access_plan.rb
95
+ - lib/kumi/core/analyzer/structs/input_meta.rb
87
96
  - lib/kumi/core/atom_unsat_solver.rb
88
- - lib/kumi/core/cascade_executor_builder.rb
89
- - lib/kumi/core/compiled_schema.rb
90
- - lib/kumi/core/compiler/expression_compiler.rb
91
- - lib/kumi/core/compiler/function_invoker.rb
92
- - lib/kumi/core/compiler/path_traversal_compiler.rb
93
- - lib/kumi/core/compiler/reference_compiler.rb
97
+ - lib/kumi/core/compiler/access_builder.rb
98
+ - lib/kumi/core/compiler/access_planner.rb
99
+ - lib/kumi/core/compiler/accessors/base.rb
100
+ - lib/kumi/core/compiler/accessors/each_indexed_accessor.rb
101
+ - lib/kumi/core/compiler/accessors/materialize_accessor.rb
102
+ - lib/kumi/core/compiler/accessors/ravel_accessor.rb
103
+ - lib/kumi/core/compiler/accessors/read_accessor.rb
94
104
  - lib/kumi/core/compiler_base.rb
95
105
  - lib/kumi/core/constraint_relationship_solver.rb
96
106
  - lib/kumi/core/domain/enum_analyzer.rb
@@ -100,7 +110,6 @@ files:
100
110
  - lib/kumi/core/error_reporter.rb
101
111
  - lib/kumi/core/error_reporting.rb
102
112
  - lib/kumi/core/errors.rb
103
- - lib/kumi/core/evaluation_wrapper.rb
104
113
  - lib/kumi/core/explain.rb
105
114
  - lib/kumi/core/export.rb
106
115
  - lib/kumi/core/export/deserializer.rb
@@ -122,10 +131,14 @@ files:
122
131
  - lib/kumi/core/input/type_matcher.rb
123
132
  - lib/kumi/core/input/validator.rb
124
133
  - lib/kumi/core/input/violation_creator.rb
134
+ - lib/kumi/core/ir.rb
135
+ - lib/kumi/core/ir/execution_engine.rb
136
+ - lib/kumi/core/ir/execution_engine/combinators.rb
137
+ - lib/kumi/core/ir/execution_engine/interpreter.rb
138
+ - lib/kumi/core/ir/execution_engine/values.rb
125
139
  - lib/kumi/core/json_schema.rb
126
140
  - lib/kumi/core/json_schema/generator.rb
127
141
  - lib/kumi/core/json_schema/validator.rb
128
- - lib/kumi/core/nested_structure_utils.rb
129
142
  - lib/kumi/core/ruby_parser.rb
130
143
  - lib/kumi/core/ruby_parser/build_context.rb
131
144
  - lib/kumi/core/ruby_parser/declaration_reference_proxy.rb
@@ -140,7 +153,6 @@ files:
140
153
  - lib/kumi/core/ruby_parser/parser.rb
141
154
  - lib/kumi/core/ruby_parser/schema_builder.rb
142
155
  - lib/kumi/core/ruby_parser/sugar.rb
143
- - lib/kumi/core/schema_instance.rb
144
156
  - lib/kumi/core/types.rb
145
157
  - lib/kumi/core/types/builder.rb
146
158
  - lib/kumi/core/types/compatibility.rb
@@ -148,14 +160,13 @@ files:
148
160
  - lib/kumi/core/types/inference.rb
149
161
  - lib/kumi/core/types/normalizer.rb
150
162
  - lib/kumi/core/types/validator.rb
151
- - lib/kumi/core/vectorized_function_builder.rb
152
163
  - lib/kumi/errors.rb
153
164
  - lib/kumi/js.rb
154
- - lib/kumi/js/compiler.rb
155
- - lib/kumi/js/function_registry.rb
156
165
  - lib/kumi/registry.rb
166
+ - lib/kumi/runtime/executable.rb
157
167
  - lib/kumi/schema.rb
158
168
  - lib/kumi/schema_metadata.rb
169
+ - lib/kumi/support/ir_dump.rb
159
170
  - lib/kumi/support/s_expression_printer.rb
160
171
  - lib/kumi/syntax/array_expression.rb
161
172
  - lib/kumi/syntax/call_expression.rb
@@ -167,12 +178,12 @@ files:
167
178
  - lib/kumi/syntax/input_element_reference.rb
168
179
  - lib/kumi/syntax/input_reference.rb
169
180
  - lib/kumi/syntax/literal.rb
181
+ - lib/kumi/syntax/location.rb
170
182
  - lib/kumi/syntax/node.rb
171
183
  - lib/kumi/syntax/root.rb
172
184
  - lib/kumi/syntax/trait_declaration.rb
173
185
  - lib/kumi/syntax/value_declaration.rb
174
186
  - lib/kumi/version.rb
175
- - migrate_to_core_iterative.rb
176
187
  - scripts/analyze_broadcast_methods.rb
177
188
  - scripts/analyze_cascade_methods.rb
178
189
  - scripts/check_broadcasting_coverage.rb
@@ -1,132 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Core
5
- # Builds cascade execution lambdas from analysis metadata
6
- class CascadeExecutorBuilder
7
- include NestedStructureUtils
8
-
9
- def self.build_executor(strategy, analysis_state)
10
- new(strategy, analysis_state).build
11
- end
12
-
13
- def initialize(strategy, analysis_state)
14
- @strategy = strategy
15
- @analysis_state = analysis_state
16
- end
17
-
18
- def build
19
- case @strategy[:mode]
20
- when :hierarchical
21
- build_hierarchical_executor
22
- when :nested_array, :deep_nested_array
23
- build_nested_array_executor
24
- when :simple_array
25
- build_simple_array_executor
26
- else
27
- build_scalar_executor
28
- end
29
- end
30
-
31
- private
32
-
33
- def build_hierarchical_executor
34
- lambda do |cond_results, res_results, base_result, pairs|
35
- # Find the result structure to use as template (deepest structure)
36
- all_values = (res_results + cond_results + [base_result].compact).select { |v| v.is_a?(Array) }
37
- result_template = all_values.max_by { |v| calculate_array_depth(v) }
38
-
39
- return execute_scalar_cascade(cond_results, res_results, base_result, pairs) unless result_template
40
-
41
- # Apply hierarchical cascade logic using the result structure as template
42
- map_nested_structure(result_template) do |*indices|
43
- result = nil
44
-
45
- # Check conditional cases first with hierarchical broadcasting for conditions
46
- pairs.each_with_index do |(_cond, _res), pair_idx|
47
- cond_val = navigate_with_hierarchical_broadcasting(cond_results[pair_idx], indices, result_template)
48
- next unless cond_val
49
-
50
- res_val = navigate_nested_indices(res_results[pair_idx], indices)
51
- result = res_val
52
- break
53
- end
54
-
55
- # If no conditional case matched, use base case
56
- result = navigate_nested_indices(base_result, indices) if result.nil? && base_result
57
-
58
- result
59
- end
60
- end
61
- end
62
-
63
- def build_nested_array_executor
64
- lambda do |cond_results, res_results, base_result, pairs|
65
- # For nested arrays, we need to find the structure template
66
- structure_template = find_structure_template(cond_results + res_results + [base_result].compact)
67
- return execute_scalar_cascade(cond_results, res_results, base_result, pairs) unless structure_template
68
-
69
- # Apply cascade logic recursively through the nested structure
70
- map_nested_structure(structure_template) do |*indices|
71
- result = nil
72
-
73
- # Check conditional cases first
74
- pairs.each_with_index do |(_cond, _res), pair_idx|
75
- cond_val = navigate_nested_indices(cond_results[pair_idx], indices)
76
- next unless cond_val
77
-
78
- res_val = navigate_nested_indices(res_results[pair_idx], indices)
79
- result = res_val
80
- break
81
- end
82
-
83
- # If no conditional case matched, use base case
84
- result = navigate_nested_indices(base_result, indices) if result.nil? && base_result
85
-
86
- result
87
- end
88
- end
89
- end
90
-
91
- def build_simple_array_executor
92
- lambda do |cond_results, res_results, base_result, pairs|
93
- array_length = determine_array_length(cond_results + res_results + [base_result].compact)
94
-
95
- (0...array_length).map do |i|
96
- result = nil
97
- # Check conditional cases first
98
- pairs.each_with_index do |(_cond, _res), pair_idx|
99
- cond_val = extract_element_at_index(cond_results[pair_idx], i)
100
- next unless cond_val
101
-
102
- res_val = extract_element_at_index(res_results[pair_idx], i)
103
- result = res_val
104
- break
105
- end
106
-
107
- # If no conditional case matched, use base case
108
- result = extract_element_at_index(base_result, i) if result.nil? && base_result
109
-
110
- result
111
- end
112
- end
113
- end
114
-
115
- def build_scalar_executor
116
- lambda do |cond_results, res_results, base_result, pairs|
117
- pairs.each_with_index do |(_cond, _res), pair_idx|
118
- return res_results[pair_idx] if cond_results[pair_idx]
119
- end
120
- base_result
121
- end
122
- end
123
-
124
- def execute_scalar_cascade(cond_results, res_results, base_result, pairs)
125
- pairs.each_with_index do |(_cond, _res), pair_idx|
126
- return res_results[pair_idx] if cond_results[pair_idx]
127
- end
128
- base_result
129
- end
130
- end
131
- end
132
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Core
5
- class CompiledSchema
6
- attr_reader :bindings
7
-
8
- def initialize(bindings)
9
- @bindings = bindings.freeze
10
- end
11
-
12
- def evaluate(ctx, *key_names)
13
- target_keys = key_names.empty? ? @bindings.keys : validate_keys(key_names)
14
-
15
- target_keys.each_with_object({}) do |key, result|
16
- result[key] = evaluate_binding(key, ctx)
17
- end
18
- end
19
-
20
- def evaluate_binding(key, ctx)
21
- memo = ctx.instance_variable_get(:@__schema_cache__)
22
- return memo[key] if memo&.key?(key)
23
-
24
- value = @bindings[key][1].call(ctx)
25
- memo[key] = value if memo
26
- value
27
- end
28
-
29
- private
30
-
31
- def hash_like?(obj)
32
- obj.respond_to?(:key?) && obj.respond_to?(:[])
33
- end
34
-
35
- def validate_keys(keys)
36
- unknown_keys = keys - @bindings.keys
37
- return keys if unknown_keys.empty?
38
-
39
- raise Kumi::Errors::RuntimeError, "No binding named #{unknown_keys.first}"
40
- end
41
- end
42
- end
43
- end
@@ -1,146 +0,0 @@
1
- module Kumi
2
- module Core
3
- module Compiler
4
- module ExpressionCompiler
5
- private
6
-
7
- def compile_list(expr)
8
- fns = expr.elements.map { |e| compile_expr(e) }
9
- ->(ctx) { fns.map { |fn| fn.call(ctx) } }
10
- end
11
-
12
- def compile_call(expr)
13
- fn_name = expr.fn_name
14
- arg_fns = expr.args.map { |a| compile_expr(a) }
15
-
16
- # Get compilation metadata once
17
- compilation_meta = @analysis.state[:broadcasts]&.dig(:compilation_metadata, @current_declaration)
18
-
19
- # Check if this is a vectorized operation
20
- if vectorized_operation?(expr)
21
- # Build vectorized executor at COMPILATION time
22
- executor = Core::VectorizedFunctionBuilder.build_executor(fn_name, compilation_meta, @analysis.state)
23
-
24
- lambda do |ctx|
25
- # Evaluate arguments and use pre-built executor at RUNTIME
26
- values = arg_fns.map { |fn| fn.call(ctx) }
27
- executor.call(values, expr.loc)
28
- end
29
- else
30
- # Use pre-computed function call strategy
31
- function_strategy = compilation_meta&.dig(:function_call_strategy) || {}
32
-
33
- if function_strategy[:flattening_required]
34
- flattening_info = @analysis.state[:broadcasts][:flattening_declarations][@current_declaration]
35
- ->(ctx) { invoke_function_with_flattening(fn_name, arg_fns, ctx, expr.loc, expr.args, flattening_info) }
36
- else
37
- ->(ctx) { invoke_function(fn_name, arg_fns, ctx, expr.loc) }
38
- end
39
- end
40
- end
41
-
42
- def compile_cascade(expr)
43
- # Use metadata to determine if this cascade is vectorized
44
- broadcast_meta = @analysis.state[:broadcasts]
45
- cascade_info = @current_declaration && broadcast_meta&.dig(:vectorized_operations, @current_declaration)
46
- is_vectorized = cascade_info && cascade_info[:source] == :cascade_with_vectorized_conditions_or_results
47
-
48
- # Separate conditional cases from base case
49
- conditional_cases = expr.cases.select(&:condition)
50
- base_case = expr.cases.find { |c| c.condition.nil? }
51
-
52
- # Compile conditional pairs
53
- pairs = conditional_cases.map do |c|
54
- condition_fn = if is_vectorized
55
- transform_vectorized_condition(c.condition)
56
- else
57
- compile_expr(c.condition)
58
- end
59
- result_fn = compile_expr(c.result)
60
- [condition_fn, result_fn]
61
- end
62
-
63
- # Compile base case
64
- base_fn = base_case ? compile_expr(base_case.result) : nil
65
-
66
- if is_vectorized
67
- # Capture the current declaration name in the closure
68
- current_decl_name = @current_declaration
69
-
70
- # Get pre-computed cascade strategy
71
- compilation_meta = @analysis.state[:broadcasts]&.dig(:compilation_metadata, current_decl_name)
72
- cascade_info = compilation_meta&.dig(:cascade_info) || {}
73
-
74
- # Build executor at COMPILATION time (outside the lambda)
75
- strategy = @analysis.state[:broadcasts][:cascade_strategies][current_decl_name]
76
- executor = strategy ? Core::CascadeExecutorBuilder.build_executor(strategy, @analysis.state) : nil
77
-
78
- # Metadata-driven vectorized cascade evaluation
79
- lambda do |ctx|
80
- # Evaluate all conditions and results
81
- cond_results = pairs.map { |cond, _res| cond.call(ctx) }
82
- res_results = pairs.map { |_cond, res| res.call(ctx) }
83
- base_result = base_fn&.call(ctx)
84
-
85
- if ENV["DEBUG_CASCADE"]
86
- puts "DEBUG: Vectorized cascade evaluation for #{current_decl_name}:"
87
- cond_results.each_with_index { |cr, i| puts " cond_results[#{i}]: #{cr.inspect}" }
88
- res_results.each_with_index { |rr, i| puts " res_results[#{i}]: #{rr.inspect}" }
89
- puts " base_result: #{base_result.inspect}"
90
- puts " Pre-computed cascade_info: #{cascade_info.inspect}"
91
- end
92
-
93
- # Use pre-built executor at RUNTIME
94
- if executor
95
- executor.call(cond_results, res_results, base_result, pairs)
96
- else
97
- # Fallback for cases without strategy
98
- pairs.each_with_index do |(_cond, _res), pair_idx|
99
- return res_results[pair_idx] if cond_results[pair_idx]
100
- end
101
- base_result
102
- end
103
- end
104
- else
105
- # Non-vectorized cascade - standard evaluation
106
- lambda do |ctx|
107
- pairs.each { |cond, res| return res.call(ctx) if cond.call(ctx) }
108
- # If no conditional case matched, return base case
109
- base_fn&.call(ctx)
110
- end
111
- end
112
- end
113
-
114
- def transform_vectorized_condition(condition_expr)
115
- if condition_expr.is_a?(Kumi::Syntax::CallExpression) &&
116
- condition_expr.fn_name == :cascade_and
117
-
118
- puts " transform_vectorized_condition: handling cascade_and with #{condition_expr.args.length} args" if ENV["DEBUG_CASCADE"]
119
-
120
- # For cascade_and in vectorized contexts, we need to compile it as a structure-level operation
121
- # rather than element-wise operation
122
- return compile_cascade_and_for_hierarchical_broadcasting(condition_expr)
123
- end
124
-
125
- # Otherwise compile normally
126
- compile_expr(condition_expr)
127
- end
128
-
129
- def compile_cascade_and_for_hierarchical_broadcasting(condition_expr)
130
- # Compile individual trait references
131
- trait_fns = condition_expr.args.map { |arg| compile_expr(arg) }
132
-
133
- lambda do |ctx|
134
- # Evaluate all traits to get their array structures
135
- trait_values = trait_fns.map { |fn| fn.call(ctx) }
136
-
137
- fn = Kumi::Registry.fetch(:cascade_and)
138
- result = fn.call(*trait_values)
139
-
140
- result
141
- end
142
- end
143
- end
144
- end
145
- end
146
- end
@@ -1,55 +0,0 @@
1
- module Kumi
2
- module Core
3
- module Compiler
4
- module FunctionInvoker
5
- private
6
-
7
- def invoke_function(name, arg_fns, ctx, loc)
8
- fn = Kumi::Registry.fetch(name)
9
- values = arg_fns.map { |fn| fn.call(ctx) }
10
-
11
- # REMOVED AUTO-FLATTENING: Let operations work on the structure they receive
12
- # If flattening is needed, it should be handled by explicit operation modes
13
- # in the InputElementReference compilation, not here.
14
- fn.call(*values)
15
- rescue StandardError => e
16
- # Preserve original error class and backtrace while adding context
17
- enhanced_message = "Error calling fn(:#{name}) at #{loc}: #{e.message}"
18
-
19
- if e.is_a?(Kumi::Core::Errors::Error)
20
- # Re-raise Kumi errors with enhanced message but preserve type
21
- e.define_singleton_method(:message) { enhanced_message }
22
- raise e
23
- else
24
- # For non-Kumi errors, wrap in RuntimeError but preserve original error info
25
- runtime_error = Errors::RuntimeError.new(enhanced_message)
26
- runtime_error.set_backtrace(e.backtrace)
27
- runtime_error.define_singleton_method(:cause) { e }
28
- raise runtime_error
29
- end
30
- end
31
-
32
- def invoke_function_with_flattening(name, arg_fns, ctx, loc, _original_args, _flattening_info)
33
- fn = Kumi::Registry.fetch(name)
34
-
35
- # Use pre-computed flattening indices from analysis
36
- compilation_meta = @analysis.state[:broadcasts]&.dig(:compilation_metadata, @current_declaration)
37
- flatten_indices = compilation_meta&.dig(:function_call_strategy, :flatten_argument_indices) || []
38
-
39
- values = arg_fns.map.with_index do |arg_fn, index|
40
- value = arg_fn.call(ctx)
41
- flatten_indices.include?(index) ? flatten_completely(value) : value
42
- end
43
-
44
- fn.call(*values)
45
- rescue StandardError => e
46
- enhanced_message = "Error calling fn(:#{name}) at #{loc}: #{e.message}"
47
- runtime_error = Errors::RuntimeError.new(enhanced_message)
48
- runtime_error.set_backtrace(e.backtrace)
49
- runtime_error.define_singleton_method(:cause) { e }
50
- raise runtime_error
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,158 +0,0 @@
1
- module Kumi
2
- module Core
3
- module Compiler
4
- module PathTraversalCompiler
5
- private
6
-
7
- def compile_element_field_reference(expr)
8
- path = expr.path
9
-
10
- # Check if we have nested paths metadata for this path
11
- nested_paths = @analysis.state[:broadcasts]&.dig(:nested_paths)
12
- unless nested_paths && nested_paths[path]
13
- raise Errors::CompilationError, "Missing nested path metadata for #{path.inspect}. This indicates an analyzer bug."
14
- end
15
-
16
- # Determine operation mode based on context
17
- operation_mode = determine_operation_mode_for_path(path)
18
- path_metadata = nested_paths[path]
19
- lambda do |ctx|
20
- traverse_nested_path(ctx, path, operation_mode, path_metadata)
21
- end
22
-
23
- # ERROR: All nested paths should have metadata from the analyzer
24
- # If we reach here, it means the BroadcastDetector didn't process this path
25
- end
26
-
27
- # Metadata-driven nested array traversal using the traversal algorithm from our design
28
- def traverse_nested_path(data, path, operation_mode, path_metadata = nil)
29
- access_mode = path_metadata&.dig(:access_mode) || :object
30
-
31
- # Use specialized traversal for element access mode
32
- result = if access_mode == :element
33
- traverse_element_path(data, path, operation_mode)
34
- else
35
- traverse_path_recursive(data, path, operation_mode, access_mode)
36
- end
37
-
38
- # Post-process result based on operation mode
39
- case operation_mode
40
- when :flatten
41
- # Completely flatten nested arrays for aggregation
42
- flatten_completely(result)
43
- else
44
- result
45
- end
46
- end
47
-
48
- # Specialized traversal for element access mode
49
- # In element access, we need to extract the specific field from EvaluationWrapper
50
- # then apply progressive traversal based on path depth
51
- def traverse_element_path(data, path, _operation_mode)
52
- # Handle EvaluationWrapper by extracting the specific field
53
- if data.is_a?(Core::EvaluationWrapper)
54
- field_name = path.first
55
- array_data = data[field_name]
56
-
57
- # Always apply progressive traversal based on path depth
58
- # This gives us the structure at the correct nesting level for both
59
- # broadcast operations and structure operations
60
- if array_data.is_a?(Array) && path.length > 1
61
- # Flatten exactly (path_depth - 1) levels to get the desired nesting level
62
- array_data.flatten(path.length - 1)
63
- else
64
- array_data
65
- end
66
- else
67
- data
68
- end
69
- end
70
-
71
- def traverse_path_recursive(data, path, operation_mode, access_mode = :object, original_path_length = nil)
72
- # Track original path length to determine traversal depth
73
- original_path_length ||= path.length
74
- current_depth = original_path_length - path.length
75
-
76
- return data if path.empty?
77
-
78
- field = path.first
79
- remaining_path = path[1..]
80
-
81
- if remaining_path.empty?
82
- # Final field - extract based on operation mode
83
- case operation_mode
84
- when :broadcast, :flatten
85
- # Extract field preserving array structure
86
- extract_field_preserving_structure(data, field, access_mode, current_depth)
87
- else
88
- # Simple field access
89
- if data.is_a?(Array)
90
- data.map do |item|
91
- access_field(item, field, access_mode, current_depth)
92
- end
93
- else
94
- access_field(data, field, access_mode, current_depth)
95
- end
96
- end
97
- elsif data.is_a?(Array)
98
- # Intermediate step - traverse deeper
99
- # Array of items - traverse each item
100
- data.map do |item|
101
- traverse_path_recursive(access_field(item, field, access_mode, current_depth), remaining_path, operation_mode, access_mode,
102
- original_path_length)
103
- end
104
- else
105
- # Single item - traverse directly
106
- traverse_path_recursive(access_field(data, field, access_mode, current_depth), remaining_path, operation_mode, access_mode,
107
- original_path_length)
108
- end
109
- end
110
-
111
- def extract_field_preserving_structure(data, field, access_mode = :object, depth = 0)
112
- if data.is_a?(Array)
113
- data.map { |item| extract_field_preserving_structure(item, field, access_mode, depth) }
114
- else
115
- access_field(data, field, access_mode, depth)
116
- end
117
- end
118
-
119
- def access_field(data, field, access_mode, _depth = 0)
120
- case access_mode
121
- when :element
122
- # Element access mode - for nested arrays, we need to traverse one level deeper
123
- # This enables progressive path traversal like input.cube.layer.row.value
124
- if data.is_a?(Core::EvaluationWrapper)
125
- data[field]
126
- elsif data.is_a?(Array)
127
- # For element access, flatten one level to traverse deeper into nested structure
128
- data.flatten(1)
129
- else
130
- # If not an array, return as-is (leaf level)
131
- data
132
- end
133
- when :object
134
- # Object access mode - normal hash/object field access
135
- data[field]
136
- else
137
- # Default to object access
138
- data[field]
139
- end
140
- end
141
-
142
- def flatten_completely(data)
143
- result = []
144
- flatten_recursive(data, result)
145
- result
146
- end
147
-
148
- def flatten_recursive(data, result)
149
- if data.is_a?(Array)
150
- data.each { |item| flatten_recursive(item, result) }
151
- else
152
- result << data
153
- end
154
- end
155
- end
156
- end
157
- end
158
- end