vinter 0.4.0 → 0.6.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/lib/vinter/ast_printer.rb +14 -0
- data/lib/vinter/lexer.rb +132 -38
- data/lib/vinter/parser.rb +471 -640
- data/lib/vinter.rb +2 -1
- metadata +4 -6
data/lib/vinter/parser.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'pry'
|
|
1
2
|
module Vinter
|
|
2
3
|
class Parser
|
|
3
4
|
def initialize(tokens, source_text = nil)
|
|
@@ -71,6 +72,95 @@ module Vinter
|
|
|
71
72
|
end
|
|
72
73
|
end
|
|
73
74
|
|
|
75
|
+
# Helper method to add errors with consistent formatting
|
|
76
|
+
def add_error(message, token = current_token)
|
|
77
|
+
@errors << {
|
|
78
|
+
message: message,
|
|
79
|
+
position: @position,
|
|
80
|
+
line: token ? token[:line] : 0,
|
|
81
|
+
column: token ? token[:column] : 0
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Helper method to expect a specific keyword value
|
|
86
|
+
def expect_keyword(value)
|
|
87
|
+
if current_token && current_token[:type] == :keyword && current_token[:value] == value
|
|
88
|
+
advance
|
|
89
|
+
true
|
|
90
|
+
else
|
|
91
|
+
add_error("Expected '#{value}' keyword")
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Helper method to parse a body of statements until one or more end keywords
|
|
97
|
+
# Returns array of parsed statements
|
|
98
|
+
def parse_body_until(*end_keywords)
|
|
99
|
+
body = []
|
|
100
|
+
while @position < @tokens.length
|
|
101
|
+
# Check if we've reached an end keyword
|
|
102
|
+
if current_token && current_token[:type] == :keyword &&
|
|
103
|
+
end_keywords.include?(current_token[:value])
|
|
104
|
+
break
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
stmt = parse_statement
|
|
108
|
+
body << stmt if stmt
|
|
109
|
+
end
|
|
110
|
+
body
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Helper method to expect and consume an end keyword
|
|
114
|
+
# Returns true if found, false and adds error if not
|
|
115
|
+
def expect_end_keyword(*keywords)
|
|
116
|
+
if current_token && current_token[:type] == :keyword &&
|
|
117
|
+
keywords.include?(current_token[:value])
|
|
118
|
+
advance
|
|
119
|
+
true
|
|
120
|
+
else
|
|
121
|
+
# Only add error if we haven't reached end of file
|
|
122
|
+
if @position < @tokens.length
|
|
123
|
+
add_error("Expected #{keywords.join(' or ')} to close block")
|
|
124
|
+
end
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Helper method to parse comma-separated loop variables
|
|
130
|
+
# Used by for loops with brackets or parentheses
|
|
131
|
+
def parse_loop_variables
|
|
132
|
+
loop_vars = []
|
|
133
|
+
|
|
134
|
+
loop do
|
|
135
|
+
if current_token && (current_token[:type] == :identifier ||
|
|
136
|
+
current_token[:type] == :local_variable ||
|
|
137
|
+
current_token[:type] == :global_variable ||
|
|
138
|
+
current_token[:type] == :script_local)
|
|
139
|
+
loop_vars << advance
|
|
140
|
+
else
|
|
141
|
+
add_error("Expected identifier in for loop variables")
|
|
142
|
+
break
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if current_token && current_token[:type] == :comma
|
|
146
|
+
advance # Skip ','
|
|
147
|
+
else
|
|
148
|
+
break
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
loop_vars
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Helper method to expect 'in' keyword in for loops
|
|
156
|
+
def expect_in_keyword
|
|
157
|
+
if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
|
|
158
|
+
add_error("Expected 'in' after for loop variables")
|
|
159
|
+
else
|
|
160
|
+
advance # Skip 'in'
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
74
164
|
def parse_program
|
|
75
165
|
statements = []
|
|
76
166
|
|
|
@@ -155,7 +245,6 @@ module Vinter
|
|
|
155
245
|
if !current_token
|
|
156
246
|
return nil
|
|
157
247
|
end
|
|
158
|
-
start_token = current_token
|
|
159
248
|
|
|
160
249
|
# Handle pipe as command separator
|
|
161
250
|
if current_token[:type] == :operator && current_token[:value] == '|'
|
|
@@ -265,6 +354,8 @@ module Vinter
|
|
|
265
354
|
when 'vim9script'
|
|
266
355
|
token = advance # Skip 'vim9script'
|
|
267
356
|
{ type: :vim9script_declaration, line: token[:line], column: token[:column] }
|
|
357
|
+
when 'scriptencoding'
|
|
358
|
+
parse_scriptencoding
|
|
268
359
|
when 'autocmd'
|
|
269
360
|
parse_autocmd_statement
|
|
270
361
|
when 'execute', 'exec'
|
|
@@ -287,7 +378,7 @@ module Vinter
|
|
|
287
378
|
parse_set_command
|
|
288
379
|
when 'syntax'
|
|
289
380
|
parse_syntax_command
|
|
290
|
-
when 'highlight'
|
|
381
|
+
when 'highlight', 'hi'
|
|
291
382
|
parse_highlight_command
|
|
292
383
|
when 'sleep'
|
|
293
384
|
parse_sleep_command
|
|
@@ -333,6 +424,24 @@ module Vinter
|
|
|
333
424
|
parse_delete_command
|
|
334
425
|
elsif current_token[:type] == :percentage
|
|
335
426
|
parse_range_command
|
|
427
|
+
elsif current_token[:type] == :global_variable
|
|
428
|
+
parse_global_variable
|
|
429
|
+
elsif current_token[:type] == :buffer_local
|
|
430
|
+
parse_buffer_var
|
|
431
|
+
elsif current_token[:type] == :vimfuncs
|
|
432
|
+
advance
|
|
433
|
+
parse_function_call(current_token[:value], current_token[:line], current_token[:column])
|
|
434
|
+
elsif current_token[:type] == :builtin_funcs
|
|
435
|
+
parse_builtin_function_call
|
|
436
|
+
elsif current_token[:type] == :compound_operator
|
|
437
|
+
advance
|
|
438
|
+
parse_expression
|
|
439
|
+
elsif current_token[:type] == :string
|
|
440
|
+
parse_string
|
|
441
|
+
elsif current_token[:type] == :comma
|
|
442
|
+
advance
|
|
443
|
+
elsif current_token[:type] == :colon
|
|
444
|
+
parse_command_line_call
|
|
336
445
|
else
|
|
337
446
|
@warnings << {
|
|
338
447
|
message: "Unexpected token type: #{current_token[:type]}",
|
|
@@ -345,6 +454,182 @@ module Vinter
|
|
|
345
454
|
end
|
|
346
455
|
end
|
|
347
456
|
|
|
457
|
+
def parse_command_line_call
|
|
458
|
+
token = advance # Skip the ':'
|
|
459
|
+
line = token[:line]
|
|
460
|
+
column = token[:column]
|
|
461
|
+
|
|
462
|
+
# Extract the command or range
|
|
463
|
+
command = token[:value][1..] # Remove the leading ':'
|
|
464
|
+
|
|
465
|
+
# Check if the command is a range (e.g., `:2`, `:$`)
|
|
466
|
+
if command.match?(/^\d+$/) || command == '$'
|
|
467
|
+
return {
|
|
468
|
+
type: :range_command,
|
|
469
|
+
range: command,
|
|
470
|
+
line: line,
|
|
471
|
+
column: column
|
|
472
|
+
}
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Otherwise, it's a regular command (e.g., `:exe`)
|
|
476
|
+
args = []
|
|
477
|
+
while current_token && current_token[:type] != :newline
|
|
478
|
+
args << current_token[:value]
|
|
479
|
+
advance
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
{
|
|
483
|
+
type: :command_line_call,
|
|
484
|
+
command: command,
|
|
485
|
+
arguments: args,
|
|
486
|
+
line: line,
|
|
487
|
+
column: column
|
|
488
|
+
}
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def parse_buffer_var
|
|
492
|
+
token = current_token
|
|
493
|
+
line = token[:line]
|
|
494
|
+
column = token[:column]
|
|
495
|
+
name = token[:value]
|
|
496
|
+
advance
|
|
497
|
+
|
|
498
|
+
# Handle property access (e.g., b:foo.bar)
|
|
499
|
+
target = {
|
|
500
|
+
type: :buffer_variable,
|
|
501
|
+
name: name,
|
|
502
|
+
line: line,
|
|
503
|
+
column: column
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
# Property access (dot notation)
|
|
507
|
+
# shared with global (TODO)
|
|
508
|
+
# would almost just take an overly simple check to the = operator and call everything after b: name
|
|
509
|
+
if current_token && current_token[:type] == :operator && current_token[:value] == '.'
|
|
510
|
+
dot_token = advance
|
|
511
|
+
if current_token && (current_token[:type] == :identifier || current_token[:type] == :keyword)
|
|
512
|
+
property_token = advance
|
|
513
|
+
target = {
|
|
514
|
+
type: :property_access,
|
|
515
|
+
object: target,
|
|
516
|
+
property: property_token[:value],
|
|
517
|
+
line: dot_token[:line],
|
|
518
|
+
column: dot_token[:column]
|
|
519
|
+
}
|
|
520
|
+
else
|
|
521
|
+
add_error("Expected property name after '.' in variable")
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Indexed access (e.g., b:foo[bar])
|
|
526
|
+
while current_token && current_token[:type] == :bracket_open
|
|
527
|
+
bracket_token = advance
|
|
528
|
+
index_expr = parse_expression
|
|
529
|
+
expect(:bracket_close)
|
|
530
|
+
target = {
|
|
531
|
+
type: :indexed_access,
|
|
532
|
+
object: target,
|
|
533
|
+
index: index_expr,
|
|
534
|
+
line: bracket_token[:line],
|
|
535
|
+
column: bracket_token[:column]
|
|
536
|
+
}
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Assignment operator (= or compound)
|
|
540
|
+
operator = nil
|
|
541
|
+
if current_token && (
|
|
542
|
+
(current_token[:type] == :operator && current_token[:value] == '=') ||
|
|
543
|
+
current_token[:type] == :compound_operator)
|
|
544
|
+
operator = current_token[:value]
|
|
545
|
+
advance
|
|
546
|
+
else
|
|
547
|
+
add_error("Expected assignment operator after global variable")
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Value expression
|
|
551
|
+
value = parse_expression
|
|
552
|
+
|
|
553
|
+
{
|
|
554
|
+
type: :buffer_variable_assignment,
|
|
555
|
+
target: target,
|
|
556
|
+
operator: operator,
|
|
557
|
+
value: value,
|
|
558
|
+
line: line,
|
|
559
|
+
column: column
|
|
560
|
+
}
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def parse_global_variable
|
|
564
|
+
token = current_token
|
|
565
|
+
line = token[:line]
|
|
566
|
+
column = token[:column]
|
|
567
|
+
name = token[:value]
|
|
568
|
+
advance # Skip the global variable token
|
|
569
|
+
|
|
570
|
+
# Handle property access (e.g., g:foo.bar)
|
|
571
|
+
target = {
|
|
572
|
+
type: :global_variable,
|
|
573
|
+
name: name,
|
|
574
|
+
line: line,
|
|
575
|
+
column: column
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
# Property access (dot notation)
|
|
579
|
+
if current_token && current_token[:type] == :operator && current_token[:value] == '.'
|
|
580
|
+
dot_token = advance
|
|
581
|
+
if current_token && (current_token[:type] == :identifier || current_token[:type] == :keyword)
|
|
582
|
+
property_token = advance
|
|
583
|
+
target = {
|
|
584
|
+
type: :property_access,
|
|
585
|
+
object: target,
|
|
586
|
+
property: property_token[:value],
|
|
587
|
+
line: dot_token[:line],
|
|
588
|
+
column: dot_token[:column]
|
|
589
|
+
}
|
|
590
|
+
else
|
|
591
|
+
add_error("Expected property name after '.' in global variable")
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Indexed access (e.g., g:foo[bar])
|
|
596
|
+
while current_token && current_token[:type] == :bracket_open
|
|
597
|
+
bracket_token = advance
|
|
598
|
+
index_expr = parse_expression
|
|
599
|
+
expect(:bracket_close)
|
|
600
|
+
target = {
|
|
601
|
+
type: :indexed_access,
|
|
602
|
+
object: target,
|
|
603
|
+
index: index_expr,
|
|
604
|
+
line: bracket_token[:line],
|
|
605
|
+
column: bracket_token[:column]
|
|
606
|
+
}
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# Assignment operator (= or compound)
|
|
610
|
+
operator = nil
|
|
611
|
+
if current_token && (
|
|
612
|
+
(current_token[:type] == :operator && current_token[:value] == '=') ||
|
|
613
|
+
current_token[:type] == :compound_operator)
|
|
614
|
+
operator = current_token[:value]
|
|
615
|
+
advance
|
|
616
|
+
else
|
|
617
|
+
add_error("Expected assignment operator after global variable")
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Value expression
|
|
621
|
+
value = parse_expression
|
|
622
|
+
|
|
623
|
+
{
|
|
624
|
+
type: :global_variable_assignment,
|
|
625
|
+
target: target,
|
|
626
|
+
operator: operator,
|
|
627
|
+
value: value,
|
|
628
|
+
line: line,
|
|
629
|
+
column: column
|
|
630
|
+
}
|
|
631
|
+
end
|
|
632
|
+
|
|
348
633
|
def parse_range_command
|
|
349
634
|
token = advance # Skip '%'
|
|
350
635
|
line = token[:line]
|
|
@@ -427,80 +712,7 @@ module Vinter
|
|
|
427
712
|
}
|
|
428
713
|
end
|
|
429
714
|
|
|
430
|
-
def old_parse_filter_command
|
|
431
|
-
token = advance # Skip 'filter' or 'filt'
|
|
432
|
-
line = token[:line]
|
|
433
|
-
column = token[:column]
|
|
434
715
|
|
|
435
|
-
# Check for bang (!)
|
|
436
|
-
has_bang = false
|
|
437
|
-
if current_token && current_token[:type] == :operator && current_token[:value] == '!'
|
|
438
|
-
has_bang = true
|
|
439
|
-
advance # Skip '!'
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
# Parse the pattern
|
|
443
|
-
pattern = nil
|
|
444
|
-
pattern_delimiter = nil
|
|
445
|
-
|
|
446
|
-
if current_token && current_token[:type] == :operator && current_token[:value] == '/'
|
|
447
|
-
# Handle /pattern/ form
|
|
448
|
-
pattern_delimiter = '/'
|
|
449
|
-
advance # Skip opening delimiter
|
|
450
|
-
|
|
451
|
-
# Collect all tokens until closing delimiter
|
|
452
|
-
pattern_parts = []
|
|
453
|
-
while @position < @tokens.length &&
|
|
454
|
-
!(current_token[:type] == :operator && current_token[:value] == pattern_delimiter)
|
|
455
|
-
pattern_parts << current_token[:value]
|
|
456
|
-
advance
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
pattern = pattern_parts.join('')
|
|
460
|
-
|
|
461
|
-
# Skip closing delimiter
|
|
462
|
-
if current_token && current_token[:type] == :operator && current_token[:value] == pattern_delimiter
|
|
463
|
-
advance
|
|
464
|
-
else
|
|
465
|
-
@errors << {
|
|
466
|
-
message: "Expected closing pattern delimiter: #{pattern_delimiter}",
|
|
467
|
-
position: @position,
|
|
468
|
-
line: current_token ? current_token[:line] : 0,
|
|
469
|
-
column: current_token ? current_token[:column] : 0
|
|
470
|
-
}
|
|
471
|
-
end
|
|
472
|
-
else
|
|
473
|
-
# Handle direct pattern form (without delimiters)
|
|
474
|
-
# Parse until we see what appears to be the command
|
|
475
|
-
pattern_parts = []
|
|
476
|
-
while @position < @tokens.length
|
|
477
|
-
# Don't consume tokens that likely belong to the command part
|
|
478
|
-
if current_token[:type] == :keyword ||
|
|
479
|
-
(current_token[:type] == :identifier &&
|
|
480
|
-
['echo', 'let', 'execute', 'exec', 'autocmd', 'au', 'oldfiles', 'clist', 'command',
|
|
481
|
-
'files', 'highlight', 'jumps', 'list', 'llist', 'marks', 'registers', 'set'].include?(current_token[:value]))
|
|
482
|
-
break
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
pattern_parts << current_token[:value]
|
|
486
|
-
advance
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
pattern = pattern_parts.join('').strip
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
# Parse the command to be filtered
|
|
493
|
-
command = parse_statement
|
|
494
|
-
|
|
495
|
-
{
|
|
496
|
-
type: :filter_command,
|
|
497
|
-
pattern: pattern,
|
|
498
|
-
has_bang: has_bang,
|
|
499
|
-
command: command,
|
|
500
|
-
line: line,
|
|
501
|
-
column: column
|
|
502
|
-
}
|
|
503
|
-
end
|
|
504
716
|
|
|
505
717
|
def parse_augroup_statement
|
|
506
718
|
token = advance # Skip 'augroup'
|
|
@@ -513,18 +725,11 @@ module Vinter
|
|
|
513
725
|
name = current_token[:value]
|
|
514
726
|
advance
|
|
515
727
|
else
|
|
516
|
-
|
|
517
|
-
message: "Expected augroup name",
|
|
518
|
-
position: @position,
|
|
519
|
-
line: current_token ? current_token[:line] : 0,
|
|
520
|
-
column: current_token ? current_token[:column] : 0
|
|
521
|
-
}
|
|
728
|
+
add_error("Expected augroup name")
|
|
522
729
|
end
|
|
523
730
|
|
|
524
731
|
# Check for augroup END
|
|
525
|
-
|
|
526
|
-
if name && (name.upcase == "END" || name == "END")
|
|
527
|
-
is_end_marker = true
|
|
732
|
+
if name && name.upcase == "END"
|
|
528
733
|
return {
|
|
529
734
|
type: :augroup_end,
|
|
530
735
|
line: line,
|
|
@@ -540,8 +745,7 @@ module Vinter
|
|
|
540
745
|
(current_token[:type] == :identifier && current_token[:value] == 'augroup')
|
|
541
746
|
# Look ahead for END
|
|
542
747
|
if peek_token &&
|
|
543
|
-
((peek_token[:type] == :identifier &&
|
|
544
|
-
(peek_token[:value].upcase == 'END' || peek_token[:value] == 'END')) ||
|
|
748
|
+
((peek_token[:type] == :identifier && peek_token[:value].upcase == 'END') ||
|
|
545
749
|
(peek_token[:type] == :keyword && peek_token[:value].upcase == 'END'))
|
|
546
750
|
advance # Skip 'augroup'
|
|
547
751
|
advance # Skip 'END'
|
|
@@ -619,12 +823,7 @@ module Vinter
|
|
|
619
823
|
column: dot_token[:column]
|
|
620
824
|
}
|
|
621
825
|
else
|
|
622
|
-
|
|
623
|
-
message: "Expected property name after '.'",
|
|
624
|
-
position: @position,
|
|
625
|
-
line: current_token ? current_token[:line] : 0,
|
|
626
|
-
column: current_token ? current_token[:column] : 0
|
|
627
|
-
}
|
|
826
|
+
add_error("Expected property name after '.'")
|
|
628
827
|
end
|
|
629
828
|
end
|
|
630
829
|
|
|
@@ -684,12 +883,7 @@ module Vinter
|
|
|
684
883
|
}
|
|
685
884
|
advance
|
|
686
885
|
else
|
|
687
|
-
|
|
688
|
-
message: "Expected variable name in destructuring assignment",
|
|
689
|
-
position: @position,
|
|
690
|
-
line: current_token ? current_token[:line] : 0,
|
|
691
|
-
column: current_token ? current_token[:column] : 0
|
|
692
|
-
}
|
|
886
|
+
add_error("Expected variable name in destructuring assignment")
|
|
693
887
|
# Try to recover by advancing to next comma or closing bracket
|
|
694
888
|
while current_token && current_token[:type] != :comma && current_token[:type] != :bracket_close
|
|
695
889
|
advance
|
|
@@ -706,12 +900,7 @@ module Vinter
|
|
|
706
900
|
column: bracket_token[:column]
|
|
707
901
|
}
|
|
708
902
|
else
|
|
709
|
-
|
|
710
|
-
message: "Expected variable name after let",
|
|
711
|
-
position: @position,
|
|
712
|
-
line: current_token ? current_token[:line] : 0,
|
|
713
|
-
column: current_token ? current_token[:column] : 0
|
|
714
|
-
}
|
|
903
|
+
add_error("Expected variable name after let")
|
|
715
904
|
end
|
|
716
905
|
end
|
|
717
906
|
|
|
@@ -723,12 +912,7 @@ module Vinter
|
|
|
723
912
|
operator = current_token[:value]
|
|
724
913
|
advance
|
|
725
914
|
else
|
|
726
|
-
|
|
727
|
-
message: "Expected assignment operator after variable in let statement",
|
|
728
|
-
position: @position,
|
|
729
|
-
line: current_token ? current_token[:line] : 0,
|
|
730
|
-
column: current_token ? current_token[:column] : 0
|
|
731
|
-
}
|
|
915
|
+
add_error("Expected assignment operator after variable in let statement")
|
|
732
916
|
end
|
|
733
917
|
|
|
734
918
|
# Parse the value expression
|
|
@@ -748,12 +932,7 @@ module Vinter
|
|
|
748
932
|
func_name = current_token[:value]
|
|
749
933
|
advance
|
|
750
934
|
else
|
|
751
|
-
|
|
752
|
-
message: "Expected string with function name in function() call",
|
|
753
|
-
position: @position,
|
|
754
|
-
line: current_token ? current_token[:line] : 0,
|
|
755
|
-
column: current_token ? current_token[:column] : 0
|
|
756
|
-
}
|
|
935
|
+
add_error("Expected string with function name in function() call")
|
|
757
936
|
end
|
|
758
937
|
|
|
759
938
|
# Expect closing parenthesis
|
|
@@ -844,9 +1023,11 @@ module Vinter
|
|
|
844
1023
|
event = nil
|
|
845
1024
|
if current_token && current_token[:type] == :identifier
|
|
846
1025
|
event = advance[:value]
|
|
1026
|
+
elsif current_token[:type] == :operator && current_token[:value] == "!"
|
|
1027
|
+
advance
|
|
847
1028
|
else
|
|
848
1029
|
@errors << {
|
|
849
|
-
message: "Expected event name after 'autocmd'",
|
|
1030
|
+
message: "Expected event name after 'autocmd' | #{current_token[:type]}",
|
|
850
1031
|
position: @position,
|
|
851
1032
|
line: current_token ? current_token[:line] : 0,
|
|
852
1033
|
column: current_token ? current_token[:column] : 0
|
|
@@ -972,32 +1153,14 @@ module Vinter
|
|
|
972
1153
|
advance # Skip the pipe
|
|
973
1154
|
|
|
974
1155
|
# Expect endif
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
else
|
|
978
|
-
@errors << {
|
|
979
|
-
message: "Expected 'endif' after '|' in one-line if statement",
|
|
980
|
-
position: @position,
|
|
981
|
-
line: current_token ? current_token[:line] : 0,
|
|
982
|
-
column: current_token ? current_token[:column] : 0
|
|
983
|
-
}
|
|
1156
|
+
unless expect_keyword('endif')
|
|
1157
|
+
add_error("Expected 'endif' after '|' in one-line if statement")
|
|
984
1158
|
end
|
|
985
1159
|
end
|
|
986
1160
|
else
|
|
987
|
-
#
|
|
988
|
-
#
|
|
989
|
-
|
|
990
|
-
# Parse statements until we hit 'else', 'elseif', or 'endif'
|
|
991
|
-
while @position < @tokens.length
|
|
992
|
-
# Check for the tokens that would terminate this block
|
|
993
|
-
if current_token && current_token[:type] == :keyword &&
|
|
994
|
-
['else', 'elseif', 'endif'].include?(current_token[:value])
|
|
995
|
-
break
|
|
996
|
-
end
|
|
997
|
-
|
|
998
|
-
stmt = parse_statement
|
|
999
|
-
then_branch << stmt if stmt
|
|
1000
|
-
end
|
|
1161
|
+
# Parse multi-line if statement
|
|
1162
|
+
# Parse statements until 'else', 'elseif', or 'endif'
|
|
1163
|
+
then_branch = parse_body_until('else', 'elseif', 'endif')
|
|
1001
1164
|
|
|
1002
1165
|
# Check for else/elseif
|
|
1003
1166
|
if current_token && current_token[:type] == :keyword
|
|
@@ -1005,14 +1168,7 @@ module Vinter
|
|
|
1005
1168
|
advance # Skip 'else'
|
|
1006
1169
|
|
|
1007
1170
|
# Parse statements until 'endif'
|
|
1008
|
-
|
|
1009
|
-
if current_token[:type] == :keyword && current_token[:value] == 'endif'
|
|
1010
|
-
break
|
|
1011
|
-
end
|
|
1012
|
-
|
|
1013
|
-
stmt = parse_statement
|
|
1014
|
-
else_branch << stmt if stmt
|
|
1015
|
-
end
|
|
1171
|
+
else_branch = parse_body_until('endif')
|
|
1016
1172
|
elsif current_token[:value] == 'elseif'
|
|
1017
1173
|
elseif_stmt = parse_if_statement
|
|
1018
1174
|
else_branch << elseif_stmt if elseif_stmt
|
|
@@ -1028,228 +1184,63 @@ module Vinter
|
|
|
1028
1184
|
end
|
|
1029
1185
|
end
|
|
1030
1186
|
|
|
1031
|
-
# Expect endif
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
else
|
|
1035
|
-
# Don't add an error if we've already reached the end of the file
|
|
1036
|
-
if @position < @tokens.length
|
|
1037
|
-
@errors << {
|
|
1038
|
-
message: "Expected 'endif' to close if statement",
|
|
1039
|
-
position: @position,
|
|
1040
|
-
line: current_token ? current_token[:line] : 0,
|
|
1041
|
-
column: current_token ? current_token[:column] : 0
|
|
1042
|
-
}
|
|
1043
|
-
end
|
|
1044
|
-
end
|
|
1045
|
-
end
|
|
1046
|
-
|
|
1047
|
-
{
|
|
1048
|
-
type: :if_statement,
|
|
1049
|
-
condition: condition,
|
|
1050
|
-
then_branch: then_branch,
|
|
1051
|
-
else_branch: else_branch,
|
|
1052
|
-
one_line: one_line_if,
|
|
1053
|
-
line: line,
|
|
1054
|
-
column: column
|
|
1055
|
-
}
|
|
1056
|
-
end
|
|
1057
|
-
|
|
1058
|
-
def parse_while_statement
|
|
1059
|
-
token = advance # Skip 'while'
|
|
1060
|
-
line = token[:line]
|
|
1061
|
-
column = token[:column]
|
|
1062
|
-
condition = parse_expression
|
|
1063
|
-
|
|
1064
|
-
body = []
|
|
1065
|
-
|
|
1066
|
-
# Parse statements until we hit 'endwhile'
|
|
1067
|
-
while @position < @tokens.length
|
|
1068
|
-
if current_token[:type] == :keyword && current_token[:value] == 'endwhile'
|
|
1069
|
-
break
|
|
1070
|
-
end
|
|
1071
|
-
|
|
1072
|
-
stmt = parse_statement
|
|
1073
|
-
body << stmt if stmt
|
|
1074
|
-
end
|
|
1075
|
-
|
|
1076
|
-
# Expect endwhile
|
|
1077
|
-
expect(:keyword) # This should be 'endwhile'
|
|
1078
|
-
|
|
1079
|
-
{
|
|
1080
|
-
type: :while_statement,
|
|
1081
|
-
condition: condition,
|
|
1082
|
-
body: body,
|
|
1083
|
-
line: line,
|
|
1084
|
-
column: column
|
|
1085
|
-
}
|
|
1086
|
-
end
|
|
1087
|
-
|
|
1088
|
-
def parse_for_statement
|
|
1089
|
-
token = advance # Skip 'for'
|
|
1090
|
-
line = token[:line]
|
|
1091
|
-
column = token[:column]
|
|
1092
|
-
|
|
1093
|
-
# Two main patterns:
|
|
1094
|
-
# 1. for [key, val] in dict - destructuring with bracket_open
|
|
1095
|
-
# 2. for var in list - simple variable with identifier
|
|
1096
|
-
|
|
1097
|
-
if current_token && current_token[:type] == :bracket_open
|
|
1098
|
-
# Handle destructuring assignment: for [key, val] in dict
|
|
1099
|
-
advance # Skip '['
|
|
1100
|
-
|
|
1101
|
-
loop_vars = []
|
|
1102
|
-
|
|
1103
|
-
loop do
|
|
1104
|
-
if current_token && (current_token[:type] == :identifier ||
|
|
1105
|
-
current_token[:type] == :local_variable ||
|
|
1106
|
-
current_token[:type] == :global_variable ||
|
|
1107
|
-
current_token[:type] == :script_local)
|
|
1108
|
-
loop_vars << advance
|
|
1109
|
-
else
|
|
1110
|
-
@errors << {
|
|
1111
|
-
message: "Expected identifier in for loop variables",
|
|
1112
|
-
position: @position,
|
|
1113
|
-
line: current_token ? current_token[:line] : 0,
|
|
1114
|
-
column: current_token ? current_token[:column] : 0
|
|
1115
|
-
}
|
|
1116
|
-
break
|
|
1117
|
-
end
|
|
1118
|
-
|
|
1119
|
-
if current_token && current_token[:type] == :comma
|
|
1120
|
-
advance # Skip ','
|
|
1121
|
-
else
|
|
1122
|
-
break
|
|
1123
|
-
end
|
|
1124
|
-
end
|
|
1125
|
-
|
|
1126
|
-
expect(:bracket_close) # Skip ']'
|
|
1127
|
-
|
|
1128
|
-
if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
|
|
1129
|
-
@errors << {
|
|
1130
|
-
message: "Expected 'in' after for loop variables",
|
|
1131
|
-
position: @position,
|
|
1132
|
-
line: current_token ? current_token[:line] : 0,
|
|
1133
|
-
column: current_token ? current_token[:column] : 0
|
|
1134
|
-
}
|
|
1135
|
-
else
|
|
1136
|
-
advance # Skip 'in'
|
|
1137
|
-
end
|
|
1138
|
-
|
|
1139
|
-
iterable = parse_expression
|
|
1140
|
-
|
|
1141
|
-
# Parse the body until 'endfor'
|
|
1142
|
-
body = []
|
|
1143
|
-
while @position < @tokens.length
|
|
1144
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1145
|
-
break
|
|
1146
|
-
end
|
|
1147
|
-
|
|
1148
|
-
stmt = parse_statement
|
|
1149
|
-
body << stmt if stmt
|
|
1150
|
-
end
|
|
1151
|
-
|
|
1152
|
-
# Expect endfor
|
|
1153
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1154
|
-
advance # Skip 'endfor'
|
|
1155
|
-
else
|
|
1156
|
-
# Only add an error if we haven't reached the end of the file
|
|
1157
|
-
if @position < @tokens.length
|
|
1158
|
-
@errors << {
|
|
1159
|
-
message: "Expected 'endfor' to close for statement",
|
|
1160
|
-
position: @position,
|
|
1161
|
-
line: current_token ? current_token[:line] : 0,
|
|
1162
|
-
column: current_token ? current_token[:column] : 0
|
|
1163
|
-
}
|
|
1164
|
-
end
|
|
1165
|
-
end
|
|
1166
|
-
|
|
1167
|
-
return {
|
|
1168
|
-
type: :for_statement,
|
|
1169
|
-
loop_vars: loop_vars,
|
|
1170
|
-
iterable: iterable,
|
|
1171
|
-
body: body,
|
|
1172
|
-
line: line,
|
|
1173
|
-
column: column
|
|
1174
|
-
}
|
|
1175
|
-
elsif current_token && current_token[:type] == :paren_open
|
|
1176
|
-
# Handle multiple variables in parentheses: for (var1, var2) in list
|
|
1177
|
-
advance # Skip '('
|
|
1178
|
-
|
|
1179
|
-
loop_vars = []
|
|
1180
|
-
|
|
1181
|
-
loop do
|
|
1182
|
-
if current_token && (current_token[:type] == :identifier ||
|
|
1183
|
-
current_token[:type] == :local_variable ||
|
|
1184
|
-
current_token[:type] == :global_variable ||
|
|
1185
|
-
current_token[:type] == :script_local)
|
|
1186
|
-
loop_vars << advance
|
|
1187
|
-
else
|
|
1188
|
-
@errors << {
|
|
1189
|
-
message: "Expected identifier in for loop variables",
|
|
1190
|
-
position: @position,
|
|
1191
|
-
line: current_token ? current_token[:line] : 0,
|
|
1192
|
-
column: current_token ? current_token[:column] : 0
|
|
1193
|
-
}
|
|
1194
|
-
break
|
|
1195
|
-
end
|
|
1196
|
-
|
|
1197
|
-
if current_token && current_token[:type] == :comma
|
|
1198
|
-
advance # Skip ','
|
|
1199
|
-
else
|
|
1200
|
-
break
|
|
1201
|
-
end
|
|
1202
|
-
end
|
|
1203
|
-
|
|
1204
|
-
expect(:paren_close) # Skip ')'
|
|
1187
|
+
# Expect endif
|
|
1188
|
+
expect_end_keyword('endif')
|
|
1189
|
+
end
|
|
1205
1190
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1191
|
+
{
|
|
1192
|
+
type: :if_statement,
|
|
1193
|
+
condition: condition,
|
|
1194
|
+
then_branch: then_branch,
|
|
1195
|
+
else_branch: else_branch,
|
|
1196
|
+
one_line: one_line_if,
|
|
1197
|
+
line: line,
|
|
1198
|
+
column: column
|
|
1199
|
+
}
|
|
1200
|
+
end
|
|
1216
1201
|
|
|
1217
|
-
|
|
1202
|
+
def parse_while_statement
|
|
1203
|
+
token = advance # Skip 'while'
|
|
1204
|
+
line = token[:line]
|
|
1205
|
+
column = token[:column]
|
|
1206
|
+
condition = parse_expression
|
|
1218
1207
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
while @position < @tokens.length
|
|
1222
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1223
|
-
break
|
|
1224
|
-
end
|
|
1208
|
+
# Parse statements until 'endwhile'
|
|
1209
|
+
body = parse_body_until('endwhile')
|
|
1225
1210
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
end
|
|
1211
|
+
# Expect endwhile
|
|
1212
|
+
expect_end_keyword('endwhile')
|
|
1229
1213
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
position: @position,
|
|
1239
|
-
line: current_token ? current_token[:line] : 0,
|
|
1240
|
-
column: current_token ? current_token[:column] : 0
|
|
1241
|
-
}
|
|
1242
|
-
end
|
|
1243
|
-
end
|
|
1214
|
+
{
|
|
1215
|
+
type: :while_statement,
|
|
1216
|
+
condition: condition,
|
|
1217
|
+
body: body,
|
|
1218
|
+
line: line,
|
|
1219
|
+
column: column
|
|
1220
|
+
}
|
|
1221
|
+
end
|
|
1244
1222
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1223
|
+
def parse_for_statement
|
|
1224
|
+
token = advance # Skip 'for'
|
|
1225
|
+
line = token[:line]
|
|
1226
|
+
column = token[:column]
|
|
1227
|
+
|
|
1228
|
+
# Determine the type of for loop and parse variables accordingly
|
|
1229
|
+
loop_var = nil
|
|
1230
|
+
loop_vars = nil
|
|
1231
|
+
|
|
1232
|
+
if current_token && current_token[:type] == :bracket_open
|
|
1233
|
+
# Handle destructuring with brackets: for [key, val] in dict
|
|
1234
|
+
advance # Skip '['
|
|
1235
|
+
loop_vars = parse_loop_variables
|
|
1236
|
+
expect(:bracket_close)
|
|
1237
|
+
expect_in_keyword
|
|
1238
|
+
elsif current_token && current_token[:type] == :paren_open
|
|
1239
|
+
# Handle multiple variables with parentheses: for (var1, var2) in list
|
|
1240
|
+
advance # Skip '('
|
|
1241
|
+
loop_vars = parse_loop_variables
|
|
1242
|
+
expect(:paren_close)
|
|
1243
|
+
expect_in_keyword
|
|
1253
1244
|
else
|
|
1254
1245
|
# Handle single variable: for var in list
|
|
1255
1246
|
if current_token && (current_token[:type] == :identifier ||
|
|
@@ -1258,63 +1249,36 @@ module Vinter
|
|
|
1258
1249
|
current_token[:type] == :script_local)
|
|
1259
1250
|
loop_var = advance
|
|
1260
1251
|
else
|
|
1261
|
-
|
|
1262
|
-
message: "Expected identifier as for loop variable",
|
|
1263
|
-
position: @position,
|
|
1264
|
-
line: current_token ? current_token[:line] : 0,
|
|
1265
|
-
column: current_token ? current_token[:column] : 0
|
|
1266
|
-
}
|
|
1267
|
-
loop_var = nil
|
|
1268
|
-
end
|
|
1269
|
-
|
|
1270
|
-
if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
|
|
1271
|
-
@errors << {
|
|
1272
|
-
message: "Expected 'in' after for loop variable",
|
|
1273
|
-
position: @position,
|
|
1274
|
-
line: current_token ? current_token[:line] : 0,
|
|
1275
|
-
column: current_token ? current_token[:column] : 0
|
|
1276
|
-
}
|
|
1277
|
-
else
|
|
1278
|
-
advance # Skip 'in'
|
|
1252
|
+
add_error("Expected identifier as for loop variable")
|
|
1279
1253
|
end
|
|
1254
|
+
expect_in_keyword
|
|
1255
|
+
end
|
|
1280
1256
|
|
|
1281
|
-
|
|
1257
|
+
# Parse the iterable expression
|
|
1258
|
+
iterable = parse_expression
|
|
1282
1259
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
while @position < @tokens.length
|
|
1286
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1287
|
-
break
|
|
1288
|
-
end
|
|
1260
|
+
# Parse the body until 'endfor'
|
|
1261
|
+
body = parse_body_until('endfor')
|
|
1289
1262
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
end
|
|
1263
|
+
# Expect endfor
|
|
1264
|
+
expect_end_keyword('endfor')
|
|
1293
1265
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
position: @position,
|
|
1303
|
-
line: current_token ? current_token[:line] : 0,
|
|
1304
|
-
column: current_token ? current_token[:column] : 0
|
|
1305
|
-
}
|
|
1306
|
-
end
|
|
1307
|
-
end
|
|
1266
|
+
# Build result hash based on whether we have single or multiple vars
|
|
1267
|
+
result = {
|
|
1268
|
+
type: :for_statement,
|
|
1269
|
+
iterable: iterable,
|
|
1270
|
+
body: body,
|
|
1271
|
+
line: line,
|
|
1272
|
+
column: column
|
|
1273
|
+
}
|
|
1308
1274
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
body: body,
|
|
1314
|
-
line: line,
|
|
1315
|
-
column: column
|
|
1316
|
-
}
|
|
1275
|
+
if loop_vars
|
|
1276
|
+
result[:loop_vars] = loop_vars
|
|
1277
|
+
else
|
|
1278
|
+
result[:loop_var] = loop_var
|
|
1317
1279
|
end
|
|
1280
|
+
|
|
1281
|
+
result
|
|
1318
1282
|
end
|
|
1319
1283
|
|
|
1320
1284
|
def parse_def_function
|
|
@@ -1331,24 +1295,16 @@ module Vinter
|
|
|
1331
1295
|
|
|
1332
1296
|
# Parse optional return type
|
|
1333
1297
|
return_type = nil
|
|
1334
|
-
if current_token && current_token[:type] == :colon
|
|
1298
|
+
if current_token && current_token[:type] == :colon && current_token[:line] == line
|
|
1335
1299
|
advance # Skip ':'
|
|
1336
1300
|
return_type = parse_type
|
|
1337
1301
|
end
|
|
1338
1302
|
|
|
1339
|
-
# Parse function body
|
|
1340
|
-
body =
|
|
1341
|
-
while @position < @tokens.length
|
|
1342
|
-
if current_token[:type] == :keyword && current_token[:value] == 'enddef'
|
|
1343
|
-
break
|
|
1344
|
-
end
|
|
1345
|
-
|
|
1346
|
-
stmt = parse_statement
|
|
1347
|
-
body << stmt if stmt
|
|
1348
|
-
end
|
|
1303
|
+
# Parse function body until 'enddef'
|
|
1304
|
+
body = parse_body_until('enddef')
|
|
1349
1305
|
|
|
1350
1306
|
# Expect enddef
|
|
1351
|
-
|
|
1307
|
+
expect_end_keyword('enddef')
|
|
1352
1308
|
|
|
1353
1309
|
{
|
|
1354
1310
|
type: :def_function,
|
|
@@ -1391,28 +1347,12 @@ module Vinter
|
|
|
1391
1347
|
|
|
1392
1348
|
# After varargs, we expect closing paren
|
|
1393
1349
|
if current_token && current_token[:type] != :paren_close
|
|
1394
|
-
|
|
1395
|
-
message: "Expected closing parenthesis after varargs",
|
|
1396
|
-
position: @position,
|
|
1397
|
-
line: current_token[:line],
|
|
1398
|
-
column: current_token[:column]
|
|
1399
|
-
}
|
|
1350
|
+
add_error("Expected closing parenthesis after varargs", current_token)
|
|
1400
1351
|
end
|
|
1401
1352
|
|
|
1402
1353
|
break
|
|
1403
1354
|
end
|
|
1404
1355
|
|
|
1405
|
-
# Get parameter name
|
|
1406
|
-
if !current_token || current_token[:type] != :identifier
|
|
1407
|
-
@errors << {
|
|
1408
|
-
message: "Expected parameter name",
|
|
1409
|
-
position: @position,
|
|
1410
|
-
line: current_token ? current_token[:line] : 0,
|
|
1411
|
-
column: current_token ? current_token[:column] : 0
|
|
1412
|
-
}
|
|
1413
|
-
break
|
|
1414
|
-
end
|
|
1415
|
-
|
|
1416
1356
|
param_name = advance
|
|
1417
1357
|
|
|
1418
1358
|
# Check for type annotation
|
|
@@ -1444,12 +1384,7 @@ module Vinter
|
|
|
1444
1384
|
advance
|
|
1445
1385
|
# If we don't have a comma, we should have a closing paren
|
|
1446
1386
|
elsif current_token && current_token[:type] != :paren_close
|
|
1447
|
-
|
|
1448
|
-
message: "Expected comma or closing parenthesis after parameter",
|
|
1449
|
-
position: @position,
|
|
1450
|
-
line: current_token[:line],
|
|
1451
|
-
column: current_token[:column]
|
|
1452
|
-
}
|
|
1387
|
+
add_error("Expected comma or closing parenthesis after parameter", current_token)
|
|
1453
1388
|
break
|
|
1454
1389
|
end
|
|
1455
1390
|
end
|
|
@@ -1478,12 +1413,7 @@ module Vinter
|
|
|
1478
1413
|
|
|
1479
1414
|
return type_name[:value]
|
|
1480
1415
|
else
|
|
1481
|
-
|
|
1482
|
-
message: "Expected type identifier",
|
|
1483
|
-
position: @position,
|
|
1484
|
-
line: current_token ? current_token[:line] : 0,
|
|
1485
|
-
column: current_token ? current_token[:column] : 0
|
|
1486
|
-
}
|
|
1416
|
+
add_error("Expected type identifier")
|
|
1487
1417
|
advance
|
|
1488
1418
|
return "unknown"
|
|
1489
1419
|
end
|
|
@@ -1496,12 +1426,7 @@ module Vinter
|
|
|
1496
1426
|
column = var_type_token[:column]
|
|
1497
1427
|
|
|
1498
1428
|
if !current_token || current_token[:type] != :identifier
|
|
1499
|
-
|
|
1500
|
-
message: "Expected variable name",
|
|
1501
|
-
position: @position,
|
|
1502
|
-
line: current_token ? current_token[:line] : 0,
|
|
1503
|
-
column: current_token ? current_token[:column] : 0
|
|
1504
|
-
}
|
|
1429
|
+
add_error("Expected variable name")
|
|
1505
1430
|
return nil
|
|
1506
1431
|
end
|
|
1507
1432
|
|
|
@@ -1517,7 +1442,7 @@ module Vinter
|
|
|
1517
1442
|
|
|
1518
1443
|
# Parse initializer if present
|
|
1519
1444
|
initializer = nil
|
|
1520
|
-
if current_token && current_token[:type] == :operator && current_token[:value] == '='
|
|
1445
|
+
if current_token && (current_token[:type] == :operator && current_token[:value] == '=')
|
|
1521
1446
|
advance # Skip '='
|
|
1522
1447
|
initializer = parse_expression
|
|
1523
1448
|
end
|
|
@@ -1810,7 +1735,7 @@ module Vinter
|
|
|
1810
1735
|
# Special handling for map-related keywords when they appear in expressions
|
|
1811
1736
|
if ['map', 'nmap', 'imap', 'vmap', 'xmap', 'noremap', 'nnoremap', 'inoremap', 'vnoremap', 'xnoremap', 'cnoremap', 'cmap'].include?(token[:value])
|
|
1812
1737
|
if peek_token[:type] == :paren_open
|
|
1813
|
-
parse_builtin_function_call
|
|
1738
|
+
parse_builtin_function_call
|
|
1814
1739
|
else
|
|
1815
1740
|
# Treat map commands as identifiers when inside expressions
|
|
1816
1741
|
advance
|
|
@@ -1823,7 +1748,7 @@ module Vinter
|
|
|
1823
1748
|
end
|
|
1824
1749
|
elsif token[:value] == 'type' && current_token && (current_token[:type] == :paren_open || peek_token && peek_token[:type] == :paren_open)
|
|
1825
1750
|
# This is the type() function call
|
|
1826
|
-
return parse_builtin_function_call
|
|
1751
|
+
return parse_builtin_function_call
|
|
1827
1752
|
elsif token[:value] == 'function'
|
|
1828
1753
|
# Check if this is a function() call or just 'function' as property name
|
|
1829
1754
|
if peek_token && peek_token[:type] == :paren_open
|
|
@@ -1909,6 +1834,8 @@ module Vinter
|
|
|
1909
1834
|
line: line,
|
|
1910
1835
|
column: column
|
|
1911
1836
|
}
|
|
1837
|
+
elsif token[:value] == 'command'
|
|
1838
|
+
advance
|
|
1912
1839
|
else
|
|
1913
1840
|
@errors << {
|
|
1914
1841
|
message: "Unexpected keyword in expression: #{token[:value]}",
|
|
@@ -2036,7 +1963,7 @@ module Vinter
|
|
|
2036
1963
|
when :identifier
|
|
2037
1964
|
# Special handling for Vim built-in functions
|
|
2038
1965
|
if ['has', 'exists', 'empty', 'get', 'type', 'map', 'copy'].include?(token[:value])
|
|
2039
|
-
expr = parse_builtin_function_call
|
|
1966
|
+
expr = parse_builtin_function_call
|
|
2040
1967
|
else
|
|
2041
1968
|
advance
|
|
2042
1969
|
|
|
@@ -2064,6 +1991,18 @@ module Vinter
|
|
|
2064
1991
|
end
|
|
2065
1992
|
end
|
|
2066
1993
|
end
|
|
1994
|
+
when :vimfuncs
|
|
1995
|
+
advance
|
|
1996
|
+
if current_token[:type] == :paren_open
|
|
1997
|
+
expr = parse_function_call(token[:value], line, column)
|
|
1998
|
+
else
|
|
1999
|
+
expr = {
|
|
2000
|
+
type: :vimfuncs,
|
|
2001
|
+
name: token[:value],
|
|
2002
|
+
line: line,
|
|
2003
|
+
column: column
|
|
2004
|
+
}
|
|
2005
|
+
end
|
|
2067
2006
|
when :namespace_prefix
|
|
2068
2007
|
advance
|
|
2069
2008
|
expr = {
|
|
@@ -2099,6 +2038,13 @@ module Vinter
|
|
|
2099
2038
|
when :line_continuation
|
|
2100
2039
|
advance
|
|
2101
2040
|
expr = parse_expression
|
|
2041
|
+
when :interpolated_string
|
|
2042
|
+
token = advance
|
|
2043
|
+
value = token[:value]
|
|
2044
|
+
expr = {
|
|
2045
|
+
type: :interpolated_string,
|
|
2046
|
+
value: token[:value],
|
|
2047
|
+
}
|
|
2102
2048
|
else
|
|
2103
2049
|
@errors << {
|
|
2104
2050
|
message: "Unexpected token in expression: #{token[:type]}",
|
|
@@ -2117,7 +2063,7 @@ module Vinter
|
|
|
2117
2063
|
# Look ahead to determine if this is property access or concatenation
|
|
2118
2064
|
next_token = peek_token
|
|
2119
2065
|
is_property_access = next_token && (next_token[:type] == :identifier || next_token[:type] == :keyword)
|
|
2120
|
-
|
|
2066
|
+
|
|
2121
2067
|
# Check if this is a property access (only when right side is identifier/keyword)
|
|
2122
2068
|
if is_property_access && (expr[:type] == :identifier || expr[:type] == :global_variable ||
|
|
2123
2069
|
expr[:type] == :script_local || expr[:type] == :namespace_prefix ||
|
|
@@ -2155,6 +2101,7 @@ module Vinter
|
|
|
2155
2101
|
# Check for method call with arrow ->
|
|
2156
2102
|
elsif current_token[:type] == :operator && current_token[:value] == '->'
|
|
2157
2103
|
arrow_token = advance # Skip '->'
|
|
2104
|
+
advance if current_token[:type] == :line_continuation
|
|
2158
2105
|
|
|
2159
2106
|
# Next token should be an identifier (method name)
|
|
2160
2107
|
if !current_token || current_token[:type] != :identifier
|
|
@@ -2219,6 +2166,12 @@ module Vinter
|
|
|
2219
2166
|
else
|
|
2220
2167
|
end_index = parse_expression
|
|
2221
2168
|
end
|
|
2169
|
+
# handles array assignment [a,b] = split(thing)
|
|
2170
|
+
elsif current_token[:type] == :comma
|
|
2171
|
+
while current_token[:type] == :comma
|
|
2172
|
+
advance
|
|
2173
|
+
parse_expression
|
|
2174
|
+
end
|
|
2222
2175
|
end
|
|
2223
2176
|
|
|
2224
2177
|
expect(:bracket_close) # Skip ']'
|
|
@@ -2281,76 +2234,19 @@ module Vinter
|
|
|
2281
2234
|
return expr
|
|
2282
2235
|
end
|
|
2283
2236
|
|
|
2284
|
-
def parse_builtin_function_call
|
|
2285
|
-
#
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
if current_token && current_token[:type] == :paren_open
|
|
2290
|
-
advance # Skip '('
|
|
2291
|
-
|
|
2292
|
-
# Parse arguments
|
|
2293
|
-
args = []
|
|
2294
|
-
|
|
2295
|
-
# Functions that take string expressions as code
|
|
2296
|
-
special_functions = ['map', 'reduce', 'sort', 'call', 'eval', 'execute', 'exec']
|
|
2297
|
-
is_special_function = special_functions.include?(name)
|
|
2298
|
-
|
|
2299
|
-
# Parse until closing parenthesis
|
|
2300
|
-
while @position < @tokens.length && current_token && current_token[:type] != :paren_close
|
|
2301
|
-
# Skip whitespace or comments
|
|
2302
|
-
if current_token[:type] == :whitespace || current_token[:type] == :comment
|
|
2303
|
-
advance
|
|
2304
|
-
next
|
|
2305
|
-
end
|
|
2306
|
-
|
|
2307
|
-
# Special handling for string arguments that contain code
|
|
2308
|
-
if is_special_function &&
|
|
2309
|
-
current_token && current_token[:type] == :string
|
|
2310
|
-
string_token = parse_expression
|
|
2311
|
-
args << {
|
|
2312
|
-
type: :literal,
|
|
2313
|
-
value: string_token[:value],
|
|
2314
|
-
token_type: :string,
|
|
2315
|
-
line: string_token[:line],
|
|
2316
|
-
column: string_token[:column]
|
|
2317
|
-
}
|
|
2318
|
-
else
|
|
2319
|
-
arg = parse_expression
|
|
2320
|
-
args << arg if arg
|
|
2321
|
-
end
|
|
2322
|
-
|
|
2323
|
-
if current_token && current_token[:type] == :comma
|
|
2324
|
-
advance
|
|
2325
|
-
elsif current_token && current_token[:type] != :paren_close
|
|
2326
|
-
@errors << {
|
|
2327
|
-
message: "Expected comma or closing parenthesis in #{name} function",
|
|
2328
|
-
position: @position,
|
|
2329
|
-
line: current_token[:line],
|
|
2330
|
-
column: current_token[:column]
|
|
2331
|
-
}
|
|
2332
|
-
break
|
|
2333
|
-
end
|
|
2334
|
-
end
|
|
2237
|
+
def parse_builtin_function_call
|
|
2238
|
+
token = advance # Consume the `builtin_funcs` token
|
|
2239
|
+
line = token[:line]
|
|
2240
|
+
column = token[:column]
|
|
2241
|
+
name = token[:value]
|
|
2335
2242
|
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
message: "Expected ')' to close #{name} function call",
|
|
2342
|
-
position: @position,
|
|
2343
|
-
line: current_token ? current_token[:line] : 0,
|
|
2344
|
-
column: current_token ? current_token[:column] : 0
|
|
2345
|
-
}
|
|
2346
|
-
end
|
|
2347
|
-
else
|
|
2348
|
-
# Handle legacy Vim script where parentheses might be omitted
|
|
2349
|
-
# Just parse one expression as the argument
|
|
2350
|
-
args = [parse_expression]
|
|
2243
|
+
# Collect arguments (if any) until the end of the line
|
|
2244
|
+
args = []
|
|
2245
|
+
while current_token && current_token[:type] != :comment && current_token[:value] != "\n"
|
|
2246
|
+
args << current_token[:value]
|
|
2247
|
+
advance
|
|
2351
2248
|
end
|
|
2352
2249
|
|
|
2353
|
-
# Return function call node
|
|
2354
2250
|
{
|
|
2355
2251
|
type: :builtin_function_call,
|
|
2356
2252
|
name: name,
|
|
@@ -2754,7 +2650,7 @@ module Vinter
|
|
|
2754
2650
|
current_token[:type] == :whitespace)
|
|
2755
2651
|
advance
|
|
2756
2652
|
end
|
|
2757
|
-
|
|
2653
|
+
|
|
2758
2654
|
# After skipping continuations, we should find the closing brace
|
|
2759
2655
|
if current_token && current_token[:type] == :brace_close
|
|
2760
2656
|
# This is fine - last entry without trailing comma
|
|
@@ -2909,42 +2805,30 @@ module Vinter
|
|
|
2909
2805
|
|
|
2910
2806
|
args = []
|
|
2911
2807
|
|
|
2912
|
-
# Special handling for Vim functions that take code strings as arguments
|
|
2913
|
-
special_functions = ['map', 'reduce', 'sort', 'call', 'eval', 'execute', 'exec']
|
|
2914
|
-
is_special_function = special_functions.include?(name)
|
|
2915
|
-
|
|
2916
2808
|
# Parse arguments until we find a closing parenthesis
|
|
2917
2809
|
while @position < @tokens.length && current_token && current_token[:type] != :paren_close
|
|
2918
|
-
# Skip
|
|
2919
|
-
if current_token
|
|
2810
|
+
# Skip line continuations
|
|
2811
|
+
if current_token[:type] == :line_continuation
|
|
2920
2812
|
advance
|
|
2921
2813
|
next
|
|
2922
2814
|
end
|
|
2923
|
-
if is_special_function && current_token && current_token[:type] == :string
|
|
2924
|
-
# For functions like map(), filter(), directly add the string as an argument
|
|
2925
|
-
string_token = parse_string
|
|
2926
|
-
args << {
|
|
2927
|
-
type: :literal,
|
|
2928
|
-
value: string_token[:value],
|
|
2929
|
-
token_type: :string,
|
|
2930
|
-
line: string_token[:line],
|
|
2931
|
-
column: string_token[:column]
|
|
2932
|
-
}
|
|
2933
|
-
else
|
|
2934
|
-
# Parse the argument
|
|
2935
|
-
arg = parse_expression
|
|
2936
|
-
args << arg if arg
|
|
2937
|
-
end
|
|
2938
2815
|
|
|
2939
|
-
#
|
|
2816
|
+
# Parse the argument
|
|
2817
|
+
arg = parse_expression
|
|
2818
|
+
args << arg if arg
|
|
2819
|
+
|
|
2820
|
+
# Break if we hit the closing parenthesis
|
|
2940
2821
|
if current_token && current_token[:type] == :paren_close
|
|
2941
2822
|
break
|
|
2942
2823
|
end
|
|
2824
|
+
|
|
2943
2825
|
# If we have a comma, advance past it and continue
|
|
2944
2826
|
if current_token && current_token[:type] == :comma
|
|
2945
2827
|
advance
|
|
2828
|
+
elsif current_token[:type] == :line_continuation
|
|
2829
|
+
advance
|
|
2946
2830
|
# If we don't have a comma and we're not at the end, it's an error
|
|
2947
|
-
elsif current_token && current_token[:type] != :paren_close
|
|
2831
|
+
elsif current_token && current_token[:type] != :paren_close
|
|
2948
2832
|
@errors << {
|
|
2949
2833
|
message: "Expected comma or closing parenthesis after argument",
|
|
2950
2834
|
position: @position,
|
|
@@ -3086,6 +2970,13 @@ module Vinter
|
|
|
3086
2970
|
column: column
|
|
3087
2971
|
}
|
|
3088
2972
|
end
|
|
2973
|
+
|
|
2974
|
+
def parse_scriptencoding
|
|
2975
|
+
token = advance
|
|
2976
|
+
advance
|
|
2977
|
+
{ type: :scriptencoding, encoding: current_token[:value] }
|
|
2978
|
+
end
|
|
2979
|
+
|
|
3089
2980
|
def parse_export_statement
|
|
3090
2981
|
token = advance # Skip 'export'
|
|
3091
2982
|
line = token[:line]
|
|
@@ -3188,7 +3079,7 @@ module Vinter
|
|
|
3188
3079
|
advance # Skip the '='
|
|
3189
3080
|
|
|
3190
3081
|
# Get the value (number or identifier)
|
|
3191
|
-
if current_token && (current_token[:type] == :number || current_token[:type] == :identifier)
|
|
3082
|
+
if current_token && (current_token[:type] == :number || current_token[:type] == :identifier) || current_token[:type] == :operator
|
|
3192
3083
|
attribute += "=#{current_token[:value]}"
|
|
3193
3084
|
attributes << attribute
|
|
3194
3085
|
advance
|
|
@@ -3198,6 +3089,7 @@ module Vinter
|
|
|
3198
3089
|
|
|
3199
3090
|
# Parse the command name
|
|
3200
3091
|
command_name = nil
|
|
3092
|
+
advance if current_token[:type] == :line_continuation
|
|
3201
3093
|
if current_token && current_token[:type] == :identifier
|
|
3202
3094
|
command_name = current_token[:value]
|
|
3203
3095
|
advance
|
|
@@ -3296,33 +3188,11 @@ module Vinter
|
|
|
3296
3188
|
end
|
|
3297
3189
|
end
|
|
3298
3190
|
|
|
3299
|
-
# Parse function body
|
|
3300
|
-
body =
|
|
3301
|
-
while @position < @tokens.length
|
|
3302
|
-
if current_token && current_token[:type] == :keyword &&
|
|
3303
|
-
['endfunction', 'endfunc'].include?(current_token[:value])
|
|
3304
|
-
break
|
|
3305
|
-
end
|
|
3306
|
-
|
|
3307
|
-
stmt = parse_statement
|
|
3308
|
-
body << stmt if stmt
|
|
3309
|
-
end
|
|
3191
|
+
# Parse function body until 'endfunction' or 'endfunc'
|
|
3192
|
+
body = parse_body_until('endfunction', 'endfunc')
|
|
3310
3193
|
|
|
3311
3194
|
# Expect endfunction/endfunc
|
|
3312
|
-
|
|
3313
|
-
['endfunction', 'endfunc'].include?(current_token[:value])
|
|
3314
|
-
advance # Skip 'endfunction' or 'endfunc'
|
|
3315
|
-
else
|
|
3316
|
-
# Only add an error if we haven't reached the end of the file
|
|
3317
|
-
if @position < @tokens.length
|
|
3318
|
-
@errors << {
|
|
3319
|
-
message: "Expected 'endfunction' or 'endfunc'",
|
|
3320
|
-
position: @position,
|
|
3321
|
-
line: current_token ? current_token[:line] : 0,
|
|
3322
|
-
column: current_token ? current_token[:column] : 0
|
|
3323
|
-
}
|
|
3324
|
-
end
|
|
3325
|
-
end
|
|
3195
|
+
expect_end_keyword('endfunction', 'endfunc')
|
|
3326
3196
|
|
|
3327
3197
|
function_name = name ? name[:value] : nil
|
|
3328
3198
|
if function_scope
|
|
@@ -3508,22 +3378,12 @@ module Vinter
|
|
|
3508
3378
|
line = token[:line]
|
|
3509
3379
|
column = token[:column]
|
|
3510
3380
|
|
|
3511
|
-
# Parse the try body
|
|
3512
|
-
body =
|
|
3381
|
+
# Parse the try body until catch/finally/endtry
|
|
3382
|
+
body = parse_body_until('catch', 'finally', 'endtry')
|
|
3383
|
+
|
|
3513
3384
|
catch_clauses = []
|
|
3514
3385
|
finally_clause = nil
|
|
3515
3386
|
|
|
3516
|
-
# Parse statements in the try block
|
|
3517
|
-
while @position < @tokens.length
|
|
3518
|
-
if current_token && current_token[:type] == :keyword &&
|
|
3519
|
-
['catch', 'finally', 'endtry'].include?(current_token[:value])
|
|
3520
|
-
break
|
|
3521
|
-
end
|
|
3522
|
-
|
|
3523
|
-
stmt = parse_statement
|
|
3524
|
-
body << stmt if stmt
|
|
3525
|
-
end
|
|
3526
|
-
|
|
3527
3387
|
# Parse catch clauses
|
|
3528
3388
|
while @position < @tokens.length &&
|
|
3529
3389
|
current_token && current_token[:type] == :keyword &&
|
|
@@ -3549,17 +3409,8 @@ module Vinter
|
|
|
3549
3409
|
advance
|
|
3550
3410
|
end
|
|
3551
3411
|
|
|
3552
|
-
# Parse the catch body
|
|
3553
|
-
catch_body =
|
|
3554
|
-
while @position < @tokens.length
|
|
3555
|
-
if current_token && current_token[:type] == :keyword &&
|
|
3556
|
-
['catch', 'finally', 'endtry'].include?(current_token[:value])
|
|
3557
|
-
break
|
|
3558
|
-
end
|
|
3559
|
-
|
|
3560
|
-
stmt = parse_statement
|
|
3561
|
-
catch_body << stmt if stmt
|
|
3562
|
-
end
|
|
3412
|
+
# Parse the catch body until next catch/finally/endtry
|
|
3413
|
+
catch_body = parse_body_until('catch', 'finally', 'endtry')
|
|
3563
3414
|
|
|
3564
3415
|
catch_clauses << {
|
|
3565
3416
|
type: :catch_clause,
|
|
@@ -3578,16 +3429,8 @@ module Vinter
|
|
|
3578
3429
|
finally_line = finally_token[:line]
|
|
3579
3430
|
finally_column = finally_token[:column]
|
|
3580
3431
|
|
|
3581
|
-
# Parse the finally body
|
|
3582
|
-
finally_body =
|
|
3583
|
-
while @position < @tokens.length
|
|
3584
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endtry'
|
|
3585
|
-
break
|
|
3586
|
-
end
|
|
3587
|
-
|
|
3588
|
-
stmt = parse_statement
|
|
3589
|
-
finally_body << stmt if stmt
|
|
3590
|
-
end
|
|
3432
|
+
# Parse the finally body until 'endtry'
|
|
3433
|
+
finally_body = parse_body_until('endtry')
|
|
3591
3434
|
|
|
3592
3435
|
finally_clause = {
|
|
3593
3436
|
type: :finally_clause,
|
|
@@ -3598,19 +3441,7 @@ module Vinter
|
|
|
3598
3441
|
end
|
|
3599
3442
|
|
|
3600
3443
|
# Expect endtry
|
|
3601
|
-
|
|
3602
|
-
advance # Skip 'endtry'
|
|
3603
|
-
else
|
|
3604
|
-
# Only add an error if we haven't reached the end of the file
|
|
3605
|
-
if @position < @tokens.length
|
|
3606
|
-
@errors << {
|
|
3607
|
-
message: "Expected 'endtry' to close try statement",
|
|
3608
|
-
position: @position,
|
|
3609
|
-
line: current_token ? current_token[:line] : 0,
|
|
3610
|
-
column: current_token ? current_token[:column] : 0
|
|
3611
|
-
}
|
|
3612
|
-
end
|
|
3613
|
-
end
|
|
3444
|
+
expect_end_keyword('endtry')
|
|
3614
3445
|
|
|
3615
3446
|
return {
|
|
3616
3447
|
type: :try_statement,
|
|
@@ -3881,14 +3712,14 @@ module Vinter
|
|
|
3881
3712
|
def parse_brace_sequence_value
|
|
3882
3713
|
# Handle special brace sequences like {{{,}}} for foldmarker
|
|
3883
3714
|
value_parts = []
|
|
3884
|
-
|
|
3885
|
-
while current_token && (current_token[:type] == :brace_open ||
|
|
3886
|
-
current_token[:type] == :brace_close ||
|
|
3715
|
+
|
|
3716
|
+
while current_token && (current_token[:type] == :brace_open ||
|
|
3717
|
+
current_token[:type] == :brace_close ||
|
|
3887
3718
|
current_token[:type] == :comma)
|
|
3888
3719
|
value_parts << current_token[:value]
|
|
3889
3720
|
advance
|
|
3890
3721
|
end
|
|
3891
|
-
|
|
3722
|
+
|
|
3892
3723
|
return {
|
|
3893
3724
|
type: :brace_sequence,
|
|
3894
3725
|
value: value_parts.join(''),
|
|
@@ -3900,7 +3731,7 @@ module Vinter
|
|
|
3900
3731
|
def parse_comma_separated_value
|
|
3901
3732
|
# Handle comma-separated option values like menu,menuone,noinsert,noselect
|
|
3902
3733
|
values = []
|
|
3903
|
-
|
|
3734
|
+
|
|
3904
3735
|
# Parse first value
|
|
3905
3736
|
if current_token && current_token[:type] == :identifier
|
|
3906
3737
|
values << current_token[:value]
|
|
@@ -3908,7 +3739,7 @@ module Vinter
|
|
|
3908
3739
|
else
|
|
3909
3740
|
return parse_expression # Fall back to regular expression parsing
|
|
3910
3741
|
end
|
|
3911
|
-
|
|
3742
|
+
|
|
3912
3743
|
# Parse additional comma-separated values
|
|
3913
3744
|
while current_token && current_token[:type] == :comma
|
|
3914
3745
|
advance # Skip comma
|
|
@@ -3919,7 +3750,7 @@ module Vinter
|
|
|
3919
3750
|
break
|
|
3920
3751
|
end
|
|
3921
3752
|
end
|
|
3922
|
-
|
|
3753
|
+
|
|
3923
3754
|
return {
|
|
3924
3755
|
type: :comma_separated_value,
|
|
3925
3756
|
values: values,
|
|
@@ -3971,7 +3802,7 @@ module Vinter
|
|
|
3971
3802
|
value = nil
|
|
3972
3803
|
if current_token && current_token[:type] == :operator && current_token[:value] == '='
|
|
3973
3804
|
advance # Skip '='
|
|
3974
|
-
|
|
3805
|
+
|
|
3975
3806
|
# Special handling for foldmarker and similar options that use brace notation
|
|
3976
3807
|
if option_name == 'foldmarker' && current_token && current_token[:type] == :brace_open
|
|
3977
3808
|
# Parse as special brace sequence value (e.g., {{{,}}})
|
|
@@ -4098,8 +3929,8 @@ module Vinter
|
|
|
4098
3929
|
advance
|
|
4099
3930
|
|
|
4100
3931
|
# Parse attribute value (can be identifier, number, or hex color)
|
|
4101
|
-
if current_token && (current_token[:type] == :identifier ||
|
|
4102
|
-
current_token[:type] == :number ||
|
|
3932
|
+
if current_token && (current_token[:type] == :identifier ||
|
|
3933
|
+
current_token[:type] == :number ||
|
|
4103
3934
|
current_token[:type] == :hex_color)
|
|
4104
3935
|
attributes[attr_name] = current_token[:value]
|
|
4105
3936
|
advance
|