vinter 0.4.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.
- checksums.yaml +4 -4
- data/lib/vinter/ast_printer.rb +14 -0
- data/lib/vinter/cli.rb +1 -1
- data/lib/vinter/lexer.rb +59 -16
- data/lib/vinter/parser.rb +217 -500
- data/lib/vinter.rb +2 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 319f22f198207f7d9d3b9e58c85327b20f7c873e905bc16a38acd6f9199d8b56
|
|
4
|
+
data.tar.gz: c5eeb2b1dd21094be8a901e570540b89a29619273b0c5fcb11b7edc278da9975
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1556c23fd42b7b7d9d3163a97ecd353902f12341c203a913a4dffec05cbd6ffe867815f223dbfe8f169fd99c514b8b39ac1fab87fd26f195d0622fff9e62966
|
|
7
|
+
data.tar.gz: 2204f688018212f0b5ec6b13a0906da8a24078fe874dc10c2b9b326668627fb985fdcc11b9682bdbb67f2a452838b8088286260de54bf8941b6501180a7a8704
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Vinter
|
|
2
|
+
module ASTPrinter
|
|
3
|
+
def self.print(ast, indent=0)
|
|
4
|
+
spaces = " " * indent * 2
|
|
5
|
+
ast[:body].each do |node|
|
|
6
|
+
puts "#{spaces}(#{node[:type]}|#{node[:value]})"
|
|
7
|
+
if node[:type] == :export_statement
|
|
8
|
+
puts "(#{node[:export][:type]})"
|
|
9
|
+
print(node[:export], indent=1)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/vinter/cli.rb
CHANGED
data/lib/vinter/lexer.rb
CHANGED
|
@@ -2,7 +2,9 @@ module Vinter
|
|
|
2
2
|
class Lexer
|
|
3
3
|
TOKEN_TYPES = {
|
|
4
4
|
# Vim9 specific keywords
|
|
5
|
-
keyword: /\b(if|else|elseif|endif|while|endwhile|for|endfor|def|enddef|function|endfunction|endfunc|return|const|var|final|import|export|class|extends|static|enum|type|vim9script|abort|autocmd|echom|echoerr|echohl|echomsg|let|unlet|execute|exec|continue|break|try|catch|finally|endtry|throw|runtime|silent|delete|command|call|set|setlocal|syntax|highlight|sleep|source|nnoremap|nmap|inoremap|imap|vnoremap|vmap|xnoremap|xmap|cnoremap|cmap|noremap|map)\b/,
|
|
5
|
+
keyword: /\b(if|else|elseif|endif|while|endwhile|for|endfor|def|enddef|function|endfunction|endfunc|return|const|var|final|import|export|class|extends|static|enum|type|vim9script|scriptencoding|abort|autocmd|echom|echoerr|echohl|echomsg|let|unlet|execute|exec|continue|break|try|catch|finally|endtry|throw|runtime|silent|delete|command|call|set|setlocal|syntax|highlight|sleep|source|nnoremap|nmap|inoremap|imap|vnoremap|vmap|xnoremap|xmap|cnoremap|cmap|noremap|map|var)\b/,
|
|
6
|
+
encodings: /\b(latin1|iso|koi8|macroman|cp437|cp737|cp775|cp850|cp852|cp855|cp857|cp860|cp861|cp862|cp863|cp865|cp866|cp869|cp874|cp1250|cp1251|cp1253|cp1254|cp1255|cp1256|cp1257|cp1258|cp932|euc\-jp|sjis|cp949|euc\-kr|cp936|euc\-cn|cp950|big5|euc\-tw|utf\-8|ucs\-2|ucs\-21e|utf\-16|utf\-16le|ucs\-4|ucs\-4le|ansi|japan|korea|prc|chinese|taiwan|utf8|unicode|ucs2be|ucs\-2be|ucs\-4be|utf\-32|utf\-32le|default)\b/,
|
|
7
|
+
vimfuncs: /\b(win_execute|win_findbuf|win_getid|win_gettype|win_gotoid|win_id2tabwin|win_id2win|win_move_separator|win_move_statusline|win_screenpos|win_splitmove)\b/,
|
|
6
8
|
# Identifiers can include # and special characters
|
|
7
9
|
identifier: /\b[a-zA-Z_][a-zA-Z0-9_#]*\b/,
|
|
8
10
|
# Single-character operators
|
|
@@ -26,7 +28,7 @@ module Vinter
|
|
|
26
28
|
comma: /,/,
|
|
27
29
|
backslash: /\\/,
|
|
28
30
|
question_mark: /\?/,
|
|
29
|
-
command_separator:
|
|
31
|
+
command_separator: /\|/
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
CONTINUATION_OPERATORS = %w(. .. + - * / = == ==# ==? != > < >= <= && || ? : -> =>)
|
|
@@ -41,21 +43,21 @@ module Vinter
|
|
|
41
43
|
def should_parse_as_regex
|
|
42
44
|
# Look at recent tokens to determine if we're in a regex context
|
|
43
45
|
recent_tokens = @tokens.last(3)
|
|
44
|
-
|
|
46
|
+
|
|
45
47
|
# Check for contexts where regex is expected
|
|
46
|
-
return true if recent_tokens.any? { |t|
|
|
47
|
-
t && t[:type] == :keyword && ['syntax'].include?(t[:value])
|
|
48
|
+
return true if recent_tokens.any? { |t|
|
|
49
|
+
t && t[:type] == :keyword && ['syntax'].include?(t[:value])
|
|
48
50
|
}
|
|
49
|
-
|
|
51
|
+
|
|
50
52
|
return true if recent_tokens.any? { |t|
|
|
51
53
|
t && t[:type] == :identifier && ['match', 'region', 'keyword'].include?(t[:value])
|
|
52
54
|
}
|
|
53
|
-
|
|
55
|
+
|
|
54
56
|
# Check for comparison operators that often use regex
|
|
55
57
|
return true if recent_tokens.any? { |t|
|
|
56
58
|
t && t[:type] == :operator && ['=~', '!~', '=~#', '!~#', '=~?', '!~?'].include?(t[:value])
|
|
57
59
|
}
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
false
|
|
60
62
|
end
|
|
61
63
|
|
|
@@ -85,7 +87,7 @@ module Vinter
|
|
|
85
87
|
end
|
|
86
88
|
line_start = temp_pos < 0 || @input[temp_pos] == "\n"
|
|
87
89
|
end
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
# If we're at the start of a line and it begins with a quote
|
|
90
92
|
if line_start && chunk.start_with?('"')
|
|
91
93
|
# Find the end of the line
|
|
@@ -103,6 +105,47 @@ module Vinter
|
|
|
103
105
|
@column += comment_text.length
|
|
104
106
|
next
|
|
105
107
|
end
|
|
108
|
+
|
|
109
|
+
# --- Interpolated String Handling ---
|
|
110
|
+
if chunk.start_with?("$'")
|
|
111
|
+
i = 2
|
|
112
|
+
string_value = "$'"
|
|
113
|
+
brace_depth = 0
|
|
114
|
+
escaped = false
|
|
115
|
+
|
|
116
|
+
while i < chunk.length
|
|
117
|
+
char = chunk[i]
|
|
118
|
+
string_value += char
|
|
119
|
+
|
|
120
|
+
if char == '\\' && !escaped
|
|
121
|
+
escaped = true
|
|
122
|
+
elsif char == "'" && !escaped && brace_depth == 0
|
|
123
|
+
# End of interpolated string
|
|
124
|
+
i += 1
|
|
125
|
+
break
|
|
126
|
+
elsif char == '{' && !escaped
|
|
127
|
+
brace_depth += 1
|
|
128
|
+
elsif char == '}' && !escaped && brace_depth > 0
|
|
129
|
+
brace_depth -= 1
|
|
130
|
+
elsif escaped
|
|
131
|
+
escaped = false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
i += 1
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@tokens << {
|
|
138
|
+
type: :interpolated_string,
|
|
139
|
+
value: string_value,
|
|
140
|
+
line: @line_num,
|
|
141
|
+
column: @column
|
|
142
|
+
}
|
|
143
|
+
@column += string_value.length
|
|
144
|
+
@position += string_value.length
|
|
145
|
+
@line_num += string_value.count("\n")
|
|
146
|
+
next
|
|
147
|
+
end
|
|
148
|
+
|
|
106
149
|
# Handle string literals manually
|
|
107
150
|
if chunk.start_with?("'") || chunk.start_with?('"')
|
|
108
151
|
quote = chunk[0]
|
|
@@ -118,7 +161,7 @@ module Vinter
|
|
|
118
161
|
if char == '\\' && !escaped
|
|
119
162
|
escaped = true
|
|
120
163
|
elsif (char == "\n" or char == quote) && !escaped
|
|
121
|
-
|
|
164
|
+
i += 1
|
|
122
165
|
break
|
|
123
166
|
elsif escaped
|
|
124
167
|
escaped = false
|
|
@@ -186,7 +229,7 @@ module Vinter
|
|
|
186
229
|
end
|
|
187
230
|
|
|
188
231
|
# Check for keywords first, before other token types
|
|
189
|
-
if match = chunk.match(/\A\b(if|else|elseif|endif|while|endwhile|for|endfor|def|enddef|function|endfunction|endfunc|return|const|var|final|import|export|class|extends|static|enum|type|vim9script|abort|autocmd|echoerr|echohl|echomsg|let|unlet|execute|setlocal|syntax|highlight|sleep|source)\b/)
|
|
232
|
+
if match = chunk.match(/\A\b(if|else|elseif|endif|while|endwhile|for|endfor|def|enddef|function|endfunction|endfunc|return|const|var|final|import|export|class|extends|static|enum|type|vim9script|abort|autocmd|echoerr|echohl|echomsg|let|unlet|var|execute|setlocal|syntax|highlight|sleep|source)\b/)
|
|
190
233
|
@tokens << {
|
|
191
234
|
type: :keyword,
|
|
192
235
|
value: match[0],
|
|
@@ -210,7 +253,7 @@ module Vinter
|
|
|
210
253
|
@position += match[0].length
|
|
211
254
|
next
|
|
212
255
|
end
|
|
213
|
-
|
|
256
|
+
|
|
214
257
|
# Handle Vim option variables with & prefix
|
|
215
258
|
if match = chunk.match(/\A&[a-zA-Z_][a-zA-Z0-9_]*/)
|
|
216
259
|
@tokens << {
|
|
@@ -385,21 +428,21 @@ module Vinter
|
|
|
385
428
|
if chunk.start_with?('/') && should_parse_as_regex
|
|
386
429
|
i = 1
|
|
387
430
|
regex_value = '/'
|
|
388
|
-
|
|
431
|
+
|
|
389
432
|
# Keep going until we find the closing slash
|
|
390
433
|
while i < chunk.length
|
|
391
434
|
char = chunk[i]
|
|
392
435
|
regex_value += char
|
|
393
|
-
|
|
436
|
+
|
|
394
437
|
if char == '/' && (i == 1 || chunk[i-1] != '\\')
|
|
395
438
|
# Found closing slash
|
|
396
439
|
i += 1
|
|
397
440
|
break
|
|
398
441
|
end
|
|
399
|
-
|
|
442
|
+
|
|
400
443
|
i += 1
|
|
401
444
|
end
|
|
402
|
-
|
|
445
|
+
|
|
403
446
|
# Add the regex token if we found a closing slash
|
|
404
447
|
if regex_value.end_with?('/')
|
|
405
448
|
@tokens << {
|
data/lib/vinter/parser.rb
CHANGED
|
@@ -71,6 +71,95 @@ module Vinter
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
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
|
+
|
|
74
163
|
def parse_program
|
|
75
164
|
statements = []
|
|
76
165
|
|
|
@@ -265,6 +354,8 @@ module Vinter
|
|
|
265
354
|
when 'vim9script'
|
|
266
355
|
token = advance # Skip 'vim9script'
|
|
267
356
|
{ type: :vim9script_declaration, line: token[:line], column: token[:column] }
|
|
357
|
+
when 'scriptencoding'
|
|
358
|
+
parse_scriptencoding
|
|
268
359
|
when 'autocmd'
|
|
269
360
|
parse_autocmd_statement
|
|
270
361
|
when 'execute', 'exec'
|
|
@@ -333,6 +424,24 @@ module Vinter
|
|
|
333
424
|
parse_delete_command
|
|
334
425
|
elsif current_token[:type] == :percentage
|
|
335
426
|
parse_range_command
|
|
427
|
+
elsif current_token[:type] == :global_variable
|
|
428
|
+
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])
|
|
336
445
|
else
|
|
337
446
|
@warnings << {
|
|
338
447
|
message: "Unexpected token type: #{current_token[:type]}",
|
|
@@ -427,80 +536,7 @@ module Vinter
|
|
|
427
536
|
}
|
|
428
537
|
end
|
|
429
538
|
|
|
430
|
-
def old_parse_filter_command
|
|
431
|
-
token = advance # Skip 'filter' or 'filt'
|
|
432
|
-
line = token[:line]
|
|
433
|
-
column = token[:column]
|
|
434
|
-
|
|
435
|
-
# Check for bang (!)
|
|
436
|
-
has_bang = false
|
|
437
|
-
if current_token && current_token[:type] == :operator && current_token[:value] == '!'
|
|
438
|
-
has_bang = true
|
|
439
|
-
advance # Skip '!'
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
# Parse the pattern
|
|
443
|
-
pattern = nil
|
|
444
|
-
pattern_delimiter = nil
|
|
445
|
-
|
|
446
|
-
if current_token && current_token[:type] == :operator && current_token[:value] == '/'
|
|
447
|
-
# Handle /pattern/ form
|
|
448
|
-
pattern_delimiter = '/'
|
|
449
|
-
advance # Skip opening delimiter
|
|
450
|
-
|
|
451
|
-
# Collect all tokens until closing delimiter
|
|
452
|
-
pattern_parts = []
|
|
453
|
-
while @position < @tokens.length &&
|
|
454
|
-
!(current_token[:type] == :operator && current_token[:value] == pattern_delimiter)
|
|
455
|
-
pattern_parts << current_token[:value]
|
|
456
|
-
advance
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
pattern = pattern_parts.join('')
|
|
460
|
-
|
|
461
|
-
# Skip closing delimiter
|
|
462
|
-
if current_token && current_token[:type] == :operator && current_token[:value] == pattern_delimiter
|
|
463
|
-
advance
|
|
464
|
-
else
|
|
465
|
-
@errors << {
|
|
466
|
-
message: "Expected closing pattern delimiter: #{pattern_delimiter}",
|
|
467
|
-
position: @position,
|
|
468
|
-
line: current_token ? current_token[:line] : 0,
|
|
469
|
-
column: current_token ? current_token[:column] : 0
|
|
470
|
-
}
|
|
471
|
-
end
|
|
472
|
-
else
|
|
473
|
-
# Handle direct pattern form (without delimiters)
|
|
474
|
-
# Parse until we see what appears to be the command
|
|
475
|
-
pattern_parts = []
|
|
476
|
-
while @position < @tokens.length
|
|
477
|
-
# Don't consume tokens that likely belong to the command part
|
|
478
|
-
if current_token[:type] == :keyword ||
|
|
479
|
-
(current_token[:type] == :identifier &&
|
|
480
|
-
['echo', 'let', 'execute', 'exec', 'autocmd', 'au', 'oldfiles', 'clist', 'command',
|
|
481
|
-
'files', 'highlight', 'jumps', 'list', 'llist', 'marks', 'registers', 'set'].include?(current_token[:value]))
|
|
482
|
-
break
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
pattern_parts << current_token[:value]
|
|
486
|
-
advance
|
|
487
|
-
end
|
|
488
539
|
|
|
489
|
-
pattern = pattern_parts.join('').strip
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
# Parse the command to be filtered
|
|
493
|
-
command = parse_statement
|
|
494
|
-
|
|
495
|
-
{
|
|
496
|
-
type: :filter_command,
|
|
497
|
-
pattern: pattern,
|
|
498
|
-
has_bang: has_bang,
|
|
499
|
-
command: command,
|
|
500
|
-
line: line,
|
|
501
|
-
column: column
|
|
502
|
-
}
|
|
503
|
-
end
|
|
504
540
|
|
|
505
541
|
def parse_augroup_statement
|
|
506
542
|
token = advance # Skip 'augroup'
|
|
@@ -513,18 +549,11 @@ module Vinter
|
|
|
513
549
|
name = current_token[:value]
|
|
514
550
|
advance
|
|
515
551
|
else
|
|
516
|
-
|
|
517
|
-
message: "Expected augroup name",
|
|
518
|
-
position: @position,
|
|
519
|
-
line: current_token ? current_token[:line] : 0,
|
|
520
|
-
column: current_token ? current_token[:column] : 0
|
|
521
|
-
}
|
|
552
|
+
add_error("Expected augroup name")
|
|
522
553
|
end
|
|
523
554
|
|
|
524
555
|
# Check for augroup END
|
|
525
|
-
|
|
526
|
-
if name && (name.upcase == "END" || name == "END")
|
|
527
|
-
is_end_marker = true
|
|
556
|
+
if name && name.upcase == "END"
|
|
528
557
|
return {
|
|
529
558
|
type: :augroup_end,
|
|
530
559
|
line: line,
|
|
@@ -540,8 +569,7 @@ module Vinter
|
|
|
540
569
|
(current_token[:type] == :identifier && current_token[:value] == 'augroup')
|
|
541
570
|
# Look ahead for END
|
|
542
571
|
if peek_token &&
|
|
543
|
-
((peek_token[:type] == :identifier &&
|
|
544
|
-
(peek_token[:value].upcase == 'END' || peek_token[:value] == 'END')) ||
|
|
572
|
+
((peek_token[:type] == :identifier && peek_token[:value].upcase == 'END') ||
|
|
545
573
|
(peek_token[:type] == :keyword && peek_token[:value].upcase == 'END'))
|
|
546
574
|
advance # Skip 'augroup'
|
|
547
575
|
advance # Skip 'END'
|
|
@@ -619,12 +647,7 @@ module Vinter
|
|
|
619
647
|
column: dot_token[:column]
|
|
620
648
|
}
|
|
621
649
|
else
|
|
622
|
-
|
|
623
|
-
message: "Expected property name after '.'",
|
|
624
|
-
position: @position,
|
|
625
|
-
line: current_token ? current_token[:line] : 0,
|
|
626
|
-
column: current_token ? current_token[:column] : 0
|
|
627
|
-
}
|
|
650
|
+
add_error("Expected property name after '.'")
|
|
628
651
|
end
|
|
629
652
|
end
|
|
630
653
|
|
|
@@ -684,12 +707,7 @@ module Vinter
|
|
|
684
707
|
}
|
|
685
708
|
advance
|
|
686
709
|
else
|
|
687
|
-
|
|
688
|
-
message: "Expected variable name in destructuring assignment",
|
|
689
|
-
position: @position,
|
|
690
|
-
line: current_token ? current_token[:line] : 0,
|
|
691
|
-
column: current_token ? current_token[:column] : 0
|
|
692
|
-
}
|
|
710
|
+
add_error("Expected variable name in destructuring assignment")
|
|
693
711
|
# Try to recover by advancing to next comma or closing bracket
|
|
694
712
|
while current_token && current_token[:type] != :comma && current_token[:type] != :bracket_close
|
|
695
713
|
advance
|
|
@@ -706,12 +724,7 @@ module Vinter
|
|
|
706
724
|
column: bracket_token[:column]
|
|
707
725
|
}
|
|
708
726
|
else
|
|
709
|
-
|
|
710
|
-
message: "Expected variable name after let",
|
|
711
|
-
position: @position,
|
|
712
|
-
line: current_token ? current_token[:line] : 0,
|
|
713
|
-
column: current_token ? current_token[:column] : 0
|
|
714
|
-
}
|
|
727
|
+
add_error("Expected variable name after let")
|
|
715
728
|
end
|
|
716
729
|
end
|
|
717
730
|
|
|
@@ -723,12 +736,7 @@ module Vinter
|
|
|
723
736
|
operator = current_token[:value]
|
|
724
737
|
advance
|
|
725
738
|
else
|
|
726
|
-
|
|
727
|
-
message: "Expected assignment operator after variable in let statement",
|
|
728
|
-
position: @position,
|
|
729
|
-
line: current_token ? current_token[:line] : 0,
|
|
730
|
-
column: current_token ? current_token[:column] : 0
|
|
731
|
-
}
|
|
739
|
+
add_error("Expected assignment operator after variable in let statement")
|
|
732
740
|
end
|
|
733
741
|
|
|
734
742
|
# Parse the value expression
|
|
@@ -748,12 +756,7 @@ module Vinter
|
|
|
748
756
|
func_name = current_token[:value]
|
|
749
757
|
advance
|
|
750
758
|
else
|
|
751
|
-
|
|
752
|
-
message: "Expected string with function name in function() call",
|
|
753
|
-
position: @position,
|
|
754
|
-
line: current_token ? current_token[:line] : 0,
|
|
755
|
-
column: current_token ? current_token[:column] : 0
|
|
756
|
-
}
|
|
759
|
+
add_error("Expected string with function name in function() call")
|
|
757
760
|
end
|
|
758
761
|
|
|
759
762
|
# Expect closing parenthesis
|
|
@@ -972,32 +975,14 @@ module Vinter
|
|
|
972
975
|
advance # Skip the pipe
|
|
973
976
|
|
|
974
977
|
# Expect endif
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
else
|
|
978
|
-
@errors << {
|
|
979
|
-
message: "Expected 'endif' after '|' in one-line if statement",
|
|
980
|
-
position: @position,
|
|
981
|
-
line: current_token ? current_token[:line] : 0,
|
|
982
|
-
column: current_token ? current_token[:column] : 0
|
|
983
|
-
}
|
|
978
|
+
unless expect_keyword('endif')
|
|
979
|
+
add_error("Expected 'endif' after '|' in one-line if statement")
|
|
984
980
|
end
|
|
985
981
|
end
|
|
986
982
|
else
|
|
987
|
-
#
|
|
988
|
-
#
|
|
989
|
-
|
|
990
|
-
# Parse statements until we hit 'else', 'elseif', or 'endif'
|
|
991
|
-
while @position < @tokens.length
|
|
992
|
-
# Check for the tokens that would terminate this block
|
|
993
|
-
if current_token && current_token[:type] == :keyword &&
|
|
994
|
-
['else', 'elseif', 'endif'].include?(current_token[:value])
|
|
995
|
-
break
|
|
996
|
-
end
|
|
997
|
-
|
|
998
|
-
stmt = parse_statement
|
|
999
|
-
then_branch << stmt if stmt
|
|
1000
|
-
end
|
|
983
|
+
# Parse multi-line if statement
|
|
984
|
+
# Parse statements until 'else', 'elseif', or 'endif'
|
|
985
|
+
then_branch = parse_body_until('else', 'elseif', 'endif')
|
|
1001
986
|
|
|
1002
987
|
# Check for else/elseif
|
|
1003
988
|
if current_token && current_token[:type] == :keyword
|
|
@@ -1005,14 +990,7 @@ module Vinter
|
|
|
1005
990
|
advance # Skip 'else'
|
|
1006
991
|
|
|
1007
992
|
# Parse statements until 'endif'
|
|
1008
|
-
|
|
1009
|
-
if current_token[:type] == :keyword && current_token[:value] == 'endif'
|
|
1010
|
-
break
|
|
1011
|
-
end
|
|
1012
|
-
|
|
1013
|
-
stmt = parse_statement
|
|
1014
|
-
else_branch << stmt if stmt
|
|
1015
|
-
end
|
|
993
|
+
else_branch = parse_body_until('endif')
|
|
1016
994
|
elsif current_token[:value] == 'elseif'
|
|
1017
995
|
elseif_stmt = parse_if_statement
|
|
1018
996
|
else_branch << elseif_stmt if elseif_stmt
|
|
@@ -1029,19 +1007,7 @@ module Vinter
|
|
|
1029
1007
|
end
|
|
1030
1008
|
|
|
1031
1009
|
# Expect endif
|
|
1032
|
-
|
|
1033
|
-
advance # Skip 'endif'
|
|
1034
|
-
else
|
|
1035
|
-
# Don't add an error if we've already reached the end of the file
|
|
1036
|
-
if @position < @tokens.length
|
|
1037
|
-
@errors << {
|
|
1038
|
-
message: "Expected 'endif' to close if statement",
|
|
1039
|
-
position: @position,
|
|
1040
|
-
line: current_token ? current_token[:line] : 0,
|
|
1041
|
-
column: current_token ? current_token[:column] : 0
|
|
1042
|
-
}
|
|
1043
|
-
end
|
|
1044
|
-
end
|
|
1010
|
+
expect_end_keyword('endif')
|
|
1045
1011
|
end
|
|
1046
1012
|
|
|
1047
1013
|
{
|
|
@@ -1061,20 +1027,11 @@ module Vinter
|
|
|
1061
1027
|
column = token[:column]
|
|
1062
1028
|
condition = parse_expression
|
|
1063
1029
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
# Parse statements until we hit 'endwhile'
|
|
1067
|
-
while @position < @tokens.length
|
|
1068
|
-
if current_token[:type] == :keyword && current_token[:value] == 'endwhile'
|
|
1069
|
-
break
|
|
1070
|
-
end
|
|
1071
|
-
|
|
1072
|
-
stmt = parse_statement
|
|
1073
|
-
body << stmt if stmt
|
|
1074
|
-
end
|
|
1030
|
+
# Parse statements until 'endwhile'
|
|
1031
|
+
body = parse_body_until('endwhile')
|
|
1075
1032
|
|
|
1076
1033
|
# Expect endwhile
|
|
1077
|
-
|
|
1034
|
+
expect_end_keyword('endwhile')
|
|
1078
1035
|
|
|
1079
1036
|
{
|
|
1080
1037
|
type: :while_statement,
|
|
@@ -1090,166 +1047,22 @@ module Vinter
|
|
|
1090
1047
|
line = token[:line]
|
|
1091
1048
|
column = token[:column]
|
|
1092
1049
|
|
|
1093
|
-
#
|
|
1094
|
-
|
|
1095
|
-
|
|
1050
|
+
# Determine the type of for loop and parse variables accordingly
|
|
1051
|
+
loop_var = nil
|
|
1052
|
+
loop_vars = nil
|
|
1096
1053
|
|
|
1097
1054
|
if current_token && current_token[:type] == :bracket_open
|
|
1098
|
-
# Handle destructuring
|
|
1055
|
+
# Handle destructuring with brackets: for [key, val] in dict
|
|
1099
1056
|
advance # Skip '['
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
loop do
|
|
1104
|
-
if current_token && (current_token[:type] == :identifier ||
|
|
1105
|
-
current_token[:type] == :local_variable ||
|
|
1106
|
-
current_token[:type] == :global_variable ||
|
|
1107
|
-
current_token[:type] == :script_local)
|
|
1108
|
-
loop_vars << advance
|
|
1109
|
-
else
|
|
1110
|
-
@errors << {
|
|
1111
|
-
message: "Expected identifier in for loop variables",
|
|
1112
|
-
position: @position,
|
|
1113
|
-
line: current_token ? current_token[:line] : 0,
|
|
1114
|
-
column: current_token ? current_token[:column] : 0
|
|
1115
|
-
}
|
|
1116
|
-
break
|
|
1117
|
-
end
|
|
1118
|
-
|
|
1119
|
-
if current_token && current_token[:type] == :comma
|
|
1120
|
-
advance # Skip ','
|
|
1121
|
-
else
|
|
1122
|
-
break
|
|
1123
|
-
end
|
|
1124
|
-
end
|
|
1125
|
-
|
|
1126
|
-
expect(:bracket_close) # Skip ']'
|
|
1127
|
-
|
|
1128
|
-
if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
|
|
1129
|
-
@errors << {
|
|
1130
|
-
message: "Expected 'in' after for loop variables",
|
|
1131
|
-
position: @position,
|
|
1132
|
-
line: current_token ? current_token[:line] : 0,
|
|
1133
|
-
column: current_token ? current_token[:column] : 0
|
|
1134
|
-
}
|
|
1135
|
-
else
|
|
1136
|
-
advance # Skip 'in'
|
|
1137
|
-
end
|
|
1138
|
-
|
|
1139
|
-
iterable = parse_expression
|
|
1140
|
-
|
|
1141
|
-
# Parse the body until 'endfor'
|
|
1142
|
-
body = []
|
|
1143
|
-
while @position < @tokens.length
|
|
1144
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1145
|
-
break
|
|
1146
|
-
end
|
|
1147
|
-
|
|
1148
|
-
stmt = parse_statement
|
|
1149
|
-
body << stmt if stmt
|
|
1150
|
-
end
|
|
1151
|
-
|
|
1152
|
-
# Expect endfor
|
|
1153
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1154
|
-
advance # Skip 'endfor'
|
|
1155
|
-
else
|
|
1156
|
-
# Only add an error if we haven't reached the end of the file
|
|
1157
|
-
if @position < @tokens.length
|
|
1158
|
-
@errors << {
|
|
1159
|
-
message: "Expected 'endfor' to close for statement",
|
|
1160
|
-
position: @position,
|
|
1161
|
-
line: current_token ? current_token[:line] : 0,
|
|
1162
|
-
column: current_token ? current_token[:column] : 0
|
|
1163
|
-
}
|
|
1164
|
-
end
|
|
1165
|
-
end
|
|
1166
|
-
|
|
1167
|
-
return {
|
|
1168
|
-
type: :for_statement,
|
|
1169
|
-
loop_vars: loop_vars,
|
|
1170
|
-
iterable: iterable,
|
|
1171
|
-
body: body,
|
|
1172
|
-
line: line,
|
|
1173
|
-
column: column
|
|
1174
|
-
}
|
|
1057
|
+
loop_vars = parse_loop_variables
|
|
1058
|
+
expect(:bracket_close)
|
|
1059
|
+
expect_in_keyword
|
|
1175
1060
|
elsif current_token && current_token[:type] == :paren_open
|
|
1176
|
-
# Handle multiple variables
|
|
1061
|
+
# Handle multiple variables with parentheses: for (var1, var2) in list
|
|
1177
1062
|
advance # Skip '('
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
loop do
|
|
1182
|
-
if current_token && (current_token[:type] == :identifier ||
|
|
1183
|
-
current_token[:type] == :local_variable ||
|
|
1184
|
-
current_token[:type] == :global_variable ||
|
|
1185
|
-
current_token[:type] == :script_local)
|
|
1186
|
-
loop_vars << advance
|
|
1187
|
-
else
|
|
1188
|
-
@errors << {
|
|
1189
|
-
message: "Expected identifier in for loop variables",
|
|
1190
|
-
position: @position,
|
|
1191
|
-
line: current_token ? current_token[:line] : 0,
|
|
1192
|
-
column: current_token ? current_token[:column] : 0
|
|
1193
|
-
}
|
|
1194
|
-
break
|
|
1195
|
-
end
|
|
1196
|
-
|
|
1197
|
-
if current_token && current_token[:type] == :comma
|
|
1198
|
-
advance # Skip ','
|
|
1199
|
-
else
|
|
1200
|
-
break
|
|
1201
|
-
end
|
|
1202
|
-
end
|
|
1203
|
-
|
|
1204
|
-
expect(:paren_close) # Skip ')'
|
|
1205
|
-
|
|
1206
|
-
if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
|
|
1207
|
-
@errors << {
|
|
1208
|
-
message: "Expected 'in' after for loop variables",
|
|
1209
|
-
position: @position,
|
|
1210
|
-
line: current_token ? current_token[:line] : 0,
|
|
1211
|
-
column: current_token ? current_token[:column] : 0
|
|
1212
|
-
}
|
|
1213
|
-
else
|
|
1214
|
-
advance # Skip 'in'
|
|
1215
|
-
end
|
|
1216
|
-
|
|
1217
|
-
iterable = parse_expression
|
|
1218
|
-
|
|
1219
|
-
# Parse the body until 'endfor'
|
|
1220
|
-
body = []
|
|
1221
|
-
while @position < @tokens.length
|
|
1222
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1223
|
-
break
|
|
1224
|
-
end
|
|
1225
|
-
|
|
1226
|
-
stmt = parse_statement
|
|
1227
|
-
body << stmt if stmt
|
|
1228
|
-
end
|
|
1229
|
-
|
|
1230
|
-
# Expect endfor
|
|
1231
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1232
|
-
advance # Skip 'endfor'
|
|
1233
|
-
else
|
|
1234
|
-
# Only add an error if we haven't reached the end of the file
|
|
1235
|
-
if @position < @tokens.length
|
|
1236
|
-
@errors << {
|
|
1237
|
-
message: "Expected 'endfor' to close for statement",
|
|
1238
|
-
position: @position,
|
|
1239
|
-
line: current_token ? current_token[:line] : 0,
|
|
1240
|
-
column: current_token ? current_token[:column] : 0
|
|
1241
|
-
}
|
|
1242
|
-
end
|
|
1243
|
-
end
|
|
1244
|
-
|
|
1245
|
-
return {
|
|
1246
|
-
type: :for_statement,
|
|
1247
|
-
loop_vars: loop_vars,
|
|
1248
|
-
iterable: iterable,
|
|
1249
|
-
body: body,
|
|
1250
|
-
line: line,
|
|
1251
|
-
column: column
|
|
1252
|
-
}
|
|
1063
|
+
loop_vars = parse_loop_variables
|
|
1064
|
+
expect(:paren_close)
|
|
1065
|
+
expect_in_keyword
|
|
1253
1066
|
else
|
|
1254
1067
|
# Handle single variable: for var in list
|
|
1255
1068
|
if current_token && (current_token[:type] == :identifier ||
|
|
@@ -1258,63 +1071,36 @@ module Vinter
|
|
|
1258
1071
|
current_token[:type] == :script_local)
|
|
1259
1072
|
loop_var = advance
|
|
1260
1073
|
else
|
|
1261
|
-
|
|
1262
|
-
message: "Expected identifier as for loop variable",
|
|
1263
|
-
position: @position,
|
|
1264
|
-
line: current_token ? current_token[:line] : 0,
|
|
1265
|
-
column: current_token ? current_token[:column] : 0
|
|
1266
|
-
}
|
|
1267
|
-
loop_var = nil
|
|
1268
|
-
end
|
|
1269
|
-
|
|
1270
|
-
if !current_token || (current_token[:type] != :identifier || current_token[:value] != 'in')
|
|
1271
|
-
@errors << {
|
|
1272
|
-
message: "Expected 'in' after for loop variable",
|
|
1273
|
-
position: @position,
|
|
1274
|
-
line: current_token ? current_token[:line] : 0,
|
|
1275
|
-
column: current_token ? current_token[:column] : 0
|
|
1276
|
-
}
|
|
1277
|
-
else
|
|
1278
|
-
advance # Skip 'in'
|
|
1074
|
+
add_error("Expected identifier as for loop variable")
|
|
1279
1075
|
end
|
|
1076
|
+
expect_in_keyword
|
|
1077
|
+
end
|
|
1280
1078
|
|
|
1281
|
-
|
|
1079
|
+
# Parse the iterable expression
|
|
1080
|
+
iterable = parse_expression
|
|
1282
1081
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
while @position < @tokens.length
|
|
1286
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endfor'
|
|
1287
|
-
break
|
|
1288
|
-
end
|
|
1082
|
+
# Parse the body until 'endfor'
|
|
1083
|
+
body = parse_body_until('endfor')
|
|
1289
1084
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
end
|
|
1085
|
+
# Expect endfor
|
|
1086
|
+
expect_end_keyword('endfor')
|
|
1293
1087
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
position: @position,
|
|
1303
|
-
line: current_token ? current_token[:line] : 0,
|
|
1304
|
-
column: current_token ? current_token[:column] : 0
|
|
1305
|
-
}
|
|
1306
|
-
end
|
|
1307
|
-
end
|
|
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
|
+
}
|
|
1308
1096
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
body: body,
|
|
1314
|
-
line: line,
|
|
1315
|
-
column: column
|
|
1316
|
-
}
|
|
1097
|
+
if loop_vars
|
|
1098
|
+
result[:loop_vars] = loop_vars
|
|
1099
|
+
else
|
|
1100
|
+
result[:loop_var] = loop_var
|
|
1317
1101
|
end
|
|
1102
|
+
|
|
1103
|
+
result
|
|
1318
1104
|
end
|
|
1319
1105
|
|
|
1320
1106
|
def parse_def_function
|
|
@@ -1336,19 +1122,11 @@ module Vinter
|
|
|
1336
1122
|
return_type = parse_type
|
|
1337
1123
|
end
|
|
1338
1124
|
|
|
1339
|
-
# Parse function body
|
|
1340
|
-
body =
|
|
1341
|
-
while @position < @tokens.length
|
|
1342
|
-
if current_token[:type] == :keyword && current_token[:value] == 'enddef'
|
|
1343
|
-
break
|
|
1344
|
-
end
|
|
1345
|
-
|
|
1346
|
-
stmt = parse_statement
|
|
1347
|
-
body << stmt if stmt
|
|
1348
|
-
end
|
|
1125
|
+
# Parse function body until 'enddef'
|
|
1126
|
+
body = parse_body_until('enddef')
|
|
1349
1127
|
|
|
1350
1128
|
# Expect enddef
|
|
1351
|
-
|
|
1129
|
+
expect_end_keyword('enddef')
|
|
1352
1130
|
|
|
1353
1131
|
{
|
|
1354
1132
|
type: :def_function,
|
|
@@ -1391,12 +1169,7 @@ module Vinter
|
|
|
1391
1169
|
|
|
1392
1170
|
# After varargs, we expect closing paren
|
|
1393
1171
|
if current_token && current_token[:type] != :paren_close
|
|
1394
|
-
|
|
1395
|
-
message: "Expected closing parenthesis after varargs",
|
|
1396
|
-
position: @position,
|
|
1397
|
-
line: current_token[:line],
|
|
1398
|
-
column: current_token[:column]
|
|
1399
|
-
}
|
|
1172
|
+
add_error("Expected closing parenthesis after varargs", current_token)
|
|
1400
1173
|
end
|
|
1401
1174
|
|
|
1402
1175
|
break
|
|
@@ -1404,12 +1177,7 @@ module Vinter
|
|
|
1404
1177
|
|
|
1405
1178
|
# Get parameter name
|
|
1406
1179
|
if !current_token || current_token[:type] != :identifier
|
|
1407
|
-
|
|
1408
|
-
message: "Expected parameter name",
|
|
1409
|
-
position: @position,
|
|
1410
|
-
line: current_token ? current_token[:line] : 0,
|
|
1411
|
-
column: current_token ? current_token[:column] : 0
|
|
1412
|
-
}
|
|
1180
|
+
add_error("Expected parameter name")
|
|
1413
1181
|
break
|
|
1414
1182
|
end
|
|
1415
1183
|
|
|
@@ -1444,12 +1212,7 @@ module Vinter
|
|
|
1444
1212
|
advance
|
|
1445
1213
|
# If we don't have a comma, we should have a closing paren
|
|
1446
1214
|
elsif current_token && current_token[:type] != :paren_close
|
|
1447
|
-
|
|
1448
|
-
message: "Expected comma or closing parenthesis after parameter",
|
|
1449
|
-
position: @position,
|
|
1450
|
-
line: current_token[:line],
|
|
1451
|
-
column: current_token[:column]
|
|
1452
|
-
}
|
|
1215
|
+
add_error("Expected comma or closing parenthesis after parameter", current_token)
|
|
1453
1216
|
break
|
|
1454
1217
|
end
|
|
1455
1218
|
end
|
|
@@ -1478,12 +1241,7 @@ module Vinter
|
|
|
1478
1241
|
|
|
1479
1242
|
return type_name[:value]
|
|
1480
1243
|
else
|
|
1481
|
-
|
|
1482
|
-
message: "Expected type identifier",
|
|
1483
|
-
position: @position,
|
|
1484
|
-
line: current_token ? current_token[:line] : 0,
|
|
1485
|
-
column: current_token ? current_token[:column] : 0
|
|
1486
|
-
}
|
|
1244
|
+
add_error("Expected type identifier")
|
|
1487
1245
|
advance
|
|
1488
1246
|
return "unknown"
|
|
1489
1247
|
end
|
|
@@ -1496,12 +1254,7 @@ module Vinter
|
|
|
1496
1254
|
column = var_type_token[:column]
|
|
1497
1255
|
|
|
1498
1256
|
if !current_token || current_token[:type] != :identifier
|
|
1499
|
-
|
|
1500
|
-
message: "Expected variable name",
|
|
1501
|
-
position: @position,
|
|
1502
|
-
line: current_token ? current_token[:line] : 0,
|
|
1503
|
-
column: current_token ? current_token[:column] : 0
|
|
1504
|
-
}
|
|
1257
|
+
add_error("Expected variable name")
|
|
1505
1258
|
return nil
|
|
1506
1259
|
end
|
|
1507
1260
|
|
|
@@ -2064,6 +1817,18 @@ module Vinter
|
|
|
2064
1817
|
end
|
|
2065
1818
|
end
|
|
2066
1819
|
end
|
|
1820
|
+
when :vimfuncs
|
|
1821
|
+
advance
|
|
1822
|
+
if current_token[:type] == :paren_open
|
|
1823
|
+
expr = parse_function_call(token[:value], line, column)
|
|
1824
|
+
else
|
|
1825
|
+
expr = {
|
|
1826
|
+
type: :vimfuncs,
|
|
1827
|
+
name: token[:value],
|
|
1828
|
+
line: line,
|
|
1829
|
+
column: column
|
|
1830
|
+
}
|
|
1831
|
+
end
|
|
2067
1832
|
when :namespace_prefix
|
|
2068
1833
|
advance
|
|
2069
1834
|
expr = {
|
|
@@ -2099,6 +1864,12 @@ module Vinter
|
|
|
2099
1864
|
when :line_continuation
|
|
2100
1865
|
advance
|
|
2101
1866
|
expr = parse_expression
|
|
1867
|
+
when :interpolated_string
|
|
1868
|
+
advance
|
|
1869
|
+
expr = {
|
|
1870
|
+
type: :interpolated_string,
|
|
1871
|
+
value: token[:value],
|
|
1872
|
+
}
|
|
2102
1873
|
else
|
|
2103
1874
|
@errors << {
|
|
2104
1875
|
message: "Unexpected token in expression: #{token[:type]}",
|
|
@@ -2117,7 +1888,7 @@ module Vinter
|
|
|
2117
1888
|
# Look ahead to determine if this is property access or concatenation
|
|
2118
1889
|
next_token = peek_token
|
|
2119
1890
|
is_property_access = next_token && (next_token[:type] == :identifier || next_token[:type] == :keyword)
|
|
2120
|
-
|
|
1891
|
+
|
|
2121
1892
|
# Check if this is a property access (only when right side is identifier/keyword)
|
|
2122
1893
|
if is_property_access && (expr[:type] == :identifier || expr[:type] == :global_variable ||
|
|
2123
1894
|
expr[:type] == :script_local || expr[:type] == :namespace_prefix ||
|
|
@@ -2754,7 +2525,7 @@ module Vinter
|
|
|
2754
2525
|
current_token[:type] == :whitespace)
|
|
2755
2526
|
advance
|
|
2756
2527
|
end
|
|
2757
|
-
|
|
2528
|
+
|
|
2758
2529
|
# After skipping continuations, we should find the closing brace
|
|
2759
2530
|
if current_token && current_token[:type] == :brace_close
|
|
2760
2531
|
# This is fine - last entry without trailing comma
|
|
@@ -3086,6 +2857,13 @@ module Vinter
|
|
|
3086
2857
|
column: column
|
|
3087
2858
|
}
|
|
3088
2859
|
end
|
|
2860
|
+
|
|
2861
|
+
def parse_scriptencoding
|
|
2862
|
+
token = advance
|
|
2863
|
+
advance
|
|
2864
|
+
{ type: :scriptencoding, encoding: current_token[:value] }
|
|
2865
|
+
end
|
|
2866
|
+
|
|
3089
2867
|
def parse_export_statement
|
|
3090
2868
|
token = advance # Skip 'export'
|
|
3091
2869
|
line = token[:line]
|
|
@@ -3296,33 +3074,11 @@ module Vinter
|
|
|
3296
3074
|
end
|
|
3297
3075
|
end
|
|
3298
3076
|
|
|
3299
|
-
# Parse function body
|
|
3300
|
-
body =
|
|
3301
|
-
while @position < @tokens.length
|
|
3302
|
-
if current_token && current_token[:type] == :keyword &&
|
|
3303
|
-
['endfunction', 'endfunc'].include?(current_token[:value])
|
|
3304
|
-
break
|
|
3305
|
-
end
|
|
3306
|
-
|
|
3307
|
-
stmt = parse_statement
|
|
3308
|
-
body << stmt if stmt
|
|
3309
|
-
end
|
|
3077
|
+
# Parse function body until 'endfunction' or 'endfunc'
|
|
3078
|
+
body = parse_body_until('endfunction', 'endfunc')
|
|
3310
3079
|
|
|
3311
3080
|
# Expect endfunction/endfunc
|
|
3312
|
-
|
|
3313
|
-
['endfunction', 'endfunc'].include?(current_token[:value])
|
|
3314
|
-
advance # Skip 'endfunction' or 'endfunc'
|
|
3315
|
-
else
|
|
3316
|
-
# Only add an error if we haven't reached the end of the file
|
|
3317
|
-
if @position < @tokens.length
|
|
3318
|
-
@errors << {
|
|
3319
|
-
message: "Expected 'endfunction' or 'endfunc'",
|
|
3320
|
-
position: @position,
|
|
3321
|
-
line: current_token ? current_token[:line] : 0,
|
|
3322
|
-
column: current_token ? current_token[:column] : 0
|
|
3323
|
-
}
|
|
3324
|
-
end
|
|
3325
|
-
end
|
|
3081
|
+
expect_end_keyword('endfunction', 'endfunc')
|
|
3326
3082
|
|
|
3327
3083
|
function_name = name ? name[:value] : nil
|
|
3328
3084
|
if function_scope
|
|
@@ -3508,22 +3264,12 @@ module Vinter
|
|
|
3508
3264
|
line = token[:line]
|
|
3509
3265
|
column = token[:column]
|
|
3510
3266
|
|
|
3511
|
-
# Parse the try body
|
|
3512
|
-
body =
|
|
3267
|
+
# Parse the try body until catch/finally/endtry
|
|
3268
|
+
body = parse_body_until('catch', 'finally', 'endtry')
|
|
3269
|
+
|
|
3513
3270
|
catch_clauses = []
|
|
3514
3271
|
finally_clause = nil
|
|
3515
3272
|
|
|
3516
|
-
# Parse statements in the try block
|
|
3517
|
-
while @position < @tokens.length
|
|
3518
|
-
if current_token && current_token[:type] == :keyword &&
|
|
3519
|
-
['catch', 'finally', 'endtry'].include?(current_token[:value])
|
|
3520
|
-
break
|
|
3521
|
-
end
|
|
3522
|
-
|
|
3523
|
-
stmt = parse_statement
|
|
3524
|
-
body << stmt if stmt
|
|
3525
|
-
end
|
|
3526
|
-
|
|
3527
3273
|
# Parse catch clauses
|
|
3528
3274
|
while @position < @tokens.length &&
|
|
3529
3275
|
current_token && current_token[:type] == :keyword &&
|
|
@@ -3549,17 +3295,8 @@ module Vinter
|
|
|
3549
3295
|
advance
|
|
3550
3296
|
end
|
|
3551
3297
|
|
|
3552
|
-
# Parse the catch body
|
|
3553
|
-
catch_body =
|
|
3554
|
-
while @position < @tokens.length
|
|
3555
|
-
if current_token && current_token[:type] == :keyword &&
|
|
3556
|
-
['catch', 'finally', 'endtry'].include?(current_token[:value])
|
|
3557
|
-
break
|
|
3558
|
-
end
|
|
3559
|
-
|
|
3560
|
-
stmt = parse_statement
|
|
3561
|
-
catch_body << stmt if stmt
|
|
3562
|
-
end
|
|
3298
|
+
# Parse the catch body until next catch/finally/endtry
|
|
3299
|
+
catch_body = parse_body_until('catch', 'finally', 'endtry')
|
|
3563
3300
|
|
|
3564
3301
|
catch_clauses << {
|
|
3565
3302
|
type: :catch_clause,
|
|
@@ -3578,16 +3315,8 @@ module Vinter
|
|
|
3578
3315
|
finally_line = finally_token[:line]
|
|
3579
3316
|
finally_column = finally_token[:column]
|
|
3580
3317
|
|
|
3581
|
-
# Parse the finally body
|
|
3582
|
-
finally_body =
|
|
3583
|
-
while @position < @tokens.length
|
|
3584
|
-
if current_token && current_token[:type] == :keyword && current_token[:value] == 'endtry'
|
|
3585
|
-
break
|
|
3586
|
-
end
|
|
3587
|
-
|
|
3588
|
-
stmt = parse_statement
|
|
3589
|
-
finally_body << stmt if stmt
|
|
3590
|
-
end
|
|
3318
|
+
# Parse the finally body until 'endtry'
|
|
3319
|
+
finally_body = parse_body_until('endtry')
|
|
3591
3320
|
|
|
3592
3321
|
finally_clause = {
|
|
3593
3322
|
type: :finally_clause,
|
|
@@ -3598,19 +3327,7 @@ module Vinter
|
|
|
3598
3327
|
end
|
|
3599
3328
|
|
|
3600
3329
|
# Expect endtry
|
|
3601
|
-
|
|
3602
|
-
advance # Skip 'endtry'
|
|
3603
|
-
else
|
|
3604
|
-
# Only add an error if we haven't reached the end of the file
|
|
3605
|
-
if @position < @tokens.length
|
|
3606
|
-
@errors << {
|
|
3607
|
-
message: "Expected 'endtry' to close try statement",
|
|
3608
|
-
position: @position,
|
|
3609
|
-
line: current_token ? current_token[:line] : 0,
|
|
3610
|
-
column: current_token ? current_token[:column] : 0
|
|
3611
|
-
}
|
|
3612
|
-
end
|
|
3613
|
-
end
|
|
3330
|
+
expect_end_keyword('endtry')
|
|
3614
3331
|
|
|
3615
3332
|
return {
|
|
3616
3333
|
type: :try_statement,
|
|
@@ -3881,14 +3598,14 @@ module Vinter
|
|
|
3881
3598
|
def parse_brace_sequence_value
|
|
3882
3599
|
# Handle special brace sequences like {{{,}}} for foldmarker
|
|
3883
3600
|
value_parts = []
|
|
3884
|
-
|
|
3885
|
-
while current_token && (current_token[:type] == :brace_open ||
|
|
3886
|
-
current_token[:type] == :brace_close ||
|
|
3601
|
+
|
|
3602
|
+
while current_token && (current_token[:type] == :brace_open ||
|
|
3603
|
+
current_token[:type] == :brace_close ||
|
|
3887
3604
|
current_token[:type] == :comma)
|
|
3888
3605
|
value_parts << current_token[:value]
|
|
3889
3606
|
advance
|
|
3890
3607
|
end
|
|
3891
|
-
|
|
3608
|
+
|
|
3892
3609
|
return {
|
|
3893
3610
|
type: :brace_sequence,
|
|
3894
3611
|
value: value_parts.join(''),
|
|
@@ -3900,7 +3617,7 @@ module Vinter
|
|
|
3900
3617
|
def parse_comma_separated_value
|
|
3901
3618
|
# Handle comma-separated option values like menu,menuone,noinsert,noselect
|
|
3902
3619
|
values = []
|
|
3903
|
-
|
|
3620
|
+
|
|
3904
3621
|
# Parse first value
|
|
3905
3622
|
if current_token && current_token[:type] == :identifier
|
|
3906
3623
|
values << current_token[:value]
|
|
@@ -3908,7 +3625,7 @@ module Vinter
|
|
|
3908
3625
|
else
|
|
3909
3626
|
return parse_expression # Fall back to regular expression parsing
|
|
3910
3627
|
end
|
|
3911
|
-
|
|
3628
|
+
|
|
3912
3629
|
# Parse additional comma-separated values
|
|
3913
3630
|
while current_token && current_token[:type] == :comma
|
|
3914
3631
|
advance # Skip comma
|
|
@@ -3919,7 +3636,7 @@ module Vinter
|
|
|
3919
3636
|
break
|
|
3920
3637
|
end
|
|
3921
3638
|
end
|
|
3922
|
-
|
|
3639
|
+
|
|
3923
3640
|
return {
|
|
3924
3641
|
type: :comma_separated_value,
|
|
3925
3642
|
values: values,
|
|
@@ -3971,7 +3688,7 @@ module Vinter
|
|
|
3971
3688
|
value = nil
|
|
3972
3689
|
if current_token && current_token[:type] == :operator && current_token[:value] == '='
|
|
3973
3690
|
advance # Skip '='
|
|
3974
|
-
|
|
3691
|
+
|
|
3975
3692
|
# Special handling for foldmarker and similar options that use brace notation
|
|
3976
3693
|
if option_name == 'foldmarker' && current_token && current_token[:type] == :brace_open
|
|
3977
3694
|
# Parse as special brace sequence value (e.g., {{{,}}})
|
|
@@ -4098,8 +3815,8 @@ module Vinter
|
|
|
4098
3815
|
advance
|
|
4099
3816
|
|
|
4100
3817
|
# Parse attribute value (can be identifier, number, or hex color)
|
|
4101
|
-
if current_token && (current_token[:type] == :identifier ||
|
|
4102
|
-
current_token[:type] == :number ||
|
|
3818
|
+
if current_token && (current_token[:type] == :identifier ||
|
|
3819
|
+
current_token[:type] == :number ||
|
|
4103
3820
|
current_token[:type] == :hex_color)
|
|
4104
3821
|
attributes[attr_name] = current_token[:value]
|
|
4105
3822
|
advance
|
data/lib/vinter.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vinter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dan Bradbury
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-11-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A linter for the Vim9 script language, helping to identify issues and
|
|
14
14
|
enforce best practices
|
|
@@ -22,6 +22,7 @@ files:
|
|
|
22
22
|
- README.md
|
|
23
23
|
- bin/vinter
|
|
24
24
|
- lib/vinter.rb
|
|
25
|
+
- lib/vinter/ast_printer.rb
|
|
25
26
|
- lib/vinter/cli.rb
|
|
26
27
|
- lib/vinter/lexer.rb
|
|
27
28
|
- lib/vinter/linter.rb
|