kumi 0.0.7 → 0.0.8

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +1 -1
  3. data/README.md +8 -5
  4. data/examples/game_of_life.rb +1 -1
  5. data/examples/static_analysis_errors.rb +7 -7
  6. data/lib/kumi/analyzer.rb +15 -15
  7. data/lib/kumi/compiler.rb +6 -6
  8. data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
  9. data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
  10. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
  11. data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
  12. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
  13. data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
  14. data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
  15. data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
  16. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
  17. data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
  18. data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
  19. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
  20. data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
  21. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
  22. data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
  23. data/lib/kumi/core/atom_unsat_solver.rb +396 -0
  24. data/lib/kumi/core/compiled_schema.rb +43 -0
  25. data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
  26. data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
  27. data/lib/kumi/core/domain/range_analyzer.rb +85 -0
  28. data/lib/kumi/core/domain/validator.rb +82 -0
  29. data/lib/kumi/core/domain/violation_formatter.rb +42 -0
  30. data/lib/kumi/core/error_reporter.rb +166 -0
  31. data/lib/kumi/core/error_reporting.rb +97 -0
  32. data/lib/kumi/core/errors.rb +120 -0
  33. data/lib/kumi/core/evaluation_wrapper.rb +40 -0
  34. data/lib/kumi/core/explain.rb +295 -0
  35. data/lib/kumi/core/export/deserializer.rb +41 -0
  36. data/lib/kumi/core/export/errors.rb +14 -0
  37. data/lib/kumi/core/export/node_builders.rb +142 -0
  38. data/lib/kumi/core/export/node_registry.rb +54 -0
  39. data/lib/kumi/core/export/node_serializers.rb +158 -0
  40. data/lib/kumi/core/export/serializer.rb +25 -0
  41. data/lib/kumi/core/export.rb +35 -0
  42. data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
  43. data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
  44. data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
  45. data/lib/kumi/core/function_registry/function_builder.rb +95 -0
  46. data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
  47. data/lib/kumi/core/function_registry/math_functions.rb +74 -0
  48. data/lib/kumi/core/function_registry/string_functions.rb +57 -0
  49. data/lib/kumi/core/function_registry/type_functions.rb +53 -0
  50. data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
  51. data/lib/kumi/core/input/type_matcher.rb +97 -0
  52. data/lib/kumi/core/input/validator.rb +51 -0
  53. data/lib/kumi/core/input/violation_creator.rb +52 -0
  54. data/lib/kumi/core/json_schema/generator.rb +65 -0
  55. data/lib/kumi/core/json_schema/validator.rb +27 -0
  56. data/lib/kumi/core/json_schema.rb +16 -0
  57. data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
  58. data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
  59. data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
  60. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
  61. data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
  62. data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
  63. data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
  64. data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
  65. data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
  66. data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
  67. data/lib/kumi/core/ruby_parser/parser.rb +71 -0
  68. data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
  69. data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
  70. data/lib/kumi/core/ruby_parser.rb +12 -0
  71. data/lib/kumi/core/schema_instance.rb +111 -0
  72. data/lib/kumi/core/types/builder.rb +23 -0
  73. data/lib/kumi/core/types/compatibility.rb +96 -0
  74. data/lib/kumi/core/types/formatter.rb +26 -0
  75. data/lib/kumi/core/types/inference.rb +42 -0
  76. data/lib/kumi/core/types/normalizer.rb +72 -0
  77. data/lib/kumi/core/types/validator.rb +37 -0
  78. data/lib/kumi/core/types.rb +66 -0
  79. data/lib/kumi/core/vectorization_metadata.rb +110 -0
  80. data/lib/kumi/errors.rb +1 -112
  81. data/lib/kumi/registry.rb +37 -0
  82. data/lib/kumi/schema.rb +5 -5
  83. data/lib/kumi/schema_metadata.rb +3 -3
  84. data/lib/kumi/syntax/array_expression.rb +6 -6
  85. data/lib/kumi/syntax/call_expression.rb +4 -4
  86. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  87. data/lib/kumi/syntax/case_expression.rb +4 -4
  88. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  89. data/lib/kumi/syntax/hash_expression.rb +4 -4
  90. data/lib/kumi/syntax/input_declaration.rb +5 -5
  91. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  92. data/lib/kumi/syntax/input_reference.rb +5 -5
  93. data/lib/kumi/syntax/literal.rb +4 -4
  94. data/lib/kumi/syntax/node.rb +34 -34
  95. data/lib/kumi/syntax/root.rb +6 -6
  96. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  97. data/lib/kumi/syntax/value_declaration.rb +4 -4
  98. data/lib/kumi/version.rb +1 -1
  99. data/migrate_to_core_iterative.rb +938 -0
  100. data/scripts/generate_function_docs.rb +9 -9
  101. metadata +75 -72
  102. data/lib/kumi/analyzer/analysis_state.rb +0 -37
  103. data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
  104. data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -246
  105. data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
  106. data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
  107. data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
  108. data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
  109. data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
  110. data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -109
  111. data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
  112. data/lib/kumi/analyzer/passes/type_checker.rb +0 -160
  113. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
  114. data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
  115. data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -404
  116. data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
  117. data/lib/kumi/atom_unsat_solver.rb +0 -394
  118. data/lib/kumi/compiled_schema.rb +0 -41
  119. data/lib/kumi/constraint_relationship_solver.rb +0 -638
  120. data/lib/kumi/domain/enum_analyzer.rb +0 -53
  121. data/lib/kumi/domain/range_analyzer.rb +0 -83
  122. data/lib/kumi/domain/validator.rb +0 -80
  123. data/lib/kumi/domain/violation_formatter.rb +0 -40
  124. data/lib/kumi/error_reporter.rb +0 -164
  125. data/lib/kumi/error_reporting.rb +0 -95
  126. data/lib/kumi/evaluation_wrapper.rb +0 -38
  127. data/lib/kumi/explain.rb +0 -293
  128. data/lib/kumi/export/deserializer.rb +0 -39
  129. data/lib/kumi/export/errors.rb +0 -12
  130. data/lib/kumi/export/node_builders.rb +0 -140
  131. data/lib/kumi/export/node_registry.rb +0 -52
  132. data/lib/kumi/export/node_serializers.rb +0 -156
  133. data/lib/kumi/export/serializer.rb +0 -23
  134. data/lib/kumi/export.rb +0 -33
  135. data/lib/kumi/function_registry/collection_functions.rb +0 -200
  136. data/lib/kumi/function_registry/comparison_functions.rb +0 -31
  137. data/lib/kumi/function_registry/conditional_functions.rb +0 -36
  138. data/lib/kumi/function_registry/function_builder.rb +0 -93
  139. data/lib/kumi/function_registry/logical_functions.rb +0 -42
  140. data/lib/kumi/function_registry/math_functions.rb +0 -72
  141. data/lib/kumi/function_registry/string_functions.rb +0 -54
  142. data/lib/kumi/function_registry/type_functions.rb +0 -51
  143. data/lib/kumi/input/type_matcher.rb +0 -95
  144. data/lib/kumi/input/validator.rb +0 -49
  145. data/lib/kumi/input/violation_creator.rb +0 -50
  146. data/lib/kumi/json_schema/generator.rb +0 -63
  147. data/lib/kumi/json_schema/validator.rb +0 -25
  148. data/lib/kumi/json_schema.rb +0 -14
  149. data/lib/kumi/ruby_parser/build_context.rb +0 -25
  150. data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +0 -36
  151. data/lib/kumi/ruby_parser/dsl.rb +0 -12
  152. data/lib/kumi/ruby_parser/dsl_cascade_builder.rb +0 -136
  153. data/lib/kumi/ruby_parser/expression_converter.rb +0 -126
  154. data/lib/kumi/ruby_parser/guard_rails.rb +0 -43
  155. data/lib/kumi/ruby_parser/input_builder.rb +0 -125
  156. data/lib/kumi/ruby_parser/input_field_proxy.rb +0 -46
  157. data/lib/kumi/ruby_parser/input_proxy.rb +0 -29
  158. data/lib/kumi/ruby_parser/nested_input.rb +0 -15
  159. data/lib/kumi/ruby_parser/parser.rb +0 -69
  160. data/lib/kumi/ruby_parser/schema_builder.rb +0 -173
  161. data/lib/kumi/ruby_parser/sugar.rb +0 -261
  162. data/lib/kumi/ruby_parser.rb +0 -10
  163. data/lib/kumi/schema_instance.rb +0 -109
  164. data/lib/kumi/types/builder.rb +0 -21
  165. data/lib/kumi/types/compatibility.rb +0 -94
  166. data/lib/kumi/types/formatter.rb +0 -24
  167. data/lib/kumi/types/inference.rb +0 -40
  168. data/lib/kumi/types/normalizer.rb +0 -70
  169. data/lib/kumi/types/validator.rb +0 -35
  170. data/lib/kumi/types.rb +0 -64
  171. data/lib/kumi/vectorization_metadata.rb +0 -108
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec2c56684edac64e9818bbf85de98e9855c3d50df80c490c8f868b3e9b701dab
4
- data.tar.gz: d5246f98e10b0365b47f6a67fed5c3a4cbc77d5ad15905d93b046251965135f5
3
+ metadata.gz: 652ef5f59f7c4469c86ecda4add1f650121d75d333231ae50306a7bf2ce7725c
4
+ data.tar.gz: 367a335e09a9d5cefaecbfa8e870c99ac49153a771f2451e3cfa8f73531d0701
5
5
  SHA512:
6
- metadata.gz: f71ba6867b72145247b5c1ea4c3b197f831996bf078798c6a9decdb4e047f83859e062e6a6411e2311d279f8a8cc03ca7e16ac194c9a27b84f8a55b67faa3fcb
7
- data.tar.gz: 70c4dab6bf036da2d507f89c2d8e9d65a24da2685412d3e517a2b3c1696d200c294c512786c85bfccf9feca301e90e70a26999f5ed1528221331b55f77175c8d
6
+ metadata.gz: 2dece8cc4284177c7f45a97768ea0c228cf41210d85cff985850a5e98f64c4ce02cc2c97cc2a20897d1f192422390ad39357b0d883e4e6aebbae1d0abc41cfe8
7
+ data.tar.gz: cb817018fab35b9594053d5bbd7fa4adb31c79a92d7e968a2739f9ffd16d194a3a45aeb75b6fcc28743fadcd93da83c9227cfe81d14bb2f9d917c18e1fb5d16b
data/CLAUDE.md CHANGED
@@ -62,7 +62,7 @@ Kumi is a Declarative logic and rules engine framework with static analysis for
62
62
  **Compiler** (`lib/kumi/compiler.rb`):
63
63
  - Transforms analyzed syntax tree into executable lambda functions
64
64
  - Maps each expression type to a compilation method
65
- - Handles function calls via `FunctionRegistry`
65
+ - Handles function calls via `Kumi::Registry`
66
66
  - Produces `CompiledSchema` with executable bindings
67
67
 
68
68
  **Function Registry** (`lib/kumi/function_registry.rb`):
data/README.md CHANGED
@@ -8,7 +8,6 @@ Kumi is a Declarative logic and rules engine framework with static analysis for
8
8
  It is well-suited for scenarios with complex, interdependent calculations, enforcing validation and consistency across your business rules while maintaining performance.
9
9
 
10
10
 
11
-
12
11
  ## What can you build?
13
12
 
14
13
  Calculate U.S. federal taxes:
@@ -118,16 +117,14 @@ value :monthly_payment, fn(:pmt, rate: 0.05/12, nper: 36, pv: -loan_amount)
118
117
  ```
119
118
  Note: You can find a list all core functions in [docs/FUNCTIONS.md](docs/FUNCTIONS.md)
120
119
 
121
- These primitives are statically analyzed during schema definition to catch logical errors before runtime.
122
-
123
120
  </details>
124
121
 
125
122
  <details>
126
- <summary><strong>🔍 Static Analysis</strong> - Catch business logic errors at definition time</summary>
123
+ <summary><strong>🔍 Static Analysis</strong> - Catch errors at definition time and provides rich metadata</summary>
127
124
 
128
125
  ### Static Analysis
129
126
 
130
- Kumi catches business logic errors that cause runtime failures or silent bugs:
127
+ Kumi catches many types of business logic errors that cause runtime failures or silent bugs:
131
128
 
132
129
  ```ruby
133
130
  module InsurancePolicyPricer
@@ -361,6 +358,12 @@ The SchemaMetadata interface provides both processed metadata for tool developme
361
358
 
362
359
  </details>
363
360
 
361
+ ## Beyond Rules: What the Metadata Unlocks
362
+ * **Auto-generated forms** – compile schema → field spec → React form
363
+ * **Scenario explorer** – derive all trait combinations, Monte Carlo outcomes
364
+ * **Coverage dashboard** – flag branches never hit in prod
365
+ * **Schema diff** – highlight behaviour changes across versions
366
+
364
367
  ## Usage
365
368
 
366
369
  **Suitable for:**
@@ -13,7 +13,7 @@ begin
13
13
  cells[neighbor_index]
14
14
  end.sum
15
15
  end
16
- Kumi::FunctionRegistry.register_with_metadata(:neighbor_cells_sum, method(:neighbor_cells_sum_method),
16
+ Kumi::Core::FunctionRegistry.register_with_metadata(:neighbor_cells_sum, method(:neighbor_cells_sum_method),
17
17
  return_type: :integer, arity: 5,
18
18
  param_types: %i[array integer integer integer integer],
19
19
  description: "Get neighbor cells for Conway's Game of Life")
@@ -22,7 +22,7 @@ begin
22
22
  value :yearly_rate, monthly_rate * 12
23
23
  end
24
24
  end
25
- rescue Kumi::Errors::SemanticError => e
25
+ rescue Kumi::Core::Errors::SemanticError => e
26
26
  puts " → #{e.message}"
27
27
  end
28
28
 
@@ -48,7 +48,7 @@ begin
48
48
  end
49
49
  end
50
50
  end
51
- rescue Kumi::Errors::SemanticError => e
51
+ rescue Kumi::Core::Errors::SemanticError => e
52
52
  puts " → #{e.message}"
53
53
  end
54
54
 
@@ -71,7 +71,7 @@ begin
71
71
  value :invalid_sum, input.name + input.age
72
72
  end
73
73
  end
74
- rescue Kumi::Errors::TypeError => e
74
+ rescue Kumi::Core::Errors::TypeError => e
75
75
  puts " → #{e.message}"
76
76
  end
77
77
 
@@ -94,7 +94,7 @@ begin
94
94
  trait :impossible_score, input.score == 150
95
95
  end
96
96
  end
97
- rescue Kumi::Errors::SemanticError => e
97
+ rescue Kumi::Core::Errors::SemanticError => e
98
98
  puts " → #{e.message}"
99
99
  end
100
100
 
@@ -114,7 +114,7 @@ begin
114
114
  value :result, ref(:nonexistent_trait) ? 100 : 0
115
115
  end
116
116
  end
117
- rescue Kumi::Errors::SemanticError => e
117
+ rescue Kumi::Core::Errors::SemanticError => e
118
118
  puts " → #{e.message}"
119
119
  end
120
120
 
@@ -134,7 +134,7 @@ begin
134
134
  value :result, fn(:nonexistent_function, input.text)
135
135
  end
136
136
  end
137
- rescue Kumi::Errors::TypeError => e
137
+ rescue Kumi::Core::Errors::TypeError => e
138
138
  puts " → #{e.message}"
139
139
  end
140
140
 
@@ -162,7 +162,7 @@ begin
162
162
  value :result, ref(:undefined_declaration)
163
163
  end
164
164
  end
165
- rescue Kumi::Errors::SemanticError => e
165
+ rescue Kumi::Core::Errors::SemanticError => e
166
166
  puts " → " + e.message.split("\n").join("\n → ")
167
167
  end
168
168
 
data/lib/kumi/analyzer.rb CHANGED
@@ -7,21 +7,21 @@ module Kumi
7
7
  module_function
8
8
 
9
9
  DEFAULT_PASSES = [
10
- Passes::NameIndexer, # 1. Finds all names and checks for duplicates.
11
- Passes::InputCollector, # 2. Collects field metadata from input declarations.
12
- Passes::DeclarationValidator, # 3. Checks the basic structure of each rule.
13
- Passes::SemanticConstraintValidator, # 4. Validates DSL semantic constraints at AST level.
14
- Passes::DependencyResolver, # 5. Builds the dependency graph with conditional dependencies.
15
- Passes::UnsatDetector, # 6. Detects unsatisfiable constraints and analyzes cascade mutual exclusion.
16
- Passes::Toposorter, # 7. Creates the final evaluation order, allowing safe cycles.
17
- Passes::BroadcastDetector, # 8. Detects which operations should be broadcast over arrays (must run before type inference).
18
- Passes::TypeInferencer, # 9. Infers types for all declarations (uses vectorization metadata).
19
- Passes::TypeConsistencyChecker, # 10. Validates declared vs inferred type consistency.
20
- Passes::TypeChecker # 11. Validates types using inferred information.
10
+ Core::Analyzer::Passes::NameIndexer, # 1. Finds all names and checks for duplicates.
11
+ Core::Analyzer::Passes::InputCollector, # 2. Collects field metadata from input declarations.
12
+ Core::Analyzer::Passes::DeclarationValidator, # 3. Checks the basic structure of each rule.
13
+ Core::Analyzer::Passes::SemanticConstraintValidator, # 4. Validates DSL semantic constraints at AST level.
14
+ Core::Analyzer::Passes::DependencyResolver, # 5. Builds the dependency graph with conditional dependencies.
15
+ Core::Analyzer::Passes::UnsatDetector, # 6. Detects unsatisfiable constraints and analyzes cascade mutual exclusion.
16
+ Core::Analyzer::Passes::Toposorter, # 7. Creates the final evaluation order, allowing safe cycles.
17
+ Core::Analyzer::Passes::BroadcastDetector, # 8. Detects which operations should be broadcast over arrays (must run before type inference).
18
+ Core::Analyzer::Passes::TypeInferencer, # 9. Infers types for all declarations (uses vectorization metadata).
19
+ Core::Analyzer::Passes::TypeConsistencyChecker, # 10. Validates declared vs inferred type consistency.
20
+ Core::Analyzer::Passes::TypeChecker # 11. Validates types using inferred information.
21
21
  ].freeze
22
22
 
23
- def analyze!(schema, passes: DEFAULT_PASSES, **opts)
24
- state = AnalysisState.new(opts)
23
+ def self.analyze!(schema, passes: DEFAULT_PASSES, **opts)
24
+ state = Core::Analyzer::AnalysisState.new(opts)
25
25
  errors = []
26
26
 
27
27
  state = run_analysis_passes(schema, passes, state, errors)
@@ -35,7 +35,7 @@ module Kumi
35
35
  begin
36
36
  state = pass_instance.run(errors)
37
37
  rescue StandardError => e
38
- errors << ErrorReporter.create_error(e.message, location: nil, type: :semantic)
38
+ errors << Core::ErrorReporter.create_error(e.message, location: nil, type: :semantic)
39
39
  end
40
40
  end
41
41
  state
@@ -62,7 +62,7 @@ module Kumi
62
62
  end
63
63
 
64
64
  # Handle both old and new error formats for backward compatibility
65
- def format_errors(errors)
65
+ def self.format_errors(errors)
66
66
  return "" if errors.empty?
67
67
 
68
68
  errors.map(&:to_s).join("\n")
data/lib/kumi/compiler.rb CHANGED
@@ -158,7 +158,7 @@ module Kumi
158
158
  compile_declaration(decl)
159
159
  end
160
160
 
161
- CompiledSchema.new(@bindings.freeze)
161
+ Core::CompiledSchema.new(@bindings.freeze)
162
162
  end
163
163
 
164
164
  private
@@ -218,7 +218,7 @@ module Kumi
218
218
  return false unless broadcast_meta
219
219
 
220
220
  # Reduction functions are NOT vectorized operations - they consume arrays
221
- return false if FunctionRegistry.reducer?(expr.fn_name)
221
+ return false if Kumi::Registry.reducer?(expr.fn_name)
222
222
 
223
223
  expr.args.any? do |arg|
224
224
  case arg
@@ -244,7 +244,7 @@ module Kumi
244
244
  vectorized_function_call(name, values)
245
245
  else
246
246
  # All arguments are scalars - regular function call
247
- fn = FunctionRegistry.fetch(name)
247
+ fn = Kumi::Registry.fetch(name)
248
248
  fn.call(*values)
249
249
  end
250
250
  rescue StandardError => e
@@ -257,7 +257,7 @@ module Kumi
257
257
 
258
258
  def vectorized_function_call(fn_name, values)
259
259
  # Get the function from registry
260
- fn = FunctionRegistry.fetch(fn_name)
260
+ fn = Kumi::Registry.fetch(fn_name)
261
261
 
262
262
  # Find array dimensions for broadcasting
263
263
  array_values = values.select { |v| v.is_a?(Array) }
@@ -276,14 +276,14 @@ module Kumi
276
276
  end
277
277
 
278
278
  def invoke_function(name, arg_fns, ctx, loc)
279
- fn = FunctionRegistry.fetch(name)
279
+ fn = Kumi::Registry.fetch(name)
280
280
  values = arg_fns.map { |fn| fn.call(ctx) }
281
281
  fn.call(*values)
282
282
  rescue StandardError => e
283
283
  # Preserve original error class and backtrace while adding context
284
284
  enhanced_message = "Error calling fn(:#{name}) at #{loc}: #{e.message}"
285
285
 
286
- if e.is_a?(Kumi::Errors::Error)
286
+ if e.is_a?(Kumi::Core::Errors::Error)
287
287
  # Re-raise Kumi errors with enhanced message but preserve type
288
288
  e.define_singleton_method(:message) { enhanced_message }
289
289
  raise e
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ # Simple immutable state wrapper to prevent accidental mutations between passes
7
+ class AnalysisState
8
+ def initialize(data = {})
9
+ @data = data.dup.freeze
10
+ end
11
+
12
+ # Get a value (same as hash access)
13
+ def [](key)
14
+ @data[key]
15
+ end
16
+
17
+ # Check if key exists (same as hash)
18
+ def key?(key)
19
+ @data.key?(key)
20
+ end
21
+
22
+ # Get all keys (same as hash)
23
+ def keys
24
+ @data.keys
25
+ end
26
+
27
+ # Create new state with additional data (simple and clean)
28
+ def with(key, value)
29
+ AnalysisState.new(@data.merge(key => value))
30
+ end
31
+
32
+ # Convert back to hash for final result
33
+ def to_h
34
+ @data.dup
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ class ConstantEvaluator
7
+ include Syntax
8
+
9
+ def initialize(definitions)
10
+ @definitions = definitions
11
+ @memo = {}
12
+ end
13
+
14
+ OPERATORS = {
15
+ add: :+,
16
+ subtract: :-,
17
+ multiply: :*,
18
+ divide: :/
19
+ }.freeze
20
+
21
+ def evaluate(node, visited = Set.new)
22
+ return :unknown unless node
23
+ return @memo[node] if @memo.key?(node)
24
+ return node.value if node.is_a?(Literal)
25
+
26
+ result = case node
27
+ when DeclarationReference then evaluate_binding(node, visited)
28
+ when CallExpression then evaluate_call_expression(node, visited)
29
+ else :unknown
30
+ end
31
+
32
+ @memo[node] = result unless result == :unknown
33
+ result
34
+ end
35
+
36
+ private
37
+
38
+ def evaluate_binding(node, visited)
39
+ return :unknown if visited.include?(node.name)
40
+
41
+ visited << node.name
42
+ definition = @definitions[node.name]
43
+ return :unknown unless definition
44
+
45
+ evaluate(definition.expression, visited)
46
+ end
47
+
48
+ def evaluate_call_expression(node, visited)
49
+ return :unknown unless OPERATORS.key?(node.fn_name)
50
+
51
+ args = node.args.map { |arg| evaluate(arg, visited) }
52
+ return :unknown if args.any?(:unknown)
53
+
54
+ args.reduce(OPERATORS[node.fn_name])
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ module Passes
7
+ # Detects which operations should be broadcast over arrays
8
+ # DEPENDENCIES: :inputs, :declarations
9
+ # PRODUCES: :broadcasts
10
+ class BroadcastDetector < PassBase
11
+ def run(errors)
12
+ input_meta = get_state(:inputs) || {}
13
+ definitions = get_state(:declarations) || {}
14
+
15
+ # Find array fields with their element types
16
+ array_fields = find_array_fields(input_meta)
17
+
18
+ # Build compiler metadata
19
+ compiler_metadata = {
20
+ array_fields: array_fields,
21
+ vectorized_operations: {},
22
+ reduction_operations: {}
23
+ }
24
+
25
+ # Track which values are vectorized for type inference
26
+ vectorized_values = {}
27
+
28
+ # Analyze traits first, then values (to handle dependencies)
29
+ traits = definitions.select { |_name, decl| decl.is_a?(Kumi::Syntax::TraitDeclaration) }
30
+ values = definitions.select { |_name, decl| decl.is_a?(Kumi::Syntax::ValueDeclaration) }
31
+
32
+ (traits.to_a + values.to_a).each do |name, decl|
33
+ result = analyze_value_vectorization(name, decl.expression, array_fields, vectorized_values, errors)
34
+
35
+ case result[:type]
36
+ when :vectorized
37
+ compiler_metadata[:vectorized_operations][name] = result[:info]
38
+ # Store array source information for dimension checking
39
+ array_source = extract_array_source(result[:info], array_fields)
40
+ vectorized_values[name] = { vectorized: true, array_source: array_source }
41
+ when :reduction
42
+ compiler_metadata[:reduction_operations][name] = result[:info]
43
+ # Reduction produces scalar, not vectorized
44
+ vectorized_values[name] = { vectorized: false }
45
+ end
46
+ end
47
+
48
+ state.with(:broadcasts, compiler_metadata.freeze)
49
+ end
50
+
51
+ private
52
+
53
+ def find_array_fields(input_meta)
54
+ result = {}
55
+ input_meta.each do |name, meta|
56
+ next unless meta[:type] == :array && meta[:children]
57
+
58
+ result[name] = {
59
+ element_fields: meta[:children].keys,
60
+ element_types: meta[:children].transform_values { |v| v[:type] || :any }
61
+ }
62
+ end
63
+ result
64
+ end
65
+
66
+ def analyze_value_vectorization(name, expr, array_fields, vectorized_values, errors)
67
+ case expr
68
+ when Kumi::Syntax::InputElementReference
69
+ if array_fields.key?(expr.path.first)
70
+ { type: :vectorized, info: { source: :array_field_access, path: expr.path } }
71
+ else
72
+ { type: :scalar }
73
+ end
74
+
75
+ when Kumi::Syntax::DeclarationReference
76
+ # Check if this references a vectorized value
77
+ vector_info = vectorized_values[expr.name]
78
+ if vector_info && vector_info[:vectorized]
79
+ { type: :vectorized, info: { source: :vectorized_declaration, name: expr.name } }
80
+ else
81
+ { type: :scalar }
82
+ end
83
+
84
+ when Kumi::Syntax::CallExpression
85
+ analyze_call_vectorization(name, expr, array_fields, vectorized_values, errors)
86
+
87
+ when Kumi::Syntax::CascadeExpression
88
+ analyze_cascade_vectorization(name, expr, array_fields, vectorized_values, errors)
89
+
90
+ else
91
+ { type: :scalar }
92
+ end
93
+ end
94
+
95
+ def analyze_call_vectorization(_name, expr, array_fields, vectorized_values, errors)
96
+ # Check if this is a reduction function using function registry metadata
97
+ if Kumi::Registry.reducer?(expr.fn_name)
98
+ # Only treat as reduction if the argument is actually vectorized
99
+ arg_info = analyze_argument_vectorization(expr.args.first, array_fields, vectorized_values)
100
+ if arg_info[:vectorized]
101
+ { type: :reduction, info: { function: expr.fn_name, source: arg_info[:source] } }
102
+ else
103
+ # Not a vectorized reduction - just a regular function call
104
+ { type: :scalar }
105
+ end
106
+
107
+ else
108
+ # Special case: all?, any?, none? functions with vectorized trait arguments should be treated as vectorized
109
+ # for cascade condition purposes (they get transformed during compilation)
110
+ if %i[all? any? none?].include?(expr.fn_name) && expr.args.length == 1
111
+ arg = expr.args.first
112
+ if arg.is_a?(Kumi::Syntax::ArrayExpression) && arg.elements.length == 1
113
+ trait_ref = arg.elements.first
114
+ if trait_ref.is_a?(Kumi::Syntax::DeclarationReference) && vectorized_values[trait_ref.name]&.[](:vectorized)
115
+ return { type: :vectorized, info: { source: :cascade_condition_with_vectorized_trait, trait: trait_ref.name } }
116
+ end
117
+ end
118
+ end
119
+
120
+ # ANY function with vectorized arguments becomes vectorized (with broadcasting)
121
+ arg_infos = expr.args.map { |arg| analyze_argument_vectorization(arg, array_fields, vectorized_values) }
122
+
123
+ if arg_infos.any? { |info| info[:vectorized] }
124
+ # Check for dimension mismatches when multiple arguments are vectorized
125
+ vectorized_sources = arg_infos.select { |info| info[:vectorized] }.filter_map { |info| info[:array_source] }.uniq
126
+
127
+ if vectorized_sources.length > 1
128
+ # Multiple different array sources - this is a dimension mismatch
129
+ # Generate enhanced error message with type information
130
+ enhanced_message = build_dimension_mismatch_error(expr, arg_infos, array_fields, vectorized_sources)
131
+
132
+ report_error(errors, enhanced_message, location: expr.loc, type: :semantic)
133
+ return { type: :scalar } # Treat as scalar to prevent further errors
134
+ end
135
+
136
+ # This is a vectorized operation - ANY function supports broadcasting
137
+ { type: :vectorized, info: {
138
+ operation: expr.fn_name,
139
+ vectorized_args: arg_infos.map.with_index { |info, i| [i, info[:vectorized]] }.to_h
140
+ } }
141
+ else
142
+ { type: :scalar }
143
+ end
144
+ end
145
+ end
146
+
147
+ def analyze_argument_vectorization(arg, array_fields, vectorized_values)
148
+ case arg
149
+ when Kumi::Syntax::InputElementReference
150
+ if array_fields.key?(arg.path.first)
151
+ { vectorized: true, source: :array_field, array_source: arg.path.first }
152
+ else
153
+ { vectorized: false }
154
+ end
155
+
156
+ when Kumi::Syntax::DeclarationReference
157
+ # Check if this references a vectorized value
158
+ vector_info = vectorized_values[arg.name]
159
+ if vector_info && vector_info[:vectorized]
160
+ array_source = vector_info[:array_source]
161
+ { vectorized: true, source: :vectorized_value, array_source: array_source }
162
+ else
163
+ { vectorized: false }
164
+ end
165
+
166
+ when Kumi::Syntax::CallExpression
167
+ # Recursively check
168
+ result = analyze_value_vectorization(nil, arg, array_fields, vectorized_values, [])
169
+ { vectorized: result[:type] == :vectorized, source: :expression }
170
+
171
+ else
172
+ { vectorized: false }
173
+ end
174
+ end
175
+
176
+ def extract_array_source(info, _array_fields)
177
+ case info[:source]
178
+ when :array_field_access
179
+ info[:path]&.first
180
+ when :cascade_condition_with_vectorized_trait
181
+ # For cascades, we'd need to trace back to the original source
182
+ nil # TODO: Could be enhanced to trace through trait dependencies
183
+ end
184
+ end
185
+
186
+ def analyze_cascade_vectorization(_name, expr, array_fields, vectorized_values, errors)
187
+ # A cascade is vectorized if:
188
+ # 1. Any of its result expressions are vectorized, OR
189
+ # 2. Any of its conditions reference vectorized values (traits or arrays)
190
+ vectorized_results = []
191
+ vectorized_conditions = []
192
+
193
+ expr.cases.each do |case_expr|
194
+ # Check if result is vectorized
195
+ result_info = analyze_value_vectorization(nil, case_expr.result, array_fields, vectorized_values, errors)
196
+ vectorized_results << (result_info[:type] == :vectorized)
197
+
198
+ # Check if condition is vectorized
199
+ condition_info = analyze_value_vectorization(nil, case_expr.condition, array_fields, vectorized_values, errors)
200
+ vectorized_conditions << (condition_info[:type] == :vectorized)
201
+ end
202
+
203
+ if vectorized_results.any? || vectorized_conditions.any?
204
+ { type: :vectorized, info: { source: :cascade_with_vectorized_conditions_or_results } }
205
+ else
206
+ { type: :scalar }
207
+ end
208
+ end
209
+
210
+ def build_dimension_mismatch_error(_expr, arg_infos, array_fields, vectorized_sources)
211
+ # Build detailed error message with type information
212
+ summary = "Cannot broadcast operation across arrays from different sources: #{vectorized_sources.join(', ')}. "
213
+
214
+ problem_desc = "Problem: Multiple operands are arrays from different sources:\n"
215
+
216
+ vectorized_args = arg_infos.select { |info| info[:vectorized] }
217
+ vectorized_args.each_with_index do |arg_info, index|
218
+ array_source = arg_info[:array_source]
219
+ next unless array_source && array_fields[array_source]
220
+
221
+ # Determine the type based on array field metadata
222
+ type_desc = determine_array_type(array_source, array_fields)
223
+ problem_desc += " - Operand #{index + 1} resolves to #{type_desc} from array '#{array_source}'\n"
224
+ end
225
+
226
+ explanation = "Direct operations on arrays from different sources is ambiguous and not supported. " \
227
+ "Vectorized operations can only work on fields from the same array input."
228
+
229
+ "#{summary}#{problem_desc}#{explanation}"
230
+ end
231
+
232
+ def determine_array_type(array_source, array_fields)
233
+ field_info = array_fields[array_source]
234
+ return "array(any)" unless field_info[:element_types]
235
+
236
+ # For nested arrays (like items.name where items is an array), this represents array(element_type)
237
+ element_types = field_info[:element_types].values.uniq
238
+ if element_types.length == 1
239
+ "array(#{element_types.first})"
240
+ else
241
+ "array(mixed)"
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ module Passes
7
+ # RESPONSIBILITY: Perform local structural validation on each declaration
8
+ # DEPENDENCIES: :definitions
9
+ # PRODUCES: None (validation only)
10
+ # INTERFACE: new(schema, state).run(errors)
11
+ class DeclarationValidator < VisitorPass
12
+ def run(errors)
13
+ each_decl do |decl|
14
+ visit(decl) { |node| validate_node(node, errors) }
15
+ end
16
+ state
17
+ end
18
+
19
+ private
20
+
21
+ def validate_node(node, errors)
22
+ case node
23
+ when Kumi::Syntax::ValueDeclaration
24
+ validate_attribute(node, errors)
25
+ when Kumi::Syntax::TraitDeclaration
26
+ validate_trait(node, errors)
27
+ end
28
+ end
29
+
30
+ def validate_attribute(node, errors)
31
+ return unless node.expression.nil?
32
+
33
+ report_error(errors, "attribute `#{node.name}` requires an expression", location: node.loc)
34
+ end
35
+
36
+ def validate_trait(node, errors)
37
+ return if node.expression.is_a?(Kumi::Syntax::CallExpression)
38
+
39
+ report_error(errors, "trait `#{node.name}` must wrap a CallExpression", location: node.loc)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end