Skip to content

Commit 7fbd8a0

Browse files
committed
invoke callback for vim9script, vim9cmd, def
1 parent 4495d39 commit 7fbd8a0

File tree

6 files changed

+217
-20
lines changed

6 files changed

+217
-20
lines changed

README.mkd

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This parser provide same feature for following languages.
1414

1515
* Vim script
1616
* Python
17-
* JavaScript
17+
* JavaScript
1818

1919
## Example
2020

@@ -37,6 +37,52 @@ This above code output following.
3737
(let = s:message (printf "hello %d" (+ 1 (* 2 3))))
3838
```
3939

40+
## Parsing Vim9 Script
41+
42+
VimL parser can detect `vim9script`, `vim9cmd`, and `def` commands via callbacks. This allows delegating Vim9 code parsing to a dedicated Vim9 parser.
43+
44+
### Using with vim-vim9parser
45+
46+
```javascript
47+
const { VimLParser, StringReader } = require('vimlparser.js');
48+
const { Vim9Parser } = require('vim9parser.js');
49+
50+
const code = [
51+
'vim9script',
52+
'var x: number = 10',
53+
'def Add(a: number, b: number): number',
54+
' return a + b',
55+
'enddef'
56+
].join('\n');
57+
58+
const reader = new StringReader(code);
59+
const vim9parser = new Vim9Parser();
60+
61+
const callbacks = {
62+
vim9script_callback: (node, content) => {
63+
// vim9script found - remaining code is Vim9
64+
console.log('Vim9 script detected at line', node.pos.lnum);
65+
// Can pass to vim9parser for detailed analysis
66+
},
67+
68+
def_callback: (node, content) => {
69+
// def found - parse the function definition
70+
console.log('Function definition at line', node.pos.lnum);
71+
const vim9ast = vim9parser.parse(content);
72+
},
73+
74+
vim9cmd_callback: (node, content) => {
75+
// vim9cmd found - parse the command
76+
console.log('Vim9 command at line', node.pos.lnum);
77+
}
78+
};
79+
80+
const parser = new VimLParser(false, callbacks);
81+
const ast = parser.parse(reader);
82+
```
83+
84+
This approach enables language servers and tools to support Vim9 syntax while maintaining full VimL compatibility.
85+
4086
## About project name
4187

4288
We know a name "VimL" is not the common short form of "Vim scripting language".

autoload/vimlparser.vim

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,12 @@ function! s:VimLParser.__init__(...) abort
446446
let self.neovim = 0
447447
endif
448448

449+
if len(a:000) > 1 && type(a:000[1]) ==# type({})
450+
let self.callbacks = a:000[1]
451+
else
452+
let self.callbacks = {}
453+
endif
454+
449455
let self.find_command_cache = {}
450456
endfunction
451457

@@ -472,6 +478,12 @@ function! s:VimLParser.add_node(node) abort
472478
call add(self.context[0].body, a:node)
473479
endfunction
474480

481+
function! s:VimLParser.invoke_callback(name, ...) abort
482+
if has_key(self.callbacks, a:name) && type(self.callbacks[a:name]) ==# type(function('tr'))
483+
call call(self.callbacks[a:name], a:000)
484+
endif
485+
endfunction
486+
475487
function! s:VimLParser.check_missing_endfunction(ends, pos) abort
476488
if self.context[0].type ==# s:NODE_FUNCTION
477489
throw s:Err(printf('E126: Missing :endfunction: %s', a:ends), a:pos)
@@ -940,6 +952,8 @@ function! s:VimLParser.find_command() abort
940952
let name = c
941953
elseif self.reader.peekn(2) ==# 'py'
942954
let name = self.reader.read_alnum()
955+
elseif self.reader.peekn(4) ==# 'vim9'
956+
let name = self.reader.read_alnum()
943957
else
944958
let pos = self.reader.tell()
945959
let name = self.reader.read_alpha()
@@ -959,13 +973,22 @@ function! s:VimLParser.find_command() abort
959973

960974
let cmd = s:NIL
961975

962-
for x in self.builtin_commands
963-
if stridx(x.name, name) ==# 0 && len(name) >= x.minlen
964-
unlet cmd
965-
let cmd = x
966-
break
967-
endif
968-
endfor
976+
" Special case for vim9script and vim9cmd to avoid matching vimgrep
977+
if name !=# 'vim9script' && name !=# 'vim9cmd'
978+
for x in self.builtin_commands
979+
if stridx(x.name, name) ==# 0 && len(name) >= x.minlen
980+
unlet cmd
981+
let cmd = x
982+
break
983+
endif
984+
endfor
985+
elseif name ==# 'vim9script'
986+
unlet cmd
987+
let cmd = {'name': 'vim9script', 'minlen': 5, 'flags': 'WORD1|CMDWIN|LOCK_OK', 'parser': 'parse_cmd_common'}
988+
elseif name ==# 'vim9cmd'
989+
unlet cmd
990+
let cmd = {'name': 'vim9cmd', 'minlen': 4, 'flags': 'NEEDARG|EXTRA|NOTRLCOM|CMDWIN|LOCK_OK', 'parser': 'parse_cmd_common'}
991+
endif
969992

970993
if self.neovim
971994
for x in self.neovim_additional_commands
@@ -1137,6 +1160,16 @@ function! s:VimLParser.parse_cmd_common() abort
11371160
let node.pos = self.ea.cmdpos
11381161
let node.ea = self.ea
11391162
let node.str = self.reader.getstr(self.ea.linepos, end)
1163+
1164+
" Invoke callback for vim9 script commands
1165+
if self.ea.cmd.name ==# 'vim9script'
1166+
call self.invoke_callback('vim9script_callback', node, node.str)
1167+
elseif self.ea.cmd.name ==# 'vim9cmd'
1168+
call self.invoke_callback('vim9cmd_callback', node, node.str)
1169+
elseif self.ea.cmd.name ==# 'def'
1170+
call self.invoke_callback('def_callback', node, node.str)
1171+
endif
1172+
11401173
call self.add_node(node)
11411174
endfunction
11421175

js/vimlfunc.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,18 @@ function viml_stridx(a, b) {
224224
return a.indexOf(b);
225225
}
226226

227+
function viml_type(obj) {
228+
if (obj === null || obj === undefined) return 0;
229+
if (typeof obj === 'number') return 0;
230+
if (typeof obj === 'string') return 1;
231+
if (Array.isArray(obj)) return 3;
232+
if (typeof obj === 'object') return 4;
233+
if (typeof obj === 'function') return 2;
234+
return 0;
235+
}
236+
237+
function viml_function(name) {
238+
// Return a dummy function for type comparison
239+
return function() {};
240+
}
241+

js/vimlparser.js

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,21 @@ function viml_stridx(a, b) {
224224
return a.indexOf(b);
225225
}
226226

227+
function viml_type(obj) {
228+
if (obj === null || obj === undefined) return 0;
229+
if (typeof obj === 'number') return 0;
230+
if (typeof obj === 'string') return 1;
231+
if (Array.isArray(obj)) return 3;
232+
if (typeof obj === 'object') return 4;
233+
if (typeof obj === 'function') return 2;
234+
return 0;
235+
}
236+
237+
function viml_function(name) {
238+
// Return a dummy function for type comparison
239+
return function() {};
240+
}
241+
227242
var NIL = [];
228243
var TRUE = 1;
229244
var FALSE = 0;
@@ -618,6 +633,12 @@ VimLParser.prototype.__init__ = function() {
618633
else {
619634
this.neovim = 0;
620635
}
636+
if (viml_len(a000) > 1 && viml_type(a000[1]) == viml_type({})) {
637+
this.callbacks = a000[1];
638+
}
639+
else {
640+
this.callbacks = {};
641+
}
621642
this.find_command_cache = {};
622643
}
623644

@@ -646,6 +667,13 @@ VimLParser.prototype.add_node = function(node) {
646667
viml_add(this.context[0].body, node);
647668
}
648669

670+
VimLParser.prototype.invoke_callback = function(name) {
671+
var a000 = Array.prototype.slice.call(arguments, 1);
672+
if (viml_has_key(this.callbacks, name) && viml_type(this.callbacks[name]) == viml_type(viml_function("tr"))) {
673+
viml_call(this.callbacks[name], a000);
674+
}
675+
}
676+
649677
VimLParser.prototype.check_missing_endfunction = function(ends, pos) {
650678
if (this.context[0].type == NODE_FUNCTION) {
651679
throw Err(viml_printf("E126: Missing :endfunction: %s", ends), pos);
@@ -1207,6 +1235,9 @@ VimLParser.prototype.find_command = function() {
12071235
else if (this.reader.peekn(2) == "py") {
12081236
var name = this.reader.read_alnum();
12091237
}
1238+
else if (this.reader.peekn(4) == "vim9") {
1239+
var name = this.reader.read_alnum();
1240+
}
12101241
else {
12111242
var pos = this.reader.tell();
12121243
var name = this.reader.read_alpha();
@@ -1222,15 +1253,26 @@ VimLParser.prototype.find_command = function() {
12221253
return this.find_command_cache[name];
12231254
}
12241255
var cmd = NIL;
1225-
var __c4 = this.builtin_commands;
1226-
for (var __i4 = 0; __i4 < __c4.length; ++__i4) {
1227-
var x = __c4[__i4];
1228-
if (viml_stridx(x.name, name) == 0 && viml_len(name) >= x.minlen) {
1229-
delete cmd;
1230-
var cmd = x;
1231-
break;
1256+
// Special case for vim9script and vim9cmd to avoid matching vimgrep
1257+
if (name != "vim9script" && name != "vim9cmd") {
1258+
var __c4 = this.builtin_commands;
1259+
for (var __i4 = 0; __i4 < __c4.length; ++__i4) {
1260+
var x = __c4[__i4];
1261+
if (viml_stridx(x.name, name) == 0 && viml_len(name) >= x.minlen) {
1262+
delete cmd;
1263+
var cmd = x;
1264+
break;
1265+
}
12321266
}
12331267
}
1268+
else if (name == "vim9script") {
1269+
delete cmd;
1270+
var cmd = {"name":"vim9script", "minlen":5, "flags":"WORD1|CMDWIN|LOCK_OK", "parser":"parse_cmd_common"};
1271+
}
1272+
else if (name == "vim9cmd") {
1273+
delete cmd;
1274+
var cmd = {"name":"vim9cmd", "minlen":4, "flags":"NEEDARG|EXTRA|NOTRLCOM|CMDWIN|LOCK_OK", "parser":"parse_cmd_common"};
1275+
}
12341276
if (this.neovim) {
12351277
var __c5 = this.neovim_additional_commands;
12361278
for (var __i5 = 0; __i5 < __c5.length; ++__i5) {
@@ -1419,6 +1461,16 @@ VimLParser.prototype.parse_cmd_common = function() {
14191461
node.pos = this.ea.cmdpos;
14201462
node.ea = this.ea;
14211463
node.str = this.reader.getstr(this.ea.linepos, end);
1464+
// Invoke callback for vim9 script commands
1465+
if (this.ea.cmd.name == "vim9script") {
1466+
this.invoke_callback("vim9script_callback", node, node.str);
1467+
}
1468+
else if (this.ea.cmd.name == "vim9cmd") {
1469+
this.invoke_callback("vim9cmd_callback", node, node.str);
1470+
}
1471+
else if (this.ea.cmd.name == "def") {
1472+
this.invoke_callback("def_callback", node, node.str);
1473+
}
14221474
this.add_node(node);
14231475
}
14241476

py/vimlfunc.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,16 @@ def viml_has_key(obj, key):
209209

210210
def viml_stridx(a, b):
211211
return a.find(b)
212+
213+
def viml_type(obj):
214+
if obj is None: return 0
215+
if isinstance(obj, (int, float)): return 0
216+
if isinstance(obj, str): return 1
217+
if isinstance(obj, list): return 3
218+
if isinstance(obj, dict): return 4
219+
if callable(obj): return 2
220+
return 0
221+
222+
def viml_function(name):
223+
# Return a dummy function for type comparison
224+
return lambda: None

py/vimlparser.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,19 @@ def viml_has_key(obj, key):
210210
def viml_stridx(a, b):
211211
return a.find(b)
212212

213+
def viml_type(obj):
214+
if obj is None: return 0
215+
if isinstance(obj, (int, float)): return 0
216+
if isinstance(obj, str): return 1
217+
if isinstance(obj, list): return 3
218+
if isinstance(obj, dict): return 4
219+
if callable(obj): return 2
220+
return 0
221+
222+
def viml_function(name):
223+
# Return a dummy function for type comparison
224+
return lambda: None
225+
213226

214227
NIL = []
215228
TRUE = 1
@@ -605,6 +618,10 @@ def __init__(self, *a000):
605618
self.neovim = a000[0]
606619
else:
607620
self.neovim = 0
621+
if viml_len(a000) > 1 and viml_type(a000[1]) == viml_type(AttributeDict({})):
622+
self.callbacks = a000[1]
623+
else:
624+
self.callbacks = AttributeDict({})
608625
self.find_command_cache = AttributeDict({})
609626

610627
def push_context(self, node):
@@ -624,6 +641,10 @@ def find_context(self, type):
624641
def add_node(self, node):
625642
viml_add(self.context[0].body, node)
626643

644+
def invoke_callback(self, name, *a000):
645+
if viml_has_key(self.callbacks, name) and viml_type(self.callbacks[name]) == viml_type(viml_function("tr")):
646+
viml_call(self.callbacks[name], a000)
647+
627648
def check_missing_endfunction(self, ends, pos):
628649
if self.context[0].type == NODE_FUNCTION:
629650
raise VimLParserException(Err(viml_printf("E126: Missing :endfunction: %s", ends), pos))
@@ -1028,6 +1049,8 @@ def find_command(self):
10281049
name = c
10291050
elif self.reader.peekn(2) == "py":
10301051
name = self.reader.read_alnum()
1052+
elif self.reader.peekn(4) == "vim9":
1053+
name = self.reader.read_alnum()
10311054
else:
10321055
pos = self.reader.tell()
10331056
name = self.reader.read_alpha()
@@ -1039,11 +1062,19 @@ def find_command(self):
10391062
if viml_has_key(self.find_command_cache, name):
10401063
return self.find_command_cache[name]
10411064
cmd = NIL
1042-
for x in self.builtin_commands:
1043-
if viml_stridx(x.name, name) == 0 and viml_len(name) >= x.minlen:
1044-
del cmd
1045-
cmd = x
1046-
break
1065+
# Special case for vim9script and vim9cmd to avoid matching vimgrep
1066+
if name != "vim9script" and name != "vim9cmd":
1067+
for x in self.builtin_commands:
1068+
if viml_stridx(x.name, name) == 0 and viml_len(name) >= x.minlen:
1069+
del cmd
1070+
cmd = x
1071+
break
1072+
elif name == "vim9script":
1073+
del cmd
1074+
cmd = AttributeDict({"name": "vim9script", "minlen": 5, "flags": "WORD1|CMDWIN|LOCK_OK", "parser": "parse_cmd_common"})
1075+
elif name == "vim9cmd":
1076+
del cmd
1077+
cmd = AttributeDict({"name": "vim9cmd", "minlen": 4, "flags": "NEEDARG|EXTRA|NOTRLCOM|CMDWIN|LOCK_OK", "parser": "parse_cmd_common"})
10471078
if self.neovim:
10481079
for x in self.neovim_additional_commands:
10491080
if viml_stridx(x.name, name) == 0 and viml_len(name) >= x.minlen:
@@ -1182,6 +1213,13 @@ def parse_cmd_common(self):
11821213
node.pos = self.ea.cmdpos
11831214
node.ea = self.ea
11841215
node.str = self.reader.getstr(self.ea.linepos, end)
1216+
# Invoke callback for vim9 script commands
1217+
if self.ea.cmd.name == "vim9script":
1218+
self.invoke_callback("vim9script_callback", node, node.str)
1219+
elif self.ea.cmd.name == "vim9cmd":
1220+
self.invoke_callback("vim9cmd_callback", node, node.str)
1221+
elif self.ea.cmd.name == "def":
1222+
self.invoke_callback("def_callback", node, node.str)
11851223
self.add_node(node)
11861224

11871225
def separate_nextcmd(self):

0 commit comments

Comments
 (0)