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
@@ -4,40 +4,41 @@ module Kumi
|
|
4
4
|
module Core
|
5
5
|
module Analyzer
|
6
6
|
module Passes
|
7
|
-
# RESPONSIBILITY: Detect unsatisfiable constraints
|
8
|
-
# DEPENDENCIES: :declarations
|
7
|
+
# RESPONSIBILITY: Detect unsatisfiable constraints using formal constraint semantics
|
8
|
+
# DEPENDENCIES: :declarations, :input_metadata, :registry, SNAST representation
|
9
9
|
# INTERFACE: new(schema, state).run(errors)
|
10
|
+
#
|
11
|
+
# Detects when constraints are clearly unsatisfiable using constraint propagation:
|
12
|
+
# 1. Same variable with contradicting equality values (v == 5 AND v == 10)
|
13
|
+
# 2. Values violating input domain constraints
|
14
|
+
# 3. Constraints derived through arithmetic operations that violate domains
|
10
15
|
class UnsatDetector < VisitorPass
|
11
16
|
include Syntax
|
12
17
|
|
13
18
|
COMPARATORS = %i[> < >= <= == !=].freeze
|
14
|
-
Atom = Kumi::Core::AtomUnsatSolver::Atom
|
15
19
|
|
16
20
|
def run(errors)
|
17
|
-
return state
|
18
21
|
definitions = get_state(:declarations)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
input_meta = get_state(:input_metadata) || {}
|
23
|
+
registry = get_state(:registry)
|
24
|
+
|
25
|
+
@propagator = FormalConstraintPropagator.new(schema, state)
|
22
26
|
|
23
27
|
each_decl do |decl|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
impossible = result
|
39
|
-
|
40
|
-
report_error(errors, "conjunction `#{decl.name}` is impossible", location: decl.loc) if impossible
|
28
|
+
# Only check trait declarations for obvious contradictions
|
29
|
+
next unless decl.is_a?(TraitDeclaration)
|
30
|
+
|
31
|
+
atoms = extract_equality_atoms(decl.expression, definitions)
|
32
|
+
next if atoms.empty?
|
33
|
+
|
34
|
+
# Check for formal, obvious contradictions
|
35
|
+
if contradicting_equalities?(atoms) || domain_violations?(atoms, input_meta) ||
|
36
|
+
propagated_violations?(atoms, definitions, input_meta, registry)
|
37
|
+
report_error(
|
38
|
+
errors,
|
39
|
+
"conjunction `#{decl.name}` is impossible",
|
40
|
+
location: decl.loc
|
41
|
+
)
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
@@ -46,221 +47,194 @@ module Kumi
|
|
46
47
|
|
47
48
|
private
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
left_atoms = gather_atoms(left_side, definitions, Set.new)
|
56
|
-
left_impossible = if left_atoms.empty?
|
57
|
-
false
|
58
|
-
elsif definitions && !definitions.empty?
|
59
|
-
Kumi::Core::ConstraintRelationshipSolver.unsat?(left_atoms, definitions, input_meta: @input_meta)
|
60
|
-
else
|
61
|
-
Kumi::Core::AtomUnsatSolver.unsat?(left_atoms)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Check if right side is impossible
|
65
|
-
right_atoms = gather_atoms(right_side, definitions, Set.new)
|
66
|
-
right_impossible = if right_atoms.empty?
|
67
|
-
false
|
68
|
-
elsif definitions && !definitions.empty?
|
69
|
-
Kumi::Core::ConstraintRelationshipSolver.unsat?(right_atoms, definitions, input_meta: @input_meta)
|
70
|
-
else
|
71
|
-
Kumi::Core::AtomUnsatSolver.unsat?(right_atoms)
|
72
|
-
end
|
73
|
-
|
74
|
-
# OR is impossible only if BOTH sides are impossible
|
75
|
-
left_impossible && right_impossible
|
76
|
-
end
|
77
|
-
|
78
|
-
def gather_atoms(node, defs, visited, list = [])
|
79
|
-
return list unless node
|
80
|
-
|
81
|
-
# Use iterative approach with stack to avoid SystemStackError on deep graphs
|
82
|
-
stack = [node]
|
50
|
+
# Extract equality constraints from expression
|
51
|
+
# Returns array of {op: :==, lhs: symbol, rhs: value} hashes
|
52
|
+
def extract_equality_atoms(expr, definitions)
|
53
|
+
atoms = []
|
54
|
+
stack = [expr]
|
55
|
+
visited = Set.new
|
83
56
|
|
84
57
|
until stack.empty?
|
85
58
|
current = stack.pop
|
86
59
|
next unless current
|
87
60
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
current.
|
100
|
-
|
101
|
-
|
102
|
-
elsif current.is_a?(DeclarationReference)
|
103
|
-
name = current.name
|
104
|
-
unless visited.include?(name)
|
105
|
-
visited << name
|
106
|
-
stack << defs[name].expression if defs.key?(name)
|
61
|
+
case current
|
62
|
+
when CallExpression
|
63
|
+
if current.fn_name == :==
|
64
|
+
lhs, rhs = current.args
|
65
|
+
lhs_val = extract_term(lhs, definitions)
|
66
|
+
rhs_val = extract_term(rhs, definitions)
|
67
|
+
atoms << { op: :==, lhs: lhs_val, rhs: rhs_val } if lhs_val && rhs_val
|
68
|
+
elsif current.fn_name == :and
|
69
|
+
current.args.each { |arg| stack << arg }
|
70
|
+
end
|
71
|
+
when DeclarationReference
|
72
|
+
unless visited.include?(current.name)
|
73
|
+
visited << current.name
|
74
|
+
stack << definitions[current.name]&.expression
|
107
75
|
end
|
108
76
|
end
|
109
|
-
|
110
|
-
current.children.each { |child| stack << child } if current.respond_to?(:children) && !current.is_a?(CascadeExpression)
|
111
77
|
end
|
112
78
|
|
113
|
-
|
79
|
+
atoms
|
114
80
|
end
|
115
81
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
82
|
+
# Extract value from AST node
|
83
|
+
# Returns symbol for variables, literal values for constants
|
84
|
+
def extract_term(node, definitions)
|
85
|
+
case node
|
86
|
+
when Literal
|
87
|
+
node.value
|
88
|
+
when DeclarationReference
|
89
|
+
val = evaluate_to_literal(node, definitions)
|
90
|
+
val == :unknown ? node.name : val
|
91
|
+
when InputReference
|
92
|
+
node.name
|
93
|
+
else
|
94
|
+
nil
|
125
95
|
end
|
96
|
+
end
|
126
97
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
98
|
+
# Try to evaluate a declaration to a literal constant
|
99
|
+
def evaluate_to_literal(decl_ref, definitions)
|
100
|
+
definition = definitions[decl_ref.name]
|
101
|
+
return :unknown unless definition&.expression.is_a?(Literal)
|
131
102
|
|
132
|
-
|
103
|
+
definition.expression.value
|
104
|
+
end
|
133
105
|
|
134
|
-
|
106
|
+
# FORMAL RULE 1: Contradicting equalities
|
107
|
+
# IF: same_variable == value1 AND same_variable == value2 AND value1 != value2
|
108
|
+
# THEN: unsatisfiable
|
109
|
+
def contradicting_equalities?(atoms)
|
110
|
+
by_lhs = atoms.group_by { |a| a[:lhs] }
|
135
111
|
|
136
|
-
|
137
|
-
|
138
|
-
|
112
|
+
by_lhs.each do |_lhs, constraints|
|
113
|
+
rhs_values = constraints.map { |c| c[:rhs] }.uniq
|
114
|
+
return true if rhs_values.size > 1
|
115
|
+
end
|
139
116
|
|
140
|
-
|
117
|
+
false
|
118
|
+
end
|
141
119
|
|
142
|
-
|
143
|
-
|
144
|
-
|
120
|
+
# FORMAL RULE 2: Domain violations
|
121
|
+
# IF: variable is input field AND domain constraint exists AND value outside domain
|
122
|
+
# THEN: unsatisfiable
|
123
|
+
def domain_violations?(atoms, input_meta)
|
124
|
+
atoms.each do |atom|
|
125
|
+
variable = atom[:lhs]
|
126
|
+
value = atom[:rhs]
|
145
127
|
|
146
|
-
|
147
|
-
result = Kumi::Core::ConstraintRelationshipSolver.unsat?(condition_atoms, definitions, input_meta: @input_meta)
|
148
|
-
if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
|
149
|
-
puts " Enhanced solver result: #{result}"
|
150
|
-
end
|
151
|
-
else
|
152
|
-
result = Kumi::Core::AtomUnsatSolver.unsat?(condition_atoms)
|
153
|
-
if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
|
154
|
-
puts " Basic solver result: #{result}"
|
155
|
-
end
|
156
|
-
end
|
157
|
-
impossible = result
|
158
|
-
next unless !condition_atoms.empty? && impossible
|
159
|
-
|
160
|
-
if when_case.condition.is_a?(CallExpression) && when_case.condition.fn_name == :cascade_and
|
161
|
-
trait_bindings = when_case.condition.args
|
162
|
-
|
163
|
-
if trait_bindings.all?(DeclarationReference)
|
164
|
-
traits = trait_bindings.map(&:name).join(" AND ")
|
165
|
-
if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
|
166
|
-
puts " -> FLAGGING AS IMPOSSIBLE CASCADE CONDITION: #{traits}"
|
167
|
-
end
|
168
|
-
report_error(errors, "conjunction `#{traits}` is impossible", location: decl.loc)
|
169
|
-
next
|
170
|
-
end
|
171
|
-
end
|
172
|
-
if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
|
173
|
-
puts " -> FLAGGING AS IMPOSSIBLE CASCADE: #{decl.name}"
|
174
|
-
end
|
175
|
-
report_error(errors, "conjunction `#{decl.name}` is impossible", location: decl.loc)
|
176
|
-
end
|
177
|
-
end
|
128
|
+
next unless value.is_a?(Numeric)
|
178
129
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
when InputElementReference
|
185
|
-
path_identifier = node.path.join(".").to_s
|
186
|
-
path_identifier.to_sym
|
187
|
-
when Literal
|
188
|
-
node.value
|
189
|
-
else
|
190
|
-
:unknown
|
130
|
+
metadata = input_meta[variable]
|
131
|
+
next unless metadata&.dig(:domain)
|
132
|
+
|
133
|
+
domain = metadata[:domain]
|
134
|
+
return true unless domain.include?(value)
|
191
135
|
end
|
136
|
+
|
137
|
+
false
|
192
138
|
end
|
193
139
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
140
|
+
# FORMAL RULE 3: Propagated constraint violations
|
141
|
+
# Propagate constraints through operations to derive hidden impossibilities
|
142
|
+
def propagated_violations?(atoms, definitions, input_meta, registry)
|
143
|
+
propagated = propagate_constraints(atoms, definitions, registry)
|
144
|
+
return false if propagated.empty?
|
145
|
+
|
146
|
+
propagated_domain_violations?(propagated, input_meta)
|
201
147
|
end
|
202
148
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
return field_literal_impossible?(lhs, rhs, operator)
|
207
|
-
elsif rhs.is_a?(InputReference) && lhs.is_a?(Literal)
|
208
|
-
return field_literal_impossible?(rhs, lhs, flip_operator(operator))
|
209
|
-
end
|
149
|
+
# Propagate constraints through operation definitions
|
150
|
+
def propagate_constraints(atoms, definitions, registry)
|
151
|
+
propagated = []
|
210
152
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
153
|
+
atoms.each do |atom|
|
154
|
+
variable = atom[:lhs]
|
155
|
+
value = atom[:rhs]
|
156
|
+
|
157
|
+
next unless value.is_a?(Numeric)
|
158
|
+
|
159
|
+
definition = definitions[variable]
|
160
|
+
next unless definition&.is_a?(ValueDeclaration)
|
161
|
+
|
162
|
+
propagated.concat(propagate_through_operation(definition, variable, value, registry, definitions))
|
216
163
|
end
|
217
164
|
|
218
|
-
|
165
|
+
propagated
|
219
166
|
end
|
220
167
|
|
221
|
-
|
222
|
-
|
223
|
-
|
168
|
+
# Propagate a single constraint through an operation
|
169
|
+
def propagate_through_operation(decl, variable, value, registry, definitions)
|
170
|
+
expr = decl.expression
|
171
|
+
return [] unless expr.is_a?(CallExpression)
|
224
172
|
|
225
|
-
|
226
|
-
|
173
|
+
fn_id = registry.resolve_function(expr.fn_name)
|
174
|
+
return [] unless fn_id
|
227
175
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
176
|
+
operation = registry.function(fn_id)
|
177
|
+
return [] unless operation
|
178
|
+
|
179
|
+
operand_map = build_operand_map(expr, definitions)
|
180
|
+
constraint = { variable: variable, op: :==, value: value }
|
181
|
+
|
182
|
+
propagated_constraint = @propagator.propagate_reverse_through_operation(
|
183
|
+
constraint,
|
184
|
+
operation,
|
185
|
+
operand_map
|
186
|
+
)
|
187
|
+
|
188
|
+
propagated_constraint ? [propagated_constraint] : []
|
236
189
|
end
|
237
190
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
191
|
+
# Build operand map for an operation expression
|
192
|
+
def build_operand_map(expr, definitions)
|
193
|
+
args = expr.args
|
194
|
+
map = {}
|
242
195
|
|
243
|
-
|
196
|
+
if args.size >= 2
|
197
|
+
left_val = extract_operand_value(args[0], definitions)
|
198
|
+
right_val = extract_operand_value(args[1], definitions)
|
244
199
|
|
245
|
-
|
246
|
-
|
247
|
-
evaluated_value != literal_value
|
248
|
-
else
|
249
|
-
false
|
200
|
+
map[:left_operand] = left_val
|
201
|
+
map[:right_operand] = right_val
|
250
202
|
end
|
203
|
+
|
204
|
+
map
|
251
205
|
end
|
252
206
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
when
|
257
|
-
|
258
|
-
when
|
259
|
-
|
260
|
-
when
|
261
|
-
|
207
|
+
# Extract operand value (symbol for variable, numeric for constant)
|
208
|
+
def extract_operand_value(node, _definitions)
|
209
|
+
case node
|
210
|
+
when Literal
|
211
|
+
node.value
|
212
|
+
when DeclarationReference
|
213
|
+
node.name
|
214
|
+
when InputReference
|
215
|
+
node.name
|
262
216
|
end
|
263
217
|
end
|
218
|
+
|
219
|
+
# Check if propagated constraints violate input domain
|
220
|
+
def propagated_domain_violations?(propagated, input_meta)
|
221
|
+
propagated.each do |constraint|
|
222
|
+
next unless constraint[:op] == :==
|
223
|
+
|
224
|
+
variable = constraint[:variable]
|
225
|
+
value = constraint[:value]
|
226
|
+
|
227
|
+
next unless value.is_a?(Numeric)
|
228
|
+
|
229
|
+
metadata = input_meta[variable]
|
230
|
+
next unless metadata&.dig(:domain)
|
231
|
+
|
232
|
+
domain = metadata[:domain]
|
233
|
+
return true unless domain.include?(value)
|
234
|
+
end
|
235
|
+
|
236
|
+
false
|
237
|
+
end
|
264
238
|
end
|
265
239
|
end
|
266
240
|
end
|
@@ -12,16 +12,51 @@ module Kumi
|
|
12
12
|
# 4. Support both immediate raising and error accumulation patterns
|
13
13
|
module ErrorReporter
|
14
14
|
# Standard error structure for internal use
|
15
|
-
|
15
|
+
# Provides clean access to location components (file, line, column)
|
16
|
+
class ErrorEntry
|
17
|
+
attr_reader :location, :message, :type, :context, :backtrace
|
18
|
+
|
19
|
+
def initialize(location:, message:, type: :semantic, context: {}, backtrace: nil)
|
20
|
+
@location = location
|
21
|
+
@message = message
|
22
|
+
@type = type
|
23
|
+
@context = context
|
24
|
+
@backtrace = backtrace
|
25
|
+
end
|
26
|
+
|
16
27
|
def to_s
|
17
28
|
location_str = format_location(location)
|
18
29
|
"#{location_str}: #{message}"
|
19
30
|
end
|
20
31
|
|
32
|
+
# Check if location information is present
|
21
33
|
def location?
|
22
34
|
location && !location.is_a?(Symbol)
|
23
35
|
end
|
24
36
|
|
37
|
+
# Extract location components cleanly
|
38
|
+
def file
|
39
|
+
location.is_a?(Syntax::Location) ? location.file : nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def line
|
43
|
+
location.is_a?(Syntax::Location) ? location.line : nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def column
|
47
|
+
location.is_a?(Syntax::Location) ? location.column : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Alias for consistency
|
51
|
+
def path
|
52
|
+
file
|
53
|
+
end
|
54
|
+
|
55
|
+
# Check if location is valid (has file and line)
|
56
|
+
def valid_location?
|
57
|
+
!!(location? && file && line && line > 0)
|
58
|
+
end
|
59
|
+
|
25
60
|
private
|
26
61
|
|
27
62
|
def format_location(loc)
|
data/lib/kumi/core/errors.rb
CHANGED
@@ -13,9 +13,41 @@ module Kumi
|
|
13
13
|
@location = location
|
14
14
|
end
|
15
15
|
|
16
|
+
# Extract location components cleanly
|
17
|
+
def location_file
|
18
|
+
@location&.file
|
19
|
+
end
|
20
|
+
|
21
|
+
def location_line
|
22
|
+
@location&.line
|
23
|
+
end
|
24
|
+
|
25
|
+
def location_column
|
26
|
+
@location&.column
|
27
|
+
end
|
28
|
+
|
29
|
+
# Aliases for convenient access
|
30
|
+
alias path location_file
|
31
|
+
alias line location_line
|
32
|
+
alias column location_column
|
33
|
+
|
34
|
+
# Check if location information is present and valid
|
35
|
+
def has_location?
|
36
|
+
@location && @location.file && @location.line && @location.line > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
# Format location for error messages
|
40
|
+
def format_location
|
41
|
+
if @location
|
42
|
+
"at #{@location.file}:#{@location.line}:#{@location.column}"
|
43
|
+
else
|
44
|
+
"at ?"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
16
48
|
def to_s
|
17
49
|
if @location
|
18
|
-
"#{super}
|
50
|
+
"#{super} #{format_location}"
|
19
51
|
else
|
20
52
|
super
|
21
53
|
end
|
@@ -5,10 +5,11 @@ module Kumi
|
|
5
5
|
module Functions
|
6
6
|
# Minimal function specification for NAST dimensional analysis
|
7
7
|
FunctionSpec = Struct.new(
|
8
|
-
:id,
|
9
|
-
:kind,
|
10
|
-
:parameter_names,
|
11
|
-
:dtype_rule,
|
8
|
+
:id, # "core.add"
|
9
|
+
:kind, # :elementwise, :reduce, :constructor
|
10
|
+
:parameter_names, # [:left_operand, :right_operand]
|
11
|
+
:dtype_rule, # "promote(left_operand,right_operand)" or proc
|
12
|
+
:constraint_semantics, # formal constraint metadata (hash or nil)
|
12
13
|
keyword_init: true
|
13
14
|
)
|
14
15
|
end
|
@@ -33,14 +33,30 @@ module Kumi
|
|
33
33
|
function_kind = fn_hash.fetch("kind").to_sym
|
34
34
|
parameter_names = (fn_hash["params"] || []).map { |p| p["name"].to_sym }
|
35
35
|
dtype_rule_fn = TypeRules.compile_dtype_rule(fn_hash.fetch("dtype"), parameter_names)
|
36
|
+
constraint_semantics = parse_constraint_semantics(fn_hash["constraint_semantics"])
|
36
37
|
|
37
38
|
Functions::FunctionSpec.new(
|
38
39
|
id: function_id,
|
39
40
|
kind: function_kind,
|
40
41
|
parameter_names: parameter_names,
|
41
|
-
dtype_rule: dtype_rule_fn
|
42
|
+
dtype_rule: dtype_rule_fn,
|
43
|
+
constraint_semantics: constraint_semantics
|
42
44
|
)
|
43
45
|
end
|
46
|
+
|
47
|
+
def parse_constraint_semantics(semantics_hash)
|
48
|
+
return nil unless semantics_hash
|
49
|
+
|
50
|
+
{
|
51
|
+
domain_effect: semantics_hash["domain_effect"]&.to_sym,
|
52
|
+
pure_combiner: semantics_hash["pure_combiner"],
|
53
|
+
commutativity: semantics_hash["commutativity"],
|
54
|
+
associativity: semantics_hash["associativity"],
|
55
|
+
identity: semantics_hash["identity"],
|
56
|
+
forward_propagation: semantics_hash["forward_propagation"],
|
57
|
+
reverse_propagation: semantics_hash["reverse_propagation"]
|
58
|
+
}
|
59
|
+
end
|
44
60
|
end
|
45
61
|
end
|
46
62
|
end
|