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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/CLAUDE.md +7 -231
- data/README.md +1 -1
- data/docs/VECTOR_SEMANTICS.md +286 -0
- data/docs/features/hierarchical-broadcasting.md +1 -1
- data/docs/features/s-expression-printer.md +2 -2
- data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +21 -15
- 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 +118 -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/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 +5 -5
- 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/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 +26 -15
- 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,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
|