tree-sitter-delphi13 1.0.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/LICENSE +27 -0
- package/README.md +50 -0
- package/binding.gyp +21 -0
- package/bindings/node/binding.cc +19 -0
- package/bindings/node/binding_test.js +9 -0
- package/bindings/node/index.d.ts +28 -0
- package/bindings/node/index.js +7 -0
- package/grammar.js +1900 -0
- package/package.json +53 -0
- package/src/grammar.json +11992 -0
- package/src/node-types.json +10575 -0
- package/src/parser.c +504483 -0
- package/src/scanner.c +511 -0
- package/src/tree_sitter/alloc.h +54 -0
- package/src/tree_sitter/array.h +291 -0
- package/src/tree_sitter/parser.h +266 -0
package/src/scanner.c
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
// External scanner for tree-sitter-delphi13.
|
|
2
|
+
//
|
|
3
|
+
// Provides two external tokens that tree-sitter regex can't handle:
|
|
4
|
+
//
|
|
5
|
+
// pp_block Entire `{$IF*} ... {$END*}` block consumed as ONE token,
|
|
6
|
+
// EXCEPT when the body begins with a structural keyword
|
|
7
|
+
// (unit/program/library/package/interface/implementation),
|
|
8
|
+
// in which case the scanner refuses so the parser can see
|
|
9
|
+
// the wrapped declaration. Handles single-level (nested
|
|
10
|
+
// IFDEF pairs close at the first matching ENDIF — tracked
|
|
11
|
+
// with a depth counter so true nesting is supported).
|
|
12
|
+
//
|
|
13
|
+
// char_literal Delphi's `^X` control-character literal (`^M` = #13,
|
|
14
|
+
// `^I` = #9, etc.) where X is in `@A-Z[\]^_` and is NOT
|
|
15
|
+
// followed by an identifier-continuation char. The
|
|
16
|
+
// lookahead requirement disambiguates from `^TFoo`
|
|
17
|
+
// pointer-to-type.
|
|
18
|
+
//
|
|
19
|
+
// Contract (tree-sitter scanner):
|
|
20
|
+
// - Returning false rolls back the lexer to the position from BEFORE
|
|
21
|
+
// scan() was called. Free to probe via advance().
|
|
22
|
+
// - On true return, mark_end(lexer) determines token end.
|
|
23
|
+
|
|
24
|
+
#include "tree_sitter/parser.h"
|
|
25
|
+
#include <stdbool.h>
|
|
26
|
+
#include <stdint.h>
|
|
27
|
+
#include <ctype.h>
|
|
28
|
+
|
|
29
|
+
enum TokenType {
|
|
30
|
+
PP_BLOCK,
|
|
31
|
+
CHAR_LITERAL,
|
|
32
|
+
TRAILING_DOT_FLOAT, // `100.` form — float with trailing dot, no fraction.
|
|
33
|
+
// Distinct from regex `_literalFloat` because tree-sitter
|
|
34
|
+
// regex can't disambiguate `100.` (float) from `100..N`
|
|
35
|
+
// (int + range op). The scanner peeks the char after `.`
|
|
36
|
+
// and only emits if it isn't `.` or a digit.
|
|
37
|
+
|
|
38
|
+
// ---- THEN-wins refactor (DESIGN-ifdef-then-wins.md) ----
|
|
39
|
+
// Order MUST match grammar.js externals list.
|
|
40
|
+
// Phase 1: declared only; not yet emitted.
|
|
41
|
+
PP_OPEN, // {$IF X} / {$IFDEF X} / {$IFNDEF X} / {$IFOPT ...}
|
|
42
|
+
PP_ELSE_TAIL, // {$ELSE} ... opaque ... {$ENDIF | $IFEND}
|
|
43
|
+
PP_END_ONLY, // {$ENDIF} / {$IFEND} (no preceding else branch)
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
void *tree_sitter_delphi13_external_scanner_create(void) { return NULL; }
|
|
47
|
+
void tree_sitter_delphi13_external_scanner_destroy(void *p) { (void)p; }
|
|
48
|
+
unsigned tree_sitter_delphi13_external_scanner_serialize(void *p, char *b) {
|
|
49
|
+
(void)p; (void)b; return 0;
|
|
50
|
+
}
|
|
51
|
+
void tree_sitter_delphi13_external_scanner_deserialize(void *p, const char *b, unsigned n) {
|
|
52
|
+
(void)p; (void)b; (void)n;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- helpers ----
|
|
56
|
+
|
|
57
|
+
static inline bool is_ident_cont(int32_t c) {
|
|
58
|
+
return (c >= 'a' && c <= 'z')
|
|
59
|
+
|| (c >= 'A' && c <= 'Z')
|
|
60
|
+
|| (c >= '0' && c <= '9')
|
|
61
|
+
|| c == '_';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static inline int32_t to_lower(int32_t c) {
|
|
65
|
+
return (c >= 'A' && c <= 'Z') ? c + 32 : c;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
|
|
69
|
+
static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
|
|
70
|
+
|
|
71
|
+
// Try to consume the literal keyword `kw` (case-insensitive) from the current
|
|
72
|
+
// lookahead. On success the lookahead is past the keyword AND we've verified
|
|
73
|
+
// the next char is NOT an identifier-continuation char (so it's word-bounded).
|
|
74
|
+
// On failure, characters MAY have been consumed (caller handles rollback by
|
|
75
|
+
// returning false from scan()).
|
|
76
|
+
static bool try_consume_keyword(TSLexer *lexer, const char *kw) {
|
|
77
|
+
for (const char *p = kw; *p; p++) {
|
|
78
|
+
if (to_lower(lexer->lookahead) != *p) return false;
|
|
79
|
+
advance(lexer);
|
|
80
|
+
}
|
|
81
|
+
if (is_ident_cont(lexer->lookahead)) return false;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Look at the current lookahead and CONDITIONALLY consume a refused keyword.
|
|
86
|
+
// Returns true if any of these matches at a word boundary:
|
|
87
|
+
// unit / program / library / package / interface / implementation (structural)
|
|
88
|
+
// packed (type modifier;
|
|
89
|
+
// refused so `{$IFDEF X}packed{$ENDIF} record` parses as
|
|
90
|
+
// [pp]packed[pp] record rather than swallowing `packed` into the pp_block.)
|
|
91
|
+
//
|
|
92
|
+
// Hand-coded char-by-char prune: try_consume_keyword can't be cascaded for
|
|
93
|
+
// candidates that share a prefix (e.g. packed vs package both start with `pack`),
|
|
94
|
+
// because it consumes chars on failure without rolling back.
|
|
95
|
+
static bool starts_with_structural_keyword(TSLexer *lexer) {
|
|
96
|
+
int32_t la = to_lower(lexer->lookahead);
|
|
97
|
+
if (la == 'u') return try_consume_keyword(lexer, "unit");
|
|
98
|
+
if (la == 'l') return try_consume_keyword(lexer, "library");
|
|
99
|
+
if (la == 'p') {
|
|
100
|
+
advance(lexer); // consume 'p'
|
|
101
|
+
int32_t la2 = to_lower(lexer->lookahead);
|
|
102
|
+
if (la2 == 'r') return try_consume_keyword(lexer, "rogram");
|
|
103
|
+
if (la2 == 'a') {
|
|
104
|
+
// package OR packed — both start with 'pack', diverge at 5th char.
|
|
105
|
+
advance(lexer); if (to_lower(lexer->lookahead) != 'c') return false;
|
|
106
|
+
advance(lexer); if (to_lower(lexer->lookahead) != 'k') return false;
|
|
107
|
+
advance(lexer);
|
|
108
|
+
int32_t la5 = to_lower(lexer->lookahead);
|
|
109
|
+
if (la5 == 'e') {
|
|
110
|
+
advance(lexer); if (to_lower(lexer->lookahead) != 'd') return false;
|
|
111
|
+
advance(lexer);
|
|
112
|
+
if (is_ident_cont(lexer->lookahead)) return false;
|
|
113
|
+
return true; // packed
|
|
114
|
+
}
|
|
115
|
+
if (la5 == 'a') {
|
|
116
|
+
advance(lexer); if (to_lower(lexer->lookahead) != 'g') return false;
|
|
117
|
+
advance(lexer); if (to_lower(lexer->lookahead) != 'e') return false;
|
|
118
|
+
advance(lexer);
|
|
119
|
+
if (is_ident_cont(lexer->lookahead)) return false;
|
|
120
|
+
return true; // package
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (la == 'i') {
|
|
126
|
+
advance(lexer); // consume 'i'
|
|
127
|
+
int32_t la2 = to_lower(lexer->lookahead);
|
|
128
|
+
if (la2 == 'n') return try_consume_keyword(lexer, "nterface");
|
|
129
|
+
if (la2 == 'm') return try_consume_keyword(lexer, "mplementation");
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---- THEN-wins refactor (phase 2) ----
|
|
136
|
+
//
|
|
137
|
+
// The refactor reads THROUGH IFDEFs: directive markers become tokens (in
|
|
138
|
+
// `extras` from the parser's POV — like comments), THEN-branch content flows
|
|
139
|
+
// out as regular tokens, ELSE-branch is consumed as one opaque tail.
|
|
140
|
+
//
|
|
141
|
+
// See DESIGN-ifdef-then-wins.md for the full plan.
|
|
142
|
+
|
|
143
|
+
// Classify the directive starting at lookahead (which must be at the char
|
|
144
|
+
// just after `{$`). Consumes 0..N chars of lookahead while classifying.
|
|
145
|
+
// Caller MUST be inside a fresh scan() call (any consumed chars are rolled
|
|
146
|
+
// back by tree-sitter if the scan() returns false).
|
|
147
|
+
typedef enum {
|
|
148
|
+
DIR_OPEN, // if / ifdef / ifndef / ifopt / ifexists / ifabbrevdef ...
|
|
149
|
+
DIR_ELSE, // else / elseif
|
|
150
|
+
DIR_END, // endif / ifend
|
|
151
|
+
DIR_OTHER, // any other {$X} directive — not an if-block boundary
|
|
152
|
+
} DirectiveKind;
|
|
153
|
+
|
|
154
|
+
static DirectiveKind classify_directive(TSLexer *lexer) {
|
|
155
|
+
int32_t c1 = to_lower(lexer->lookahead);
|
|
156
|
+
if (c1 == 'i') {
|
|
157
|
+
advance(lexer);
|
|
158
|
+
if (to_lower(lexer->lookahead) != 'f') return DIR_OTHER;
|
|
159
|
+
advance(lexer);
|
|
160
|
+
// `if<X>`: if it's `ife` followed by `nd`, that's `ifend` (DIR_END).
|
|
161
|
+
// Otherwise it's an `if`-family opener (`if`, `ifdef`, `ifndef`,
|
|
162
|
+
// `ifopt`, `ifexists`, etc.) — DIR_OPEN.
|
|
163
|
+
if (to_lower(lexer->lookahead) == 'e') {
|
|
164
|
+
advance(lexer);
|
|
165
|
+
if (to_lower(lexer->lookahead) == 'n') {
|
|
166
|
+
advance(lexer);
|
|
167
|
+
if (to_lower(lexer->lookahead) == 'd') {
|
|
168
|
+
advance(lexer);
|
|
169
|
+
return DIR_END;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return DIR_OTHER;
|
|
173
|
+
}
|
|
174
|
+
return DIR_OPEN;
|
|
175
|
+
}
|
|
176
|
+
if (c1 == 'e') {
|
|
177
|
+
advance(lexer);
|
|
178
|
+
int32_t c2 = to_lower(lexer->lookahead);
|
|
179
|
+
if (c2 == 'l') {
|
|
180
|
+
// else / elseif
|
|
181
|
+
advance(lexer);
|
|
182
|
+
if (to_lower(lexer->lookahead) == 's') {
|
|
183
|
+
advance(lexer);
|
|
184
|
+
if (to_lower(lexer->lookahead) == 'e') {
|
|
185
|
+
advance(lexer);
|
|
186
|
+
return DIR_ELSE;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return DIR_OTHER;
|
|
190
|
+
}
|
|
191
|
+
if (c2 == 'n') {
|
|
192
|
+
// end / endif — both close the IFDEF
|
|
193
|
+
advance(lexer);
|
|
194
|
+
if (to_lower(lexer->lookahead) == 'd') {
|
|
195
|
+
advance(lexer);
|
|
196
|
+
return DIR_END;
|
|
197
|
+
}
|
|
198
|
+
return DIR_OTHER;
|
|
199
|
+
}
|
|
200
|
+
return DIR_OTHER;
|
|
201
|
+
}
|
|
202
|
+
return DIR_OTHER;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Consume the rest of the current directive up through the closing `}`.
|
|
206
|
+
// Called after classify_directive; lookahead is somewhere inside the
|
|
207
|
+
// directive body.
|
|
208
|
+
static bool consume_directive_to_close(TSLexer *lexer) {
|
|
209
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') {
|
|
210
|
+
advance(lexer);
|
|
211
|
+
}
|
|
212
|
+
if (lexer->lookahead != '}') return false;
|
|
213
|
+
advance(lexer);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Look at the upcoming directive WITHOUT advancing past `{$`. Used by
|
|
218
|
+
// pp_else_tail to decide whether to keep consuming (nested IFDEF) or stop
|
|
219
|
+
// (matched {$END*}). Returns DIR_* and ALWAYS advances past the `{$XXX`
|
|
220
|
+
// keyword portion of the directive (regardless of return value) — the
|
|
221
|
+
// caller then calls consume_directive_to_close() to finish.
|
|
222
|
+
static DirectiveKind peek_and_consume_directive_kw(TSLexer *lexer) {
|
|
223
|
+
if (lexer->lookahead != '{') return DIR_OTHER;
|
|
224
|
+
advance(lexer);
|
|
225
|
+
if (lexer->lookahead != '$') return DIR_OTHER;
|
|
226
|
+
advance(lexer);
|
|
227
|
+
return classify_directive(lexer);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ---- THEN-wins unified dispatch ----
|
|
231
|
+
//
|
|
232
|
+
// Single function that classifies the directive at lookahead, then commits
|
|
233
|
+
// to ONE of: PP_OPEN, PP_END_ONLY, PP_ELSE_TAIL (or returns false). Must be
|
|
234
|
+
// called from a fresh scan() — within a single scan() call we cannot roll
|
|
235
|
+
// back consumed lookahead, so the classification + commit must happen
|
|
236
|
+
// linearly.
|
|
237
|
+
//
|
|
238
|
+
// On entry: lookahead = '{', at least one of PP_OPEN / PP_END_ONLY /
|
|
239
|
+
// PP_ELSE_TAIL is in valid_symbols.
|
|
240
|
+
//
|
|
241
|
+
// Returns true and sets result_symbol on success.
|
|
242
|
+
static bool scan_pp_then_wins(TSLexer *lexer, const bool *valid_symbols) {
|
|
243
|
+
if (lexer->lookahead != '{') return false;
|
|
244
|
+
advance(lexer);
|
|
245
|
+
if (lexer->lookahead != '$') return false;
|
|
246
|
+
advance(lexer);
|
|
247
|
+
|
|
248
|
+
DirectiveKind k = classify_directive(lexer);
|
|
249
|
+
|
|
250
|
+
if (k == DIR_OPEN && valid_symbols[PP_OPEN]) {
|
|
251
|
+
if (!consume_directive_to_close(lexer)) return false;
|
|
252
|
+
lexer->result_symbol = PP_OPEN;
|
|
253
|
+
lexer->mark_end(lexer);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (k == DIR_END && valid_symbols[PP_END_ONLY]) {
|
|
258
|
+
if (!consume_directive_to_close(lexer)) return false;
|
|
259
|
+
lexer->result_symbol = PP_END_ONLY;
|
|
260
|
+
lexer->mark_end(lexer);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (k == DIR_ELSE && valid_symbols[PP_ELSE_TAIL]) {
|
|
265
|
+
if (!consume_directive_to_close(lexer)) return false;
|
|
266
|
+
// Consume opaque ELSE body up to the matching {$END*}, depth-tracking
|
|
267
|
+
// nested {$IF*}/{$END*} pairs.
|
|
268
|
+
int depth = 1;
|
|
269
|
+
int safety = 8 * 1024 * 1024;
|
|
270
|
+
while (lexer->lookahead != 0 && safety-- > 0) {
|
|
271
|
+
if (lexer->lookahead != '{') { advance(lexer); continue; }
|
|
272
|
+
DirectiveKind dk = peek_and_consume_directive_kw(lexer);
|
|
273
|
+
if (dk == DIR_OPEN) {
|
|
274
|
+
consume_directive_to_close(lexer);
|
|
275
|
+
depth++;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (dk == DIR_END) {
|
|
279
|
+
consume_directive_to_close(lexer);
|
|
280
|
+
if (--depth == 0) {
|
|
281
|
+
lexer->result_symbol = PP_ELSE_TAIL;
|
|
282
|
+
lexer->mark_end(lexer);
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
// DIR_ELSE inside else body or DIR_OTHER — just skip the directive.
|
|
288
|
+
consume_directive_to_close(lexer);
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Directive doesn't match any requested token type.
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---- pp_block ----
|
|
298
|
+
//
|
|
299
|
+
// On entry: lookahead = '{'. Try to consume the entire `{$IF*}...{$END*}`
|
|
300
|
+
// block as one token. Returns true on success.
|
|
301
|
+
static bool scan_pp_block(TSLexer *lexer) {
|
|
302
|
+
// Opening `{$if...` check.
|
|
303
|
+
if (lexer->lookahead != '{') return false;
|
|
304
|
+
advance(lexer);
|
|
305
|
+
if (lexer->lookahead != '$') return false;
|
|
306
|
+
advance(lexer);
|
|
307
|
+
if (to_lower(lexer->lookahead) != 'i') return false;
|
|
308
|
+
advance(lexer);
|
|
309
|
+
if (to_lower(lexer->lookahead) != 'f') return false;
|
|
310
|
+
advance(lexer);
|
|
311
|
+
|
|
312
|
+
// Consume rest of opening directive up to '}'.
|
|
313
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') {
|
|
314
|
+
advance(lexer);
|
|
315
|
+
}
|
|
316
|
+
if (lexer->lookahead != '}') return false;
|
|
317
|
+
advance(lexer);
|
|
318
|
+
|
|
319
|
+
// Skip whitespace.
|
|
320
|
+
while (lexer->lookahead == ' ' || lexer->lookahead == '\t'
|
|
321
|
+
|| lexer->lookahead == '\n' || lexer->lookahead == '\r') {
|
|
322
|
+
advance(lexer);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Refuse if body begins with a structural keyword — let the parser see
|
|
326
|
+
// the wrapped declaration via the regex-pp fallback.
|
|
327
|
+
if (starts_with_structural_keyword(lexer)) return false;
|
|
328
|
+
|
|
329
|
+
// Scan body, tracking nested {$if*}/{$end*}/{$ifend*} pairs.
|
|
330
|
+
int depth = 1;
|
|
331
|
+
int safety = 8 * 1024 * 1024; // 8 MiB hard cap
|
|
332
|
+
while (lexer->lookahead != 0 && safety-- > 0) {
|
|
333
|
+
if (lexer->lookahead != '{') { advance(lexer); continue; }
|
|
334
|
+
advance(lexer);
|
|
335
|
+
if (lexer->lookahead != '$') continue;
|
|
336
|
+
advance(lexer);
|
|
337
|
+
|
|
338
|
+
int32_t c1 = to_lower(lexer->lookahead);
|
|
339
|
+
if (c1 == 'i') {
|
|
340
|
+
advance(lexer);
|
|
341
|
+
int32_t c2 = to_lower(lexer->lookahead);
|
|
342
|
+
if (c2 == 'f') {
|
|
343
|
+
advance(lexer);
|
|
344
|
+
// Could be `{$if...}` (open, depth++) or `{$ifend...}` (close).
|
|
345
|
+
int32_t c3 = to_lower(lexer->lookahead);
|
|
346
|
+
if (c3 == 'e') {
|
|
347
|
+
advance(lexer);
|
|
348
|
+
if (to_lower(lexer->lookahead) == 'n') {
|
|
349
|
+
advance(lexer);
|
|
350
|
+
if (to_lower(lexer->lookahead) == 'd') {
|
|
351
|
+
advance(lexer);
|
|
352
|
+
// {$ifend...} — consume to '}', decrement.
|
|
353
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') advance(lexer);
|
|
354
|
+
if (lexer->lookahead != '}') return false;
|
|
355
|
+
advance(lexer);
|
|
356
|
+
if (--depth == 0) {
|
|
357
|
+
lexer->result_symbol = PP_BLOCK;
|
|
358
|
+
lexer->mark_end(lexer);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// {$ife...} but not ifend; treat as a stray directive,
|
|
365
|
+
// consume to '}' and don't change depth.
|
|
366
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') advance(lexer);
|
|
367
|
+
if (lexer->lookahead == '}') advance(lexer);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
// {$if...} — open another nesting level.
|
|
371
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') advance(lexer);
|
|
372
|
+
if (lexer->lookahead != '}') return false;
|
|
373
|
+
advance(lexer);
|
|
374
|
+
depth++;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
// {$i...} but not {$if...} — some other directive.
|
|
378
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') advance(lexer);
|
|
379
|
+
if (lexer->lookahead == '}') advance(lexer);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (c1 == 'e') {
|
|
383
|
+
advance(lexer);
|
|
384
|
+
if (to_lower(lexer->lookahead) == 'n') {
|
|
385
|
+
advance(lexer);
|
|
386
|
+
if (to_lower(lexer->lookahead) == 'd') {
|
|
387
|
+
advance(lexer);
|
|
388
|
+
// {$end...} — consume to '}', decrement depth.
|
|
389
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') advance(lexer);
|
|
390
|
+
if (lexer->lookahead != '}') return false;
|
|
391
|
+
advance(lexer);
|
|
392
|
+
if (--depth == 0) {
|
|
393
|
+
lexer->result_symbol = PP_BLOCK;
|
|
394
|
+
lexer->mark_end(lexer);
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// {$e...} but not {$end} — other directive, consume.
|
|
401
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') advance(lexer);
|
|
402
|
+
if (lexer->lookahead == '}') advance(lexer);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
// Some other {$X...} directive — consume.
|
|
406
|
+
while (lexer->lookahead != 0 && lexer->lookahead != '}') advance(lexer);
|
|
407
|
+
if (lexer->lookahead == '}') advance(lexer);
|
|
408
|
+
}
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ---- char_literal ----
|
|
413
|
+
//
|
|
414
|
+
// `^X` where X is in @A-Z[\]^_ (case-insensitive) and is NOT followed by an
|
|
415
|
+
// identifier-continuation character (so `^Tx` parses as kHat + identifier,
|
|
416
|
+
// while `^M:` parses as char_literal then ':').
|
|
417
|
+
static bool scan_char_literal(TSLexer *lexer) {
|
|
418
|
+
if (lexer->lookahead != '^') return false;
|
|
419
|
+
advance(lexer);
|
|
420
|
+
int32_t c = lexer->lookahead;
|
|
421
|
+
// Accept @ A-Z [ \ ] ^ _ and lowercase variants.
|
|
422
|
+
bool valid_char =
|
|
423
|
+
c == '@' || c == '[' || c == '\\' || c == ']' ||
|
|
424
|
+
c == '^' || c == '_' ||
|
|
425
|
+
(c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
|
426
|
+
if (!valid_char) return false;
|
|
427
|
+
advance(lexer);
|
|
428
|
+
// Critical: the NEXT char must not be an identifier-continuation char.
|
|
429
|
+
// Otherwise this is `^TFoo` pointer-to-type, not a char literal.
|
|
430
|
+
if (is_ident_cont(lexer->lookahead)) return false;
|
|
431
|
+
lexer->result_symbol = CHAR_LITERAL;
|
|
432
|
+
lexer->mark_end(lexer);
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ---- trailing_dot_float ----
|
|
437
|
+
//
|
|
438
|
+
// `123.` style float literal — digits followed by a dot followed by ANYTHING
|
|
439
|
+
// other than a dot or digit. Disambiguates from `123..N` (int + range op)
|
|
440
|
+
// because tree-sitter regex can't peek-without-consuming.
|
|
441
|
+
//
|
|
442
|
+
// Examples:
|
|
443
|
+
// 100. * x -> trailing_dot_float
|
|
444
|
+
// 6. ; -> trailing_dot_float
|
|
445
|
+
// 100..200 -> NOT matched (followed by another `.`)
|
|
446
|
+
// 100.5 -> NOT matched (followed by digit)
|
|
447
|
+
static bool scan_trailing_dot_float(TSLexer *lexer) {
|
|
448
|
+
// Must start with a digit.
|
|
449
|
+
if (lexer->lookahead < '0' || lexer->lookahead > '9') return false;
|
|
450
|
+
// Consume all leading digits.
|
|
451
|
+
while (lexer->lookahead >= '0' && lexer->lookahead <= '9') {
|
|
452
|
+
advance(lexer);
|
|
453
|
+
}
|
|
454
|
+
// Must see `.`
|
|
455
|
+
if (lexer->lookahead != '.') return false;
|
|
456
|
+
advance(lexer);
|
|
457
|
+
// The next char must NOT be `.` (range) or a digit (regular float `.N`).
|
|
458
|
+
if (lexer->lookahead == '.') return false;
|
|
459
|
+
if (lexer->lookahead >= '0' && lexer->lookahead <= '9') return false;
|
|
460
|
+
// Confirmed trailing-dot float.
|
|
461
|
+
lexer->result_symbol = TRAILING_DOT_FLOAT;
|
|
462
|
+
lexer->mark_end(lexer);
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ---- entry point ----
|
|
467
|
+
|
|
468
|
+
bool tree_sitter_delphi13_external_scanner_scan(
|
|
469
|
+
void *payload, TSLexer *lexer, const bool *valid_symbols)
|
|
470
|
+
{
|
|
471
|
+
(void)payload;
|
|
472
|
+
|
|
473
|
+
// Skip leading whitespace before trying any external token.
|
|
474
|
+
while (lexer->lookahead == ' ' || lexer->lookahead == '\t'
|
|
475
|
+
|| lexer->lookahead == '\n' || lexer->lookahead == '\r') {
|
|
476
|
+
skip(lexer);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// THEN-wins refactor: unified dispatch on `{$...` directives.
|
|
480
|
+
//
|
|
481
|
+
// Single classification step then commits to one of PP_OPEN /
|
|
482
|
+
// PP_END_ONLY / PP_ELSE_TAIL. If none of those are valid (or the
|
|
483
|
+
// directive shape doesn't match), falls back to legacy PP_BLOCK
|
|
484
|
+
// — which is itself dead code once the new tokens are in extras,
|
|
485
|
+
// but kept as belt-and-braces for now.
|
|
486
|
+
if (lexer->lookahead == '{') {
|
|
487
|
+
bool new_token_valid = valid_symbols[PP_OPEN]
|
|
488
|
+
|| valid_symbols[PP_END_ONLY]
|
|
489
|
+
|| valid_symbols[PP_ELSE_TAIL];
|
|
490
|
+
if (new_token_valid) {
|
|
491
|
+
if (scan_pp_then_wins(lexer, valid_symbols)) return true;
|
|
492
|
+
// If dispatch returned false, the lexer state is undefined
|
|
493
|
+
// (we may have consumed chars). Tree-sitter rolls back on
|
|
494
|
+
// false-return, so we cannot then try PP_BLOCK in this call.
|
|
495
|
+
// That's fine: in the new model PP_BLOCK is never the
|
|
496
|
+
// expected token when the new ones are valid.
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
if (valid_symbols[PP_BLOCK]) {
|
|
500
|
+
if (scan_pp_block(lexer)) return true;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (valid_symbols[CHAR_LITERAL] && lexer->lookahead == '^') {
|
|
504
|
+
if (scan_char_literal(lexer)) return true;
|
|
505
|
+
}
|
|
506
|
+
if (valid_symbols[TRAILING_DOT_FLOAT] &&
|
|
507
|
+
lexer->lookahead >= '0' && lexer->lookahead <= '9') {
|
|
508
|
+
if (scan_trailing_dot_float(lexer)) return true;
|
|
509
|
+
}
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#ifndef TREE_SITTER_ALLOC_H_
|
|
2
|
+
#define TREE_SITTER_ALLOC_H_
|
|
3
|
+
|
|
4
|
+
#ifdef __cplusplus
|
|
5
|
+
extern "C" {
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
#include <stdbool.h>
|
|
9
|
+
#include <stdio.h>
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
|
|
12
|
+
// Allow clients to override allocation functions
|
|
13
|
+
#ifdef TREE_SITTER_REUSE_ALLOCATOR
|
|
14
|
+
|
|
15
|
+
extern void *(*ts_current_malloc)(size_t size);
|
|
16
|
+
extern void *(*ts_current_calloc)(size_t count, size_t size);
|
|
17
|
+
extern void *(*ts_current_realloc)(void *ptr, size_t size);
|
|
18
|
+
extern void (*ts_current_free)(void *ptr);
|
|
19
|
+
|
|
20
|
+
#ifndef ts_malloc
|
|
21
|
+
#define ts_malloc ts_current_malloc
|
|
22
|
+
#endif
|
|
23
|
+
#ifndef ts_calloc
|
|
24
|
+
#define ts_calloc ts_current_calloc
|
|
25
|
+
#endif
|
|
26
|
+
#ifndef ts_realloc
|
|
27
|
+
#define ts_realloc ts_current_realloc
|
|
28
|
+
#endif
|
|
29
|
+
#ifndef ts_free
|
|
30
|
+
#define ts_free ts_current_free
|
|
31
|
+
#endif
|
|
32
|
+
|
|
33
|
+
#else
|
|
34
|
+
|
|
35
|
+
#ifndef ts_malloc
|
|
36
|
+
#define ts_malloc malloc
|
|
37
|
+
#endif
|
|
38
|
+
#ifndef ts_calloc
|
|
39
|
+
#define ts_calloc calloc
|
|
40
|
+
#endif
|
|
41
|
+
#ifndef ts_realloc
|
|
42
|
+
#define ts_realloc realloc
|
|
43
|
+
#endif
|
|
44
|
+
#ifndef ts_free
|
|
45
|
+
#define ts_free free
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
#ifdef __cplusplus
|
|
51
|
+
}
|
|
52
|
+
#endif
|
|
53
|
+
|
|
54
|
+
#endif // TREE_SITTER_ALLOC_H_
|