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
@@ -1,261 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module RubyParser
|
5
|
-
module Sugar
|
6
|
-
include Syntax
|
7
|
-
|
8
|
-
ARITHMETIC_OPS = {
|
9
|
-
:+ => :add, :- => :subtract, :* => :multiply,
|
10
|
-
:/ => :divide, :% => :modulo, :** => :power
|
11
|
-
}.freeze
|
12
|
-
|
13
|
-
COMPARISON_OPS = %i[< <= > >= == !=].freeze
|
14
|
-
|
15
|
-
LITERAL_TYPES = [
|
16
|
-
Integer, String, Symbol, TrueClass, FalseClass, Float, Regexp, NilClass
|
17
|
-
].freeze
|
18
|
-
|
19
|
-
# Collection methods that can be applied to arrays/syntax nodes
|
20
|
-
COLLECTION_METHODS = %i[
|
21
|
-
sum size length first last sort reverse unique min max empty? flatten
|
22
|
-
map_with_index indices
|
23
|
-
].freeze
|
24
|
-
|
25
|
-
def self.ensure_literal(obj)
|
26
|
-
return Kumi::Syntax::Literal.new(obj) if LITERAL_TYPES.any? { |type| obj.is_a?(type) }
|
27
|
-
return obj if obj.is_a?(Syntax::Node)
|
28
|
-
return obj.to_ast_node if obj.respond_to?(:to_ast_node)
|
29
|
-
|
30
|
-
Kumi::Syntax::Literal.new(obj)
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.syntax_expression?(obj)
|
34
|
-
obj.is_a?(Syntax::Node) || obj.respond_to?(:to_ast_node)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Create a call expression with consistent error handling
|
38
|
-
def self.create_call_expression(fn_name, args)
|
39
|
-
Kumi::Syntax::CallExpression.new(fn_name, args)
|
40
|
-
end
|
41
|
-
|
42
|
-
module ExpressionRefinement
|
43
|
-
refine Syntax::Node do
|
44
|
-
# Arithmetic operations
|
45
|
-
ARITHMETIC_OPS.each do |op, op_name|
|
46
|
-
define_method(op) do |other|
|
47
|
-
other_node = Sugar.ensure_literal(other)
|
48
|
-
Sugar.create_call_expression(op_name, [self, other_node])
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# Comparison operations
|
53
|
-
COMPARISON_OPS.each do |op|
|
54
|
-
define_method(op) do |other|
|
55
|
-
other_node = Sugar.ensure_literal(other)
|
56
|
-
Sugar.create_call_expression(op, [self, other_node])
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# Array access
|
61
|
-
def [](index)
|
62
|
-
Sugar.create_call_expression(:at, [self, Sugar.ensure_literal(index)])
|
63
|
-
end
|
64
|
-
|
65
|
-
# Unary minus
|
66
|
-
def -@
|
67
|
-
Sugar.create_call_expression(:subtract, [Sugar.ensure_literal(0), self])
|
68
|
-
end
|
69
|
-
|
70
|
-
# Logical operations
|
71
|
-
def &(other)
|
72
|
-
Sugar.create_call_expression(:and, [self, Sugar.ensure_literal(other)])
|
73
|
-
end
|
74
|
-
|
75
|
-
def |(other)
|
76
|
-
Sugar.create_call_expression(:or, [self, Sugar.ensure_literal(other)])
|
77
|
-
end
|
78
|
-
|
79
|
-
# Collection methods - single argument (self)
|
80
|
-
COLLECTION_METHODS.each do |method_name|
|
81
|
-
next if method_name == :include? # Special case with element argument
|
82
|
-
|
83
|
-
define_method(method_name) do
|
84
|
-
Sugar.create_call_expression(method_name, [self])
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
# Special case: include? takes an element argument
|
89
|
-
def include?(element)
|
90
|
-
Sugar.create_call_expression(:include?, [self, Sugar.ensure_literal(element)])
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
module NumericRefinement
|
96
|
-
[Integer, Float].each do |klass|
|
97
|
-
refine klass do
|
98
|
-
# Arithmetic operations with syntax expressions
|
99
|
-
ARITHMETIC_OPS.each do |op, op_name|
|
100
|
-
define_method(op) do |other|
|
101
|
-
if Sugar.syntax_expression?(other)
|
102
|
-
other_node = Sugar.ensure_literal(other)
|
103
|
-
Sugar.create_call_expression(op_name, [Kumi::Syntax::Literal.new(self), other_node])
|
104
|
-
else
|
105
|
-
super(other)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# Comparison operations with syntax expressions
|
111
|
-
COMPARISON_OPS.each do |op|
|
112
|
-
define_method(op) do |other|
|
113
|
-
if Sugar.syntax_expression?(other)
|
114
|
-
other_node = Sugar.ensure_literal(other)
|
115
|
-
Sugar.create_call_expression(op, [Kumi::Syntax::Literal.new(self), other_node])
|
116
|
-
else
|
117
|
-
super(other)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
module StringRefinement
|
126
|
-
refine String do
|
127
|
-
def +(other)
|
128
|
-
if Sugar.syntax_expression?(other)
|
129
|
-
other_node = Sugar.ensure_literal(other)
|
130
|
-
Sugar.create_call_expression(:concat, [Kumi::Syntax::Literal.new(self), other_node])
|
131
|
-
else
|
132
|
-
super
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
%i[== !=].each do |op|
|
137
|
-
define_method(op) do |other|
|
138
|
-
if Sugar.syntax_expression?(other)
|
139
|
-
other_node = Sugar.ensure_literal(other)
|
140
|
-
Sugar.create_call_expression(op, [Kumi::Syntax::Literal.new(self), other_node])
|
141
|
-
else
|
142
|
-
super(other)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
module ArrayRefinement
|
150
|
-
refine Array do
|
151
|
-
# Helper method to check if array contains any syntax expressions
|
152
|
-
def any_syntax_expressions?
|
153
|
-
any? { |item| Sugar.syntax_expression?(item) }
|
154
|
-
end
|
155
|
-
|
156
|
-
# Convert array to syntax list expression with all elements as syntax nodes
|
157
|
-
def to_syntax_list
|
158
|
-
syntax_elements = map { |item| Sugar.ensure_literal(item) }
|
159
|
-
Kumi::Syntax::ArrayExpression.new(syntax_elements)
|
160
|
-
end
|
161
|
-
|
162
|
-
# Create array method that works with syntax expressions
|
163
|
-
def self.define_array_syntax_method(method_name, has_argument: false)
|
164
|
-
define_method(method_name) do |*args|
|
165
|
-
if any_syntax_expressions?
|
166
|
-
array_literal = to_syntax_list
|
167
|
-
call_args = [array_literal]
|
168
|
-
call_args.concat(args.map { |arg| Sugar.ensure_literal(arg) }) if has_argument
|
169
|
-
Sugar.create_call_expression(method_name, call_args)
|
170
|
-
else
|
171
|
-
super(*args)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
# Define collection methods without arguments
|
177
|
-
%i[sum size length first last sort reverse unique min max empty? flatten].each do |method_name|
|
178
|
-
define_array_syntax_method(method_name)
|
179
|
-
end
|
180
|
-
|
181
|
-
# Define methods with arguments
|
182
|
-
define_array_syntax_method(:include?, has_argument: true)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
module ModuleRefinement
|
187
|
-
refine Module do
|
188
|
-
# Allow modules to provide schema utilities and helpers
|
189
|
-
def with_schema_utilities
|
190
|
-
include Kumi::Schema if respond_to?(:include)
|
191
|
-
extend Kumi::Schema if respond_to?(:extend)
|
192
|
-
end
|
193
|
-
|
194
|
-
# Helper for defining schema constants that can be used in multiple schemas
|
195
|
-
def schema_const(name, value)
|
196
|
-
const_set(name, value.freeze)
|
197
|
-
end
|
198
|
-
|
199
|
-
# Enable easy schema composition
|
200
|
-
def compose_schema(*modules)
|
201
|
-
modules.each do |mod|
|
202
|
-
include mod if mod.is_a?(Module)
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
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
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
data/lib/kumi/ruby_parser.rb
DELETED
@@ -1,10 +0,0 @@
|
|
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_instance.rb
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
# A bound pair of <compiled schema + context>. Immutable.
|
5
|
-
#
|
6
|
-
# Public API ----------------------------------------------------------
|
7
|
-
# instance.evaluate # => full Hash of all bindings
|
8
|
-
# instance.evaluate(:tax_due, :rate)
|
9
|
-
# instance.slice(:tax_due) # alias for evaluate(*keys)
|
10
|
-
# instance.explain(:tax_due) # pretty trace string
|
11
|
-
# instance.input # original context (read‑only)
|
12
|
-
|
13
|
-
class SchemaInstance
|
14
|
-
attr_reader :compiled_schema, :metadata, :context
|
15
|
-
|
16
|
-
def initialize(compiled_schema, metadata, context)
|
17
|
-
@compiled_schema = compiled_schema # Kumi::CompiledSchema
|
18
|
-
@metadata = metadata # Frozen state hash
|
19
|
-
@context = context.is_a?(EvaluationWrapper) ? context : EvaluationWrapper.new(context)
|
20
|
-
end
|
21
|
-
|
22
|
-
# Hash‑like read of one or many bindings
|
23
|
-
def evaluate(*key_names)
|
24
|
-
if key_names.empty?
|
25
|
-
@compiled_schema.evaluate(@context)
|
26
|
-
else
|
27
|
-
@compiled_schema.evaluate(@context, *key_names)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def slice(*key_names)
|
32
|
-
return {} if key_names.empty?
|
33
|
-
|
34
|
-
evaluate(*key_names)
|
35
|
-
end
|
36
|
-
|
37
|
-
def [](key_name)
|
38
|
-
evaluate(key_name)[key_name]
|
39
|
-
end
|
40
|
-
|
41
|
-
# Update input values and clear affected cached computations
|
42
|
-
def update(**changes)
|
43
|
-
changes.each do |field, value|
|
44
|
-
# Validate field exists
|
45
|
-
raise ArgumentError, "unknown input field: #{field}" unless input_field_exists?(field)
|
46
|
-
|
47
|
-
# Validate domain constraints
|
48
|
-
validate_domain_constraint(field, value)
|
49
|
-
|
50
|
-
# Update the input data
|
51
|
-
@context[field] = value
|
52
|
-
|
53
|
-
# Clear affected cached values using transitive closure by default
|
54
|
-
if ENV["KUMI_SIMPLE_CACHE"] == "true"
|
55
|
-
# Simple fallback: clear all cached values
|
56
|
-
@context.clear_cache
|
57
|
-
else
|
58
|
-
# Default: selective cache clearing using precomputed transitive closure
|
59
|
-
affected_keys = find_dependent_declarations_optimized(field)
|
60
|
-
affected_keys.each { |key| @context.clear_cache(key) }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
self # Return self for chaining
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
|
-
def input_field_exists?(field)
|
70
|
-
# Check if field is declared in input block
|
71
|
-
input_meta = @metadata[:inputs] || {}
|
72
|
-
input_meta.key?(field) || @context.key?(field)
|
73
|
-
end
|
74
|
-
|
75
|
-
def validate_domain_constraint(field, value)
|
76
|
-
input_meta = @metadata[:inputs] || {}
|
77
|
-
field_meta = input_meta[field]
|
78
|
-
return unless field_meta&.dig(:domain)
|
79
|
-
|
80
|
-
domain = field_meta[:domain]
|
81
|
-
return unless violates_domain?(value, domain)
|
82
|
-
|
83
|
-
raise ArgumentError, "value #{value} is not in domain #{domain}"
|
84
|
-
end
|
85
|
-
|
86
|
-
def violates_domain?(value, domain)
|
87
|
-
case domain
|
88
|
-
when Range
|
89
|
-
!domain.include?(value)
|
90
|
-
when Array
|
91
|
-
!domain.include?(value)
|
92
|
-
when Proc
|
93
|
-
# For Proc domains, we can't statically analyze
|
94
|
-
false
|
95
|
-
else
|
96
|
-
false
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def find_dependent_declarations_optimized(field)
|
101
|
-
# Use precomputed transitive closure for true O(1) lookup!
|
102
|
-
transitive_dependents = @metadata[:dependents]
|
103
|
-
return [] unless transitive_dependents
|
104
|
-
|
105
|
-
# This is truly O(1) - just array lookup, no traversal needed
|
106
|
-
transitive_dependents[field] || []
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
data/lib/kumi/types/builder.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Types
|
5
|
-
# Builds complex type structures
|
6
|
-
class Builder
|
7
|
-
def self.array(elem_type)
|
8
|
-
raise ArgumentError, "Invalid array element type: #{elem_type}" unless Validator.valid_type?(elem_type)
|
9
|
-
|
10
|
-
{ array: elem_type }
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.hash(key_type, val_type)
|
14
|
-
raise ArgumentError, "Invalid hash key type: #{key_type}" unless Validator.valid_type?(key_type)
|
15
|
-
raise ArgumentError, "Invalid hash value type: #{val_type}" unless Validator.valid_type?(val_type)
|
16
|
-
|
17
|
-
{ hash: [key_type, val_type] }
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,94 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Types
|
5
|
-
# Handles type compatibility and unification
|
6
|
-
class Compatibility
|
7
|
-
# Check if two types are compatible
|
8
|
-
def self.compatible?(type1, type2)
|
9
|
-
# Any type is compatible with anything
|
10
|
-
return true if type1 == :any || type2 == :any
|
11
|
-
|
12
|
-
# Exact match
|
13
|
-
return true if type1 == type2
|
14
|
-
|
15
|
-
# Generic array compatibility: :array is compatible with any structured array
|
16
|
-
return true if (type1 == :array && Validator.array_type?(type2)) ||
|
17
|
-
(type2 == :array && Validator.array_type?(type1))
|
18
|
-
|
19
|
-
# Numeric compatibility
|
20
|
-
return true if numeric_compatible?(type1, type2)
|
21
|
-
|
22
|
-
# Array compatibility
|
23
|
-
return array_compatible?(type1, type2) if array_types?(type1, type2)
|
24
|
-
|
25
|
-
# Hash compatibility
|
26
|
-
return hash_compatible?(type1, type2) if hash_types?(type1, type2)
|
27
|
-
|
28
|
-
false
|
29
|
-
end
|
30
|
-
|
31
|
-
# Find the most specific common type between two types
|
32
|
-
def self.unify(type1, type2)
|
33
|
-
return type1 if type1 == type2
|
34
|
-
|
35
|
-
# :any unifies to the other type (more specific)
|
36
|
-
return type2 if type1 == :any
|
37
|
-
return type1 if type2 == :any
|
38
|
-
|
39
|
-
# Generic array unification: structured array is more specific than :array
|
40
|
-
return type2 if type1 == :array && Validator.array_type?(type2)
|
41
|
-
return type1 if type2 == :array && Validator.array_type?(type1)
|
42
|
-
|
43
|
-
# Numeric unification
|
44
|
-
if numeric_compatible?(type1, type2)
|
45
|
-
return :integer if type1 == :integer && type2 == :integer
|
46
|
-
|
47
|
-
return :float # One or both are float
|
48
|
-
end
|
49
|
-
|
50
|
-
# Array unification
|
51
|
-
if array_types?(type1, type2)
|
52
|
-
elem1 = type1[:array]
|
53
|
-
elem2 = type2[:array]
|
54
|
-
unified_elem = unify(elem1, elem2)
|
55
|
-
return Kumi::Types.array(unified_elem)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Hash unification
|
59
|
-
if hash_types?(type1, type2)
|
60
|
-
key1, val1 = type1[:hash]
|
61
|
-
key2, val2 = type2[:hash]
|
62
|
-
unified_key = unify(key1, key2)
|
63
|
-
unified_val = unify(val1, val2)
|
64
|
-
return Kumi::Types.hash(unified_key, unified_val)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Fall back to :any for incompatible types
|
68
|
-
:any
|
69
|
-
end
|
70
|
-
|
71
|
-
def self.numeric_compatible?(type1, type2)
|
72
|
-
numeric_types = %i[integer float]
|
73
|
-
numeric_types.include?(type1) && numeric_types.include?(type2)
|
74
|
-
end
|
75
|
-
|
76
|
-
def self.array_types?(type1, type2)
|
77
|
-
Validator.array_type?(type1) && Validator.array_type?(type2)
|
78
|
-
end
|
79
|
-
|
80
|
-
def self.hash_types?(type1, type2)
|
81
|
-
Validator.hash_type?(type1) && Validator.hash_type?(type2)
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.array_compatible?(type1, type2)
|
85
|
-
compatible?(type1[:array], type2[:array])
|
86
|
-
end
|
87
|
-
|
88
|
-
def self.hash_compatible?(type1, type2)
|
89
|
-
compatible?(type1[:hash][0], type2[:hash][0]) &&
|
90
|
-
compatible?(type1[:hash][1], type2[:hash][1])
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
data/lib/kumi/types/formatter.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Types
|
5
|
-
# Formats types for display and debugging
|
6
|
-
class Formatter
|
7
|
-
# Convert types to string representation
|
8
|
-
def self.type_to_s(type)
|
9
|
-
case type
|
10
|
-
when Hash
|
11
|
-
if type[:array]
|
12
|
-
"array(#{type_to_s(type[:array])})"
|
13
|
-
elsif type[:hash]
|
14
|
-
"hash(#{type_to_s(type[:hash][0])}, #{type_to_s(type[:hash][1])})"
|
15
|
-
else
|
16
|
-
type.to_s
|
17
|
-
end
|
18
|
-
else
|
19
|
-
type.to_s
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
data/lib/kumi/types/inference.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "date"
|
4
|
-
|
5
|
-
module Kumi
|
6
|
-
module Types
|
7
|
-
# Infers types from Ruby values
|
8
|
-
class Inference
|
9
|
-
def self.infer_from_value(value)
|
10
|
-
case value
|
11
|
-
when String then :string
|
12
|
-
when Integer then :integer
|
13
|
-
when Float then :float
|
14
|
-
when TrueClass, FalseClass then :boolean
|
15
|
-
when Symbol then :symbol
|
16
|
-
when Regexp then :regexp
|
17
|
-
when Time then :time
|
18
|
-
when DateTime then :datetime
|
19
|
-
when Date then :date
|
20
|
-
when Array
|
21
|
-
return Kumi::Types.array(:any) if value.empty?
|
22
|
-
|
23
|
-
# Infer element type from first element (simple heuristic)
|
24
|
-
first_elem_type = infer_from_value(value.first)
|
25
|
-
Kumi::Types.array(first_elem_type)
|
26
|
-
when Hash
|
27
|
-
return Kumi::Types.hash(:any, :any) if value.empty?
|
28
|
-
|
29
|
-
# Infer key/value types from first pair (simple heuristic)
|
30
|
-
first_key, first_value = value.first
|
31
|
-
key_type = infer_from_value(first_key)
|
32
|
-
value_type = infer_from_value(first_value)
|
33
|
-
Kumi::Types.hash(key_type, value_type)
|
34
|
-
else
|
35
|
-
:any
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "date"
|
4
|
-
|
5
|
-
module Kumi
|
6
|
-
module Types
|
7
|
-
# Normalizes different type inputs to canonical forms
|
8
|
-
class Normalizer
|
9
|
-
# Type normalization - convert various inputs to canonical type symbols
|
10
|
-
def self.normalize(type_input)
|
11
|
-
case type_input
|
12
|
-
when Symbol
|
13
|
-
return type_input if Validator.valid_type?(type_input)
|
14
|
-
|
15
|
-
raise ArgumentError, "Invalid type symbol: #{type_input}"
|
16
|
-
when String
|
17
|
-
symbol_type = type_input.to_sym
|
18
|
-
return symbol_type if Validator.valid_type?(symbol_type)
|
19
|
-
|
20
|
-
raise ArgumentError, "Invalid type string: #{type_input}"
|
21
|
-
when Hash
|
22
|
-
return type_input if Validator.valid_type?(type_input)
|
23
|
-
|
24
|
-
raise ArgumentError, "Invalid type hash: #{type_input}"
|
25
|
-
when Class
|
26
|
-
# Handle Ruby class inputs
|
27
|
-
case type_input.name
|
28
|
-
when "Integer" then :integer
|
29
|
-
when "String" then :string
|
30
|
-
when "Float" then :float
|
31
|
-
when "TrueClass", "FalseClass" then :boolean
|
32
|
-
when "Array" then raise ArgumentError, "Use array(:type) helper for array types"
|
33
|
-
when "Hash" then raise ArgumentError, "Use hash(:key_type, :value_type) helper for hash types"
|
34
|
-
else
|
35
|
-
raise ArgumentError, "Unsupported class type: #{type_input}"
|
36
|
-
end
|
37
|
-
else
|
38
|
-
case type_input
|
39
|
-
when Integer, Float, Numeric
|
40
|
-
raise ArgumentError, "Type must be a symbol, got #{type_input} (#{type_input.class})"
|
41
|
-
else
|
42
|
-
raise ArgumentError, "Invalid type input: #{type_input} (#{type_input.class})"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Legacy compatibility - coerce old constants to symbols
|
48
|
-
def self.coerce(type_input)
|
49
|
-
# Handle legacy constant usage
|
50
|
-
return type_input if type_input.is_a?(Symbol) && Validator.valid_type?(type_input)
|
51
|
-
|
52
|
-
# Handle legacy constant objects
|
53
|
-
case type_input
|
54
|
-
when STRING then :string
|
55
|
-
when INT then :integer
|
56
|
-
when FLOAT, NUMERIC then :float # Both FLOAT and NUMERIC map to :float
|
57
|
-
when BOOL then :boolean
|
58
|
-
when ANY then :any
|
59
|
-
when SYMBOL then :symbol
|
60
|
-
when REGEXP then :regexp
|
61
|
-
when TIME then :time
|
62
|
-
when DATE then :date
|
63
|
-
when DATETIME then :datetime
|
64
|
-
else
|
65
|
-
normalize(type_input)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
data/lib/kumi/types/validator.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Types
|
5
|
-
# Validates type definitions and structures
|
6
|
-
class Validator
|
7
|
-
VALID_TYPES = %i[string integer float boolean any symbol regexp time date datetime array].freeze
|
8
|
-
|
9
|
-
def self.valid_type?(type)
|
10
|
-
return true if VALID_TYPES.include?(type)
|
11
|
-
return true if array_type?(type)
|
12
|
-
return true if hash_type?(type)
|
13
|
-
|
14
|
-
false
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.array_type?(type)
|
18
|
-
type.is_a?(Hash) && type.keys == [:array] && valid_type?(type[:array])
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.hash_type?(type)
|
22
|
-
type.is_a?(Hash) &&
|
23
|
-
type.keys.sort == [:hash] &&
|
24
|
-
type[:hash].is_a?(Array) &&
|
25
|
-
type[:hash].size == 2 &&
|
26
|
-
valid_type?(type[:hash][0]) &&
|
27
|
-
valid_type?(type[:hash][1])
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.primitive_type?(type)
|
31
|
-
VALID_TYPES.include?(type)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|