kumi 0.0.25 → 0.0.27
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 +16 -0
- data/CLAUDE.md +4 -0
- data/README.md +86 -78
- data/data/functions/agg/boolean.yaml +6 -2
- data/data/functions/agg/numeric.yaml +32 -16
- data/data/functions/agg/string.yaml +4 -3
- data/data/functions/core/arithmetic.yaml +62 -14
- data/data/functions/core/boolean.yaml +12 -6
- data/data/functions/core/comparison.yaml +25 -13
- data/data/functions/core/constructor.yaml +16 -8
- data/data/functions/core/conversion.yaml +32 -0
- data/data/functions/core/select.yaml +3 -1
- data/data/functions/core/stencil.yaml +14 -5
- data/data/functions/core/string.yaml +9 -4
- data/data/kernels/javascript/core/coercion.yaml +20 -0
- data/data/kernels/ruby/agg/numeric.yaml +1 -1
- data/data/kernels/ruby/core/coercion.yaml +20 -0
- data/docs/ARCHITECTURE.md +277 -0
- data/docs/DEVELOPMENT.md +62 -0
- data/docs/FUNCTIONS.md +955 -0
- data/docs/SYNTAX.md +8 -0
- data/docs/UNSAT_DETECTION.md +83 -0
- data/docs/VSCODE_EXTENSION.md +114 -0
- data/docs/functions-reference.json +1821 -0
- data/golden/array_element/expected/nast.txt +1 -1
- data/golden/array_element/expected/schema_ruby.rb +1 -1
- data/golden/array_index/expected/nast.txt +7 -7
- data/golden/array_index/expected/schema_ruby.rb +1 -1
- data/golden/array_operations/expected/nast.txt +2 -2
- data/golden/array_operations/expected/schema_ruby.rb +1 -1
- data/golden/array_operations/expected/snast.txt +3 -3
- data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
- data/golden/cascade_logic/expected/snast.txt +2 -2
- data/golden/chained_fusion/expected/nast.txt +2 -2
- data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
- data/golden/decimal_explicit/expected/ast.txt +38 -0
- data/golden/decimal_explicit/expected/input_plan.txt +3 -0
- data/golden/decimal_explicit/expected/lir_00_unoptimized.txt +30 -0
- data/golden/decimal_explicit/expected/lir_01_hoist_scalar_references.txt +30 -0
- data/golden/decimal_explicit/expected/lir_02_inlined.txt +44 -0
- data/golden/decimal_explicit/expected/lir_03_cse.txt +40 -0
- data/golden/decimal_explicit/expected/lir_04_1_loop_fusion.txt +40 -0
- data/golden/decimal_explicit/expected/lir_04_loop_invcm.txt +40 -0
- data/golden/decimal_explicit/expected/lir_06_const_prop.txt +40 -0
- data/golden/decimal_explicit/expected/nast.txt +30 -0
- data/golden/decimal_explicit/expected/schema_javascript.mjs +31 -0
- data/golden/decimal_explicit/expected/schema_ruby.rb +57 -0
- data/golden/decimal_explicit/expected/snast.txt +30 -0
- data/golden/decimal_explicit/expected.json +1 -0
- data/golden/decimal_explicit/input.json +5 -0
- data/golden/decimal_explicit/schema.kumi +14 -0
- data/golden/element_arrays/expected/nast.txt +2 -2
- data/golden/element_arrays/expected/schema_ruby.rb +1 -1
- data/golden/element_arrays/expected/snast.txt +1 -1
- data/golden/empty_and_null_inputs/expected/nast.txt +3 -3
- data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
- data/golden/function_overload/expected/ast.txt +29 -0
- data/golden/function_overload/expected/input_plan.txt +4 -0
- data/golden/function_overload/expected/lir_00_unoptimized.txt +18 -0
- data/golden/function_overload/expected/lir_01_hoist_scalar_references.txt +18 -0
- data/golden/function_overload/expected/lir_02_inlined.txt +20 -0
- data/golden/function_overload/expected/lir_03_cse.txt +20 -0
- data/golden/function_overload/expected/lir_04_1_loop_fusion.txt +20 -0
- data/golden/function_overload/expected/lir_04_loop_invcm.txt +20 -0
- data/golden/function_overload/expected/lir_06_const_prop.txt +20 -0
- data/golden/function_overload/expected/nast.txt +22 -0
- data/golden/function_overload/expected/schema_javascript.mjs +12 -0
- data/golden/function_overload/expected/schema_ruby.rb +39 -0
- data/golden/function_overload/expected/snast.txt +22 -0
- data/golden/function_overload/input.json +8 -0
- data/golden/function_overload/schema.kumi +19 -0
- data/golden/game_of_life/expected/lir_00_unoptimized.txt +4 -4
- data/golden/game_of_life/expected/lir_01_hoist_scalar_references.txt +4 -4
- data/golden/game_of_life/expected/lir_02_inlined.txt +16 -16
- data/golden/game_of_life/expected/lir_03_cse.txt +20 -16
- data/golden/game_of_life/expected/lir_04_1_loop_fusion.txt +20 -16
- data/golden/game_of_life/expected/lir_04_loop_invcm.txt +20 -16
- data/golden/game_of_life/expected/lir_06_const_prop.txt +20 -16
- data/golden/game_of_life/expected/nast.txt +4 -4
- data/golden/game_of_life/expected/schema_javascript.mjs +4 -2
- data/golden/game_of_life/expected/schema_ruby.rb +5 -3
- data/golden/game_of_life/expected/snast.txt +10 -10
- data/golden/hash_keys/expected/schema_ruby.rb +1 -1
- data/golden/hash_value/expected/nast.txt +1 -1
- data/golden/hash_value/expected/schema_ruby.rb +1 -1
- data/golden/hash_value/expected/snast.txt +1 -1
- data/golden/hierarchical_complex/expected/nast.txt +3 -3
- data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
- data/golden/hierarchical_complex/expected/snast.txt +3 -3
- data/golden/inline_rename_scope_leak/expected/nast.txt +3 -3
- data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
- data/golden/input_reference/expected/nast.txt +2 -2
- data/golden/input_reference/expected/schema_ruby.rb +1 -1
- data/golden/interleaved_fusion/expected/nast.txt +2 -2
- data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
- data/golden/let_inline/expected/nast.txt +4 -4
- data/golden/let_inline/expected/schema_ruby.rb +1 -1
- data/golden/loop_fusion/expected/nast.txt +1 -1
- data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
- data/golden/min_reduce_scope/expected/nast.txt +3 -3
- data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
- data/golden/min_reduce_scope/expected/snast.txt +1 -1
- data/golden/mixed_dimensions/expected/nast.txt +2 -2
- data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
- data/golden/multirank_hoisting/expected/nast.txt +7 -7
- data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
- data/golden/nested_hash/expected/nast.txt +1 -1
- data/golden/nested_hash/expected/schema_ruby.rb +1 -1
- data/golden/reduction_broadcast/expected/nast.txt +3 -3
- data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
- data/golden/reduction_broadcast/expected/snast.txt +1 -1
- data/golden/roll/expected/schema_ruby.rb +1 -1
- data/golden/shift/expected/schema_ruby.rb +1 -1
- data/golden/shift_2d/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/lir_00_unoptimized.txt +1 -1
- data/golden/simple_math/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/simple_math/expected/lir_02_inlined.txt +1 -1
- data/golden/simple_math/expected/lir_03_cse.txt +1 -1
- data/golden/simple_math/expected/lir_04_1_loop_fusion.txt +1 -1
- data/golden/simple_math/expected/lir_04_loop_invcm.txt +1 -1
- data/golden/simple_math/expected/lir_06_const_prop.txt +1 -1
- data/golden/simple_math/expected/nast.txt +5 -5
- data/golden/simple_math/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/snast.txt +2 -2
- data/golden/streaming_basics/expected/nast.txt +8 -8
- data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
- data/golden/streaming_basics/expected/snast.txt +1 -1
- data/golden/tuples/expected/lir_00_unoptimized.txt +5 -5
- data/golden/tuples/expected/lir_01_hoist_scalar_references.txt +5 -5
- data/golden/tuples/expected/lir_02_inlined.txt +5 -5
- data/golden/tuples/expected/lir_03_cse.txt +5 -5
- data/golden/tuples/expected/lir_04_1_loop_fusion.txt +5 -5
- data/golden/tuples/expected/lir_04_loop_invcm.txt +5 -5
- data/golden/tuples/expected/lir_06_const_prop.txt +5 -5
- data/golden/tuples/expected/nast.txt +4 -4
- data/golden/tuples/expected/schema_ruby.rb +1 -1
- data/golden/tuples/expected/snast.txt +6 -6
- data/golden/tuples_and_arrays/expected/lir_00_unoptimized.txt +1 -1
- data/golden/tuples_and_arrays/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/tuples_and_arrays/expected/lir_02_inlined.txt +2 -2
- data/golden/tuples_and_arrays/expected/lir_03_cse.txt +2 -2
- data/golden/tuples_and_arrays/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/tuples_and_arrays/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/tuples_and_arrays/expected/lir_06_const_prop.txt +2 -2
- data/golden/tuples_and_arrays/expected/nast.txt +3 -3
- data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
- data/golden/tuples_and_arrays/expected/snast.txt +2 -2
- data/golden/us_tax_2024/expected/ast.txt +63 -670
- data/golden/us_tax_2024/expected/input_plan.txt +8 -45
- data/golden/us_tax_2024/expected/lir_00_unoptimized.txt +253 -863
- data/golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt +253 -863
- data/golden/us_tax_2024/expected/lir_02_inlined.txt +1215 -5139
- data/golden/us_tax_2024/expected/lir_03_cse.txt +587 -2460
- data/golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt +632 -2480
- data/golden/us_tax_2024/expected/lir_04_loop_invcm.txt +587 -2460
- data/golden/us_tax_2024/expected/lir_06_const_prop.txt +587 -2460
- data/golden/us_tax_2024/expected/nast.txt +123 -826
- data/golden/us_tax_2024/expected/schema_javascript.mjs +127 -581
- data/golden/us_tax_2024/expected/schema_ruby.rb +135 -610
- data/golden/us_tax_2024/expected/snast.txt +155 -858
- data/golden/us_tax_2024/expected.json +120 -1
- data/golden/us_tax_2024/input.json +18 -9
- data/golden/us_tax_2024/schema.kumi +48 -178
- data/golden/with_constants/expected/lir_00_unoptimized.txt +1 -1
- data/golden/with_constants/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/with_constants/expected/lir_02_inlined.txt +1 -1
- data/golden/with_constants/expected/lir_03_cse.txt +1 -1
- data/golden/with_constants/expected/lir_04_1_loop_fusion.txt +1 -1
- data/golden/with_constants/expected/lir_04_loop_invcm.txt +1 -1
- data/golden/with_constants/expected/lir_06_const_prop.txt +1 -1
- data/golden/with_constants/expected/nast.txt +2 -2
- data/golden/with_constants/expected/schema_ruby.rb +1 -1
- data/golden/with_constants/expected/snast.txt +2 -2
- data/lib/kumi/analyzer.rb +12 -12
- data/lib/kumi/configuration.rb +6 -0
- data/lib/kumi/core/analyzer/passes/formal_constraint_propagator.rb +236 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +22 -4
- data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +64 -18
- data/lib/kumi/core/analyzer/passes/normalize_to_nast_pass.rb +9 -4
- data/lib/kumi/core/analyzer/passes/snast_pass.rb +3 -1
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +172 -198
- data/lib/kumi/core/error_reporter.rb +36 -1
- data/lib/kumi/core/errors.rb +33 -1
- data/lib/kumi/core/functions/function_spec.rb +5 -4
- data/lib/kumi/core/functions/loader.rb +17 -1
- data/lib/kumi/core/functions/overload_resolver.rb +164 -0
- data/lib/kumi/core/functions/type_error_reporter.rb +118 -0
- data/lib/kumi/core/functions/type_rules.rb +155 -35
- data/lib/kumi/core/input/type_matcher.rb +8 -1
- data/lib/kumi/core/ruby_parser/input_builder.rb +2 -2
- data/lib/kumi/core/types/inference.rb +29 -22
- data/lib/kumi/core/types/normalizer.rb +30 -45
- data/lib/kumi/core/types/validator.rb +17 -28
- data/lib/kumi/core/types/value_objects.rb +116 -0
- data/lib/kumi/core/types.rb +45 -37
- data/lib/kumi/dev/golden/reporter.rb +9 -0
- data/lib/kumi/dev/golden/result.rb +3 -1
- data/lib/kumi/dev/golden/runtime_test.rb +25 -0
- data/lib/kumi/dev/golden/suite.rb +4 -4
- data/lib/kumi/dev/golden/value_normalizer.rb +80 -0
- data/lib/kumi/dev/golden.rb +21 -12
- data/lib/kumi/doc_generator/formatters/json.rb +39 -0
- data/lib/kumi/doc_generator/formatters/markdown.rb +175 -0
- data/lib/kumi/doc_generator/loader.rb +37 -0
- data/lib/kumi/doc_generator/merger.rb +54 -0
- data/lib/kumi/doc_generator.rb +4 -0
- data/lib/kumi/registry_v2/loader.rb +90 -0
- data/lib/kumi/registry_v2.rb +18 -1
- data/lib/kumi/version.rb +1 -1
- data/vscode-extension/.gitignore +4 -0
- data/vscode-extension/README.md +59 -0
- data/vscode-extension/TESTING.md +151 -0
- data/vscode-extension/package.json +51 -0
- data/vscode-extension/src/extension.ts +295 -0
- data/vscode-extension/tsconfig.json +15 -0
- metadata +57 -7
- data/lib/kumi/core/analyzer/unsat_constant_evaluator.rb +0 -59
- data/lib/kumi/core/atom_unsat_solver.rb +0 -396
- data/lib/kumi/core/constraint_relationship_solver.rb +0 -641
- data/lib/kumi/core/types/builder.rb +0 -23
- data/lib/kumi/core/types/compatibility.rb +0 -96
- data/lib/kumi/core/types/formatter.rb +0 -26
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Analyzer
|
6
|
+
module Passes
|
7
|
+
# RESPONSIBILITY: Propagate constraints forward and backward through operations
|
8
|
+
# DEPENDENCIES: :registry (function registry), constraint metadata from function specs
|
9
|
+
# INTERFACE: propagate_forward(constraint), propagate_reverse_through_operation(...)
|
10
|
+
#
|
11
|
+
# Implements formal constraint propagation rules based on function semantics.
|
12
|
+
# Forward: x == 5, y = x + 10 => y == 15
|
13
|
+
# Reverse: y == 15, y = x + 10 => x == 5
|
14
|
+
class FormalConstraintPropagator
|
15
|
+
def initialize(schema, state)
|
16
|
+
@schema = schema
|
17
|
+
@state = state
|
18
|
+
@registry = state[:registry]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Forward propagate a constraint through a single operation
|
22
|
+
def propagate_forward_through_operation(constraint, operation_spec, operand_map)
|
23
|
+
case constraint[:op]
|
24
|
+
when :==
|
25
|
+
propagate_equality_forward(constraint, operation_spec, operand_map)
|
26
|
+
when :range
|
27
|
+
propagate_range_forward(constraint, operation_spec, operand_map)
|
28
|
+
else
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Reverse propagate: derive input constraints from output constraints
|
34
|
+
def propagate_reverse_through_operation(constraint, operation_spec, operand_map)
|
35
|
+
case constraint[:op]
|
36
|
+
when :==
|
37
|
+
propagate_equality_reverse(constraint, operation_spec, operand_map)
|
38
|
+
when :range
|
39
|
+
propagate_range_reverse(constraint, operation_spec, operand_map)
|
40
|
+
else
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# FORWARD PROPAGATION: Compute output value from input constraints
|
48
|
+
def propagate_equality_forward(constraint, operation_spec, operand_map)
|
49
|
+
result_var = operand_map[:result]
|
50
|
+
input_var = constraint[:variable]
|
51
|
+
input_value = constraint[:value]
|
52
|
+
|
53
|
+
case operation_spec.id
|
54
|
+
when "core.add"
|
55
|
+
# x == V, result = x + C => result == V + C
|
56
|
+
other_operand = get_other_operand_value(constraint, operand_map, "add")
|
57
|
+
return nil unless other_operand.is_a?(Numeric)
|
58
|
+
|
59
|
+
output_value = input_value + other_operand
|
60
|
+
{ variable: result_var, op: :==, value: output_value }
|
61
|
+
|
62
|
+
when "core.mul"
|
63
|
+
# x == V, result = x * C => result == V * C
|
64
|
+
other_operand = get_other_operand_value(constraint, operand_map, "mul")
|
65
|
+
return nil unless other_operand.is_a?(Numeric)
|
66
|
+
|
67
|
+
output_value = input_value * other_operand
|
68
|
+
{ variable: result_var, op: :==, value: output_value }
|
69
|
+
|
70
|
+
when "core.sub"
|
71
|
+
# x == V, result = x - C => result == V - C
|
72
|
+
other_operand = get_other_operand_value(constraint, operand_map, "sub")
|
73
|
+
return nil unless other_operand.is_a?(Numeric)
|
74
|
+
|
75
|
+
output_value = input_value - other_operand
|
76
|
+
{ variable: result_var, op: :==, value: output_value }
|
77
|
+
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# FORWARD PROPAGATION: Compute output range from input range
|
84
|
+
def propagate_range_forward(constraint, operation_spec, operand_map)
|
85
|
+
result_var = operand_map[:result]
|
86
|
+
input_min = constraint[:min]
|
87
|
+
input_max = constraint[:max]
|
88
|
+
|
89
|
+
case operation_spec.id
|
90
|
+
when "core.add"
|
91
|
+
# x in [min, max], result = x + C => result in [min + C, max + C]
|
92
|
+
other = get_other_operand_value(constraint, operand_map, "add")
|
93
|
+
return nil unless other.is_a?(Numeric)
|
94
|
+
|
95
|
+
output_min = input_min + other
|
96
|
+
output_max = input_max + other
|
97
|
+
{ variable: result_var, op: :range, min: output_min, max: output_max }
|
98
|
+
|
99
|
+
when "core.mul"
|
100
|
+
# x in [min, max], result = x * C => depends on sign of C
|
101
|
+
other = get_other_operand_value(constraint, operand_map, "mul")
|
102
|
+
return nil unless other.is_a?(Numeric)
|
103
|
+
|
104
|
+
if other > 0
|
105
|
+
output_min = input_min * other
|
106
|
+
output_max = input_max * other
|
107
|
+
elsif other < 0
|
108
|
+
output_min = input_max * other
|
109
|
+
output_max = input_min * other
|
110
|
+
else
|
111
|
+
output_min = 0
|
112
|
+
output_max = 0
|
113
|
+
end
|
114
|
+
{ variable: result_var, op: :range, min: output_min, max: output_max }
|
115
|
+
|
116
|
+
when "core.sub"
|
117
|
+
# x in [min, max], result = x - C => result in [min - C, max - C]
|
118
|
+
other = get_other_operand_value(constraint, operand_map, "sub")
|
119
|
+
return nil unless other.is_a?(Numeric)
|
120
|
+
|
121
|
+
output_min = input_min - other
|
122
|
+
output_max = input_max - other
|
123
|
+
{ variable: result_var, op: :range, min: output_min, max: output_max }
|
124
|
+
|
125
|
+
else
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# REVERSE PROPAGATION: Derive input equality from output equality
|
131
|
+
def propagate_equality_reverse(constraint, operation_spec, operand_map)
|
132
|
+
result_var = constraint[:variable]
|
133
|
+
result_value = constraint[:value]
|
134
|
+
left_var = operand_map[:left_operand]
|
135
|
+
right_var = operand_map[:right_operand]
|
136
|
+
|
137
|
+
case operation_spec.id
|
138
|
+
when "core.add"
|
139
|
+
# result == V, result = x + C => x == V - C
|
140
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric)
|
141
|
+
{ variable: left_var, op: :==, value: result_value - right_var }
|
142
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
143
|
+
{ variable: right_var, op: :==, value: result_value - left_var }
|
144
|
+
else
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
|
148
|
+
when "core.mul"
|
149
|
+
# result == V, result = x * C => x == V / C (if C != 0)
|
150
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric) && right_var != 0
|
151
|
+
return nil unless (result_value % right_var).zero?
|
152
|
+
{ variable: left_var, op: :==, value: result_value / right_var }
|
153
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric) && left_var != 0
|
154
|
+
return nil unless (result_value % left_var).zero?
|
155
|
+
{ variable: right_var, op: :==, value: result_value / left_var }
|
156
|
+
else
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
|
160
|
+
when "core.sub"
|
161
|
+
# result == V, result = x - C => x == V + C
|
162
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric)
|
163
|
+
{ variable: left_var, op: :==, value: result_value + right_var }
|
164
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
165
|
+
{ variable: right_var, op: :==, value: left_var - result_value }
|
166
|
+
else
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
|
170
|
+
else
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# REVERSE PROPAGATION: Derive input range from output range
|
176
|
+
def propagate_range_reverse(constraint, operation_spec, operand_map)
|
177
|
+
result_min = constraint[:min]
|
178
|
+
result_max = constraint[:max]
|
179
|
+
left_var = operand_map[:left_operand]
|
180
|
+
right_var = operand_map[:right_operand]
|
181
|
+
|
182
|
+
case operation_spec.id
|
183
|
+
when "core.add"
|
184
|
+
# result in [min, max], result = x + C => x in [min - C, max - C]
|
185
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric)
|
186
|
+
return { variable: left_var, op: :range, min: result_min - right_var, max: result_max - right_var }
|
187
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
188
|
+
return { variable: right_var, op: :range, min: result_min - left_var, max: result_max - left_var }
|
189
|
+
end
|
190
|
+
|
191
|
+
when "core.mul"
|
192
|
+
# result in [min, max], result = x * C => x in [min/C, max/C] (depends on sign)
|
193
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric) && right_var != 0
|
194
|
+
if right_var > 0
|
195
|
+
return { variable: left_var, op: :range, min: result_min / right_var, max: result_max / right_var }
|
196
|
+
else
|
197
|
+
return { variable: left_var, op: :range, min: result_max / right_var, max: result_min / right_var }
|
198
|
+
end
|
199
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric) && left_var != 0
|
200
|
+
if left_var > 0
|
201
|
+
return { variable: right_var, op: :range, min: result_min / left_var, max: result_max / left_var }
|
202
|
+
else
|
203
|
+
return { variable: right_var, op: :range, min: result_max / left_var, max: result_min / left_var }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
when "core.sub"
|
208
|
+
# result in [min, max], result = x - C => x in [min + C, max + C]
|
209
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric)
|
210
|
+
return { variable: left_var, op: :range, min: result_min + right_var, max: result_max + right_var }
|
211
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
212
|
+
return { variable: right_var, op: :range, min: left_var - result_max, max: left_var - result_min }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
nil
|
217
|
+
end
|
218
|
+
|
219
|
+
def get_other_operand_value(constraint, operand_map, operation)
|
220
|
+
input_var = constraint[:variable]
|
221
|
+
left_var = operand_map[:left_operand] || operand_map.values[0]
|
222
|
+
right_var = operand_map[:right_operand] || operand_map.values[1]
|
223
|
+
|
224
|
+
if input_var == left_var && right_var.is_a?(Numeric)
|
225
|
+
right_var
|
226
|
+
elsif input_var == right_var && left_var.is_a?(Numeric)
|
227
|
+
left_var
|
228
|
+
else
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -103,10 +103,28 @@ module Kumi
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def kind_from_type(t)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
106
|
+
# Handle both symbols (legacy) and Type objects (new)
|
107
|
+
case t
|
108
|
+
when Kumi::Core::Types::ArrayType
|
109
|
+
:array
|
110
|
+
when Kumi::Core::Types::TupleType
|
111
|
+
:array # Tuples behave like arrays for input access
|
112
|
+
when :array, Kumi::Core::Types::ScalarType
|
113
|
+
# Check if it's a hash scalar or :hash symbol
|
114
|
+
if t.is_a?(Kumi::Core::Types::ScalarType) && t.kind == :hash
|
115
|
+
:hash
|
116
|
+
elsif t == :hash
|
117
|
+
:hash
|
118
|
+
elsif t == :array
|
119
|
+
:array
|
120
|
+
else
|
121
|
+
:scalar
|
122
|
+
end
|
123
|
+
when :hash
|
124
|
+
:hash
|
125
|
+
else
|
126
|
+
:scalar
|
127
|
+
end
|
110
128
|
end
|
111
129
|
end
|
112
130
|
end
|
@@ -68,18 +68,61 @@ module Kumi
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def analyze_call_expression(call, errors)
|
71
|
-
|
72
|
-
|
71
|
+
# Step 1: Analyze arguments to get their types and scopes
|
73
72
|
arg_metadata = call.args.map { |arg| analyze_expression(arg, errors) }
|
74
73
|
arg_types = arg_metadata.map { |m| m[:type] }
|
75
74
|
arg_scopes = arg_metadata.map { |m| m[:scope] }
|
76
75
|
|
77
|
-
|
76
|
+
# Ensure all arg_types are Type objects (defensive programming)
|
77
|
+
arg_types = arg_types.map do |t|
|
78
|
+
case t
|
79
|
+
when Types::Type
|
80
|
+
t
|
81
|
+
when :array
|
82
|
+
# :array is actually an ArrayType marker, not a scalar kind
|
83
|
+
Types.array(Types.scalar(:any))
|
84
|
+
when :hash
|
85
|
+
Types.scalar(:hash)
|
86
|
+
when Symbol
|
87
|
+
# Try to normalize as scalar kind
|
88
|
+
Types.normalize(t)
|
89
|
+
else
|
90
|
+
# Already a Type object or unknown format
|
91
|
+
t
|
92
|
+
end
|
93
|
+
end
|
78
94
|
|
79
|
-
|
80
|
-
|
95
|
+
debug " Call #{call.fn}: arg_scopes=#{arg_scopes.inspect}, arg_types=#{arg_types.inspect}"
|
96
|
+
|
97
|
+
# Step 2: Resolve function using type-aware overload resolution
|
98
|
+
begin
|
99
|
+
resolved_fn_id = @registry.resolve_function_with_types(call.fn.to_s, arg_types)
|
100
|
+
function_spec = @registry.function(resolved_fn_id)
|
101
|
+
debug " Resolved '#{call.fn}' with types #{arg_types.inspect} to #{resolved_fn_id}"
|
102
|
+
rescue Core::Functions::OverloadResolver::ResolutionError => e
|
103
|
+
# Type-aware overload resolution failed - report with location
|
104
|
+
report_type_error(
|
105
|
+
errors,
|
106
|
+
e.message,
|
107
|
+
location: call.loc,
|
108
|
+
context: {
|
109
|
+
function: call.fn.to_s,
|
110
|
+
arg_types: arg_types
|
111
|
+
}
|
112
|
+
)
|
113
|
+
raise Kumi::Core::Errors::TypeError, e.message
|
114
|
+
rescue StandardError => e
|
115
|
+
# Other function resolution errors
|
116
|
+
report_semantic_error(
|
117
|
+
errors,
|
118
|
+
"Function resolution error for '#{call.fn}': #{e.message}",
|
119
|
+
location: call.loc,
|
120
|
+
context: { function: call.fn.to_s }
|
121
|
+
)
|
122
|
+
raise Kumi::Core::Errors::SemanticError, e.message
|
81
123
|
end
|
82
124
|
|
125
|
+
# Step 3: Compute result type
|
83
126
|
named_types =
|
84
127
|
if function_spec.params.size == arg_types.size
|
85
128
|
Hash[function_spec.param_names.zip(arg_types)]
|
@@ -89,11 +132,17 @@ module Kumi
|
|
89
132
|
|
90
133
|
begin
|
91
134
|
result_type = function_spec.dtype_rule.call(named_types)
|
92
|
-
rescue StandardError
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
135
|
+
rescue StandardError => e
|
136
|
+
report_type_error(
|
137
|
+
errors,
|
138
|
+
"Type rule evaluation failed for #{function_spec.id}: #{e.message}",
|
139
|
+
location: call.loc,
|
140
|
+
context: {
|
141
|
+
function: function_spec.id,
|
142
|
+
arg_types: arg_types
|
143
|
+
}
|
144
|
+
)
|
145
|
+
raise Kumi::Core::Errors::TypeError, "Type rule failed for #{function_spec.id}: #{e.message}"
|
97
146
|
end
|
98
147
|
|
99
148
|
over_collection = arg_types.size == 1 && Types.collection?(arg_types[0])
|
@@ -120,11 +169,8 @@ module Kumi
|
|
120
169
|
element_scopes = elems.map { |m| m[:scope] }
|
121
170
|
result_scope = lub_by_prefix(element_scopes)
|
122
171
|
|
123
|
-
|
124
|
-
|
125
|
-
else
|
126
|
-
"tuple<#{element_types.join(', ')}>"
|
127
|
-
end
|
172
|
+
# Create TupleType from element Types
|
173
|
+
result_type = Types.tuple(element_types)
|
128
174
|
|
129
175
|
@metadata_table[node_id(node)] = {
|
130
176
|
parameter_names: [],
|
@@ -143,7 +189,7 @@ module Kumi
|
|
143
189
|
fields = node.pairs.map { |e| analyze_expression(e, errors) }
|
144
190
|
fields_scopes = fields.map { |m| m[:scope] }
|
145
191
|
scope = lub_by_prefix(fields_scopes)
|
146
|
-
dtype = :hash
|
192
|
+
dtype = Types.scalar(:hash)
|
147
193
|
|
148
194
|
@metadata_table[node_id(node)] = {
|
149
195
|
type: dtype,
|
@@ -153,7 +199,7 @@ module Kumi
|
|
153
199
|
|
154
200
|
def analyze_pair(node, errors)
|
155
201
|
value_node = analyze_expression(node.value, errors)
|
156
|
-
dtype = :pair
|
202
|
+
dtype = Types.scalar(:pair)
|
157
203
|
|
158
204
|
@metadata_table[node_id(node)] = {
|
159
205
|
type: dtype,
|
@@ -182,7 +228,7 @@ module Kumi
|
|
182
228
|
def analyze_index_ref(node, _errors)
|
183
229
|
meta = @input_table.find { _1.path_fqn == node.input_fqn } or raise "Index plan found: #{n.name.inspect}"
|
184
230
|
axes = Array(meta[:axes])
|
185
|
-
type = :integer
|
231
|
+
type = Types.scalar(:integer)
|
186
232
|
|
187
233
|
debug " IndexRef #{node.name}: input_fqn=#{node.input_fqn}, axes=#{axes.inspect}"
|
188
234
|
|
@@ -75,14 +75,18 @@ module Kumi
|
|
75
75
|
|
76
76
|
def normalize_call_expression(node, errors)
|
77
77
|
begin
|
78
|
-
|
79
|
-
|
78
|
+
fn_alias = FnAliases::MAP[node.fn_name] || node.fn_name
|
79
|
+
|
80
|
+
# Try to get the function to check if it's expandable
|
81
|
+
# For expandable functions, we need to resolve now
|
82
|
+
# For regular functions, we defer resolution to NASTDimensionalAnalyzerPass
|
83
|
+
func = @registry.function(fn_alias) rescue nil
|
80
84
|
rescue StandardError
|
81
85
|
# puts "MISSING_FUNCTION: #{node.fn_name.inspect}"
|
82
86
|
raise
|
83
87
|
end
|
84
88
|
|
85
|
-
if func.expand
|
89
|
+
if func && func.expand
|
86
90
|
# 1. Normalize the arguments FIRST.
|
87
91
|
normalized_args = node.args.map { |arg| normalize_expr(arg, errors) }
|
88
92
|
|
@@ -91,8 +95,9 @@ module Kumi
|
|
91
95
|
MacroExpander.expand(func, normalized_args, node.loc, errors)
|
92
96
|
else
|
93
97
|
# Regular, non-expandable function call.
|
98
|
+
# Keep the alias, don't resolve yet - let NASTDimensionalAnalyzerPass handle overload resolution
|
94
99
|
args = node.args.map { |a| normalize_expr(a, errors) }
|
95
|
-
NAST::Call.new(fn:
|
100
|
+
NAST::Call.new(fn: fn_alias.to_sym, args: args, opts: node.opts, loc: node.loc)
|
96
101
|
end
|
97
102
|
end
|
98
103
|
|
@@ -174,7 +174,9 @@ module Kumi
|
|
174
174
|
# regular elementwise
|
175
175
|
args = n.args.map { _1.accept(self) }
|
176
176
|
m = meta_for(n)
|
177
|
-
|
177
|
+
# Use the function ID from metadata (already resolved with type awareness in NASTDimensionalAnalyzerPass)
|
178
|
+
fn_id = m[:function] || @registry.resolve_function(n.fn)
|
179
|
+
out = n.class.new(id: n.id, fn: fn_id.to_sym, args:, opts: n.opts, loc: n.loc)
|
178
180
|
stamp!(out, m[:result_scope], m[:result_type])
|
179
181
|
end
|
180
182
|
|