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/grammar.js ADDED
@@ -0,0 +1,1900 @@
1
+ // Feature flags — locked to Delphi 13 only.
2
+ // (Originally tree-sitter-pascal had these toggleable; here they're
3
+ // constants set to the values that match modern Delphi 13.)
4
+ const public_name = false; // fpc 'public name' hint — Delphi has different syntax
5
+ const rtti = true; // [Attribute(...)] declarations
6
+ const lambda = true; // anonymous procedures/functions
7
+ const fpc = false; // no FPC-only syntax
8
+ const delphi = true; // Delphi syntax
9
+ const objc = false; // no PasCocoa
10
+ const templates = true; // generic types
11
+ const use_pp = true; // preprocessor handling
12
+
13
+ // Helpers
14
+
15
+ const op = {
16
+ infix: (prio, lhs, op, rhs) => prec.left(prio, seq(
17
+ field('lhs', lhs),
18
+ field('operator', op),
19
+ field('rhs', rhs)
20
+ )),
21
+ prefix: (prio, operator, operand) => prec.left(prio, seq(
22
+ field('operator', operator),
23
+ field('operand', operand)
24
+ )),
25
+ postfix: (prio, operand, operator) => prec.left(prio, seq(
26
+ field('operand', operand),
27
+ field('operator', operator)
28
+ )),
29
+
30
+ args: (prio, entity, open, args, close) => prec.left(prio, seq(
31
+ field('entity', entity), open, field('args', args), close
32
+ ))
33
+ }
34
+
35
+ function delimited1(rule, delimiter = ',', precedence=0) {
36
+ return seq(
37
+ optional(repeat1(prec(precedence,seq(rule, delimiter)))),
38
+ rule
39
+ );
40
+ }
41
+
42
+ function delimited(rule, delimiter = ',') {
43
+ return optional(delimited1(rule, delimiter));
44
+ }
45
+
46
+ // Preprocessor wrapper.
47
+ // This just supports a single `if[def] ... [else[if] ...]* endif` right now.
48
+ // It is inteded for code like this:
49
+ //
50
+ // procedure foo;
51
+ // {$ifdef bla}
52
+ // var i: integer;
53
+ // begin
54
+ // inc(i);
55
+ // end;
56
+ // {$else}
57
+ // var j: integer;
58
+ // begin
59
+ // dec(j);
60
+ // end;
61
+ // {$endif}
62
+ //
63
+ // If we don't handle this case explicitly, tree-sitter produces a completely
64
+ // broken AST, which severely messes up the syntax highlighting.
65
+ //
66
+ // Ideally, we would want to support nested ifdefs as well, but that will be
67
+ // more complex.
68
+ //
69
+ // A word of caution: It is tempting to sprinkle this macro in many more
70
+ // places, but unfortunately tihs results in a significant performance penalty.
71
+ // Use it sparingly! A general rule of thumb is to use it only in situations
72
+ // where otherwise a severly broken parse tree would be generated. For small
73
+ // errors that TreeSitter can recover from automatically, it is better not to
74
+ // use it.
75
+ function pp($, ...rule) {
76
+ if (!use_pp)
77
+ return seq(...rule);
78
+ return (
79
+ choice(
80
+ seq(...rule),
81
+ seq(
82
+ alias(/\{\$if[^}]*\}/i, $.pp),
83
+ ...rule,
84
+ repeat(seq(
85
+ alias(/\{\$else[^}]*\}/i, $.pp),
86
+ ...rule
87
+ )),
88
+ alias(/\{\$end[^}]*\}/i, $.pp)
89
+ ),
90
+ )
91
+ );
92
+ }
93
+
94
+ // tr = Trailing
95
+ // Return the trailing equivalent of a rule, aliased to the non-trailing version.
96
+ const tr = ($,rule) =>
97
+ rule[0] == '_' ? $[rule+'Tr'] : alias($[rule+'Tr'], $[rule])
98
+
99
+
100
+ function enable_if(cond, ...args) {
101
+ return cond ? args : [];
102
+ }
103
+
104
+ // Generate rules for trailing & non-trailing statements
105
+ function statements(trailing) {
106
+ let rn = x => trailing ? x + 'Tr' : x
107
+ let lastStatement = $ => trailing ? optional(tr($,'_statement')) : $._statement;
108
+ let lastStatement1= $ => trailing ? tr($,'_statement') : $._statement;
109
+ let semicolon = trailing ? [] : [';'];
110
+
111
+ return Object.fromEntries([
112
+ [rn('if'), $ => seq(
113
+ $.kIf, field('condition', $._expr), $.kThen,
114
+ field('then', lastStatement($))
115
+ )],
116
+
117
+ [rn('nestedIf'), $ => prec(1,$.if)],
118
+
119
+ [rn('ifElse'), $ => prec.right(1, seq(
120
+ $.kIf, field('condition', $._expr), $.kThen,
121
+ field('then', optional(choice(tr($,'_statement'), $.if))),
122
+ $.kElse,
123
+ field('else', lastStatement($))
124
+ ))],
125
+
126
+ [rn('while'), $ => seq(
127
+ $.kWhile, field('condition', $._expr), $.kDo,
128
+ field('body', lastStatement($))
129
+ )],
130
+
131
+ [rn('repeat'), $ => prec(2,seq(
132
+ $.kRepeat,
133
+ field('body', optional(tr($,'statements'))),
134
+ $.kUntil, field('condition', $._expr),
135
+ ...semicolon
136
+ ))],
137
+
138
+ [rn('for'), $ => seq(
139
+ $.kFor,
140
+ field('start', $.assignment),
141
+ choice($.kTo, $.kDownto),
142
+ field('end', $._expr), $.kDo,
143
+ field('body', lastStatement($))
144
+ )],
145
+
146
+ [rn('foreach'), $ => seq(
147
+ $.kFor,
148
+ // Iterator can be an existing variable OR a Delphi 12+ inline-var
149
+ // declaration: `for var fname in List do`. The varAssignDef rule
150
+ // already handles `var x` and `var x: T`.
151
+ field('iterator', choice($._expr, $.varAssignDef)), $.kIn,
152
+ field('iterable', $._expr), $.kDo,
153
+ field('body', lastStatement($))
154
+ )],
155
+
156
+ [rn('exceptionHandler'), $ => seq(
157
+ $.kOn,
158
+ field('variable', optional(seq($.identifier, ':'))),
159
+ field('exception', $.typeref), $.kDo,
160
+ field('body', lastStatement($))
161
+ )],
162
+
163
+ [rn('exceptionElse'), $ => seq(
164
+ $.kElse, repeat($._statement), lastStatement($)
165
+ )],
166
+
167
+ [rn('_exceptionHandlers'), $ => seq(
168
+ repeat($.exceptionHandler),
169
+ choice($.exceptionHandler, tr($,'exceptionHandler')),
170
+ // exceptionElse's body is always followed by `end` (of try), so
171
+ // allow the trailing-form (last statement may omit `;`).
172
+ optional(choice($.exceptionElse, tr($,'exceptionElse')))
173
+ )],
174
+
175
+ [rn('try'), $ => prec(2,seq(
176
+ $.kTry,
177
+ field('try', optional(tr($,'statements'))),
178
+ choice(
179
+ field('except', seq(
180
+ $.kExcept,
181
+ optional(
182
+ choice(tr($,'statements'),
183
+ tr($,'_exceptionHandlers'))
184
+ )
185
+ )),
186
+ field('finally', seq(
187
+ $.kFinally,
188
+ optional(tr($,'statements'))
189
+ ))
190
+ ),
191
+ $.kEnd, ...semicolon
192
+ ))],
193
+
194
+ [rn('caseCase'), $ => seq(
195
+ field('label', $.caseLabel),
196
+ // Optional Pascal `label:` prefix before the body. Spring4D's
197
+ // enumerator state-machines use this to expose goto targets
198
+ // from inside a case clause:
199
+ // STATE_RUNNING:
200
+ // _STATE_RUNNING: // pascal label
201
+ // begin ... end;
202
+ field('jumpLabel', optional($.label)),
203
+ field('body', lastStatement($))
204
+ )],
205
+
206
+ [rn('case'), $ => prec(2,seq(
207
+ $.kCase, $._expr, $.kOf,
208
+ repeat($.caseCase),
209
+ optional(tr($,'caseCase')),
210
+ optional(seq(
211
+ $.kElse,
212
+ optional(':'),
213
+ optional(tr($,'_statements'))
214
+ )),
215
+ $.kEnd, ...semicolon
216
+ ))],
217
+
218
+ [rn('block'), $ => seq(
219
+ $.kBegin,
220
+ optional(tr($,'_statements')),
221
+ $.kEnd, ...semicolon
222
+ )],
223
+
224
+ [rn('asm'), $ => seq(
225
+ $.kAsm,
226
+ optional($.asmBody),
227
+ $.kEnd, ...semicolon
228
+ )],
229
+
230
+ [rn('with'), $ => seq(
231
+ $.kWith, delimited1(field('entity', $._expr)), $.kDo,
232
+ field('body', lastStatement($))
233
+ )],
234
+
235
+ [rn('raise'), $ => seq(
236
+ $.kRaise,
237
+ field('exception', optional($._expr)),
238
+ // Optional `at <addr-expr>` — Delphi exception re-raise with a
239
+ // specific call-site address: `raise EFoo.Create(msg) at ReturnAddress`.
240
+ optional(seq($.kAtWord, field('address', $._expr))),
241
+ ...semicolon
242
+ )],
243
+
244
+ [rn('statement'), $ => choice(
245
+ seq($._expr, ...semicolon),
246
+ )],
247
+
248
+ [rn('goto'), $ => seq($.kGoto, $.identifier, ...semicolon)],
249
+
250
+ [rn('_statement'), $ => choice(
251
+ ...semicolon,
252
+ seq($.assignment, ...semicolon),
253
+ seq($.varDef, ...semicolon),
254
+ // Delphi 12+ inline `const NAME [: T] = value;` inside statement body.
255
+ seq($.constInline, ...semicolon),
256
+ alias($[rn('statement')], $.statement),
257
+ alias($[rn('if')], $.if),
258
+ alias($[rn('ifElse')], $.ifElse),
259
+ alias($[rn('while')], $.while),
260
+ alias($[rn('repeat')], $.repeat),
261
+ alias($[rn('for')], $.for),
262
+ alias($[rn('foreach')], $.foreach),
263
+ alias($[rn('try')], $.try),
264
+ alias($[rn('case')], $.case),
265
+ alias($[rn('block')], $.block),
266
+ alias($[rn('with')], $.with),
267
+ alias($[rn('raise')], $.raise),
268
+ alias($[rn('goto')], $.goto),
269
+ alias($[rn('asm')], $.asm),
270
+ )],
271
+
272
+ ]);
273
+ }
274
+
275
+ module.exports = grammar({
276
+ name: "delphi13",
277
+
278
+ extras: $ => [
279
+ $._space,
280
+ $.comment,
281
+ $.pp,
282
+ $.pp_block,
283
+ // THEN-wins refactor (phase 2): directive markers as extras so the
284
+ // THEN-body parses normally and the ELSE-tail is consumed opaquely.
285
+ $.pp_open,
286
+ $.pp_else_tail,
287
+ $.pp_end_only,
288
+ ],
289
+
290
+ externals: $ => [
291
+ // Multi-line `{$IF*} ... {$END*}` block consumed wholesale by the
292
+ // external scanner so the parser skips over disabled-branch content
293
+ // (free text, unexpanded macros, platform-gated alternatives).
294
+ // Scanner refuses blocks whose body starts with a structural keyword
295
+ // (unit/program/library/package/interface/implementation) so it
296
+ // doesn't swallow `{$IFNDEF X} unit Y; {$ENDIF}` patterns.
297
+ // Phase 4 of refactor/ifdef-then-wins will drop this in favor of
298
+ // the THEN-wins token set below.
299
+ $.pp_block,
300
+ // Context-aware `^X` control-char literal — matches `^` followed by
301
+ // one of @A-Z[\]^_ and NOT followed by an identifier character.
302
+ // Disambiguates from `^TFoo` pointer-to-type.
303
+ $.char_literal,
304
+ // Trailing-dot float literal `100.` — disambiguated from `100..N`
305
+ // (int + range op) by scanner-level lookahead at the char after `.`.
306
+ $.trailing_dot_float,
307
+
308
+ // ---- THEN-wins refactor (DESIGN-ifdef-then-wins.md) ----
309
+ // These three tokens let the scanner read THROUGH IFDEF directives:
310
+ // the THEN-branch content flows out as regular tokens, the ELSE
311
+ // branch is consumed as one opaque tail, and the directive markers
312
+ // themselves act as whitespace-like extras.
313
+ //
314
+ // Phase 1: declared only — scanner emits them only when valid_symbols
315
+ // asks, and grammar does not put them in extras yet. ABI-shift only;
316
+ // no behavioral change vs. master.
317
+ $.pp_open, // {$IF X} / {$IFDEF X} / {$IFNDEF X} / {$IFOPT ...}
318
+ $.pp_else_tail, // {$ELSE} ... opaque ... {$ENDIF | $IFEND}
319
+ $.pp_end_only, // {$ENDIF} / {$IFEND} (no preceding else branch)
320
+ ],
321
+
322
+ word: $ => $.identifier,
323
+
324
+ conflicts: $ => [
325
+ // Anonymous record/class types in type position conflict with the
326
+ // type-declaration form (`type Foo = record ... end;`). Both reach
327
+ // declClass but at different parser states.
328
+ [$.type, $.declType],
329
+ // Inline if-expression vs if-statement: both start with `if cond then`.
330
+ // At a position where both are valid (e.g. RHS of `x := if ...`), let
331
+ // tree-sitter GLR-fork and decide via context.
332
+ [$.exprIf, $.statementTr],
333
+ // try-except `else` last-statement: trailing form lets last statement
334
+ // omit `;` since `end` follows; ambiguous with non-trailing repeat
335
+ // (both reach `_statement` before `end`). GLR resolves at parse time.
336
+ [$.exceptionElse, $.exceptionElseTr],
337
+ [$.exceptionElse],
338
+ // Soft-keyword identifiers (Message, Name, Index, Read, Write, Reference)
339
+ // allowed as variable names — ambiguous with next-statement starts.
340
+ [$.declVar],
341
+ // Same alias set on const names (`Default = $00010203;` etc.)
342
+ [$.declConst],
343
+ // Qualified-id subrange `TFoo.Bar..TFoo.Baz` vs `_typeref` (typerefDot).
344
+ [$._typeref, $._subrangeBound],
345
+ // Iter 58: exprTpl args narrowed to typeref — generic-arg identifier
346
+ // vs general expression-ref identifier conflict.
347
+ [$._ref, $._typeref],
348
+ // Iter 61: declField has optional declaration hint AND typeref allows
349
+ // trailing kDeprecated. Ambiguous which level the `deprecated` belongs.
350
+ [$.typeref],
351
+ [$._declClass],
352
+ // Iter 65: trailing `inline` (no `;`) before body in defProc conflicts
353
+ // with `_declProc`'s `inline ;` _procAttributeNoExt repeat. Same for
354
+ // _declOperator (class operators can also be inlined).
355
+ [$._declProc],
356
+ [$._declOperator],
357
+ // Phase 3b iter 7: declEnum trailing platform/deprecated hint is
358
+ // ambiguous with the same hint appearing on the enclosing declType.
359
+ [$.declEnum],
360
+ // Phase 3b iter 21: declSet has same trailing-hint ambiguity.
361
+ [$.declSet],
362
+ // Phase 3b iter 10: _declFields choice of repeat1(declField) vs
363
+ // trailing declFieldNoSemi — both reach declField from the same state.
364
+ [$._declFields],
365
+ [$.declClass],
366
+ // Phase 3b iter 11: declFieldNoSemi uses a narrower type set; conflicts
367
+ // with the full `type` rule's choices.
368
+ [$.type, $.declFieldNoSemi],
369
+ // The following conflict rules are only needed because "public" can be
370
+ // a visibility or an attribute. *sigh*
371
+ // TODO: We would probably avoid this by having separate decl* clauses
372
+ // for use inside classes and at unit scope, since the "public"
373
+ // attribute seems to only be valid for standalone routines.
374
+ ...enable_if(public_name,
375
+ [$._declProc ], [ $._declOperator], [$.declConst], [$.declVar],
376
+ [$.declType], [$.declProp]
377
+ ),
378
+ // RTTI attributes clash with fpc declaration hints syntax since both
379
+ // are surrounded by brackets.
380
+ ...enable_if(rtti,
381
+ [ $.declProcFwd ], [ $.declVars], [ $.declConsts ], [ $.declTypes]
382
+ ),
383
+ // `procedure (` could be a declaration of an anonymous procedure or
384
+ // the call of a function named "procedure" (which doesn't actually
385
+ // make sense, but for Treesitter it does), so we need another conflict
386
+ // here.
387
+ //...enable_if(lambda, [ $.lambda ]),
388
+ ],
389
+
390
+ rules: {
391
+ root: $ => optional(choice(
392
+ $.program,
393
+ $.library,
394
+ $.unit,
395
+ $.package,
396
+ $._definitions // For include files
397
+ )),
398
+
399
+ // HIGH LEVEL ----------------------------------------------------------
400
+
401
+ program: $ => seq(
402
+ $.kProgram, $.moduleName, ';',
403
+ optional($._definitions),
404
+ // Phase 3b iter 34: CIL/.NET attribute on program entry (`[STAThread]`).
405
+ // (UnitTests4Net.dpr Delphi.NET pattern.)
406
+ optional($.rttiAttributes),
407
+ tr($,'block'),
408
+ $.kEndDot
409
+ ),
410
+
411
+ library: $ => seq(
412
+ $.kLibrary, $.moduleName, ';',
413
+ optional($._definitions),
414
+ choice(tr($,'block'), $.kEnd),
415
+ $.kEndDot
416
+ ),
417
+
418
+ unit: $ => seq(
419
+ $.kUnit, $.moduleName,
420
+ // `unit Spring.Foo deprecated 'Use Spring.Bar instead';` — unit-level
421
+ // deprecation hint (Spring4D pattern in retired units).
422
+ optional(seq($.kDeprecated, optional($._expr))),
423
+ ';',
424
+ repeat(choice(
425
+ $.interface,
426
+ $.implementation,
427
+ $.initialization,
428
+ $.finalization,
429
+ )),
430
+ $.kEnd, $.kEndDot
431
+ ),
432
+
433
+ // Delphi package file (.dpk). Structure:
434
+ // package <Ident>;
435
+ // <compiler directives / $I includes>
436
+ // [requires <pkgList> ;]
437
+ // [contains <unitList> ;] -- units may carry in 'path.pas'
438
+ // end .
439
+ package: $ => seq(
440
+ $.kPackage, $.moduleName, ';',
441
+ optional($._definitions),
442
+ optional($.declRequires),
443
+ optional($.declContains),
444
+ $.kEnd, $.kEndDot
445
+ ),
446
+
447
+ interface: $ => seq($.kInterface, optional($._declarations)),
448
+ implementation: $ => seq($.kImplementation, optional($._definitions)),
449
+ initialization: $ => seq($.kInitialization, optional(tr($,'_statements'))),
450
+ finalization: $ => seq($.kFinalization, optional(tr($,'_statements'))),
451
+
452
+ moduleName: $ => delimited1($.identifier, $.kDot),
453
+
454
+ // STATEMENTS ---------------------------------------------------------
455
+
456
+ ...statements(false),
457
+ ...statements(true),
458
+
459
+ assignment: $ => op.infix(1,
460
+ choice($._expr, $.varAssignDef),
461
+ choice(
462
+ $.kAssign,
463
+ // Phase 3b iter 29: compound assignment `+=`/`-=`/`*=`/`/=`.
464
+ // FPC has always supported these; Delphi accepts them when
465
+ // `{$EXTENDEDCOMPATIBILITY}` directive is on (since 2005).
466
+ // FPC-corpus files (lazutils) use them heavily.
467
+ $.kAssignAdd, $.kAssignSub, $.kAssignMul, $.kAssignDiv,
468
+ ),
469
+ $._expr
470
+ ),
471
+ varAssignDef: $ => seq($.kVar, $.identifier,
472
+ optional(seq(
473
+ ':',
474
+ field('type', $.typeref)
475
+ ))),
476
+ varDef: $ => seq($.kVar, delimited1($.identifier), ':', field('type', $.typeref)),
477
+ // Delphi 12+ inline `const NAME [: T] = value`. Used inside statement
478
+ // bodies (begin..end blocks). Distinct from `declConst` which lives
479
+ // in unit/procedure-local `const X = 1;\n Y = 2;` blocks under kConst.
480
+ constInline: $ => seq(
481
+ $.kConst, field('name', $.identifier),
482
+ optional(seq(':', field('type', $.typeref))),
483
+ '=', field('value', $._expr)
484
+ ),
485
+ label: $ => seq($.identifier, ':'),
486
+ caseLabel: $ => seq(delimited1(choice($._expr, $.range)), ':'),
487
+
488
+ _statements: $ => repeat1(choice($.varDef, $._statement, $.label)),
489
+ _statementsTr: $ => seq(
490
+ repeat(choice($._statement, $.label)),
491
+ choice(tr($,'_statement'), $._statement)
492
+ ),
493
+
494
+ statements: $ => $._statements,
495
+ statementsTr: $ => $._statementsTr,
496
+
497
+ // asmBody is treated as ONE opaque token — content between `asm` and
498
+ // the next word-boundary `end`. This grammar is not tree-sitter-asm;
499
+ // the body is consumed wholesale and not subdivided into instructions,
500
+ // labels, registers, etc. Use a dedicated assembler grammar if you
501
+ // need that.
502
+ //
503
+ // The regex matches any character except an `end` keyword at a word
504
+ // boundary. Alternations: any non-e/E char, e/E not followed by n/N,
505
+ // en/EN not followed by d/D, end immediately followed by ident-char
506
+ // (so it's NOT the keyword, e.g. 'endif' / 'endloop').
507
+ asmBody: $ => token(prec(-1, repeat1(choice(
508
+ // Identifier not starting with e/E — consume as a unit so an
509
+ // embedded `end` substring (like in `addend` or `xchg ext`)
510
+ // doesn't falsely terminate the body.
511
+ /[a-df-zA-DF-Z_][a-zA-Z0-9_]*/,
512
+ // Phase 3b iter 18: local asm labels `@@end:` / `@@loop:` etc.
513
+ // Without this the `@@end` parses as `@@` (two `[^eE]`) + `end`
514
+ // (asm terminator), falsely closing the asm block.
515
+ // Match `@@<ident>` as one chunk so `end` after `@@` doesn't
516
+ // terminate.
517
+ /@@[a-zA-Z_][a-zA-Z0-9_]*/,
518
+ /@[a-zA-Z_][a-zA-Z0-9_]*/,
519
+ // Single-char alternatives — preserve the original e/E word-boundary
520
+ // detection: stop when we see the bare `end` keyword.
521
+ /[^eE]/,
522
+ /[eE][^nN]/,
523
+ /[eE][nN][^dD]/,
524
+ /[eE][nN][dD][A-Za-z0-9_]/
525
+ )))),
526
+
527
+ // EXPRESSIONS ---------------------------------------------------------
528
+
529
+ _expr: $ => choice(
530
+ $._ref, $.exprBinary, $.exprUnary,
531
+ // Delphi 12+ inline if-then-else expression:
532
+ // X := if Cond then 1 else 0;
533
+ // Result := (if Assigned(X) then X.Name else '');
534
+ $.exprIf,
535
+ ),
536
+
537
+ // Inline if-expression: `if <cond> then <a> else <b>`. Distinct from
538
+ // the if-then-else STATEMENT (rn('ifElse')) — this one returns a value.
539
+ // Right-associative + lower precedence than binary operators so that
540
+ // `a + if c then x else y` groups as `a + (if c then x else y)`.
541
+ exprIf: $ => prec.right(0, seq(
542
+ $.kIf, field('condition', $._expr),
543
+ $.kThen, field('then', $._expr),
544
+ $.kElse, field('else', $._expr),
545
+ )),
546
+
547
+ _ref: $ => choice(
548
+ ...enable_if(templates && fpc,
549
+ // TODO: Ideally, the kSpecialize should be part of exprTpl,
550
+ // but for some reason this leads to a rule conflict, so for
551
+ // now we just put it here.
552
+ //
553
+ // Also, we have to write the rule in this weird weird way,
554
+ // because if we just do
555
+ //
556
+ // seq(optional($.kSpecialize), $.identifier)
557
+ //
558
+ // then we can't have a standalone identifier named
559
+ // "specialize". (Bug in tree-sitter?)
560
+ prec.left(choice(
561
+ seq($.kSpecialize, $.identifier),
562
+ seq(alias($.kSpecialize, $.identifier)),
563
+ ))
564
+ ),
565
+ $.identifier,
566
+ $._literal, $.inherited, $.exprDot,
567
+ $.exprBrackets, $.exprParens, $.exprSubscript, $.exprCall,
568
+ alias($.exprDeref, $.exprUnary),
569
+ alias($.exprAs, $.exprBinary),
570
+ ...enable_if(templates, $.exprTpl),
571
+ ...enable_if(lambda, $.lambda)
572
+ ),
573
+
574
+ lambda: $ => seq(
575
+ choice($.kProcedure, $.kFunction),
576
+ field('args', optional($.declArgs)),
577
+ optional(seq(
578
+ ':',
579
+ field('type', $.typeref),
580
+ )),
581
+ // Optional inline calling-convention on the lambda (Vcl.Edge.pas
582
+ // passes anonymous-method callbacks with `stdcall` to interop with
583
+ // WebView2 COM):
584
+ // var h := function(R: HRESULT): HRESULT stdcall begin ... end;
585
+ optional(choice(
586
+ $.kStdcall, $.kCdecl, $.kSafecall, $.kPascal,
587
+ $.kRegister, $.kWinapi, $.kInline
588
+ )),
589
+ field('local', optional($._definitions)),
590
+ field('body', choice(tr($, 'block'), tr($, 'asm'))),
591
+ ),
592
+
593
+ inherited: $ => prec.right(seq($.kInherited, optional($.identifier))),
594
+
595
+ exprDot: $ => choice(
596
+ op.infix(5, $._ref, $.kDot, $._ref),
597
+ // Keyword-as-identifier on RHS of dot: `TdxUnaryOp.Not`, `T.And`,
598
+ // `T.Or`, `T.Xor` — common in DevExpress enum-class patterns where
599
+ // the enum members are named after logical operators.
600
+ // Phase 3b iter 21: add kFunction/kProcedure for `TdxToken.FUNCTION`
601
+ // (DevExpress dxEMF criteria parser enum-class).
602
+ prec.left(5, seq(
603
+ field('lhs', $._ref),
604
+ field('operator', $.kDot),
605
+ field('rhs', choice(
606
+ alias($.kNot, $.identifier),
607
+ alias($.kAnd, $.identifier),
608
+ alias($.kOr, $.identifier),
609
+ alias($.kXor, $.identifier),
610
+ alias($.kFunction, $.identifier),
611
+ alias($.kProcedure, $.identifier),
612
+ ))
613
+ )),
614
+ ),
615
+ exprDeref: $ => op.postfix(4, $._expr, $.kHat),
616
+
617
+ exprAs: $ => op.infix(3, $._expr, $.kAs, $._expr),
618
+
619
+ // Unfortunately, we can't use $.exprArgs for $.exprTpl because the
620
+ // parser cannot handle it.
621
+ //
622
+ // There are two conflicting rules:
623
+ //
624
+ // 0. Binary comparison: a < b
625
+ // 1. Template use: a < b >
626
+ // ^^^^^
627
+ // prefix
628
+ //
629
+ // In order for this to work, the prefix must produce the same nodes in
630
+ // both cases. This is not the case when we introduce a wrapper node.
631
+ //
632
+ // Example:
633
+ //
634
+ // exprBinary
635
+ // identifier
636
+ // <
637
+ // identifier
638
+ //
639
+ // vs.
640
+ //
641
+ // exprTpl
642
+ // exprArgs <-- extra node
643
+ // identifier
644
+ // <
645
+ // identifier
646
+ // >
647
+ //
648
+ // Basically the way this works is that there is a tentative node like
649
+ // "exprTplOrBinary", which looks like this:
650
+ //
651
+ // exprTplOrBinary
652
+ // identifier
653
+ // <
654
+ // identifier
655
+ //
656
+ // At this point we don't yet know what we are dealing with. The next
657
+ // token will determine whether we are dealing with a comparison or a
658
+ // template. Then the existing node is simply "renamed". Because of
659
+ // this, we can't have an extra node in only one of the branches.
660
+ //
661
+ // Generic instantiation `Foo<A, B, C>`. Originally `delimited1($._expr)`
662
+ // but that caused `H < 0` (binary less-than) to be parsed as `Foo<0>`
663
+ // (exprTpl) and parser then inserted MISSING `>` when no matching `>`
664
+ // arrived. Narrowed to `typeref` — covers `TList<TFoo>`,
665
+ // `TDictionary<string, TBar>` etc. (real generic instantiation).
666
+ // Constant generic args (rare in Delphi corpus) lose structural parsing
667
+ // but the program-as-a-whole parses cleanly.
668
+ exprTpl: $ => op.args(5, $._ref, $.kLt, delimited1($.typeref, ',', 5), $.kGt),
669
+ exprSubscript: $ => op.args(5, $._ref, '[', $.exprArgs, ']' ),
670
+ exprCall: $ => op.args(5, $._ref, '(', optional($.exprArgs), ')' ),
671
+
672
+ // Pascal legacy string formatting for WriteLn(foo:4:3) etc.
673
+ legacyFormat: $ => repeat1(seq(':', $._expr)),
674
+
675
+ exprArgs: $ => delimited1(seq($._expr, optional($.legacyFormat))),
676
+
677
+ exprBinary: $ => choice(
678
+ op.infix(1, $._expr, $.kLt, $._expr),
679
+ op.infix(1, $._ref, $.kLt, $._expr),
680
+ op.infix(1, $._expr, $.kEq, $._expr),
681
+ op.infix(1, $._expr, $.kNeq, $._expr),
682
+ op.infix(1, $._expr, $.kGt, $._expr),
683
+ op.infix(1, $._expr, $.kLte, $._expr),
684
+ op.infix(1, $._expr, $.kGte, $._expr),
685
+ op.infix(1, $._expr, $.kIn, $._expr),
686
+ op.infix(1, $._expr, $.kIs, $._expr),
687
+
688
+ op.infix(2, $._expr, $.kAdd, $._expr),
689
+ op.infix(2, $._expr, $.kSub, $._expr),
690
+ op.infix(2, $._expr, $.kOr, $._expr),
691
+ op.infix(2, $._expr, $.kXor, $._expr),
692
+
693
+ op.infix(3, $._expr, $.kMul, $._expr),
694
+ op.infix(3, $._expr, $.kFdiv,$._expr),
695
+ op.infix(3, $._expr, $.kDiv, $._expr),
696
+ op.infix(3, $._expr, $.kMod, $._expr),
697
+ op.infix(3, $._expr, $.kAnd, $._expr),
698
+ op.infix(3, $._expr, $.kShl, $._expr),
699
+ op.infix(3, $._expr, $.kShr, $._expr),
700
+ // Phase 3b iter 30: C-style shift `<<` and `>>` — FPC supports
701
+ // these as alias for `shl`/`shr`. lazutils uses them heavily.
702
+ op.infix(3, $._expr, alias(token('<<'), $.kShl), $._expr),
703
+ op.infix(3, $._expr, alias(token('>>'), $.kShr), $._expr),
704
+ ),
705
+
706
+ exprUnary: $ => choice(
707
+ op.prefix(4, $.kNot, $._expr),
708
+ op.prefix(4, $.kAdd, $._expr),
709
+ op.prefix(4, $.kSub, $._expr),
710
+ op.prefix(4, $.kAt, $._expr),
711
+ ),
712
+
713
+ exprParens: $ => prec.left(5,seq('(', $._expr, ')')),
714
+
715
+ // Set or array literal
716
+ exprBrackets: $ => seq(
717
+ '[', delimited(choice($._expr, $.range)), ']'
718
+ ),
719
+
720
+ // TYPES ---------------------------------------------------------------
721
+
722
+ type: $ => pp($,choice(
723
+ $.typeref,
724
+ $.declMetaClass,
725
+ $.declEnum,
726
+ $.declSet,
727
+ $.declArray,
728
+ $.declFile,
729
+ $.declString,
730
+ $.declProcRef,
731
+ // Anonymous record/class/object types in type position. Required for
732
+ // patterns like:
733
+ // const Errors: array [0..N] of record A: T; B: T; end = (...);
734
+ // var X: record I: Integer; end;
735
+ $.declClass,
736
+ // IFDEF-as-type: `FStream: {$IFDEF X}System.Classes{$ELSE}Classes{$ENDIF};`
737
+ // (the external scanner produces pp_block as one opaque token at this
738
+ // position; the regex pp / pp_block-in-extras paths still handle the
739
+ // "wraps a whole statement/declaration" case.)
740
+ // prec(-1): only used when no real type follows the pp_block — handles
741
+ // `TRecInfo = {$IFDEF CPUX86}packed{$ENDIF} record...end;` correctly
742
+ // (pp_block becomes extras, real type is `packed record...` after).
743
+ prec(-1, $.pp_block),
744
+ // Qualified type where IFDEF wraps the namespace prefix:
745
+ // Response: {$IFDEF USE_NAMESPACES}Web.HTTPApp{$ELSE}HTTPApp{$ENDIF}.TWebResponse
746
+ // (EurekaLog / Embarcadero RTL XE2+ unit-scope-name pattern.)
747
+ // Explicit prec(1) so the pp_block is consumed structurally, not as
748
+ // extras whitespace that would orphan the trailing `.TFoo`.
749
+ prec(1, seq($.pp_block, $.kDot, $.typeref)),
750
+ )),
751
+
752
+ typeref: $ => seq(
753
+ ...enable_if(fpc, field('_dummy', optional($.kSpecialize))),
754
+ $._typeref,
755
+ ...enable_if(delphi, optional(seq($.kDeprecated, $._expr))),
756
+ ),
757
+
758
+ _typeref: $ => choice(
759
+ $.identifier, $.typerefDot,
760
+ ...enable_if(templates, $.typerefTpl),
761
+ $.typerefPtr,
762
+ // Soft keywords that can also serve as type names. Delphi allows
763
+ // many context-sensitive keywords to be used as identifiers when
764
+ // the surrounding syntax requires one. These come up most often
765
+ // in OleServer / Excel / Word automation wrappers where the OLE
766
+ // IDL was translated mechanically and used clashing names.
767
+ //
768
+ // Phase 3b iter 19: kIndex removed from this list so that at
769
+ // expression positions, kIndex is NOT in valid_symbols (it's
770
+ // only reachable from declProp's index clause and procExternal's
771
+ // index ordinal — both class/external scope). With kIndex out of
772
+ // reach, tree-sitter's word-rule soft-keyword promotion fires
773
+ // and "Index" tokenizes as identifier in expressions like
774
+ // `if i < Index then ...`. _typeref still gets "Index" via the
775
+ // $.identifier path (no functional regression).
776
+ alias($.kReference, $.identifier),
777
+ alias($.kMessage, $.identifier),
778
+ alias($.kName, $.identifier),
779
+ alias($.kRead, $.identifier),
780
+ alias($.kWrite, $.identifier),
781
+ ),
782
+
783
+ typerefDot: $ => op.infix(1,$._typeref, $.kDot, $._typeref),
784
+ typerefTpl: $ => op.args(1, $._typeref, $.kLt, $.typerefArgs, $.kGt),
785
+ typerefPtr: $ => op.prefix(1,$.kHat, $._typeref),
786
+ typerefArgs: $ => delimited1($._typeref),
787
+
788
+ // GENERIC TYPE DECLARATION --------------------------------------------
789
+ //
790
+ // E.g. Foo<A: B, C: D<E>>.XYZ<T>
791
+ // ^ ^
792
+ // Note the optional constraints, which makes this different from a
793
+ // specialization
794
+ //
795
+ // We treat regular names as a special case of generic names. I.e. if
796
+ // you see $._genericName somewhere, it doesn't mean that the name HAS
797
+ // to be generic, it could just be a regular name like "TFoobar" or
798
+ // "MyUnit.Foo".
799
+
800
+ genericDot: $ => op.infix(1,$._genericName, $.kDot, $._genericName),
801
+ genericTpl: $ => op.args(2,$._genericName, $.kLt, $.genericArgs, $.kGt),
802
+
803
+ _genericName: $ => choice(
804
+ $.identifier, $.genericDot, ...enable_if(templates, $.genericTpl)
805
+ ),
806
+ genericArgs: $ => delimited1($.genericArg, ';'),
807
+ genericArg: $ => seq(
808
+ field('name', delimited1($.identifier)),
809
+ // Type constraint: `T: BaseType` or one/more of the special
810
+ // constraint keywords `class` / `record` / `constructor`, comma-
811
+ // separated. Examples:
812
+ // <T: class> must be a class type
813
+ // <T: constructor> must have parameterless ctor
814
+ // <T: class, constructor> both
815
+ // <T: IFoo> must implement interface
816
+ // <T: IFoo, constructor> both
817
+ field('type', optional(seq(
818
+ ':',
819
+ $._genericConstraint,
820
+ repeat(seq(',', $._genericConstraint))
821
+ ))),
822
+ field('defaultValue', optional($.defaultValue))
823
+ ),
824
+ _genericConstraint: $ => choice(
825
+ $.typeref,
826
+ $.kClass,
827
+ $.kRecord,
828
+ $.kConstructor,
829
+ ),
830
+
831
+ // LITERALS -----------------------------------------------------------
832
+
833
+ _literal: $ => choice(
834
+ $.literalString,
835
+ $.literalNumber,
836
+ $.kNil, $.kTrue, $.kFalse
837
+ ),
838
+ literalString: $ => repeat1($._literalString),
839
+ // Delphi 12+ multi-line string (triple-quoted, like Python's """) — must
840
+ // be matched BEFORE the single-quoted form because tree-sitter uses
841
+ // longest-match. The body matches any chars (including newlines and
842
+ // single quotes) up to a closing `'''`.
843
+ _literalString: $ => choice(
844
+ // Delphi 12+ triple-quoted multi-line string. prec(2) so the
845
+ // regex engine prefers this over the single-quoted form when
846
+ // three quotes start the literal.
847
+ token(prec(2, /'''[\s\S]*?'''/)),
848
+ // Single-quoted string. `''` (doubled single-quote) is Delphi's
849
+ // escape for one literal apostrophe — `'Can''t'` is "Can't".
850
+ // Body alternation: any non-quote char, OR a doubled quote.
851
+ token(/'([^']|'')*'/),
852
+ $.literalChar
853
+ ),
854
+ // Char literal: `#NN` decimal/hex char code, OR `^X` control char
855
+ // (the latter handled by the external scanner with proper lookahead
856
+ // to disambiguate from `^TFoo` pointer-to-type).
857
+ literalChar: $ => choice(
858
+ seq('#', $._literalInt),
859
+ $.char_literal
860
+ ),
861
+ literalNumber: $ => choice($._literalInt, $._literalFloat, $.trailing_dot_float),
862
+ // Delphi 11+ allows `_` as a digit-group separator: `1_000_000_000`
863
+ // or `$FFFF_FFFF`. Underscore is between digits, never leading/trailing.
864
+ _literalInt: $ => choice(
865
+ token.immediate(/[-+]?[0-9](_?[0-9])*/),
866
+ token.immediate(/\$[a-fA-F0-9](_?[a-fA-F0-9])*/),
867
+ // Phase 3b iter 24: Delphi octal literal `&NNN` (System.Beacon
868
+ // uses `&1`, `&0`; RTL Linux/Posix code uses `&777` perm masks).
869
+ token.immediate(/&[0-7](_?[0-7])*/),
870
+ // Delphi binary literal `%NNN` — used in lower-level RTL code.
871
+ token.immediate(/%[01](_?[01])*/),
872
+ ),
873
+ // Float: optional sign, digits with optional decimal portion, optional
874
+ // scientific exponent. Exponent letter is `e` OR `E` (case-insensitive).
875
+ // NOTE: trailing-dot form (`100.`) is intentionally NOT supported here
876
+ // because it conflicts with the `..` range operator (e.g. `array [0..N]`
877
+ // would be lex-greedy-matched as `0.` float + `.N` and break the array).
878
+ _literalFloat: $ => prec(10, /[-+]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?/),
879
+
880
+ range: $ => seq(
881
+ $._expr, '..', $._expr
882
+ ),
883
+
884
+ // DEFINITIONS --------------------------------------------------------
885
+
886
+ _definitions: $ => repeat1($._definition),
887
+ _definition: $ => choice(
888
+ $.declTypes, $.declVars, $.declConsts, $.defProc,
889
+ alias($.declProcFwd, $.declProc),
890
+ $.declLabels, $.declUses, $.declExports,
891
+
892
+ // Top-level CIL/.NET attributes such as
893
+ // [assembly: AssemblyDescription('...')]
894
+ // found in Delphi.NET .inc files (Indy assembly metadata).
895
+ $.declAssemblyAttribute,
896
+
897
+ // Not actually valid syntax, but helps the parser recover:
898
+ prec(-1,$.blockTr)
899
+ ),
900
+
901
+ // A single bracketed attribute that applies to a module-level target
902
+ // (e.g. assembly, module). Distinct from rttiAttributes (which attach
903
+ // to declarations) because these are standalone.
904
+ declAssemblyAttribute: $ => prec(1, seq(
905
+ '[', $.identifier, ':', delimited($._ref), ']'
906
+ )),
907
+
908
+ defProc: $ => seq(
909
+ /*pp($,*/ field('header', $.declProc)/*)*/,
910
+ // DevExpress nested fn pattern (iter 65, extended iter 66):
911
+ // function Production(...): Single; inline
912
+ // begin ... end;
913
+ // function EnumChildProc(...): BOOL; stdcall
914
+ // var ...
915
+ // Trailing calling-conv / inline without `;` before body.
916
+ // Restricted to safe keyword set (kDeprecated 'msg' conflicts with []).
917
+ optional(field('trailingAttr', choice(
918
+ $.kInline,
919
+ $.kStdcall, $.kCdecl, $.kSafecall, $.kPascal,
920
+ $.kRegister, $.kWinapi
921
+ ))),
922
+ pp(
923
+ $,
924
+ field('local', optional($._definitions)),
925
+ field('body', choice(tr($, 'block'), tr($, 'asm'))),
926
+ ';'
927
+ )
928
+ ),
929
+
930
+ declProcFwd: $ => seq(
931
+ $._declProc,
932
+ choice(seq($.kForward, ';'), $.procExternal),
933
+ repeat($._procAttribute)
934
+ ),
935
+
936
+ // DECLARATIONS -------------------------------------------------------
937
+
938
+ _visibility: $ => choice(
939
+ $.kPublished, $.kPublic, $.kProtected, $.kPrivate
940
+ ),
941
+
942
+ _declarations: $ => repeat1(choice(
943
+ $.declTypes, $.declVars, $.declConsts, $.declProc, $.declProp,
944
+ alias($.declProcFwd, $.declProc),
945
+ $.declUses, $.declLabels, $.declExports
946
+ )),
947
+ _classDeclarations: $ => repeat1(choice(
948
+ $.declTypes, $.declVars, $.declConsts, $.declProc, $.declProp
949
+ )),
950
+
951
+ defaultValue: $ => seq($.kEq, $._initializer),
952
+
953
+ // Declaration sections
954
+
955
+ // Permissive uses-clause: accepts the normal `kUses delimited(unit) ;`
956
+ // form AND patterns where `{$IF}/{$ELSE}/{$ENDIF}` directives appear
957
+ // between units OR wrap the entire `units ;` block:
958
+ // uses
959
+ // {$IFDEF HAS_UNITSCOPE}
960
+ // System.SysUtils;
961
+ // {$ELSE}
962
+ // SysUtils;
963
+ // {$ENDIF}
964
+ // We allow units, commas, pp tokens AND `;` to interleave; the rule
965
+ // terminates on the LAST `;` it can consume. Trades grammatical
966
+ // strictness for parsing robustness on real-world JEDI/JCL/Indy code.
967
+ declUses: $ => seq(
968
+ $.kUses,
969
+ // pp_block (external-scanner token) is added so a uses-clause whose
970
+ // `;` lives INSIDE an IFDEF block doesn't dangle:
971
+ // uses
972
+ // {$IFDEF MSWINDOWS}Winapi.Windows;{$ENDIF MSWINDOWS}
973
+ // {$IFDEF POSIX}Posix.Stdlib;{$ENDIF POSIX}
974
+ repeat1(choice($.declUsesUnit, ',', $.pp, $.pp_block, ';'))
975
+ ),
976
+ // A unit reference inside a uses / contains clause. In program (.dpr) and
977
+ // package (.dpk) files the unit may carry an explicit source-file path
978
+ // via in '<literal>' . Optional braces with a form-name hint
979
+ // (e.g. {Form1}) are stripped as comments by the extras layer.
980
+ declUsesUnit: $ => seq($.moduleName, optional(seq($.kIn, $.literalString))),
981
+ // Permissive same as declUses — allows IFDEF interleaving / wrapping.
982
+ declRequires: $ => seq(
983
+ $.kRequires,
984
+ repeat1(choice($.moduleName, ',', $.pp, $.pp_block, ';'))
985
+ ),
986
+ declContains: $ => seq(
987
+ $.kContains,
988
+ repeat1(choice($.declUsesUnit, ',', $.pp, ';'))
989
+ ),
990
+ declExports: $ => seq($.kExports, delimited($.declExport), ';'),
991
+
992
+ declTypes: $ => seq(
993
+ $.kType,
994
+ repeat($.declType)
995
+ ),
996
+
997
+ declVars: $ => seq(
998
+ optional($.kClass),
999
+ choice($.kVar, $.kThreadvar),
1000
+ repeat($.declVar)
1001
+ ),
1002
+
1003
+ declConsts: $ => seq(
1004
+ optional($.kClass),
1005
+ choice($.kConst, $.kResourcestring),
1006
+ repeat($.declConst),
1007
+ ),
1008
+
1009
+ // Declarations
1010
+
1011
+ declType: $ => seq(
1012
+ ...enable_if(rtti, optional($.rttiAttributes)),
1013
+ // Phase 3b iter 30: `generic TFoo<T> = class` keyword form — was
1014
+ // FPC-only; lazutils uses it heavily. Accepting in Delphi mode is
1015
+ // harmless because real Delphi code never uses `generic` here.
1016
+ optional($.kGeneric),
1017
+ field('name', $._genericName), $.kEq,
1018
+ field('type',
1019
+ choice(
1020
+ seq(optional($.kType), $.type),
1021
+ choice($.type),
1022
+ $.declClass,
1023
+ $.declIntf,
1024
+ $.declHelper,
1025
+ // Subrange types: `type TJvMargin = 2..24;`, also
1026
+ // `type T = Low(Integer)..High(Integer);`
1027
+ $.subrangeType,
1028
+ // Phase 3b iter 8: strong-typed string with code-page
1029
+ // argument — `UTF8String = type _AnsiString(65001);`
1030
+ // and `RawByteString = type _AnsiString($ffff);`
1031
+ // (System.pas RTL pattern).
1032
+ seq($.kType, $.identifier, '(', $._literalInt, ')'),
1033
+ )
1034
+ ),
1035
+ ';',
1036
+ repeat($._procAttribute)
1037
+ ),
1038
+
1039
+ // Narrow subrange specialization used only as a type-declaration RHS,
1040
+ // to avoid the precedence/conflict cascade that `range` (uses _expr)
1041
+ // triggers in this position. Accepts numeric literals, negative
1042
+ // numbers, simple identifiers, and call-like `Func(arg)` forms which
1043
+ // covers Low/High/Ord patterns.
1044
+ subrangeType: $ => seq(
1045
+ $._subrangeBound, '..', $._subrangeBound
1046
+ ),
1047
+ _subrangeBound: $ => choice(
1048
+ $._literalInt,
1049
+ $._literalFloat,
1050
+ seq('-', $._literalInt),
1051
+ seq('+', $._literalInt),
1052
+ // Phase 3b iter 25: `-$7FFFFFFF-1..$7FFFFFFF` (System.UITypes TColor)
1053
+ // signed-hex with explicit binary correction: `-$N-K` / `+$N+K`.
1054
+ seq(choice('-','+'), $._literalInt, choice('-','+'), $._literalInt),
1055
+ // Phase 3b iter 5: `-Identifier` / `+Identifier` form for
1056
+ // `THelpContext = -MaxInt..MaxInt` (System.Classes pattern).
1057
+ seq('-', $.identifier),
1058
+ seq('+', $.identifier),
1059
+ $.identifier,
1060
+ // Qualified identifier `TFoo.Bar` (DevExpress enum-class subrange:
1061
+ // TdxChartActualToolTipMode = TdxChartToolTipMode.None .. TdxChartToolTipMode.Crosshair;
1062
+ // Narrow form: two-level only; full qualified-name uses _ref which
1063
+ // conflicts with typerefDot in this position.)
1064
+ seq($.identifier, '.', $.identifier),
1065
+ seq($.identifier, '(', $.identifier, ')'),
1066
+ // `hid - 1`, `MaxN + 2` — narrow `identifier OP integer` form for
1067
+ // real-corpus patterns like `TNmbrRange = 0 .. hid - 1;`.
1068
+ // Kept narrow (no full _expr) to avoid the conflict cascade hit
1069
+ // when wider _expr was tried earlier.
1070
+ seq($.identifier, choice('-', '+'), $._literalInt),
1071
+ // `SizeOf(Integer) * 8 - 1`-style binary chain — narrow
1072
+ // `ident(ident) OP int OP int` form for the DevExpress pattern
1073
+ // `TcxContainerStyleValue = 0..SizeOf(Integer) * 8 - 1;`.
1074
+ seq($.identifier, '(', $.identifier, ')',
1075
+ choice('*', '/'), $._literalInt,
1076
+ choice('-', '+'), $._literalInt),
1077
+ // `#NN` numeric char-literal bound: `TCodeLetter = #64..#82;`.
1078
+ // (Excluding `^X` external char_literal — narrower keeps Spring4D
1079
+ // safe from the earlier regression.)
1080
+ seq('#', $._literalInt),
1081
+ // Phase 3b iter 41: parenthesized binary then trailing op + arg
1082
+ // for `(MaxInt div SizeOf(JOCTET))-1` (Vcl.Imaging.jpeg) and
1083
+ // `(High(Integer) - $F) div SizeOf(DWORD)` (EBorDebug). Narrow
1084
+ // shape: paren-wrap a 3-term binary then one more op + literal-or-call.
1085
+ seq(
1086
+ '(',
1087
+ choice($.identifier, seq($.identifier, '(', $.identifier, ')')),
1088
+ choice('-', '+', '*', '/', $.kDiv, $.kMod),
1089
+ choice($.identifier, $._literalInt, seq($.identifier, '(', $.identifier, ')')),
1090
+ ')',
1091
+ choice('-', '+', '*', '/', $.kDiv, $.kMod),
1092
+ choice($._literalInt, seq($.identifier, '(', $.identifier, ')')),
1093
+ ),
1094
+ ),
1095
+
1096
+ declProc: $ => seq(
1097
+ ...enable_if(rtti, optional($.rttiAttributes)),
1098
+ choice($._declProc, $._declOperator),
1099
+ ),
1100
+
1101
+ declVar: $ => seq(
1102
+ ...enable_if(rtti, optional($.rttiAttributes)),
1103
+ // Allow soft-keyword names as variable identifiers. Same set as
1104
+ // `_typeref`'s alias list — `Message: TFoo;`, `Name: string;`,
1105
+ // `Index: integer;` etc. show up frequently in legacy code.
1106
+ field('name', delimited1(choice(
1107
+ $.identifier,
1108
+ alias($.kMessage, $.identifier),
1109
+ alias($.kName, $.identifier),
1110
+ alias($.kIndex, $.identifier),
1111
+ alias($.kRead, $.identifier),
1112
+ alias($.kWrite, $.identifier),
1113
+ alias($.kReference, $.identifier),
1114
+ // Phase 3b iter 14: kDefault as var name (FMX.Media.Win,
1115
+ // WBComp, System.Classes use `Default: Boolean;` etc.)
1116
+ alias($.kDefault, $.identifier),
1117
+ // Phase 3b iter 16: kOperator as var name (Data.Win.ADODB,
1118
+ // Data.DBCommon use `Operator: TCANOperator;` etc.)
1119
+ alias($.kOperator, $.identifier),
1120
+ // Phase 3b iter 36: kFinal as var name (Rave RvCsRpt
1121
+ // `Final: boolean;` — local var named Final).
1122
+ alias($.kFinal, $.identifier),
1123
+ ))),
1124
+ ':',
1125
+ // Phase 3b iter 36: declVar type may be a subrange:
1126
+ // Element: 0..MaxSet absolute Value;
1127
+ // (Raize CodeSiteLogging.)
1128
+ field('type', choice($.type, $.subrangeType)),
1129
+ optional(choice(
1130
+ seq($.kAbsolute, $._ref),
1131
+ field('defaultValue', $.defaultValue)
1132
+ )),
1133
+ // Phase 3b iter 14: declaration hints on module-level vars:
1134
+ // CmdShow: Integer platform;
1135
+ // SupportsAnimateWindow: Boolean = False deprecated 'msg';
1136
+ optional(choice(
1137
+ seq($.kDeprecated, optional($._expr)),
1138
+ $.kPlatform,
1139
+ $.kExperimental
1140
+ )),
1141
+ ';',
1142
+ // Phase 3b iter 13: function-typed module-level vars with
1143
+ // callconv + default value:
1144
+ // var Name: function(...): T; stdcall = nil;
1145
+ // Without this, the `;` before stdcall closes declVar and
1146
+ // `stdcall = nil ;` is orphaned. Match at declVar scope only
1147
+ // (not declProcRef) to avoid the iter-12 GLR cascade.
1148
+ optional(seq(
1149
+ choice(
1150
+ $.kStdcall, $.kCdecl, $.kSafecall, $.kPascal,
1151
+ $.kRegister, $.kWinapi, $.kInline
1152
+ ),
1153
+ '=',
1154
+ field('defaultValueAfterCC', $._expr),
1155
+ ';',
1156
+ )),
1157
+ repeat(choice($._procAttribute, $.procExternal))
1158
+ ),
1159
+
1160
+ declConst: $ => seq(
1161
+ ...enable_if(rtti, optional($.rttiAttributes)),
1162
+ // Allow soft-keyword names as const identifiers (DevExpress
1163
+ // dxCoreGraphics uses `Default` as a color-name constant; other
1164
+ // libraries use Message/Name/Index/etc.)
1165
+ field('name', choice(
1166
+ $.identifier,
1167
+ alias($.kDefault, $.identifier),
1168
+ alias($.kMessage, $.identifier),
1169
+ alias($.kName, $.identifier),
1170
+ alias($.kIndex, $.identifier),
1171
+ alias($.kRead, $.identifier),
1172
+ alias($.kWrite, $.identifier),
1173
+ alias($.kReference, $.identifier),
1174
+ )),
1175
+ // Phase 3b iter 31: declConst type may be a subrange:
1176
+ // const allocatedCount : 0..MaxAllocEntries = 0;
1177
+ // const Element : 0..MaxSet absolute Value;
1178
+ // (fibplus Zutil, Raize CodeSiteLogging patterns.)
1179
+ optional(seq(':', field('type', choice($.type, $.subrangeType)))),
1180
+ field('defaultValue', $.defaultValue),
1181
+ // Declaration hints before the terminating ';':
1182
+ // const X = 'foo' deprecated 'use Y instead';
1183
+ // const Y = 1 platform;
1184
+ // const Z = 'bar' experimental;
1185
+ // Phase 3b iter 27: chained hints `platform deprecated;`
1186
+ // (System.SysUtils faVolumeID = $00000008 platform deprecated).
1187
+ // Phase 3b iter 28: `library` as legacy declaration hint
1188
+ // (YADF DeprecatedOnConst MyConst3 = 'test4' library).
1189
+ repeat(choice(
1190
+ seq($.kDeprecated, optional($._expr)),
1191
+ $.kPlatform,
1192
+ $.kExperimental,
1193
+ $.kLibrary,
1194
+ )),
1195
+ ';',
1196
+ repeat($._procAttribute)
1197
+ ),
1198
+
1199
+ declLabels: $ => seq($.kLabel, delimited1($.declLabel), ';'),
1200
+ declLabel: $ => field('name', $.identifier),
1201
+
1202
+ declExport: $ => seq($._genericName, repeat(seq(choice($.kName, $.kIndex), $._expr))),
1203
+
1204
+ // Type declarations
1205
+
1206
+ declEnum: $ => seq(
1207
+ '(', delimited1($.declEnumValue), ')',
1208
+ // Trailing platform/deprecated hint on the enum body itself:
1209
+ // TFPUPrecisionMode = (pmSingle, pmReserved, ...) platform;
1210
+ // (System.Math pattern.)
1211
+ optional(choice(
1212
+ seq($.kDeprecated, optional($._expr)),
1213
+ $.kPlatform,
1214
+ $.kExperimental,
1215
+ )),
1216
+ ),
1217
+ declEnumValue: $ => seq(field('name', $.identifier), field('value', optional($.defaultValue))),
1218
+ // `set of T` where T is a type OR a subrange (`set of 1..100;`).
1219
+ declSet: $ => seq(
1220
+ $.kSet, $.kOf, choice($.type, $.subrangeType),
1221
+ // Phase 3b iter 21: trailing platform/deprecated hint on set-type
1222
+ // declaration. `TFileAttributes = set of TFileAttribute platform;`
1223
+ // (System.IOUtils pattern.)
1224
+ optional(choice(
1225
+ seq($.kDeprecated, optional($._expr)),
1226
+ $.kPlatform,
1227
+ $.kExperimental,
1228
+ )),
1229
+ ),
1230
+ declArray: $ => seq(
1231
+ optional($.kPacked),
1232
+ $.kArray,
1233
+ optional(seq('[', delimited(choice($.range, $._expr)), ']')),
1234
+ // Element type can be a regular type OR an inline subrange:
1235
+ // array [0..7] of 0..9 = (0, 1, 2, ...);
1236
+ $.kOf, choice($.type, $.subrangeType)
1237
+ ),
1238
+ declFile: $ => seq($.kFile, optional(seq($.kOf, $.type))),
1239
+ declString: $ => prec.left(seq(
1240
+ $.kString,
1241
+ optional(seq('[', choice($._expr), ']'))
1242
+ )),
1243
+
1244
+ declProcRef: $ => prec.right(1,seq(
1245
+ optional(seq($.kReference, $.kTo)),
1246
+ choice($.kProcedure, $.kFunction),
1247
+ field('args', optional($.declArgs)),
1248
+ optional(seq(
1249
+ ':',
1250
+ field('type', $.typeref),
1251
+ )),
1252
+ optional(seq($.kOf, $.kObject)),
1253
+ // Trailing calling convention. Restricted to a narrow keyword list
1254
+ // (NOT the full procAttribute, which causes typeref ambiguity)
1255
+ // to keep the grammar deterministic.
1256
+ // Phase 3b iter 25: `repeat` allows chained callconvs like
1257
+ // `cdecl varargs` (FireDAC.Phys.SQLiteCli Tsqlite3_config pattern).
1258
+ repeat(choice(
1259
+ $.kStdcall, $.kCdecl, $.kSafecall, $.kPascal,
1260
+ $.kRegister, $.kWinapi, $.kInline, $.kVarargs
1261
+ ))
1262
+ )),
1263
+
1264
+ declMetaClass: $ => seq($.kClass, $.kOf, $.typeref),
1265
+
1266
+ declClass: $ => seq(
1267
+ optional($.kPacked),
1268
+ choice(
1269
+ $.kClass, $.kRecord, $.kObject,
1270
+ ...enable_if(objc,
1271
+ $.kObjcclass, $.kObjccategory, $.kObjcprotocol
1272
+ )
1273
+ ),
1274
+ optional(choice(
1275
+ $.kAbstract, $.kSealed,
1276
+ ...enable_if(objc,
1277
+ seq($.kExternal, optional(seq($.kName, $._expr)))
1278
+ )
1279
+ )),
1280
+ field('parent', optional(seq('(',delimited($.typeref),')'))),
1281
+ optional($._declClass)
1282
+ ),
1283
+
1284
+ declIntf: $ => seq(
1285
+ optional($.kPacked),
1286
+ choice(
1287
+ $.kInterface,
1288
+ ...enable_if(delphi, $.kDispInterface)
1289
+ ),
1290
+ field('parent', optional(seq('(',delimited($.typeref),')'))),
1291
+ field('guid', optional($.guid)),
1292
+ optional($._declClass)
1293
+ ),
1294
+
1295
+ declHelper: $ => seq(
1296
+ choice($.kClass, $.kRecord, $.kType), $.kHelper,
1297
+ field('parent', optional(seq('(',delimited($.typeref),')'))),
1298
+ $.kFor, $.typeref,
1299
+ $._declClass
1300
+ ),
1301
+
1302
+ // Stuff for class/record/interface declarations
1303
+
1304
+ guid: $ => prec(1,seq('[', $._ref, ']')),
1305
+
1306
+ _declClass: $ => seq(
1307
+ optional($._declFields),
1308
+ optional($._classDeclarations),
1309
+ repeat($.declSection),
1310
+ optional($.declVariant),
1311
+ $.kEnd,
1312
+ // Record alignment hint (Win API patterns):
1313
+ // _SLIST_ENTRY = record ... end align 16;
1314
+ // _SLIST_ENTRY = record ... end align(16);
1315
+ // sockaddr_storage = record ... end align (_SS_ALIGNSIZE);
1316
+ optional(seq(
1317
+ $.kAlign,
1318
+ choice(
1319
+ $._literalInt,
1320
+ seq('(', choice($._literalInt, $.identifier), ')'),
1321
+ ),
1322
+ )),
1323
+ // Whole-type declaration hints AFTER `end`:
1324
+ // type IFoo = interface ... end deprecated;
1325
+ // type IBar = interface ... end deprecated 'use IBaz';
1326
+ optional(choice(
1327
+ seq($.kDeprecated, optional($._expr)),
1328
+ $.kPlatform,
1329
+ $.kExperimental,
1330
+ ))
1331
+ ),
1332
+
1333
+ declSection: $ => seq(
1334
+ optional($.kStrict),
1335
+ choice($._visibility, ...enable_if(objc, $.kRequired, $.kOptional)),
1336
+ optional($._declFields),
1337
+ optional($._classDeclarations)
1338
+ ),
1339
+
1340
+ // Phase 3b iter 10: allow LAST field to omit trailing `;` before `end`.
1341
+ // Pattern (extremely common in DevExpress and RTL records):
1342
+ // TFoo = record
1343
+ // A: TA;
1344
+ // B: TB // no `;` here
1345
+ // end;
1346
+ // Tried in iter 62 with broader scope and reverted. Narrow form here:
1347
+ // repeat of normal declField (each with `;`) optionally followed by ONE
1348
+ // declFieldNoSemi at end. Anchor: declFieldNoSemi only fires before
1349
+ // `end` or kEnd-equivalent, since the parent rule expects that.
1350
+ _declFields: $ => choice(
1351
+ repeat1($.declField),
1352
+ seq(repeat($.declField), $.declFieldNoSemi),
1353
+ ),
1354
+ declFieldNoSemi: $ => seq(
1355
+ ...enable_if(rtti, optional($.rttiAttributes)),
1356
+ field('name', delimited1($.identifier)),
1357
+ ':',
1358
+ // Narrower type set than declField. Excludes declString with the
1359
+ // `[N]` short-string size spec — that conflicts with declFieldNoSemi
1360
+ // because the `[N]` looks like declFieldNoSemi's continuation when
1361
+ // in GLR fork. Excludes declArray for the same reason
1362
+ // (`Array[0..4] of String[1]` in ORM3 Z19b5.pas).
1363
+ // declString and declArray almost never appear as last-field with
1364
+ // no `;` in the corpus (those use the trailing `;` form already).
1365
+ field('type', choice(
1366
+ $.typeref,
1367
+ $.declMetaClass,
1368
+ $.declEnum,
1369
+ $.declSet,
1370
+ $.declFile,
1371
+ $.declProcRef,
1372
+ $.declClass,
1373
+ prec(-1, $.pp_block),
1374
+ )),
1375
+ field('defaultValue', optional($.defaultValue)),
1376
+ ),
1377
+
1378
+ declField: $ => seq(
1379
+ ...enable_if(rtti, optional($.rttiAttributes)),
1380
+ field('name', delimited1($.identifier)),
1381
+ ':',
1382
+ field('type', $.type),
1383
+ field('defaultValue', optional($.defaultValue)),
1384
+ // Declaration hints on record/class fields (Embarcadero RTL):
1385
+ // FMin: Integer deprecated;
1386
+ // FOldField: T platform;
1387
+ optional(choice(
1388
+ seq($.kDeprecated, optional($._expr)),
1389
+ $.kPlatform,
1390
+ $.kExperimental
1391
+ )),
1392
+ ';',
1393
+ // Phase 3b iter 9: trailing `<callconv>;` clause for procedural-type
1394
+ // fields that import procDecl's `; cdecl;` convention. Pattern:
1395
+ // DispInvoke: procedure(Dest: PVarData; ...); cdecl;
1396
+ // VarArrayGet: function(...): Variant; cdecl;
1397
+ // (System.pas TVarManager + similar Win API records.)
1398
+ optional(seq(
1399
+ choice(
1400
+ $.kStdcall, $.kCdecl, $.kSafecall, $.kPascal,
1401
+ $.kRegister, $.kWinapi, $.kInline,
1402
+ ),
1403
+ ';',
1404
+ )),
1405
+ ),
1406
+
1407
+ declProp: $ => seq(
1408
+ ...enable_if(rtti, optional($.rttiAttributes)),
1409
+ optional($.kClass),
1410
+ $.kProperty,
1411
+ field('name', $.identifier),
1412
+ field('args', optional($.declPropArgs)),
1413
+ // Type clause is omitted in the "redirection" form: `property Name;`
1414
+ // or `property Name; default;` — used to expose an inherited property
1415
+ // at a different visibility without changing accessors.
1416
+ optional(seq(':', field('type', $.type))),
1417
+ repeat(choice(
1418
+ seq($.kIndex, field('index', $._expr)),
1419
+ ...enable_if(delphi, seq($.kDispId, field('dispid', $._expr))),
1420
+ // read/write may reference: identifier (plain), qualified
1421
+ // identifier (Foo.Bar), or array-element access (fChilds[0])
1422
+ // — full _ref covers all of these.
1423
+ seq($.kRead, field('getter', $._ref)),
1424
+ seq($.kWrite, field('setter', $._ref)),
1425
+ seq($.kImplements, field('implements', delimited($._expr))),
1426
+ seq($.kDefault, field('defaultValue', $._expr)),
1427
+ seq($.kStored, field('stored', $._expr)),
1428
+ $.kNodefault,
1429
+ $.kReadonly, // OLE dispinterface property: readonly dispid 0
1430
+ $.kWriteonly, // OLE dispinterface property: writeonly dispid N
1431
+ )),
1432
+ ';',
1433
+ repeat($._procAttribute)
1434
+ ),
1435
+
1436
+ declPropArgs: $ => seq('[', delimited($.declArg, ';'), ']'),
1437
+
1438
+ // Variant records
1439
+
1440
+ declVariant: $ => prec.right(seq(
1441
+ $.kCase,
1442
+ field('name', optional(seq($.identifier, ':'))),
1443
+ field('type', $.typeref), $.kOf,
1444
+ delimited1($.declVariantClause, ';'),
1445
+ optional(';'),
1446
+ )),
1447
+
1448
+ declVariantClause: $ => seq(
1449
+ $.caseLabel,
1450
+ '(',
1451
+ choice(
1452
+ seq(delimited(alias($.declVariantField, $.declField), ';'), optional(seq(';', $.declVariant))),
1453
+ seq($.declVariant),
1454
+ ),
1455
+ optional(';'),
1456
+ ')',
1457
+ ),
1458
+
1459
+ declVariantField: $ => seq(
1460
+ // Phase 3b iter 32: allow RTTI attribute on variant-record field.
1461
+ // Pattern: `case byte of 1:( [Example] V: Integer; );` (YADF test).
1462
+ optional($.rttiAttributes),
1463
+ field('name', delimited1($.identifier)),
1464
+ ':',
1465
+ field('type', $.type),
1466
+ field('defaultValue', optional($.defaultValue))
1467
+ ),
1468
+
1469
+ // Stuff for procedure / function / operator declarations
1470
+
1471
+ _declProc: $ => seq(
1472
+ ...enable_if(fpc, optional($.kGeneric)),
1473
+ optional($.kClass),
1474
+ choice($.kProcedure, $.kFunction, $.kConstructor, $.kDestructor),
1475
+ field('name', $._genericName),
1476
+ field('args', optional($.declArgs)),
1477
+ optional(seq(
1478
+ ':',
1479
+ field('type', $.typeref),
1480
+ )),
1481
+ field('assign', optional($.defaultValue)),
1482
+ // Inline calling-convention or procAttribute BEFORE the terminating
1483
+ // ';' — Embarcadero RTL / AzureAPI use lenient forms:
1484
+ // function DbiInitFn(...): DBIResult stdcall; (iter 56)
1485
+ // procedure PreflightBlobRequest(...) overload; (iter 60)
1486
+ // `repeat` allows multiple chained attributes like `... cdecl overload`.
1487
+ repeat(choice(
1488
+ $.kStdcall, $.kCdecl, $.kSafecall, $.kPascal,
1489
+ $.kRegister, $.kWinapi, $.kInline,
1490
+ $.kOverload, $.kVirtual, $.kAbstract, $.kOverride,
1491
+ $.kReintroduce, $.kStatic, $.kDynamic, $.kFinal
1492
+ )),
1493
+ ';',
1494
+ repeat($._procAttributeNoExt)
1495
+ ),
1496
+
1497
+ _declOperator: $ => seq(
1498
+ optional($.kClass),
1499
+ $.kOperator,
1500
+ field('name', $._operatorName),
1501
+ field('args', optional($.declArgs)),
1502
+ ...enable_if(fpc, field('resultName', optional($.identifier))),
1503
+ // Return type is omitted for procedure-style class operators on
1504
+ // managed records: Initialize, Finalize, Assign.
1505
+ optional(seq(
1506
+ ':',
1507
+ field('type', $.type),
1508
+ field('assign', optional($.defaultValue))
1509
+ )),
1510
+ ';',
1511
+ repeat($._procAttributeNoExt)
1512
+ ),
1513
+
1514
+ operatorDot: $ => op.infix(0, $._genericName, $.kDot, $.operatorName),
1515
+ _operatorName: $ => seq(
1516
+ choice(
1517
+ $._genericName,
1518
+ ...enable_if(fpc,
1519
+ $.operatorName,
1520
+ alias($.operatorDot, $.genericDot)
1521
+ )
1522
+ )
1523
+ ),
1524
+ operatorName: $ => choice(
1525
+ $.kDot, $.kLt, $.kEq, $.kNeq, $.kGt, $.kLte, $.kGte,
1526
+ $.kAdd, $.kSub, $.kMul, $.kFdiv, $.kDiv, $.kMod,
1527
+ $.kAssign,
1528
+ $.kOr, $.kXor, $.kAnd, $.kShl, $.kShr, $.kNot,
1529
+ $.kIn,
1530
+ ),
1531
+
1532
+ declArgs: $ => seq('(', delimited($.declArg, ';'), ')'),
1533
+
1534
+ declArg: $ => choice(
1535
+ seq(
1536
+ // Phase 3b iter 20: allow rttiAttributes BEFORE the modifier
1537
+ // keyword, for IFDEF-wrapped attribute patterns:
1538
+ // class operator Equal({$IFDEF X}[ref]{$ENDIF}const left: T)
1539
+ // THEN-wins exposes `[ref] const left: T`, so the bracketed
1540
+ // attribute precedes `const`. Original between-modifier-and-name
1541
+ // placement is still supported via the second optional below.
1542
+ optional($.rttiAttributes),
1543
+ choice($.kVar, $.kConst, $.kOut, $.kConstref),
1544
+ // Inline attribute between modifier and name:
1545
+ // `const [ref] X: T` — pass-by-reference const (Delphi 10+).
1546
+ // Generally any bracketed attribute is allowed here.
1547
+ optional($.rttiAttributes),
1548
+ field('name', delimited1($.identifier)),
1549
+ optional(seq(
1550
+ ':', field('type', $.type),
1551
+ field('defaultValue', optional($.defaultValue))
1552
+ ))
1553
+ ),
1554
+ seq(
1555
+ // Phase 3b iter 15: allow bare `[Ref]` / `[in, ...]` attribute
1556
+ // before arg name without a leading modifier keyword. Win API
1557
+ // D3D11 wrappers use `[Ref] ppConstantBuffers: ID3D11Buffer`.
1558
+ optional($.rttiAttributes),
1559
+ field('name', delimited1($.identifier)), ':',
1560
+ field('type', $.type),
1561
+ field('defaultValue', optional($.defaultValue))
1562
+ )
1563
+ ),
1564
+
1565
+ // Attributes & declaration hints
1566
+
1567
+ _procAttribute: $ => /*pp($,*/choice(
1568
+ // Phase 3b iter 26: allow CHAINED proc attributes sharing one `;`,
1569
+ // for FireDAC.Phys.SQLiteCli pattern `; cdecl varargs;` on
1570
+ // procedural-type declarations. Single-attr form still matches.
1571
+ seq(repeat1(field('attribute', $.procAttribute)), ';'),
1572
+ // FPC-specific syntax, e.g. procedure myproc; [public; alias:'bla'; cdecl];
1573
+ ...enable_if(fpc, seq(
1574
+ '[',
1575
+ delimited(field('attribute', choice($.procAttribute, $.procExternal))),
1576
+ ']', ';'
1577
+ ))
1578
+ )/*)*/,
1579
+ _procAttributeNoExt: $ => /*pp($,*/ choice(
1580
+ seq(repeat1(field('attribute', $.procAttribute)), ';'),
1581
+ // FPC-specific syntax, e.g. procedure myproc; [public; alias:'bla'; cdecl];
1582
+ ...enable_if(fpc, seq('[', delimited(field('attribute', choice($.procAttribute)), ';'), ']', ';'))
1583
+ )/*)*/,
1584
+
1585
+ procAttribute: $ => choice(
1586
+ $.kStatic, $.kVirtual, $.kDynamic, $.kAbstract, $.kOverride,
1587
+ $.kOverload, $.kReintroduce, $.kInline, $.kStdcall,
1588
+ $.kCdecl, $.kPascal, $.kRegister, $.kSafecall, $.kAssembler,
1589
+ $.kNoreturn, $.kLocal, $.kFar, $.kNear,
1590
+ $.kDefault, $.kNodefault, $.kDeprecated, $.kExperimental,
1591
+ $.kFinal, // sealed-method modifier: 'function X; override; final;'
1592
+ $.kPlatform, // valid in Delphi too (was fpc-only)
1593
+ $.kExport, // legacy DLL-export modifier (valid in Delphi too)
1594
+ $.kVarargs, // variadic-arg modifier on cdecl functions
1595
+ $.kWinapi, // calling convention (alias for stdcall on Win32)
1596
+ $.kInterrupt,
1597
+ // Phase 3b iter 39: legacy `library` hint on proc decls
1598
+ // (Rave11 RpCanvas/RpFiler etc. `override; deprecated; library;`).
1599
+ $.kLibrary,
1600
+ // NOTE: kForward intentionally NOT here — it conflicts with the
1601
+ // declProcFwd rule which has its own `; forward;` handling.
1602
+
1603
+ seq(
1604
+ choice(
1605
+ seq($.kMessage, optional($.kName)),
1606
+ $.kDeprecated
1607
+ ),
1608
+ $._expr
1609
+ ),
1610
+
1611
+ ...enable_if(fpc,
1612
+ $.kPlatform, $.kUnimplemented,
1613
+ $.kCppdecl, $.kCvar, $.kMwpascal, $.kNostackframe,
1614
+ $.kInterrupt, $.kIocheck, $.kHardfloat,
1615
+ $.kSoftfloat, $.kMs_abi_default, $.kMs_abi_cdecl,
1616
+ $.kSaveregisters, $.kSysv_abi_default, $.kSysv_abi_cdecl,
1617
+ $.kVectorcall, $.kVarargs, $.kWinapi,
1618
+ ...enable_if(public_name, $.kPublic),
1619
+ seq(
1620
+ choice(
1621
+ $.kExport,
1622
+ seq($.kAlias, ':'),
1623
+ ...enable_if(public_name, seq($.kPublic, $.kName)),
1624
+ ),
1625
+ $._expr
1626
+ )
1627
+ ),
1628
+
1629
+ ...enable_if(delphi, field('dispid', seq($.kDispId, $._expr))),
1630
+ ),
1631
+
1632
+ rttiAttributes: $ => repeat1(seq(
1633
+ // Note: "Identifier:" is for tagging parameters of procedures (Delphi)
1634
+ '[', optional(seq($.identifier, ':')), delimited($._ref), ']'
1635
+ )),
1636
+
1637
+ procExternal: $ => seq(
1638
+ $.kExternal,
1639
+ optional($._expr),
1640
+ optional(seq(choice($.kName, $.kIndex), $._expr)),
1641
+ ...enable_if(delphi, optional($.kDelayed)),
1642
+ // Phase 3b iter 17: native library dependency hint
1643
+ // function f; cdecl; external LIB name 'foo' dependency 'c++_static','c++abi';
1644
+ // (Embarcadero FlatBox2D Android/iOS linker pattern.)
1645
+ // Phase 3b iter 35: `dependency` clause may use identifier-name
1646
+ // constants too: `dependency LibCPP, LibCPP_ABI` (FireDAC.Phys.IBCli).
1647
+ optional(seq(
1648
+ $.kDependency,
1649
+ delimited1(choice($._literalString, $.identifier)),
1650
+ )),
1651
+ ';'
1652
+ ),
1653
+
1654
+ // INITIALIZERS --------------------------------------------------------
1655
+
1656
+ _initializer: $ => prec(2,seq(
1657
+ choice($._expr, $.recInitializer, $.arrInitializer)
1658
+ )),
1659
+
1660
+ // record initializer
1661
+ recInitializer: $ => seq(
1662
+ '(',
1663
+ delimited1($.recInitializerField, ';'),
1664
+ // Optional trailing `;` before `)` — accepted by Delphi compiler,
1665
+ // used in DevExpress dxCore Unicode tables and similar generated
1666
+ // data files.
1667
+ optional(';'),
1668
+ ')'
1669
+ ),
1670
+
1671
+ recInitializerField: $ => choice(
1672
+ seq(field('name',$.identifier), ':', field('value', $._initializer)),
1673
+ field('value', $._initializer)
1674
+ ),
1675
+
1676
+ // array initializer
1677
+ // Array / set initializer. Allows empty `()` (Spring4D const sentinels:
1678
+ // `Arg: TArg = ();`) as well as `(a, b, c)` populated form.
1679
+ arrInitializer: $ => prec(1,seq('(', delimited($._initializer), ')')),
1680
+
1681
+ // TERMINAL SYMBOLS ----------------------------------------------------
1682
+
1683
+ kProgram: $ => /program/i,
1684
+ kLibrary: $ => /library/i,
1685
+ kUnit: $ => /unit/i,
1686
+ kPackage: $ => /package/i,
1687
+ kRequires: $ => /requires/i,
1688
+ kContains: $ => /contains/i,
1689
+ kUses: $ => /uses/i,
1690
+ kInterface: $ => /interface/i,
1691
+ kDispInterface: $ => /dispinterface/i,
1692
+ kImplementation: $ => /implementation/i,
1693
+ kInitialization: $ => /initialization/i,
1694
+ kFinalization: $ => /finalization/i,
1695
+ kEndDot: $ => '.',
1696
+
1697
+ kBegin: $ => /begin/i,
1698
+ kEnd: $ => /end/i,
1699
+ kAsm: $ => /asm/i,
1700
+
1701
+ kVar: $ => /var/i,
1702
+ kThreadvar: $ => /threadvar/i,
1703
+ kConst: $ => /const/i,
1704
+ kConstref: $ => /constref/i,
1705
+ kResourcestring: $ => /resourcestring/i,
1706
+ kOut: $ => /out/i,
1707
+ kType: $ => /type/i,
1708
+ kLabel: $ => /label/i,
1709
+ kExports: $ => /exports/i,
1710
+
1711
+ kAbsolute: $ => /absolute/i,
1712
+
1713
+ kProperty: $ => /property/i,
1714
+ kRead: $ => /read/i,
1715
+ kWrite: $ => /write/i,
1716
+ kImplements: $ => /implements/i,
1717
+ kDefault: $ => /default/i,
1718
+ kNodefault: $ => /nodefault/i,
1719
+ kStored: $ => /stored/i,
1720
+ kIndex: $ => /index/i,
1721
+ kDispId: $ => /dispid/i,
1722
+
1723
+ kClass: $ => /class/i,
1724
+ kInterface: $ => /interface/i,
1725
+ kObject: $ => /object/i,
1726
+ kRecord: $ => /record/i,
1727
+ kObjcclass: $ => /objcclass/i,
1728
+ kObjccategory: $ => /objccategory/i,
1729
+ kObjcprotocol: $ => /objcprotocol/i,
1730
+ kArray: $ => /array/i,
1731
+ kFile: $ => /file/i,
1732
+ kString: $ => /string/i,
1733
+ kSet: $ => /set/i,
1734
+ kOf: $ => /of/i,
1735
+ kHelper: $ => /helper/i,
1736
+ kPacked: $ => /packed/i,
1737
+
1738
+ kGeneric: $ => /generic/i,
1739
+ kSpecialize: $ => /specialize/i,
1740
+
1741
+ kDot: $ => '.',
1742
+ kLt: $ => '<',
1743
+ kEq: $ => '=',
1744
+ kNeq: $ => '<>',
1745
+ kGt: $ => '>',
1746
+ kLte: $ => '<=',
1747
+ kGte: $ => '>=',
1748
+ kAdd: $ => '+',
1749
+ kSub: $ => '-',
1750
+ kMul: $ => '*',
1751
+ kFdiv: $ => '/',
1752
+ kAt: $ => '@',
1753
+ // `at` keyword (raise with address): `raise E at ReturnAddress`.
1754
+ // Distinct from `@` operator (kAt). Soft keyword — must not clash
1755
+ // with normal identifiers.
1756
+ kAtWord: $ => /at/i,
1757
+ kHat: $ => '^',
1758
+ kAssign: $ => ':=',
1759
+ kAssignAdd: $ => '+=', // Freepascal
1760
+ kAssignSub: $ => '-=', // Freepascal
1761
+ kAssignMul: $ => '*=', // Freepascal
1762
+ kAssignDiv: $ => '/=', // Freepascal
1763
+ kOr: $ => /or/i,
1764
+ kXor: $ => /xor/i,
1765
+ kDiv: $ => /div/i,
1766
+ kMod: $ => /mod/i,
1767
+ kAnd: $ => /and/i,
1768
+ kShl: $ => /shl/i,
1769
+ kShr: $ => /shr/i,
1770
+ kNot: $ => /not/i,
1771
+ kIs: $ => /is/i,
1772
+ kAs: $ => /as/i,
1773
+ kIn: $ => /in/i,
1774
+
1775
+ kFor: $ => /for/i,
1776
+ kTo: $ => /to/i,
1777
+ kDownto: $ => /downto/i,
1778
+ kIf: $ => /if/i,
1779
+ kThen: $ => /then/i,
1780
+ kElse: $ => /else/i,
1781
+ kDo: $ => /do/i,
1782
+ kWhile: $ => /while/i,
1783
+ kRepeat: $ => /repeat/i,
1784
+ kUntil: $ => /until/i,
1785
+ kTry: $ => /try/i,
1786
+ kExcept: $ => /except/i,
1787
+ kFinally: $ => /finally/i,
1788
+ kRaise: $ => /raise/i,
1789
+ kOn: $ => /on/i,
1790
+ kCase: $ => /case/i,
1791
+ kWith: $ => /with/i,
1792
+ kGoto: $ => /goto/i,
1793
+
1794
+ kFunction: $ => /function/i,
1795
+ kProcedure: $ => /procedure/i,
1796
+ kConstructor: $ => /constructor/i,
1797
+ kDestructor: $ => /destructor/i,
1798
+ kOperator: $ => /operator/i,
1799
+ kReference: $ => /reference/i,
1800
+
1801
+ kPublished: $ => /published/i,
1802
+ kPublic: $ => /public/i,
1803
+ kProtected: $ => /protected/i,
1804
+ kPrivate: $ => /private/i,
1805
+ kStrict: $ => /strict/i,
1806
+ kRequired: $ => /required/i,
1807
+ kOptional: $ => /optional/i,
1808
+
1809
+ kForward: $ => /forward/i,
1810
+
1811
+ kStatic: $ => /static/i,
1812
+ kVirtual: $ => /virtual/i,
1813
+ kAbstract: $ => /abstract/i,
1814
+ kSealed: $ => /sealed/i,
1815
+ kFinal: $ => /final/i,
1816
+ kReadonly: $ => /readonly/i,
1817
+ kWriteonly: $ => /writeonly/i,
1818
+ kDynamic: $ => /dynamic/i,
1819
+ kOverride: $ => /override/i,
1820
+ kOverload: $ => /overload/i,
1821
+ kReintroduce: $ => /reintroduce/i,
1822
+ kInherited: $ => /inherited/i,
1823
+ kInline: $ => /inline/i,
1824
+
1825
+ kStdcall: $ => /stdcall/i,
1826
+ kCdecl: $ => /cdecl/i,
1827
+ kCppdecl: $ => /cppdecl/i,
1828
+ kPascal: $ => /pascal/i,
1829
+ kRegister: $ => /register/i,
1830
+ kMwpascal: $ => /mwpascal/i,
1831
+ kExternal: $ => /external/i,
1832
+ kName: $ => /name/i,
1833
+ kMessage: $ => /message/i,
1834
+ kDeprecated: $ => /deprecated/i,
1835
+ kExperimental: $ => /experimental/i,
1836
+ kPlatform: $ => /platform/i,
1837
+ // Soft keyword: only used in `record ... end align N` record-alignment
1838
+ // hint (Win API records). Tokenizes everywhere but only consumed
1839
+ // structurally in _declClass post-end clause.
1840
+ kAlign: $ => /align/i,
1841
+ // Soft keyword: native library dependency hint in procExternal.
1842
+ kDependency: $ => /dependency/i,
1843
+ kUnimplemented: $ => /unimplemented/i,
1844
+ kCvar: $ => /cvar/i,
1845
+ kExport: $ => /export/i,
1846
+ kFar: $ => /far/i,
1847
+ kNear: $ => /near/i,
1848
+ kSafecall: $ => /safecall/i,
1849
+ kAssembler: $ => /assembler/i,
1850
+ kNostackframe: $ => /nostackframe/i,
1851
+ kInterrupt: $ => /interrupt/i,
1852
+ kNoreturn: $ => /noreturn/i,
1853
+ kIocheck: $ => /iocheck/i,
1854
+ kLocal: $ => /local/i,
1855
+ kHardfloat: $ => /hardfloat/i,
1856
+ kSoftfloat: $ => /softfloat/i,
1857
+ kMs_abi_default: $ => /ms_abi_default/i,
1858
+ kMs_abi_cdecl: $ => /ms_abi_cdecl/i,
1859
+ kSaveregisters: $ => /saveregisters/i,
1860
+ kSysv_abi_default: $ => /sysv_abi_default/i,
1861
+ kSysv_abi_cdecl: $ => /sysv_abi_cdecl/i,
1862
+ kVectorcall: $ => /vectorcall/i,
1863
+ kVarargs: $ => /varargs/i,
1864
+ kWinapi: $ => /winapi/i,
1865
+ kAlias: $ => /alias/i,
1866
+ // Delphi
1867
+ kDelayed: $ => /delayed/i,
1868
+
1869
+ kNil: $ => /nil/i,
1870
+ kTrue: $ => /true/i,
1871
+ kFalse: $ => /false/i,
1872
+
1873
+ kIfdef: $ => /ifdef/i,
1874
+ kIfndef: $ => /ifndef/i,
1875
+ kEndif: $ => /endif/i,
1876
+
1877
+ // `&` prefix escapes a keyword as an identifier (`&type`).
1878
+ // `&&` prefix is the Delphi.NET operator-name convention
1879
+ // (`&&op_Equality`, `&&op_Implicit`, etc.) still used in Spring4D's
1880
+ // generic operator overloads.
1881
+ // Phase 3b iter 40: identifier accepts Unicode letters too.
1882
+ // Delphi allows non-ASCII identifiers (umlauts, accented letters,
1883
+ // Cyrillic, etc.). Pattern: ASCII letter/underscore start, then
1884
+ // any letter/digit/underscore including the Unicode-letter range
1885
+ // U+0080+ (Latin-1 Supplement onwards). Excludes ASCII punctuation
1886
+ // so structural tokens stay intact.
1887
+ identifier: $ => /&{0,2}[a-zA-Z_€-￿][0-9_a-zA-Z€-￿]*/,
1888
+
1889
+ _space: $ => /[\s\r\n\t]+/,
1890
+ // Single preprocessor directive. The external scanner (src/scanner.c)
1891
+ // handles multi-line `{$IF*}...{$END*}` blocks via the pp_block token,
1892
+ // refusing blocks that wrap structural decls so they parse normally.
1893
+ pp: $ => /\{\$[^}]*\}/,
1894
+ comment: $ => token(choice(
1895
+ seq('//', /.*/),
1896
+ seq('{', /([^$}][^}]*)?/, '}'),
1897
+ /[(][*]([^*]*[*]+[^)*])*[^*]*[*]+[)]/
1898
+ )),
1899
+ }
1900
+ });