tree-sitter-batch 0.7.2 → 0.10.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.
package/README.md CHANGED
@@ -6,10 +6,11 @@ Parses `.bat` and `.cmd` files into a concrete syntax tree for syntax highlighti
6
6
 
7
7
  ## Features
8
8
 
9
- - **Control flow** — `IF`/`ELSE` (EXIST, DEFINED, ERRORLEVEL, comparison with NOT), `FOR` (/D /R /L /F), `GOTO`, `CALL`
10
- - **Variables** — `SET` (plain, `/A` arithmetic, `/P` prompt), `%VAR%`, `!VAR!`, `%%i`, `%~dp0`, `%VAR:old=new%`
11
- - **Operators** — pipes `|`, redirects `>` `>>` `2>` `2>&1`, conditional `&&` `||`
12
- - **Structure** — labels `:name`, comments `REM` `::`, parenthesized blocks, `@ECHO OFF`
9
+ - **Control flow** — `IF`/`ELSE` (EXIST, DEFINED, ERRORLEVEL, comparison with NOT), `FOR` (/D /R /L /F), `GOTO`, `CALL`, `EXIT /B`
10
+ - **Variables** — `SET` (plain, `/A` arithmetic, `/P` prompt), `%VAR%`, `!VAR!`, `%%i`, `%~dp0`, `%VAR:old=new%`, escaped forms `%%VAR%%` `%%%%i`
11
+ - **Echo** — free-form text with literal `(` `)` `!` `%`, inline strings, and variable references
12
+ - **Operators** — pipes `|`, redirects `>` `>>` `2>` `2>&1`, conditional `&&` `||`, separator `&`
13
+ - **Structure** — labels `:name`, comments `REM` `::`, parenthesized blocks, `@ECHO OFF`, macro invocations
13
14
  - **Scope** — `SETLOCAL`/`ENDLOCAL` with `ENABLEDELAYEDEXPANSION`
14
15
  - **Case-insensitive** — all keywords match regardless of casing
15
16
 
@@ -51,20 +52,20 @@ Parsed tree:
51
52
  (variable_assignment
52
53
  (set_keyword) (variable_name) (assignment_value))
53
54
  (variable_assignment
54
- (set_keyword) (set_option) (variable_name) (assignment_value))
55
+ (set_keyword) (arithmetic_assignment (set_option) (arithmetic_expression)))
55
56
  (if_stmt
56
57
  (string)
57
58
  (parenthesized
58
59
  (cmd (command_name) (argument_list (argument_value)))))
59
60
  (for_stmt
60
61
  (for_variable)
61
- (for_set)
62
+ (for_set (for_set_literal))
62
63
  (parenthesized
63
64
  (cmd (command_name) (argument_list (string) (string)))))
64
65
  (if_stmt
65
66
  (variable_reference)
66
67
  (comparison_op)
67
- (integer)
68
+ (argument_value)
68
69
  (parenthesized
69
70
  (cmd (command_name) (argument_list (argument_value) (argument_value))))
70
71
  (else_clause
package/grammar.js CHANGED
@@ -1,11 +1,36 @@
1
1
  const ci = (word) => new RegExp(word.split('').map((c) => /[a-zA-Z]/.test(c) ? `[${c.toLowerCase()}${c.toUpperCase()}]` : c).join(''));
2
2
  const kw = (word) => token(prec(10, ci(word)));
3
- const operand = ($) => [$.cond_exec, $.pipe_stmt, $.redirect_stmt, $.call_stmt, $.cmd, $.parenthesized];
3
+ const varRefChoice = () => choice(
4
+ seq('%%', /[$@a-zA-Z_][$@a-zA-Z0-9_.#()\[\]]*/, '%%'),
5
+ seq('%', /[$@a-zA-Z_][$@a-zA-Z0-9_.#()\[\]]*/, '%'),
6
+ seq('%~', /[a-zA-Z]*/, /[0-9]/),
7
+ seq('%', /[0-9]/),
8
+ '%*',
9
+ seq('%%%%%%', optional('~'), /[a-zA-Z0-9]/),
10
+ seq('%%%%', optional('~'), /[a-zA-Z0-9]/),
11
+ seq('%%', optional('~'), /[a-zA-Z]/),
12
+ seq('%%', /[0-9]/),
13
+ seq('!', /[$%a-zA-Z_][$a-zA-Z0-9_.#]*/, '!'),
14
+ seq('%', /[$@a-zA-Z_][$@a-zA-Z0-9_.#()\[\]]*/, ':', /[^%\r\n]+/, '%'),
15
+ seq('%', /[^%=\s\r\n]/, '%'),
16
+ seq('!', /[%$a-zA-Z_][%$a-zA-Z0-9_.#()\[\]]*/, /:[^!\r\n]+/, '!'),
17
+ seq('%', /[<>\/]+[@a-zA-Z_0-9.]*/, '%'),
18
+ seq('%', /\\[@a-zA-Z_0-9.]+/, '%'),
19
+ seq('%', /"[^"%\r\n]+"/, '%'),
20
+ );
21
+ const operand = ($) => [
22
+ $.cond_exec, $.pipe_stmt, $.redirect_stmt, $.call_stmt, $.cmd, $.parenthesized,
23
+ $.variable_assignment, $.goto_stmt, $.exit_stmt, $.setlocal_stmt, $.endlocal_stmt,
24
+ $.if_stmt, $.for_stmt, $.macro_invocation,
25
+ ];
4
26
 
5
27
  export default grammar({
6
28
  name: 'batch',
7
29
  extras: () => [/[ \t]/],
8
30
  word: ($) => $.command_name,
31
+ conflicts: ($) => [
32
+ [$.parenthesized, $.paren_expression],
33
+ ],
9
34
  rules: {
10
35
  program: ($) => repeat(choice(seq($._stmt, /\r?\n/), /\r?\n/)),
11
36
  _stmt: ($) => choice(
@@ -13,14 +38,15 @@ export default grammar({
13
38
  $.if_stmt, $.goto_stmt, $.call_stmt, $.exit_stmt,
14
39
  $.setlocal_stmt, $.endlocal_stmt, $.for_stmt,
15
40
  $.redirect_stmt, $.pipe_stmt, $.cond_exec, $.command_sep,
16
- $.parenthesized, $.cmd,
41
+ $.parenthesized, $.macro_invocation, $.cmd,
17
42
  ),
18
43
  echo_off: () => prec(10, seq('@', kw('echo'), choice(kw('off'), kw('on')))),
19
44
  comment: () => token(prec(10, choice(
20
45
  seq(optional('@'), /[rR][eE][mM]/, optional(seq(/[ \t]/, /[^\r\n]*/))),
21
- seq('::', /[^\r\n]*/),
46
+ seq(':', /[^$a-zA-Z_\r\n]/, /[^\r\n]*/),
47
+ seq('%#', /[^\r\n]*/, '#%'),
22
48
  ))),
23
- label: () => token(seq(':', /[a-zA-Z_][a-zA-Z0-9_-]*/)),
49
+ label: () => token(seq(':', /[$a-zA-Z_][$a-zA-Z0-9_.#-]*/, optional(seq(/[ \t]/, /[^\r\n]*/)))),
24
50
  variable_assignment: ($) => prec(8, seq(
25
51
  optional('@'), alias(kw('set'), $.set_keyword),
26
52
  choice(
@@ -29,12 +55,26 @@ export default grammar({
29
55
  seq(
30
56
  /[ \t]+/,
31
57
  choice(
32
- seq('"', alias(/[a-zA-Z_][a-zA-Z0-9_()\[\]]*/, $.variable_name), '=', optional($.quoted_assignment_value), '"'),
33
- seq(alias(/[a-zA-Z_][a-zA-Z0-9_()\[\]]*/, $.variable_name), '=', optional($.assignment_value)),
58
+ seq('"', repeat1(choice($.variable_reference, alias($._quoted_var_name_pattern, $.variable_name))), '=', optional($.quoted_assignment_value), '"', optional($.argument_list)),
59
+ seq('^"', repeat1(choice($.variable_reference, alias($._quoted_var_name_pattern, $.variable_name))), '=', optional($.caret_quoted_assignment_value), optional('^"')),
60
+ seq(choice($.variable_reference, alias($._var_name_pattern, $.variable_name)), '=', optional($.assignment_value)),
34
61
  ),
35
62
  ),
36
63
  ),
37
64
  )),
65
+ caret_quoted_assignment_value: ($) => prec.right(repeat1(choice(
66
+ $.variable_reference,
67
+ alias(/[^%!\r\n^]+/, $.assignment_literal),
68
+ alias('%', $.assignment_literal),
69
+ alias('!', $.assignment_literal),
70
+ alias(token(prec(1, '^^')), $.assignment_literal),
71
+ alias('^', $.assignment_literal),
72
+ ))),
73
+ _var_name_pattern: () => token(choice(
74
+ /[$@a-zA-Z_][$@a-zA-Z0-9_.#()\[\]]*/,
75
+ /\/[@a-zA-Z_][@a-zA-Z0-9_.#()\[\]]+/,
76
+ )),
77
+ _quoted_var_name_pattern: () => token(prec(1, choice(/[^\s="%][^="%]*/, /%%[a-zA-Z]?/, /"[^="\r\n]+"/))),
38
78
  arithmetic_assignment: ($) => seq(
39
79
  optional(/[ \t]+/), alias(ci('/a'), $.set_option),
40
80
  optional(/[ \t]+/), $.arithmetic_expression,
@@ -42,7 +82,7 @@ export default grammar({
42
82
  prompt_assignment: ($) => seq(
43
83
  optional(/[ \t]+/), alias(ci('/p'), $.set_option),
44
84
  optional(/[ \t]+/),
45
- alias(/[a-zA-Z_][a-zA-Z0-9_()\[\]]*/, $.variable_name), '=', optional($.assignment_value),
85
+ alias(/[@a-zA-Z_][@a-zA-Z0-9_()\[\]]*/, $.variable_name), '=', optional($.assignment_value),
46
86
  ),
47
87
  arithmetic_expression: () => token(choice(
48
88
  seq('"', /[^"\r\n]*/, '"'),
@@ -50,10 +90,19 @@ export default grammar({
50
90
  )),
51
91
  assignment_value: ($) => prec.right(repeat1(choice(
52
92
  $.variable_reference,
53
- alias(/[^%!\r\n]+/, $.assignment_literal),
93
+ $.assignment_paren_group,
94
+ alias(/[^%!()\r\n]+/, $.assignment_literal),
54
95
  alias('%', $.assignment_literal),
55
96
  alias('!', $.assignment_literal),
56
97
  ))),
98
+ assignment_paren_group: ($) => seq('(', repeat(choice(
99
+ $.variable_reference,
100
+ $.assignment_paren_group,
101
+ alias(/[^%!()\r\n]+/, $.assignment_literal),
102
+ /\r?\n/,
103
+ alias('%', $.assignment_literal),
104
+ alias('!', $.assignment_literal),
105
+ )), ')'),
57
106
  quoted_assignment_value: ($) => prec.right(repeat1(choice(
58
107
  $.variable_reference,
59
108
  alias(/[^%!"\r\n]+/, $.assignment_literal),
@@ -62,36 +111,58 @@ export default grammar({
62
111
  ))),
63
112
  if_stmt: ($) => prec.right(8, seq(
64
113
  optional('@'), kw('if'),
114
+ optional(alias(token(prec(2, ci('/i'))), $.if_option)),
65
115
  optional(kw('not')),
66
116
  choice(
67
- seq(kw('exist'), choice($.string, $.variable_reference)),
68
- seq(kw('defined'), /[a-zA-Z_][a-zA-Z0-9_]*/),
117
+ seq(kw('exist'), $._if_operand),
118
+ seq(kw('defined'), choice(/[$a-zA-Z_][$a-zA-Z0-9_.]*/, $._if_operand)),
69
119
  seq(kw('errorlevel'), $.integer),
70
120
  seq(
71
- choice($.string, $.variable_reference, $.integer),
121
+ $._if_operand,
72
122
  $.comparison_op,
73
- choice($.string, $.variable_reference, $.integer),
123
+ $._if_operand,
74
124
  ),
75
125
  ),
76
126
  choice(
77
127
  // Parenthesized form: supports else clause
78
128
  seq($.parenthesized, optional($.else_clause)),
79
129
  // Inline command form: no else (ambiguous)
80
- $.cmd,
130
+ $._body_stmt,
81
131
  ),
82
132
  )),
83
133
  else_clause: ($) => prec.right(8, seq(
84
134
  kw('else'),
85
- choice($.parenthesized, $.cmd),
135
+ choice($.parenthesized, $._body_stmt),
86
136
  )),
137
+ _body_stmt: ($) => choice(
138
+ $.cmd, $.variable_assignment, $.call_stmt, $.goto_stmt, $.exit_stmt,
139
+ $.setlocal_stmt, $.endlocal_stmt, $.if_stmt, $.for_stmt,
140
+ $.redirect_stmt, $.pipe_stmt, $.comment,
141
+ ),
142
+ _if_operand: ($) => choice(
143
+ $.string, $.bracketed_value, $.paren_expression,
144
+ prec.right(seq(
145
+ choice($.variable_reference, alias($._if_word, $.argument_value), $.integer),
146
+ repeat(choice(
147
+ alias($._variable_reference_immediate, $.variable_reference),
148
+ alias($._if_word_rest, $.argument_value),
149
+ )),
150
+ )),
151
+ ),
152
+ _if_word: () => token(prec(1, /[^=<>\s\[\]"|&()%!][^=<>\s"|&()%!]*/)),
153
+ _if_word_rest: () => token.immediate(/[^=<>\s\[\]"|&()%!][^=<>\s"|&()%!]*/),
87
154
  comparison_op: () => token(prec(10, choice('==', ci('equ'), ci('neq'), ci('lss'), ci('leq'), ci('gtr'), ci('geq')))),
88
- goto_stmt: () => prec(8, seq(
155
+ goto_stmt: ($) => prec(8, seq(
89
156
  optional('@'), kw('goto'),
90
- choice(token(prec(10, ci(':eof'))), token(seq(optional(':'), /[a-zA-Z_][a-zA-Z0-9_-]*/))),
157
+ optional(choice(
158
+ token(prec(10, ci(':eof'))),
159
+ seq(token(seq(optional(':'), /[$a-zA-Z_][$a-zA-Z0-9_.#-]*/)), optional($.variable_reference)),
160
+ )),
161
+ optional($.comment),
91
162
  )),
92
163
  call_stmt: ($) => prec(8, seq(
93
164
  optional('@'), kw('call'),
94
- choice(token(seq(':', /[a-zA-Z_][a-zA-Z0-9_-]*/)), $.command_name),
165
+ choice(token(seq(':', /[$a-zA-Z_][$a-zA-Z0-9_.#-]*/)), $.command_name, $.variable_reference),
95
166
  optional($.argument_list),
96
167
  )),
97
168
  exit_stmt: ($) => prec(8, seq(
@@ -104,31 +175,46 @@ export default grammar({
104
175
  )),
105
176
  setlocal_stmt: () => prec(8, seq(
106
177
  optional('@'), kw('setlocal'),
107
- optional(choice(kw('enabledelayedexpansion'), kw('disabledelayedexpansion'), kw('enableextensions'), kw('disableextensions'))),
178
+ repeat(choice(kw('enabledelayedexpansion'), kw('disabledelayedexpansion'), kw('enableextensions'), kw('disableextensions'))),
108
179
  )),
109
180
  endlocal_stmt: () => prec(8, seq(optional('@'), kw('endlocal'))),
110
181
  for_stmt: ($) => prec(8, seq(
111
- optional('@'), kw('for'),
112
- optional($.for_options), $.for_variable,
182
+ choice(
183
+ seq(optional('@'), kw('for'), optional($.for_options)),
184
+ $.variable_reference,
185
+ ),
186
+ $.for_variable,
113
187
  kw('in'), '(', optional($.for_set), ')', kw('do'),
114
- choice($.parenthesized, $.cmd),
188
+ choice($.parenthesized, $._body_stmt),
115
189
  )),
116
190
  for_options: () => token(prec(10, choice(ci('/d'), seq(ci('/r'), optional(seq(/[ \t]+/, /(%[^\s%]|[^\s%])+%?/))), ci('/l'), seq(ci('/f'), optional(seq(/[ \t]+/, '"', /[^"]*/, '"')))))),
117
191
  for_variable: () => token(seq('%%', optional('~'), /[a-zA-Z]/)),
118
192
  for_set: ($) => prec.right(repeat1(choice(
119
193
  $.variable_reference,
120
- alias(/[^%!)\r\n]+/, $.for_set_literal),
194
+ $.for_set_group,
195
+ alias(/[^%!()\r\n]+/, $.for_set_literal),
196
+ /\r?\n/,
121
197
  alias('%', $.for_set_literal),
122
198
  alias('!', $.for_set_literal),
123
199
  ))),
200
+ for_set_group: ($) => seq('(', repeat(choice(
201
+ $.variable_reference,
202
+ $.for_set_group,
203
+ alias(/[^%!()\r\n]+/, $.for_set_literal),
204
+ alias('%', $.for_set_literal),
205
+ alias('!', $.for_set_literal),
206
+ )), ')'),
124
207
  parenthesized: ($) => seq('(', repeat(choice(seq($._stmt, /\r?\n/), /\r?\n/)), optional($._stmt), ')'),
125
- redirect_stmt: ($) => prec.right(4, seq(choice($.call_stmt, $.cmd, $.parenthesized), $.redirection)),
208
+ redirect_stmt: ($) => prec.right(4, choice(
209
+ seq(choice($.call_stmt, $.cmd, $.parenthesized), $.redirection),
210
+ seq($.redirection, choice($.call_stmt, $.cmd, $.parenthesized)),
211
+ )),
126
212
  redirection: ($) => {
127
213
  const file_redir = seq(optional(/[0-2]/), $.redirect_op, $.redirect_target);
128
214
  const one_redir = choice(file_redir, $.fd_redirect);
129
215
  return prec.right(repeat1(one_redir));
130
216
  },
131
- fd_redirect: () => token(choice('2>&1', '>&1')),
217
+ fd_redirect: () => token(seq(optional(/[0-2]/), '>&', /[0-9]/)),
132
218
  redirect_op: () => token(choice('2>>', '2>', '>>', '>', '<')),
133
219
  redirect_target: () => token(choice(ci('nul'), ci('con'), /[^\s|&><\r\n]+/)),
134
220
  pipe_stmt: ($) => prec.left(3, seq(choice($.pipe_stmt, $.redirect_stmt, $.call_stmt, $.cmd, $.parenthesized), '|', choice($.redirect_stmt, $.call_stmt, $.cmd, $.parenthesized))),
@@ -139,19 +225,24 @@ export default grammar({
139
225
  command_sep: ($) => prec.left(0, seq(
140
226
  choice($.command_sep, ...operand($)),
141
227
  '&',
142
- choice(...operand($)),
143
- )),
144
- variable_reference: () => token(choice(
145
- seq('%', /[a-zA-Z_][a-zA-Z0-9_()\[\]]*/, '%'),
146
- seq('%~', /[a-zA-Z]*/, /[0-9]/),
147
- seq('%', /[0-9]/),
148
- seq('%%', optional('~'), /[a-zA-Z]/),
149
- seq('!', /[a-zA-Z_][a-zA-Z0-9_]*/, '!'),
150
- seq('%', /[a-zA-Z_][a-zA-Z0-9_()\[\]]*/, ':', /[^%]+/, '%'),
228
+ choice(...operand($), $.comment),
151
229
  )),
230
+ variable_reference: () => token(varRefChoice()),
231
+ _variable_reference_immediate: () => token.immediate(varRefChoice()),
152
232
  string: () => token(seq('"', /[^"\r\n]*/, '"')),
153
- cmd: ($) => prec.right(5, seq(optional('@'), $.command_name, optional($.argument_list))),
154
- command_name: () => /[a-zA-Z_][a-zA-Z0-9_.-]*/,
233
+ bracketed_value: ($) => seq('[', repeat(choice($.variable_reference, alias(token(/[^%!\[\]\r\n]+/), $.bracketed_literal))), ']'),
234
+ cmd: ($) => prec.right(5, choice(
235
+ seq(optional('@'), alias(kw('echo'), $.command_name), optional(alias($._echo_text, $.argument_list))),
236
+ seq(optional('@'), choice($.command_name, $.variable_reference), optional($.argument_list)),
237
+ )),
238
+ _echo_text: ($) => prec.right(repeat1(choice(
239
+ $.variable_reference,
240
+ $.string,
241
+ alias($._echo_literal, $.argument_value),
242
+ ))),
243
+ _echo_literal: () => token(/(?:\^[&|<>^()]|[^\s|&><"\r\n%!()])+|[()!%]/),
244
+ macro_invocation: ($) => prec.right(6, seq($.variable_reference, $.parenthesized, optional($.else_clause))),
245
+ command_name: () => /[$a-zA-Z_][$a-zA-Z0-9_.#-]*/,
155
246
  argument_list: ($) => prec.right(repeat1($._arg)),
156
247
  _arg: ($) => choice($.string, $.variable_reference, $.command_option, $.paren_expression, $.argument_value),
157
248
  command_option: () => token(seq('/', /[a-zA-Z_?][a-zA-Z0-9_:]*/)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tree-sitter-batch",
3
- "version": "0.7.2",
3
+ "version": "0.10.1",
4
4
  "description": "A Windows Batch/CMD grammar for tree-sitter",
5
5
  "type": "module",
6
6
  "repository": {
@@ -34,12 +34,16 @@
34
34
  "node-gyp-build": "^4.8.4"
35
35
  },
36
36
  "devDependencies": {
37
- "eslint": "^9.15.0",
37
+ "eslint": "^10.2.0",
38
38
  "eslint-config-treesitter": "^1.0.2",
39
+ "globals": "^17.5.0",
39
40
  "prebuildify": "^6.0.1",
40
- "tree-sitter-cli": "0.24.7",
41
+ "tree-sitter-cli": "0.26.8",
41
42
  "tree-sitter-go-types": "^0.1.0"
42
43
  },
44
+ "overrides": {
45
+ "eslint-plugin-jsdoc": "^62.9.0"
46
+ },
43
47
  "peerDependencies": {
44
48
  "tree-sitter": ">=0.25.0"
45
49
  },
@@ -52,7 +56,7 @@
52
56
  "install": "node-gyp-build",
53
57
  "prestart": "tree-sitter build --wasm",
54
58
  "start": "tree-sitter playground",
55
- "generate": "tree-sitter generate && tree-sitter-go-types",
59
+ "generate": "tree-sitter generate --abi 14 && tree-sitter-go-types",
56
60
  "lint": "eslint grammar.js",
57
61
  "test": "node --test bindings/node/*_test.js"
58
62
  }