kumi 0.0.5 → 0.0.6
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.
- checksums.yaml +4 -4
- data/CLAUDE.md +51 -6
- data/README.md +173 -51
- data/{documents → docs}/AST.md +29 -29
- data/{documents → docs}/SYNTAX.md +93 -1
- data/docs/features/README.md +45 -0
- data/docs/features/analysis-cascade-mutual-exclusion.md +89 -0
- data/docs/features/analysis-type-inference.md +42 -0
- data/docs/features/analysis-unsat-detection.md +71 -0
- data/docs/features/array-broadcasting.md +170 -0
- data/docs/features/input-declaration-system.md +42 -0
- data/docs/features/performance.md +16 -0
- data/examples/federal_tax_calculator_2024.rb +11 -6
- data/lib/kumi/analyzer/constant_evaluator.rb +1 -1
- data/lib/kumi/analyzer/passes/broadcast_detector.rb +251 -0
- data/lib/kumi/analyzer/passes/{definition_validator.rb → declaration_validator.rb} +4 -4
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +72 -32
- data/lib/kumi/analyzer/passes/input_collector.rb +90 -29
- data/lib/kumi/analyzer/passes/pass_base.rb +1 -1
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +9 -9
- data/lib/kumi/analyzer/passes/toposorter.rb +42 -6
- data/lib/kumi/analyzer/passes/type_checker.rb +32 -10
- data/lib/kumi/analyzer/passes/type_inferencer.rb +126 -17
- data/lib/kumi/analyzer/passes/unsat_detector.rb +133 -53
- data/lib/kumi/analyzer/passes/visitor_pass.rb +2 -2
- data/lib/kumi/analyzer.rb +11 -12
- data/lib/kumi/compiler.rb +194 -16
- data/lib/kumi/constraint_relationship_solver.rb +6 -6
- data/lib/kumi/domain/validator.rb +0 -4
- data/lib/kumi/explain.rb +20 -20
- data/lib/kumi/export/node_registry.rb +26 -12
- data/lib/kumi/export/node_serializers.rb +1 -1
- data/lib/kumi/function_registry/collection_functions.rb +14 -9
- data/lib/kumi/function_registry/function_builder.rb +4 -3
- data/lib/kumi/function_registry.rb +8 -2
- data/lib/kumi/input/type_matcher.rb +3 -0
- data/lib/kumi/input/validator.rb +0 -3
- data/lib/kumi/parser/declaration_reference_proxy.rb +36 -0
- data/lib/kumi/parser/dsl_cascade_builder.rb +3 -3
- data/lib/kumi/parser/expression_converter.rb +6 -6
- data/lib/kumi/parser/input_builder.rb +40 -9
- data/lib/kumi/parser/input_field_proxy.rb +46 -0
- data/lib/kumi/parser/input_proxy.rb +3 -3
- data/lib/kumi/parser/nested_input.rb +15 -0
- data/lib/kumi/parser/schema_builder.rb +10 -9
- data/lib/kumi/parser/sugar.rb +61 -9
- data/lib/kumi/syntax/array_expression.rb +15 -0
- data/lib/kumi/syntax/call_expression.rb +11 -0
- data/lib/kumi/syntax/cascade_expression.rb +11 -0
- data/lib/kumi/syntax/case_expression.rb +11 -0
- data/lib/kumi/syntax/declaration_reference.rb +11 -0
- data/lib/kumi/syntax/hash_expression.rb +11 -0
- data/lib/kumi/syntax/input_declaration.rb +12 -0
- data/lib/kumi/syntax/input_element_reference.rb +12 -0
- data/lib/kumi/syntax/input_reference.rb +12 -0
- data/lib/kumi/syntax/literal.rb +11 -0
- data/lib/kumi/syntax/trait_declaration.rb +11 -0
- data/lib/kumi/syntax/value_declaration.rb +11 -0
- data/lib/kumi/vectorization_metadata.rb +108 -0
- data/lib/kumi/version.rb +1 -1
- metadata +31 -14
- data/lib/kumi/domain.rb +0 -8
- data/lib/kumi/input.rb +0 -8
- data/lib/kumi/syntax/declarations.rb +0 -26
- data/lib/kumi/syntax/expressions.rb +0 -34
- data/lib/kumi/syntax/terminal_expressions.rb +0 -30
- data/lib/kumi/syntax.rb +0 -9
- /data/{documents → docs}/DSL.md +0 -0
- /data/{documents → docs}/FUNCTIONS.md +0 -0
@@ -10,6 +10,7 @@ This document provides a comprehensive comparison of Kumi's DSL syntax showing b
|
|
10
10
|
- [Trait Declarations](#trait-declarations)
|
11
11
|
- [Expressions](#expressions)
|
12
12
|
- [Functions](#functions)
|
13
|
+
- [Array Broadcasting](#array-broadcasting)
|
13
14
|
- [Cascade Logic](#cascade-logic)
|
14
15
|
- [References](#references)
|
15
16
|
|
@@ -187,7 +188,7 @@ value :sorted_scores, fn(:sort, input.score_array)
|
|
187
188
|
|
188
189
|
### Built-in Functions Available
|
189
190
|
|
190
|
-
See [FUNCTIONS.md](
|
191
|
+
See [FUNCTIONS.md](FUNCTIONS.md)
|
191
192
|
|
192
193
|
| Category | Sugar | Sugar-Free |
|
193
194
|
|----------|-------|------------|
|
@@ -210,6 +211,97 @@ value :clamped_score, fn(:clamp, input.raw_score, 0, 1600)
|
|
210
211
|
value :formatted_name, fn(:add, fn(:add, input.first_name, " "), input.last_name)
|
211
212
|
```
|
212
213
|
|
214
|
+
## Array Broadcasting
|
215
|
+
|
216
|
+
Array broadcasting enables element-wise operations on array fields with automatic vectorization.
|
217
|
+
|
218
|
+
### Array Input Declarations
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
input do
|
222
|
+
array :line_items do
|
223
|
+
float :price
|
224
|
+
integer :quantity
|
225
|
+
string :category
|
226
|
+
end
|
227
|
+
|
228
|
+
array :orders do
|
229
|
+
array :items do
|
230
|
+
hash :product do
|
231
|
+
string :name
|
232
|
+
float :base_price
|
233
|
+
end
|
234
|
+
integer :quantity
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
### Element-wise Operations
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
# With Sugar - Automatic Broadcasting
|
244
|
+
value :subtotals, input.line_items.price * input.line_items.quantity
|
245
|
+
trait :is_taxable, (input.line_items.category != "digital")
|
246
|
+
value :discounted_prices, input.line_items.price * 0.9
|
247
|
+
|
248
|
+
# Sugar-Free - Explicit Broadcasting
|
249
|
+
value :subtotals, fn(:multiply, input.line_items.price, input.line_items.quantity)
|
250
|
+
trait :is_taxable, fn(:!=, input.line_items.category, "digital")
|
251
|
+
value :discounted_prices, fn(:multiply, input.line_items.price, 0.9)
|
252
|
+
```
|
253
|
+
|
254
|
+
### Aggregation Operations
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
# With Sugar - Automatic Aggregation Detection
|
258
|
+
value :total_subtotal, fn(:sum, subtotals)
|
259
|
+
value :avg_price, fn(:avg, input.line_items.price)
|
260
|
+
value :max_quantity, fn(:max, input.line_items.quantity)
|
261
|
+
value :item_count, fn(:size, input.line_items)
|
262
|
+
|
263
|
+
# Sugar-Free - Same Syntax
|
264
|
+
value :total_subtotal, fn(:sum, subtotals)
|
265
|
+
value :avg_price, fn(:avg, input.line_items.price)
|
266
|
+
value :max_quantity, fn(:max, input.line_items.quantity)
|
267
|
+
value :item_count, fn(:size, input.line_items)
|
268
|
+
```
|
269
|
+
|
270
|
+
### Nested Array Access
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
# With Sugar - Deep Field Access
|
274
|
+
value :all_product_names, input.orders.items.product.name
|
275
|
+
value :total_values, input.orders.items.product.base_price * input.orders.items.quantity
|
276
|
+
|
277
|
+
# Sugar-Free - Same Deep Access
|
278
|
+
value :all_product_names, input.orders.items.product.name
|
279
|
+
value :total_values, fn(:multiply, input.orders.items.product.base_price, input.orders.items.quantity)
|
280
|
+
```
|
281
|
+
|
282
|
+
### Mixed Operations
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
# With Sugar - Element-wise then Aggregation
|
286
|
+
value :line_totals, input.items.price * input.items.quantity
|
287
|
+
value :order_total, fn(:sum, line_totals)
|
288
|
+
value :avg_line_total, fn(:avg, line_totals)
|
289
|
+
trait :has_expensive, fn(:any?, expensive_items)
|
290
|
+
|
291
|
+
# Sugar-Free - Same Pattern
|
292
|
+
value :line_totals, fn(:multiply, input.items.price, input.items.quantity)
|
293
|
+
value :order_total, fn(:sum, line_totals)
|
294
|
+
value :avg_line_total, fn(:avg, line_totals)
|
295
|
+
trait :has_expensive, fn(:any?, expensive_items)
|
296
|
+
```
|
297
|
+
|
298
|
+
### Broadcasting Type Inference
|
299
|
+
|
300
|
+
The type system automatically infers appropriate types:
|
301
|
+
- `input.items.price` (float array) → inferred as `:float` per element
|
302
|
+
- `input.items.price * input.items.quantity` → element-wise `:float` result
|
303
|
+
- `fn(:sum, input.items.price)` → scalar `:float` result
|
304
|
+
|
213
305
|
## Cascade Logic
|
214
306
|
|
215
307
|
Cascades are similar to Ruby when case, where each case is one of more trait reference and finally the value if that branch is true.
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Features
|
2
|
+
|
3
|
+
## Core Features
|
4
|
+
|
5
|
+
### [Unsatisfiability Detection](analysis-unsat-detection.md)
|
6
|
+
Analyzes rule combinations to detect logical impossibilities across dependency chains.
|
7
|
+
|
8
|
+
- Detects impossible combinations at compile-time
|
9
|
+
- Validates domain constraints
|
10
|
+
- Reports multiple errors
|
11
|
+
|
12
|
+
### [Cascade Mutual Exclusion](analysis-cascade-mutual-exclusion.md)
|
13
|
+
Enables safe mutual recursion when cascade conditions are mutually exclusive.
|
14
|
+
|
15
|
+
- Allows mathematically sound recursive patterns
|
16
|
+
- Detects mutually exclusive conditions
|
17
|
+
- Prevents unsafe cycles while enabling safe ones
|
18
|
+
|
19
|
+
### [Type Inference](analysis-type-inference.md)
|
20
|
+
Determines types from expressions and propagates them through dependencies.
|
21
|
+
|
22
|
+
- Infers types from literals and function calls
|
23
|
+
- Propagates types through dependency graph
|
24
|
+
- Validates function arguments
|
25
|
+
|
26
|
+
### [Input Declarations](input-declaration-system.md)
|
27
|
+
Defines expected inputs with types and constraints.
|
28
|
+
|
29
|
+
- Type-specific declaration methods
|
30
|
+
- Domain validation at runtime
|
31
|
+
- Separates input metadata from business logic
|
32
|
+
|
33
|
+
### [Performance](performance.md)
|
34
|
+
TODO: Add benchmark data
|
35
|
+
Processes large schemas with optimized algorithms.
|
36
|
+
|
37
|
+
- Result caching
|
38
|
+
- Selective evaluation
|
39
|
+
|
40
|
+
## Integration
|
41
|
+
|
42
|
+
- Type inference uses input declarations
|
43
|
+
- Unsatisfiability detection uses type information for validation
|
44
|
+
- Cascade mutual exclusion integrates with dependency analysis and cycle detection
|
45
|
+
- Performance optimizations apply to all analysis passes
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Cascade Mutual Exclusion Detection
|
2
|
+
|
3
|
+
Analyzes cascade expressions to allow safe recursive patterns when conditions are mutually exclusive.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The cascade mutual exclusion detector identifies when all conditions in a cascade expression cannot be true simultaneously, enabling safe mutual recursion patterns that would otherwise be rejected as cycles.
|
8
|
+
|
9
|
+
## Core Mechanism
|
10
|
+
|
11
|
+
The system performs three-stage analysis:
|
12
|
+
|
13
|
+
1. **Conditional Dependency Tracking** - DependencyResolver marks base case dependencies as conditional
|
14
|
+
2. **Mutual Exclusion Analysis** - UnsatDetector determines if cascade conditions are mutually exclusive
|
15
|
+
3. **Safe Cycle Detection** - Toposorter allows cycles where all edges are conditional and conditions are mutually exclusive
|
16
|
+
|
17
|
+
## Example: Processing Workflow
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
schema do
|
21
|
+
input do
|
22
|
+
string :operation # "forward", "reverse", "unknown"
|
23
|
+
integer :value
|
24
|
+
end
|
25
|
+
|
26
|
+
trait :is_forward, input.operation == "forward"
|
27
|
+
trait :is_reverse, input.operation == "reverse"
|
28
|
+
|
29
|
+
# Safe mutual recursion - conditions are mutually exclusive
|
30
|
+
value :forward_processor do
|
31
|
+
on is_forward, input.value * 2 # Direct calculation
|
32
|
+
on is_reverse, reverse_processor + 10 # Delegates to reverse (safe)
|
33
|
+
base "invalid operation" # Fallback for unknown operations
|
34
|
+
end
|
35
|
+
|
36
|
+
value :reverse_processor do
|
37
|
+
on is_forward, forward_processor - 5 # Delegates to forward (safe)
|
38
|
+
on is_reverse, input.value / 2 # Direct calculation
|
39
|
+
base "invalid operation" # Fallback for unknown operations
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
## Safety Guarantees
|
45
|
+
|
46
|
+
**Allowed**: Cycles where conditions are mutually exclusive
|
47
|
+
- `is_forward` and `is_reverse` cannot both be true (operation has single value)
|
48
|
+
- Each recursion executes exactly one step before hitting direct calculation
|
49
|
+
- Bounded recursion with guaranteed termination
|
50
|
+
|
51
|
+
**Rejected**: Cycles with overlapping conditions
|
52
|
+
```ruby
|
53
|
+
# This would be rejected - conditions can overlap
|
54
|
+
value :unsafe_cycle do
|
55
|
+
on input.n > 0, "positive"
|
56
|
+
on input.n > 5, "large" # Both can be true!
|
57
|
+
base fn(:not, unsafe_cycle)
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
## Implementation Details
|
62
|
+
|
63
|
+
### Conditional Dependencies
|
64
|
+
Base case dependencies are marked as conditional because they only execute when no explicit conditions match.
|
65
|
+
|
66
|
+
### Mutual Exclusion Analysis
|
67
|
+
Conditions are analyzed for mutual exclusion:
|
68
|
+
- Same field equality comparisons: `field == value1` vs `field == value2`
|
69
|
+
- Domain constraints ensuring impossibility
|
70
|
+
- All condition pairs must be mutually exclusive
|
71
|
+
|
72
|
+
### Metadata Generation
|
73
|
+
Analysis results stored in `cascade_metadata` state:
|
74
|
+
```ruby
|
75
|
+
{
|
76
|
+
condition_traits: [:is_forward, :is_reverse],
|
77
|
+
condition_count: 2,
|
78
|
+
all_mutually_exclusive: true,
|
79
|
+
exclusive_pairs: 1,
|
80
|
+
total_pairs: 1
|
81
|
+
}
|
82
|
+
```
|
83
|
+
|
84
|
+
## Use Cases
|
85
|
+
|
86
|
+
- Processing workflows with bidirectional logic
|
87
|
+
- State machine fallback patterns
|
88
|
+
- Recursive decision trees with termination conditions
|
89
|
+
- Complex business rules with safe delegation patterns
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Type Inference
|
2
|
+
|
3
|
+
Infers types from expressions and propagates them through dependency chains for compile-time type checking.
|
4
|
+
|
5
|
+
## Type Inference Rules
|
6
|
+
|
7
|
+
**Literals:**
|
8
|
+
- `42` → `:integer`
|
9
|
+
- `3.14` → `:float`
|
10
|
+
- `"hello"` → `:string`
|
11
|
+
- `true` → `:boolean`
|
12
|
+
- `[1, 2, 3]` → `{ array: :integer }`
|
13
|
+
|
14
|
+
**Operations:**
|
15
|
+
- Integer arithmetic → `:integer`
|
16
|
+
- Mixed numeric operations → `:numeric`
|
17
|
+
- Comparisons → `:boolean`
|
18
|
+
|
19
|
+
**Functions:**
|
20
|
+
- Return types defined in function registry
|
21
|
+
- Arguments validated against parameter types
|
22
|
+
|
23
|
+
## Error Detection
|
24
|
+
|
25
|
+
**Type mismatches:**
|
26
|
+
```
|
27
|
+
TypeError: argument 2 of addition expects numeric, got input field `age` of declared type integer,
|
28
|
+
but argument 1 is input field `customer_name` of declared type string
|
29
|
+
```
|
30
|
+
|
31
|
+
**Function validation:**
|
32
|
+
- Arity validation (correct number of arguments)
|
33
|
+
- Type compatibility validation
|
34
|
+
- Unknown function detection
|
35
|
+
|
36
|
+
## Inference Process
|
37
|
+
|
38
|
+
- Processes declarations in topological order to resolve dependencies
|
39
|
+
- Literal types determined from values
|
40
|
+
- Function return types from function registry
|
41
|
+
- Array types unified from element types
|
42
|
+
- Cascade types inferred from result expressions
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Unsatisfiability Detection
|
2
|
+
|
3
|
+
Analyzes logical relationships across dependency chains to detect impossible rule combinations at compile time.
|
4
|
+
|
5
|
+
## Example: Credit Card Approval System
|
6
|
+
|
7
|
+
This schema contains impossible rule combinations:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
module CreditCardApproval
|
11
|
+
extend Kumi::Schema
|
12
|
+
|
13
|
+
schema do
|
14
|
+
input do
|
15
|
+
integer :annual_income
|
16
|
+
integer :monthly_debt
|
17
|
+
integer :credit_score, domain: 300..850
|
18
|
+
string :employment_type, domain: %w[full_time part_time contract self_employed]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Financial calculations
|
22
|
+
value :monthly_income, input.annual_income / 12
|
23
|
+
value :available_monthly, monthly_income - input.monthly_debt
|
24
|
+
value :credit_limit_base, input.annual_income * 0.3
|
25
|
+
value :score_multiplier, (input.credit_score - 600) * 0.01
|
26
|
+
value :final_credit_limit, credit_limit_base * score_multiplier
|
27
|
+
value :employment_stability_factor,
|
28
|
+
input.employment_type == "full_time" ? 1.0 :
|
29
|
+
input.employment_type == "part_time" ? 0.7 :
|
30
|
+
input.employment_type == "contract" ? 0.5 : 0.3
|
31
|
+
|
32
|
+
# Business rules
|
33
|
+
trait :stable_income, (input.annual_income >= 60_000)
|
34
|
+
trait :good_credit, (input.credit_score >= 700)
|
35
|
+
trait :high_available_income, (available_monthly >= 4_000)
|
36
|
+
trait :premium_limit_qualified, (final_credit_limit >= 50_000)
|
37
|
+
trait :stable_employment, (input.employment_type == "full_time")
|
38
|
+
trait :high_stability_factor, (employment_stability_factor >= 0.8)
|
39
|
+
trait :excellent_credit, (input.credit_score == 900)
|
40
|
+
|
41
|
+
# Approval tiers - which combinations are impossible?
|
42
|
+
value :approval_tier do
|
43
|
+
on stable_income,good_credit,premium_limit_qualified, "platinum_tier"
|
44
|
+
on stable_employment,high_stability_factor, "executive_tier"
|
45
|
+
on excellent_credit,good_credit, "perfect_score_tier"
|
46
|
+
on stable_income,good_credit, "standard_tier"
|
47
|
+
base "manual_review"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
**Detected errors:**
|
54
|
+
|
55
|
+
```
|
56
|
+
SemanticError:
|
57
|
+
conjunction `excellent_credit AND good_credit` is impossible
|
58
|
+
conjunction `excellent_credit` is impossible
|
59
|
+
```
|
60
|
+
|
61
|
+
**Root cause:**
|
62
|
+
- `excellent_credit` requires `credit_score == 900`
|
63
|
+
- Input domain constrains `credit_score` to `300..850`
|
64
|
+
- Any cascade condition using `excellent_credit` becomes impossible
|
65
|
+
|
66
|
+
## Detection Mechanisms
|
67
|
+
|
68
|
+
- Domain constraint violations: `field == value` where value is outside declared domain
|
69
|
+
- Cascade condition analysis: each `on` condition checked independently
|
70
|
+
- OR expression handling: impossible only if both sides are impossible
|
71
|
+
- Cross-variable mathematical constraint analysis
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# Array Broadcasting
|
2
|
+
|
3
|
+
Automatic vectorization of operations over array fields with element-wise computation and aggregation.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The array broadcasting system enables natural field access syntax on array inputs (`input.items.price`) that automatically applies operations element-wise across the array, with intelligent detection of map vs reduce operations.
|
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
|
+
## Basic Broadcasting
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
schema do
|
21
|
+
input do
|
22
|
+
array :line_items do
|
23
|
+
float :price
|
24
|
+
integer :quantity
|
25
|
+
string :category
|
26
|
+
end
|
27
|
+
scalar :tax_rate, type: :float
|
28
|
+
end
|
29
|
+
|
30
|
+
# Element-wise computation - broadcasts over each item
|
31
|
+
value :subtotals, input.line_items.price * input.line_items.quantity
|
32
|
+
|
33
|
+
# Element-wise traits - applied to each item
|
34
|
+
trait :is_taxable, (input.line_items.category != "digital")
|
35
|
+
|
36
|
+
# Conditional logic - element-wise evaluation
|
37
|
+
value :taxes, fn(:if, is_taxable, subtotals * input.tax_rate, 0.0)
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
## Aggregation Operations
|
42
|
+
|
43
|
+
Operations that consume arrays to produce scalars are automatically detected:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
schema do
|
47
|
+
# These aggregate the vectorized results
|
48
|
+
value :total_subtotal, fn(:sum, subtotals)
|
49
|
+
value :total_tax, fn(:sum, taxes)
|
50
|
+
value :grand_total, total_subtotal + total_tax
|
51
|
+
|
52
|
+
# Statistics over arrays
|
53
|
+
value :avg_price, fn(:avg, input.line_items.price)
|
54
|
+
value :max_quantity, fn(:max, input.line_items.quantity)
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
## Field Access Nesting
|
59
|
+
|
60
|
+
Supports arbitrary depth field access with path building:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
schema do
|
64
|
+
input do
|
65
|
+
array :orders do
|
66
|
+
array :items do
|
67
|
+
hash :product do
|
68
|
+
string :name
|
69
|
+
float :base_price
|
70
|
+
end
|
71
|
+
integer :quantity
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Deep field access - automatically broadcasts over nested arrays
|
77
|
+
value :all_product_names, input.orders.items.product.name
|
78
|
+
value :total_values, input.orders.items.product.base_price * input.orders.items.quantity
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
## Type Inference
|
83
|
+
|
84
|
+
The type system automatically infers appropriate types for broadcasted operations:
|
85
|
+
|
86
|
+
- `input.items.price` (float array) → inferred as `:float` per element
|
87
|
+
- `input.items.price * input.items.quantity` → element-wise `:float` result
|
88
|
+
- `fn(:sum, input.items.price)` → scalar `:float` result
|
89
|
+
|
90
|
+
## Implementation Details
|
91
|
+
|
92
|
+
### Parser Layer
|
93
|
+
- **InputFieldProxy** - Handles `input.field.subfield...` with path building
|
94
|
+
- **InputElementReference** - AST node representing array field access paths
|
95
|
+
|
96
|
+
### Analysis Layer
|
97
|
+
- **BroadcastDetector** - Identifies vectorized vs scalar operations
|
98
|
+
- **TypeInferencer** - Infers types for array element access patterns
|
99
|
+
|
100
|
+
### Compilation Layer
|
101
|
+
- **Automatic Dispatch** - Maps element-wise operations to array map functions
|
102
|
+
- **Reduction Detection** - Converts aggregation functions to array reduce operations
|
103
|
+
|
104
|
+
## Usage Patterns
|
105
|
+
|
106
|
+
### Element-wise Operations
|
107
|
+
```ruby
|
108
|
+
# All of these broadcast element-wise
|
109
|
+
value :discounted_prices, input.items.price * 0.9
|
110
|
+
trait :expensive, (input.items.price > 100.0)
|
111
|
+
value :categories, input.items.category
|
112
|
+
```
|
113
|
+
|
114
|
+
### Aggregation Operations
|
115
|
+
```ruby
|
116
|
+
# These consume arrays to produce scalars
|
117
|
+
value :item_count, fn(:size, input.items)
|
118
|
+
value :total_price, fn(:sum, input.items.price)
|
119
|
+
value :has_expensive, fn(:any?, expensive)
|
120
|
+
```
|
121
|
+
|
122
|
+
### Mixed Operations
|
123
|
+
```ruby
|
124
|
+
# Element-wise computation followed by aggregation
|
125
|
+
value :line_totals, input.items.price * input.items.quantity
|
126
|
+
value :order_total, fn(:sum, line_totals)
|
127
|
+
value :avg_line_total, fn(:avg, line_totals)
|
128
|
+
```
|
129
|
+
|
130
|
+
## Error Handling
|
131
|
+
|
132
|
+
### Dimension Mismatch Detection
|
133
|
+
|
134
|
+
Array broadcasting operations are only valid within the same array source. Attempting to broadcast across different arrays generates detailed error messages:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
schema do
|
138
|
+
input do
|
139
|
+
array :items do
|
140
|
+
string :name
|
141
|
+
end
|
142
|
+
array :logs do
|
143
|
+
string :user_name
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# This will generate a dimension mismatch error
|
148
|
+
trait :same_name, input.items.name == input.logs.user_name
|
149
|
+
end
|
150
|
+
|
151
|
+
# Error:
|
152
|
+
# Cannot broadcast operation across arrays from different sources: items, logs.
|
153
|
+
# Problem: Multiple operands are arrays from different sources:
|
154
|
+
# - Operand 1 resolves to array(string) from array 'items'
|
155
|
+
# - Operand 2 resolves to array(string) from array 'logs'
|
156
|
+
# Direct operations on arrays from different sources is ambiguous and not supported.
|
157
|
+
# Vectorized operations can only work on fields from the same array input.
|
158
|
+
```
|
159
|
+
|
160
|
+
The error messages provide:
|
161
|
+
- **Quick Summary**: Identifies the conflicting array sources
|
162
|
+
- **Type Information**: Shows the resolved types of each operand
|
163
|
+
- **Clear Explanation**: Why the operation is ambiguous and not supported
|
164
|
+
|
165
|
+
## Performance Characteristics
|
166
|
+
|
167
|
+
- **Single Pass** - Each array is traversed once per computation chain
|
168
|
+
- **Lazy Evaluation** - Operations are composed into efficient pipelines
|
169
|
+
- **Memory Efficient** - No intermediate array allocations for simple operations
|
170
|
+
- **Type Safe** - Full compile-time type checking for array element operations
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Input Declarations
|
2
|
+
|
3
|
+
Declares expected inputs with types and domain constraints, separating input metadata from business logic.
|
4
|
+
|
5
|
+
## Declaration Syntax
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
schema do
|
9
|
+
input do
|
10
|
+
string :customer_name
|
11
|
+
integer :age, domain: 18..120
|
12
|
+
float :balance, domain: 0.0..Float::INFINITY
|
13
|
+
boolean :verified
|
14
|
+
array :tags, elem: { type: :string }
|
15
|
+
hash :metadata, key: { type: :string }, val: { type: :any }
|
16
|
+
any :flexible
|
17
|
+
end
|
18
|
+
|
19
|
+
trait :adult, (input.age >= 18)
|
20
|
+
value :status, input.verified ? "verified" : "pending"
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
## Domain Constraints
|
25
|
+
|
26
|
+
**Validation occurs at runtime:**
|
27
|
+
```ruby
|
28
|
+
schema.from(credit_score: 900) # Domain: 300..850
|
29
|
+
# => InputValidationError: Field :credit_score value 900 is outside domain 300..850
|
30
|
+
```
|
31
|
+
|
32
|
+
**Constraint types:**
|
33
|
+
- Range domains: `domain: 18..120`
|
34
|
+
- Array domains: `domain: %w[active inactive]`
|
35
|
+
- Regex domains: `domain: /^[a-zA-Z]+$/`
|
36
|
+
|
37
|
+
## Validation Process
|
38
|
+
|
39
|
+
- Input data validated against declared field metadata
|
40
|
+
- Type validation checks value matches declared type
|
41
|
+
- Domain validation checks value satisfies constraints
|
42
|
+
- Detailed error messages for violations
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Performance
|
2
|
+
|
3
|
+
TODO: Add benchmark data
|
4
|
+
|
5
|
+
Processes large schemas with optimized algorithms for analysis, compilation, and execution.
|
6
|
+
|
7
|
+
## Execution Model
|
8
|
+
|
9
|
+
**Compilation:**
|
10
|
+
- Each expression compiled to executable lambda
|
11
|
+
- Direct function calls for operations
|
12
|
+
|
13
|
+
**Runtime:**
|
14
|
+
- Result caching to avoid recomputation
|
15
|
+
- Selective evaluation: only requested keys computed
|
16
|
+
- Direct lambda invocation
|
@@ -28,7 +28,7 @@ module FederalTaxCalculator
|
|
28
28
|
schema do
|
29
29
|
input do
|
30
30
|
float :income
|
31
|
-
string :filing_status, domain: %
|
31
|
+
string :filing_status, domain: %w[single married_joint married_separate head_of_household]
|
32
32
|
end
|
33
33
|
|
34
34
|
# ── standard deduction table ───────────────────────────────────────
|
@@ -59,7 +59,7 @@ module FederalTaxCalculator
|
|
59
59
|
|
60
60
|
value :fed_tax, fed_calc[0]
|
61
61
|
value :fed_marginal, fed_calc[1]
|
62
|
-
value :fed_eff, fed_tax /
|
62
|
+
value :fed_eff, fed_tax / [input.income, 1.0].max
|
63
63
|
|
64
64
|
# ── FICA (employee share) ─────────────────────────────────────────────
|
65
65
|
value :ss_wage_base, 168_600.0
|
@@ -96,10 +96,11 @@ module FederalTaxCalculator
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
-
def
|
99
|
+
def print_tax_summary(args)
|
100
|
+
r = FederalTaxCalculator.from(args)
|
100
101
|
puts "\n=== 2024 U.S. Income‑Tax Example ==="
|
101
|
-
printf "Income: $%0.2f\n", income
|
102
|
-
puts "Filing status: #{
|
102
|
+
printf "Income: $%0.2f\n", args[:income]
|
103
|
+
puts "Filing status: #{args[:filing_status]}\n\n"
|
103
104
|
|
104
105
|
puts "Federal tax: $#{r[:fed_tax].round(2)} (#{(r[:fed_eff] * 100).round(2)}% effective)"
|
105
106
|
puts "FICA tax: $#{r[:fica_tax].round(2)} (#{(r[:fica_eff] * 100).round(2)}% effective)"
|
@@ -107,4 +108,8 @@ def calculate_tax(calculator, income: 1_000_000, status: "single")
|
|
107
108
|
puts "After-tax income: $#{r[:after_tax].round(2)}"
|
108
109
|
end
|
109
110
|
|
110
|
-
|
111
|
+
|
112
|
+
input = { income: 1_000_000,
|
113
|
+
filing_status: "single"
|
114
|
+
}
|
115
|
+
print_tax_summary(input)
|
@@ -23,7 +23,7 @@ module Kumi
|
|
23
23
|
return node.value if node.is_a?(Literal)
|
24
24
|
|
25
25
|
result = case node
|
26
|
-
when
|
26
|
+
when DeclarationReference then evaluate_binding(node, visited)
|
27
27
|
when CallExpression then evaluate_call_expression(node, visited)
|
28
28
|
else :unknown
|
29
29
|
end
|