vinter 0.3.0 → 0.5.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.
data/lib/vinter/parser.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  module Vinter
2
2
  class Parser
3
- def initialize(tokens)
3
+ def initialize(tokens, source_text = nil)
4
4
  @tokens = tokens
5
5
  @position = 0
6
6
  @errors = []
7
7
  @warnings = []
8
+ @source_text = source_text
8
9
  end
9
10
 
10
11
  def parse
@@ -42,12 +43,123 @@ module Vinter
42
43
  found = current_token ? current_token[:type] : "end of input"
43
44
  line = current_token ? current_token[:line] : 0
44
45
  column = current_token ? current_token[:column] : 0
46
+
47
+ # Get the full line content from the input if available
48
+ line_content = nil
49
+ if line > 0 && @tokens.length > 0
50
+ # Find tokens on the same line
51
+ same_line_tokens = @tokens.select { |t| t[:line] == line }
52
+ if !same_line_tokens.empty?
53
+ # Create a representation of the line with a marker at the error position
54
+ line_representation = same_line_tokens.map { |t| t[:value] }.join('')
55
+ line_content = "Line content: #{line_representation}"
56
+ end
57
+ end
58
+
45
59
  error = "Expected #{expected} but found #{found}"
46
- @errors << { message: error, position: @position, line: line, column: column }
60
+ error += ", at line #{line}, column #{column}"
61
+ error += "\n#{line_content}" if line_content
62
+
63
+ @errors << {
64
+ message: error,
65
+ position: @position,
66
+ line: line,
67
+ column: column,
68
+ line_content: line_content
69
+ }
47
70
  nil
48
71
  end
49
72
  end
50
73
 
74
+ # Helper method to add errors with consistent formatting
75
+ def add_error(message, token = current_token)
76
+ @errors << {
77
+ message: message,
78
+ position: @position,
79
+ line: token ? token[:line] : 0,
80
+ column: token ? token[:column] : 0
81
+ }
82
+ end
83
+
84
+ # Helper method to expect a specific keyword value
85
+ def expect_keyword(value)
86
+ if current_token && current_token[:type] == :keyword && current_token[:value] == value
87
+ advance
88
+ true
89
+ else
90
+ add_error("Expected '#{value}' keyword")
91
+ false
92
+ end
93
+ end
94
+
95
+ # Helper method to parse a body of statements until one or more end keywords
96
+ # Returns array of parsed statements
97
+ def parse_body_until(*end_keywords)
98
+ body = []
99
+ while @position < @tokens.length
100
+ # Check if we've reached an end keyword
101
+ if current_token && current_token[:type] == :keyword &&
102
+ end_keywords.include?(current_token[:value])
103
+ break
104
+ end
105
+
106
+ stmt = parse_statement
107
+ body << stmt if stmt
108
+ end
109
+ body
110
+ end
111
+
112
+ # Helper method to expect and consume an end keyword
113
+ # Returns true if found, false and adds error if not
114
+ def expect_end_keyword(*keywords)
115
+ if current_token && current_token[:type] == :keyword &&
116
+ keywords.include?(current_token[:value])
117
+ advance
118
+ true
119
+ else
120
+ # Only add error if we haven't reached end of file
121
+ if @position < @tokens.length
122
+ add_error("Expected #{keywords.join(' or ')} to close block")
123
+ end
124
+ false
125
+ end
126
+ end
127
+
128
+ # Helper method to parse comma-separated loop variables
129
+ # Used by for loops with brackets or parentheses
130
+ def parse_loop_variables
131
+ loop_vars = []
132
+
133
+ loop do
134
+ if current_token && (current_token[:type] == :identifier ||
135
+ current_token[:type] == :local_variable ||
136
+ current_token[:type] == :global_variable ||
137
+ current_token[:type] == :script_local)
138
+ loop_vars << advance
139
+ else
140
+ add_error("Expected identifier in for loop variables")
141
+ break
142
+ end
143
+
144
+ if current_token && current_token[:type] == :comma
145
+ advance # Skip ','
146
+ else
147
+ break
148
+ end
149
+ end
150
+
151
+ loop_vars
152
+ end
153
+
154
+ # Helper method to expect 'in' keyword in for loops
155
+ def expect_in_keyword
156
+ if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
157
+ add_error("Expected 'in' after for loop variables")
158
+ else
159
+ advance # Skip 'in'
160
+ end
161
+ end
162
+
51
163
  def parse_program
52
164
  statements = []
53
165
 
@@ -65,15 +177,153 @@ module Vinter
65
177
  { type: :program, body: statements }
66
178
  end
67
179
 
180
+ def parse_mapping_statement
181
+ token = current_token
182
+ advance # Skip the mapping command
183
+ line = token[:line]
184
+ column = token[:column]
185
+ mapping_command = token[:value]
186
+
187
+ # Parse options (like <silent>, <buffer>, etc.)
188
+ options = []
189
+ while current_token &&
190
+ (current_token[:type] == :identifier || current_token[:type] == :special_key) &&
191
+ current_token[:value].start_with?('<') &&
192
+ current_token[:value].end_with?('>')
193
+ options << current_token[:value]
194
+ advance
195
+ end
196
+
197
+ # Parse the key sequence
198
+ key_sequence = nil
199
+ if current_token
200
+ if current_token[:type] == :special_key ||
201
+ (current_token[:value].start_with?('<') && current_token[:value].end_with?('>'))
202
+ key_sequence = current_token[:value]
203
+ advance
204
+ else
205
+ key_sequence = current_token[:value]
206
+ advance
207
+ end
208
+ end
209
+
210
+ # Collect everything else until end of line as the mapping target
211
+ # This is raw text and shouldn't be parsed as an expression
212
+ target_tokens = []
213
+
214
+ # Continue collecting tokens until we hit a newline or comment
215
+ while current_token &&
216
+ current_token[:type] != :comment &&
217
+ (current_token[:type] != :whitespace || current_token[:value].strip != '')
218
+
219
+ # If we hit a newline not preceded by a continuation character, we're done
220
+ if current_token[:value] == "\n" &&
221
+ (target_tokens.empty? || target_tokens.last[:value][-1] != '\\')
222
+ break
223
+ end
224
+
225
+ target_tokens << current_token
226
+ advance
227
+ end
228
+
229
+ # Join the target tokens to form the raw mapping target
230
+ target = target_tokens.map { |t| t[:value] }.join('')
231
+
232
+ {
233
+ type: :mapping_statement,
234
+ command: mapping_command,
235
+ options: options,
236
+ key_sequence: key_sequence,
237
+ target: target,
238
+ line: line,
239
+ column: column
240
+ }
241
+ end
242
+
68
243
  def parse_statement
69
244
  if !current_token
70
245
  return nil
71
246
  end
247
+ start_token = current_token
72
248
 
73
- if current_token[:type] == :keyword
249
+ # Handle pipe as command separator
250
+ if current_token[:type] == :operator && current_token[:value] == '|'
251
+ advance # Skip the pipe
252
+ return parse_statement
253
+ end
254
+
255
+ # Handle endif keyword outside normal if structure (likely from one-line if)
256
+ if current_token[:type] == :keyword && current_token[:value] == 'endif'
257
+ token = advance # Skip the endif
258
+ return {
259
+ type: :endif_marker,
260
+ line: token[:line],
261
+ column: token[:column]
262
+ }
263
+ end
264
+
265
+ # Add special cases for other ending keywords too to be thorough
266
+ if current_token[:type] == :keyword &&
267
+ ['endwhile', 'endfor', 'endfunction', 'endfunc', 'enddef'].include?(current_token[:value])
268
+ token = advance # Skip the keyword
269
+ return {
270
+ type: :"#{token[:value]}_marker",
271
+ line: token[:line],
272
+ column: token[:column]
273
+ }
274
+ end
275
+
276
+ # Now, add specific handling for continue and break
277
+ if current_token[:type] == :keyword && current_token[:value] == 'continue'
278
+ token = advance # Skip 'continue'
279
+ return {
280
+ type: :continue_statement,
281
+ line: token[:line],
282
+ column: token[:column]
283
+ }
284
+ end
285
+
286
+ if current_token[:type] == :keyword && current_token[:value] == 'break'
287
+ token = advance # Skip 'break'
288
+ return {
289
+ type: :break_statement,
290
+ line: token[:line],
291
+ column: token[:column]
292
+ }
293
+ end
294
+
295
+ # Handle try-catch
296
+ if current_token[:type] == :keyword && current_token[:value] == 'try'
297
+ return parse_try_statement
298
+ end
299
+
300
+ # Handle throw
301
+ if current_token[:type] == :keyword && current_token[:value] == 'throw'
302
+ return parse_throw_statement
303
+ end
304
+
305
+ # Skip standalone catch, finally, endtry keywords outside of try blocks
306
+ if current_token[:type] == :keyword &&
307
+ ['catch', 'finally', 'endtry'].include?(current_token[:value])
308
+ token = advance # Skip these keywords
309
+ return nil
310
+ end
311
+
312
+ # Add case for mapping commands
313
+ if current_token[:type] == :keyword &&
314
+ ['nnoremap', 'nmap', 'inoremap', 'imap', 'vnoremap', 'vmap',
315
+ 'xnoremap', 'xmap', 'cnoremap', 'cmap', 'noremap', 'map'].include?(current_token[:value])
316
+ parse_mapping_statement
317
+ elsif current_token[:type] == :runtime_command
318
+ parse_runtime_statement
319
+ elsif current_token[:type] == :keyword && current_token[:value] == 'runtime'
320
+ parse_runtime_statement
321
+ elsif current_token[:type] == :keyword
74
322
  case current_token[:value]
75
323
  when 'if'
76
324
  parse_if_statement
325
+ when 'command'
326
+ parse_command_definition
77
327
  when 'while'
78
328
  parse_while_statement
79
329
  when 'for'
@@ -81,7 +331,18 @@ module Vinter
81
331
  when 'def'
82
332
  parse_def_function
83
333
  when 'function'
84
- parse_legacy_function
334
+ # Only parse as function declaration if it looks like one
335
+ # Function declarations are followed by ! or identifier (function name)
336
+ if peek_token && (
337
+ (peek_token[:type] == :operator && peek_token[:value] == '!') ||
338
+ peek_token[:type] == :identifier ||
339
+ peek_token[:type] == :script_local
340
+ )
341
+ parse_legacy_function
342
+ else
343
+ # Not a function declaration, parse as expression statement
344
+ parse_expression_statement
345
+ end
85
346
  when 'return'
86
347
  parse_return_statement
87
348
  when 'var', 'const', 'final'
@@ -93,14 +354,36 @@ module Vinter
93
354
  when 'vim9script'
94
355
  token = advance # Skip 'vim9script'
95
356
  { type: :vim9script_declaration, line: token[:line], column: token[:column] }
357
+ when 'scriptencoding'
358
+ parse_scriptencoding
96
359
  when 'autocmd'
97
360
  parse_autocmd_statement
98
- when 'execute'
361
+ when 'execute', 'exec'
99
362
  parse_execute_statement
100
363
  when 'let'
101
- parse_let_statement
102
- when 'echohl', 'echomsg'
364
+ parse_let_statement
365
+ when 'unlet'
366
+ parse_unlet_statement
367
+ when 'echohl', 'echomsg', 'echoerr', 'echom'
103
368
  parse_echo_statement
369
+ when 'augroup'
370
+ parse_augroup_statement
371
+ when 'silent'
372
+ parse_silent_command
373
+ when 'call'
374
+ parse_call_statement
375
+ when 'delete'
376
+ parse_delete_statement
377
+ when 'set', 'setlocal'
378
+ parse_set_command
379
+ when 'syntax'
380
+ parse_syntax_command
381
+ when 'highlight'
382
+ parse_highlight_command
383
+ when 'sleep'
384
+ parse_sleep_command
385
+ when 'source'
386
+ parse_source_command
104
387
  else
105
388
  @warnings << {
106
389
  message: "Unexpected keyword: #{current_token[:value]}",
@@ -114,11 +397,51 @@ module Vinter
114
397
  elsif current_token[:type] == :identifier
115
398
  if current_token[:value] == "echo"
116
399
  parse_echo_statement
400
+ elsif current_token[:value] == "augroup"
401
+ parse_augroup_statement
402
+ elsif current_token[:value] == "au" || current_token[:value] == "autocmd"
403
+ parse_autocmd_statement
404
+ elsif current_token[:value] == "filter" || current_token[:value] == "filt"
405
+ parse_filter_command
406
+ elsif current_token[:value] == "command"
407
+ parse_command_definition
117
408
  else
118
409
  parse_expression_statement
119
410
  end
120
411
  elsif current_token[:type] == :comment
121
412
  parse_comment
413
+ elsif current_token[:type] == :string && current_token[:value].start_with?('"')
414
+ parse_comment
415
+ # token = current_token
416
+ # line = token[:line]
417
+ # column = token[:column]
418
+ # value = token[:value]
419
+ # advance
420
+ # { type: :comment, value: value, line: line, column: column }
421
+ elsif current_token[:type] == :silent_bang
422
+ parse_silent_command
423
+ elsif current_token[:type] == :identifier && current_token[:value] == 'delete'
424
+ parse_delete_command
425
+ elsif current_token[:type] == :percentage
426
+ parse_range_command
427
+ elsif current_token[:type] == :global_variable
428
+ name = current_token[:value]
429
+ #advance#skip value
430
+ #advance#operator
431
+ ##parse_expression_statement
432
+ #advance#value
433
+ while peek_token[:line] == current_token[:line]
434
+ advance
435
+ end
436
+ advance
437
+ {
438
+ type: :global_variable,
439
+ name: name
440
+ }
441
+
442
+ elsif current_token[:type] == :vimfuncs
443
+ advance
444
+ parse_function_call(current_token[:value], current_token[:line], current_token[:column])
122
445
  else
123
446
  @warnings << {
124
447
  message: "Unexpected token type: #{current_token[:type]}",
@@ -131,391 +454,666 @@ module Vinter
131
454
  end
132
455
  end
133
456
 
134
- def parse_execute_statement
135
- token = advance # Skip 'execute'
457
+ def parse_range_command
458
+ token = advance # Skip '%'
136
459
  line = token[:line]
137
460
  column = token[:column]
138
461
 
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
462
+ # Parse the command that follows the range
463
+ command = parse_statement
144
464
 
145
- # Return the execute statement
146
465
  {
147
- type: :execute_statement,
148
- expressions: expressions,
466
+ type: :range_command,
467
+ range: '%',
468
+ command: command,
149
469
  line: line,
150
470
  column: column
151
471
  }
152
472
  end
153
473
 
154
- def parse_let_statement
155
- token = advance # Skip 'let'
474
+ def parse_runtime_statement
475
+ token = advance # Skip 'runtime' or 'runtime!'
156
476
  line = token[:line]
157
477
  column = token[:column]
158
478
 
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
479
+ is_bang = token[:type] == :runtime_command ||
480
+ (token[:value] == 'runtime' && current_token && current_token[:type] == :operator && current_token[:value] == '!')
481
+
482
+ # Skip the '!' if it's separate token
483
+ if token[:type] != :runtime_command && is_bang
484
+ advance # Skip '!'
179
485
  end
180
486
 
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]
487
+ # Collect the pattern argument
488
+ pattern_parts = []
489
+ while @position < @tokens.length &&
490
+ !(current_token[:type] == :keyword || current_token[:value] == "\n")
491
+ pattern_parts << current_token[:value]
187
492
  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
493
  end
196
494
 
197
- # Parse the value expression
198
- value = parse_expression
495
+ pattern = pattern_parts.join('').strip
199
496
 
200
497
  {
201
- type: :let_statement,
202
- target: target,
203
- operator: operator,
204
- value: value,
498
+ type: :runtime_statement,
499
+ bang: is_bang,
500
+ pattern: pattern,
205
501
  line: line,
206
502
  column: column
207
503
  }
208
504
  end
209
505
 
210
- def parse_autocmd_statement
211
- token = advance # Skip 'autocmd'
506
+ def parse_filter_command
507
+ token = advance # Skip 'execute'
212
508
  line = token[:line]
213
509
  column = token[:column]
510
+ # lets count the parens that are created as we parse through the filter command
511
+ expect(:paren_open)
512
+ open_parens = 1
513
+ while open_parens > 0
514
+ advance
515
+ if current_token[:type] == :paren_open
516
+ open_parens += 1
517
+ elsif current_token[:type] == :paren_close
518
+ open_parens -= 1
519
+ end
520
+ end
521
+ advance
214
522
 
215
- # Parse event name (like BufNewFile)
216
- event = nil
523
+ # Parse arguments - typically string expressions with concatenation
524
+ # Just accept any tokens until we hit a statement terminator or another command
525
+ #expressions = []
526
+ #expr = parse_expression
527
+ #expr = parse_string
528
+ #expressions << expr if expr
529
+
530
+ # Return the execute statement
531
+ {
532
+ type: :filter_command,
533
+ #expressions: expressions,
534
+ line: line,
535
+ column: column
536
+ }
537
+ end
538
+
539
+
540
+
541
+ def parse_augroup_statement
542
+ token = advance # Skip 'augroup'
543
+ line = token[:line]
544
+ column = token[:column]
545
+
546
+ # Get the augroup name
547
+ name = nil
217
548
  if current_token && current_token[:type] == :identifier
218
- event = advance[:value]
549
+ name = current_token[:value]
550
+ advance
219
551
  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
- }
552
+ add_error("Expected augroup name")
226
553
  end
227
554
 
228
- # Parse pattern (like *.match)
229
- pattern = nil
230
- if current_token
231
- pattern = current_token[:value]
232
- advance
555
+ # Check for augroup END
556
+ if name && name.upcase == "END"
557
+ return {
558
+ type: :augroup_end,
559
+ line: line,
560
+ column: column
561
+ }
233
562
  end
234
563
 
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
564
+ # Parse statements within the augroup until we find 'augroup END'
565
+ body = []
566
+ while @position < @tokens.length
567
+ # Check for 'augroup END'
568
+ if (current_token[:type] == :keyword && current_token[:value] == 'augroup') ||
569
+ (current_token[:type] == :identifier && current_token[:value] == 'augroup')
570
+ # Look ahead for END
571
+ if peek_token &&
572
+ ((peek_token[:type] == :identifier && peek_token[:value].upcase == 'END') ||
573
+ (peek_token[:type] == :keyword && peek_token[:value].upcase == 'END'))
574
+ advance # Skip 'augroup'
575
+ advance # Skip 'END'
576
+ break
257
577
  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
578
  end
264
579
 
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
580
+ stmt = parse_statement
581
+ body << stmt if stmt
269
582
  end
270
583
 
271
- return {
272
- type: :autocmd_statement,
273
- event: event,
274
- pattern: pattern,
275
- commands: commands,
584
+ {
585
+ type: :augroup_statement,
586
+ name: name,
587
+ body: body,
276
588
  line: line,
277
589
  column: column
278
590
  }
279
591
  end
280
592
 
281
- def parse_echo_statement
282
- token = advance #Skip 'echo'
593
+ def parse_execute_statement
594
+ token = advance # Skip 'execute'
283
595
  line = token[:line]
284
596
  column = token[:column]
285
597
 
286
- expression = parse_expression
598
+ # Parse arguments - typically string expressions with concatenation
599
+ # Just accept any tokens until we hit a statement terminator or another command
600
+ expressions = []
601
+ expr = parse_expression
602
+ #expr = parse_string
603
+ expressions << expr if expr
287
604
 
605
+ # Return the execute statement
288
606
  {
289
- type: :echo_statement,
290
- expression: expression,
607
+ type: :execute_statement,
608
+ expressions: expressions,
291
609
  line: line,
292
610
  column: column
293
611
  }
294
612
  end
295
613
 
296
- def parse_comment
297
- comment = current_token[:value]
298
- line = current_token[:line]
299
- column = current_token[:column]
300
- advance
301
- { type: :comment, value: comment, line: line, column: column }
302
- end
303
-
304
- def parse_if_statement
305
- token = advance # Skip 'if'
614
+ def parse_let_statement
615
+ # binding.pry
616
+ token = advance # Skip 'let'
306
617
  line = token[:line]
307
618
  column = token[:column]
308
- condition = parse_expression
309
619
 
310
- then_branch = []
311
- else_branch = []
620
+ # Parse the target variable
621
+ target = nil
622
+ if current_token
623
+ case current_token[:type]
624
+ when :identifier, :global_variable, :script_local, :buffer_local, :window_local, :tab_local, :arg_variable, :option_variable, :special_variable, :local_variable
625
+ target = {
626
+ type: current_token[:type],
627
+ name: current_token[:value],
628
+ line: current_token[:line],
629
+ column: current_token[:column]
630
+ }
631
+ advance
312
632
 
313
- # Parse statements until we hit 'else', 'elseif', or 'endif'
314
- while @position < @tokens.length
315
- if current_token[:type] == :keyword &&
316
- ['else', 'elseif', 'endif'].include?(current_token[:value])
317
- break
633
+ # Check for property access with dot notation (e.g., person.job)
634
+ if current_token && current_token[:type] == :operator && current_token[:value] == '.'
635
+ dot_token = advance # Skip '.'
636
+
637
+ # Next token should be an identifier (property name)
638
+ if current_token && (current_token[:type] == :identifier || current_token[:type] == :keyword)
639
+ property_token = advance # Get the property name
640
+
641
+ # Update the target to be a property access
642
+ target = {
643
+ type: :property_access,
644
+ object: target,
645
+ property: property_token[:value],
646
+ line: dot_token[:line],
647
+ column: dot_token[:column]
648
+ }
649
+ else
650
+ add_error("Expected property name after '.'")
651
+ end
652
+ end
653
+
654
+ # Check for chained indexed access (dictionary key lookup)
655
+ while current_token && current_token[:type] == :bracket_open
656
+ bracket_token = advance # Skip '['
657
+ index_expr = parse_expression
658
+ expect(:bracket_close) # Skip ']'
659
+
660
+ # Update target to be an indexed access
661
+ target = {
662
+ type: :indexed_access,
663
+ object: target,
664
+ index: index_expr,
665
+ line: bracket_token[:line],
666
+ column: bracket_token[:column]
667
+ }
668
+ end
669
+ when :register_access
670
+ target = {
671
+ type: :register_access,
672
+ register: current_token[:value],
673
+ line: current_token[:line],
674
+ column: current_token[:column]
675
+ }
676
+ advance
677
+ when :bracket_open
678
+ # Handle array destructuring like: let [key, value] = split(header, ': ')
679
+ bracket_token = advance # Skip '['
680
+
681
+ # Parse the variable names inside the brackets
682
+ destructuring_targets = []
683
+
684
+ # Parse comma-separated list of variables
685
+ while current_token && current_token[:type] != :bracket_close
686
+ # Skip commas between variables
687
+ if current_token[:type] == :comma
688
+ advance
689
+ next
690
+ end
691
+
692
+ # Parse the variable name
693
+ if current_token && (
694
+ current_token[:type] == :identifier ||
695
+ current_token[:type] == :global_variable ||
696
+ current_token[:type] == :script_local ||
697
+ current_token[:type] == :arg_variable ||
698
+ current_token[:type] == :option_variable ||
699
+ current_token[:type] == :special_variable ||
700
+ current_token[:type] == :local_variable
701
+ )
702
+ destructuring_targets << {
703
+ type: current_token[:type],
704
+ name: current_token[:value],
705
+ line: current_token[:line],
706
+ column: current_token[:column]
707
+ }
708
+ advance
709
+ else
710
+ add_error("Expected variable name in destructuring assignment")
711
+ # Try to recover by advancing to next comma or closing bracket
712
+ while current_token && current_token[:type] != :comma && current_token[:type] != :bracket_close
713
+ advance
714
+ end
715
+ end
716
+ end
717
+
718
+ expect(:bracket_close) # Skip ']'
719
+
720
+ target = {
721
+ type: :destructuring_assignment,
722
+ targets: destructuring_targets,
723
+ line: bracket_token[:line],
724
+ column: bracket_token[:column]
725
+ }
726
+ else
727
+ add_error("Expected variable name after let")
318
728
  end
729
+ end
319
730
 
320
- stmt = parse_statement
321
- then_branch << stmt if stmt
731
+ # Skip the '=' or other assignment operator
732
+ operator = nil
733
+ if current_token && (
734
+ (current_token[:type] == :operator && current_token[:value] == '=') ||
735
+ current_token[:type] == :compound_operator)
736
+ operator = current_token[:value]
737
+ advance
738
+ else
739
+ add_error("Expected assignment operator after variable in let statement")
322
740
  end
323
741
 
324
- # Check for else/elseif
325
- if current_token && current_token[:type] == :keyword
326
- if current_token[:value] == 'else'
327
- advance # Skip 'else'
742
+ # Parse the value expression
743
+ value = nil
744
+
745
+ # Special handling for function() references in let statements
746
+ if current_token && current_token[:type] == :keyword && current_token[:value] == 'function'
747
+ advance # Skip 'function'
328
748
 
329
- # Parse statements until 'endif'
330
- while @position < @tokens.length
331
- if current_token[:type] == :keyword && current_token[:value] == 'endif'
332
- break
333
- end
749
+ # Expect opening parenthesis
750
+ if current_token && current_token[:type] == :paren_open
751
+ paren_token = advance # Skip '('
334
752
 
335
- stmt = parse_statement
336
- else_branch << stmt if stmt
753
+ # Parse the function name as a string
754
+ func_name = nil
755
+ if current_token && current_token[:type] == :string
756
+ func_name = current_token[:value]
757
+ advance
758
+ else
759
+ add_error("Expected string with function name in function() call")
337
760
  end
338
- elsif current_token[:value] == 'elseif'
339
- # This is a simplified handling - elseif should be treated as a nested if
340
- else_branch << parse_if_statement
761
+
762
+ # Expect closing parenthesis
763
+ expect(:paren_close) # Skip ')'
764
+
765
+ value = {
766
+ type: :function_reference,
767
+ function_name: func_name,
768
+ line: line,
769
+ column: column
770
+ }
771
+ else
772
+ # If not followed by parenthesis, parse as a normal expression
773
+ @position -= 1 # Go back to the 'function' keyword
774
+ value = parse_expression
341
775
  end
776
+ else
777
+ # Normal expression parsing
778
+ value = parse_expression
342
779
  end
780
+ {
781
+ type: :let_statement,
782
+ target: target,
783
+ operator: operator,
784
+ value: value,
785
+ line: line,
786
+ column: column
787
+ }
788
+ end
789
+
790
+ def parse_unlet_statement
791
+ token = advance # Skip 'unlet'
792
+ line = token[:line]
793
+ column = token[:column]
794
+
795
+ # Parse the variable to unlet
796
+ target = nil
797
+ if current_token
798
+ case current_token[:type]
799
+ when :identifier, :global_variable, :script_local, :buffer_local, :window_local, :tab_local, :arg_variable, :option_variable, :special_variable, :local_variable
800
+ target = {
801
+ type: current_token[:type],
802
+ name: current_token[:value],
803
+ line: current_token[:line],
804
+ column: current_token[:column]
805
+ }
806
+ advance
343
807
 
344
- # Expect endif
345
- expect(:keyword) # This should be 'endif'
808
+ # Check for chained indexed access (like variable[key])
809
+ while current_token && current_token[:type] == :bracket_open
810
+ bracket_token = advance # Skip '['
811
+ index_expr = parse_expression
812
+ expect(:bracket_close) # Skip ']'
813
+
814
+ # Update target to be an indexed access
815
+ target = {
816
+ type: :indexed_access,
817
+ object: target,
818
+ index: index_expr,
819
+ line: bracket_token[:line],
820
+ column: bracket_token[:column]
821
+ }
822
+ end
823
+ else
824
+ @errors << {
825
+ message: "Expected variable name after 'unlet'",
826
+ position: @position,
827
+ line: current_token ? current_token[:line] : 0,
828
+ column: current_token ? current_token[:column] : 0
829
+ }
830
+ end
831
+ end
346
832
 
347
833
  {
348
- type: :if_statement,
349
- condition: condition,
350
- then_branch: then_branch,
351
- else_branch: else_branch,
834
+ type: :unlet_statement,
835
+ target: target,
352
836
  line: line,
353
837
  column: column
354
838
  }
355
839
  end
356
840
 
357
- def parse_while_statement
358
- token = advance # Skip 'while'
841
+ def parse_autocmd_statement
842
+ token = advance # Skip 'autocmd'
359
843
  line = token[:line]
360
844
  column = token[:column]
361
- condition = parse_expression
362
845
 
363
- body = []
846
+ # Parse event name (like BufNewFile or VimEnter)
847
+ event = nil
848
+ if current_token && current_token[:type] == :identifier
849
+ event = advance[:value]
850
+ else
851
+ @errors << {
852
+ message: "Expected event name after 'autocmd'",
853
+ position: @position,
854
+ line: current_token ? current_token[:line] : 0,
855
+ column: current_token ? current_token[:column] : 0
856
+ }
857
+ end
364
858
 
365
- # Parse statements until we hit 'endwhile'
859
+ # Parse pattern (like *.match or *)
860
+ pattern = nil
861
+ if current_token
862
+ pattern = current_token[:value]
863
+ advance
864
+ end
865
+
866
+ # Parse everything after as the command - collect as raw tokens
867
+ command_parts = []
366
868
  while @position < @tokens.length
367
- if current_token[:type] == :keyword && current_token[:value] == 'endwhile'
869
+ if !current_token || current_token[:type] == :comment ||
870
+ (current_token[:value] == "\n" && !current_token[:value].start_with?("\\"))
368
871
  break
369
872
  end
370
873
 
371
- stmt = parse_statement
372
- body << stmt if stmt
874
+ command_parts << current_token
875
+ advance
373
876
  end
374
877
 
375
- # Expect endwhile
376
- expect(:keyword) # This should be 'endwhile'
377
-
378
- {
379
- type: :while_statement,
380
- condition: condition,
381
- body: body,
878
+ return {
879
+ type: :autocmd_statement,
880
+ event: event,
881
+ pattern: pattern,
882
+ command_parts: command_parts,
382
883
  line: line,
383
884
  column: column
384
885
  }
385
886
  end
386
887
 
387
- def parse_for_statement
388
- token = advance # Skip 'for'
888
+ def parse_echo_statement
889
+ token = advance #Skip 'echo'
389
890
  line = token[:line]
390
891
  column = token[:column]
391
892
 
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 '('
893
+ expression = parse_expression
396
894
 
397
- loop_vars = []
895
+ {
896
+ type: :echo_statement,
897
+ expression: expression,
898
+ line: line,
899
+ column: column
900
+ }
901
+ end
398
902
 
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
903
+ def parse_comment
904
+ original_token = current_token
905
+ line = original_token[:line]
906
+ column = original_token[:column]
907
+ comment = original_token[:value]
908
+
909
+ # puts "INSIDE PARSE COMMENT"
910
+ # puts "Current token: #{current_token.inspect}"
911
+ # puts "Peek token: #{peek_token.inspect}"
912
+
913
+ # Check if the comment contains a newline
914
+ if comment.include?("\n")
915
+ # Split the comment at the newline
916
+ parts = comment.split("\n", 2)
917
+ comment = parts[0] # Only use the part before newline
918
+
919
+ # Create a new token for the content after the newline
920
+ remainder = parts[1].strip
921
+
922
+ if !remainder.empty?
923
+ # Position correctly - we'll advance past the current token
924
+ # But should process the remainder separately later
925
+
926
+ # For debugging only, you can print what's being processed
927
+ # puts "Found comment with newline. Using: '#{comment}', Remainder: '#{remainder}'"
928
+
929
+ # Don't call advance() - we'll modify the current token instead
930
+ @tokens[@position] = {
931
+ type: :string, # Keep original type
932
+ value: comment, # Use only the part before newline
933
+ line: line,
934
+ column: column
935
+ }
411
936
 
412
- if current_token && current_token[:type] == :comma
413
- advance # Skip ','
414
- else
415
- break
416
- end
937
+ # Insert the remainder as a new token after the current one
938
+ @tokens.insert(@position + 1, {
939
+ type: :comment, # Same type for consistency
940
+ value: remainder, # Preserve as a string token
941
+ line: line + 1, # Increment line number for the part after newline
942
+ column: 0 # Approximate column based on indentation
943
+ })
417
944
  end
945
+ end
418
946
 
419
- expect(:paren_close) # Skip ')'
947
+ # Now advance past the (potentially modified) current token
948
+ advance
949
+ { type: :comment, value: comment, line: line, column: column }
950
+ end
420
951
 
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
952
+ def parse_if_statement
953
+ token = advance # Skip 'if'
954
+ line = token[:line]
955
+ column = token[:column]
431
956
 
432
- iterable = parse_expression
957
+ condition = parse_expression
433
958
 
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
959
+ then_branch = []
960
+ else_branch = []
440
961
 
441
- stmt = parse_statement
442
- body << stmt if stmt
443
- end
962
+ # Check if this might be a one-line if (look ahead for pipe character)
963
+ one_line_if = false
444
964
 
445
- # Expect endfor
446
- expect(:keyword) # This should be 'endfor'
965
+ if current_token && current_token[:type] == :operator && current_token[:value] == '|'
966
+ one_line_if = true
967
+ advance # Skip the pipe
447
968
 
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
969
+ # Parse the then statement
970
+ stmt = parse_statement
971
+ then_branch << stmt if stmt
467
972
 
468
- loop_var = advance[:value]
973
+ # Check for the closing pipe and endif
974
+ if current_token && current_token[:type] == :operator && current_token[:value] == '|'
975
+ advance # Skip the pipe
469
976
 
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'
977
+ # Expect endif
978
+ unless expect_keyword('endif')
979
+ add_error("Expected 'endif' after '|' in one-line if statement")
980
+ end
479
981
  end
982
+ else
983
+ # Parse multi-line if statement
984
+ # Parse statements until 'else', 'elseif', or 'endif'
985
+ then_branch = parse_body_until('else', 'elseif', 'endif')
480
986
 
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
987
+ # Check for else/elseif
988
+ if current_token && current_token[:type] == :keyword
989
+ if current_token[:value] == 'else'
990
+ advance # Skip 'else'
991
+
992
+ # Parse statements until 'endif'
993
+ else_branch = parse_body_until('endif')
994
+ elsif current_token[:value] == 'elseif'
995
+ elseif_stmt = parse_if_statement
996
+ else_branch << elseif_stmt if elseif_stmt
997
+
998
+ return {
999
+ type: :if_statement,
1000
+ condition: condition,
1001
+ then_branch: then_branch,
1002
+ else_branch: else_branch,
1003
+ line: line,
1004
+ column: column
1005
+ }
488
1006
  end
489
-
490
- stmt = parse_statement
491
- body << stmt if stmt
492
1007
  end
493
1008
 
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
- }
1009
+ # Expect endif
1010
+ expect_end_keyword('endif')
505
1011
  end
1012
+
1013
+ {
1014
+ type: :if_statement,
1015
+ condition: condition,
1016
+ then_branch: then_branch,
1017
+ else_branch: else_branch,
1018
+ one_line: one_line_if,
1019
+ line: line,
1020
+ column: column
1021
+ }
506
1022
  end
507
1023
 
508
- def parse_def_function
509
- token = advance # Skip 'def'
1024
+ def parse_while_statement
1025
+ token = advance # Skip 'while'
510
1026
  line = token[:line]
511
1027
  column = token[:column]
1028
+ condition = parse_expression
512
1029
 
513
- name = expect(:identifier)
1030
+ # Parse statements until 'endwhile'
1031
+ body = parse_body_until('endwhile')
514
1032
 
515
- # Parse parameter list
516
- expect(:paren_open)
517
- params = parse_parameter_list
518
- expect(:paren_close)
1033
+ # Expect endwhile
1034
+ expect_end_keyword('endwhile')
1035
+
1036
+ {
1037
+ type: :while_statement,
1038
+ condition: condition,
1039
+ body: body,
1040
+ line: line,
1041
+ column: column
1042
+ }
1043
+ end
1044
+
1045
+ def parse_for_statement
1046
+ token = advance # Skip 'for'
1047
+ line = token[:line]
1048
+ column = token[:column]
1049
+
1050
+ # Determine the type of for loop and parse variables accordingly
1051
+ loop_var = nil
1052
+ loop_vars = nil
1053
+
1054
+ if current_token && current_token[:type] == :bracket_open
1055
+ # Handle destructuring with brackets: for [key, val] in dict
1056
+ advance # Skip '['
1057
+ loop_vars = parse_loop_variables
1058
+ expect(:bracket_close)
1059
+ expect_in_keyword
1060
+ elsif current_token && current_token[:type] == :paren_open
1061
+ # Handle multiple variables with parentheses: for (var1, var2) in list
1062
+ advance # Skip '('
1063
+ loop_vars = parse_loop_variables
1064
+ expect(:paren_close)
1065
+ expect_in_keyword
1066
+ else
1067
+ # Handle single variable: for var in list
1068
+ if current_token && (current_token[:type] == :identifier ||
1069
+ current_token[:type] == :local_variable ||
1070
+ current_token[:type] == :global_variable ||
1071
+ current_token[:type] == :script_local)
1072
+ loop_var = advance
1073
+ else
1074
+ add_error("Expected identifier as for loop variable")
1075
+ end
1076
+ expect_in_keyword
1077
+ end
1078
+
1079
+ # Parse the iterable expression
1080
+ iterable = parse_expression
1081
+
1082
+ # Parse the body until 'endfor'
1083
+ body = parse_body_until('endfor')
1084
+
1085
+ # Expect endfor
1086
+ expect_end_keyword('endfor')
1087
+
1088
+ # Build result hash based on whether we have single or multiple vars
1089
+ result = {
1090
+ type: :for_statement,
1091
+ iterable: iterable,
1092
+ body: body,
1093
+ line: line,
1094
+ column: column
1095
+ }
1096
+
1097
+ if loop_vars
1098
+ result[:loop_vars] = loop_vars
1099
+ else
1100
+ result[:loop_var] = loop_var
1101
+ end
1102
+
1103
+ result
1104
+ end
1105
+
1106
+ def parse_def_function
1107
+ token = advance # Skip 'def'
1108
+ line = token[:line]
1109
+ column = token[:column]
1110
+
1111
+ name = expect(:identifier)
1112
+
1113
+ # Parse parameter list
1114
+ expect(:paren_open)
1115
+ params = parse_parameter_list
1116
+ expect(:paren_close)
519
1117
 
520
1118
  # Parse optional return type
521
1119
  return_type = nil
@@ -524,19 +1122,11 @@ module Vinter
524
1122
  return_type = parse_type
525
1123
  end
526
1124
 
527
- # Parse function body
528
- body = []
529
- while @position < @tokens.length
530
- if current_token[:type] == :keyword && current_token[:value] == 'enddef'
531
- break
532
- end
533
-
534
- stmt = parse_statement
535
- body << stmt if stmt
536
- end
1125
+ # Parse function body until 'enddef'
1126
+ body = parse_body_until('enddef')
537
1127
 
538
1128
  # Expect enddef
539
- expect(:keyword) # This should be 'enddef'
1129
+ expect_end_keyword('enddef')
540
1130
 
541
1131
  {
542
1132
  type: :def_function,
@@ -579,12 +1169,7 @@ module Vinter
579
1169
 
580
1170
  # After varargs, we expect closing paren
581
1171
  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
- }
1172
+ add_error("Expected closing parenthesis after varargs", current_token)
588
1173
  end
589
1174
 
590
1175
  break
@@ -592,12 +1177,7 @@ module Vinter
592
1177
 
593
1178
  # Get parameter name
594
1179
  if !current_token || current_token[:type] != :identifier
595
- @errors << {
596
- message: "Expected parameter name",
597
- position: @position,
598
- line: current_token ? current_token[:line] : 0,
599
- column: current_token ? current_token[:column] : 0
600
- }
1180
+ add_error("Expected parameter name")
601
1181
  break
602
1182
  end
603
1183
 
@@ -632,12 +1212,7 @@ module Vinter
632
1212
  advance
633
1213
  # If we don't have a comma, we should have a closing paren
634
1214
  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
- }
1215
+ add_error("Expected comma or closing parenthesis after parameter", current_token)
641
1216
  break
642
1217
  end
643
1218
  end
@@ -646,7 +1221,7 @@ module Vinter
646
1221
  end
647
1222
 
648
1223
  def parse_type
649
- if current_token && current_token[:type] == :identifier
1224
+ if current_token && [:identifier, :keyword].include?(current_token[:type])
650
1225
  type_name = advance
651
1226
 
652
1227
  # Handle generic types like list<string>
@@ -666,12 +1241,7 @@ module Vinter
666
1241
 
667
1242
  return type_name[:value]
668
1243
  else
669
- @errors << {
670
- message: "Expected type identifier",
671
- position: @position,
672
- line: current_token ? current_token[:line] : 0,
673
- column: current_token ? current_token[:column] : 0
674
- }
1244
+ add_error("Expected type identifier")
675
1245
  advance
676
1246
  return "unknown"
677
1247
  end
@@ -684,12 +1254,7 @@ module Vinter
684
1254
  column = var_type_token[:column]
685
1255
 
686
1256
  if !current_token || current_token[:type] != :identifier
687
- @errors << {
688
- message: "Expected variable name",
689
- position: @position,
690
- line: current_token ? current_token[:line] : 0,
691
- column: current_token ? current_token[:column] : 0
692
- }
1257
+ add_error("Expected variable name")
693
1258
  return nil
694
1259
  end
695
1260
 
@@ -727,7 +1292,12 @@ module Vinter
727
1292
  column = token[:column]
728
1293
 
729
1294
  value = nil
730
- if @position < @tokens.length && current_token[:type] != :semicolon
1295
+ # Check if we've reached the end of the file, end of line, or a semicolon
1296
+ if @position < @tokens.length &&
1297
+ current_token &&
1298
+ current_token[:type] != :semicolon &&
1299
+ !(current_token[:type] == :keyword &&
1300
+ ['endif', 'endwhile', 'endfor', 'endfunction', 'endfunc'].include?(current_token[:value]))
731
1301
  value = parse_expression
732
1302
  end
733
1303
 
@@ -783,7 +1353,72 @@ module Vinter
783
1353
  end
784
1354
 
785
1355
  def parse_expression
786
- return parse_binary_expression
1356
+ # binding.pry
1357
+ # Special case for empty return statements or standalone keywords that shouldn't be expressions
1358
+ if current_token && current_token[:type] == :keyword &&
1359
+ ['return', 'endif', 'endwhile', 'endfor', 'endfunction', 'endfunc'].include?(current_token[:value])
1360
+ return nil
1361
+ end
1362
+
1363
+ if current_token[:type] == :string
1364
+ string_value = current_token[:value]
1365
+ while current_token && peek_token && [:line_continuation, :identifier].include?(peek_token[:type])
1366
+ # Handle strings with line continuation
1367
+ if ["'", '"'].include? current_token[:value][-1]
1368
+ token_line = current_token[:line]
1369
+ token_column = current_token[:column]
1370
+ advance # Consume the string token
1371
+ return {
1372
+ type: :literal,
1373
+ value: string_value,
1374
+ token_type: :string,
1375
+ line: token_line,
1376
+ column: token_column
1377
+ }
1378
+ else
1379
+ advance
1380
+ string_value += current_token[:value]
1381
+ end
1382
+ end
1383
+ end
1384
+
1385
+ # Parse the condition expression
1386
+ expr = parse_binary_expression
1387
+
1388
+ # Check if this is a ternary expression
1389
+ if current_token && (current_token[:type] == :question_mark || (current_token[:type] == :operator && current_token[:value] == '?'))
1390
+ question_token = advance # Skip '?'
1391
+
1392
+ # Parse the "then" expression
1393
+ then_expr = parse_expression
1394
+
1395
+ # Expect the colon
1396
+ if current_token && current_token[:type] == :colon
1397
+ colon_token = advance # Skip ':'
1398
+
1399
+ # Parse the "else" expression
1400
+ else_expr = parse_expression
1401
+
1402
+ # Return the ternary expression
1403
+ return {
1404
+ type: :ternary_expression,
1405
+ condition: expr,
1406
+ then_expr: then_expr,
1407
+ else_expr: else_expr,
1408
+ line: question_token[:line],
1409
+ column: question_token[:column]
1410
+ }
1411
+ else
1412
+ @errors << {
1413
+ message: "Expected ':' in ternary expression",
1414
+ position: @position,
1415
+ line: current_token ? current_token[:line] : 0,
1416
+ column: current_token ? current_token[:column] : 0
1417
+ }
1418
+ end
1419
+ end
1420
+
1421
+ return expr
787
1422
  end
788
1423
 
789
1424
  def parse_binary_expression(precedence = 0)
@@ -799,9 +1434,65 @@ module Vinter
799
1434
  advance
800
1435
  end
801
1436
 
1437
+ if current_token && current_token[:type] == :operator && current_token[:value] == '.' &&
1438
+ operator_precedence(current_token[:value]) >= precedence
1439
+ op_token = advance # Skip the operator
1440
+
1441
+ # Check if we're dealing with a command placeholder on either side
1442
+ if (left && left[:type] == :command_arg_placeholder) ||
1443
+ (peek_token && peek_token[:type] == :command_arg_placeholder)
1444
+ # Handle command-specific concatenation
1445
+ right = parse_binary_expression(operator_precedence('.') + 1)
1446
+
1447
+ left = {
1448
+ type: :command_concat_expression, # Special type for command concatenation
1449
+ operator: '.',
1450
+ left: left,
1451
+ right: right,
1452
+ line: op_token[:line],
1453
+ column: op_token[:column]
1454
+ }
1455
+ else
1456
+ # Normal expression concatenation
1457
+ right = parse_binary_expression(operator_precedence('.') + 1)
1458
+
1459
+ left = {
1460
+ type: :binary_expression,
1461
+ operator: '.',
1462
+ left: left,
1463
+ right: right,
1464
+ line: op_token[:line],
1465
+ column: op_token[:column]
1466
+ }
1467
+ end
1468
+ elsif current_token && current_token[:type] == :operator &&
1469
+ ['<', '>', '=', '!'].include?(current_token[:value]) &&
1470
+ peek_token && peek_token[:type] == :operator && peek_token[:value] == '='
1471
+
1472
+ # Combine the two operators into one token
1473
+ op_token = current_token
1474
+ op = current_token[:value] + peek_token[:value]
1475
+ advance # Skip the first operator
1476
+ advance # Skip the second operator
1477
+
1478
+ # Now process the combined operator
1479
+ op_precedence = operator_precedence(op)
1480
+
1481
+ if op_precedence >= precedence
1482
+ right = parse_binary_expression(op_precedence + 1)
1483
+
1484
+ left = {
1485
+ type: :binary_expression,
1486
+ operator: op,
1487
+ left: left,
1488
+ right: right,
1489
+ line: op_token[:line],
1490
+ column: op_token[:column]
1491
+ }
1492
+ end
802
1493
  # Now we should be at the operator
803
- if current_token && current_token[:type] == :operator &&
804
- operator_precedence(current_token[:value]) >= precedence
1494
+ elsif current_token && current_token[:type] == :operator &&
1495
+ operator_precedence(current_token[:value]) >= precedence
805
1496
  op_token = advance
806
1497
  op = op_token[:value]
807
1498
  op_precedence = operator_precedence(op)
@@ -858,24 +1549,160 @@ module Vinter
858
1549
  expr = nil
859
1550
 
860
1551
  case token[:type]
861
- when :number
1552
+ # Add handling for command arg placeholders
1553
+ when :command_arg_placeholder
862
1554
  advance
863
1555
  expr = {
864
- type: :literal,
1556
+ type: :command_arg_placeholder,
865
1557
  value: token[:value],
866
- token_type: :number,
867
1558
  line: line,
868
1559
  column: column
869
1560
  }
870
- when :string
1561
+ # Add special handling for keywords that might appear in expressions
1562
+ when :keyword
1563
+ # Special handling for map-related keywords when they appear in expressions
1564
+ if ['map', 'nmap', 'imap', 'vmap', 'xmap', 'noremap', 'nnoremap', 'inoremap', 'vnoremap', 'xnoremap', 'cnoremap', 'cmap'].include?(token[:value])
1565
+ if peek_token[:type] == :paren_open
1566
+ parse_builtin_function_call(token[:value], line, column)
1567
+ else
1568
+ # Treat map commands as identifiers when inside expressions
1569
+ advance
1570
+ return {
1571
+ type: :identifier,
1572
+ name: token[:value],
1573
+ line: line,
1574
+ column: column
1575
+ }
1576
+ end
1577
+ elsif token[:value] == 'type' && current_token && (current_token[:type] == :paren_open || peek_token && peek_token[:type] == :paren_open)
1578
+ # This is the type() function call
1579
+ return parse_builtin_function_call(token[:value], line, column)
1580
+ elsif token[:value] == 'function'
1581
+ # Check if this is a function() call or just 'function' as property name
1582
+ if peek_token && peek_token[:type] == :paren_open
1583
+ advance # Skip 'function'
1584
+ # This is a function() call
1585
+ if current_token && current_token[:type] == :paren_open
1586
+ # This is a function reference call
1587
+ paren_token = advance # Skip '('
1588
+
1589
+ # Parse the function name as a string or arrow function
1590
+ if current_token && current_token[:type] == :string
1591
+ func_name = current_token[:value]
1592
+ advance
1593
+
1594
+ # Check for additional arguments (function() can take multiple arguments)
1595
+ args = []
1596
+ while current_token && current_token[:type] == :comma
1597
+ advance # Skip comma
1598
+ arg = parse_expression
1599
+ args << arg if arg
1600
+ end
1601
+
1602
+ # Expect closing parenthesis
1603
+ expect(:paren_close) # Skip ')'
1604
+
1605
+ return {
1606
+ type: :function_reference,
1607
+ function_name: func_name,
1608
+ arguments: args,
1609
+ line: line,
1610
+ column: column
1611
+ }
1612
+ elsif current_token && current_token[:type] == :brace_open
1613
+ # This is an arrow function inside function()
1614
+ arrow_function = parse_vim_lambda(line, column)
1615
+
1616
+ # Expect closing parenthesis
1617
+ expect(:paren_close) # Skip ')'
1618
+
1619
+ return {
1620
+ type: :function_reference,
1621
+ function_body: arrow_function,
1622
+ line: line,
1623
+ column: column
1624
+ }
1625
+ else
1626
+ @errors << {
1627
+ message: "Expected string or arrow function definition in function() call",
1628
+ position: @position,
1629
+ line: current_token ? current_token[:line] : 0,
1630
+ column: current_token ? current_token[:column] : 0
1631
+ }
1632
+ end
1633
+ end
1634
+ else
1635
+ # If not followed by parenthesis, treat 'function' as an identifier (property name)
1636
+ advance # Skip 'function'
1637
+ return {
1638
+ type: :identifier,
1639
+ name: token[:value],
1640
+ line: line,
1641
+ column: column
1642
+ }
1643
+ end
1644
+ # Legacy Vim allows certain keywords as identifiers in expressions
1645
+ elsif ['return', 'type'].include?(token[:value])
1646
+ # Handle 'return' keyword specially when it appears in an expression context
1647
+ advance
1648
+ @warnings << {
1649
+ message: "Keyword '#{token[:value]}' used in an expression context",
1650
+ position: @position,
1651
+ line: line,
1652
+ column: column
1653
+ }
1654
+ # Check if this is a function call for 'type'
1655
+ if token[:value] == 'type' && current_token && current_token[:type] == :paren_open
1656
+ return parse_function_call(token[:value], line, column)
1657
+ end
1658
+
1659
+ expr = {
1660
+ type: :identifier,
1661
+ name: token[:value],
1662
+ line: line,
1663
+ column: column
1664
+ }
1665
+ else
1666
+ @errors << {
1667
+ message: "Unexpected keyword in expression: #{token[:value]}",
1668
+ position: @position,
1669
+ line: line,
1670
+ column: column
1671
+ }
1672
+ advance
1673
+ return nil
1674
+ end
1675
+ when :number
871
1676
  advance
872
1677
  expr = {
873
1678
  type: :literal,
874
1679
  value: token[:value],
875
- token_type: :string,
1680
+ token_type: :number,
876
1681
  line: line,
877
1682
  column: column
878
1683
  }
1684
+ when :string
1685
+ # parse_string
1686
+ if token[:value].start_with?('"')
1687
+ advance
1688
+ return {
1689
+ type: :comment,
1690
+ value: token[:value],
1691
+ line: line,
1692
+ column: column
1693
+ }
1694
+ else
1695
+ string_value = token[:value]
1696
+ advance
1697
+ expr = {
1698
+ type: :literal,
1699
+ value: string_value,
1700
+ raw_value: string_value, # Store the raw string to preserve escapes
1701
+ token_type: :string,
1702
+ line: line,
1703
+ column: column
1704
+ }
1705
+ end
879
1706
  when :option_variable
880
1707
  # Handle Vim option variables (like &compatible)
881
1708
  advance
@@ -885,6 +1712,14 @@ module Vinter
885
1712
  line: line,
886
1713
  column: column
887
1714
  }
1715
+ when :scoped_option_variable
1716
+ advance
1717
+ expr = {
1718
+ type: :scoped_option_variable,
1719
+ name: token[:value],
1720
+ line: line,
1721
+ column: column
1722
+ }
888
1723
  when :special_variable
889
1724
  # Handle Vim special variables (like v:version)
890
1725
  advance
@@ -909,6 +1744,21 @@ module Vinter
909
1744
  line: line,
910
1745
  column: column
911
1746
  }
1747
+ when :buffer_local
1748
+ # Handle script-local variables/functions (like s:var)
1749
+ advance
1750
+
1751
+ # Check if this is a function call
1752
+ if current_token && current_token[:type] == :paren_open
1753
+ return parse_function_call(token[:value], line, column)
1754
+ end
1755
+
1756
+ expr = {
1757
+ type: :buffer_local,
1758
+ name: token[:value],
1759
+ line: line,
1760
+ column: column
1761
+ }
912
1762
  when :global_variable
913
1763
  # Handle global variables (like g:var)
914
1764
  advance
@@ -927,32 +1777,66 @@ module Vinter
927
1777
  line: line,
928
1778
  column: column
929
1779
  }
930
- when :identifier
1780
+ when :local_variable
1781
+ # Handle local variables (like l:var)
931
1782
  advance
1783
+ expr = {
1784
+ type: :local_variable,
1785
+ name: token[:value],
1786
+ line: line,
1787
+ column: column
1788
+ }
1789
+ when :identifier
1790
+ # Special handling for Vim built-in functions
1791
+ if ['has', 'exists', 'empty', 'get', 'type', 'map', 'copy'].include?(token[:value])
1792
+ expr = parse_builtin_function_call(token[:value], line, column)
1793
+ else
1794
+ advance
932
1795
 
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)
1796
+ # Check if this is a function call
1797
+ if current_token && current_token[:type] == :paren_open
1798
+ expr = parse_function_call(token[:value], line, column)
1799
+ else
1800
+ # Special handling for execute command
1801
+ if token[:value] == 'execute'
1802
+ # Parse the string expressions for execute
1803
+ # For now we'll just treat it as a normal identifier
1804
+ expr = {
1805
+ type: :identifier,
1806
+ name: token[:value],
1807
+ line: line,
1808
+ column: column
1809
+ }
1810
+ else
1811
+ expr = {
1812
+ type: :identifier,
1813
+ name: token[:value],
1814
+ line: line,
1815
+ column: column
1816
+ }
1817
+ end
1818
+ end
936
1819
  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
- }
1820
+ when :vimfuncs
1821
+ advance
1822
+ if current_token[:type] == :paren_open
1823
+ expr = parse_function_call(token[:value], line, column)
948
1824
  else
949
1825
  expr = {
950
- type: :identifier,
1826
+ type: :vimfuncs,
951
1827
  name: token[:value],
952
1828
  line: line,
953
1829
  column: column
954
1830
  }
955
1831
  end
1832
+ when :namespace_prefix
1833
+ advance
1834
+ expr = {
1835
+ type: :namespace_prefix,
1836
+ name: token[:value],
1837
+ line: line,
1838
+ column: column
1839
+ }
956
1840
  when :paren_open
957
1841
  if is_lambda_expression
958
1842
  return parse_lambda_expression(line, column)
@@ -964,11 +1848,28 @@ module Vinter
964
1848
  when :bracket_open
965
1849
  expr = parse_list_literal(line, column)
966
1850
  when :brace_open
967
- expr = parse_dict_literal(line, column)
1851
+ expr = parse_vim_lambda_or_dict(line, column)
968
1852
  when :backslash
969
1853
  # Handle line continuation with backslash
970
1854
  advance
971
1855
  expr = parse_expression
1856
+ when :register_access
1857
+ advance
1858
+ expr = {
1859
+ type: :register_access,
1860
+ register: token[:value][1..-1], # Remove the @ symbol
1861
+ line: line,
1862
+ column: column
1863
+ }
1864
+ when :line_continuation
1865
+ advance
1866
+ expr = parse_expression
1867
+ when :interpolated_string
1868
+ advance
1869
+ expr = {
1870
+ type: :interpolated_string,
1871
+ value: token[:value],
1872
+ }
972
1873
  else
973
1874
  @errors << {
974
1875
  message: "Unexpected token in expression: #{token[:type]}",
@@ -984,28 +1885,44 @@ module Vinter
984
1885
  while current_token
985
1886
  # Check for property access with dot
986
1887
  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
1888
+ # Look ahead to determine if this is property access or concatenation
1889
+ next_token = peek_token
1890
+ is_property_access = next_token && (next_token[:type] == :identifier || next_token[:type] == :keyword)
1891
+
1892
+ # Check if this is a property access (only when right side is identifier/keyword)
1893
+ if is_property_access && (expr[:type] == :identifier || expr[:type] == :global_variable ||
1894
+ expr[:type] == :script_local || expr[:type] == :namespace_prefix ||
1895
+ expr[:type] == :arg_variable || expr[:type] == :buffer_local ||
1896
+ expr[:type] == :window_local || expr[:type] == :tab_local ||
1897
+ expr[:type] == :local_variable || expr[:type] == :property_access ||
1898
+ expr[:type] == :indexed_access)
1899
+
1900
+ dot_token = advance # Skip '.'
1901
+ # Next token should be an identifier (property name)
1902
+ if !current_token || (current_token[:type] != :identifier &&
1903
+ current_token[:type] != :keyword)
1904
+ @errors << {
1905
+ message: "Expected property name after '.'",
1906
+ position: @position,
1907
+ line: current_token ? current_token[:line] : 0,
1908
+ column: current_token ? current_token[:column] : 0
1909
+ }
1910
+ break
1911
+ end
1912
+ property_token = advance
1913
+
1914
+ expr = {
1915
+ type: :property_access,
1916
+ object: expr,
1917
+ property: property_token[:value],
1918
+ line: dot_token[:line],
1919
+ column: dot_token[:column]
996
1920
  }
1921
+ else
1922
+ # This is concatenation or other binary operation, break to let binary expression parser handle it
997
1923
  break
998
1924
  end
999
1925
 
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
1926
  # Check for method call with arrow ->
1010
1927
  elsif current_token[:type] == :operator && current_token[:value] == '->'
1011
1928
  arrow_token = advance # Skip '->'
@@ -1056,18 +1973,47 @@ module Vinter
1056
1973
  # Check for indexing with brackets
1057
1974
  elsif current_token[:type] == :bracket_open
1058
1975
  bracket_token = advance # Skip '['
1059
-
1060
1976
  index_expr = parse_expression
1061
1977
 
1978
+ # Add support for list slicing with colon
1979
+ end_index = nil
1980
+ if current_token && current_token[:type] == :colon
1981
+ advance # Skip ':'
1982
+ # handle omitted end index case like [6:]
1983
+ if current_token && current_token[:type] == :bracket_close
1984
+ end_index = {
1985
+ type: :implicit_end_index,
1986
+ value: nil,
1987
+ line: current_token[:line],
1988
+ column: current_token[:column]
1989
+ }
1990
+ else
1991
+ end_index = parse_expression
1992
+ end
1993
+ end
1994
+
1062
1995
  expect(:bracket_close) # Skip ']'
1063
1996
 
1064
- expr = {
1065
- type: :indexed_access,
1066
- object: expr,
1067
- index: index_expr,
1068
- line: bracket_token[:line],
1069
- column: bracket_token[:column]
1070
- }
1997
+ if end_index
1998
+ # This is a slice operation
1999
+ expr = {
2000
+ type: :slice_access,
2001
+ object: expr,
2002
+ start_index: index_expr,
2003
+ end_index: end_index,
2004
+ line: bracket_token[:line],
2005
+ column: bracket_token[:column]
2006
+ }
2007
+ else
2008
+ # Regular indexed access
2009
+ expr = {
2010
+ type: :indexed_access,
2011
+ object: expr,
2012
+ index: index_expr,
2013
+ line: bracket_token[:line],
2014
+ column: bracket_token[:column]
2015
+ }
2016
+ end
1071
2017
  # Check for function call directly on an expression
1072
2018
  elsif current_token[:type] == :paren_open
1073
2019
  paren_token = advance # Skip '('
@@ -1106,6 +2052,85 @@ module Vinter
1106
2052
  return expr
1107
2053
  end
1108
2054
 
2055
+ def parse_builtin_function_call(name, line, column)
2056
+ # Skip the function name (already consumed)
2057
+ advance if current_token[:value] == name
2058
+
2059
+ # Check if there's an opening parenthesis
2060
+ if current_token && current_token[:type] == :paren_open
2061
+ advance # Skip '('
2062
+
2063
+ # Parse arguments
2064
+ args = []
2065
+
2066
+ # Functions that take string expressions as code
2067
+ special_functions = ['map', 'reduce', 'sort', 'call', 'eval', 'execute', 'exec']
2068
+ is_special_function = special_functions.include?(name)
2069
+
2070
+ # Parse until closing parenthesis
2071
+ while @position < @tokens.length && current_token && current_token[:type] != :paren_close
2072
+ # Skip whitespace or comments
2073
+ if current_token[:type] == :whitespace || current_token[:type] == :comment
2074
+ advance
2075
+ next
2076
+ end
2077
+
2078
+ # Special handling for string arguments that contain code
2079
+ if is_special_function &&
2080
+ current_token && current_token[:type] == :string
2081
+ string_token = parse_expression
2082
+ args << {
2083
+ type: :literal,
2084
+ value: string_token[:value],
2085
+ token_type: :string,
2086
+ line: string_token[:line],
2087
+ column: string_token[:column]
2088
+ }
2089
+ else
2090
+ arg = parse_expression
2091
+ args << arg if arg
2092
+ end
2093
+
2094
+ if current_token && current_token[:type] == :comma
2095
+ advance
2096
+ elsif current_token && current_token[:type] != :paren_close
2097
+ @errors << {
2098
+ message: "Expected comma or closing parenthesis in #{name} function",
2099
+ position: @position,
2100
+ line: current_token[:line],
2101
+ column: current_token[:column]
2102
+ }
2103
+ break
2104
+ end
2105
+ end
2106
+
2107
+ # Check for closing parenthesis
2108
+ if current_token && current_token[:type] == :paren_close
2109
+ advance # Skip ')'
2110
+ else
2111
+ @errors << {
2112
+ message: "Expected ')' to close #{name} function call",
2113
+ position: @position,
2114
+ line: current_token ? current_token[:line] : 0,
2115
+ column: current_token ? current_token[:column] : 0
2116
+ }
2117
+ end
2118
+ else
2119
+ # Handle legacy Vim script where parentheses might be omitted
2120
+ # Just parse one expression as the argument
2121
+ args = [parse_expression]
2122
+ end
2123
+
2124
+ # Return function call node
2125
+ {
2126
+ type: :builtin_function_call,
2127
+ name: name,
2128
+ arguments: args,
2129
+ line: line,
2130
+ column: column
2131
+ }
2132
+ end
2133
+
1109
2134
  def is_lambda_expression
1110
2135
  # Save the current position
1111
2136
  original_position = @position
@@ -1153,7 +2178,7 @@ module Vinter
1153
2178
  # There might be a return type annotation
1154
2179
  advance
1155
2180
  # Skip the type
1156
- advance if current_token && current_token[:type] == :identifier
2181
+ advance if current_token && [:identifier, :keyword].include?(current_token[:type])
1157
2182
  # Check for the arrow
1158
2183
  has_arrow = current_token && current_token[:type] == :operator && current_token[:value] == '=>'
1159
2184
  end
@@ -1165,6 +2190,109 @@ module Vinter
1165
2190
  return has_params && has_arrow
1166
2191
  end
1167
2192
 
2193
+ def parse_vim_lambda(line, column)
2194
+ advance # Skip opening brace
2195
+
2196
+ # Parse parameters
2197
+ params = []
2198
+
2199
+ # Check if this is a no-parameter lambda (starts with ->)
2200
+ if current_token && current_token[:type] == :operator && current_token[:value] == '->'
2201
+ # No parameters, skip to arrow parsing
2202
+ else
2203
+ # Parse parameter(s)
2204
+ if current_token && (current_token[:type] == :identifier ||
2205
+ current_token[:type] == :local_variable ||
2206
+ current_token[:type] == :global_variable)
2207
+ param_name = current_token[:value]
2208
+ param_line = current_token[:line]
2209
+ param_column = current_token[:column]
2210
+ advance
2211
+
2212
+ params << {
2213
+ type: :parameter,
2214
+ name: param_name,
2215
+ line: param_line,
2216
+ column: param_column
2217
+ }
2218
+
2219
+ # Handle multiple parameters (comma-separated)
2220
+ while current_token && current_token[:type] == :comma
2221
+ advance # Skip comma
2222
+
2223
+ if current_token && (current_token[:type] == :identifier ||
2224
+ current_token[:type] == :local_variable ||
2225
+ current_token[:type] == :global_variable)
2226
+ param_name = current_token[:value]
2227
+ param_line = current_token[:line]
2228
+ param_column = current_token[:column]
2229
+ advance
2230
+
2231
+ params << {
2232
+ type: :parameter,
2233
+ name: param_name,
2234
+ line: param_line,
2235
+ column: param_column
2236
+ }
2237
+ else
2238
+ @errors << {
2239
+ message: "Expected parameter name after comma in lambda function",
2240
+ position: @position,
2241
+ line: current_token ? current_token[:line] : 0,
2242
+ column: current_token ? current_token[:column] : 0
2243
+ }
2244
+ break
2245
+ end
2246
+ end
2247
+ else
2248
+ # For no-parameter lambdas, this is not an error
2249
+ if !(current_token && current_token[:type] == :operator && current_token[:value] == '->')
2250
+ @errors << {
2251
+ message: "Expected parameter name or '->' in lambda function",
2252
+ position: @position,
2253
+ line: current_token ? current_token[:line] : 0,
2254
+ column: current_token ? current_token[:column] : 0
2255
+ }
2256
+ end
2257
+ end
2258
+ end
2259
+
2260
+ # Expect the arrow token
2261
+ if current_token && current_token[:type] == :operator && current_token[:value] == '->'
2262
+ advance # Skip ->
2263
+ else
2264
+ @errors << {
2265
+ message: "Expected '->' in lambda function",
2266
+ position: @position,
2267
+ line: current_token ? current_token[:line] : 0,
2268
+ column: current_token ? current_token[:column] : 0
2269
+ }
2270
+ end
2271
+
2272
+ # Parse the lambda body expression (everything until the closing brace)
2273
+ body = parse_expression
2274
+
2275
+ # Expect closing brace
2276
+ if current_token && current_token[:type] == :brace_close
2277
+ advance # Skip }
2278
+ else
2279
+ @errors << {
2280
+ message: "Expected closing brace for lambda function",
2281
+ position: @position,
2282
+ line: current_token ? current_token[:line] : 0,
2283
+ column: current_token ? current_token[:column] : 0
2284
+ }
2285
+ end
2286
+
2287
+ return {
2288
+ type: :vim_lambda,
2289
+ params: params,
2290
+ body: body,
2291
+ line: line,
2292
+ column: column
2293
+ }
2294
+ end
2295
+
1168
2296
  def parse_lambda_expression(line, column)
1169
2297
  expect(:paren_open) # Skip '('
1170
2298
 
@@ -1292,6 +2420,11 @@ module Vinter
1292
2420
  advance # Skip '{'
1293
2421
  entries = []
1294
2422
 
2423
+ # Handle whitespace after opening brace
2424
+ while current_token && current_token[:type] == :whitespace
2425
+ advance
2426
+ end
2427
+
1295
2428
  # Empty dictionary
1296
2429
  if current_token && current_token[:type] == :brace_close
1297
2430
  advance # Skip '}'
@@ -1304,11 +2437,21 @@ module Vinter
1304
2437
  end
1305
2438
 
1306
2439
  # Parse dictionary entries
1307
- loop do
2440
+ while current_token && current_token[:type] != :brace_close
2441
+ # Skip any backslash line continuation markers and whitespace
2442
+ while current_token && (current_token[:type] == :backslash || current_token[:type] == :whitespace || current_token[:type] == :line_continuation)
2443
+ advance
2444
+ end
2445
+
2446
+ # Break if we reached the end or found closing brace
2447
+ if !current_token || current_token[:type] == :brace_close
2448
+ break
2449
+ end
2450
+
1308
2451
  # Parse key (string or identifier)
1309
2452
  key = nil
1310
2453
  if current_token && (current_token[:type] == :string || current_token[:type] == :identifier)
1311
- key = current_token[:type] == :string ? current_token[:value] : current_token[:value]
2454
+ key = current_token[:value]
1312
2455
  advance # Skip key
1313
2456
  else
1314
2457
  @errors << {
@@ -1317,487 +2460,1429 @@ module Vinter
1317
2460
  line: current_token ? current_token[:line] : 0,
1318
2461
  column: current_token ? current_token[:column] : 0
1319
2462
  }
1320
- break
2463
+ # Try to recover by advancing until we find a colon or closing brace
2464
+ while current_token && current_token[:type] != :colon && current_token[:type] != :brace_close
2465
+ advance
2466
+ end
2467
+ if !current_token || current_token[:type] == :brace_close
2468
+ break
2469
+ end
2470
+ end
2471
+
2472
+ # Skip whitespace after key
2473
+ while current_token && current_token[:type] == :whitespace
2474
+ advance
1321
2475
  end
1322
2476
 
1323
2477
  # Expect colon
1324
- expect(:colon)
2478
+ if current_token && current_token[:type] == :colon
2479
+ advance # Skip colon
2480
+ else
2481
+ @errors << {
2482
+ message: "Expected colon after dictionary key",
2483
+ position: @position,
2484
+ line: current_token ? current_token[:line] : 0,
2485
+ column: current_token ? current_token[:column] : 0
2486
+ }
2487
+ end
2488
+
2489
+ # Skip whitespace after colon
2490
+ while current_token && current_token[:type] == :whitespace
2491
+ advance
2492
+ end
2493
+
2494
+ # Parse value
2495
+ if current_token && current_token[:type] == :brace_open
2496
+ lambda_or_dict = parse_vim_lambda_or_dict(line, column)
2497
+ value = lambda_or_dict
2498
+ else
2499
+ value = parse_expression
2500
+ end
2501
+
2502
+ entries << {
2503
+ key: key,
2504
+ value: value
2505
+ }
2506
+
2507
+ # After parsing an entry, skip whitespace and look for comma or closing brace
2508
+ while current_token && current_token[:type] == :whitespace
2509
+ advance
2510
+ end
2511
+
2512
+ # Check for comma (indicates more entries follow)
2513
+ if current_token && current_token[:type] == :comma
2514
+ advance # Skip comma
2515
+ # Skip any line continuations and whitespace after comma
2516
+ while current_token && (current_token[:type] == :whitespace ||
2517
+ current_token[:type] == :backslash ||
2518
+ current_token[:type] == :line_continuation)
2519
+ advance
2520
+ end
2521
+ else
2522
+ # No comma - skip any line continuations before checking for closing brace
2523
+ while current_token && (current_token[:type] == :backslash ||
2524
+ current_token[:type] == :line_continuation ||
2525
+ current_token[:type] == :whitespace)
2526
+ advance
2527
+ end
2528
+
2529
+ # After skipping continuations, we should find the closing brace
2530
+ if current_token && current_token[:type] == :brace_close
2531
+ # This is fine - last entry without trailing comma
2532
+ break
2533
+ elsif current_token && current_token[:type] != :brace_close
2534
+ @errors << {
2535
+ message: "Expected comma or closing brace after dictionary entry",
2536
+ position: @position,
2537
+ line: current_token ? current_token[:line] : 0,
2538
+ column: current_token ? current_token[:column] : 0
2539
+ }
2540
+ break
2541
+ end
2542
+ end
2543
+ end
2544
+
2545
+ # Make sure we have a closing brace
2546
+ if current_token && current_token[:type] == :brace_close
2547
+ advance # Skip '}'
2548
+ else
2549
+ @errors << {
2550
+ message: "Expected closing brace for dictionary",
2551
+ position: @position,
2552
+ line: current_token ? current_token[:line] : 0,
2553
+ column: current_token ? current_token[:column] : 0
2554
+ }
2555
+ end
2556
+
2557
+ {
2558
+ type: :dict_literal,
2559
+ entries: entries,
2560
+ line: line,
2561
+ column: column
2562
+ }
2563
+ end
2564
+
2565
+ def parse_list_literal(line, column)
2566
+ advance # Skip '['
2567
+ elements = []
2568
+
2569
+ # Empty list
2570
+ if current_token && current_token[:type] == :bracket_close
2571
+ advance # Skip ']'
2572
+ return {
2573
+ type: :list_literal,
2574
+ elements: elements,
2575
+ line: line,
2576
+ column: column
2577
+ }
2578
+ end
2579
+
2580
+ # Parse list elements
2581
+ while @position < @tokens.length
2582
+ # Handle line continuation characters (backslash)
2583
+ if current_token && [:backslash, :line_continuation].include?(current_token[:type])
2584
+ # Skip the backslash token
2585
+ advance
2586
+
2587
+ # Skip any whitespace that might follow the backslash
2588
+ while current_token && current_token[:type] == :whitespace
2589
+ advance
2590
+ end
2591
+
2592
+ # Don't add a comma - just continue to parse the next element
2593
+ next
2594
+ end
2595
+
2596
+ # Check if we've reached the end of the list
2597
+ if current_token && current_token[:type] == :bracket_close
2598
+ advance # Skip ']'
2599
+ break
2600
+ end
2601
+
2602
+ element = parse_expression
2603
+ elements << element if element
2604
+
2605
+ # Continue if there's a comma
2606
+ if current_token && current_token[:type] == :comma
2607
+ advance # Skip comma
2608
+ # Or if the next token is a backslash (line continuation)
2609
+ else
2610
+ # binding.pry
2611
+ # If no comma and not a closing bracket or backslash, then it's an error
2612
+ # if current_token && current_token[:type] != :bracket_close
2613
+ # @errors << {
2614
+ # message: "Expected comma, backslash, or closing bracket after list element",
2615
+ # position: @position,
2616
+ # line: current_token[:line],
2617
+ # column: current_token[:column]
2618
+ # }
2619
+ # end
2620
+
2621
+ # We still want to skip the closing bracket if it's there
2622
+ if current_token && current_token[:type] == :bracket_close
2623
+ advance
2624
+ break
2625
+ end
2626
+ advance
2627
+ #next
2628
+ end
2629
+ end
2630
+
2631
+ # Check if we have a second list immediately following this one (Vim's special syntax)
2632
+ if current_token && current_token[:type] == :bracket_open
2633
+ next_list = parse_list_literal(current_token[:line], current_token[:column])
2634
+
2635
+ return {
2636
+ type: :list_concat_expression,
2637
+ left: {
2638
+ type: :list_literal,
2639
+ elements: elements,
2640
+ line: line,
2641
+ column: column
2642
+ },
2643
+ right: next_list,
2644
+ line: line,
2645
+ column: column
2646
+ }
2647
+ end
2648
+
2649
+ return {
2650
+ type: :list_literal,
2651
+ elements: elements,
2652
+ line: line,
2653
+ column: column
2654
+ }
2655
+ end
2656
+
2657
+ def parse_unary_expression
2658
+ # Check for unary operators (!, -, +)
2659
+ if current_token && current_token[:type] == :operator &&
2660
+ ['!', '-', '+'].include?(current_token[:value])
2661
+
2662
+ op_token = advance # Skip the operator
2663
+ operand = parse_unary_expression # Recursively parse the operand
2664
+
2665
+ return {
2666
+ type: :unary_expression,
2667
+ operator: op_token[:value],
2668
+ operand: operand,
2669
+ line: op_token[:line],
2670
+ column: op_token[:column]
2671
+ }
2672
+ end
2673
+
2674
+ # If no unary operator, parse a primary expression
2675
+ return parse_primary_expression
2676
+ end
2677
+
2678
+ def parse_function_call(name, line, column)
2679
+ expect(:paren_open)
2680
+
2681
+ args = []
2682
+
2683
+ # Special handling for Vim functions that take code strings as arguments
2684
+ special_functions = ['map', 'reduce', 'sort', 'call', 'eval', 'execute', 'exec']
2685
+ is_special_function = special_functions.include?(name)
2686
+
2687
+ # Parse arguments until we find a closing parenthesis
2688
+ while @position < @tokens.length && current_token && current_token[:type] != :paren_close
2689
+ # Skip comments inside parameter lists
2690
+ if current_token && current_token[:type] == :comment
2691
+ advance
2692
+ next
2693
+ end
2694
+ if is_special_function && current_token && current_token[:type] == :string
2695
+ # For functions like map(), filter(), directly add the string as an argument
2696
+ string_token = parse_string
2697
+ args << {
2698
+ type: :literal,
2699
+ value: string_token[:value],
2700
+ token_type: :string,
2701
+ line: string_token[:line],
2702
+ column: string_token[:column]
2703
+ }
2704
+ else
2705
+ # Parse the argument
2706
+ arg = parse_expression
2707
+ args << arg if arg
2708
+ end
2709
+
2710
+ # Break if we hit the closing paren
2711
+ if current_token && current_token[:type] == :paren_close
2712
+ break
2713
+ end
2714
+ # If we have a comma, advance past it and continue
2715
+ if current_token && current_token[:type] == :comma
2716
+ advance
2717
+ # If we don't have a comma and we're not at the end, it's an error
2718
+ elsif current_token && current_token[:type] != :paren_close && current_token[:type] != :comment
2719
+ @errors << {
2720
+ message: "Expected comma or closing parenthesis after argument",
2721
+ position: @position,
2722
+ line: current_token[:line],
2723
+ column: current_token[:column]
2724
+ }
2725
+ break
2726
+ end
2727
+ end
2728
+
2729
+ expect(:paren_close)
2730
+
2731
+ {
2732
+ type: :function_call,
2733
+ name: name,
2734
+ arguments: args,
2735
+ line: line,
2736
+ column: column
2737
+ }
2738
+ end
2739
+
2740
+ def parse_vim_lambda_or_dict(line, column)
2741
+ # Save current position to peek ahead
2742
+ start_position = @position
2743
+
2744
+ advance # Skip opening brace
2745
+
2746
+ # Check if this is a lambda by looking for parameter names followed by arrow
2747
+ is_lambda = false
2748
+ param_names = []
2749
+
2750
+ # Check for immediate arrow (no parameters lambda)
2751
+ if current_token && current_token[:type] == :operator && current_token[:value] == '->'
2752
+ is_lambda = true
2753
+ else
2754
+ # Parse until closing brace or arrow
2755
+ while @position < @tokens.length && current_token[:type] != :brace_close
2756
+ if current_token[:type] == :identifier
2757
+ param_names << current_token[:value]
2758
+ advance
2759
+
2760
+ # Skip comma between parameters
2761
+ if current_token && current_token[:type] == :comma
2762
+ advance
2763
+ next
2764
+ end
2765
+ elsif current_token[:type] == :operator && current_token[:value] == '->'
2766
+ is_lambda = true
2767
+ break
2768
+ else
2769
+ # If we see a colon, this is likely a nested dictionary
2770
+ if current_token && current_token[:type] == :colon
2771
+ break
2772
+ end
2773
+ advance
2774
+ end
2775
+ end
2776
+ end
2777
+
2778
+ # Reset position
2779
+ @position = start_position
2780
+
2781
+ if is_lambda
2782
+ return parse_vim_lambda(line, column)
2783
+ else
2784
+ return parse_dict_literal(line, column)
2785
+ end
2786
+ end
2787
+
2788
+ def parse_import_statement
2789
+ token = advance # Skip 'import'
2790
+ line = token[:line]
2791
+ column = token[:column]
2792
+
2793
+ # Handle 'import autoload'
2794
+ is_autoload = false
2795
+ module_name = nil
2796
+ path = nil
2797
+
2798
+ if current_token && current_token[:type] == :identifier && current_token[:value] == 'autoload'
2799
+ is_autoload = true
2800
+ module_name = advance[:value] # Store "autoload" as the module name
2801
+
2802
+ # After "autoload" keyword, expect a string path
2803
+ if current_token && current_token[:type] == :string
2804
+ path = current_token[:value]
2805
+ advance
2806
+ else
2807
+ @errors << {
2808
+ message: "Expected string path after 'autoload'",
2809
+ position: @position,
2810
+ line: current_token ? current_token[:line] : 0,
2811
+ column: current_token ? current_token[:column] : 0
2812
+ }
2813
+ end
2814
+ else
2815
+ # Regular import with a string path
2816
+ if current_token && current_token[:type] == :string
2817
+ path = current_token[:value]
2818
+
2819
+ # Extract module name from the path
2820
+ # This is simplified logic - you might need more complex extraction
2821
+ module_name = path.gsub(/['"]/, '').split('/').last.split('.').first
2822
+
2823
+ advance
2824
+ else
2825
+ # Handle other import formats
2826
+ module_expr = parse_expression()
2827
+ if module_expr && module_expr[:type] == :literal && module_expr[:token_type] == :string
2828
+ path = module_expr[:value]
2829
+ module_name = path.gsub(/['"]/, '').split('/').last.split('.').first
2830
+ end
2831
+ end
2832
+ end
2833
+
2834
+ # Handle 'as name'
2835
+ as_name = nil
2836
+ if current_token && current_token[:type] == :identifier && current_token[:value] == 'as'
2837
+ advance # Skip 'as'
2838
+ if current_token && current_token[:type] == :identifier
2839
+ as_name = advance[:value]
2840
+ else
2841
+ @errors << {
2842
+ message: "Expected identifier after 'as'",
2843
+ position: @position,
2844
+ line: current_token ? current_token[:line] : 0,
2845
+ column: current_token ? current_token[:column] : 0
2846
+ }
2847
+ end
2848
+ end
2849
+
2850
+ {
2851
+ type: :import_statement,
2852
+ module: module_name,
2853
+ path: path,
2854
+ is_autoload: is_autoload,
2855
+ as_name: as_name,
2856
+ line: line,
2857
+ column: column
2858
+ }
2859
+ end
2860
+
2861
+ def parse_scriptencoding
2862
+ token = advance
2863
+ advance
2864
+ { type: :scriptencoding, encoding: current_token[:value] }
2865
+ end
2866
+
2867
+ def parse_export_statement
2868
+ token = advance # Skip 'export'
2869
+ line = token[:line]
2870
+ column = token[:column]
2871
+
2872
+ # Export can be followed by var/const/def/function declarations
2873
+ if !current_token
2874
+ @errors << {
2875
+ message: "Expected declaration after export",
2876
+ position: @position,
2877
+ line: line,
2878
+ column: column
2879
+ }
2880
+ return nil
2881
+ end
2882
+
2883
+ exported_item = nil
2884
+
2885
+ if current_token[:type] == :keyword
2886
+ case current_token[:value]
2887
+ when 'def'
2888
+ exported_item = parse_def_function
2889
+ when 'function'
2890
+ exported_item = parse_legacy_function
2891
+ when 'var', 'const', 'final'
2892
+ exported_item = parse_variable_declaration
2893
+ when 'class'
2894
+ # Handle class export when implemented
2895
+ @errors << {
2896
+ message: "Class export not implemented yet",
2897
+ position: @position,
2898
+ line: current_token[:line],
2899
+ column: current_token[:column]
2900
+ }
2901
+ advance
2902
+ return nil
2903
+ else
2904
+ @errors << {
2905
+ message: "Unexpected keyword after export: #{current_token[:value]}",
2906
+ position: @position,
2907
+ line: current_token[:line],
2908
+ column: current_token[:column]
2909
+ }
2910
+ advance
2911
+ return nil
2912
+ end
2913
+ else
2914
+ @errors << {
2915
+ message: "Expected declaration after export",
2916
+ position: @position,
2917
+ line: current_token[:line],
2918
+ column: current_token[:column]
2919
+ }
2920
+ advance
2921
+ return nil
2922
+ end
2923
+
2924
+ {
2925
+ type: :export_statement,
2926
+ export: exported_item,
2927
+ line: line,
2928
+ column: column
2929
+ }
2930
+ end
2931
+
2932
+ def parse_command_definition
2933
+ token = advance # Skip 'command' or 'command!'
2934
+ line = token[:line]
2935
+ column = token[:column]
2936
+
2937
+ # Check if the command has a bang (!)
2938
+ has_bang = false
2939
+ if current_token && current_token[:type] == :operator && current_token[:value] == '!'
2940
+ has_bang = true
2941
+ advance # Skip '!'
2942
+ end
2943
+
2944
+ # Parse command options/attributes (starting with hyphen)
2945
+ attributes = []
2946
+ while current_token && (
2947
+ (current_token[:type] == :operator && current_token[:value] == '-') ||
2948
+ (current_token[:type] == :identifier && current_token[:value].start_with?('-'))
2949
+ )
2950
+ # Handle option as a single attribute if it's already combined
2951
+ if current_token[:type] == :identifier && current_token[:value].start_with?('-')
2952
+ attributes << current_token[:value]
2953
+ advance
2954
+ else
2955
+ # Otherwise combine the hyphen with the following identifier
2956
+ advance # Skip the hyphen
2957
+ if current_token && current_token[:type] == :identifier
2958
+ attributes << "-#{current_token[:value]}"
2959
+ advance
2960
+ end
2961
+ end
2962
+
2963
+ # If there's an = followed by a value, include it in the attribute
2964
+ if current_token && current_token[:type] == :operator && current_token[:value] == '='
2965
+ attribute = attributes.pop # Take the last attribute we added
2966
+ advance # Skip the '='
2967
+
2968
+ # Get the value (number or identifier)
2969
+ if current_token && (current_token[:type] == :number || current_token[:type] == :identifier)
2970
+ attribute += "=#{current_token[:value]}"
2971
+ attributes << attribute
2972
+ advance
2973
+ end
2974
+ end
2975
+ end
2976
+
2977
+ # Parse the command name
2978
+ command_name = nil
2979
+ if current_token && current_token[:type] == :identifier
2980
+ command_name = current_token[:value]
2981
+ advance
2982
+ else
2983
+ @errors << {
2984
+ message: "Expected command name",
2985
+ position: @position,
2986
+ line: current_token ? current_token[:line] : 0,
2987
+ column: current_token ? current_token[:column] : 0
2988
+ }
2989
+ end
2990
+
2991
+ # Parse the command implementation - collect all remaining tokens as raw parts
2992
+ implementation_parts = []
2993
+ while @position < @tokens.length
2994
+ # Break on end of line or comment
2995
+ if !current_token || current_token[:type] == :comment ||
2996
+ (current_token[:value] == "\n" && !current_token[:value].start_with?("\\"))
2997
+ break
2998
+ end
2999
+
3000
+ implementation_parts << current_token
3001
+ advance
3002
+ end
3003
+
3004
+ {
3005
+ type: :command_definition,
3006
+ name: command_name,
3007
+ has_bang: has_bang,
3008
+ attributes: attributes,
3009
+ implementation: implementation_parts,
3010
+ line: line,
3011
+ column: column
3012
+ }
3013
+ end
3014
+
3015
+ def parse_legacy_function
3016
+ token = advance # Skip 'function'
3017
+ line = token[:line]
3018
+ column = token[:column]
3019
+
3020
+ # Check for bang (!) in function definition
3021
+ has_bang = false
3022
+ if current_token && current_token[:type] == :operator && current_token[:value] == '!'
3023
+ has_bang = true
3024
+ advance # Skip '!'
3025
+ end
3026
+
3027
+ # For script-local functions or other scoped functions
3028
+ is_script_local = false
3029
+ function_scope = nil
3030
+
3031
+ # Check if we have a script-local function (s:)
3032
+ if current_token && current_token[:type] == :script_local
3033
+ is_script_local = true
3034
+ function_scope = current_token[:value]
3035
+ advance # Skip s: prefix
3036
+ elsif current_token && current_token[:type] == :identifier && current_token[:value].include?(':')
3037
+ # Handle other scoped functions like g: or b:
3038
+ parts = current_token[:value].split(':')
3039
+ if parts.length == 2
3040
+ function_scope = parts[0] + ':'
3041
+ advance
3042
+ end
3043
+ end
3044
+
3045
+ # Now handle the function name, which might be separate from the scope
3046
+ name = nil
3047
+ if !is_script_local && function_scope.nil? && current_token && current_token[:type] == :identifier
3048
+ name = current_token
3049
+ advance
3050
+ elsif is_script_local || !function_scope.nil?
3051
+ if current_token && current_token[:type] == :identifier
3052
+ name = current_token
3053
+ advance
3054
+ end
3055
+ else
3056
+ name = expect(:identifier)
3057
+ end
3058
+
3059
+ # Parse parameter list
3060
+ expect(:paren_open)
3061
+ params = parse_parameter_list_legacy
3062
+ expect(:paren_close)
3063
+
3064
+ # Check for optional attributes (range, dict, abort, closure)
3065
+ attributes = []
3066
+ while current_token
3067
+ if current_token[:type] == :keyword && current_token[:value] == 'abort'
3068
+ attributes << advance[:value]
3069
+ elsif current_token[:type] == :identifier &&
3070
+ ['range', 'dict', 'closure'].include?(current_token[:value])
3071
+ attributes << advance[:value]
3072
+ else
3073
+ break
3074
+ end
3075
+ end
3076
+
3077
+ # Parse function body until 'endfunction' or 'endfunc'
3078
+ body = parse_body_until('endfunction', 'endfunc')
3079
+
3080
+ # Expect endfunction/endfunc
3081
+ expect_end_keyword('endfunction', 'endfunc')
3082
+
3083
+ function_name = name ? name[:value] : nil
3084
+ if function_scope
3085
+ function_name = function_scope + function_name if function_name
3086
+ end
3087
+
3088
+ {
3089
+ type: :legacy_function,
3090
+ name: function_name,
3091
+ is_script_local: is_script_local,
3092
+ scope: function_scope,
3093
+ params: params,
3094
+ has_bang: has_bang,
3095
+ attributes: attributes,
3096
+ body: body,
3097
+ line: line,
3098
+ column: column
3099
+ }
3100
+ end
3101
+
3102
+ # Legacy function parameters are different - they can use a:name syntax
3103
+ def parse_parameter_list_legacy
3104
+ params = []
3105
+
3106
+ # Empty parameter list
3107
+ if current_token && current_token[:type] == :paren_close
3108
+ return params
3109
+ end
3110
+
3111
+ # Handle special case: varargs at the start of the parameter list
3112
+ if current_token && (
3113
+ (current_token[:type] == :ellipsis) ||
3114
+ (current_token[:type] == :operator && current_token[:value] == '.')
3115
+ )
3116
+ if current_token[:type] == :ellipsis
3117
+ token = advance
3118
+ params << {
3119
+ type: :var_args_legacy,
3120
+ name: '...',
3121
+ line: token[:line],
3122
+ column: token[:column]
3123
+ }
3124
+ return params
3125
+ else
3126
+ # Count consecutive dots
3127
+ dot_count = 0
3128
+ first_dot_token = current_token
3129
+
3130
+ # Store the line and column before we advance
3131
+ dot_line = first_dot_token[:line]
3132
+ dot_column = first_dot_token[:column]
3133
+
3134
+ while current_token && current_token[:type] == :operator && current_token[:value] == '.'
3135
+ dot_count += 1
3136
+ advance
3137
+ end
3138
+
3139
+ if dot_count == 3
3140
+ params << {
3141
+ type: :var_args_legacy,
3142
+ name: '...',
3143
+ line: dot_line,
3144
+ column: dot_column
3145
+ }
3146
+ return params
3147
+ end
3148
+ end
3149
+ end
3150
+
3151
+ # Regular parameter parsing (existing logic)
3152
+ loop do
3153
+ if current_token && current_token[:type] == :identifier
3154
+ param_name = advance
3155
+
3156
+ # Check for default value
3157
+ default_value = nil
3158
+ if current_token && current_token[:type] == :operator && current_token[:value] == '='
3159
+ advance # Skip '='
3160
+ default_value = parse_expression
3161
+ end
3162
+
3163
+ params << {
3164
+ type: :parameter,
3165
+ name: param_name[:value],
3166
+ default_value: default_value,
3167
+ optional: default_value != nil,
3168
+ line: param_name[:line],
3169
+ column: param_name[:column]
3170
+ }
3171
+ elsif current_token && current_token[:type] == :arg_variable
3172
+ param_name = advance
3173
+ name_without_prefix = param_name[:value].sub(/^a:/, '')
3174
+
3175
+ # Check for default value
3176
+ default_value = nil
3177
+ if current_token && current_token[:type] == :operator && current_token[:value] == '='
3178
+ advance # Skip '='
3179
+ default_value = parse_expression
3180
+ end
3181
+
3182
+ params << {
3183
+ type: :parameter,
3184
+ name: name_without_prefix,
3185
+ default_value: default_value,
3186
+ optional: default_value != nil,
3187
+ line: param_name[:line],
3188
+ column: param_name[:column],
3189
+ is_arg_prefixed: true
3190
+ }
3191
+ else
3192
+ # We might be at varargs after other parameters
3193
+ if current_token && (
3194
+ (current_token[:type] == :ellipsis) ||
3195
+ (current_token[:type] == :operator && current_token[:value] == '.')
3196
+ )
3197
+ # Add debug
3198
+ #puts "Found potential varargs token: #{current_token[:type]} #{current_token[:value]}"
3199
+
3200
+ if current_token[:type] == :ellipsis
3201
+ token = current_token # STORE the token before advancing
3202
+ advance
3203
+ params << {
3204
+ type: :var_args_legacy,
3205
+ name: '...',
3206
+ line: token[:line], # Use stored token
3207
+ column: token[:column]
3208
+ }
3209
+ else
3210
+ dot_count = 0
3211
+ first_dot_token = current_token
3212
+
3213
+ # Store line/column BEFORE advancing
3214
+ dot_line = first_dot_token[:line]
3215
+ dot_column = first_dot_token[:column]
3216
+
3217
+ #puts "Starting dot sequence at line #{dot_line}, column #{dot_column}"
3218
+
3219
+ while current_token && current_token[:type] == :operator && current_token[:value] == '.'
3220
+ dot_count += 1
3221
+ #puts "Found dot #{dot_count}"
3222
+ advance
3223
+ end
3224
+
3225
+ if dot_count == 3
3226
+ #puts "Complete varargs found (3 dots)"
3227
+ params << {
3228
+ type: :var_args_legacy,
3229
+ name: '...',
3230
+ line: dot_line, # Use stored values
3231
+ column: dot_column
3232
+ }
3233
+ else
3234
+ #puts "Incomplete varargs: only #{dot_count} dots found"
3235
+ end
3236
+ end
3237
+ break
3238
+ else
3239
+ # Add debug to see what unexpected token we're encountering
3240
+ #puts "Unexpected token in parameter list: #{current_token ? current_token[:type] : 'nil'} #{current_token ? current_token[:value] : ''}"
3241
+
3242
+ # Not a valid parameter or varargs
3243
+ @errors << {
3244
+ message: "Expected parameter name",
3245
+ position: @position,
3246
+ line: current_token ? current_token[:line] : 0,
3247
+ column: current_token ? current_token[:column] : 0
3248
+ }
3249
+ break
3250
+ end
3251
+ end
3252
+ if current_token && current_token[:type] == :comma
3253
+ advance
3254
+ else
3255
+ break
3256
+ end
3257
+ end
3258
+
3259
+ params
3260
+ end
3261
+
3262
+ def parse_try_statement
3263
+ token = advance # Skip 'try'
3264
+ line = token[:line]
3265
+ column = token[:column]
3266
+
3267
+ # Parse the try body until catch/finally/endtry
3268
+ body = parse_body_until('catch', 'finally', 'endtry')
3269
+
3270
+ catch_clauses = []
3271
+ finally_clause = nil
3272
+
3273
+ # Parse catch clauses
3274
+ while @position < @tokens.length &&
3275
+ current_token && current_token[:type] == :keyword &&
3276
+ current_token[:value] == 'catch'
3277
+ catch_token = advance # Skip 'catch'
3278
+ catch_line = catch_token[:line]
3279
+ catch_column = catch_token[:column]
3280
+
3281
+ # Parse the pattern (anything until the next statement)
3282
+ pattern = ''
3283
+ pattern_tokens = []
3284
+
3285
+ # Collect all tokens until we hit a newline or a statement
3286
+ while @position < @tokens.length
3287
+ if !current_token ||
3288
+ (current_token[:type] == :whitespace && current_token[:value].include?("\n")) ||
3289
+ current_token[:type] == :comment
3290
+ break
3291
+ end
3292
+
3293
+ pattern_tokens << current_token
3294
+ pattern += current_token[:value]
3295
+ advance
3296
+ end
1325
3297
 
1326
- # Parse value
1327
- value = parse_expression
3298
+ # Parse the catch body until next catch/finally/endtry
3299
+ catch_body = parse_body_until('catch', 'finally', 'endtry')
1328
3300
 
1329
- entries << {
1330
- key: key,
1331
- value: value
3301
+ catch_clauses << {
3302
+ type: :catch_clause,
3303
+ pattern: pattern.strip,
3304
+ body: catch_body,
3305
+ line: catch_line,
3306
+ column: catch_column
1332
3307
  }
3308
+ end
1333
3309
 
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
3310
+ # Parse finally clause if present
3311
+ if @position < @tokens.length &&
3312
+ current_token && current_token[:type] == :keyword &&
3313
+ current_token[:value] == 'finally'
3314
+ finally_token = advance # Skip 'finally'
3315
+ finally_line = finally_token[:line]
3316
+ finally_column = finally_token[:column]
3317
+
3318
+ # Parse the finally body until 'endtry'
3319
+ finally_body = parse_body_until('endtry')
3320
+
3321
+ finally_clause = {
3322
+ type: :finally_clause,
3323
+ body: finally_body,
3324
+ line: finally_line,
3325
+ column: finally_column
3326
+ }
1341
3327
  end
1342
3328
 
1343
- expect(:brace_close) # Expect and skip '}'
3329
+ # Expect endtry
3330
+ expect_end_keyword('endtry')
1344
3331
 
1345
- {
1346
- type: :dict_literal,
1347
- entries: entries,
3332
+ return {
3333
+ type: :try_statement,
3334
+ body: body,
3335
+ catch_clauses: catch_clauses,
3336
+ finally_clause: finally_clause,
1348
3337
  line: line,
1349
3338
  column: column
1350
3339
  }
1351
3340
  end
1352
3341
 
1353
- def parse_list_literal(line, column)
1354
- advance # Skip '['
1355
- elements = []
3342
+ # Also add a method to parse throw statements:
1356
3343
 
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
3344
+ def parse_throw_statement
3345
+ token = advance # Skip 'throw'
3346
+ line = token[:line]
3347
+ column = token[:column]
1367
3348
 
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
3349
+ # Parse the expression to throw
3350
+ expression = parse_expression
1375
3351
 
1376
- element = parse_expression
1377
- elements << element if element
3352
+ return {
3353
+ type: :throw_statement,
3354
+ expression: expression,
3355
+ line: line,
3356
+ column: column
3357
+ }
3358
+ end
1378
3359
 
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
3360
+ def parse_call_statement
3361
+ token = advance # Skip 'call'
3362
+ line = token[:line]
3363
+ column = token[:column]
1392
3364
 
1393
- # We still want to skip the closing bracket if it's there
1394
- if current_token && current_token[:type] == :bracket_close
1395
- advance
3365
+ # Parse the function call expression that follows 'call'
3366
+ func_expr = nil
3367
+
3368
+ if current_token && current_token[:type] == :script_local
3369
+ # Handle script-local function call (s:func_name)
3370
+ func_name = current_token[:value]
3371
+ func_line = current_token[:line]
3372
+ func_column = current_token[:column]
3373
+ advance
3374
+
3375
+ # Parse arguments
3376
+ args = []
3377
+ if current_token && current_token[:type] == :paren_open
3378
+ expect(:paren_open) # Skip '('
3379
+
3380
+ # Parse arguments if any
3381
+ unless current_token && current_token[:type] == :paren_close
3382
+ loop do
3383
+ arg = parse_expression
3384
+ args << arg if arg
3385
+
3386
+ if current_token && current_token[:type] == :comma
3387
+ advance # Skip comma
3388
+ else
3389
+ break
3390
+ end
3391
+ end
1396
3392
  end
1397
3393
 
1398
- break
3394
+ expect(:paren_close) # Skip ')'
1399
3395
  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
3396
 
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
3397
+ func_expr = {
3398
+ type: :script_local_call,
3399
+ name: func_name,
3400
+ arguments: args,
3401
+ line: func_line,
3402
+ column: func_column
1417
3403
  }
3404
+ else
3405
+ # For other function calls
3406
+ func_expr = parse_expression
1418
3407
  end
1419
3408
 
1420
- return {
1421
- type: :list_literal,
1422
- elements: elements,
3409
+ {
3410
+ type: :call_statement,
3411
+ expression: func_expr,
1423
3412
  line: line,
1424
3413
  column: column
1425
3414
  }
1426
3415
  end
1427
3416
 
1428
- def parse_unary_expression
1429
- # Check for unary operators (!, -, +)
1430
- if current_token && current_token[:type] == :operator &&
1431
- ['!', '-', '+'].include?(current_token[:value])
3417
+ def parse_string
3418
+ # Start with the first string or expression
3419
+ left = parse_primary_term
3420
+
3421
+ # Continue as long as we see the '.' or '..' string concatenation operator
3422
+ while current_token && current_token[:type] == :operator &&
3423
+ (current_token[:value] == '.' || current_token[:value] == '..')
1432
3424
 
3425
+ # Store the operator token
1433
3426
  op_token = advance # Skip the operator
1434
- operand = parse_unary_expression # Recursively parse the operand
3427
+ op = op_token[:value]
1435
3428
 
1436
- return {
1437
- type: :unary_expression,
1438
- operator: op_token[:value],
1439
- operand: operand,
3429
+ # If this is the dot operator, check if it's actually part of '..'
3430
+ if op == '.' && peek_token && peek_token[:type] == :operator && peek_token[:value] == '.'
3431
+ advance # Skip the second dot
3432
+ op = '..'
3433
+ end
3434
+
3435
+ # Parse the right side of the concatenation
3436
+ right = parse_primary_term
3437
+
3438
+ # Create a string concatenation expression
3439
+ left = {
3440
+ type: :string_concatenation,
3441
+ operator: op,
3442
+ left: left,
3443
+ right: right,
1440
3444
  line: op_token[:line],
1441
3445
  column: op_token[:column]
1442
3446
  }
1443
3447
  end
1444
3448
 
1445
- # If no unary operator, parse a primary expression
1446
- return parse_primary_expression
3449
+ return left
1447
3450
  end
1448
3451
 
1449
- def parse_function_call(name, line, column)
1450
- expect(:paren_open)
3452
+ # Helper function to parse a primary term in string expressions
3453
+ def parse_primary_term
3454
+ if !current_token
3455
+ return nil
3456
+ end
1451
3457
 
1452
- args = []
3458
+ token = current_token
3459
+ line = token[:line]
3460
+ column = token[:column]
1453
3461
 
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
3462
+ case token[:type]
3463
+ when :string
3464
+ # Handle string literal
3465
+ advance
3466
+ return {
3467
+ type: :literal,
3468
+ value: token[:value],
3469
+ token_type: :string,
3470
+ line: line,
3471
+ column: column
3472
+ }
3473
+ when :identifier, :global_variable, :script_local, :arg_variable,
3474
+ :local_variable, :buffer_local, :window_local, :tab_local
3475
+ # Handle variable references
3476
+ advance
3477
+ return {
3478
+ type: token[:type],
3479
+ name: token[:value],
3480
+ line: line,
3481
+ column: column
3482
+ }
3483
+ when :paren_open
3484
+ # Handle parenthesized expressions
3485
+ advance # Skip '('
3486
+ expr = parse_expression
3487
+ expect(:paren_close) # Skip ')'
3488
+ return expr
3489
+ when :function
3490
+ # Handle function calls
3491
+ return parse_function_call(token[:value], line, column)
3492
+ else
3493
+ # For anything else, use the standard expression parser
3494
+ return parse_expression
3495
+ end
3496
+ end
1461
3497
 
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
1466
- arg = parse_expression
1467
- end
3498
+ def parse_silent_command
3499
+ token = advance # Skip 'silent'
3500
+ line = token[:line]
3501
+ column = token[:column]
1468
3502
 
1469
- args << arg if arg
3503
+ # Check for ! after silent
3504
+ has_bang = false
3505
+ if current_token && current_token[:type] == :operator && current_token[:value] == '!'
3506
+ has_bang = true
3507
+ advance # Skip '!'
3508
+ end
1470
3509
 
1471
- # Break if we hit the closing paren
1472
- if current_token && current_token[:type] == :paren_close
1473
- break
1474
- end
3510
+ # Now parse the command that follows silent
3511
+ # It could be a standard command or a command with a range
3512
+ command = nil
1475
3513
 
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
3514
+ # Check if the next token is a range operator (% in this case)
3515
+ if current_token && current_token[:type] == :operator && current_token[:value] == '%'
3516
+ range_token = advance # Skip '%'
3517
+
3518
+ # Now we expect a command (like 'delete')
3519
+ if current_token &&
3520
+ (current_token[:type] == :keyword || current_token[:type] == :identifier)
3521
+ cmd_token = advance # Skip the command name
3522
+ cmd_name = cmd_token[:value]
3523
+
3524
+ # Parse any arguments to the command
3525
+ args = []
3526
+ while current_token &&
3527
+ current_token[:type] != :comment &&
3528
+ (current_token[:value] != "\n" ||
3529
+ (current_token[:value] == "\n" &&
3530
+ !current_token[:value].start_with?("\\")))
3531
+
3532
+ # Add the token as an argument
3533
+ args << current_token
3534
+ advance
3535
+ end
3536
+
3537
+ command = {
3538
+ type: :range_command,
3539
+ range: '%',
3540
+ command: {
3541
+ type: :command,
3542
+ name: cmd_name,
3543
+ args: args,
3544
+ line: cmd_token[:line],
3545
+ column: cmd_token[:column]
3546
+ },
3547
+ line: range_token[:line],
3548
+ column: range_token[:column]
3549
+ }
3550
+ else
1481
3551
  @errors << {
1482
- message: "Expected comma or closing parenthesis after argument",
3552
+ message: "Expected command after range operator '%'",
1483
3553
  position: @position,
1484
- line: current_token[:line],
1485
- column: current_token[:column]
3554
+ line: current_token ? current_token[:line] : range_token[:line],
3555
+ column: current_token ? current_token[:column] : range_token[:column] + 1
1486
3556
  }
1487
- break
1488
3557
  end
3558
+ else
3559
+ # Parse a regular command
3560
+ command = parse_statement
1489
3561
  end
1490
3562
 
1491
- expect(:paren_close)
1492
-
1493
- {
1494
- type: :function_call,
1495
- name: name,
1496
- arguments: args,
3563
+ return {
3564
+ type: :silent_command,
3565
+ has_bang: has_bang,
3566
+ command: command,
1497
3567
  line: line,
1498
3568
  column: column
1499
3569
  }
1500
3570
  end
1501
3571
 
1502
- def parse_import_statement
1503
- token = advance # Skip 'import'
3572
+ def parse_delete_command
3573
+ token = advance # Skip 'delete'
1504
3574
  line = token[:line]
1505
3575
  column = token[:column]
1506
3576
 
1507
- # Handle 'import autoload'
1508
- is_autoload = false
1509
- module_name = nil
1510
- path = nil
1511
-
1512
- if current_token && current_token[:type] == :identifier && current_token[:value] == 'autoload'
1513
- is_autoload = true
1514
- module_name = advance[:value] # Store "autoload" as the module name
1515
-
1516
- # After "autoload" keyword, expect a string path
1517
- if current_token && current_token[:type] == :string
1518
- path = current_token[:value]
3577
+ # Check for register argument (could be an underscore '_')
3578
+ register = nil
3579
+ if current_token
3580
+ if current_token[:type] == :identifier && current_token[:value] == '_'
3581
+ register = current_token[:value]
3582
+ advance
3583
+ elsif current_token[:type] == :operator && current_token[:value] == '_'
3584
+ # Handle underscore as an operator (some lexers might classify it this way)
3585
+ register = current_token[:value]
1519
3586
  advance
1520
- else
1521
- @errors << {
1522
- message: "Expected string path after 'autoload'",
1523
- position: @position,
1524
- line: current_token ? current_token[:line] : 0,
1525
- column: current_token ? current_token[:column] : 0
1526
- }
1527
3587
  end
1528
- else
1529
- # Regular import with a string path
1530
- if current_token && current_token[:type] == :string
1531
- path = current_token[:value]
3588
+ end
1532
3589
 
1533
- # Extract module name from the path
1534
- # This is simplified logic - you might need more complex extraction
1535
- module_name = path.gsub(/['"]/, '').split('/').last.split('.').first
3590
+ return {
3591
+ type: :delete_command,
3592
+ register: register,
3593
+ line: line,
3594
+ column: column
3595
+ }
3596
+ end
1536
3597
 
1537
- advance
1538
- else
1539
- # Handle other import formats
1540
- module_expr = parse_expression()
1541
- if module_expr && module_expr[:type] == :literal && module_expr[:token_type] == :string
1542
- path = module_expr[:value]
1543
- module_name = path.gsub(/['"]/, '').split('/').last.split('.').first
1544
- end
1545
- end
3598
+ def parse_brace_sequence_value
3599
+ # Handle special brace sequences like {{{,}}} for foldmarker
3600
+ value_parts = []
3601
+
3602
+ while current_token && (current_token[:type] == :brace_open ||
3603
+ current_token[:type] == :brace_close ||
3604
+ current_token[:type] == :comma)
3605
+ value_parts << current_token[:value]
3606
+ advance
3607
+ end
3608
+
3609
+ return {
3610
+ type: :brace_sequence,
3611
+ value: value_parts.join(''),
3612
+ line: current_token ? current_token[:line] : 0,
3613
+ column: current_token ? current_token[:column] : 0
3614
+ }
3615
+ end
3616
+
3617
+ def parse_comma_separated_value
3618
+ # Handle comma-separated option values like menu,menuone,noinsert,noselect
3619
+ values = []
3620
+
3621
+ # Parse first value
3622
+ if current_token && current_token[:type] == :identifier
3623
+ values << current_token[:value]
3624
+ advance
3625
+ else
3626
+ return parse_expression # Fall back to regular expression parsing
1546
3627
  end
1547
3628
 
1548
- # Handle 'as name'
1549
- as_name = nil
1550
- if current_token && current_token[:type] == :identifier && current_token[:value] == 'as'
1551
- advance # Skip 'as'
3629
+ # Parse additional comma-separated values
3630
+ while current_token && current_token[:type] == :comma
3631
+ advance # Skip comma
1552
3632
  if current_token && current_token[:type] == :identifier
1553
- as_name = advance[:value]
3633
+ values << current_token[:value]
3634
+ advance
1554
3635
  else
1555
- @errors << {
1556
- message: "Expected identifier after 'as'",
1557
- position: @position,
1558
- line: current_token ? current_token[:line] : 0,
1559
- column: current_token ? current_token[:column] : 0
1560
- }
3636
+ break
1561
3637
  end
1562
3638
  end
1563
3639
 
1564
- {
1565
- type: :import_statement,
1566
- module: module_name,
1567
- path: path,
1568
- is_autoload: is_autoload,
1569
- as_name: as_name,
1570
- line: line,
1571
- column: column
3640
+ return {
3641
+ type: :comma_separated_value,
3642
+ values: values,
3643
+ line: current_token ? current_token[:line] : 0,
3644
+ column: current_token ? current_token[:column] : 0
1572
3645
  }
1573
3646
  end
1574
- def parse_export_statement
1575
- token = advance # Skip 'export'
3647
+
3648
+ def parse_set_command
3649
+ token = advance # Skip 'set'
1576
3650
  line = token[:line]
1577
3651
  column = token[:column]
1578
3652
 
1579
- # Export can be followed by var/const/def/function declarations
1580
- if !current_token
3653
+ # Parse option name
3654
+ option_name = nil
3655
+ if current_token && current_token[:type] == :identifier
3656
+ option_name = current_token[:value]
3657
+ advance
3658
+ elsif current_token && current_token[:type] == :option_variable
3659
+ # Handle option variable directly (like &option)
3660
+ option_name = current_token[:value].sub(/^&/, '')
3661
+ advance
3662
+ else
1581
3663
  @errors << {
1582
- message: "Expected declaration after export",
3664
+ message: "Expected option name after 'set'",
1583
3665
  position: @position,
3666
+ line: current_token ? current_token[:line] : line,
3667
+ column: current_token ? current_token[:column] : column + 3
3668
+ }
3669
+ return {
3670
+ type: :set_command,
3671
+ option: nil,
3672
+ value: nil,
3673
+ reset_to_vim_default: false,
1584
3674
  line: line,
1585
3675
  column: column
1586
3676
  }
1587
- return nil
1588
3677
  end
1589
3678
 
1590
- exported_item = nil
3679
+ # Check for &vim suffix (reset to Vim default)
3680
+ reset_to_vim_default = false
3681
+ if current_token && current_token[:type] == :option_variable &&
3682
+ (current_token[:value] == '&vim' || current_token[:value] == '&')
3683
+ reset_to_vim_default = true
3684
+ advance
3685
+ end
1591
3686
 
1592
- if current_token[:type] == :keyword
1593
- case current_token[:value]
1594
- when 'def'
1595
- exported_item = parse_def_function
1596
- when 'function'
1597
- exported_item = parse_legacy_function
1598
- when 'var', 'const', 'final'
1599
- exported_item = parse_variable_declaration
1600
- when 'class'
1601
- # Handle class export when implemented
1602
- @errors << {
1603
- message: "Class export not implemented yet",
1604
- position: @position,
1605
- line: current_token[:line],
1606
- column: current_token[:column]
1607
- }
1608
- advance
1609
- return nil
3687
+ # Check for equals sign and value
3688
+ value = nil
3689
+ if current_token && current_token[:type] == :operator && current_token[:value] == '='
3690
+ advance # Skip '='
3691
+
3692
+ # Special handling for foldmarker and similar options that use brace notation
3693
+ if option_name == 'foldmarker' && current_token && current_token[:type] == :brace_open
3694
+ # Parse as special brace sequence value (e.g., {{{,}}})
3695
+ value = parse_brace_sequence_value
3696
+ # Special handling for comma-separated option values
3697
+ elsif ['completeopt', 'formatoptions', 'guioptions', 'wildoptions'].include?(option_name)
3698
+ value = parse_comma_separated_value
1610
3699
  else
1611
- @errors << {
1612
- message: "Unexpected keyword after export: #{current_token[:value]}",
1613
- position: @position,
1614
- line: current_token[:line],
1615
- column: current_token[:column]
1616
- }
1617
- advance
1618
- return nil
3700
+ value = parse_expression
1619
3701
  end
1620
- else
1621
- @errors << {
1622
- message: "Expected declaration after export",
1623
- position: @position,
1624
- line: current_token[:line],
1625
- column: current_token[:column]
3702
+ elsif current_token && current_token[:type] == :operator && current_token[:value] == '-'
3703
+ # Handle syntaxes like 'set option-=value'
3704
+ op = current_token[:value]
3705
+ advance # Skip the operator
3706
+
3707
+ if current_token && current_token[:type] == :operator &&
3708
+ ['+', '=', '^'].include?(current_token[:value])
3709
+ op += current_token[:value]
3710
+ advance # Skip the second part of the operator
3711
+ end
3712
+
3713
+ value = {
3714
+ type: :option_operation,
3715
+ operator: op,
3716
+ value: parse_expression,
3717
+ line: line,
3718
+ column: column
3719
+ }
3720
+ elsif current_token && current_token[:type] == :operator && current_token[:value] == '+'
3721
+ # Handle syntaxes like 'set option+=value'
3722
+ op = current_token[:value]
3723
+ advance # Skip the operator
3724
+
3725
+ if current_token && current_token[:type] == :operator && current_token[:value] == '='
3726
+ op += current_token[:value]
3727
+ advance # Skip the '=' part
3728
+ end
3729
+
3730
+ value = {
3731
+ type: :option_operation,
3732
+ operator: op,
3733
+ value: parse_expression,
3734
+ line: line,
3735
+ column: column
1626
3736
  }
1627
- advance
1628
- return nil
1629
3737
  end
1630
3738
 
1631
- {
1632
- type: :export_statement,
1633
- export: exported_item,
3739
+ return {
3740
+ type: :set_command,
3741
+ option: option_name,
3742
+ value: value,
3743
+ reset_to_vim_default: reset_to_vim_default,
1634
3744
  line: line,
1635
3745
  column: column
1636
3746
  }
1637
3747
  end
1638
3748
 
1639
- def parse_legacy_function
1640
- token = advance # Skip 'function'
3749
+ def parse_syntax_command
3750
+ token = advance # Skip 'syntax'
1641
3751
  line = token[:line]
1642
3752
  column = token[:column]
1643
3753
 
1644
- # Check for bang (!) in function definition
1645
- has_bang = false
1646
- if current_token && current_token[:type] == :operator && current_token[:value] == '!'
1647
- has_bang = true
1648
- advance # Skip '!'
3754
+ # Parse syntax subcommand (match, region, keyword, etc.)
3755
+ subcommand = nil
3756
+ if current_token && current_token[:type] == :identifier
3757
+ subcommand = current_token[:value]
3758
+ advance
1649
3759
  end
1650
3760
 
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
3761
+ # Parse syntax group name
3762
+ group_name = nil
3763
+ if current_token && current_token[:type] == :identifier
3764
+ group_name = current_token[:value]
3765
+ advance
1667
3766
  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
3767
+
3768
+ # Parse pattern (regex or other pattern)
3769
+ pattern = nil
3770
+ if current_token && current_token[:type] == :regex
3771
+ pattern = current_token[:value]
1673
3772
  advance
1674
- elsif is_script_local || !function_scope.nil?
3773
+ end
3774
+
3775
+ # Parse any remaining arguments as raw text until end of line
3776
+ args = []
3777
+ while current_token && current_token[:type] != :comment
3778
+ args << current_token[:value]
3779
+ advance
3780
+ end
3781
+
3782
+ {
3783
+ type: :syntax_command,
3784
+ subcommand: subcommand,
3785
+ group_name: group_name,
3786
+ pattern: pattern,
3787
+ args: args.join(' ').strip,
3788
+ line: line,
3789
+ column: column
3790
+ }
3791
+ end
3792
+
3793
+ def parse_highlight_command
3794
+ token = advance # Skip 'highlight'
3795
+ line = token[:line]
3796
+ column = token[:column]
3797
+
3798
+ # Parse highlight group name
3799
+ group_name = nil
3800
+ if current_token && current_token[:type] == :identifier
3801
+ group_name = current_token[:value]
3802
+ advance
3803
+ end
3804
+
3805
+ # Parse highlight attributes as key=value pairs
3806
+ attributes = {}
3807
+ while current_token && current_token[:type] != :comment
3808
+ # Parse attribute name
1675
3809
  if current_token && current_token[:type] == :identifier
1676
- name = current_token
3810
+ attr_name = current_token[:value]
1677
3811
  advance
1678
- end
1679
- else
1680
- name = expect(:identifier)
1681
- end
1682
3812
 
1683
- # Parse parameter list
1684
- expect(:paren_open)
1685
- params = parse_parameter_list_legacy
1686
- expect(:paren_close)
3813
+ # Parse = sign
3814
+ if current_token && current_token[:type] == :operator && current_token[:value] == '='
3815
+ advance
1687
3816
 
1688
- # Check for optional attributes (range, dict, abort, closure)
1689
- attributes = []
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])
1695
- attributes << advance[:value]
3817
+ # Parse attribute value (can be identifier, number, or hex color)
3818
+ if current_token && (current_token[:type] == :identifier ||
3819
+ current_token[:type] == :number ||
3820
+ current_token[:type] == :hex_color)
3821
+ attributes[attr_name] = current_token[:value]
3822
+ advance
3823
+ end
3824
+ end
1696
3825
  else
1697
- break
3826
+ advance
1698
3827
  end
1699
3828
  end
1700
3829
 
1701
- # Parse function body
1702
- body = []
1703
- while @position < @tokens.length
1704
- if current_token && current_token[:type] == :keyword &&
1705
- ['endfunction', 'endfunc'].include?(current_token[:value])
1706
- break
1707
- end
3830
+ {
3831
+ type: :highlight_command,
3832
+ group_name: group_name,
3833
+ attributes: attributes,
3834
+ line: line,
3835
+ column: column
3836
+ }
3837
+ end
1708
3838
 
1709
- stmt = parse_statement
1710
- body << stmt if stmt
1711
- end
3839
+ def parse_sleep_command
3840
+ token = advance # Skip 'sleep'
3841
+ line = token[:line]
3842
+ column = token[:column]
1712
3843
 
1713
- # Expect endfunction/endfunc
1714
- end_token = advance # This should be 'endfunction' or 'endfunc'
1715
- if end_token && end_token[:type] != :keyword &&
1716
- !['endfunction', 'endfunc'].include?(end_token[:value])
3844
+ # Parse duration (number with optional suffix)
3845
+ duration = nil
3846
+ if current_token && current_token[:type] == :number
3847
+ duration = current_token[:value]
3848
+ advance
3849
+ else
1717
3850
  @errors << {
1718
- message: "Expected 'endfunction' or 'endfunc'",
3851
+ message: "Expected duration after 'sleep'",
1719
3852
  position: @position,
1720
- line: end_token ? end_token[:line] : 0,
1721
- column: end_token ? end_token[:column] : 0
3853
+ line: current_token ? current_token[:line] : line,
3854
+ column: current_token ? current_token[:column] : column + 5
1722
3855
  }
1723
3856
  end
1724
3857
 
1725
- function_name = name ? name[:value] : nil
1726
- if function_scope
1727
- function_name = function_scope + function_name if function_name
1728
- end
1729
-
1730
3858
  {
1731
- type: :legacy_function,
1732
- name: function_name,
1733
- is_script_local: is_script_local,
1734
- scope: function_scope,
1735
- params: params,
1736
- has_bang: has_bang,
1737
- attributes: attributes,
1738
- body: body,
3859
+ type: :sleep_command,
3860
+ duration: duration,
1739
3861
  line: line,
1740
3862
  column: column
1741
3863
  }
1742
3864
  end
1743
3865
 
1744
- # Legacy function parameters are different - they can use a:name syntax
1745
- def parse_parameter_list_legacy
1746
- params = []
3866
+ def parse_source_command
3867
+ token = advance # Skip 'source'
3868
+ line = token[:line]
3869
+ column = token[:column]
1747
3870
 
1748
- # Empty parameter list
1749
- if current_token && current_token[:type] == :paren_close
1750
- return params
3871
+ # Parse file path - collect all tokens until end of line/comment
3872
+ path_parts = []
3873
+ while current_token && current_token[:type] != :comment
3874
+ path_parts << current_token[:value]
3875
+ advance
1751
3876
  end
1752
3877
 
1753
- loop do
1754
- # Check for ... (varargs) in legacy function
1755
- if current_token && current_token[:type] == :ellipsis
1756
- advance
1757
- params << { type: :var_args_legacy, name: '...' }
1758
- break
1759
- end
1760
-
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
1783
- @errors << {
1784
- message: "Expected parameter name",
1785
- position: @position,
1786
- line: current_token ? current_token[:line] : 0,
1787
- column: current_token ? current_token[:column] : 0
1788
- }
1789
- break
1790
- end
1791
-
1792
- if current_token && current_token[:type] == :comma
1793
- advance
1794
- else
1795
- break
1796
- end
1797
- end
3878
+ file_path = path_parts.join('')
1798
3879
 
1799
- params
3880
+ {
3881
+ type: :source_command,
3882
+ file_path: file_path,
3883
+ line: line,
3884
+ column: column
3885
+ }
1800
3886
  end
1801
-
1802
3887
  end
1803
3888
  end