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
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Input
5
- class TypeMatcher
6
- def self.matches?(value, declared_type)
7
- case declared_type
8
- when :integer
9
- value.is_a?(Integer)
10
- when :float
11
- value.is_a?(Float) || value.is_a?(Integer) # Allow integer for float
12
- when :string
13
- value.is_a?(String)
14
- when :boolean
15
- value.is_a?(TrueClass) || value.is_a?(FalseClass)
16
- when :symbol
17
- value.is_a?(Symbol)
18
- when :array
19
- # Simple :array type - just check if it's an Array
20
- value.is_a?(Array)
21
- when :any
22
- true
23
- else
24
- # Handle complex types (arrays, hashes)
25
- handle_complex_type(value, declared_type)
26
- end
27
- end
28
-
29
- def self.infer_type(value)
30
- case value
31
- when Integer then :integer
32
- when Float then :float
33
- when String then :string
34
- when TrueClass, FalseClass then :boolean
35
- when Symbol then :symbol
36
- when Array then { array: :mixed }
37
- when Hash then { hash: %i[mixed mixed] }
38
- else :unknown
39
- end
40
- end
41
-
42
- def self.format_type(type)
43
- case type
44
- when Symbol
45
- type.to_s
46
- when Hash
47
- format_complex_type(type)
48
- else
49
- type.inspect
50
- end
51
- end
52
-
53
- private_class_method def self.handle_complex_type(value, declared_type)
54
- return false unless declared_type.is_a?(Hash)
55
-
56
- if declared_type.key?(:array)
57
- handle_array_type(value, declared_type[:array])
58
- elsif declared_type.key?(:hash)
59
- handle_hash_type(value, declared_type[:hash])
60
- else
61
- false
62
- end
63
- end
64
-
65
- private_class_method def self.handle_array_type(value, element_type)
66
- return false unless value.is_a?(Array)
67
- return true if element_type == :any
68
-
69
- value.all? { |elem| matches?(elem, element_type) }
70
- end
71
-
72
- private_class_method def self.handle_hash_type(value, hash_spec)
73
- return false unless value.is_a?(Hash)
74
-
75
- key_type, value_type = hash_spec
76
- return true if key_type == :any && value_type == :any
77
-
78
- value.all? do |k, v|
79
- matches?(k, key_type) && matches?(v, value_type)
80
- end
81
- end
82
-
83
- private_class_method def self.format_complex_type(type)
84
- if type.key?(:array)
85
- "array(#{format_type(type[:array])})"
86
- elsif type.key?(:hash)
87
- key_type, value_type = type[:hash]
88
- "hash(#{format_type(key_type)}, #{format_type(value_type)})"
89
- else
90
- type.inspect
91
- end
92
- end
93
- end
94
- end
95
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Input
5
- class Validator
6
- def self.validate_context(context, input_meta)
7
- violations = []
8
-
9
- context.each do |field, value|
10
- meta = input_meta[field]
11
- next unless meta
12
-
13
- # Type validation first
14
- if should_validate_type?(meta) && !TypeMatcher.matches?(value, meta[:type])
15
- violations << ViolationCreator.create_type_violation(field, value, meta[:type])
16
- next # Skip domain validation if type is wrong
17
- end
18
-
19
- # Domain validation second (only if type is correct)
20
- if should_validate_domain?(meta) && !Domain::Validator.validate_field(field, value, meta[:domain])
21
- violations << ViolationCreator.create_domain_violation(field, value, meta[:domain])
22
- end
23
- end
24
-
25
- violations
26
- end
27
-
28
- def self.type_matches?(value, declared_type)
29
- TypeMatcher.matches?(value, declared_type)
30
- end
31
-
32
- def self.infer_type(value)
33
- TypeMatcher.infer_type(value)
34
- end
35
-
36
- def self.format_type(type)
37
- TypeMatcher.format_type(type)
38
- end
39
-
40
- private_class_method def self.should_validate_type?(meta)
41
- meta[:type] && meta[:type] != :any
42
- end
43
-
44
- private_class_method def self.should_validate_domain?(meta)
45
- meta[:domain]
46
- end
47
- end
48
- end
49
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Input
5
- class ViolationCreator
6
- def self.create_type_violation(field, value, expected_type)
7
- {
8
- type: :type_violation,
9
- field: field,
10
- value: value,
11
- expected_type: expected_type,
12
- actual_type: TypeMatcher.infer_type(value),
13
- message: format_type_violation_message(field, value, expected_type)
14
- }
15
- end
16
-
17
- def self.create_domain_violation(field, value, domain)
18
- {
19
- type: :domain_violation,
20
- field: field,
21
- value: value,
22
- domain: domain,
23
- message: Kumi::Domain::ViolationFormatter.format_message(field, value, domain)
24
- }
25
- end
26
-
27
- def self.create_missing_field_violation(field, expected_type)
28
- {
29
- type: :missing_field_violation,
30
- field: field,
31
- expected_type: expected_type,
32
- message: format_missing_field_message(field, expected_type)
33
- }
34
- end
35
-
36
- private_class_method def self.format_type_violation_message(field, value, expected_type)
37
- actual_type = TypeMatcher.infer_type(value)
38
- expected_formatted = TypeMatcher.format_type(expected_type)
39
- actual_formatted = TypeMatcher.format_type(actual_type)
40
-
41
- "Field :#{field} expected #{expected_formatted}, got #{value.inspect} of type #{actual_formatted}"
42
- end
43
-
44
- private_class_method def self.format_missing_field_message(field, expected_type)
45
- expected_formatted = TypeMatcher.format_type(expected_type)
46
- "Missing required field :#{field} of type #{expected_formatted}"
47
- end
48
- end
49
- end
50
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Parser
5
- class BuildContext
6
- attr_reader :inputs, :attributes, :traits
7
- attr_accessor :current_location
8
-
9
- def initialize
10
- @inputs = []
11
- @attributes = []
12
- @traits = []
13
- @input_block_defined = false
14
- end
15
-
16
- def input_block_defined?
17
- @input_block_defined
18
- end
19
-
20
- def mark_input_block_defined!
21
- @input_block_defined = true
22
- end
23
- end
24
- end
25
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Parser
5
- # DSL proxy for declaration references (traits and values)
6
- # Handles references to declared items and field access on them
7
- class DeclarationReferenceProxy
8
- include Syntax
9
-
10
- # Use shared operator methods
11
- extend Sugar::ProxyRefinement
12
-
13
- def initialize(name, context)
14
- @name = name
15
- @context = context
16
- end
17
-
18
- # Convert to DeclarationReference AST node
19
- def to_ast_node
20
- Kumi::Syntax::DeclarationReference.new(@name, loc: @context.current_location)
21
- end
22
-
23
- private
24
-
25
- def method_missing(method_name, *args, &block)
26
- # All operators are handled by ProxyRefinement methods
27
- # Field access should use input.field.subfield syntax, not bare identifiers
28
- super
29
- end
30
-
31
- def respond_to_missing?(_method_name, _include_private = false)
32
- true
33
- end
34
- end
35
- end
36
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Parser
5
- module Dsl
6
- def self.build_syntax_tree(&rule_block)
7
- parser = Parser.new
8
- parser.parse(&rule_block)
9
- end
10
- end
11
- end
12
- end
@@ -1,136 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Parser
5
- class DslCascadeBuilder
6
- include Syntax
7
-
8
- attr_reader :cases
9
-
10
- def initialize(context, loc)
11
- @context = context
12
- @cases = []
13
- @loc = loc
14
- end
15
-
16
- def on(*args)
17
- on_loc = current_location
18
- validate_on_args(args, "on", on_loc)
19
-
20
- trait_names = args[0..-2]
21
- expr = args.last
22
-
23
- trait_bindings = convert_trait_names_to_bindings(trait_names, on_loc)
24
- condition = create_fn(:all?, trait_bindings)
25
- result = ensure_syntax(expr)
26
- add_case(condition, result)
27
- end
28
-
29
- def on_any(*args)
30
- on_loc = current_location
31
- validate_on_args(args, "on_any", on_loc)
32
-
33
- trait_names = args[0..-2]
34
- expr = args.last
35
-
36
- trait_bindings = convert_trait_names_to_bindings(trait_names, on_loc)
37
- condition = create_fn(:any?, trait_bindings)
38
- result = ensure_syntax(expr)
39
- add_case(condition, result)
40
- end
41
-
42
- def on_none(*args)
43
- on_loc = current_location
44
- validate_on_args(args, "on_none", on_loc)
45
-
46
- trait_names = args[0..-2]
47
- expr = args.last
48
-
49
- trait_bindings = convert_trait_names_to_bindings(trait_names, on_loc)
50
- condition = create_fn(:none?, trait_bindings)
51
- result = ensure_syntax(expr)
52
- add_case(condition, result)
53
- end
54
-
55
- def base(expr)
56
- result = ensure_syntax(expr)
57
- add_case(create_literal(true), result)
58
- end
59
-
60
- def method_missing(method_name, *args, &block)
61
- return super if !args.empty? || block_given?
62
-
63
- # Allow direct trait references in cascade conditions
64
- create_binding(method_name, @loc)
65
- end
66
-
67
- def respond_to_missing?(_method_name, _include_private = false)
68
- true
69
- end
70
-
71
- private
72
-
73
- def current_location
74
- caller_info = caller_locations(1, 1).first
75
- Location.new(file: caller_info.path, line: caller_info.lineno, column: 0)
76
- end
77
-
78
- def validate_on_args(args, method_name, location)
79
- raise_error("cascade '#{method_name}' requires at least one trait name", location) if args.empty?
80
-
81
- return unless args.size == 1
82
-
83
- raise_error("cascade '#{method_name}' requires an expression as the last argument", location)
84
- end
85
-
86
- def convert_trait_names_to_bindings(trait_names, location)
87
- trait_names.map do |name|
88
- case name
89
- when Symbol
90
- create_binding(name, location)
91
- when DeclarationReference
92
- name # Already a binding from method_missing
93
- else
94
- raise_error("trait reference must be a symbol or bare identifier, got #{name.class}", location)
95
- end
96
- end
97
- end
98
-
99
- def add_case(condition, result)
100
- @cases << Kumi::Syntax::CaseExpression.new(condition, result)
101
- end
102
-
103
- def ref(name)
104
- @context.ref(name)
105
- end
106
-
107
- def fn(name, *args)
108
- @context.fn(name, *args)
109
- end
110
-
111
- def create_literal(value)
112
- @context.literal(value)
113
- end
114
-
115
- def create_fn(name, args)
116
- @context.fn(name, args)
117
- end
118
-
119
- def input
120
- @context.input
121
- end
122
-
123
- def ensure_syntax(expr)
124
- @context.ensure_syntax(expr)
125
- end
126
-
127
- def raise_error(message, location)
128
- @context.raise_error(message, location)
129
- end
130
-
131
- def create_binding(name, location)
132
- Kumi::Syntax::DeclarationReference.new(name, loc: location)
133
- end
134
- end
135
- end
136
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Parser
5
- # Converts Ruby objects and DSL expressions into AST nodes
6
- # This is the bridge between Ruby's native types and Kumi's syntax tree
7
- class ExpressionConverter
8
- include Syntax
9
- include ErrorReporting
10
-
11
- # Use the same literal types as Sugar module to avoid duplication
12
- LITERAL_TYPES = Sugar::LITERAL_TYPES
13
-
14
- def initialize(context)
15
- @context = context
16
- end
17
-
18
- # Convert any Ruby object into a syntax node
19
- # @param obj [Object] The object to convert
20
- # @return [Syntax::Node] The corresponding AST node
21
- def ensure_syntax(obj)
22
- case obj
23
- when *LITERAL_TYPES
24
- create_literal(obj)
25
- when Array
26
- create_list(obj)
27
- when Syntax::Node
28
- obj
29
- else
30
- handle_custom_object(obj)
31
- end
32
- end
33
-
34
- # Create a reference to another declaration
35
- # @param name [Symbol] The name to reference
36
- # @return [Syntax::DeclarationReference] Reference node
37
- def ref(name)
38
- validate_reference_name(name)
39
- Kumi::Syntax::DeclarationReference.new(name, loc: current_location)
40
- end
41
-
42
- # Create a literal value node
43
- # @param value [Object] The literal value
44
- # @return [Syntax::Literal] Literal node
45
- def literal(value)
46
- Kumi::Syntax::Literal.new(value, loc: current_location)
47
- end
48
-
49
- # Create a function call expression
50
- # @param fn_name [Symbol] The function name
51
- # @param args [Array] The function arguments
52
- # @return [Syntax::CallExpression] Function call node
53
- def fn(fn_name, *args)
54
- validate_function_name(fn_name)
55
- expr_args = convert_arguments(args)
56
- Kumi::Syntax::CallExpression.new(fn_name, expr_args, loc: current_location)
57
- end
58
-
59
- # Access the input proxy for field references
60
- # @return [InputProxy] Proxy for input field access
61
- def input
62
- InputProxy.new(@context)
63
- end
64
-
65
- # Raise a syntax error with location information
66
- # @param message [String] Error message
67
- # @param location [Location] Error location
68
- def raise_error(message, location)
69
- raise_syntax_error(message, location: location)
70
- end
71
-
72
- private
73
-
74
- def create_literal(value)
75
- Kumi::Syntax::Literal.new(value, loc: current_location)
76
- end
77
-
78
- def create_list(array)
79
- elements = array.map { |element| ensure_syntax(element) }
80
- Kumi::Syntax::ArrayExpression.new(elements, loc: current_location)
81
- end
82
-
83
- def handle_custom_object(obj)
84
- if obj.respond_to?(:to_ast_node)
85
- obj.to_ast_node
86
- else
87
- raise_invalid_expression_error(obj)
88
- end
89
- end
90
-
91
- def validate_reference_name(name)
92
- unless name.is_a?(Symbol)
93
- raise_syntax_error(
94
- "Reference name must be a symbol, got #{name.class}",
95
- location: current_location
96
- )
97
- end
98
- end
99
-
100
- def validate_function_name(fn_name)
101
- unless fn_name.is_a?(Symbol)
102
- raise_syntax_error(
103
- "Function name must be a symbol, got #{fn_name.class}",
104
- location: current_location
105
- )
106
- end
107
- end
108
-
109
- def convert_arguments(args)
110
- args.map { |arg| ensure_syntax(arg) }
111
- end
112
-
113
- def raise_invalid_expression_error(obj)
114
- raise_syntax_error(
115
- "Cannot convert #{obj.class} to AST node. " \
116
- "Value: #{obj.inspect}",
117
- location: current_location
118
- )
119
- end
120
-
121
- def current_location
122
- @context.current_location
123
- end
124
- end
125
- end
126
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Parser
5
- module GuardRails
6
- RESERVED = %i[input trait value fn lit ref].freeze
7
-
8
- def self.included(base)
9
- base.singleton_class.prepend(ClassMethods)
10
- end
11
-
12
- module ClassMethods
13
- # prevent accidental addition of new DSL keywords
14
- def method_added(name)
15
- if GuardRails::RESERVED.include?(name)
16
- # Check if this is a redefinition by looking at the call stack
17
- # We want to allow the original definition but prevent redefinition
18
- calling_location = caller_locations(1, 1).first
19
-
20
- # Allow the original definition from schema_builder.rb
21
- if calling_location&.path&.include?("schema_builder.rb")
22
- super
23
- return
24
- end
25
-
26
- # This is a redefinition attempt, prevent it
27
- raise Kumi::Errors::SemanticError,
28
- "DSL keyword `#{name}` is reserved; " \
29
- "do not redefine it inside SchemaBuilder"
30
- end
31
- super
32
- end
33
- end
34
-
35
- # catch any stray method call inside DSL block
36
- def method_missing(name, *_args)
37
- raise NoMethodError, "unknown DSL keyword `#{name}`"
38
- end
39
-
40
- def respond_to_missing?(*) = false
41
- end
42
- end
43
- end
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Parser
5
- class InputBuilder
6
- include Syntax
7
- include ErrorReporting
8
-
9
- def initialize(context)
10
- @context = context
11
- end
12
-
13
- def key(name, type: :any, domain: nil)
14
- normalized_type = normalize_type(type, name)
15
- @context.inputs << Kumi::Syntax::InputDeclaration.new(name, domain, normalized_type, [], loc: @context.current_location)
16
- end
17
-
18
- %i[integer float string boolean any scalar].each do |type_name|
19
- define_method(type_name) do |name, type: nil, domain: nil|
20
- actual_type = type || (type_name == :scalar ? :any : type_name)
21
- @context.inputs << Kumi::Syntax::InputDeclaration.new(name, domain, actual_type, [], loc: @context.current_location)
22
- end
23
- end
24
-
25
- def array(name_or_elem_type, **kwargs, &block)
26
- if block_given?
27
- create_array_field_with_block(name_or_elem_type, kwargs, &block)
28
- elsif kwargs.any?
29
- create_array_field(name_or_elem_type, kwargs)
30
- else
31
- Kumi::Types.array(name_or_elem_type)
32
- end
33
- end
34
-
35
- def hash(name_or_key_type, val_type = nil, **kwargs)
36
- return Kumi::Types.hash(name_or_key_type, val_type) unless val_type.nil?
37
-
38
- create_hash_field(name_or_key_type, kwargs)
39
- end
40
-
41
- def method_missing(method_name, *_args)
42
- allowed_methods = "'key', 'integer', 'float', 'string', 'boolean', 'any', 'scalar', 'array', and 'hash'"
43
- raise_syntax_error("Unknown method '#{method_name}' in input block. Only #{allowed_methods} are allowed.",
44
- location: @context.current_location)
45
- end
46
-
47
- def respond_to_missing?(_method_name, _include_private = false)
48
- false
49
- end
50
-
51
- private
52
-
53
- def normalize_type(type, name)
54
- Kumi::Types.normalize(type)
55
- rescue ArgumentError => e
56
- raise_syntax_error("Invalid type for input `#{name}`: #{e.message}", location: @context.current_location)
57
- end
58
-
59
- def create_array_field(field_name, options)
60
- elem_spec = options[:elem]
61
- domain = options[:domain]
62
- elem_type = elem_spec.is_a?(Hash) && elem_spec[:type] ? elem_spec[:type] : :any
63
-
64
- array_type = create_array_type(field_name, elem_type)
65
- @context.inputs << Kumi::Syntax::InputDeclaration.new(field_name, domain, array_type, [], loc: @context.current_location)
66
- end
67
-
68
- def create_array_type(field_name, elem_type)
69
- Kumi::Types.array(elem_type)
70
- rescue ArgumentError => e
71
- raise_syntax_error("Invalid element type for array `#{field_name}`: #{e.message}", location: @context.current_location)
72
- end
73
-
74
- def create_hash_field(field_name, options)
75
- key_spec = options[:key]
76
- val_spec = options[:val] || options[:value]
77
- domain = options[:domain]
78
-
79
- key_type = extract_type(key_spec)
80
- val_type = extract_type(val_spec)
81
-
82
- hash_type = create_hash_type(field_name, key_type, val_type)
83
- @context.inputs << Kumi::Syntax::InputDeclaration.new(field_name, domain, hash_type, [], loc: @context.current_location)
84
- end
85
-
86
- def extract_type(spec)
87
- spec.is_a?(Hash) && spec[:type] ? spec[:type] : :any
88
- end
89
-
90
- def create_hash_type(field_name, key_type, val_type)
91
- Kumi::Types.hash(key_type, val_type)
92
- rescue ArgumentError => e
93
- raise_syntax_error("Invalid types for hash `#{field_name}`: #{e.message}", location: @context.current_location)
94
- end
95
-
96
- def create_array_field_with_block(field_name, options, &block)
97
- domain = options[:domain]
98
-
99
- # Collect children by creating a nested context
100
- children = collect_array_children(&block)
101
-
102
- # Create the InputDeclaration with children
103
- @context.inputs << Kumi::Syntax::InputDeclaration.new(
104
- field_name,
105
- domain,
106
- :array,
107
- children,
108
- loc: @context.current_location
109
- )
110
- end
111
-
112
- def collect_array_children(&block)
113
- # Create a temporary nested context to collect children
114
- nested_inputs = []
115
- nested_context = NestedInput.new(nested_inputs, @context.current_location)
116
- nested_builder = InputBuilder.new(nested_context)
117
-
118
- # Execute the block in the nested context
119
- nested_builder.instance_eval(&block)
120
-
121
- nested_inputs
122
- end
123
- end
124
- end
125
- end