vinter 0.5.0 → 0.6.1
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/cli.rb +1 -1
- data/lib/vinter/lexer.rb +76 -25
- data/lib/vinter/parser.rb +233 -120
- data/lib/vinter.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 18b99e927ea645baaa6bf8290f53625a800c64a36e7bcb681d8096f7591ab862
|
|
4
|
+
data.tar.gz: 35660a038fcc98c284731d75fe3644d41ff5a9212dd1ca7930d21d72b9f02764
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9ed5e432fcdc6e0a88545e15da0996dda4c31176c88fdcbb46f2ea4228bc4c431fe1e569b3e7420f370ab8147ac290738ac27dd21f1f7bed0a0d2093da5b933
|
|
7
|
+
data.tar.gz: be685a411f5592425d122ea8a3080208c1cad58566dc88b7e5850a0e017ae9beebfff1c1a019955a47eb7e54a732ba94e4f2966d52e536404ce35ad1096465f9
|
data/lib/vinter/cli.rb
CHANGED
data/lib/vinter/lexer.rb
CHANGED
|
@@ -2,9 +2,10 @@ 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|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|
|
|
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
|
|
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/,
|
|
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|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)\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|setbufline)\b/,
|
|
8
|
+
builtin_funcs: /\b(highlight|hi|exe|normal!|normal)\b/,
|
|
8
9
|
# Identifiers can include # and special characters
|
|
9
10
|
identifier: /\b[a-zA-Z_][a-zA-Z0-9_#]*\b/,
|
|
10
11
|
# Single-character operators
|
|
@@ -146,44 +147,84 @@ module Vinter
|
|
|
146
147
|
next
|
|
147
148
|
end
|
|
148
149
|
|
|
150
|
+
# XXX: i dont like this not being combined with $' condition above
|
|
151
|
+
if chunk.start_with?('$"')
|
|
152
|
+
i = 2
|
|
153
|
+
string_value = '$"'
|
|
154
|
+
brace_depth = 0
|
|
155
|
+
escaped = false
|
|
156
|
+
|
|
157
|
+
while i < chunk.length
|
|
158
|
+
char = chunk[i]
|
|
159
|
+
string_value += char
|
|
160
|
+
|
|
161
|
+
if char == '\\' && !escaped
|
|
162
|
+
escaped = true
|
|
163
|
+
elsif char == '"' && !escaped && brace_depth == 0
|
|
164
|
+
# End of interpolated string
|
|
165
|
+
i += 1
|
|
166
|
+
break
|
|
167
|
+
elsif char == '{' && !escaped
|
|
168
|
+
brace_depth += 1
|
|
169
|
+
elsif char == '}' && !escaped && brace_depth > 0
|
|
170
|
+
brace_depth -= 1
|
|
171
|
+
elsif escaped
|
|
172
|
+
escaped = false
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
i += 1
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
@tokens << {
|
|
179
|
+
type: :interpolated_string,
|
|
180
|
+
value: string_value,
|
|
181
|
+
line: @line_num,
|
|
182
|
+
column: @column
|
|
183
|
+
}
|
|
184
|
+
@column += string_value.length
|
|
185
|
+
@position += string_value.length
|
|
186
|
+
@line_num += string_value.count("\n")
|
|
187
|
+
next
|
|
188
|
+
end
|
|
189
|
+
|
|
149
190
|
# Handle string literals manually
|
|
150
191
|
if chunk.start_with?("'") || chunk.start_with?('"')
|
|
151
192
|
quote = chunk[0]
|
|
152
193
|
i = 1
|
|
153
|
-
escaped = false
|
|
154
194
|
string_value = quote
|
|
155
195
|
|
|
156
196
|
# Keep going until we find an unescaped closing quote
|
|
157
197
|
while i < chunk.length
|
|
158
198
|
char = chunk[i]
|
|
199
|
+
next_char = chunk[i + 1] if i + 1 < chunk.length
|
|
200
|
+
|
|
159
201
|
string_value += char
|
|
160
202
|
|
|
161
|
-
if char ==
|
|
162
|
-
escaped
|
|
163
|
-
|
|
203
|
+
if char == quote && next_char == quote
|
|
204
|
+
# Handle escaped single quote ('') or double quote ("")
|
|
205
|
+
string_value += next_char
|
|
206
|
+
i += 1
|
|
207
|
+
elsif char == quote
|
|
208
|
+
# End of string
|
|
164
209
|
i += 1
|
|
165
210
|
break
|
|
166
|
-
elsif escaped
|
|
167
|
-
escaped = false
|
|
168
211
|
end
|
|
169
212
|
|
|
170
213
|
i += 1
|
|
171
214
|
end
|
|
172
215
|
|
|
173
216
|
# Add the string token if we found a closing quote
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
217
|
+
@tokens << {
|
|
218
|
+
type: :string,
|
|
219
|
+
value: string_value,
|
|
220
|
+
line: @line_num,
|
|
221
|
+
column: @column
|
|
222
|
+
}
|
|
181
223
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
end
|
|
224
|
+
@column += string_value.length
|
|
225
|
+
@position += string_value.length
|
|
226
|
+
@line_num += string_value.count("\n")
|
|
227
|
+
next
|
|
187
228
|
end
|
|
188
229
|
|
|
189
230
|
# Add special handling for command options in the tokenize method
|
|
@@ -373,7 +414,7 @@ module Vinter
|
|
|
373
414
|
end
|
|
374
415
|
|
|
375
416
|
# Add support for standalone namespace prefixes (like g:)
|
|
376
|
-
if match = chunk.match(/\A([
|
|
417
|
+
if match = chunk.match(/\A([gbwt]):/)
|
|
377
418
|
@tokens << {
|
|
378
419
|
type: :namespace_prefix,
|
|
379
420
|
value: match[0],
|
|
@@ -485,8 +526,7 @@ module Vinter
|
|
|
485
526
|
end
|
|
486
527
|
|
|
487
528
|
# In the tokenize method, add special handling for common mapping components
|
|
488
|
-
if chunk.start_with?('<CR>', '<Esc>', '<Tab>', '<Space>', '<C-')
|
|
489
|
-
(chunk =~ /\A<[A-Za-z0-9\-_]+>/)
|
|
529
|
+
if chunk.start_with?('<CR>', '<Esc>', '<Tab>', '<Space>', '<C-')
|
|
490
530
|
# Extract the special key notation
|
|
491
531
|
match = chunk.match(/\A(<[^>]+>)/)
|
|
492
532
|
if match
|
|
@@ -529,13 +569,24 @@ module Vinter
|
|
|
529
569
|
@column += 1
|
|
530
570
|
@position += 1
|
|
531
571
|
|
|
532
|
-
# If followed by a newline, advance to next line
|
|
572
|
+
# If followed by a newline, advance to the next line
|
|
533
573
|
if @position < @input.length && @input[@position] == "\n"
|
|
534
574
|
@line_num += 1
|
|
535
575
|
@column = 1
|
|
536
576
|
@position += 1
|
|
537
577
|
end
|
|
538
578
|
|
|
579
|
+
# Skip whitespace after the continuation
|
|
580
|
+
while @position < @input.length && @input[@position] =~ /\s/
|
|
581
|
+
if @input[@position] == "\n"
|
|
582
|
+
@line_num += 1
|
|
583
|
+
@column = 1
|
|
584
|
+
else
|
|
585
|
+
@column += 1
|
|
586
|
+
end
|
|
587
|
+
@position += 1
|
|
588
|
+
end
|
|
589
|
+
|
|
539
590
|
next
|
|
540
591
|
end
|
|
541
592
|
|
data/lib/vinter/parser.rb
CHANGED
|
@@ -244,7 +244,6 @@ module Vinter
|
|
|
244
244
|
if !current_token
|
|
245
245
|
return nil
|
|
246
246
|
end
|
|
247
|
-
start_token = current_token
|
|
248
247
|
|
|
249
248
|
# Handle pipe as command separator
|
|
250
249
|
if current_token[:type] == :operator && current_token[:value] == '|'
|
|
@@ -378,7 +377,7 @@ module Vinter
|
|
|
378
377
|
parse_set_command
|
|
379
378
|
when 'syntax'
|
|
380
379
|
parse_syntax_command
|
|
381
|
-
when 'highlight'
|
|
380
|
+
when 'highlight', 'hi'
|
|
382
381
|
parse_highlight_command
|
|
383
382
|
when 'sleep'
|
|
384
383
|
parse_sleep_command
|
|
@@ -425,23 +424,23 @@ module Vinter
|
|
|
425
424
|
elsif current_token[:type] == :percentage
|
|
426
425
|
parse_range_command
|
|
427
426
|
elsif current_token[:type] == :global_variable
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
427
|
+
parse_global_variable
|
|
428
|
+
elsif current_token[:type] == :buffer_local
|
|
429
|
+
parse_buffer_var
|
|
442
430
|
elsif current_token[:type] == :vimfuncs
|
|
443
431
|
advance
|
|
444
432
|
parse_function_call(current_token[:value], current_token[:line], current_token[:column])
|
|
433
|
+
elsif current_token[:type] == :builtin_funcs
|
|
434
|
+
parse_builtin_function_call
|
|
435
|
+
elsif current_token[:type] == :compound_operator
|
|
436
|
+
advance
|
|
437
|
+
parse_expression
|
|
438
|
+
elsif current_token[:type] == :string
|
|
439
|
+
parse_string
|
|
440
|
+
elsif current_token[:type] == :comma
|
|
441
|
+
advance
|
|
442
|
+
elsif current_token[:type] == :colon
|
|
443
|
+
parse_command_line_call
|
|
445
444
|
else
|
|
446
445
|
@warnings << {
|
|
447
446
|
message: "Unexpected token type: #{current_token[:type]}",
|
|
@@ -454,6 +453,182 @@ module Vinter
|
|
|
454
453
|
end
|
|
455
454
|
end
|
|
456
455
|
|
|
456
|
+
def parse_command_line_call
|
|
457
|
+
token = advance # Skip the ':'
|
|
458
|
+
line = token[:line]
|
|
459
|
+
column = token[:column]
|
|
460
|
+
|
|
461
|
+
# Extract the command or range
|
|
462
|
+
command = token[:value][1..] # Remove the leading ':'
|
|
463
|
+
|
|
464
|
+
# Check if the command is a range (e.g., `:2`, `:$`)
|
|
465
|
+
if command.match?(/^\d+$/) || command == '$'
|
|
466
|
+
return {
|
|
467
|
+
type: :range_command,
|
|
468
|
+
range: command,
|
|
469
|
+
line: line,
|
|
470
|
+
column: column
|
|
471
|
+
}
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# Otherwise, it's a regular command (e.g., `:exe`)
|
|
475
|
+
args = []
|
|
476
|
+
while current_token && current_token[:type] != :newline
|
|
477
|
+
args << current_token[:value]
|
|
478
|
+
advance
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
{
|
|
482
|
+
type: :command_line_call,
|
|
483
|
+
command: command,
|
|
484
|
+
arguments: args,
|
|
485
|
+
line: line,
|
|
486
|
+
column: column
|
|
487
|
+
}
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def parse_buffer_var
|
|
491
|
+
token = current_token
|
|
492
|
+
line = token[:line]
|
|
493
|
+
column = token[:column]
|
|
494
|
+
name = token[:value]
|
|
495
|
+
advance
|
|
496
|
+
|
|
497
|
+
# Handle property access (e.g., b:foo.bar)
|
|
498
|
+
target = {
|
|
499
|
+
type: :buffer_variable,
|
|
500
|
+
name: name,
|
|
501
|
+
line: line,
|
|
502
|
+
column: column
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
# Property access (dot notation)
|
|
506
|
+
# shared with global (TODO)
|
|
507
|
+
# would almost just take an overly simple check to the = operator and call everything after b: name
|
|
508
|
+
if current_token && current_token[:type] == :operator && current_token[:value] == '.'
|
|
509
|
+
dot_token = advance
|
|
510
|
+
if current_token && (current_token[:type] == :identifier || current_token[:type] == :keyword)
|
|
511
|
+
property_token = advance
|
|
512
|
+
target = {
|
|
513
|
+
type: :property_access,
|
|
514
|
+
object: target,
|
|
515
|
+
property: property_token[:value],
|
|
516
|
+
line: dot_token[:line],
|
|
517
|
+
column: dot_token[:column]
|
|
518
|
+
}
|
|
519
|
+
else
|
|
520
|
+
add_error("Expected property name after '.' in variable")
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Indexed access (e.g., b:foo[bar])
|
|
525
|
+
while current_token && current_token[:type] == :bracket_open
|
|
526
|
+
bracket_token = advance
|
|
527
|
+
index_expr = parse_expression
|
|
528
|
+
expect(:bracket_close)
|
|
529
|
+
target = {
|
|
530
|
+
type: :indexed_access,
|
|
531
|
+
object: target,
|
|
532
|
+
index: index_expr,
|
|
533
|
+
line: bracket_token[:line],
|
|
534
|
+
column: bracket_token[:column]
|
|
535
|
+
}
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# Assignment operator (= or compound)
|
|
539
|
+
operator = nil
|
|
540
|
+
if current_token && (
|
|
541
|
+
(current_token[:type] == :operator && current_token[:value] == '=') ||
|
|
542
|
+
current_token[:type] == :compound_operator)
|
|
543
|
+
operator = current_token[:value]
|
|
544
|
+
advance
|
|
545
|
+
else
|
|
546
|
+
add_error("Expected assignment operator after global variable")
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Value expression
|
|
550
|
+
value = parse_expression
|
|
551
|
+
|
|
552
|
+
{
|
|
553
|
+
type: :buffer_variable_assignment,
|
|
554
|
+
target: target,
|
|
555
|
+
operator: operator,
|
|
556
|
+
value: value,
|
|
557
|
+
line: line,
|
|
558
|
+
column: column
|
|
559
|
+
}
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def parse_global_variable
|
|
563
|
+
token = current_token
|
|
564
|
+
line = token[:line]
|
|
565
|
+
column = token[:column]
|
|
566
|
+
name = token[:value]
|
|
567
|
+
advance # Skip the global variable token
|
|
568
|
+
|
|
569
|
+
# Handle property access (e.g., g:foo.bar)
|
|
570
|
+
target = {
|
|
571
|
+
type: :global_variable,
|
|
572
|
+
name: name,
|
|
573
|
+
line: line,
|
|
574
|
+
column: column
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
# Property access (dot notation)
|
|
578
|
+
if current_token && current_token[:type] == :operator && current_token[:value] == '.'
|
|
579
|
+
dot_token = advance
|
|
580
|
+
if current_token && (current_token[:type] == :identifier || current_token[:type] == :keyword)
|
|
581
|
+
property_token = advance
|
|
582
|
+
target = {
|
|
583
|
+
type: :property_access,
|
|
584
|
+
object: target,
|
|
585
|
+
property: property_token[:value],
|
|
586
|
+
line: dot_token[:line],
|
|
587
|
+
column: dot_token[:column]
|
|
588
|
+
}
|
|
589
|
+
else
|
|
590
|
+
add_error("Expected property name after '.' in global variable")
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
# Indexed access (e.g., g:foo[bar])
|
|
595
|
+
while current_token && current_token[:type] == :bracket_open
|
|
596
|
+
bracket_token = advance
|
|
597
|
+
index_expr = parse_expression
|
|
598
|
+
expect(:bracket_close)
|
|
599
|
+
target = {
|
|
600
|
+
type: :indexed_access,
|
|
601
|
+
object: target,
|
|
602
|
+
index: index_expr,
|
|
603
|
+
line: bracket_token[:line],
|
|
604
|
+
column: bracket_token[:column]
|
|
605
|
+
}
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Assignment operator (= or compound)
|
|
609
|
+
operator = nil
|
|
610
|
+
if current_token && (
|
|
611
|
+
(current_token[:type] == :operator && current_token[:value] == '=') ||
|
|
612
|
+
current_token[:type] == :compound_operator)
|
|
613
|
+
operator = current_token[:value]
|
|
614
|
+
advance
|
|
615
|
+
else
|
|
616
|
+
add_error("Expected assignment operator after global variable")
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# Value expression
|
|
620
|
+
value = parse_expression
|
|
621
|
+
|
|
622
|
+
{
|
|
623
|
+
type: :global_variable_assignment,
|
|
624
|
+
target: target,
|
|
625
|
+
operator: operator,
|
|
626
|
+
value: value,
|
|
627
|
+
line: line,
|
|
628
|
+
column: column
|
|
629
|
+
}
|
|
630
|
+
end
|
|
631
|
+
|
|
457
632
|
def parse_range_command
|
|
458
633
|
token = advance # Skip '%'
|
|
459
634
|
line = token[:line]
|
|
@@ -847,9 +1022,11 @@ module Vinter
|
|
|
847
1022
|
event = nil
|
|
848
1023
|
if current_token && current_token[:type] == :identifier
|
|
849
1024
|
event = advance[:value]
|
|
1025
|
+
elsif current_token[:type] == :operator && current_token[:value] == "!"
|
|
1026
|
+
advance
|
|
850
1027
|
else
|
|
851
1028
|
@errors << {
|
|
852
|
-
message: "Expected event name after 'autocmd'",
|
|
1029
|
+
message: "Expected event name after 'autocmd' | #{current_token[:type]}",
|
|
853
1030
|
position: @position,
|
|
854
1031
|
line: current_token ? current_token[:line] : 0,
|
|
855
1032
|
column: current_token ? current_token[:column] : 0
|
|
@@ -1117,7 +1294,7 @@ module Vinter
|
|
|
1117
1294
|
|
|
1118
1295
|
# Parse optional return type
|
|
1119
1296
|
return_type = nil
|
|
1120
|
-
if current_token && current_token[:type] == :colon
|
|
1297
|
+
if current_token && current_token[:type] == :colon && current_token[:line] == line
|
|
1121
1298
|
advance # Skip ':'
|
|
1122
1299
|
return_type = parse_type
|
|
1123
1300
|
end
|
|
@@ -1175,12 +1352,6 @@ module Vinter
|
|
|
1175
1352
|
break
|
|
1176
1353
|
end
|
|
1177
1354
|
|
|
1178
|
-
# Get parameter name
|
|
1179
|
-
if !current_token || current_token[:type] != :identifier
|
|
1180
|
-
add_error("Expected parameter name")
|
|
1181
|
-
break
|
|
1182
|
-
end
|
|
1183
|
-
|
|
1184
1355
|
param_name = advance
|
|
1185
1356
|
|
|
1186
1357
|
# Check for type annotation
|
|
@@ -1270,7 +1441,7 @@ module Vinter
|
|
|
1270
1441
|
|
|
1271
1442
|
# Parse initializer if present
|
|
1272
1443
|
initializer = nil
|
|
1273
|
-
if current_token && current_token[:type] == :operator && current_token[:value] == '='
|
|
1444
|
+
if current_token && (current_token[:type] == :operator && current_token[:value] == '=')
|
|
1274
1445
|
advance # Skip '='
|
|
1275
1446
|
initializer = parse_expression
|
|
1276
1447
|
end
|
|
@@ -1563,7 +1734,7 @@ module Vinter
|
|
|
1563
1734
|
# Special handling for map-related keywords when they appear in expressions
|
|
1564
1735
|
if ['map', 'nmap', 'imap', 'vmap', 'xmap', 'noremap', 'nnoremap', 'inoremap', 'vnoremap', 'xnoremap', 'cnoremap', 'cmap'].include?(token[:value])
|
|
1565
1736
|
if peek_token[:type] == :paren_open
|
|
1566
|
-
parse_builtin_function_call
|
|
1737
|
+
parse_builtin_function_call
|
|
1567
1738
|
else
|
|
1568
1739
|
# Treat map commands as identifiers when inside expressions
|
|
1569
1740
|
advance
|
|
@@ -1576,7 +1747,7 @@ module Vinter
|
|
|
1576
1747
|
end
|
|
1577
1748
|
elsif token[:value] == 'type' && current_token && (current_token[:type] == :paren_open || peek_token && peek_token[:type] == :paren_open)
|
|
1578
1749
|
# This is the type() function call
|
|
1579
|
-
return parse_builtin_function_call
|
|
1750
|
+
return parse_builtin_function_call
|
|
1580
1751
|
elsif token[:value] == 'function'
|
|
1581
1752
|
# Check if this is a function() call or just 'function' as property name
|
|
1582
1753
|
if peek_token && peek_token[:type] == :paren_open
|
|
@@ -1662,6 +1833,8 @@ module Vinter
|
|
|
1662
1833
|
line: line,
|
|
1663
1834
|
column: column
|
|
1664
1835
|
}
|
|
1836
|
+
elsif token[:value] == 'command'
|
|
1837
|
+
advance
|
|
1665
1838
|
else
|
|
1666
1839
|
@errors << {
|
|
1667
1840
|
message: "Unexpected keyword in expression: #{token[:value]}",
|
|
@@ -1789,7 +1962,7 @@ module Vinter
|
|
|
1789
1962
|
when :identifier
|
|
1790
1963
|
# Special handling for Vim built-in functions
|
|
1791
1964
|
if ['has', 'exists', 'empty', 'get', 'type', 'map', 'copy'].include?(token[:value])
|
|
1792
|
-
expr = parse_builtin_function_call
|
|
1965
|
+
expr = parse_builtin_function_call
|
|
1793
1966
|
else
|
|
1794
1967
|
advance
|
|
1795
1968
|
|
|
@@ -1865,7 +2038,8 @@ module Vinter
|
|
|
1865
2038
|
advance
|
|
1866
2039
|
expr = parse_expression
|
|
1867
2040
|
when :interpolated_string
|
|
1868
|
-
advance
|
|
2041
|
+
token = advance
|
|
2042
|
+
value = token[:value]
|
|
1869
2043
|
expr = {
|
|
1870
2044
|
type: :interpolated_string,
|
|
1871
2045
|
value: token[:value],
|
|
@@ -1926,6 +2100,7 @@ module Vinter
|
|
|
1926
2100
|
# Check for method call with arrow ->
|
|
1927
2101
|
elsif current_token[:type] == :operator && current_token[:value] == '->'
|
|
1928
2102
|
arrow_token = advance # Skip '->'
|
|
2103
|
+
advance if current_token[:type] == :line_continuation
|
|
1929
2104
|
|
|
1930
2105
|
# Next token should be an identifier (method name)
|
|
1931
2106
|
if !current_token || current_token[:type] != :identifier
|
|
@@ -1990,6 +2165,12 @@ module Vinter
|
|
|
1990
2165
|
else
|
|
1991
2166
|
end_index = parse_expression
|
|
1992
2167
|
end
|
|
2168
|
+
# handles array assignment [a,b] = split(thing)
|
|
2169
|
+
elsif current_token[:type] == :comma
|
|
2170
|
+
while current_token[:type] == :comma
|
|
2171
|
+
advance
|
|
2172
|
+
parse_expression
|
|
2173
|
+
end
|
|
1993
2174
|
end
|
|
1994
2175
|
|
|
1995
2176
|
expect(:bracket_close) # Skip ']'
|
|
@@ -2052,76 +2233,19 @@ module Vinter
|
|
|
2052
2233
|
return expr
|
|
2053
2234
|
end
|
|
2054
2235
|
|
|
2055
|
-
def parse_builtin_function_call
|
|
2056
|
-
#
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
if current_token && current_token[:type] == :paren_open
|
|
2061
|
-
advance # Skip '('
|
|
2062
|
-
|
|
2063
|
-
# Parse arguments
|
|
2064
|
-
args = []
|
|
2065
|
-
|
|
2066
|
-
# Functions that take string expressions as code
|
|
2067
|
-
special_functions = ['map', 'reduce', 'sort', 'call', 'eval', 'execute', 'exec']
|
|
2068
|
-
is_special_function = special_functions.include?(name)
|
|
2069
|
-
|
|
2070
|
-
# Parse until closing parenthesis
|
|
2071
|
-
while @position < @tokens.length && current_token && current_token[:type] != :paren_close
|
|
2072
|
-
# Skip whitespace or comments
|
|
2073
|
-
if current_token[:type] == :whitespace || current_token[:type] == :comment
|
|
2074
|
-
advance
|
|
2075
|
-
next
|
|
2076
|
-
end
|
|
2077
|
-
|
|
2078
|
-
# Special handling for string arguments that contain code
|
|
2079
|
-
if is_special_function &&
|
|
2080
|
-
current_token && current_token[:type] == :string
|
|
2081
|
-
string_token = parse_expression
|
|
2082
|
-
args << {
|
|
2083
|
-
type: :literal,
|
|
2084
|
-
value: string_token[:value],
|
|
2085
|
-
token_type: :string,
|
|
2086
|
-
line: string_token[:line],
|
|
2087
|
-
column: string_token[:column]
|
|
2088
|
-
}
|
|
2089
|
-
else
|
|
2090
|
-
arg = parse_expression
|
|
2091
|
-
args << arg if arg
|
|
2092
|
-
end
|
|
2093
|
-
|
|
2094
|
-
if current_token && current_token[:type] == :comma
|
|
2095
|
-
advance
|
|
2096
|
-
elsif current_token && current_token[:type] != :paren_close
|
|
2097
|
-
@errors << {
|
|
2098
|
-
message: "Expected comma or closing parenthesis in #{name} function",
|
|
2099
|
-
position: @position,
|
|
2100
|
-
line: current_token[:line],
|
|
2101
|
-
column: current_token[:column]
|
|
2102
|
-
}
|
|
2103
|
-
break
|
|
2104
|
-
end
|
|
2105
|
-
end
|
|
2236
|
+
def parse_builtin_function_call
|
|
2237
|
+
token = advance # Consume the `builtin_funcs` token
|
|
2238
|
+
line = token[:line]
|
|
2239
|
+
column = token[:column]
|
|
2240
|
+
name = token[:value]
|
|
2106
2241
|
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
message: "Expected ')' to close #{name} function call",
|
|
2113
|
-
position: @position,
|
|
2114
|
-
line: current_token ? current_token[:line] : 0,
|
|
2115
|
-
column: current_token ? current_token[:column] : 0
|
|
2116
|
-
}
|
|
2117
|
-
end
|
|
2118
|
-
else
|
|
2119
|
-
# Handle legacy Vim script where parentheses might be omitted
|
|
2120
|
-
# Just parse one expression as the argument
|
|
2121
|
-
args = [parse_expression]
|
|
2242
|
+
# Collect arguments (if any) until the end of the line
|
|
2243
|
+
args = []
|
|
2244
|
+
while current_token && current_token[:type] != :comment && current_token[:value] != "\n"
|
|
2245
|
+
args << current_token[:value]
|
|
2246
|
+
advance
|
|
2122
2247
|
end
|
|
2123
2248
|
|
|
2124
|
-
# Return function call node
|
|
2125
2249
|
{
|
|
2126
2250
|
type: :builtin_function_call,
|
|
2127
2251
|
name: name,
|
|
@@ -2680,42 +2804,30 @@ module Vinter
|
|
|
2680
2804
|
|
|
2681
2805
|
args = []
|
|
2682
2806
|
|
|
2683
|
-
# Special handling for Vim functions that take code strings as arguments
|
|
2684
|
-
special_functions = ['map', 'reduce', 'sort', 'call', 'eval', 'execute', 'exec']
|
|
2685
|
-
is_special_function = special_functions.include?(name)
|
|
2686
|
-
|
|
2687
2807
|
# Parse arguments until we find a closing parenthesis
|
|
2688
2808
|
while @position < @tokens.length && current_token && current_token[:type] != :paren_close
|
|
2689
|
-
# Skip
|
|
2690
|
-
if current_token
|
|
2809
|
+
# Skip line continuations
|
|
2810
|
+
if current_token[:type] == :line_continuation
|
|
2691
2811
|
advance
|
|
2692
2812
|
next
|
|
2693
2813
|
end
|
|
2694
|
-
if is_special_function && current_token && current_token[:type] == :string
|
|
2695
|
-
# For functions like map(), filter(), directly add the string as an argument
|
|
2696
|
-
string_token = parse_string
|
|
2697
|
-
args << {
|
|
2698
|
-
type: :literal,
|
|
2699
|
-
value: string_token[:value],
|
|
2700
|
-
token_type: :string,
|
|
2701
|
-
line: string_token[:line],
|
|
2702
|
-
column: string_token[:column]
|
|
2703
|
-
}
|
|
2704
|
-
else
|
|
2705
|
-
# Parse the argument
|
|
2706
|
-
arg = parse_expression
|
|
2707
|
-
args << arg if arg
|
|
2708
|
-
end
|
|
2709
2814
|
|
|
2710
|
-
#
|
|
2815
|
+
# Parse the argument
|
|
2816
|
+
arg = parse_expression
|
|
2817
|
+
args << arg if arg
|
|
2818
|
+
|
|
2819
|
+
# Break if we hit the closing parenthesis
|
|
2711
2820
|
if current_token && current_token[:type] == :paren_close
|
|
2712
2821
|
break
|
|
2713
2822
|
end
|
|
2823
|
+
|
|
2714
2824
|
# If we have a comma, advance past it and continue
|
|
2715
2825
|
if current_token && current_token[:type] == :comma
|
|
2716
2826
|
advance
|
|
2827
|
+
elsif current_token[:type] == :line_continuation
|
|
2828
|
+
advance
|
|
2717
2829
|
# If we don't have a comma and we're not at the end, it's an error
|
|
2718
|
-
elsif current_token && current_token[:type] != :paren_close
|
|
2830
|
+
elsif current_token && current_token[:type] != :paren_close
|
|
2719
2831
|
@errors << {
|
|
2720
2832
|
message: "Expected comma or closing parenthesis after argument",
|
|
2721
2833
|
position: @position,
|
|
@@ -2966,7 +3078,7 @@ module Vinter
|
|
|
2966
3078
|
advance # Skip the '='
|
|
2967
3079
|
|
|
2968
3080
|
# Get the value (number or identifier)
|
|
2969
|
-
if current_token && (current_token[:type] == :number || current_token[:type] == :identifier)
|
|
3081
|
+
if current_token && (current_token[:type] == :number || current_token[:type] == :identifier) || current_token[:type] == :operator
|
|
2970
3082
|
attribute += "=#{current_token[:value]}"
|
|
2971
3083
|
attributes << attribute
|
|
2972
3084
|
advance
|
|
@@ -2976,6 +3088,7 @@ module Vinter
|
|
|
2976
3088
|
|
|
2977
3089
|
# Parse the command name
|
|
2978
3090
|
command_name = nil
|
|
3091
|
+
advance if current_token[:type] == :line_continuation
|
|
2979
3092
|
if current_token && current_token[:type] == :identifier
|
|
2980
3093
|
command_name = current_token[:value]
|
|
2981
3094
|
advance
|
data/lib/vinter.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vinter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dan Bradbury
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: A linter for the Vim9 script language, helping to identify issues and
|
|
14
13
|
enforce best practices
|
|
@@ -31,7 +30,6 @@ homepage: https://github.com/DanBradbury/vinter
|
|
|
31
30
|
licenses:
|
|
32
31
|
- MIT
|
|
33
32
|
metadata: {}
|
|
34
|
-
post_install_message:
|
|
35
33
|
rdoc_options: []
|
|
36
34
|
require_paths:
|
|
37
35
|
- lib
|
|
@@ -46,8 +44,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
46
44
|
- !ruby/object:Gem::Version
|
|
47
45
|
version: '0'
|
|
48
46
|
requirements: []
|
|
49
|
-
rubygems_version: 3.
|
|
50
|
-
signing_key:
|
|
47
|
+
rubygems_version: 3.6.9
|
|
51
48
|
specification_version: 4
|
|
52
49
|
summary: A linter for leagacy + Vim9 script
|
|
53
50
|
test_files: []
|