vinter 0.4.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6f99b1ca16b7bbca35229a549ceed21f3cdf9b8bc86a3fedff9b978b18717de
4
- data.tar.gz: 2aa4e1df5ae4fc08e78c06cf2107c7192099326b073bf45e45a9532b3fb5d0c3
3
+ metadata.gz: 617e3da0fed64bcf718e3c1e3bd82a66e910e4a2099a27942a2390c93421779b
4
+ data.tar.gz: a54306cbf4b2e25921c3518985acb0d648096a66e1f24a86e59ac3aa53d56810
5
5
  SHA512:
6
- metadata.gz: d739b01780f6ce12654b09b701f343e850bd2eb750e705ca608f793e7e9cfef26b226927157ff595dcbb3f62ddbbd4f530439074ef9fdbc9a2cfd075dd78a3da
7
- data.tar.gz: 7ec3181349383a4b9667aab046f56099d25f830a07aaa6a6135b8640a25d59e85c2191b3ffbecd263afe703f1f17a986f40f769e458bab532e70f837f9c9a029
6
+ metadata.gz: 614e2e7ef3f02f16a8888069c40244761a7c3afb142b84aafb410c39693e25d4e3d9afa9e4a8bafea24512dc9b339364bf8e0ace1a665d424d0f638428d4b7f9
7
+ data.tar.gz: 8ace7e570a66a113e66c411d5725f1a72fc3596faffcda899e3590b37b7893aaff21aeefc4d1e1e60fa2cc20f830377417c493b88af4ab90f126f72b843ee521
@@ -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/lexer.rb CHANGED
@@ -2,7 +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|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|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/,
6
9
  # Identifiers can include # and special characters
7
10
  identifier: /\b[a-zA-Z_][a-zA-Z0-9_#]*\b/,
8
11
  # Single-character operators
@@ -26,7 +29,7 @@ module Vinter
26
29
  comma: /,/,
27
30
  backslash: /\\/,
28
31
  question_mark: /\?/,
29
- command_separator: /\|/,
32
+ command_separator: /\|/
30
33
  }
31
34
 
32
35
  CONTINUATION_OPERATORS = %w(. .. + - * / = == ==# ==? != > < >= <= && || ? : -> =>)
@@ -41,21 +44,21 @@ module Vinter
41
44
  def should_parse_as_regex
42
45
  # Look at recent tokens to determine if we're in a regex context
43
46
  recent_tokens = @tokens.last(3)
44
-
47
+
45
48
  # Check for contexts where regex is expected
46
- return true if recent_tokens.any? { |t|
47
- t && t[:type] == :keyword && ['syntax'].include?(t[:value])
49
+ return true if recent_tokens.any? { |t|
50
+ t && t[:type] == :keyword && ['syntax'].include?(t[:value])
48
51
  }
49
-
52
+
50
53
  return true if recent_tokens.any? { |t|
51
54
  t && t[:type] == :identifier && ['match', 'region', 'keyword'].include?(t[:value])
52
55
  }
53
-
56
+
54
57
  # Check for comparison operators that often use regex
55
58
  return true if recent_tokens.any? { |t|
56
59
  t && t[:type] == :operator && ['=~', '!~', '=~#', '!~#', '=~?', '!~?'].include?(t[:value])
57
60
  }
58
-
61
+
59
62
  false
60
63
  end
61
64
 
@@ -85,7 +88,7 @@ module Vinter
85
88
  end
86
89
  line_start = temp_pos < 0 || @input[temp_pos] == "\n"
87
90
  end
88
-
91
+
89
92
  # If we're at the start of a line and it begins with a quote
90
93
  if line_start && chunk.start_with?('"')
91
94
  # Find the end of the line
@@ -103,44 +106,125 @@ module Vinter
103
106
  @column += comment_text.length
104
107
  next
105
108
  end
109
+
110
+ # --- Interpolated String Handling ---
111
+ if chunk.start_with?("$'")
112
+ i = 2
113
+ string_value = "$'"
114
+ brace_depth = 0
115
+ escaped = false
116
+
117
+ while i < chunk.length
118
+ char = chunk[i]
119
+ string_value += char
120
+
121
+ if char == '\\' && !escaped
122
+ escaped = true
123
+ elsif char == "'" && !escaped && brace_depth == 0
124
+ # End of interpolated string
125
+ i += 1
126
+ break
127
+ elsif char == '{' && !escaped
128
+ brace_depth += 1
129
+ elsif char == '}' && !escaped && brace_depth > 0
130
+ brace_depth -= 1
131
+ elsif escaped
132
+ escaped = false
133
+ end
134
+
135
+ i += 1
136
+ end
137
+
138
+ @tokens << {
139
+ type: :interpolated_string,
140
+ value: string_value,
141
+ line: @line_num,
142
+ column: @column
143
+ }
144
+ @column += string_value.length
145
+ @position += string_value.length
146
+ @line_num += string_value.count("\n")
147
+ next
148
+ end
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
+
106
190
  # Handle string literals manually
107
191
  if chunk.start_with?("'") || chunk.start_with?('"')
108
192
  quote = chunk[0]
109
193
  i = 1
110
- escaped = false
111
194
  string_value = quote
112
195
 
113
196
  # Keep going until we find an unescaped closing quote
114
197
  while i < chunk.length
115
198
  char = chunk[i]
199
+ next_char = chunk[i + 1] if i + 1 < chunk.length
200
+
116
201
  string_value += char
117
202
 
118
- if char == '\\' && !escaped
119
- escaped = true
120
- elsif (char == "\n" or char == quote) && !escaped
121
- # Found closing quote
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
209
+ i += 1
122
210
  break
123
- elsif escaped
124
- escaped = false
125
211
  end
126
212
 
127
213
  i += 1
128
214
  end
129
215
 
130
216
  # Add the string token if we found a closing quote
131
- if i < chunk.length || (i == chunk.length && chunk[-1] == quote)
132
- @tokens << {
133
- type: :string,
134
- value: string_value,
135
- line: @line_num,
136
- column: @column
137
- }
217
+ @tokens << {
218
+ type: :string,
219
+ value: string_value,
220
+ line: @line_num,
221
+ column: @column
222
+ }
138
223
 
139
- @column += string_value.length
140
- @position += string_value.length
141
- @line_num += 1 if string_value.include?("\n")
142
- next
143
- end
224
+ @column += string_value.length
225
+ @position += string_value.length
226
+ @line_num += string_value.count("\n")
227
+ next
144
228
  end
145
229
 
146
230
  # Add special handling for command options in the tokenize method
@@ -186,7 +270,7 @@ module Vinter
186
270
  end
187
271
 
188
272
  # 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/)
273
+ 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
274
  @tokens << {
191
275
  type: :keyword,
192
276
  value: match[0],
@@ -210,7 +294,7 @@ module Vinter
210
294
  @position += match[0].length
211
295
  next
212
296
  end
213
-
297
+
214
298
  # Handle Vim option variables with & prefix
215
299
  if match = chunk.match(/\A&[a-zA-Z_][a-zA-Z0-9_]*/)
216
300
  @tokens << {
@@ -330,7 +414,7 @@ module Vinter
330
414
  end
331
415
 
332
416
  # Add support for standalone namespace prefixes (like g:)
333
- if match = chunk.match(/\A([sgbwtal]):/)
417
+ if match = chunk.match(/\A([gbwt]):/)
334
418
  @tokens << {
335
419
  type: :namespace_prefix,
336
420
  value: match[0],
@@ -385,21 +469,21 @@ module Vinter
385
469
  if chunk.start_with?('/') && should_parse_as_regex
386
470
  i = 1
387
471
  regex_value = '/'
388
-
472
+
389
473
  # Keep going until we find the closing slash
390
474
  while i < chunk.length
391
475
  char = chunk[i]
392
476
  regex_value += char
393
-
477
+
394
478
  if char == '/' && (i == 1 || chunk[i-1] != '\\')
395
479
  # Found closing slash
396
480
  i += 1
397
481
  break
398
482
  end
399
-
483
+
400
484
  i += 1
401
485
  end
402
-
486
+
403
487
  # Add the regex token if we found a closing slash
404
488
  if regex_value.end_with?('/')
405
489
  @tokens << {
@@ -442,8 +526,7 @@ module Vinter
442
526
  end
443
527
 
444
528
  # In the tokenize method, add special handling for common mapping components
445
- if chunk.start_with?('<CR>', '<Esc>', '<Tab>', '<Space>', '<C-') ||
446
- (chunk =~ /\A<[A-Za-z0-9\-_]+>/)
529
+ if chunk.start_with?('<CR>', '<Esc>', '<Tab>', '<Space>', '<C-')
447
530
  # Extract the special key notation
448
531
  match = chunk.match(/\A(<[^>]+>)/)
449
532
  if match
@@ -486,13 +569,24 @@ module Vinter
486
569
  @column += 1
487
570
  @position += 1
488
571
 
489
- # If followed by a newline, advance to next line
572
+ # If followed by a newline, advance to the next line
490
573
  if @position < @input.length && @input[@position] == "\n"
491
574
  @line_num += 1
492
575
  @column = 1
493
576
  @position += 1
494
577
  end
495
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
+
496
590
  next
497
591
  end
498
592