kumi 0.0.9 → 0.0.11

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/CLAUDE.md +18 -258
  4. data/README.md +188 -121
  5. data/docs/AST.md +1 -1
  6. data/docs/FUNCTIONS.md +52 -8
  7. data/docs/VECTOR_SEMANTICS.md +286 -0
  8. data/docs/compiler_design_principles.md +86 -0
  9. data/docs/features/README.md +15 -2
  10. data/docs/features/hierarchical-broadcasting.md +349 -0
  11. data/docs/features/javascript-transpiler.md +148 -0
  12. data/docs/features/performance.md +1 -3
  13. data/docs/features/s-expression-printer.md +2 -2
  14. data/docs/schema_metadata.md +7 -7
  15. data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +21 -15
  16. data/examples/game_of_life.rb +2 -4
  17. data/lib/kumi/analyzer.rb +34 -14
  18. data/lib/kumi/compiler.rb +4 -283
  19. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +717 -66
  20. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +1 -1
  21. data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +47 -0
  22. data/lib/kumi/core/analyzer/passes/input_collector.rb +118 -99
  23. data/lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb +293 -0
  24. data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +993 -0
  25. data/lib/kumi/core/analyzer/passes/pass_base.rb +2 -2
  26. data/lib/kumi/core/analyzer/passes/scope_resolution_pass.rb +346 -0
  27. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +28 -0
  28. data/lib/kumi/core/analyzer/passes/toposorter.rb +9 -3
  29. data/lib/kumi/core/analyzer/passes/type_checker.rb +9 -5
  30. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +2 -2
  31. data/lib/kumi/core/analyzer/passes/{type_inferencer.rb → type_inferencer_pass.rb} +4 -4
  32. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +92 -48
  33. data/lib/kumi/core/analyzer/plans.rb +52 -0
  34. data/lib/kumi/core/analyzer/structs/access_plan.rb +20 -0
  35. data/lib/kumi/core/analyzer/structs/input_meta.rb +29 -0
  36. data/lib/kumi/core/compiler/access_builder.rb +36 -0
  37. data/lib/kumi/core/compiler/access_planner.rb +219 -0
  38. data/lib/kumi/core/compiler/accessors/base.rb +69 -0
  39. data/lib/kumi/core/compiler/accessors/each_indexed_accessor.rb +84 -0
  40. data/lib/kumi/core/compiler/accessors/materialize_accessor.rb +55 -0
  41. data/lib/kumi/core/compiler/accessors/ravel_accessor.rb +73 -0
  42. data/lib/kumi/core/compiler/accessors/read_accessor.rb +41 -0
  43. data/lib/kumi/core/compiler_base.rb +137 -0
  44. data/lib/kumi/core/error_reporter.rb +6 -5
  45. data/lib/kumi/core/errors.rb +4 -0
  46. data/lib/kumi/core/explain.rb +157 -205
  47. data/lib/kumi/core/export/node_builders.rb +2 -2
  48. data/lib/kumi/core/export/node_serializers.rb +1 -1
  49. data/lib/kumi/core/function_registry/collection_functions.rb +100 -6
  50. data/lib/kumi/core/function_registry/conditional_functions.rb +14 -4
  51. data/lib/kumi/core/function_registry/function_builder.rb +142 -53
  52. data/lib/kumi/core/function_registry/logical_functions.rb +173 -3
  53. data/lib/kumi/core/function_registry/stat_functions.rb +156 -0
  54. data/lib/kumi/core/function_registry.rb +138 -98
  55. data/lib/kumi/core/ir/execution_engine/combinators.rb +117 -0
  56. data/lib/kumi/core/ir/execution_engine/interpreter.rb +336 -0
  57. data/lib/kumi/core/ir/execution_engine/values.rb +46 -0
  58. data/lib/kumi/core/ir/execution_engine.rb +50 -0
  59. data/lib/kumi/core/ir.rb +58 -0
  60. data/lib/kumi/core/ruby_parser/build_context.rb +2 -2
  61. data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +0 -12
  62. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +37 -16
  63. data/lib/kumi/core/ruby_parser/input_builder.rb +61 -8
  64. data/lib/kumi/core/ruby_parser/parser.rb +1 -1
  65. data/lib/kumi/core/ruby_parser/schema_builder.rb +2 -2
  66. data/lib/kumi/core/ruby_parser/sugar.rb +7 -0
  67. data/lib/kumi/errors.rb +2 -0
  68. data/lib/kumi/js.rb +23 -0
  69. data/lib/kumi/registry.rb +17 -22
  70. data/lib/kumi/runtime/executable.rb +213 -0
  71. data/lib/kumi/schema.rb +15 -4
  72. data/lib/kumi/schema_metadata.rb +2 -2
  73. data/lib/kumi/support/ir_dump.rb +491 -0
  74. data/lib/kumi/support/s_expression_printer.rb +17 -16
  75. data/lib/kumi/syntax/array_expression.rb +6 -6
  76. data/lib/kumi/syntax/call_expression.rb +4 -4
  77. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  78. data/lib/kumi/syntax/case_expression.rb +4 -4
  79. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  80. data/lib/kumi/syntax/hash_expression.rb +4 -4
  81. data/lib/kumi/syntax/input_declaration.rb +6 -5
  82. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  83. data/lib/kumi/syntax/input_reference.rb +5 -5
  84. data/lib/kumi/syntax/literal.rb +4 -4
  85. data/lib/kumi/syntax/location.rb +5 -0
  86. data/lib/kumi/syntax/node.rb +33 -34
  87. data/lib/kumi/syntax/root.rb +6 -6
  88. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  89. data/lib/kumi/syntax/value_declaration.rb +4 -4
  90. data/lib/kumi/version.rb +1 -1
  91. data/lib/kumi.rb +6 -15
  92. data/scripts/analyze_broadcast_methods.rb +68 -0
  93. data/scripts/analyze_cascade_methods.rb +74 -0
  94. data/scripts/check_broadcasting_coverage.rb +51 -0
  95. data/scripts/find_dead_code.rb +114 -0
  96. metadata +36 -9
  97. data/docs/features/array-broadcasting.md +0 -170
  98. data/lib/kumi/cli.rb +0 -449
  99. data/lib/kumi/core/compiled_schema.rb +0 -43
  100. data/lib/kumi/core/evaluation_wrapper.rb +0 -40
  101. data/lib/kumi/core/schema_instance.rb +0 -111
  102. data/lib/kumi/core/vectorization_metadata.rb +0 -110
  103. data/migrate_to_core_iterative.rb +0 -938
@@ -0,0 +1,349 @@
1
+ # Hierarchical Broadcasting
2
+
3
+ Automatic vectorization of operations over hierarchical data structures with two complementary access modes for different use cases.
4
+
5
+ ## Overview
6
+
7
+ The hierarchical broadcasting system enables natural field access syntax that automatically applies operations element-wise across complex nested data structures, with intelligent detection of map vs reduce operations and support for both structured objects and raw multi-dimensional arrays.
8
+
9
+ ## Core Mechanism
10
+
11
+ The system uses a three-stage pipeline:
12
+
13
+ 1. **Parser** - Creates InputElementReference AST nodes for nested field access
14
+ 2. **BroadcastDetector** - Identifies which operations should be vectorized vs scalar
15
+ 3. **Compiler** - Generates appropriate map/reduce functions based on usage context
16
+
17
+ ## Access Modes
18
+
19
+ The system supports two complementary access modes that can be mixed within the same schema:
20
+
21
+ ### Object Access Mode (Default)
22
+ For structured business data with named fields:
23
+
24
+ ```ruby
25
+ input do
26
+ array :users do
27
+ string :name
28
+ integer :age
29
+ end
30
+ end
31
+
32
+ value :user_names, input.users.name # Broadcasts over structured objects
33
+ trait :adults, input.users.age >= 18 # Boolean array from age comparison
34
+ ```
35
+
36
+ ### Element Access Mode
37
+ For multi-dimensional raw arrays using `element` syntax with **progressive path traversal**:
38
+
39
+ ```ruby
40
+ input do
41
+ array :cube do
42
+ element :array, :layer do
43
+ element :array, :row do
44
+ element :integer, :cell
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Progressive path traversal - each level goes deeper
51
+ value :dimensions, [
52
+ fn(:size, input.cube), # Number of layers
53
+ fn(:size, input.cube.layer), # Total matrices across all layers
54
+ fn(:size, input.cube.layer.row), # Total rows across all matrices
55
+ fn(:size, input.cube.layer.row.cell) # Total cells (leaf elements)
56
+ ]
57
+
58
+ # Operations at different dimensional levels
59
+ value :layer_data, input.cube.layer # 3D matrices
60
+ value :row_data, input.cube.layer.row # 2D rows
61
+ value :cell_data, input.cube.layer.row.cell # 1D values
62
+ ```
63
+
64
+ **Key Benefits of Progressive Path Traversal:**
65
+ - **Intuitive syntax**: `input.cube.layer.row` naturally represents "rows within layers within cube"
66
+ - **Direct dimensional access**: No need for complex flattening operations for basic dimensional analysis
67
+ - **Ranked polymorphism**: Same operations work across different dimensional arrays
68
+ - **Clean code**: `fn(:size, input.cube.layer.row)` instead of `fn(:size, fn(:flatten_one, input.cube.layer))`
69
+
70
+ ## Business Use Cases
71
+
72
+ Element access mode is essential for common business scenarios involving simple nested arrays:
73
+
74
+ ```ruby
75
+ # E-commerce: Product availability analysis
76
+ input do
77
+ array :products do
78
+ string :name
79
+ array :daily_sales do # Simple array: [12, 8, 15, 3]
80
+ element :integer, :units
81
+ end
82
+ end
83
+ end
84
+
85
+ # Create flags for business rules
86
+ trait :had_slow_days, fn(:any?, input.products.daily_sales.units < 5)
87
+ trait :consistently_strong, fn(:all?, input.products.daily_sales.units >= 10)
88
+
89
+ # Progressive analysis
90
+ value :sales_summary, [
91
+ fn(:size, input.products), # Number of products
92
+ fn(:size, input.products.daily_sales.units), # Total sales data points
93
+ fn(:sum, fn(:flatten, input.products.daily_sales.units)) # Total units sold
94
+ ]
95
+ ```
96
+
97
+ ### Mixed Access Modes
98
+ Both modes work together seamlessly:
99
+
100
+ ```ruby
101
+ input do
102
+ array :users do # Object access
103
+ string :name
104
+ integer :age
105
+ end
106
+ array :activity_logs do # Element access
107
+ element :integer, :day
108
+ end
109
+ end
110
+
111
+ value :user_count, fn(:size, input.users)
112
+ value :total_activity_days, fn(:size, fn(:flatten, input.activity_logs.day))
113
+ ```
114
+
115
+ ## Basic Broadcasting (Object Access)
116
+
117
+ ```ruby
118
+ schema do
119
+ input do
120
+ array :line_items do
121
+ float :price
122
+ integer :quantity
123
+ string :category
124
+ end
125
+ float :tax_rate, type: :float
126
+ end
127
+
128
+ # Element-wise computation - broadcasts over each item
129
+ value :subtotals, input.line_items.price * input.line_items.quantity
130
+
131
+ # Element-wise traits - applied to each item
132
+ trait :is_taxable, (input.line_items.category != "digital")
133
+
134
+ # Conditional logic - element-wise evaluation
135
+ value :taxes, fn(:if, is_taxable, subtotals * input.tax_rate, 0.0)
136
+ end
137
+ ```
138
+
139
+ ## Aggregation Operations
140
+
141
+ Operations that consume arrays to produce scalars are automatically detected:
142
+
143
+ ```ruby
144
+ schema do
145
+ # These aggregate the vectorized results
146
+ value :total_subtotal, fn(:sum, subtotals)
147
+ value :total_tax, fn(:sum, taxes)
148
+ value :grand_total, total_subtotal + total_tax
149
+
150
+ # Statistics over arrays
151
+ value :avg_price, fn(:avg, input.line_items.price)
152
+ value :max_quantity, fn(:max, input.line_items.quantity)
153
+ end
154
+ ```
155
+
156
+ ## Field Access Nesting
157
+
158
+ Supports arbitrary depth field access with path building:
159
+
160
+ ```ruby
161
+ schema do
162
+ input do
163
+ array :orders do
164
+ array :items do
165
+ hash :product do
166
+ string :name
167
+ float :base_price
168
+ end
169
+ integer :quantity
170
+ end
171
+ end
172
+ end
173
+
174
+ # Deep field access - automatically broadcasts over nested arrays
175
+ value :all_product_names, input.orders.items.product.name
176
+ value :total_values, input.orders.items.product.base_price * input.orders.items.quantity
177
+ end
178
+ ```
179
+
180
+ ## Type Inference
181
+
182
+ The type system automatically infers appropriate types for broadcasted operations:
183
+
184
+ - `input.items.price` (float array) → inferred as `:float` per element
185
+ - `input.items.price * input.items.quantity` → element-wise `:float` result
186
+ - `fn(:sum, input.items.price)` → scalar `:float` result
187
+
188
+ ## Implementation Details
189
+
190
+ ### Parser Layer
191
+ - **InputFieldProxy** - Handles `input.field.subfield...` with path building
192
+ - **InputElementReference** - AST node representing array field access paths
193
+
194
+ ### Analysis Layer
195
+ - **BroadcastDetector** - Identifies vectorized vs scalar operations
196
+ - **TypeInferencerPass** - Infers types for array element access patterns
197
+
198
+ ### Compilation Layer
199
+ - **Automatic Dispatch** - Maps element-wise operations to array map functions
200
+ - **Reduction Detection** - Converts aggregation functions to array reduce operations
201
+
202
+ ## Usage Patterns
203
+
204
+ ### Element-wise Operations
205
+ ```ruby
206
+ # All of these broadcast element-wise
207
+ value :discounted_prices, input.items.price * 0.9
208
+ trait :expensive, (input.items.price > 100.0)
209
+ value :categories, input.items.category
210
+ ```
211
+
212
+ ### Aggregation Operations
213
+ ```ruby
214
+ # These consume arrays to produce scalars
215
+ value :item_count, fn(:size, input.items)
216
+ value :total_price, fn(:sum, input.items.price)
217
+ value :has_expensive, fn(:any?, expensive)
218
+ ```
219
+
220
+ ### Mixed Operations
221
+ ```ruby
222
+ # Element-wise computation followed by aggregation
223
+ value :line_totals, input.items.price * input.items.quantity
224
+ value :order_total, fn(:sum, line_totals)
225
+ value :avg_line_total, fn(:avg, line_totals)
226
+ ```
227
+
228
+ ### Conditional Aggregation Functions
229
+
230
+ Kumi provides powerful conditional aggregation functions that make working with vectorized traits clean and intuitive:
231
+
232
+ ```ruby
233
+ # Step 1: Create vectorized values and traits
234
+ value :revenues, input.sales.price * input.sales.quantity # → [3000.0, 2000.0, 1250.0]
235
+ trait :expensive, input.sales.price > 100 # → [true, true, false]
236
+ trait :bulk_order, input.sales.quantity >= 15 # → [false, false, true]
237
+
238
+ # Step 2: Use conditional aggregation functions (NEW - clean and readable)
239
+ value :expensive_count, fn(:count_if, expensive) # → 2
240
+ value :expensive_total, fn(:sum_if, revenues, expensive) # → 5000.0
241
+ value :expensive_average, fn(:avg_if, revenues, expensive) # → 2500.0
242
+
243
+ # Step 3: Combine conditions for complex filtering
244
+ trait :expensive_bulk, expensive & bulk_order # → [false, false, false]
245
+ value :expensive_bulk_total, fn(:sum_if, revenues, expensive_bulk) # → 0.0
246
+ ```
247
+
248
+ **Old cascade pattern** (verbose, harder to read):
249
+ ```ruby
250
+ # OLD WAY - required verbose cascade patterns
251
+ value :expensive_amounts do
252
+ on expensive, revenues
253
+ base 0.0
254
+ end
255
+ value :expensive_total, fn(:sum, expensive_amounts)
256
+
257
+ value :expensive_count_markers do
258
+ on expensive, 1.0
259
+ base 0.0
260
+ end
261
+ value :expensive_count, fn(:sum, expensive_count_markers)
262
+ ```
263
+
264
+ **New conditional functions** (clean, direct):
265
+ ```ruby
266
+ # NEW WAY - direct and readable
267
+ value :expensive_total, fn(:sum_if, revenues, expensive)
268
+ value :expensive_count, fn(:count_if, expensive)
269
+ value :expensive_average, fn(:avg_if, revenues, expensive)
270
+ ```
271
+
272
+ The conditional aggregation functions are now the idiomatic way to work with boolean arrays in Kumi.
273
+
274
+ ## Common Patterns and Gotchas
275
+
276
+ ### Operator Precedence with Mixed Arithmetic
277
+
278
+ **Problem**: Complex arithmetic expressions with arrays can fail due to precedence parsing:
279
+
280
+ ```ruby
281
+ # This fails with confusing error message
282
+ value :ones, input.items.price * 0 + 1
283
+ # Error: "no implicit conversion of Integer into Array"
284
+
285
+ # The expression is parsed as: (input.items.price * 0) + 1
286
+ # Which becomes: [0.0, 0.0, 0.0] + 1 (array + scalar in wrong context)
287
+ ```
288
+
289
+ **Solutions**:
290
+
291
+ ```ruby
292
+ # Use explicit function calls
293
+ value :ones, fn(:add, input.items.price * 0, 1)
294
+
295
+ # Use cascade pattern (most idiomatic)
296
+ trait :always_true, input.items.price >= 0
297
+ value :ones do
298
+ on always_true, 1.0
299
+ base 0.0
300
+ end
301
+
302
+ # Use separate steps
303
+ value :zeros, input.items.price * 0
304
+ value :ones, zeros + 1
305
+ ```
306
+
307
+ The cascade pattern is preferred as it's more explicit about intent and leverages Kumi's automatic scalar broadcasting.
308
+
309
+ ## Error Handling
310
+
311
+ ### Dimension Mismatch Detection
312
+
313
+ Array broadcasting operations are only valid within the same array source. Attempting to broadcast across different arrays generates detailed error messages:
314
+
315
+ ```ruby
316
+ schema do
317
+ input do
318
+ array :items do
319
+ string :name
320
+ end
321
+ array :logs do
322
+ string :user_name
323
+ end
324
+ end
325
+
326
+ # This will generate a dimension mismatch error
327
+ trait :same_name, input.items.name == input.logs.user_name
328
+ end
329
+
330
+ # Error:
331
+ # Cannot broadcast operation across arrays from different sources: items, logs.
332
+ # Problem: Multiple operands are arrays from different sources:
333
+ # - Operand 1 resolves to array(string) from array 'items'
334
+ # - Operand 2 resolves to array(string) from array 'logs'
335
+ # Direct operations on arrays from different sources is ambiguous and not supported.
336
+ # Vectorized operations can only work on fields from the same array input.
337
+ ```
338
+
339
+ The error messages provide:
340
+ - **Quick Summary**: Identifies the conflicting array sources
341
+ - **Type Information**: Shows the resolved types of each operand
342
+ - **Clear Explanation**: Why the operation is ambiguous and not supported
343
+
344
+ ## Performance Characteristics
345
+
346
+ - **Single Pass** - Each array is traversed once per computation chain
347
+ - **Lazy Evaluation** - Operations are composed into pipelines
348
+ - **Memory Efficient** - No intermediate array allocations for simple operations
349
+ - **Type Safe** - Full compile-time type checking for array element operations
@@ -0,0 +1,148 @@
1
+ # JavaScript Transpiler
2
+
3
+ Transpiles compiled schemas to standalone JavaScript code.
4
+
5
+ ## Usage
6
+
7
+ ### Export Schema
8
+
9
+ ```ruby
10
+ class TaxCalculator
11
+ extend Kumi::Schema
12
+
13
+ schema do
14
+ input do
15
+ float :income
16
+ string :filing_status
17
+ end
18
+
19
+ trait :single, input.filing_status == "single"
20
+
21
+ value :std_deduction do
22
+ on single, 14_600
23
+ base 29_200
24
+ end
25
+
26
+ value :taxable_income, fn(:max, [input.income - std_deduction, 0])
27
+ value :tax_owed, taxable_income * 0.22
28
+ end
29
+ end
30
+
31
+ Kumi::Js.export_to_file(TaxCalculator, "tax-calculator.js")
32
+ ```
33
+
34
+ ### Use in JavaScript
35
+
36
+ ```javascript
37
+ const { schema } = require('./tax-calculator.js');
38
+
39
+ const taxpayer = {
40
+ income: 75000,
41
+ filing_status: "single"
42
+ };
43
+
44
+ const calculator = schema.from(taxpayer);
45
+ console.log(calculator.fetch('tax_owed'));
46
+
47
+ const results = calculator.slice('taxable_income', 'tax_owed');
48
+ ```
49
+
50
+ ## Export Methods
51
+
52
+ ### Command Line
53
+
54
+ ```bash
55
+ bundle exec kumi --export-js output.js SchemaClass
56
+ ```
57
+
58
+ ### Programmatic
59
+
60
+ ```ruby
61
+ Kumi::Js.export_to_file(MySchema, "schema.js")
62
+
63
+ js_code = Kumi::Js.compile(MySchema)
64
+ File.write("output.js", js_code)
65
+ ```
66
+
67
+ ## JavaScript API
68
+
69
+ ### schema.from(input)
70
+
71
+ Creates runner instance.
72
+
73
+ ```javascript
74
+ const runner = schema.from({ income: 50000, status: "single" });
75
+ ```
76
+
77
+ ### runner.fetch(key)
78
+
79
+ Returns computed value. Results are cached.
80
+
81
+ ```javascript
82
+ const tax = runner.fetch('tax_owed');
83
+ ```
84
+
85
+ ### runner.slice(...keys)
86
+
87
+ Returns multiple values.
88
+
89
+ ```javascript
90
+ const results = runner.slice('taxable_income', 'tax_owed');
91
+ // Returns: { taxable_income: 35400, tax_owed: 7788 }
92
+ ```
93
+
94
+ ### runner.functionsUsed
95
+
96
+ Array of functions used by the schema.
97
+
98
+ ```javascript
99
+ console.log(runner.functionsUsed); // ["max", "subtract", "multiply"]
100
+ ```
101
+
102
+ ## Function Optimization
103
+
104
+ The transpiler only includes functions actually used by the schema.
105
+
106
+ Example schema using 4 functions generates ~3 KB instead of ~8 KB with all 67 functions.
107
+
108
+ ## Browser Compatibility
109
+
110
+ - ES6+ (Chrome 60+, Firefox 55+, Safari 10+)
111
+ - Modern bundlers (Webpack, Rollup, Vite)
112
+ - Node.js 12+
113
+
114
+ ## Limitations
115
+
116
+ - No `explain()` method (Ruby only)
117
+ - Custom Ruby functions need JavaScript equivalents
118
+
119
+ ## Module Formats
120
+
121
+ Generated JavaScript supports:
122
+ - CommonJS (`require()`)
123
+ - ES Modules (`import`)
124
+ - Global variables (browser)
125
+
126
+ ## Minification
127
+
128
+ Use production minifiers like Terser or UglifyJS for smaller bundles.
129
+
130
+ ## Dual Mode Validation
131
+
132
+ Set `KUMI_DUAL_MODE=true` to automatically execute both Ruby and JavaScript versions and validate they produce identical results:
133
+
134
+ ```bash
135
+ KUMI_DUAL_MODE=true ruby my_script.rb
136
+ ```
137
+
138
+ Every calculation is validated in real-time. Mismatches throw detailed error reports with both results for debugging.
139
+
140
+ ## Error Handling
141
+
142
+ ```javascript
143
+ try {
144
+ const runner = schema.from({ invalid: "data" });
145
+ } catch (error) {
146
+ console.error(error.message);
147
+ }
148
+ ```
@@ -1,8 +1,6 @@
1
1
  # Performance
2
2
 
3
- TODO: Add benchmark data
4
-
5
- Processes large schemas with optimized algorithms for analysis, compilation, and execution.
3
+ Analysis, compilation, and execution performance for large schemas.
6
4
 
7
5
  ## Execution Model
8
6
 
@@ -42,7 +42,7 @@ The printer produces indented S-expressions that clearly show the hierarchical s
42
42
  (InputDeclaration :age :integer)
43
43
  (InputDeclaration :name :string)
44
44
  ]
45
- attributes: [
45
+ values: [
46
46
  (ValueDeclaration :greeting
47
47
  (CallExpression :concat
48
48
  (Literal "Hello ")
@@ -65,7 +65,7 @@ The printer produces indented S-expressions that clearly show the hierarchical s
65
65
 
66
66
  The printer handles all Kumi AST node types:
67
67
 
68
- - **Root** - Schema container with inputs, attributes, and traits
68
+ - **Root** - Schema container with inputs, values, and traits
69
69
  - **Declarations** - InputDeclaration, ValueDeclaration, TraitDeclaration
70
70
  - **Expressions** - CallExpression, ArrayExpression, CascadeExpression, CaseExpression
71
71
  - **References** - InputReference, InputElementReference, DeclarationReference
@@ -1,6 +1,6 @@
1
1
  # Schema Metadata
2
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.
3
+ Kumi's SchemaMetadata interface accesses analyzed schema information for building external tools like form generators, documentation systems, and analysis utilities.
4
4
 
5
5
  ## Primary Interface
6
6
 
@@ -10,7 +10,7 @@ SchemaMetadata is the main interface for extracting metadata from Kumi schemas:
10
10
  metadata = MySchema.schema_metadata
11
11
  ```
12
12
 
13
- See the comprehensive API documentation in the SchemaMetadata class for detailed method documentation, examples, and usage patterns.
13
+ See the API documentation in the SchemaMetadata class for method documentation, examples, and usage patterns.
14
14
 
15
15
  ## Processed Metadata (Tool-Friendly)
16
16
 
@@ -83,24 +83,24 @@ metadata.values
83
83
  # }
84
84
  ```
85
85
 
86
- ### Clean Public Interface Examples
86
+ ### Public Interface Examples
87
87
  ```ruby
88
- # Processed dependency information (clean hashes)
88
+ # Processed dependency information
89
89
  metadata.dependencies
90
90
  # => { :tax_amount => [{ to: :income, conditional: false }, { to: :tax_rate, conditional: false }] }
91
91
 
92
- # Processed declaration metadata (clean hashes)
92
+ # Processed declaration metadata
93
93
  metadata.declarations
94
94
  # => { :adult => { type: :trait, expression: ">=(input.age, 18)" }, :tax_amount => { type: :value, expression: "multiply(input.income, tax_rate)" } }
95
95
 
96
- # Type inference results (clean data)
96
+ # Type inference results
97
97
  metadata.inferred_types
98
98
  # => { :adult => :boolean, :tax_amount => :float, :item_totals => { array: :float } }
99
99
  ```
100
100
 
101
101
  ### Raw Analyzer State (Advanced Usage)
102
102
  ```ruby
103
- # Complete raw state hash with internal objects (AST nodes, Edge objects)
103
+ # Raw state hash with internal objects (AST nodes, Edge objects)
104
104
  metadata.analyzer_state
105
105
  # => { declarations: {AST nodes...}, dependencies: {Edge objects...}, ... }
106
106
  ```
@@ -86,21 +86,27 @@ puts
86
86
  # ------------------------------------------------------------------
87
87
  Benchmark.ips do |x|
88
88
  schemas.each do |d, schema|
89
- runner = schema.from(seed: 0) # memoised runner
90
- x.report("eval #{d}-deep") { runner[:final_result] }
89
+ # 1) HOT (memoized): expect ~flat, nanosecond-level if cached
90
+ hot = schema.from(seed: 0)
91
+ x.report("HOT fetch #{d}-deep") do
92
+ hot[:final_result]
93
+ end
94
+
95
+ # 2) COLD via UPDATE (no memoized result): change a dependent input each iter
96
+ upd = schema.from(seed: 0)
97
+ i = 0
98
+ x.report("COLD update #{d}-deep") do
99
+ i += 1
100
+ upd.update(seed: i) # invalidates v0..vN; forces recompute
101
+ upd[:final_result]
102
+ end
103
+
104
+ # 3) COLD new runner (includes construction)
105
+ prng = Random.new(42)
106
+ x.report("COLD new #{d}-deep") do
107
+ r = schema.from(seed: prng.rand(1_000_000))
108
+ r[:final_result]
109
+ end
91
110
  end
92
111
  x.compare!
93
112
  end
94
- # Warming up --------------------------------------
95
- # eval 50-deep 222.000 i/100ms
96
- # eval 100-deep 57.000 i/100ms
97
- # eval 150-deep 26.000 i/100ms
98
- # Calculating -------------------------------------
99
- # eval 50-deep 2.166k (± 1.9%) i/s (461.70 μs/i) - 10.878k in 5.024320s
100
- # eval 100-deep 561.698 (± 1.4%) i/s (1.78 ms/i) - 2.850k in 5.075057s
101
- # eval 150-deep 253.732 (± 0.8%) i/s (3.94 ms/i) - 1.274k in 5.021499s
102
-
103
- # Comparison:
104
- # eval 50-deep: 2165.9 i/s
105
- # eval 100-deep: 561.7 i/s - 3.86x slower
106
- # eval 150-deep: 253.7 i/s - 8.54x slower
@@ -1,19 +1,17 @@
1
- $LOAD_PATH.unshift(File.join(__dir__, "..", "lib"))
2
1
  require "kumi"
3
2
 
4
- NEIGHBOR_DELTAS = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
5
3
  begin
6
4
  # in a block so we dont define this globally
7
5
  def neighbor_cells_sum_method(cells, row, col, height, width)
8
6
  # Calculate neighbor indices with wraparound
9
- NEIGHBOR_DELTAS.map do |dr, dc|
7
+ [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]].map do |dr, dc|
10
8
  neighbor_row = (row + dr) % height
11
9
  neighbor_col = (col + dc) % width
12
10
  neighbor_index = (neighbor_row * width) + neighbor_col
13
11
  cells[neighbor_index]
14
12
  end.sum
15
13
  end
16
- Kumi::Core::FunctionRegistry.register_with_metadata(:neighbor_cells_sum, method(:neighbor_cells_sum_method),
14
+ Kumi::Registry.register_with_metadata(:neighbor_cells_sum, method(:neighbor_cells_sum_method),
17
15
  return_type: :integer, arity: 5,
18
16
  param_types: %i[array integer integer integer integer],
19
17
  description: "Get neighbor cells for Conway's Game of Life")