@abapify/aclass 0.0.0 → 0.4.6
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.d.mts +359 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1593 -0
- package/dist/index.mjs.map +1 -0
- package/dist/rolldown-runtime-w6R9maHv.mjs +18 -0
- package/package.json +42 -1
- package/README.md +0 -1
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
|