kumi-parser 0.0.13 → 0.0.15
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 +1 -1
- data/lib/kumi/parser/direct_parser.rb +90 -136
- data/lib/kumi/parser/smart_tokenizer.rb +23 -15
- data/lib/kumi/parser/token.rb +1 -1
- data/lib/kumi/parser/token_metadata.rb +13 -4
- 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: 1d86288cacf8d5e80d38541472d08b58ff202a7ec04efd0689c3b2698480fde1
|
4
|
+
data.tar.gz: 86088f162d87519650cb0e6473176962967f292479bafb71973294bf723a4752
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ab143e2e5a1f3e2e84c3ebb1a23283d4c24ce727dd4a4145d0beac7415e823bb0a0ce29711a8354010c662402f0ecaea7da0a254b8a352f200dee3bcae7fa61
|
7
|
+
data.tar.gz: 76abe4b2565162a1b49421de36355bf011aa444ca3372bfb3b42ff4361e50959079103b2f85b704c17ffe06ee689c3dc39f45359fe84cd165590a82c9e74982c
|
data/README.md
CHANGED
@@ -57,7 +57,7 @@ end
|
|
57
57
|
```
|
58
58
|
|
59
59
|
**Function calls**: `fn(:name, arg1, arg2, ...)`
|
60
|
-
**Operators**: `+` `-` `*` `/` `%` `>` `<` `>=` `<=` `==` `!=` `&` `|`
|
60
|
+
**Operators**: `+` `-` `*` `**` `` `/` `%` `>` `<` `>=` `<=` `==` `!=` `&` `|`
|
61
61
|
**References**: `input.field`, `value_name`, `array[index]`
|
62
62
|
**Strings**: Both `"double"` and `'single'` quotes supported
|
63
63
|
**Element syntax**: `element :type, :name` for array element specifications
|
@@ -72,7 +72,6 @@ module Kumi
|
|
72
72
|
|
73
73
|
expect_token(:end)
|
74
74
|
|
75
|
-
# Construct Root with exact AST.md structure
|
76
75
|
Kumi::Syntax::Root.new(
|
77
76
|
input_declarations,
|
78
77
|
value_declarations, # values
|
@@ -93,7 +92,6 @@ module Kumi
|
|
93
92
|
break unless current_token.metadata[:category] == :type_keyword
|
94
93
|
|
95
94
|
declarations << parse_input_declaration
|
96
|
-
|
97
95
|
skip_comments_and_newlines
|
98
96
|
end
|
99
97
|
|
@@ -101,28 +99,31 @@ module Kumi
|
|
101
99
|
declarations
|
102
100
|
end
|
103
101
|
|
104
|
-
# Input declaration: 'integer :name' or 'array :items do ... end'
|
102
|
+
# Input declaration: 'integer :name' or 'array :items do ... end' or 'element :type, :name'
|
103
|
+
#
|
104
|
+
# IMPORTANT: For array nodes with a block, this sets the node's access_mode:
|
105
|
+
# - :element if the block contains exactly one child introduced by `element`
|
106
|
+
# - :field otherwise
|
105
107
|
def parse_input_declaration
|
106
108
|
type_token = current_token
|
107
|
-
|
108
|
-
if type_token.metadata[:category] != :type_keyword
|
109
|
+
unless type_token.metadata[:category] == :type_keyword
|
109
110
|
raise_parse_error("Expected type keyword, got #{type_token.type}")
|
110
111
|
end
|
111
|
-
|
112
112
|
advance
|
113
|
-
|
114
|
-
#
|
115
|
-
|
113
|
+
|
114
|
+
# element :type, :name (syntactic sugar: the child was declared via `element`)
|
115
|
+
declared_with_element = (type_token.metadata[:type_name] == :element)
|
116
|
+
if declared_with_element
|
116
117
|
element_type_token = expect_token(:symbol)
|
117
118
|
expect_token(:comma)
|
118
119
|
name_token = expect_token(:symbol)
|
119
120
|
actual_type = element_type_token.value
|
120
121
|
else
|
121
|
-
name_token
|
122
|
+
name_token = expect_token(:symbol)
|
122
123
|
actual_type = type_token.metadata[:type_name]
|
123
124
|
end
|
124
125
|
|
125
|
-
#
|
126
|
+
# Optional: ', domain: ...'
|
126
127
|
domain = nil
|
127
128
|
if current_token.type == :comma
|
128
129
|
advance
|
@@ -131,13 +132,15 @@ module Kumi
|
|
131
132
|
expect_token(:colon)
|
132
133
|
domain = parse_domain_specification
|
133
134
|
else
|
134
|
-
# Put comma back for other parsers
|
135
135
|
@pos -= 1
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
-
#
|
139
|
+
# Parse nested declarations for block forms
|
140
140
|
children = []
|
141
|
+
any_element_children = false
|
142
|
+
any_field_children = false
|
143
|
+
|
141
144
|
if %i[array hash element].include?(actual_type) && current_token.type == :do
|
142
145
|
advance # consume 'do'
|
143
146
|
skip_comments_and_newlines
|
@@ -145,12 +148,31 @@ module Kumi
|
|
145
148
|
until %i[end eof].include?(current_token.type)
|
146
149
|
break unless current_token.metadata[:category] == :type_keyword
|
147
150
|
|
148
|
-
|
151
|
+
# Syntactic decision (NO counting): is this child introduced by `element`?
|
152
|
+
child_is_element_keyword = (current_token.metadata[:type_name] == :element)
|
153
|
+
any_element_children ||= child_is_element_keyword
|
154
|
+
any_field_children ||= !child_is_element_keyword
|
149
155
|
|
156
|
+
children << parse_input_declaration
|
150
157
|
skip_comments_and_newlines
|
151
158
|
end
|
152
159
|
|
153
160
|
expect_token(:end)
|
161
|
+
|
162
|
+
# For array blocks, access_mode derives strictly from syntax:
|
163
|
+
# - :element if ANY direct child used `element`
|
164
|
+
# - :field if NONE used `element`
|
165
|
+
# Mixing is invalid.
|
166
|
+
if actual_type == :array
|
167
|
+
if any_element_children && any_field_children
|
168
|
+
raise_parse_error("array :#{name_token.value} mixes `element` and field children; choose one style")
|
169
|
+
end
|
170
|
+
access_mode = any_element_children ? :element : :field
|
171
|
+
else
|
172
|
+
access_mode = :field # objects/hashes with blocks behave like field containers
|
173
|
+
end
|
174
|
+
else
|
175
|
+
access_mode = nil # leaves carry no access_mode
|
154
176
|
end
|
155
177
|
|
156
178
|
if children.empty?
|
@@ -162,58 +184,50 @@ module Kumi
|
|
162
184
|
loc: type_token.location
|
163
185
|
)
|
164
186
|
else
|
187
|
+
# 5th positional arg in your existing ctor is access_mode
|
165
188
|
Kumi::Syntax::InputDeclaration.new(
|
166
189
|
name_token.value,
|
167
190
|
domain,
|
168
191
|
actual_type,
|
169
192
|
children,
|
170
|
-
:field,
|
193
|
+
access_mode || :field,
|
171
194
|
loc: type_token.location
|
172
195
|
)
|
173
196
|
end
|
174
197
|
end
|
175
198
|
|
176
199
|
def parse_domain_specification
|
177
|
-
# Parse domain specifications: domain: ["x", "y"], domain: [1, 2, 3], domain: 1..10, domain: 1...10
|
178
200
|
case current_token.type
|
179
201
|
when :lbracket
|
180
|
-
# Array domain: ["a", "b", "c"] or [1, 2, 3]
|
181
202
|
array_expr = parse_array_literal
|
182
|
-
# Convert ArrayExpression to Ruby Array for analyzer compatibility
|
183
203
|
convert_array_expression_to_ruby_array(array_expr)
|
184
204
|
when :integer, :float
|
185
|
-
# Range domain: 1..10 or 1...10
|
186
205
|
parse_range_domain
|
187
206
|
else
|
188
|
-
# Skip unknown domain specs for now
|
189
207
|
advance until %i[comma newline eof end].include?(current_token.type)
|
190
208
|
nil
|
191
209
|
end
|
192
210
|
end
|
193
211
|
|
194
212
|
def parse_range_domain
|
195
|
-
# Parse numeric ranges like 1..10 or 0.0...100.0
|
196
213
|
start_token = current_token
|
197
214
|
start_value = start_token.type == :integer ? start_token.value.to_i : start_token.value.to_f
|
198
215
|
advance
|
199
216
|
|
200
217
|
case current_token.type
|
201
218
|
when :dot_dot
|
202
|
-
|
203
|
-
advance # consume ..
|
219
|
+
advance
|
204
220
|
end_token = current_token
|
205
221
|
end_value = end_token.type == :integer ? end_token.value.to_i : end_token.value.to_f
|
206
222
|
advance
|
207
223
|
(start_value..end_value)
|
208
224
|
when :dot_dot_dot
|
209
|
-
|
210
|
-
advance # consume ...
|
225
|
+
advance
|
211
226
|
end_token = current_token
|
212
227
|
end_value = end_token.type == :integer ? end_token.value.to_i : end_token.value.to_f
|
213
228
|
advance
|
214
229
|
(start_value...end_value)
|
215
230
|
else
|
216
|
-
# Just a single number, treat as single-element array
|
217
231
|
[start_value]
|
218
232
|
end
|
219
233
|
end
|
@@ -225,8 +239,6 @@ module Kumi
|
|
225
239
|
if element.is_a?(Kumi::Syntax::Literal)
|
226
240
|
element.value
|
227
241
|
else
|
228
|
-
# For non-literal elements, we'd need more complex evaluation
|
229
|
-
# For now, just return the element as-is
|
230
242
|
element
|
231
243
|
end
|
232
244
|
end
|
@@ -238,10 +250,8 @@ module Kumi
|
|
238
250
|
name_token = expect_token(:symbol)
|
239
251
|
|
240
252
|
if current_token.type == :do
|
241
|
-
# Cascade expression: value :name do ... end
|
242
253
|
expression = parse_cascade_expression
|
243
254
|
else
|
244
|
-
# Simple expression: value :name, expression
|
245
255
|
expect_token(:comma)
|
246
256
|
expression = parse_expression
|
247
257
|
end
|
@@ -271,55 +281,42 @@ module Kumi
|
|
271
281
|
def parse_cascade_expression
|
272
282
|
start_token = expect_token(:do)
|
273
283
|
cases = []
|
274
|
-
|
275
284
|
skip_comments_and_newlines
|
276
285
|
while %i[on base].include?(current_token.type)
|
277
286
|
cases << parse_case_expression
|
278
287
|
skip_comments_and_newlines
|
279
288
|
end
|
280
|
-
|
281
289
|
expect_token(:end)
|
282
|
-
|
283
290
|
Kumi::Syntax::CascadeExpression.new(cases, loc: start_token.location)
|
284
291
|
end
|
285
292
|
|
286
|
-
# Case expression: 'on condition1, condition2, ..., result' or 'base result'
|
287
293
|
def parse_case_expression
|
288
294
|
case current_token.type
|
289
295
|
when :on
|
290
296
|
on_token = advance_and_return_token
|
291
|
-
|
292
|
-
# Parse all comma-separated expressions
|
297
|
+
|
293
298
|
expressions = []
|
294
299
|
expressions << parse_expression
|
295
|
-
|
296
|
-
# Continue parsing comma-separated expressions until end of case
|
297
300
|
while current_token.type == :comma
|
298
|
-
advance
|
301
|
+
advance
|
299
302
|
expressions << parse_expression
|
300
303
|
end
|
301
|
-
|
302
|
-
# Last expression is the result, all others are conditions
|
304
|
+
|
303
305
|
result = expressions.pop
|
304
306
|
conditions = expressions
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
# Multiple conditions: combine with cascade_and
|
313
|
-
condition = Kumi::Syntax::CallExpression.new(:cascade_and, conditions, loc: on_token.location)
|
314
|
-
end
|
307
|
+
condition =
|
308
|
+
if conditions.length == 1
|
309
|
+
c = conditions[0]
|
310
|
+
simple_trait_reference?(c) ? wrap_condition_in_all(c) : c
|
311
|
+
else
|
312
|
+
Kumi::Syntax::CallExpression.new(:cascade_and, conditions, loc: on_token.location)
|
313
|
+
end
|
315
314
|
|
316
315
|
Kumi::Syntax::CaseExpression.new(condition, result, loc: on_token.location)
|
317
316
|
|
318
317
|
when :base
|
319
318
|
base_token = advance_and_return_token
|
320
319
|
result = parse_expression
|
321
|
-
|
322
|
-
# Base case has condition = true
|
323
320
|
true_literal = Kumi::Syntax::Literal.new(true, loc: base_token.location)
|
324
321
|
Kumi::Syntax::CaseExpression.new(true_literal, result, loc: base_token.location)
|
325
322
|
|
@@ -334,26 +331,22 @@ module Kumi
|
|
334
331
|
token
|
335
332
|
end
|
336
333
|
|
337
|
-
#
|
334
|
+
# Pratt parser for expressions
|
338
335
|
def parse_expression(min_precedence = 0)
|
339
336
|
left = parse_primary_expression
|
340
|
-
|
341
|
-
# Skip whitespace before checking for operators
|
342
337
|
skip_comments_and_newlines
|
343
338
|
|
344
339
|
while current_token.operator? && current_token.precedence >= min_precedence
|
345
340
|
operator_token = current_token
|
346
341
|
advance
|
347
|
-
|
348
|
-
# Skip whitespace after operator
|
349
342
|
skip_comments_and_newlines
|
350
343
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
344
|
+
next_min_precedence =
|
345
|
+
if operator_token.left_associative?
|
346
|
+
operator_token.precedence + 1
|
347
|
+
else
|
348
|
+
operator_token.precedence
|
349
|
+
end
|
357
350
|
|
358
351
|
right = parse_expression(next_min_precedence)
|
359
352
|
left = Kumi::Syntax::CallExpression.new(
|
@@ -361,8 +354,6 @@ module Kumi
|
|
361
354
|
[left, right],
|
362
355
|
loc: operator_token.location
|
363
356
|
)
|
364
|
-
|
365
|
-
# Skip whitespace before checking for next operator
|
366
357
|
skip_comments_and_newlines
|
367
358
|
end
|
368
359
|
|
@@ -374,25 +365,24 @@ module Kumi
|
|
374
365
|
|
375
366
|
case token.type
|
376
367
|
when :integer, :float, :string, :boolean, :constant
|
377
|
-
# Direct AST construction using token metadata
|
378
368
|
value = convert_literal_value(token)
|
379
369
|
advance
|
380
370
|
Kumi::Syntax::Literal.new(value, loc: token.location)
|
371
|
+
when :function_sugar
|
372
|
+
parse_function_sugar
|
381
373
|
|
382
374
|
when :identifier
|
375
|
+
|
383
376
|
if token.value == 'input' && peek_token.type == :dot
|
384
377
|
parse_input_reference
|
385
378
|
elsif peek_token.type == :lbracket
|
386
379
|
parse_array_access_reference
|
387
|
-
elsif token.value == 'fn'
|
388
|
-
parse_function_call
|
389
380
|
else
|
390
381
|
advance
|
391
382
|
Kumi::Syntax::DeclarationReference.new(token.value.to_sym, loc: token.location)
|
392
383
|
end
|
393
384
|
|
394
385
|
when :input
|
395
|
-
# Handle input references in expressions (input.field)
|
396
386
|
if peek_token.type == :dot
|
397
387
|
parse_input_reference_from_input_token
|
398
388
|
else
|
@@ -400,7 +390,7 @@ module Kumi
|
|
400
390
|
end
|
401
391
|
|
402
392
|
when :lparen
|
403
|
-
advance
|
393
|
+
advance
|
404
394
|
expr = parse_expression
|
405
395
|
expect_token(:rparen)
|
406
396
|
expr
|
@@ -409,14 +399,13 @@ module Kumi
|
|
409
399
|
parse_array_literal
|
410
400
|
|
411
401
|
when :fn
|
412
|
-
|
402
|
+
# expect_token(:fn)
|
403
|
+
parse_function_call
|
413
404
|
|
414
405
|
when :subtract
|
415
|
-
|
416
|
-
advance # consume '-'
|
406
|
+
advance
|
417
407
|
skip_comments_and_newlines
|
418
408
|
operand = parse_primary_expression
|
419
|
-
# Convert to subtraction from zero: (subtract 0 operand)
|
420
409
|
Kumi::Syntax::CallExpression.new(
|
421
410
|
:subtract,
|
422
411
|
[Kumi::Syntax::Literal.new(0, loc: token.location), operand],
|
@@ -424,7 +413,6 @@ module Kumi
|
|
424
413
|
)
|
425
414
|
|
426
415
|
when :newline, :comment
|
427
|
-
# Skip newlines and comments in expressions
|
428
416
|
skip_comments_and_newlines
|
429
417
|
parse_primary_expression
|
430
418
|
|
@@ -438,10 +426,8 @@ module Kumi
|
|
438
426
|
expect_token(:dot)
|
439
427
|
|
440
428
|
path = [expect_field_name_token.to_sym]
|
441
|
-
|
442
|
-
# Handle nested access: input.field.subfield
|
443
429
|
while current_token.type == :dot
|
444
|
-
advance
|
430
|
+
advance
|
445
431
|
path << expect_field_name_token.to_sym
|
446
432
|
end
|
447
433
|
|
@@ -453,14 +439,12 @@ module Kumi
|
|
453
439
|
end
|
454
440
|
|
455
441
|
def parse_input_reference_from_input_token
|
456
|
-
input_token = expect_token(:input)
|
442
|
+
input_token = expect_token(:input)
|
457
443
|
expect_token(:dot)
|
458
444
|
|
459
445
|
path = [expect_field_name_token.to_sym]
|
460
|
-
|
461
|
-
# Handle nested access: input.field.subfield
|
462
446
|
while current_token.type == :dot
|
463
|
-
advance
|
447
|
+
advance
|
464
448
|
path << expect_field_name_token.to_sym
|
465
449
|
end
|
466
450
|
|
@@ -478,54 +462,29 @@ module Kumi
|
|
478
462
|
expect_token(:rbracket)
|
479
463
|
|
480
464
|
base_ref = Kumi::Syntax::DeclarationReference.new(name_token.value.to_sym, loc: name_token.location)
|
481
|
-
Kumi::Syntax::CallExpression.new(
|
482
|
-
:at,
|
483
|
-
[base_ref, index_expr],
|
484
|
-
loc: name_token.location
|
485
|
-
)
|
465
|
+
Kumi::Syntax::CallExpression.new(:at, [base_ref, index_expr], loc: name_token.location)
|
486
466
|
end
|
487
467
|
|
488
|
-
def
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
advance # consume '('
|
494
|
-
fn_name_token = expect_token(:symbol)
|
495
|
-
fn_name = fn_name_token.value
|
496
|
-
|
497
|
-
args = []
|
498
|
-
while current_token.type == :comma
|
499
|
-
advance # consume comma
|
500
|
-
args << parse_expression
|
501
|
-
end
|
502
|
-
|
503
|
-
expect_token(:rparen)
|
504
|
-
Kumi::Syntax::CallExpression.new(fn_name, args, loc: fn_name_token.location)
|
505
|
-
|
506
|
-
else
|
507
|
-
raise_parse_error("Expected '(' after 'fn'")
|
508
|
-
end
|
468
|
+
def parse_function_sugar
|
469
|
+
sugar_token = current_token
|
470
|
+
advance
|
471
|
+
args = parse_argument_list
|
472
|
+
Kumi::Syntax::CallExpression.new(sugar_token.value.to_sym, args, loc: sugar_token.location)
|
509
473
|
end
|
510
474
|
|
511
|
-
def
|
512
|
-
|
513
|
-
|
475
|
+
def parse_function_call
|
476
|
+
advance
|
514
477
|
if current_token.type == :lparen
|
515
|
-
|
516
|
-
advance # consume '('
|
478
|
+
advance
|
517
479
|
fn_name_token = expect_token(:symbol)
|
518
480
|
fn_name = fn_name_token.value
|
519
|
-
|
520
481
|
args = []
|
521
482
|
while current_token.type == :comma
|
522
|
-
advance
|
483
|
+
advance
|
523
484
|
args << parse_expression
|
524
485
|
end
|
525
|
-
|
526
486
|
expect_token(:rparen)
|
527
487
|
Kumi::Syntax::CallExpression.new(fn_name, args, loc: fn_name_token.location)
|
528
|
-
|
529
488
|
else
|
530
489
|
raise_parse_error("Expected '(' after 'fn'")
|
531
490
|
end
|
@@ -534,13 +493,15 @@ module Kumi
|
|
534
493
|
def parse_argument_list
|
535
494
|
args = []
|
536
495
|
|
496
|
+
expect_token(:lparen)
|
537
497
|
unless current_token.type == :rparen
|
538
498
|
args << parse_expression
|
539
499
|
while current_token.type == :comma
|
540
|
-
advance
|
500
|
+
advance
|
541
501
|
args << parse_expression
|
542
502
|
end
|
543
503
|
end
|
504
|
+
expect_token(:rparen)
|
544
505
|
|
545
506
|
args
|
546
507
|
end
|
@@ -548,25 +509,23 @@ module Kumi
|
|
548
509
|
def parse_array_literal
|
549
510
|
start_token = expect_token(:lbracket)
|
550
511
|
elements = []
|
551
|
-
|
552
512
|
unless current_token.type == :rbracket
|
553
513
|
elements << parse_expression
|
554
514
|
while current_token.type == :comma
|
555
|
-
advance
|
515
|
+
advance
|
556
516
|
elements << parse_expression unless current_token.type == :rbracket
|
557
517
|
end
|
558
518
|
end
|
559
|
-
|
560
519
|
expect_token(:rbracket)
|
561
520
|
Kumi::Syntax::ArrayExpression.new(elements, loc: start_token.location)
|
562
521
|
end
|
563
522
|
|
564
523
|
def convert_literal_value(token)
|
565
524
|
case token.type
|
566
|
-
when :integer
|
567
|
-
when :float
|
568
|
-
when :string
|
569
|
-
when :boolean
|
525
|
+
when :integer then token.value.gsub('_', '').to_i
|
526
|
+
when :float then token.value.gsub('_', '').to_f
|
527
|
+
when :string then token.value
|
528
|
+
when :boolean then token.value == 'true'
|
570
529
|
when :constant
|
571
530
|
case token.value
|
572
531
|
when 'Float::INFINITY' then Float::INFINITY
|
@@ -577,7 +536,6 @@ module Kumi
|
|
577
536
|
end
|
578
537
|
|
579
538
|
def expect_field_name_token
|
580
|
-
# Field names can be identifiers or keywords (like 'base', 'input', etc.)
|
581
539
|
token = current_token
|
582
540
|
if token.identifier? || token.keyword?
|
583
541
|
advance
|
@@ -592,28 +550,24 @@ module Kumi
|
|
592
550
|
raise Errors::ParseError.new(message, token: current_token)
|
593
551
|
end
|
594
552
|
|
595
|
-
# Helper method to check if condition is a simple trait reference
|
596
553
|
def simple_trait_reference?(condition)
|
597
554
|
condition.is_a?(Kumi::Syntax::DeclarationReference)
|
598
555
|
end
|
599
556
|
|
600
|
-
|
601
|
-
# Helper method to wrap condition in cascade_and function call
|
602
557
|
def wrap_condition_in_all(condition)
|
603
558
|
Kumi::Syntax::CallExpression.new(:cascade_and, [condition], loc: condition.loc)
|
604
559
|
end
|
605
560
|
|
606
|
-
# Map operator token types to function names for Ruby DSL compatibility
|
607
561
|
def map_operator_token_to_function_name(token_type)
|
608
562
|
case token_type
|
609
|
-
when :eq
|
610
|
-
when :ne
|
611
|
-
when :gt
|
612
|
-
when :lt
|
563
|
+
when :eq then :==
|
564
|
+
when :ne then :!=
|
565
|
+
when :gt then :>
|
566
|
+
when :lt then :<
|
613
567
|
when :gte then :>=
|
614
568
|
when :lte then :<=
|
615
569
|
when :and then :and
|
616
|
-
when :or
|
570
|
+
when :or then :or
|
617
571
|
when :exponent then :power
|
618
572
|
else token_type
|
619
573
|
end
|
@@ -168,12 +168,14 @@ module Kumi
|
|
168
168
|
advance # consume second :
|
169
169
|
constant_name = consume_while { |c| c.match?(/[a-zA-Z0-9_]/) }
|
170
170
|
full_constant = "#{identifier}::#{constant_name}"
|
171
|
-
|
171
|
+
|
172
172
|
location = Kumi::Syntax::Location.new(file: @source_file, line: @line, column: start_column)
|
173
173
|
@tokens << Token.new(:constant, full_constant, location, Kumi::Parser::TOKEN_METADATA[:constant])
|
174
174
|
return
|
175
175
|
end
|
176
176
|
|
177
|
+
location = Kumi::Syntax::Location.new(file: @source_file, line: @line, column: start_column)
|
178
|
+
|
177
179
|
# Check if it's a keyword
|
178
180
|
if keyword_type = Kumi::Parser::KEYWORDS[identifier]
|
179
181
|
metadata = Kumi::Parser::TOKEN_METADATA[keyword_type].dup
|
@@ -188,23 +190,29 @@ module Kumi
|
|
188
190
|
metadata[:closes_context] = closed_context
|
189
191
|
end
|
190
192
|
|
191
|
-
location = Kumi::Syntax::Location.new(file: @source_file, line: @line, column: start_column)
|
192
193
|
@tokens << Token.new(keyword_type, identifier, location, metadata)
|
193
|
-
|
194
|
-
|
195
|
-
metadata = Kumi::Parser::TOKEN_METADATA[:identifier].dup
|
196
|
-
|
197
|
-
# Add context-specific metadata
|
198
|
-
case current_context
|
199
|
-
when :input
|
200
|
-
metadata[:context] = :input_declaration
|
201
|
-
when :schema
|
202
|
-
metadata[:context] = :schema_body
|
203
|
-
end
|
194
|
+
return
|
195
|
+
end
|
204
196
|
|
205
|
-
|
206
|
-
|
197
|
+
# Check if its a function sugar
|
198
|
+
if Kumi::Parser::FUNCTION_SUGAR[identifier]
|
199
|
+
metadata = Kumi::Parser::TOKEN_METADATA[:function_sugar].dup
|
200
|
+
@tokens << Token.new(:function_sugar, identifier, location, metadata)
|
201
|
+
return
|
202
|
+
end
|
203
|
+
|
204
|
+
# Otherwise is an Idenfier
|
205
|
+
metadata = Kumi::Parser::TOKEN_METADATA[:identifier].dup
|
206
|
+
|
207
|
+
# Add context-specific metadata
|
208
|
+
case current_context
|
209
|
+
when :input
|
210
|
+
metadata[:context] = :input_declaration
|
211
|
+
when :schema
|
212
|
+
metadata[:context] = :schema_body
|
207
213
|
end
|
214
|
+
|
215
|
+
@tokens << Token.new(:identifier, identifier, location, metadata)
|
208
216
|
end
|
209
217
|
|
210
218
|
def consume_symbol_or_colon
|
data/lib/kumi/parser/token.rb
CHANGED
@@ -38,8 +38,8 @@ module Kumi
|
|
38
38
|
FN = :fn
|
39
39
|
|
40
40
|
# Operators (by precedence)
|
41
|
-
EXPONENT = :exponent
|
42
|
-
MULTIPLY = :multiply
|
41
|
+
EXPONENT = :exponent # **
|
42
|
+
MULTIPLY = :multiply # *
|
43
43
|
DIVIDE = :divide # /
|
44
44
|
MODULO = :modulo # %
|
45
45
|
ADD = :add # +
|
@@ -67,7 +67,7 @@ module Kumi
|
|
67
67
|
# Special
|
68
68
|
NEWLINE = :newline
|
69
69
|
EOF = :eof
|
70
|
-
COMMENT = :comment
|
70
|
+
COMMENT = :comment # # comment
|
71
71
|
end
|
72
72
|
|
73
73
|
# Rich metadata for each token type
|
@@ -163,6 +163,11 @@ module Kumi
|
|
163
163
|
starts_expression: true
|
164
164
|
},
|
165
165
|
|
166
|
+
function_sugar: {
|
167
|
+
function_keyword: true,
|
168
|
+
starts_expression: true
|
169
|
+
},
|
170
|
+
|
166
171
|
# Operators with precedence and associativity
|
167
172
|
exponent: {
|
168
173
|
category: :operator,
|
@@ -372,6 +377,10 @@ module Kumi
|
|
372
377
|
'|' => :or
|
373
378
|
}.freeze
|
374
379
|
|
380
|
+
FUNCTION_SUGAR = {
|
381
|
+
'select' => '__select__'
|
382
|
+
}
|
383
|
+
|
375
384
|
# Keywords mapping
|
376
385
|
KEYWORDS = {
|
377
386
|
'schema' => :schema,
|
@@ -401,4 +410,4 @@ module Kumi
|
|
401
410
|
rbracket: :lbracket
|
402
411
|
}.freeze
|
403
412
|
end
|
404
|
-
end
|
413
|
+
end
|
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.15
|
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-
|
11
|
+
date: 2025-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kumi
|