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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +33 -176
  3. data/README.md +33 -2
  4. data/docs/SYNTAX.md +2 -7
  5. data/docs/features/array-broadcasting.md +1 -1
  6. data/docs/schema_metadata/broadcasts.md +53 -0
  7. data/docs/schema_metadata/cascades.md +45 -0
  8. data/docs/schema_metadata/declarations.md +54 -0
  9. data/docs/schema_metadata/dependencies.md +57 -0
  10. data/docs/schema_metadata/evaluation_order.md +29 -0
  11. data/docs/schema_metadata/examples.md +95 -0
  12. data/docs/schema_metadata/inferred_types.md +46 -0
  13. data/docs/schema_metadata/inputs.md +86 -0
  14. data/docs/schema_metadata.md +108 -0
  15. data/lib/kumi/analyzer/passes/broadcast_detector.rb +52 -57
  16. data/lib/kumi/analyzer/passes/dependency_resolver.rb +8 -8
  17. data/lib/kumi/analyzer/passes/input_collector.rb +2 -2
  18. data/lib/kumi/analyzer/passes/name_indexer.rb +2 -2
  19. data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +15 -16
  20. data/lib/kumi/analyzer/passes/toposorter.rb +23 -23
  21. data/lib/kumi/analyzer/passes/type_checker.rb +7 -9
  22. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +2 -2
  23. data/lib/kumi/analyzer/passes/type_inferencer.rb +24 -24
  24. data/lib/kumi/analyzer/passes/unsat_detector.rb +11 -13
  25. data/lib/kumi/analyzer.rb +5 -5
  26. data/lib/kumi/compiler.rb +39 -45
  27. data/lib/kumi/error_reporting.rb +1 -1
  28. data/lib/kumi/explain.rb +12 -0
  29. data/lib/kumi/export/node_registry.rb +2 -2
  30. data/lib/kumi/json_schema/generator.rb +63 -0
  31. data/lib/kumi/json_schema/validator.rb +25 -0
  32. data/lib/kumi/json_schema.rb +14 -0
  33. data/lib/kumi/{parser → ruby_parser}/build_context.rb +1 -1
  34. data/lib/kumi/{parser → ruby_parser}/declaration_reference_proxy.rb +3 -3
  35. data/lib/kumi/{parser → ruby_parser}/dsl.rb +1 -1
  36. data/lib/kumi/{parser → ruby_parser}/dsl_cascade_builder.rb +2 -2
  37. data/lib/kumi/{parser → ruby_parser}/expression_converter.rb +14 -14
  38. data/lib/kumi/{parser → ruby_parser}/guard_rails.rb +1 -1
  39. data/lib/kumi/{parser → ruby_parser}/input_builder.rb +1 -1
  40. data/lib/kumi/{parser → ruby_parser}/input_field_proxy.rb +4 -4
  41. data/lib/kumi/{parser → ruby_parser}/input_proxy.rb +1 -1
  42. data/lib/kumi/{parser → ruby_parser}/nested_input.rb +1 -1
  43. data/lib/kumi/{parser → ruby_parser}/parser.rb +11 -10
  44. data/lib/kumi/{parser → ruby_parser}/schema_builder.rb +1 -1
  45. data/lib/kumi/{parser → ruby_parser}/sugar.rb +1 -1
  46. data/lib/kumi/ruby_parser.rb +10 -0
  47. data/lib/kumi/schema.rb +10 -4
  48. data/lib/kumi/schema_instance.rb +6 -6
  49. data/lib/kumi/schema_metadata.rb +524 -0
  50. data/lib/kumi/vectorization_metadata.rb +4 -4
  51. data/lib/kumi/version.rb +1 -1
  52. data/lib/kumi.rb +14 -0
  53. metadata +28 -15
  54. 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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
4
+ module RubyParser
5
5
  class BuildContext
6
6
  attr_reader :inputs, :attributes, :traits
7
7
  attr_accessor :current_location
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
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 Parser
4
+ module RubyParser
5
5
  module Dsl
6
6
  def self.build_syntax_tree(&rule_block)
7
7
  parser = Parser.new
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
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 # Already a binding from method_missing
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 Parser
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
- 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
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
- 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
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 Parser
4
+ module RubyParser
5
5
  module GuardRails
6
6
  RESERVED = %i[input trait value fn lit ref].freeze
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
4
+ module RubyParser
5
5
  class InputBuilder
6
6
  include Syntax
7
7
  include ErrorReporting
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
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) # Ensure it's always an array
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,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
4
+ module RubyParser
5
5
  # Proxy object for input field references (input.field_name)
6
6
  class InputProxy
7
7
  include Syntax
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
4
+ module RubyParser
5
5
  # Simple context struct for nested input collection
6
6
  class NestedInput
7
7
  attr_reader :inputs, :current_location
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
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::Parser::Sugar::ExpressionRefinement")
37
- rule_block.binding.eval("using Kumi::Parser::Sugar::NumericRefinement")
38
- rule_block.binding.eval("using Kumi::Parser::Sugar::StringRefinement")
39
- rule_block.binding.eval("using Kumi::Parser::Sugar::ArrayRefinement")
40
- rule_block.binding.eval("using Kumi::Parser::Sugar::ModuleRefinement")
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 Parser
4
+ module RubyParser
5
5
  class SchemaBuilder
6
6
  include GuardRails
7
7
  include Syntax
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- module Parser
4
+ module RubyParser
5
5
  module Sugar
6
6
  include Syntax
7
7
 
@@ -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[:input_meta] || {}
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[:input_meta] || {}
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::Parser::Dsl.build_syntax_tree(&block).freeze
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
@@ -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, :analysis, :context
14
+ attr_reader :compiled_schema, :metadata, :context
15
15
 
16
- def initialize(compiled_schema, analysis, context)
16
+ def initialize(compiled_schema, metadata, context)
17
17
  @compiled_schema = compiled_schema # Kumi::CompiledSchema
18
- @analysis = analysis # Analyzer result (for deps)
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 = @analysis&.state&.dig(: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 = @analysis&.state&.dig(: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 = @analysis&.state&.dig(: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