vinter 0.2.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]
@@ -375,8 +549,6 @@ module Vinter
375
549
  }
376
550
  end
377
551
 
378
-
379
-
380
552
  def parse_parameter_list
381
553
  params = []
382
554
 
@@ -385,7 +557,8 @@ module Vinter
385
557
  return params
386
558
  end
387
559
 
388
- loop do
560
+ # Parse parameters until we find a closing parenthesis
561
+ while @position < @tokens.length && current_token && current_token[:type] != :paren_close
389
562
  # Check for variable args
390
563
  if current_token && current_token[:type] == :ellipsis
391
564
  ellipsis_token = advance
@@ -403,6 +576,17 @@ module Vinter
403
576
  line: ellipsis_token[:line],
404
577
  column: ellipsis_token[:column]
405
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
+
406
590
  break
407
591
  end
408
592
 
@@ -437,15 +621,23 @@ module Vinter
437
621
  type: :parameter,
438
622
  name: param_name[:value],
439
623
  param_type: param_type,
440
- optional: false, # Set this based on default value
624
+ optional: default_value != nil,
441
625
  default_value: default_value,
442
626
  line: param_name[:line],
443
627
  column: param_name[:column]
444
628
  }
445
629
 
630
+ # If we have a comma, advance past it and continue
446
631
  if current_token && current_token[:type] == :comma
447
632
  advance
448
- 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
+ }
449
641
  break
450
642
  end
451
643
  end
@@ -548,6 +740,39 @@ module Vinter
548
740
  end
549
741
 
550
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
551
776
  expr = parse_expression
552
777
  {
553
778
  type: :expression_statement,
@@ -562,24 +787,38 @@ module Vinter
562
787
  end
563
788
 
564
789
  def parse_binary_expression(precedence = 0)
565
- left = parse_primary_expression
790
+ left = parse_unary_expression
566
791
 
567
- while current_token && current_token[:type] == :operator &&
568
- operator_precedence(current_token[:value]) >= precedence
569
- op_token = advance
570
- op = op_token[:value]
571
- 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))
572
796
 
573
- 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
574
801
 
575
- left = {
576
- type: :binary_expression,
577
- operator: op,
578
- left: left,
579
- right: right,
580
- line: op_token[:line],
581
- column: op_token[:column]
582
- }
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
583
822
  end
584
823
 
585
824
  return left
@@ -599,6 +838,10 @@ module Vinter
599
838
  5
600
839
  when '*', '/', '%' # Multiplication, division, modulo
601
840
  6
841
+ when '.'
842
+ 7
843
+ when '!'
844
+ 8
602
845
  else
603
846
  0
604
847
  end
@@ -611,10 +854,13 @@ module Vinter
611
854
  line = token[:line]
612
855
  column = token[:column]
613
856
 
857
+ # First parse the basic expression
858
+ expr = nil
859
+
614
860
  case token[:type]
615
861
  when :number
616
862
  advance
617
- {
863
+ expr = {
618
864
  type: :literal,
619
865
  value: token[:value],
620
866
  token_type: :number,
@@ -623,14 +869,33 @@ module Vinter
623
869
  }
624
870
  when :string
625
871
  advance
626
- {
872
+ expr = {
627
873
  type: :literal,
628
874
  value: token[:value],
629
875
  token_type: :string,
630
876
  line: line,
631
877
  column: column
632
878
  }
633
- 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)
634
899
  advance
635
900
 
636
901
  # Check if this is a function call
@@ -638,17 +903,72 @@ module Vinter
638
903
  return parse_function_call(token[:value], line, column)
639
904
  end
640
905
 
641
- {
642
- 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,
643
917
  name: token[:value],
644
918
  line: line,
645
919
  column: column
646
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
647
956
  when :paren_open
648
- 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
649
971
  expr = parse_expression
650
- expect(:paren_close) # Expect and skip ')'
651
- return expr
652
972
  else
653
973
  @errors << {
654
974
  message: "Unexpected token in expression: #{token[:type]}",
@@ -659,6 +979,471 @@ module Vinter
659
979
  advance
660
980
  return nil
661
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
662
1447
  end
663
1448
 
664
1449
  def parse_function_call(name, line, column)
@@ -666,14 +1451,40 @@ module Vinter
666
1451
 
667
1452
  args = []
668
1453
 
669
- # Parse arguments
670
- unless current_token && current_token[:type] == :paren_close
671
- 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
672
1466
  arg = parse_expression
673
- args << arg if arg
1467
+ end
674
1468
 
675
- break unless current_token && current_token[:type] == :comma
676
- 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
677
1488
  end
678
1489
  end
679
1490
 
@@ -837,7 +1648,37 @@ module Vinter
837
1648
  advance # Skip '!'
838
1649
  end
839
1650
 
840
- 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
841
1682
 
842
1683
  # Parse parameter list
843
1684
  expect(:paren_open)
@@ -846,8 +1687,11 @@ module Vinter
846
1687
 
847
1688
  # Check for optional attributes (range, dict, abort, closure)
848
1689
  attributes = []
849
- while current_token && current_token[:type] == :identifier
850
- 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])
851
1695
  attributes << advance[:value]
852
1696
  else
853
1697
  break
@@ -878,9 +1722,16 @@ module Vinter
878
1722
  }
879
1723
  end
880
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
+
881
1730
  {
882
1731
  type: :legacy_function,
883
- name: name ? name[:value] : nil,
1732
+ name: function_name,
1733
+ is_script_local: is_script_local,
1734
+ scope: function_scope,
884
1735
  params: params,
885
1736
  has_bang: has_bang,
886
1737
  attributes: attributes,
@@ -890,7 +1741,7 @@ module Vinter
890
1741
  }
891
1742
  end
892
1743
 
893
- # Legacy function parameters are different - they use a:name syntax
1744
+ # Legacy function parameters are different - they can use a:name syntax
894
1745
  def parse_parameter_list_legacy
895
1746
  params = []
896
1747
 
@@ -907,7 +1758,28 @@ module Vinter
907
1758
  break
908
1759
  end
909
1760
 
910
- 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
911
1783
  @errors << {
912
1784
  message: "Expected parameter name",
913
1785
  position: @position,
@@ -917,15 +1789,6 @@ module Vinter
917
1789
  break
918
1790
  end
919
1791
 
920
- param_name = advance
921
-
922
- params << {
923
- type: :parameter,
924
- name: param_name[:value],
925
- line: param_name[:line],
926
- column: param_name[:column]
927
- }
928
-
929
1792
  if current_token && current_token[:type] == :comma
930
1793
  advance
931
1794
  else