kumi 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CLAUDE.md +51 -6
- data/README.md +173 -51
- data/{documents → docs}/AST.md +29 -29
- data/{documents → docs}/SYNTAX.md +93 -1
- data/docs/features/README.md +45 -0
- data/docs/features/analysis-cascade-mutual-exclusion.md +89 -0
- data/docs/features/analysis-type-inference.md +42 -0
- data/docs/features/analysis-unsat-detection.md +71 -0
- data/docs/features/array-broadcasting.md +170 -0
- data/docs/features/input-declaration-system.md +42 -0
- data/docs/features/performance.md +16 -0
- data/examples/federal_tax_calculator_2024.rb +11 -6
- data/lib/kumi/analyzer/constant_evaluator.rb +1 -1
- data/lib/kumi/analyzer/passes/broadcast_detector.rb +251 -0
- data/lib/kumi/analyzer/passes/{definition_validator.rb → declaration_validator.rb} +4 -4
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +72 -32
- data/lib/kumi/analyzer/passes/input_collector.rb +90 -29
- data/lib/kumi/analyzer/passes/pass_base.rb +1 -1
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +9 -9
- data/lib/kumi/analyzer/passes/toposorter.rb +42 -6
- data/lib/kumi/analyzer/passes/type_checker.rb +32 -10
- data/lib/kumi/analyzer/passes/type_inferencer.rb +126 -17
- data/lib/kumi/analyzer/passes/unsat_detector.rb +133 -53
- data/lib/kumi/analyzer/passes/visitor_pass.rb +2 -2
- data/lib/kumi/analyzer.rb +11 -12
- data/lib/kumi/compiler.rb +194 -16
- data/lib/kumi/constraint_relationship_solver.rb +6 -6
- data/lib/kumi/domain/validator.rb +0 -4
- data/lib/kumi/explain.rb +20 -20
- data/lib/kumi/export/node_registry.rb +26 -12
- data/lib/kumi/export/node_serializers.rb +1 -1
- data/lib/kumi/function_registry/collection_functions.rb +14 -9
- data/lib/kumi/function_registry/function_builder.rb +4 -3
- data/lib/kumi/function_registry.rb +8 -2
- data/lib/kumi/input/type_matcher.rb +3 -0
- data/lib/kumi/input/validator.rb +0 -3
- data/lib/kumi/parser/declaration_reference_proxy.rb +36 -0
- data/lib/kumi/parser/dsl_cascade_builder.rb +3 -3
- data/lib/kumi/parser/expression_converter.rb +6 -6
- data/lib/kumi/parser/input_builder.rb +40 -9
- data/lib/kumi/parser/input_field_proxy.rb +46 -0
- data/lib/kumi/parser/input_proxy.rb +3 -3
- data/lib/kumi/parser/nested_input.rb +15 -0
- data/lib/kumi/parser/schema_builder.rb +10 -9
- data/lib/kumi/parser/sugar.rb +61 -9
- data/lib/kumi/syntax/array_expression.rb +15 -0
- data/lib/kumi/syntax/call_expression.rb +11 -0
- data/lib/kumi/syntax/cascade_expression.rb +11 -0
- data/lib/kumi/syntax/case_expression.rb +11 -0
- data/lib/kumi/syntax/declaration_reference.rb +11 -0
- data/lib/kumi/syntax/hash_expression.rb +11 -0
- data/lib/kumi/syntax/input_declaration.rb +12 -0
- data/lib/kumi/syntax/input_element_reference.rb +12 -0
- data/lib/kumi/syntax/input_reference.rb +12 -0
- data/lib/kumi/syntax/literal.rb +11 -0
- data/lib/kumi/syntax/trait_declaration.rb +11 -0
- data/lib/kumi/syntax/value_declaration.rb +11 -0
- data/lib/kumi/vectorization_metadata.rb +108 -0
- data/lib/kumi/version.rb +1 -1
- metadata +31 -14
- data/lib/kumi/domain.rb +0 -8
- data/lib/kumi/input.rb +0 -8
- data/lib/kumi/syntax/declarations.rb +0 -26
- data/lib/kumi/syntax/expressions.rb +0 -34
- data/lib/kumi/syntax/terminal_expressions.rb +0 -30
- data/lib/kumi/syntax.rb +0 -9
- /data/{documents → docs}/DSL.md +0 -0
- /data/{documents → docs}/FUNCTIONS.md +0 -0
@@ -0,0 +1,36 @@
|
|
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
|
@@ -88,7 +88,7 @@ module Kumi
|
|
88
88
|
case name
|
89
89
|
when Symbol
|
90
90
|
create_binding(name, location)
|
91
|
-
when
|
91
|
+
when DeclarationReference
|
92
92
|
name # Already a binding from method_missing
|
93
93
|
else
|
94
94
|
raise_error("trait reference must be a symbol or bare identifier, got #{name.class}", location)
|
@@ -97,7 +97,7 @@ module Kumi
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def add_case(condition, result)
|
100
|
-
@cases <<
|
100
|
+
@cases << Kumi::Syntax::CaseExpression.new(condition, result)
|
101
101
|
end
|
102
102
|
|
103
103
|
def ref(name)
|
@@ -129,7 +129,7 @@ module Kumi
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def create_binding(name, location)
|
132
|
-
|
132
|
+
Kumi::Syntax::DeclarationReference.new(name, loc: location)
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
@@ -33,17 +33,17 @@ module Kumi
|
|
33
33
|
|
34
34
|
# Create a reference to another declaration
|
35
35
|
# @param name [Symbol] The name to reference
|
36
|
-
# @return [Syntax::
|
36
|
+
# @return [Syntax::DeclarationReference] Reference node
|
37
37
|
def ref(name)
|
38
38
|
validate_reference_name(name)
|
39
|
-
|
39
|
+
Kumi::Syntax::DeclarationReference.new(name, loc: current_location)
|
40
40
|
end
|
41
41
|
|
42
42
|
# Create a literal value node
|
43
43
|
# @param value [Object] The literal value
|
44
44
|
# @return [Syntax::Literal] Literal node
|
45
45
|
def literal(value)
|
46
|
-
Literal.new(value, loc: current_location)
|
46
|
+
Kumi::Syntax::Literal.new(value, loc: current_location)
|
47
47
|
end
|
48
48
|
|
49
49
|
# Create a function call expression
|
@@ -53,7 +53,7 @@ module Kumi
|
|
53
53
|
def fn(fn_name, *args)
|
54
54
|
validate_function_name(fn_name)
|
55
55
|
expr_args = convert_arguments(args)
|
56
|
-
CallExpression.new(fn_name, expr_args, loc: current_location)
|
56
|
+
Kumi::Syntax::CallExpression.new(fn_name, expr_args, loc: current_location)
|
57
57
|
end
|
58
58
|
|
59
59
|
# Access the input proxy for field references
|
@@ -72,12 +72,12 @@ module Kumi
|
|
72
72
|
private
|
73
73
|
|
74
74
|
def create_literal(value)
|
75
|
-
Literal.new(value, loc: current_location)
|
75
|
+
Kumi::Syntax::Literal.new(value, loc: current_location)
|
76
76
|
end
|
77
77
|
|
78
78
|
def create_list(array)
|
79
79
|
elements = array.map { |element| ensure_syntax(element) }
|
80
|
-
|
80
|
+
Kumi::Syntax::ArrayExpression.new(elements, loc: current_location)
|
81
81
|
end
|
82
82
|
|
83
83
|
def handle_custom_object(obj)
|
@@ -12,17 +12,20 @@ module Kumi
|
|
12
12
|
|
13
13
|
def key(name, type: :any, domain: nil)
|
14
14
|
normalized_type = normalize_type(type, name)
|
15
|
-
@context.inputs <<
|
15
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(name, domain, normalized_type, [], loc: @context.current_location)
|
16
16
|
end
|
17
17
|
|
18
|
-
%i[integer float string boolean any].each do |type_name|
|
19
|
-
define_method(type_name) do |name, domain: nil|
|
20
|
-
|
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)
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
def array(name_or_elem_type, **kwargs)
|
25
|
-
if
|
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?
|
26
29
|
create_array_field(name_or_elem_type, kwargs)
|
27
30
|
else
|
28
31
|
Kumi::Types.array(name_or_elem_type)
|
@@ -36,7 +39,7 @@ module Kumi
|
|
36
39
|
end
|
37
40
|
|
38
41
|
def method_missing(method_name, *_args)
|
39
|
-
allowed_methods = "'key', 'integer', 'float', 'string', 'boolean', 'any', 'array', and 'hash'"
|
42
|
+
allowed_methods = "'key', 'integer', 'float', 'string', 'boolean', 'any', 'scalar', 'array', and 'hash'"
|
40
43
|
raise_syntax_error("Unknown method '#{method_name}' in input block. Only #{allowed_methods} are allowed.",
|
41
44
|
location: @context.current_location)
|
42
45
|
end
|
@@ -59,7 +62,7 @@ module Kumi
|
|
59
62
|
elem_type = elem_spec.is_a?(Hash) && elem_spec[:type] ? elem_spec[:type] : :any
|
60
63
|
|
61
64
|
array_type = create_array_type(field_name, elem_type)
|
62
|
-
@context.inputs <<
|
65
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(field_name, domain, array_type, [], loc: @context.current_location)
|
63
66
|
end
|
64
67
|
|
65
68
|
def create_array_type(field_name, elem_type)
|
@@ -77,7 +80,7 @@ module Kumi
|
|
77
80
|
val_type = extract_type(val_spec)
|
78
81
|
|
79
82
|
hash_type = create_hash_type(field_name, key_type, val_type)
|
80
|
-
@context.inputs <<
|
83
|
+
@context.inputs << Kumi::Syntax::InputDeclaration.new(field_name, domain, hash_type, [], loc: @context.current_location)
|
81
84
|
end
|
82
85
|
|
83
86
|
def extract_type(spec)
|
@@ -89,6 +92,34 @@ module Kumi
|
|
89
92
|
rescue ArgumentError => e
|
90
93
|
raise_syntax_error("Invalid types for hash `#{field_name}`: #{e.message}", location: @context.current_location)
|
91
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
|
92
123
|
end
|
93
124
|
end
|
94
125
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Parser
|
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
|
@@ -13,13 +13,13 @@ module Kumi
|
|
13
13
|
private
|
14
14
|
|
15
15
|
def method_missing(method_name, *_args)
|
16
|
-
# Create
|
17
|
-
|
16
|
+
# Create InputFieldProxy that can handle further field access
|
17
|
+
InputFieldProxy.new(method_name, @context)
|
18
18
|
end
|
19
19
|
|
20
20
|
# This method is called when the user tries to access a field
|
21
21
|
# on the input object, e.g. `input.field_name`.
|
22
|
-
# It is used to create
|
22
|
+
# It is used to create an InputReference node in the AST.
|
23
23
|
|
24
24
|
def respond_to_missing?(_method_name, _include_private = false)
|
25
25
|
true # Allow any field name
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Parser
|
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
|
@@ -18,7 +18,7 @@ module Kumi
|
|
18
18
|
validate_value_args(name, expr, blk)
|
19
19
|
|
20
20
|
expression = blk ? build_cascade(&blk) : ensure_syntax(expr)
|
21
|
-
@context.attributes <<
|
21
|
+
@context.attributes << Kumi::Syntax::ValueDeclaration.new(name, expression, loc: @context.current_location)
|
22
22
|
end
|
23
23
|
|
24
24
|
def trait(*args, **kwargs)
|
@@ -40,24 +40,25 @@ module Kumi
|
|
40
40
|
|
41
41
|
def ref(name)
|
42
42
|
update_location
|
43
|
-
|
43
|
+
Kumi::Syntax::DeclarationReference.new(name, loc: @context.current_location)
|
44
44
|
end
|
45
45
|
|
46
46
|
def literal(value)
|
47
47
|
update_location
|
48
|
-
Literal.new(value, loc: @context.current_location)
|
48
|
+
Kumi::Syntax::Literal.new(value, loc: @context.current_location)
|
49
49
|
end
|
50
50
|
|
51
51
|
def fn(fn_name, *args)
|
52
52
|
update_location
|
53
53
|
expr_args = args.map { |a| ensure_syntax(a) }
|
54
|
-
CallExpression.new(fn_name, expr_args, loc: @context.current_location)
|
54
|
+
Kumi::Syntax::CallExpression.new(fn_name, expr_args, loc: @context.current_location)
|
55
55
|
end
|
56
56
|
|
57
57
|
def method_missing(method_name, *args, &block)
|
58
58
|
if args.empty? && !block_given?
|
59
59
|
update_location
|
60
|
-
|
60
|
+
# Create proxy for declaration references (traits/values)
|
61
|
+
DeclarationReferenceProxy.new(method_name, @context)
|
61
62
|
else
|
62
63
|
super
|
63
64
|
end
|
@@ -109,7 +110,7 @@ module Kumi
|
|
109
110
|
name, expression = args
|
110
111
|
validate_trait_name(name)
|
111
112
|
expr = ensure_syntax(expression)
|
112
|
-
@context.traits <<
|
113
|
+
@context.traits << Kumi::Syntax::TraitDeclaration.new(name, expr, loc: @context.current_location)
|
113
114
|
else
|
114
115
|
handle_deprecated_trait_syntax(args)
|
115
116
|
end
|
@@ -137,8 +138,8 @@ module Kumi
|
|
137
138
|
validate_operator(operator)
|
138
139
|
|
139
140
|
rhs_exprs = rhs.map { |r| ensure_syntax(r) }
|
140
|
-
expr = CallExpression.new(operator, [ensure_syntax(lhs)] + rhs_exprs, loc: @context.current_location)
|
141
|
-
@context.traits <<
|
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)
|
142
143
|
end
|
143
144
|
|
144
145
|
def validate_trait_name(name)
|
@@ -161,7 +162,7 @@ module Kumi
|
|
161
162
|
expression_converter = ExpressionConverter.new(@context)
|
162
163
|
cascade_builder = DslCascadeBuilder.new(expression_converter, @context.current_location)
|
163
164
|
cascade_builder.instance_eval(&blk)
|
164
|
-
CascadeExpression.new(cascade_builder.cases, loc: @context.current_location)
|
165
|
+
Kumi::Syntax::CascadeExpression.new(cascade_builder.cases, loc: @context.current_location)
|
165
166
|
end
|
166
167
|
|
167
168
|
def ensure_syntax(obj)
|
data/lib/kumi/parser/sugar.rb
CHANGED
@@ -13,7 +13,7 @@ module Kumi
|
|
13
13
|
COMPARISON_OPS = %i[< <= > >= == !=].freeze
|
14
14
|
|
15
15
|
LITERAL_TYPES = [
|
16
|
-
Integer, String, Symbol, TrueClass, FalseClass, Float, Regexp
|
16
|
+
Integer, String, Symbol, TrueClass, FalseClass, Float, Regexp, NilClass
|
17
17
|
].freeze
|
18
18
|
|
19
19
|
# Collection methods that can be applied to arrays/syntax nodes
|
@@ -23,11 +23,11 @@ module Kumi
|
|
23
23
|
].freeze
|
24
24
|
|
25
25
|
def self.ensure_literal(obj)
|
26
|
-
return Literal.new(obj) if LITERAL_TYPES.any? { |type| obj.is_a?(type) }
|
26
|
+
return Kumi::Syntax::Literal.new(obj) if LITERAL_TYPES.any? { |type| obj.is_a?(type) }
|
27
27
|
return obj if obj.is_a?(Syntax::Node)
|
28
28
|
return obj.to_ast_node if obj.respond_to?(:to_ast_node)
|
29
29
|
|
30
|
-
Literal.new(obj)
|
30
|
+
Kumi::Syntax::Literal.new(obj)
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.syntax_expression?(obj)
|
@@ -36,7 +36,7 @@ module Kumi
|
|
36
36
|
|
37
37
|
# Create a call expression with consistent error handling
|
38
38
|
def self.create_call_expression(fn_name, args)
|
39
|
-
Syntax::CallExpression.new(fn_name, args)
|
39
|
+
Kumi::Syntax::CallExpression.new(fn_name, args)
|
40
40
|
end
|
41
41
|
|
42
42
|
module ExpressionRefinement
|
@@ -100,7 +100,7 @@ module Kumi
|
|
100
100
|
define_method(op) do |other|
|
101
101
|
if Sugar.syntax_expression?(other)
|
102
102
|
other_node = Sugar.ensure_literal(other)
|
103
|
-
Sugar.create_call_expression(op_name, [Syntax::Literal.new(self), other_node])
|
103
|
+
Sugar.create_call_expression(op_name, [Kumi::Syntax::Literal.new(self), other_node])
|
104
104
|
else
|
105
105
|
super(other)
|
106
106
|
end
|
@@ -112,7 +112,7 @@ module Kumi
|
|
112
112
|
define_method(op) do |other|
|
113
113
|
if Sugar.syntax_expression?(other)
|
114
114
|
other_node = Sugar.ensure_literal(other)
|
115
|
-
Sugar.create_call_expression(op, [Syntax::Literal.new(self), other_node])
|
115
|
+
Sugar.create_call_expression(op, [Kumi::Syntax::Literal.new(self), other_node])
|
116
116
|
else
|
117
117
|
super(other)
|
118
118
|
end
|
@@ -127,7 +127,7 @@ module Kumi
|
|
127
127
|
def +(other)
|
128
128
|
if Sugar.syntax_expression?(other)
|
129
129
|
other_node = Sugar.ensure_literal(other)
|
130
|
-
Sugar.create_call_expression(:concat, [Syntax::Literal.new(self), other_node])
|
130
|
+
Sugar.create_call_expression(:concat, [Kumi::Syntax::Literal.new(self), other_node])
|
131
131
|
else
|
132
132
|
super
|
133
133
|
end
|
@@ -137,7 +137,7 @@ module Kumi
|
|
137
137
|
define_method(op) do |other|
|
138
138
|
if Sugar.syntax_expression?(other)
|
139
139
|
other_node = Sugar.ensure_literal(other)
|
140
|
-
Sugar.create_call_expression(op, [Syntax::Literal.new(self), other_node])
|
140
|
+
Sugar.create_call_expression(op, [Kumi::Syntax::Literal.new(self), other_node])
|
141
141
|
else
|
142
142
|
super(other)
|
143
143
|
end
|
@@ -156,7 +156,7 @@ module Kumi
|
|
156
156
|
# Convert array to syntax list expression with all elements as syntax nodes
|
157
157
|
def to_syntax_list
|
158
158
|
syntax_elements = map { |item| Sugar.ensure_literal(item) }
|
159
|
-
Syntax::
|
159
|
+
Kumi::Syntax::ArrayExpression.new(syntax_elements)
|
160
160
|
end
|
161
161
|
|
162
162
|
# Create array method that works with syntax expressions
|
@@ -204,6 +204,58 @@ module Kumi
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
end
|
207
|
+
|
208
|
+
# Shared refinement for proxy objects that need to handle operators
|
209
|
+
# Both DeclarationReferenceProxy and InputFieldProxy can use this
|
210
|
+
module ProxyRefinement
|
211
|
+
def self.extended(proxy_class)
|
212
|
+
# Add operator methods directly to the proxy class
|
213
|
+
proxy_class.class_eval do
|
214
|
+
# Arithmetic operations
|
215
|
+
ARITHMETIC_OPS.each do |op, op_name|
|
216
|
+
define_method(op) do |other|
|
217
|
+
ast_node = to_ast_node
|
218
|
+
other_node = Sugar.ensure_literal(other)
|
219
|
+
Sugar.create_call_expression(op_name, [ast_node, other_node])
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Comparison operations (including == and != that don't work with refinements)
|
224
|
+
COMPARISON_OPS.each do |op|
|
225
|
+
define_method(op) do |other|
|
226
|
+
ast_node = to_ast_node
|
227
|
+
other_node = Sugar.ensure_literal(other)
|
228
|
+
Sugar.create_call_expression(op, [ast_node, other_node])
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Logical operations
|
233
|
+
define_method(:&) do |other|
|
234
|
+
ast_node = to_ast_node
|
235
|
+
other_node = Sugar.ensure_literal(other)
|
236
|
+
Sugar.create_call_expression(:and, [ast_node, other_node])
|
237
|
+
end
|
238
|
+
|
239
|
+
define_method(:|) do |other|
|
240
|
+
ast_node = to_ast_node
|
241
|
+
other_node = Sugar.ensure_literal(other)
|
242
|
+
Sugar.create_call_expression(:or, [ast_node, other_node])
|
243
|
+
end
|
244
|
+
|
245
|
+
# Array access
|
246
|
+
define_method(:[]) do |index|
|
247
|
+
ast_node = to_ast_node
|
248
|
+
Sugar.create_call_expression(:at, [ast_node, Sugar.ensure_literal(index)])
|
249
|
+
end
|
250
|
+
|
251
|
+
# Unary minus
|
252
|
+
define_method(:-@) do
|
253
|
+
ast_node = to_ast_node
|
254
|
+
Sugar.create_call_expression(:subtract, [Sugar.ensure_literal(0), ast_node])
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
207
259
|
end
|
208
260
|
end
|
209
261
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Syntax
|
5
|
+
# For field metadata declarations inside input blocks
|
6
|
+
InputDeclaration = Struct.new(:name, :domain, :type, :children) do
|
7
|
+
include Node
|
8
|
+
|
9
|
+
def children = self[:children] || []
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|