@dekobon/tree-sitter-groovy 0.4.0 → 0.5.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 +21 -0
- package/grammar.js +135 -35
- 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/src/grammar.json +168 -49
- package/src/node-types.json +82 -0
- package/src/parser.c +137206 -130550
- package/src/scanner.c +135 -5
- package/tree-sitter.json +1 -1
package/src/scanner.c
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
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.
|
|
6
9
|
//
|
|
7
10
|
// Stateless — `create` returns NULL and the runtime calls
|
|
8
11
|
// `destroy` / `serialize` / `deserialize` as no-ops. When a future
|
|
@@ -15,6 +18,7 @@ enum TokenType {
|
|
|
15
18
|
LINE_COMMENT,
|
|
16
19
|
BLOCK_COMMENT,
|
|
17
20
|
GROOVYDOC_COMMENT,
|
|
21
|
+
RECORD_KEYWORD,
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
static inline void advance(TSLexer *lexer) {
|
|
@@ -25,6 +29,112 @@ static inline bool is_whitespace(int32_t c) {
|
|
|
25
29
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
static inline bool is_inline_space(int32_t c) {
|
|
33
|
+
return c == ' ' || c == '\t';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Mirrors the grammar's `identifier` token, /[A-Za-z_$][A-Za-z0-9_$]*/.
|
|
37
|
+
static inline bool is_identifier_start(int32_t c) {
|
|
38
|
+
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|
|
39
|
+
|| c == '_' || c == '$';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static inline bool is_identifier_char(int32_t c) {
|
|
43
|
+
return is_identifier_start(c) || (c >= '0' && c <= '9');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Skips the trivia Groovy's lexer hides inside a record header:
|
|
47
|
+
// spaces, tabs and block comments, plus line breaks and line comments
|
|
48
|
+
// when `newlines` is set. Returns false on a `/` that opens neither
|
|
49
|
+
// comment kind (a slashy string or division cannot be part of a
|
|
50
|
+
// header) and on an unterminated block comment.
|
|
51
|
+
static bool skip_header_trivia(TSLexer *lexer, bool newlines) {
|
|
52
|
+
for (;;) {
|
|
53
|
+
while (is_inline_space(lexer->lookahead)
|
|
54
|
+
|| (newlines
|
|
55
|
+
&& (lexer->lookahead == '\n' || lexer->lookahead == '\r'))) {
|
|
56
|
+
advance(lexer);
|
|
57
|
+
}
|
|
58
|
+
if (lexer->lookahead != '/') {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
advance(lexer);
|
|
62
|
+
if (lexer->lookahead == '*') {
|
|
63
|
+
advance(lexer);
|
|
64
|
+
// EOF-bounded: an unterminated comment ends the scan here.
|
|
65
|
+
for (;;) {
|
|
66
|
+
if (lexer->eof(lexer)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (lexer->lookahead == '*') {
|
|
70
|
+
advance(lexer);
|
|
71
|
+
if (lexer->lookahead == '/') {
|
|
72
|
+
advance(lexer);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
advance(lexer);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} else if (newlines && lexer->lookahead == '/') {
|
|
80
|
+
while (!lexer->eof(lexer) && lexer->lookahead != '\n') {
|
|
81
|
+
advance(lexer);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// SPECIFICATION.md §6.7 — `record` is a keyword only at the start of
|
|
90
|
+
// a record header: `record`, a name on the same line, then `(` with
|
|
91
|
+
// Groovy's lexer trivia allowed in between, or `<` straight after the
|
|
92
|
+
// name (type parameters; the grammar's `type_parameters` opener is
|
|
93
|
+
// `token.immediate` too). Everywhere else the word
|
|
94
|
+
// is an identifier (`record.key()`, `private Record record`,
|
|
95
|
+
// `public void record()`), as Apache's `identifier` rule allows. The
|
|
96
|
+
// grammar cannot make that choice: with `word: $.identifier` the
|
|
97
|
+
// keyword wins wherever it has an action, and once
|
|
98
|
+
// record_declaration is a class member that is every member start.
|
|
99
|
+
//
|
|
100
|
+
// The scanner has already consumed `record` when this is called.
|
|
101
|
+
// `mark_end` fixes the token there; the look-ahead that follows is
|
|
102
|
+
// read but not kept. Between `record` and the name only spaces, tabs
|
|
103
|
+
// and block comments may appear: `classDeclaration` in
|
|
104
|
+
// GroovyParser.g4 has no `nls` there, so `record` at the end of a line
|
|
105
|
+
// is never a keyword, which keeps `private Record record` followed by
|
|
106
|
+
// a constructor on the next line a field and a constructor. Before
|
|
107
|
+
// `(` the rule has `nls`, so line breaks and line comments are
|
|
108
|
+
// skipped there too. A command-style call `record Point(1, 2)`
|
|
109
|
+
// matches the pattern and misfires; §6.7 lists the residuals.
|
|
110
|
+
static bool scan_record_header(TSLexer *lexer) {
|
|
111
|
+
if (is_identifier_char(lexer->lookahead)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
lexer->mark_end(lexer);
|
|
115
|
+
if (!is_inline_space(lexer->lookahead) && lexer->lookahead != '/') {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (!skip_header_trivia(lexer, false)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (!is_identifier_start(lexer->lookahead)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
// EOF-bounded: lookahead is 0 at end of input, which fails the
|
|
125
|
+
// predicate.
|
|
126
|
+
while (is_identifier_char(lexer->lookahead)) {
|
|
127
|
+
advance(lexer);
|
|
128
|
+
}
|
|
129
|
+
if (lexer->lookahead == '<') {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (!skip_header_trivia(lexer, true)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return lexer->lookahead == '(';
|
|
136
|
+
}
|
|
137
|
+
|
|
28
138
|
void *tree_sitter_groovy_external_scanner_create(void) {
|
|
29
139
|
return NULL;
|
|
30
140
|
}
|
|
@@ -67,18 +177,38 @@ bool tree_sitter_groovy_external_scanner_scan(
|
|
|
67
177
|
// in-grammar `\s` SKIP rule — without this, tree-sitter's lexer
|
|
68
178
|
// eats `\n`, does NOT re-invoke the external scanner, then
|
|
69
179
|
// fails on the following `/` and consumes it through error
|
|
70
|
-
// recovery.
|
|
180
|
+
// recovery. §6.7 `record` shares the whitespace skip and is
|
|
181
|
+
// dispatched on `r` before the `/` branches.
|
|
71
182
|
bool wants_comment = valid_symbols[LINE_COMMENT]
|
|
72
183
|
|| valid_symbols[BLOCK_COMMENT]
|
|
73
184
|
|| valid_symbols[GROOVYDOC_COMMENT];
|
|
74
185
|
bool wants_slashy = valid_symbols[SLASHY_STRING_START];
|
|
75
|
-
|
|
186
|
+
bool wants_record = valid_symbols[RECORD_KEYWORD];
|
|
187
|
+
if (!wants_comment && !wants_slashy && !wants_record) {
|
|
76
188
|
return false;
|
|
77
189
|
}
|
|
78
190
|
|
|
79
191
|
while (is_whitespace(lexer->lookahead)) {
|
|
80
192
|
lexer->advance(lexer, true);
|
|
81
193
|
}
|
|
194
|
+
if (lexer->lookahead == 'r' && wants_record) {
|
|
195
|
+
// An `r` can never open a comment or a slashy string, so
|
|
196
|
+
// every path out of this branch returns. On `false` the
|
|
197
|
+
// runtime rewinds to the token start and the in-grammar
|
|
198
|
+
// lexer reads the word as an identifier.
|
|
199
|
+
static const char keyword[] = "record";
|
|
200
|
+
for (const char *c = keyword; *c != '\0'; c++) {
|
|
201
|
+
if (lexer->lookahead != *c) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
advance(lexer);
|
|
205
|
+
}
|
|
206
|
+
if (!scan_record_header(lexer)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
lexer->result_symbol = RECORD_KEYWORD;
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
82
212
|
if (lexer->lookahead != '/') {
|
|
83
213
|
return false;
|
|
84
214
|
}
|