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,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