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 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
- **99.33% pass rate** on a 35,556-file curated Delphi 13 corpus (zero ERROR nodes).
9
-
10
- | corpus root | pass rate |
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
- | ORM3 (production application code) | **100.00%** |
13
- | Spring4D | 99.87% |
14
- | DevExpress VCL | 99.82% |
15
- | OmniThreadLibrary | 99.62% |
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 remaining ~0.5% are intentionally broken DUnitX test cases, real vendor source typos, C-language code mistakenly placed in `.pas` files, and a handful of pathologically large auto-generated TypeLib units.
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 (93.48% on its own). |
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.33%**. Use the master grammar alone if you don't need conditional-compilation awareness; use the pipeline if you do.
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
- let lastStatement = $ => trailing ? optional(tr($,'_statement')) : $._statement;
108
- let lastStatement1= $ => trailing ? tr($,'_statement') : $._statement;
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
- $.program,
408
- $.library,
409
- $.unit,
410
- $.package,
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
- $.kEnd, $.kEndDot
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
- ...enable_if(delphi, optional(seq($.kDeprecated, $._expr))),
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
- field('name', delimited1($.identifier)),
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, $.declProc, $.declProp,
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('name', delimited1($.identifier)),
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
- ...enable_if(fpc, optional($.kGeneric)),
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
- _space: $ => /[\s\r\n\t]+/,
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tree-sitter-delphi13",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Tree-sitter grammar focused on Delphi 13 (RAD Studio 37.0)",
5
5
  "main": "bindings/node",
6
6
  "types": "bindings/node",