@abapify/aclass 0.0.0 → 0.4.5

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/dist/index.mjs ADDED
@@ -0,0 +1,1593 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-w6R9maHv.mjs";
2
+ import { Lexer, createToken } from "chevrotain";
3
+
4
+ //#region src/tokens.ts
5
+ /**
6
+ * Chevrotain Token Definitions for the ABAP OO surface grammar.
7
+ *
8
+ * Scope: everything `@abapify/aclass` needs to tokenise a `.clas.abap` or
9
+ * `.intf.abap` source file up to the point where method bodies become
10
+ * opaque text. Method-body statements are NOT tokenised here — they are
11
+ * captured verbatim by a dedicated body-capture token.
12
+ *
13
+ * ABAP is case-insensitive. All keywords use `/i` patterns so `CLASS`,
14
+ * `Class`, and `class` all tokenise the same way.
15
+ *
16
+ * Keyword order inside `allTokens` matters:
17
+ * 1. Multi-char operators before their single-char prefixes.
18
+ * 2. Longer keywords before their prefixes (e.g. `CLASS-DATA` before
19
+ * `CLASS`, `INTERFACES` before `INTERFACE`, `NON-UNIQUE` before
20
+ * `UNIQUE`).
21
+ * 3. `ABAPDocLine` BEFORE `LineComment` so `"! foo` is captured as
22
+ * documentation instead of as a regular line comment.
23
+ * 4. `Identifier` must come last among the word-shaped tokens so that
24
+ * keywords win their matches via `longer_alt`.
25
+ */
26
+ var tokens_exports = /* @__PURE__ */ __exportAll({
27
+ ABAPDocLine: () => ABAPDocLine,
28
+ Abstract: () => Abstract,
29
+ AclassLexer: () => AclassLexer,
30
+ Aliases: () => Aliases,
31
+ Ampersand: () => Ampersand,
32
+ Arrow: () => Arrow,
33
+ As: () => As,
34
+ At: () => At,
35
+ BacktickLiteral: () => BacktickLiteral,
36
+ Begin: () => Begin,
37
+ Changing: () => Changing,
38
+ Class: () => Class,
39
+ ClassData: () => ClassData,
40
+ ClassEvents: () => ClassEvents,
41
+ ClassMethods: () => ClassMethods,
42
+ Colon: () => Colon,
43
+ ColonColon: () => ColonColon,
44
+ Comma: () => Comma,
45
+ Constants: () => Constants,
46
+ Create: () => Create,
47
+ Data: () => Data,
48
+ Default: () => Default,
49
+ Deferred: () => Deferred,
50
+ Definition: () => Definition,
51
+ Dot: () => Dot,
52
+ Duration: () => Duration,
53
+ Empty: () => Empty,
54
+ End: () => End,
55
+ EndClass: () => EndClass,
56
+ EndInterface: () => EndInterface,
57
+ EndMethod: () => EndMethod,
58
+ Eq: () => Eq,
59
+ Events: () => Events,
60
+ Exporting: () => Exporting,
61
+ FatArrow: () => FatArrow,
62
+ Final: () => Final,
63
+ For: () => For,
64
+ From: () => From,
65
+ Gt: () => Gt,
66
+ Hash: () => Hash,
67
+ Hashed: () => Hashed,
68
+ Identifier: () => Identifier,
69
+ Implementation: () => Implementation,
70
+ Importing: () => Importing,
71
+ Inheriting: () => Inheriting,
72
+ IntegerLiteral: () => IntegerLiteral,
73
+ Interface: () => Interface,
74
+ Interfaces: () => Interfaces,
75
+ Key: () => Key,
76
+ LBrace: () => LBrace,
77
+ LBracket: () => LBracket,
78
+ LParen: () => LParen,
79
+ Level: () => Level,
80
+ Like: () => Like,
81
+ LineComment: () => LineComment,
82
+ Load: () => Load,
83
+ Lt: () => Lt,
84
+ Method: () => Method,
85
+ Methods: () => Methods,
86
+ Minus: () => Minus,
87
+ Newline: () => Newline,
88
+ NonUnique: () => NonUnique,
89
+ Of: () => Of,
90
+ Optional: () => Optional,
91
+ Pipe: () => Pipe,
92
+ Plus: () => Plus,
93
+ Private: () => Private,
94
+ Protected: () => Protected,
95
+ Public: () => Public,
96
+ Question: () => Question,
97
+ RBrace: () => RBrace,
98
+ RBracket: () => RBracket,
99
+ RParen: () => RParen,
100
+ Raising: () => Raising,
101
+ ReadOnly: () => ReadOnly,
102
+ Redefinition: () => Redefinition,
103
+ Ref: () => Ref,
104
+ Returning: () => Returning,
105
+ Risk: () => Risk,
106
+ Section: () => Section,
107
+ Slash: () => Slash,
108
+ Sorted: () => Sorted,
109
+ Standard: () => Standard,
110
+ Star: () => Star,
111
+ StarComment: () => StarComment,
112
+ StringLiteral: () => StringLiteral,
113
+ Table: () => Table,
114
+ Testing: () => Testing,
115
+ Tilde: () => Tilde,
116
+ To: () => To,
117
+ Type: () => Type,
118
+ Types: () => Types,
119
+ Unique: () => Unique,
120
+ Value: () => Value,
121
+ WhiteSpace: () => WhiteSpace,
122
+ With: () => With,
123
+ allTokens: () => allTokens
124
+ });
125
+ const WhiteSpace = createToken({
126
+ name: "WhiteSpace",
127
+ pattern: /[ \t]+/,
128
+ group: Lexer.SKIPPED
129
+ });
130
+ const Newline = createToken({
131
+ name: "Newline",
132
+ pattern: /\r?\n/,
133
+ group: Lexer.SKIPPED
134
+ });
135
+ /**
136
+ * ABAPDoc line: `"! …` up to end of line. MUST come before LineComment
137
+ * because `"!` is a prefix of `"`.
138
+ */
139
+ const ABAPDocLine = createToken({
140
+ name: "ABAPDocLine",
141
+ pattern: /"![^\r\n]*/
142
+ });
143
+ /** Regular line comment: `" …` up to end of line. */
144
+ const LineComment = createToken({
145
+ name: "LineComment",
146
+ pattern: /"[^\r\n]*/,
147
+ group: Lexer.SKIPPED
148
+ });
149
+ const starCommentPattern = {
150
+ line_breaks: false,
151
+ exec: (text, offset) => {
152
+ if (offset !== 0 && text.charCodeAt(offset - 1) !== 10) return null;
153
+ if (text.charCodeAt(offset) !== 42) return null;
154
+ let end = offset;
155
+ while (end < text.length) {
156
+ const c = text.charCodeAt(end);
157
+ if (c === 10 || c === 13) break;
158
+ end++;
159
+ }
160
+ return [text.slice(offset, end)];
161
+ }
162
+ };
163
+ const StarComment = createToken({
164
+ name: "StarComment",
165
+ pattern: starCommentPattern,
166
+ line_breaks: false,
167
+ group: Lexer.SKIPPED
168
+ });
169
+ /** Single-quoted ABAP text literal: `'...'` with doubled `''` for escaping. */
170
+ const StringLiteral = createToken({
171
+ name: "StringLiteral",
172
+ pattern: /'(?:[^']|'')*'/
173
+ });
174
+ /** Backtick string literal: \`...\`. */
175
+ const BacktickLiteral = createToken({
176
+ name: "BacktickLiteral",
177
+ pattern: /`(?:[^`]|``)*`/
178
+ });
179
+ const IntegerLiteral = createToken({
180
+ name: "IntegerLiteral",
181
+ pattern: /-?\d+/
182
+ });
183
+ /**
184
+ * ABAP identifier: starts with letter or underscore; may contain letters,
185
+ * digits, underscores, slash (for namespaces like `/ui2/cl_json`), and
186
+ * tilde (for qualified names like `zif_foo~method`) — but tilde is
187
+ * reserved for qualified references, so at the lexer level we only allow
188
+ * the "simple" form here. Qualified `~` and `=>` are handled in the
189
+ * parser via separate tokens.
190
+ *
191
+ * Note: the `=>` operator (static scope) is tokenised as a distinct
192
+ * symbol, not as part of the identifier.
193
+ */
194
+ const Identifier = createToken({
195
+ name: "Identifier",
196
+ pattern: /[A-Za-z_][A-Za-z0-9_/]*/
197
+ });
198
+ const REGEX_META = /[.*+?^${}()|[\]\\/-]/g;
199
+ function kw(name, word) {
200
+ const pattern = new RegExp(word.replace(REGEX_META, "\\$&"), "i");
201
+ return createToken({
202
+ name,
203
+ pattern,
204
+ longer_alt: Identifier
205
+ });
206
+ }
207
+ const ClassMethods = kw("ClassMethods", "class-methods");
208
+ const ClassData = kw("ClassData", "class-data");
209
+ const ClassEvents = kw("ClassEvents", "class-events");
210
+ const NonUnique = kw("NonUnique", "non-unique");
211
+ const Class = kw("Class", "class");
212
+ const Interfaces = kw("Interfaces", "interfaces");
213
+ const Interface = kw("Interface", "interface");
214
+ const EndClass = kw("EndClass", "endclass");
215
+ const EndInterface = kw("EndInterface", "endinterface");
216
+ const EndMethod = kw("EndMethod", "endmethod");
217
+ const Definition = kw("Definition", "definition");
218
+ const Implementation = kw("Implementation", "implementation");
219
+ const Deferred = kw("Deferred", "deferred");
220
+ const Load = kw("Load", "load");
221
+ const Public = kw("Public", "public");
222
+ const Protected = kw("Protected", "protected");
223
+ const Private = kw("Private", "private");
224
+ const Section = kw("Section", "section");
225
+ const Methods = kw("Methods", "methods");
226
+ const Method = kw("Method", "method");
227
+ const Data = kw("Data", "data");
228
+ const Types = kw("Types", "types");
229
+ const Constants = kw("Constants", "constants");
230
+ const Events = kw("Events", "events");
231
+ const Aliases = kw("Aliases", "aliases");
232
+ const Inheriting = kw("Inheriting", "inheriting");
233
+ const From = kw("From", "from");
234
+ const For = kw("For", "for");
235
+ const Testing = kw("Testing", "testing");
236
+ const Risk = kw("Risk", "risk");
237
+ const Level = kw("Level", "level");
238
+ const Duration = kw("Duration", "duration");
239
+ const Final = kw("Final", "final");
240
+ const Abstract = kw("Abstract", "abstract");
241
+ const Create = kw("Create", "create");
242
+ const Importing = kw("Importing", "importing");
243
+ const Exporting = kw("Exporting", "exporting");
244
+ const Changing = kw("Changing", "changing");
245
+ const Returning = kw("Returning", "returning");
246
+ const Raising = kw("Raising", "raising");
247
+ const Value = kw("Value", "value");
248
+ const Optional = kw("Optional", "optional");
249
+ const Default = kw("Default", "default");
250
+ const Redefinition = kw("Redefinition", "redefinition");
251
+ const Type = kw("Type", "type");
252
+ const Ref = kw("Ref", "ref");
253
+ const To = kw("To", "to");
254
+ const Like = kw("Like", "like");
255
+ const Begin = kw("Begin", "begin");
256
+ const End = kw("End", "end");
257
+ const Of = kw("Of", "of");
258
+ const Standard = kw("Standard", "standard");
259
+ const Sorted = kw("Sorted", "sorted");
260
+ const Hashed = kw("Hashed", "hashed");
261
+ const Table = kw("Table", "table");
262
+ const With = kw("With", "with");
263
+ const Key = kw("Key", "key");
264
+ const Empty = kw("Empty", "empty");
265
+ const Unique = kw("Unique", "unique");
266
+ const As = kw("As", "as");
267
+ const ReadOnly = kw("ReadOnly", "read-only");
268
+ /** Static scope operator: `=>` */
269
+ const FatArrow = createToken({
270
+ name: "FatArrow",
271
+ pattern: /=>/
272
+ });
273
+ /** Instance member operator: `->` */
274
+ const Arrow = createToken({
275
+ name: "Arrow",
276
+ pattern: /->/
277
+ });
278
+ /** Component selector inside generic reference: `::` (rare, reserved). */
279
+ const ColonColon = createToken({
280
+ name: "ColonColon",
281
+ pattern: /::/
282
+ });
283
+ const Dot = createToken({
284
+ name: "Dot",
285
+ pattern: /\./
286
+ });
287
+ const Comma = createToken({
288
+ name: "Comma",
289
+ pattern: /,/
290
+ });
291
+ const Colon = createToken({
292
+ name: "Colon",
293
+ pattern: /:/
294
+ });
295
+ const Tilde = createToken({
296
+ name: "Tilde",
297
+ pattern: /~/
298
+ });
299
+ const LParen = createToken({
300
+ name: "LParen",
301
+ pattern: /\(/
302
+ });
303
+ const RParen = createToken({
304
+ name: "RParen",
305
+ pattern: /\)/
306
+ });
307
+ const LBracket = createToken({
308
+ name: "LBracket",
309
+ pattern: /\[/
310
+ });
311
+ const RBracket = createToken({
312
+ name: "RBracket",
313
+ pattern: /\]/
314
+ });
315
+ const Eq = createToken({
316
+ name: "Eq",
317
+ pattern: /=/
318
+ });
319
+ const Hash = createToken({
320
+ name: "Hash",
321
+ pattern: /#/
322
+ });
323
+ const Pipe = createToken({
324
+ name: "Pipe",
325
+ pattern: /\|/
326
+ });
327
+ const LBrace = createToken({
328
+ name: "LBrace",
329
+ pattern: /\{/
330
+ });
331
+ const RBrace = createToken({
332
+ name: "RBrace",
333
+ pattern: /\}/
334
+ });
335
+ const Plus = createToken({
336
+ name: "Plus",
337
+ pattern: /\+/
338
+ });
339
+ const Minus = createToken({
340
+ name: "Minus",
341
+ pattern: /-/
342
+ });
343
+ const Star = createToken({
344
+ name: "Star",
345
+ pattern: /\*/
346
+ });
347
+ const Slash = createToken({
348
+ name: "Slash",
349
+ pattern: /\//
350
+ });
351
+ const Lt = createToken({
352
+ name: "Lt",
353
+ pattern: /</
354
+ });
355
+ const Gt = createToken({
356
+ name: "Gt",
357
+ pattern: />/
358
+ });
359
+ const Question = createToken({
360
+ name: "Question",
361
+ pattern: /\?/
362
+ });
363
+ const At = createToken({
364
+ name: "At",
365
+ pattern: /@/
366
+ });
367
+ const Ampersand = createToken({
368
+ name: "Ampersand",
369
+ pattern: /&/
370
+ });
371
+ const allTokens = [
372
+ WhiteSpace,
373
+ Newline,
374
+ StarComment,
375
+ ABAPDocLine,
376
+ LineComment,
377
+ FatArrow,
378
+ Arrow,
379
+ ColonColon,
380
+ StringLiteral,
381
+ BacktickLiteral,
382
+ IntegerLiteral,
383
+ ClassMethods,
384
+ ClassData,
385
+ ClassEvents,
386
+ NonUnique,
387
+ ReadOnly,
388
+ Class,
389
+ Interfaces,
390
+ Interface,
391
+ EndClass,
392
+ EndInterface,
393
+ EndMethod,
394
+ Definition,
395
+ Implementation,
396
+ Deferred,
397
+ Load,
398
+ Public,
399
+ Protected,
400
+ Private,
401
+ Section,
402
+ Methods,
403
+ Method,
404
+ Data,
405
+ Types,
406
+ Constants,
407
+ Events,
408
+ Aliases,
409
+ Inheriting,
410
+ From,
411
+ For,
412
+ Testing,
413
+ Risk,
414
+ Level,
415
+ Duration,
416
+ Final,
417
+ Abstract,
418
+ Create,
419
+ Importing,
420
+ Exporting,
421
+ Changing,
422
+ Returning,
423
+ Raising,
424
+ Value,
425
+ Optional,
426
+ Default,
427
+ Redefinition,
428
+ Type,
429
+ Ref,
430
+ To,
431
+ Like,
432
+ As,
433
+ Begin,
434
+ End,
435
+ Of,
436
+ Standard,
437
+ Sorted,
438
+ Hashed,
439
+ Table,
440
+ With,
441
+ Key,
442
+ Empty,
443
+ Unique,
444
+ Identifier,
445
+ Dot,
446
+ Comma,
447
+ Colon,
448
+ Tilde,
449
+ LParen,
450
+ RParen,
451
+ LBracket,
452
+ RBracket,
453
+ Eq,
454
+ Hash,
455
+ Pipe,
456
+ LBrace,
457
+ RBrace,
458
+ Plus,
459
+ Minus,
460
+ Star,
461
+ Slash,
462
+ Lt,
463
+ Gt,
464
+ Question,
465
+ At,
466
+ Ampersand
467
+ ];
468
+ const AclassLexer = new Lexer(allTokens, { positionTracking: "full" });
469
+
470
+ //#endregion
471
+ //#region src/errors.ts
472
+ function fromLexError(err) {
473
+ return {
474
+ severity: "error",
475
+ message: err.message,
476
+ line: err.line ?? 1,
477
+ column: err.column ?? 1,
478
+ offset: err.offset,
479
+ length: err.length
480
+ };
481
+ }
482
+ function fromParseError(err) {
483
+ const token = err.token;
484
+ return {
485
+ severity: "error",
486
+ message: err.message,
487
+ line: token.startLine ?? 1,
488
+ column: token.startColumn ?? 1,
489
+ offset: token.startOffset,
490
+ length: (token.endOffset ?? token.startOffset) - token.startOffset + 1
491
+ };
492
+ }
493
+
494
+ //#endregion
495
+ //#region src/lex.ts
496
+ /**
497
+ * Wave 0 entry point: tokenise an ABAP OO source string and return the
498
+ * resulting token list along with any lex errors, normalised to
499
+ * `ParseError`.
500
+ *
501
+ * Wave 1 replaces this with a full `parse()` that feeds the token list
502
+ * into a Chevrotain `CstParser` and yields a typed AST. This function
503
+ * stays around as the low-level primitive.
504
+ */
505
+ function tokenize(source) {
506
+ const result = AclassLexer.tokenize(source);
507
+ return {
508
+ tokens: result.tokens,
509
+ errors: result.errors.map(fromLexError)
510
+ };
511
+ }
512
+
513
+ //#endregion
514
+ //#region src/parser.ts
515
+ /**
516
+ * Parse an ABAP OO source file into a typed AST plus any diagnostics.
517
+ * Returns a `{ ast, errors }` shape; never throws for malformed input.
518
+ */
519
+ function parse(source) {
520
+ const errors = [];
521
+ const { tokens, errors: lexErrors } = tokenize(source);
522
+ errors.push(...lexErrors);
523
+ const definitions = [];
524
+ const cursor = new Cursor(tokens, source, errors);
525
+ try {
526
+ while (!cursor.eof()) {
527
+ const def = parseTopLevel(cursor);
528
+ if (def) definitions.push(def);
529
+ }
530
+ } catch (err) {
531
+ errors.push({
532
+ severity: "error",
533
+ message: `internal parser error: ${err instanceof Error ? err.message : String(err)}`,
534
+ line: 1,
535
+ column: 1,
536
+ offset: 0,
537
+ length: 0
538
+ });
539
+ }
540
+ return {
541
+ ast: {
542
+ kind: "AbapSourceFile",
543
+ source,
544
+ definitions
545
+ },
546
+ errors
547
+ };
548
+ }
549
+ var Cursor = class {
550
+ tokens;
551
+ source;
552
+ errors;
553
+ position = 0;
554
+ constructor(tokens, source, errors) {
555
+ this.tokens = tokens;
556
+ this.source = source;
557
+ this.errors = errors;
558
+ }
559
+ eof() {
560
+ return this.position >= this.tokens.length;
561
+ }
562
+ peek(offset = 0) {
563
+ return this.tokens[this.position + offset];
564
+ }
565
+ current() {
566
+ const t = this.tokens[this.position];
567
+ if (!t) throw new Error("Cursor.current() called past end of stream");
568
+ return t;
569
+ }
570
+ advance() {
571
+ const t = this.current();
572
+ this.position += 1;
573
+ return t;
574
+ }
575
+ matches(name, offset = 0) {
576
+ return this.peek(offset)?.tokenType.name === name;
577
+ }
578
+ /** Collect tokens up to (not including) the next `Dot` at top level. */
579
+ collectStatement() {
580
+ const collected = [];
581
+ const start = this.peek()?.startOffset ?? 0;
582
+ while (!this.eof()) {
583
+ const t = this.current();
584
+ if (t.tokenType.name === "Dot") {
585
+ this.advance();
586
+ return {
587
+ tokens: collected,
588
+ terminator: t,
589
+ startOffset: start,
590
+ endOffset: t.endOffset ?? t.startOffset
591
+ };
592
+ }
593
+ collected.push(t);
594
+ this.advance();
595
+ }
596
+ return {
597
+ tokens: collected,
598
+ startOffset: start,
599
+ endOffset: collected[collected.length - 1]?.endOffset ?? start
600
+ };
601
+ }
602
+ /** Accumulate consecutive ABAPDoc lines at the current position. */
603
+ consumeAbapDoc() {
604
+ const doc = [];
605
+ while (this.matches("ABAPDocLine")) {
606
+ const t = this.advance();
607
+ doc.push(t.image.replace(/^"!\s?/, ""));
608
+ }
609
+ return doc.length > 0 ? doc : void 0;
610
+ }
611
+ report(message, at, severity = "error") {
612
+ this.errors.push({
613
+ severity,
614
+ message,
615
+ line: at.startLine ?? 1,
616
+ column: at.startColumn ?? 1,
617
+ offset: at.startOffset,
618
+ length: (at.endOffset ?? at.startOffset) - at.startOffset + 1
619
+ });
620
+ }
621
+ sliceSource(startOffset, endOffset) {
622
+ return this.source.slice(startOffset, endOffset + 1);
623
+ }
624
+ /**
625
+ * Return the first token whose start offset is greater than or equal
626
+ * to the given offset. Useful for recovering line/column metadata for
627
+ * a source-slice boundary that doesn't land on a token (e.g. the
628
+ * start of a method body, which is usually whitespace).
629
+ */
630
+ firstTokenAtOrAfter(offset) {
631
+ for (const t of this.tokens) if (t.startOffset >= offset) return t;
632
+ }
633
+ };
634
+ function parseTopLevel(c) {
635
+ const abapDoc = c.consumeAbapDoc();
636
+ if (c.eof()) return null;
637
+ const t = c.current();
638
+ const name = t.tokenType.name;
639
+ if (name === "Class") return parseClassOrImpl(c, abapDoc);
640
+ if (name === "Interface") return parseInterface(c, abapDoc);
641
+ c.report(`unexpected top-level token "${t.image}"`, t);
642
+ c.collectStatement();
643
+ return null;
644
+ }
645
+ function parseClassOrImpl(c, abapDoc) {
646
+ const classTok = c.current();
647
+ const startOffset = classTok.startOffset;
648
+ const startLine = classTok.startLine ?? 1;
649
+ const startColumn = classTok.startColumn ?? 1;
650
+ const header = c.collectStatement();
651
+ const tokens = header.tokens;
652
+ if (tokens[0]?.tokenType.name !== "Class") {
653
+ c.report("expected CLASS keyword", classTok);
654
+ return null;
655
+ }
656
+ const nameTok = tokens[1];
657
+ if (!nameTok || nameTok.tokenType.name !== "Identifier") {
658
+ c.report("expected class name after CLASS", nameTok ?? classTok);
659
+ return null;
660
+ }
661
+ const kindTok = tokens[2];
662
+ if (!kindTok) {
663
+ c.report("expected DEFINITION or IMPLEMENTATION after class name", nameTok);
664
+ return null;
665
+ }
666
+ if (kindTok.tokenType.name === "Implementation") return parseClassImpl(c, nameTok.image, {
667
+ startOffset,
668
+ endOffset: header.endOffset,
669
+ startLine,
670
+ startColumn
671
+ });
672
+ if (kindTok.tokenType.name !== "Definition") {
673
+ c.report(`expected DEFINITION or IMPLEMENTATION, got "${kindTok.image}"`, kindTok);
674
+ return null;
675
+ }
676
+ return parseClassDef(c, tokens, abapDoc, {
677
+ startOffset,
678
+ endOffset: header.endOffset,
679
+ startLine,
680
+ startColumn
681
+ });
682
+ }
683
+ function parseClassDef(c, headerTokens, abapDoc, headerSpan) {
684
+ const name = headerTokens[1].image;
685
+ const mods = headerTokens.slice(3);
686
+ if (mods.some((t) => t.tokenType.name === "Deferred" || t.tokenType.name === "Load")) return {
687
+ kind: "ClassDef",
688
+ name,
689
+ abapDoc,
690
+ isFinal: false,
691
+ isAbstract: false,
692
+ isForTesting: false,
693
+ createVisibility: "public",
694
+ sections: [],
695
+ span: headerSpan
696
+ };
697
+ let isFinal = false;
698
+ let isAbstract = false;
699
+ let isForTesting = false;
700
+ let createVisibility = "public";
701
+ let superClass;
702
+ for (let i = 0; i < mods.length; i++) switch (mods[i].tokenType.name) {
703
+ case "Public": break;
704
+ case "Final":
705
+ isFinal = true;
706
+ break;
707
+ case "Abstract":
708
+ isAbstract = true;
709
+ break;
710
+ case "Inheriting": {
711
+ const from = mods[i + 1];
712
+ const sup = mods[i + 2];
713
+ if (from?.tokenType.name === "From" && sup) {
714
+ superClass = sup.image;
715
+ i += 2;
716
+ }
717
+ break;
718
+ }
719
+ case "Create": {
720
+ const v = mods[i + 1];
721
+ if (v?.tokenType.name === "Private") createVisibility = "private";
722
+ else if (v?.tokenType.name === "Protected") createVisibility = "protected";
723
+ else createVisibility = "public";
724
+ i += 1;
725
+ break;
726
+ }
727
+ case "For": if (mods[i + 1]?.tokenType.name === "Testing") isForTesting = true;
728
+ }
729
+ const sections = [];
730
+ while (!c.eof() && !c.matches("EndClass")) {
731
+ const sec = parseSection(c);
732
+ if (sec) sections.push(sec);
733
+ else break;
734
+ }
735
+ if (c.matches("EndClass")) c.collectStatement();
736
+ else c.report(`unterminated CLASS ${name} DEFINITION — expected ENDCLASS`, headerTokens[0]);
737
+ return {
738
+ kind: "ClassDef",
739
+ name,
740
+ abapDoc,
741
+ isFinal,
742
+ isAbstract,
743
+ isForTesting,
744
+ createVisibility,
745
+ superClass,
746
+ sections,
747
+ span: headerSpan
748
+ };
749
+ }
750
+ function parseClassImpl(c, name, headerSpan) {
751
+ const methods = [];
752
+ while (!c.eof() && !c.matches("EndClass")) {
753
+ c.consumeAbapDoc();
754
+ if (c.eof() || c.matches("EndClass")) break;
755
+ if (!c.matches("Method")) {
756
+ if (c.collectStatement().tokens.length === 0) break;
757
+ continue;
758
+ }
759
+ const mi = parseMethodImpl(c);
760
+ if (mi) methods.push(mi);
761
+ }
762
+ if (c.matches("EndClass")) c.collectStatement();
763
+ else c["errors"].push({
764
+ severity: "error",
765
+ message: `unterminated CLASS ${name} IMPLEMENTATION — expected ENDCLASS`,
766
+ line: headerSpan.startLine,
767
+ column: headerSpan.startColumn,
768
+ offset: headerSpan.startOffset,
769
+ length: headerSpan.endOffset - headerSpan.startOffset + 1
770
+ });
771
+ return {
772
+ kind: "ClassImpl",
773
+ name,
774
+ methods,
775
+ span: headerSpan
776
+ };
777
+ }
778
+ function parseMethodImpl(c) {
779
+ const methodTok = c.current();
780
+ const header = c.collectStatement();
781
+ const qual = readQualifiedName(header.tokens, 1);
782
+ if (!qual) {
783
+ c.report("expected method name after METHOD", header.tokens[1] ?? methodTok);
784
+ return null;
785
+ }
786
+ const bodyStart = header.endOffset + 1;
787
+ while (!c.eof()) {
788
+ if (c.matches("EndMethod")) {
789
+ const endMethodStart = c.current().startOffset;
790
+ const stmt = c.collectStatement();
791
+ const bodyEnd = endMethodStart - 1;
792
+ const totalEnd = stmt.endOffset;
793
+ const body = c["sliceSource"](bodyStart, bodyEnd);
794
+ const firstBodyTok = c.firstTokenAtOrAfter(bodyStart);
795
+ const bodyLine = firstBodyTok?.startLine ?? methodTok.startLine ?? 1;
796
+ const bodyCol = firstBodyTok?.startColumn ?? 1;
797
+ return {
798
+ kind: "MethodImpl",
799
+ name: qual.name,
800
+ body,
801
+ bodySpan: {
802
+ startOffset: bodyStart,
803
+ endOffset: bodyEnd,
804
+ startLine: bodyLine,
805
+ startColumn: bodyCol
806
+ },
807
+ span: {
808
+ startOffset: methodTok.startOffset,
809
+ endOffset: totalEnd,
810
+ startLine: methodTok.startLine ?? 1,
811
+ startColumn: methodTok.startColumn ?? 1
812
+ }
813
+ };
814
+ }
815
+ c.advance();
816
+ }
817
+ c.report("ENDMETHOD not found for METHOD block", methodTok);
818
+ return null;
819
+ }
820
+ function parseInterface(c, abapDoc) {
821
+ const ifaceTok = c.current();
822
+ const header = c.collectStatement();
823
+ const tokens = header.tokens;
824
+ if (tokens[0]?.tokenType.name !== "Interface") {
825
+ c.report("expected INTERFACE keyword", ifaceTok);
826
+ return null;
827
+ }
828
+ const nameTok = tokens[1];
829
+ if (!nameTok || nameTok.tokenType.name !== "Identifier") {
830
+ c.report("expected interface name after INTERFACE", nameTok ?? ifaceTok);
831
+ return null;
832
+ }
833
+ const isPublic = tokens.slice(2).some((t) => t.tokenType.name === "Public");
834
+ const members = [];
835
+ while (!c.eof() && !c.matches("EndInterface")) {
836
+ const m = parseMember(c);
837
+ if (m) members.push(m);
838
+ else break;
839
+ }
840
+ if (c.matches("EndInterface")) c.collectStatement();
841
+ return {
842
+ kind: "InterfaceDef",
843
+ name: nameTok.image,
844
+ abapDoc,
845
+ isPublic,
846
+ members,
847
+ span: {
848
+ startOffset: ifaceTok.startOffset,
849
+ endOffset: header.endOffset,
850
+ startLine: ifaceTok.startLine ?? 1,
851
+ startColumn: ifaceTok.startColumn ?? 1
852
+ }
853
+ };
854
+ }
855
+ function parseSection(c) {
856
+ c.consumeAbapDoc();
857
+ if (c.eof()) return null;
858
+ const first = c.current();
859
+ let visibility;
860
+ if (first.tokenType.name === "Public") visibility = "public";
861
+ else if (first.tokenType.name === "Protected") visibility = "protected";
862
+ else if (first.tokenType.name === "Private") visibility = "private";
863
+ else return null;
864
+ const header = c.collectStatement();
865
+ const sectionStart = header.startOffset;
866
+ const members = [];
867
+ while (!c.eof() && !isSectionBoundary(c)) {
868
+ const m = parseMember(c);
869
+ if (m) members.push(m);
870
+ else break;
871
+ }
872
+ const end = members.length > 0 ? members[members.length - 1].span.endOffset : header.endOffset;
873
+ return {
874
+ kind: "Section",
875
+ visibility,
876
+ members,
877
+ span: {
878
+ startOffset: sectionStart,
879
+ endOffset: end,
880
+ startLine: first.startLine ?? 1,
881
+ startColumn: first.startColumn ?? 1
882
+ }
883
+ };
884
+ }
885
+ function isSectionBoundary(c) {
886
+ if (c.matches("EndClass")) return true;
887
+ if (c.matches("EndInterface")) return true;
888
+ const t = c.peek();
889
+ if (!t) return true;
890
+ if (t.tokenType.name === "Public" || t.tokenType.name === "Protected" || t.tokenType.name === "Private") return c.peek(1)?.tokenType.name === "Section";
891
+ return false;
892
+ }
893
+ function parseMember(c) {
894
+ const abapDoc = c.consumeAbapDoc();
895
+ if (c.eof()) return null;
896
+ const head = c.current();
897
+ switch (head.tokenType.name) {
898
+ case "Methods":
899
+ case "ClassMethods": return parseMethodDecl(c, abapDoc);
900
+ case "Data":
901
+ case "ClassData": return parseAttributeDecl(c, abapDoc);
902
+ case "Types": return parseTypeDecl(c, abapDoc);
903
+ case "Constants": return parseConstantDecl(c, abapDoc);
904
+ case "Events":
905
+ case "ClassEvents": return parseEventDecl(c, abapDoc);
906
+ case "Interfaces": return parseInterfaceStmt(c, abapDoc);
907
+ case "Aliases": return parseAliasDecl(c, abapDoc);
908
+ default: {
909
+ const stmt = c.collectStatement();
910
+ return {
911
+ kind: "RawMember",
912
+ source: c["sliceSource"](stmt.startOffset, stmt.endOffset),
913
+ abapDoc,
914
+ span: {
915
+ startOffset: stmt.startOffset,
916
+ endOffset: stmt.endOffset,
917
+ startLine: head.startLine ?? 1,
918
+ startColumn: head.startColumn ?? 1
919
+ }
920
+ };
921
+ }
922
+ }
923
+ }
924
+ function parseMethodDecl(c, abapDoc) {
925
+ const head = c.current();
926
+ const isClassMethod = head.tokenType.name === "ClassMethods";
927
+ const stmt = c.collectStatement();
928
+ const toks = stmt.tokens;
929
+ const qual = readQualifiedName(toks, 1);
930
+ if (!qual) {
931
+ c.report("expected method name", toks[1] ?? head);
932
+ return null;
933
+ }
934
+ const importing = [];
935
+ const exporting = [];
936
+ const changing = [];
937
+ let returning;
938
+ const raising = [];
939
+ let isAbstract = false;
940
+ let isFinal = false;
941
+ let isRedefinition = false;
942
+ let isForTesting = false;
943
+ let section = "none";
944
+ let i = qual.lastIdx + 1;
945
+ while (i < toks.length) {
946
+ const t = toks[i];
947
+ const tn = t.tokenType.name;
948
+ if (tn === "Abstract") {
949
+ isAbstract = true;
950
+ i += 1;
951
+ continue;
952
+ }
953
+ if (tn === "Final") {
954
+ isFinal = true;
955
+ i += 1;
956
+ continue;
957
+ }
958
+ if (tn === "Redefinition") {
959
+ isRedefinition = true;
960
+ i += 1;
961
+ continue;
962
+ }
963
+ if (tn === "For" && toks[i + 1]?.tokenType.name === "Testing") {
964
+ isForTesting = true;
965
+ i += 2;
966
+ continue;
967
+ }
968
+ if (tn === "Importing") {
969
+ section = "importing";
970
+ i += 1;
971
+ continue;
972
+ }
973
+ if (tn === "Exporting") {
974
+ section = "exporting";
975
+ i += 1;
976
+ continue;
977
+ }
978
+ if (tn === "Changing") {
979
+ section = "changing";
980
+ i += 1;
981
+ continue;
982
+ }
983
+ if (tn === "Returning") {
984
+ section = "returning";
985
+ i += 1;
986
+ continue;
987
+ }
988
+ if (tn === "Raising") {
989
+ section = "raising";
990
+ i += 1;
991
+ continue;
992
+ }
993
+ if (section === "raising") {
994
+ if (tn === "Identifier") {
995
+ raising.push(t.image);
996
+ i += 1;
997
+ continue;
998
+ }
999
+ i += 1;
1000
+ continue;
1001
+ }
1002
+ if (section === "none") {
1003
+ i += 1;
1004
+ continue;
1005
+ }
1006
+ const consumed = consumeMethodParam(toks, i, c);
1007
+ if (!consumed) break;
1008
+ const { param, nextIndex } = consumed;
1009
+ if (section === "importing") importing.push(param);
1010
+ else if (section === "exporting") exporting.push(param);
1011
+ else if (section === "changing") changing.push(param);
1012
+ else if (section === "returning") returning = param;
1013
+ i = nextIndex;
1014
+ }
1015
+ return {
1016
+ kind: "MethodDecl",
1017
+ name: qual.name,
1018
+ abapDoc,
1019
+ isClassMethod,
1020
+ isAbstract,
1021
+ isFinal,
1022
+ isRedefinition,
1023
+ isForTesting,
1024
+ importing,
1025
+ exporting,
1026
+ changing,
1027
+ returning,
1028
+ raising,
1029
+ span: spanFromStmt(stmt, head)
1030
+ };
1031
+ }
1032
+ function consumeMethodParam(toks, start, c) {
1033
+ let i = start;
1034
+ let isValue = false;
1035
+ if (toks[i]?.tokenType.name === "Value" && toks[i + 1]?.tokenType.name === "LParen") {
1036
+ isValue = true;
1037
+ const nameTok = toks[i + 2];
1038
+ if (!isNameLike(nameTok)) return null;
1039
+ if (toks[i + 3]?.tokenType.name !== "RParen") return null;
1040
+ i += 4;
1041
+ const paramName = nameTok.image;
1042
+ return parseParamTail(toks, i, c, paramName, isValue);
1043
+ }
1044
+ const nameTok = toks[i];
1045
+ if (!isNameLike(nameTok)) return null;
1046
+ i += 1;
1047
+ return parseParamTail(toks, i, c, nameTok.image, isValue);
1048
+ }
1049
+ function parseParamTail(toks, start, c, name, isValue) {
1050
+ let i = start;
1051
+ if (toks[i]?.tokenType.name !== "Type") {
1052
+ c.report("expected TYPE in parameter declaration", toks[i] ?? toks[start - 1]);
1053
+ return null;
1054
+ }
1055
+ i += 1;
1056
+ const typeRes = consumeTypeRef(toks, i, c);
1057
+ if (!typeRes) return null;
1058
+ const type = typeRes.type;
1059
+ i = typeRes.nextIndex;
1060
+ let isOptional = false;
1061
+ let defaultValue;
1062
+ while (i < toks.length) {
1063
+ const tn = toks[i].tokenType.name;
1064
+ if (tn === "Optional") {
1065
+ isOptional = true;
1066
+ i += 1;
1067
+ continue;
1068
+ }
1069
+ if (tn === "Default") {
1070
+ i += 1;
1071
+ const startTok = toks[i];
1072
+ if (!startTok) break;
1073
+ const valStart = startTok.startOffset;
1074
+ let valEnd = startTok.endOffset ?? startTok.startOffset;
1075
+ while (i < toks.length) {
1076
+ const nt = toks[i]?.tokenType.name;
1077
+ if (nt === "Optional" || nt === "Importing" || nt === "Exporting" || nt === "Changing" || nt === "Returning" || nt === "Raising") break;
1078
+ valEnd = toks[i].endOffset ?? valEnd;
1079
+ i += 1;
1080
+ if (i < toks.length && toks[i]?.tokenType.name === "Identifier") break;
1081
+ }
1082
+ defaultValue = c["sliceSource"](valStart, valEnd);
1083
+ continue;
1084
+ }
1085
+ break;
1086
+ }
1087
+ const startTok = toks[start - (isValue ? 4 : 1)];
1088
+ const endTok = toks[i - 1] ?? startTok;
1089
+ return {
1090
+ param: {
1091
+ kind: "MethodParam",
1092
+ name,
1093
+ type,
1094
+ isValue,
1095
+ isOptional,
1096
+ defaultValue,
1097
+ span: {
1098
+ startOffset: startTok.startOffset,
1099
+ endOffset: endTok.endOffset ?? endTok.startOffset,
1100
+ startLine: startTok.startLine ?? 1,
1101
+ startColumn: startTok.startColumn ?? 1
1102
+ }
1103
+ },
1104
+ nextIndex: i
1105
+ };
1106
+ }
1107
+ /**
1108
+ * Shared body for member declarations shaped like `<head> <name> TYPE
1109
+ * <typeref> [rest…]`. Returns `{ nameTok, typeRes, toks, stmt, tailIdx }`
1110
+ * where `tailIdx` is the index of the first token AFTER the type-ref so
1111
+ * callers can inspect modifiers such as `READ-ONLY` / `VALUE <lit>`.
1112
+ */
1113
+ function parseNameTypeStatement(c, expectation) {
1114
+ const head = c.current();
1115
+ const stmt = c.collectStatement();
1116
+ const toks = stmt.tokens;
1117
+ const nameTok = toks[1];
1118
+ if (!nameTok || nameTok.tokenType.name !== "Identifier") {
1119
+ c.report(`expected ${expectation.name}`, nameTok ?? head);
1120
+ return null;
1121
+ }
1122
+ if (toks[2]?.tokenType.name !== "Type") {
1123
+ c.report(`expected TYPE in ${expectation.type}`, toks[2] ?? head);
1124
+ return null;
1125
+ }
1126
+ const typeRes = consumeTypeRef(toks, 3, c);
1127
+ if (!typeRes) return null;
1128
+ return {
1129
+ nameTok,
1130
+ typeRes,
1131
+ toks,
1132
+ stmt,
1133
+ tailIdx: typeRes.nextIndex
1134
+ };
1135
+ }
1136
+ function parseAttributeDecl(c, abapDoc) {
1137
+ const head = c.current();
1138
+ const isClassData = head.tokenType.name === "ClassData";
1139
+ const parsed = parseNameTypeStatement(c, {
1140
+ name: "attribute name",
1141
+ type: "attribute declaration"
1142
+ });
1143
+ if (!parsed) return null;
1144
+ const { nameTok, typeRes, toks, stmt, tailIdx } = parsed;
1145
+ const isReadOnly = toks[tailIdx]?.tokenType.name === "ReadOnly";
1146
+ return {
1147
+ kind: "AttributeDecl",
1148
+ name: nameTok.image,
1149
+ abapDoc,
1150
+ isClassData,
1151
+ isReadOnly,
1152
+ type: typeRes.type,
1153
+ span: spanFromStmt(stmt, head)
1154
+ };
1155
+ }
1156
+ function parseTypeDecl(c, abapDoc) {
1157
+ const head = c.current();
1158
+ const first = c.peek(1);
1159
+ const second = c.peek(2);
1160
+ const third = c.peek(3);
1161
+ if (first?.tokenType.name === "Colon" && second?.tokenType.name === "Begin" && third?.tokenType.name === "Of") return parseStructureTypes(c, abapDoc);
1162
+ const parsed = parseNameTypeStatement(c, {
1163
+ name: "type name after TYPES",
1164
+ type: "TYPES declaration"
1165
+ });
1166
+ if (!parsed) return null;
1167
+ const { nameTok, typeRes, stmt } = parsed;
1168
+ return {
1169
+ kind: "TypeDecl",
1170
+ name: nameTok.image,
1171
+ abapDoc,
1172
+ shape: {
1173
+ kind: "alias",
1174
+ type: typeRes.type
1175
+ },
1176
+ span: spanFromStmt(stmt, head)
1177
+ };
1178
+ }
1179
+ function parseStructureTypes(c, abapDoc) {
1180
+ const head = c.current();
1181
+ const stmt = c.collectStatement();
1182
+ const toks = stmt.tokens;
1183
+ const nameTok = toks[4];
1184
+ if (!nameTok || nameTok.tokenType.name !== "Identifier") {
1185
+ c.report("expected struct name in BEGIN OF", nameTok ?? head);
1186
+ return null;
1187
+ }
1188
+ const structName = nameTok.image;
1189
+ let endIdx = -1;
1190
+ for (let k = 5; k < toks.length - 1; k++) if (toks[k].tokenType.name === "End" && toks[k + 1]?.tokenType.name === "Of") {
1191
+ endIdx = k;
1192
+ break;
1193
+ }
1194
+ const fieldsEnd = endIdx >= 0 ? endIdx : toks.length;
1195
+ const fields = [];
1196
+ parseFieldRun(toks.slice(5, fieldsEnd), fields, c);
1197
+ return {
1198
+ kind: "TypeDecl",
1199
+ name: structName,
1200
+ abapDoc,
1201
+ shape: {
1202
+ kind: "structure",
1203
+ fields
1204
+ },
1205
+ span: {
1206
+ startOffset: head.startOffset,
1207
+ endOffset: stmt.endOffset,
1208
+ startLine: head.startLine ?? 1,
1209
+ startColumn: head.startColumn ?? 1
1210
+ }
1211
+ };
1212
+ }
1213
+ function parseFieldRun(toks, out, c) {
1214
+ let i = 0;
1215
+ while (i < toks.length) {
1216
+ if (toks[i]?.tokenType.name === "Comma") {
1217
+ i += 1;
1218
+ continue;
1219
+ }
1220
+ const nameTok = toks[i];
1221
+ if (!isNameLike(nameTok)) {
1222
+ while (i < toks.length && toks[i].tokenType.name !== "Comma") i += 1;
1223
+ continue;
1224
+ }
1225
+ if (toks[i + 1]?.tokenType.name !== "Type") {
1226
+ i += 1;
1227
+ continue;
1228
+ }
1229
+ let j = i + 2;
1230
+ const typeStart = j;
1231
+ while (j < toks.length && toks[j].tokenType.name !== "Comma") j += 1;
1232
+ const typeRes = consumeTypeRef(toks, typeStart, c, j);
1233
+ if (typeRes) {
1234
+ const last = toks[j - 1] ?? nameTok;
1235
+ out.push({
1236
+ kind: "StructureField",
1237
+ name: nameTok.image,
1238
+ type: typeRes.type,
1239
+ span: {
1240
+ startOffset: nameTok.startOffset,
1241
+ endOffset: last.endOffset ?? last.startOffset,
1242
+ startLine: nameTok.startLine ?? 1,
1243
+ startColumn: nameTok.startColumn ?? 1
1244
+ }
1245
+ });
1246
+ }
1247
+ i = j;
1248
+ }
1249
+ }
1250
+ function parseConstantDecl(c, abapDoc) {
1251
+ const head = c.current();
1252
+ const stmt = c.collectStatement();
1253
+ const toks = stmt.tokens;
1254
+ const nameTok = toks[1];
1255
+ if (!nameTok || nameTok.tokenType.name !== "Identifier") {
1256
+ c.report("expected constant name", nameTok ?? head);
1257
+ return null;
1258
+ }
1259
+ if (toks[2]?.tokenType.name !== "Type") {
1260
+ c.report("expected TYPE in CONSTANTS", toks[2] ?? head);
1261
+ return null;
1262
+ }
1263
+ let vIdx = -1;
1264
+ for (let k = 3; k < toks.length; k++) if (toks[k].tokenType.name === "Value") {
1265
+ vIdx = k;
1266
+ break;
1267
+ }
1268
+ if (vIdx < 0) {
1269
+ c.report("expected VALUE in CONSTANTS", head);
1270
+ return null;
1271
+ }
1272
+ const typeRes = consumeTypeRef(toks, 3, c, vIdx);
1273
+ if (!typeRes) return null;
1274
+ const valueStart = toks[vIdx + 1]?.startOffset ?? 0;
1275
+ const valueEnd = toks[toks.length - 1]?.endOffset ?? valueStart;
1276
+ const value = c["sliceSource"](valueStart, valueEnd);
1277
+ return {
1278
+ kind: "ConstantDecl",
1279
+ name: nameTok.image,
1280
+ abapDoc,
1281
+ type: typeRes.type,
1282
+ value,
1283
+ span: spanFromStmt(stmt, head)
1284
+ };
1285
+ }
1286
+ function parseEventDecl(c, abapDoc) {
1287
+ const head = c.current();
1288
+ const isClassEvent = head.tokenType.name === "ClassEvents";
1289
+ const stmt = c.collectStatement();
1290
+ const toks = stmt.tokens;
1291
+ const nameTok = toks[1];
1292
+ if (!isNameLike(nameTok)) {
1293
+ c.report("expected event name", nameTok ?? head);
1294
+ return null;
1295
+ }
1296
+ const exporting = [];
1297
+ if (toks[2]?.tokenType.name === "Exporting") {
1298
+ let i = 3;
1299
+ while (i < toks.length) {
1300
+ const consumed = consumeMethodParam(toks, i, c);
1301
+ if (!consumed) break;
1302
+ exporting.push(consumed.param);
1303
+ i = consumed.nextIndex;
1304
+ }
1305
+ }
1306
+ return {
1307
+ kind: "EventDecl",
1308
+ name: nameTok.image,
1309
+ abapDoc,
1310
+ isClassEvent,
1311
+ exporting,
1312
+ span: spanFromStmt(stmt, head)
1313
+ };
1314
+ }
1315
+ function parseInterfaceStmt(c, abapDoc) {
1316
+ const head = c.current();
1317
+ const stmt = c.collectStatement();
1318
+ const nameTok = stmt.tokens[1];
1319
+ if (!nameTok || nameTok.tokenType.name !== "Identifier") {
1320
+ c.report("expected interface name after INTERFACES", nameTok ?? head);
1321
+ return null;
1322
+ }
1323
+ return {
1324
+ kind: "InterfaceStmt",
1325
+ name: nameTok.image,
1326
+ abapDoc,
1327
+ span: spanFromStmt(stmt, head)
1328
+ };
1329
+ }
1330
+ function parseAliasDecl(c, abapDoc) {
1331
+ const head = c.current();
1332
+ const stmt = c.collectStatement();
1333
+ const toks = stmt.tokens;
1334
+ const nameTok = toks[1];
1335
+ if (!nameTok || nameTok.tokenType.name !== "Identifier") {
1336
+ c.report("expected alias name", nameTok ?? head);
1337
+ return null;
1338
+ }
1339
+ if (toks[2]?.tokenType.name !== "For") {
1340
+ c.report("expected FOR in ALIASES", toks[2] ?? head);
1341
+ return null;
1342
+ }
1343
+ const targetStart = toks[3]?.startOffset ?? 0;
1344
+ const targetEnd = toks[toks.length - 1]?.endOffset ?? targetStart;
1345
+ const target = c["sliceSource"](targetStart, targetEnd);
1346
+ return {
1347
+ kind: "AliasDecl",
1348
+ name: nameTok.image,
1349
+ target,
1350
+ abapDoc,
1351
+ span: spanFromStmt(stmt, head)
1352
+ };
1353
+ }
1354
+ function consumeTypeRef(toks, start, c, endBound) {
1355
+ const limit = endBound ?? toks.length;
1356
+ const i = start;
1357
+ if (i >= limit) {
1358
+ c.report("expected type reference", toks[i - 1] ?? toks[0]);
1359
+ return null;
1360
+ }
1361
+ if (toks[i]?.tokenType.name === "Ref" && toks[i + 1]?.tokenType.name === "To") {
1362
+ const inner = consumeTypeRef(toks, i + 2, c, limit);
1363
+ if (!inner) return null;
1364
+ const startTok = toks[i];
1365
+ const lastTok = toks[inner.nextIndex - 1] ?? startTok;
1366
+ return {
1367
+ type: {
1368
+ kind: "RefToTypeRef",
1369
+ target: inner.type,
1370
+ source: c["sliceSource"](startTok.startOffset, lastTok.endOffset ?? lastTok.startOffset)
1371
+ },
1372
+ nextIndex: inner.nextIndex
1373
+ };
1374
+ }
1375
+ const tableKind = peekTableKind(toks, i);
1376
+ if (tableKind) return consumeTableTypeRef(toks, i, c, limit, tableKind);
1377
+ const startTok = toks[i];
1378
+ if (!isNameLike(startTok)) {
1379
+ const at = toks[i] ?? toks[i - 1] ?? toks[0];
1380
+ const label = at ? at.image : "<eof>";
1381
+ c.report(`expected type name, got "${label}"`, at);
1382
+ return null;
1383
+ }
1384
+ let lastIdx = i;
1385
+ const nameParts = [startTok.image];
1386
+ let j = i + 1;
1387
+ while (j < limit) {
1388
+ const t = toks[j]?.tokenType.name;
1389
+ if (t === "FatArrow" || t === "Tilde") {
1390
+ const next = toks[j + 1];
1391
+ if (!isNameLike(next)) break;
1392
+ nameParts.push(t === "FatArrow" ? "=>" : "~", next.image);
1393
+ lastIdx = j + 1;
1394
+ j += 2;
1395
+ continue;
1396
+ }
1397
+ break;
1398
+ }
1399
+ const lastTok = toks[lastIdx];
1400
+ const source = c["sliceSource"](startTok.startOffset, lastTok.endOffset ?? lastTok.startOffset);
1401
+ const fullName = nameParts.join("");
1402
+ return {
1403
+ type: isBuiltinName(fullName) ? {
1404
+ kind: "BuiltinTypeRef",
1405
+ name: fullName,
1406
+ source
1407
+ } : {
1408
+ kind: "NamedTypeRef",
1409
+ name: fullName,
1410
+ source
1411
+ },
1412
+ nextIndex: lastIdx + 1
1413
+ };
1414
+ }
1415
+ function peekTableKind(toks, i) {
1416
+ const t = toks[i]?.tokenType.name;
1417
+ if (t === "Standard" && toks[i + 1]?.tokenType.name === "Table") return "standard";
1418
+ if (t === "Sorted" && toks[i + 1]?.tokenType.name === "Table") return "sorted";
1419
+ if (t === "Hashed" && toks[i + 1]?.tokenType.name === "Table") return "hashed";
1420
+ return null;
1421
+ }
1422
+ function consumeTableTypeRef(toks, start, c, limit, tableKind) {
1423
+ let i = start + 2;
1424
+ if (toks[i]?.tokenType.name !== "Of") {
1425
+ c.report("expected OF in TABLE OF", toks[i] ?? toks[start]);
1426
+ return fallbackTable(toks, start, i, tableKind, c);
1427
+ }
1428
+ i += 1;
1429
+ const rowRes = consumeTypeRef(toks, i, c, limit);
1430
+ if (!rowRes) return fallbackTable(toks, start, i, tableKind, c);
1431
+ i = rowRes.nextIndex;
1432
+ const keyStart = i;
1433
+ while (i < limit) i += 1;
1434
+ const keyTokens = toks.slice(keyStart, limit);
1435
+ const keyClause = keyTokens.length > 0 ? c["sliceSource"](keyTokens[0].startOffset, keyTokens[keyTokens.length - 1].endOffset ?? keyTokens[keyTokens.length - 1].startOffset) : "";
1436
+ const startTok = toks[start];
1437
+ const lastTok = toks[limit - 1] ?? startTok;
1438
+ return {
1439
+ type: {
1440
+ kind: "TableTypeRef",
1441
+ tableKind,
1442
+ row: rowRes.type,
1443
+ keyClause,
1444
+ source: c["sliceSource"](startTok.startOffset, lastTok.endOffset ?? lastTok.startOffset)
1445
+ },
1446
+ nextIndex: i
1447
+ };
1448
+ }
1449
+ function fallbackTable(toks, start, i, tableKind, c) {
1450
+ const startTok = toks[start];
1451
+ const lastTok = toks[i - 1] ?? startTok;
1452
+ return {
1453
+ type: {
1454
+ kind: "TableTypeRef",
1455
+ tableKind,
1456
+ row: {
1457
+ kind: "NamedTypeRef",
1458
+ name: "?",
1459
+ source: "?"
1460
+ },
1461
+ keyClause: "",
1462
+ source: c["sliceSource"](startTok.startOffset, lastTok.endOffset ?? lastTok.startOffset)
1463
+ },
1464
+ nextIndex: i
1465
+ };
1466
+ }
1467
+ function spanFromStmt(stmt, anchor) {
1468
+ return {
1469
+ startOffset: stmt.startOffset,
1470
+ endOffset: stmt.endOffset,
1471
+ startLine: anchor.startLine ?? 1,
1472
+ startColumn: anchor.startColumn ?? 1
1473
+ };
1474
+ }
1475
+ /**
1476
+ * True when the token can legally appear in a position that expects an
1477
+ * ABAP identifier (type name, field name, parameter name, qualified part).
1478
+ *
1479
+ * The ABAP grammar allows many reserved words to be reused as names in
1480
+ * declaration positions — e.g. `DATA data TYPE i.`, `METHODS foo IMPORTING
1481
+ * type TYPE string.`, `TYPES: BEGIN OF x, data TYPE i, END OF x.`.
1482
+ * At the lexer level those tokens are classified as keywords; the parser
1483
+ * has to reinterpret them contextually.
1484
+ *
1485
+ * We accept: `Identifier`, plus any token whose `image` is a plain ABAP
1486
+ * identifier (starts with letter/underscore, only contains the chars a
1487
+ * real identifier would). This keeps the rule cheap (no huge keyword
1488
+ * allowlist) and catches every keyword that can legally be a name.
1489
+ */
1490
+ /**
1491
+ * Read a possibly-qualified ABAP name starting at `start`: a chain of
1492
+ * identifier-shaped tokens separated by `~` (interface member) or `=>`
1493
+ * (static scope). Returns the joined `image` plus the last-consumed
1494
+ * index, or `null` if the head token is not name-like.
1495
+ *
1496
+ * `zif_foo~bar` → `{ name: 'zif_foo~bar', lastIdx: start + 2 }`
1497
+ * `zcl_x=>method` → `{ name: 'zcl_x=>method', lastIdx: start + 2 }`
1498
+ * `plain_name` → `{ name: 'plain_name', lastIdx: start }`
1499
+ */
1500
+ function readQualifiedName(toks, start) {
1501
+ const head = toks[start];
1502
+ if (!isNameLike(head)) return null;
1503
+ const parts = [head.image];
1504
+ let lastIdx = start;
1505
+ let j = start + 1;
1506
+ while (j < toks.length) {
1507
+ const sep = toks[j]?.tokenType.name;
1508
+ if (sep !== "Tilde" && sep !== "FatArrow") break;
1509
+ const next = toks[j + 1];
1510
+ if (!isNameLike(next)) break;
1511
+ parts.push(sep === "Tilde" ? "~" : "=>", next.image);
1512
+ lastIdx = j + 1;
1513
+ j += 2;
1514
+ }
1515
+ return {
1516
+ name: parts.join(""),
1517
+ lastIdx
1518
+ };
1519
+ }
1520
+ function isNameLike(t) {
1521
+ if (!t) return false;
1522
+ if (t.tokenType.name === "Identifier") return true;
1523
+ return /^[A-Za-z_][A-Za-z0-9_/]*$/.test(t.image);
1524
+ }
1525
+ const BUILTIN_ABAP_TYPES = /* @__PURE__ */ new Set([
1526
+ "string",
1527
+ "xstring",
1528
+ "i",
1529
+ "int8",
1530
+ "int4",
1531
+ "int2",
1532
+ "int1",
1533
+ "f",
1534
+ "p",
1535
+ "c",
1536
+ "n",
1537
+ "d",
1538
+ "t",
1539
+ "decfloat16",
1540
+ "decfloat34",
1541
+ "timestampl",
1542
+ "timestamp",
1543
+ "abap_bool",
1544
+ "abap_boolean",
1545
+ "abap_true",
1546
+ "abap_false",
1547
+ "sy-subrc",
1548
+ "sy-tabix"
1549
+ ]);
1550
+ function isBuiltinName(name) {
1551
+ return BUILTIN_ABAP_TYPES.has(name.toLowerCase());
1552
+ }
1553
+
1554
+ //#endregion
1555
+ //#region src/assert.ts
1556
+ /**
1557
+ * Assertions derived from `parse()`. Useful for downstream consumers
1558
+ * (generators, linters, CI gates) that want to guarantee their output
1559
+ * stays inside the structural subset of ABAP OO that `aclass`
1560
+ * understands, without writing the same filtering logic each time.
1561
+ */
1562
+ var AclassParseError = class extends Error {
1563
+ source;
1564
+ errors;
1565
+ constructor(fileLabel, errors, source) {
1566
+ super(`aclass: ${errors.length} parse error${errors.length === 1 ? "" : "s"} in ${fileLabel}:\n` + errors.slice(0, 10).map((e) => ` [${e.line}:${e.column}] ${e.message}`).join("\n"));
1567
+ this.name = "AclassParseError";
1568
+ this.source = source;
1569
+ this.errors = errors;
1570
+ }
1571
+ };
1572
+ /**
1573
+ * Throw `AclassParseError` if the source has any lex / parse errors.
1574
+ * `fileLabel` is included in the message so CI output points at the
1575
+ * offending file without the caller having to format it.
1576
+ *
1577
+ * Intended use in test suites:
1578
+ *
1579
+ * ```ts
1580
+ * for (const f of generated) {
1581
+ * assertCleanParse(readFileSync(f, 'utf8'), f);
1582
+ * }
1583
+ * ```
1584
+ */
1585
+ function assertCleanParse(source, fileLabel = "<source>") {
1586
+ const { errors } = parse(source);
1587
+ if (errors.length === 0) return;
1588
+ throw new AclassParseError(fileLabel, errors, source);
1589
+ }
1590
+
1591
+ //#endregion
1592
+ export { AclassLexer, AclassParseError, allTokens, assertCleanParse, fromLexError, fromParseError, parse, tokenize, tokens_exports as tokens };
1593
+ //# sourceMappingURL=index.mjs.map