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.
- checksums.yaml +4 -4
- data/CLAUDE.md +1 -1
- data/README.md +8 -5
- data/examples/game_of_life.rb +1 -1
- data/examples/static_analysis_errors.rb +7 -7
- data/lib/kumi/analyzer.rb +15 -15
- data/lib/kumi/compiler.rb +6 -6
- 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 +5 -5
- data/lib/kumi/schema_metadata.rb +3 -3
- 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/migrate_to_core_iterative.rb +938 -0
- data/scripts/generate_function_docs.rb +9 -9
- metadata +75 -72
- 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 -246
- 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 -109
- data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
- data/lib/kumi/analyzer/passes/type_checker.rb +0 -160
- 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 -404
- 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 -293
- 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/json_schema/generator.rb +0 -63
- data/lib/kumi/json_schema/validator.rb +0 -25
- data/lib/kumi/json_schema.rb +0 -14
- data/lib/kumi/ruby_parser/build_context.rb +0 -25
- data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +0 -36
- data/lib/kumi/ruby_parser/dsl.rb +0 -12
- data/lib/kumi/ruby_parser/dsl_cascade_builder.rb +0 -136
- data/lib/kumi/ruby_parser/expression_converter.rb +0 -126
- data/lib/kumi/ruby_parser/guard_rails.rb +0 -43
- data/lib/kumi/ruby_parser/input_builder.rb +0 -125
- data/lib/kumi/ruby_parser/input_field_proxy.rb +0 -46
- data/lib/kumi/ruby_parser/input_proxy.rb +0 -29
- data/lib/kumi/ruby_parser/nested_input.rb +0 -15
- data/lib/kumi/ruby_parser/parser.rb +0 -69
- data/lib/kumi/ruby_parser/schema_builder.rb +0 -173
- data/lib/kumi/ruby_parser/sugar.rb +0 -261
- data/lib/kumi/ruby_parser.rb +0 -10
- 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
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
# Mathematical operations
|
7
|
+
module MathFunctions
|
8
|
+
def self.definitions
|
9
|
+
{
|
10
|
+
# Basic arithmetic
|
11
|
+
add: FunctionBuilder.math_binary(:add, "Add two numbers", :+),
|
12
|
+
subtract: FunctionBuilder.math_binary(:subtract, "Subtract second number from first", :-),
|
13
|
+
multiply: FunctionBuilder.math_binary(:multiply, "Multiply two numbers", :*),
|
14
|
+
divide: FunctionBuilder.math_binary(:divide, "Divide first number by second", :/),
|
15
|
+
modulo: FunctionBuilder.math_binary(:modulo, "Modulo operation", :%),
|
16
|
+
power: FunctionBuilder.math_binary(:power, "Raise first number to power of second", :**),
|
17
|
+
|
18
|
+
# Unary operations
|
19
|
+
abs: FunctionBuilder.math_unary(:abs, "Absolute value", :abs),
|
20
|
+
floor: FunctionBuilder.math_unary(:floor, "Floor of number", :floor, return_type: :integer),
|
21
|
+
ceil: FunctionBuilder.math_unary(:ceil, "Ceiling of number", :ceil, return_type: :integer),
|
22
|
+
|
23
|
+
# Special operations
|
24
|
+
round: FunctionBuilder::Entry.new(
|
25
|
+
fn: ->(a, precision = 0) { a.round(precision) },
|
26
|
+
arity: -1,
|
27
|
+
param_types: [:float],
|
28
|
+
return_type: :float,
|
29
|
+
description: "Round number to specified precision"
|
30
|
+
),
|
31
|
+
|
32
|
+
clamp: FunctionBuilder::Entry.new(
|
33
|
+
fn: ->(value, min, max) { value.clamp(min, max) },
|
34
|
+
arity: 3,
|
35
|
+
param_types: %i[float float float],
|
36
|
+
return_type: :float,
|
37
|
+
description: "Clamp value between min and max"
|
38
|
+
),
|
39
|
+
piecewise_sum: FunctionBuilder::Entry.new(
|
40
|
+
# Tiered / piece‑wise accumulator
|
41
|
+
fn: lambda do |value, breaks, rates|
|
42
|
+
raise ArgumentError, "breaks & rates size mismatch" unless breaks.size == rates.size
|
43
|
+
|
44
|
+
acc = 0.0
|
45
|
+
previous = 0.0
|
46
|
+
marginal = rates.last
|
47
|
+
|
48
|
+
breaks.zip(rates).each do |upper, rate|
|
49
|
+
if value <= upper
|
50
|
+
marginal = rate
|
51
|
+
acc += (value - previous) * rate
|
52
|
+
break
|
53
|
+
else
|
54
|
+
acc += (upper - previous) * rate
|
55
|
+
previous = upper
|
56
|
+
end
|
57
|
+
end
|
58
|
+
[acc, marginal] # => [sum, marginal_rate]
|
59
|
+
end,
|
60
|
+
arity: 3,
|
61
|
+
param_types: [
|
62
|
+
:float,
|
63
|
+
Kumi::Core::Types.array(:float), # breaks
|
64
|
+
Kumi::Core::Types.array(:float) # rates
|
65
|
+
],
|
66
|
+
return_type: Kumi::Core::Types.array(:float), # 2‑element [sum, marginal]
|
67
|
+
description: "Accumulate over tiered ranges; returns [sum, marginal_rate]"
|
68
|
+
)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
# String manipulation functions
|
7
|
+
module StringFunctions
|
8
|
+
def self.definitions
|
9
|
+
{
|
10
|
+
# String transformations
|
11
|
+
upcase: FunctionBuilder.string_unary(:upcase, "Convert string to uppercase", :upcase),
|
12
|
+
downcase: FunctionBuilder.string_unary(:downcase, "Convert string to lowercase", :downcase),
|
13
|
+
capitalize: FunctionBuilder.string_unary(:capitalize, "Capitalize first letter of string", :capitalize),
|
14
|
+
strip: FunctionBuilder.string_unary(:strip, "Remove leading and trailing whitespace", :strip),
|
15
|
+
|
16
|
+
# String queries
|
17
|
+
string_length: FunctionBuilder::Entry.new(
|
18
|
+
fn: ->(str) { str.to_s.length },
|
19
|
+
arity: 1,
|
20
|
+
param_types: [:string],
|
21
|
+
return_type: :integer,
|
22
|
+
description: "Get string length"
|
23
|
+
),
|
24
|
+
|
25
|
+
# Keep the original length for backward compatibility, but it will be overridden
|
26
|
+
length: FunctionBuilder::Entry.new(
|
27
|
+
fn: ->(str) { str.to_s.length },
|
28
|
+
arity: 1,
|
29
|
+
param_types: [:string],
|
30
|
+
return_type: :integer,
|
31
|
+
description: "Get string length"
|
32
|
+
),
|
33
|
+
|
34
|
+
# String inclusion using different name to avoid conflict with collection include?
|
35
|
+
string_include?: FunctionBuilder.string_binary(:include?, "Check if string contains substring", :include?,
|
36
|
+
return_type: :boolean),
|
37
|
+
includes?: FunctionBuilder.string_binary(:include?, "Check if string contains substring", :include?, return_type: :boolean),
|
38
|
+
contains?: FunctionBuilder.string_binary(:include?, "Check if string contains substring", :include?, return_type: :boolean),
|
39
|
+
|
40
|
+
start_with?: FunctionBuilder.string_binary(:start_with?, "Check if string starts with prefix", :start_with?,
|
41
|
+
return_type: :boolean),
|
42
|
+
end_with?: FunctionBuilder.string_binary(:end_with?, "Check if string ends with suffix", :end_with?, return_type: :boolean),
|
43
|
+
|
44
|
+
# String building
|
45
|
+
concat: FunctionBuilder::Entry.new(
|
46
|
+
fn: ->(*strings) { strings.join },
|
47
|
+
arity: -1,
|
48
|
+
param_types: [:string],
|
49
|
+
return_type: :string,
|
50
|
+
description: "Concatenate multiple strings"
|
51
|
+
)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
# Type checking and conversion functions
|
7
|
+
module TypeFunctions
|
8
|
+
def self.definitions
|
9
|
+
{
|
10
|
+
fetch: FunctionBuilder::Entry.new(
|
11
|
+
fn: ->(hash, key, default = nil) { hash.fetch(key, default) },
|
12
|
+
arity: -1, # Variable arity (2 or 3)
|
13
|
+
param_types: [Kumi::Core::Types.hash(:any, :any), :any, :any],
|
14
|
+
return_type: :any,
|
15
|
+
description: "Fetch value from hash with optional default"
|
16
|
+
),
|
17
|
+
|
18
|
+
has_key?: FunctionBuilder::Entry.new(
|
19
|
+
fn: ->(hash, key) { hash.key?(key) },
|
20
|
+
arity: 2,
|
21
|
+
param_types: [Kumi::Core::Types.hash(:any, :any), :any],
|
22
|
+
return_type: :boolean,
|
23
|
+
description: "Check if hash has the given key"
|
24
|
+
),
|
25
|
+
|
26
|
+
keys: FunctionBuilder::Entry.new(
|
27
|
+
fn: lambda(&:keys),
|
28
|
+
arity: 1,
|
29
|
+
param_types: [Kumi::Core::Types.hash(:any, :any)],
|
30
|
+
return_type: Kumi::Core::Types.array(:any),
|
31
|
+
description: "Get all keys from hash"
|
32
|
+
),
|
33
|
+
|
34
|
+
values: FunctionBuilder::Entry.new(
|
35
|
+
fn: lambda(&:values),
|
36
|
+
arity: 1,
|
37
|
+
param_types: [Kumi::Core::Types.hash(:any, :any)],
|
38
|
+
return_type: Kumi::Core::Types.array(:any),
|
39
|
+
description: "Get all values from hash"
|
40
|
+
),
|
41
|
+
at: FunctionBuilder::Entry.new(
|
42
|
+
fn: ->(array, index) { array[index] },
|
43
|
+
arity: 2,
|
44
|
+
param_types: [Kumi::Core::Types.array(:any), :integer],
|
45
|
+
return_type: :any,
|
46
|
+
description: "Get element at index from array"
|
47
|
+
)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,31 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
4
|
+
module Core
|
5
|
+
# Registry for functions that can be used in Kumi schemas
|
6
|
+
# This is the public interface for registering custom functions
|
7
|
+
module FunctionRegistry
|
8
|
+
# Re-export the Entry struct from FunctionBuilder for compatibility
|
9
|
+
Entry = FunctionBuilder::Entry
|
10
|
+
|
11
|
+
# Core operators that are always available
|
12
|
+
CORE_OPERATORS = %i[== > < >= <= != between?].freeze
|
13
|
+
|
14
|
+
# Build the complete function registry by combining all categories
|
15
|
+
CORE_FUNCTIONS = {}.tap do |registry|
|
16
|
+
registry.merge!(ComparisonFunctions.definitions)
|
17
|
+
registry.merge!(MathFunctions.definitions)
|
18
|
+
registry.merge!(StringFunctions.definitions)
|
19
|
+
registry.merge!(LogicalFunctions.definitions)
|
20
|
+
registry.merge!(CollectionFunctions.definitions)
|
21
|
+
registry.merge!(ConditionalFunctions.definitions)
|
22
|
+
registry.merge!(TypeFunctions.definitions)
|
23
|
+
end.freeze
|
24
|
+
|
25
|
+
@functions = CORE_FUNCTIONS.dup
|
26
|
+
@frozen = false
|
27
|
+
|
28
|
+
# class << self
|
29
29
|
# Public interface for registering custom functions
|
30
30
|
def register(name, &block)
|
31
31
|
raise ArgumentError, "Function #{name.inspect} already registered" if @functions.key?(name)
|
@@ -75,11 +75,11 @@ module Kumi
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def fetch(name)
|
78
|
-
@functions.fetch(name) { raise UnknownFunction, "Unknown function: #{name}" }.fn
|
78
|
+
@functions.fetch(name) { raise Kumi::Errors::UnknownFunction, "Unknown function: #{name}" }.fn
|
79
79
|
end
|
80
80
|
|
81
81
|
def signature(name)
|
82
|
-
entry = @functions.fetch(name) { raise UnknownFunction, "Unknown function: #{name}" }
|
82
|
+
entry = @functions.fetch(name) { raise Kumi::Errors::UnknownFunction, "Unknown function: #{name}" }
|
83
83
|
{
|
84
84
|
arity: entry.arity,
|
85
85
|
param_types: entry.param_types,
|
@@ -130,15 +130,7 @@ module Kumi
|
|
130
130
|
def type_operations
|
131
131
|
TypeFunctions.definitions.keys
|
132
132
|
end
|
133
|
-
|
134
|
-
# Development helpers
|
135
|
-
def reset!
|
136
|
-
@functions = CORE_OPERATIONS.dup
|
137
|
-
end
|
138
|
-
|
139
|
-
def freeze!
|
140
|
-
@functions.freeze
|
141
|
-
end
|
142
133
|
end
|
143
134
|
end
|
144
135
|
end
|
136
|
+
# end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Input
|
6
|
+
class TypeMatcher
|
7
|
+
def self.matches?(value, declared_type)
|
8
|
+
case declared_type
|
9
|
+
when :integer
|
10
|
+
value.is_a?(Integer)
|
11
|
+
when :float
|
12
|
+
value.is_a?(Float) || value.is_a?(Integer) # Allow integer for float
|
13
|
+
when :string
|
14
|
+
value.is_a?(String)
|
15
|
+
when :boolean
|
16
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
17
|
+
when :symbol
|
18
|
+
value.is_a?(Symbol)
|
19
|
+
when :array
|
20
|
+
# Simple :array type - just check if it's an Array
|
21
|
+
value.is_a?(Array)
|
22
|
+
when :any
|
23
|
+
true
|
24
|
+
else
|
25
|
+
# Handle complex types (arrays, hashes)
|
26
|
+
handle_complex_type(value, declared_type)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.infer_type(value)
|
31
|
+
case value
|
32
|
+
when Integer then :integer
|
33
|
+
when Float then :float
|
34
|
+
when String then :string
|
35
|
+
when TrueClass, FalseClass then :boolean
|
36
|
+
when Symbol then :symbol
|
37
|
+
when Array then { array: :mixed }
|
38
|
+
when Hash then { hash: %i[mixed mixed] }
|
39
|
+
else :unknown
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.format_type(type)
|
44
|
+
case type
|
45
|
+
when Symbol
|
46
|
+
type.to_s
|
47
|
+
when Hash
|
48
|
+
format_complex_type(type)
|
49
|
+
else
|
50
|
+
type.inspect
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private_class_method def self.handle_complex_type(value, declared_type)
|
55
|
+
return false unless declared_type.is_a?(Hash)
|
56
|
+
|
57
|
+
if declared_type.key?(:array)
|
58
|
+
handle_array_type(value, declared_type[:array])
|
59
|
+
elsif declared_type.key?(:hash)
|
60
|
+
handle_hash_type(value, declared_type[:hash])
|
61
|
+
else
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private_class_method def self.handle_array_type(value, element_type)
|
67
|
+
return false unless value.is_a?(Array)
|
68
|
+
return true if element_type == :any
|
69
|
+
|
70
|
+
value.all? { |elem| matches?(elem, element_type) }
|
71
|
+
end
|
72
|
+
|
73
|
+
private_class_method def self.handle_hash_type(value, hash_spec)
|
74
|
+
return false unless value.is_a?(Hash)
|
75
|
+
|
76
|
+
key_type, value_type = hash_spec
|
77
|
+
return true if key_type == :any && value_type == :any
|
78
|
+
|
79
|
+
value.all? do |k, v|
|
80
|
+
matches?(k, key_type) && matches?(v, value_type)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private_class_method def self.format_complex_type(type)
|
85
|
+
if type.key?(:array)
|
86
|
+
"array(#{format_type(type[:array])})"
|
87
|
+
elsif type.key?(:hash)
|
88
|
+
key_type, value_type = type[:hash]
|
89
|
+
"hash(#{format_type(key_type)}, #{format_type(value_type)})"
|
90
|
+
else
|
91
|
+
type.inspect
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Input
|
6
|
+
class Validator
|
7
|
+
def self.validate_context(context, input_meta)
|
8
|
+
violations = []
|
9
|
+
|
10
|
+
context.each do |field, value|
|
11
|
+
meta = input_meta[field]
|
12
|
+
next unless meta
|
13
|
+
|
14
|
+
# Type validation first
|
15
|
+
if should_validate_type?(meta) && !TypeMatcher.matches?(value, meta[:type])
|
16
|
+
violations << ViolationCreator.create_type_violation(field, value, meta[:type])
|
17
|
+
next # Skip domain validation if type is wrong
|
18
|
+
end
|
19
|
+
|
20
|
+
# Domain validation second (only if type is correct)
|
21
|
+
if should_validate_domain?(meta) && !Domain::Validator.validate_field(field, value, meta[:domain])
|
22
|
+
violations << ViolationCreator.create_domain_violation(field, value, meta[:domain])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
violations
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.type_matches?(value, declared_type)
|
30
|
+
TypeMatcher.matches?(value, declared_type)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.infer_type(value)
|
34
|
+
TypeMatcher.infer_type(value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.format_type(type)
|
38
|
+
TypeMatcher.format_type(type)
|
39
|
+
end
|
40
|
+
|
41
|
+
private_class_method def self.should_validate_type?(meta)
|
42
|
+
meta[:type] && meta[:type] != :any
|
43
|
+
end
|
44
|
+
|
45
|
+
private_class_method def self.should_validate_domain?(meta)
|
46
|
+
meta[:domain]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Input
|
6
|
+
class ViolationCreator
|
7
|
+
def self.create_type_violation(field, value, expected_type)
|
8
|
+
{
|
9
|
+
type: :type_violation,
|
10
|
+
field: field,
|
11
|
+
value: value,
|
12
|
+
expected_type: expected_type,
|
13
|
+
actual_type: TypeMatcher.infer_type(value),
|
14
|
+
message: format_type_violation_message(field, value, expected_type)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create_domain_violation(field, value, domain)
|
19
|
+
{
|
20
|
+
type: :domain_violation,
|
21
|
+
field: field,
|
22
|
+
value: value,
|
23
|
+
domain: domain,
|
24
|
+
message: Kumi::Core::Domain::ViolationFormatter.format_message(field, value, domain)
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.create_missing_field_violation(field, expected_type)
|
29
|
+
{
|
30
|
+
type: :missing_field_violation,
|
31
|
+
field: field,
|
32
|
+
expected_type: expected_type,
|
33
|
+
message: format_missing_field_message(field, expected_type)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
private_class_method def self.format_type_violation_message(field, value, expected_type)
|
38
|
+
actual_type = TypeMatcher.infer_type(value)
|
39
|
+
expected_formatted = TypeMatcher.format_type(expected_type)
|
40
|
+
actual_formatted = TypeMatcher.format_type(actual_type)
|
41
|
+
|
42
|
+
"Field :#{field} expected #{expected_formatted}, got #{value.inspect} of type #{actual_formatted}"
|
43
|
+
end
|
44
|
+
|
45
|
+
private_class_method def self.format_missing_field_message(field, expected_type)
|
46
|
+
expected_formatted = TypeMatcher.format_type(expected_type)
|
47
|
+
"Missing required field :#{field} of type #{expected_formatted}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Kumi
|
6
|
+
module Core
|
7
|
+
module JsonSchema
|
8
|
+
# Converts Kumi schema metadata to JSON Schema format
|
9
|
+
class Generator
|
10
|
+
def initialize(schema_metadata)
|
11
|
+
@metadata = schema_metadata
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate
|
15
|
+
{
|
16
|
+
type: "object",
|
17
|
+
properties: build_properties,
|
18
|
+
required: extract_required_fields,
|
19
|
+
"x-kumi-values": @metadata.values,
|
20
|
+
"x-kumi-traits": @metadata.traits
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_properties
|
27
|
+
@metadata.inputs.transform_values { |spec| convert_input_to_json_schema(spec) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_required_fields
|
31
|
+
@metadata.inputs.select { |_k, v| v[:required] }.keys
|
32
|
+
end
|
33
|
+
|
34
|
+
def convert_input_to_json_schema(input_spec)
|
35
|
+
base = { type: map_kumi_type_to_json_schema(input_spec[:type]) }
|
36
|
+
|
37
|
+
domain = input_spec[:domain]
|
38
|
+
return base unless domain
|
39
|
+
|
40
|
+
case domain[:type]
|
41
|
+
when :range
|
42
|
+
base[:minimum] = domain[:min]
|
43
|
+
base[:maximum] = domain[:max]
|
44
|
+
when :enum
|
45
|
+
base[:enum] = domain[:values]
|
46
|
+
end
|
47
|
+
|
48
|
+
base
|
49
|
+
end
|
50
|
+
|
51
|
+
def map_kumi_type_to_json_schema(kumi_type)
|
52
|
+
case kumi_type
|
53
|
+
when :string then "string"
|
54
|
+
when :integer then "integer"
|
55
|
+
when :float then "number"
|
56
|
+
when :boolean then "boolean"
|
57
|
+
when :array then "array"
|
58
|
+
when :hash then "object"
|
59
|
+
else "string"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module JsonSchema
|
6
|
+
# Validates data against JSON Schema (placeholder for future implementation)
|
7
|
+
class Validator
|
8
|
+
def initialize(json_schema)
|
9
|
+
@schema = json_schema
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate(_data)
|
13
|
+
# Placeholder implementation
|
14
|
+
# In a real implementation, this would validate data against the JSON Schema
|
15
|
+
{
|
16
|
+
valid: true,
|
17
|
+
errors: []
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.validate(json_schema, data)
|
22
|
+
new(json_schema).validate(data)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "json_schema/generator"
|
4
|
+
require_relative "json_schema/validator"
|
5
|
+
|
6
|
+
module Kumi
|
7
|
+
module Core
|
8
|
+
module JsonSchema
|
9
|
+
# Entry point for the JsonSchema functionality
|
10
|
+
#
|
11
|
+
# Available components:
|
12
|
+
# - Generator: Converts Kumi metadata to JSON Schema
|
13
|
+
# - Validator: Validates data against JSON Schema (placeholder)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module RubyParser
|
6
|
+
class BuildContext
|
7
|
+
attr_reader :inputs, :attributes, :traits
|
8
|
+
attr_accessor :current_location
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@inputs = []
|
12
|
+
@attributes = []
|
13
|
+
@traits = []
|
14
|
+
@input_block_defined = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def input_block_defined?
|
18
|
+
@input_block_defined
|
19
|
+
end
|
20
|
+
|
21
|
+
def mark_input_block_defined!
|
22
|
+
@input_block_defined = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module RubyParser
|
6
|
+
# DSL proxy for declaration references (traits and values)
|
7
|
+
# Handles references to declared items and field access on them
|
8
|
+
class DeclarationReferenceProxy
|
9
|
+
include Syntax
|
10
|
+
|
11
|
+
# Use shared operator methods
|
12
|
+
extend Sugar::ProxyRefinement
|
13
|
+
|
14
|
+
def initialize(name, context)
|
15
|
+
@name = name
|
16
|
+
@context = context
|
17
|
+
end
|
18
|
+
|
19
|
+
# Convert to DeclarationReference AST node
|
20
|
+
def to_ast_node
|
21
|
+
Kumi::Syntax::DeclarationReference.new(@name, loc: @context.current_location)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def method_missing(method_name, *args, &block)
|
27
|
+
# All operators are handled by ProxyRefinement methods
|
28
|
+
# Field access should use input.field.subfield syntax, not bare identifiers
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|