kumi 0.0.4 → 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 +160 -8
- data/README.md +278 -200
- data/{documents → docs}/AST.md +29 -29
- data/{documents → docs}/DSL.md +3 -3
- data/{documents → docs}/SYNTAX.md +107 -24
- 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 +43 -40
- data/examples/game_of_life.rb +97 -0
- data/examples/simple_rpg_game.rb +1000 -0
- data/examples/static_analysis_errors.rb +178 -0
- data/examples/wide_schema_compilation_and_evaluation_benchmark.rb +1 -1
- data/lib/kumi/analyzer/analysis_state.rb +37 -0
- data/lib/kumi/analyzer/constant_evaluator.rb +22 -16
- data/lib/kumi/analyzer/passes/broadcast_detector.rb +251 -0
- data/lib/kumi/analyzer/passes/{definition_validator.rb → declaration_validator.rb} +8 -7
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +106 -26
- data/lib/kumi/analyzer/passes/input_collector.rb +105 -23
- data/lib/kumi/analyzer/passes/name_indexer.rb +2 -2
- data/lib/kumi/analyzer/passes/pass_base.rb +11 -28
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +110 -0
- data/lib/kumi/analyzer/passes/toposorter.rb +45 -9
- data/lib/kumi/analyzer/passes/type_checker.rb +34 -11
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +2 -1
- data/lib/kumi/analyzer/passes/type_inferencer.rb +128 -21
- data/lib/kumi/analyzer/passes/unsat_detector.rb +312 -13
- data/lib/kumi/analyzer/passes/visitor_pass.rb +4 -3
- data/lib/kumi/analyzer.rb +41 -24
- data/lib/kumi/atom_unsat_solver.rb +45 -0
- data/lib/kumi/cli.rb +449 -0
- data/lib/kumi/compiler.rb +194 -16
- data/lib/kumi/constraint_relationship_solver.rb +638 -0
- data/lib/kumi/domain/validator.rb +0 -4
- data/lib/kumi/error_reporter.rb +6 -6
- data/lib/kumi/evaluation_wrapper.rb +20 -4
- data/lib/kumi/explain.rb +28 -28
- 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 +117 -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 +19 -8
- data/lib/kumi/parser/expression_converter.rb +80 -12
- 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/parser.rb +2 -0
- data/lib/kumi/parser/schema_builder.rb +10 -9
- data/lib/kumi/parser/sugar.rb +171 -18
- data/lib/kumi/schema.rb +3 -1
- data/lib/kumi/schema_instance.rb +69 -3
- 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/root.rb +1 -0
- data/lib/kumi/syntax/trait_declaration.rb +11 -0
- data/lib/kumi/syntax/value_declaration.rb +11 -0
- data/lib/kumi/types/compatibility.rb +8 -0
- data/lib/kumi/types/validator.rb +1 -1
- data/lib/kumi/vectorization_metadata.rb +108 -0
- data/lib/kumi/version.rb +1 -1
- data/scripts/generate_function_docs.rb +22 -10
- metadata +38 -17
- data/CHANGELOG.md +0 -25
- data/lib/kumi/domain.rb +0 -8
- data/lib/kumi/input.rb +0 -8
- data/lib/kumi/syntax/declarations.rb +0 -23
- data/lib/kumi/syntax/expressions.rb +0 -30
- data/lib/kumi/syntax/terminal_expressions.rb +0 -27
- data/lib/kumi/syntax.rb +0 -9
- data/test_impossible_cascade.rb +0 -51
- /data/{documents → docs}/FUNCTIONS.md +0 -0
data/{documents → docs}/AST.md
RENAMED
@@ -7,22 +7,22 @@
|
|
7
7
|
Root = Struct.new(:inputs, :attributes, :traits)
|
8
8
|
```
|
9
9
|
|
10
|
-
**
|
10
|
+
**InputDeclaration**: Input field metadata
|
11
11
|
```ruby
|
12
|
-
|
13
|
-
# DSL: integer :age, domain: 18..65 →
|
12
|
+
InputDeclaration = Struct.new(:name, :domain, :type)
|
13
|
+
# DSL: integer :age, domain: 18..65 → InputDeclaration(:age, 18..65, :integer)
|
14
14
|
```
|
15
15
|
|
16
|
-
**
|
16
|
+
**TraitDeclaration**: Boolean predicate
|
17
17
|
```ruby
|
18
|
-
|
19
|
-
# DSL: trait :adult, (input.age >= 18) →
|
18
|
+
TraitDeclaration = Struct.new(:name, :expression)
|
19
|
+
# DSL: trait :adult, (input.age >= 18) → TraitDeclaration(:adult, CallExpression(...))
|
20
20
|
```
|
21
21
|
|
22
|
-
**
|
22
|
+
**ValueDeclaration**: Computed value
|
23
23
|
```ruby
|
24
|
-
|
25
|
-
# DSL: value :total, fn(:add, a, b) →
|
24
|
+
ValueDeclaration = Struct.new(:name, :expression)
|
25
|
+
# DSL: value :total, fn(:add, a, b) → ValueDeclaration(:total, CallExpression(:add, [...]))
|
26
26
|
```
|
27
27
|
|
28
28
|
## Expression Nodes
|
@@ -33,18 +33,18 @@ CallExpression = Struct.new(:fn_name, :args)
|
|
33
33
|
def &(other) = CallExpression.new(:and, [self, other]) # Enable chaining
|
34
34
|
```
|
35
35
|
|
36
|
-
**
|
36
|
+
**InputReference**: Field access (`input.field_name`)
|
37
37
|
```ruby
|
38
|
-
|
38
|
+
InputReference = Struct.new(:name)
|
39
39
|
# Has operator methods: >=, <=, >, <, ==, != that create CallExpression nodes
|
40
40
|
```
|
41
41
|
|
42
|
-
**
|
42
|
+
**DeclarationReference**: References to other declarations
|
43
43
|
```ruby
|
44
|
-
|
44
|
+
DeclarationReference = Struct.new(:name)
|
45
45
|
# Created by: ref(:name) OR bare identifier (trait_name) in composite traits
|
46
|
-
# DSL: ref(:adult) →
|
47
|
-
# DSL: adult & verified → CallExpression(:and, [
|
46
|
+
# DSL: ref(:adult) → DeclarationReference(:adult)
|
47
|
+
# DSL: adult & verified → CallExpression(:and, [DeclarationReference(:adult), DeclarationReference(:verified)])
|
48
48
|
```
|
49
49
|
|
50
50
|
**Literal**: Constants (`18`, `"text"`, `true`)
|
@@ -52,9 +52,9 @@ Binding = Struct.new(:name)
|
|
52
52
|
Literal = Struct.new(:value)
|
53
53
|
```
|
54
54
|
|
55
|
-
**
|
55
|
+
**ArrayExpression**: Arrays (`[1, 2, 3]`)
|
56
56
|
```ruby
|
57
|
-
|
57
|
+
ArrayExpression = Struct.new(:elements)
|
58
58
|
```
|
59
59
|
|
60
60
|
## Cascade Expressions (Conditional Values)
|
@@ -64,9 +64,9 @@ ListExpression = Struct.new(:elements)
|
|
64
64
|
CascadeExpression = Struct.new(:cases)
|
65
65
|
```
|
66
66
|
|
67
|
-
**
|
67
|
+
**CaseExpression**: Individual conditions
|
68
68
|
```ruby
|
69
|
-
|
69
|
+
CaseExpression = Struct.new(:condition, :result)
|
70
70
|
```
|
71
71
|
|
72
72
|
**Case type mappings**:
|
@@ -76,7 +76,7 @@ WhenCaseExpression = Struct.new(:condition, :result)
|
|
76
76
|
|
77
77
|
## Key Nuances
|
78
78
|
|
79
|
-
**Operator methods on
|
79
|
+
**Operator methods on InputReference**: Enable `input.age >= 18` syntax by defining operators that create `CallExpression` nodes
|
80
80
|
|
81
81
|
**CallExpression `&` method**: Enables expression chaining like `(expr1) & (expr2)`
|
82
82
|
|
@@ -92,14 +92,14 @@ WhenCaseExpression = Struct.new(:condition, :result)
|
|
92
92
|
|
93
93
|
**Simple**: `(input.age >= 18)`
|
94
94
|
```
|
95
|
-
CallExpression(:>=, [
|
95
|
+
CallExpression(:>=, [InputReference(:age), Literal(18)])
|
96
96
|
```
|
97
97
|
|
98
98
|
**Chained AND**: `(input.age >= 21) & (input.verified == true)`
|
99
99
|
```
|
100
100
|
CallExpression(:and, [
|
101
|
-
CallExpression(:>=, [
|
102
|
-
CallExpression(:==, [
|
101
|
+
CallExpression(:>=, [InputReference(:age), Literal(21)]),
|
102
|
+
CallExpression(:==, [InputReference(:verified), Literal(true)])
|
103
103
|
])
|
104
104
|
```
|
105
105
|
|
@@ -107,10 +107,10 @@ CallExpression(:and, [
|
|
107
107
|
```
|
108
108
|
CallExpression(:and, [
|
109
109
|
CallExpression(:and, [
|
110
|
-
|
111
|
-
|
110
|
+
DeclarationReference(:adult),
|
111
|
+
DeclarationReference(:verified)
|
112
112
|
]),
|
113
|
-
|
113
|
+
DeclarationReference(:high_income)
|
114
114
|
])
|
115
115
|
```
|
116
116
|
|
@@ -118,9 +118,9 @@ CallExpression(:and, [
|
|
118
118
|
```
|
119
119
|
CallExpression(:and, [
|
120
120
|
CallExpression(:and, [
|
121
|
-
|
122
|
-
CallExpression(:>, [
|
121
|
+
DeclarationReference(:adult),
|
122
|
+
CallExpression(:>, [InputReference(:score), Literal(80)])
|
123
123
|
]),
|
124
|
-
|
124
|
+
DeclarationReference(:verified)
|
125
125
|
])
|
126
126
|
```
|
data/{documents → docs}/DSL.md
RENAMED
@@ -109,13 +109,13 @@ For conditional logic, a `value` takes a block to create a **cascade**. Cascades
|
|
109
109
|
```ruby
|
110
110
|
value :access_level do
|
111
111
|
# `on` implies AND: user must be :premium AND :verified.
|
112
|
-
on
|
112
|
+
on premium,verified, "Full Access"
|
113
113
|
|
114
114
|
# `on_any` implies OR: user can be :staff OR :admin.
|
115
|
-
on_any
|
115
|
+
on_any staff,admin, "Elevated Access"
|
116
116
|
|
117
117
|
# `on_none` implies NOT (A OR B): user is neither :blocked NOR :suspended.
|
118
|
-
on_none
|
118
|
+
on_none blocked,suspended, "Limited Access"
|
119
119
|
|
120
120
|
# `base` is the default if no other conditions match.
|
121
121
|
base "No Access"
|
@@ -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,11 +188,13 @@ value :sorted_scores, fn(:sort, input.score_array)
|
|
187
188
|
|
188
189
|
### Built-in Functions Available
|
189
190
|
|
191
|
+
See [FUNCTIONS.md](FUNCTIONS.md)
|
192
|
+
|
190
193
|
| Category | Sugar | Sugar-Free |
|
191
194
|
|----------|-------|------------|
|
192
195
|
| **Arithmetic** | `+`, `-`, `*`, `/`, `**` | `fn(:add, a, b)`, `fn(:subtract, a, b)`, etc. |
|
193
196
|
| **Comparison** | `>`, `<`, `>=`, `<=`, `==`, `!=` | `fn(:>, a, b)`, `fn(:<, a, b)`, etc. |
|
194
|
-
| **Logical** | `&`
|
197
|
+
| **Logical** | `&` `|` | `fn(:and, a, b)`, `fn(:or, a, b)`, `fn(:not, a)` |
|
195
198
|
| **Math** | `abs`, `round`, `ceil`, `floor` | `fn(:abs, x)`, `fn(:round, x)`, etc. |
|
196
199
|
| **String** | `.length`, `.upcase`, `.downcase` | `fn(:string_length, s)`, `fn(:upcase, s)`, etc. |
|
197
200
|
| **Collection** | `.sum`, `.size`, `.max`, `.min` | `fn(:sum, arr)`, `fn(:size, arr)`, etc. |
|
@@ -208,31 +211,110 @@ value :clamped_score, fn(:clamp, input.raw_score, 0, 1600)
|
|
208
211
|
value :formatted_name, fn(:add, fn(:add, input.first_name, " "), input.last_name)
|
209
212
|
```
|
210
213
|
|
211
|
-
##
|
214
|
+
## Array Broadcasting
|
215
|
+
|
216
|
+
Array broadcasting enables element-wise operations on array fields with automatic vectorization.
|
212
217
|
|
213
|
-
|
218
|
+
### Array Input Declarations
|
214
219
|
|
215
220
|
```ruby
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
223
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
|
224
255
|
|
225
|
-
|
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
|
+
|
305
|
+
## Cascade Logic
|
306
|
+
|
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.
|
308
|
+
```ruby
|
226
309
|
value :grade_letter do
|
227
|
-
on
|
228
|
-
on
|
229
|
-
on
|
230
|
-
on
|
310
|
+
on excellent_student, "A+"
|
311
|
+
on high_scorer, "A"
|
312
|
+
on above_average, "B"
|
313
|
+
on needs_improvement, "C"
|
231
314
|
base "F"
|
232
315
|
end
|
233
316
|
```
|
234
317
|
|
235
|
-
The difference is in how the traits referenced in cascade conditions are defined (see Trait Declarations above).
|
236
318
|
|
237
319
|
## References
|
238
320
|
|
@@ -287,9 +369,9 @@ module StudentEvaluation
|
|
287
369
|
|
288
370
|
# Cascade with sugar-defined traits
|
289
371
|
value :scholarship_amount do
|
290
|
-
on
|
291
|
-
on
|
292
|
-
on
|
372
|
+
on scholarship_candidate, 10000
|
373
|
+
on high_performer, 5000
|
374
|
+
on math_excellence, 2500
|
293
375
|
base 0
|
294
376
|
end
|
295
377
|
end
|
@@ -324,9 +406,9 @@ module StudentEvaluation
|
|
324
406
|
|
325
407
|
# Cascade with sugar-free defined traits
|
326
408
|
value :scholarship_amount do
|
327
|
-
on
|
328
|
-
on
|
329
|
-
on
|
409
|
+
on scholarship_candidate, 10000
|
410
|
+
on high_performer, 5000
|
411
|
+
on math_excellence, 2500
|
330
412
|
base 0
|
331
413
|
end
|
332
414
|
end
|
@@ -351,10 +433,11 @@ end
|
|
351
433
|
## Syntax Limitations
|
352
434
|
|
353
435
|
### Sugar Syntax Limitations:
|
354
|
-
-
|
355
|
-
-
|
436
|
+
- Supports `&` for logical AND (no `&&` due to Ruby precedence)
|
437
|
+
- Supports `|` for logical OR
|
356
438
|
- Limited operator precedence control
|
357
439
|
- Some Ruby methods not available as sugar
|
440
|
+
- **Refinement scope**: Array methods like `.max` on syntax expressions may not work in certain contexts (e.g., test helpers, Ruby < 3.0). Use `fn(:max, array)` instead.
|
358
441
|
|
359
442
|
### Sugar-Free Advantages:
|
360
443
|
- Full access to all registered functions
|
@@ -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
|