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.
- checksums.yaml +4 -4
- data/CLAUDE.md +34 -177
- data/README.md +41 -7
- data/docs/SYNTAX.md +2 -7
- data/docs/features/array-broadcasting.md +1 -1
- 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/game_of_life.rb +1 -1
- data/examples/static_analysis_errors.rb +7 -7
- data/lib/kumi/analyzer.rb +20 -20
- data/lib/kumi/compiler.rb +44 -50
- data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
- data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
- data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
- data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
- data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
- data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
- data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
- data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
- data/lib/kumi/core/atom_unsat_solver.rb +396 -0
- data/lib/kumi/core/compiled_schema.rb +43 -0
- data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
- data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
- data/lib/kumi/core/domain/range_analyzer.rb +85 -0
- data/lib/kumi/core/domain/validator.rb +82 -0
- data/lib/kumi/core/domain/violation_formatter.rb +42 -0
- data/lib/kumi/core/error_reporter.rb +166 -0
- data/lib/kumi/core/error_reporting.rb +97 -0
- data/lib/kumi/core/errors.rb +120 -0
- data/lib/kumi/core/evaluation_wrapper.rb +40 -0
- data/lib/kumi/core/explain.rb +295 -0
- data/lib/kumi/core/export/deserializer.rb +41 -0
- data/lib/kumi/core/export/errors.rb +14 -0
- data/lib/kumi/core/export/node_builders.rb +142 -0
- data/lib/kumi/core/export/node_registry.rb +54 -0
- data/lib/kumi/core/export/node_serializers.rb +158 -0
- data/lib/kumi/core/export/serializer.rb +25 -0
- data/lib/kumi/core/export.rb +35 -0
- data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
- data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
- data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
- data/lib/kumi/core/function_registry/function_builder.rb +95 -0
- data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
- data/lib/kumi/core/function_registry/math_functions.rb +74 -0
- data/lib/kumi/core/function_registry/string_functions.rb +57 -0
- data/lib/kumi/core/function_registry/type_functions.rb +53 -0
- data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
- data/lib/kumi/core/input/type_matcher.rb +97 -0
- data/lib/kumi/core/input/validator.rb +51 -0
- data/lib/kumi/core/input/violation_creator.rb +52 -0
- data/lib/kumi/core/json_schema/generator.rb +65 -0
- data/lib/kumi/core/json_schema/validator.rb +27 -0
- data/lib/kumi/core/json_schema.rb +16 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
- data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
- data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
- data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
- data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
- data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
- data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
- data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
- data/lib/kumi/core/ruby_parser/parser.rb +71 -0
- data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
- data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
- data/lib/kumi/core/ruby_parser.rb +12 -0
- data/lib/kumi/core/schema_instance.rb +111 -0
- data/lib/kumi/core/types/builder.rb +23 -0
- data/lib/kumi/core/types/compatibility.rb +96 -0
- data/lib/kumi/core/types/formatter.rb +26 -0
- data/lib/kumi/core/types/inference.rb +42 -0
- data/lib/kumi/core/types/normalizer.rb +72 -0
- data/lib/kumi/core/types/validator.rb +37 -0
- data/lib/kumi/core/types.rb +66 -0
- data/lib/kumi/core/vectorization_metadata.rb +110 -0
- data/lib/kumi/errors.rb +1 -112
- data/lib/kumi/registry.rb +37 -0
- data/lib/kumi/schema.rb +13 -7
- data/lib/kumi/schema_metadata.rb +524 -0
- data/lib/kumi/syntax/array_expression.rb +6 -6
- data/lib/kumi/syntax/call_expression.rb +4 -4
- data/lib/kumi/syntax/cascade_expression.rb +4 -4
- data/lib/kumi/syntax/case_expression.rb +4 -4
- data/lib/kumi/syntax/declaration_reference.rb +4 -4
- data/lib/kumi/syntax/hash_expression.rb +4 -4
- data/lib/kumi/syntax/input_declaration.rb +5 -5
- data/lib/kumi/syntax/input_element_reference.rb +5 -5
- data/lib/kumi/syntax/input_reference.rb +5 -5
- data/lib/kumi/syntax/literal.rb +4 -4
- data/lib/kumi/syntax/node.rb +34 -34
- data/lib/kumi/syntax/root.rb +6 -6
- data/lib/kumi/syntax/trait_declaration.rb +4 -4
- data/lib/kumi/syntax/value_declaration.rb +4 -4
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +14 -0
- data/migrate_to_core_iterative.rb +938 -0
- data/scripts/generate_function_docs.rb +9 -9
- metadata +85 -69
- data/lib/generators/trait_engine/templates/schema_spec.rb.erb +0 -27
- data/lib/kumi/analyzer/analysis_state.rb +0 -37
- data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
- data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -251
- data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
- data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
- data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
- data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -110
- data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
- data/lib/kumi/analyzer/passes/type_checker.rb +0 -162
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
- data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
- data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -406
- data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
- data/lib/kumi/atom_unsat_solver.rb +0 -394
- data/lib/kumi/compiled_schema.rb +0 -41
- data/lib/kumi/constraint_relationship_solver.rb +0 -638
- data/lib/kumi/domain/enum_analyzer.rb +0 -53
- data/lib/kumi/domain/range_analyzer.rb +0 -83
- data/lib/kumi/domain/validator.rb +0 -80
- data/lib/kumi/domain/violation_formatter.rb +0 -40
- data/lib/kumi/error_reporter.rb +0 -164
- data/lib/kumi/error_reporting.rb +0 -95
- data/lib/kumi/evaluation_wrapper.rb +0 -38
- data/lib/kumi/explain.rb +0 -281
- data/lib/kumi/export/deserializer.rb +0 -39
- data/lib/kumi/export/errors.rb +0 -12
- data/lib/kumi/export/node_builders.rb +0 -140
- data/lib/kumi/export/node_registry.rb +0 -52
- data/lib/kumi/export/node_serializers.rb +0 -156
- data/lib/kumi/export/serializer.rb +0 -23
- data/lib/kumi/export.rb +0 -33
- data/lib/kumi/function_registry/collection_functions.rb +0 -200
- data/lib/kumi/function_registry/comparison_functions.rb +0 -31
- data/lib/kumi/function_registry/conditional_functions.rb +0 -36
- data/lib/kumi/function_registry/function_builder.rb +0 -93
- data/lib/kumi/function_registry/logical_functions.rb +0 -42
- data/lib/kumi/function_registry/math_functions.rb +0 -72
- data/lib/kumi/function_registry/string_functions.rb +0 -54
- data/lib/kumi/function_registry/type_functions.rb +0 -51
- data/lib/kumi/input/type_matcher.rb +0 -95
- data/lib/kumi/input/validator.rb +0 -49
- data/lib/kumi/input/violation_creator.rb +0 -50
- data/lib/kumi/parser/build_context.rb +0 -25
- data/lib/kumi/parser/declaration_reference_proxy.rb +0 -36
- data/lib/kumi/parser/dsl.rb +0 -12
- data/lib/kumi/parser/dsl_cascade_builder.rb +0 -136
- data/lib/kumi/parser/expression_converter.rb +0 -126
- data/lib/kumi/parser/guard_rails.rb +0 -43
- data/lib/kumi/parser/input_builder.rb +0 -125
- data/lib/kumi/parser/input_field_proxy.rb +0 -46
- data/lib/kumi/parser/input_proxy.rb +0 -29
- data/lib/kumi/parser/nested_input.rb +0 -15
- data/lib/kumi/parser/parser.rb +0 -68
- data/lib/kumi/parser/schema_builder.rb +0 -173
- data/lib/kumi/parser/sugar.rb +0 -261
- data/lib/kumi/schema_instance.rb +0 -109
- data/lib/kumi/types/builder.rb +0 -21
- data/lib/kumi/types/compatibility.rb +0 -94
- data/lib/kumi/types/formatter.rb +0 -24
- data/lib/kumi/types/inference.rb +0 -40
- data/lib/kumi/types/normalizer.rb +0 -70
- data/lib/kumi/types/validator.rb +0 -35
- data/lib/kumi/types.rb +0 -64
- 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
|
data/lib/kumi/input/validator.rb
DELETED
@@ -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
|
data/lib/kumi/parser/dsl.rb
DELETED
@@ -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
|