kumi 0.0.10 → 0.0.12
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/.rubocop.yml +1 -1
- data/CHANGELOG.md +23 -0
- data/CLAUDE.md +7 -231
- data/README.md +5 -5
- data/docs/SYNTAX.md +66 -0
- data/docs/VECTOR_SEMANTICS.md +286 -0
- data/docs/features/hierarchical-broadcasting.md +67 -1
- data/docs/features/input-declaration-system.md +16 -0
- data/docs/features/s-expression-printer.md +2 -2
- data/lib/kumi/analyzer.rb +34 -12
- data/lib/kumi/compiler.rb +2 -12
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +157 -64
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +1 -1
- data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +47 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +123 -101
- data/lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb +293 -0
- data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +993 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +2 -2
- data/lib/kumi/core/analyzer/passes/scope_resolution_pass.rb +346 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +2 -1
- data/lib/kumi/core/analyzer/passes/toposorter.rb +9 -3
- data/lib/kumi/core/analyzer/passes/type_checker.rb +3 -3
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +2 -2
- data/lib/kumi/core/analyzer/passes/{type_inferencer.rb → type_inferencer_pass.rb} +4 -4
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +2 -2
- data/lib/kumi/core/analyzer/plans.rb +52 -0
- data/lib/kumi/core/analyzer/structs/access_plan.rb +20 -0
- data/lib/kumi/core/analyzer/structs/input_meta.rb +29 -0
- data/lib/kumi/core/compiler/access_builder.rb +36 -0
- data/lib/kumi/core/compiler/access_planner.rb +219 -0
- data/lib/kumi/core/compiler/accessors/base.rb +69 -0
- data/lib/kumi/core/compiler/accessors/each_indexed_accessor.rb +84 -0
- data/lib/kumi/core/compiler/accessors/materialize_accessor.rb +55 -0
- data/lib/kumi/core/compiler/accessors/ravel_accessor.rb +73 -0
- data/lib/kumi/core/compiler/accessors/read_accessor.rb +41 -0
- data/lib/kumi/core/compiler_base.rb +2 -2
- data/lib/kumi/core/error_reporter.rb +6 -5
- data/lib/kumi/core/errors.rb +4 -0
- data/lib/kumi/core/explain.rb +157 -205
- data/lib/kumi/core/export/node_builders.rb +2 -2
- data/lib/kumi/core/export/node_serializers.rb +1 -1
- data/lib/kumi/core/function_registry/collection_functions.rb +21 -10
- data/lib/kumi/core/function_registry/conditional_functions.rb +14 -4
- data/lib/kumi/core/function_registry/function_builder.rb +142 -55
- data/lib/kumi/core/function_registry/logical_functions.rb +5 -5
- data/lib/kumi/core/function_registry/stat_functions.rb +2 -2
- data/lib/kumi/core/function_registry.rb +126 -108
- data/lib/kumi/core/input/validator.rb +1 -1
- data/lib/kumi/core/ir/execution_engine/combinators.rb +117 -0
- data/lib/kumi/core/ir/execution_engine/interpreter.rb +336 -0
- data/lib/kumi/core/ir/execution_engine/values.rb +46 -0
- data/lib/kumi/core/ir/execution_engine.rb +50 -0
- data/lib/kumi/core/ir.rb +58 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +2 -2
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +0 -12
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +36 -15
- data/lib/kumi/core/ruby_parser/input_builder.rb +30 -9
- data/lib/kumi/core/ruby_parser/parser.rb +1 -1
- data/lib/kumi/core/ruby_parser/schema_builder.rb +2 -2
- data/lib/kumi/core/ruby_parser/sugar.rb +7 -0
- data/lib/kumi/core/types/validator.rb +1 -1
- data/lib/kumi/registry.rb +14 -79
- data/lib/kumi/runtime/executable.rb +213 -0
- data/lib/kumi/schema.rb +14 -3
- data/lib/kumi/schema_metadata.rb +2 -2
- data/lib/kumi/support/ir_dump.rb +491 -0
- data/lib/kumi/support/s_expression_printer.rb +1 -1
- data/lib/kumi/syntax/location.rb +5 -0
- data/lib/kumi/syntax/node.rb +0 -1
- data/lib/kumi/syntax/root.rb +2 -2
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +6 -15
- metadata +37 -19
- data/lib/kumi/core/cascade_executor_builder.rb +0 -132
- data/lib/kumi/core/compiled_schema.rb +0 -43
- data/lib/kumi/core/compiler/expression_compiler.rb +0 -146
- data/lib/kumi/core/compiler/function_invoker.rb +0 -55
- data/lib/kumi/core/compiler/path_traversal_compiler.rb +0 -158
- data/lib/kumi/core/compiler/reference_compiler.rb +0 -46
- data/lib/kumi/core/evaluation_wrapper.rb +0 -40
- data/lib/kumi/core/nested_structure_utils.rb +0 -78
- data/lib/kumi/core/schema_instance.rb +0 -115
- data/lib/kumi/core/vectorized_function_builder.rb +0 -88
- data/lib/kumi/js/compiler.rb +0 -878
- data/lib/kumi/js/function_registry.rb +0 -333
- data/migrate_to_core_iterative.rb +0 -938
@@ -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
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Kumi
|
2
|
-
module Core
|
3
|
-
module Compiler
|
4
|
-
module ReferenceCompiler
|
5
|
-
private
|
6
|
-
|
7
|
-
def compile_literal(expr)
|
8
|
-
v = expr.value
|
9
|
-
->(_ctx) { v }
|
10
|
-
end
|
11
|
-
|
12
|
-
def compile_field_node(expr)
|
13
|
-
compile_field(expr)
|
14
|
-
end
|
15
|
-
|
16
|
-
def compile_binding_node(expr)
|
17
|
-
name = expr.name
|
18
|
-
# Handle forward references in cycles by deferring binding lookup to runtime
|
19
|
-
lambda do |ctx|
|
20
|
-
fn = @bindings[name].last
|
21
|
-
fn.call(ctx)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def compile_field(node)
|
26
|
-
name = node.name
|
27
|
-
loc = node.loc
|
28
|
-
lambda do |ctx|
|
29
|
-
return ctx[name] if ctx.respond_to?(:key?) && ctx.key?(name)
|
30
|
-
|
31
|
-
raise Errors::RuntimeError,
|
32
|
-
"Key '#{name}' not found at #{loc}. Available: #{ctx.respond_to?(:keys) ? ctx.keys.join(', ') : 'N/A'}"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def compile_declaration(decl)
|
37
|
-
@current_declaration = decl.name
|
38
|
-
kind = decl.is_a?(Kumi::Syntax::TraitDeclaration) ? :trait : :attr
|
39
|
-
fn = compile_expr(decl.expression)
|
40
|
-
@bindings[decl.name] = [kind, fn]
|
41
|
-
@current_declaration = nil
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Core
|
5
|
-
EvaluationWrapper = Struct.new(:ctx) do
|
6
|
-
def initialize(ctx)
|
7
|
-
super
|
8
|
-
@__schema_cache__ = {} # memoization cache for bindings
|
9
|
-
end
|
10
|
-
|
11
|
-
def [](key)
|
12
|
-
ctx[key]
|
13
|
-
end
|
14
|
-
|
15
|
-
def []=(key, value)
|
16
|
-
ctx[key] = value
|
17
|
-
end
|
18
|
-
|
19
|
-
def keys
|
20
|
-
ctx.keys
|
21
|
-
end
|
22
|
-
|
23
|
-
def key?(key)
|
24
|
-
ctx.key?(key)
|
25
|
-
end
|
26
|
-
|
27
|
-
def clear
|
28
|
-
@__schema_cache__.clear
|
29
|
-
end
|
30
|
-
|
31
|
-
def clear_cache(*keys)
|
32
|
-
if keys.empty?
|
33
|
-
@__schema_cache__.clear
|
34
|
-
else
|
35
|
-
keys.each { |key| @__schema_cache__.delete(key) }
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Core
|
5
|
-
# Shared utilities for working with nested array structures
|
6
|
-
module NestedStructureUtils
|
7
|
-
def calculate_array_depth(arr)
|
8
|
-
return 0 unless arr.is_a?(Array)
|
9
|
-
return 1 if arr.empty? || !arr.first.is_a?(Array)
|
10
|
-
|
11
|
-
1 + calculate_array_depth(arr.first)
|
12
|
-
end
|
13
|
-
|
14
|
-
def map_nested_structure(structure, indices = [], &block)
|
15
|
-
if structure.is_a?(Array) && structure.first.is_a?(Array)
|
16
|
-
# Still nested - recurse deeper
|
17
|
-
structure.map.with_index do |sub_structure, i|
|
18
|
-
map_nested_structure(sub_structure, indices + [i], &block)
|
19
|
-
end
|
20
|
-
elsif structure.is_a?(Array)
|
21
|
-
# Leaf array level - apply function to elements
|
22
|
-
structure.map.with_index do |_element, i|
|
23
|
-
yield(*(indices + [i]))
|
24
|
-
end
|
25
|
-
else
|
26
|
-
# Single element - apply function
|
27
|
-
yield(*indices)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def navigate_nested_indices(structure, indices)
|
32
|
-
indices.reduce(structure) do |current, index|
|
33
|
-
if current.is_a?(Array)
|
34
|
-
current[index]
|
35
|
-
else
|
36
|
-
# If we hit a non-array during navigation, it means we're dealing with
|
37
|
-
# mixed nesting levels - return the current value
|
38
|
-
current
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def navigate_with_hierarchical_broadcasting(value, indices, template)
|
44
|
-
# Navigate through value with hierarchical broadcasting to match template structure
|
45
|
-
value_depth = calculate_array_depth(value)
|
46
|
-
template_depth = calculate_array_depth(template)
|
47
|
-
|
48
|
-
if value_depth < template_depth
|
49
|
-
# Value is at parent level - broadcast to child level by using fewer indices
|
50
|
-
parent_indices = indices[0, value_depth]
|
51
|
-
navigate_nested_indices(value, parent_indices)
|
52
|
-
else
|
53
|
-
# Same or deeper level - navigate normally
|
54
|
-
navigate_nested_indices(value, indices)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def find_structure_template(all_results)
|
59
|
-
# Find the first array to use as structure template
|
60
|
-
all_results.find { |v| v.is_a?(Array) }
|
61
|
-
end
|
62
|
-
|
63
|
-
def determine_array_length(arrays)
|
64
|
-
# Find the first array and use its length
|
65
|
-
first_array = arrays.find { |v| v.is_a?(Array) }
|
66
|
-
first_array ? first_array.length : 1
|
67
|
-
end
|
68
|
-
|
69
|
-
def extract_element_at_index(value, index)
|
70
|
-
if value.is_a?(Array)
|
71
|
-
index < value.length ? value[index] : nil
|
72
|
-
else
|
73
|
-
value # Scalar value - same for all indices
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Core
|
5
|
-
# A bound pair of <compiled schema + context>. Immutable.
|
6
|
-
#
|
7
|
-
# Public API ----------------------------------------------------------
|
8
|
-
# instance.evaluate # => full Hash of all bindings
|
9
|
-
# instance.evaluate(:tax_due, :rate)
|
10
|
-
# instance.slice(:tax_due) # alias for evaluate(*keys)
|
11
|
-
# instance.explain(:tax_due) # pretty trace string
|
12
|
-
# instance.input # original context (read‑only)
|
13
|
-
|
14
|
-
class SchemaInstance
|
15
|
-
attr_reader :compiled_schema, :metadata, :context
|
16
|
-
|
17
|
-
def initialize(compiled_schema, metadata, context)
|
18
|
-
@compiled_schema = compiled_schema # Kumi::Core::CompiledSchema
|
19
|
-
@metadata = metadata # Frozen state hash
|
20
|
-
@context = context.is_a?(EvaluationWrapper) ? context : EvaluationWrapper.new(context)
|
21
|
-
end
|
22
|
-
|
23
|
-
# Hash‑like read of one or many bindings
|
24
|
-
def evaluate(*key_names)
|
25
|
-
if key_names.empty?
|
26
|
-
@compiled_schema.evaluate(@context)
|
27
|
-
else
|
28
|
-
@compiled_schema.evaluate(@context, *key_names)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def slice(*key_names)
|
33
|
-
return {} if key_names.empty?
|
34
|
-
|
35
|
-
evaluate(*key_names)
|
36
|
-
end
|
37
|
-
|
38
|
-
def [](key_name)
|
39
|
-
evaluate(key_name)[key_name]
|
40
|
-
end
|
41
|
-
|
42
|
-
def functions_used
|
43
|
-
@metadata[:functions_required].to_a
|
44
|
-
end
|
45
|
-
|
46
|
-
# Update input values and clear affected cached computations
|
47
|
-
def update(**changes)
|
48
|
-
changes.each do |field, value|
|
49
|
-
# Validate field exists
|
50
|
-
raise ArgumentError, "unknown input field: #{field}" unless input_field_exists?(field)
|
51
|
-
|
52
|
-
# Validate domain constraints
|
53
|
-
validate_domain_constraint(field, value)
|
54
|
-
|
55
|
-
# Update the input data
|
56
|
-
@context[field] = value
|
57
|
-
|
58
|
-
# Clear affected cached values using transitive closure by default
|
59
|
-
if ENV["KUMI_SIMPLE_CACHE"] == "true"
|
60
|
-
# Simple fallback: clear all cached values
|
61
|
-
@context.clear_cache
|
62
|
-
else
|
63
|
-
# Default: selective cache clearing using precomputed transitive closure
|
64
|
-
affected_keys = find_dependent_declarations_optimized(field)
|
65
|
-
affected_keys.each { |key| @context.clear_cache(key) }
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
self # Return self for chaining
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
def input_field_exists?(field)
|
75
|
-
# Check if field is declared in input block
|
76
|
-
input_meta = @metadata[:inputs] || {}
|
77
|
-
input_meta.key?(field) || @context.key?(field)
|
78
|
-
end
|
79
|
-
|
80
|
-
def validate_domain_constraint(field, value)
|
81
|
-
input_meta = @metadata[:inputs] || {}
|
82
|
-
field_meta = input_meta[field]
|
83
|
-
return unless field_meta&.dig(:domain)
|
84
|
-
|
85
|
-
domain = field_meta[:domain]
|
86
|
-
return unless violates_domain?(value, domain)
|
87
|
-
|
88
|
-
raise ArgumentError, "value #{value} is not in domain #{domain}"
|
89
|
-
end
|
90
|
-
|
91
|
-
def violates_domain?(value, domain)
|
92
|
-
case domain
|
93
|
-
when Range
|
94
|
-
!domain.include?(value)
|
95
|
-
when Array
|
96
|
-
!domain.include?(value)
|
97
|
-
when Proc
|
98
|
-
# For Proc domains, we can't statically analyze
|
99
|
-
false
|
100
|
-
else
|
101
|
-
false
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def find_dependent_declarations_optimized(field)
|
106
|
-
# Use precomputed transitive closure for true O(1) lookup!
|
107
|
-
transitive_dependents = @metadata[:dependents]
|
108
|
-
return [] unless transitive_dependents
|
109
|
-
|
110
|
-
# This is truly O(1) - just array lookup, no traversal needed
|
111
|
-
transitive_dependents[field] || []
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,88 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Core
|
5
|
-
# Builds vectorized function execution lambdas from analysis metadata
|
6
|
-
class VectorizedFunctionBuilder
|
7
|
-
include NestedStructureUtils
|
8
|
-
|
9
|
-
def self.build_executor(fn_name, compilation_meta, analysis_state)
|
10
|
-
new(fn_name, compilation_meta, analysis_state).build
|
11
|
-
end
|
12
|
-
|
13
|
-
def initialize(fn_name, compilation_meta, analysis_state)
|
14
|
-
@fn_name = fn_name
|
15
|
-
@compilation_meta = compilation_meta
|
16
|
-
@analysis_state = analysis_state
|
17
|
-
end
|
18
|
-
|
19
|
-
def build
|
20
|
-
# Get the function from registry
|
21
|
-
fn = Kumi::Registry.fetch(@fn_name)
|
22
|
-
|
23
|
-
lambda do |arg_values, loc|
|
24
|
-
# Check if any argument is vectorized (array)
|
25
|
-
has_vectorized_args = arg_values.any?(Array)
|
26
|
-
|
27
|
-
if has_vectorized_args
|
28
|
-
# Apply function with broadcasting to all vectorized arguments
|
29
|
-
vectorized_function_call(fn, arg_values)
|
30
|
-
else
|
31
|
-
# All arguments are scalars - regular function call
|
32
|
-
fn.call(*arg_values)
|
33
|
-
end
|
34
|
-
rescue StandardError => e
|
35
|
-
enhanced_message = "Error calling fn(:#{@fn_name}) at #{loc}: #{e.message}"
|
36
|
-
runtime_error = Errors::RuntimeError.new(enhanced_message)
|
37
|
-
runtime_error.set_backtrace(e.backtrace)
|
38
|
-
runtime_error.define_singleton_method(:cause) { e }
|
39
|
-
raise runtime_error
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def vectorized_function_call(fn, values)
|
46
|
-
# Find array dimensions for broadcasting
|
47
|
-
array_values = values.select { |v| v.is_a?(Array) }
|
48
|
-
return fn.call(*values) if array_values.empty?
|
49
|
-
|
50
|
-
# Check if we have deeply nested arrays (arrays containing arrays)
|
51
|
-
has_nested_arrays = array_values.any? { |arr| arr.is_a?(Array) && arr.first.is_a?(Array) }
|
52
|
-
|
53
|
-
if has_nested_arrays
|
54
|
-
# Use recursive element-wise operation for nested arrays
|
55
|
-
apply_function_to_nested_structure(fn, values)
|
56
|
-
else
|
57
|
-
# Original flat array logic
|
58
|
-
array_length = array_values.first.size
|
59
|
-
(0...array_length).map do |i|
|
60
|
-
element_args = values.map do |v|
|
61
|
-
v.is_a?(Array) ? v[i] : v # Broadcast scalars
|
62
|
-
end
|
63
|
-
fn.call(*element_args)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def apply_function_to_nested_structure(fn, values)
|
69
|
-
# Find the first array to determine structure
|
70
|
-
array_value = values.find { |v| v.is_a?(Array) }
|
71
|
-
|
72
|
-
# Apply function element-wise, preserving nested structure
|
73
|
-
map_nested_structure(array_value) do |*indices|
|
74
|
-
element_args = values.map do |value|
|
75
|
-
if value.is_a?(Array)
|
76
|
-
# Navigate to the corresponding element in nested structure
|
77
|
-
navigate_nested_indices(value, indices)
|
78
|
-
else
|
79
|
-
# Scalar - broadcast to all positions
|
80
|
-
value
|
81
|
-
end
|
82
|
-
end
|
83
|
-
fn.call(*element_args)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|