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.
- checksums.yaml +4 -4
- data/CLAUDE.md +28 -44
- data/README.md +187 -120
- data/docs/AST.md +1 -1
- data/docs/FUNCTIONS.md +52 -8
- data/docs/compiler_design_principles.md +86 -0
- data/docs/features/README.md +15 -2
- data/docs/features/hierarchical-broadcasting.md +349 -0
- data/docs/features/javascript-transpiler.md +148 -0
- data/docs/features/performance.md +1 -3
- data/docs/schema_metadata.md +7 -7
- data/examples/game_of_life.rb +2 -4
- data/lib/kumi/analyzer.rb +0 -2
- data/lib/kumi/compiler.rb +6 -275
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +600 -42
- data/lib/kumi/core/analyzer/passes/input_collector.rb +4 -2
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +27 -0
- data/lib/kumi/core/analyzer/passes/type_checker.rb +6 -2
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +90 -46
- data/lib/kumi/core/cascade_executor_builder.rb +132 -0
- data/lib/kumi/core/compiler/expression_compiler.rb +146 -0
- data/lib/kumi/core/compiler/function_invoker.rb +55 -0
- data/lib/kumi/core/compiler/path_traversal_compiler.rb +158 -0
- data/lib/kumi/core/compiler/reference_compiler.rb +46 -0
- data/lib/kumi/core/compiler_base.rb +137 -0
- data/lib/kumi/core/explain.rb +2 -2
- data/lib/kumi/core/function_registry/collection_functions.rb +86 -3
- data/lib/kumi/core/function_registry/function_builder.rb +5 -3
- data/lib/kumi/core/function_registry/logical_functions.rb +171 -1
- data/lib/kumi/core/function_registry/stat_functions.rb +156 -0
- data/lib/kumi/core/function_registry.rb +32 -10
- data/lib/kumi/core/nested_structure_utils.rb +78 -0
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +2 -2
- data/lib/kumi/core/ruby_parser/input_builder.rb +61 -8
- data/lib/kumi/core/schema_instance.rb +4 -0
- data/lib/kumi/core/vectorized_function_builder.rb +88 -0
- data/lib/kumi/errors.rb +2 -0
- data/lib/kumi/js/compiler.rb +878 -0
- data/lib/kumi/js/function_registry.rb +333 -0
- data/lib/kumi/js.rb +23 -0
- data/lib/kumi/registry.rb +61 -1
- data/lib/kumi/schema.rb +1 -1
- data/lib/kumi/support/s_expression_printer.rb +16 -15
- data/lib/kumi/syntax/array_expression.rb +6 -6
- data/lib/kumi/syntax/call_expression.rb +4 -4
- data/lib/kumi/syntax/cascade_expression.rb +4 -4
- data/lib/kumi/syntax/case_expression.rb +4 -4
- data/lib/kumi/syntax/declaration_reference.rb +4 -4
- data/lib/kumi/syntax/hash_expression.rb +4 -4
- data/lib/kumi/syntax/input_declaration.rb +6 -5
- data/lib/kumi/syntax/input_element_reference.rb +5 -5
- data/lib/kumi/syntax/input_reference.rb +5 -5
- data/lib/kumi/syntax/literal.rb +4 -4
- data/lib/kumi/syntax/node.rb +34 -34
- data/lib/kumi/syntax/root.rb +6 -6
- data/lib/kumi/syntax/trait_declaration.rb +4 -4
- data/lib/kumi/syntax/value_declaration.rb +4 -4
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +1 -1
- data/scripts/analyze_broadcast_methods.rb +68 -0
- data/scripts/analyze_cascade_methods.rb +74 -0
- data/scripts/check_broadcasting_coverage.rb +51 -0
- data/scripts/find_dead_code.rb +114 -0
- metadata +20 -4
- data/docs/features/array-broadcasting.md +0 -170
- data/lib/kumi/cli.rb +0 -449
- data/lib/kumi/core/vectorization_metadata.rb +0 -110
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
module StatFunctions
|
7
|
+
def self.definitions
|
8
|
+
{
|
9
|
+
# Statistical Functions
|
10
|
+
avg: FunctionBuilder::Entry.new(
|
11
|
+
fn: ->(array) { array.sum.to_f / array.size },
|
12
|
+
arity: 1,
|
13
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
14
|
+
return_type: :float,
|
15
|
+
description: "Calculate arithmetic mean (average) of numeric collection",
|
16
|
+
reducer: true
|
17
|
+
),
|
18
|
+
|
19
|
+
mean: FunctionBuilder::Entry.new(
|
20
|
+
fn: ->(array) { array.sum.to_f / array.size },
|
21
|
+
arity: 1,
|
22
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
23
|
+
return_type: :float,
|
24
|
+
description: "Calculate arithmetic mean (average) of numeric collection (alias for avg)",
|
25
|
+
reducer: true
|
26
|
+
),
|
27
|
+
|
28
|
+
median: FunctionBuilder::Entry.new(
|
29
|
+
fn: lambda do |array|
|
30
|
+
sorted = array.sort
|
31
|
+
len = sorted.length
|
32
|
+
if len.odd?
|
33
|
+
sorted[len / 2]
|
34
|
+
else
|
35
|
+
(sorted[(len / 2) - 1] + sorted[len / 2]) / 2.0
|
36
|
+
end
|
37
|
+
end,
|
38
|
+
arity: 1,
|
39
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
40
|
+
return_type: :float,
|
41
|
+
description: "Calculate median (middle value) of numeric collection",
|
42
|
+
reducer: true
|
43
|
+
),
|
44
|
+
|
45
|
+
variance: FunctionBuilder::Entry.new(
|
46
|
+
fn: lambda do |array|
|
47
|
+
mean = array.sum.to_f / array.size
|
48
|
+
sum_of_squares = array.sum { |x| (x - mean)**2 }
|
49
|
+
sum_of_squares / array.size
|
50
|
+
end,
|
51
|
+
arity: 1,
|
52
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
53
|
+
return_type: :float,
|
54
|
+
description: "Calculate population variance of numeric collection",
|
55
|
+
reducer: true
|
56
|
+
),
|
57
|
+
|
58
|
+
stdev: FunctionBuilder::Entry.new(
|
59
|
+
fn: lambda do |array|
|
60
|
+
mean = array.sum.to_f / array.size
|
61
|
+
sum_of_squares = array.sum { |x| (x - mean)**2 }
|
62
|
+
variance = sum_of_squares / array.size
|
63
|
+
Math.sqrt(variance)
|
64
|
+
end,
|
65
|
+
arity: 1,
|
66
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
67
|
+
return_type: :float,
|
68
|
+
description: "Calculate population standard deviation of numeric collection",
|
69
|
+
reducer: true
|
70
|
+
),
|
71
|
+
|
72
|
+
sample_variance: FunctionBuilder::Entry.new(
|
73
|
+
fn: lambda do |array|
|
74
|
+
return 0.0 if array.size <= 1
|
75
|
+
|
76
|
+
mean = array.sum.to_f / array.size
|
77
|
+
sum_of_squares = array.sum { |x| (x - mean)**2 }
|
78
|
+
sum_of_squares / (array.size - 1)
|
79
|
+
end,
|
80
|
+
arity: 1,
|
81
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
82
|
+
return_type: :float,
|
83
|
+
description: "Calculate sample variance of numeric collection",
|
84
|
+
reducer: true
|
85
|
+
),
|
86
|
+
|
87
|
+
sample_stdev: FunctionBuilder::Entry.new(
|
88
|
+
fn: lambda do |array|
|
89
|
+
return 0.0 if array.size <= 1
|
90
|
+
|
91
|
+
mean = array.sum.to_f / array.size
|
92
|
+
sum_of_squares = array.sum { |x| (x - mean)**2 }
|
93
|
+
variance = sum_of_squares / (array.size - 1)
|
94
|
+
Math.sqrt(variance)
|
95
|
+
end,
|
96
|
+
arity: 1,
|
97
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
98
|
+
return_type: :float,
|
99
|
+
description: "Calculate sample standard deviation of numeric collection",
|
100
|
+
reducer: true
|
101
|
+
),
|
102
|
+
|
103
|
+
# Convenience functions for flattened statistics
|
104
|
+
flat_size: FunctionBuilder::Entry.new(
|
105
|
+
fn: ->(nested_array) { nested_array.flatten.size },
|
106
|
+
arity: 1,
|
107
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
108
|
+
return_type: :integer,
|
109
|
+
description: "Count total elements across all nested levels",
|
110
|
+
reducer: true
|
111
|
+
),
|
112
|
+
|
113
|
+
flat_sum: FunctionBuilder::Entry.new(
|
114
|
+
fn: ->(nested_array) { nested_array.flatten.sum },
|
115
|
+
arity: 1,
|
116
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
117
|
+
return_type: :float,
|
118
|
+
description: "Sum all numeric elements across all nested levels",
|
119
|
+
reducer: true
|
120
|
+
),
|
121
|
+
|
122
|
+
flat_avg: FunctionBuilder::Entry.new(
|
123
|
+
fn: lambda do |nested_array|
|
124
|
+
flattened = nested_array.flatten
|
125
|
+
flattened.sum.to_f / flattened.size
|
126
|
+
end,
|
127
|
+
arity: 1,
|
128
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
129
|
+
return_type: :float,
|
130
|
+
description: "Calculate average across all nested levels",
|
131
|
+
reducer: true
|
132
|
+
),
|
133
|
+
|
134
|
+
flat_max: FunctionBuilder::Entry.new(
|
135
|
+
fn: ->(nested_array) { nested_array.flatten.max },
|
136
|
+
arity: 1,
|
137
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
138
|
+
return_type: :float,
|
139
|
+
description: "Find maximum value across all nested levels",
|
140
|
+
reducer: true
|
141
|
+
),
|
142
|
+
|
143
|
+
flat_min: FunctionBuilder::Entry.new(
|
144
|
+
fn: ->(nested_array) { nested_array.flatten.min },
|
145
|
+
arity: 1,
|
146
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
147
|
+
return_type: :float,
|
148
|
+
description: "Find minimum value across all nested levels",
|
149
|
+
reducer: true
|
150
|
+
)
|
151
|
+
}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
module Kumi
|
4
4
|
module Core
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# Internal function registry implementation
|
6
|
+
# Use Kumi::Registry for the public interface to register custom functions
|
7
7
|
module FunctionRegistry
|
8
8
|
# Re-export the Entry struct from FunctionBuilder for compatibility
|
9
9
|
Entry = FunctionBuilder::Entry
|
@@ -13,20 +13,33 @@ module Kumi
|
|
13
13
|
|
14
14
|
# Build the complete function registry by combining all categories
|
15
15
|
CORE_FUNCTIONS = {}.tap do |registry|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
function_modules = [
|
17
|
+
ComparisonFunctions.definitions,
|
18
|
+
MathFunctions.definitions,
|
19
|
+
StringFunctions.definitions,
|
20
|
+
LogicalFunctions.definitions,
|
21
|
+
CollectionFunctions.definitions,
|
22
|
+
ConditionalFunctions.definitions,
|
23
|
+
TypeFunctions.definitions,
|
24
|
+
StatFunctions.definitions
|
25
|
+
]
|
26
|
+
|
27
|
+
function_modules.each do |module_definitions|
|
28
|
+
module_definitions.each do |name, definition|
|
29
|
+
if registry.key?(name)
|
30
|
+
raise ArgumentError, "Function #{name.inspect} is already registered. Found duplicate in function registry."
|
31
|
+
end
|
32
|
+
|
33
|
+
registry[name] = definition
|
34
|
+
end
|
35
|
+
end
|
23
36
|
end.freeze
|
24
37
|
|
25
38
|
@functions = CORE_FUNCTIONS.dup
|
26
39
|
@frozen = false
|
27
40
|
|
28
41
|
# class << self
|
29
|
-
#
|
42
|
+
# Internal interface for registering custom functions
|
30
43
|
def register(name, &block)
|
31
44
|
raise ArgumentError, "Function #{name.inspect} already registered" if @functions.key?(name)
|
32
45
|
|
@@ -97,6 +110,11 @@ module Kumi
|
|
97
110
|
entry.reducer || false
|
98
111
|
end
|
99
112
|
|
113
|
+
def structure_function?(name)
|
114
|
+
entry = @functions.fetch(name) { return false }
|
115
|
+
entry.structure_function || false
|
116
|
+
end
|
117
|
+
|
100
118
|
# Alias for compatibility
|
101
119
|
def all
|
102
120
|
@functions.keys
|
@@ -130,6 +148,10 @@ module Kumi
|
|
130
148
|
def type_operations
|
131
149
|
TypeFunctions.definitions.keys
|
132
150
|
end
|
151
|
+
|
152
|
+
def stat_operations
|
153
|
+
StatFunctions.definitions.keys
|
154
|
+
end
|
133
155
|
end
|
134
156
|
end
|
135
157
|
end
|
@@ -0,0 +1,78 @@
|
|
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
|
@@ -22,7 +22,7 @@ module Kumi
|
|
22
22
|
expr = args.last
|
23
23
|
|
24
24
|
trait_bindings = convert_trait_names_to_bindings(trait_names, on_loc)
|
25
|
-
condition =
|
25
|
+
condition = @context.fn(:cascade_and, *trait_bindings)
|
26
26
|
result = ensure_syntax(expr)
|
27
27
|
add_case(condition, result)
|
28
28
|
end
|
@@ -92,7 +92,7 @@ module Kumi
|
|
92
92
|
when DeclarationReference
|
93
93
|
name # Already a binding from method_missing
|
94
94
|
else
|
95
|
-
raise_error("trait reference must be a
|
95
|
+
raise_error("trait reference must be a bare trait identifier, got #{name.class}", location)
|
96
96
|
end
|
97
97
|
end
|
98
98
|
end
|
@@ -13,13 +13,13 @@ module Kumi
|
|
13
13
|
|
14
14
|
def key(name, type: :any, domain: nil)
|
15
15
|
normalized_type = normalize_type(type, name)
|
16
|
-
@context.inputs << Kumi::Syntax::InputDeclaration.new(name, domain, normalized_type, [], loc: @context.current_location)
|
16
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(name, domain, normalized_type, [], nil, loc: @context.current_location)
|
17
17
|
end
|
18
18
|
|
19
19
|
%i[integer float string boolean any scalar].each do |type_name|
|
20
20
|
define_method(type_name) do |name, type: nil, domain: nil|
|
21
21
|
actual_type = type || (type_name == :scalar ? :any : type_name)
|
22
|
-
@context.inputs << Kumi::Syntax::InputDeclaration.new(name, domain, actual_type, [], loc: @context.current_location)
|
22
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(name, domain, actual_type, [], nil, loc: @context.current_location)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -40,7 +40,7 @@ module Kumi
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def method_missing(method_name, *_args)
|
43
|
-
allowed_methods = "'key', 'integer', 'float', 'string', 'boolean', 'any', 'scalar', 'array', and '
|
43
|
+
allowed_methods = "'key', 'integer', 'float', 'string', 'boolean', 'any', 'scalar', 'array', 'hash', and 'element'"
|
44
44
|
raise_syntax_error("Unknown method '#{method_name}' in input block. Only #{allowed_methods} are allowed.",
|
45
45
|
location: @context.current_location)
|
46
46
|
end
|
@@ -63,7 +63,7 @@ module Kumi
|
|
63
63
|
elem_type = elem_spec.is_a?(Hash) && elem_spec[:type] ? elem_spec[:type] : :any
|
64
64
|
|
65
65
|
array_type = create_array_type(field_name, elem_type)
|
66
|
-
@context.inputs << Kumi::Syntax::InputDeclaration.new(field_name, domain, array_type, [], loc: @context.current_location)
|
66
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(field_name, domain, array_type, [], :object, loc: @context.current_location)
|
67
67
|
end
|
68
68
|
|
69
69
|
def create_array_type(field_name, elem_type)
|
@@ -81,7 +81,7 @@ module Kumi
|
|
81
81
|
val_type = extract_type(val_spec)
|
82
82
|
|
83
83
|
hash_type = create_hash_type(field_name, key_type, val_type)
|
84
|
-
@context.inputs << Kumi::Syntax::InputDeclaration.new(field_name, domain, hash_type, [], loc: @context.current_location)
|
84
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(field_name, domain, hash_type, [], nil, loc: @context.current_location)
|
85
85
|
end
|
86
86
|
|
87
87
|
def extract_type(spec)
|
@@ -98,14 +98,16 @@ module Kumi
|
|
98
98
|
domain = options[:domain]
|
99
99
|
|
100
100
|
# Collect children by creating a nested context
|
101
|
-
children = collect_array_children(&block)
|
101
|
+
children, _, using_elements = collect_array_children(&block)
|
102
102
|
|
103
|
-
# Create the InputDeclaration with children
|
103
|
+
# Create the InputDeclaration with children and access_mode
|
104
|
+
access_mode = using_elements ? :element : :object
|
104
105
|
@context.inputs << Kumi::Syntax::InputDeclaration.new(
|
105
106
|
field_name,
|
106
107
|
domain,
|
107
108
|
:array,
|
108
109
|
children,
|
110
|
+
access_mode,
|
109
111
|
loc: @context.current_location
|
110
112
|
)
|
111
113
|
end
|
@@ -119,7 +121,58 @@ module Kumi
|
|
119
121
|
# Execute the block in the nested context
|
120
122
|
nested_builder.instance_eval(&block)
|
121
123
|
|
122
|
-
|
124
|
+
# Determine element type based on what was declared
|
125
|
+
elem_type = determine_element_type(nested_builder, nested_inputs)
|
126
|
+
|
127
|
+
# Check if element() was used
|
128
|
+
using_elements = nested_builder.instance_variable_get(:@using_elements) || false
|
129
|
+
|
130
|
+
[nested_inputs, elem_type, using_elements]
|
131
|
+
end
|
132
|
+
|
133
|
+
def determine_element_type(_builder, inputs)
|
134
|
+
# Since element() always creates named children now,
|
135
|
+
# we just use the standard logic
|
136
|
+
if inputs.any?
|
137
|
+
# If fields were declared, it's a hash/object structure
|
138
|
+
:hash
|
139
|
+
else
|
140
|
+
# No fields declared, default to :any
|
141
|
+
:any
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def primitive_element_type?(elem_type)
|
146
|
+
%i[string integer float boolean bool any symbol].include?(elem_type)
|
147
|
+
end
|
148
|
+
|
149
|
+
# New method: element() declaration - always requires a name
|
150
|
+
def element(type_spec, name, &block)
|
151
|
+
@using_elements = true
|
152
|
+
if block_given?
|
153
|
+
# Named element with nested structure: element(:array, :rows) do ... end
|
154
|
+
# These DO set @using_elements to enable element access mode for multi-dimensional arrays
|
155
|
+
case type_spec
|
156
|
+
when :array
|
157
|
+
create_array_field_with_block(name, {}, &block)
|
158
|
+
when :object
|
159
|
+
# Create nested object structure
|
160
|
+
create_object_element(name, &block)
|
161
|
+
else
|
162
|
+
raise_syntax_error("element(#{type_spec.inspect}, #{name.inspect}) with block only supports :array or :object types",
|
163
|
+
location: @context.current_location)
|
164
|
+
end
|
165
|
+
else
|
166
|
+
# Named primitive element: element(:boolean, :active)
|
167
|
+
# Only primitive elements mark the parent as using element access
|
168
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(name, nil, type_spec, [], nil, loc: @context.current_location)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def create_object_element(name, &block)
|
173
|
+
# Similar to create_array_field_with_block but for objects
|
174
|
+
children, = collect_array_children(&block)
|
175
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(name, nil, :object, children, nil, loc: @context.current_location)
|
123
176
|
end
|
124
177
|
end
|
125
178
|
end
|
@@ -0,0 +1,88 @@
|
|
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
|