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
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module RubyParser
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
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module RubyParser
5
- # Proxy for input field access that can handle arbitrary depth nesting
6
- # Handles input.field.subfield.subsubfield... syntax by building up path arrays
7
- class InputFieldProxy
8
- include Syntax
9
-
10
- # Use shared operator methods instead of refinements
11
- extend Sugar::ProxyRefinement
12
-
13
- def initialize(path, context)
14
- @path = Array(path) # Ensure it's always an array
15
- @context = context
16
- end
17
-
18
- # Convert to appropriate AST node based on path length
19
- def to_ast_node
20
- if @path.length == 1
21
- # Single field: input.field -> InputReference
22
- Kumi::Syntax::InputReference.new(@path.first, loc: @context.current_location)
23
- else
24
- # Nested fields: input.field.subfield... -> InputElementReference
25
- Kumi::Syntax::InputElementReference.new(@path, loc: @context.current_location)
26
- end
27
- end
28
-
29
- private
30
-
31
- def method_missing(method_name, *args, &block)
32
- if args.empty? && block.nil?
33
- # Extend the path: input.user.details -> InputFieldProxy([user, details])
34
- InputFieldProxy.new(@path + [method_name], @context)
35
- else
36
- # Operators are now handled by ProxyRefinement methods
37
- super
38
- end
39
- end
40
-
41
- def respond_to_missing?(_method_name, _include_private = false)
42
- true
43
- end
44
- end
45
- end
46
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module RubyParser
5
- # Proxy object for input field references (input.field_name)
6
- class InputProxy
7
- include Syntax
8
-
9
- def initialize(context)
10
- @context = context
11
- end
12
-
13
- private
14
-
15
- def method_missing(method_name, *_args)
16
- # Create InputFieldProxy that can handle further field access
17
- InputFieldProxy.new(method_name, @context)
18
- end
19
-
20
- # This method is called when the user tries to access a field
21
- # on the input object, e.g. `input.field_name`.
22
- # It is used to create an InputReference node in the AST.
23
-
24
- def respond_to_missing?(_method_name, _include_private = false)
25
- true # Allow any field name
26
- end
27
- end
28
- end
29
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module RubyParser
5
- # Simple context struct for nested input collection
6
- class NestedInput
7
- attr_reader :inputs, :current_location
8
-
9
- def initialize(inputs_array, location)
10
- @inputs = inputs_array
11
- @current_location = location
12
- end
13
- end
14
- end
15
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module RubyParser
5
- # Main parser class for Ruby DSL
6
- class Parser
7
- include Syntax
8
- include ErrorReporting
9
-
10
- def initialize
11
- @context = BuildContext.new
12
- @interface = SchemaBuilder.new(@context)
13
- end
14
-
15
- def parse(&rule_block)
16
- enable_refinements(rule_block)
17
-
18
- before_consts = ::Object.constants
19
- @interface.freeze # stop singleton hacks
20
- @interface.instance_eval(&rule_block)
21
- added = ::Object.constants - before_consts
22
-
23
- unless added.empty?
24
- raise Kumi::Errors::SemanticError,
25
- "DSL cannot define global constants: #{added.join(', ')}"
26
- end
27
-
28
- build_syntax_tree
29
- rescue ArgumentError => e
30
- handle_parse_error(e)
31
- raise
32
- end
33
-
34
- private
35
-
36
- def enable_refinements(rule_block)
37
- rule_block.binding.eval("using Kumi::RubyParser::Sugar::ExpressionRefinement")
38
- rule_block.binding.eval("using Kumi::RubyParser::Sugar::NumericRefinement")
39
- rule_block.binding.eval("using Kumi::RubyParser::Sugar::StringRefinement")
40
- rule_block.binding.eval("using Kumi::RubyParser::Sugar::ArrayRefinement")
41
- rule_block.binding.eval("using Kumi::RubyParser::Sugar::ModuleRefinement")
42
- rescue RuntimeError, NoMethodError
43
- # Refinements disabled in method scope - continue without them
44
- end
45
-
46
- def build_syntax_tree
47
- Root.new(@context.inputs, @context.attributes, @context.traits)
48
- end
49
-
50
- def handle_parse_error(error)
51
- return unless literal_comparison_error?(error)
52
-
53
- warn <<~HINT
54
- #{error.backtrace.first.split(':', 2).join(':')}: \
55
- Literal‑left comparison failed because the schema block is \
56
- defined inside a method (Ruby disallows refinements there).
57
-
58
- • Move the `schema do … end` block to the top level of a class or module, OR
59
- • Write the comparison as `input.age >= 80` (preferred), OR
60
- • Wrap the literal: `lit(80) <= input.age`.
61
- HINT
62
- end
63
-
64
- def literal_comparison_error?(error)
65
- error.message =~ /comparison of Integer with Kumi::Syntax::/i
66
- end
67
- end
68
- end
69
- end
@@ -1,173 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module RubyParser
5
- class SchemaBuilder
6
- include GuardRails
7
- include Syntax
8
- include ErrorReporting
9
-
10
- DSL_METHODS = %i[value trait input ref literal fn].freeze
11
-
12
- def initialize(context)
13
- @context = context
14
- end
15
-
16
- def value(name = nil, expr = nil, &blk)
17
- update_location
18
- validate_value_args(name, expr, blk)
19
-
20
- expression = blk ? build_cascade(&blk) : ensure_syntax(expr)
21
- @context.attributes << Kumi::Syntax::ValueDeclaration.new(name, expression, loc: @context.current_location)
22
- end
23
-
24
- def trait(*args, **kwargs)
25
- update_location
26
- raise_syntax_error("keyword trait syntax not supported", location: @context.current_location) unless kwargs.empty?
27
- build_positional_trait(args)
28
- end
29
-
30
- def input(&blk)
31
- return InputProxy.new(@context) unless block_given?
32
-
33
- raise_syntax_error("input block already defined", location: @context.current_location) if @context.input_block_defined?
34
- @context.mark_input_block_defined!
35
-
36
- update_location
37
- input_builder = InputBuilder.new(@context)
38
- input_builder.instance_eval(&blk)
39
- end
40
-
41
- def ref(name)
42
- update_location
43
- Kumi::Syntax::DeclarationReference.new(name, loc: @context.current_location)
44
- end
45
-
46
- def literal(value)
47
- update_location
48
- Kumi::Syntax::Literal.new(value, loc: @context.current_location)
49
- end
50
-
51
- def fn(fn_name, *args)
52
- update_location
53
- expr_args = args.map { |a| ensure_syntax(a) }
54
- Kumi::Syntax::CallExpression.new(fn_name, expr_args, loc: @context.current_location)
55
- end
56
-
57
- def method_missing(method_name, *args, &block)
58
- if args.empty? && !block_given?
59
- update_location
60
- # Create proxy for declaration references (traits/values)
61
- DeclarationReferenceProxy.new(method_name, @context)
62
- else
63
- super
64
- end
65
- end
66
-
67
- def respond_to_missing?(_method_name, _include_private = false)
68
- true
69
- end
70
-
71
- private
72
-
73
- def update_location
74
- # Use caller_locations(2, 1) to skip the DSL method and get the actual user code location
75
- # Stack: [0] update_location, [1] DSL method (value/trait/etc), [2] user's DSL code
76
- caller_location = caller_locations(2, 1).first
77
-
78
- @context.current_location = Location.new(
79
- file: caller_location.path,
80
- line: caller_location.lineno,
81
- column: 0
82
- )
83
- end
84
-
85
- def validate_value_args(name, expr, blk)
86
- raise_syntax_error("value requires a name as first argument", location: @context.current_location) if name.nil?
87
- unless name.is_a?(Symbol)
88
- raise_syntax_error("The name for 'value' must be a Symbol, got #{name.class}",
89
- location: @context.current_location)
90
- end
91
-
92
- has_expr = !expr.nil?
93
- has_block = blk
94
-
95
- if has_expr && has_block
96
- raise_syntax_error("value '#{name}' cannot be called with both an expression and a block", location: @context.current_location)
97
- elsif !has_expr && !has_block
98
- raise_syntax_error("value '#{name}' requires an expression or a block", location: @context.current_location)
99
- end
100
- end
101
-
102
- def build_positional_trait(args)
103
- case args.size
104
- when 0
105
- raise_syntax_error("trait requires a name and expression", location: @context.current_location)
106
- when 1
107
- name = args.first
108
- raise_syntax_error("trait '#{name}' requires an expression", location: @context.current_location)
109
- when 2
110
- name, expression = args
111
- validate_trait_name(name)
112
- expr = ensure_syntax(expression)
113
- @context.traits << Kumi::Syntax::TraitDeclaration.new(name, expr, loc: @context.current_location)
114
- else
115
- handle_deprecated_trait_syntax(args)
116
- end
117
- end
118
-
119
- def handle_deprecated_trait_syntax(args)
120
- if args.size == 3
121
- name, = args
122
- raise_syntax_error("trait '#{name}' requires exactly 3 arguments: lhs, operator, and rhs", location: @context.current_location)
123
- end
124
-
125
- # warn "DEPRECATION: trait(:name, lhs, operator, rhs) syntax is deprecated. Use: trait :name, (lhs operator rhs)"
126
-
127
- if args.size == 4
128
- name, lhs, operator, rhs = args
129
- build_deprecated_trait(name, lhs, operator, [rhs])
130
- else
131
- name, lhs, operator, *rhs = args
132
- build_deprecated_trait(name, lhs, operator, rhs)
133
- end
134
- end
135
-
136
- def build_deprecated_trait(name, lhs, operator, rhs)
137
- validate_trait_name(name)
138
- validate_operator(operator)
139
-
140
- rhs_exprs = rhs.map { |r| ensure_syntax(r) }
141
- expr = Kumi::Syntax::CallExpression.new(operator, [ensure_syntax(lhs)] + rhs_exprs, loc: @context.current_location)
142
- @context.traits << Kumi::Syntax::TraitDeclaration.new(name, expr, loc: @context.current_location)
143
- end
144
-
145
- def validate_trait_name(name)
146
- return if name.is_a?(Symbol)
147
-
148
- raise_syntax_error("The name for 'trait' must be a Symbol, got #{name.class}", location: @context.current_location)
149
- end
150
-
151
- def validate_operator(operator)
152
- unless operator.is_a?(Symbol)
153
- raise_syntax_error("expects a symbol for an operator, got #{operator.class}", location: @context.current_location)
154
- end
155
-
156
- return if FunctionRegistry.operator?(operator)
157
-
158
- raise_syntax_error("unsupported operator `#{operator}`", location: @context.current_location)
159
- end
160
-
161
- def build_cascade(&blk)
162
- expression_converter = ExpressionConverter.new(@context)
163
- cascade_builder = DslCascadeBuilder.new(expression_converter, @context.current_location)
164
- cascade_builder.instance_eval(&blk)
165
- Kumi::Syntax::CascadeExpression.new(cascade_builder.cases, loc: @context.current_location)
166
- end
167
-
168
- def ensure_syntax(obj)
169
- ExpressionConverter.new(@context).ensure_syntax(obj)
170
- end
171
- end
172
- end
173
- end