kumi-parser 0.0.3 → 0.0.4
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 +120 -0
- data/README.md +38 -41
- data/lib/kumi/parser/base.rb +51 -0
- data/lib/kumi/parser/direct_parser.rb +502 -0
- data/lib/kumi/parser/errors.rb +40 -0
- data/lib/kumi/parser/smart_tokenizer.rb +287 -0
- data/lib/kumi/parser/syntax_validator.rb +3 -25
- data/lib/kumi/parser/text_parser.rb +19 -34
- data/lib/kumi/parser/token.rb +84 -0
- data/lib/kumi/parser/token_metadata.rb +370 -0
- data/lib/kumi/parser/version.rb +1 -1
- data/lib/kumi/text_parser.rb +40 -0
- data/lib/kumi/text_schema.rb +31 -0
- data/lib/kumi-parser.rb +1 -0
- metadata +10 -8
- data/lib/kumi/parser/analyzer_diagnostic_converter.rb +0 -84
- data/lib/kumi/parser/text_parser/editor_diagnostic.rb +0 -102
- data/lib/kumi/parser/text_parser/grammar.rb +0 -214
- data/lib/kumi/parser/text_parser/parser.rb +0 -168
- data/lib/kumi/parser/text_parser/transform.rb +0 -170
- data/lib/kumi/parser.rb +0 -8
- data/test_basic.rb +0 -44
@@ -1,214 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'parslet'
|
4
|
-
|
5
|
-
module Kumi
|
6
|
-
module Parser
|
7
|
-
module TextParser
|
8
|
-
# Parslet grammar with proper arithmetic operator precedence
|
9
|
-
class Grammar < Parslet::Parser
|
10
|
-
# Basic tokens
|
11
|
-
rule(:space) { match('\s').repeat(1) }
|
12
|
-
rule(:space?) { space.maybe }
|
13
|
-
rule(:newline?) { match('\n').maybe }
|
14
|
-
|
15
|
-
# Comments
|
16
|
-
rule(:comment) { str('#') >> match('[^\n]').repeat }
|
17
|
-
rule(:ws) { (space | comment).repeat }
|
18
|
-
rule(:ws?) { ws.maybe }
|
19
|
-
|
20
|
-
# Identifiers and symbols
|
21
|
-
rule(:identifier) { match('[a-zA-Z_]') >> match('[a-zA-Z0-9_]').repeat }
|
22
|
-
rule(:symbol) { str(':') >> identifier.as(:symbol) }
|
23
|
-
|
24
|
-
# Literals
|
25
|
-
rule(:integer) { match('[0-9]').repeat(1) }
|
26
|
-
rule(:float) { integer >> str('.') >> match('[0-9]').repeat(1) }
|
27
|
-
rule(:number) { float.as(:float) | integer.as(:integer) }
|
28
|
-
rule(:string_literal) do
|
29
|
-
str('"') >> (str('"').absent? >> any).repeat.as(:string) >> str('"')
|
30
|
-
end
|
31
|
-
rule(:boolean) { (str('true').as(:true) | str('false').as(:false)) }
|
32
|
-
rule(:literal) { number | string_literal | boolean }
|
33
|
-
|
34
|
-
# Keywords
|
35
|
-
rule(:schema_kw) { str('schema') }
|
36
|
-
rule(:input_kw) { str('input') }
|
37
|
-
rule(:value_kw) { str('value') }
|
38
|
-
rule(:trait_kw) { str('trait') }
|
39
|
-
rule(:do_kw) { str('do') }
|
40
|
-
rule(:end_kw) { str('end') }
|
41
|
-
|
42
|
-
# Type keywords
|
43
|
-
rule(:type_name) do
|
44
|
-
str('integer') | str('float') | str('string') | str('boolean') | str('any')
|
45
|
-
end
|
46
|
-
|
47
|
-
# Operators (ordered by precedence, highest to lowest)
|
48
|
-
rule(:mult_op) { str('*').as(:multiply) | str('/').as(:divide) | str('%').as(:modulo) }
|
49
|
-
rule(:add_op) { str('+').as(:add) | str('-').as(:subtract) }
|
50
|
-
rule(:comp_op) do
|
51
|
-
str('>=').as(:>=) | str('<=').as(:<=) | str('==').as(:==) |
|
52
|
-
str('!=').as(:!=) | str('>').as(:>) | str('<').as(:<)
|
53
|
-
end
|
54
|
-
rule(:logical_and_op) { str('&').as(:and) }
|
55
|
-
rule(:logical_or_op) { str('|').as(:or) }
|
56
|
-
|
57
|
-
# Expressions with proper precedence (using left recursion elimination)
|
58
|
-
rule(:primary_expr) do
|
59
|
-
str('(') >> ws? >> expression >> ws? >> str(')') |
|
60
|
-
function_call |
|
61
|
-
input_reference |
|
62
|
-
declaration_reference |
|
63
|
-
literal
|
64
|
-
end
|
65
|
-
|
66
|
-
# Function calls: fn(:name, arg1, arg2, ...)
|
67
|
-
rule(:function_call) do
|
68
|
-
str('fn(') >> ws? >>
|
69
|
-
symbol.as(:fn_name) >>
|
70
|
-
(str(',') >> ws? >> expression).repeat(0).as(:args) >>
|
71
|
-
ws? >> str(')')
|
72
|
-
end
|
73
|
-
|
74
|
-
# Multiplication/Division (left-associative)
|
75
|
-
rule(:mult_expr) do
|
76
|
-
primary_expr.as(:left) >>
|
77
|
-
(space? >> mult_op.as(:op) >> space? >> primary_expr.as(:right)).repeat.as(:ops)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Addition/Subtraction (left-associative)
|
81
|
-
rule(:add_expr) do
|
82
|
-
mult_expr.as(:left) >>
|
83
|
-
(space? >> add_op.as(:op) >> space? >> mult_expr.as(:right)).repeat.as(:ops)
|
84
|
-
end
|
85
|
-
|
86
|
-
# Comparison operators
|
87
|
-
rule(:comp_expr) do
|
88
|
-
add_expr.as(:left) >>
|
89
|
-
(space? >> comp_op.as(:op) >> space? >> add_expr.as(:right)).maybe.as(:comp)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Logical AND (higher precedence than OR)
|
93
|
-
rule(:logical_and_expr) do
|
94
|
-
comp_expr.as(:left) >>
|
95
|
-
(space? >> logical_and_op.as(:op) >> space? >> comp_expr.as(:right)).repeat.as(:ops)
|
96
|
-
end
|
97
|
-
|
98
|
-
# Logical OR (lowest precedence)
|
99
|
-
rule(:logical_or_expr) do
|
100
|
-
logical_and_expr.as(:left) >>
|
101
|
-
(space? >> logical_or_op.as(:op) >> space? >> logical_and_expr.as(:right)).repeat.as(:ops)
|
102
|
-
end
|
103
|
-
|
104
|
-
rule(:expression) { logical_or_expr }
|
105
|
-
|
106
|
-
# Input references: input.field or input.field.subfield
|
107
|
-
rule(:input_reference) do
|
108
|
-
str('input.') >> input_path.as(:input_ref)
|
109
|
-
end
|
110
|
-
|
111
|
-
rule(:input_path) do
|
112
|
-
identifier >> (str('.') >> identifier).repeat
|
113
|
-
end
|
114
|
-
|
115
|
-
# Declaration references: just identifier
|
116
|
-
rule(:declaration_reference) do
|
117
|
-
identifier.as(:decl_ref)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Input declarations
|
121
|
-
rule(:input_declaration) do
|
122
|
-
nested_array_declaration | simple_input_declaration
|
123
|
-
end
|
124
|
-
|
125
|
-
rule(:simple_input_declaration) do
|
126
|
-
ws? >> type_name.as(:type) >> space >> symbol.as(:name) >>
|
127
|
-
(str(',') >> ws? >> domain_spec).maybe.as(:domain) >> ws? >> newline?
|
128
|
-
end
|
129
|
-
|
130
|
-
rule(:nested_array_declaration) do
|
131
|
-
ws? >> str('array') >> space >> symbol.as(:name) >> space >> do_kw >> ws? >> newline? >>
|
132
|
-
(ws? >> input_declaration >> ws?).repeat.as(:nested_fields) >>
|
133
|
-
ws? >> end_kw >> ws? >> newline?
|
134
|
-
end
|
135
|
-
|
136
|
-
rule(:domain_spec) do
|
137
|
-
str('domain:') >> ws? >> domain_value.as(:domain_value)
|
138
|
-
end
|
139
|
-
|
140
|
-
rule(:domain_value) do
|
141
|
-
# Ranges: 1..10, 1...10, 0.0..100.0
|
142
|
-
range_value |
|
143
|
-
# Word arrays: %w[active inactive]
|
144
|
-
word_array_value |
|
145
|
-
# String arrays: ["active", "inactive"]
|
146
|
-
string_array_value
|
147
|
-
end
|
148
|
-
|
149
|
-
rule(:range_value) do
|
150
|
-
(float | integer) >> str('..') >> (float | integer)
|
151
|
-
end
|
152
|
-
|
153
|
-
rule(:word_array_value) do
|
154
|
-
str('%w[') >> (identifier >> space?).repeat.as(:words) >> str(']')
|
155
|
-
end
|
156
|
-
|
157
|
-
rule(:string_array_value) do
|
158
|
-
str('[') >> space? >>
|
159
|
-
(string_literal >> (str(',') >> space? >> string_literal).repeat).maybe >>
|
160
|
-
space? >> str(']')
|
161
|
-
end
|
162
|
-
|
163
|
-
# Value declarations
|
164
|
-
rule(:value_declaration) do
|
165
|
-
cascade_value_declaration | simple_value_declaration
|
166
|
-
end
|
167
|
-
|
168
|
-
rule(:simple_value_declaration) do
|
169
|
-
ws? >> value_kw.as(:type) >> space >> symbol.as(:name) >> str(',') >> ws? >>
|
170
|
-
expression.as(:expr) >> ws? >> newline?
|
171
|
-
end
|
172
|
-
|
173
|
-
rule(:cascade_value_declaration) do
|
174
|
-
ws? >> value_kw.as(:type) >> space >> symbol.as(:name) >> space >> do_kw >> ws? >> newline? >>
|
175
|
-
(ws? >> cascade_case >> ws?).repeat.as(:cases) >>
|
176
|
-
ws? >> end_kw >> ws? >> newline?
|
177
|
-
end
|
178
|
-
|
179
|
-
rule(:cascade_case) do
|
180
|
-
(ws? >> str('on') >> space >> identifier.as(:condition) >> str(',') >> ws? >>
|
181
|
-
expression.as(:result) >> ws? >> newline?) |
|
182
|
-
(ws? >> str('base') >> space >> expression.as(:base_result) >> ws? >> newline?)
|
183
|
-
end
|
184
|
-
|
185
|
-
# Trait declarations
|
186
|
-
rule(:trait_declaration) do
|
187
|
-
ws? >> trait_kw.as(:type) >> space >> symbol.as(:name) >> str(',') >> ws? >>
|
188
|
-
expression.as(:expr) >> ws? >> newline?
|
189
|
-
end
|
190
|
-
|
191
|
-
# Input block
|
192
|
-
rule(:input_block) do
|
193
|
-
ws? >> input_kw >> space >> do_kw >> ws? >> newline? >>
|
194
|
-
(ws? >> input_declaration >> ws?).repeat.as(:declarations) >>
|
195
|
-
ws? >> end_kw >> ws? >> newline?
|
196
|
-
end
|
197
|
-
|
198
|
-
# Schema structure
|
199
|
-
rule(:schema_body) do
|
200
|
-
input_block.as(:input) >>
|
201
|
-
(ws? >> (value_declaration | trait_declaration) >> ws?).repeat.as(:declarations)
|
202
|
-
end
|
203
|
-
|
204
|
-
rule(:schema) do
|
205
|
-
ws? >> schema_kw >> space >> do_kw >> ws? >> newline? >>
|
206
|
-
schema_body >>
|
207
|
-
ws? >> end_kw >> ws?
|
208
|
-
end
|
209
|
-
|
210
|
-
root(:schema)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
@@ -1,168 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'grammar'
|
4
|
-
require_relative 'transform'
|
5
|
-
require 'kumi/core/error_reporting'
|
6
|
-
|
7
|
-
module Kumi
|
8
|
-
module Parser
|
9
|
-
module TextParser
|
10
|
-
# Parslet-based parser with proper arithmetic operator precedence
|
11
|
-
class Parser
|
12
|
-
include Kumi::Core::ErrorReporting
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
@grammar = Grammar.new
|
16
|
-
@transform = Transform.new
|
17
|
-
end
|
18
|
-
|
19
|
-
def parse(text_dsl, source_file: '<parslet_parser>')
|
20
|
-
# Parse with Parslet grammar
|
21
|
-
parse_tree = @grammar.parse(text_dsl)
|
22
|
-
|
23
|
-
# Transform to AST
|
24
|
-
ast = @transform.apply(parse_tree)
|
25
|
-
|
26
|
-
# Post-process to create final Root node if needed
|
27
|
-
post_process(ast)
|
28
|
-
rescue Parslet::ParseFailed => e
|
29
|
-
raise_syntax_error(
|
30
|
-
"Parse error: #{e.parse_failure_cause.ascii_tree}",
|
31
|
-
location: create_location(source_file, 1, 1)
|
32
|
-
)
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def post_process(ast)
|
38
|
-
# If it's already a Root node, return it
|
39
|
-
return ast if ast.is_a?(Syntax::Root)
|
40
|
-
|
41
|
-
# If it's a hash with input and declarations, convert it
|
42
|
-
if ast.is_a?(Hash) && ast[:input] && ast[:declarations]
|
43
|
-
input_decls = ast[:input][:declarations] || []
|
44
|
-
input_decls = [input_decls] unless input_decls.is_a?(Array)
|
45
|
-
|
46
|
-
# Process input declarations
|
47
|
-
processed_input_decls = input_decls.map do |input_decl|
|
48
|
-
process_input_declaration(input_decl)
|
49
|
-
end
|
50
|
-
|
51
|
-
other_decls = ast[:declarations] || []
|
52
|
-
other_decls = [other_decls] unless other_decls.is_a?(Array)
|
53
|
-
|
54
|
-
# Convert remaining hash values to proper nodes
|
55
|
-
values = []
|
56
|
-
traits = []
|
57
|
-
|
58
|
-
other_decls.each do |decl|
|
59
|
-
if decl.is_a?(Hash) && decl[:name] && decl[:expr]
|
60
|
-
expr = process_expression(decl[:expr])
|
61
|
-
values << Syntax::ValueDeclaration.new(decl[:name], expr, loc: create_location('<parslet>', 1, 1))
|
62
|
-
elsif decl.is_a?(Syntax::ValueDeclaration)
|
63
|
-
# Need to process the expression if it's still a hash
|
64
|
-
if decl.expression.is_a?(Hash)
|
65
|
-
processed_expr = process_expression(decl.expression)
|
66
|
-
values << Syntax::ValueDeclaration.new(decl.name, processed_expr, loc: decl.loc)
|
67
|
-
else
|
68
|
-
values << decl
|
69
|
-
end
|
70
|
-
elsif decl.is_a?(Syntax::TraitDeclaration)
|
71
|
-
traits << decl
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
Syntax::Root.new(processed_input_decls, values, traits, loc: create_location('<parslet>', 1, 1))
|
76
|
-
else
|
77
|
-
ast
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def process_expression(expr)
|
82
|
-
# If it's already a proper AST node, return it
|
83
|
-
return expr unless expr.is_a?(Hash)
|
84
|
-
|
85
|
-
# Handle nested expression structures
|
86
|
-
if expr[:left] && expr[:ops]
|
87
|
-
left_expr = process_expression(expr[:left])
|
88
|
-
ops = expr[:ops] || []
|
89
|
-
ops = [ops] unless ops.is_a?(Array)
|
90
|
-
|
91
|
-
result = ops.inject(left_expr) do |left, op|
|
92
|
-
if op.is_a?(Hash) && op[:op] && op[:right]
|
93
|
-
op_name = op[:op].keys.first
|
94
|
-
right_expr = process_expression(op[:right])
|
95
|
-
Syntax::CallExpression.new(op_name, [left, right_expr], loc: create_location('<parslet>', 1, 1))
|
96
|
-
else
|
97
|
-
left
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Handle comparison
|
102
|
-
if expr[:comp] && expr[:comp][:op] && expr[:comp][:right]
|
103
|
-
comp = expr[:comp]
|
104
|
-
op_name = comp[:op].keys.first
|
105
|
-
right_expr = process_expression(comp[:right])
|
106
|
-
Syntax::CallExpression.new(op_name, [result, right_expr], loc: create_location('<parslet>', 1, 1))
|
107
|
-
else
|
108
|
-
result
|
109
|
-
end
|
110
|
-
else
|
111
|
-
expr
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def process_input_declaration(input_decl)
|
116
|
-
return input_decl if input_decl.is_a?(Syntax::InputDeclaration)
|
117
|
-
|
118
|
-
if input_decl.is_a?(Hash)
|
119
|
-
if input_decl[:nested_fields]
|
120
|
-
# Array input declaration
|
121
|
-
nested_fields = input_decl[:nested_fields] || []
|
122
|
-
nested_fields = [nested_fields] unless nested_fields.is_a?(Array)
|
123
|
-
|
124
|
-
processed_fields = nested_fields.map do |field|
|
125
|
-
if field.is_a?(Hash) && field[:type] && field[:name]
|
126
|
-
Syntax::InputDeclaration.new(
|
127
|
-
field[:name],
|
128
|
-
field[:domain],
|
129
|
-
field[:type].to_sym,
|
130
|
-
[],
|
131
|
-
loc: create_location('<parslet>', 1, 1)
|
132
|
-
)
|
133
|
-
else
|
134
|
-
field
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
Syntax::InputDeclaration.new(
|
139
|
-
input_decl[:name],
|
140
|
-
nil,
|
141
|
-
:array,
|
142
|
-
processed_fields,
|
143
|
-
loc: create_location('<parslet>', 1, 1)
|
144
|
-
)
|
145
|
-
elsif input_decl[:type] && input_decl[:name]
|
146
|
-
# Simple input declaration
|
147
|
-
Syntax::InputDeclaration.new(
|
148
|
-
input_decl[:name],
|
149
|
-
input_decl[:domain],
|
150
|
-
input_decl[:type].to_sym,
|
151
|
-
[],
|
152
|
-
loc: create_location('<parslet>', 1, 1)
|
153
|
-
)
|
154
|
-
else
|
155
|
-
input_decl
|
156
|
-
end
|
157
|
-
else
|
158
|
-
input_decl
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def create_location(file, line, column)
|
163
|
-
Syntax::Location.new(file: file, line: line, column: column)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
@@ -1,170 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'parslet'
|
4
|
-
require 'kumi/syntax/node'
|
5
|
-
require 'kumi/syntax/root'
|
6
|
-
require 'kumi/syntax/input_declaration'
|
7
|
-
require 'kumi/syntax/value_declaration'
|
8
|
-
require 'kumi/syntax/trait_declaration'
|
9
|
-
require 'kumi/syntax/call_expression'
|
10
|
-
require 'kumi/syntax/input_reference'
|
11
|
-
require 'kumi/syntax/input_element_reference'
|
12
|
-
require 'kumi/syntax/declaration_reference'
|
13
|
-
require 'kumi/syntax/literal'
|
14
|
-
|
15
|
-
module Kumi
|
16
|
-
module Parser
|
17
|
-
module TextParser
|
18
|
-
class Transform < Parslet::Transform
|
19
|
-
LOC = Kumi::Syntax::Location.new(file: '<parslet_parser>', line: 1, column: 1)
|
20
|
-
|
21
|
-
# Literals
|
22
|
-
rule(integer: simple(:x)) { Kumi::Syntax::Literal.new(x.to_i, loc: LOC) }
|
23
|
-
rule(float: simple(:x)) { Kumi::Syntax::Literal.new(x.to_f, loc: LOC) }
|
24
|
-
rule(string: simple(:x)) { Kumi::Syntax::Literal.new(x.to_s, loc: LOC) }
|
25
|
-
rule(true: simple(:_)) { Kumi::Syntax::Literal.new(true, loc: LOC) }
|
26
|
-
rule(false: simple(:_)) { Kumi::Syntax::Literal.new(false, loc: LOC) }
|
27
|
-
|
28
|
-
# Symbols
|
29
|
-
rule(symbol: simple(:name)) { name.to_sym }
|
30
|
-
|
31
|
-
# Input and declaration references
|
32
|
-
rule(input_ref: simple(:path)) do
|
33
|
-
# Handle multi-level paths like "items.price"
|
34
|
-
path_parts = path.to_s.split('.')
|
35
|
-
if path_parts.length == 1
|
36
|
-
Kumi::Syntax::InputReference.new(path_parts[0].to_sym, loc: LOC)
|
37
|
-
else
|
38
|
-
Kumi::Syntax::InputElementReference.new(path_parts.map(&:to_sym), loc: LOC)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
rule(decl_ref: simple(:name)) { Kumi::Syntax::DeclarationReference.new(name.to_sym, loc: LOC) }
|
43
|
-
|
44
|
-
# Function calls
|
45
|
-
rule(fn_name: simple(:name), args: sequence(:args)) do
|
46
|
-
Kumi::Syntax::CallExpression.new(name, args, loc: LOC)
|
47
|
-
end
|
48
|
-
|
49
|
-
rule(fn_name: simple(:name), args: []) do
|
50
|
-
Kumi::Syntax::CallExpression.new(name, [], loc: LOC)
|
51
|
-
end
|
52
|
-
|
53
|
-
# Arithmetic expressions with left-associativity
|
54
|
-
rule(left: simple(:l), ops: sequence(:operations)) do
|
55
|
-
operations.inject(l) do |left_expr, op|
|
56
|
-
op_name = op[:op].keys.first
|
57
|
-
Kumi::Syntax::CallExpression.new(op_name, [left_expr, op[:right]], loc: LOC)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
rule(left: simple(:l), ops: []) { l }
|
62
|
-
|
63
|
-
# Comparison expressions
|
64
|
-
rule(left: simple(:l), comp: simple(:comparison)) do
|
65
|
-
if comparison && comparison[:op] && comparison[:right]
|
66
|
-
op_name = comparison[:op].keys.first
|
67
|
-
Kumi::Syntax::CallExpression.new(op_name, [l, comparison[:right]], loc: LOC)
|
68
|
-
else
|
69
|
-
l
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
rule(left: simple(:l), comp: nil) { l }
|
74
|
-
|
75
|
-
# Simple input declarations
|
76
|
-
rule(type: simple(:type), name: simple(:name)) do
|
77
|
-
Kumi::Syntax::InputDeclaration.new(name, nil, type.to_sym, [], loc: LOC)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Nested array declarations
|
81
|
-
rule(name: simple(:name), nested_fields: sequence(:fields)) do
|
82
|
-
# Transform nested field hashes to InputDeclaration objects
|
83
|
-
transformed_fields = fields.map do |field|
|
84
|
-
if field.is_a?(Hash) && field[:type] && field[:name]
|
85
|
-
Kumi::Syntax::InputDeclaration.new(
|
86
|
-
field[:name],
|
87
|
-
field[:domain],
|
88
|
-
field[:type].to_sym,
|
89
|
-
[],
|
90
|
-
loc: LOC
|
91
|
-
)
|
92
|
-
else
|
93
|
-
field
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
# Create an array input declaration with nested fields
|
98
|
-
Kumi::Syntax::InputDeclaration.new(
|
99
|
-
name,
|
100
|
-
nil,
|
101
|
-
:array,
|
102
|
-
transformed_fields,
|
103
|
-
loc: LOC
|
104
|
-
)
|
105
|
-
end
|
106
|
-
|
107
|
-
rule(name: simple(:name), nested_fields: simple(:field)) do
|
108
|
-
# Single nested field case
|
109
|
-
transformed_field = if field.is_a?(Hash) && field[:type] && field[:name]
|
110
|
-
Kumi::Syntax::InputDeclaration.new(
|
111
|
-
field[:name],
|
112
|
-
field[:domain],
|
113
|
-
field[:type].to_sym,
|
114
|
-
[],
|
115
|
-
loc: LOC
|
116
|
-
)
|
117
|
-
else
|
118
|
-
field
|
119
|
-
end
|
120
|
-
|
121
|
-
Kumi::Syntax::InputDeclaration.new(
|
122
|
-
name,
|
123
|
-
nil,
|
124
|
-
:array,
|
125
|
-
[transformed_field],
|
126
|
-
loc: LOC
|
127
|
-
)
|
128
|
-
end
|
129
|
-
|
130
|
-
rule(type: simple(:type), name: simple(:name), expr: simple(:expr)) do
|
131
|
-
# Differentiate between value and trait declarations based on type
|
132
|
-
if type.to_s == 'value'
|
133
|
-
Kumi::Syntax::ValueDeclaration.new(name, expr, loc: LOC)
|
134
|
-
elsif type.to_s == 'trait'
|
135
|
-
Kumi::Syntax::TraitDeclaration.new(name, expr, loc: LOC)
|
136
|
-
else
|
137
|
-
# Fallback - shouldn't happen
|
138
|
-
Kumi::Syntax::ValueDeclaration.new(name, expr, loc: LOC)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Handle the intermediate case before the expression gets fully transformed
|
143
|
-
rule(type: simple(:type), name: { symbol: simple(:name) }, expr: simple(:expr)) do
|
144
|
-
# Differentiate between value and trait declarations based on type
|
145
|
-
if type.to_s == 'value'
|
146
|
-
Kumi::Syntax::ValueDeclaration.new(name.to_sym, expr, loc: LOC)
|
147
|
-
elsif type.to_s == 'trait'
|
148
|
-
Kumi::Syntax::TraitDeclaration.new(name.to_sym, expr, loc: LOC)
|
149
|
-
else
|
150
|
-
# Fallback - shouldn't happen
|
151
|
-
Kumi::Syntax::ValueDeclaration.new(name.to_sym, expr, loc: LOC)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
# Schema structure - convert the hash to Root node
|
156
|
-
rule(input: { declarations: sequence(:input_decls) }, declarations: sequence(:other_decls)) do
|
157
|
-
values = other_decls.select { |d| d.is_a?(Kumi::Syntax::ValueDeclaration) }
|
158
|
-
traits = other_decls.select { |d| d.is_a?(Kumi::Syntax::TraitDeclaration) }
|
159
|
-
Kumi::Syntax::Root.new(input_decls, values, traits, loc: LOC)
|
160
|
-
end
|
161
|
-
|
162
|
-
rule(input: { declarations: simple(:input_decl) }, declarations: sequence(:other_decls)) do
|
163
|
-
values = other_decls.select { |d| d.is_a?(Kumi::Syntax::ValueDeclaration) }
|
164
|
-
traits = other_decls.select { |d| d.is_a?(Kumi::Syntax::TraitDeclaration) }
|
165
|
-
Kumi::Syntax::Root.new([input_decl], values, traits, loc: LOC)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
data/lib/kumi/parser.rb
DELETED
data/test_basic.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# Basic functionality test for kumi-parser
|
3
|
-
|
4
|
-
require_relative 'lib/kumi/parser'
|
5
|
-
|
6
|
-
schema_text = <<~SCHEMA
|
7
|
-
schema do
|
8
|
-
input do
|
9
|
-
integer :age
|
10
|
-
end
|
11
|
-
#{' '}
|
12
|
-
trait :adult, input.age >= 18
|
13
|
-
value :bonus, 100
|
14
|
-
end
|
15
|
-
SCHEMA
|
16
|
-
|
17
|
-
puts 'Testing kumi-parser...'
|
18
|
-
puts '=' * 40
|
19
|
-
|
20
|
-
begin
|
21
|
-
# Test validation
|
22
|
-
puts '1. Testing validation...'
|
23
|
-
diagnostics = Kumi::Parser::TextParser.validate(schema_text)
|
24
|
-
puts " ✅ Validation: #{diagnostics.empty? ? 'PASSED' : 'FAILED'}"
|
25
|
-
|
26
|
-
unless diagnostics.empty?
|
27
|
-
diagnostics.to_a.each do |d|
|
28
|
-
puts " Error: Line #{d.line}, Column #{d.column}: #{d.message}"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Test parsing if validation passed
|
33
|
-
if diagnostics.empty?
|
34
|
-
puts '2. Testing parsing...'
|
35
|
-
ast = Kumi::Parser::TextParser.parse(schema_text)
|
36
|
-
puts " ✅ Parsing: #{ast ? 'PASSED' : 'FAILED'}"
|
37
|
-
puts " AST type: #{ast.class}"
|
38
|
-
end
|
39
|
-
|
40
|
-
puts "\n🎉 Basic functionality test completed!"
|
41
|
-
rescue StandardError => e
|
42
|
-
puts "❌ Error: #{e.message}"
|
43
|
-
puts e.backtrace.first(5)
|
44
|
-
end
|