kumi 0.0.5 → 0.0.7
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 +76 -174
- data/README.md +205 -52
- data/{documents → docs}/AST.md +29 -29
- data/{documents → docs}/SYNTAX.md +95 -8
- 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/docs/schema_metadata/broadcasts.md +53 -0
- data/docs/schema_metadata/cascades.md +45 -0
- data/docs/schema_metadata/declarations.md +54 -0
- data/docs/schema_metadata/dependencies.md +57 -0
- data/docs/schema_metadata/evaluation_order.md +29 -0
- data/docs/schema_metadata/examples.md +95 -0
- data/docs/schema_metadata/inferred_types.md +46 -0
- data/docs/schema_metadata/inputs.md +86 -0
- data/docs/schema_metadata.md +108 -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 +246 -0
- data/lib/kumi/analyzer/passes/{definition_validator.rb → declaration_validator.rb} +4 -4
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +78 -38
- data/lib/kumi/analyzer/passes/input_collector.rb +91 -30
- data/lib/kumi/analyzer/passes/name_indexer.rb +2 -2
- data/lib/kumi/analyzer/passes/pass_base.rb +1 -1
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +24 -25
- data/lib/kumi/analyzer/passes/toposorter.rb +44 -8
- data/lib/kumi/analyzer/passes/type_checker.rb +34 -14
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +2 -2
- data/lib/kumi/analyzer/passes/type_inferencer.rb +130 -21
- data/lib/kumi/analyzer/passes/unsat_detector.rb +134 -56
- data/lib/kumi/analyzer/passes/visitor_pass.rb +2 -2
- data/lib/kumi/analyzer.rb +16 -17
- data/lib/kumi/compiler.rb +188 -16
- data/lib/kumi/constraint_relationship_solver.rb +6 -6
- data/lib/kumi/domain/validator.rb +0 -4
- data/lib/kumi/error_reporting.rb +1 -1
- data/lib/kumi/explain.rb +32 -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/json_schema/generator.rb +63 -0
- data/lib/kumi/json_schema/validator.rb +25 -0
- data/lib/kumi/json_schema.rb +14 -0
- data/lib/kumi/{parser → ruby_parser}/build_context.rb +1 -1
- data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +36 -0
- data/lib/kumi/{parser → ruby_parser}/dsl.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/dsl_cascade_builder.rb +5 -5
- data/lib/kumi/{parser → ruby_parser}/expression_converter.rb +20 -20
- data/lib/kumi/{parser → ruby_parser}/guard_rails.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/input_builder.rb +41 -10
- data/lib/kumi/ruby_parser/input_field_proxy.rb +46 -0
- data/lib/kumi/{parser → ruby_parser}/input_proxy.rb +4 -4
- data/lib/kumi/ruby_parser/nested_input.rb +15 -0
- data/lib/kumi/{parser → ruby_parser}/parser.rb +11 -10
- data/lib/kumi/{parser → ruby_parser}/schema_builder.rb +11 -10
- data/lib/kumi/{parser → ruby_parser}/sugar.rb +62 -10
- data/lib/kumi/ruby_parser.rb +10 -0
- data/lib/kumi/schema.rb +10 -4
- data/lib/kumi/schema_instance.rb +6 -6
- data/lib/kumi/schema_metadata.rb +524 -0
- 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
- data/lib/kumi.rb +14 -0
- metadata +55 -25
- data/lib/generators/trait_engine/templates/schema_spec.rb.erb +0 -27
- 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
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
5
|
module Sugar
|
6
6
|
include Syntax
|
7
7
|
|
@@ -13,7 +13,7 @@ module Kumi
|
|
13
13
|
COMPARISON_OPS = %i[< <= > >= == !=].freeze
|
14
14
|
|
15
15
|
LITERAL_TYPES = [
|
16
|
-
Integer, String, Symbol, TrueClass, FalseClass, Float, Regexp
|
16
|
+
Integer, String, Symbol, TrueClass, FalseClass, Float, Regexp, NilClass
|
17
17
|
].freeze
|
18
18
|
|
19
19
|
# Collection methods that can be applied to arrays/syntax nodes
|
@@ -23,11 +23,11 @@ module Kumi
|
|
23
23
|
].freeze
|
24
24
|
|
25
25
|
def self.ensure_literal(obj)
|
26
|
-
return Literal.new(obj) if LITERAL_TYPES.any? { |type| obj.is_a?(type) }
|
26
|
+
return Kumi::Syntax::Literal.new(obj) if LITERAL_TYPES.any? { |type| obj.is_a?(type) }
|
27
27
|
return obj if obj.is_a?(Syntax::Node)
|
28
28
|
return obj.to_ast_node if obj.respond_to?(:to_ast_node)
|
29
29
|
|
30
|
-
Literal.new(obj)
|
30
|
+
Kumi::Syntax::Literal.new(obj)
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.syntax_expression?(obj)
|
@@ -36,7 +36,7 @@ module Kumi
|
|
36
36
|
|
37
37
|
# Create a call expression with consistent error handling
|
38
38
|
def self.create_call_expression(fn_name, args)
|
39
|
-
Syntax::CallExpression.new(fn_name, args)
|
39
|
+
Kumi::Syntax::CallExpression.new(fn_name, args)
|
40
40
|
end
|
41
41
|
|
42
42
|
module ExpressionRefinement
|
@@ -100,7 +100,7 @@ module Kumi
|
|
100
100
|
define_method(op) do |other|
|
101
101
|
if Sugar.syntax_expression?(other)
|
102
102
|
other_node = Sugar.ensure_literal(other)
|
103
|
-
Sugar.create_call_expression(op_name, [Syntax::Literal.new(self), other_node])
|
103
|
+
Sugar.create_call_expression(op_name, [Kumi::Syntax::Literal.new(self), other_node])
|
104
104
|
else
|
105
105
|
super(other)
|
106
106
|
end
|
@@ -112,7 +112,7 @@ module Kumi
|
|
112
112
|
define_method(op) do |other|
|
113
113
|
if Sugar.syntax_expression?(other)
|
114
114
|
other_node = Sugar.ensure_literal(other)
|
115
|
-
Sugar.create_call_expression(op, [Syntax::Literal.new(self), other_node])
|
115
|
+
Sugar.create_call_expression(op, [Kumi::Syntax::Literal.new(self), other_node])
|
116
116
|
else
|
117
117
|
super(other)
|
118
118
|
end
|
@@ -127,7 +127,7 @@ module Kumi
|
|
127
127
|
def +(other)
|
128
128
|
if Sugar.syntax_expression?(other)
|
129
129
|
other_node = Sugar.ensure_literal(other)
|
130
|
-
Sugar.create_call_expression(:concat, [Syntax::Literal.new(self), other_node])
|
130
|
+
Sugar.create_call_expression(:concat, [Kumi::Syntax::Literal.new(self), other_node])
|
131
131
|
else
|
132
132
|
super
|
133
133
|
end
|
@@ -137,7 +137,7 @@ module Kumi
|
|
137
137
|
define_method(op) do |other|
|
138
138
|
if Sugar.syntax_expression?(other)
|
139
139
|
other_node = Sugar.ensure_literal(other)
|
140
|
-
Sugar.create_call_expression(op, [Syntax::Literal.new(self), other_node])
|
140
|
+
Sugar.create_call_expression(op, [Kumi::Syntax::Literal.new(self), other_node])
|
141
141
|
else
|
142
142
|
super(other)
|
143
143
|
end
|
@@ -156,7 +156,7 @@ module Kumi
|
|
156
156
|
# Convert array to syntax list expression with all elements as syntax nodes
|
157
157
|
def to_syntax_list
|
158
158
|
syntax_elements = map { |item| Sugar.ensure_literal(item) }
|
159
|
-
Syntax::
|
159
|
+
Kumi::Syntax::ArrayExpression.new(syntax_elements)
|
160
160
|
end
|
161
161
|
|
162
162
|
# Create array method that works with syntax expressions
|
@@ -204,6 +204,58 @@ module Kumi
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
end
|
207
|
+
|
208
|
+
# Shared refinement for proxy objects that need to handle operators
|
209
|
+
# Both DeclarationReferenceProxy and InputFieldProxy can use this
|
210
|
+
module ProxyRefinement
|
211
|
+
def self.extended(proxy_class)
|
212
|
+
# Add operator methods directly to the proxy class
|
213
|
+
proxy_class.class_eval do
|
214
|
+
# Arithmetic operations
|
215
|
+
ARITHMETIC_OPS.each do |op, op_name|
|
216
|
+
define_method(op) do |other|
|
217
|
+
ast_node = to_ast_node
|
218
|
+
other_node = Sugar.ensure_literal(other)
|
219
|
+
Sugar.create_call_expression(op_name, [ast_node, other_node])
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Comparison operations (including == and != that don't work with refinements)
|
224
|
+
COMPARISON_OPS.each do |op|
|
225
|
+
define_method(op) do |other|
|
226
|
+
ast_node = to_ast_node
|
227
|
+
other_node = Sugar.ensure_literal(other)
|
228
|
+
Sugar.create_call_expression(op, [ast_node, other_node])
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Logical operations
|
233
|
+
define_method(:&) do |other|
|
234
|
+
ast_node = to_ast_node
|
235
|
+
other_node = Sugar.ensure_literal(other)
|
236
|
+
Sugar.create_call_expression(:and, [ast_node, other_node])
|
237
|
+
end
|
238
|
+
|
239
|
+
define_method(:|) do |other|
|
240
|
+
ast_node = to_ast_node
|
241
|
+
other_node = Sugar.ensure_literal(other)
|
242
|
+
Sugar.create_call_expression(:or, [ast_node, other_node])
|
243
|
+
end
|
244
|
+
|
245
|
+
# Array access
|
246
|
+
define_method(:[]) do |index|
|
247
|
+
ast_node = to_ast_node
|
248
|
+
Sugar.create_call_expression(:at, [ast_node, Sugar.ensure_literal(index)])
|
249
|
+
end
|
250
|
+
|
251
|
+
# Unary minus
|
252
|
+
define_method(:-@) do
|
253
|
+
ast_node = to_ast_node
|
254
|
+
Sugar.create_call_expression(:subtract, [Sugar.ensure_literal(0), ast_node])
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
207
259
|
end
|
208
260
|
end
|
209
261
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
# Ruby DSL parser for Kumi schemas
|
5
|
+
# Converts Ruby block syntax into AST nodes
|
6
|
+
module RubyParser
|
7
|
+
# This module contains all Ruby DSL parsing functionality
|
8
|
+
# The main entry point is through Dsl.build_syntax_tree
|
9
|
+
end
|
10
|
+
end
|
data/lib/kumi/schema.rb
CHANGED
@@ -16,19 +16,19 @@ module Kumi
|
|
16
16
|
raise("No schema defined") unless @__compiled_schema__
|
17
17
|
|
18
18
|
# Validate input types and domain constraints
|
19
|
-
input_meta = @__analyzer_result__.state[:
|
19
|
+
input_meta = @__analyzer_result__.state[:inputs] || {}
|
20
20
|
violations = Input::Validator.validate_context(context, input_meta)
|
21
21
|
|
22
22
|
raise Errors::InputValidationError, violations unless violations.empty?
|
23
23
|
|
24
|
-
SchemaInstance.new(@__compiled_schema__, @__analyzer_result__, context)
|
24
|
+
SchemaInstance.new(@__compiled_schema__, @__analyzer_result__.state, context)
|
25
25
|
end
|
26
26
|
|
27
27
|
def explain(context, *keys)
|
28
28
|
raise("No schema defined") unless @__compiled_schema__
|
29
29
|
|
30
30
|
# Validate input types and domain constraints
|
31
|
-
input_meta = @__analyzer_result__.state[:
|
31
|
+
input_meta = @__analyzer_result__.state[:inputs] || {}
|
32
32
|
violations = Input::Validator.validate_context(context, input_meta)
|
33
33
|
|
34
34
|
raise Errors::InputValidationError, violations unless violations.empty?
|
@@ -41,11 +41,17 @@ module Kumi
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def schema(&block)
|
44
|
-
@__syntax_tree__ = Kumi::
|
44
|
+
@__syntax_tree__ = Kumi::RubyParser::Dsl.build_syntax_tree(&block).freeze
|
45
45
|
@__analyzer_result__ = Analyzer.analyze!(@__syntax_tree__).freeze
|
46
46
|
@__compiled_schema__ = Compiler.compile(@__syntax_tree__, analyzer: @__analyzer_result__).freeze
|
47
47
|
|
48
48
|
Inspector.new(@__syntax_tree__, @__analyzer_result__, @__compiled_schema__)
|
49
49
|
end
|
50
|
+
|
51
|
+
def schema_metadata
|
52
|
+
raise("No schema defined") unless @__analyzer_result__
|
53
|
+
|
54
|
+
@schema_metadata ||= SchemaMetadata.new(@__analyzer_result__.state, @__syntax_tree__)
|
55
|
+
end
|
50
56
|
end
|
51
57
|
end
|
data/lib/kumi/schema_instance.rb
CHANGED
@@ -11,11 +11,11 @@ module Kumi
|
|
11
11
|
# instance.input # original context (read‑only)
|
12
12
|
|
13
13
|
class SchemaInstance
|
14
|
-
attr_reader :compiled_schema, :
|
14
|
+
attr_reader :compiled_schema, :metadata, :context
|
15
15
|
|
16
|
-
def initialize(compiled_schema,
|
16
|
+
def initialize(compiled_schema, metadata, context)
|
17
17
|
@compiled_schema = compiled_schema # Kumi::CompiledSchema
|
18
|
-
@
|
18
|
+
@metadata = metadata # Frozen state hash
|
19
19
|
@context = context.is_a?(EvaluationWrapper) ? context : EvaluationWrapper.new(context)
|
20
20
|
end
|
21
21
|
|
@@ -68,12 +68,12 @@ module Kumi
|
|
68
68
|
|
69
69
|
def input_field_exists?(field)
|
70
70
|
# Check if field is declared in input block
|
71
|
-
input_meta = @
|
71
|
+
input_meta = @metadata[:inputs] || {}
|
72
72
|
input_meta.key?(field) || @context.key?(field)
|
73
73
|
end
|
74
74
|
|
75
75
|
def validate_domain_constraint(field, value)
|
76
|
-
input_meta = @
|
76
|
+
input_meta = @metadata[:inputs] || {}
|
77
77
|
field_meta = input_meta[field]
|
78
78
|
return unless field_meta&.dig(:domain)
|
79
79
|
|
@@ -99,7 +99,7 @@ module Kumi
|
|
99
99
|
|
100
100
|
def find_dependent_declarations_optimized(field)
|
101
101
|
# Use precomputed transitive closure for true O(1) lookup!
|
102
|
-
transitive_dependents = @
|
102
|
+
transitive_dependents = @metadata[:dependents]
|
103
103
|
return [] unless transitive_dependents
|
104
104
|
|
105
105
|
# This is truly O(1) - just array lookup, no traversal needed
|