tree-sitter-delphi13 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -11
- package/grammar.js +247 -39
- package/package.json +1 -1
- package/src/grammar.json +982 -77
- package/src/node-types.json +83 -0
- package/src/parser.c +424082 -369480
package/README.md
CHANGED
|
@@ -5,27 +5,37 @@
|
|
|
5
5
|
|
|
6
6
|
Tree-sitter grammar pipeline focused on **Delphi 13 (RAD Studio 37.0 / Florence)** and the surrounding modern Delphi ecosystem.
|
|
7
7
|
|
|
8
|
-
**
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
**v1.2.1: 100.000% pass rate on compilable Delphi 13** (zero ERROR nodes), measured
|
|
9
|
+
on an 11,722-unique-file corpus spanning the RAD Studio 13 RTL/VCL (including
|
|
10
|
+
`System.pas`), DevExpress, Spring4D, EurekaLog, Indy, FireDAC, AsyncPro/Orpheus,
|
|
11
|
+
mORMot-era community code and a full production application. Every failure that
|
|
12
|
+
remains is individually diagnosed, dcc32-cross-checked, and *supposed* to fail
|
|
13
|
+
(see [CORPUS-CEILING-REPORT.md](CORPUS-CEILING-REPORT.md)).
|
|
14
|
+
|
|
15
|
+
| measurement basis | pass rate |
|
|
11
16
|
|---|---|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
| Embarcadero RAD Studio 13 RTL/VCL | 99.24% |
|
|
17
|
+
| orchestrated pipeline (preprocessor → pure), all rows | 99.82% |
|
|
18
|
+
| deduplicated files | 99.74% |
|
|
19
|
+
| deduplicated, valid-Delphi-13-only (excludes broken fixtures, FPC/.NET files) | **100.000%** |
|
|
20
|
+
| own production code (ORM3, drag-lint) | **100.00%** |
|
|
17
21
|
|
|
18
|
-
The
|
|
22
|
+
The 30 files that still fail are intentionally broken test fixtures,
|
|
23
|
+
truncated/corrupted files, FreePascal- or .NET-only sources, and vendor files with
|
|
24
|
+
genuine syntax errors that dcc32 rejects too — a parser that accepted them would be
|
|
25
|
+
wrong. Zero known gaps on valid Delphi 13 remain ([TODO.md](TODO.md) tracks the
|
|
26
|
+
watchlist).
|
|
19
27
|
|
|
20
28
|
## Three packages, one repo
|
|
21
29
|
|
|
22
30
|
| Package | What it does |
|
|
23
31
|
|---|---|
|
|
24
|
-
| **`tree-sitter-delphi13`** *(this directory)* | Master grammar — Delphi-13 syntax, all proven fixes from upstream merged + new extensions. Self-contained "THEN-wins" parser (
|
|
32
|
+
| **`tree-sitter-delphi13`** *(this directory)* | Master grammar — Delphi-13 syntax, all proven fixes from upstream merged + new extensions. Self-contained "THEN-wins" parser (98.6% raw on its own). |
|
|
25
33
|
| **[`tree-sitter-delphi13-pure`](pure/)** | Simpler sub-grammar that drops `pp_*` tokens entirely. Expects preprocessor-resolved source. |
|
|
26
34
|
| **[`delphi13-preprocessor`](preprocessor/)** | Standalone JavaScript library that resolves `{$IFDEF}` / `{$IF}` / `{$DEFINE}` / `{$I X.inc}` directives as a text transformation. |
|
|
27
35
|
|
|
28
|
-
The full pipeline `preprocessor → pure → AST` is what reaches **99.
|
|
36
|
+
The full pipeline `preprocessor → pure → AST` is what reaches **99.97%** on valid
|
|
37
|
+
Delphi 13. Use the master grammar alone (98.6% raw) if you don't need
|
|
38
|
+
conditional-compilation awareness; use the pipeline if you do.
|
|
29
39
|
|
|
30
40
|
## Architecture
|
|
31
41
|
|
package/grammar.js
CHANGED
|
@@ -104,8 +104,18 @@ function enable_if(cond, ...args) {
|
|
|
104
104
|
// Generate rules for trailing & non-trailing statements
|
|
105
105
|
function statements(trailing) {
|
|
106
106
|
let rn = x => trailing ? x + 'Tr' : x
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
// Iter 2026-07-16: a goto label may prefix the single-statement BODY of
|
|
108
|
+
// then/else/do/on (labels already parse at statement-LIST level via
|
|
109
|
+
// `_statements`): `if Index = 0 then Found: LFNParamStr := ...`
|
|
110
|
+
// (AsyncPro LFN.pas), `else FoundMismatch: Exit(-1)` (System.pas),
|
|
111
|
+
// `while true do redo: case ...` (superobject). prec.dynamic(-1): when
|
|
112
|
+
// the label could equally close an EMPTY body and open a case-arm
|
|
113
|
+
// (`case x of 1: if c then Foo: ...` — Foo as arm label vs body label),
|
|
114
|
+
// the arm interpretation must keep winning — that ambiguity is what
|
|
115
|
+
// cascaded the previous broad-scope attempt.
|
|
116
|
+
let bodyLabel = $ => optional(prec.dynamic(-1, $.label));
|
|
117
|
+
let lastStatement = $ => trailing ? optional(seq(bodyLabel($), tr($,'_statement'))) : seq(bodyLabel($), $._statement);
|
|
118
|
+
let lastStatement1= $ => trailing ? seq(bodyLabel($), tr($,'_statement')) : seq(bodyLabel($), $._statement);
|
|
109
119
|
let semicolon = trailing ? [] : [';'];
|
|
110
120
|
|
|
111
121
|
return Object.fromEntries([
|
|
@@ -272,6 +282,40 @@ function statements(trailing) {
|
|
|
272
282
|
]);
|
|
273
283
|
}
|
|
274
284
|
|
|
285
|
+
// Shared front of a routine declaration (everything before the first `;`):
|
|
286
|
+
// used by BOTH `_declProc` (strict directive tail — backs defProc, where a
|
|
287
|
+
// body follows) and `_declProcLenient` (separator-form tail with optional
|
|
288
|
+
// final `;` — interface/forward declaration lists only).
|
|
289
|
+
function declProcFront($) {
|
|
290
|
+
return [
|
|
291
|
+
...enable_if(fpc, optional($.kGeneric)),
|
|
292
|
+
optional($.kClass),
|
|
293
|
+
choice($.kProcedure, $.kFunction, $.kConstructor, $.kDestructor),
|
|
294
|
+
field('name', $._genericName),
|
|
295
|
+
field('args', optional($.declArgs)),
|
|
296
|
+
optional(seq(
|
|
297
|
+
':',
|
|
298
|
+
field('type', $.typeref),
|
|
299
|
+
)),
|
|
300
|
+
field('assign', optional($.defaultValue)),
|
|
301
|
+
// Inline calling-convention or procAttribute BEFORE the terminating
|
|
302
|
+
// ';' — Embarcadero RTL / AzureAPI use lenient forms:
|
|
303
|
+
// function DbiInitFn(...): DBIResult stdcall; (iter 56)
|
|
304
|
+
// procedure PreflightBlobRequest(...) overload; (iter 60)
|
|
305
|
+
// `repeat` allows multiple chained attributes like `... cdecl overload`.
|
|
306
|
+
repeat(choice(
|
|
307
|
+
$.kStdcall, $.kCdecl, $.kSafecall, $.kPascal,
|
|
308
|
+
$.kRegister, $.kWinapi, $.kInline,
|
|
309
|
+
$.kOverload, $.kVirtual, $.kAbstract, $.kOverride,
|
|
310
|
+
$.kReintroduce, $.kStatic, $.kDynamic, $.kFinal,
|
|
311
|
+
// ARC/AUTOREFCOUNT method directive: `class function NewInstance:
|
|
312
|
+
// TObject unsafe; override;` (System.pas / JsonDataObjects /
|
|
313
|
+
// FireDAC guard NewInstance with {$IFDEF AUTOREFCOUNT} unsafe).
|
|
314
|
+
$.kUnsafe
|
|
315
|
+
)),
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
|
|
275
319
|
module.exports = grammar({
|
|
276
320
|
name: "delphi13",
|
|
277
321
|
|
|
@@ -381,6 +425,37 @@ module.exports = grammar({
|
|
|
381
425
|
// the two declArg branches ambiguous at the group boundary; let GLR resolve.
|
|
382
426
|
[$.rttiAttributes],
|
|
383
427
|
[$.declArg],
|
|
428
|
+
// Iter 2026-07-16 (implicit-init): the unit tail's `tr($,'block')` arm
|
|
429
|
+
// forks against `implementation` absorbing the same `begin` into its
|
|
430
|
+
// `_definitions` (the prec(-1) blockTr recovery in `_definition`).
|
|
431
|
+
// Reduce-implementation-early must be a live GLR fork; precedence picks
|
|
432
|
+
// the unit-tail block (prec 0 beats the recovery's -1). This is the
|
|
433
|
+
// single declarable conflict the old flat-repeat shape could not
|
|
434
|
+
// express (its fork landed inside `_statementsTr_repeat1`).
|
|
435
|
+
[$.implementation],
|
|
436
|
+
// Iter 2026-07-16 (label-as-body): a label prefixing a case-arm's BODY
|
|
437
|
+
// statement forks against the arm's own jumpLabel slot (`1: Foo: x`).
|
|
438
|
+
// GLR keeps both; prec.dynamic(-1) on the body label makes the
|
|
439
|
+
// jumpLabel/arm interpretation win wherever both complete.
|
|
440
|
+
[$.caseCase],
|
|
441
|
+
[$.caseCaseTr],
|
|
442
|
+
// Iter 2026-07-16 (lenient directive tail): inside _declProcLenient a
|
|
443
|
+
// `;` after an attribute group is either the group SEPARATOR (another
|
|
444
|
+
// group follows) or the optional FINAL `;` — one further token
|
|
445
|
+
// decides; GLR forks briefly at the attribute keyword.
|
|
446
|
+
[$.procAttribute],
|
|
447
|
+
// ...and the lenient tail's groups fork against declProcFwd's strict
|
|
448
|
+
// `_procAttributeNoExt` groups while both interpretations are still
|
|
449
|
+
// live (`function F; stdcall; forward;` — forward only decides later).
|
|
450
|
+
[$._declProcLenient, $._procAttributeNoExt],
|
|
451
|
+
// Iter 2026-07-16 (Register as a field name): after `Foo: T;` the
|
|
452
|
+
// token `Register` is either the previous field's trailing callconv
|
|
453
|
+
// (`; register;`) or the NEXT field's name (`Register: UINT;`) —
|
|
454
|
+
// `;` vs `:` one token later decides; GLR forks across the
|
|
455
|
+
// declField/declFieldNoSemi boundary AND within declField itself
|
|
456
|
+
// (trailing-callconv slot vs end-of-field).
|
|
457
|
+
[$.declFieldNoSemi, $.declField],
|
|
458
|
+
[$.declField],
|
|
384
459
|
// The following conflict rules are only needed because "public" can be
|
|
385
460
|
// a visibility or an attribute. *sigh*
|
|
386
461
|
// TODO: We would probably avoid this by having separate decl* clauses
|
|
@@ -404,13 +479,30 @@ module.exports = grammar({
|
|
|
404
479
|
|
|
405
480
|
rules: {
|
|
406
481
|
root: $ => optional(choice(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
482
|
+
seq(
|
|
483
|
+
choice(
|
|
484
|
+
$.program,
|
|
485
|
+
$.library,
|
|
486
|
+
$.unit,
|
|
487
|
+
$.package,
|
|
488
|
+
),
|
|
489
|
+
// Iter 2026-07-16: dcc ignores everything after the final
|
|
490
|
+
// `end.` (warning W1011 only) — e.g. a stray `uses X;` left
|
|
491
|
+
// after the closer (mvvm-in-delphi MainScreenForm.pas). One
|
|
492
|
+
// low-precedence catch-all token; linters can flag the node.
|
|
493
|
+
optional($.trailingText),
|
|
494
|
+
),
|
|
411
495
|
$._definitions // For include files
|
|
412
496
|
)),
|
|
413
497
|
|
|
498
|
+
// Everything from the first non-blank char after the module's final
|
|
499
|
+
// `end.` to EOF. token(prec(-2)) so real tokens (incl. comments,
|
|
500
|
+
// which remain ordinary extras after `end.`) always win over it.
|
|
501
|
+
// NOTE: `(.|\s)*` rather than `[\s\S]*` — tree-sitter's regex engine
|
|
502
|
+
// compiles the latter to a non-greedy/empty match (token stopped after
|
|
503
|
+
// one char; observed 2026-07-16).
|
|
504
|
+
trailingText: $ => token(prec(-2, /[^\s](.|\s)*/)),
|
|
505
|
+
|
|
414
506
|
// HIGH LEVEL ----------------------------------------------------------
|
|
415
507
|
|
|
416
508
|
program: $ => seq(
|
|
@@ -443,13 +535,41 @@ module.exports = grammar({
|
|
|
443
535
|
$.kLibrary,
|
|
444
536
|
)),
|
|
445
537
|
';',
|
|
538
|
+
// Unit tail, restructured 2026-07-16 (iter: implicit-init). The old
|
|
539
|
+
// shape was one flat `repeat(choice(interface, implementation,
|
|
540
|
+
// initialization, finalization))` followed by `kEnd kEndDot`. That
|
|
541
|
+
// made the Turbo-Pascal implicit initialization block (`begin
|
|
542
|
+
// <stmts> end.` — still accepted by dcc 13; bdemts.pas, SHDocVw.pas,
|
|
543
|
+
// System.Win.InternetExplorer.pas, Winapi.OpenGL.PkgHelper.pas,
|
|
544
|
+
// AsyncPro APFPDENG.pas) unparseable: `optional(blockish)` before
|
|
545
|
+
// `kEnd` forks against `initialization`'s own statement list and
|
|
546
|
+
// cascades into the auto-generated `_statementsTr_repeat1` states,
|
|
547
|
+
// which cannot be named in `conflicts` (attempted + reverted).
|
|
548
|
+
// The fix: split `initialization`/`finalization` OUT of the flat
|
|
549
|
+
// repeat into the kEnd tail arm, and give the implicit block its own
|
|
550
|
+
// mutually-exclusive arm — after `initialization` appears, the block
|
|
551
|
+
// arm is unreachable, so the fork never exists. The block arm itself
|
|
552
|
+
// is the same `tr($,'block')` used by `program`/`library` tails, so
|
|
553
|
+
// the only fork is blockTr-inside-_definitions (the prec(-1)
|
|
554
|
+
// recovery rule in `_definition`) vs blockTr-as-unit-tail (prec 0),
|
|
555
|
+
// which GLR resolves by precedence exactly as it already does for
|
|
556
|
+
// `library`. CST: the implicit block renders as the same `block`
|
|
557
|
+
// node `program`/`library` produce.
|
|
446
558
|
repeat(choice(
|
|
447
559
|
$.interface,
|
|
448
560
|
$.implementation,
|
|
449
|
-
$.initialization,
|
|
450
|
-
$.finalization,
|
|
451
561
|
)),
|
|
452
|
-
|
|
562
|
+
choice(
|
|
563
|
+
seq(
|
|
564
|
+
repeat(choice(
|
|
565
|
+
$.initialization,
|
|
566
|
+
$.finalization,
|
|
567
|
+
)),
|
|
568
|
+
$.kEnd
|
|
569
|
+
),
|
|
570
|
+
tr($,'block'),
|
|
571
|
+
),
|
|
572
|
+
$.kEndDot
|
|
453
573
|
),
|
|
454
574
|
|
|
455
575
|
// Delphi package file (.dpk). Structure:
|
|
@@ -540,6 +660,17 @@ module.exports = grammar({
|
|
|
540
660
|
// en/EN not followed by d/D, end immediately followed by ident-char
|
|
541
661
|
// (so it's NOT the keyword, e.g. 'endif' / 'endloop').
|
|
542
662
|
asmBody: $ => token(prec(-1, repeat1(choice(
|
|
663
|
+
// Iter 2026-07-16: consume a whole COMMENT as one chunk, so a bare
|
|
664
|
+
// `end` *inside* a comment doesn't falsely terminate the asm block.
|
|
665
|
+
// add edi,ecx {point EDI to end of destination} <-- AsyncPro AwFView
|
|
666
|
+
// mov eax,1 // end of loop
|
|
667
|
+
// `{$IFDEF}`-style directives were already safe (the `end[A-Za-z0-9_]`
|
|
668
|
+
// alternative below covers `endif`), but a comment containing the bare
|
|
669
|
+
// WORD `end` was not. Brace comments do not nest in Delphi, so
|
|
670
|
+
// `[^}]*` is exact. These must precede the single-char alternatives:
|
|
671
|
+
// the lexer takes the longest match, so `{...}` wins over `[^eE]`.
|
|
672
|
+
/\{[^}]*\}/,
|
|
673
|
+
/\/\/[^\n]*/,
|
|
543
674
|
// Identifier not starting with e/E — consume as a unit so an
|
|
544
675
|
// embedded `end` substring (like in `addend` or `xchg ext`)
|
|
545
676
|
// doesn't falsely terminate the body.
|
|
@@ -792,7 +923,19 @@ module.exports = grammar({
|
|
|
792
923
|
typeref: $ => seq(
|
|
793
924
|
...enable_if(fpc, field('_dummy', optional($.kSpecialize))),
|
|
794
925
|
$._typeref,
|
|
795
|
-
|
|
926
|
+
// Trailing declaration hints on the type reference itself. The
|
|
927
|
+
// kDeprecated+message form predates this note; kPlatform (bare)
|
|
928
|
+
// added 2026-07-16 for the hint-BEFORE-initializer pattern
|
|
929
|
+
// Default8087CW: Word platform = $033F; (System.pas, x6)
|
|
930
|
+
// — with the hint absorbed here, declVar's `= expr` defaultValue
|
|
931
|
+
// follows naturally. A hint-then-value ARM inside declVar itself
|
|
932
|
+
// was tried first and is a bisect-confirmed generate table bomb
|
|
933
|
+
// (see TODO.md); this mirrors the existing kDeprecated role-set
|
|
934
|
+
// instead, which the tables already accommodate.
|
|
935
|
+
...enable_if(delphi, optional(choice(
|
|
936
|
+
seq($.kDeprecated, $._expr),
|
|
937
|
+
$.kPlatform,
|
|
938
|
+
))),
|
|
796
939
|
),
|
|
797
940
|
|
|
798
941
|
_typeref: $ => choice(
|
|
@@ -850,8 +993,24 @@ module.exports = grammar({
|
|
|
850
993
|
$.identifier, $.genericDot, ...enable_if(templates, $.genericTpl)
|
|
851
994
|
),
|
|
852
995
|
genericArgs: $ => delimited1($.genericArg, ';'),
|
|
996
|
+
// A nested generic instantiation as a genericArg name element — a USE
|
|
997
|
+
// site reached through the declaration rules: the method resolution
|
|
998
|
+
// clause `function TFunc<T1, IEnumerable<TResult>>.Invoke = Bind;`
|
|
999
|
+
// (YADF generic-delegation snippet). Deliberately self-contained
|
|
1000
|
+
// (identifier-only, self-recursive) rather than referencing
|
|
1001
|
+
// genericTpl/_genericName: pulling that mutually-recursive web into
|
|
1002
|
+
// genericArg's item sets explodes table construction (observed: a
|
|
1003
|
+
// 20-minute generate, killed).
|
|
1004
|
+
genericArgTpl: $ => seq(
|
|
1005
|
+
$.identifier, $.kLt,
|
|
1006
|
+
delimited1(choice($.identifier, $.genericArgTpl)),
|
|
1007
|
+
$.kGt
|
|
1008
|
+
),
|
|
853
1009
|
genericArg: $ => seq(
|
|
854
|
-
|
|
1010
|
+
// Name element: bare identifier (declaration site `TFoo<T; U:
|
|
1011
|
+
// class>`) or a nested instantiation (use site, see genericArgTpl).
|
|
1012
|
+
// `<`-lookahead after the identifier disambiguates.
|
|
1013
|
+
field('name', delimited1(choice($.identifier, $.genericArgTpl))),
|
|
855
1014
|
// Type constraint: `T: BaseType` or one/more of the special
|
|
856
1015
|
// constraint keywords `class` / `record` / `constructor`, comma-
|
|
857
1016
|
// separated. Examples:
|
|
@@ -990,7 +1149,12 @@ module.exports = grammar({
|
|
|
990
1149
|
),
|
|
991
1150
|
|
|
992
1151
|
_declarations: $ => repeat1(choice(
|
|
993
|
-
$.declTypes, $.declVars, $.declConsts,
|
|
1152
|
+
$.declTypes, $.declVars, $.declConsts,
|
|
1153
|
+
// Interface declaration lists take the LENIENT directive tail
|
|
1154
|
+
// (final group may omit its `;` — dxCryptoAPI, dxServerModeUtils,
|
|
1155
|
+
// dxGDIPlusAPI); rendered as a plain declProc node.
|
|
1156
|
+
alias($.declProcLenient, $.declProc),
|
|
1157
|
+
$.declProp,
|
|
994
1158
|
alias($.declProcFwd, $.declProc),
|
|
995
1159
|
$.declUses, $.declLabels, $.declExports
|
|
996
1160
|
)),
|
|
@@ -1158,6 +1322,13 @@ module.exports = grammar({
|
|
|
1158
1322
|
...enable_if(rtti, optional($.rttiAttributes)),
|
|
1159
1323
|
choice($._declProc, $._declOperator),
|
|
1160
1324
|
),
|
|
1325
|
+
// Interface/forward flavor: lenient directive tail (see
|
|
1326
|
+
// _declProcLenient). Aliased back to `declProc` at its use site so
|
|
1327
|
+
// the CST is indistinguishable.
|
|
1328
|
+
declProcLenient: $ => seq(
|
|
1329
|
+
...enable_if(rtti, optional($.rttiAttributes)),
|
|
1330
|
+
choice($._declProcLenient, $._declOperator),
|
|
1331
|
+
),
|
|
1161
1332
|
|
|
1162
1333
|
declVar: $ => seq(
|
|
1163
1334
|
...enable_if(rtti, optional($.rttiAttributes)),
|
|
@@ -1191,6 +1362,16 @@ module.exports = grammar({
|
|
|
1191
1362
|
alias($.kDeprecated, $.identifier),
|
|
1192
1363
|
alias($.kExperimental, $.identifier),
|
|
1193
1364
|
alias($.kRegister, $.identifier),
|
|
1365
|
+
// Iter 2026-07-16: same family — `local` is a procAttribute
|
|
1366
|
+
// (FPC directive), so `var X: Integer; Local: Integer;` had the
|
|
1367
|
+
// parser eat `Local` as a trailing directive on X. This is the
|
|
1368
|
+
// L8398/DoSelfTestManifestMerge error f85b412 deferred as "not
|
|
1369
|
+
// the keyword-name family" — it is; it just isn't a hint/callconv.
|
|
1370
|
+
alias($.kLocal, $.identifier),
|
|
1371
|
+
// Iter 2026-07-16: `dispid` is a property/method directive, so
|
|
1372
|
+
// `var Foo: Integer; DispID: Integer;` hit the same trap.
|
|
1373
|
+
// (System.Win.ObjComAuto L341, Vcl.OleCtrls L2143.)
|
|
1374
|
+
alias($.kDispId, $.identifier),
|
|
1194
1375
|
))),
|
|
1195
1376
|
':',
|
|
1196
1377
|
// Phase 3b iter 36: declVar type may be a subrange:
|
|
@@ -1456,6 +1637,14 @@ module.exports = grammar({
|
|
|
1456
1637
|
// Bare `string`/`ShortString` has no such tail, so it is
|
|
1457
1638
|
// unambiguous here.
|
|
1458
1639
|
$.kString,
|
|
1640
|
+
// NOTE: `array[...] of T` as a last-field-no-`;` is intentionally
|
|
1641
|
+
// NOT here. Adding declArray fixes SHX/MongoDBCli/ShlObj (+3) but
|
|
1642
|
+
// regresses OoMisc/Z19b5 (-2/3): `array[0..4] of String[1];` (a
|
|
1643
|
+
// short-string element, WITH a trailing `;`) becomes ambiguous with
|
|
1644
|
+
// the no-semi array form and mis-parses. The GLR can't split them
|
|
1645
|
+
// (the `[N]` overlap is lexical). Needs a constrained array element
|
|
1646
|
+
// (exclude declString) to do safely — deferred; not worth the
|
|
1647
|
+
// parser-table cost for ~1 net file. See TODO.
|
|
1459
1648
|
prec(-1, $.pp_block),
|
|
1460
1649
|
)),
|
|
1461
1650
|
field('defaultValue', optional($.defaultValue)),
|
|
@@ -1463,7 +1652,18 @@ module.exports = grammar({
|
|
|
1463
1652
|
|
|
1464
1653
|
declField: $ => seq(
|
|
1465
1654
|
...enable_if(rtti, optional($.rttiAttributes)),
|
|
1466
|
-
field
|
|
1655
|
+
// Iter 2026-07-16: `Register` as a FIELD NAME after a prior field
|
|
1656
|
+
// (`Name: PAnsiChar; Register: UINT;` — Winapi.D3D10 shader
|
|
1657
|
+
// reflection records). Without the alias, the trailing-callconv
|
|
1658
|
+
// slot below ate `Register` and choked on the `:`. ONLY kRegister
|
|
1659
|
+
// is aliased — the 2026-07-06 attempt aliased all 7 callconv
|
|
1660
|
+
// keywords into BOTH declField and declFieldNoSemi and blew up
|
|
1661
|
+
// the tables; the narrow form + one-token lookahead (`:` vs `;`)
|
|
1662
|
+
// is all D3D10 needs.
|
|
1663
|
+
field('name', delimited1(choice(
|
|
1664
|
+
$.identifier,
|
|
1665
|
+
alias($.kRegister, $.identifier),
|
|
1666
|
+
))),
|
|
1467
1667
|
':',
|
|
1468
1668
|
// Iter 2026-07-06: a record/class field type may be a subrange, same
|
|
1469
1669
|
// as declVar already allows. `FtrListCount: 0 .. FTRRECMAXCOUNT;`
|
|
@@ -1559,35 +1759,40 @@ module.exports = grammar({
|
|
|
1559
1759
|
// Stuff for procedure / function / operator declarations
|
|
1560
1760
|
|
|
1561
1761
|
_declProc: $ => seq(
|
|
1562
|
-
...
|
|
1563
|
-
optional($.kClass),
|
|
1564
|
-
choice($.kProcedure, $.kFunction, $.kConstructor, $.kDestructor),
|
|
1565
|
-
field('name', $._genericName),
|
|
1566
|
-
field('args', optional($.declArgs)),
|
|
1567
|
-
optional(seq(
|
|
1568
|
-
':',
|
|
1569
|
-
field('type', $.typeref),
|
|
1570
|
-
)),
|
|
1571
|
-
field('assign', optional($.defaultValue)),
|
|
1572
|
-
// Inline calling-convention or procAttribute BEFORE the terminating
|
|
1573
|
-
// ';' — Embarcadero RTL / AzureAPI use lenient forms:
|
|
1574
|
-
// function DbiInitFn(...): DBIResult stdcall; (iter 56)
|
|
1575
|
-
// procedure PreflightBlobRequest(...) overload; (iter 60)
|
|
1576
|
-
// `repeat` allows multiple chained attributes like `... cdecl overload`.
|
|
1577
|
-
repeat(choice(
|
|
1578
|
-
$.kStdcall, $.kCdecl, $.kSafecall, $.kPascal,
|
|
1579
|
-
$.kRegister, $.kWinapi, $.kInline,
|
|
1580
|
-
$.kOverload, $.kVirtual, $.kAbstract, $.kOverride,
|
|
1581
|
-
$.kReintroduce, $.kStatic, $.kDynamic, $.kFinal,
|
|
1582
|
-
// ARC/AUTOREFCOUNT method directive: `class function NewInstance:
|
|
1583
|
-
// TObject unsafe; override;` (System.pas / JsonDataObjects /
|
|
1584
|
-
// FireDAC guard NewInstance with {$IFDEF AUTOREFCOUNT} unsafe).
|
|
1585
|
-
$.kUnsafe
|
|
1586
|
-
)),
|
|
1762
|
+
...declProcFront($),
|
|
1587
1763
|
';',
|
|
1764
|
+
// The FINAL directive group may omit its trailing `;` and dcc32
|
|
1765
|
+
// accepts it (VERIFIED 2026-07-16, exit 0):
|
|
1766
|
+
// `function F(x: Integer): Integer; deprecated 'msg'`
|
|
1767
|
+
// `function IsEq(...): Boolean; overload`
|
|
1768
|
+
// `function G: LongWord; stdcall; external 'k32' name 'GetTickCount'`
|
|
1769
|
+
// This STRICT variant does not tolerate that — it backs defProc's
|
|
1770
|
+
// header, where the `;` before the body is mandatory. The lenient
|
|
1771
|
+
// SEPARATOR-form tail lives in `_declProcLenient` (used only via
|
|
1772
|
+
// `_declarations`); a naive `repeat($._procAttributeNoExt)` +
|
|
1773
|
+
// `optional(repeat1(procAttribute))` here would share its whole
|
|
1774
|
+
// prefix and fork inside auto-generated repeat states that cannot
|
|
1775
|
+
// be declared as conflicts (the old KNOWN-GAP note).
|
|
1588
1776
|
repeat($._procAttributeNoExt)
|
|
1589
1777
|
),
|
|
1590
1778
|
|
|
1779
|
+
// Lenient twin of _declProc for FORWARD/interface declaration lists:
|
|
1780
|
+
// directive groups are `;`-SEPARATED with the final `;` optional, so
|
|
1781
|
+
// `function F: Boolean; overload` before another declaration parses.
|
|
1782
|
+
// After each `;` one token of lookahead decides (attribute keyword →
|
|
1783
|
+
// another group, declaration keyword → done) — no shared-prefix fork.
|
|
1784
|
+
// Never used where a body can follow (defProc header stays strict, so
|
|
1785
|
+
// `procedure P; stdcall begin` is still rejected like dcc rejects it).
|
|
1786
|
+
_declProcLenient: $ => seq(
|
|
1787
|
+
...declProcFront($),
|
|
1788
|
+
';',
|
|
1789
|
+
optional(seq(
|
|
1790
|
+
repeat1(field('attribute', $.procAttribute)),
|
|
1791
|
+
repeat(seq(';', repeat1(field('attribute', $.procAttribute)))),
|
|
1792
|
+
optional(';')
|
|
1793
|
+
))
|
|
1794
|
+
),
|
|
1795
|
+
|
|
1591
1796
|
_declOperator: $ => seq(
|
|
1592
1797
|
optional($.kClass),
|
|
1593
1798
|
$.kOperator,
|
|
@@ -1984,7 +2189,10 @@ module.exports = grammar({
|
|
|
1984
2189
|
// so structural tokens stay intact.
|
|
1985
2190
|
identifier: $ => /&{0,2}[a-zA-Z_-][0-9_a-zA-Z-]*/,
|
|
1986
2191
|
|
|
1987
|
-
|
|
2192
|
+
// Classic Pascal treats every control char <= #31 as a token
|
|
2193
|
+
// separator (dcc compiles sources with stray bytes like the 0x12 in
|
|
2194
|
+
// DevExpress dxPDFForm.pas), so the whitespace extra does too.
|
|
2195
|
+
_space: $ => /[\s\x00-\x1F]+/,
|
|
1988
2196
|
// Single preprocessor directive. The external scanner (src/scanner.c)
|
|
1989
2197
|
// handles multi-line `{$IF*}...{$END*}` blocks via the pp_block token,
|
|
1990
2198
|
// refusing blocks that wrap structural decls so they parse normally.
|