@konomanoasa/tree-sitter-gitattributes 0.1.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/Cargo.toml +36 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/bindings/rust/build.rs +23 -0
- package/bindings/rust/lib.rs +12 -0
- package/grammar.js +268 -0
- package/package.json +45 -0
- package/queries/highlights.scm +53 -0
- package/src/grammar.json +1857 -0
- package/src/node-types.json +787 -0
- package/src/parser.c +5378 -0
- package/src/scanner.c +841 -0
- package/src/tree_sitter/alloc.h +54 -0
- package/src/tree_sitter/array.h +336 -0
- package/src/tree_sitter/parser.h +286 -0
- package/test/bindings.test.rs +30 -0
- package/tree-sitter.json +30 -0
package/src/scanner.c
ADDED
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
#include "tree_sitter/alloc.h"
|
|
2
|
+
#include "tree_sitter/parser.h"
|
|
3
|
+
#include <string.h>
|
|
4
|
+
|
|
5
|
+
enum Token {
|
|
6
|
+
LINE_START,
|
|
7
|
+
BLANK_START,
|
|
8
|
+
COMMENT_START,
|
|
9
|
+
RULE_START,
|
|
10
|
+
MACRO_START,
|
|
11
|
+
END_OF_FILE,
|
|
12
|
+
LINE_ENDING,
|
|
13
|
+
LAYOUT,
|
|
14
|
+
SEPARATOR,
|
|
15
|
+
ATTRIBUTE_START,
|
|
16
|
+
ATTRIBUTE_END,
|
|
17
|
+
NAME_START,
|
|
18
|
+
NAME_END,
|
|
19
|
+
VALUE_START,
|
|
20
|
+
VALUE_END,
|
|
21
|
+
PATTERN_END,
|
|
22
|
+
GLOB_START,
|
|
23
|
+
SET_ITEM_START,
|
|
24
|
+
COMMENT_MARKER,
|
|
25
|
+
COMMENT_TEXT,
|
|
26
|
+
MACRO_MARKER,
|
|
27
|
+
UNSET_MARKER,
|
|
28
|
+
UNSPECIFIED_MARKER,
|
|
29
|
+
ASSIGNMENT_OPERATOR,
|
|
30
|
+
NAME_TEXT,
|
|
31
|
+
VALUE_TEXT,
|
|
32
|
+
QUOTE_OPEN,
|
|
33
|
+
QUOTE_CLOSE,
|
|
34
|
+
LITERAL,
|
|
35
|
+
WILDCARD,
|
|
36
|
+
RECURSIVE_WILDCARD,
|
|
37
|
+
SINGLE_CHARACTER,
|
|
38
|
+
PATH_SEPARATOR,
|
|
39
|
+
ESCAPE,
|
|
40
|
+
QUOTED_ESCAPE,
|
|
41
|
+
ESCAPE_PREFIX,
|
|
42
|
+
SET_OPEN,
|
|
43
|
+
SET_NEGATION,
|
|
44
|
+
SET_TEXT,
|
|
45
|
+
SET_CLOSE,
|
|
46
|
+
RANGE_START,
|
|
47
|
+
RANGE_CHARACTER,
|
|
48
|
+
RANGE_OPERATOR,
|
|
49
|
+
COMPOUND_OPEN,
|
|
50
|
+
COMPOUND_CLOSE,
|
|
51
|
+
CLASS_OPEN,
|
|
52
|
+
CLASS_NAME,
|
|
53
|
+
CLASS_CLOSE,
|
|
54
|
+
MISSING_SET_CLOSE,
|
|
55
|
+
INCOMPLETE_SET_CLOSE,
|
|
56
|
+
INVALID_ENCODING,
|
|
57
|
+
NEGATIVE_PATTERN,
|
|
58
|
+
INVALID_ESCAPE,
|
|
59
|
+
INCOMPLETE_ESCAPE,
|
|
60
|
+
INVALID_QUOTED_ESCAPE,
|
|
61
|
+
ENDED_QUOTED_ESCAPE,
|
|
62
|
+
INCOMPLETE_QUOTED_ESCAPE,
|
|
63
|
+
MISSING_QUOTE,
|
|
64
|
+
INCOMPLETE_QUOTE,
|
|
65
|
+
MISSING_SEPARATOR,
|
|
66
|
+
MISSING_NAME,
|
|
67
|
+
INCOMPLETE_NAME,
|
|
68
|
+
INVALID_NAME_CHARACTER,
|
|
69
|
+
ERROR_SENTINEL
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
#define NONE UINT32_MAX
|
|
73
|
+
|
|
74
|
+
// A bracket item: text, an escape, or a class term such as `[:alpha:]`.
|
|
75
|
+
typedef struct {
|
|
76
|
+
uint32_t kind, end, opening_end, closing_start;
|
|
77
|
+
} Item;
|
|
78
|
+
|
|
79
|
+
// Positions count source characters from the current line start. Quoted units
|
|
80
|
+
// are characters or C escapes; ranges stay on the original source. Zero-width
|
|
81
|
+
// start tokens save lookahead decisions before their source is consumed.
|
|
82
|
+
typedef struct {
|
|
83
|
+
uint32_t position, leading, line_end, content_start, content_end, kind;
|
|
84
|
+
uint32_t token_end, name_start, name_end;
|
|
85
|
+
uint32_t next, next_end, prefix_end, prefix_kind;
|
|
86
|
+
uint32_t set_end, set_close_end, class_start, range_operator_end;
|
|
87
|
+
Item class, lower, upper;
|
|
88
|
+
bool quoted, component_start, after_separator;
|
|
89
|
+
} Scanner;
|
|
90
|
+
|
|
91
|
+
typedef char scanner_fits_buffer
|
|
92
|
+
[sizeof(Scanner) <= TREE_SITTER_SERIALIZATION_BUFFER_SIZE ? 1 : -1];
|
|
93
|
+
|
|
94
|
+
// Negative c values: -1 decode failure, -2 cursor limit, -3 invalid or
|
|
95
|
+
// incomplete escape.
|
|
96
|
+
typedef struct {
|
|
97
|
+
Scanner *s;
|
|
98
|
+
TSLexer *lexer;
|
|
99
|
+
uint32_t raw, limit, start, end, kind;
|
|
100
|
+
int32_t c;
|
|
101
|
+
} Cursor;
|
|
102
|
+
|
|
103
|
+
static bool whitespace(int32_t c) {
|
|
104
|
+
return c == ' ' || c == '\t' || c == '\v' || c == '\f' || c == '\r';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static bool alphanumeric(int32_t c) {
|
|
108
|
+
return (c >= 'a' && c <= 'z') ||
|
|
109
|
+
(c >= 'A' && c <= 'Z') ||
|
|
110
|
+
(c >= '0' && c <= '9');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static bool name_character(int32_t c, bool first) {
|
|
114
|
+
return alphanumeric(c) || c == '_' || c == '.' || (!first && c == '-');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static void advance(Scanner *s, TSLexer *lexer) {
|
|
118
|
+
lexer->advance(lexer, false);
|
|
119
|
+
s->position++;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static bool emit(TSLexer *lexer, const bool *valid, enum Token token) {
|
|
123
|
+
if (!valid[token])
|
|
124
|
+
return false;
|
|
125
|
+
lexer->result_symbol = token;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static enum Token
|
|
130
|
+
absent(TSLexer *lexer, enum Token missing, enum Token incomplete) {
|
|
131
|
+
return lexer->eof(lexer) ? incomplete : missing;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static bool consume(
|
|
135
|
+
Scanner *s,
|
|
136
|
+
TSLexer *lexer,
|
|
137
|
+
const bool *valid,
|
|
138
|
+
enum Token token,
|
|
139
|
+
uint32_t end
|
|
140
|
+
) {
|
|
141
|
+
while (s->position < end)
|
|
142
|
+
advance(s, lexer);
|
|
143
|
+
lexer->mark_end(lexer);
|
|
144
|
+
return emit(lexer, valid, token);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static bool text_run(
|
|
148
|
+
Scanner *s,
|
|
149
|
+
TSLexer *lexer,
|
|
150
|
+
const bool *valid,
|
|
151
|
+
enum Token token,
|
|
152
|
+
uint32_t end
|
|
153
|
+
) {
|
|
154
|
+
if (lexer->lookahead == -1)
|
|
155
|
+
return consume(s, lexer, valid, INVALID_ENCODING, s->position + 1);
|
|
156
|
+
while (s->position < end && lexer->lookahead != -1)
|
|
157
|
+
advance(s, lexer);
|
|
158
|
+
lexer->mark_end(lexer);
|
|
159
|
+
return emit(lexer, valid, token);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static void raw_step(Cursor *r) {
|
|
163
|
+
r->lexer->advance(r->lexer, false);
|
|
164
|
+
r->raw++;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Quoted units decode named C escapes or three octal digits starting with 0
|
|
168
|
+
// to 3.
|
|
169
|
+
static void step(Cursor *r) {
|
|
170
|
+
r->start = r->end = r->raw;
|
|
171
|
+
r->kind = 0;
|
|
172
|
+
r->c = -2;
|
|
173
|
+
if (r->raw >= r->limit)
|
|
174
|
+
return;
|
|
175
|
+
int32_t c = r->lexer->lookahead;
|
|
176
|
+
raw_step(r);
|
|
177
|
+
r->c = c;
|
|
178
|
+
if (c == -1)
|
|
179
|
+
r->kind = INVALID_ENCODING;
|
|
180
|
+
if (r->s->quoted && r->start < r->s->content_end && c == '\\') {
|
|
181
|
+
r->kind = QUOTED_ESCAPE;
|
|
182
|
+
c = r->lexer->lookahead;
|
|
183
|
+
if (r->raw >= r->limit) {
|
|
184
|
+
r->c = -3;
|
|
185
|
+
r->kind = absent(r->lexer, ENDED_QUOTED_ESCAPE, INCOMPLETE_QUOTED_ESCAPE);
|
|
186
|
+
} else if (c == -1) {
|
|
187
|
+
r->c = -3;
|
|
188
|
+
r->kind = ESCAPE_PREFIX;
|
|
189
|
+
} else if (c >= '0' && c <= '3') {
|
|
190
|
+
int32_t value = 0;
|
|
191
|
+
unsigned digits = 0;
|
|
192
|
+
while (digits < 3 && r->raw < r->limit) {
|
|
193
|
+
int32_t digit = r->lexer->lookahead;
|
|
194
|
+
if (digit < '0' || digit > '7')
|
|
195
|
+
break;
|
|
196
|
+
value = value * 8 + digit - '0';
|
|
197
|
+
raw_step(r);
|
|
198
|
+
digits++;
|
|
199
|
+
}
|
|
200
|
+
r->c = value;
|
|
201
|
+
if (digits != 3) {
|
|
202
|
+
r->c = -3;
|
|
203
|
+
r->kind = r->raw == r->limit
|
|
204
|
+
? absent(r->lexer, ENDED_QUOTED_ESCAPE, INCOMPLETE_QUOTED_ESCAPE)
|
|
205
|
+
: ENDED_QUOTED_ESCAPE;
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
raw_step(r);
|
|
209
|
+
switch (c) {
|
|
210
|
+
case 'a':
|
|
211
|
+
r->c = '\a';
|
|
212
|
+
break;
|
|
213
|
+
case 'b':
|
|
214
|
+
r->c = '\b';
|
|
215
|
+
break;
|
|
216
|
+
case 't':
|
|
217
|
+
r->c = '\t';
|
|
218
|
+
break;
|
|
219
|
+
case 'n':
|
|
220
|
+
r->c = '\n';
|
|
221
|
+
break;
|
|
222
|
+
case 'v':
|
|
223
|
+
r->c = '\v';
|
|
224
|
+
break;
|
|
225
|
+
case 'f':
|
|
226
|
+
r->c = '\f';
|
|
227
|
+
break;
|
|
228
|
+
case 'r':
|
|
229
|
+
r->c = '\r';
|
|
230
|
+
break;
|
|
231
|
+
case '\\':
|
|
232
|
+
case '"':
|
|
233
|
+
r->c = c;
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
r->c = -3;
|
|
237
|
+
r->kind = INVALID_QUOTED_ESCAPE;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
r->end = r->raw;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static Cursor cursor(Scanner *s, TSLexer *lexer, uint32_t limit) {
|
|
246
|
+
Cursor r = {.s = s, .lexer = lexer, .raw = s->position, .limit = limit};
|
|
247
|
+
step(&r);
|
|
248
|
+
return r;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// An unclosed quote leaves the pattern content ending at the line end.
|
|
252
|
+
static bool start_line(Scanner *s, TSLexer *lexer, const bool *valid) {
|
|
253
|
+
if (lexer->eof(lexer))
|
|
254
|
+
return false;
|
|
255
|
+
Scanner line;
|
|
256
|
+
memset(&line, 0, sizeof(line));
|
|
257
|
+
int32_t last = 0;
|
|
258
|
+
lexer->mark_end(lexer);
|
|
259
|
+
while (!lexer->eof(lexer) && whitespace(lexer->lookahead)) {
|
|
260
|
+
last = lexer->lookahead;
|
|
261
|
+
advance(&line, lexer);
|
|
262
|
+
}
|
|
263
|
+
line.leading = line.position;
|
|
264
|
+
line.kind = lexer->lookahead == '#' ? COMMENT_START : RULE_START;
|
|
265
|
+
line.quoted = lexer->lookahead == '"';
|
|
266
|
+
line.content_start = line.leading + (line.quoted ? 1 : 0);
|
|
267
|
+
line.content_end = NONE;
|
|
268
|
+
bool escaped = false;
|
|
269
|
+
while (!lexer->eof(lexer) && lexer->lookahead != '\n') {
|
|
270
|
+
int32_t c = lexer->lookahead;
|
|
271
|
+
if (line.position >= line.content_start && line.content_end == NONE) {
|
|
272
|
+
if (line.quoted) {
|
|
273
|
+
if (c == '"' && !escaped)
|
|
274
|
+
line.content_end = line.position;
|
|
275
|
+
escaped = c == '\\' && !escaped;
|
|
276
|
+
} else if (whitespace(c))
|
|
277
|
+
line.content_end = line.position;
|
|
278
|
+
}
|
|
279
|
+
last = c;
|
|
280
|
+
advance(&line, lexer);
|
|
281
|
+
}
|
|
282
|
+
line.line_end = line.position;
|
|
283
|
+
if (last == '\r' && lexer->lookahead == '\n')
|
|
284
|
+
line.line_end--;
|
|
285
|
+
// The CR of a CRLF ending is neither leading whitespace nor content.
|
|
286
|
+
if (line.leading > line.line_end)
|
|
287
|
+
line.leading = line.line_end;
|
|
288
|
+
if (line.content_end == NONE || line.content_end > line.line_end)
|
|
289
|
+
line.content_end = line.line_end;
|
|
290
|
+
if (line.leading >= line.line_end)
|
|
291
|
+
line.kind = BLANK_START;
|
|
292
|
+
line.position = 0;
|
|
293
|
+
line.class_start = NONE;
|
|
294
|
+
line.component_start = true;
|
|
295
|
+
*s = line;
|
|
296
|
+
return emit(lexer, valid, LINE_START);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Compare the macro marker after decoding C escapes.
|
|
300
|
+
static bool start_content(Scanner *s, TSLexer *lexer, const bool *valid) {
|
|
301
|
+
while (s->position < s->leading)
|
|
302
|
+
advance(s, lexer);
|
|
303
|
+
lexer->mark_end(lexer);
|
|
304
|
+
if (s->kind == RULE_START) {
|
|
305
|
+
if (s->quoted)
|
|
306
|
+
lexer->advance(lexer, false);
|
|
307
|
+
Cursor r = {
|
|
308
|
+
.s = s,
|
|
309
|
+
.lexer = lexer,
|
|
310
|
+
.raw = s->content_start,
|
|
311
|
+
.limit = s->content_end
|
|
312
|
+
};
|
|
313
|
+
static const char marker[] = "[attr]";
|
|
314
|
+
bool macro = true;
|
|
315
|
+
for (unsigned i = 0; i < sizeof(marker) - 1; i++) {
|
|
316
|
+
step(&r);
|
|
317
|
+
if (r.c != marker[i]) {
|
|
318
|
+
macro = false;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (macro) {
|
|
323
|
+
s->kind = MACRO_START;
|
|
324
|
+
s->name_start = r.end;
|
|
325
|
+
s->name_end = s->content_end;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return emit(lexer, valid, (enum Token)s->kind);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
static void skip_escaped(Cursor *r, Cursor *last, uint32_t *candidate) {
|
|
332
|
+
*last = *r;
|
|
333
|
+
if (r->c == ']')
|
|
334
|
+
*candidate = NONE;
|
|
335
|
+
step(r);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Escapes and range upper endpoints take precedence over class candidates.
|
|
339
|
+
// A candidate starts at [: and ends at the first unescaped ], preceded by a
|
|
340
|
+
// colon other than the opening one. An escaped ] cancels it; candidate units
|
|
341
|
+
// remain ordinary items. `lower` records an available lower endpoint.
|
|
342
|
+
// Success leaves the cursor after the class; failure at ] or the limit.
|
|
343
|
+
static bool next_class(Cursor *r, bool lower, uint32_t *start, Item *class) {
|
|
344
|
+
Item a = {CLASS_OPEN, 0, 0, 0};
|
|
345
|
+
uint32_t candidate = NONE;
|
|
346
|
+
Cursor last = *r;
|
|
347
|
+
while (r->c != -2) {
|
|
348
|
+
int32_t here = r->c;
|
|
349
|
+
if (here == ']') {
|
|
350
|
+
if (candidate == NONE || last.c != ':' || last.end == a.opening_end)
|
|
351
|
+
return false;
|
|
352
|
+
a.closing_start = last.start;
|
|
353
|
+
a.end = r->end;
|
|
354
|
+
step(r);
|
|
355
|
+
*start = candidate;
|
|
356
|
+
*class = a;
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
last = *r;
|
|
360
|
+
step(r);
|
|
361
|
+
if (here == '\\') {
|
|
362
|
+
lower = r->c >= 0;
|
|
363
|
+
if (r->c != -2)
|
|
364
|
+
skip_escaped(r, &last, &candidate);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (here == '-' && lower && r->c != -2 && r->c != ']') {
|
|
368
|
+
int32_t upper = r->c;
|
|
369
|
+
skip_escaped(r, &last, &candidate);
|
|
370
|
+
if (upper == '\\' && r->c != -2)
|
|
371
|
+
skip_escaped(r, &last, &candidate);
|
|
372
|
+
lower = false;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
lower = here >= 0;
|
|
376
|
+
if (candidate == NONE && here == '[' && r->c == ':') {
|
|
377
|
+
candidate = last.start;
|
|
378
|
+
a.opening_end = r->end;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// The cursor must be after the opening bracket.
|
|
385
|
+
static void find_set(Scanner *s, Cursor *r) {
|
|
386
|
+
s->set_end = s->set_close_end = s->content_end;
|
|
387
|
+
s->class_start = NONE;
|
|
388
|
+
if (r->c == '!' || r->c == '^')
|
|
389
|
+
step(r);
|
|
390
|
+
bool lower = r->c == ']';
|
|
391
|
+
if (lower)
|
|
392
|
+
step(r);
|
|
393
|
+
Item class;
|
|
394
|
+
uint32_t class_start;
|
|
395
|
+
while (next_class(r, lower, &class_start, &class)) {
|
|
396
|
+
lower = false;
|
|
397
|
+
if (s->class_start == NONE) {
|
|
398
|
+
s->class_start = class_start;
|
|
399
|
+
s->class = class;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (r->c == ']') {
|
|
403
|
+
s->set_end = r->start;
|
|
404
|
+
s->set_close_end = r->end;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
static Item item(Cursor *r) {
|
|
409
|
+
Item a = {SET_TEXT, 0, 0, 0};
|
|
410
|
+
if (r->c == -2)
|
|
411
|
+
return a;
|
|
412
|
+
int32_t c = r->c;
|
|
413
|
+
uint32_t end = r->end;
|
|
414
|
+
if (c == '[' && r->start == r->s->class_start) {
|
|
415
|
+
a = r->s->class;
|
|
416
|
+
while (r->c != -2 && r->start < a.end)
|
|
417
|
+
step(r);
|
|
418
|
+
return a;
|
|
419
|
+
}
|
|
420
|
+
if (r->kind)
|
|
421
|
+
a.kind = r->kind;
|
|
422
|
+
step(r);
|
|
423
|
+
if (c == '\\') {
|
|
424
|
+
if (r->c == -2)
|
|
425
|
+
a.kind = absent(r->lexer, INVALID_ESCAPE, INCOMPLETE_ESCAPE);
|
|
426
|
+
else if (r->c < 0)
|
|
427
|
+
a.kind = ESCAPE_PREFIX;
|
|
428
|
+
else {
|
|
429
|
+
a.kind = ESCAPE;
|
|
430
|
+
a.end = r->end;
|
|
431
|
+
step(r);
|
|
432
|
+
return a;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
a.end = end;
|
|
436
|
+
return a;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
static bool endpoint(Item a) {
|
|
440
|
+
return a.end &&
|
|
441
|
+
(a.kind == SET_TEXT || a.kind == ESCAPE || a.kind == QUOTED_ESCAPE);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
static bool glob_special(int32_t c) {
|
|
445
|
+
return c == '\\' || c == '*' || c == '?' || c == '/';
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// End literal runs before [ so the set can be decided.
|
|
449
|
+
static bool special(const Cursor *r) {
|
|
450
|
+
if (r->c < 0 || r->kind == QUOTED_ESCAPE)
|
|
451
|
+
return true;
|
|
452
|
+
return glob_special(r->c) || r->c == '[';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// A backslash followed by a unit that cannot be escaped is held by the owner
|
|
456
|
+
// up to `prefix_end`, and the unit's own issue follows. A C escape prefix
|
|
457
|
+
// before an undecodable byte extends the held prefix to that byte.
|
|
458
|
+
static void hold_prefix(Scanner *s, Cursor *r, uint32_t prefix_end) {
|
|
459
|
+
s->next = ESCAPE_PREFIX;
|
|
460
|
+
s->prefix_end = prefix_end;
|
|
461
|
+
s->prefix_kind = r->kind;
|
|
462
|
+
if (r->kind == ESCAPE_PREFIX) {
|
|
463
|
+
s->prefix_end = r->end;
|
|
464
|
+
s->prefix_kind = INVALID_ENCODING;
|
|
465
|
+
step(r);
|
|
466
|
+
}
|
|
467
|
+
s->next_end = r->end;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Advance through every lookahead unit so edits inside it invalidate the
|
|
471
|
+
// decision. Nonliteral tokens are decided here and consumed by the next call.
|
|
472
|
+
static bool prepare_glob(Scanner *s, TSLexer *lexer, const bool *valid) {
|
|
473
|
+
lexer->mark_end(lexer);
|
|
474
|
+
Cursor r = cursor(s, lexer, s->content_end);
|
|
475
|
+
uint32_t start = s->position;
|
|
476
|
+
s->next_end = r.end;
|
|
477
|
+
if (r.c < 0) {
|
|
478
|
+
if (r.kind == ESCAPE_PREFIX)
|
|
479
|
+
hold_prefix(s, &r, r.end);
|
|
480
|
+
else
|
|
481
|
+
s->next = r.kind;
|
|
482
|
+
} else if (r.c == '!' && start == s->content_start)
|
|
483
|
+
s->next = NEGATIVE_PATTERN;
|
|
484
|
+
else if (r.c == '?')
|
|
485
|
+
s->next = SINGLE_CHARACTER;
|
|
486
|
+
else if (r.c == '/')
|
|
487
|
+
s->next = PATH_SEPARATOR;
|
|
488
|
+
else if (r.c == '\\') {
|
|
489
|
+
step(&r);
|
|
490
|
+
if (r.c == -2)
|
|
491
|
+
s->next = absent(lexer, INVALID_ESCAPE, INCOMPLETE_ESCAPE);
|
|
492
|
+
else if (r.c < 0)
|
|
493
|
+
hold_prefix(s, &r, s->next_end);
|
|
494
|
+
else {
|
|
495
|
+
s->next = ESCAPE;
|
|
496
|
+
s->next_end = r.end;
|
|
497
|
+
}
|
|
498
|
+
} else if (r.c == '*') {
|
|
499
|
+
unsigned count = 0;
|
|
500
|
+
while (r.c == '*') {
|
|
501
|
+
count++;
|
|
502
|
+
s->next_end = r.end;
|
|
503
|
+
step(&r);
|
|
504
|
+
}
|
|
505
|
+
bool pair = count == 2 && s->component_start;
|
|
506
|
+
bool slash = r.c == '/';
|
|
507
|
+
bool tail = r.c == -2 && s->after_separator;
|
|
508
|
+
s->next = pair && (slash || tail) ? RECURSIVE_WILDCARD : WILDCARD;
|
|
509
|
+
} else if (r.kind == QUOTED_ESCAPE && r.c != '[')
|
|
510
|
+
s->next = QUOTED_ESCAPE;
|
|
511
|
+
else if (r.c == '[') {
|
|
512
|
+
s->next = SET_OPEN;
|
|
513
|
+
step(&r);
|
|
514
|
+
find_set(s, &r);
|
|
515
|
+
} else {
|
|
516
|
+
uint32_t end = r.end;
|
|
517
|
+
lexer->mark_end(lexer);
|
|
518
|
+
step(&r);
|
|
519
|
+
while (!special(&r)) {
|
|
520
|
+
end = r.end;
|
|
521
|
+
lexer->mark_end(lexer);
|
|
522
|
+
step(&r);
|
|
523
|
+
}
|
|
524
|
+
s->position = end;
|
|
525
|
+
s->component_start = false;
|
|
526
|
+
return emit(lexer, valid, LITERAL);
|
|
527
|
+
}
|
|
528
|
+
return emit(lexer, valid, GLOB_START);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
static bool prepare_set(Scanner *s, TSLexer *lexer, const bool *valid) {
|
|
532
|
+
lexer->mark_end(lexer);
|
|
533
|
+
Cursor r = cursor(s, lexer, s->set_end);
|
|
534
|
+
uint32_t start = s->position;
|
|
535
|
+
if (valid[SET_NEGATION] && (r.c == '!' || r.c == '^')) {
|
|
536
|
+
s->position = r.end;
|
|
537
|
+
lexer->mark_end(lexer);
|
|
538
|
+
return emit(lexer, valid, SET_NEGATION);
|
|
539
|
+
}
|
|
540
|
+
s->next = SET_TEXT;
|
|
541
|
+
s->next_end = start;
|
|
542
|
+
uint32_t here = start;
|
|
543
|
+
Item current = item(&r);
|
|
544
|
+
for (;;) {
|
|
545
|
+
if (endpoint(current) && r.c == '-' && r.end < s->set_end) {
|
|
546
|
+
uint32_t operator_end = r.end;
|
|
547
|
+
bool plain_operator = r.kind != QUOTED_ESCAPE;
|
|
548
|
+
step(&r);
|
|
549
|
+
Item upper = item(&r);
|
|
550
|
+
if (endpoint(upper)) {
|
|
551
|
+
if (here == start) {
|
|
552
|
+
s->next = RANGE_START;
|
|
553
|
+
s->lower = current;
|
|
554
|
+
s->upper = upper;
|
|
555
|
+
s->range_operator_end = operator_end;
|
|
556
|
+
} else
|
|
557
|
+
s->next_end = here;
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
if (current.kind == SET_TEXT) {
|
|
561
|
+
// A literal hyphen joins the text run; an escaped one stays a
|
|
562
|
+
// separate `quoted_escape`.
|
|
563
|
+
if (!plain_operator) {
|
|
564
|
+
s->next_end = current.end;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
s->next_end = operator_end;
|
|
568
|
+
here = operator_end;
|
|
569
|
+
current = upper;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (current.kind != SET_TEXT) {
|
|
574
|
+
if (here == start) {
|
|
575
|
+
s->next = current.kind;
|
|
576
|
+
s->next_end = current.end;
|
|
577
|
+
if (current.kind == ESCAPE_PREFIX)
|
|
578
|
+
hold_prefix(s, &r, current.end);
|
|
579
|
+
} else
|
|
580
|
+
s->next_end = here;
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
s->next_end = current.end;
|
|
584
|
+
here = r.start;
|
|
585
|
+
if (r.c == -2)
|
|
586
|
+
break;
|
|
587
|
+
current = item(&r);
|
|
588
|
+
}
|
|
589
|
+
return emit(lexer, valid, SET_ITEM_START);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
static bool
|
|
593
|
+
unit_token(Scanner *s, TSLexer *lexer, const bool *valid, enum Token token) {
|
|
594
|
+
Cursor r = cursor(s, lexer, s->class.end);
|
|
595
|
+
s->position = r.end;
|
|
596
|
+
lexer->mark_end(lexer);
|
|
597
|
+
return emit(lexer, valid, token);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Closing a class term must find the next class in the set.
|
|
601
|
+
static bool class_piece(Scanner *s, TSLexer *lexer, const bool *valid) {
|
|
602
|
+
if (s->position < s->class.opening_end)
|
|
603
|
+
return consume(s, lexer, valid, CLASS_OPEN, s->class.opening_end);
|
|
604
|
+
if (s->position == s->class.closing_start)
|
|
605
|
+
return unit_token(s, lexer, valid, CLASS_CLOSE);
|
|
606
|
+
if (s->position > s->class.closing_start) {
|
|
607
|
+
while (s->position < s->class.end)
|
|
608
|
+
advance(s, lexer);
|
|
609
|
+
lexer->mark_end(lexer);
|
|
610
|
+
Cursor r = cursor(s, lexer, s->set_end);
|
|
611
|
+
if (!next_class(&r, false, &s->class_start, &s->class))
|
|
612
|
+
s->class_start = NONE;
|
|
613
|
+
return emit(lexer, valid, COMPOUND_CLOSE);
|
|
614
|
+
}
|
|
615
|
+
Cursor r = cursor(s, lexer, s->class.closing_start);
|
|
616
|
+
enum Token token = r.kind ? (enum Token)r.kind : CLASS_NAME;
|
|
617
|
+
uint32_t end = r.end;
|
|
618
|
+
lexer->mark_end(lexer);
|
|
619
|
+
if (token == CLASS_NAME) {
|
|
620
|
+
step(&r);
|
|
621
|
+
while (r.c >= 0 && !r.kind) {
|
|
622
|
+
end = r.end;
|
|
623
|
+
lexer->mark_end(lexer);
|
|
624
|
+
step(&r);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
s->position = end;
|
|
628
|
+
return emit(lexer, valid, token);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
static bool name_piece(Scanner *s, TSLexer *lexer, const bool *valid) {
|
|
632
|
+
Cursor r = cursor(s, lexer, s->name_end);
|
|
633
|
+
enum Token token;
|
|
634
|
+
if (r.kind == ESCAPE_PREFIX) {
|
|
635
|
+
s->next = INVALID_ENCODING;
|
|
636
|
+
s->next_end = r.end + 1;
|
|
637
|
+
}
|
|
638
|
+
if (r.c < 0)
|
|
639
|
+
token = (enum Token)r.kind;
|
|
640
|
+
else if (!name_character(r.c, s->position == s->name_start))
|
|
641
|
+
token = INVALID_NAME_CHARACTER;
|
|
642
|
+
else
|
|
643
|
+
token = r.kind == QUOTED_ESCAPE ? QUOTED_ESCAPE : NAME_TEXT;
|
|
644
|
+
uint32_t end = r.end;
|
|
645
|
+
lexer->mark_end(lexer);
|
|
646
|
+
if (token == NAME_TEXT) {
|
|
647
|
+
step(&r);
|
|
648
|
+
while (r.c >= 0 && !r.kind && name_character(r.c, false)) {
|
|
649
|
+
end = r.end;
|
|
650
|
+
lexer->mark_end(lexer);
|
|
651
|
+
step(&r);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
s->position = end;
|
|
655
|
+
return emit(lexer, valid, token);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
static bool prepare_attribute(Scanner *s, TSLexer *lexer, const bool *valid) {
|
|
659
|
+
lexer->mark_end(lexer);
|
|
660
|
+
uint32_t position = s->position;
|
|
661
|
+
bool modified = lexer->lookahead == '-' || lexer->lookahead == '!';
|
|
662
|
+
s->name_start = s->position + (modified ? 1 : 0);
|
|
663
|
+
s->name_end = NONE;
|
|
664
|
+
while (position < s->line_end && !whitespace(lexer->lookahead)) {
|
|
665
|
+
if (lexer->lookahead == '=' && s->name_end == NONE)
|
|
666
|
+
s->name_end = position;
|
|
667
|
+
lexer->advance(lexer, false);
|
|
668
|
+
position++;
|
|
669
|
+
}
|
|
670
|
+
s->token_end = position;
|
|
671
|
+
if (s->name_end == NONE)
|
|
672
|
+
s->name_end = position;
|
|
673
|
+
return emit(lexer, valid, ATTRIBUTE_START);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static bool scan(Scanner *s, TSLexer *lexer, const bool *valid) {
|
|
677
|
+
if (valid[ERROR_SENTINEL])
|
|
678
|
+
return false;
|
|
679
|
+
if (valid[LINE_START])
|
|
680
|
+
return start_line(s, lexer, valid);
|
|
681
|
+
if (
|
|
682
|
+
valid[BLANK_START] ||
|
|
683
|
+
valid[COMMENT_START] ||
|
|
684
|
+
valid[RULE_START] ||
|
|
685
|
+
valid[MACRO_START]
|
|
686
|
+
)
|
|
687
|
+
return start_content(s, lexer, valid);
|
|
688
|
+
lexer->mark_end(lexer);
|
|
689
|
+
if (
|
|
690
|
+
(valid[SET_CLOSE] ||
|
|
691
|
+
valid[MISSING_SET_CLOSE] ||
|
|
692
|
+
valid[INCOMPLETE_SET_CLOSE]) &&
|
|
693
|
+
s->position == s->set_end
|
|
694
|
+
) {
|
|
695
|
+
if (s->set_close_end > s->set_end)
|
|
696
|
+
return consume(s, lexer, valid, SET_CLOSE, s->set_close_end);
|
|
697
|
+
return emit(
|
|
698
|
+
lexer,
|
|
699
|
+
valid,
|
|
700
|
+
absent(lexer, MISSING_SET_CLOSE, INCOMPLETE_SET_CLOSE)
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
if (valid[ATTRIBUTE_START])
|
|
704
|
+
return prepare_attribute(s, lexer, valid);
|
|
705
|
+
if (valid[ATTRIBUTE_END] && s->position == s->token_end)
|
|
706
|
+
return emit(lexer, valid, ATTRIBUTE_END);
|
|
707
|
+
bool at_name = s->position == s->name_start && s->position < s->name_end;
|
|
708
|
+
if (valid[NAME_START] && at_name)
|
|
709
|
+
return emit(lexer, valid, NAME_START);
|
|
710
|
+
if (valid[NAME_END] && s->position == s->name_end)
|
|
711
|
+
return emit(lexer, valid, NAME_END);
|
|
712
|
+
if (valid[VALUE_START] && s->position < s->token_end)
|
|
713
|
+
return emit(lexer, valid, VALUE_START);
|
|
714
|
+
if (valid[VALUE_END] && s->position == s->token_end)
|
|
715
|
+
return emit(lexer, valid, VALUE_END);
|
|
716
|
+
if (valid[MISSING_NAME] && s->position == s->name_end)
|
|
717
|
+
return emit(lexer, valid, absent(lexer, MISSING_NAME, INCOMPLETE_NAME));
|
|
718
|
+
if (
|
|
719
|
+
(valid[QUOTE_CLOSE] || valid[MISSING_QUOTE]) &&
|
|
720
|
+
s->quoted &&
|
|
721
|
+
s->position == s->content_end
|
|
722
|
+
) {
|
|
723
|
+
if (s->content_end < s->line_end)
|
|
724
|
+
return consume(s, lexer, valid, QUOTE_CLOSE, s->position + 1);
|
|
725
|
+
return emit(lexer, valid, absent(lexer, MISSING_QUOTE, INCOMPLETE_QUOTE));
|
|
726
|
+
}
|
|
727
|
+
if (valid[PATTERN_END] && s->position >= s->content_end)
|
|
728
|
+
return emit(lexer, valid, PATTERN_END);
|
|
729
|
+
if (s->position >= s->line_end) {
|
|
730
|
+
if (lexer->eof(lexer))
|
|
731
|
+
return emit(lexer, valid, END_OF_FILE);
|
|
732
|
+
if (lexer->lookahead == '\r')
|
|
733
|
+
advance(s, lexer);
|
|
734
|
+
if (lexer->lookahead != '\n')
|
|
735
|
+
return false;
|
|
736
|
+
advance(s, lexer);
|
|
737
|
+
lexer->mark_end(lexer);
|
|
738
|
+
return emit(lexer, valid, LINE_ENDING);
|
|
739
|
+
}
|
|
740
|
+
if (s->kind == COMMENT_START) {
|
|
741
|
+
if (s->position == s->leading)
|
|
742
|
+
return consume(s, lexer, valid, COMMENT_MARKER, s->position + 1);
|
|
743
|
+
return text_run(s, lexer, valid, COMMENT_TEXT, s->line_end);
|
|
744
|
+
}
|
|
745
|
+
if (valid[QUOTE_OPEN] && s->quoted && s->position == s->leading)
|
|
746
|
+
return consume(s, lexer, valid, QUOTE_OPEN, s->position + 1);
|
|
747
|
+
if (valid[MACRO_MARKER])
|
|
748
|
+
return consume(s, lexer, valid, MACRO_MARKER, s->name_start);
|
|
749
|
+
if (
|
|
750
|
+
(valid[UNSET_MARKER] || valid[UNSPECIFIED_MARKER]) &&
|
|
751
|
+
s->position < s->name_start
|
|
752
|
+
)
|
|
753
|
+
return consume(
|
|
754
|
+
s,
|
|
755
|
+
lexer,
|
|
756
|
+
valid,
|
|
757
|
+
lexer->lookahead == '-' ? UNSET_MARKER : UNSPECIFIED_MARKER,
|
|
758
|
+
s->position + 1
|
|
759
|
+
);
|
|
760
|
+
if (valid[NAME_TEXT])
|
|
761
|
+
return name_piece(s, lexer, valid);
|
|
762
|
+
if (valid[VALUE_TEXT])
|
|
763
|
+
return text_run(s, lexer, valid, VALUE_TEXT, s->token_end);
|
|
764
|
+
if (valid[ASSIGNMENT_OPERATOR] && lexer->lookahead == '=')
|
|
765
|
+
return consume(s, lexer, valid, ASSIGNMENT_OPERATOR, s->position + 1);
|
|
766
|
+
if (valid[SEPARATOR] || valid[LAYOUT]) {
|
|
767
|
+
if (!whitespace(lexer->lookahead))
|
|
768
|
+
return emit(lexer, valid, MISSING_SEPARATOR);
|
|
769
|
+
while (s->position < s->line_end && whitespace(lexer->lookahead))
|
|
770
|
+
advance(s, lexer);
|
|
771
|
+
lexer->mark_end(lexer);
|
|
772
|
+
return emit(lexer, valid, s->position < s->line_end ? SEPARATOR : LAYOUT);
|
|
773
|
+
}
|
|
774
|
+
if (valid[GLOB_START])
|
|
775
|
+
return prepare_glob(s, lexer, valid);
|
|
776
|
+
if (valid[SET_ITEM_START] || valid[SET_NEGATION])
|
|
777
|
+
return prepare_set(s, lexer, valid);
|
|
778
|
+
if (s->class_start < s->position && s->position < s->class.end)
|
|
779
|
+
return class_piece(s, lexer, valid);
|
|
780
|
+
if (s->next == RANGE_START) {
|
|
781
|
+
s->next = RANGE_CHARACTER;
|
|
782
|
+
return emit(lexer, valid, RANGE_START);
|
|
783
|
+
}
|
|
784
|
+
if (s->upper.end > s->position) {
|
|
785
|
+
if (s->position == s->lower.end)
|
|
786
|
+
return consume(s, lexer, valid, RANGE_OPERATOR, s->range_operator_end);
|
|
787
|
+
Item a = s->position < s->lower.end ? s->lower : s->upper;
|
|
788
|
+
return consume(
|
|
789
|
+
s,
|
|
790
|
+
lexer,
|
|
791
|
+
valid,
|
|
792
|
+
a.kind == SET_TEXT ? RANGE_CHARACTER : (enum Token)a.kind,
|
|
793
|
+
a.end
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
enum Token token = (enum Token)s->next;
|
|
797
|
+
if (token == CLASS_OPEN)
|
|
798
|
+
return unit_token(s, lexer, valid, COMPOUND_OPEN);
|
|
799
|
+
if (token == ESCAPE_PREFIX) {
|
|
800
|
+
s->next = s->prefix_kind;
|
|
801
|
+
return consume(s, lexer, valid, token, s->prefix_end);
|
|
802
|
+
}
|
|
803
|
+
s->component_start = token == PATH_SEPARATOR || token == NEGATIVE_PATTERN;
|
|
804
|
+
if (token == PATH_SEPARATOR)
|
|
805
|
+
s->after_separator = true;
|
|
806
|
+
return consume(s, lexer, valid, token, s->next_end);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
void *tree_sitter_gitattributes_external_scanner_create(void) {
|
|
810
|
+
return ts_calloc(1, sizeof(Scanner));
|
|
811
|
+
}
|
|
812
|
+
void tree_sitter_gitattributes_external_scanner_destroy(void *payload) {
|
|
813
|
+
ts_free(payload);
|
|
814
|
+
}
|
|
815
|
+
unsigned tree_sitter_gitattributes_external_scanner_serialize(
|
|
816
|
+
void *payload,
|
|
817
|
+
char *buffer
|
|
818
|
+
) {
|
|
819
|
+
memcpy(buffer, payload, sizeof(Scanner));
|
|
820
|
+
return sizeof(Scanner);
|
|
821
|
+
}
|
|
822
|
+
void tree_sitter_gitattributes_external_scanner_deserialize(
|
|
823
|
+
void *payload,
|
|
824
|
+
const char *buffer,
|
|
825
|
+
unsigned length
|
|
826
|
+
) {
|
|
827
|
+
memset(payload, 0, sizeof(Scanner));
|
|
828
|
+
if (length == sizeof(Scanner))
|
|
829
|
+
memcpy(payload, buffer, length);
|
|
830
|
+
}
|
|
831
|
+
bool tree_sitter_gitattributes_external_scanner_scan(
|
|
832
|
+
void *payload,
|
|
833
|
+
TSLexer *lexer,
|
|
834
|
+
const bool *valid
|
|
835
|
+
) {
|
|
836
|
+
Scanner next = *(Scanner *)payload;
|
|
837
|
+
if (!scan(&next, lexer, valid))
|
|
838
|
+
return false;
|
|
839
|
+
*(Scanner *)payload = next;
|
|
840
|
+
return true;
|
|
841
|
+
}
|