kumi 0.0.7 → 0.0.9
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 +1 -1
- data/README.md +21 -5
- data/docs/AST.md +7 -0
- data/docs/features/README.md +7 -0
- data/docs/features/s-expression-printer.md +77 -0
- data/examples/game_of_life.rb +1 -1
- data/examples/static_analysis_errors.rb +7 -7
- data/lib/kumi/analyzer.rb +15 -15
- data/lib/kumi/compiler.rb +6 -6
- data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
- data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
- data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
- data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
- data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
- data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
- data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
- data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
- data/lib/kumi/core/atom_unsat_solver.rb +396 -0
- data/lib/kumi/core/compiled_schema.rb +43 -0
- data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
- data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
- data/lib/kumi/core/domain/range_analyzer.rb +85 -0
- data/lib/kumi/core/domain/validator.rb +82 -0
- data/lib/kumi/core/domain/violation_formatter.rb +42 -0
- data/lib/kumi/core/error_reporter.rb +166 -0
- data/lib/kumi/core/error_reporting.rb +97 -0
- data/lib/kumi/core/errors.rb +120 -0
- data/lib/kumi/core/evaluation_wrapper.rb +40 -0
- data/lib/kumi/core/explain.rb +295 -0
- data/lib/kumi/core/export/deserializer.rb +41 -0
- data/lib/kumi/core/export/errors.rb +14 -0
- data/lib/kumi/core/export/node_builders.rb +142 -0
- data/lib/kumi/core/export/node_registry.rb +54 -0
- data/lib/kumi/core/export/node_serializers.rb +158 -0
- data/lib/kumi/core/export/serializer.rb +25 -0
- data/lib/kumi/core/export.rb +35 -0
- data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
- data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
- data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
- data/lib/kumi/core/function_registry/function_builder.rb +95 -0
- data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
- data/lib/kumi/core/function_registry/math_functions.rb +74 -0
- data/lib/kumi/core/function_registry/string_functions.rb +57 -0
- data/lib/kumi/core/function_registry/type_functions.rb +53 -0
- data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
- data/lib/kumi/core/input/type_matcher.rb +97 -0
- data/lib/kumi/core/input/validator.rb +51 -0
- data/lib/kumi/core/input/violation_creator.rb +52 -0
- data/lib/kumi/core/json_schema/generator.rb +65 -0
- data/lib/kumi/core/json_schema/validator.rb +27 -0
- data/lib/kumi/core/json_schema.rb +16 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
- data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
- data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
- data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
- data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
- data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
- data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
- data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
- data/lib/kumi/core/ruby_parser/parser.rb +71 -0
- data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
- data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
- data/lib/kumi/core/ruby_parser.rb +12 -0
- data/lib/kumi/core/schema_instance.rb +111 -0
- data/lib/kumi/core/types/builder.rb +23 -0
- data/lib/kumi/core/types/compatibility.rb +96 -0
- data/lib/kumi/core/types/formatter.rb +26 -0
- data/lib/kumi/core/types/inference.rb +42 -0
- data/lib/kumi/core/types/normalizer.rb +72 -0
- data/lib/kumi/core/types/validator.rb +37 -0
- data/lib/kumi/core/types.rb +66 -0
- data/lib/kumi/core/vectorization_metadata.rb +110 -0
- data/lib/kumi/errors.rb +1 -112
- data/lib/kumi/registry.rb +37 -0
- data/lib/kumi/schema.rb +5 -5
- data/lib/kumi/schema_metadata.rb +3 -3
- data/lib/kumi/support/s_expression_printer.rb +161 -0
- data/lib/kumi/syntax/array_expression.rb +6 -6
- data/lib/kumi/syntax/call_expression.rb +4 -4
- data/lib/kumi/syntax/cascade_expression.rb +4 -4
- data/lib/kumi/syntax/case_expression.rb +4 -4
- data/lib/kumi/syntax/declaration_reference.rb +4 -4
- data/lib/kumi/syntax/hash_expression.rb +4 -4
- data/lib/kumi/syntax/input_declaration.rb +5 -5
- data/lib/kumi/syntax/input_element_reference.rb +5 -5
- data/lib/kumi/syntax/input_reference.rb +5 -5
- data/lib/kumi/syntax/literal.rb +4 -4
- data/lib/kumi/syntax/node.rb +34 -34
- data/lib/kumi/syntax/root.rb +6 -6
- data/lib/kumi/syntax/trait_declaration.rb +4 -4
- data/lib/kumi/syntax/value_declaration.rb +4 -4
- data/lib/kumi/version.rb +1 -1
- data/migrate_to_core_iterative.rb +938 -0
- data/scripts/generate_function_docs.rb +9 -9
- metadata +77 -72
- data/lib/kumi/analyzer/analysis_state.rb +0 -37
- data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
- data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -246
- data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
- data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
- data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
- data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -109
- data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
- data/lib/kumi/analyzer/passes/type_checker.rb +0 -160
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
- data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
- data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -404
- data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
- data/lib/kumi/atom_unsat_solver.rb +0 -394
- data/lib/kumi/compiled_schema.rb +0 -41
- data/lib/kumi/constraint_relationship_solver.rb +0 -638
- data/lib/kumi/domain/enum_analyzer.rb +0 -53
- data/lib/kumi/domain/range_analyzer.rb +0 -83
- data/lib/kumi/domain/validator.rb +0 -80
- data/lib/kumi/domain/violation_formatter.rb +0 -40
- data/lib/kumi/error_reporter.rb +0 -164
- data/lib/kumi/error_reporting.rb +0 -95
- data/lib/kumi/evaluation_wrapper.rb +0 -38
- data/lib/kumi/explain.rb +0 -293
- data/lib/kumi/export/deserializer.rb +0 -39
- data/lib/kumi/export/errors.rb +0 -12
- data/lib/kumi/export/node_builders.rb +0 -140
- data/lib/kumi/export/node_registry.rb +0 -52
- data/lib/kumi/export/node_serializers.rb +0 -156
- data/lib/kumi/export/serializer.rb +0 -23
- data/lib/kumi/export.rb +0 -33
- data/lib/kumi/function_registry/collection_functions.rb +0 -200
- data/lib/kumi/function_registry/comparison_functions.rb +0 -31
- data/lib/kumi/function_registry/conditional_functions.rb +0 -36
- data/lib/kumi/function_registry/function_builder.rb +0 -93
- data/lib/kumi/function_registry/logical_functions.rb +0 -42
- data/lib/kumi/function_registry/math_functions.rb +0 -72
- data/lib/kumi/function_registry/string_functions.rb +0 -54
- data/lib/kumi/function_registry/type_functions.rb +0 -51
- data/lib/kumi/input/type_matcher.rb +0 -95
- data/lib/kumi/input/validator.rb +0 -49
- data/lib/kumi/input/violation_creator.rb +0 -50
- data/lib/kumi/json_schema/generator.rb +0 -63
- data/lib/kumi/json_schema/validator.rb +0 -25
- data/lib/kumi/json_schema.rb +0 -14
- data/lib/kumi/ruby_parser/build_context.rb +0 -25
- data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +0 -36
- data/lib/kumi/ruby_parser/dsl.rb +0 -12
- data/lib/kumi/ruby_parser/dsl_cascade_builder.rb +0 -136
- data/lib/kumi/ruby_parser/expression_converter.rb +0 -126
- data/lib/kumi/ruby_parser/guard_rails.rb +0 -43
- data/lib/kumi/ruby_parser/input_builder.rb +0 -125
- data/lib/kumi/ruby_parser/input_field_proxy.rb +0 -46
- data/lib/kumi/ruby_parser/input_proxy.rb +0 -29
- data/lib/kumi/ruby_parser/nested_input.rb +0 -15
- data/lib/kumi/ruby_parser/parser.rb +0 -69
- data/lib/kumi/ruby_parser/schema_builder.rb +0 -173
- data/lib/kumi/ruby_parser/sugar.rb +0 -261
- data/lib/kumi/ruby_parser.rb +0 -10
- data/lib/kumi/schema_instance.rb +0 -109
- data/lib/kumi/types/builder.rb +0 -21
- data/lib/kumi/types/compatibility.rb +0 -94
- data/lib/kumi/types/formatter.rb +0 -24
- data/lib/kumi/types/inference.rb +0 -40
- data/lib/kumi/types/normalizer.rb +0 -70
- data/lib/kumi/types/validator.rb +0 -35
- data/lib/kumi/types.rb +0 -64
- data/lib/kumi/vectorization_metadata.rb +0 -108
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5f9f087001a482c906f041da8cb229511966a11c56e3ced99930b59d4141543
|
4
|
+
data.tar.gz: fe5fafe03a5ca1e414b5bc3af9eccd830553aa8f44762e9d9b38e589fa342fd1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b26279c08a9387616104252d24147c920f27b0f66574cde1987024325c5fd1ba87aff876b39fdc3d4f472d9aba2c713b19a5105890b66ac3ef10a3018a0b1bca
|
7
|
+
data.tar.gz: d4c8db755880cd7088fd4b6087d2f38e86a4fdee11f867d58a480e143494ca2b85440abd9249b8c02a469a9ad21642c287d5ef8f30948bdc6d67695a895c1c13
|
data/CLAUDE.md
CHANGED
@@ -62,7 +62,7 @@ Kumi is a Declarative logic and rules engine framework with static analysis for
|
|
62
62
|
**Compiler** (`lib/kumi/compiler.rb`):
|
63
63
|
- Transforms analyzed syntax tree into executable lambda functions
|
64
64
|
- Maps each expression type to a compilation method
|
65
|
-
- Handles function calls via `
|
65
|
+
- Handles function calls via `Kumi::Registry`
|
66
66
|
- Produces `CompiledSchema` with executable bindings
|
67
67
|
|
68
68
|
**Function Registry** (`lib/kumi/function_registry.rb`):
|
data/README.md
CHANGED
@@ -8,7 +8,6 @@ Kumi is a Declarative logic and rules engine framework with static analysis for
|
|
8
8
|
It is well-suited for scenarios with complex, interdependent calculations, enforcing validation and consistency across your business rules while maintaining performance.
|
9
9
|
|
10
10
|
|
11
|
-
|
12
11
|
## What can you build?
|
13
12
|
|
14
13
|
Calculate U.S. federal taxes:
|
@@ -118,16 +117,14 @@ value :monthly_payment, fn(:pmt, rate: 0.05/12, nper: 36, pv: -loan_amount)
|
|
118
117
|
```
|
119
118
|
Note: You can find a list all core functions in [docs/FUNCTIONS.md](docs/FUNCTIONS.md)
|
120
119
|
|
121
|
-
These primitives are statically analyzed during schema definition to catch logical errors before runtime.
|
122
|
-
|
123
120
|
</details>
|
124
121
|
|
125
122
|
<details>
|
126
|
-
<summary><strong>🔍 Static Analysis</strong> - Catch
|
123
|
+
<summary><strong>🔍 Static Analysis</strong> - Catch errors at definition time and provides rich metadata</summary>
|
127
124
|
|
128
125
|
### Static Analysis
|
129
126
|
|
130
|
-
Kumi catches business logic errors that cause runtime failures or silent bugs:
|
127
|
+
Kumi catches many types of business logic errors that cause runtime failures or silent bugs:
|
131
128
|
|
132
129
|
```ruby
|
133
130
|
module InsurancePolicyPricer
|
@@ -328,6 +325,19 @@ Kumi::Explain.call(FederalTax2024, :fed_tax, inputs: {income: 100_000, filing_st
|
|
328
325
|
# = 15,099.50
|
329
326
|
```
|
330
327
|
|
328
|
+
**Debug AST Structure**: Visualize the parsed schema as S-expressions:
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
puts Kumi::Support::SExpressionPrinter.print(FederalTax2024.__syntax_tree__)
|
332
|
+
# => (Root
|
333
|
+
# inputs: [
|
334
|
+
# (InputDeclaration :income :float)
|
335
|
+
# (InputDeclaration :filing_status :string domain: ["single", "married_joint"])
|
336
|
+
# ]
|
337
|
+
# traits: [...]
|
338
|
+
# attributes: [...])
|
339
|
+
```
|
340
|
+
|
331
341
|
</details>
|
332
342
|
|
333
343
|
<details>
|
@@ -361,6 +371,12 @@ The SchemaMetadata interface provides both processed metadata for tool developme
|
|
361
371
|
|
362
372
|
</details>
|
363
373
|
|
374
|
+
## Beyond Rules: What the Metadata Unlocks
|
375
|
+
* **Auto-generated forms** – compile schema → field spec → React form
|
376
|
+
* **Scenario explorer** – derive all trait combinations, Monte Carlo outcomes
|
377
|
+
* **Coverage dashboard** – flag branches never hit in prod
|
378
|
+
* **Schema diff** – highlight behaviour changes across versions
|
379
|
+
|
364
380
|
## Usage
|
365
381
|
|
366
382
|
**Suitable for:**
|
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)
|
data/docs/features/README.md
CHANGED
@@ -37,6 +37,13 @@ Processes large schemas with optimized algorithms.
|
|
37
37
|
- Result caching
|
38
38
|
- Selective evaluation
|
39
39
|
|
40
|
+
### [S-Expression Printer](s-expression-printer.md)
|
41
|
+
Debug and inspect AST structures with readable S-expression notation output.
|
42
|
+
|
43
|
+
- Visitor pattern implementation for all node types
|
44
|
+
- Proper indentation and hierarchical structure
|
45
|
+
- Useful for debugging schema parsing and AST analysis
|
46
|
+
|
40
47
|
## Integration
|
41
48
|
|
42
49
|
- Type inference uses input declarations
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# S-Expression Printer
|
2
|
+
|
3
|
+
Debug and inspect Kumi AST structures with readable S-expression notation output.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The S-Expression Printer provides a clean, structured way to visualize Kumi's Abstract Syntax Tree (AST) nodes in traditional Lisp-style S-expression format. This is particularly useful for debugging schema parsing, understanding AST structure, and analyzing complex expressions.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'kumi/support/s_expression_printer'
|
13
|
+
|
14
|
+
# Print any AST node
|
15
|
+
Kumi::Support::SExpressionPrinter.print(node)
|
16
|
+
|
17
|
+
# Print a complete schema AST
|
18
|
+
module MySchema
|
19
|
+
extend Kumi::Schema
|
20
|
+
|
21
|
+
schema do
|
22
|
+
input do
|
23
|
+
integer :age
|
24
|
+
string :name
|
25
|
+
end
|
26
|
+
|
27
|
+
trait :adult, (input.age >= 18)
|
28
|
+
value :greeting, fn(:concat, "Hello ", input.name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
puts Kumi::Support::SExpressionPrinter.print(MySchema.__syntax_tree__)
|
33
|
+
```
|
34
|
+
|
35
|
+
## Output Format
|
36
|
+
|
37
|
+
The printer produces indented S-expressions that clearly show the hierarchical structure:
|
38
|
+
|
39
|
+
```lisp
|
40
|
+
(Root
|
41
|
+
inputs: [
|
42
|
+
(InputDeclaration :age :integer)
|
43
|
+
(InputDeclaration :name :string)
|
44
|
+
]
|
45
|
+
attributes: [
|
46
|
+
(ValueDeclaration :greeting
|
47
|
+
(CallExpression :concat
|
48
|
+
(Literal "Hello ")
|
49
|
+
(InputReference :name)
|
50
|
+
)
|
51
|
+
)
|
52
|
+
]
|
53
|
+
traits: [
|
54
|
+
(TraitDeclaration :adult
|
55
|
+
(CallExpression :>=
|
56
|
+
(InputReference :age)
|
57
|
+
(Literal 18)
|
58
|
+
)
|
59
|
+
)
|
60
|
+
]
|
61
|
+
)
|
62
|
+
```
|
63
|
+
|
64
|
+
## Supported Node Types
|
65
|
+
|
66
|
+
The printer handles all Kumi AST node types:
|
67
|
+
|
68
|
+
- **Root** - Schema container with inputs, attributes, and traits
|
69
|
+
- **Declarations** - InputDeclaration, ValueDeclaration, TraitDeclaration
|
70
|
+
- **Expressions** - CallExpression, ArrayExpression, CascadeExpression, CaseExpression
|
71
|
+
- **References** - InputReference, InputElementReference, DeclarationReference
|
72
|
+
- **Literals** - Literal values (strings, numbers, booleans)
|
73
|
+
- **Collections** - Arrays and HashExpression nodes
|
74
|
+
|
75
|
+
## Implementation
|
76
|
+
|
77
|
+
Built as a visitor pattern class that traverses AST nodes recursively, with each node type having its own specialized formatting method. The printer preserves proper indentation and handles nested structures gracefully.
|
data/examples/game_of_life.rb
CHANGED
@@ -13,7 +13,7 @@ begin
|
|
13
13
|
cells[neighbor_index]
|
14
14
|
end.sum
|
15
15
|
end
|
16
|
-
Kumi::FunctionRegistry.register_with_metadata(:neighbor_cells_sum, method(:neighbor_cells_sum_method),
|
16
|
+
Kumi::Core::FunctionRegistry.register_with_metadata(:neighbor_cells_sum, method(:neighbor_cells_sum_method),
|
17
17
|
return_type: :integer, arity: 5,
|
18
18
|
param_types: %i[array integer integer integer integer],
|
19
19
|
description: "Get neighbor cells for Conway's Game of Life")
|
@@ -22,7 +22,7 @@ begin
|
|
22
22
|
value :yearly_rate, monthly_rate * 12
|
23
23
|
end
|
24
24
|
end
|
25
|
-
rescue Kumi::Errors::SemanticError => e
|
25
|
+
rescue Kumi::Core::Errors::SemanticError => e
|
26
26
|
puts " → #{e.message}"
|
27
27
|
end
|
28
28
|
|
@@ -48,7 +48,7 @@ begin
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
51
|
-
rescue Kumi::Errors::SemanticError => e
|
51
|
+
rescue Kumi::Core::Errors::SemanticError => e
|
52
52
|
puts " → #{e.message}"
|
53
53
|
end
|
54
54
|
|
@@ -71,7 +71,7 @@ begin
|
|
71
71
|
value :invalid_sum, input.name + input.age
|
72
72
|
end
|
73
73
|
end
|
74
|
-
rescue Kumi::Errors::TypeError => e
|
74
|
+
rescue Kumi::Core::Errors::TypeError => e
|
75
75
|
puts " → #{e.message}"
|
76
76
|
end
|
77
77
|
|
@@ -94,7 +94,7 @@ begin
|
|
94
94
|
trait :impossible_score, input.score == 150
|
95
95
|
end
|
96
96
|
end
|
97
|
-
rescue Kumi::Errors::SemanticError => e
|
97
|
+
rescue Kumi::Core::Errors::SemanticError => e
|
98
98
|
puts " → #{e.message}"
|
99
99
|
end
|
100
100
|
|
@@ -114,7 +114,7 @@ begin
|
|
114
114
|
value :result, ref(:nonexistent_trait) ? 100 : 0
|
115
115
|
end
|
116
116
|
end
|
117
|
-
rescue Kumi::Errors::SemanticError => e
|
117
|
+
rescue Kumi::Core::Errors::SemanticError => e
|
118
118
|
puts " → #{e.message}"
|
119
119
|
end
|
120
120
|
|
@@ -134,7 +134,7 @@ begin
|
|
134
134
|
value :result, fn(:nonexistent_function, input.text)
|
135
135
|
end
|
136
136
|
end
|
137
|
-
rescue Kumi::Errors::TypeError => e
|
137
|
+
rescue Kumi::Core::Errors::TypeError => e
|
138
138
|
puts " → #{e.message}"
|
139
139
|
end
|
140
140
|
|
@@ -162,7 +162,7 @@ begin
|
|
162
162
|
value :result, ref(:undefined_declaration)
|
163
163
|
end
|
164
164
|
end
|
165
|
-
rescue Kumi::Errors::SemanticError => e
|
165
|
+
rescue Kumi::Core::Errors::SemanticError => e
|
166
166
|
puts " → " + e.message.split("\n").join("\n → ")
|
167
167
|
end
|
168
168
|
|
data/lib/kumi/analyzer.rb
CHANGED
@@ -7,21 +7,21 @@ module Kumi
|
|
7
7
|
module_function
|
8
8
|
|
9
9
|
DEFAULT_PASSES = [
|
10
|
-
Passes::NameIndexer, # 1. Finds all names and checks for duplicates.
|
11
|
-
Passes::InputCollector, # 2. Collects field metadata from input declarations.
|
12
|
-
Passes::DeclarationValidator, # 3. Checks the basic structure of each rule.
|
13
|
-
Passes::SemanticConstraintValidator, # 4. Validates DSL semantic constraints at AST level.
|
14
|
-
Passes::DependencyResolver, # 5. Builds the dependency graph with conditional dependencies.
|
15
|
-
Passes::UnsatDetector, # 6. Detects unsatisfiable constraints and analyzes cascade mutual exclusion.
|
16
|
-
Passes::Toposorter, # 7. Creates the final evaluation order, allowing safe cycles.
|
17
|
-
Passes::BroadcastDetector, # 8. Detects which operations should be broadcast over arrays (must run before type inference).
|
18
|
-
Passes::TypeInferencer, # 9. Infers types for all declarations (uses vectorization metadata).
|
19
|
-
Passes::TypeConsistencyChecker, # 10. Validates declared vs inferred type consistency.
|
20
|
-
Passes::TypeChecker # 11. Validates types using inferred information.
|
10
|
+
Core::Analyzer::Passes::NameIndexer, # 1. Finds all names and checks for duplicates.
|
11
|
+
Core::Analyzer::Passes::InputCollector, # 2. Collects field metadata from input declarations.
|
12
|
+
Core::Analyzer::Passes::DeclarationValidator, # 3. Checks the basic structure of each rule.
|
13
|
+
Core::Analyzer::Passes::SemanticConstraintValidator, # 4. Validates DSL semantic constraints at AST level.
|
14
|
+
Core::Analyzer::Passes::DependencyResolver, # 5. Builds the dependency graph with conditional dependencies.
|
15
|
+
Core::Analyzer::Passes::UnsatDetector, # 6. Detects unsatisfiable constraints and analyzes cascade mutual exclusion.
|
16
|
+
Core::Analyzer::Passes::Toposorter, # 7. Creates the final evaluation order, allowing safe cycles.
|
17
|
+
Core::Analyzer::Passes::BroadcastDetector, # 8. Detects which operations should be broadcast over arrays (must run before type inference).
|
18
|
+
Core::Analyzer::Passes::TypeInferencer, # 9. Infers types for all declarations (uses vectorization metadata).
|
19
|
+
Core::Analyzer::Passes::TypeConsistencyChecker, # 10. Validates declared vs inferred type consistency.
|
20
|
+
Core::Analyzer::Passes::TypeChecker # 11. Validates types using inferred information.
|
21
21
|
].freeze
|
22
22
|
|
23
|
-
def analyze!(schema, passes: DEFAULT_PASSES, **opts)
|
24
|
-
state = AnalysisState.new(opts)
|
23
|
+
def self.analyze!(schema, passes: DEFAULT_PASSES, **opts)
|
24
|
+
state = Core::Analyzer::AnalysisState.new(opts)
|
25
25
|
errors = []
|
26
26
|
|
27
27
|
state = run_analysis_passes(schema, passes, state, errors)
|
@@ -35,7 +35,7 @@ module Kumi
|
|
35
35
|
begin
|
36
36
|
state = pass_instance.run(errors)
|
37
37
|
rescue StandardError => e
|
38
|
-
errors << ErrorReporter.create_error(e.message, location: nil, type: :semantic)
|
38
|
+
errors << Core::ErrorReporter.create_error(e.message, location: nil, type: :semantic)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
state
|
@@ -62,7 +62,7 @@ module Kumi
|
|
62
62
|
end
|
63
63
|
|
64
64
|
# Handle both old and new error formats for backward compatibility
|
65
|
-
def format_errors(errors)
|
65
|
+
def self.format_errors(errors)
|
66
66
|
return "" if errors.empty?
|
67
67
|
|
68
68
|
errors.map(&:to_s).join("\n")
|
data/lib/kumi/compiler.rb
CHANGED
@@ -158,7 +158,7 @@ module Kumi
|
|
158
158
|
compile_declaration(decl)
|
159
159
|
end
|
160
160
|
|
161
|
-
CompiledSchema.new(@bindings.freeze)
|
161
|
+
Core::CompiledSchema.new(@bindings.freeze)
|
162
162
|
end
|
163
163
|
|
164
164
|
private
|
@@ -218,7 +218,7 @@ module Kumi
|
|
218
218
|
return false unless broadcast_meta
|
219
219
|
|
220
220
|
# Reduction functions are NOT vectorized operations - they consume arrays
|
221
|
-
return false if
|
221
|
+
return false if Kumi::Registry.reducer?(expr.fn_name)
|
222
222
|
|
223
223
|
expr.args.any? do |arg|
|
224
224
|
case arg
|
@@ -244,7 +244,7 @@ module Kumi
|
|
244
244
|
vectorized_function_call(name, values)
|
245
245
|
else
|
246
246
|
# All arguments are scalars - regular function call
|
247
|
-
fn =
|
247
|
+
fn = Kumi::Registry.fetch(name)
|
248
248
|
fn.call(*values)
|
249
249
|
end
|
250
250
|
rescue StandardError => e
|
@@ -257,7 +257,7 @@ module Kumi
|
|
257
257
|
|
258
258
|
def vectorized_function_call(fn_name, values)
|
259
259
|
# Get the function from registry
|
260
|
-
fn =
|
260
|
+
fn = Kumi::Registry.fetch(fn_name)
|
261
261
|
|
262
262
|
# Find array dimensions for broadcasting
|
263
263
|
array_values = values.select { |v| v.is_a?(Array) }
|
@@ -276,14 +276,14 @@ module Kumi
|
|
276
276
|
end
|
277
277
|
|
278
278
|
def invoke_function(name, arg_fns, ctx, loc)
|
279
|
-
fn =
|
279
|
+
fn = Kumi::Registry.fetch(name)
|
280
280
|
values = arg_fns.map { |fn| fn.call(ctx) }
|
281
281
|
fn.call(*values)
|
282
282
|
rescue StandardError => e
|
283
283
|
# Preserve original error class and backtrace while adding context
|
284
284
|
enhanced_message = "Error calling fn(:#{name}) at #{loc}: #{e.message}"
|
285
285
|
|
286
|
-
if e.is_a?(Kumi::Errors::Error)
|
286
|
+
if e.is_a?(Kumi::Core::Errors::Error)
|
287
287
|
# Re-raise Kumi errors with enhanced message but preserve type
|
288
288
|
e.define_singleton_method(:message) { enhanced_message }
|
289
289
|
raise e
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Analyzer
|
6
|
+
# Simple immutable state wrapper to prevent accidental mutations between passes
|
7
|
+
class AnalysisState
|
8
|
+
def initialize(data = {})
|
9
|
+
@data = data.dup.freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get a value (same as hash access)
|
13
|
+
def [](key)
|
14
|
+
@data[key]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Check if key exists (same as hash)
|
18
|
+
def key?(key)
|
19
|
+
@data.key?(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get all keys (same as hash)
|
23
|
+
def keys
|
24
|
+
@data.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create new state with additional data (simple and clean)
|
28
|
+
def with(key, value)
|
29
|
+
AnalysisState.new(@data.merge(key => value))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Convert back to hash for final result
|
33
|
+
def to_h
|
34
|
+
@data.dup
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Analyzer
|
6
|
+
class ConstantEvaluator
|
7
|
+
include Syntax
|
8
|
+
|
9
|
+
def initialize(definitions)
|
10
|
+
@definitions = definitions
|
11
|
+
@memo = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
OPERATORS = {
|
15
|
+
add: :+,
|
16
|
+
subtract: :-,
|
17
|
+
multiply: :*,
|
18
|
+
divide: :/
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def evaluate(node, visited = Set.new)
|
22
|
+
return :unknown unless node
|
23
|
+
return @memo[node] if @memo.key?(node)
|
24
|
+
return node.value if node.is_a?(Literal)
|
25
|
+
|
26
|
+
result = case node
|
27
|
+
when DeclarationReference then evaluate_binding(node, visited)
|
28
|
+
when CallExpression then evaluate_call_expression(node, visited)
|
29
|
+
else :unknown
|
30
|
+
end
|
31
|
+
|
32
|
+
@memo[node] = result unless result == :unknown
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def evaluate_binding(node, visited)
|
39
|
+
return :unknown if visited.include?(node.name)
|
40
|
+
|
41
|
+
visited << node.name
|
42
|
+
definition = @definitions[node.name]
|
43
|
+
return :unknown unless definition
|
44
|
+
|
45
|
+
evaluate(definition.expression, visited)
|
46
|
+
end
|
47
|
+
|
48
|
+
def evaluate_call_expression(node, visited)
|
49
|
+
return :unknown unless OPERATORS.key?(node.fn_name)
|
50
|
+
|
51
|
+
args = node.args.map { |arg| evaluate(arg, visited) }
|
52
|
+
return :unknown if args.any?(:unknown)
|
53
|
+
|
54
|
+
args.reduce(OPERATORS[node.fn_name])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|