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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +34 -177
  3. data/README.md +41 -7
  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/examples/game_of_life.rb +1 -1
  16. data/examples/static_analysis_errors.rb +7 -7
  17. data/lib/kumi/analyzer.rb +20 -20
  18. data/lib/kumi/compiler.rb +44 -50
  19. data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
  20. data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
  21. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
  22. data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
  23. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
  24. data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
  25. data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
  26. data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
  27. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
  28. data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
  29. data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
  30. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
  31. data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
  32. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
  33. data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
  34. data/lib/kumi/core/atom_unsat_solver.rb +396 -0
  35. data/lib/kumi/core/compiled_schema.rb +43 -0
  36. data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
  37. data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
  38. data/lib/kumi/core/domain/range_analyzer.rb +85 -0
  39. data/lib/kumi/core/domain/validator.rb +82 -0
  40. data/lib/kumi/core/domain/violation_formatter.rb +42 -0
  41. data/lib/kumi/core/error_reporter.rb +166 -0
  42. data/lib/kumi/core/error_reporting.rb +97 -0
  43. data/lib/kumi/core/errors.rb +120 -0
  44. data/lib/kumi/core/evaluation_wrapper.rb +40 -0
  45. data/lib/kumi/core/explain.rb +295 -0
  46. data/lib/kumi/core/export/deserializer.rb +41 -0
  47. data/lib/kumi/core/export/errors.rb +14 -0
  48. data/lib/kumi/core/export/node_builders.rb +142 -0
  49. data/lib/kumi/core/export/node_registry.rb +54 -0
  50. data/lib/kumi/core/export/node_serializers.rb +158 -0
  51. data/lib/kumi/core/export/serializer.rb +25 -0
  52. data/lib/kumi/core/export.rb +35 -0
  53. data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
  54. data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
  55. data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
  56. data/lib/kumi/core/function_registry/function_builder.rb +95 -0
  57. data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
  58. data/lib/kumi/core/function_registry/math_functions.rb +74 -0
  59. data/lib/kumi/core/function_registry/string_functions.rb +57 -0
  60. data/lib/kumi/core/function_registry/type_functions.rb +53 -0
  61. data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
  62. data/lib/kumi/core/input/type_matcher.rb +97 -0
  63. data/lib/kumi/core/input/validator.rb +51 -0
  64. data/lib/kumi/core/input/violation_creator.rb +52 -0
  65. data/lib/kumi/core/json_schema/generator.rb +65 -0
  66. data/lib/kumi/core/json_schema/validator.rb +27 -0
  67. data/lib/kumi/core/json_schema.rb +16 -0
  68. data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
  69. data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
  70. data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
  71. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
  72. data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
  73. data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
  74. data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
  75. data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
  76. data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
  77. data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
  78. data/lib/kumi/core/ruby_parser/parser.rb +71 -0
  79. data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
  80. data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
  81. data/lib/kumi/core/ruby_parser.rb +12 -0
  82. data/lib/kumi/core/schema_instance.rb +111 -0
  83. data/lib/kumi/core/types/builder.rb +23 -0
  84. data/lib/kumi/core/types/compatibility.rb +96 -0
  85. data/lib/kumi/core/types/formatter.rb +26 -0
  86. data/lib/kumi/core/types/inference.rb +42 -0
  87. data/lib/kumi/core/types/normalizer.rb +72 -0
  88. data/lib/kumi/core/types/validator.rb +37 -0
  89. data/lib/kumi/core/types.rb +66 -0
  90. data/lib/kumi/core/vectorization_metadata.rb +110 -0
  91. data/lib/kumi/errors.rb +1 -112
  92. data/lib/kumi/registry.rb +37 -0
  93. data/lib/kumi/schema.rb +13 -7
  94. data/lib/kumi/schema_metadata.rb +524 -0
  95. data/lib/kumi/syntax/array_expression.rb +6 -6
  96. data/lib/kumi/syntax/call_expression.rb +4 -4
  97. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  98. data/lib/kumi/syntax/case_expression.rb +4 -4
  99. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  100. data/lib/kumi/syntax/hash_expression.rb +4 -4
  101. data/lib/kumi/syntax/input_declaration.rb +5 -5
  102. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  103. data/lib/kumi/syntax/input_reference.rb +5 -5
  104. data/lib/kumi/syntax/literal.rb +4 -4
  105. data/lib/kumi/syntax/node.rb +34 -34
  106. data/lib/kumi/syntax/root.rb +6 -6
  107. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  108. data/lib/kumi/syntax/value_declaration.rb +4 -4
  109. data/lib/kumi/version.rb +1 -1
  110. data/lib/kumi.rb +14 -0
  111. data/migrate_to_core_iterative.rb +938 -0
  112. data/scripts/generate_function_docs.rb +9 -9
  113. metadata +85 -69
  114. data/lib/generators/trait_engine/templates/schema_spec.rb.erb +0 -27
  115. data/lib/kumi/analyzer/analysis_state.rb +0 -37
  116. data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
  117. data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -251
  118. data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
  119. data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
  120. data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
  121. data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
  122. data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
  123. data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -110
  124. data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
  125. data/lib/kumi/analyzer/passes/type_checker.rb +0 -162
  126. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
  127. data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
  128. data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -406
  129. data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
  130. data/lib/kumi/atom_unsat_solver.rb +0 -394
  131. data/lib/kumi/compiled_schema.rb +0 -41
  132. data/lib/kumi/constraint_relationship_solver.rb +0 -638
  133. data/lib/kumi/domain/enum_analyzer.rb +0 -53
  134. data/lib/kumi/domain/range_analyzer.rb +0 -83
  135. data/lib/kumi/domain/validator.rb +0 -80
  136. data/lib/kumi/domain/violation_formatter.rb +0 -40
  137. data/lib/kumi/error_reporter.rb +0 -164
  138. data/lib/kumi/error_reporting.rb +0 -95
  139. data/lib/kumi/evaluation_wrapper.rb +0 -38
  140. data/lib/kumi/explain.rb +0 -281
  141. data/lib/kumi/export/deserializer.rb +0 -39
  142. data/lib/kumi/export/errors.rb +0 -12
  143. data/lib/kumi/export/node_builders.rb +0 -140
  144. data/lib/kumi/export/node_registry.rb +0 -52
  145. data/lib/kumi/export/node_serializers.rb +0 -156
  146. data/lib/kumi/export/serializer.rb +0 -23
  147. data/lib/kumi/export.rb +0 -33
  148. data/lib/kumi/function_registry/collection_functions.rb +0 -200
  149. data/lib/kumi/function_registry/comparison_functions.rb +0 -31
  150. data/lib/kumi/function_registry/conditional_functions.rb +0 -36
  151. data/lib/kumi/function_registry/function_builder.rb +0 -93
  152. data/lib/kumi/function_registry/logical_functions.rb +0 -42
  153. data/lib/kumi/function_registry/math_functions.rb +0 -72
  154. data/lib/kumi/function_registry/string_functions.rb +0 -54
  155. data/lib/kumi/function_registry/type_functions.rb +0 -51
  156. data/lib/kumi/input/type_matcher.rb +0 -95
  157. data/lib/kumi/input/validator.rb +0 -49
  158. data/lib/kumi/input/violation_creator.rb +0 -50
  159. data/lib/kumi/parser/build_context.rb +0 -25
  160. data/lib/kumi/parser/declaration_reference_proxy.rb +0 -36
  161. data/lib/kumi/parser/dsl.rb +0 -12
  162. data/lib/kumi/parser/dsl_cascade_builder.rb +0 -136
  163. data/lib/kumi/parser/expression_converter.rb +0 -126
  164. data/lib/kumi/parser/guard_rails.rb +0 -43
  165. data/lib/kumi/parser/input_builder.rb +0 -125
  166. data/lib/kumi/parser/input_field_proxy.rb +0 -46
  167. data/lib/kumi/parser/input_proxy.rb +0 -29
  168. data/lib/kumi/parser/nested_input.rb +0 -15
  169. data/lib/kumi/parser/parser.rb +0 -68
  170. data/lib/kumi/parser/schema_builder.rb +0 -173
  171. data/lib/kumi/parser/sugar.rb +0 -261
  172. data/lib/kumi/schema_instance.rb +0 -109
  173. data/lib/kumi/types/builder.rb +0 -21
  174. data/lib/kumi/types/compatibility.rb +0 -94
  175. data/lib/kumi/types/formatter.rb +0 -24
  176. data/lib/kumi/types/inference.rb +0 -40
  177. data/lib/kumi/types/normalizer.rb +0 -70
  178. data/lib/kumi/types/validator.rb +0 -35
  179. data/lib/kumi/types.rb +0 -64
  180. data/lib/kumi/vectorization_metadata.rb +0 -108
data/lib/kumi/explain.rb DELETED
@@ -1,281 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Explain
5
- class ExplanationGenerator
6
- def initialize(syntax_tree, analyzer_result, inputs)
7
- @analyzer_result = analyzer_result
8
- @inputs = EvaluationWrapper.new(inputs)
9
- @definitions = analyzer_result.definitions
10
- @compiled_schema = Compiler.compile(syntax_tree, analyzer: analyzer_result)
11
-
12
- # TODO: REFACTOR QUICK!
13
- # Set up compiler once for expression evaluation
14
- @compiler = Compiler.new(syntax_tree, analyzer_result)
15
- @compiler.send(:build_index)
16
-
17
- # Populate bindings from the compiled schema
18
- @compiled_schema.bindings.each do |name, (type, fn)|
19
- @compiler.instance_variable_get(:@bindings)[name] = [type, fn]
20
- end
21
- end
22
-
23
- def explain(target_name)
24
- declaration = @definitions[target_name]
25
- raise ArgumentError, "Unknown declaration: #{target_name}" unless declaration
26
-
27
- expression = declaration.expression
28
- result_value = @compiled_schema.evaluate_binding(target_name, @inputs)
29
-
30
- prefix = "#{target_name} = "
31
- expression_str = format_expression(expression, indent_context: prefix.length)
32
-
33
- "#{prefix}#{expression_str} => #{format_value(result_value)}"
34
- end
35
-
36
- private
37
-
38
- def format_expression(expr, indent_context: 0, nested: false)
39
- case expr
40
- when Kumi::Syntax::InputReference
41
- "input.#{expr.name}"
42
- when Kumi::Syntax::DeclarationReference
43
- expr.name.to_s
44
- when Kumi::Syntax::Literal
45
- format_value(expr.value)
46
- when Kumi::Syntax::CallExpression
47
- format_call_expression(expr, indent_context: indent_context, nested: nested)
48
- when Kumi::Syntax::ArrayExpression
49
- "[#{expr.elements.map { |e| format_expression(e, indent_context: indent_context, nested: nested) }.join(', ')}]"
50
- when Kumi::Syntax::CascadeExpression
51
- format_cascade_expression(expr, indent_context: indent_context)
52
- else
53
- expr.class.name.split("::").last
54
- end
55
- end
56
-
57
- def format_call_expression(expr, indent_context: 0, nested: false)
58
- if pretty_printable?(expr.fn_name)
59
- format_pretty_function(expr, expr.fn_name, indent_context, nested: nested)
60
- else
61
- format_generic_function(expr, indent_context)
62
- end
63
- end
64
-
65
- def format_pretty_function(expr, fn_name, _indent_context, nested: false)
66
- if needs_evaluation?(expr.args) && !nested
67
- # For top-level expressions, show the flattened symbolic form and evaluation
68
- if chain_of_same_operator?(expr, fn_name)
69
- # For chains like a + b + c, flatten to show all operands
70
- all_operands = flatten_operator_chain(expr, fn_name)
71
- symbolic_operands = all_operands.map { |op| format_expression(op, indent_context: 0, nested: true) }
72
- symbolic_format = symbolic_operands.join(" #{get_operator_symbol(fn_name)} ")
73
-
74
- evaluated_operands = all_operands.map do |op|
75
- if op.is_a?(Kumi::Syntax::Literal)
76
- format_expression(op, indent_context: 0, nested: true)
77
- else
78
- arg_value = format_value(evaluate_expression(op))
79
- if op.is_a?(Kumi::Syntax::DeclarationReference) && all_operands.length > 1
80
- "(#{format_expression(op, indent_context: 0, nested: true)} = #{arg_value})"
81
- else
82
- arg_value
83
- end
84
- end
85
- end
86
- evaluated_format = evaluated_operands.join(" #{get_operator_symbol(fn_name)} ")
87
-
88
- else
89
- # Regular pretty formatting for non-chain expressions
90
- symbolic_args = expr.args.map { |arg| format_expression(arg, indent_context: 0, nested: true) }
91
- symbolic_format = display_format(fn_name, symbolic_args)
92
-
93
- evaluated_args = expr.args.map do |arg|
94
- if arg.is_a?(Kumi::Syntax::Literal)
95
- format_expression(arg, indent_context: 0, nested: true)
96
- else
97
- arg_value = format_value(evaluate_expression(arg))
98
- if arg.is_a?(Kumi::Syntax::DeclarationReference) &&
99
- expr.args.count { |a| !a.is_a?(Kumi::Syntax::Literal) } > 1
100
- "(#{format_expression(arg, indent_context: 0, nested: true)} = #{arg_value})"
101
- else
102
- arg_value
103
- end
104
- end
105
- end
106
- evaluated_format = display_format(fn_name, evaluated_args)
107
-
108
- end
109
- "#{symbolic_format} = #{evaluated_format}"
110
- else
111
- # For nested expressions, just show the symbolic form without evaluation details
112
- args = expr.args.map { |arg| format_expression(arg, indent_context: 0, nested: true) }
113
- display_format(fn_name, args)
114
- end
115
- end
116
-
117
- def chain_of_same_operator?(expr, fn_name)
118
- return false unless %i[add subtract multiply divide].include?(fn_name)
119
-
120
- # Check if any argument is the same operator
121
- expr.args.any? do |arg|
122
- arg.is_a?(Kumi::Syntax::CallExpression) && arg.fn_name == fn_name
123
- end
124
- end
125
-
126
- def flatten_operator_chain(expr, operator)
127
- operands = []
128
-
129
- expr.args.each do |arg|
130
- if arg.is_a?(Kumi::Syntax::CallExpression) && arg.fn_name == operator
131
- # Recursively flatten nested operations of the same type
132
- operands.concat(flatten_operator_chain(arg, operator))
133
- else
134
- operands << arg
135
- end
136
- end
137
-
138
- operands
139
- end
140
-
141
- def get_operator_symbol(fn_name)
142
- case fn_name
143
- when :add then "+"
144
- when :subtract then "-"
145
- when :multiply then "×"
146
- when :divide then "÷"
147
- else fn_name.to_s
148
- end
149
- end
150
-
151
- def pretty_printable?(fn_name)
152
- %i[add subtract multiply divide == != > < >= <= and or not].include?(fn_name)
153
- end
154
-
155
- def display_format(fn_name, args)
156
- case fn_name
157
- when :add then args.join(" + ")
158
- when :subtract then args.join(" - ")
159
- when :multiply then args.join(" × ")
160
- when :divide then args.join(" ÷ ")
161
- when :== then "#{args[0]} == #{args[1]}"
162
- when :!= then "#{args[0]} != #{args[1]}"
163
- when :> then "#{args[0]} > #{args[1]}"
164
- when :< then "#{args[0]} < #{args[1]}"
165
- when :>= then "#{args[0]} >= #{args[1]}"
166
- when :<= then "#{args[0]} <= #{args[1]}"
167
- when :and then args.join(" && ")
168
- when :or then args.join(" || ")
169
- when :not then "!#{args[0]}"
170
- else "#{fn_name}(#{args.join(', ')})"
171
- end
172
- end
173
-
174
- def format_generic_function(expr, indent_context)
175
- args = expr.args.map do |arg|
176
- arg_desc = format_expression(arg, indent_context: indent_context)
177
-
178
- # For literals and literal lists, just show the value, no need for "100 = 100"
179
- if arg.is_a?(Kumi::Syntax::Literal) ||
180
- (arg.is_a?(Kumi::Syntax::ArrayExpression) && arg.elements.all?(Kumi::Syntax::Literal))
181
- arg_desc
182
- else
183
- arg_value = evaluate_expression(arg)
184
- "#{arg_desc} = #{format_value(arg_value)}"
185
- end
186
- end
187
-
188
- if args.length > 1
189
- # Align with opening parenthesis, accounting for the full context
190
- function_indent = indent_context + expr.fn_name.to_s.length + 1
191
- indent = " " * function_indent
192
- "#{expr.fn_name}(#{args.join(",\n#{indent}")})"
193
- else
194
- "#{expr.fn_name}(#{args.join(', ')})"
195
- end
196
- end
197
-
198
- def needs_evaluation?(args)
199
- args.any? do |arg|
200
- !arg.is_a?(Kumi::Syntax::Literal) &&
201
- !(arg.is_a?(Kumi::Syntax::ArrayExpression) && arg.elements.all?(Kumi::Syntax::Literal))
202
- end
203
- end
204
-
205
- def format_cascade_expression(expr, indent_context: 0)
206
- lines = []
207
- expr.cases.each do |case_expr|
208
- condition_result = evaluate_expression(case_expr.condition)
209
- condition_desc = format_expression(case_expr.condition, indent_context: indent_context)
210
- result_desc = format_expression(case_expr.result, indent_context: indent_context)
211
-
212
- status = condition_result ? "✓" : "✗"
213
- lines << " #{status} on #{condition_desc}, #{result_desc}"
214
-
215
- break if condition_result
216
- end
217
-
218
- "\n#{lines.join("\n")}"
219
- end
220
-
221
- def format_value(value)
222
- case value
223
- when Float, Integer
224
- format_number(value)
225
- when String
226
- "\"#{value}\""
227
- when Array
228
- if value.length <= 4
229
- "[#{value.map { |v| format_value(v) }.join(', ')}]"
230
- else
231
- "[#{value.take(4).map { |v| format_value(v) }.join(', ')}, …]"
232
- end
233
- else
234
- value.to_s
235
- end
236
- end
237
-
238
- def format_number(num)
239
- return num.to_s unless num.is_a?(Numeric)
240
-
241
- if num.is_a?(Integer) || (num.is_a?(Float) && num == num.to_i)
242
- int_val = num.to_i
243
- if int_val.abs >= 1000
244
- int_val.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1 ').reverse
245
- else
246
- int_val.to_s
247
- end
248
- else
249
- num.to_s
250
- end
251
- end
252
-
253
- def evaluate_expression(expr)
254
- case expr
255
- when Kumi::Syntax::DeclarationReference
256
- @compiled_schema.evaluate_binding(expr.name, @inputs)
257
- when Kumi::Syntax::InputReference
258
- @inputs[expr.name]
259
- when Kumi::Syntax::Literal
260
- expr.value
261
- else
262
- # For complex expressions, compile and evaluate using existing compiler
263
- compiled_fn = @compiler.send(:compile_expr, expr)
264
- compiled_fn.call(@inputs)
265
- end
266
- end
267
- end
268
-
269
- module_function
270
-
271
- def call(schema_class, target_name, inputs:)
272
- syntax_tree = schema_class.instance_variable_get(:@__syntax_tree__)
273
- analyzer_result = schema_class.instance_variable_get(:@__analyzer_result__)
274
-
275
- raise ArgumentError, "Schema not found or not compiled" unless syntax_tree && analyzer_result
276
-
277
- generator = ExplanationGenerator.new(syntax_tree, analyzer_result, inputs)
278
- generator.explain(target_name)
279
- end
280
- end
281
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Export
5
- class Deserializer
6
- include NodeBuilders
7
-
8
- def initialize(validate: true)
9
- @validate = validate
10
- end
11
-
12
- def deserialize(json_string)
13
- data = parse_json(json_string)
14
- validate_format(data) if @validate
15
-
16
- build_node(data[:ast])
17
- end
18
-
19
- private
20
-
21
- def parse_json(json_string)
22
- JSON.parse(json_string, symbolize_names: true)
23
- rescue JSON::ParserError => e
24
- raise Kumi::Export::Errors::DeserializationError, "Invalid JSON: #{e.message}"
25
- end
26
-
27
- def validate_format(data)
28
- unless data[:kumi_version] && data[:ast]
29
- raise Kumi::Export::Errors::DeserializationError,
30
- "Missing required fields: kumi_version, ast"
31
- end
32
-
33
- return if data[:ast][:type] == "root"
34
-
35
- raise Kumi::Export::Errors::DeserializationError, "Root node must have type 'root'"
36
- end
37
- end
38
- end
39
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Export
5
- module Errors
6
- class ExportError < StandardError; end
7
- class SerializationError < ExportError; end
8
- class DeserializationError < ExportError; end
9
- class VersionMismatchError < ExportError; end
10
- end
11
- end
12
- end
@@ -1,140 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Export
5
- module NodeBuilders
6
- def build_root(data, node_class)
7
- inputs = data[:inputs].map { |input_data| build_node(input_data) }
8
- attributes = data[:attributes].map { |attr_data| build_node(attr_data) }
9
- traits = data[:traits].map { |trait_data| build_node(trait_data) }
10
-
11
- node_class.new(inputs, attributes, traits)
12
- end
13
-
14
- def build_field_declaration(data, node_class)
15
- name = restore_name_type(data[:name], data[:name_type])
16
- type = deserialize_type(data[:field_type])
17
- domain = deserialize_domain(data[:domain])
18
-
19
- # Match the Struct signature of FieldDecl: (name, domain, type)
20
- node_class.new(name, domain, type)
21
- end
22
-
23
- def build_attribute_declaration(data, node_class)
24
- name = restore_name_type(data[:name], data[:name_type])
25
- expression = build_node(data[:expression])
26
-
27
- node_class.new(name, expression)
28
- end
29
-
30
- def build_trait_declaration(data, node_class)
31
- name = restore_name_type(data[:name], data[:name_type])
32
- expression = build_node(data[:expression])
33
-
34
- node_class.new(name, expression)
35
- end
36
-
37
- def build_call_expression(data, node_class)
38
- function_name = restore_name_type(data[:function_name], data[:function_name_type])
39
- arguments = data[:arguments].map { |arg_data| build_node(arg_data) }
40
-
41
- node_class.new(function_name, arguments)
42
- end
43
-
44
- def build_literal(data, node_class)
45
- value = data[:value]
46
-
47
- # Restore proper Ruby type if needed
48
- value = coerce_to_type(value, data[:ruby_type]) if data[:ruby_type] && value.is_a?(String)
49
-
50
- node_class.new(value)
51
- end
52
-
53
- def build_field_reference(data, node_class)
54
- field_name = restore_name_type(data[:field_name], data[:name_type])
55
- node_class.new(field_name)
56
- end
57
-
58
- def build_binding_reference(data, node_class)
59
- binding_name = restore_name_type(data[:binding_name], data[:name_type])
60
- node_class.new(binding_name)
61
- end
62
-
63
- def build_list_expression(data, node_class)
64
- elements = data[:elements].map { |element_data| build_node(element_data) }
65
- node_class.new(elements)
66
- end
67
-
68
- def build_cascade_expression(data, node_class)
69
- cases = data[:cases].map { |case_data| build_node(case_data) }
70
- node_class.new(cases)
71
- end
72
-
73
- def build_when_case_expression(data, node_class)
74
- condition = build_node(data[:condition])
75
- result = build_node(data[:result])
76
- node_class.new(condition, result)
77
- end
78
-
79
- private
80
-
81
- def deserialize_type(type_data)
82
- # Handle simple types that weren't serialized with the new format
83
- return type_data unless type_data.is_a?(Hash) && type_data.key?(:type)
84
-
85
- case type_data[:type]
86
- when "symbol"
87
- type_data[:value].to_sym
88
- when "array"
89
- { array: deserialize_type(type_data[:element_type]) }
90
- when "hash"
91
- { hash: [deserialize_type(type_data[:key_type]), deserialize_type(type_data[:value_type])] }
92
- when "literal", nil
93
- type_data[:value]
94
- end
95
- end
96
-
97
- def build_node(node_data)
98
- type_name = node_data[:type]
99
- node_class = NodeRegistry.class_for_type(type_name)
100
-
101
- build_method = "build_#{type_name}"
102
- raise Kumi::Export::Errors::DeserializationError, "No builder for type: #{type_name}" unless respond_to?(build_method, true)
103
-
104
- send(build_method, node_data, node_class)
105
- end
106
-
107
- def deserialize_domain(domain_data)
108
- return nil unless domain_data
109
-
110
- case domain_data[:type]
111
- when "range"
112
- min, max = domain_data.values_at(:min, :max)
113
- domain_data[:exclude_end] ? (min...max) : (min..max)
114
- when "array"
115
- domain_data[:values]
116
- when "custom"
117
- # For custom domains, we might need to eval or have a registry
118
- domain_data[:value]
119
- end
120
- end
121
-
122
- def coerce_to_type(value, type_name)
123
- case type_name
124
- when "Integer" then value.to_i
125
- when "Float" then value.to_f
126
- when "Symbol" then value.to_sym
127
- else value
128
- end
129
- end
130
-
131
- def restore_name_type(name_string, name_type)
132
- case name_type
133
- when "Symbol" then name_string.to_sym
134
- when "String" then name_string.to_s
135
- else name_string
136
- end
137
- end
138
- end
139
- end
140
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Export
5
- class NodeRegistry
6
- # Maps AST classes to JSON type names
7
- SERIALIZATION_MAP = {
8
- "Kumi::Syntax::Root" => "root",
9
- "Kumi::Syntax::InputDeclaration" => "field_declaration",
10
- "Kumi::Syntax::ValueDeclaration" => "attribute_declaration",
11
- "Kumi::Syntax::TraitDeclaration" => "trait_declaration",
12
- "Kumi::Syntax::CallExpression" => "call_expression",
13
- "Kumi::Syntax::ArrayExpression" => "list_expression",
14
- "Kumi::Syntax::HashExpression" => "hash_expression",
15
- "Kumi::Syntax::CascadeExpression" => "cascade_expression",
16
- "Kumi::Syntax::CaseExpression" => "when_case_expression",
17
- "Kumi::Syntax::Literal" => "literal",
18
- "Kumi::Syntax::InputReference" => "field_reference",
19
- "Kumi::Syntax::DeclarationReference" => "binding_reference"
20
- }.freeze
21
-
22
- # Maps JSON type names back to AST classes (using new canonical class names)
23
- DESERIALIZATION_MAP = {
24
- "root" => "Kumi::Syntax::Root",
25
- "field_declaration" => "Kumi::Syntax::InputDeclaration",
26
- "attribute_declaration" => "Kumi::Syntax::ValueDeclaration",
27
- "trait_declaration" => "Kumi::Syntax::TraitDeclaration",
28
- "call_expression" => "Kumi::Syntax::CallExpression",
29
- "list_expression" => "Kumi::Syntax::ArrayExpression",
30
- "hash_expression" => "Kumi::Syntax::HashExpression",
31
- "cascade_expression" => "Kumi::Syntax::CascadeExpression",
32
- "when_case_expression" => "Kumi::Syntax::CaseExpression",
33
- "literal" => "Kumi::Syntax::Literal",
34
- "field_reference" => "Kumi::Syntax::InputReference",
35
- "binding_reference" => "Kumi::Syntax::DeclarationReference"
36
- }.freeze
37
-
38
- def self.type_name_for(node)
39
- SERIALIZATION_MAP[node.class.name] or
40
- raise Kumi::Export::Errors::SerializationError, "Unknown node type: #{node.class.name}"
41
- end
42
-
43
- def self.class_for_type(type_name)
44
- class_name = DESERIALIZATION_MAP[type_name] or
45
- raise Kumi::Export::Errors::DeserializationError, "Unknown type name: #{type_name}"
46
-
47
- # Resolve the class from string name
48
- class_name.split("::").reduce(Object) { |const, name| const.const_get(name) }
49
- end
50
- end
51
- end
52
- end
@@ -1,156 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Export
5
- module NodeSerializers
6
- # Root node: top-level container
7
- def serialize_root(node)
8
- {
9
- type: "root",
10
- inputs: node.inputs.map { |input| serialize_node(input) },
11
- attributes: node.attributes.map { |attr| serialize_node(attr) },
12
- traits: node.traits.map { |trait| serialize_node(trait) }
13
- }
14
- end
15
-
16
- # Field Declaration: preserves type info for analyzer
17
- def serialize_field_declaration(node)
18
- {
19
- name: node.name.to_s,
20
- name_type: node.name.class.name,
21
- field_type: serialize_type(node.type),
22
- domain: serialize_domain(node.domain)
23
- }
24
- end
25
-
26
- # Attribute Declaration: preserves name and expression tree
27
- def serialize_attribute_declaration(node)
28
- {
29
- name: node.name.to_s,
30
- name_type: node.name.class.name,
31
- expression: serialize_node(node.expression)
32
- }
33
- end
34
-
35
- # Trait Declaration: preserves name and expression tree
36
- def serialize_trait_declaration(node)
37
- {
38
- name: node.name.to_s,
39
- name_type: node.name.class.name,
40
- expression: serialize_node(node.expression)
41
- }
42
- end
43
-
44
- # Call Expression: critical for dependency analysis
45
- def serialize_call_expression(node)
46
- {
47
- function_name: node.fn_name.to_s,
48
- function_name_type: node.fn_name.class.name,
49
- arguments: node.args.map { |arg| serialize_node(arg) }
50
- }
51
- end
52
-
53
- # Literal: preserve exact value and Ruby type
54
- def serialize_literal(node)
55
- {
56
- value: node.value,
57
- ruby_type: node.value.class.name
58
- }
59
- end
60
-
61
- # Field Reference: critical for dependency resolution
62
- def serialize_field_reference(node)
63
- {
64
- field_name: node.name.to_s,
65
- name_type: node.name.class.name
66
- }
67
- end
68
-
69
- # DeclarationReference Reference: critical for dependency resolution
70
- def serialize_binding_reference(node)
71
- {
72
- binding_name: node.name.to_s,
73
- name_type: node.name.class.name
74
- }
75
- end
76
-
77
- # List Expression: preserve order and elements
78
- def serialize_list_expression(node)
79
- {
80
- elements: node.elements.map { |element| serialize_node(element) }
81
- }
82
- end
83
-
84
- # Cascade Expression: preserve condition/result pairs
85
- def serialize_cascade_expression(node)
86
- {
87
- cases: node.cases.map { |case_node| serialize_node(case_node) }
88
- }
89
- end
90
-
91
- # When Case Expression: individual case in cascade
92
- def serialize_when_case_expression(node)
93
- {
94
- condition: serialize_node(node.condition),
95
- result: serialize_node(node.result)
96
- }
97
- end
98
-
99
- private
100
-
101
- def serialize_type(type)
102
- case type
103
- when Symbol
104
- { type: "symbol", value: type.to_s }
105
- when Hash
106
- if type.key?(:array)
107
- { type: "array", element_type: serialize_type(type[:array]) }
108
- elsif type.key?(:hash)
109
- { type: "hash", key_type: serialize_type(type[:hash][0]), value_type: serialize_type(type[:hash][1]) }
110
- else
111
- { type: "hash", value: type }
112
- end
113
- when String, Integer, Float, TrueClass, FalseClass, NilClass
114
- { type: "literal", value: type }
115
- else
116
- { type: "unknown", value: type.to_s }
117
- end
118
- end
119
-
120
- def serialize_domain(domain)
121
- return nil unless domain
122
-
123
- case domain
124
- when Range
125
- { type: "range", min: domain.min, max: domain.max, exclude_end: domain.exclude_end? }
126
- when Array
127
- { type: "array", values: domain }
128
- else
129
- { type: "custom", value: domain.to_s }
130
- end
131
- end
132
-
133
- def serialize_node(node)
134
- type_name = NodeRegistry.type_name_for(node)
135
-
136
- base_data = {
137
- type: type_name,
138
- **send("serialize_#{type_name}", node)
139
- }
140
-
141
- add_location_if_present(base_data, node) if @include_locations
142
- base_data
143
- end
144
-
145
- def add_location_if_present(data, node)
146
- return unless node.respond_to?(:loc) && node.loc
147
-
148
- data[:location] = {
149
- line: node.loc.line,
150
- column: node.loc.column,
151
- file: node.loc.file
152
- }
153
- end
154
- end
155
- end
156
- end