kumi-parser 0.0.12 → 0.0.14
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/README.md +9 -3
- data/lib/kumi/parser/direct_parser.rb +90 -124
- data/lib/kumi/parser/smart_tokenizer.rb +5 -3
- data/lib/kumi/parser/token_metadata.rb +8 -1
- data/lib/kumi/parser/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83ae18a38c78f9513121f579f024309c162ebbc1fdf909aecbe8aceff04e100a
|
4
|
+
data.tar.gz: 8bfab6fb7250f7e39289ceb07b84ae556958a77be19b66fe253b19c7284359ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39f5759930377f813b10d8ce148dc5f26bd9a6b4a21a577b91bbb24fe7d47d923cf3967f25454666bb9c8c23f0190909f6283fe629a7bd935d51ca9033c25047
|
7
|
+
data.tar.gz: d06bd58249f51ef7edafb51f291c2b18720ea259b3a375b9624ac593631f7786aa2c920da92bea99ec1ad27fc517c71c9b7bbd41e7626faa814949e317b56f29
|
data/README.md
CHANGED
@@ -58,7 +58,15 @@ end
|
|
58
58
|
|
59
59
|
**Function calls**: `fn(:name, arg1, arg2, ...)`
|
60
60
|
**Operators**: `+` `-` `*` `/` `%` `>` `<` `>=` `<=` `==` `!=` `&` `|`
|
61
|
-
**References**: `input.field`, `value_name`, `array[index]`
|
61
|
+
**References**: `input.field`, `value_name`, `array[index]`
|
62
|
+
**Strings**: Both `"double"` and `'single'` quotes supported
|
63
|
+
**Element syntax**: `element :type, :name` for array element specifications
|
64
|
+
|
65
|
+
## Ruby DSL Differences
|
66
|
+
|
67
|
+
**String concatenation**: Ruby DSL evaluates `"Hello" + "World"` → `Literal("HelloWorld")`, text parser → `CallExpression(:add, [...])`.
|
68
|
+
|
69
|
+
**Semantically equivalent** - both should execute identically.
|
62
70
|
|
63
71
|
## Architecture
|
64
72
|
|
@@ -66,8 +74,6 @@ end
|
|
66
74
|
- `direct_ast_parser.rb` - Recursive descent parser, direct AST construction
|
67
75
|
- `token_metadata.rb` - Token types, precedence, and semantic hints
|
68
76
|
|
69
|
-
See `docs/` for technical details.
|
70
|
-
|
71
77
|
## License
|
72
78
|
|
73
79
|
MIT
|
@@ -25,7 +25,6 @@ module Kumi
|
|
25
25
|
def peek_token(offset = 1)
|
26
26
|
peek_pos = @pos + offset
|
27
27
|
return @tokens.last if peek_pos >= @tokens.length # Return EOF
|
28
|
-
|
29
28
|
@tokens[peek_pos]
|
30
29
|
end
|
31
30
|
|
@@ -72,7 +71,6 @@ module Kumi
|
|
72
71
|
|
73
72
|
expect_token(:end)
|
74
73
|
|
75
|
-
# Construct Root with exact AST.md structure
|
76
74
|
Kumi::Syntax::Root.new(
|
77
75
|
input_declarations,
|
78
76
|
value_declarations, # values
|
@@ -91,9 +89,7 @@ module Kumi
|
|
91
89
|
|
92
90
|
until %i[end eof].include?(current_token.type)
|
93
91
|
break unless current_token.metadata[:category] == :type_keyword
|
94
|
-
|
95
92
|
declarations << parse_input_declaration
|
96
|
-
|
97
93
|
skip_comments_and_newlines
|
98
94
|
end
|
99
95
|
|
@@ -101,18 +97,31 @@ module Kumi
|
|
101
97
|
declarations
|
102
98
|
end
|
103
99
|
|
104
|
-
# Input declaration: 'integer :name' or 'array :items do ... end'
|
100
|
+
# Input declaration: 'integer :name' or 'array :items do ... end' or 'element :type, :name'
|
101
|
+
#
|
102
|
+
# IMPORTANT: For array nodes with a block, this sets the node's access_mode:
|
103
|
+
# - :element if the block contains exactly one child introduced by `element`
|
104
|
+
# - :field otherwise
|
105
105
|
def parse_input_declaration
|
106
106
|
type_token = current_token
|
107
|
-
|
108
|
-
if type_token.metadata[:category] != :type_keyword
|
107
|
+
unless type_token.metadata[:category] == :type_keyword
|
109
108
|
raise_parse_error("Expected type keyword, got #{type_token.type}")
|
110
109
|
end
|
111
|
-
|
112
110
|
advance
|
113
|
-
name_token = expect_token(:symbol)
|
114
111
|
|
115
|
-
#
|
112
|
+
# element :type, :name (syntactic sugar: the child was declared via `element`)
|
113
|
+
declared_with_element = (type_token.metadata[:type_name] == :element)
|
114
|
+
if declared_with_element
|
115
|
+
element_type_token = expect_token(:symbol)
|
116
|
+
expect_token(:comma)
|
117
|
+
name_token = expect_token(:symbol)
|
118
|
+
actual_type = element_type_token.value
|
119
|
+
else
|
120
|
+
name_token = expect_token(:symbol)
|
121
|
+
actual_type = type_token.metadata[:type_name]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Optional: ', domain: ...'
|
116
125
|
domain = nil
|
117
126
|
if current_token.type == :comma
|
118
127
|
advance
|
@@ -121,102 +130,112 @@ module Kumi
|
|
121
130
|
expect_token(:colon)
|
122
131
|
domain = parse_domain_specification
|
123
132
|
else
|
124
|
-
# Put comma back for other parsers
|
125
133
|
@pos -= 1
|
126
134
|
end
|
127
135
|
end
|
128
136
|
|
129
|
-
#
|
137
|
+
# Parse nested declarations for block forms
|
130
138
|
children = []
|
131
|
-
|
139
|
+
any_element_children = false
|
140
|
+
any_field_children = false
|
141
|
+
|
142
|
+
if %i[array hash element].include?(actual_type) && current_token.type == :do
|
132
143
|
advance # consume 'do'
|
133
144
|
skip_comments_and_newlines
|
134
145
|
|
135
146
|
until %i[end eof].include?(current_token.type)
|
136
147
|
break unless current_token.metadata[:category] == :type_keyword
|
137
148
|
|
138
|
-
|
149
|
+
# Syntactic decision (NO counting): is this child introduced by `element`?
|
150
|
+
child_is_element_keyword = (current_token.metadata[:type_name] == :element)
|
151
|
+
any_element_children ||= child_is_element_keyword
|
152
|
+
any_field_children ||= !child_is_element_keyword
|
139
153
|
|
154
|
+
children << parse_input_declaration
|
140
155
|
skip_comments_and_newlines
|
141
156
|
end
|
142
157
|
|
143
158
|
expect_token(:end)
|
159
|
+
|
160
|
+
# For array blocks, access_mode derives strictly from syntax:
|
161
|
+
# - :element if ANY direct child used `element`
|
162
|
+
# - :field if NONE used `element`
|
163
|
+
# Mixing is invalid.
|
164
|
+
if actual_type == :array
|
165
|
+
if any_element_children && any_field_children
|
166
|
+
raise_parse_error("array :#{name_token.value} mixes `element` and field children; choose one style")
|
167
|
+
end
|
168
|
+
access_mode = any_element_children ? :element : :field
|
169
|
+
else
|
170
|
+
access_mode = :field # objects/hashes with blocks behave like field containers
|
171
|
+
end
|
172
|
+
else
|
173
|
+
access_mode = nil # leaves carry no access_mode
|
144
174
|
end
|
145
175
|
|
146
176
|
if children.empty?
|
147
177
|
Kumi::Syntax::InputDeclaration.new(
|
148
178
|
name_token.value,
|
149
179
|
domain,
|
150
|
-
|
180
|
+
actual_type,
|
151
181
|
children,
|
152
182
|
loc: type_token.location
|
153
183
|
)
|
154
184
|
else
|
185
|
+
# 5th positional arg in your existing ctor is access_mode
|
155
186
|
Kumi::Syntax::InputDeclaration.new(
|
156
187
|
name_token.value,
|
157
188
|
domain,
|
158
|
-
|
189
|
+
actual_type,
|
159
190
|
children,
|
160
|
-
:field,
|
191
|
+
access_mode || :field,
|
161
192
|
loc: type_token.location
|
162
193
|
)
|
163
194
|
end
|
164
195
|
end
|
165
196
|
|
166
197
|
def parse_domain_specification
|
167
|
-
# Parse domain specifications: domain: ["x", "y"], domain: [1, 2, 3], domain: 1..10, domain: 1...10
|
168
198
|
case current_token.type
|
169
199
|
when :lbracket
|
170
|
-
# Array domain: ["a", "b", "c"] or [1, 2, 3]
|
171
200
|
array_expr = parse_array_literal
|
172
|
-
# Convert ArrayExpression to Ruby Array for analyzer compatibility
|
173
201
|
convert_array_expression_to_ruby_array(array_expr)
|
174
202
|
when :integer, :float
|
175
|
-
# Range domain: 1..10 or 1...10
|
176
203
|
parse_range_domain
|
177
204
|
else
|
178
|
-
# Skip unknown domain specs for now
|
179
205
|
advance until %i[comma newline eof end].include?(current_token.type)
|
180
206
|
nil
|
181
207
|
end
|
182
208
|
end
|
183
209
|
|
184
210
|
def parse_range_domain
|
185
|
-
# Parse numeric ranges like 1..10 or 0.0...100.0
|
186
211
|
start_token = current_token
|
187
212
|
start_value = start_token.type == :integer ? start_token.value.to_i : start_token.value.to_f
|
188
213
|
advance
|
189
214
|
|
190
215
|
case current_token.type
|
191
216
|
when :dot_dot
|
192
|
-
|
193
|
-
advance # consume ..
|
217
|
+
advance
|
194
218
|
end_token = current_token
|
195
219
|
end_value = end_token.type == :integer ? end_token.value.to_i : end_token.value.to_f
|
196
220
|
advance
|
197
221
|
(start_value..end_value)
|
198
222
|
when :dot_dot_dot
|
199
|
-
|
200
|
-
advance # consume ...
|
223
|
+
advance
|
201
224
|
end_token = current_token
|
202
225
|
end_value = end_token.type == :integer ? end_token.value.to_i : end_token.value.to_f
|
203
226
|
advance
|
204
227
|
(start_value...end_value)
|
205
228
|
else
|
206
|
-
# Just a single number, treat as single-element array
|
207
229
|
[start_value]
|
208
230
|
end
|
209
231
|
end
|
210
232
|
|
211
233
|
def convert_array_expression_to_ruby_array(array_expr)
|
212
234
|
return nil unless array_expr.is_a?(Kumi::Syntax::ArrayExpression)
|
213
|
-
|
214
235
|
array_expr.elements.map do |element|
|
215
236
|
if element.is_a?(Kumi::Syntax::Literal)
|
216
237
|
element.value
|
217
238
|
else
|
218
|
-
# For non-literal elements, we'd need more complex evaluation
|
219
|
-
# For now, just return the element as-is
|
220
239
|
element
|
221
240
|
end
|
222
241
|
end
|
@@ -228,10 +247,8 @@ module Kumi
|
|
228
247
|
name_token = expect_token(:symbol)
|
229
248
|
|
230
249
|
if current_token.type == :do
|
231
|
-
# Cascade expression: value :name do ... end
|
232
250
|
expression = parse_cascade_expression
|
233
251
|
else
|
234
|
-
# Simple expression: value :name, expression
|
235
252
|
expect_token(:comma)
|
236
253
|
expression = parse_expression
|
237
254
|
end
|
@@ -261,55 +278,42 @@ module Kumi
|
|
261
278
|
def parse_cascade_expression
|
262
279
|
start_token = expect_token(:do)
|
263
280
|
cases = []
|
264
|
-
|
265
281
|
skip_comments_and_newlines
|
266
282
|
while %i[on base].include?(current_token.type)
|
267
283
|
cases << parse_case_expression
|
268
284
|
skip_comments_and_newlines
|
269
285
|
end
|
270
|
-
|
271
286
|
expect_token(:end)
|
272
|
-
|
273
287
|
Kumi::Syntax::CascadeExpression.new(cases, loc: start_token.location)
|
274
288
|
end
|
275
289
|
|
276
|
-
# Case expression: 'on condition1, condition2, ..., result' or 'base result'
|
277
290
|
def parse_case_expression
|
278
291
|
case current_token.type
|
279
292
|
when :on
|
280
293
|
on_token = advance_and_return_token
|
281
|
-
|
282
|
-
# Parse all comma-separated expressions
|
294
|
+
|
283
295
|
expressions = []
|
284
296
|
expressions << parse_expression
|
285
|
-
|
286
|
-
# Continue parsing comma-separated expressions until end of case
|
287
297
|
while current_token.type == :comma
|
288
|
-
advance
|
298
|
+
advance
|
289
299
|
expressions << parse_expression
|
290
300
|
end
|
291
|
-
|
292
|
-
# Last expression is the result, all others are conditions
|
301
|
+
|
293
302
|
result = expressions.pop
|
294
303
|
conditions = expressions
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
# Multiple conditions: combine with cascade_and
|
303
|
-
condition = Kumi::Syntax::CallExpression.new(:cascade_and, conditions, loc: on_token.location)
|
304
|
-
end
|
304
|
+
condition =
|
305
|
+
if conditions.length == 1
|
306
|
+
c = conditions[0]
|
307
|
+
simple_trait_reference?(c) ? wrap_condition_in_all(c) : c
|
308
|
+
else
|
309
|
+
Kumi::Syntax::CallExpression.new(:cascade_and, conditions, loc: on_token.location)
|
310
|
+
end
|
305
311
|
|
306
312
|
Kumi::Syntax::CaseExpression.new(condition, result, loc: on_token.location)
|
307
313
|
|
308
314
|
when :base
|
309
315
|
base_token = advance_and_return_token
|
310
316
|
result = parse_expression
|
311
|
-
|
312
|
-
# Base case has condition = true
|
313
317
|
true_literal = Kumi::Syntax::Literal.new(true, loc: base_token.location)
|
314
318
|
Kumi::Syntax::CaseExpression.new(true_literal, result, loc: base_token.location)
|
315
319
|
|
@@ -324,26 +328,22 @@ module Kumi
|
|
324
328
|
token
|
325
329
|
end
|
326
330
|
|
327
|
-
#
|
331
|
+
# Pratt parser for expressions
|
328
332
|
def parse_expression(min_precedence = 0)
|
329
333
|
left = parse_primary_expression
|
330
|
-
|
331
|
-
# Skip whitespace before checking for operators
|
332
334
|
skip_comments_and_newlines
|
333
335
|
|
334
336
|
while current_token.operator? && current_token.precedence >= min_precedence
|
335
337
|
operator_token = current_token
|
336
338
|
advance
|
337
|
-
|
338
|
-
# Skip whitespace after operator
|
339
339
|
skip_comments_and_newlines
|
340
340
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
341
|
+
next_min_precedence =
|
342
|
+
if operator_token.left_associative?
|
343
|
+
operator_token.precedence + 1
|
344
|
+
else
|
345
|
+
operator_token.precedence
|
346
|
+
end
|
347
347
|
|
348
348
|
right = parse_expression(next_min_precedence)
|
349
349
|
left = Kumi::Syntax::CallExpression.new(
|
@@ -351,8 +351,6 @@ module Kumi
|
|
351
351
|
[left, right],
|
352
352
|
loc: operator_token.location
|
353
353
|
)
|
354
|
-
|
355
|
-
# Skip whitespace before checking for next operator
|
356
354
|
skip_comments_and_newlines
|
357
355
|
end
|
358
356
|
|
@@ -364,7 +362,6 @@ module Kumi
|
|
364
362
|
|
365
363
|
case token.type
|
366
364
|
when :integer, :float, :string, :boolean, :constant
|
367
|
-
# Direct AST construction using token metadata
|
368
365
|
value = convert_literal_value(token)
|
369
366
|
advance
|
370
367
|
Kumi::Syntax::Literal.new(value, loc: token.location)
|
@@ -382,7 +379,6 @@ module Kumi
|
|
382
379
|
end
|
383
380
|
|
384
381
|
when :input
|
385
|
-
# Handle input references in expressions (input.field)
|
386
382
|
if peek_token.type == :dot
|
387
383
|
parse_input_reference_from_input_token
|
388
384
|
else
|
@@ -390,7 +386,7 @@ module Kumi
|
|
390
386
|
end
|
391
387
|
|
392
388
|
when :lparen
|
393
|
-
advance
|
389
|
+
advance
|
394
390
|
expr = parse_expression
|
395
391
|
expect_token(:rparen)
|
396
392
|
expr
|
@@ -402,11 +398,9 @@ module Kumi
|
|
402
398
|
parse_function_call_from_fn_token
|
403
399
|
|
404
400
|
when :subtract
|
405
|
-
|
406
|
-
advance # consume '-'
|
401
|
+
advance
|
407
402
|
skip_comments_and_newlines
|
408
403
|
operand = parse_primary_expression
|
409
|
-
# Convert to subtraction from zero: (subtract 0 operand)
|
410
404
|
Kumi::Syntax::CallExpression.new(
|
411
405
|
:subtract,
|
412
406
|
[Kumi::Syntax::Literal.new(0, loc: token.location), operand],
|
@@ -414,7 +408,6 @@ module Kumi
|
|
414
408
|
)
|
415
409
|
|
416
410
|
when :newline, :comment
|
417
|
-
# Skip newlines and comments in expressions
|
418
411
|
skip_comments_and_newlines
|
419
412
|
parse_primary_expression
|
420
413
|
|
@@ -428,10 +421,8 @@ module Kumi
|
|
428
421
|
expect_token(:dot)
|
429
422
|
|
430
423
|
path = [expect_field_name_token.to_sym]
|
431
|
-
|
432
|
-
# Handle nested access: input.field.subfield
|
433
424
|
while current_token.type == :dot
|
434
|
-
advance
|
425
|
+
advance
|
435
426
|
path << expect_field_name_token.to_sym
|
436
427
|
end
|
437
428
|
|
@@ -443,14 +434,12 @@ module Kumi
|
|
443
434
|
end
|
444
435
|
|
445
436
|
def parse_input_reference_from_input_token
|
446
|
-
input_token = expect_token(:input)
|
437
|
+
input_token = expect_token(:input)
|
447
438
|
expect_token(:dot)
|
448
439
|
|
449
440
|
path = [expect_field_name_token.to_sym]
|
450
|
-
|
451
|
-
# Handle nested access: input.field.subfield
|
452
441
|
while current_token.type == :dot
|
453
|
-
advance
|
442
|
+
advance
|
454
443
|
path << expect_field_name_token.to_sym
|
455
444
|
end
|
456
445
|
|
@@ -468,54 +457,40 @@ module Kumi
|
|
468
457
|
expect_token(:rbracket)
|
469
458
|
|
470
459
|
base_ref = Kumi::Syntax::DeclarationReference.new(name_token.value.to_sym, loc: name_token.location)
|
471
|
-
Kumi::Syntax::CallExpression.new(
|
472
|
-
:at,
|
473
|
-
[base_ref, index_expr],
|
474
|
-
loc: name_token.location
|
475
|
-
)
|
460
|
+
Kumi::Syntax::CallExpression.new(:at, [base_ref, index_expr], loc: name_token.location)
|
476
461
|
end
|
477
462
|
|
478
463
|
def parse_function_call
|
479
|
-
fn_token = expect_token(:identifier)
|
480
|
-
|
464
|
+
fn_token = expect_token(:identifier)
|
481
465
|
if current_token.type == :lparen
|
482
|
-
|
483
|
-
advance # consume '('
|
466
|
+
advance
|
484
467
|
fn_name_token = expect_token(:symbol)
|
485
468
|
fn_name = fn_name_token.value
|
486
|
-
|
487
469
|
args = []
|
488
470
|
while current_token.type == :comma
|
489
|
-
advance
|
471
|
+
advance
|
490
472
|
args << parse_expression
|
491
473
|
end
|
492
|
-
|
493
474
|
expect_token(:rparen)
|
494
475
|
Kumi::Syntax::CallExpression.new(fn_name, args, loc: fn_name_token.location)
|
495
|
-
|
496
476
|
else
|
497
477
|
raise_parse_error("Expected '(' after 'fn'")
|
498
478
|
end
|
499
479
|
end
|
500
480
|
|
501
481
|
def parse_function_call_from_fn_token
|
502
|
-
fn_token = expect_token(:fn)
|
503
|
-
|
482
|
+
fn_token = expect_token(:fn)
|
504
483
|
if current_token.type == :lparen
|
505
|
-
|
506
|
-
advance # consume '('
|
484
|
+
advance
|
507
485
|
fn_name_token = expect_token(:symbol)
|
508
486
|
fn_name = fn_name_token.value
|
509
|
-
|
510
487
|
args = []
|
511
488
|
while current_token.type == :comma
|
512
|
-
advance
|
489
|
+
advance
|
513
490
|
args << parse_expression
|
514
491
|
end
|
515
|
-
|
516
492
|
expect_token(:rparen)
|
517
493
|
Kumi::Syntax::CallExpression.new(fn_name, args, loc: fn_name_token.location)
|
518
|
-
|
519
494
|
else
|
520
495
|
raise_parse_error("Expected '(' after 'fn'")
|
521
496
|
end
|
@@ -523,40 +498,36 @@ module Kumi
|
|
523
498
|
|
524
499
|
def parse_argument_list
|
525
500
|
args = []
|
526
|
-
|
527
501
|
unless current_token.type == :rparen
|
528
502
|
args << parse_expression
|
529
503
|
while current_token.type == :comma
|
530
|
-
advance
|
504
|
+
advance
|
531
505
|
args << parse_expression
|
532
506
|
end
|
533
507
|
end
|
534
|
-
|
535
508
|
args
|
536
509
|
end
|
537
510
|
|
538
511
|
def parse_array_literal
|
539
512
|
start_token = expect_token(:lbracket)
|
540
513
|
elements = []
|
541
|
-
|
542
514
|
unless current_token.type == :rbracket
|
543
515
|
elements << parse_expression
|
544
516
|
while current_token.type == :comma
|
545
|
-
advance
|
517
|
+
advance
|
546
518
|
elements << parse_expression unless current_token.type == :rbracket
|
547
519
|
end
|
548
520
|
end
|
549
|
-
|
550
521
|
expect_token(:rbracket)
|
551
522
|
Kumi::Syntax::ArrayExpression.new(elements, loc: start_token.location)
|
552
523
|
end
|
553
524
|
|
554
525
|
def convert_literal_value(token)
|
555
526
|
case token.type
|
556
|
-
when :integer
|
557
|
-
when :float
|
558
|
-
when :string
|
559
|
-
when :boolean
|
527
|
+
when :integer then token.value.gsub('_', '').to_i
|
528
|
+
when :float then token.value.gsub('_', '').to_f
|
529
|
+
when :string then token.value
|
530
|
+
when :boolean then token.value == 'true'
|
560
531
|
when :constant
|
561
532
|
case token.value
|
562
533
|
when 'Float::INFINITY' then Float::INFINITY
|
@@ -567,7 +538,6 @@ module Kumi
|
|
567
538
|
end
|
568
539
|
|
569
540
|
def expect_field_name_token
|
570
|
-
# Field names can be identifiers or keywords (like 'base', 'input', etc.)
|
571
541
|
token = current_token
|
572
542
|
if token.identifier? || token.keyword?
|
573
543
|
advance
|
@@ -582,28 +552,24 @@ module Kumi
|
|
582
552
|
raise Errors::ParseError.new(message, token: current_token)
|
583
553
|
end
|
584
554
|
|
585
|
-
# Helper method to check if condition is a simple trait reference
|
586
555
|
def simple_trait_reference?(condition)
|
587
556
|
condition.is_a?(Kumi::Syntax::DeclarationReference)
|
588
557
|
end
|
589
558
|
|
590
|
-
|
591
|
-
# Helper method to wrap condition in cascade_and function call
|
592
559
|
def wrap_condition_in_all(condition)
|
593
560
|
Kumi::Syntax::CallExpression.new(:cascade_and, [condition], loc: condition.loc)
|
594
561
|
end
|
595
562
|
|
596
|
-
# Map operator token types to function names for Ruby DSL compatibility
|
597
563
|
def map_operator_token_to_function_name(token_type)
|
598
564
|
case token_type
|
599
|
-
when :eq
|
600
|
-
when :ne
|
601
|
-
when :gt
|
602
|
-
when :lt
|
565
|
+
when :eq then :==
|
566
|
+
when :ne then :!=
|
567
|
+
when :gt then :>
|
568
|
+
when :lt then :<
|
603
569
|
when :gte then :>=
|
604
570
|
when :lte then :<=
|
605
571
|
when :and then :and
|
606
|
-
when :or
|
572
|
+
when :or then :or
|
607
573
|
when :exponent then :power
|
608
574
|
else token_type
|
609
575
|
end
|
@@ -26,7 +26,7 @@ module Kumi
|
|
26
26
|
when nil then break
|
27
27
|
when "\n" then handle_newline
|
28
28
|
when '#' then consume_comment
|
29
|
-
when '"' then consume_string
|
29
|
+
when '"', "'" then consume_string
|
30
30
|
when /\d/ then consume_number
|
31
31
|
when '-'
|
32
32
|
if peek_char && peek_char.match?(/\d/)
|
@@ -95,10 +95,11 @@ module Kumi
|
|
95
95
|
|
96
96
|
def consume_string
|
97
97
|
start_column = @column
|
98
|
+
quote_char = current_char # Remember which quote type we're using
|
98
99
|
advance # skip opening quote
|
99
100
|
|
100
101
|
string_content = ''
|
101
|
-
while current_char && current_char !=
|
102
|
+
while current_char && current_char != quote_char
|
102
103
|
if current_char == '\\'
|
103
104
|
advance
|
104
105
|
# Handle escape sequences
|
@@ -108,6 +109,7 @@ module Kumi
|
|
108
109
|
when 'r' then string_content += "\r"
|
109
110
|
when '\\' then string_content += '\\'
|
110
111
|
when '"' then string_content += '"'
|
112
|
+
when "'" then string_content += "'"
|
111
113
|
else
|
112
114
|
string_content += current_char if current_char
|
113
115
|
end
|
@@ -117,7 +119,7 @@ module Kumi
|
|
117
119
|
advance
|
118
120
|
end
|
119
121
|
|
120
|
-
raise_tokenizer_error('Unterminated string literal') if current_char !=
|
122
|
+
raise_tokenizer_error('Unterminated string literal') if current_char != quote_char
|
121
123
|
|
122
124
|
advance # skip closing quote
|
123
125
|
|
@@ -32,6 +32,7 @@ module Kumi
|
|
32
32
|
BOOLEAN_TYPE = :boolean_type # boolean
|
33
33
|
ANY_TYPE = :any_type # any
|
34
34
|
ARRAY_TYPE = :array_type # array
|
35
|
+
ELEMENT_TYPE = :element_type # element
|
35
36
|
|
36
37
|
# Function keywords
|
37
38
|
FN = :fn
|
@@ -149,6 +150,11 @@ module Kumi
|
|
149
150
|
starts_declaration: true,
|
150
151
|
type_name: :hash
|
151
152
|
},
|
153
|
+
element_type: {
|
154
|
+
category: :type_keyword,
|
155
|
+
starts_declaration: true,
|
156
|
+
type_name: :element
|
157
|
+
},
|
152
158
|
|
153
159
|
# Function keyword
|
154
160
|
fn: {
|
@@ -385,7 +391,8 @@ module Kumi
|
|
385
391
|
'boolean' => :boolean_type,
|
386
392
|
'any' => :any_type,
|
387
393
|
'array' => :array_type,
|
388
|
-
'hash' => :hash_type
|
394
|
+
'hash' => :hash_type,
|
395
|
+
'element' => :element_type
|
389
396
|
}.freeze
|
390
397
|
|
391
398
|
# Opener to closer mappings for error recovery
|
data/lib/kumi/parser/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kumi-parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kumi Team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kumi
|