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.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +1 -1
  3. data/README.md +8 -5
  4. data/examples/game_of_life.rb +1 -1
  5. data/examples/static_analysis_errors.rb +7 -7
  6. data/lib/kumi/analyzer.rb +15 -15
  7. data/lib/kumi/compiler.rb +6 -6
  8. data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
  9. data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
  10. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
  11. data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
  12. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
  13. data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
  14. data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
  15. data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
  16. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
  17. data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
  18. data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
  19. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
  20. data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
  21. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
  22. data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
  23. data/lib/kumi/core/atom_unsat_solver.rb +396 -0
  24. data/lib/kumi/core/compiled_schema.rb +43 -0
  25. data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
  26. data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
  27. data/lib/kumi/core/domain/range_analyzer.rb +85 -0
  28. data/lib/kumi/core/domain/validator.rb +82 -0
  29. data/lib/kumi/core/domain/violation_formatter.rb +42 -0
  30. data/lib/kumi/core/error_reporter.rb +166 -0
  31. data/lib/kumi/core/error_reporting.rb +97 -0
  32. data/lib/kumi/core/errors.rb +120 -0
  33. data/lib/kumi/core/evaluation_wrapper.rb +40 -0
  34. data/lib/kumi/core/explain.rb +295 -0
  35. data/lib/kumi/core/export/deserializer.rb +41 -0
  36. data/lib/kumi/core/export/errors.rb +14 -0
  37. data/lib/kumi/core/export/node_builders.rb +142 -0
  38. data/lib/kumi/core/export/node_registry.rb +54 -0
  39. data/lib/kumi/core/export/node_serializers.rb +158 -0
  40. data/lib/kumi/core/export/serializer.rb +25 -0
  41. data/lib/kumi/core/export.rb +35 -0
  42. data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
  43. data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
  44. data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
  45. data/lib/kumi/core/function_registry/function_builder.rb +95 -0
  46. data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
  47. data/lib/kumi/core/function_registry/math_functions.rb +74 -0
  48. data/lib/kumi/core/function_registry/string_functions.rb +57 -0
  49. data/lib/kumi/core/function_registry/type_functions.rb +53 -0
  50. data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
  51. data/lib/kumi/core/input/type_matcher.rb +97 -0
  52. data/lib/kumi/core/input/validator.rb +51 -0
  53. data/lib/kumi/core/input/violation_creator.rb +52 -0
  54. data/lib/kumi/core/json_schema/generator.rb +65 -0
  55. data/lib/kumi/core/json_schema/validator.rb +27 -0
  56. data/lib/kumi/core/json_schema.rb +16 -0
  57. data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
  58. data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
  59. data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
  60. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
  61. data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
  62. data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
  63. data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
  64. data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
  65. data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
  66. data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
  67. data/lib/kumi/core/ruby_parser/parser.rb +71 -0
  68. data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
  69. data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
  70. data/lib/kumi/core/ruby_parser.rb +12 -0
  71. data/lib/kumi/core/schema_instance.rb +111 -0
  72. data/lib/kumi/core/types/builder.rb +23 -0
  73. data/lib/kumi/core/types/compatibility.rb +96 -0
  74. data/lib/kumi/core/types/formatter.rb +26 -0
  75. data/lib/kumi/core/types/inference.rb +42 -0
  76. data/lib/kumi/core/types/normalizer.rb +72 -0
  77. data/lib/kumi/core/types/validator.rb +37 -0
  78. data/lib/kumi/core/types.rb +66 -0
  79. data/lib/kumi/core/vectorization_metadata.rb +110 -0
  80. data/lib/kumi/errors.rb +1 -112
  81. data/lib/kumi/registry.rb +37 -0
  82. data/lib/kumi/schema.rb +5 -5
  83. data/lib/kumi/schema_metadata.rb +3 -3
  84. data/lib/kumi/syntax/array_expression.rb +6 -6
  85. data/lib/kumi/syntax/call_expression.rb +4 -4
  86. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  87. data/lib/kumi/syntax/case_expression.rb +4 -4
  88. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  89. data/lib/kumi/syntax/hash_expression.rb +4 -4
  90. data/lib/kumi/syntax/input_declaration.rb +5 -5
  91. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  92. data/lib/kumi/syntax/input_reference.rb +5 -5
  93. data/lib/kumi/syntax/literal.rb +4 -4
  94. data/lib/kumi/syntax/node.rb +34 -34
  95. data/lib/kumi/syntax/root.rb +6 -6
  96. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  97. data/lib/kumi/syntax/value_declaration.rb +4 -4
  98. data/lib/kumi/version.rb +1 -1
  99. data/migrate_to_core_iterative.rb +938 -0
  100. data/scripts/generate_function_docs.rb +9 -9
  101. metadata +75 -72
  102. data/lib/kumi/analyzer/analysis_state.rb +0 -37
  103. data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
  104. data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -246
  105. data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
  106. data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
  107. data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
  108. data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
  109. data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
  110. data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -109
  111. data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
  112. data/lib/kumi/analyzer/passes/type_checker.rb +0 -160
  113. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
  114. data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
  115. data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -404
  116. data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
  117. data/lib/kumi/atom_unsat_solver.rb +0 -394
  118. data/lib/kumi/compiled_schema.rb +0 -41
  119. data/lib/kumi/constraint_relationship_solver.rb +0 -638
  120. data/lib/kumi/domain/enum_analyzer.rb +0 -53
  121. data/lib/kumi/domain/range_analyzer.rb +0 -83
  122. data/lib/kumi/domain/validator.rb +0 -80
  123. data/lib/kumi/domain/violation_formatter.rb +0 -40
  124. data/lib/kumi/error_reporter.rb +0 -164
  125. data/lib/kumi/error_reporting.rb +0 -95
  126. data/lib/kumi/evaluation_wrapper.rb +0 -38
  127. data/lib/kumi/explain.rb +0 -293
  128. data/lib/kumi/export/deserializer.rb +0 -39
  129. data/lib/kumi/export/errors.rb +0 -12
  130. data/lib/kumi/export/node_builders.rb +0 -140
  131. data/lib/kumi/export/node_registry.rb +0 -52
  132. data/lib/kumi/export/node_serializers.rb +0 -156
  133. data/lib/kumi/export/serializer.rb +0 -23
  134. data/lib/kumi/export.rb +0 -33
  135. data/lib/kumi/function_registry/collection_functions.rb +0 -200
  136. data/lib/kumi/function_registry/comparison_functions.rb +0 -31
  137. data/lib/kumi/function_registry/conditional_functions.rb +0 -36
  138. data/lib/kumi/function_registry/function_builder.rb +0 -93
  139. data/lib/kumi/function_registry/logical_functions.rb +0 -42
  140. data/lib/kumi/function_registry/math_functions.rb +0 -72
  141. data/lib/kumi/function_registry/string_functions.rb +0 -54
  142. data/lib/kumi/function_registry/type_functions.rb +0 -51
  143. data/lib/kumi/input/type_matcher.rb +0 -95
  144. data/lib/kumi/input/validator.rb +0 -49
  145. data/lib/kumi/input/violation_creator.rb +0 -50
  146. data/lib/kumi/json_schema/generator.rb +0 -63
  147. data/lib/kumi/json_schema/validator.rb +0 -25
  148. data/lib/kumi/json_schema.rb +0 -14
  149. data/lib/kumi/ruby_parser/build_context.rb +0 -25
  150. data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +0 -36
  151. data/lib/kumi/ruby_parser/dsl.rb +0 -12
  152. data/lib/kumi/ruby_parser/dsl_cascade_builder.rb +0 -136
  153. data/lib/kumi/ruby_parser/expression_converter.rb +0 -126
  154. data/lib/kumi/ruby_parser/guard_rails.rb +0 -43
  155. data/lib/kumi/ruby_parser/input_builder.rb +0 -125
  156. data/lib/kumi/ruby_parser/input_field_proxy.rb +0 -46
  157. data/lib/kumi/ruby_parser/input_proxy.rb +0 -29
  158. data/lib/kumi/ruby_parser/nested_input.rb +0 -15
  159. data/lib/kumi/ruby_parser/parser.rb +0 -69
  160. data/lib/kumi/ruby_parser/schema_builder.rb +0 -173
  161. data/lib/kumi/ruby_parser/sugar.rb +0 -261
  162. data/lib/kumi/ruby_parser.rb +0 -10
  163. data/lib/kumi/schema_instance.rb +0 -109
  164. data/lib/kumi/types/builder.rb +0 -21
  165. data/lib/kumi/types/compatibility.rb +0 -94
  166. data/lib/kumi/types/formatter.rb +0 -24
  167. data/lib/kumi/types/inference.rb +0 -40
  168. data/lib/kumi/types/normalizer.rb +0 -70
  169. data/lib/kumi/types/validator.rb +0 -35
  170. data/lib/kumi/types.rb +0 -64
  171. data/lib/kumi/vectorization_metadata.rb +0 -108
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Domain
6
+ class RangeAnalyzer
7
+ def self.analyze(range)
8
+ {
9
+ type: :range,
10
+ min: range.begin,
11
+ max: range.end,
12
+ exclusive_end: range.exclude_end?,
13
+ size: calculate_size(range),
14
+ sample_values: generate_samples(range),
15
+ boundary_values: [range.begin, range.end],
16
+ invalid_samples: generate_invalid_samples(range)
17
+ }
18
+ end
19
+
20
+ def self.calculate_size(range)
21
+ return :infinite if range.begin.nil? || range.end.nil?
22
+ return :large if range.end - range.begin > 1000
23
+
24
+ if integer_range?(range)
25
+ range.exclude_end? ? range.end - range.begin : range.end - range.begin + 1
26
+ else
27
+ :continuous
28
+ end
29
+ end
30
+
31
+ def self.generate_samples(range)
32
+ samples = [range.begin]
33
+
34
+ samples << calculate_midpoint(range) if numeric_range?(range)
35
+
36
+ samples << calculate_endpoint(range)
37
+ samples.uniq
38
+ end
39
+
40
+ def self.generate_invalid_samples(range)
41
+ invalid = []
42
+
43
+ invalid << calculate_before_start(range) if range.begin.is_a?(Numeric)
44
+
45
+ invalid << calculate_after_end(range) if range.end.is_a?(Numeric)
46
+
47
+ invalid
48
+ end
49
+
50
+ private_class_method def self.integer_range?(range)
51
+ range.begin.is_a?(Integer) && range.end.is_a?(Integer)
52
+ end
53
+
54
+ private_class_method def self.numeric_range?(range)
55
+ range.begin.is_a?(Numeric) && range.end.is_a?(Numeric)
56
+ end
57
+
58
+ private_class_method def self.calculate_midpoint(range)
59
+ mid = (range.begin + range.end) / 2.0
60
+ range.begin.is_a?(Integer) ? mid.round : mid
61
+ end
62
+
63
+ private_class_method def self.calculate_endpoint(range)
64
+ if range.exclude_end?
65
+ range.end - (range.begin.is_a?(Integer) ? 1 : 0.1)
66
+ else
67
+ range.end
68
+ end
69
+ end
70
+
71
+ private_class_method def self.calculate_before_start(range)
72
+ range.begin - (range.begin.is_a?(Integer) ? 1 : 0.1)
73
+ end
74
+
75
+ private_class_method def self.calculate_after_end(range)
76
+ if range.exclude_end?
77
+ range.end
78
+ else
79
+ range.end + (range.end.is_a?(Integer) ? 1 : 0.1)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Domain
6
+ class Validator
7
+ def self.validate_field(_field_name, value, domain)
8
+ return true if domain.nil?
9
+
10
+ case domain
11
+ when Range
12
+ domain.cover?(value)
13
+ when Array
14
+ domain.include?(value)
15
+ when Proc
16
+ domain.call(value)
17
+ else
18
+ true
19
+ end
20
+ end
21
+
22
+ def self.validate_context(context, input_meta)
23
+ violations = []
24
+
25
+ context.each do |field, value|
26
+ meta = input_meta[field]
27
+ next unless meta&.dig(:domain)
28
+
29
+ violations << create_violation(field, value, meta[:domain]) unless validate_field(field, value, meta[:domain])
30
+ end
31
+
32
+ violations
33
+ end
34
+
35
+ def self.extract_domain_metadata(input_meta)
36
+ metadata = {}
37
+
38
+ input_meta.each do |field, meta|
39
+ domain = meta[:domain]
40
+ next unless domain
41
+
42
+ metadata[field] = analyze_domain(field, domain)
43
+ end
44
+
45
+ metadata
46
+ end
47
+
48
+ def self.create_violation(field, value, domain)
49
+ {
50
+ field: field,
51
+ value: value,
52
+ domain: domain,
53
+ message: ViolationFormatter.format_message(field, value, domain)
54
+ }
55
+ end
56
+
57
+ def self.analyze_domain(_field, domain)
58
+ case domain
59
+ when Range
60
+ RangeAnalyzer.analyze(domain)
61
+ when Array
62
+ EnumAnalyzer.analyze(domain)
63
+ when Proc
64
+ {
65
+ type: :custom,
66
+ description: "Custom constraint function",
67
+ sample_values: [],
68
+ invalid_samples: []
69
+ }
70
+ else
71
+ {
72
+ type: :unknown,
73
+ constraint: domain,
74
+ sample_values: [],
75
+ invalid_samples: []
76
+ }
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Domain
6
+ class ViolationFormatter
7
+ def self.format_message(field, value, domain)
8
+ case domain
9
+ when Range
10
+ format_range_violation(field, value, domain)
11
+ when Array
12
+ format_array_violation(field, value, domain)
13
+ when Proc
14
+ format_proc_violation(field, value)
15
+ else
16
+ format_default_violation(field, value, domain)
17
+ end
18
+ end
19
+
20
+ private_class_method def self.format_range_violation(field, value, range)
21
+ if range.exclude_end?
22
+ "Field :#{field} value #{value.inspect} is outside domain #{range.begin}...#{range.end} (exclusive)"
23
+ else
24
+ "Field :#{field} value #{value.inspect} is outside domain #{range.begin}..#{range.end}"
25
+ end
26
+ end
27
+
28
+ private_class_method def self.format_array_violation(field, value, array)
29
+ "Field :#{field} value #{value.inspect} is not in allowed values #{array.inspect}"
30
+ end
31
+
32
+ private_class_method def self.format_proc_violation(field, value)
33
+ "Field :#{field} value #{value.inspect} does not satisfy custom domain constraint"
34
+ end
35
+
36
+ private_class_method def self.format_default_violation(field, value, domain)
37
+ "Field :#{field} value #{value.inspect} violates domain constraint #{domain.inspect}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ # Centralized error reporting interface for consistent location handling
6
+ # and error message formatting across the entire codebase.
7
+ #
8
+ # This module provides a unified way to:
9
+ # 1. Report errors with consistent location information
10
+ # 2. Format error messages uniformly
11
+ # 3. Handle missing location data gracefully
12
+ # 4. Support both immediate raising and error accumulation patterns
13
+ module ErrorReporter
14
+ # Standard error structure for internal use
15
+ ErrorEntry = Struct.new(:location, :message, :type, :context, keyword_init: true) do
16
+ def to_s
17
+ location_str = format_location(location)
18
+ "#{location_str}: #{message}"
19
+ end
20
+
21
+ def location?
22
+ location && !location.is_a?(Symbol)
23
+ end
24
+
25
+ private
26
+
27
+ def format_location(loc)
28
+ case loc
29
+ when nil
30
+ "at ?"
31
+ when Symbol
32
+ "at #{loc}"
33
+ when Syntax::Location
34
+ "at #{loc.file}:#{loc.line}:#{loc.column}"
35
+ else
36
+ "at <unknown>"
37
+ end
38
+ end
39
+ end
40
+
41
+ module_function
42
+
43
+ # Create a standardized error entry
44
+ #
45
+ # @param message [String] The error message
46
+ # @param location [Syntax::Location, Symbol, nil] Location information
47
+ # @param type [Symbol] Optional error category (:syntax, :semantic, :type, etc.)
48
+ # @param context [Hash] Optional additional context
49
+ # @return [ErrorEntry] Structured error entry
50
+ def create_error(message, location: nil, type: :semantic, context: {})
51
+ ErrorEntry.new(
52
+ location: location,
53
+ message: message,
54
+ type: type,
55
+ context: context
56
+ )
57
+ end
58
+
59
+ # Add an error to an accumulator array (for analyzer passes)
60
+ #
61
+ # @param errors [Array] Error accumulator array
62
+ # @param message [String] The error message
63
+ # @param location [Syntax::Location, Symbol, nil] Location information
64
+ # @param type [Symbol] Error category
65
+ # @param context [Hash] Additional context
66
+ def add_error(errors, message, location: nil, type: :semantic, context: {})
67
+ entry = create_error(message, location: location, type: type, context: context)
68
+ errors << entry
69
+ entry
70
+ end
71
+
72
+ # Immediately raise a localized error (for parser)
73
+ #
74
+ # @param message [String] The error message
75
+ # @param location [Syntax::Location, Symbol, nil] Location information
76
+ # @param error_class [Class] Exception class to raise
77
+ # @param type [Symbol] Error category
78
+ # @param context [Hash] Additional context
79
+ def raise_error(message, location: nil, error_class: Errors::SemanticError, type: :semantic, context: {})
80
+ entry = create_error(message, location: location, type: type, context: context)
81
+ # Pass both the formatted message and the original location to the error constructor
82
+ raise error_class.new(entry.to_s, location)
83
+ end
84
+
85
+ # Format multiple errors into a single message
86
+ #
87
+ # @param errors [Array<ErrorEntry>] Array of error entries
88
+ # @return [String] Formatted error message
89
+ def format_errors(errors)
90
+ errors.map(&:to_s).join("\n")
91
+ end
92
+
93
+ # Group errors by type for better organization
94
+ #
95
+ # @param errors [Array<ErrorEntry>] Array of error entries
96
+ # @return [Hash] Errors grouped by type
97
+ def group_errors_by_type(errors)
98
+ errors.group_by(&:type)
99
+ end
100
+
101
+ # Check if any errors lack location information
102
+ #
103
+ # @param errors [Array<ErrorEntry>] Array of error entries
104
+ # @return [Array<ErrorEntry>] Errors without location info
105
+ def missing_location_errors(errors)
106
+ errors.reject(&:location?)
107
+ end
108
+
109
+ # Enhanced error reporting with suggestions and context
110
+ #
111
+ # @param message [String] Base error message
112
+ # @param location [Syntax::Location, nil] Location information
113
+ # @param suggestions [Array<String>] Suggested fixes
114
+ # @param similar_names [Array<String>] Similar names for typo suggestions
115
+ # @param type [Symbol] Error category
116
+ # @return [ErrorEntry] Enhanced error entry
117
+ def create_enhanced_error(message, location: nil, suggestions: [], similar_names: [], type: :semantic)
118
+ enhanced_message = build_enhanced_message(message, suggestions, similar_names)
119
+ create_error(enhanced_message, location: location, type: type, context: {
120
+ suggestions: suggestions,
121
+ similar_names: similar_names
122
+ })
123
+ end
124
+
125
+ # Validate that location information is present where expected
126
+ #
127
+ # @param errors [Array<ErrorEntry>] Array of error entries
128
+ # @param expected_with_location [Array<Symbol>] Error types that should have locations
129
+ # @return [Hash] Validation report
130
+ def validate_error_locations(errors, expected_with_location: %i[syntax semantic type])
131
+ report = {
132
+ total_errors: errors.size,
133
+ errors_with_location: errors.count(&:location?),
134
+ errors_without_location: errors.reject(&:location?),
135
+ location_coverage: 0.0
136
+ }
137
+
138
+ report[:location_coverage] = (report[:errors_with_location].to_f / report[:total_errors]) * 100 if report[:total_errors].positive?
139
+
140
+ # Check specific types that should have locations
141
+ report[:problematic_errors] = errors.select do |error|
142
+ expected_with_location.include?(error.type) && !error.location?
143
+ end
144
+
145
+ report
146
+ end
147
+
148
+ private
149
+
150
+ def build_enhanced_message(base_message, suggestions, similar_names)
151
+ parts = [base_message]
152
+
153
+ parts << "Did you mean: #{similar_names.map { |name| "`#{name}`" }.join(', ')}?" unless similar_names.empty?
154
+
155
+ unless suggestions.empty?
156
+ parts << "Suggestions:"
157
+ suggestions.each { |suggestion| parts << " - #{suggestion}" }
158
+ end
159
+
160
+ parts.join("\n")
161
+ end
162
+
163
+ module_function :build_enhanced_message
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ # Mixin module providing error reporting capabilities to classes
6
+ # that need to report localized errors consistently.
7
+ #
8
+ # Usage:
9
+ # class MyAnalyzer
10
+ # include ErrorReporting
11
+ #
12
+ # def analyze(errors)
13
+ # report_error(errors, "Something went wrong", location: some_location)
14
+ # raise_localized_error("Critical error", location: some_location)
15
+ # end
16
+ # end
17
+ module ErrorReporting
18
+ # Report an error to an accumulator (analyzer pattern)
19
+ #
20
+ # @param errors [Array] Error accumulator array
21
+ # @param message [String] Error message
22
+ # @param location [Syntax::Location, Symbol, nil] Location info
23
+ # @param type [Symbol] Error category (:syntax, :semantic, :type, etc.)
24
+ # @param context [Hash] Additional context
25
+ # @return [ErrorReporter::ErrorEntry] The created error entry
26
+ def report_error(errors, message, location: nil, type: :semantic, context: {})
27
+ ErrorReporter.add_error(errors, message, location: location, type: type, context: context)
28
+ end
29
+
30
+ # Immediately raise a localized error (parser pattern)
31
+ #
32
+ # @param message [String] Error message
33
+ # @param location [Syntax::Location, Symbol, nil] Location info
34
+ # @param error_class [Class] Exception class to raise
35
+ # @param type [Symbol] Error category
36
+ # @param context [Hash] Additional context
37
+ def raise_localized_error(message, location: nil, error_class: Errors::SemanticError, type: :semantic, context: {})
38
+ ErrorReporter.raise_error(message, location: location, error_class: error_class, type: type, context: context)
39
+ end
40
+
41
+ # Report a syntax error to an accumulator
42
+ def report_syntax_error(errors, message, location: nil, context: {})
43
+ report_error(errors, message, location: location, type: :syntax, context: context)
44
+ end
45
+
46
+ # Report a type error to an accumulator
47
+ def report_type_error(errors, message, location: nil, context: {})
48
+ report_error(errors, message, location: location, type: :type, context: context)
49
+ end
50
+
51
+ # Report a semantic error to an accumulator
52
+ def report_semantic_error(errors, message, location: nil, context: {})
53
+ report_error(errors, message, location: location, type: :semantic, context: context)
54
+ end
55
+
56
+ # Immediately raise a syntax error
57
+ def raise_syntax_error(message, location: nil, context: {})
58
+ raise_localized_error(message, location: location, error_class: Kumi::Core::Errors::SyntaxError, type: :syntax, context: context)
59
+ end
60
+
61
+ # Immediately raise a type error
62
+ def raise_type_error(message, location: nil, context: {})
63
+ raise_localized_error(message, location: location, error_class: Errors::TypeError, type: :type, context: context)
64
+ end
65
+
66
+ # Create an enhanced error with suggestions
67
+ #
68
+ # @param errors [Array] Error accumulator array
69
+ # @param message [String] Base error message
70
+ # @param location [Syntax::Location, nil] Location info
71
+ # @param suggestions [Array<String>] Suggested fixes
72
+ # @param similar_names [Array<String>] Similar names for typos
73
+ # @param type [Symbol] Error category
74
+ def report_enhanced_error(errors, message, location: nil, suggestions: [], similar_names: [], type: :semantic)
75
+ entry = ErrorReporter.create_enhanced_error(
76
+ message,
77
+ location: location,
78
+ suggestions: suggestions,
79
+ similar_names: similar_names,
80
+ type: type
81
+ )
82
+ errors << entry
83
+ entry
84
+ end
85
+
86
+ # Get current location from caller stack (fallback method)
87
+ #
88
+ # @return [Syntax::Location] Location based on caller stack
89
+ def inferred_location
90
+ fallback = caller_locations.find(&:absolute_path)
91
+ return nil unless fallback
92
+
93
+ Syntax::Location.new(file: fallback.path, line: fallback.lineno, column: 0)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Errors
6
+ class Error < StandardError; end
7
+
8
+ class LocatedError < Error
9
+ attr_reader :location
10
+
11
+ def initialize(message, location = nil)
12
+ super(message)
13
+ @location = location
14
+ end
15
+
16
+ def to_s
17
+ if @location
18
+ "#{super} at #{@location.file}:#{@location.line}:#{@location.column}"
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
24
+
25
+ class UnknownFunction < Error; end
26
+
27
+ class SemanticError < LocatedError; end
28
+
29
+ class TypeError < SemanticError; end
30
+
31
+ class FieldMetadataError < SemanticError; end
32
+
33
+ class SyntaxError < LocatedError; end
34
+
35
+ class RuntimeError < Error; end
36
+
37
+ class DomainViolationError < Error
38
+ attr_reader :violations
39
+
40
+ def initialize(violations)
41
+ @violations = violations
42
+ super(format_message)
43
+ end
44
+
45
+ def single_violation?
46
+ violations.size == 1
47
+ end
48
+
49
+ def multiple_violations?
50
+ violations.size > 1
51
+ end
52
+
53
+ private
54
+
55
+ def format_message
56
+ if single_violation?
57
+ violations.first[:message]
58
+ else
59
+ "Multiple domain violations:\n#{violations.map { |v| " - #{v[:message]}" }.join("\n")}"
60
+ end
61
+ end
62
+ end
63
+
64
+ class InputValidationError < Error
65
+ attr_reader :violations
66
+
67
+ def initialize(violations)
68
+ @violations = violations
69
+ super(format_message)
70
+ end
71
+
72
+ def single_violation?
73
+ violations.size == 1
74
+ end
75
+
76
+ def multiple_violations?
77
+ violations.size > 1
78
+ end
79
+
80
+ def type_violations
81
+ violations.select { |v| v[:type] == :type_violation }
82
+ end
83
+
84
+ def domain_violations
85
+ violations.select { |v| v[:type] == :domain_violation }
86
+ end
87
+
88
+ def type_violations?
89
+ type_violations.any?
90
+ end
91
+
92
+ def domain_violations?
93
+ domain_violations.any?
94
+ end
95
+
96
+ private
97
+
98
+ def format_message
99
+ if single_violation?
100
+ violations.first[:message]
101
+ else
102
+ message_parts = []
103
+
104
+ if type_violations?
105
+ message_parts << "Type violations:"
106
+ type_violations.each { |v| message_parts << " - #{v[:message]}" }
107
+ end
108
+
109
+ if domain_violations?
110
+ message_parts << "Domain violations:"
111
+ domain_violations.each { |v| message_parts << " - #{v[:message]}" }
112
+ end
113
+
114
+ message_parts.join("\n")
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ EvaluationWrapper = Struct.new(:ctx) do
6
+ def initialize(ctx)
7
+ super
8
+ @__schema_cache__ = {} # memoization cache for bindings
9
+ end
10
+
11
+ def [](key)
12
+ ctx[key]
13
+ end
14
+
15
+ def []=(key, value)
16
+ ctx[key] = value
17
+ end
18
+
19
+ def keys
20
+ ctx.keys
21
+ end
22
+
23
+ def key?(key)
24
+ ctx.key?(key)
25
+ end
26
+
27
+ def clear
28
+ @__schema_cache__.clear
29
+ end
30
+
31
+ def clear_cache(*keys)
32
+ if keys.empty?
33
+ @__schema_cache__.clear
34
+ else
35
+ keys.each { |key| @__schema_cache__.delete(key) }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end