kumi 0.0.6 → 0.0.8

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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +34 -177
  3. data/README.md +41 -7
  4. data/docs/SYNTAX.md +2 -7
  5. data/docs/features/array-broadcasting.md +1 -1
  6. data/docs/schema_metadata/broadcasts.md +53 -0
  7. data/docs/schema_metadata/cascades.md +45 -0
  8. data/docs/schema_metadata/declarations.md +54 -0
  9. data/docs/schema_metadata/dependencies.md +57 -0
  10. data/docs/schema_metadata/evaluation_order.md +29 -0
  11. data/docs/schema_metadata/examples.md +95 -0
  12. data/docs/schema_metadata/inferred_types.md +46 -0
  13. data/docs/schema_metadata/inputs.md +86 -0
  14. data/docs/schema_metadata.md +108 -0
  15. data/examples/game_of_life.rb +1 -1
  16. data/examples/static_analysis_errors.rb +7 -7
  17. data/lib/kumi/analyzer.rb +20 -20
  18. data/lib/kumi/compiler.rb +44 -50
  19. data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
  20. data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
  21. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
  22. data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
  23. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
  24. data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
  25. data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
  26. data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
  27. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
  28. data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
  29. data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
  30. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
  31. data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
  32. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
  33. data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
  34. data/lib/kumi/core/atom_unsat_solver.rb +396 -0
  35. data/lib/kumi/core/compiled_schema.rb +43 -0
  36. data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
  37. data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
  38. data/lib/kumi/core/domain/range_analyzer.rb +85 -0
  39. data/lib/kumi/core/domain/validator.rb +82 -0
  40. data/lib/kumi/core/domain/violation_formatter.rb +42 -0
  41. data/lib/kumi/core/error_reporter.rb +166 -0
  42. data/lib/kumi/core/error_reporting.rb +97 -0
  43. data/lib/kumi/core/errors.rb +120 -0
  44. data/lib/kumi/core/evaluation_wrapper.rb +40 -0
  45. data/lib/kumi/core/explain.rb +295 -0
  46. data/lib/kumi/core/export/deserializer.rb +41 -0
  47. data/lib/kumi/core/export/errors.rb +14 -0
  48. data/lib/kumi/core/export/node_builders.rb +142 -0
  49. data/lib/kumi/core/export/node_registry.rb +54 -0
  50. data/lib/kumi/core/export/node_serializers.rb +158 -0
  51. data/lib/kumi/core/export/serializer.rb +25 -0
  52. data/lib/kumi/core/export.rb +35 -0
  53. data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
  54. data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
  55. data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
  56. data/lib/kumi/core/function_registry/function_builder.rb +95 -0
  57. data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
  58. data/lib/kumi/core/function_registry/math_functions.rb +74 -0
  59. data/lib/kumi/core/function_registry/string_functions.rb +57 -0
  60. data/lib/kumi/core/function_registry/type_functions.rb +53 -0
  61. data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
  62. data/lib/kumi/core/input/type_matcher.rb +97 -0
  63. data/lib/kumi/core/input/validator.rb +51 -0
  64. data/lib/kumi/core/input/violation_creator.rb +52 -0
  65. data/lib/kumi/core/json_schema/generator.rb +65 -0
  66. data/lib/kumi/core/json_schema/validator.rb +27 -0
  67. data/lib/kumi/core/json_schema.rb +16 -0
  68. data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
  69. data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
  70. data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
  71. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
  72. data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
  73. data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
  74. data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
  75. data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
  76. data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
  77. data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
  78. data/lib/kumi/core/ruby_parser/parser.rb +71 -0
  79. data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
  80. data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
  81. data/lib/kumi/core/ruby_parser.rb +12 -0
  82. data/lib/kumi/core/schema_instance.rb +111 -0
  83. data/lib/kumi/core/types/builder.rb +23 -0
  84. data/lib/kumi/core/types/compatibility.rb +96 -0
  85. data/lib/kumi/core/types/formatter.rb +26 -0
  86. data/lib/kumi/core/types/inference.rb +42 -0
  87. data/lib/kumi/core/types/normalizer.rb +72 -0
  88. data/lib/kumi/core/types/validator.rb +37 -0
  89. data/lib/kumi/core/types.rb +66 -0
  90. data/lib/kumi/core/vectorization_metadata.rb +110 -0
  91. data/lib/kumi/errors.rb +1 -112
  92. data/lib/kumi/registry.rb +37 -0
  93. data/lib/kumi/schema.rb +13 -7
  94. data/lib/kumi/schema_metadata.rb +524 -0
  95. data/lib/kumi/syntax/array_expression.rb +6 -6
  96. data/lib/kumi/syntax/call_expression.rb +4 -4
  97. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  98. data/lib/kumi/syntax/case_expression.rb +4 -4
  99. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  100. data/lib/kumi/syntax/hash_expression.rb +4 -4
  101. data/lib/kumi/syntax/input_declaration.rb +5 -5
  102. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  103. data/lib/kumi/syntax/input_reference.rb +5 -5
  104. data/lib/kumi/syntax/literal.rb +4 -4
  105. data/lib/kumi/syntax/node.rb +34 -34
  106. data/lib/kumi/syntax/root.rb +6 -6
  107. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  108. data/lib/kumi/syntax/value_declaration.rb +4 -4
  109. data/lib/kumi/version.rb +1 -1
  110. data/lib/kumi.rb +14 -0
  111. data/migrate_to_core_iterative.rb +938 -0
  112. data/scripts/generate_function_docs.rb +9 -9
  113. metadata +85 -69
  114. data/lib/generators/trait_engine/templates/schema_spec.rb.erb +0 -27
  115. data/lib/kumi/analyzer/analysis_state.rb +0 -37
  116. data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
  117. data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -251
  118. data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
  119. data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
  120. data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
  121. data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
  122. data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
  123. data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -110
  124. data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
  125. data/lib/kumi/analyzer/passes/type_checker.rb +0 -162
  126. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
  127. data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
  128. data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -406
  129. data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
  130. data/lib/kumi/atom_unsat_solver.rb +0 -394
  131. data/lib/kumi/compiled_schema.rb +0 -41
  132. data/lib/kumi/constraint_relationship_solver.rb +0 -638
  133. data/lib/kumi/domain/enum_analyzer.rb +0 -53
  134. data/lib/kumi/domain/range_analyzer.rb +0 -83
  135. data/lib/kumi/domain/validator.rb +0 -80
  136. data/lib/kumi/domain/violation_formatter.rb +0 -40
  137. data/lib/kumi/error_reporter.rb +0 -164
  138. data/lib/kumi/error_reporting.rb +0 -95
  139. data/lib/kumi/evaluation_wrapper.rb +0 -38
  140. data/lib/kumi/explain.rb +0 -281
  141. data/lib/kumi/export/deserializer.rb +0 -39
  142. data/lib/kumi/export/errors.rb +0 -12
  143. data/lib/kumi/export/node_builders.rb +0 -140
  144. data/lib/kumi/export/node_registry.rb +0 -52
  145. data/lib/kumi/export/node_serializers.rb +0 -156
  146. data/lib/kumi/export/serializer.rb +0 -23
  147. data/lib/kumi/export.rb +0 -33
  148. data/lib/kumi/function_registry/collection_functions.rb +0 -200
  149. data/lib/kumi/function_registry/comparison_functions.rb +0 -31
  150. data/lib/kumi/function_registry/conditional_functions.rb +0 -36
  151. data/lib/kumi/function_registry/function_builder.rb +0 -93
  152. data/lib/kumi/function_registry/logical_functions.rb +0 -42
  153. data/lib/kumi/function_registry/math_functions.rb +0 -72
  154. data/lib/kumi/function_registry/string_functions.rb +0 -54
  155. data/lib/kumi/function_registry/type_functions.rb +0 -51
  156. data/lib/kumi/input/type_matcher.rb +0 -95
  157. data/lib/kumi/input/validator.rb +0 -49
  158. data/lib/kumi/input/violation_creator.rb +0 -50
  159. data/lib/kumi/parser/build_context.rb +0 -25
  160. data/lib/kumi/parser/declaration_reference_proxy.rb +0 -36
  161. data/lib/kumi/parser/dsl.rb +0 -12
  162. data/lib/kumi/parser/dsl_cascade_builder.rb +0 -136
  163. data/lib/kumi/parser/expression_converter.rb +0 -126
  164. data/lib/kumi/parser/guard_rails.rb +0 -43
  165. data/lib/kumi/parser/input_builder.rb +0 -125
  166. data/lib/kumi/parser/input_field_proxy.rb +0 -46
  167. data/lib/kumi/parser/input_proxy.rb +0 -29
  168. data/lib/kumi/parser/nested_input.rb +0 -15
  169. data/lib/kumi/parser/parser.rb +0 -68
  170. data/lib/kumi/parser/schema_builder.rb +0 -173
  171. data/lib/kumi/parser/sugar.rb +0 -261
  172. data/lib/kumi/schema_instance.rb +0 -109
  173. data/lib/kumi/types/builder.rb +0 -21
  174. data/lib/kumi/types/compatibility.rb +0 -94
  175. data/lib/kumi/types/formatter.rb +0 -24
  176. data/lib/kumi/types/inference.rb +0 -40
  177. data/lib/kumi/types/normalizer.rb +0 -70
  178. data/lib/kumi/types/validator.rb +0 -35
  179. data/lib/kumi/types.rb +0 -64
  180. data/lib/kumi/vectorization_metadata.rb +0 -108
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module RubyParser
6
+ class SchemaBuilder
7
+ include GuardRails
8
+ include Syntax
9
+ include ErrorReporting
10
+
11
+ DSL_METHODS = %i[value trait input ref literal fn].freeze
12
+
13
+ def initialize(context)
14
+ @context = context
15
+ end
16
+
17
+ def value(name = nil, expr = nil, &blk)
18
+ update_location
19
+ validate_value_args(name, expr, blk)
20
+
21
+ expression = blk ? build_cascade(&blk) : ensure_syntax(expr)
22
+ @context.attributes << Kumi::Syntax::ValueDeclaration.new(name, expression, loc: @context.current_location)
23
+ end
24
+
25
+ def trait(*args, **kwargs)
26
+ update_location
27
+ raise_syntax_error("keyword trait syntax not supported", location: @context.current_location) unless kwargs.empty?
28
+ build_positional_trait(args)
29
+ end
30
+
31
+ def input(&blk)
32
+ return InputProxy.new(@context) unless block_given?
33
+
34
+ raise_syntax_error("input block already defined", location: @context.current_location) if @context.input_block_defined?
35
+ @context.mark_input_block_defined!
36
+
37
+ update_location
38
+ input_builder = InputBuilder.new(@context)
39
+ input_builder.instance_eval(&blk)
40
+ end
41
+
42
+ def ref(name)
43
+ update_location
44
+ Kumi::Syntax::DeclarationReference.new(name, loc: @context.current_location)
45
+ end
46
+
47
+ def literal(value)
48
+ update_location
49
+ Kumi::Syntax::Literal.new(value, loc: @context.current_location)
50
+ end
51
+
52
+ def fn(fn_name, *args)
53
+ update_location
54
+ expr_args = args.map { |a| ensure_syntax(a) }
55
+ Kumi::Syntax::CallExpression.new(fn_name, expr_args, loc: @context.current_location)
56
+ end
57
+
58
+ def method_missing(method_name, *args, &block)
59
+ if args.empty? && !block_given?
60
+ update_location
61
+ # Create proxy for declaration references (traits/values)
62
+ DeclarationReferenceProxy.new(method_name, @context)
63
+ else
64
+ super
65
+ end
66
+ end
67
+
68
+ def respond_to_missing?(_method_name, _include_private = false)
69
+ true
70
+ end
71
+
72
+ private
73
+
74
+ def update_location
75
+ # Use caller_locations(2, 1) to skip the DSL method and get the actual user code location
76
+ # Stack: [0] update_location, [1] DSL method (value/trait/etc), [2] user's DSL code
77
+ caller_location = caller_locations(2, 1).first
78
+
79
+ @context.current_location = Location.new(
80
+ file: caller_location.path,
81
+ line: caller_location.lineno,
82
+ column: 0
83
+ )
84
+ end
85
+
86
+ def validate_value_args(name, expr, blk)
87
+ raise_syntax_error("value requires a name as first argument", location: @context.current_location) if name.nil?
88
+ unless name.is_a?(Symbol)
89
+ raise_syntax_error("The name for 'value' must be a Symbol, got #{name.class}",
90
+ location: @context.current_location)
91
+ end
92
+
93
+ has_expr = !expr.nil?
94
+ has_block = blk
95
+
96
+ if has_expr && has_block
97
+ raise_syntax_error("value '#{name}' cannot be called with both an expression and a block", location: @context.current_location)
98
+ elsif !has_expr && !has_block
99
+ raise_syntax_error("value '#{name}' requires an expression or a block", location: @context.current_location)
100
+ end
101
+ end
102
+
103
+ def build_positional_trait(args)
104
+ case args.size
105
+ when 0
106
+ raise_syntax_error("trait requires a name and expression", location: @context.current_location)
107
+ when 1
108
+ name = args.first
109
+ raise_syntax_error("trait '#{name}' requires an expression", location: @context.current_location)
110
+ when 2
111
+ name, expression = args
112
+ validate_trait_name(name)
113
+ expr = ensure_syntax(expression)
114
+ @context.traits << Kumi::Syntax::TraitDeclaration.new(name, expr, loc: @context.current_location)
115
+ else
116
+ handle_deprecated_trait_syntax(args)
117
+ end
118
+ end
119
+
120
+ def handle_deprecated_trait_syntax(args)
121
+ if args.size == 3
122
+ name, = args
123
+ raise_syntax_error("trait '#{name}' requires exactly 3 arguments: lhs, operator, and rhs", location: @context.current_location)
124
+ end
125
+
126
+ # warn "DEPRECATION: trait(:name, lhs, operator, rhs) syntax is deprecated. Use: trait :name, (lhs operator rhs)"
127
+
128
+ if args.size == 4
129
+ name, lhs, operator, rhs = args
130
+ build_deprecated_trait(name, lhs, operator, [rhs])
131
+ else
132
+ name, lhs, operator, *rhs = args
133
+ build_deprecated_trait(name, lhs, operator, rhs)
134
+ end
135
+ end
136
+
137
+ def build_deprecated_trait(name, lhs, operator, rhs)
138
+ validate_trait_name(name)
139
+ validate_operator(operator)
140
+
141
+ rhs_exprs = rhs.map { |r| ensure_syntax(r) }
142
+ expr = Kumi::Syntax::CallExpression.new(operator, [ensure_syntax(lhs)] + rhs_exprs, loc: @context.current_location)
143
+ @context.traits << Kumi::Syntax::TraitDeclaration.new(name, expr, loc: @context.current_location)
144
+ end
145
+
146
+ def validate_trait_name(name)
147
+ return if name.is_a?(Symbol)
148
+
149
+ raise_syntax_error("The name for 'trait' must be a Symbol, got #{name.class}", location: @context.current_location)
150
+ end
151
+
152
+ def validate_operator(operator)
153
+ unless operator.is_a?(Symbol)
154
+ raise_syntax_error("expects a symbol for an operator, got #{operator.class}", location: @context.current_location)
155
+ end
156
+
157
+ return if Kumi::Registry.operator?(operator)
158
+
159
+ raise_syntax_error("unsupported operator `#{operator}`", location: @context.current_location)
160
+ end
161
+
162
+ def build_cascade(&blk)
163
+ expression_converter = ExpressionConverter.new(@context)
164
+ cascade_builder = DslCascadeBuilder.new(expression_converter, @context.current_location)
165
+ cascade_builder.instance_eval(&blk)
166
+ Kumi::Syntax::CascadeExpression.new(cascade_builder.cases, loc: @context.current_location)
167
+ end
168
+
169
+ def ensure_syntax(obj)
170
+ ExpressionConverter.new(@context).ensure_syntax(obj)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module RubyParser
6
+ module Sugar
7
+ include Syntax
8
+
9
+ ARITHMETIC_OPS = {
10
+ :+ => :add, :- => :subtract, :* => :multiply,
11
+ :/ => :divide, :% => :modulo, :** => :power
12
+ }.freeze
13
+
14
+ COMPARISON_OPS = %i[< <= > >= == !=].freeze
15
+
16
+ LITERAL_TYPES = [
17
+ Integer, String, Symbol, TrueClass, FalseClass, Float, Regexp, NilClass
18
+ ].freeze
19
+
20
+ # Collection methods that can be applied to arrays/syntax nodes
21
+ COLLECTION_METHODS = %i[
22
+ sum size length first last sort reverse unique min max empty? flatten
23
+ map_with_index indices
24
+ ].freeze
25
+
26
+ def self.ensure_literal(obj)
27
+ return Kumi::Syntax::Literal.new(obj) if LITERAL_TYPES.any? { |type| obj.is_a?(type) }
28
+ return obj if obj.is_a?(Syntax::Node)
29
+ return obj.to_ast_node if obj.respond_to?(:to_ast_node)
30
+
31
+ Kumi::Syntax::Literal.new(obj)
32
+ end
33
+
34
+ def self.syntax_expression?(obj)
35
+ obj.is_a?(Syntax::Node) || obj.respond_to?(:to_ast_node)
36
+ end
37
+
38
+ # Create a call expression with consistent error handling
39
+ def self.create_call_expression(fn_name, args)
40
+ Kumi::Syntax::CallExpression.new(fn_name, args)
41
+ end
42
+
43
+ module ExpressionRefinement
44
+ refine Syntax::Node do
45
+ # Arithmetic operations
46
+ ARITHMETIC_OPS.each do |op, op_name|
47
+ define_method(op) do |other|
48
+ other_node = Sugar.ensure_literal(other)
49
+ Sugar.create_call_expression(op_name, [self, other_node])
50
+ end
51
+ end
52
+
53
+ # Comparison operations
54
+ COMPARISON_OPS.each do |op|
55
+ define_method(op) do |other|
56
+ other_node = Sugar.ensure_literal(other)
57
+ Sugar.create_call_expression(op, [self, other_node])
58
+ end
59
+ end
60
+
61
+ # Array access
62
+ def [](index)
63
+ Sugar.create_call_expression(:at, [self, Sugar.ensure_literal(index)])
64
+ end
65
+
66
+ # Unary minus
67
+ def -@
68
+ Sugar.create_call_expression(:subtract, [Sugar.ensure_literal(0), self])
69
+ end
70
+
71
+ # Logical operations
72
+ def &(other)
73
+ Sugar.create_call_expression(:and, [self, Sugar.ensure_literal(other)])
74
+ end
75
+
76
+ def |(other)
77
+ Sugar.create_call_expression(:or, [self, Sugar.ensure_literal(other)])
78
+ end
79
+
80
+ # Collection methods - single argument (self)
81
+ COLLECTION_METHODS.each do |method_name|
82
+ next if method_name == :include? # Special case with element argument
83
+
84
+ define_method(method_name) do
85
+ Sugar.create_call_expression(method_name, [self])
86
+ end
87
+ end
88
+
89
+ # Special case: include? takes an element argument
90
+ def include?(element)
91
+ Sugar.create_call_expression(:include?, [self, Sugar.ensure_literal(element)])
92
+ end
93
+ end
94
+ end
95
+
96
+ module NumericRefinement
97
+ [Integer, Float].each do |klass|
98
+ refine klass do
99
+ # Arithmetic operations with syntax expressions
100
+ ARITHMETIC_OPS.each do |op, op_name|
101
+ define_method(op) do |other|
102
+ if Sugar.syntax_expression?(other)
103
+ other_node = Sugar.ensure_literal(other)
104
+ Sugar.create_call_expression(op_name, [Kumi::Syntax::Literal.new(self), other_node])
105
+ else
106
+ super(other)
107
+ end
108
+ end
109
+ end
110
+
111
+ # Comparison operations with syntax expressions
112
+ COMPARISON_OPS.each do |op|
113
+ define_method(op) do |other|
114
+ if Sugar.syntax_expression?(other)
115
+ other_node = Sugar.ensure_literal(other)
116
+ Sugar.create_call_expression(op, [Kumi::Syntax::Literal.new(self), other_node])
117
+ else
118
+ super(other)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ module StringRefinement
127
+ refine String do
128
+ def +(other)
129
+ if Sugar.syntax_expression?(other)
130
+ other_node = Sugar.ensure_literal(other)
131
+ Sugar.create_call_expression(:concat, [Kumi::Syntax::Literal.new(self), other_node])
132
+ else
133
+ super
134
+ end
135
+ end
136
+
137
+ %i[== !=].each do |op|
138
+ define_method(op) do |other|
139
+ if Sugar.syntax_expression?(other)
140
+ other_node = Sugar.ensure_literal(other)
141
+ Sugar.create_call_expression(op, [Kumi::Syntax::Literal.new(self), other_node])
142
+ else
143
+ super(other)
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ module ArrayRefinement
151
+ refine Array do
152
+ # Helper method to check if array contains any syntax expressions
153
+ def any_syntax_expressions?
154
+ any? { |item| Sugar.syntax_expression?(item) }
155
+ end
156
+
157
+ # Convert array to syntax list expression with all elements as syntax nodes
158
+ def to_syntax_list
159
+ syntax_elements = map { |item| Sugar.ensure_literal(item) }
160
+ Kumi::Syntax::ArrayExpression.new(syntax_elements)
161
+ end
162
+
163
+ # Create array method that works with syntax expressions
164
+ def self.define_array_syntax_method(method_name, has_argument: false)
165
+ define_method(method_name) do |*args|
166
+ if any_syntax_expressions?
167
+ array_literal = to_syntax_list
168
+ call_args = [array_literal]
169
+ call_args.concat(args.map { |arg| Sugar.ensure_literal(arg) }) if has_argument
170
+ Sugar.create_call_expression(method_name, call_args)
171
+ else
172
+ super(*args)
173
+ end
174
+ end
175
+ end
176
+
177
+ # Define collection methods without arguments
178
+ %i[sum size length first last sort reverse unique min max empty? flatten].each do |method_name|
179
+ define_array_syntax_method(method_name)
180
+ end
181
+
182
+ # Define methods with arguments
183
+ define_array_syntax_method(:include?, has_argument: true)
184
+ end
185
+ end
186
+
187
+ module ModuleRefinement
188
+ refine Module do
189
+ # Allow modules to provide schema utilities and helpers
190
+ def with_schema_utilities
191
+ include Kumi::Schema if respond_to?(:include)
192
+ extend Kumi::Schema if respond_to?(:extend)
193
+ end
194
+
195
+ # Helper for defining schema constants that can be used in multiple schemas
196
+ def schema_const(name, value)
197
+ const_set(name, value.freeze)
198
+ end
199
+
200
+ # Enable easy schema composition
201
+ def compose_schema(*modules)
202
+ modules.each do |mod|
203
+ include mod if mod.is_a?(Module)
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ # Shared refinement for proxy objects that need to handle operators
210
+ # Both DeclarationReferenceProxy and InputFieldProxy can use this
211
+ module ProxyRefinement
212
+ def self.extended(proxy_class)
213
+ # Add operator methods directly to the proxy class
214
+ proxy_class.class_eval do
215
+ # Arithmetic operations
216
+ ARITHMETIC_OPS.each do |op, op_name|
217
+ define_method(op) do |other|
218
+ ast_node = to_ast_node
219
+ other_node = Sugar.ensure_literal(other)
220
+ Sugar.create_call_expression(op_name, [ast_node, other_node])
221
+ end
222
+ end
223
+
224
+ # Comparison operations (including == and != that don't work with refinements)
225
+ COMPARISON_OPS.each do |op|
226
+ define_method(op) do |other|
227
+ ast_node = to_ast_node
228
+ other_node = Sugar.ensure_literal(other)
229
+ Sugar.create_call_expression(op, [ast_node, other_node])
230
+ end
231
+ end
232
+
233
+ # Logical operations
234
+ define_method(:&) do |other|
235
+ ast_node = to_ast_node
236
+ other_node = Sugar.ensure_literal(other)
237
+ Sugar.create_call_expression(:and, [ast_node, other_node])
238
+ end
239
+
240
+ define_method(:|) do |other|
241
+ ast_node = to_ast_node
242
+ other_node = Sugar.ensure_literal(other)
243
+ Sugar.create_call_expression(:or, [ast_node, other_node])
244
+ end
245
+
246
+ # Array access
247
+ define_method(:[]) do |index|
248
+ ast_node = to_ast_node
249
+ Sugar.create_call_expression(:at, [ast_node, Sugar.ensure_literal(index)])
250
+ end
251
+
252
+ # Unary minus
253
+ define_method(:-@) do
254
+ ast_node = to_ast_node
255
+ Sugar.create_call_expression(:subtract, [Sugar.ensure_literal(0), ast_node])
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ # Ruby DSL parser for Kumi schemas
6
+ # Converts Ruby block syntax into AST nodes
7
+ module RubyParser
8
+ # This module contains all Ruby DSL parsing functionality
9
+ # The main entry point is through Dsl.build_syntax_tree
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ # A bound pair of <compiled schema + context>. Immutable.
6
+ #
7
+ # Public API ----------------------------------------------------------
8
+ # instance.evaluate # => full Hash of all bindings
9
+ # instance.evaluate(:tax_due, :rate)
10
+ # instance.slice(:tax_due) # alias for evaluate(*keys)
11
+ # instance.explain(:tax_due) # pretty trace string
12
+ # instance.input # original context (read‑only)
13
+
14
+ class SchemaInstance
15
+ attr_reader :compiled_schema, :metadata, :context
16
+
17
+ def initialize(compiled_schema, metadata, context)
18
+ @compiled_schema = compiled_schema # Kumi::Core::CompiledSchema
19
+ @metadata = metadata # Frozen state hash
20
+ @context = context.is_a?(EvaluationWrapper) ? context : EvaluationWrapper.new(context)
21
+ end
22
+
23
+ # Hash‑like read of one or many bindings
24
+ def evaluate(*key_names)
25
+ if key_names.empty?
26
+ @compiled_schema.evaluate(@context)
27
+ else
28
+ @compiled_schema.evaluate(@context, *key_names)
29
+ end
30
+ end
31
+
32
+ def slice(*key_names)
33
+ return {} if key_names.empty?
34
+
35
+ evaluate(*key_names)
36
+ end
37
+
38
+ def [](key_name)
39
+ evaluate(key_name)[key_name]
40
+ end
41
+
42
+ # Update input values and clear affected cached computations
43
+ def update(**changes)
44
+ changes.each do |field, value|
45
+ # Validate field exists
46
+ raise ArgumentError, "unknown input field: #{field}" unless input_field_exists?(field)
47
+
48
+ # Validate domain constraints
49
+ validate_domain_constraint(field, value)
50
+
51
+ # Update the input data
52
+ @context[field] = value
53
+
54
+ # Clear affected cached values using transitive closure by default
55
+ if ENV["KUMI_SIMPLE_CACHE"] == "true"
56
+ # Simple fallback: clear all cached values
57
+ @context.clear_cache
58
+ else
59
+ # Default: selective cache clearing using precomputed transitive closure
60
+ affected_keys = find_dependent_declarations_optimized(field)
61
+ affected_keys.each { |key| @context.clear_cache(key) }
62
+ end
63
+ end
64
+
65
+ self # Return self for chaining
66
+ end
67
+
68
+ private
69
+
70
+ def input_field_exists?(field)
71
+ # Check if field is declared in input block
72
+ input_meta = @metadata[:inputs] || {}
73
+ input_meta.key?(field) || @context.key?(field)
74
+ end
75
+
76
+ def validate_domain_constraint(field, value)
77
+ input_meta = @metadata[:inputs] || {}
78
+ field_meta = input_meta[field]
79
+ return unless field_meta&.dig(:domain)
80
+
81
+ domain = field_meta[:domain]
82
+ return unless violates_domain?(value, domain)
83
+
84
+ raise ArgumentError, "value #{value} is not in domain #{domain}"
85
+ end
86
+
87
+ def violates_domain?(value, domain)
88
+ case domain
89
+ when Range
90
+ !domain.include?(value)
91
+ when Array
92
+ !domain.include?(value)
93
+ when Proc
94
+ # For Proc domains, we can't statically analyze
95
+ false
96
+ else
97
+ false
98
+ end
99
+ end
100
+
101
+ def find_dependent_declarations_optimized(field)
102
+ # Use precomputed transitive closure for true O(1) lookup!
103
+ transitive_dependents = @metadata[:dependents]
104
+ return [] unless transitive_dependents
105
+
106
+ # This is truly O(1) - just array lookup, no traversal needed
107
+ transitive_dependents[field] || []
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Types
6
+ # Builds complex type structures
7
+ class Builder
8
+ def self.array(elem_type)
9
+ raise ArgumentError, "Invalid array element type: #{elem_type}" unless Validator.valid_type?(elem_type)
10
+
11
+ { array: elem_type }
12
+ end
13
+
14
+ def self.hash(key_type, val_type)
15
+ raise ArgumentError, "Invalid hash key type: #{key_type}" unless Validator.valid_type?(key_type)
16
+ raise ArgumentError, "Invalid hash value type: #{val_type}" unless Validator.valid_type?(val_type)
17
+
18
+ { hash: [key_type, val_type] }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Types
6
+ # Handles type compatibility and unification
7
+ class Compatibility
8
+ # Check if two types are compatible
9
+ def self.compatible?(type1, type2)
10
+ # Any type is compatible with anything
11
+ return true if type1 == :any || type2 == :any
12
+
13
+ # Exact match
14
+ return true if type1 == type2
15
+
16
+ # Generic array compatibility: :array is compatible with any structured array
17
+ return true if (type1 == :array && Validator.array_type?(type2)) ||
18
+ (type2 == :array && Validator.array_type?(type1))
19
+
20
+ # Numeric compatibility
21
+ return true if numeric_compatible?(type1, type2)
22
+
23
+ # Array compatibility
24
+ return array_compatible?(type1, type2) if array_types?(type1, type2)
25
+
26
+ # Hash compatibility
27
+ return hash_compatible?(type1, type2) if hash_types?(type1, type2)
28
+
29
+ false
30
+ end
31
+
32
+ # Find the most specific common type between two types
33
+ def self.unify(type1, type2)
34
+ return type1 if type1 == type2
35
+
36
+ # :any unifies to the other type (more specific)
37
+ return type2 if type1 == :any
38
+ return type1 if type2 == :any
39
+
40
+ # Generic array unification: structured array is more specific than :array
41
+ return type2 if type1 == :array && Validator.array_type?(type2)
42
+ return type1 if type2 == :array && Validator.array_type?(type1)
43
+
44
+ # Numeric unification
45
+ if numeric_compatible?(type1, type2)
46
+ return :integer if type1 == :integer && type2 == :integer
47
+
48
+ return :float # One or both are float
49
+ end
50
+
51
+ # Array unification
52
+ if array_types?(type1, type2)
53
+ elem1 = type1[:array]
54
+ elem2 = type2[:array]
55
+ unified_elem = unify(elem1, elem2)
56
+ return Kumi::Core::Types.array(unified_elem)
57
+ end
58
+
59
+ # Hash unification
60
+ if hash_types?(type1, type2)
61
+ key1, val1 = type1[:hash]
62
+ key2, val2 = type2[:hash]
63
+ unified_key = unify(key1, key2)
64
+ unified_val = unify(val1, val2)
65
+ return Kumi::Core::Types.hash(unified_key, unified_val)
66
+ end
67
+
68
+ # Fall back to :any for incompatible types
69
+ :any
70
+ end
71
+
72
+ def self.numeric_compatible?(type1, type2)
73
+ numeric_types = %i[integer float]
74
+ numeric_types.include?(type1) && numeric_types.include?(type2)
75
+ end
76
+
77
+ def self.array_types?(type1, type2)
78
+ Validator.array_type?(type1) && Validator.array_type?(type2)
79
+ end
80
+
81
+ def self.hash_types?(type1, type2)
82
+ Validator.hash_type?(type1) && Validator.hash_type?(type2)
83
+ end
84
+
85
+ def self.array_compatible?(type1, type2)
86
+ compatible?(type1[:array], type2[:array])
87
+ end
88
+
89
+ def self.hash_compatible?(type1, type2)
90
+ compatible?(type1[:hash][0], type2[:hash][0]) &&
91
+ compatible?(type1[:hash][1], type2[:hash][1])
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end