kumi 0.0.7 → 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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +1 -1
  3. data/README.md +8 -5
  4. data/examples/game_of_life.rb +1 -1
  5. data/examples/static_analysis_errors.rb +7 -7
  6. data/lib/kumi/analyzer.rb +15 -15
  7. data/lib/kumi/compiler.rb +6 -6
  8. data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
  9. data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
  10. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
  11. data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
  12. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
  13. data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
  14. data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
  15. data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
  16. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
  17. data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
  18. data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
  19. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
  20. data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
  21. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
  22. data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
  23. data/lib/kumi/core/atom_unsat_solver.rb +396 -0
  24. data/lib/kumi/core/compiled_schema.rb +43 -0
  25. data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
  26. data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
  27. data/lib/kumi/core/domain/range_analyzer.rb +85 -0
  28. data/lib/kumi/core/domain/validator.rb +82 -0
  29. data/lib/kumi/core/domain/violation_formatter.rb +42 -0
  30. data/lib/kumi/core/error_reporter.rb +166 -0
  31. data/lib/kumi/core/error_reporting.rb +97 -0
  32. data/lib/kumi/core/errors.rb +120 -0
  33. data/lib/kumi/core/evaluation_wrapper.rb +40 -0
  34. data/lib/kumi/core/explain.rb +295 -0
  35. data/lib/kumi/core/export/deserializer.rb +41 -0
  36. data/lib/kumi/core/export/errors.rb +14 -0
  37. data/lib/kumi/core/export/node_builders.rb +142 -0
  38. data/lib/kumi/core/export/node_registry.rb +54 -0
  39. data/lib/kumi/core/export/node_serializers.rb +158 -0
  40. data/lib/kumi/core/export/serializer.rb +25 -0
  41. data/lib/kumi/core/export.rb +35 -0
  42. data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
  43. data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
  44. data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
  45. data/lib/kumi/core/function_registry/function_builder.rb +95 -0
  46. data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
  47. data/lib/kumi/core/function_registry/math_functions.rb +74 -0
  48. data/lib/kumi/core/function_registry/string_functions.rb +57 -0
  49. data/lib/kumi/core/function_registry/type_functions.rb +53 -0
  50. data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
  51. data/lib/kumi/core/input/type_matcher.rb +97 -0
  52. data/lib/kumi/core/input/validator.rb +51 -0
  53. data/lib/kumi/core/input/violation_creator.rb +52 -0
  54. data/lib/kumi/core/json_schema/generator.rb +65 -0
  55. data/lib/kumi/core/json_schema/validator.rb +27 -0
  56. data/lib/kumi/core/json_schema.rb +16 -0
  57. data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
  58. data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
  59. data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
  60. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
  61. data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
  62. data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
  63. data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
  64. data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
  65. data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
  66. data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
  67. data/lib/kumi/core/ruby_parser/parser.rb +71 -0
  68. data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
  69. data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
  70. data/lib/kumi/core/ruby_parser.rb +12 -0
  71. data/lib/kumi/core/schema_instance.rb +111 -0
  72. data/lib/kumi/core/types/builder.rb +23 -0
  73. data/lib/kumi/core/types/compatibility.rb +96 -0
  74. data/lib/kumi/core/types/formatter.rb +26 -0
  75. data/lib/kumi/core/types/inference.rb +42 -0
  76. data/lib/kumi/core/types/normalizer.rb +72 -0
  77. data/lib/kumi/core/types/validator.rb +37 -0
  78. data/lib/kumi/core/types.rb +66 -0
  79. data/lib/kumi/core/vectorization_metadata.rb +110 -0
  80. data/lib/kumi/errors.rb +1 -112
  81. data/lib/kumi/registry.rb +37 -0
  82. data/lib/kumi/schema.rb +5 -5
  83. data/lib/kumi/schema_metadata.rb +3 -3
  84. data/lib/kumi/syntax/array_expression.rb +6 -6
  85. data/lib/kumi/syntax/call_expression.rb +4 -4
  86. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  87. data/lib/kumi/syntax/case_expression.rb +4 -4
  88. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  89. data/lib/kumi/syntax/hash_expression.rb +4 -4
  90. data/lib/kumi/syntax/input_declaration.rb +5 -5
  91. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  92. data/lib/kumi/syntax/input_reference.rb +5 -5
  93. data/lib/kumi/syntax/literal.rb +4 -4
  94. data/lib/kumi/syntax/node.rb +34 -34
  95. data/lib/kumi/syntax/root.rb +6 -6
  96. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  97. data/lib/kumi/syntax/value_declaration.rb +4 -4
  98. data/lib/kumi/version.rb +1 -1
  99. data/migrate_to_core_iterative.rb +938 -0
  100. data/scripts/generate_function_docs.rb +9 -9
  101. metadata +75 -72
  102. data/lib/kumi/analyzer/analysis_state.rb +0 -37
  103. data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
  104. data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -246
  105. data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
  106. data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
  107. data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
  108. data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
  109. data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
  110. data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -109
  111. data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
  112. data/lib/kumi/analyzer/passes/type_checker.rb +0 -160
  113. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
  114. data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
  115. data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -404
  116. data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
  117. data/lib/kumi/atom_unsat_solver.rb +0 -394
  118. data/lib/kumi/compiled_schema.rb +0 -41
  119. data/lib/kumi/constraint_relationship_solver.rb +0 -638
  120. data/lib/kumi/domain/enum_analyzer.rb +0 -53
  121. data/lib/kumi/domain/range_analyzer.rb +0 -83
  122. data/lib/kumi/domain/validator.rb +0 -80
  123. data/lib/kumi/domain/violation_formatter.rb +0 -40
  124. data/lib/kumi/error_reporter.rb +0 -164
  125. data/lib/kumi/error_reporting.rb +0 -95
  126. data/lib/kumi/evaluation_wrapper.rb +0 -38
  127. data/lib/kumi/explain.rb +0 -293
  128. data/lib/kumi/export/deserializer.rb +0 -39
  129. data/lib/kumi/export/errors.rb +0 -12
  130. data/lib/kumi/export/node_builders.rb +0 -140
  131. data/lib/kumi/export/node_registry.rb +0 -52
  132. data/lib/kumi/export/node_serializers.rb +0 -156
  133. data/lib/kumi/export/serializer.rb +0 -23
  134. data/lib/kumi/export.rb +0 -33
  135. data/lib/kumi/function_registry/collection_functions.rb +0 -200
  136. data/lib/kumi/function_registry/comparison_functions.rb +0 -31
  137. data/lib/kumi/function_registry/conditional_functions.rb +0 -36
  138. data/lib/kumi/function_registry/function_builder.rb +0 -93
  139. data/lib/kumi/function_registry/logical_functions.rb +0 -42
  140. data/lib/kumi/function_registry/math_functions.rb +0 -72
  141. data/lib/kumi/function_registry/string_functions.rb +0 -54
  142. data/lib/kumi/function_registry/type_functions.rb +0 -51
  143. data/lib/kumi/input/type_matcher.rb +0 -95
  144. data/lib/kumi/input/validator.rb +0 -49
  145. data/lib/kumi/input/violation_creator.rb +0 -50
  146. data/lib/kumi/json_schema/generator.rb +0 -63
  147. data/lib/kumi/json_schema/validator.rb +0 -25
  148. data/lib/kumi/json_schema.rb +0 -14
  149. data/lib/kumi/ruby_parser/build_context.rb +0 -25
  150. data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +0 -36
  151. data/lib/kumi/ruby_parser/dsl.rb +0 -12
  152. data/lib/kumi/ruby_parser/dsl_cascade_builder.rb +0 -136
  153. data/lib/kumi/ruby_parser/expression_converter.rb +0 -126
  154. data/lib/kumi/ruby_parser/guard_rails.rb +0 -43
  155. data/lib/kumi/ruby_parser/input_builder.rb +0 -125
  156. data/lib/kumi/ruby_parser/input_field_proxy.rb +0 -46
  157. data/lib/kumi/ruby_parser/input_proxy.rb +0 -29
  158. data/lib/kumi/ruby_parser/nested_input.rb +0 -15
  159. data/lib/kumi/ruby_parser/parser.rb +0 -69
  160. data/lib/kumi/ruby_parser/schema_builder.rb +0 -173
  161. data/lib/kumi/ruby_parser/sugar.rb +0 -261
  162. data/lib/kumi/ruby_parser.rb +0 -10
  163. data/lib/kumi/schema_instance.rb +0 -109
  164. data/lib/kumi/types/builder.rb +0 -21
  165. data/lib/kumi/types/compatibility.rb +0 -94
  166. data/lib/kumi/types/formatter.rb +0 -24
  167. data/lib/kumi/types/inference.rb +0 -40
  168. data/lib/kumi/types/normalizer.rb +0 -70
  169. data/lib/kumi/types/validator.rb +0 -35
  170. data/lib/kumi/types.rb +0 -64
  171. data/lib/kumi/vectorization_metadata.rb +0 -108
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module FunctionRegistry
6
+ # Mathematical operations
7
+ module MathFunctions
8
+ def self.definitions
9
+ {
10
+ # Basic arithmetic
11
+ add: FunctionBuilder.math_binary(:add, "Add two numbers", :+),
12
+ subtract: FunctionBuilder.math_binary(:subtract, "Subtract second number from first", :-),
13
+ multiply: FunctionBuilder.math_binary(:multiply, "Multiply two numbers", :*),
14
+ divide: FunctionBuilder.math_binary(:divide, "Divide first number by second", :/),
15
+ modulo: FunctionBuilder.math_binary(:modulo, "Modulo operation", :%),
16
+ power: FunctionBuilder.math_binary(:power, "Raise first number to power of second", :**),
17
+
18
+ # Unary operations
19
+ abs: FunctionBuilder.math_unary(:abs, "Absolute value", :abs),
20
+ floor: FunctionBuilder.math_unary(:floor, "Floor of number", :floor, return_type: :integer),
21
+ ceil: FunctionBuilder.math_unary(:ceil, "Ceiling of number", :ceil, return_type: :integer),
22
+
23
+ # Special operations
24
+ round: FunctionBuilder::Entry.new(
25
+ fn: ->(a, precision = 0) { a.round(precision) },
26
+ arity: -1,
27
+ param_types: [:float],
28
+ return_type: :float,
29
+ description: "Round number to specified precision"
30
+ ),
31
+
32
+ clamp: FunctionBuilder::Entry.new(
33
+ fn: ->(value, min, max) { value.clamp(min, max) },
34
+ arity: 3,
35
+ param_types: %i[float float float],
36
+ return_type: :float,
37
+ description: "Clamp value between min and max"
38
+ ),
39
+ piecewise_sum: FunctionBuilder::Entry.new(
40
+ # Tiered / piece‑wise accumulator ­­­­­­­­­­­­­­­­­­­­­­­­­
41
+ fn: lambda do |value, breaks, rates|
42
+ raise ArgumentError, "breaks & rates size mismatch" unless breaks.size == rates.size
43
+
44
+ acc = 0.0
45
+ previous = 0.0
46
+ marginal = rates.last
47
+
48
+ breaks.zip(rates).each do |upper, rate|
49
+ if value <= upper
50
+ marginal = rate
51
+ acc += (value - previous) * rate
52
+ break
53
+ else
54
+ acc += (upper - previous) * rate
55
+ previous = upper
56
+ end
57
+ end
58
+ [acc, marginal] # => [sum, marginal_rate]
59
+ end,
60
+ arity: 3,
61
+ param_types: [
62
+ :float,
63
+ Kumi::Core::Types.array(:float), # breaks
64
+ Kumi::Core::Types.array(:float) # rates
65
+ ],
66
+ return_type: Kumi::Core::Types.array(:float), # 2‑element [sum, marginal]
67
+ description: "Accumulate over tiered ranges; returns [sum, marginal_rate]"
68
+ )
69
+ }
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module FunctionRegistry
6
+ # String manipulation functions
7
+ module StringFunctions
8
+ def self.definitions
9
+ {
10
+ # String transformations
11
+ upcase: FunctionBuilder.string_unary(:upcase, "Convert string to uppercase", :upcase),
12
+ downcase: FunctionBuilder.string_unary(:downcase, "Convert string to lowercase", :downcase),
13
+ capitalize: FunctionBuilder.string_unary(:capitalize, "Capitalize first letter of string", :capitalize),
14
+ strip: FunctionBuilder.string_unary(:strip, "Remove leading and trailing whitespace", :strip),
15
+
16
+ # String queries
17
+ string_length: FunctionBuilder::Entry.new(
18
+ fn: ->(str) { str.to_s.length },
19
+ arity: 1,
20
+ param_types: [:string],
21
+ return_type: :integer,
22
+ description: "Get string length"
23
+ ),
24
+
25
+ # Keep the original length for backward compatibility, but it will be overridden
26
+ length: FunctionBuilder::Entry.new(
27
+ fn: ->(str) { str.to_s.length },
28
+ arity: 1,
29
+ param_types: [:string],
30
+ return_type: :integer,
31
+ description: "Get string length"
32
+ ),
33
+
34
+ # String inclusion using different name to avoid conflict with collection include?
35
+ string_include?: FunctionBuilder.string_binary(:include?, "Check if string contains substring", :include?,
36
+ return_type: :boolean),
37
+ includes?: FunctionBuilder.string_binary(:include?, "Check if string contains substring", :include?, return_type: :boolean),
38
+ contains?: FunctionBuilder.string_binary(:include?, "Check if string contains substring", :include?, return_type: :boolean),
39
+
40
+ start_with?: FunctionBuilder.string_binary(:start_with?, "Check if string starts with prefix", :start_with?,
41
+ return_type: :boolean),
42
+ end_with?: FunctionBuilder.string_binary(:end_with?, "Check if string ends with suffix", :end_with?, return_type: :boolean),
43
+
44
+ # String building
45
+ concat: FunctionBuilder::Entry.new(
46
+ fn: ->(*strings) { strings.join },
47
+ arity: -1,
48
+ param_types: [:string],
49
+ return_type: :string,
50
+ description: "Concatenate multiple strings"
51
+ )
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module FunctionRegistry
6
+ # Type checking and conversion functions
7
+ module TypeFunctions
8
+ def self.definitions
9
+ {
10
+ fetch: FunctionBuilder::Entry.new(
11
+ fn: ->(hash, key, default = nil) { hash.fetch(key, default) },
12
+ arity: -1, # Variable arity (2 or 3)
13
+ param_types: [Kumi::Core::Types.hash(:any, :any), :any, :any],
14
+ return_type: :any,
15
+ description: "Fetch value from hash with optional default"
16
+ ),
17
+
18
+ has_key?: FunctionBuilder::Entry.new(
19
+ fn: ->(hash, key) { hash.key?(key) },
20
+ arity: 2,
21
+ param_types: [Kumi::Core::Types.hash(:any, :any), :any],
22
+ return_type: :boolean,
23
+ description: "Check if hash has the given key"
24
+ ),
25
+
26
+ keys: FunctionBuilder::Entry.new(
27
+ fn: lambda(&:keys),
28
+ arity: 1,
29
+ param_types: [Kumi::Core::Types.hash(:any, :any)],
30
+ return_type: Kumi::Core::Types.array(:any),
31
+ description: "Get all keys from hash"
32
+ ),
33
+
34
+ values: FunctionBuilder::Entry.new(
35
+ fn: lambda(&:values),
36
+ arity: 1,
37
+ param_types: [Kumi::Core::Types.hash(:any, :any)],
38
+ return_type: Kumi::Core::Types.array(:any),
39
+ description: "Get all values from hash"
40
+ ),
41
+ at: FunctionBuilder::Entry.new(
42
+ fn: ->(array, index) { array[index] },
43
+ arity: 2,
44
+ param_types: [Kumi::Core::Types.array(:any), :integer],
45
+ return_type: :any,
46
+ description: "Get element at index from array"
47
+ )
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,31 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- # Registry for functions that can be used in Kumi schemas
5
- # This is the public interface for registering custom functions
6
- module FunctionRegistry
7
- class UnknownFunction < StandardError; end
8
-
9
- # Re-export the Entry struct from FunctionBuilder for compatibility
10
- Entry = FunctionBuilder::Entry
11
-
12
- # Core operators that are always available
13
- CORE_OPERATORS = %i[== > < >= <= != between?].freeze
14
-
15
- # Build the complete function registry by combining all categories
16
- CORE_OPERATIONS = {}.tap do |registry|
17
- registry.merge!(ComparisonFunctions.definitions)
18
- registry.merge!(MathFunctions.definitions)
19
- registry.merge!(StringFunctions.definitions)
20
- registry.merge!(LogicalFunctions.definitions)
21
- registry.merge!(CollectionFunctions.definitions)
22
- registry.merge!(ConditionalFunctions.definitions)
23
- registry.merge!(TypeFunctions.definitions)
24
- end.freeze
25
-
26
- @functions = CORE_OPERATIONS.dup
27
-
28
- class << self
4
+ module Core
5
+ # Registry for functions that can be used in Kumi schemas
6
+ # This is the public interface for registering custom functions
7
+ module FunctionRegistry
8
+ # Re-export the Entry struct from FunctionBuilder for compatibility
9
+ Entry = FunctionBuilder::Entry
10
+
11
+ # Core operators that are always available
12
+ CORE_OPERATORS = %i[== > < >= <= != between?].freeze
13
+
14
+ # Build the complete function registry by combining all categories
15
+ CORE_FUNCTIONS = {}.tap do |registry|
16
+ registry.merge!(ComparisonFunctions.definitions)
17
+ registry.merge!(MathFunctions.definitions)
18
+ registry.merge!(StringFunctions.definitions)
19
+ registry.merge!(LogicalFunctions.definitions)
20
+ registry.merge!(CollectionFunctions.definitions)
21
+ registry.merge!(ConditionalFunctions.definitions)
22
+ registry.merge!(TypeFunctions.definitions)
23
+ end.freeze
24
+
25
+ @functions = CORE_FUNCTIONS.dup
26
+ @frozen = false
27
+
28
+ # class << self
29
29
  # Public interface for registering custom functions
30
30
  def register(name, &block)
31
31
  raise ArgumentError, "Function #{name.inspect} already registered" if @functions.key?(name)
@@ -75,11 +75,11 @@ module Kumi
75
75
  end
76
76
 
77
77
  def fetch(name)
78
- @functions.fetch(name) { raise UnknownFunction, "Unknown function: #{name}" }.fn
78
+ @functions.fetch(name) { raise Kumi::Errors::UnknownFunction, "Unknown function: #{name}" }.fn
79
79
  end
80
80
 
81
81
  def signature(name)
82
- entry = @functions.fetch(name) { raise UnknownFunction, "Unknown function: #{name}" }
82
+ entry = @functions.fetch(name) { raise Kumi::Errors::UnknownFunction, "Unknown function: #{name}" }
83
83
  {
84
84
  arity: entry.arity,
85
85
  param_types: entry.param_types,
@@ -130,15 +130,7 @@ module Kumi
130
130
  def type_operations
131
131
  TypeFunctions.definitions.keys
132
132
  end
133
-
134
- # Development helpers
135
- def reset!
136
- @functions = CORE_OPERATIONS.dup
137
- end
138
-
139
- def freeze!
140
- @functions.freeze
141
- end
142
133
  end
143
134
  end
144
135
  end
136
+ # end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Input
6
+ class TypeMatcher
7
+ def self.matches?(value, declared_type)
8
+ case declared_type
9
+ when :integer
10
+ value.is_a?(Integer)
11
+ when :float
12
+ value.is_a?(Float) || value.is_a?(Integer) # Allow integer for float
13
+ when :string
14
+ value.is_a?(String)
15
+ when :boolean
16
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
17
+ when :symbol
18
+ value.is_a?(Symbol)
19
+ when :array
20
+ # Simple :array type - just check if it's an Array
21
+ value.is_a?(Array)
22
+ when :any
23
+ true
24
+ else
25
+ # Handle complex types (arrays, hashes)
26
+ handle_complex_type(value, declared_type)
27
+ end
28
+ end
29
+
30
+ def self.infer_type(value)
31
+ case value
32
+ when Integer then :integer
33
+ when Float then :float
34
+ when String then :string
35
+ when TrueClass, FalseClass then :boolean
36
+ when Symbol then :symbol
37
+ when Array then { array: :mixed }
38
+ when Hash then { hash: %i[mixed mixed] }
39
+ else :unknown
40
+ end
41
+ end
42
+
43
+ def self.format_type(type)
44
+ case type
45
+ when Symbol
46
+ type.to_s
47
+ when Hash
48
+ format_complex_type(type)
49
+ else
50
+ type.inspect
51
+ end
52
+ end
53
+
54
+ private_class_method def self.handle_complex_type(value, declared_type)
55
+ return false unless declared_type.is_a?(Hash)
56
+
57
+ if declared_type.key?(:array)
58
+ handle_array_type(value, declared_type[:array])
59
+ elsif declared_type.key?(:hash)
60
+ handle_hash_type(value, declared_type[:hash])
61
+ else
62
+ false
63
+ end
64
+ end
65
+
66
+ private_class_method def self.handle_array_type(value, element_type)
67
+ return false unless value.is_a?(Array)
68
+ return true if element_type == :any
69
+
70
+ value.all? { |elem| matches?(elem, element_type) }
71
+ end
72
+
73
+ private_class_method def self.handle_hash_type(value, hash_spec)
74
+ return false unless value.is_a?(Hash)
75
+
76
+ key_type, value_type = hash_spec
77
+ return true if key_type == :any && value_type == :any
78
+
79
+ value.all? do |k, v|
80
+ matches?(k, key_type) && matches?(v, value_type)
81
+ end
82
+ end
83
+
84
+ private_class_method def self.format_complex_type(type)
85
+ if type.key?(:array)
86
+ "array(#{format_type(type[:array])})"
87
+ elsif type.key?(:hash)
88
+ key_type, value_type = type[:hash]
89
+ "hash(#{format_type(key_type)}, #{format_type(value_type)})"
90
+ else
91
+ type.inspect
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Input
6
+ class Validator
7
+ def self.validate_context(context, input_meta)
8
+ violations = []
9
+
10
+ context.each do |field, value|
11
+ meta = input_meta[field]
12
+ next unless meta
13
+
14
+ # Type validation first
15
+ if should_validate_type?(meta) && !TypeMatcher.matches?(value, meta[:type])
16
+ violations << ViolationCreator.create_type_violation(field, value, meta[:type])
17
+ next # Skip domain validation if type is wrong
18
+ end
19
+
20
+ # Domain validation second (only if type is correct)
21
+ if should_validate_domain?(meta) && !Domain::Validator.validate_field(field, value, meta[:domain])
22
+ violations << ViolationCreator.create_domain_violation(field, value, meta[:domain])
23
+ end
24
+ end
25
+
26
+ violations
27
+ end
28
+
29
+ def self.type_matches?(value, declared_type)
30
+ TypeMatcher.matches?(value, declared_type)
31
+ end
32
+
33
+ def self.infer_type(value)
34
+ TypeMatcher.infer_type(value)
35
+ end
36
+
37
+ def self.format_type(type)
38
+ TypeMatcher.format_type(type)
39
+ end
40
+
41
+ private_class_method def self.should_validate_type?(meta)
42
+ meta[:type] && meta[:type] != :any
43
+ end
44
+
45
+ private_class_method def self.should_validate_domain?(meta)
46
+ meta[:domain]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Input
6
+ class ViolationCreator
7
+ def self.create_type_violation(field, value, expected_type)
8
+ {
9
+ type: :type_violation,
10
+ field: field,
11
+ value: value,
12
+ expected_type: expected_type,
13
+ actual_type: TypeMatcher.infer_type(value),
14
+ message: format_type_violation_message(field, value, expected_type)
15
+ }
16
+ end
17
+
18
+ def self.create_domain_violation(field, value, domain)
19
+ {
20
+ type: :domain_violation,
21
+ field: field,
22
+ value: value,
23
+ domain: domain,
24
+ message: Kumi::Core::Domain::ViolationFormatter.format_message(field, value, domain)
25
+ }
26
+ end
27
+
28
+ def self.create_missing_field_violation(field, expected_type)
29
+ {
30
+ type: :missing_field_violation,
31
+ field: field,
32
+ expected_type: expected_type,
33
+ message: format_missing_field_message(field, expected_type)
34
+ }
35
+ end
36
+
37
+ private_class_method def self.format_type_violation_message(field, value, expected_type)
38
+ actual_type = TypeMatcher.infer_type(value)
39
+ expected_formatted = TypeMatcher.format_type(expected_type)
40
+ actual_formatted = TypeMatcher.format_type(actual_type)
41
+
42
+ "Field :#{field} expected #{expected_formatted}, got #{value.inspect} of type #{actual_formatted}"
43
+ end
44
+
45
+ private_class_method def self.format_missing_field_message(field, expected_type)
46
+ expected_formatted = TypeMatcher.format_type(expected_type)
47
+ "Missing required field :#{field} of type #{expected_formatted}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Kumi
6
+ module Core
7
+ module JsonSchema
8
+ # Converts Kumi schema metadata to JSON Schema format
9
+ class Generator
10
+ def initialize(schema_metadata)
11
+ @metadata = schema_metadata
12
+ end
13
+
14
+ def generate
15
+ {
16
+ type: "object",
17
+ properties: build_properties,
18
+ required: extract_required_fields,
19
+ "x-kumi-values": @metadata.values,
20
+ "x-kumi-traits": @metadata.traits
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ def build_properties
27
+ @metadata.inputs.transform_values { |spec| convert_input_to_json_schema(spec) }
28
+ end
29
+
30
+ def extract_required_fields
31
+ @metadata.inputs.select { |_k, v| v[:required] }.keys
32
+ end
33
+
34
+ def convert_input_to_json_schema(input_spec)
35
+ base = { type: map_kumi_type_to_json_schema(input_spec[:type]) }
36
+
37
+ domain = input_spec[:domain]
38
+ return base unless domain
39
+
40
+ case domain[:type]
41
+ when :range
42
+ base[:minimum] = domain[:min]
43
+ base[:maximum] = domain[:max]
44
+ when :enum
45
+ base[:enum] = domain[:values]
46
+ end
47
+
48
+ base
49
+ end
50
+
51
+ def map_kumi_type_to_json_schema(kumi_type)
52
+ case kumi_type
53
+ when :string then "string"
54
+ when :integer then "integer"
55
+ when :float then "number"
56
+ when :boolean then "boolean"
57
+ when :array then "array"
58
+ when :hash then "object"
59
+ else "string"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module JsonSchema
6
+ # Validates data against JSON Schema (placeholder for future implementation)
7
+ class Validator
8
+ def initialize(json_schema)
9
+ @schema = json_schema
10
+ end
11
+
12
+ def validate(_data)
13
+ # Placeholder implementation
14
+ # In a real implementation, this would validate data against the JSON Schema
15
+ {
16
+ valid: true,
17
+ errors: []
18
+ }
19
+ end
20
+
21
+ def self.validate(json_schema, data)
22
+ new(json_schema).validate(data)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "json_schema/generator"
4
+ require_relative "json_schema/validator"
5
+
6
+ module Kumi
7
+ module Core
8
+ module JsonSchema
9
+ # Entry point for the JsonSchema functionality
10
+ #
11
+ # Available components:
12
+ # - Generator: Converts Kumi metadata to JSON Schema
13
+ # - Validator: Validates data against JSON Schema (placeholder)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module RubyParser
6
+ class BuildContext
7
+ attr_reader :inputs, :attributes, :traits
8
+ attr_accessor :current_location
9
+
10
+ def initialize
11
+ @inputs = []
12
+ @attributes = []
13
+ @traits = []
14
+ @input_block_defined = false
15
+ end
16
+
17
+ def input_block_defined?
18
+ @input_block_defined
19
+ end
20
+
21
+ def mark_input_block_defined!
22
+ @input_block_defined = true
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module RubyParser
6
+ # DSL proxy for declaration references (traits and values)
7
+ # Handles references to declared items and field access on them
8
+ class DeclarationReferenceProxy
9
+ include Syntax
10
+
11
+ # Use shared operator methods
12
+ extend Sugar::ProxyRefinement
13
+
14
+ def initialize(name, context)
15
+ @name = name
16
+ @context = context
17
+ end
18
+
19
+ # Convert to DeclarationReference AST node
20
+ def to_ast_node
21
+ Kumi::Syntax::DeclarationReference.new(@name, loc: @context.current_location)
22
+ end
23
+
24
+ private
25
+
26
+ def method_missing(method_name, *args, &block)
27
+ # All operators are handled by ProxyRefinement methods
28
+ # Field access should use input.field.subfield syntax, not bare identifiers
29
+ super
30
+ end
31
+
32
+ def respond_to_missing?(_method_name, _include_private = false)
33
+ true
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module RubyParser
6
+ module Dsl
7
+ def self.build_syntax_tree(&rule_block)
8
+ parser = Parser.new
9
+ parser.parse(&rule_block)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end