kumi 0.0.6 → 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 +33 -176
- data/README.md +33 -2
- 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/lib/kumi/analyzer/passes/broadcast_detector.rb +52 -57
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +8 -8
- data/lib/kumi/analyzer/passes/input_collector.rb +2 -2
- data/lib/kumi/analyzer/passes/name_indexer.rb +2 -2
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +15 -16
- data/lib/kumi/analyzer/passes/toposorter.rb +23 -23
- data/lib/kumi/analyzer/passes/type_checker.rb +7 -9
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +2 -2
- data/lib/kumi/analyzer/passes/type_inferencer.rb +24 -24
- data/lib/kumi/analyzer/passes/unsat_detector.rb +11 -13
- data/lib/kumi/analyzer.rb +5 -5
- data/lib/kumi/compiler.rb +39 -45
- data/lib/kumi/error_reporting.rb +1 -1
- data/lib/kumi/explain.rb +12 -0
- data/lib/kumi/export/node_registry.rb +2 -2
- 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/{parser → ruby_parser}/declaration_reference_proxy.rb +3 -3
- data/lib/kumi/{parser → ruby_parser}/dsl.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/dsl_cascade_builder.rb +2 -2
- data/lib/kumi/{parser → ruby_parser}/expression_converter.rb +14 -14
- data/lib/kumi/{parser → ruby_parser}/guard_rails.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/input_builder.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/input_field_proxy.rb +4 -4
- data/lib/kumi/{parser → ruby_parser}/input_proxy.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/nested_input.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/parser.rb +11 -10
- data/lib/kumi/{parser → ruby_parser}/schema_builder.rb +1 -1
- data/lib/kumi/{parser → ruby_parser}/sugar.rb +1 -1
- 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/vectorization_metadata.rb +4 -4
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +14 -0
- metadata +28 -15
- data/lib/generators/trait_engine/templates/schema_spec.rb.erb +0 -27
@@ -7,7 +7,7 @@ module Kumi
|
|
7
7
|
SERIALIZATION_MAP = {
|
8
8
|
"Kumi::Syntax::Root" => "root",
|
9
9
|
"Kumi::Syntax::InputDeclaration" => "field_declaration",
|
10
|
-
"Kumi::Syntax::ValueDeclaration" => "attribute_declaration",
|
10
|
+
"Kumi::Syntax::ValueDeclaration" => "attribute_declaration",
|
11
11
|
"Kumi::Syntax::TraitDeclaration" => "trait_declaration",
|
12
12
|
"Kumi::Syntax::CallExpression" => "call_expression",
|
13
13
|
"Kumi::Syntax::ArrayExpression" => "list_expression",
|
@@ -23,7 +23,7 @@ module Kumi
|
|
23
23
|
DESERIALIZATION_MAP = {
|
24
24
|
"root" => "Kumi::Syntax::Root",
|
25
25
|
"field_declaration" => "Kumi::Syntax::InputDeclaration",
|
26
|
-
"attribute_declaration" => "Kumi::Syntax::ValueDeclaration",
|
26
|
+
"attribute_declaration" => "Kumi::Syntax::ValueDeclaration",
|
27
27
|
"trait_declaration" => "Kumi::Syntax::TraitDeclaration",
|
28
28
|
"call_expression" => "Kumi::Syntax::CallExpression",
|
29
29
|
"list_expression" => "Kumi::Syntax::ArrayExpression",
|
@@ -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
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
5
|
# DSL proxy for declaration references (traits and values)
|
6
6
|
# Handles references to declared items and field access on them
|
7
7
|
class DeclarationReferenceProxy
|
8
8
|
include Syntax
|
9
|
-
|
9
|
+
|
10
10
|
# Use shared operator methods
|
11
11
|
extend Sugar::ProxyRefinement
|
12
12
|
|
@@ -33,4 +33,4 @@ module Kumi
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
-
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
|
|
@@ -89,7 +89,7 @@ module Kumi
|
|
89
89
|
when Symbol
|
90
90
|
create_binding(name, location)
|
91
91
|
when DeclarationReference
|
92
|
-
name
|
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
|
@@ -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
|
@@ -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,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Kumi
|
4
|
-
module
|
4
|
+
module RubyParser
|
5
5
|
# Proxy for input field access that can handle arbitrary depth nesting
|
6
6
|
# Handles input.field.subfield.subsubfield... syntax by building up path arrays
|
7
7
|
class InputFieldProxy
|
8
8
|
include Syntax
|
9
|
-
|
9
|
+
|
10
10
|
# Use shared operator methods instead of refinements
|
11
11
|
extend Sugar::ProxyRefinement
|
12
12
|
|
13
13
|
def initialize(path, context)
|
14
|
-
@path = Array(path)
|
14
|
+
@path = Array(path) # Ensure it's always an array
|
15
15
|
@context = context
|
16
16
|
end
|
17
17
|
|
@@ -43,4 +43,4 @@ module Kumi
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
46
|
-
end
|
46
|
+
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
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
# Ruby DSL parser for Kumi schemas
|
5
|
+
# Converts Ruby block syntax into AST nodes
|
6
|
+
module RubyParser
|
7
|
+
# This module contains all Ruby DSL parsing functionality
|
8
|
+
# The main entry point is through Dsl.build_syntax_tree
|
9
|
+
end
|
10
|
+
end
|
data/lib/kumi/schema.rb
CHANGED
@@ -16,19 +16,19 @@ module Kumi
|
|
16
16
|
raise("No schema defined") unless @__compiled_schema__
|
17
17
|
|
18
18
|
# Validate input types and domain constraints
|
19
|
-
input_meta = @__analyzer_result__.state[:
|
19
|
+
input_meta = @__analyzer_result__.state[:inputs] || {}
|
20
20
|
violations = Input::Validator.validate_context(context, input_meta)
|
21
21
|
|
22
22
|
raise Errors::InputValidationError, violations unless violations.empty?
|
23
23
|
|
24
|
-
SchemaInstance.new(@__compiled_schema__, @__analyzer_result__, context)
|
24
|
+
SchemaInstance.new(@__compiled_schema__, @__analyzer_result__.state, context)
|
25
25
|
end
|
26
26
|
|
27
27
|
def explain(context, *keys)
|
28
28
|
raise("No schema defined") unless @__compiled_schema__
|
29
29
|
|
30
30
|
# Validate input types and domain constraints
|
31
|
-
input_meta = @__analyzer_result__.state[:
|
31
|
+
input_meta = @__analyzer_result__.state[:inputs] || {}
|
32
32
|
violations = Input::Validator.validate_context(context, input_meta)
|
33
33
|
|
34
34
|
raise Errors::InputValidationError, violations unless violations.empty?
|
@@ -41,11 +41,17 @@ module Kumi
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def schema(&block)
|
44
|
-
@__syntax_tree__ = Kumi::
|
44
|
+
@__syntax_tree__ = Kumi::RubyParser::Dsl.build_syntax_tree(&block).freeze
|
45
45
|
@__analyzer_result__ = Analyzer.analyze!(@__syntax_tree__).freeze
|
46
46
|
@__compiled_schema__ = Compiler.compile(@__syntax_tree__, analyzer: @__analyzer_result__).freeze
|
47
47
|
|
48
48
|
Inspector.new(@__syntax_tree__, @__analyzer_result__, @__compiled_schema__)
|
49
49
|
end
|
50
|
+
|
51
|
+
def schema_metadata
|
52
|
+
raise("No schema defined") unless @__analyzer_result__
|
53
|
+
|
54
|
+
@schema_metadata ||= SchemaMetadata.new(@__analyzer_result__.state, @__syntax_tree__)
|
55
|
+
end
|
50
56
|
end
|
51
57
|
end
|
data/lib/kumi/schema_instance.rb
CHANGED
@@ -11,11 +11,11 @@ module Kumi
|
|
11
11
|
# instance.input # original context (read‑only)
|
12
12
|
|
13
13
|
class SchemaInstance
|
14
|
-
attr_reader :compiled_schema, :
|
14
|
+
attr_reader :compiled_schema, :metadata, :context
|
15
15
|
|
16
|
-
def initialize(compiled_schema,
|
16
|
+
def initialize(compiled_schema, metadata, context)
|
17
17
|
@compiled_schema = compiled_schema # Kumi::CompiledSchema
|
18
|
-
@
|
18
|
+
@metadata = metadata # Frozen state hash
|
19
19
|
@context = context.is_a?(EvaluationWrapper) ? context : EvaluationWrapper.new(context)
|
20
20
|
end
|
21
21
|
|
@@ -68,12 +68,12 @@ module Kumi
|
|
68
68
|
|
69
69
|
def input_field_exists?(field)
|
70
70
|
# Check if field is declared in input block
|
71
|
-
input_meta = @
|
71
|
+
input_meta = @metadata[:inputs] || {}
|
72
72
|
input_meta.key?(field) || @context.key?(field)
|
73
73
|
end
|
74
74
|
|
75
75
|
def validate_domain_constraint(field, value)
|
76
|
-
input_meta = @
|
76
|
+
input_meta = @metadata[:inputs] || {}
|
77
77
|
field_meta = input_meta[field]
|
78
78
|
return unless field_meta&.dig(:domain)
|
79
79
|
|
@@ -99,7 +99,7 @@ module Kumi
|
|
99
99
|
|
100
100
|
def find_dependent_declarations_optimized(field)
|
101
101
|
# Use precomputed transitive closure for true O(1) lookup!
|
102
|
-
transitive_dependents = @
|
102
|
+
transitive_dependents = @metadata[:dependents]
|
103
103
|
return [] unless transitive_dependents
|
104
104
|
|
105
105
|
# This is truly O(1) - just array lookup, no traversal needed
|