@dekobon/tree-sitter-groovy 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.
- package/README.md +42 -0
- package/grammar.js +752 -154
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/@dekobon+tree-sitter-groovy.node +0 -0
- package/prebuilds/linux-x64/@dekobon+tree-sitter-groovy.node +0 -0
- package/prebuilds/win32-x64/@dekobon+tree-sitter-groovy.node +0 -0
- package/queries/groovy/highlights.scm +10 -4
- package/queries/groovy/locals.scm +7 -0
- package/queries/groovy/tags.scm +32 -0
- package/src/grammar.json +1881 -439
- package/src/node-types.json +673 -1
- package/src/parser.c +195459 -140150
- package/src/scanner.c +391 -10
- package/tree-sitter.json +2 -1
package/src/scanner.c
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
// Hand-written external scanner for tree-sitter-groovy.
|
|
2
2
|
//
|
|
3
|
-
// Handles §6.2 slashy-string-start
|
|
4
|
-
// comments
|
|
5
|
-
//
|
|
3
|
+
// Handles §6.2 slashy-string-start, §6.6 line/block/groovydoc
|
|
4
|
+
// comments, and the §6.7 contextual `record` keyword. The first two
|
|
5
|
+
// start with `/`; combining their dispatch keeps the in-grammar lexer
|
|
6
|
+
// from partially consuming the leading `/`. The third is a look-ahead
|
|
7
|
+
// decision the grammar cannot express: `record` is a keyword only
|
|
8
|
+
// when a record header follows it. Also handles §6.8
|
|
9
|
+
// COMMAND_NAMED_KEY, an empty token before the key of a named
|
|
10
|
+
// argument in a parenthesis-free call; §6.9 SUBSCRIPT_OPEN, a `[` on
|
|
11
|
+
// the same line as the token before it; §6.11 CAST_CLOSE, a `)` with
|
|
12
|
+
// something after it on its line; and §6.12 NOT_IN, the `!in`
|
|
13
|
+
// operator. STRING_BODY (§6.10) is never emitted; it is valid only
|
|
14
|
+
// inside a string body, where the scanner emits nothing. ERROR_SENTINEL is never emitted either; the parser marks it
|
|
15
|
+
// valid only during error recovery, where COMMAND_NAMED_KEY,
|
|
16
|
+
// SUBSCRIPT_OPEN, CAST_CLOSE, NOT_IN and the STRING_BODY check are
|
|
17
|
+
// suppressed.
|
|
6
18
|
//
|
|
7
19
|
// Stateless — `create` returns NULL and the runtime calls
|
|
8
20
|
// `destroy` / `serialize` / `deserialize` as no-ops. When a future
|
|
@@ -10,13 +22,113 @@
|
|
|
10
22
|
|
|
11
23
|
#include "tree_sitter/parser.h"
|
|
12
24
|
|
|
25
|
+
#include <string.h>
|
|
26
|
+
|
|
13
27
|
enum TokenType {
|
|
14
28
|
SLASHY_STRING_START,
|
|
15
29
|
LINE_COMMENT,
|
|
16
30
|
BLOCK_COMMENT,
|
|
17
31
|
GROOVYDOC_COMMENT,
|
|
32
|
+
RECORD_KEYWORD,
|
|
33
|
+
COMMAND_NAMED_KEY,
|
|
34
|
+
SUBSCRIPT_OPEN,
|
|
35
|
+
CAST_CLOSE,
|
|
36
|
+
NOT_IN,
|
|
37
|
+
STRING_BODY,
|
|
38
|
+
ERROR_SENTINEL,
|
|
18
39
|
};
|
|
19
40
|
|
|
41
|
+
// Wider than the grammar's `identifier` token (SPECIFICATION.md
|
|
42
|
+
// §5.2): every non-ASCII code point counts as an identifier
|
|
43
|
+
// character, because the scanner has no Unicode tables. That is safe
|
|
44
|
+
// because the scanner never emits a name. It only looks ahead, for
|
|
45
|
+
// the name of a record header and for the key of a named argument,
|
|
46
|
+
// and the in-grammar lexer then reads the name with the real class.
|
|
47
|
+
// On valid input the two agree: outside strings and comments, a
|
|
48
|
+
// non-ASCII character can only be part of a name. On other input the
|
|
49
|
+
// name the lexer reads is shorter, and the parse fails there.
|
|
50
|
+
static inline bool is_identifier_start(int32_t c) {
|
|
51
|
+
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|
|
52
|
+
|| c == '_' || c == '$' || c > 0x7F;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static inline bool is_identifier_char(int32_t c) {
|
|
56
|
+
return is_identifier_start(c) || (c >= '0' && c <= '9');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// SPECIFICATION.md §5.10 — a line escape, `\` followed by a line
|
|
60
|
+
// break, is whitespace to Groovy's lexer (GroovyLexer.g4
|
|
61
|
+
// `WS : ([ \t]+ | LineEscape+)`), so the line after it continues the
|
|
62
|
+
// current one. Skips spaces, tabs and line escapes. Returns false at a
|
|
63
|
+
// `\` that does not start a line escape: the `\` is consumed by then
|
|
64
|
+
// and no token starts with one, so the caller must return false to
|
|
65
|
+
// make the lexer rewind.
|
|
66
|
+
static bool skip_same_line_space(TSLexer *lexer, bool skip) {
|
|
67
|
+
for (;;) {
|
|
68
|
+
if (lexer->lookahead == ' ' || lexer->lookahead == '\t') {
|
|
69
|
+
lexer->advance(lexer, skip);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (lexer->lookahead != '\\') {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
lexer->advance(lexer, skip);
|
|
76
|
+
if (lexer->lookahead == '\r') {
|
|
77
|
+
lexer->advance(lexer, skip);
|
|
78
|
+
}
|
|
79
|
+
if (lexer->lookahead != '\n') {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
lexer->advance(lexer, skip);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// SPECIFICATION.md §6.8 — decides that the key of a named argument
|
|
87
|
+
// in a parenthesis-free call (`exclude group: 'a'`) starts here: a
|
|
88
|
+
// name on the current line, followed by `:` and not `::`. The token is
|
|
89
|
+
// empty. `mark_end` fixes it at the start of the name, and the
|
|
90
|
+
// in-grammar lexer reads the name itself, so the key is an identifier
|
|
91
|
+
// by the grammar's own definition, non-ASCII letters included. A
|
|
92
|
+
// newline before the key means a new statement (a Spock `where:`
|
|
93
|
+
// label, say), so the caller does not skip one.
|
|
94
|
+
//
|
|
95
|
+
// The caller has skipped the same-line space and seen an identifier
|
|
96
|
+
// start.
|
|
97
|
+
static bool scan_command_named_key(TSLexer *lexer) {
|
|
98
|
+
lexer->mark_end(lexer);
|
|
99
|
+
// Keep the first few chars to reject `case:` / `default:`, which
|
|
100
|
+
// are switch labels, not keys. Apache's `namedArgLabel` accepts both
|
|
101
|
+
// keywords, so `foo default: 1` diverges on purpose: the one-liner
|
|
102
|
+
// `switch (x) { case 1: foo default: bar }` in statements-switch.txt
|
|
103
|
+
// depends on the rejection. A non-ASCII character is stored as 0,
|
|
104
|
+
// which matches neither word; cut to a `char` it could.
|
|
105
|
+
char word[8];
|
|
106
|
+
unsigned len = 0;
|
|
107
|
+
while (is_identifier_char(lexer->lookahead)) {
|
|
108
|
+
if (len < sizeof(word)) {
|
|
109
|
+
word[len] = lexer->lookahead <= 0x7F ? (char)lexer->lookahead : 0;
|
|
110
|
+
}
|
|
111
|
+
len++;
|
|
112
|
+
lexer->advance(lexer, false);
|
|
113
|
+
}
|
|
114
|
+
if ((len == 4 && memcmp(word, "case", 4) == 0)
|
|
115
|
+
|| (len == 7 && memcmp(word, "default", 7) == 0)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (!skip_same_line_space(lexer, false)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (lexer->lookahead != ':') {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
lexer->advance(lexer, false);
|
|
125
|
+
if (lexer->lookahead == ':') {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
lexer->result_symbol = COMMAND_NAMED_KEY;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
20
132
|
static inline void advance(TSLexer *lexer) {
|
|
21
133
|
lexer->advance(lexer, false);
|
|
22
134
|
}
|
|
@@ -25,6 +137,163 @@ static inline bool is_whitespace(int32_t c) {
|
|
|
25
137
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
|
26
138
|
}
|
|
27
139
|
|
|
140
|
+
static inline bool is_inline_space(int32_t c) {
|
|
141
|
+
return c == ' ' || c == '\t';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static inline bool is_line_break(int32_t c) {
|
|
145
|
+
return c == '\n' || c == '\r';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Skips the trivia Groovy's lexer hides inside a record header:
|
|
149
|
+
// spaces, tabs, line escapes and block comments, plus line breaks and
|
|
150
|
+
// line comments when `newlines` is set. Returns false on a `/` that
|
|
151
|
+
// opens neither comment kind (a slashy string or division cannot be
|
|
152
|
+
// part of a header), on a `\` that is not a line escape, and on an
|
|
153
|
+
// unterminated block comment.
|
|
154
|
+
static bool skip_header_trivia(TSLexer *lexer, bool newlines) {
|
|
155
|
+
for (;;) {
|
|
156
|
+
for (;;) {
|
|
157
|
+
if (!skip_same_line_space(lexer, false)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (!newlines || !is_line_break(lexer->lookahead)) {
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
advance(lexer);
|
|
164
|
+
}
|
|
165
|
+
if (lexer->lookahead != '/') {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
advance(lexer);
|
|
169
|
+
if (lexer->lookahead == '*') {
|
|
170
|
+
advance(lexer);
|
|
171
|
+
// EOF-bounded: an unterminated comment ends the scan here.
|
|
172
|
+
for (;;) {
|
|
173
|
+
if (lexer->eof(lexer)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (lexer->lookahead == '*') {
|
|
177
|
+
advance(lexer);
|
|
178
|
+
if (lexer->lookahead == '/') {
|
|
179
|
+
advance(lexer);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
advance(lexer);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else if (newlines && lexer->lookahead == '/') {
|
|
187
|
+
while (!lexer->eof(lexer) && !is_line_break(lexer->lookahead)) {
|
|
188
|
+
advance(lexer);
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// SPECIFICATION.md §6.7 — `record` is a keyword only at the start of
|
|
197
|
+
// a record header: `record`, a name on the same line, then `(` with
|
|
198
|
+
// Groovy's lexer trivia allowed in between, or `<` straight after the
|
|
199
|
+
// name (type parameters; the grammar's `type_parameters` opener is
|
|
200
|
+
// `token.immediate` too). Everywhere else the word
|
|
201
|
+
// is an identifier (`record.key()`, `private Record record`,
|
|
202
|
+
// `public void record()`), as Apache's `identifier` rule allows. The
|
|
203
|
+
// grammar cannot make that choice: with `word: $.identifier` the
|
|
204
|
+
// keyword wins wherever it has an action, and once
|
|
205
|
+
// record_declaration is a class member that is every member start.
|
|
206
|
+
//
|
|
207
|
+
// The scanner has already consumed `record` when this is called.
|
|
208
|
+
// `mark_end` fixes the token there; the look-ahead that follows is
|
|
209
|
+
// read but not kept. Between `record` and the name only spaces, tabs
|
|
210
|
+
// and block comments may appear: `classDeclaration` in
|
|
211
|
+
// GroovyParser.g4 has no `nls` there, so `record` at the end of a line
|
|
212
|
+
// is never a keyword, which keeps `private Record record` followed by
|
|
213
|
+
// a constructor on the next line a field and a constructor. Before
|
|
214
|
+
// `(` the rule has `nls`, so line breaks and line comments are
|
|
215
|
+
// skipped there too. A command-style call `record Point(1, 2)`
|
|
216
|
+
// matches the pattern and misfires; §6.7 lists the residuals.
|
|
217
|
+
static bool scan_record_header(TSLexer *lexer) {
|
|
218
|
+
if (is_identifier_char(lexer->lookahead)) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
lexer->mark_end(lexer);
|
|
222
|
+
if (!is_inline_space(lexer->lookahead) && lexer->lookahead != '/'
|
|
223
|
+
&& lexer->lookahead != '\\') {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
if (!skip_header_trivia(lexer, false)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if (!is_identifier_start(lexer->lookahead)) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
// EOF-bounded: lookahead is 0 at end of input, which fails the
|
|
233
|
+
// predicate.
|
|
234
|
+
while (is_identifier_char(lexer->lookahead)) {
|
|
235
|
+
advance(lexer);
|
|
236
|
+
}
|
|
237
|
+
if (lexer->lookahead == '<') {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (!skip_header_trivia(lexer, true)) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return lexer->lookahead == '(';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// SPECIFICATION.md §6.11 — whether something follows a `)` on its
|
|
247
|
+
// line. The `)` is already consumed when this is called.
|
|
248
|
+
// `castParExpression castOperandExpression` in GroovyParser.g4 has no
|
|
249
|
+
// `nls` between the two, so where a line break can end a statement,
|
|
250
|
+
// `x = (a)` followed by `foo()` on the next line is two statements.
|
|
251
|
+
// The grammar ranks a cast by this answer; it does not forbid one,
|
|
252
|
+
// because inside `(…)` and `[…]` a line break ends nothing. Spaces,
|
|
253
|
+
// tabs, line escapes and block comments that close on the line may
|
|
254
|
+
// come first. A line comment ends the line.
|
|
255
|
+
static bool cast_operand_follows(TSLexer *lexer) {
|
|
256
|
+
lexer->mark_end(lexer);
|
|
257
|
+
for (;;) {
|
|
258
|
+
if (!skip_same_line_space(lexer, false)) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (lexer->eof(lexer) || is_line_break(lexer->lookahead)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
if (lexer->lookahead != '/') {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
advance(lexer);
|
|
268
|
+
if (lexer->lookahead == '/') {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (lexer->lookahead != '*') {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
// A block comment that closes on this line is skipped, and the
|
|
275
|
+
// answer depends on what follows it. One that runs past the
|
|
276
|
+
// end of the line counts as something on the line: Groovy's
|
|
277
|
+
// lexer drops the comment with its line breaks, and reading
|
|
278
|
+
// on would scan an unterminated comment to the end of input.
|
|
279
|
+
advance(lexer);
|
|
280
|
+
for (;;) {
|
|
281
|
+
if (lexer->eof(lexer) || is_line_break(lexer->lookahead)) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
if (lexer->lookahead == '*') {
|
|
285
|
+
advance(lexer);
|
|
286
|
+
if (lexer->lookahead == '/') {
|
|
287
|
+
advance(lexer);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
advance(lexer);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
28
297
|
void *tree_sitter_groovy_external_scanner_create(void) {
|
|
29
298
|
return NULL;
|
|
30
299
|
}
|
|
@@ -59,6 +328,53 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
59
328
|
) {
|
|
60
329
|
(void)payload;
|
|
61
330
|
|
|
331
|
+
// SPECIFICATION.md §6.10 — STRING_BODY is valid only between the
|
|
332
|
+
// quotes of a string whose body is lexed in pieces. Comments are
|
|
333
|
+
// extras, so the parser offers them there as well, and this
|
|
334
|
+
// function is called at every piece boundary. Without this check a
|
|
335
|
+
// body that starts with `//` or `/*`, or has one after an
|
|
336
|
+
// interpolation, lost that text to a comment token. The body
|
|
337
|
+
// belongs to the in-grammar lexer.
|
|
338
|
+
if (valid_symbols[STRING_BODY] && !valid_symbols[ERROR_SENTINEL]) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Same-line tokens: SUBSCRIPT_OPEN (§5.10) and COMMAND_NAMED_KEY
|
|
343
|
+
// (§5.5). Both skip only spaces, tabs and line escapes, the
|
|
344
|
+
// same-line whitespace of Groovy's lexer; at a newline we fall
|
|
345
|
+
// through to the branches below. Their first characters (`[` vs. an
|
|
346
|
+
// identifier start) are disjoint, so one whitespace skip serves
|
|
347
|
+
// both.
|
|
348
|
+
bool wants_subscript = valid_symbols[SUBSCRIPT_OPEN];
|
|
349
|
+
bool wants_named_key = valid_symbols[COMMAND_NAMED_KEY];
|
|
350
|
+
if ((wants_subscript || wants_named_key)
|
|
351
|
+
&& !valid_symbols[ERROR_SENTINEL]) {
|
|
352
|
+
if (!skip_same_line_space(lexer, true)) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
// SPECIFICATION.md §5.10 — a `[` on the current line can continue
|
|
356
|
+
// the expression (subscript or array type). After a newline the
|
|
357
|
+
// in-grammar `[` starts a list or map literal instead.
|
|
358
|
+
if (wants_subscript && lexer->lookahead == '[') {
|
|
359
|
+
lexer->advance(lexer, false);
|
|
360
|
+
lexer->result_symbol = SUBSCRIPT_OPEN;
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
// An identifier is either the key or, once consumed, not something
|
|
364
|
+
// the code below could start (a comment or slashy string), so stop
|
|
365
|
+
// here. Falling through after the scan advanced past `b` in
|
|
366
|
+
// `foo a, b` and a newline made the comment branch emit `b` as a
|
|
367
|
+
// line_comment, since mark_end had already been called.
|
|
368
|
+
// RECORD_KEYWORD can be valid here too, after a statement-start
|
|
369
|
+
// identifier (`foo` then `record R(…)`). The key wins on the same
|
|
370
|
+
// line: `foo record: 1` is a named argument, while a record header
|
|
371
|
+
// there is not valid Groovy. A `record` on the next line is not
|
|
372
|
+
// reached here, since a newline is not skipped.
|
|
373
|
+
if (wants_named_key && is_identifier_start(lexer->lookahead)) {
|
|
374
|
+
return scan_command_named_key(lexer);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
62
378
|
// SPECIFICATION.md §6.6 — line / block / groovydoc comments
|
|
63
379
|
// and §6.2 slashy strings. Both start with `/`; combining their
|
|
64
380
|
// dispatch keeps the in-grammar lexer from partially consuming
|
|
@@ -67,17 +383,81 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
67
383
|
// in-grammar `\s` SKIP rule — without this, tree-sitter's lexer
|
|
68
384
|
// eats `\n`, does NOT re-invoke the external scanner, then
|
|
69
385
|
// fails on the following `/` and consumes it through error
|
|
70
|
-
// recovery.
|
|
386
|
+
// recovery. §6.7 `record` shares the whitespace skip and is
|
|
387
|
+
// dispatched on `r` before the `/` branches.
|
|
71
388
|
bool wants_comment = valid_symbols[LINE_COMMENT]
|
|
72
389
|
|| valid_symbols[BLOCK_COMMENT]
|
|
73
390
|
|| valid_symbols[GROOVYDOC_COMMENT];
|
|
74
391
|
bool wants_slashy = valid_symbols[SLASHY_STRING_START];
|
|
75
|
-
|
|
392
|
+
bool wants_record = valid_symbols[RECORD_KEYWORD];
|
|
393
|
+
bool wants_cast_close = valid_symbols[CAST_CLOSE]
|
|
394
|
+
&& !valid_symbols[ERROR_SENTINEL];
|
|
395
|
+
bool wants_not_in = valid_symbols[NOT_IN]
|
|
396
|
+
&& !valid_symbols[ERROR_SENTINEL];
|
|
397
|
+
if (!wants_comment && !wants_slashy && !wants_record
|
|
398
|
+
&& !wants_cast_close && !wants_not_in) {
|
|
76
399
|
return false;
|
|
77
400
|
}
|
|
78
401
|
|
|
79
|
-
|
|
80
|
-
|
|
402
|
+
// A line escape is whitespace too, so a comment, a slashy string or
|
|
403
|
+
// `record` may follow one.
|
|
404
|
+
while (is_whitespace(lexer->lookahead) || lexer->lookahead == '\\') {
|
|
405
|
+
if (lexer->lookahead == '\\') {
|
|
406
|
+
if (!skip_same_line_space(lexer, true)) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
lexer->advance(lexer, true);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (lexer->lookahead == 'r' && wants_record) {
|
|
414
|
+
// An `r` can never open a comment or a slashy string, so
|
|
415
|
+
// every path out of this branch returns. On `false` the
|
|
416
|
+
// runtime rewinds to the token start and the in-grammar
|
|
417
|
+
// lexer reads the word as an identifier.
|
|
418
|
+
static const char keyword[] = "record";
|
|
419
|
+
for (const char *c = keyword; *c != '\0'; c++) {
|
|
420
|
+
if (lexer->lookahead != *c) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
advance(lexer);
|
|
424
|
+
}
|
|
425
|
+
if (!scan_record_header(lexer)) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
lexer->result_symbol = RECORD_KEYWORD;
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
if (lexer->lookahead == '!' && wants_not_in) {
|
|
432
|
+
// SPECIFICATION.md §6.12 — `!in` is an operator only before
|
|
433
|
+
// whitespace or an opening bracket (GroovyLexer.g4 `NOT_IN`),
|
|
434
|
+
// so that `a !inside` is `!` applied to a name. The character
|
|
435
|
+
// after it is read and not taken: `a !in[1, 2]`.
|
|
436
|
+
static const char not_in[] = "!in";
|
|
437
|
+
for (const char *c = not_in; *c != '\0'; c++) {
|
|
438
|
+
if (lexer->lookahead != *c) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
advance(lexer);
|
|
442
|
+
}
|
|
443
|
+
int32_t next = lexer->lookahead;
|
|
444
|
+
if (!is_whitespace(next) && next != '[' && next != '('
|
|
445
|
+
&& next != '{') {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
lexer->result_symbol = NOT_IN;
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
if (lexer->lookahead == ')' && wants_cast_close) {
|
|
452
|
+
// On `false` the runtime rewinds and the in-grammar lexer reads
|
|
453
|
+
// a plain `)`. A cast that closes with one ranks below a
|
|
454
|
+
// parenthesized expression.
|
|
455
|
+
advance(lexer);
|
|
456
|
+
if (!cast_operand_follows(lexer)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
lexer->result_symbol = CAST_CLOSE;
|
|
460
|
+
return true;
|
|
81
461
|
}
|
|
82
462
|
if (lexer->lookahead != '/') {
|
|
83
463
|
return false;
|
|
@@ -97,8 +477,9 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
97
477
|
advance(lexer);
|
|
98
478
|
// EOF-bounded loop: lexer->eof() guarantees termination on
|
|
99
479
|
// unterminated input. Do not tighten without preserving the
|
|
100
|
-
// EOF guard.
|
|
101
|
-
|
|
480
|
+
// EOF guard. A lone `\r` ends the line too, as in Groovy's
|
|
481
|
+
// lexer (`LineTerminator`), so the comment stops before it.
|
|
482
|
+
while (!lexer->eof(lexer) && !is_line_break(lexer->lookahead)) {
|
|
102
483
|
advance(lexer);
|
|
103
484
|
}
|
|
104
485
|
lexer->result_symbol = LINE_COMMENT;
|
|
@@ -200,7 +581,7 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
200
581
|
found_close = true;
|
|
201
582
|
break;
|
|
202
583
|
}
|
|
203
|
-
if (lexer->lookahead
|
|
584
|
+
if (is_line_break(lexer->lookahead)) {
|
|
204
585
|
break;
|
|
205
586
|
}
|
|
206
587
|
advance(lexer);
|
package/tree-sitter.json
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
"file-types": ["groovy", "gradle", "gvy", "gy", "jenkinsfile"],
|
|
10
10
|
"injection-regex": "^groovy$",
|
|
11
11
|
"highlights": "queries/groovy/highlights.scm",
|
|
12
|
+
"tags": "queries/groovy/tags.scm",
|
|
12
13
|
"class-name": "TreeSitterGroovy"
|
|
13
14
|
}
|
|
14
15
|
],
|
|
15
16
|
"metadata": {
|
|
16
|
-
"version": "0.
|
|
17
|
+
"version": "0.6.0",
|
|
17
18
|
"license": "Apache-2.0 OR MIT",
|
|
18
19
|
"description": "Apache Groovy grammar for tree-sitter (dekobon fork)",
|
|
19
20
|
"authors": [
|