kumi 0.0.6 → 0.0.7

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +33 -176
  3. data/README.md +33 -2
  4. data/docs/SYNTAX.md +2 -7
  5. data/docs/features/array-broadcasting.md +1 -1
  6. data/docs/schema_metadata/broadcasts.md +53 -0
  7. data/docs/schema_metadata/cascades.md +45 -0
  8. data/docs/schema_metadata/declarations.md +54 -0
  9. data/docs/schema_metadata/dependencies.md +57 -0
  10. data/docs/schema_metadata/evaluation_order.md +29 -0
  11. data/docs/schema_metadata/examples.md +95 -0
  12. data/docs/schema_metadata/inferred_types.md +46 -0
  13. data/docs/schema_metadata/inputs.md +86 -0
  14. data/docs/schema_metadata.md +108 -0
  15. data/lib/kumi/analyzer/passes/broadcast_detector.rb +52 -57
  16. data/lib/kumi/analyzer/passes/dependency_resolver.rb +8 -8
  17. data/lib/kumi/analyzer/passes/input_collector.rb +2 -2
  18. data/lib/kumi/analyzer/passes/name_indexer.rb +2 -2
  19. data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +15 -16
  20. data/lib/kumi/analyzer/passes/toposorter.rb +23 -23
  21. data/lib/kumi/analyzer/passes/type_checker.rb +7 -9
  22. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +2 -2
  23. data/lib/kumi/analyzer/passes/type_inferencer.rb +24 -24
  24. data/lib/kumi/analyzer/passes/unsat_detector.rb +11 -13
  25. data/lib/kumi/analyzer.rb +5 -5
  26. data/lib/kumi/compiler.rb +39 -45
  27. data/lib/kumi/error_reporting.rb +1 -1
  28. data/lib/kumi/explain.rb +12 -0
  29. data/lib/kumi/export/node_registry.rb +2 -2
  30. data/lib/kumi/json_schema/generator.rb +63 -0
  31. data/lib/kumi/json_schema/validator.rb +25 -0
  32. data/lib/kumi/json_schema.rb +14 -0
  33. data/lib/kumi/{parser → ruby_parser}/build_context.rb +1 -1
  34. data/lib/kumi/{parser → ruby_parser}/declaration_reference_proxy.rb +3 -3
  35. data/lib/kumi/{parser → ruby_parser}/dsl.rb +1 -1
  36. data/lib/kumi/{parser → ruby_parser}/dsl_cascade_builder.rb +2 -2
  37. data/lib/kumi/{parser → ruby_parser}/expression_converter.rb +14 -14
  38. data/lib/kumi/{parser → ruby_parser}/guard_rails.rb +1 -1
  39. data/lib/kumi/{parser → ruby_parser}/input_builder.rb +1 -1
  40. data/lib/kumi/{parser → ruby_parser}/input_field_proxy.rb +4 -4
  41. data/lib/kumi/{parser → ruby_parser}/input_proxy.rb +1 -1
  42. data/lib/kumi/{parser → ruby_parser}/nested_input.rb +1 -1
  43. data/lib/kumi/{parser → ruby_parser}/parser.rb +11 -10
  44. data/lib/kumi/{parser → ruby_parser}/schema_builder.rb +1 -1
  45. data/lib/kumi/{parser → ruby_parser}/sugar.rb +1 -1
  46. data/lib/kumi/ruby_parser.rb +10 -0
  47. data/lib/kumi/schema.rb +10 -4
  48. data/lib/kumi/schema_instance.rb +6 -6
  49. data/lib/kumi/schema_metadata.rb +524 -0
  50. data/lib/kumi/vectorization_metadata.rb +4 -4
  51. data/lib/kumi/version.rb +1 -1
  52. data/lib/kumi.rb +14 -0
  53. metadata +28 -15
  54. data/lib/generators/trait_engine/templates/schema_spec.rb.erb +0 -27
@@ -0,0 +1,95 @@
1
+ # Schema Metadata Examples
2
+
3
+ For comprehensive API documentation with detailed examples, see the YARD documentation in the SchemaMetadata class.
4
+
5
+ ## Basic Usage
6
+
7
+ ```ruby
8
+ class TaxSchema
9
+ extend Kumi::Schema
10
+
11
+ schema do
12
+ input do
13
+ integer :income, domain: 0..1_000_000
14
+ string :filing_status, domain: %w[single married]
15
+ integer :age, domain: 18..100
16
+ end
17
+
18
+ trait :adult, (input.age >= 18)
19
+ trait :high_income, (input.income > 100_000)
20
+
21
+ value :tax_rate do
22
+ on high_income, 0.25
23
+ base 0.15
24
+ end
25
+
26
+ value :tax_amount, input.income * tax_rate
27
+ end
28
+ end
29
+
30
+ # Access schema metadata - clean object interface!
31
+ metadata = TaxSchema.schema_metadata
32
+
33
+ # Processed semantic metadata (rich, transformed from AST)
34
+ puts metadata.inputs
35
+ # => { :income => { type: :integer, domain: {...}, required: true }, ... }
36
+
37
+ puts metadata.values
38
+ # => { :tax_rate => { type: :float, cascade: {...} }, ... }
39
+
40
+ puts metadata.traits
41
+ # => { :adult => { type: :boolean, condition: "input.age >= 18" }, ... }
42
+
43
+ # Raw analyzer state (direct from analysis passes)
44
+ puts metadata.evaluation_order
45
+ # => [:adult, :high_income, :tax_rate, :tax_amount]
46
+
47
+ puts metadata.dependencies
48
+ # => { :tax_amount => [#<Edge to: :tax_rate>, #<Edge to: :income>], ... }
49
+
50
+ puts metadata.inferred_types
51
+ # => { :adult => :boolean, :tax_rate => :float, :tax_amount => :float }
52
+
53
+ # Serializable processed hash
54
+ processed_hash = metadata.to_h
55
+ puts processed_hash.keys
56
+ # => [:inputs, :values, :traits, :functions]
57
+
58
+ # Raw analyzer state (contains AST nodes)
59
+ raw_state = metadata.analyzer_state
60
+ puts raw_state.keys
61
+ # => [:declarations, :inputs, :dependencies, :dependents, :leaves, :evaluation_order, :inferred_types, :cascades, :broadcasts]
62
+ ```
63
+
64
+ ## Tool Integration
65
+
66
+ ```ruby
67
+ # Form generator example
68
+ def generate_form_fields(schema_class)
69
+ metadata = schema_class.schema_metadata
70
+
71
+ metadata.inputs.map do |field_name, field_info|
72
+ case field_info[:type]
73
+ when :integer
74
+ create_number_input(field_name, field_info[:domain])
75
+ when :string
76
+ create_select_input(field_name, field_info[:domain])
77
+ when :boolean
78
+ create_checkbox_input(field_name)
79
+ end
80
+ end
81
+ end
82
+
83
+ # Dependency analysis example
84
+ def analyze_field_dependencies(schema_class, field_name)
85
+ metadata = schema_class.schema_metadata
86
+
87
+ # Find what depends on this field
88
+ dependents = metadata.dependents[field_name] || []
89
+
90
+ # Find what this field depends on
91
+ dependencies = metadata.dependencies[field_name]&.map(&:to) || []
92
+
93
+ { affects: dependents, requires: dependencies }
94
+ end
95
+ ```
@@ -0,0 +1,46 @@
1
+ # Inferred Types Metadata
2
+
3
+ Type inference results for all declarations based on expression analysis.
4
+
5
+ ## Access
6
+
7
+ ```ruby
8
+ metadata = MySchema.schema_metadata
9
+ types = metadata.inferred_types
10
+ ```
11
+
12
+ ## Structure
13
+
14
+ ```ruby
15
+ # Returns Hash<Symbol, Object>
16
+ {
17
+ declaration_name => type_specification
18
+ }
19
+ ```
20
+
21
+ ## Example
22
+
23
+ ```ruby
24
+ metadata.inferred_types
25
+ # => {
26
+ # :adult => :boolean,
27
+ # :age_group => :string,
28
+ # :tax_rate => :float,
29
+ # :count => :integer,
30
+ # :item_prices => { array: :float },
31
+ # :categories => { array: :string }
32
+ # }
33
+ ```
34
+
35
+ ## Type Values
36
+
37
+ - `:boolean`, `:string`, `:integer`, `:float`, `:any`
38
+ - `{ array: element_type }` for arrays
39
+ - `{ hash: { key: key_type, value: value_type } }` for hashes
40
+
41
+ ## Usage
42
+
43
+ - Type checking
44
+ - Code generation
45
+ - Editor support
46
+ - Runtime validation
@@ -0,0 +1,86 @@
1
+ # Input Metadata
2
+
3
+ Raw input field metadata extracted from `input` blocks during analysis.
4
+
5
+ ## Access
6
+
7
+ ```ruby
8
+ metadata = MySchema.schema_metadata
9
+
10
+ # Processed input metadata (recommended for tools)
11
+ inputs = metadata.inputs
12
+
13
+ # Raw input metadata (advanced usage)
14
+ raw_inputs = metadata.analyzer_state[:inputs]
15
+ ```
16
+
17
+ ## Raw Structure
18
+
19
+ ```ruby
20
+ # Raw analyzer state format
21
+ {
22
+ field_name => {
23
+ type: Symbol, # :integer, :string, :float, :boolean, :array, etc.
24
+ domain: Range|Array, # optional domain constraints
25
+ children: Hash # for array/hash types
26
+ }
27
+ }
28
+ ```
29
+
30
+ ## Processed Structure
31
+
32
+ ```ruby
33
+ # Processed metadata format (via metadata.inputs)
34
+ {
35
+ field_name => {
36
+ type: Symbol, # normalized type
37
+ domain: Hash, # normalized domain metadata
38
+ required: Boolean # always true currently
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Examples
44
+
45
+ **Processed Input Metadata:**
46
+ ```ruby
47
+ metadata.inputs
48
+ # => {
49
+ # :age => {
50
+ # type: :integer,
51
+ # domain: { type: :range, min: 0, max: 120, exclusive_end: false },
52
+ # required: true
53
+ # },
54
+ # :name => { type: :string, required: true },
55
+ # :active => { type: :boolean, required: true }
56
+ # }
57
+ ```
58
+
59
+ **Raw Input Metadata:**
60
+ ```ruby
61
+ metadata.analyzer_state[:inputs]
62
+ # => {
63
+ # :age => { type: :integer, domain: 0..120 },
64
+ # :name => { type: :string },
65
+ # :line_items => {
66
+ # type: :array,
67
+ # children: {
68
+ # :price => { type: :float, domain: 0..Float::INFINITY },
69
+ # :quantity => { type: :integer, domain: 1..100 }
70
+ # }
71
+ # }
72
+ # }
73
+ ```
74
+
75
+ **Domain Types:**
76
+ - Range: `18..65`, `0..Float::INFINITY`
77
+ - Array: `%w[active inactive suspended]`
78
+ - Proc: Custom validation functions
79
+
80
+ ## Usage
81
+
82
+ Form generators use this metadata to:
83
+ - Create appropriate input controls
84
+ - Set validation rules
85
+ - Build nested forms for arrays
86
+ - Generate type-safe schemas
@@ -0,0 +1,108 @@
1
+ # Schema Metadata
2
+
3
+ Kumi's SchemaMetadata interface provides structured access to analyzed schema information for building external tools like form generators, documentation systems, and analysis utilities.
4
+
5
+ ## Primary Interface
6
+
7
+ SchemaMetadata is the main interface for extracting metadata from Kumi schemas:
8
+
9
+ ```ruby
10
+ metadata = MySchema.schema_metadata
11
+ ```
12
+
13
+ See the comprehensive API documentation in the SchemaMetadata class for detailed method documentation, examples, and usage patterns.
14
+
15
+ ## Processed Metadata (Tool-Friendly)
16
+
17
+ These methods return clean, serializable data structures:
18
+
19
+ | Method | Returns | Description |
20
+ |--------|---------|-------------|
21
+ | `inputs` | Hash | Input field metadata with normalized types and domains |
22
+ | `values` | Hash | Value declarations with dependencies and expressions |
23
+ | `traits` | Hash | Trait conditions with dependency information |
24
+ | `functions` | Hash | Function registry info for functions used in schema |
25
+ | `to_h` | Hash | Complete processed metadata (inputs, values, traits, functions) |
26
+ | `to_json` | String | JSON serialization of processed metadata |
27
+ | `to_json_schema` | Hash | JSON Schema document for input validation |
28
+
29
+ ## Raw Analyzer State (Advanced)
30
+
31
+ Direct access to internal analyzer results:
32
+
33
+ | Method | Returns | Description |
34
+ |--------|---------|-------------|
35
+ | [`declarations`](schema_metadata/declarations.md) | Hash | Raw AST declaration nodes by name |
36
+ | [`dependencies`](schema_metadata/dependencies.md) | Hash | Dependency graph with Edge objects |
37
+ | `dependents` | Hash | Reverse dependency lookup |
38
+ | `leaves` | Hash | Leaf nodes (no dependencies) by type |
39
+ | [`evaluation_order`](schema_metadata/evaluation_order.md) | Array | Topologically sorted evaluation order |
40
+ | [`inferred_types`](schema_metadata/inferred_types.md) | Hash | Type inference results for declarations |
41
+ | [`cascades`](schema_metadata/cascades.md) | Hash | Cascade mutual exclusion analysis |
42
+ | [`broadcasts`](schema_metadata/broadcasts.md) | Hash | Array broadcasting operation metadata |
43
+ | `analyzer_state` | Hash | Complete raw analyzer state with AST nodes |
44
+
45
+ Note: Raw `inputs` metadata is available via `analyzer_state[:inputs]` but the processed `inputs` method is recommended for tool development.
46
+
47
+ ## Usage Patterns
48
+
49
+ ```ruby
50
+ # Tool development - use processed metadata
51
+ metadata = MySchema.schema_metadata
52
+ form_fields = metadata.inputs.map { |name, info| create_field(name, info) }
53
+ documentation = metadata.values.map { |name, info| document_value(name, info) }
54
+
55
+ # Advanced analysis - use raw state when needed
56
+ dependency_graph = metadata.dependencies
57
+ ast_nodes = metadata.declarations
58
+ evaluation_sequence = metadata.evaluation_order
59
+ ```
60
+
61
+ ## Data Structure Examples
62
+
63
+ ### Processed Input Metadata
64
+ ```ruby
65
+ metadata.inputs
66
+ # => {
67
+ # :age => { type: :integer, domain: { type: :range, min: 18, max: 65 }, required: true },
68
+ # :name => { type: :string, required: true },
69
+ # :items => { type: :array, required: true }
70
+ # }
71
+ ```
72
+
73
+ ### Processed Value Metadata
74
+ ```ruby
75
+ metadata.values
76
+ # => {
77
+ # :tax_amount => {
78
+ # type: :float,
79
+ # dependencies: [:income, :tax_rate],
80
+ # computed: true,
81
+ # expression: "multiply(input.income, tax_rate)"
82
+ # }
83
+ # }
84
+ ```
85
+
86
+ ### Clean Public Interface Examples
87
+ ```ruby
88
+ # Processed dependency information (clean hashes)
89
+ metadata.dependencies
90
+ # => { :tax_amount => [{ to: :income, conditional: false }, { to: :tax_rate, conditional: false }] }
91
+
92
+ # Processed declaration metadata (clean hashes)
93
+ metadata.declarations
94
+ # => { :adult => { type: :trait, expression: ">=(input.age, 18)" }, :tax_amount => { type: :value, expression: "multiply(input.income, tax_rate)" } }
95
+
96
+ # Type inference results (clean data)
97
+ metadata.inferred_types
98
+ # => { :adult => :boolean, :tax_amount => :float, :item_totals => { array: :float } }
99
+ ```
100
+
101
+ ### Raw Analyzer State (Advanced Usage)
102
+ ```ruby
103
+ # Complete raw state hash with internal objects (AST nodes, Edge objects)
104
+ metadata.analyzer_state
105
+ # => { declarations: {AST nodes...}, dependencies: {Edge objects...}, ... }
106
+ ```
107
+
108
+ See `docs/schema_metadata/` for detailed examples.
@@ -4,34 +4,33 @@ module Kumi
4
4
  module Analyzer
5
5
  module Passes
6
6
  # Detects which operations should be broadcast over arrays
7
- # DEPENDENCIES: :input_meta, :definitions
8
- # PRODUCES: :broadcast_metadata
7
+ # DEPENDENCIES: :inputs, :declarations
8
+ # PRODUCES: :broadcasts
9
9
  class BroadcastDetector < PassBase
10
10
  def run(errors)
11
- input_meta = get_state(:input_meta) || {}
12
- definitions = get_state(:definitions) || {}
13
-
11
+ input_meta = get_state(:inputs) || {}
12
+ definitions = get_state(:declarations) || {}
13
+
14
14
  # Find array fields with their element types
15
15
  array_fields = find_array_fields(input_meta)
16
-
16
+
17
17
  # Build compiler metadata
18
18
  compiler_metadata = {
19
19
  array_fields: array_fields,
20
20
  vectorized_operations: {},
21
21
  reduction_operations: {}
22
22
  }
23
-
23
+
24
24
  # Track which values are vectorized for type inference
25
25
  vectorized_values = {}
26
-
26
+
27
27
  # Analyze traits first, then values (to handle dependencies)
28
- traits = definitions.select { |name, decl| decl.is_a?(Kumi::Syntax::TraitDeclaration) }
29
- values = definitions.select { |name, decl| decl.is_a?(Kumi::Syntax::ValueDeclaration) }
30
-
28
+ traits = definitions.select { |_name, decl| decl.is_a?(Kumi::Syntax::TraitDeclaration) }
29
+ values = definitions.select { |_name, decl| decl.is_a?(Kumi::Syntax::ValueDeclaration) }
30
+
31
31
  (traits.to_a + values.to_a).each do |name, decl|
32
32
  result = analyze_value_vectorization(name, decl.expression, array_fields, vectorized_values, errors)
33
-
34
-
33
+
35
34
  case result[:type]
36
35
  when :vectorized
37
36
  compiler_metadata[:vectorized_operations][name] = result[:info]
@@ -44,8 +43,8 @@ module Kumi
44
43
  vectorized_values[name] = { vectorized: false }
45
44
  end
46
45
  end
47
-
48
- state.with(:broadcast_metadata, compiler_metadata.freeze)
46
+
47
+ state.with(:broadcasts, compiler_metadata.freeze)
49
48
  end
50
49
 
51
50
  private
@@ -53,12 +52,12 @@ module Kumi
53
52
  def find_array_fields(input_meta)
54
53
  result = {}
55
54
  input_meta.each do |name, meta|
56
- if meta[:type] == :array && meta[:children]
57
- result[name] = {
58
- element_fields: meta[:children].keys,
59
- element_types: meta[:children].transform_values { |v| v[:type] || :any }
60
- }
61
- end
55
+ next unless meta[:type] == :array && meta[:children]
56
+
57
+ result[name] = {
58
+ element_fields: meta[:children].keys,
59
+ element_types: meta[:children].transform_values { |v| v[:type] || :any }
60
+ }
62
61
  end
63
62
  result
64
63
  end
@@ -71,7 +70,7 @@ module Kumi
71
70
  else
72
71
  { type: :scalar }
73
72
  end
74
-
73
+
75
74
  when Kumi::Syntax::DeclarationReference
76
75
  # Check if this references a vectorized value
77
76
  vector_info = vectorized_values[expr.name]
@@ -80,19 +79,19 @@ module Kumi
80
79
  else
81
80
  { type: :scalar }
82
81
  end
83
-
82
+
84
83
  when Kumi::Syntax::CallExpression
85
84
  analyze_call_vectorization(name, expr, array_fields, vectorized_values, errors)
86
-
85
+
87
86
  when Kumi::Syntax::CascadeExpression
88
87
  analyze_cascade_vectorization(name, expr, array_fields, vectorized_values, errors)
89
-
88
+
90
89
  else
91
90
  { type: :scalar }
92
91
  end
93
92
  end
94
93
 
95
- def analyze_call_vectorization(name, expr, array_fields, vectorized_values, errors)
94
+ def analyze_call_vectorization(_name, expr, array_fields, vectorized_values, errors)
96
95
  # Check if this is a reduction function using function registry metadata
97
96
  if FunctionRegistry.reducer?(expr.fn_name)
98
97
  # Only treat as reduction if the argument is actually vectorized
@@ -103,11 +102,11 @@ module Kumi
103
102
  # Not a vectorized reduction - just a regular function call
104
103
  { type: :scalar }
105
104
  end
106
-
105
+
107
106
  else
108
107
  # Special case: all?, any?, none? functions with vectorized trait arguments should be treated as vectorized
109
108
  # for cascade condition purposes (they get transformed during compilation)
110
- if [:all?, :any?, :none?].include?(expr.fn_name) && expr.args.length == 1
109
+ if %i[all? any? none?].include?(expr.fn_name) && expr.args.length == 1
111
110
  arg = expr.args.first
112
111
  if arg.is_a?(Kumi::Syntax::ArrayExpression) && arg.elements.length == 1
113
112
  trait_ref = arg.elements.first
@@ -116,28 +115,28 @@ module Kumi
116
115
  end
117
116
  end
118
117
  end
119
-
118
+
120
119
  # ANY function with vectorized arguments becomes vectorized (with broadcasting)
121
120
  arg_infos = expr.args.map { |arg| analyze_argument_vectorization(arg, array_fields, vectorized_values) }
122
-
121
+
123
122
  if arg_infos.any? { |info| info[:vectorized] }
124
123
  # Check for dimension mismatches when multiple arguments are vectorized
125
- vectorized_sources = arg_infos.select { |info| info[:vectorized] }.map { |info| info[:array_source] }.compact.uniq
126
-
124
+ vectorized_sources = arg_infos.select { |info| info[:vectorized] }.filter_map { |info| info[:array_source] }.uniq
125
+
127
126
  if vectorized_sources.length > 1
128
127
  # Multiple different array sources - this is a dimension mismatch
129
128
  # Generate enhanced error message with type information
130
129
  enhanced_message = build_dimension_mismatch_error(expr, arg_infos, array_fields, vectorized_sources)
131
-
130
+
132
131
  report_error(errors, enhanced_message, location: expr.loc, type: :semantic)
133
- return { type: :scalar } # Treat as scalar to prevent further errors
132
+ return { type: :scalar } # Treat as scalar to prevent further errors
134
133
  end
135
-
134
+
136
135
  # 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
- }}
136
+ { type: :vectorized, info: {
137
+ operation: expr.fn_name,
138
+ vectorized_args: arg_infos.map.with_index { |info, i| [i, info[:vectorized]] }.to_h
139
+ } }
141
140
  else
142
141
  { type: :scalar }
143
142
  end
@@ -152,7 +151,7 @@ module Kumi
152
151
  else
153
152
  { vectorized: false }
154
153
  end
155
-
154
+
156
155
  when Kumi::Syntax::DeclarationReference
157
156
  # Check if this references a vectorized value
158
157
  vector_info = vectorized_values[arg.name]
@@ -162,47 +161,44 @@ module Kumi
162
161
  else
163
162
  { vectorized: false }
164
163
  end
165
-
164
+
166
165
  when Kumi::Syntax::CallExpression
167
166
  # Recursively check
168
167
  result = analyze_value_vectorization(nil, arg, array_fields, vectorized_values, [])
169
168
  { vectorized: result[:type] == :vectorized, source: :expression }
170
-
169
+
171
170
  else
172
171
  { vectorized: false }
173
172
  end
174
173
  end
175
174
 
176
- def extract_array_source(info, array_fields)
175
+ def extract_array_source(info, _array_fields)
177
176
  case info[:source]
178
177
  when :array_field_access
179
178
  info[:path]&.first
180
179
  when :cascade_condition_with_vectorized_trait
181
180
  # For cascades, we'd need to trace back to the original source
182
- nil # TODO: Could be enhanced to trace through trait dependencies
183
- else
184
- nil
181
+ nil # TODO: Could be enhanced to trace through trait dependencies
185
182
  end
186
183
  end
187
184
 
188
- def analyze_cascade_vectorization(name, expr, array_fields, vectorized_values, errors)
185
+ def analyze_cascade_vectorization(_name, expr, array_fields, vectorized_values, errors)
189
186
  # A cascade is vectorized if:
190
187
  # 1. Any of its result expressions are vectorized, OR
191
188
  # 2. Any of its conditions reference vectorized values (traits or arrays)
192
189
  vectorized_results = []
193
190
  vectorized_conditions = []
194
-
191
+
195
192
  expr.cases.each do |case_expr|
196
193
  # Check if result is vectorized
197
194
  result_info = analyze_value_vectorization(nil, case_expr.result, array_fields, vectorized_values, errors)
198
195
  vectorized_results << (result_info[:type] == :vectorized)
199
-
196
+
200
197
  # Check if condition is vectorized
201
198
  condition_info = analyze_value_vectorization(nil, case_expr.condition, array_fields, vectorized_values, errors)
202
199
  vectorized_conditions << (condition_info[:type] == :vectorized)
203
-
204
200
  end
205
-
201
+
206
202
  if vectorized_results.any? || vectorized_conditions.any?
207
203
  { type: :vectorized, info: { source: :cascade_with_vectorized_conditions_or_results } }
208
204
  else
@@ -213,9 +209,9 @@ module Kumi
213
209
  def build_dimension_mismatch_error(_expr, arg_infos, array_fields, vectorized_sources)
214
210
  # Build detailed error message with type information
215
211
  summary = "Cannot broadcast operation across arrays from different sources: #{vectorized_sources.join(', ')}. "
216
-
212
+
217
213
  problem_desc = "Problem: Multiple operands are arrays from different sources:\n"
218
-
214
+
219
215
  vectorized_args = arg_infos.select { |info| info[:vectorized] }
220
216
  vectorized_args.each_with_index do |arg_info, index|
221
217
  array_source = arg_info[:array_source]
@@ -225,10 +221,10 @@ module Kumi
225
221
  type_desc = determine_array_type(array_source, array_fields)
226
222
  problem_desc += " - Operand #{index + 1} resolves to #{type_desc} from array '#{array_source}'\n"
227
223
  end
228
-
224
+
229
225
  explanation = "Direct operations on arrays from different sources is ambiguous and not supported. " \
230
226
  "Vectorized operations can only work on fields from the same array input."
231
-
227
+
232
228
  "#{summary}#{problem_desc}#{explanation}"
233
229
  end
234
230
 
@@ -244,8 +240,7 @@ module Kumi
244
240
  "array(mixed)"
245
241
  end
246
242
  end
247
-
248
243
  end
249
244
  end
250
245
  end
251
- end
246
+ end
@@ -4,8 +4,8 @@ module Kumi
4
4
  module Analyzer
5
5
  module Passes
6
6
  # RESPONSIBILITY: Build dependency graph and detect conditional dependencies in cascades
7
- # DEPENDENCIES: :definitions from NameIndexer, :input_meta from InputCollector
8
- # PRODUCES: :dependency_graph, :transitive_dependents, :leaf_map - Dependency analysis results
7
+ # DEPENDENCIES: :declarations from NameIndexer, :inputs from InputCollector
8
+ # PRODUCES: :dependencies, :dependents, :leaves - Dependency analysis results
9
9
  # INTERFACE: new(schema, state).run(errors)
10
10
  class DependencyResolver < PassBase
11
11
  # Enhanced edge with conditional flag and cascade metadata
@@ -24,8 +24,8 @@ module Kumi
24
24
  include Syntax
25
25
 
26
26
  def run(errors)
27
- definitions = get_state(:definitions)
28
- input_meta = get_state(:input_meta)
27
+ definitions = get_state(:declarations)
28
+ input_meta = get_state(:inputs)
29
29
 
30
30
  dependency_graph = Hash.new { |h, k| h[k] = [] }
31
31
  reverse_dependencies = Hash.new { |h, k| h[k] = [] }
@@ -41,14 +41,14 @@ module Kumi
41
41
  # Compute transitive closure of reverse dependencies
42
42
  transitive_dependents = compute_transitive_closure(reverse_dependencies)
43
43
 
44
- state.with(:dependency_graph, dependency_graph.transform_values(&:freeze).freeze)
45
- .with(:transitive_dependents, transitive_dependents.freeze)
46
- .with(:leaf_map, leaf_map.transform_values(&:freeze).freeze)
44
+ state.with(:dependencies, dependency_graph.transform_values(&:freeze).freeze)
45
+ .with(:dependents, transitive_dependents.freeze)
46
+ .with(:leaves, leaf_map.transform_values(&:freeze).freeze)
47
47
  end
48
48
 
49
49
  private
50
50
 
51
- def process_node(node, decl, graph, reverse_deps, leaves, definitions, input_meta, errors, context)
51
+ def process_node(node, decl, graph, reverse_deps, leaves, definitions, _input_meta, errors, context)
52
52
  case node
53
53
  when DeclarationReference
54
54
  report_error(errors, "undefined reference to `#{node.name}`", location: node.loc) unless definitions.key?(node.name)
@@ -5,7 +5,7 @@ module Kumi
5
5
  module Passes
6
6
  # RESPONSIBILITY: Collect field metadata from input declarations and validate consistency
7
7
  # DEPENDENCIES: :definitions
8
- # PRODUCES: :input_meta - Hash mapping field names to {type:, domain:} metadata
8
+ # PRODUCES: :inputs - Hash mapping field names to {type:, domain:} metadata
9
9
  # INTERFACE: new(schema, state).run(errors)
10
10
  class InputCollector < PassBase
11
11
  def run(errors)
@@ -30,7 +30,7 @@ module Kumi
30
30
  end
31
31
  end
32
32
 
33
- state.with(:input_meta, freeze_nested_hash(input_meta))
33
+ state.with(:inputs, freeze_nested_hash(input_meta))
34
34
  end
35
35
 
36
36
  private
@@ -5,7 +5,7 @@ module Kumi
5
5
  module Passes
6
6
  # RESPONSIBILITY: Build definitions index and detect duplicate names
7
7
  # DEPENDENCIES: None (first pass in pipeline)
8
- # PRODUCES: :definitions - Hash mapping names to declaration nodes
8
+ # PRODUCES: :declarations - Hash mapping names to declaration nodes
9
9
  # INTERFACE: new(schema, state).run(errors)
10
10
  class NameIndexer < PassBase
11
11
  def run(errors)
@@ -16,7 +16,7 @@ module Kumi
16
16
  definitions[decl.name] = decl
17
17
  end
18
18
 
19
- state.with(:definitions, definitions.freeze)
19
+ state.with(:declarations, definitions.freeze)
20
20
  end
21
21
  end
22
22
  end