kumi 0.0.8 → 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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +28 -44
  3. data/README.md +188 -108
  4. data/docs/AST.md +8 -1
  5. data/docs/FUNCTIONS.md +52 -8
  6. data/docs/compiler_design_principles.md +86 -0
  7. data/docs/features/README.md +22 -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/features/s-expression-printer.md +77 -0
  12. data/docs/schema_metadata.md +7 -7
  13. data/examples/game_of_life.rb +2 -4
  14. data/lib/kumi/analyzer.rb +0 -2
  15. data/lib/kumi/compiler.rb +6 -275
  16. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +600 -42
  17. data/lib/kumi/core/analyzer/passes/input_collector.rb +4 -2
  18. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +27 -0
  19. data/lib/kumi/core/analyzer/passes/type_checker.rb +6 -2
  20. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +90 -46
  21. data/lib/kumi/core/cascade_executor_builder.rb +132 -0
  22. data/lib/kumi/core/compiler/expression_compiler.rb +146 -0
  23. data/lib/kumi/core/compiler/function_invoker.rb +55 -0
  24. data/lib/kumi/core/compiler/path_traversal_compiler.rb +158 -0
  25. data/lib/kumi/core/compiler/reference_compiler.rb +46 -0
  26. data/lib/kumi/core/compiler_base.rb +137 -0
  27. data/lib/kumi/core/explain.rb +2 -2
  28. data/lib/kumi/core/function_registry/collection_functions.rb +86 -3
  29. data/lib/kumi/core/function_registry/function_builder.rb +5 -3
  30. data/lib/kumi/core/function_registry/logical_functions.rb +171 -1
  31. data/lib/kumi/core/function_registry/stat_functions.rb +156 -0
  32. data/lib/kumi/core/function_registry.rb +32 -10
  33. data/lib/kumi/core/nested_structure_utils.rb +78 -0
  34. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +2 -2
  35. data/lib/kumi/core/ruby_parser/input_builder.rb +61 -8
  36. data/lib/kumi/core/schema_instance.rb +4 -0
  37. data/lib/kumi/core/vectorized_function_builder.rb +88 -0
  38. data/lib/kumi/errors.rb +2 -0
  39. data/lib/kumi/js/compiler.rb +878 -0
  40. data/lib/kumi/js/function_registry.rb +333 -0
  41. data/lib/kumi/js.rb +23 -0
  42. data/lib/kumi/registry.rb +61 -1
  43. data/lib/kumi/schema.rb +1 -1
  44. data/lib/kumi/support/s_expression_printer.rb +162 -0
  45. data/lib/kumi/syntax/array_expression.rb +6 -6
  46. data/lib/kumi/syntax/call_expression.rb +4 -4
  47. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  48. data/lib/kumi/syntax/case_expression.rb +4 -4
  49. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  50. data/lib/kumi/syntax/hash_expression.rb +4 -4
  51. data/lib/kumi/syntax/input_declaration.rb +6 -5
  52. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  53. data/lib/kumi/syntax/input_reference.rb +5 -5
  54. data/lib/kumi/syntax/literal.rb +4 -4
  55. data/lib/kumi/syntax/node.rb +34 -34
  56. data/lib/kumi/syntax/root.rb +6 -6
  57. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  58. data/lib/kumi/syntax/value_declaration.rb +4 -4
  59. data/lib/kumi/version.rb +1 -1
  60. data/lib/kumi.rb +1 -1
  61. data/scripts/analyze_broadcast_methods.rb +68 -0
  62. data/scripts/analyze_cascade_methods.rb +74 -0
  63. data/scripts/check_broadcasting_coverage.rb +51 -0
  64. data/scripts/find_dead_code.rb +114 -0
  65. metadata +22 -4
  66. data/docs/features/array-broadcasting.md +0 -170
  67. data/lib/kumi/cli.rb +0 -449
  68. data/lib/kumi/core/vectorization_metadata.rb +0 -110
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 652ef5f59f7c4469c86ecda4add1f650121d75d333231ae50306a7bf2ce7725c
4
- data.tar.gz: 367a335e09a9d5cefaecbfa8e870c99ac49153a771f2451e3cfa8f73531d0701
3
+ metadata.gz: 98308b4c27cb40488f14215c8da9700c62a51fc54d11b9d822a1eb25d396a724
4
+ data.tar.gz: 3fbbb50dc0c74ba14d83fbcf71c5866efa814b71b4d2679c45b57d59a1f778da
5
5
  SHA512:
6
- metadata.gz: 2dece8cc4284177c7f45a97768ea0c228cf41210d85cff985850a5e98f64c4ce02cc2c97cc2a20897d1f192422390ad39357b0d883e4e6aebbae1d0abc41cfe8
7
- data.tar.gz: cb817018fab35b9594053d5bbd7fa4adb31c79a92d7e968a2739f9ffd16d194a3a45aeb75b6fcc28743fadcd93da83c9227cfe81d14bb2f9d917c18e1fb5d16b
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,155 +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>
239
+ **Explainability**: Trace exactly how any value is computed, step-by-step. This is invaluable for debugging complex logic and auditing results.
240
+
241
+ ```ruby
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
248
+ ```
237
249
 
238
- ### Array Broadcasting
250
+ #### **Schema Metadata API: Build Tooling**
239
251
 
240
- Kumi broadcasts operations over array fields for element-wise computation:
252
+ Programmatically access the analyzed structure of your schema to build tools like form generators, documentation sites, or custom validators.
241
253
 
242
254
  ```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
255
+ metadata = FederalTax2024.schema_metadata
252
256
 
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
263
- ```
257
+ # Processed, tool-friendly metadata
258
+ metadata.inputs # => { name: { type: :string, domain: ... } }
259
+ metadata.values # => { name: { dependencies: [...], expression: "..." } }
260
+ metadata.traits # => { name: { condition: "...", dependencies: [...] } }
264
261
 
265
- **Dimension Mismatch Detection**: Operations across different arrays generate error messages:
262
+ # Raw analyzer state for deep analysis
263
+ metadata.dependencies # Dependency graph between all declarations
264
+ metadata.evaluation_order # Topologically sorted computation order
266
265
 
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
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
+ ```
277
270
 
278
- # This generates an error
279
- trait :same_name, input.items.name == input.logs.user_name
280
- end
271
+ #### **AST Visualization: See the Structure**
272
+
273
+ For deep debugging, you can print the raw Abstract Syntax Tree (AST) of a schema.
281
274
 
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.
275
+ ```ruby
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: [...])
288
284
  ```
289
285
 
290
286
  </details>
291
287
 
292
288
  <details>
293
- <summary><strong>💾 Automatic Memoization</strong> - Each value computed exactly once</summary>
289
+ <summary><strong>Hierarchical Broadcasting</strong> - Vectorization over nested data structures</summary>
294
290
 
295
- ### Automatic Memoization
291
+ ### Hierarchical Broadcasting
296
292
 
297
- Each value is computed exactly once:
293
+ Kumi broadcasts operations over hierarchical data structures with two complementary access modes for maximum flexibility.
298
294
 
299
- ```ruby
300
- runner = FederalTax2024.from(income: 250_000, filing_status: "married_joint")
295
+ See [docs/features/hierarchical-broadcasting.md](docs/features/hierarchical-broadcasting.md) for detailed documentation.
301
296
 
302
- # First access computes full dependency chain
303
- runner[:total_tax] # => 53,155.20
297
+ **Business Scenario**: E-commerce checkout with dynamic pricing rules
304
298
 
305
- # Subsequent access uses cached values
306
- runner[:fed_tax] # => 39,077.00 (cached)
307
- runner[:after_tax] # => 196,844.80 (cached)
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):
308
+ ```ruby
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
308
349
  ```
309
350
 
310
- </details>
351
+ **Mixed Access Modes**: Both object and element access can be used together in the same schema:
311
352
 
312
- <details>
313
- <summary><strong>🔍 Introspection</strong> - See exactly how values are calculated</summary>
353
+ ```ruby
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
314
370
 
315
- ### Introspection
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
316
391
 
317
- Show how values are calculated:
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
+ })
318
397
 
319
- ```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
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)
326
402
  ```
327
403
 
328
404
  </details>
329
405
 
330
406
  <details>
331
- <summary><strong>📋 Schema Metadata</strong> - Extract structured information for tooling</summary>
407
+ <summary><strong>Memoization</strong> - Each value computed exactly once</summary>
332
408
 
333
- ### Schema Metadata
409
+ ### Memoization
334
410
 
335
- Access structured metadata for building tools like form generators and dependency analyzers:
411
+ Each value is computed exactly once:
336
412
 
337
413
  ```ruby
338
- metadata = FederalTax2024.schema_metadata
339
-
340
- # Processed metadata (tool-friendly)
341
- metadata.inputs # Input field types and domains
342
- metadata.values # Value declarations with dependencies
343
- metadata.traits # Trait conditions and metadata
344
- metadata.functions # Function registry information
414
+ runner = FederalTax2024.from(income: 250_000, filing_status: "married_joint")
345
415
 
346
- # Raw analyzer state (advanced usage)
347
- metadata.dependencies # Dependency graph between declarations
348
- metadata.evaluation_order # Topologically sorted computation order
349
- metadata.inferred_types # Type inference results
350
- metadata.declarations # Raw AST declaration nodes
416
+ # First access computes full dependency chain
417
+ runner[:total_tax] # => 53,155.20
351
418
 
352
- # Export formats
353
- metadata.to_h # Serializable hash for JSON/APIs
354
- 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)
355
422
  ```
356
-
357
- 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).
358
-
359
423
  </details>
360
424
 
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
-
367
425
  ## Usage
368
426
 
369
427
  **Suitable for:**
370
428
  - Complex interdependent business rules
371
429
  - Tax calculation engines
372
430
  - Insurance premium calculators
373
- - Loan amortization schedules
374
431
  - Commission structures with complex tiers
375
432
  - Pricing engines with multiple discount rules
376
433
 
377
434
  **Not suitable for:**
378
- - Simple conditional statements
435
+ - Basic conditional statements
379
436
  - Sequential procedural workflows
380
- - Rules that change during execution
381
- - 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.
382
456
 
383
457
  ## Performance
384
458
 
@@ -387,6 +461,12 @@ Benchmarks on Linux with Ruby 3.3.8 on a Dell Latitude 7450:
387
461
  - 1,000 attributes: **131,000/sec** (analysis <50ms)
388
462
  - 10,000 attributes: **14,200/sec** (analysis ~300ms)
389
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
+
390
470
  ## Learn More
391
471
 
392
472
  - [DSL Syntax Reference](docs/SYNTAX.md)
data/docs/AST.md CHANGED
@@ -39,6 +39,13 @@ InputReference = Struct.new(:name)
39
39
  # Has operator methods: >=, <=, >, <, ==, != that create CallExpression nodes
40
40
  ```
41
41
 
42
+ **InputElementReference**: Access of nested input fields (`input.field_name.element.subelement.subsubelement`)
43
+ ```ruby
44
+ InputElementReference = Struct.new(:path)
45
+ # Represents nested input access
46
+ # DSL: input.address.street → InputElementReference([:address, :street])
47
+ ```
48
+
42
49
  **DeclarationReference**: References to other declarations
43
50
  ```ruby
44
51
  DeclarationReference = Struct.new(:name)
@@ -70,7 +77,7 @@ CaseExpression = Struct.new(:condition, :result)
70
77
  ```
71
78
 
72
79
  **Case type mappings**:
73
- - `on :a, :b, result` → `condition: fn(:all?, ref(:a), ref(:b))`
80
+ - `on :a, :b, result` → `condition: fn(:cascade_and, ref(:a), ref(:b))`
74
81
  - `on_any :a, :b, result` → `condition: fn(:any?, ref(:a), ref(:b))`
75
82
  - `base result` → `condition: literal(true)`
76
83