vinter 0.1.0 → 0.3.0

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