kumi 0.0.9 → 0.0.10

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +28 -44
  3. data/README.md +187 -120
  4. data/docs/AST.md +1 -1
  5. data/docs/FUNCTIONS.md +52 -8
  6. data/docs/compiler_design_principles.md +86 -0
  7. data/docs/features/README.md +15 -2
  8. data/docs/features/hierarchical-broadcasting.md +349 -0
  9. data/docs/features/javascript-transpiler.md +148 -0
  10. data/docs/features/performance.md +1 -3
  11. data/docs/schema_metadata.md +7 -7
  12. data/examples/game_of_life.rb +2 -4
  13. data/lib/kumi/analyzer.rb +0 -2
  14. data/lib/kumi/compiler.rb +6 -275
  15. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +600 -42
  16. data/lib/kumi/core/analyzer/passes/input_collector.rb +4 -2
  17. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +27 -0
  18. data/lib/kumi/core/analyzer/passes/type_checker.rb +6 -2
  19. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +90 -46
  20. data/lib/kumi/core/cascade_executor_builder.rb +132 -0
  21. data/lib/kumi/core/compiler/expression_compiler.rb +146 -0
  22. data/lib/kumi/core/compiler/function_invoker.rb +55 -0
  23. data/lib/kumi/core/compiler/path_traversal_compiler.rb +158 -0
  24. data/lib/kumi/core/compiler/reference_compiler.rb +46 -0
  25. data/lib/kumi/core/compiler_base.rb +137 -0
  26. data/lib/kumi/core/explain.rb +2 -2
  27. data/lib/kumi/core/function_registry/collection_functions.rb +86 -3
  28. data/lib/kumi/core/function_registry/function_builder.rb +5 -3
  29. data/lib/kumi/core/function_registry/logical_functions.rb +171 -1
  30. data/lib/kumi/core/function_registry/stat_functions.rb +156 -0
  31. data/lib/kumi/core/function_registry.rb +32 -10
  32. data/lib/kumi/core/nested_structure_utils.rb +78 -0
  33. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +2 -2
  34. data/lib/kumi/core/ruby_parser/input_builder.rb +61 -8
  35. data/lib/kumi/core/schema_instance.rb +4 -0
  36. data/lib/kumi/core/vectorized_function_builder.rb +88 -0
  37. data/lib/kumi/errors.rb +2 -0
  38. data/lib/kumi/js/compiler.rb +878 -0
  39. data/lib/kumi/js/function_registry.rb +333 -0
  40. data/lib/kumi/js.rb +23 -0
  41. data/lib/kumi/registry.rb +61 -1
  42. data/lib/kumi/schema.rb +1 -1
  43. data/lib/kumi/support/s_expression_printer.rb +16 -15
  44. data/lib/kumi/syntax/array_expression.rb +6 -6
  45. data/lib/kumi/syntax/call_expression.rb +4 -4
  46. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  47. data/lib/kumi/syntax/case_expression.rb +4 -4
  48. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  49. data/lib/kumi/syntax/hash_expression.rb +4 -4
  50. data/lib/kumi/syntax/input_declaration.rb +6 -5
  51. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  52. data/lib/kumi/syntax/input_reference.rb +5 -5
  53. data/lib/kumi/syntax/literal.rb +4 -4
  54. data/lib/kumi/syntax/node.rb +34 -34
  55. data/lib/kumi/syntax/root.rb +6 -6
  56. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  57. data/lib/kumi/syntax/value_declaration.rb +4 -4
  58. data/lib/kumi/version.rb +1 -1
  59. data/lib/kumi.rb +1 -1
  60. data/scripts/analyze_broadcast_methods.rb +68 -0
  61. data/scripts/analyze_cascade_methods.rb +74 -0
  62. data/scripts/check_broadcasting_coverage.rb +51 -0
  63. data/scripts/find_dead_code.rb +114 -0
  64. metadata +20 -4
  65. data/docs/features/array-broadcasting.md +0 -170
  66. data/lib/kumi/cli.rb +0 -449
  67. data/lib/kumi/core/vectorization_metadata.rb +0 -110
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5f9f087001a482c906f041da8cb229511966a11c56e3ced99930b59d4141543
4
- data.tar.gz: fe5fafe03a5ca1e414b5bc3af9eccd830553aa8f44762e9d9b38e589fa342fd1
3
+ metadata.gz: 98308b4c27cb40488f14215c8da9700c62a51fc54d11b9d822a1eb25d396a724
4
+ data.tar.gz: 3fbbb50dc0c74ba14d83fbcf71c5866efa814b71b4d2679c45b57d59a1f778da
5
5
  SHA512:
6
- metadata.gz: b26279c08a9387616104252d24147c920f27b0f66574cde1987024325c5fd1ba87aff876b39fdc3d4f472d9aba2c713b19a5105890b66ac3ef10a3018a0b1bca
7
- data.tar.gz: d4c8db755880cd7088fd4b6087d2f38e86a4fdee11f867d58a480e143494ca2b85440abd9249b8c02a469a9ad21642c287d5ef8f30948bdc6d67695a895c1c13
6
+ metadata.gz: 4453ea76de50c433696c3ae5ebe4d39ae049cb35e40f34cf9a3cce0e52e5d0967a8ccfd9ca7627eafe33444ea4da186015a59796a59e022fdf5b5edc04c50444
7
+ data.tar.gz: dfdb9f118ee7c0c16fa30392b3591b88c8320a2d19ff331253a4344e49fa773967e6b8d2451c0fd490f200171846fec2b408ecd000e6ad111e3c2d89bd92c000
data/CLAUDE.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # CLAUDE.md
2
2
 
3
+ !! Important:
3
4
  !! Remember, this gem is not on production yet, so no backward compatilibity is necessary. But do not change the public interfaces (e.g. DSL, Schema) without explicitly requested or demanded.
4
5
  !! We are using zeitwerk, i.e.: no requires
5
- !! Do not care about linter or coverage unless asked to do so.
6
- !! IMPORTANT: Communication style - Write direct, factual statements. Avoid promotional language, unnecessary claims, or marketing speak. State what the system does, not what benefits it provides. Use TODOs for missing information rather than placeholder claims.
6
+ !! Disregard linting or coverage issues unless asked to do so.
7
+ !! Communication style - Write direct, factual statements. Avoid promotional language, unnecessary claims, or marketing speak. State what the system does, not what benefits it provides. Use TODOs for missing information rather than placeholder claims.
8
+ !! See all Available Functions in docs/FUNCTIONS.md
7
9
 
8
10
  ## Project Overview
9
11
 
@@ -27,7 +29,7 @@ Kumi is a Declarative logic and rules engine framework with static analysis for
27
29
 
28
30
  **Schema System** (`lib/kumi/schema.rb`):
29
31
  - Entry point that ties together parsing, analysis, and compilation
30
- - Provides the `schema(&block)` DSL method that builds the syntax tree, runs analysis, and compiles to executable form
32
+ - DSL method `schema(&block)` builds the syntax tree, runs analysis, and compiles to executable form
31
33
  - Generates a `Runner` instance for executing queries against input data
32
34
 
33
35
  **Parser** (`lib/kumi/ruby_parser{/*,.rb}`):
@@ -60,25 +62,19 @@ Kumi is a Declarative logic and rules engine framework with static analysis for
60
62
  - **Pass 8**: `type_checker.rb` - Validate function types and compatibility using inferred types
61
63
 
62
64
  **Compiler** (`lib/kumi/compiler.rb`):
63
- - Transforms analyzed syntax tree into executable lambda functions
65
+ - Compiles analyzed syntax tree into executable lambda functions
64
66
  - Maps each expression type to a compilation method
65
67
  - Handles function calls via `Kumi::Registry`
66
68
  - Produces `CompiledSchema` with executable bindings
67
69
 
68
70
  **Function Registry** (`lib/kumi/function_registry.rb`):
69
71
  - Registry of available functions (operators, math, string, logical, collection operations)
70
- - Supports custom function registration with comprehensive type metadata
72
+ - Supports custom function registration with type metadata
71
73
  - Each function includes param_types, return_type, arity, and description
72
74
  - Core functions include: `==`, `>`, `<`, `add`, `multiply`, `and`, `or`, `clamp`, etc.
73
75
  - Maintains backward compatibility with legacy type checking system
74
76
  - Function documents are generated by the script ./scripts/generate_function_docs.rb
75
77
 
76
- **Runner** (`lib/kumi/runner.rb`):
77
- - Executes compiled schemas against input data
78
- - Provides `fetch(key)` for individual value retrieval with caching
79
- - Provides `slice(*keys)` for batch evaluation
80
- - Provides `explain(key)` for detailed execution tracing
81
-
82
78
  **Input Validation System** (`lib/kumi/input/` and `lib/kumi/domain/`):
83
79
  - `input/validator.rb` - Main validation coordinator for type and domain checking
84
80
  - `input/type_matcher.rb` - Type validation logic for primitive and complex types
@@ -92,7 +88,7 @@ Kumi is a Declarative logic and rules engine framework with static analysis for
92
88
 
93
89
  ### Critical Syntax Rules
94
90
 
95
- **Module Definition Structure** (REQUIRED for CLI):
91
+ **Module Definition Structure**
96
92
  ```ruby
97
93
  # CORRECT - CLI can find and load this
98
94
  module SchemaName
@@ -102,11 +98,6 @@ module SchemaName
102
98
  # schema definition here
103
99
  end
104
100
  end
105
-
106
- # INCORRECT - CLI cannot load standalone schemas
107
- schema do # This won't work with CLI
108
- # schema definition
109
- end
110
101
  ```
111
102
 
112
103
  **Function Call Syntax**:
@@ -115,7 +106,7 @@ end
115
106
  **Arithmetic Operations**:
116
107
  - **Sugar Syntax**: `input.field1 + input.field2` - Works for input fields and value references
117
108
  - **Function Syntax**: `fn(:add, input.field1, input.field2)` - Always works, more explicit
118
- - **Mixed**: Use sugar for simple operations, functions for complex ones
109
+ - **Mixed**: Sugar syntax for basic operations, function syntax for complex ones
119
110
 
120
111
  **Cascade Condition Syntax**:
121
112
  ```ruby
@@ -159,13 +150,6 @@ end
159
150
 
160
151
  **IMPORTANT CASCADE CONDITION SYNTAX:**
161
152
  In cascade expressions (`value :name do ... end`), trait references use bare identifiers:
162
- ```ruby
163
- value :status do
164
- on adult, "Adult Status"
165
- on verified, "Verified User"
166
- base "Unverified"
167
- end
168
- ```
169
153
 
170
154
  **Input Block System**:
171
155
  - **Required**: All schemas must have an `input` block declaring expected fields
@@ -190,9 +174,9 @@ end
190
174
  4. Execute with Runner
191
175
 
192
176
  **Type System** (`lib/kumi/types.rb`):
193
- - Simple symbol-based type system for clean and intuitive declaration
177
+ - Symbol-based type system
194
178
  - **Dual Type System**: Declared types (from input blocks) and inferred types (from expressions)
195
- - Automatic type inference for all declarations based on expression analysis
179
+ - Type inference for all declarations based on expression analysis
196
180
  - Type primitives: `:string`, `:integer`, `:float`, `:boolean`, `:any`, `:symbol`, `:regexp`, `:time`, `:date`, `:datetime`
197
181
  - Collection types: `array(:element_type)` and `hash(:key_type, :value_type)` helper functions
198
182
  - Type compatibility checking and unification algorithms for numeric types
@@ -201,13 +185,13 @@ end
201
185
 
202
186
  ### Examples Directory
203
187
 
204
- The `examples/` directory contains comprehensive examples showing Kumi usage patterns:
188
+ The `examples/` directory contains examples showing Kumi usage patterns:
205
189
  - `cascade_demonstration.rb` - Demonstrates cascade logic with UnsatDetector fixes (working)
206
- - `working_comprehensive_schema.rb` - Complete feature showcase (current best practices, working)
190
+ - `working_comprehensive_schema.rb` - Feature showcase (current best practices, working)
207
191
  - Mathematical predicate examples - Safe mutual recursion patterns using cascade mutual exclusion
208
192
  - `federal_tax_calculator_2024.rb` - Real-world tax calculation example (working)
209
- - `tax_2024.rb` - Simple tax example with explain functionality (working)
210
- - `wide_schema_compilation_and_evaluation_benchmark.rb` - Performance benchmark for wide schemas (compilation and evaluation scaling)
193
+ - `tax_2024.rb` - Tax example with explain functionality (working)
194
+ - `wide_schema_compilation_and_evaluation_benchmark.rb` - Benchmark for wide schemas (compilation and evaluation)
211
195
  - `deep_schema_compilation_and_evaluation_benchmark.rb` - Performance benchmark for deep dependency chains (stack-safe evaluation)
212
196
  - `comprehensive_god_schema.rb` - Complex example (currently has UnsatDetector semantic errors)
213
197
 
@@ -223,11 +207,11 @@ The `examples/` directory contains comprehensive examples showing Kumi usage pat
223
207
  ## Files for Understanding
224
208
 
225
209
  . `docs/*` - Documents about Kumi, its features, DSL syntax, ...
226
- - `examples/*` Random examples of very diverse contexts.
210
+ - `examples/*` Random examples of diverse contexts.
227
211
 
228
212
  ### Troubleshooting Schema Issues
229
213
  - **Parse Errors**: Check function syntax (avoid empty `fn()` calls)
230
- - **Module Not Found**: Ensure proper module structure and naming, see examples
214
+ - **Module Not Found**: Check module structure and naming, see examples
231
215
  - **UnsatDetector Errors**: Review trait logic for contradictions, add debugs!
232
216
  - **Type Errors**: Check input block type declarations match usage, add debugs!
233
217
  - **Runtime Errors**: Use explain to trace computation dependencies, add debugs!
@@ -237,7 +221,7 @@ The `examples/` directory contains comprehensive examples showing Kumi usage pat
237
221
  ### Required Input Blocks
238
222
  - **All schemas must have an input block** -
239
223
  - Input blocks declare expected fields with optional type and domain constraints
240
- - **Empty input blocks are allowed** -`input {}` Even if its not very useful.
224
+ - **Empty input blocks are allowed** -`input {}` Even if not useful.
241
225
  - Fields are accessed via `input.field_name` or `input.field.nested_field.nested_nested_field` which
242
226
  works for referencing nested array input declarations.
243
227
 
@@ -263,13 +247,13 @@ input do
263
247
  hash :metadata, key: { type: :string }, val: { type: :any }
264
248
 
265
249
  #generic type
266
- any :misc # this will make Kumi lose most of its analyze/inference power
250
+ any :misc # this reduces Kumi's analyze/inference capabilities
267
251
  end
268
252
  ```
269
253
 
270
254
  ### Array Broadcasting System
271
255
 
272
- **Automatic Vectorization**: Field access on array inputs (`input.items.price`) applies operations element-wise with intelligent map/reduce detection.
256
+ **Vectorization**: Field access on array inputs (`input.items.price`) applies operations element-wise with map/reduce detection.
273
257
 
274
258
  **Basic Broadcasting**:
275
259
  ```ruby
@@ -286,7 +270,7 @@ value :subtotals, input.line_items.price * input.line_items.quantity
286
270
  trait :is_taxable, (input.line_items.category != "digital")
287
271
  ```
288
272
 
289
- **Aggregation Operations**: Functions consuming arrays automatically detected:
273
+ **Aggregation Operations**: Functions consuming arrays are detected:
290
274
  ```ruby
291
275
  value :total_subtotal, fn(:sum, subtotals)
292
276
  value :avg_price, fn(:avg, input.line_items.price)
@@ -297,7 +281,7 @@ value :max_quantity, fn(:max, input.line_items.quantity)
297
281
  - **InputElementReference** AST nodes for nested field access paths
298
282
  - **BroadcastDetector** analyzer pass identifies vectorized vs scalar operations
299
283
  - **Compiler** generates appropriate map/reduce functions based on usage context
300
- - **Type Inference** automatically infers types for array element operations
284
+ - **Type Inference** infers types for array element operations
301
285
  - Supports arbitrary depth field access with nested arrays and hashes
302
286
 
303
287
  ### Trait Syntax Evolution
@@ -330,7 +314,7 @@ trait :qualified, input.age, :>=, 21, input.score # OLD - shows deprecation war
330
314
  ```
331
315
 
332
316
  **Key Changes**:
333
- - **NEW**: Bare identifier syntax allows direct trait reference: `adult` instead of `ref(:adult)`
317
+ - **NEW**: Bare identifier syntax for direct trait reference: `adult` instead of `ref(:adult)`
334
318
  - New syntax uses parenthesized expressions: `trait :name, (expression)`
335
319
  - FieldRef nodes have operator methods that create CallExpression nodes
336
320
  - Logical AND chaining via `&` operator (Ruby limitation prevents `&&`)
@@ -350,9 +334,9 @@ trait :qualified, input.age, :>=, 21, input.score # OLD - shows deprecation war
350
334
 
351
335
  - **Multi-pass Analysis**: Each analysis pass has a single responsibility and builds on previous passes
352
336
  - **Immutable Syntax Tree**: AST nodes are immutable; analysis results stored separately in analyzer state
353
- - **Dependency-driven Evaluation**: All computation follows dependency graph to ensure correct order
354
- - **Type Safety**: Optional but comprehensive type checking without breaking existing schemas
355
- - **Ruby Integration**: Leverages Ruby's metaprogramming while providing structured analysis
337
+ - **Dependency-driven Evaluation**: All computation follows dependency graph for correct order
338
+ - **Type Safety**: Optional type checking without breaking existing schemas
339
+ - **Ruby Integration**: Leverages Ruby's metaprogramming with structured analysis
356
340
  - **Unified Error Reporting**: Consistent, localized error messages throughout the system with clear interface patterns
357
341
 
358
342
  ## Code Organization Patterns
@@ -370,7 +354,7 @@ class MyParser
370
354
  include ErrorReporting
371
355
 
372
356
  def parse_something
373
- # Immediate error raising
357
+ # Error raising
374
358
  raise_syntax_error("Invalid syntax", location: current_location)
375
359
  end
376
360
  end
@@ -389,7 +373,7 @@ class MyAnalyzerPass < PassBase
389
373
  end
390
374
  ```
391
375
  ### Testing Error Scenarios
392
- - Use `spec/integration/dsl_breakage_spec.rb` patterns for comprehensive error testing
376
+ - Use `spec/integration/dsl_breakage_spec.rb` patterns for error testing
393
377
  - Use `spec/integration/potential_breakage_spec.rb` for edge cases break
394
378
  - Use `spec/fixtures/location_tracking_test_schema.rb` fixture for testing different syntax error types
395
379
 
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Kumi is a Declarative logic and rules engine framework with static analysis for Ruby.
7
7
 
8
- It is well-suited for scenarios with complex, interdependent calculations, enforcing validation and consistency across your business rules while maintaining performance.
8
+ It handles complex, interdependent calculations with validation and consistency checking.
9
9
 
10
10
 
11
11
  ## What can you build?
@@ -59,9 +59,9 @@ result[:total_tax] # => 21,491.00
59
59
  result[:after_tax] # => 78,509.00
60
60
  ```
61
61
 
62
- Real tax calculation with brackets, deductions, and FICA caps. Validation happens during schema definition.
62
+ Real tax calculation with brackets, deductions, and FICA caps.
63
63
 
64
- Kumi is well-suited for scenarios with complex, interdependent calculations, enforcing validation and consistency across your business rules while maintaining performance.
64
+ Validation happens during schema definition.
65
65
 
66
66
  ## Installation
67
67
 
@@ -74,7 +74,7 @@ gem install kumi
74
74
  ## Core Features
75
75
 
76
76
  <details>
77
- <summary><strong>📊 Schema Primitives</strong> - Four building blocks for business logic</summary>
77
+ <summary><strong>Schema Primitives</strong> - Four building blocks for business logic</summary>
78
78
 
79
79
  ### Schema Primitives
80
80
 
@@ -96,7 +96,7 @@ value :tax_rate, 0.08
96
96
  value :tax_amount, subtotal * tax_rate
97
97
  ```
98
98
 
99
- **Traits** are boolean conditions that enable branching logic:
99
+ **Traits** are boolean conditions for branching logic:
100
100
  ```ruby
101
101
  trait :bulk_order, input.quantity >= 100
102
102
  trait :premium_customer, input.tier == "premium"
@@ -109,7 +109,7 @@ value :discount do
109
109
  end
110
110
  ```
111
111
 
112
- **Functions** provide computational building blocks:
112
+ **Functions** are computational building blocks:
113
113
 
114
114
  ```ruby
115
115
  value :final_price, [subtotal - discount_amount, 0].max
@@ -120,9 +120,13 @@ Note: You can find a list all core functions in [docs/FUNCTIONS.md](docs/FUNCTIO
120
120
  </details>
121
121
 
122
122
  <details>
123
- <summary><strong>🔍 Static Analysis</strong> - Catch errors at definition time and provides rich metadata</summary>
123
+ <summary><strong>Analysis & Introspection</strong> - Static verification, runtime inspection, and metadata extraction</summary>
124
124
 
125
- ### Static Analysis
125
+ ### Analysis & Introspection
126
+
127
+ Kumi provides comprehensive analysis capabilities - catching errors at definition time and exposing schema structure for tooling and debugging.
128
+
129
+ #### **Static Analysis: Catch Errors Early**
126
130
 
127
131
  Kumi catches many types of business logic errors that cause runtime failures or silent bugs:
128
132
 
@@ -203,7 +207,7 @@ end
203
207
  # ❌ Function arity error: divide expects 2 arguments, got 1
204
208
  ```
205
209
 
206
- **Bounded Recursion**: Kumi allows mutual recursion when cascade conditions are mutually exclusive:
210
+ **Mutual Recursion**: Kumi supports mutual recursion when cascade conditions are mutually exclusive:
207
211
 
208
212
  ```ruby
209
213
  trait :is_forward, input.operation == "forward"
@@ -212,7 +216,7 @@ trait :is_reverse, input.operation == "reverse"
212
216
  # Safe mutual recursion - conditions are mutually exclusive
213
217
  value :forward_processor do
214
218
  on is_forward, input.value * 2 # Direct calculation
215
- on is_reverse, reverse_processor + 10 # Delegates to reverse (safe)
219
+ on is_reverse, reveAnalysisrse_processor + 10 # Delegates to reverse (safe)
216
220
  base "invalid operation"
217
221
  end
218
222
 
@@ -230,168 +234,225 @@ end
230
234
 
231
235
  This compiles because `operation` can only be "forward" or "reverse", never both. Each recursion executes one step before hitting a direct calculation.
232
236
 
233
- </details>
237
+ #### **Runtime Introspection: Debug and Understand**
234
238
 
235
- <details>
236
- <summary><strong>🔍 Array Broadcasting</strong> - Automatic vectorization over array fields</summary>
237
-
238
- ### Array Broadcasting
239
-
240
- Kumi broadcasts operations over array fields for element-wise computation:
239
+ **Explainability**: Trace exactly how any value is computed, step-by-step. This is invaluable for debugging complex logic and auditing results.
241
240
 
242
241
  ```ruby
243
- schema do
244
- input do
245
- array :line_items do
246
- float :price
247
- integer :quantity
248
- string :category
249
- end
250
- float :tax_rate
251
- end
252
-
253
- # Element-wise computation - broadcasts over each item
254
- value :subtotals, input.line_items.price * input.line_items.quantity
255
-
256
- # Element-wise traits - applied to each item
257
- trait :is_taxable, (input.line_items.category != "digital")
258
-
259
- # Aggregation operations - consume arrays to produce scalars
260
- value :total_subtotal, fn(:sum, subtotals)
261
- value :item_count, fn(:size, input.line_items)
262
- end
242
+ Kumi::Explain.call(FederalTax2024, :fed_tax, inputs: {income: 100_000, filing_status: "single"})
243
+ # => fed_tax = fed_calc[0]
244
+ # = (fed_calc = piecewise_sum(taxable_income, fed_breaks, fed_rates)
245
+ # = piecewise_sum(85_400, [11_600, 47_150, ...], [0.10, 0.12, ...])
246
+ # = [15_099.50, 0.22])
247
+ # = 15_099.50
263
248
  ```
264
249
 
265
- **Dimension Mismatch Detection**: Operations across different arrays generate error messages:
250
+ #### **Schema Metadata API: Build Tooling**
266
251
 
267
- ```ruby
268
- schema do
269
- input do
270
- array :items do
271
- string :name
272
- end
273
- array :logs do
274
- string :user_name
275
- end
276
- end
252
+ Programmatically access the analyzed structure of your schema to build tools like form generators, documentation sites, or custom validators.
277
253
 
278
- # This generates an error
279
- trait :same_name, input.items.name == input.logs.user_name
280
- end
254
+ ```ruby
255
+ metadata = FederalTax2024.schema_metadata
281
256
 
282
- # Error:
283
- # Cannot broadcast operation across arrays from different sources: items, logs.
284
- # Problem: Multiple operands are arrays from different sources:
285
- # - Operand 1 resolves to array(string) from array 'items'
286
- # - Operand 2 resolves to array(string) from array 'logs'
287
- # Direct operations on arrays from different sources is ambiguous and not supported.
288
- ```
257
+ # Processed, tool-friendly metadata
258
+ metadata.inputs # => { name: { type: :string, domain: ... } }
259
+ metadata.values # => { name: { dependencies: [...], expression: "..." } }
260
+ metadata.traits # => { name: { condition: "...", dependencies: [...] } }
289
261
 
290
- </details>
262
+ # Raw analyzer state for deep analysis
263
+ metadata.dependencies # Dependency graph between all declarations
264
+ metadata.evaluation_order # Topologically sorted computation order
291
265
 
292
- <details>
293
- <summary><strong>💾 Automatic Memoization</strong> - Each value computed exactly once</summary>
266
+ # Export to standard formats
267
+ metadata.to_h # => Serializable hash for JSON/APIs
268
+ metadata.to_json_schema # => JSON Schema for input validation
269
+ ```
294
270
 
295
- ### Automatic Memoization
271
+ #### **AST Visualization: See the Structure**
296
272
 
297
- Each value is computed exactly once:
273
+ For deep debugging, you can print the raw Abstract Syntax Tree (AST) of a schema.
298
274
 
299
275
  ```ruby
300
- runner = FederalTax2024.from(income: 250_000, filing_status: "married_joint")
301
-
302
- # First access computes full dependency chain
303
- runner[:total_tax] # => 53,155.20
304
-
305
- # Subsequent access uses cached values
306
- runner[:fed_tax] # => 39,077.00 (cached)
307
- runner[:after_tax] # => 196,844.80 (cached)
276
+ puts Kumi::Support::SExpressionPrinter.print(FederalTax2024.__syntax_tree__)
277
+ # => (Root
278
+ # inputs: [
279
+ # (InputDeclaration :income :float)
280
+ # (InputDeclaration :filing_status :string domain: ["single", "married_joint"])
281
+ # ]
282
+ # traits: [...]
283
+ # attributes: [...])
308
284
  ```
309
285
 
310
286
  </details>
311
287
 
312
288
  <details>
313
- <summary><strong>🔍 Introspection</strong> - See exactly how values are calculated</summary>
289
+ <summary><strong>Hierarchical Broadcasting</strong> - Vectorization over nested data structures</summary>
290
+
291
+ ### Hierarchical Broadcasting
292
+
293
+ Kumi broadcasts operations over hierarchical data structures with two complementary access modes for maximum flexibility.
314
294
 
315
- ### Introspection
295
+ See [docs/features/hierarchical-broadcasting.md](docs/features/hierarchical-broadcasting.md) for detailed documentation.
316
296
 
317
- Show how values are calculated:
297
+ **Business Scenario**: E-commerce checkout with dynamic pricing rules
318
298
 
299
+ > **"As an e-commerce platform, I need to calculate order totals with complex discount rules:**
300
+ > - Premium members get 15% off electronics
301
+ > - Bulk orders (5+ items) get 10% off that item
302
+ > - Free shipping on orders over $100
303
+ > - Calculate: item subtotals, total discounts, shipping, final total
304
+ >
305
+ > **The challenge:** Each order has different items, quantities, categories, and customer tiers. The discount logic involves multiple conditions - some items qualify for multiple discounts, others for none. Traditional pricing code requires nested if-statements and manual calculations."
306
+
307
+ **Kumi Solution** (16 lines of declarative pricing logic):
319
308
  ```ruby
320
- Kumi::Explain.call(FederalTax2024, :fed_tax, inputs: {income: 100_000, filing_status: "single"})
321
- # => fed_tax = fed_calc[0]
322
- # = (fed_calc = piecewise_sum(taxable_income, fed_breaks, fed_rates)
323
- # = piecewise_sum(85,400, [11,600, 47,150, ...], [0.10, 0.12, ...])
324
- # = [15,099.50, 0.22])
325
- # = 15,099.50
309
+ module OrderPricing
310
+ extend Kumi::Schema
311
+
312
+ schema do
313
+ input do
314
+ array :items do
315
+ float :price
316
+ integer :quantity
317
+ string :category
318
+ end
319
+ string :customer_tier
320
+ float :shipping_threshold
321
+ end
322
+
323
+ # Calculate item subtotals and discount eligibility
324
+ value :subtotals, input.items.price * input.items.quantity
325
+ trait :electronics, input.items.category == "electronics"
326
+ trait :bulk_item, input.items.quantity >= 5
327
+ trait :premium_customer, input.customer_tier == "premium"
328
+
329
+ # Apply layered discounts (premium + bulk can stack)
330
+ trait :premium_electronics, premium_customer & electronics
331
+ trait :stacked_discount, premium_electronics & bulk_item
332
+
333
+ value :discounted_prices do
334
+ on stacked_discount, input.items.price * 0.75 # 15% + 10% = 25% off
335
+ on premium_electronics, input.items.price * 0.85 # 15% off
336
+ on bulk_item, input.items.price * 0.90 # 10% off
337
+ base input.items.price # No discount
338
+ end
339
+
340
+ value :final_subtotals, discounted_prices * input.items.quantity
341
+
342
+ # Order totals and conditional shipping
343
+ value :subtotal, fn(:sum, final_subtotals)
344
+ value :total_savings, fn(:sum, subtotals) - subtotal
345
+ value :shipping, subtotal > input.shipping_threshold ? 0.0 : 9.99
346
+ value :total, subtotal + shipping
347
+ end
348
+ end
326
349
  ```
327
350
 
328
- **Debug AST Structure**: Visualize the parsed schema as S-expressions:
351
+ **Mixed Access Modes**: Both object and element access can be used together in the same schema:
329
352
 
330
353
  ```ruby
331
- puts Kumi::Support::SExpressionPrinter.print(FederalTax2024.__syntax_tree__)
332
- # => (Root
333
- # inputs: [
334
- # (InputDeclaration :income :float)
335
- # (InputDeclaration :filing_status :string domain: ["single", "married_joint"])
336
- # ]
337
- # traits: [...]
338
- # attributes: [...])
354
+ module UserAnalytics
355
+ extend Kumi::Schema
356
+
357
+ schema do
358
+ input do
359
+ # Object access mode - structured business data
360
+ array :users do
361
+ string :name
362
+ integer :age
363
+ end
364
+
365
+ # Element access mode - multi-dimensional raw arrays
366
+ array :recent_purchases do
367
+ element :integer, :days_ago
368
+ end
369
+ end
370
+
371
+ # Object access works normally
372
+ value :user_names, input.users.name
373
+ value :avg_age, fn(:avg, input.users.age)
374
+
375
+ # Element access handles nested arrays with progressive path traversal
376
+ value :all_purchase_days, fn(:flatten, input.recent_purchases.days_ago)
377
+ value :recent_flags, input.recent_purchases.days_ago < 5
378
+ trait :has_recent_purchase, fn(:any?, fn(:flatten, recent_flags))
379
+
380
+ # Progressive dimensional analysis - each path level goes deeper
381
+ value :purchase_dimensions, [
382
+ fn(:size, input.recent_purchases), # Number of purchase groups
383
+ fn(:size, input.recent_purchases.days_ago) # Total individual purchase days
384
+ ]
385
+
386
+ # Mixed usage in conditions
387
+ trait :adult_users, input.users.age >= 18
388
+ value :adult_count, fn(:count_if, adult_users)
389
+ end
390
+ end
391
+
392
+ # Works with mixed data structures
393
+ result = UserAnalytics.from({
394
+ users: [{ name: "Alice", age: 25 }, { name: "Bob", age: 17 }],
395
+ recent_purchases: [[1, 3, 7], [2, 4], [10, 15]] # Nested arrays
396
+ })
397
+
398
+ result[:user_names] # => ["Alice", "Bob"]
399
+ result[:has_recent_purchase] # => true (some purchases < 5 days ago)
400
+ result[:adult_count] # => 1
401
+ result[:purchase_dimensions] # => [3, 8] (3 groups, 8 total days)
339
402
  ```
340
403
 
341
404
  </details>
342
405
 
343
406
  <details>
344
- <summary><strong>📋 Schema Metadata</strong> - Extract structured information for tooling</summary>
407
+ <summary><strong>Memoization</strong> - Each value computed exactly once</summary>
345
408
 
346
- ### Schema Metadata
409
+ ### Memoization
347
410
 
348
- Access structured metadata for building tools like form generators and dependency analyzers:
411
+ Each value is computed exactly once:
349
412
 
350
413
  ```ruby
351
- metadata = FederalTax2024.schema_metadata
352
-
353
- # Processed metadata (tool-friendly)
354
- metadata.inputs # Input field types and domains
355
- metadata.values # Value declarations with dependencies
356
- metadata.traits # Trait conditions and metadata
357
- metadata.functions # Function registry information
414
+ runner = FederalTax2024.from(income: 250_000, filing_status: "married_joint")
358
415
 
359
- # Raw analyzer state (advanced usage)
360
- metadata.dependencies # Dependency graph between declarations
361
- metadata.evaluation_order # Topologically sorted computation order
362
- metadata.inferred_types # Type inference results
363
- metadata.declarations # Raw AST declaration nodes
416
+ # First access computes full dependency chain
417
+ runner[:total_tax] # => 53,155.20
364
418
 
365
- # Export formats
366
- metadata.to_h # Serializable hash for JSON/APIs
367
- metadata.to_json_schema # JSON Schema for input validation
419
+ # Subsequent access uses cached values
420
+ runner[:fed_tax] # => 39,077.00 (cached)
421
+ runner[:after_tax] # => 196,844.80 (cached)
368
422
  ```
369
-
370
- The SchemaMetadata interface provides both processed metadata for tool development and raw analyzer state for advanced use cases. Complete documentation available in the SchemaMetadata class and [docs/schema_metadata.md](docs/schema_metadata.md).
371
-
372
423
  </details>
373
424
 
374
- ## Beyond Rules: What the Metadata Unlocks
375
- * **Auto-generated forms** – compile schema → field spec → React form
376
- * **Scenario explorer** – derive all trait combinations, Monte Carlo outcomes
377
- * **Coverage dashboard** – flag branches never hit in prod
378
- * **Schema diff** – highlight behaviour changes across versions
379
-
380
425
  ## Usage
381
426
 
382
427
  **Suitable for:**
383
428
  - Complex interdependent business rules
384
429
  - Tax calculation engines
385
430
  - Insurance premium calculators
386
- - Loan amortization schedules
387
431
  - Commission structures with complex tiers
388
432
  - Pricing engines with multiple discount rules
389
433
 
390
434
  **Not suitable for:**
391
- - Simple conditional statements
435
+ - Basic conditional statements
392
436
  - Sequential procedural workflows
393
- - Rules that change during execution
394
- - High-frequency real-time processing
437
+ - High-frequency processing
438
+
439
+ ## JavaScript Transpiler
440
+
441
+ Transpiles compiled schemas to standalone JavaScript code. See [docs/features/javascript-transpiler.md](docs/features/javascript-transpiler.md) for details.
442
+
443
+ ```ruby
444
+ Kumi::Js.export_to_file(FederalTax2024, "federal-tax-2024.js")
445
+ ```
446
+
447
+ ```javascript
448
+ const { schema } = require('./federal-tax-2024.js');
449
+ const calculator = schema.from({ income: 100_000, filing_status: "single" });
450
+ console.log(calculator.fetch('total_tax')); // 21491
451
+ ```
452
+
453
+ Generated JavaScript includes only functions used by the schema.
454
+
455
+ All tests run in dual mode to verify compiled schemas produce identical results in both Ruby and JavaScript.
395
456
 
396
457
  ## Performance
397
458
 
@@ -400,6 +461,12 @@ Benchmarks on Linux with Ruby 3.3.8 on a Dell Latitude 7450:
400
461
  - 1,000 attributes: **131,000/sec** (analysis <50ms)
401
462
  - 10,000 attributes: **14,200/sec** (analysis ~300ms)
402
463
 
464
+ See [docs/features/performance.md](docs/features/performance.md) for detailed benchmarks.
465
+
466
+ ## What Kumi does not guarantee
467
+
468
+ Lambdas (e.g. -> inside the schema), external IO, floating-point vs bignum, JS transpiler edge cases, time-zone math differences, etc.
469
+
403
470
  ## Learn More
404
471
 
405
472
  - [DSL Syntax Reference](docs/SYNTAX.md)
data/docs/AST.md CHANGED
@@ -77,7 +77,7 @@ CaseExpression = Struct.new(:condition, :result)
77
77
  ```
78
78
 
79
79
  **Case type mappings**:
80
- - `on :a, :b, result` → `condition: fn(:all?, ref(:a), ref(:b))`
80
+ - `on :a, :b, result` → `condition: fn(:cascade_and, ref(:a), ref(:b))`
81
81
  - `on_any :a, :b, result` → `condition: fn(:any?, ref(:a), ref(:b))`
82
82
  - `base result` → `condition: literal(true)`
83
83