vinter 0.1.0 → 0.3.0
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/LICENSE +13 -0
- data/README.md +4 -8
- data/lib/vinter/lexer.rb +125 -32
- data/lib/vinter/parser.rb +1078 -64
- data/lib/vinter.rb +1 -1
- metadata +4 -3
data/lib/vinter/parser.rb
CHANGED
@@ -93,6 +93,14 @@ module Vinter
|
|
93
93
|
when 'vim9script'
|
94
94
|
token = advance # Skip 'vim9script'
|
95
95
|
{ type: :vim9script_declaration, line: token[:line], column: token[:column] }
|
96
|
+
when 'autocmd'
|
97
|
+
parse_autocmd_statement
|
98
|
+
when 'execute'
|
99
|
+
parse_execute_statement
|
100
|
+
when 'let'
|
101
|
+
parse_let_statement
|
102
|
+
when 'echohl', 'echomsg'
|
103
|
+
parse_echo_statement
|
96
104
|
else
|
97
105
|
@warnings << {
|
98
106
|
message: "Unexpected keyword: #{current_token[:value]}",
|
@@ -104,7 +112,11 @@ module Vinter
|
|
104
112
|
nil
|
105
113
|
end
|
106
114
|
elsif current_token[:type] == :identifier
|
107
|
-
|
115
|
+
if current_token[:value] == "echo"
|
116
|
+
parse_echo_statement
|
117
|
+
else
|
118
|
+
parse_expression_statement
|
119
|
+
end
|
108
120
|
elsif current_token[:type] == :comment
|
109
121
|
parse_comment
|
110
122
|
else
|
@@ -119,6 +131,168 @@ module Vinter
|
|
119
131
|
end
|
120
132
|
end
|
121
133
|
|
134
|
+
def parse_execute_statement
|
135
|
+
token = advance # Skip 'execute'
|
136
|
+
line = token[:line]
|
137
|
+
column = token[:column]
|
138
|
+
|
139
|
+
# Parse arguments - typically string expressions with concatenation
|
140
|
+
# Just accept any tokens until we hit a statement terminator or another command
|
141
|
+
expressions = []
|
142
|
+
expr = parse_expression
|
143
|
+
expressions << expr if expr
|
144
|
+
|
145
|
+
# Return the execute statement
|
146
|
+
{
|
147
|
+
type: :execute_statement,
|
148
|
+
expressions: expressions,
|
149
|
+
line: line,
|
150
|
+
column: column
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
def parse_let_statement
|
155
|
+
token = advance # Skip 'let'
|
156
|
+
line = token[:line]
|
157
|
+
column = token[:column]
|
158
|
+
|
159
|
+
# Parse the target variable
|
160
|
+
target = nil
|
161
|
+
if current_token
|
162
|
+
case current_token[:type]
|
163
|
+
when :identifier, :global_variable, :script_local, :arg_variable, :option_variable, :special_variable
|
164
|
+
target = {
|
165
|
+
type: current_token[:type],
|
166
|
+
name: current_token[:value],
|
167
|
+
line: current_token[:line],
|
168
|
+
column: current_token[:column]
|
169
|
+
}
|
170
|
+
advance
|
171
|
+
else
|
172
|
+
@errors << {
|
173
|
+
message: "Expected variable name after let",
|
174
|
+
position: @position,
|
175
|
+
line: current_token ? current_token[:line] : 0,
|
176
|
+
column: current_token ? current_token[:column] : 0
|
177
|
+
}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Skip the '=' or other assignment operator
|
182
|
+
operator = nil
|
183
|
+
if current_token && (
|
184
|
+
(current_token[:type] == :operator && current_token[:value] == '=') ||
|
185
|
+
current_token[:type] == :compound_operator)
|
186
|
+
operator = current_token[:value]
|
187
|
+
advance
|
188
|
+
else
|
189
|
+
@errors << {
|
190
|
+
message: "Expected assignment operator after variable in let statement",
|
191
|
+
position: @position,
|
192
|
+
line: current_token ? current_token[:line] : 0,
|
193
|
+
column: current_token ? current_token[:column] : 0
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
# Parse the value expression
|
198
|
+
value = parse_expression
|
199
|
+
|
200
|
+
{
|
201
|
+
type: :let_statement,
|
202
|
+
target: target,
|
203
|
+
operator: operator,
|
204
|
+
value: value,
|
205
|
+
line: line,
|
206
|
+
column: column
|
207
|
+
}
|
208
|
+
end
|
209
|
+
|
210
|
+
def parse_autocmd_statement
|
211
|
+
token = advance # Skip 'autocmd'
|
212
|
+
line = token[:line]
|
213
|
+
column = token[:column]
|
214
|
+
|
215
|
+
# Parse event name (like BufNewFile)
|
216
|
+
event = nil
|
217
|
+
if current_token && current_token[:type] == :identifier
|
218
|
+
event = advance[:value]
|
219
|
+
else
|
220
|
+
@errors << {
|
221
|
+
message: "Expected event name after 'autocmd'",
|
222
|
+
position: @position,
|
223
|
+
line: current_token ? current_token[:line] : 0,
|
224
|
+
column: current_token ? current_token[:column] : 0
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
# Parse pattern (like *.match)
|
229
|
+
pattern = nil
|
230
|
+
if current_token
|
231
|
+
pattern = current_token[:value]
|
232
|
+
advance
|
233
|
+
end
|
234
|
+
|
235
|
+
# Parse command (can be complex, including if statements)
|
236
|
+
commands = []
|
237
|
+
|
238
|
+
# Handle pipe-separated commands
|
239
|
+
in_command = true
|
240
|
+
while in_command && @position < @tokens.length
|
241
|
+
if current_token && current_token[:value] == '|'
|
242
|
+
advance # Skip '|'
|
243
|
+
end
|
244
|
+
|
245
|
+
# Parse the command
|
246
|
+
if current_token && current_token[:type] == :keyword
|
247
|
+
case current_token[:value]
|
248
|
+
when 'if'
|
249
|
+
commands << parse_if_statement
|
250
|
+
when 'echo'
|
251
|
+
commands << parse_echo_statement
|
252
|
+
# Add other command types as needed
|
253
|
+
else
|
254
|
+
# Generic command handling
|
255
|
+
cmd = parse_expression_statement
|
256
|
+
commands << cmd if cmd
|
257
|
+
end
|
258
|
+
elsif current_token && current_token[:type] == :identifier
|
259
|
+
cmd = parse_expression_statement
|
260
|
+
commands << cmd if cmd
|
261
|
+
else
|
262
|
+
in_command = false
|
263
|
+
end
|
264
|
+
|
265
|
+
# Check if we've reached the end of the autocmd command
|
266
|
+
if !current_token || current_token[:type] == :comment || current_token[:value] == "\n"
|
267
|
+
in_command = false
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
return {
|
272
|
+
type: :autocmd_statement,
|
273
|
+
event: event,
|
274
|
+
pattern: pattern,
|
275
|
+
commands: commands,
|
276
|
+
line: line,
|
277
|
+
column: column
|
278
|
+
}
|
279
|
+
end
|
280
|
+
|
281
|
+
def parse_echo_statement
|
282
|
+
token = advance #Skip 'echo'
|
283
|
+
line = token[:line]
|
284
|
+
column = token[:column]
|
285
|
+
|
286
|
+
expression = parse_expression
|
287
|
+
|
288
|
+
{
|
289
|
+
type: :echo_statement,
|
290
|
+
expression: expression,
|
291
|
+
line: line,
|
292
|
+
column: column
|
293
|
+
}
|
294
|
+
end
|
295
|
+
|
122
296
|
def parse_comment
|
123
297
|
comment = current_token[:value]
|
124
298
|
line = current_token[:line]
|
@@ -180,6 +354,157 @@ module Vinter
|
|
180
354
|
}
|
181
355
|
end
|
182
356
|
|
357
|
+
def parse_while_statement
|
358
|
+
token = advance # Skip 'while'
|
359
|
+
line = token[:line]
|
360
|
+
column = token[:column]
|
361
|
+
condition = parse_expression
|
362
|
+
|
363
|
+
body = []
|
364
|
+
|
365
|
+
# Parse statements until we hit 'endwhile'
|
366
|
+
while @position < @tokens.length
|
367
|
+
if current_token[:type] == :keyword && current_token[:value] == 'endwhile'
|
368
|
+
break
|
369
|
+
end
|
370
|
+
|
371
|
+
stmt = parse_statement
|
372
|
+
body << stmt if stmt
|
373
|
+
end
|
374
|
+
|
375
|
+
# Expect endwhile
|
376
|
+
expect(:keyword) # This should be 'endwhile'
|
377
|
+
|
378
|
+
{
|
379
|
+
type: :while_statement,
|
380
|
+
condition: condition,
|
381
|
+
body: body,
|
382
|
+
line: line,
|
383
|
+
column: column
|
384
|
+
}
|
385
|
+
end
|
386
|
+
|
387
|
+
def parse_for_statement
|
388
|
+
token = advance # Skip 'for'
|
389
|
+
line = token[:line]
|
390
|
+
column = token[:column]
|
391
|
+
|
392
|
+
# Parse the loop variable(s)
|
393
|
+
if current_token && current_token[:type] == :paren_open
|
394
|
+
# Handle tuple assignment: for (key, val) in dict
|
395
|
+
advance # Skip '('
|
396
|
+
|
397
|
+
loop_vars = []
|
398
|
+
|
399
|
+
loop do
|
400
|
+
if current_token && current_token[:type] == :identifier
|
401
|
+
loop_vars << advance[:value]
|
402
|
+
else
|
403
|
+
@errors << {
|
404
|
+
message: "Expected identifier in for loop variables",
|
405
|
+
position: @position,
|
406
|
+
line: current_token ? current_token[:line] : 0,
|
407
|
+
column: current_token ? current_token[:column] : 0
|
408
|
+
}
|
409
|
+
break
|
410
|
+
end
|
411
|
+
|
412
|
+
if current_token && current_token[:type] == :comma
|
413
|
+
advance # Skip ','
|
414
|
+
else
|
415
|
+
break
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
expect(:paren_close) # Skip ')'
|
420
|
+
|
421
|
+
if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
|
422
|
+
@errors << {
|
423
|
+
message: "Expected 'in' after for loop variables",
|
424
|
+
position: @position,
|
425
|
+
line: current_token ? current_token[:line] : 0,
|
426
|
+
column: current_token ? current_token[:column] : 0
|
427
|
+
}
|
428
|
+
else
|
429
|
+
advance # Skip 'in'
|
430
|
+
end
|
431
|
+
|
432
|
+
iterable = parse_expression
|
433
|
+
|
434
|
+
# Parse the body until 'endfor'
|
435
|
+
body = []
|
436
|
+
while @position < @tokens.length
|
437
|
+
if current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
438
|
+
break
|
439
|
+
end
|
440
|
+
|
441
|
+
stmt = parse_statement
|
442
|
+
body << stmt if stmt
|
443
|
+
end
|
444
|
+
|
445
|
+
# Expect endfor
|
446
|
+
expect(:keyword) # This should be 'endfor'
|
447
|
+
|
448
|
+
return {
|
449
|
+
type: :for_statement,
|
450
|
+
loop_vars: loop_vars,
|
451
|
+
iterable: iterable,
|
452
|
+
body: body,
|
453
|
+
line: line,
|
454
|
+
column: column
|
455
|
+
}
|
456
|
+
else
|
457
|
+
# Simple for var in list
|
458
|
+
if !current_token || current_token[:type] != :identifier
|
459
|
+
@errors << {
|
460
|
+
message: "Expected identifier as for loop variable",
|
461
|
+
position: @position,
|
462
|
+
line: current_token ? current_token[:line] : 0,
|
463
|
+
column: current_token ? current_token[:column] : 0
|
464
|
+
}
|
465
|
+
return nil
|
466
|
+
end
|
467
|
+
|
468
|
+
loop_var = advance[:value]
|
469
|
+
|
470
|
+
if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
|
471
|
+
@errors << {
|
472
|
+
message: "Expected 'in' after for loop variable",
|
473
|
+
position: @position,
|
474
|
+
line: current_token ? current_token[:line] : 0,
|
475
|
+
column: current_token ? current_token[:column] : 0
|
476
|
+
}
|
477
|
+
else
|
478
|
+
advance # Skip 'in'
|
479
|
+
end
|
480
|
+
|
481
|
+
iterable = parse_expression
|
482
|
+
|
483
|
+
# Parse the body until 'endfor'
|
484
|
+
body = []
|
485
|
+
while @position < @tokens.length
|
486
|
+
if current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
487
|
+
break
|
488
|
+
end
|
489
|
+
|
490
|
+
stmt = parse_statement
|
491
|
+
body << stmt if stmt
|
492
|
+
end
|
493
|
+
|
494
|
+
# Expect endfor
|
495
|
+
expect(:keyword) # This should be 'endfor'
|
496
|
+
|
497
|
+
return {
|
498
|
+
type: :for_statement,
|
499
|
+
loop_var: loop_var,
|
500
|
+
iterable: iterable,
|
501
|
+
body: body,
|
502
|
+
line: line,
|
503
|
+
column: column
|
504
|
+
}
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
183
508
|
def parse_def_function
|
184
509
|
token = advance # Skip 'def'
|
185
510
|
line = token[:line]
|
@@ -224,8 +549,6 @@ module Vinter
|
|
224
549
|
}
|
225
550
|
end
|
226
551
|
|
227
|
-
|
228
|
-
|
229
552
|
def parse_parameter_list
|
230
553
|
params = []
|
231
554
|
|
@@ -234,7 +557,8 @@ module Vinter
|
|
234
557
|
return params
|
235
558
|
end
|
236
559
|
|
237
|
-
|
560
|
+
# Parse parameters until we find a closing parenthesis
|
561
|
+
while @position < @tokens.length && current_token && current_token[:type] != :paren_close
|
238
562
|
# Check for variable args
|
239
563
|
if current_token && current_token[:type] == :ellipsis
|
240
564
|
ellipsis_token = advance
|
@@ -252,6 +576,17 @@ module Vinter
|
|
252
576
|
line: ellipsis_token[:line],
|
253
577
|
column: ellipsis_token[:column]
|
254
578
|
}
|
579
|
+
|
580
|
+
# After varargs, we expect closing paren
|
581
|
+
if current_token && current_token[:type] != :paren_close
|
582
|
+
@errors << {
|
583
|
+
message: "Expected closing parenthesis after varargs",
|
584
|
+
position: @position,
|
585
|
+
line: current_token[:line],
|
586
|
+
column: current_token[:column]
|
587
|
+
}
|
588
|
+
end
|
589
|
+
|
255
590
|
break
|
256
591
|
end
|
257
592
|
|
@@ -286,15 +621,23 @@ module Vinter
|
|
286
621
|
type: :parameter,
|
287
622
|
name: param_name[:value],
|
288
623
|
param_type: param_type,
|
289
|
-
optional:
|
624
|
+
optional: default_value != nil,
|
290
625
|
default_value: default_value,
|
291
626
|
line: param_name[:line],
|
292
627
|
column: param_name[:column]
|
293
628
|
}
|
294
629
|
|
630
|
+
# If we have a comma, advance past it and continue
|
295
631
|
if current_token && current_token[:type] == :comma
|
296
632
|
advance
|
297
|
-
|
633
|
+
# If we don't have a comma, we should have a closing paren
|
634
|
+
elsif current_token && current_token[:type] != :paren_close
|
635
|
+
@errors << {
|
636
|
+
message: "Expected comma or closing parenthesis after parameter",
|
637
|
+
position: @position,
|
638
|
+
line: current_token[:line],
|
639
|
+
column: current_token[:column]
|
640
|
+
}
|
298
641
|
break
|
299
642
|
end
|
300
643
|
end
|
@@ -397,6 +740,39 @@ module Vinter
|
|
397
740
|
end
|
398
741
|
|
399
742
|
def parse_expression_statement
|
743
|
+
# Check if this is an assignment using a compound operator
|
744
|
+
if current_token && current_token[:type] == :identifier
|
745
|
+
variable_name = current_token[:value]
|
746
|
+
variable_token = current_token
|
747
|
+
advance # Move past the identifier
|
748
|
+
|
749
|
+
if current_token && current_token[:type] == :compound_operator
|
750
|
+
operator = current_token[:value]
|
751
|
+
operator_token = current_token
|
752
|
+
advance # Move past the operator
|
753
|
+
|
754
|
+
right = parse_expression
|
755
|
+
|
756
|
+
return {
|
757
|
+
type: :compound_assignment,
|
758
|
+
operator: operator,
|
759
|
+
target: {
|
760
|
+
type: :identifier,
|
761
|
+
name: variable_name,
|
762
|
+
line: variable_token[:line],
|
763
|
+
column: variable_token[:column]
|
764
|
+
},
|
765
|
+
value: right,
|
766
|
+
line: operator_token[:line],
|
767
|
+
column: operator_token[:column]
|
768
|
+
}
|
769
|
+
end
|
770
|
+
|
771
|
+
# If it wasn't a compound assignment, backtrack
|
772
|
+
@position -= 1
|
773
|
+
end
|
774
|
+
|
775
|
+
# Regular expression statement handling
|
400
776
|
expr = parse_expression
|
401
777
|
{
|
402
778
|
type: :expression_statement,
|
@@ -411,24 +787,38 @@ module Vinter
|
|
411
787
|
end
|
412
788
|
|
413
789
|
def parse_binary_expression(precedence = 0)
|
414
|
-
left =
|
790
|
+
left = parse_unary_expression
|
415
791
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
op_precedence = operator_precedence(op)
|
792
|
+
# Handle multi-line expressions where operators may appear at line beginnings
|
793
|
+
while current_token &&
|
794
|
+
(current_token[:type] == :operator ||
|
795
|
+
(peek_token && peek_token[:type] == :operator && current_token[:type] == :whitespace))
|
421
796
|
|
422
|
-
|
797
|
+
# Skip any whitespace before the operator if it's at the beginning of a line
|
798
|
+
if current_token[:type] == :whitespace
|
799
|
+
advance
|
800
|
+
end
|
423
801
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
802
|
+
# Now we should be at the operator
|
803
|
+
if current_token && current_token[:type] == :operator &&
|
804
|
+
operator_precedence(current_token[:value]) >= precedence
|
805
|
+
op_token = advance
|
806
|
+
op = op_token[:value]
|
807
|
+
op_precedence = operator_precedence(op)
|
808
|
+
|
809
|
+
right = parse_binary_expression(op_precedence + 1)
|
810
|
+
|
811
|
+
left = {
|
812
|
+
type: :binary_expression,
|
813
|
+
operator: op,
|
814
|
+
left: left,
|
815
|
+
right: right,
|
816
|
+
line: op_token[:line],
|
817
|
+
column: op_token[:column]
|
818
|
+
}
|
819
|
+
else
|
820
|
+
break
|
821
|
+
end
|
432
822
|
end
|
433
823
|
|
434
824
|
return left
|
@@ -448,6 +838,10 @@ module Vinter
|
|
448
838
|
5
|
449
839
|
when '*', '/', '%' # Multiplication, division, modulo
|
450
840
|
6
|
841
|
+
when '.'
|
842
|
+
7
|
843
|
+
when '!'
|
844
|
+
8
|
451
845
|
else
|
452
846
|
0
|
453
847
|
end
|
@@ -460,10 +854,13 @@ module Vinter
|
|
460
854
|
line = token[:line]
|
461
855
|
column = token[:column]
|
462
856
|
|
857
|
+
# First parse the basic expression
|
858
|
+
expr = nil
|
859
|
+
|
463
860
|
case token[:type]
|
464
861
|
when :number
|
465
862
|
advance
|
466
|
-
{
|
863
|
+
expr = {
|
467
864
|
type: :literal,
|
468
865
|
value: token[:value],
|
469
866
|
token_type: :number,
|
@@ -472,14 +869,33 @@ module Vinter
|
|
472
869
|
}
|
473
870
|
when :string
|
474
871
|
advance
|
475
|
-
{
|
872
|
+
expr = {
|
476
873
|
type: :literal,
|
477
874
|
value: token[:value],
|
478
875
|
token_type: :string,
|
479
876
|
line: line,
|
480
877
|
column: column
|
481
878
|
}
|
482
|
-
when :
|
879
|
+
when :option_variable
|
880
|
+
# Handle Vim option variables (like &compatible)
|
881
|
+
advance
|
882
|
+
expr = {
|
883
|
+
type: :option_variable,
|
884
|
+
name: token[:value],
|
885
|
+
line: line,
|
886
|
+
column: column
|
887
|
+
}
|
888
|
+
when :special_variable
|
889
|
+
# Handle Vim special variables (like v:version)
|
890
|
+
advance
|
891
|
+
expr = {
|
892
|
+
type: :special_variable,
|
893
|
+
name: token[:value],
|
894
|
+
line: line,
|
895
|
+
column: column
|
896
|
+
}
|
897
|
+
when :script_local
|
898
|
+
# Handle script-local variables/functions (like s:var)
|
483
899
|
advance
|
484
900
|
|
485
901
|
# Check if this is a function call
|
@@ -487,17 +903,72 @@ module Vinter
|
|
487
903
|
return parse_function_call(token[:value], line, column)
|
488
904
|
end
|
489
905
|
|
490
|
-
{
|
491
|
-
type: :
|
906
|
+
expr = {
|
907
|
+
type: :script_local,
|
908
|
+
name: token[:value],
|
909
|
+
line: line,
|
910
|
+
column: column
|
911
|
+
}
|
912
|
+
when :global_variable
|
913
|
+
# Handle global variables (like g:var)
|
914
|
+
advance
|
915
|
+
expr = {
|
916
|
+
type: :global_variable,
|
492
917
|
name: token[:value],
|
493
918
|
line: line,
|
494
919
|
column: column
|
495
920
|
}
|
921
|
+
when :arg_variable
|
922
|
+
# Handle function argument variables (like a:var)
|
923
|
+
advance
|
924
|
+
expr = {
|
925
|
+
type: :arg_variable,
|
926
|
+
name: token[:value],
|
927
|
+
line: line,
|
928
|
+
column: column
|
929
|
+
}
|
930
|
+
when :identifier
|
931
|
+
advance
|
932
|
+
|
933
|
+
# Check if this is a function call
|
934
|
+
if current_token && current_token[:type] == :paren_open
|
935
|
+
return parse_function_call(token[:value], line, column)
|
936
|
+
end
|
937
|
+
|
938
|
+
# Special handling for execute command
|
939
|
+
if token[:value] == 'execute'
|
940
|
+
# Parse the string expressions for execute
|
941
|
+
# For now we'll just treat it as a normal identifier
|
942
|
+
expr = {
|
943
|
+
type: :identifier,
|
944
|
+
name: token[:value],
|
945
|
+
line: line,
|
946
|
+
column: column
|
947
|
+
}
|
948
|
+
else
|
949
|
+
expr = {
|
950
|
+
type: :identifier,
|
951
|
+
name: token[:value],
|
952
|
+
line: line,
|
953
|
+
column: column
|
954
|
+
}
|
955
|
+
end
|
496
956
|
when :paren_open
|
497
|
-
|
957
|
+
if is_lambda_expression
|
958
|
+
return parse_lambda_expression(line, column)
|
959
|
+
else
|
960
|
+
advance # Skip '('
|
961
|
+
expr = parse_expression
|
962
|
+
expect(:paren_close) # Expect and skip ')'
|
963
|
+
end
|
964
|
+
when :bracket_open
|
965
|
+
expr = parse_list_literal(line, column)
|
966
|
+
when :brace_open
|
967
|
+
expr = parse_dict_literal(line, column)
|
968
|
+
when :backslash
|
969
|
+
# Handle line continuation with backslash
|
970
|
+
advance
|
498
971
|
expr = parse_expression
|
499
|
-
expect(:paren_close) # Expect and skip ')'
|
500
|
-
return expr
|
501
972
|
else
|
502
973
|
@errors << {
|
503
974
|
message: "Unexpected token in expression: #{token[:type]}",
|
@@ -508,6 +979,471 @@ module Vinter
|
|
508
979
|
advance
|
509
980
|
return nil
|
510
981
|
end
|
982
|
+
|
983
|
+
# Now handle any chained property access or method calls
|
984
|
+
while current_token
|
985
|
+
# Check for property access with dot
|
986
|
+
if current_token[:type] == :operator && current_token[:value] == '.'
|
987
|
+
dot_token = advance # Skip '.'
|
988
|
+
|
989
|
+
# Next token should be an identifier (property name)
|
990
|
+
if !current_token || current_token[:type] != :identifier
|
991
|
+
@errors << {
|
992
|
+
message: "Expected property name after '.'",
|
993
|
+
position: @position,
|
994
|
+
line: current_token ? current_token[:line] : 0,
|
995
|
+
column: current_token ? current_token[:column] : 0
|
996
|
+
}
|
997
|
+
break
|
998
|
+
end
|
999
|
+
|
1000
|
+
property_token = advance # Get property name
|
1001
|
+
|
1002
|
+
expr = {
|
1003
|
+
type: :property_access,
|
1004
|
+
object: expr,
|
1005
|
+
property: property_token[:value],
|
1006
|
+
line: dot_token[:line],
|
1007
|
+
column: dot_token[:column]
|
1008
|
+
}
|
1009
|
+
# Check for method call with arrow ->
|
1010
|
+
elsif current_token[:type] == :operator && current_token[:value] == '->'
|
1011
|
+
arrow_token = advance # Skip '->'
|
1012
|
+
|
1013
|
+
# Next token should be an identifier (method name)
|
1014
|
+
if !current_token || current_token[:type] != :identifier
|
1015
|
+
@errors << {
|
1016
|
+
message: "Expected method name after '->'",
|
1017
|
+
position: @position,
|
1018
|
+
line: current_token ? current_token[:line] : 0,
|
1019
|
+
column: current_token ? current_token[:column] : 0
|
1020
|
+
}
|
1021
|
+
break
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
method_name = advance[:value] # Get method name
|
1025
|
+
|
1026
|
+
# Check for arguments
|
1027
|
+
args = []
|
1028
|
+
if current_token && current_token[:type] == :paren_open
|
1029
|
+
expect(:paren_open) # Skip '('
|
1030
|
+
|
1031
|
+
# Parse arguments if any
|
1032
|
+
unless current_token && current_token[:type] == :paren_close
|
1033
|
+
loop do
|
1034
|
+
arg = parse_expression
|
1035
|
+
args << arg if arg
|
1036
|
+
|
1037
|
+
if current_token && current_token[:type] == :comma
|
1038
|
+
advance # Skip comma
|
1039
|
+
else
|
1040
|
+
break
|
1041
|
+
end
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
expect(:paren_close) # Skip ')'
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
expr = {
|
1049
|
+
type: :method_call,
|
1050
|
+
object: expr,
|
1051
|
+
method: method_name,
|
1052
|
+
arguments: args,
|
1053
|
+
line: arrow_token[:line],
|
1054
|
+
column: arrow_token[:column]
|
1055
|
+
}
|
1056
|
+
# Check for indexing with brackets
|
1057
|
+
elsif current_token[:type] == :bracket_open
|
1058
|
+
bracket_token = advance # Skip '['
|
1059
|
+
|
1060
|
+
index_expr = parse_expression
|
1061
|
+
|
1062
|
+
expect(:bracket_close) # Skip ']'
|
1063
|
+
|
1064
|
+
expr = {
|
1065
|
+
type: :indexed_access,
|
1066
|
+
object: expr,
|
1067
|
+
index: index_expr,
|
1068
|
+
line: bracket_token[:line],
|
1069
|
+
column: bracket_token[:column]
|
1070
|
+
}
|
1071
|
+
# Check for function call directly on an expression
|
1072
|
+
elsif current_token[:type] == :paren_open
|
1073
|
+
paren_token = advance # Skip '('
|
1074
|
+
|
1075
|
+
args = []
|
1076
|
+
|
1077
|
+
# Parse arguments if any
|
1078
|
+
unless current_token && current_token[:type] == :paren_close
|
1079
|
+
loop do
|
1080
|
+
arg = parse_expression
|
1081
|
+
args << arg if arg
|
1082
|
+
|
1083
|
+
if current_token && current_token[:type] == :comma
|
1084
|
+
advance # Skip comma
|
1085
|
+
else
|
1086
|
+
break
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
expect(:paren_close) # Skip ')'
|
1092
|
+
|
1093
|
+
expr = {
|
1094
|
+
type: :call_expression,
|
1095
|
+
callee: expr,
|
1096
|
+
arguments: args,
|
1097
|
+
line: paren_token[:line],
|
1098
|
+
column: paren_token[:column]
|
1099
|
+
}
|
1100
|
+
else
|
1101
|
+
# No more chaining
|
1102
|
+
break
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
return expr
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def is_lambda_expression
|
1110
|
+
# Save the current position
|
1111
|
+
original_position = @position
|
1112
|
+
|
1113
|
+
# Skip the opening parenthesis
|
1114
|
+
advance if current_token && current_token[:type] == :paren_open
|
1115
|
+
|
1116
|
+
# Check for a parameter list followed by => or ): =>
|
1117
|
+
has_params = false
|
1118
|
+
has_arrow = false
|
1119
|
+
|
1120
|
+
# Skip past parameters
|
1121
|
+
loop do
|
1122
|
+
break if !current_token
|
1123
|
+
|
1124
|
+
if current_token[:type] == :identifier
|
1125
|
+
has_params = true
|
1126
|
+
advance
|
1127
|
+
|
1128
|
+
# Either comma for another parameter, or close paren
|
1129
|
+
if current_token && current_token[:type] == :comma
|
1130
|
+
advance
|
1131
|
+
elsif current_token && current_token[:type] == :paren_close
|
1132
|
+
advance
|
1133
|
+
break
|
1134
|
+
else
|
1135
|
+
# Not a valid lambda parameter list
|
1136
|
+
break
|
1137
|
+
end
|
1138
|
+
elsif current_token[:type] == :paren_close
|
1139
|
+
# Empty parameter list is valid
|
1140
|
+
advance
|
1141
|
+
break
|
1142
|
+
else
|
1143
|
+
# Not a valid lambda parameter list
|
1144
|
+
break
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
# After parameters, check for either => or ): =>
|
1149
|
+
if current_token
|
1150
|
+
if current_token[:type] == :operator && current_token[:value] == '=>'
|
1151
|
+
has_arrow = true
|
1152
|
+
elsif current_token[:type] == :colon
|
1153
|
+
# There might be a return type annotation
|
1154
|
+
advance
|
1155
|
+
# Skip the type
|
1156
|
+
advance if current_token && current_token[:type] == :identifier
|
1157
|
+
# Check for the arrow
|
1158
|
+
has_arrow = current_token && current_token[:type] == :operator && current_token[:value] == '=>'
|
1159
|
+
end
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
# Reset position to where we started
|
1163
|
+
@position = original_position
|
1164
|
+
|
1165
|
+
return has_params && has_arrow
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
def parse_lambda_expression(line, column)
|
1169
|
+
expect(:paren_open) # Skip '('
|
1170
|
+
|
1171
|
+
params = []
|
1172
|
+
|
1173
|
+
# Parse parameters
|
1174
|
+
unless current_token && current_token[:type] == :paren_close
|
1175
|
+
loop do
|
1176
|
+
if current_token && current_token[:type] == :identifier
|
1177
|
+
param_name = current_token[:value]
|
1178
|
+
param_token = current_token
|
1179
|
+
advance
|
1180
|
+
|
1181
|
+
# Check for type annotation
|
1182
|
+
param_type = nil
|
1183
|
+
if current_token && current_token[:type] == :colon
|
1184
|
+
advance # Skip ':'
|
1185
|
+
param_type = parse_type
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
params << {
|
1189
|
+
type: :parameter,
|
1190
|
+
name: param_name,
|
1191
|
+
param_type: param_type,
|
1192
|
+
line: param_token[:line],
|
1193
|
+
column: param_token[:column]
|
1194
|
+
}
|
1195
|
+
else
|
1196
|
+
@errors << {
|
1197
|
+
message: "Expected parameter name in lambda expression",
|
1198
|
+
position: @position,
|
1199
|
+
line: current_token ? current_token[:line] : 0,
|
1200
|
+
column: current_token ? current_token[:column] : 0
|
1201
|
+
}
|
1202
|
+
break
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
if current_token && current_token[:type] == :comma
|
1206
|
+
advance # Skip comma
|
1207
|
+
else
|
1208
|
+
break
|
1209
|
+
end
|
1210
|
+
end
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
expect(:paren_close) # Skip ')'
|
1214
|
+
|
1215
|
+
# Check for return type annotation
|
1216
|
+
return_type = nil
|
1217
|
+
if current_token && current_token[:type] == :colon
|
1218
|
+
advance # Skip ':'
|
1219
|
+
return_type = parse_type
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
# Expect the arrow '=>'
|
1223
|
+
arrow_found = false
|
1224
|
+
if current_token && current_token[:type] == :operator && current_token[:value] == '=>'
|
1225
|
+
arrow_found = true
|
1226
|
+
advance # Skip '=>'
|
1227
|
+
else
|
1228
|
+
@errors << {
|
1229
|
+
message: "Expected '=>' in lambda expression",
|
1230
|
+
position: @position,
|
1231
|
+
line: current_token ? current_token[:line] : 0,
|
1232
|
+
column: current_token ? current_token[:column] : 0
|
1233
|
+
}
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
# Parse the lambda body - check if it's a block or an expression
|
1237
|
+
if arrow_found
|
1238
|
+
if current_token && current_token[:type] == :brace_open
|
1239
|
+
# Parse block body
|
1240
|
+
advance # Skip '{'
|
1241
|
+
|
1242
|
+
block_body = []
|
1243
|
+
brace_count = 1 # Track nested braces
|
1244
|
+
|
1245
|
+
while @position < @tokens.length && brace_count > 0
|
1246
|
+
if current_token[:type] == :brace_open
|
1247
|
+
brace_count += 1
|
1248
|
+
elsif current_token[:type] == :brace_close
|
1249
|
+
brace_count -= 1
|
1250
|
+
|
1251
|
+
# Skip the final closing brace, but don't process it as a statement
|
1252
|
+
if brace_count == 0
|
1253
|
+
advance
|
1254
|
+
break
|
1255
|
+
end
|
1256
|
+
end
|
1257
|
+
|
1258
|
+
stmt = parse_statement
|
1259
|
+
block_body << stmt if stmt
|
1260
|
+
|
1261
|
+
# Break if we've reached the end or found the matching closing brace
|
1262
|
+
if brace_count == 0 || @position >= @tokens.length
|
1263
|
+
break
|
1264
|
+
end
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
body = {
|
1268
|
+
type: :block_expression,
|
1269
|
+
statements: block_body,
|
1270
|
+
line: line,
|
1271
|
+
column: column
|
1272
|
+
}
|
1273
|
+
else
|
1274
|
+
# Simple expression body
|
1275
|
+
body = parse_expression
|
1276
|
+
end
|
1277
|
+
else
|
1278
|
+
body = nil
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
return {
|
1282
|
+
type: :lambda_expression,
|
1283
|
+
params: params,
|
1284
|
+
return_type: return_type,
|
1285
|
+
body: body,
|
1286
|
+
line: line,
|
1287
|
+
column: column
|
1288
|
+
}
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
def parse_dict_literal(line, column)
|
1292
|
+
advance # Skip '{'
|
1293
|
+
entries = []
|
1294
|
+
|
1295
|
+
# Empty dictionary
|
1296
|
+
if current_token && current_token[:type] == :brace_close
|
1297
|
+
advance # Skip '}'
|
1298
|
+
return {
|
1299
|
+
type: :dict_literal,
|
1300
|
+
entries: entries,
|
1301
|
+
line: line,
|
1302
|
+
column: column
|
1303
|
+
}
|
1304
|
+
end
|
1305
|
+
|
1306
|
+
# Parse dictionary entries
|
1307
|
+
loop do
|
1308
|
+
# Parse key (string or identifier)
|
1309
|
+
key = nil
|
1310
|
+
if current_token && (current_token[:type] == :string || current_token[:type] == :identifier)
|
1311
|
+
key = current_token[:type] == :string ? current_token[:value] : current_token[:value]
|
1312
|
+
advance # Skip key
|
1313
|
+
else
|
1314
|
+
@errors << {
|
1315
|
+
message: "Expected string or identifier as dictionary key",
|
1316
|
+
position: @position,
|
1317
|
+
line: current_token ? current_token[:line] : 0,
|
1318
|
+
column: current_token ? current_token[:column] : 0
|
1319
|
+
}
|
1320
|
+
break
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
# Expect colon
|
1324
|
+
expect(:colon)
|
1325
|
+
|
1326
|
+
# Parse value
|
1327
|
+
value = parse_expression
|
1328
|
+
|
1329
|
+
entries << {
|
1330
|
+
key: key,
|
1331
|
+
value: value
|
1332
|
+
}
|
1333
|
+
|
1334
|
+
if current_token && current_token[:type] == :comma
|
1335
|
+
advance # Skip comma
|
1336
|
+
# Allow trailing comma
|
1337
|
+
break if current_token && current_token[:type] == :brace_close
|
1338
|
+
else
|
1339
|
+
break
|
1340
|
+
end
|
1341
|
+
end
|
1342
|
+
|
1343
|
+
expect(:brace_close) # Expect and skip '}'
|
1344
|
+
|
1345
|
+
{
|
1346
|
+
type: :dict_literal,
|
1347
|
+
entries: entries,
|
1348
|
+
line: line,
|
1349
|
+
column: column
|
1350
|
+
}
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
def parse_list_literal(line, column)
|
1354
|
+
advance # Skip '['
|
1355
|
+
elements = []
|
1356
|
+
|
1357
|
+
# Empty list
|
1358
|
+
if current_token && current_token[:type] == :bracket_close
|
1359
|
+
advance # Skip ']'
|
1360
|
+
return {
|
1361
|
+
type: :list_literal,
|
1362
|
+
elements: elements,
|
1363
|
+
line: line,
|
1364
|
+
column: column
|
1365
|
+
}
|
1366
|
+
end
|
1367
|
+
|
1368
|
+
# Parse list elements
|
1369
|
+
while @position < @tokens.length
|
1370
|
+
# Check if we've reached the end of the list
|
1371
|
+
if current_token && current_token[:type] == :bracket_close
|
1372
|
+
advance # Skip ']'
|
1373
|
+
break
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
element = parse_expression
|
1377
|
+
elements << element if element
|
1378
|
+
|
1379
|
+
# Continue if there's a comma
|
1380
|
+
if current_token && current_token[:type] == :comma
|
1381
|
+
advance # Skip comma
|
1382
|
+
else
|
1383
|
+
# If no comma and not a closing bracket, then it's an error
|
1384
|
+
if current_token && current_token[:type] != :bracket_close
|
1385
|
+
@errors << {
|
1386
|
+
message: "Expected comma or closing bracket after list element",
|
1387
|
+
position: @position,
|
1388
|
+
line: current_token[:line],
|
1389
|
+
column: current_token[:column]
|
1390
|
+
}
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
# We still want to skip the closing bracket if it's there
|
1394
|
+
if current_token && current_token[:type] == :bracket_close
|
1395
|
+
advance
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
break
|
1399
|
+
end
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
# Check if we have a second list immediately following this one (Vim's special syntax)
|
1403
|
+
if current_token && current_token[:type] == :bracket_open
|
1404
|
+
next_list = parse_list_literal(current_token[:line], current_token[:column])
|
1405
|
+
|
1406
|
+
return {
|
1407
|
+
type: :list_concat_expression,
|
1408
|
+
left: {
|
1409
|
+
type: :list_literal,
|
1410
|
+
elements: elements,
|
1411
|
+
line: line,
|
1412
|
+
column: column
|
1413
|
+
},
|
1414
|
+
right: next_list,
|
1415
|
+
line: line,
|
1416
|
+
column: column
|
1417
|
+
}
|
1418
|
+
end
|
1419
|
+
|
1420
|
+
return {
|
1421
|
+
type: :list_literal,
|
1422
|
+
elements: elements,
|
1423
|
+
line: line,
|
1424
|
+
column: column
|
1425
|
+
}
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
def parse_unary_expression
|
1429
|
+
# Check for unary operators (!, -, +)
|
1430
|
+
if current_token && current_token[:type] == :operator &&
|
1431
|
+
['!', '-', '+'].include?(current_token[:value])
|
1432
|
+
|
1433
|
+
op_token = advance # Skip the operator
|
1434
|
+
operand = parse_unary_expression # Recursively parse the operand
|
1435
|
+
|
1436
|
+
return {
|
1437
|
+
type: :unary_expression,
|
1438
|
+
operator: op_token[:value],
|
1439
|
+
operand: operand,
|
1440
|
+
line: op_token[:line],
|
1441
|
+
column: op_token[:column]
|
1442
|
+
}
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
# If no unary operator, parse a primary expression
|
1446
|
+
return parse_primary_expression
|
511
1447
|
end
|
512
1448
|
|
513
1449
|
def parse_function_call(name, line, column)
|
@@ -515,14 +1451,40 @@ module Vinter
|
|
515
1451
|
|
516
1452
|
args = []
|
517
1453
|
|
518
|
-
# Parse arguments
|
519
|
-
|
520
|
-
|
1454
|
+
# Parse arguments until we find a closing parenthesis
|
1455
|
+
while @position < @tokens.length && current_token && current_token[:type] != :paren_close
|
1456
|
+
# Skip comments inside parameter lists
|
1457
|
+
if current_token && current_token[:type] == :comment
|
1458
|
+
advance
|
1459
|
+
next
|
1460
|
+
end
|
1461
|
+
|
1462
|
+
# Check if the argument might be a lambda expression
|
1463
|
+
if current_token && current_token[:type] == :paren_open && is_lambda_expression
|
1464
|
+
arg = parse_lambda_expression(current_token[:line], current_token[:column])
|
1465
|
+
else
|
521
1466
|
arg = parse_expression
|
522
|
-
|
1467
|
+
end
|
523
1468
|
|
524
|
-
|
525
|
-
|
1469
|
+
args << arg if arg
|
1470
|
+
|
1471
|
+
# Break if we hit the closing paren
|
1472
|
+
if current_token && current_token[:type] == :paren_close
|
1473
|
+
break
|
1474
|
+
end
|
1475
|
+
|
1476
|
+
# If we have a comma, advance past it and continue
|
1477
|
+
if current_token && current_token[:type] == :comma
|
1478
|
+
advance
|
1479
|
+
# If we don't have a comma and we're not at the end, it's an error
|
1480
|
+
elsif current_token && current_token[:type] != :paren_close && current_token[:type] != :comment
|
1481
|
+
@errors << {
|
1482
|
+
message: "Expected comma or closing parenthesis after argument",
|
1483
|
+
position: @position,
|
1484
|
+
line: current_token[:line],
|
1485
|
+
column: current_token[:column]
|
1486
|
+
}
|
1487
|
+
break
|
526
1488
|
end
|
527
1489
|
end
|
528
1490
|
|
@@ -541,23 +1503,23 @@ module Vinter
|
|
541
1503
|
token = advance # Skip 'import'
|
542
1504
|
line = token[:line]
|
543
1505
|
column = token[:column]
|
544
|
-
|
1506
|
+
|
545
1507
|
# Handle 'import autoload'
|
546
1508
|
is_autoload = false
|
547
1509
|
module_name = nil
|
548
1510
|
path = nil
|
549
|
-
|
1511
|
+
|
550
1512
|
if current_token && current_token[:type] == :identifier && current_token[:value] == 'autoload'
|
551
1513
|
is_autoload = true
|
552
1514
|
module_name = advance[:value] # Store "autoload" as the module name
|
553
|
-
|
1515
|
+
|
554
1516
|
# After "autoload" keyword, expect a string path
|
555
1517
|
if current_token && current_token[:type] == :string
|
556
1518
|
path = current_token[:value]
|
557
1519
|
advance
|
558
1520
|
else
|
559
|
-
@errors << {
|
560
|
-
message: "Expected string path after 'autoload'",
|
1521
|
+
@errors << {
|
1522
|
+
message: "Expected string path after 'autoload'",
|
561
1523
|
position: @position,
|
562
1524
|
line: current_token ? current_token[:line] : 0,
|
563
1525
|
column: current_token ? current_token[:column] : 0
|
@@ -567,11 +1529,11 @@ module Vinter
|
|
567
1529
|
# Regular import with a string path
|
568
1530
|
if current_token && current_token[:type] == :string
|
569
1531
|
path = current_token[:value]
|
570
|
-
|
1532
|
+
|
571
1533
|
# Extract module name from the path
|
572
1534
|
# This is simplified logic - you might need more complex extraction
|
573
1535
|
module_name = path.gsub(/['"]/, '').split('/').last.split('.').first
|
574
|
-
|
1536
|
+
|
575
1537
|
advance
|
576
1538
|
else
|
577
1539
|
# Handle other import formats
|
@@ -582,7 +1544,7 @@ module Vinter
|
|
582
1544
|
end
|
583
1545
|
end
|
584
1546
|
end
|
585
|
-
|
1547
|
+
|
586
1548
|
# Handle 'as name'
|
587
1549
|
as_name = nil
|
588
1550
|
if current_token && current_token[:type] == :identifier && current_token[:value] == 'as'
|
@@ -590,17 +1552,17 @@ module Vinter
|
|
590
1552
|
if current_token && current_token[:type] == :identifier
|
591
1553
|
as_name = advance[:value]
|
592
1554
|
else
|
593
|
-
@errors << {
|
594
|
-
message: "Expected identifier after 'as'",
|
1555
|
+
@errors << {
|
1556
|
+
message: "Expected identifier after 'as'",
|
595
1557
|
position: @position,
|
596
1558
|
line: current_token ? current_token[:line] : 0,
|
597
1559
|
column: current_token ? current_token[:column] : 0
|
598
1560
|
}
|
599
1561
|
end
|
600
1562
|
end
|
601
|
-
|
602
|
-
{
|
603
|
-
type: :import_statement,
|
1563
|
+
|
1564
|
+
{
|
1565
|
+
type: :import_statement,
|
604
1566
|
module: module_name,
|
605
1567
|
path: path,
|
606
1568
|
is_autoload: is_autoload,
|
@@ -608,7 +1570,7 @@ module Vinter
|
|
608
1570
|
line: line,
|
609
1571
|
column: column
|
610
1572
|
}
|
611
|
-
end
|
1573
|
+
end
|
612
1574
|
def parse_export_statement
|
613
1575
|
token = advance # Skip 'export'
|
614
1576
|
line = token[:line]
|
@@ -686,7 +1648,37 @@ module Vinter
|
|
686
1648
|
advance # Skip '!'
|
687
1649
|
end
|
688
1650
|
|
689
|
-
|
1651
|
+
# For script-local functions or other scoped functions
|
1652
|
+
is_script_local = false
|
1653
|
+
function_scope = nil
|
1654
|
+
|
1655
|
+
# Check if we have a script-local function (s:)
|
1656
|
+
if current_token && current_token[:type] == :script_local
|
1657
|
+
is_script_local = true
|
1658
|
+
function_scope = current_token[:value]
|
1659
|
+
advance # Skip s: prefix
|
1660
|
+
elsif current_token && current_token[:type] == :identifier && current_token[:value].include?(':')
|
1661
|
+
# Handle other scoped functions like g: or b:
|
1662
|
+
parts = current_token[:value].split(':')
|
1663
|
+
if parts.length == 2
|
1664
|
+
function_scope = parts[0] + ':'
|
1665
|
+
advance
|
1666
|
+
end
|
1667
|
+
end
|
1668
|
+
|
1669
|
+
# Now handle the function name, which might be separate from the scope
|
1670
|
+
name = nil
|
1671
|
+
if !is_script_local && function_scope.nil? && current_token && current_token[:type] == :identifier
|
1672
|
+
name = current_token
|
1673
|
+
advance
|
1674
|
+
elsif is_script_local || !function_scope.nil?
|
1675
|
+
if current_token && current_token[:type] == :identifier
|
1676
|
+
name = current_token
|
1677
|
+
advance
|
1678
|
+
end
|
1679
|
+
else
|
1680
|
+
name = expect(:identifier)
|
1681
|
+
end
|
690
1682
|
|
691
1683
|
# Parse parameter list
|
692
1684
|
expect(:paren_open)
|
@@ -695,8 +1687,11 @@ module Vinter
|
|
695
1687
|
|
696
1688
|
# Check for optional attributes (range, dict, abort, closure)
|
697
1689
|
attributes = []
|
698
|
-
while current_token
|
699
|
-
if [
|
1690
|
+
while current_token
|
1691
|
+
if current_token[:type] == :keyword && current_token[:value] == 'abort'
|
1692
|
+
attributes << advance[:value]
|
1693
|
+
elsif current_token[:type] == :identifier &&
|
1694
|
+
['range', 'dict', 'closure'].include?(current_token[:value])
|
700
1695
|
attributes << advance[:value]
|
701
1696
|
else
|
702
1697
|
break
|
@@ -727,9 +1722,16 @@ module Vinter
|
|
727
1722
|
}
|
728
1723
|
end
|
729
1724
|
|
1725
|
+
function_name = name ? name[:value] : nil
|
1726
|
+
if function_scope
|
1727
|
+
function_name = function_scope + function_name if function_name
|
1728
|
+
end
|
1729
|
+
|
730
1730
|
{
|
731
1731
|
type: :legacy_function,
|
732
|
-
name:
|
1732
|
+
name: function_name,
|
1733
|
+
is_script_local: is_script_local,
|
1734
|
+
scope: function_scope,
|
733
1735
|
params: params,
|
734
1736
|
has_bang: has_bang,
|
735
1737
|
attributes: attributes,
|
@@ -739,7 +1741,7 @@ module Vinter
|
|
739
1741
|
}
|
740
1742
|
end
|
741
1743
|
|
742
|
-
# Legacy function parameters are different - they use a:name syntax
|
1744
|
+
# Legacy function parameters are different - they can use a:name syntax
|
743
1745
|
def parse_parameter_list_legacy
|
744
1746
|
params = []
|
745
1747
|
|
@@ -756,7 +1758,28 @@ module Vinter
|
|
756
1758
|
break
|
757
1759
|
end
|
758
1760
|
|
759
|
-
if
|
1761
|
+
if current_token && current_token[:type] == :identifier
|
1762
|
+
# Regular parameter
|
1763
|
+
param_name = advance
|
1764
|
+
params << {
|
1765
|
+
type: :parameter,
|
1766
|
+
name: param_name[:value],
|
1767
|
+
line: param_name[:line],
|
1768
|
+
column: param_name[:column]
|
1769
|
+
}
|
1770
|
+
elsif current_token && current_token[:type] == :arg_variable
|
1771
|
+
# Parameter with a: prefix
|
1772
|
+
param_name = advance
|
1773
|
+
# Extract name without 'a:' prefix
|
1774
|
+
name_without_prefix = param_name[:value].sub(/^a:/, '')
|
1775
|
+
params << {
|
1776
|
+
type: :parameter,
|
1777
|
+
name: name_without_prefix,
|
1778
|
+
line: param_name[:line],
|
1779
|
+
column: param_name[:column],
|
1780
|
+
is_arg_prefixed: true
|
1781
|
+
}
|
1782
|
+
else
|
760
1783
|
@errors << {
|
761
1784
|
message: "Expected parameter name",
|
762
1785
|
position: @position,
|
@@ -766,15 +1789,6 @@ module Vinter
|
|
766
1789
|
break
|
767
1790
|
end
|
768
1791
|
|
769
|
-
param_name = advance
|
770
|
-
|
771
|
-
params << {
|
772
|
-
type: :parameter,
|
773
|
-
name: param_name[:value],
|
774
|
-
line: param_name[:line],
|
775
|
-
column: param_name[:column]
|
776
|
-
}
|
777
|
-
|
778
1792
|
if current_token && current_token[:type] == :comma
|
779
1793
|
advance
|
780
1794
|
else
|