kumi 0.0.5 → 0.0.7
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 +76 -174
- data/README.md +205 -52
- data/{documents → docs}/AST.md +29 -29
- data/{documents → docs}/SYNTAX.md +95 -8
- 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/docs/schema_metadata/broadcasts.md +53 -0
- data/docs/schema_metadata/cascades.md +45 -0
- data/docs/schema_metadata/declarations.md +54 -0
- data/docs/schema_metadata/dependencies.md +57 -0
- data/docs/schema_metadata/evaluation_order.md +29 -0
- data/docs/schema_metadata/examples.md +95 -0
- data/docs/schema_metadata/inferred_types.md +46 -0
- data/docs/schema_metadata/inputs.md +86 -0
- data/docs/schema_metadata.md +108 -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 +246 -0
- data/lib/kumi/analyzer/passes/{definition_validator.rb → declaration_validator.rb} +4 -4
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +78 -38
- data/lib/kumi/analyzer/passes/input_collector.rb +91 -30
- data/lib/kumi/analyzer/passes/name_indexer.rb +2 -2
- data/lib/kumi/analyzer/passes/pass_base.rb +1 -1
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +24 -25
- data/lib/kumi/analyzer/passes/toposorter.rb +44 -8
- data/lib/kumi/analyzer/passes/type_checker.rb +34 -14
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +2 -2
- data/lib/kumi/analyzer/passes/type_inferencer.rb +130 -21
- data/lib/kumi/analyzer/passes/unsat_detector.rb +134 -56
- data/lib/kumi/analyzer/passes/visitor_pass.rb +2 -2
- data/lib/kumi/analyzer.rb +16 -17
- data/lib/kumi/compiler.rb +188 -16
- data/lib/kumi/constraint_relationship_solver.rb +6 -6
- data/lib/kumi/domain/validator.rb +0 -4
- data/lib/kumi/error_reporting.rb +1 -1
- data/lib/kumi/explain.rb +32 -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/json_schema/generator.rb +63 -0
- data/lib/kumi/json_schema/validator.rb +25 -0
- data/lib/kumi/json_schema.rb +14 -0
- data/lib/kumi/{parser → ruby_parser}/build_context.rb +1 -1
- data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +36 -0
- data/lib/kumi/{parser → ruby_parser}/dsl.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/dsl_cascade_builder.rb +5 -5
- data/lib/kumi/{parser → ruby_parser}/expression_converter.rb +20 -20
- data/lib/kumi/{parser → ruby_parser}/guard_rails.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/input_builder.rb +41 -10
- data/lib/kumi/ruby_parser/input_field_proxy.rb +46 -0
- data/lib/kumi/{parser → ruby_parser}/input_proxy.rb +4 -4
- data/lib/kumi/ruby_parser/nested_input.rb +15 -0
- data/lib/kumi/{parser → ruby_parser}/parser.rb +11 -10
- data/lib/kumi/{parser → ruby_parser}/schema_builder.rb +11 -10
- data/lib/kumi/{parser → ruby_parser}/sugar.rb +62 -10
- data/lib/kumi/ruby_parser.rb +10 -0
- data/lib/kumi/schema.rb +10 -4
- data/lib/kumi/schema_instance.rb +6 -6
- data/lib/kumi/schema_metadata.rb +524 -0
- 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
- data/lib/kumi.rb +14 -0
- metadata +55 -25
- data/lib/generators/trait_engine/templates/schema_spec.rb.erb +0 -27
- 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,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Kumi
|
6
|
+
module JsonSchema
|
7
|
+
# Converts Kumi schema metadata to JSON Schema format
|
8
|
+
class Generator
|
9
|
+
def initialize(schema_metadata)
|
10
|
+
@metadata = schema_metadata
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate
|
14
|
+
{
|
15
|
+
type: "object",
|
16
|
+
properties: build_properties,
|
17
|
+
required: extract_required_fields,
|
18
|
+
"x-kumi-values": @metadata.values,
|
19
|
+
"x-kumi-traits": @metadata.traits
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def build_properties
|
26
|
+
@metadata.inputs.transform_values { |spec| convert_input_to_json_schema(spec) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_required_fields
|
30
|
+
@metadata.inputs.select { |_k, v| v[:required] }.keys
|
31
|
+
end
|
32
|
+
|
33
|
+
def convert_input_to_json_schema(input_spec)
|
34
|
+
base = { type: map_kumi_type_to_json_schema(input_spec[:type]) }
|
35
|
+
|
36
|
+
domain = input_spec[:domain]
|
37
|
+
return base unless domain
|
38
|
+
|
39
|
+
case domain[:type]
|
40
|
+
when :range
|
41
|
+
base[:minimum] = domain[:min]
|
42
|
+
base[:maximum] = domain[:max]
|
43
|
+
when :enum
|
44
|
+
base[:enum] = domain[:values]
|
45
|
+
end
|
46
|
+
|
47
|
+
base
|
48
|
+
end
|
49
|
+
|
50
|
+
def map_kumi_type_to_json_schema(kumi_type)
|
51
|
+
case kumi_type
|
52
|
+
when :string then "string"
|
53
|
+
when :integer then "integer"
|
54
|
+
when :float then "number"
|
55
|
+
when :boolean then "boolean"
|
56
|
+
when :array then "array"
|
57
|
+
when :hash then "object"
|
58
|
+
else "string"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module JsonSchema
|
5
|
+
# Validates data against JSON Schema (placeholder for future implementation)
|
6
|
+
class Validator
|
7
|
+
def initialize(json_schema)
|
8
|
+
@schema = json_schema
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(_data)
|
12
|
+
# Placeholder implementation
|
13
|
+
# In a real implementation, this would validate data against the JSON Schema
|
14
|
+
{
|
15
|
+
valid: true,
|
16
|
+
errors: []
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.validate(json_schema, data)
|
21
|
+
new(json_schema).validate(data)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "json_schema/generator"
|
4
|
+
require_relative "json_schema/validator"
|
5
|
+
|
6
|
+
module Kumi
|
7
|
+
module JsonSchema
|
8
|
+
# Entry point for the JsonSchema functionality
|
9
|
+
#
|
10
|
+
# Available components:
|
11
|
+
# - Generator: Converts Kumi metadata to JSON Schema
|
12
|
+
# - Validator: Validates data against JSON Schema (placeholder)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module RubyParser
|
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,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
5
|
class DslCascadeBuilder
|
6
6
|
include Syntax
|
7
7
|
|
@@ -88,8 +88,8 @@ module Kumi
|
|
88
88
|
case name
|
89
89
|
when Symbol
|
90
90
|
create_binding(name, location)
|
91
|
-
when
|
92
|
-
name
|
91
|
+
when DeclarationReference
|
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)
|
95
95
|
end
|
@@ -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
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
5
|
# Converts Ruby objects and DSL expressions into AST nodes
|
6
6
|
# This is the bridge between Ruby's native types and Kumi's syntax tree
|
7
7
|
class ExpressionConverter
|
@@ -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)
|
@@ -89,21 +89,21 @@ module Kumi
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def validate_reference_name(name)
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
92
|
+
return if name.is_a?(Symbol)
|
93
|
+
|
94
|
+
raise_syntax_error(
|
95
|
+
"Reference name must be a symbol, got #{name.class}",
|
96
|
+
location: current_location
|
97
|
+
)
|
98
98
|
end
|
99
99
|
|
100
100
|
def validate_function_name(fn_name)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
return if fn_name.is_a?(Symbol)
|
102
|
+
|
103
|
+
raise_syntax_error(
|
104
|
+
"Function name must be a symbol, got #{fn_name.class}",
|
105
|
+
location: current_location
|
106
|
+
)
|
107
107
|
end
|
108
108
|
|
109
109
|
def convert_arguments(args)
|
@@ -123,4 +123,4 @@ module Kumi
|
|
123
123
|
end
|
124
124
|
end
|
125
125
|
end
|
126
|
-
end
|
126
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
5
|
class InputBuilder
|
6
6
|
include Syntax
|
7
7
|
include ErrorReporting
|
@@ -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 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,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
5
|
# Proxy object for input field references (input.field_name)
|
6
6
|
class InputProxy
|
7
7
|
include Syntax
|
@@ -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 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,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
|
+
# Main parser class for Ruby DSL
|
5
6
|
class Parser
|
6
7
|
include Syntax
|
7
8
|
include ErrorReporting
|
@@ -33,15 +34,19 @@ module Kumi
|
|
33
34
|
private
|
34
35
|
|
35
36
|
def enable_refinements(rule_block)
|
36
|
-
rule_block.binding.eval("using Kumi::
|
37
|
-
rule_block.binding.eval("using Kumi::
|
38
|
-
rule_block.binding.eval("using Kumi::
|
39
|
-
rule_block.binding.eval("using Kumi::
|
40
|
-
rule_block.binding.eval("using Kumi::
|
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")
|
41
42
|
rescue RuntimeError, NoMethodError
|
42
43
|
# Refinements disabled in method scope - continue without them
|
43
44
|
end
|
44
45
|
|
46
|
+
def build_syntax_tree
|
47
|
+
Root.new(@context.inputs, @context.attributes, @context.traits)
|
48
|
+
end
|
49
|
+
|
45
50
|
def handle_parse_error(error)
|
46
51
|
return unless literal_comparison_error?(error)
|
47
52
|
|
@@ -59,10 +64,6 @@ module Kumi
|
|
59
64
|
def literal_comparison_error?(error)
|
60
65
|
error.message =~ /comparison of Integer with Kumi::Syntax::/i
|
61
66
|
end
|
62
|
-
|
63
|
-
def build_syntax_tree
|
64
|
-
Root.new(@context.inputs, @context.attributes, @context.traits)
|
65
|
-
end
|
66
67
|
end
|
67
68
|
end
|
68
69
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
5
|
class SchemaBuilder
|
6
6
|
include GuardRails
|
7
7
|
include Syntax
|
@@ -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)
|