@abapify/acds 0.4.0 → 0.4.3
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 +357 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1660 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +1 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1660 @@
|
|
|
1
|
+
import { CstParser, Lexer, createToken } from "chevrotain";
|
|
2
|
+
|
|
3
|
+
//#region src/tokens.ts
|
|
4
|
+
/**
|
|
5
|
+
* Chevrotain Token Definitions for ABAP CDS
|
|
6
|
+
*
|
|
7
|
+
* Token order matters: more specific patterns must come before general ones.
|
|
8
|
+
* Keywords use `longer_alt` to avoid matching prefixes of identifiers.
|
|
9
|
+
*/
|
|
10
|
+
const WhiteSpace = createToken({
|
|
11
|
+
name: "WhiteSpace",
|
|
12
|
+
pattern: /\s+/,
|
|
13
|
+
group: Lexer.SKIPPED
|
|
14
|
+
});
|
|
15
|
+
const LineComment = createToken({
|
|
16
|
+
name: "LineComment",
|
|
17
|
+
pattern: /\/\/[^\n]*/,
|
|
18
|
+
group: Lexer.SKIPPED
|
|
19
|
+
});
|
|
20
|
+
const BlockComment = createToken({
|
|
21
|
+
name: "BlockComment",
|
|
22
|
+
pattern: /\/\*[\s\S]*?\*\//,
|
|
23
|
+
group: Lexer.SKIPPED
|
|
24
|
+
});
|
|
25
|
+
const StringLiteral = createToken({
|
|
26
|
+
name: "StringLiteral",
|
|
27
|
+
pattern: /'(?:[^'\\]|\\.)*'/
|
|
28
|
+
});
|
|
29
|
+
const NumberLiteral = createToken({
|
|
30
|
+
name: "NumberLiteral",
|
|
31
|
+
pattern: /\d+(?:\.\d+)?/
|
|
32
|
+
});
|
|
33
|
+
/** Hash-prefixed enum value: #TRANSPARENT, #NOT_EXTENSIBLE */
|
|
34
|
+
const EnumLiteral = createToken({
|
|
35
|
+
name: "EnumLiteral",
|
|
36
|
+
pattern: /#[A-Za-z_][A-Za-z0-9_]*/
|
|
37
|
+
});
|
|
38
|
+
const Identifier = createToken({
|
|
39
|
+
name: "Identifier",
|
|
40
|
+
pattern: /[A-Za-z_][A-Za-z0-9_]*/
|
|
41
|
+
});
|
|
42
|
+
const Define = createToken({
|
|
43
|
+
name: "Define",
|
|
44
|
+
pattern: /define/,
|
|
45
|
+
longer_alt: Identifier
|
|
46
|
+
});
|
|
47
|
+
const Table = createToken({
|
|
48
|
+
name: "Table",
|
|
49
|
+
pattern: /table/,
|
|
50
|
+
longer_alt: Identifier
|
|
51
|
+
});
|
|
52
|
+
const Structure = createToken({
|
|
53
|
+
name: "Structure",
|
|
54
|
+
pattern: /structure/,
|
|
55
|
+
longer_alt: Identifier
|
|
56
|
+
});
|
|
57
|
+
const Type = createToken({
|
|
58
|
+
name: "Type",
|
|
59
|
+
pattern: /type/,
|
|
60
|
+
longer_alt: Identifier
|
|
61
|
+
});
|
|
62
|
+
const Service = createToken({
|
|
63
|
+
name: "Service",
|
|
64
|
+
pattern: /service/,
|
|
65
|
+
longer_alt: Identifier
|
|
66
|
+
});
|
|
67
|
+
const Expose = createToken({
|
|
68
|
+
name: "Expose",
|
|
69
|
+
pattern: /expose/,
|
|
70
|
+
longer_alt: Identifier
|
|
71
|
+
});
|
|
72
|
+
const Annotate = createToken({
|
|
73
|
+
name: "Annotate",
|
|
74
|
+
pattern: /annotate/,
|
|
75
|
+
longer_alt: Identifier
|
|
76
|
+
});
|
|
77
|
+
const Entity = createToken({
|
|
78
|
+
name: "Entity",
|
|
79
|
+
pattern: /entity/,
|
|
80
|
+
longer_alt: Identifier
|
|
81
|
+
});
|
|
82
|
+
const With = createToken({
|
|
83
|
+
name: "With",
|
|
84
|
+
pattern: /with/,
|
|
85
|
+
longer_alt: Identifier
|
|
86
|
+
});
|
|
87
|
+
const Key = createToken({
|
|
88
|
+
name: "Key",
|
|
89
|
+
pattern: /key/,
|
|
90
|
+
longer_alt: Identifier
|
|
91
|
+
});
|
|
92
|
+
const Not = createToken({
|
|
93
|
+
name: "Not",
|
|
94
|
+
pattern: /not/,
|
|
95
|
+
longer_alt: Identifier
|
|
96
|
+
});
|
|
97
|
+
const Null = createToken({
|
|
98
|
+
name: "Null",
|
|
99
|
+
pattern: /null/,
|
|
100
|
+
longer_alt: Identifier
|
|
101
|
+
});
|
|
102
|
+
const As = createToken({
|
|
103
|
+
name: "As",
|
|
104
|
+
pattern: /as/,
|
|
105
|
+
longer_alt: Identifier
|
|
106
|
+
});
|
|
107
|
+
const Include = createToken({
|
|
108
|
+
name: "Include",
|
|
109
|
+
pattern: /include/,
|
|
110
|
+
longer_alt: Identifier
|
|
111
|
+
});
|
|
112
|
+
const Suffix = createToken({
|
|
113
|
+
name: "Suffix",
|
|
114
|
+
pattern: /suffix/,
|
|
115
|
+
longer_alt: Identifier
|
|
116
|
+
});
|
|
117
|
+
const True = createToken({
|
|
118
|
+
name: "True",
|
|
119
|
+
pattern: /true/,
|
|
120
|
+
longer_alt: Identifier
|
|
121
|
+
});
|
|
122
|
+
const False = createToken({
|
|
123
|
+
name: "False",
|
|
124
|
+
pattern: /false/,
|
|
125
|
+
longer_alt: Identifier
|
|
126
|
+
});
|
|
127
|
+
const Abap = createToken({
|
|
128
|
+
name: "Abap",
|
|
129
|
+
pattern: /abap/,
|
|
130
|
+
longer_alt: Identifier
|
|
131
|
+
});
|
|
132
|
+
const View = createToken({
|
|
133
|
+
name: "View",
|
|
134
|
+
pattern: /view/,
|
|
135
|
+
longer_alt: Identifier
|
|
136
|
+
});
|
|
137
|
+
const Projection = createToken({
|
|
138
|
+
name: "Projection",
|
|
139
|
+
pattern: /projection/,
|
|
140
|
+
longer_alt: Identifier
|
|
141
|
+
});
|
|
142
|
+
const Select = createToken({
|
|
143
|
+
name: "Select",
|
|
144
|
+
pattern: /select/,
|
|
145
|
+
longer_alt: Identifier
|
|
146
|
+
});
|
|
147
|
+
const From = createToken({
|
|
148
|
+
name: "From",
|
|
149
|
+
pattern: /from/,
|
|
150
|
+
longer_alt: Identifier
|
|
151
|
+
});
|
|
152
|
+
const On = createToken({
|
|
153
|
+
name: "On",
|
|
154
|
+
pattern: /on/,
|
|
155
|
+
longer_alt: Identifier
|
|
156
|
+
});
|
|
157
|
+
const Association = createToken({
|
|
158
|
+
name: "Association",
|
|
159
|
+
pattern: /association/,
|
|
160
|
+
longer_alt: Identifier
|
|
161
|
+
});
|
|
162
|
+
const Composition = createToken({
|
|
163
|
+
name: "Composition",
|
|
164
|
+
pattern: /composition/,
|
|
165
|
+
longer_alt: Identifier
|
|
166
|
+
});
|
|
167
|
+
const To = createToken({
|
|
168
|
+
name: "To",
|
|
169
|
+
pattern: /to/,
|
|
170
|
+
longer_alt: Identifier
|
|
171
|
+
});
|
|
172
|
+
const Many = createToken({
|
|
173
|
+
name: "Many",
|
|
174
|
+
pattern: /many/,
|
|
175
|
+
longer_alt: Identifier
|
|
176
|
+
});
|
|
177
|
+
const One = createToken({
|
|
178
|
+
name: "One",
|
|
179
|
+
pattern: /one/,
|
|
180
|
+
longer_alt: Identifier
|
|
181
|
+
});
|
|
182
|
+
const Of = createToken({
|
|
183
|
+
name: "Of",
|
|
184
|
+
pattern: /of/,
|
|
185
|
+
longer_alt: Identifier
|
|
186
|
+
});
|
|
187
|
+
const Parameters = createToken({
|
|
188
|
+
name: "Parameters",
|
|
189
|
+
pattern: /parameters/,
|
|
190
|
+
longer_alt: Identifier
|
|
191
|
+
});
|
|
192
|
+
const Role = createToken({
|
|
193
|
+
name: "Role",
|
|
194
|
+
pattern: /role/,
|
|
195
|
+
longer_alt: Identifier
|
|
196
|
+
});
|
|
197
|
+
const Grant = createToken({
|
|
198
|
+
name: "Grant",
|
|
199
|
+
pattern: /grant/,
|
|
200
|
+
longer_alt: Identifier
|
|
201
|
+
});
|
|
202
|
+
const Where = createToken({
|
|
203
|
+
name: "Where",
|
|
204
|
+
pattern: /where/,
|
|
205
|
+
longer_alt: Identifier
|
|
206
|
+
});
|
|
207
|
+
const Abstract = createToken({
|
|
208
|
+
name: "Abstract",
|
|
209
|
+
pattern: /abstract/,
|
|
210
|
+
longer_alt: Identifier
|
|
211
|
+
});
|
|
212
|
+
const Custom = createToken({
|
|
213
|
+
name: "Custom",
|
|
214
|
+
pattern: /custom/,
|
|
215
|
+
longer_alt: Identifier
|
|
216
|
+
});
|
|
217
|
+
const Virtual = createToken({
|
|
218
|
+
name: "Virtual",
|
|
219
|
+
pattern: /virtual/,
|
|
220
|
+
longer_alt: Identifier
|
|
221
|
+
});
|
|
222
|
+
const Redirected = createToken({
|
|
223
|
+
name: "Redirected",
|
|
224
|
+
pattern: /redirected/,
|
|
225
|
+
longer_alt: Identifier
|
|
226
|
+
});
|
|
227
|
+
const Default = createToken({
|
|
228
|
+
name: "Default",
|
|
229
|
+
pattern: /default/,
|
|
230
|
+
longer_alt: Identifier
|
|
231
|
+
});
|
|
232
|
+
const EqEq = createToken({
|
|
233
|
+
name: "EqEq",
|
|
234
|
+
pattern: /==/
|
|
235
|
+
});
|
|
236
|
+
const Eq = createToken({
|
|
237
|
+
name: "Eq",
|
|
238
|
+
pattern: /=/
|
|
239
|
+
});
|
|
240
|
+
const NotEq = createToken({
|
|
241
|
+
name: "NotEq",
|
|
242
|
+
pattern: /!=|<>/
|
|
243
|
+
});
|
|
244
|
+
const LtEq = createToken({
|
|
245
|
+
name: "LtEq",
|
|
246
|
+
pattern: /<=/
|
|
247
|
+
});
|
|
248
|
+
const GtEq = createToken({
|
|
249
|
+
name: "GtEq",
|
|
250
|
+
pattern: />=/
|
|
251
|
+
});
|
|
252
|
+
const Lt = createToken({
|
|
253
|
+
name: "Lt",
|
|
254
|
+
pattern: /</
|
|
255
|
+
});
|
|
256
|
+
const Gt = createToken({
|
|
257
|
+
name: "Gt",
|
|
258
|
+
pattern: />/
|
|
259
|
+
});
|
|
260
|
+
const And = createToken({
|
|
261
|
+
name: "And",
|
|
262
|
+
pattern: /and/,
|
|
263
|
+
longer_alt: Identifier
|
|
264
|
+
});
|
|
265
|
+
const Or = createToken({
|
|
266
|
+
name: "Or",
|
|
267
|
+
pattern: /or/,
|
|
268
|
+
longer_alt: Identifier
|
|
269
|
+
});
|
|
270
|
+
const At = createToken({
|
|
271
|
+
name: "At",
|
|
272
|
+
pattern: /@/
|
|
273
|
+
});
|
|
274
|
+
const Colon = createToken({
|
|
275
|
+
name: "Colon",
|
|
276
|
+
pattern: /:/
|
|
277
|
+
});
|
|
278
|
+
const Semicolon = createToken({
|
|
279
|
+
name: "Semicolon",
|
|
280
|
+
pattern: /;/
|
|
281
|
+
});
|
|
282
|
+
const Dot = createToken({
|
|
283
|
+
name: "Dot",
|
|
284
|
+
pattern: /\./
|
|
285
|
+
});
|
|
286
|
+
const Comma = createToken({
|
|
287
|
+
name: "Comma",
|
|
288
|
+
pattern: /,/
|
|
289
|
+
});
|
|
290
|
+
const LBrace = createToken({
|
|
291
|
+
name: "LBrace",
|
|
292
|
+
pattern: /\{/
|
|
293
|
+
});
|
|
294
|
+
const RBrace = createToken({
|
|
295
|
+
name: "RBrace",
|
|
296
|
+
pattern: /\}/
|
|
297
|
+
});
|
|
298
|
+
const LParen = createToken({
|
|
299
|
+
name: "LParen",
|
|
300
|
+
pattern: /\(/
|
|
301
|
+
});
|
|
302
|
+
const RParen = createToken({
|
|
303
|
+
name: "RParen",
|
|
304
|
+
pattern: /\)/
|
|
305
|
+
});
|
|
306
|
+
const LBracket = createToken({
|
|
307
|
+
name: "LBracket",
|
|
308
|
+
pattern: /\[/
|
|
309
|
+
});
|
|
310
|
+
const RBracket = createToken({
|
|
311
|
+
name: "RBracket",
|
|
312
|
+
pattern: /\]/
|
|
313
|
+
});
|
|
314
|
+
const Star = createToken({
|
|
315
|
+
name: "Star",
|
|
316
|
+
pattern: /\*/
|
|
317
|
+
});
|
|
318
|
+
const Plus = createToken({
|
|
319
|
+
name: "Plus",
|
|
320
|
+
pattern: /\+/
|
|
321
|
+
});
|
|
322
|
+
const Minus = createToken({
|
|
323
|
+
name: "Minus",
|
|
324
|
+
pattern: /-/
|
|
325
|
+
});
|
|
326
|
+
const Slash = createToken({
|
|
327
|
+
name: "Slash",
|
|
328
|
+
pattern: /\//
|
|
329
|
+
});
|
|
330
|
+
const allTokens = [
|
|
331
|
+
WhiteSpace,
|
|
332
|
+
LineComment,
|
|
333
|
+
BlockComment,
|
|
334
|
+
StringLiteral,
|
|
335
|
+
NumberLiteral,
|
|
336
|
+
EnumLiteral,
|
|
337
|
+
Association,
|
|
338
|
+
Composition,
|
|
339
|
+
Abstract,
|
|
340
|
+
Annotate,
|
|
341
|
+
Define,
|
|
342
|
+
Table,
|
|
343
|
+
Structure,
|
|
344
|
+
Type,
|
|
345
|
+
Service,
|
|
346
|
+
Expose,
|
|
347
|
+
Entity,
|
|
348
|
+
With,
|
|
349
|
+
Key,
|
|
350
|
+
Not,
|
|
351
|
+
Null,
|
|
352
|
+
As,
|
|
353
|
+
Include,
|
|
354
|
+
Suffix,
|
|
355
|
+
True,
|
|
356
|
+
False,
|
|
357
|
+
Abap,
|
|
358
|
+
One,
|
|
359
|
+
View,
|
|
360
|
+
Projection,
|
|
361
|
+
Select,
|
|
362
|
+
From,
|
|
363
|
+
On,
|
|
364
|
+
To,
|
|
365
|
+
Many,
|
|
366
|
+
Of,
|
|
367
|
+
Parameters,
|
|
368
|
+
Role,
|
|
369
|
+
Grant,
|
|
370
|
+
Where,
|
|
371
|
+
Custom,
|
|
372
|
+
Virtual,
|
|
373
|
+
Redirected,
|
|
374
|
+
Default,
|
|
375
|
+
And,
|
|
376
|
+
Or,
|
|
377
|
+
Identifier,
|
|
378
|
+
EqEq,
|
|
379
|
+
NotEq,
|
|
380
|
+
LtEq,
|
|
381
|
+
GtEq,
|
|
382
|
+
At,
|
|
383
|
+
Colon,
|
|
384
|
+
Semicolon,
|
|
385
|
+
Dot,
|
|
386
|
+
Comma,
|
|
387
|
+
LBrace,
|
|
388
|
+
RBrace,
|
|
389
|
+
LParen,
|
|
390
|
+
RParen,
|
|
391
|
+
LBracket,
|
|
392
|
+
RBracket,
|
|
393
|
+
Eq,
|
|
394
|
+
Lt,
|
|
395
|
+
Gt,
|
|
396
|
+
Star,
|
|
397
|
+
Plus,
|
|
398
|
+
Minus,
|
|
399
|
+
Slash
|
|
400
|
+
];
|
|
401
|
+
const CdsLexer = new Lexer(allTokens);
|
|
402
|
+
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/parser.ts
|
|
405
|
+
/**
|
|
406
|
+
* Chevrotain CstParser for ABAP CDS.
|
|
407
|
+
*
|
|
408
|
+
* Grammar coverage (see src/lib/grammar/*.ts for the per-topic index):
|
|
409
|
+
* - DDL : table, structure, simple type, view entity, projection view,
|
|
410
|
+
* abstract/custom entity
|
|
411
|
+
* - DCL : role definitions (grant select on ... where ...)
|
|
412
|
+
* - DDLX : metadata extensions (`annotate entity ... with { ... }`)
|
|
413
|
+
* - SRVD : service definitions (`define service ... { expose ...; }`)
|
|
414
|
+
* - Elements / associations / parameters / annotations
|
|
415
|
+
*
|
|
416
|
+
* Rules are kept in this single class (Chevrotain requirement). Logical
|
|
417
|
+
* groupings are delimited with section banners and documented in
|
|
418
|
+
* `src/lib/grammar/`.
|
|
419
|
+
*/
|
|
420
|
+
var CdsParser = class extends CstParser {
|
|
421
|
+
constructor() {
|
|
422
|
+
super(allTokens, {
|
|
423
|
+
recoveryEnabled: true,
|
|
424
|
+
maxLookahead: 4
|
|
425
|
+
});
|
|
426
|
+
this.performSelfAnalysis();
|
|
427
|
+
}
|
|
428
|
+
sourceFile = this.RULE("sourceFile", () => {
|
|
429
|
+
this.MANY(() => {
|
|
430
|
+
this.SUBRULE(this.topLevelAnnotation);
|
|
431
|
+
});
|
|
432
|
+
this.SUBRULE(this.definition);
|
|
433
|
+
});
|
|
434
|
+
definition = this.RULE("definition", () => {
|
|
435
|
+
this.OR([{ ALT: () => this.SUBRULE(this.defineStatement) }, { ALT: () => this.SUBRULE(this.annotateStatement) }]);
|
|
436
|
+
});
|
|
437
|
+
defineStatement = this.RULE("defineStatement", () => {
|
|
438
|
+
this.CONSUME(Define);
|
|
439
|
+
this.OR([
|
|
440
|
+
{ ALT: () => this.SUBRULE(this.tableDefinition) },
|
|
441
|
+
{ ALT: () => this.SUBRULE(this.structureDefinition) },
|
|
442
|
+
{ ALT: () => this.SUBRULE(this.viewEntityDefinition) },
|
|
443
|
+
{ ALT: () => this.SUBRULE(this.abstractEntityDefinition) },
|
|
444
|
+
{ ALT: () => this.SUBRULE(this.customEntityDefinition) },
|
|
445
|
+
{ ALT: () => this.SUBRULE(this.simpleTypeDefinition) },
|
|
446
|
+
{ ALT: () => this.SUBRULE(this.serviceDefinition) },
|
|
447
|
+
{ ALT: () => this.SUBRULE(this.roleDefinition) }
|
|
448
|
+
]);
|
|
449
|
+
});
|
|
450
|
+
tableDefinition = this.RULE("tableDefinition", () => {
|
|
451
|
+
this.CONSUME(Table);
|
|
452
|
+
this.SUBRULE(this.cdsName);
|
|
453
|
+
this.CONSUME(LBrace);
|
|
454
|
+
this.MANY(() => {
|
|
455
|
+
this.SUBRULE(this.tableMember);
|
|
456
|
+
});
|
|
457
|
+
this.CONSUME(RBrace);
|
|
458
|
+
});
|
|
459
|
+
structureDefinition = this.RULE("structureDefinition", () => {
|
|
460
|
+
this.CONSUME(Structure);
|
|
461
|
+
this.SUBRULE(this.cdsName);
|
|
462
|
+
this.CONSUME(LBrace);
|
|
463
|
+
this.MANY(() => {
|
|
464
|
+
this.SUBRULE(this.tableMember);
|
|
465
|
+
});
|
|
466
|
+
this.CONSUME(RBrace);
|
|
467
|
+
});
|
|
468
|
+
viewEntityDefinition = this.RULE("viewEntityDefinition", () => {
|
|
469
|
+
this.CONSUME(View);
|
|
470
|
+
this.CONSUME(Entity);
|
|
471
|
+
this.SUBRULE(this.cdsName);
|
|
472
|
+
this.OPTION(() => {
|
|
473
|
+
this.SUBRULE(this.parametersClause);
|
|
474
|
+
});
|
|
475
|
+
this.CONSUME(As);
|
|
476
|
+
this.OR([{ ALT: () => this.SUBRULE(this.selectFromClause) }, { ALT: () => this.SUBRULE(this.projectionOnClause) }]);
|
|
477
|
+
this.OPTION2(() => {
|
|
478
|
+
this.SUBRULE(this.elementList);
|
|
479
|
+
});
|
|
480
|
+
this.OPTION3(() => {
|
|
481
|
+
this.CONSUME(Where);
|
|
482
|
+
this.SUBRULE(this.expression);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
abstractEntityDefinition = this.RULE("abstractEntityDefinition", () => {
|
|
486
|
+
this.CONSUME(Abstract);
|
|
487
|
+
this.CONSUME(Entity);
|
|
488
|
+
this.SUBRULE(this.cdsName);
|
|
489
|
+
this.OPTION(() => {
|
|
490
|
+
this.SUBRULE(this.parametersClause);
|
|
491
|
+
});
|
|
492
|
+
this.SUBRULE(this.elementList);
|
|
493
|
+
});
|
|
494
|
+
customEntityDefinition = this.RULE("customEntityDefinition", () => {
|
|
495
|
+
this.CONSUME(Custom);
|
|
496
|
+
this.CONSUME(Entity);
|
|
497
|
+
this.SUBRULE(this.cdsName);
|
|
498
|
+
this.OPTION(() => {
|
|
499
|
+
this.SUBRULE(this.parametersClause);
|
|
500
|
+
});
|
|
501
|
+
this.SUBRULE(this.elementList);
|
|
502
|
+
});
|
|
503
|
+
selectFromClause = this.RULE("selectFromClause", () => {
|
|
504
|
+
this.CONSUME(Select);
|
|
505
|
+
this.OPTION(() => {
|
|
506
|
+
this.CONSUME(From);
|
|
507
|
+
});
|
|
508
|
+
this.SUBRULE(this.dataSource);
|
|
509
|
+
});
|
|
510
|
+
projectionOnClause = this.RULE("projectionOnClause", () => {
|
|
511
|
+
this.CONSUME(Projection);
|
|
512
|
+
this.CONSUME(On);
|
|
513
|
+
this.SUBRULE(this.dataSource);
|
|
514
|
+
});
|
|
515
|
+
dataSource = this.RULE("dataSource", () => {
|
|
516
|
+
this.SUBRULE(this.qualifiedName);
|
|
517
|
+
this.OPTION(() => {
|
|
518
|
+
this.OPTION2(() => this.CONSUME(As));
|
|
519
|
+
this.SUBRULE(this.cdsName);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
elementList = this.RULE("elementList", () => {
|
|
523
|
+
this.CONSUME(LBrace);
|
|
524
|
+
this.MANY(() => {
|
|
525
|
+
this.SUBRULE(this.elementMember);
|
|
526
|
+
});
|
|
527
|
+
this.CONSUME(RBrace);
|
|
528
|
+
});
|
|
529
|
+
elementMember = this.RULE("elementMember", () => {
|
|
530
|
+
this.MANY(() => this.SUBRULE(this.annotation));
|
|
531
|
+
this.OR([{ ALT: () => this.SUBRULE(this.associationTail) }, { ALT: () => this.SUBRULE(this.viewElementTail) }]);
|
|
532
|
+
});
|
|
533
|
+
viewElementTail = this.RULE("viewElementTail", () => {
|
|
534
|
+
this.OPTION(() => this.CONSUME(Key));
|
|
535
|
+
this.OPTION2(() => this.CONSUME(Virtual));
|
|
536
|
+
this.OPTION3(() => this.CONSUME(Redirected));
|
|
537
|
+
this.OR([{
|
|
538
|
+
GATE: () => this.isTypedField(),
|
|
539
|
+
ALT: () => this.SUBRULE(this.typedFieldTail)
|
|
540
|
+
}, { ALT: () => this.SUBRULE(this.projectionExpression) }]);
|
|
541
|
+
this.OPTION4(() => {
|
|
542
|
+
this.OR2([{ ALT: () => this.CONSUME(Semicolon) }, { ALT: () => this.CONSUME(Comma) }]);
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
typedFieldTail = this.RULE("typedFieldTail", () => {
|
|
546
|
+
this.SUBRULE(this.cdsName);
|
|
547
|
+
this.CONSUME(Colon);
|
|
548
|
+
this.SUBRULE(this.typeReference);
|
|
549
|
+
this.OPTION(() => {
|
|
550
|
+
this.CONSUME(Not);
|
|
551
|
+
this.CONSUME(Null);
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
/**
|
|
555
|
+
* projection expression:
|
|
556
|
+
* <qualified-name> [as <alias>]
|
|
557
|
+
* Kept intentionally narrow — full SQL expression support is out of scope.
|
|
558
|
+
*/
|
|
559
|
+
projectionExpression = this.RULE("projectionExpression", () => {
|
|
560
|
+
this.SUBRULE(this.qualifiedName);
|
|
561
|
+
this.OPTION(() => {
|
|
562
|
+
this.CONSUME(As);
|
|
563
|
+
this.SUBRULE(this.cdsName);
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
/** Cheap lookahead: `ident :` (optionally following `key`/`virtual`) → typed field. */
|
|
567
|
+
isTypedField() {
|
|
568
|
+
const first = this.LA(1);
|
|
569
|
+
const second = this.LA(2);
|
|
570
|
+
if (!first || !second) return false;
|
|
571
|
+
return (first.tokenType?.name === "Identifier" || false) && second.tokenType?.name === "Colon";
|
|
572
|
+
}
|
|
573
|
+
associationTail = this.RULE("associationTail", () => {
|
|
574
|
+
this.OR([{ ALT: () => this.CONSUME(Association) }, { ALT: () => this.CONSUME(Composition) }]);
|
|
575
|
+
this.OPTION(() => this.SUBRULE(this.cardinality));
|
|
576
|
+
this.OR2([{ ALT: () => {
|
|
577
|
+
this.CONSUME(To);
|
|
578
|
+
this.OPTION2(() => {
|
|
579
|
+
this.CONSUME(Redirected);
|
|
580
|
+
this.CONSUME2(To);
|
|
581
|
+
});
|
|
582
|
+
} }, { ALT: () => {
|
|
583
|
+
this.CONSUME(Of);
|
|
584
|
+
this.OPTION3(() => {
|
|
585
|
+
this.OR3([{ ALT: () => this.CONSUME(Many) }, { ALT: () => this.CONSUME(One) }]);
|
|
586
|
+
});
|
|
587
|
+
this.OPTION4(() => this.CONSUME3(To));
|
|
588
|
+
} }]);
|
|
589
|
+
this.SUBRULE(this.qualifiedName);
|
|
590
|
+
this.OPTION5(() => {
|
|
591
|
+
this.CONSUME(As);
|
|
592
|
+
this.SUBRULE(this.cdsName);
|
|
593
|
+
});
|
|
594
|
+
this.OPTION6(() => {
|
|
595
|
+
this.CONSUME(On);
|
|
596
|
+
this.SUBRULE(this.expression);
|
|
597
|
+
});
|
|
598
|
+
this.OPTION7(() => {
|
|
599
|
+
this.OR4([{ ALT: () => this.CONSUME(Semicolon) }, { ALT: () => this.CONSUME(Comma) }]);
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
/** `[<lower>..<upper>]`, `[<n>]`, `[*]`, `[<lower>..*]` */
|
|
603
|
+
cardinality = this.RULE("cardinality", () => {
|
|
604
|
+
this.CONSUME(LBracket);
|
|
605
|
+
this.OR([{ ALT: () => this.CONSUME(Star) }, { ALT: () => {
|
|
606
|
+
this.CONSUME(NumberLiteral);
|
|
607
|
+
this.OPTION(() => {
|
|
608
|
+
this.CONSUME(Dot);
|
|
609
|
+
this.CONSUME2(Dot);
|
|
610
|
+
this.OR2([{ ALT: () => this.CONSUME2(NumberLiteral) }, { ALT: () => this.CONSUME2(Star) }]);
|
|
611
|
+
});
|
|
612
|
+
} }]);
|
|
613
|
+
this.CONSUME(RBracket);
|
|
614
|
+
});
|
|
615
|
+
parametersClause = this.RULE("parametersClause", () => {
|
|
616
|
+
this.CONSUME(With);
|
|
617
|
+
this.CONSUME(Parameters);
|
|
618
|
+
this.SUBRULE(this.parameterDefinition);
|
|
619
|
+
this.MANY(() => {
|
|
620
|
+
this.CONSUME(Comma);
|
|
621
|
+
this.SUBRULE2(this.parameterDefinition);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
parameterDefinition = this.RULE("parameterDefinition", () => {
|
|
625
|
+
this.MANY(() => this.SUBRULE(this.annotation));
|
|
626
|
+
this.SUBRULE(this.cdsName);
|
|
627
|
+
this.CONSUME(Colon);
|
|
628
|
+
this.SUBRULE(this.typeReference);
|
|
629
|
+
this.OPTION(() => {
|
|
630
|
+
this.CONSUME(Default);
|
|
631
|
+
this.SUBRULE(this.literal);
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
simpleTypeDefinition = this.RULE("simpleTypeDefinition", () => {
|
|
635
|
+
this.CONSUME(Type);
|
|
636
|
+
this.SUBRULE(this.cdsName);
|
|
637
|
+
this.CONSUME(Colon);
|
|
638
|
+
this.SUBRULE(this.typeReference);
|
|
639
|
+
this.CONSUME(Semicolon);
|
|
640
|
+
});
|
|
641
|
+
serviceDefinition = this.RULE("serviceDefinition", () => {
|
|
642
|
+
this.CONSUME(Service);
|
|
643
|
+
this.SUBRULE(this.cdsName);
|
|
644
|
+
this.CONSUME(LBrace);
|
|
645
|
+
this.MANY(() => {
|
|
646
|
+
this.SUBRULE(this.exposeStatement);
|
|
647
|
+
});
|
|
648
|
+
this.CONSUME(RBrace);
|
|
649
|
+
});
|
|
650
|
+
exposeStatement = this.RULE("exposeStatement", () => {
|
|
651
|
+
this.CONSUME(Expose);
|
|
652
|
+
this.SUBRULE(this.cdsName);
|
|
653
|
+
this.OPTION(() => {
|
|
654
|
+
this.CONSUME(As);
|
|
655
|
+
this.SUBRULE2(this.cdsName);
|
|
656
|
+
});
|
|
657
|
+
this.CONSUME(Semicolon);
|
|
658
|
+
});
|
|
659
|
+
roleDefinition = this.RULE("roleDefinition", () => {
|
|
660
|
+
this.CONSUME(Role);
|
|
661
|
+
this.SUBRULE(this.cdsName);
|
|
662
|
+
this.CONSUME(LBrace);
|
|
663
|
+
this.MANY(() => {
|
|
664
|
+
this.SUBRULE(this.grantStatement);
|
|
665
|
+
});
|
|
666
|
+
this.CONSUME(RBrace);
|
|
667
|
+
});
|
|
668
|
+
grantStatement = this.RULE("grantStatement", () => {
|
|
669
|
+
this.CONSUME(Grant);
|
|
670
|
+
this.CONSUME(Select);
|
|
671
|
+
this.CONSUME(On);
|
|
672
|
+
this.SUBRULE(this.qualifiedName);
|
|
673
|
+
this.OPTION(() => {
|
|
674
|
+
this.CONSUME(Where);
|
|
675
|
+
this.SUBRULE(this.expression);
|
|
676
|
+
});
|
|
677
|
+
this.CONSUME(Semicolon);
|
|
678
|
+
});
|
|
679
|
+
tableMember = this.RULE("tableMember", () => {
|
|
680
|
+
this.OR([{ ALT: () => this.SUBRULE(this.includeDirective) }, { ALT: () => this.SUBRULE(this.fieldDefinition) }]);
|
|
681
|
+
});
|
|
682
|
+
includeDirective = this.RULE("includeDirective", () => {
|
|
683
|
+
this.CONSUME(Include);
|
|
684
|
+
this.SUBRULE(this.cdsName);
|
|
685
|
+
this.OPTION(() => {
|
|
686
|
+
this.CONSUME(With);
|
|
687
|
+
this.CONSUME(Suffix);
|
|
688
|
+
this.SUBRULE2(this.cdsName);
|
|
689
|
+
});
|
|
690
|
+
this.CONSUME(Semicolon);
|
|
691
|
+
});
|
|
692
|
+
fieldDefinition = this.RULE("fieldDefinition", () => {
|
|
693
|
+
this.MANY(() => {
|
|
694
|
+
this.SUBRULE(this.annotation);
|
|
695
|
+
});
|
|
696
|
+
this.OPTION(() => {
|
|
697
|
+
this.CONSUME(Key);
|
|
698
|
+
});
|
|
699
|
+
this.SUBRULE(this.cdsName);
|
|
700
|
+
this.CONSUME(Colon);
|
|
701
|
+
this.SUBRULE(this.typeReference);
|
|
702
|
+
this.OPTION2(() => {
|
|
703
|
+
this.CONSUME(Not);
|
|
704
|
+
this.CONSUME(Null);
|
|
705
|
+
});
|
|
706
|
+
this.CONSUME(Semicolon);
|
|
707
|
+
});
|
|
708
|
+
annotateStatement = this.RULE("annotateStatement", () => {
|
|
709
|
+
this.CONSUME(Annotate);
|
|
710
|
+
this.CONSUME(Entity);
|
|
711
|
+
this.SUBRULE(this.cdsName);
|
|
712
|
+
this.CONSUME(With);
|
|
713
|
+
this.CONSUME(LBrace);
|
|
714
|
+
this.MANY(() => {
|
|
715
|
+
this.SUBRULE(this.annotatedElement);
|
|
716
|
+
});
|
|
717
|
+
this.CONSUME(RBrace);
|
|
718
|
+
});
|
|
719
|
+
annotatedElement = this.RULE("annotatedElement", () => {
|
|
720
|
+
this.MANY(() => {
|
|
721
|
+
this.SUBRULE(this.annotation);
|
|
722
|
+
});
|
|
723
|
+
this.SUBRULE(this.cdsName);
|
|
724
|
+
this.CONSUME(Semicolon);
|
|
725
|
+
});
|
|
726
|
+
topLevelAnnotation = this.RULE("topLevelAnnotation", () => {
|
|
727
|
+
this.SUBRULE(this.annotation);
|
|
728
|
+
});
|
|
729
|
+
annotation = this.RULE("annotation", () => {
|
|
730
|
+
this.CONSUME(At);
|
|
731
|
+
this.SUBRULE(this.dottedName);
|
|
732
|
+
this.OPTION(() => {
|
|
733
|
+
this.CONSUME(Colon);
|
|
734
|
+
this.SUBRULE(this.annotationValue);
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
dottedName = this.RULE("dottedName", () => {
|
|
738
|
+
this.SUBRULE(this.cdsName);
|
|
739
|
+
this.MANY(() => {
|
|
740
|
+
this.CONSUME(Dot);
|
|
741
|
+
this.SUBRULE2(this.cdsName);
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
annotationValue = this.RULE("annotationValue", () => {
|
|
745
|
+
this.OR([
|
|
746
|
+
{ ALT: () => this.CONSUME(StringLiteral) },
|
|
747
|
+
{ ALT: () => this.CONSUME(EnumLiteral) },
|
|
748
|
+
{ ALT: () => this.CONSUME(True) },
|
|
749
|
+
{ ALT: () => this.CONSUME(False) },
|
|
750
|
+
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
751
|
+
{ ALT: () => this.SUBRULE(this.annotationArray) },
|
|
752
|
+
{ ALT: () => this.SUBRULE(this.annotationObject) }
|
|
753
|
+
]);
|
|
754
|
+
});
|
|
755
|
+
annotationArray = this.RULE("annotationArray", () => {
|
|
756
|
+
this.CONSUME(LBracket);
|
|
757
|
+
this.MANY_SEP({
|
|
758
|
+
SEP: Comma,
|
|
759
|
+
DEF: () => {
|
|
760
|
+
this.SUBRULE(this.annotationValue);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
this.CONSUME(RBracket);
|
|
764
|
+
});
|
|
765
|
+
annotationObject = this.RULE("annotationObject", () => {
|
|
766
|
+
this.CONSUME(LBrace);
|
|
767
|
+
this.MANY_SEP({
|
|
768
|
+
SEP: Comma,
|
|
769
|
+
DEF: () => {
|
|
770
|
+
this.SUBRULE(this.annotationProperty);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
this.CONSUME(RBrace);
|
|
774
|
+
});
|
|
775
|
+
annotationProperty = this.RULE("annotationProperty", () => {
|
|
776
|
+
this.SUBRULE(this.dottedName);
|
|
777
|
+
this.CONSUME(Colon);
|
|
778
|
+
this.SUBRULE(this.annotationValue);
|
|
779
|
+
});
|
|
780
|
+
literal = this.RULE("literal", () => {
|
|
781
|
+
this.OR([
|
|
782
|
+
{ ALT: () => this.CONSUME(StringLiteral) },
|
|
783
|
+
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
784
|
+
{ ALT: () => this.CONSUME(EnumLiteral) },
|
|
785
|
+
{ ALT: () => this.CONSUME(True) },
|
|
786
|
+
{ ALT: () => this.CONSUME(False) }
|
|
787
|
+
]);
|
|
788
|
+
});
|
|
789
|
+
expression = this.RULE("expression", () => {
|
|
790
|
+
this.SUBRULE(this.comparison);
|
|
791
|
+
this.MANY(() => {
|
|
792
|
+
this.OR([{ ALT: () => this.CONSUME(And) }, { ALT: () => this.CONSUME(Or) }]);
|
|
793
|
+
this.SUBRULE2(this.comparison);
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
comparison = this.RULE("comparison", () => {
|
|
797
|
+
this.SUBRULE(this.operand);
|
|
798
|
+
this.OPTION(() => {
|
|
799
|
+
this.OR([
|
|
800
|
+
{ ALT: () => this.CONSUME(EqEq) },
|
|
801
|
+
{ ALT: () => this.CONSUME(Eq) },
|
|
802
|
+
{ ALT: () => this.CONSUME(NotEq) },
|
|
803
|
+
{ ALT: () => this.CONSUME(LtEq) },
|
|
804
|
+
{ ALT: () => this.CONSUME(GtEq) },
|
|
805
|
+
{ ALT: () => this.CONSUME(Lt) },
|
|
806
|
+
{ ALT: () => this.CONSUME(Gt) }
|
|
807
|
+
]);
|
|
808
|
+
this.SUBRULE2(this.operand);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
operand = this.RULE("operand", () => {
|
|
812
|
+
this.OR([{ ALT: () => this.SUBRULE(this.literal) }, { ALT: () => this.SUBRULE(this.qualifiedName) }]);
|
|
813
|
+
});
|
|
814
|
+
typeReference = this.RULE("typeReference", () => {
|
|
815
|
+
this.OR([{ ALT: () => this.SUBRULE(this.builtinType) }, { ALT: () => this.SUBRULE(this.namedType) }]);
|
|
816
|
+
});
|
|
817
|
+
/** `abap.char(10)` or `abap.dec(11,2)` */
|
|
818
|
+
builtinType = this.RULE("builtinType", () => {
|
|
819
|
+
this.CONSUME(Abap);
|
|
820
|
+
this.CONSUME(Dot);
|
|
821
|
+
this.SUBRULE(this.cdsName);
|
|
822
|
+
this.OPTION(() => {
|
|
823
|
+
this.CONSUME(LParen);
|
|
824
|
+
this.CONSUME(NumberLiteral);
|
|
825
|
+
this.OPTION2(() => {
|
|
826
|
+
this.CONSUME(Comma);
|
|
827
|
+
this.CONSUME2(NumberLiteral);
|
|
828
|
+
});
|
|
829
|
+
this.CONSUME(RParen);
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
/** Data element or other named type reference */
|
|
833
|
+
namedType = this.RULE("namedType", () => {
|
|
834
|
+
this.SUBRULE(this.qualifiedName);
|
|
835
|
+
});
|
|
836
|
+
/** An identifier that may also be a keyword used as a name. */
|
|
837
|
+
cdsName = this.RULE("cdsName", () => {
|
|
838
|
+
this.OR([
|
|
839
|
+
{ ALT: () => this.CONSUME(Identifier) },
|
|
840
|
+
{ ALT: () => this.CONSUME(Table) },
|
|
841
|
+
{ ALT: () => this.CONSUME(Structure) },
|
|
842
|
+
{ ALT: () => this.CONSUME(Type) },
|
|
843
|
+
{ ALT: () => this.CONSUME(Service) },
|
|
844
|
+
{ ALT: () => this.CONSUME(Entity) },
|
|
845
|
+
{ ALT: () => this.CONSUME(Key) },
|
|
846
|
+
{ ALT: () => this.CONSUME(Expose) },
|
|
847
|
+
{ ALT: () => this.CONSUME(View) },
|
|
848
|
+
{ ALT: () => this.CONSUME(Role) },
|
|
849
|
+
{ ALT: () => this.CONSUME(Association) },
|
|
850
|
+
{ ALT: () => this.CONSUME(Composition) },
|
|
851
|
+
{ ALT: () => this.CONSUME(Abstract) },
|
|
852
|
+
{ ALT: () => this.CONSUME(Custom) },
|
|
853
|
+
{ ALT: () => this.CONSUME(Virtual) },
|
|
854
|
+
{ ALT: () => this.CONSUME(Redirected) },
|
|
855
|
+
{ ALT: () => this.CONSUME(Default) },
|
|
856
|
+
{ ALT: () => this.CONSUME(Parameters) },
|
|
857
|
+
{ ALT: () => this.CONSUME(Grant) },
|
|
858
|
+
{ ALT: () => this.CONSUME(Many) },
|
|
859
|
+
{ ALT: () => this.CONSUME(One) },
|
|
860
|
+
{ ALT: () => this.CONSUME(Of) },
|
|
861
|
+
{ ALT: () => this.CONSUME(Projection) },
|
|
862
|
+
{ ALT: () => this.CONSUME(Select) },
|
|
863
|
+
{ ALT: () => this.CONSUME(From) },
|
|
864
|
+
{ ALT: () => this.CONSUME(To) },
|
|
865
|
+
{ ALT: () => this.CONSUME(Where) }
|
|
866
|
+
]);
|
|
867
|
+
});
|
|
868
|
+
/** Dot-separated qualified name: foo.bar.baz */
|
|
869
|
+
qualifiedName = this.RULE("qualifiedName", () => {
|
|
870
|
+
this.SUBRULE(this.cdsName);
|
|
871
|
+
this.MANY(() => {
|
|
872
|
+
this.CONSUME(Dot);
|
|
873
|
+
this.SUBRULE2(this.cdsName);
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
/** Singleton parser instance (Chevrotain parsers are reusable) */
|
|
878
|
+
const cdsParser = new CdsParser();
|
|
879
|
+
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/visitor.ts
|
|
882
|
+
const BaseCstVisitor = cdsParser.getBaseCstVisitorConstructor();
|
|
883
|
+
function asNodes(v) {
|
|
884
|
+
return v ?? [];
|
|
885
|
+
}
|
|
886
|
+
function asTokens(v) {
|
|
887
|
+
return v ?? [];
|
|
888
|
+
}
|
|
889
|
+
function collectTokenImages(node) {
|
|
890
|
+
if (!node) return [];
|
|
891
|
+
const out = [];
|
|
892
|
+
const walk = (n) => {
|
|
893
|
+
for (const children of Object.values(n.children)) for (const child of children) if ("tokenType" in child) out.push(child);
|
|
894
|
+
else walk(child);
|
|
895
|
+
};
|
|
896
|
+
walk(node);
|
|
897
|
+
out.sort((a, b) => (a.startOffset ?? 0) - (b.startOffset ?? 0));
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
function toExpression(node) {
|
|
901
|
+
const toks = collectTokenImages(node);
|
|
902
|
+
return {
|
|
903
|
+
source: toks.map((t) => t.image).join(" "),
|
|
904
|
+
tokens: toks.map((t) => t.image)
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
var CdsVisitor = class extends BaseCstVisitor {
|
|
908
|
+
constructor() {
|
|
909
|
+
super();
|
|
910
|
+
this.validateVisitor();
|
|
911
|
+
}
|
|
912
|
+
sourceFile(ctx) {
|
|
913
|
+
const annotations = asNodes(ctx.topLevelAnnotation).map((node) => this.visit(node));
|
|
914
|
+
const def = this.visit(asNodes(ctx.definition)[0]);
|
|
915
|
+
if ("annotations" in def) def.annotations = [...annotations, ...def.annotations];
|
|
916
|
+
return { definitions: [def] };
|
|
917
|
+
}
|
|
918
|
+
definition(ctx) {
|
|
919
|
+
if (ctx.defineStatement) return this.visit(asNodes(ctx.defineStatement)[0]);
|
|
920
|
+
return this.visit(asNodes(ctx.annotateStatement)[0]);
|
|
921
|
+
}
|
|
922
|
+
defineStatement(ctx) {
|
|
923
|
+
const dispatch = [
|
|
924
|
+
["tableDefinition", asNodes(ctx.tableDefinition)[0]],
|
|
925
|
+
["structureDefinition", asNodes(ctx.structureDefinition)[0]],
|
|
926
|
+
["viewEntityDefinition", asNodes(ctx.viewEntityDefinition)[0]],
|
|
927
|
+
["abstractEntityDefinition", asNodes(ctx.abstractEntityDefinition)[0]],
|
|
928
|
+
["customEntityDefinition", asNodes(ctx.customEntityDefinition)[0]],
|
|
929
|
+
["simpleTypeDefinition", asNodes(ctx.simpleTypeDefinition)[0]],
|
|
930
|
+
["serviceDefinition", asNodes(ctx.serviceDefinition)[0]],
|
|
931
|
+
["roleDefinition", asNodes(ctx.roleDefinition)[0]]
|
|
932
|
+
];
|
|
933
|
+
for (const [, node] of dispatch) if (node) return this.visit(node);
|
|
934
|
+
return {
|
|
935
|
+
kind: "table",
|
|
936
|
+
name: "",
|
|
937
|
+
annotations: [],
|
|
938
|
+
members: []
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
tableDefinition(ctx) {
|
|
942
|
+
return {
|
|
943
|
+
kind: "table",
|
|
944
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
945
|
+
annotations: [],
|
|
946
|
+
members: asNodes(ctx.tableMember).map((node) => this.visit(node))
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
structureDefinition(ctx) {
|
|
950
|
+
return {
|
|
951
|
+
kind: "structure",
|
|
952
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
953
|
+
annotations: [],
|
|
954
|
+
members: asNodes(ctx.tableMember).map((node) => this.visit(node))
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
viewEntityDefinition(ctx) {
|
|
958
|
+
const name = this.visit(asNodes(ctx.cdsName)[0]);
|
|
959
|
+
const parameters = ctx.parametersClause ? this.visit(asNodes(ctx.parametersClause)[0]) : [];
|
|
960
|
+
let sourceKind = "select";
|
|
961
|
+
let source = { name: "" };
|
|
962
|
+
if (ctx.selectFromClause) {
|
|
963
|
+
sourceKind = "select";
|
|
964
|
+
source = this.visit(asNodes(ctx.selectFromClause)[0]);
|
|
965
|
+
} else if (ctx.projectionOnClause) {
|
|
966
|
+
sourceKind = "projection";
|
|
967
|
+
source = this.visit(asNodes(ctx.projectionOnClause)[0]);
|
|
968
|
+
}
|
|
969
|
+
const members = ctx.elementList ? this.visit(asNodes(ctx.elementList)[0]) : [];
|
|
970
|
+
const where = ctx.expression ? toExpression(asNodes(ctx.expression)[0]) : void 0;
|
|
971
|
+
return {
|
|
972
|
+
kind: "viewEntity",
|
|
973
|
+
name,
|
|
974
|
+
annotations: [],
|
|
975
|
+
sourceKind,
|
|
976
|
+
source,
|
|
977
|
+
parameters,
|
|
978
|
+
members,
|
|
979
|
+
...where && { where }
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
abstractEntityDefinition(ctx) {
|
|
983
|
+
return {
|
|
984
|
+
kind: "abstractEntity",
|
|
985
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
986
|
+
annotations: [],
|
|
987
|
+
parameters: ctx.parametersClause ? this.visit(asNodes(ctx.parametersClause)[0]) : [],
|
|
988
|
+
members: this.visit(asNodes(ctx.elementList)[0])
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
customEntityDefinition(ctx) {
|
|
992
|
+
return {
|
|
993
|
+
kind: "customEntity",
|
|
994
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
995
|
+
annotations: [],
|
|
996
|
+
parameters: ctx.parametersClause ? this.visit(asNodes(ctx.parametersClause)[0]) : [],
|
|
997
|
+
members: this.visit(asNodes(ctx.elementList)[0])
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
selectFromClause(ctx) {
|
|
1001
|
+
return this.visit(asNodes(ctx.dataSource)[0]);
|
|
1002
|
+
}
|
|
1003
|
+
projectionOnClause(ctx) {
|
|
1004
|
+
return this.visit(asNodes(ctx.dataSource)[0]);
|
|
1005
|
+
}
|
|
1006
|
+
dataSource(ctx) {
|
|
1007
|
+
const name = this.visit(asNodes(ctx.qualifiedName)[0]);
|
|
1008
|
+
const aliasNode = asNodes(ctx.cdsName)[0];
|
|
1009
|
+
const alias = aliasNode ? this.visit(aliasNode) : void 0;
|
|
1010
|
+
return {
|
|
1011
|
+
name,
|
|
1012
|
+
...alias && { alias }
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
elementList(ctx) {
|
|
1016
|
+
return asNodes(ctx.elementMember).map((node) => this.visit(node));
|
|
1017
|
+
}
|
|
1018
|
+
elementMember(ctx) {
|
|
1019
|
+
const annotations = asNodes(ctx.annotation).map((n) => this.visit(n));
|
|
1020
|
+
if (ctx.associationTail) {
|
|
1021
|
+
const decl = this.visit(asNodes(ctx.associationTail)[0]);
|
|
1022
|
+
decl.annotations = annotations;
|
|
1023
|
+
return decl;
|
|
1024
|
+
}
|
|
1025
|
+
const el = this.visit(asNodes(ctx.viewElementTail)[0]);
|
|
1026
|
+
el.annotations = annotations;
|
|
1027
|
+
return el;
|
|
1028
|
+
}
|
|
1029
|
+
viewElementTail(ctx) {
|
|
1030
|
+
const isKey = !!asTokens(ctx.Key).length;
|
|
1031
|
+
const isVirtual = !!asTokens(ctx.Virtual).length;
|
|
1032
|
+
const isRedirected = !!asTokens(ctx.Redirected).length;
|
|
1033
|
+
if (ctx.typedFieldTail) {
|
|
1034
|
+
const tail = this.visit(asNodes(ctx.typedFieldTail)[0]);
|
|
1035
|
+
return {
|
|
1036
|
+
annotations: [],
|
|
1037
|
+
isKey,
|
|
1038
|
+
isVirtual,
|
|
1039
|
+
isRedirected,
|
|
1040
|
+
expression: tail.name,
|
|
1041
|
+
type: tail.type,
|
|
1042
|
+
notNull: tail.notNull
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
const proj = this.visit(asNodes(ctx.projectionExpression)[0]);
|
|
1046
|
+
return {
|
|
1047
|
+
annotations: [],
|
|
1048
|
+
isKey,
|
|
1049
|
+
isVirtual,
|
|
1050
|
+
isRedirected,
|
|
1051
|
+
expression: proj.expression,
|
|
1052
|
+
...proj.alias && { alias: proj.alias }
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
typedFieldTail(ctx) {
|
|
1056
|
+
return {
|
|
1057
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
1058
|
+
type: this.visit(asNodes(ctx.typeReference)[0]),
|
|
1059
|
+
notNull: !!asTokens(ctx.Not).length
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
projectionExpression(ctx) {
|
|
1063
|
+
const expression = this.visit(asNodes(ctx.qualifiedName)[0]);
|
|
1064
|
+
const aliasNode = asNodes(ctx.cdsName)[0];
|
|
1065
|
+
const alias = aliasNode ? this.visit(aliasNode) : void 0;
|
|
1066
|
+
return {
|
|
1067
|
+
expression,
|
|
1068
|
+
...alias && { alias }
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
associationTail(ctx) {
|
|
1072
|
+
const associationKind = asTokens(ctx.Association).length ? "association" : "composition";
|
|
1073
|
+
const cardinality = ctx.cardinality ? this.visit(asNodes(ctx.cardinality)[0]) : void 0;
|
|
1074
|
+
let targetMultiplicity;
|
|
1075
|
+
if (asTokens(ctx.Many).length) targetMultiplicity = "many";
|
|
1076
|
+
else if (asTokens(ctx.One).length) targetMultiplicity = "one";
|
|
1077
|
+
const redirected = !!asTokens(ctx.Redirected).length;
|
|
1078
|
+
const target = this.visit(asNodes(ctx.qualifiedName)[0]);
|
|
1079
|
+
const aliasNode = asNodes(ctx.cdsName)[0];
|
|
1080
|
+
const alias = aliasNode ? this.visit(aliasNode) : void 0;
|
|
1081
|
+
const on = ctx.expression ? toExpression(asNodes(ctx.expression)[0]) : void 0;
|
|
1082
|
+
return {
|
|
1083
|
+
kind: "association",
|
|
1084
|
+
associationKind,
|
|
1085
|
+
annotations: [],
|
|
1086
|
+
...cardinality && { cardinality },
|
|
1087
|
+
...targetMultiplicity && { targetMultiplicity },
|
|
1088
|
+
target,
|
|
1089
|
+
...redirected && { redirected: true },
|
|
1090
|
+
...alias && { alias },
|
|
1091
|
+
...on && { on }
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
cardinality(ctx) {
|
|
1095
|
+
const nums = asTokens(ctx.NumberLiteral);
|
|
1096
|
+
const stars = asTokens(ctx.Star);
|
|
1097
|
+
if (nums.length === 0 && stars.length >= 1) return { max: "*" };
|
|
1098
|
+
if (nums.length === 1) {
|
|
1099
|
+
if (asTokens(ctx.Dot).length === 0) return { max: Number.parseInt(nums[0].image, 10) };
|
|
1100
|
+
if (stars.length >= 1) return {
|
|
1101
|
+
min: Number.parseInt(nums[0].image, 10),
|
|
1102
|
+
max: "*"
|
|
1103
|
+
};
|
|
1104
|
+
return {
|
|
1105
|
+
min: Number.parseInt(nums[0].image, 10),
|
|
1106
|
+
max: "*"
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
if (nums.length === 2) return {
|
|
1110
|
+
min: Number.parseInt(nums[0].image, 10),
|
|
1111
|
+
max: Number.parseInt(nums[1].image, 10)
|
|
1112
|
+
};
|
|
1113
|
+
return { max: "*" };
|
|
1114
|
+
}
|
|
1115
|
+
parametersClause(ctx) {
|
|
1116
|
+
return asNodes(ctx.parameterDefinition).map((n) => this.visit(n));
|
|
1117
|
+
}
|
|
1118
|
+
parameterDefinition(ctx) {
|
|
1119
|
+
const annotations = asNodes(ctx.annotation).map((n) => this.visit(n));
|
|
1120
|
+
const name = this.visit(asNodes(ctx.cdsName)[0]);
|
|
1121
|
+
const type = this.visit(asNodes(ctx.typeReference)[0]);
|
|
1122
|
+
const defaultValue = ctx.literal ? this.visit(asNodes(ctx.literal)[0]) : void 0;
|
|
1123
|
+
return {
|
|
1124
|
+
annotations,
|
|
1125
|
+
name,
|
|
1126
|
+
type,
|
|
1127
|
+
...defaultValue && { defaultValue }
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
tableMember(ctx) {
|
|
1131
|
+
if (ctx.includeDirective) return this.visit(asNodes(ctx.includeDirective)[0]);
|
|
1132
|
+
return this.visit(asNodes(ctx.fieldDefinition)[0]);
|
|
1133
|
+
}
|
|
1134
|
+
includeDirective(ctx) {
|
|
1135
|
+
const name = this.visit(asNodes(ctx.cdsName)[0]);
|
|
1136
|
+
const suffix = asNodes(ctx.cdsName)[1] ? this.visit(asNodes(ctx.cdsName)[1]) : void 0;
|
|
1137
|
+
return {
|
|
1138
|
+
kind: "include",
|
|
1139
|
+
name,
|
|
1140
|
+
...suffix !== void 0 && { suffix }
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
fieldDefinition(ctx) {
|
|
1144
|
+
const annotations = asNodes(ctx.annotation).map((node) => this.visit(node));
|
|
1145
|
+
const isKey = !!asTokens(ctx.Key).length;
|
|
1146
|
+
return {
|
|
1147
|
+
annotations,
|
|
1148
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
1149
|
+
type: this.visit(asNodes(ctx.typeReference)[0]),
|
|
1150
|
+
isKey,
|
|
1151
|
+
notNull: !!asTokens(ctx.Not).length
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
simpleTypeDefinition(ctx) {
|
|
1155
|
+
return {
|
|
1156
|
+
kind: "simpleType",
|
|
1157
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
1158
|
+
annotations: [],
|
|
1159
|
+
type: this.visit(asNodes(ctx.typeReference)[0])
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
serviceDefinition(ctx) {
|
|
1163
|
+
return {
|
|
1164
|
+
kind: "service",
|
|
1165
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
1166
|
+
annotations: [],
|
|
1167
|
+
exposes: asNodes(ctx.exposeStatement).map((node) => this.visit(node))
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
exposeStatement(ctx) {
|
|
1171
|
+
const names = asNodes(ctx.cdsName).map((n) => this.visit(n));
|
|
1172
|
+
const entity = names[0];
|
|
1173
|
+
const alias = names.length > 1 ? names[1] : void 0;
|
|
1174
|
+
return {
|
|
1175
|
+
entity,
|
|
1176
|
+
...alias && { alias }
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
roleDefinition(ctx) {
|
|
1180
|
+
return {
|
|
1181
|
+
kind: "role",
|
|
1182
|
+
name: this.visit(asNodes(ctx.cdsName)[0]),
|
|
1183
|
+
annotations: [],
|
|
1184
|
+
grants: asNodes(ctx.grantStatement).map((n) => this.visit(n))
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
grantStatement(ctx) {
|
|
1188
|
+
const entity = this.visit(asNodes(ctx.qualifiedName)[0]);
|
|
1189
|
+
const where = ctx.expression ? toExpression(asNodes(ctx.expression)[0]) : void 0;
|
|
1190
|
+
return {
|
|
1191
|
+
privilege: "select",
|
|
1192
|
+
entity,
|
|
1193
|
+
...where && { where }
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
annotateStatement(ctx) {
|
|
1197
|
+
return {
|
|
1198
|
+
kind: "metadataExtension",
|
|
1199
|
+
entity: this.visit(asNodes(ctx.cdsName)[0]),
|
|
1200
|
+
annotations: [],
|
|
1201
|
+
elements: asNodes(ctx.annotatedElement).map((node) => this.visit(node))
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
annotatedElement(ctx) {
|
|
1205
|
+
return {
|
|
1206
|
+
annotations: asNodes(ctx.annotation).map((node) => this.visit(node)),
|
|
1207
|
+
name: this.visit(asNodes(ctx.cdsName)[0])
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
topLevelAnnotation(ctx) {
|
|
1211
|
+
return this.visit(asNodes(ctx.annotation)[0]);
|
|
1212
|
+
}
|
|
1213
|
+
annotation(ctx) {
|
|
1214
|
+
return {
|
|
1215
|
+
key: this.visit(asNodes(ctx.dottedName)[0]),
|
|
1216
|
+
value: ctx.annotationValue ? this.visit(asNodes(ctx.annotationValue)[0]) : {
|
|
1217
|
+
kind: "boolean",
|
|
1218
|
+
value: true
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
dottedName(ctx) {
|
|
1223
|
+
return asNodes(ctx.cdsName).map((node) => this.visit(node)).join(".");
|
|
1224
|
+
}
|
|
1225
|
+
annotationValue(ctx) {
|
|
1226
|
+
if (ctx.StringLiteral) return {
|
|
1227
|
+
kind: "string",
|
|
1228
|
+
value: asTokens(ctx.StringLiteral)[0].image.slice(1, -1)
|
|
1229
|
+
};
|
|
1230
|
+
if (ctx.EnumLiteral) return {
|
|
1231
|
+
kind: "enum",
|
|
1232
|
+
value: asTokens(ctx.EnumLiteral)[0].image.slice(1)
|
|
1233
|
+
};
|
|
1234
|
+
if (ctx.True) return {
|
|
1235
|
+
kind: "boolean",
|
|
1236
|
+
value: true
|
|
1237
|
+
};
|
|
1238
|
+
if (ctx.False) return {
|
|
1239
|
+
kind: "boolean",
|
|
1240
|
+
value: false
|
|
1241
|
+
};
|
|
1242
|
+
if (ctx.NumberLiteral) return {
|
|
1243
|
+
kind: "number",
|
|
1244
|
+
value: Number.parseFloat(asTokens(ctx.NumberLiteral)[0].image)
|
|
1245
|
+
};
|
|
1246
|
+
if (ctx.annotationArray) return this.visit(asNodes(ctx.annotationArray)[0]);
|
|
1247
|
+
return this.visit(asNodes(ctx.annotationObject)[0]);
|
|
1248
|
+
}
|
|
1249
|
+
annotationArray(ctx) {
|
|
1250
|
+
return {
|
|
1251
|
+
kind: "array",
|
|
1252
|
+
items: asNodes(ctx.annotationValue).map((node) => this.visit(node))
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
annotationObject(ctx) {
|
|
1256
|
+
return {
|
|
1257
|
+
kind: "object",
|
|
1258
|
+
properties: asNodes(ctx.annotationProperty).map((node) => this.visit(node))
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
annotationProperty(ctx) {
|
|
1262
|
+
return {
|
|
1263
|
+
key: this.visit(asNodes(ctx.dottedName)[0]),
|
|
1264
|
+
value: this.visit(asNodes(ctx.annotationValue)[0])
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
literal(ctx) {
|
|
1268
|
+
if (ctx.StringLiteral) return {
|
|
1269
|
+
kind: "string",
|
|
1270
|
+
value: asTokens(ctx.StringLiteral)[0].image.slice(1, -1)
|
|
1271
|
+
};
|
|
1272
|
+
if (ctx.EnumLiteral) return {
|
|
1273
|
+
kind: "enum",
|
|
1274
|
+
value: asTokens(ctx.EnumLiteral)[0].image.slice(1)
|
|
1275
|
+
};
|
|
1276
|
+
if (ctx.True) return {
|
|
1277
|
+
kind: "boolean",
|
|
1278
|
+
value: true
|
|
1279
|
+
};
|
|
1280
|
+
if (ctx.False) return {
|
|
1281
|
+
kind: "boolean",
|
|
1282
|
+
value: false
|
|
1283
|
+
};
|
|
1284
|
+
return {
|
|
1285
|
+
kind: "number",
|
|
1286
|
+
value: Number.parseFloat(asTokens(ctx.NumberLiteral)[0].image)
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
expression() {
|
|
1290
|
+
return null;
|
|
1291
|
+
}
|
|
1292
|
+
comparison() {
|
|
1293
|
+
return null;
|
|
1294
|
+
}
|
|
1295
|
+
operand() {
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
typeReference(ctx) {
|
|
1299
|
+
if (ctx.builtinType) return this.visit(asNodes(ctx.builtinType)[0]);
|
|
1300
|
+
return this.visit(asNodes(ctx.namedType)[0]);
|
|
1301
|
+
}
|
|
1302
|
+
builtinType(ctx) {
|
|
1303
|
+
const name = this.visit(asNodes(ctx.cdsName)[0]);
|
|
1304
|
+
const numberTokens = asTokens(ctx.NumberLiteral);
|
|
1305
|
+
return {
|
|
1306
|
+
kind: "builtin",
|
|
1307
|
+
name,
|
|
1308
|
+
length: numberTokens[0] ? Number.parseInt(numberTokens[0].image, 10) : void 0,
|
|
1309
|
+
decimals: numberTokens[1] ? Number.parseInt(numberTokens[1].image, 10) : void 0
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
namedType(ctx) {
|
|
1313
|
+
return {
|
|
1314
|
+
kind: "named",
|
|
1315
|
+
name: this.visit(asNodes(ctx.qualifiedName)[0])
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
cdsName(ctx) {
|
|
1319
|
+
const t = ctx;
|
|
1320
|
+
for (const key of Object.keys(t)) {
|
|
1321
|
+
const tok = t[key]?.[0];
|
|
1322
|
+
if (tok && "image" in tok) return tok.image;
|
|
1323
|
+
}
|
|
1324
|
+
return "";
|
|
1325
|
+
}
|
|
1326
|
+
qualifiedName(ctx) {
|
|
1327
|
+
return asNodes(ctx.cdsName).map((node) => this.visit(node)).join(".");
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
/** Singleton visitor instance */
|
|
1331
|
+
const cdsVisitor = new CdsVisitor();
|
|
1332
|
+
|
|
1333
|
+
//#endregion
|
|
1334
|
+
//#region src/errors.ts
|
|
1335
|
+
function fromLexError(err) {
|
|
1336
|
+
return {
|
|
1337
|
+
message: err.message,
|
|
1338
|
+
line: err.line ?? 1,
|
|
1339
|
+
column: err.column ?? 1,
|
|
1340
|
+
offset: err.offset,
|
|
1341
|
+
length: err.length
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
function fromParseError(err) {
|
|
1345
|
+
const token = err.token;
|
|
1346
|
+
return {
|
|
1347
|
+
message: err.message,
|
|
1348
|
+
line: token.startLine ?? 1,
|
|
1349
|
+
column: token.startColumn ?? 1,
|
|
1350
|
+
offset: token.startOffset,
|
|
1351
|
+
length: (token.endOffset ?? token.startOffset) - token.startOffset + 1
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
//#endregion
|
|
1356
|
+
//#region src/lib/ast/walker.ts
|
|
1357
|
+
/** True when the definition has a `members` array (view-like entities). */
|
|
1358
|
+
function hasMembers(def) {
|
|
1359
|
+
return def.kind === "viewEntity" || def.kind === "abstractEntity" || def.kind === "customEntity";
|
|
1360
|
+
}
|
|
1361
|
+
/** True when the definition has a `members` array of typed fields (TABL/structure). */
|
|
1362
|
+
function hasFields(def) {
|
|
1363
|
+
return def.kind === "table" || def.kind === "structure";
|
|
1364
|
+
}
|
|
1365
|
+
function hasParameters(def) {
|
|
1366
|
+
return def.kind === "viewEntity" || def.kind === "abstractEntity" || def.kind === "customEntity";
|
|
1367
|
+
}
|
|
1368
|
+
/** Yield every definition in the file (currently one, future-proofed). */
|
|
1369
|
+
function* walkDefinitions(file) {
|
|
1370
|
+
for (const def of file.definitions) yield def;
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Yield every annotation attached anywhere in the AST (definitions,
|
|
1374
|
+
* elements/fields, parameters, associations).
|
|
1375
|
+
*/
|
|
1376
|
+
function* walkAnnotations(file) {
|
|
1377
|
+
for (const def of walkDefinitions(file)) {
|
|
1378
|
+
for (const a of getAnnotations(def)) yield a;
|
|
1379
|
+
if (hasFields(def)) {
|
|
1380
|
+
for (const m of def.members) if ("annotations" in m) for (const a of m.annotations) yield a;
|
|
1381
|
+
}
|
|
1382
|
+
if (hasMembers(def)) for (const m of def.members) for (const a of m.annotations) yield a;
|
|
1383
|
+
if (hasParameters(def)) for (const p of def.parameters) for (const a of p.annotations) yield a;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
/** Safe accessor — returns an empty array if the definition carries none. */
|
|
1387
|
+
function getAnnotations(def) {
|
|
1388
|
+
return "annotations" in def ? def.annotations : [];
|
|
1389
|
+
}
|
|
1390
|
+
/** Yield every element (`ViewElement`) across all view-like definitions. */
|
|
1391
|
+
function* walkViewElements(file) {
|
|
1392
|
+
for (const def of walkDefinitions(file)) {
|
|
1393
|
+
if (!hasMembers(def)) continue;
|
|
1394
|
+
for (const m of def.members) {
|
|
1395
|
+
if (isAssociation(m)) continue;
|
|
1396
|
+
yield {
|
|
1397
|
+
owner: def,
|
|
1398
|
+
element: m
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
/** Yield every association declaration across view-like definitions. */
|
|
1404
|
+
function* walkAssociations(file) {
|
|
1405
|
+
for (const def of walkDefinitions(file)) {
|
|
1406
|
+
if (!hasMembers(def)) continue;
|
|
1407
|
+
for (const m of def.members) {
|
|
1408
|
+
if (!isAssociation(m)) continue;
|
|
1409
|
+
yield {
|
|
1410
|
+
owner: def,
|
|
1411
|
+
association: m
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/** Yield every field definition from tables and structures. */
|
|
1417
|
+
function* walkFields(file) {
|
|
1418
|
+
for (const def of walkDefinitions(file)) {
|
|
1419
|
+
if (!hasFields(def)) continue;
|
|
1420
|
+
for (const m of def.members) {
|
|
1421
|
+
if ("kind" in m && m.kind === "include") continue;
|
|
1422
|
+
yield {
|
|
1423
|
+
owner: def,
|
|
1424
|
+
field: m
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
/** Yield every parameter definition across view-like entities. */
|
|
1430
|
+
function* walkParameters(file) {
|
|
1431
|
+
for (const def of walkDefinitions(file)) {
|
|
1432
|
+
if (!hasParameters(def)) continue;
|
|
1433
|
+
for (const p of def.parameters) yield {
|
|
1434
|
+
owner: def,
|
|
1435
|
+
parameter: p
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
function isAssociation(m) {
|
|
1440
|
+
return "kind" in m && m.kind === "association";
|
|
1441
|
+
}
|
|
1442
|
+
/** Look up an annotation by its dotted key. Returns the first match. */
|
|
1443
|
+
function findAnnotation(annotations, key) {
|
|
1444
|
+
return annotations.find((a) => a.key === key);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
//#endregion
|
|
1448
|
+
//#region src/lib/validate/cardinality.ts
|
|
1449
|
+
function validateCardinality(file) {
|
|
1450
|
+
const diagnostics = [];
|
|
1451
|
+
for (const { association } of walkAssociations(file)) {
|
|
1452
|
+
const card = association.cardinality;
|
|
1453
|
+
if (!card) continue;
|
|
1454
|
+
const { min, max } = card;
|
|
1455
|
+
const name = association.alias ?? association.target;
|
|
1456
|
+
if (min !== void 0 && min < 0) diagnostics.push({
|
|
1457
|
+
code: "ACDS001",
|
|
1458
|
+
severity: "error",
|
|
1459
|
+
message: `Association '${name}': lower bound (${min}) must be >= 0.`,
|
|
1460
|
+
target: name
|
|
1461
|
+
});
|
|
1462
|
+
if (typeof max === "number" && max < 1) diagnostics.push({
|
|
1463
|
+
code: "ACDS002",
|
|
1464
|
+
severity: "error",
|
|
1465
|
+
message: `Association '${name}': upper bound (${max}) must be >= 1.`,
|
|
1466
|
+
target: name
|
|
1467
|
+
});
|
|
1468
|
+
if (min !== void 0 && typeof max === "number" && min > max) diagnostics.push({
|
|
1469
|
+
code: "ACDS003",
|
|
1470
|
+
severity: "error",
|
|
1471
|
+
message: `Association '${name}': lower bound (${min}) exceeds upper bound (${max}).`,
|
|
1472
|
+
target: name
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
return diagnostics;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
//#endregion
|
|
1479
|
+
//#region src/lib/validate/elements.ts
|
|
1480
|
+
function validateViewElements(file) {
|
|
1481
|
+
const diagnostics = [];
|
|
1482
|
+
for (const { element } of walkViewElements(file)) {
|
|
1483
|
+
const name = element.alias ?? element.expression;
|
|
1484
|
+
if (element.isVirtual && element.type) diagnostics.push({
|
|
1485
|
+
code: "ACDS010",
|
|
1486
|
+
severity: "error",
|
|
1487
|
+
message: `View element '${name}' cannot be virtual and typed at the same time.`,
|
|
1488
|
+
target: name
|
|
1489
|
+
});
|
|
1490
|
+
if (element.isKey && element.isVirtual) diagnostics.push({
|
|
1491
|
+
code: "ACDS011",
|
|
1492
|
+
severity: "error",
|
|
1493
|
+
message: `View element '${name}' cannot be both 'key' and 'virtual'.`,
|
|
1494
|
+
target: name
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
return diagnostics;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
//#endregion
|
|
1501
|
+
//#region src/lib/validate/index.ts
|
|
1502
|
+
/** Run all validators. */
|
|
1503
|
+
function validate(file) {
|
|
1504
|
+
return [...validateCardinality(file), ...validateViewElements(file)];
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
//#endregion
|
|
1508
|
+
//#region src/lib/grammar/ddl.ts
|
|
1509
|
+
const ddlCoverage = {
|
|
1510
|
+
topic: "ddl",
|
|
1511
|
+
constructs: [
|
|
1512
|
+
"define table",
|
|
1513
|
+
"define structure",
|
|
1514
|
+
"define type",
|
|
1515
|
+
"define view entity",
|
|
1516
|
+
"define abstract entity",
|
|
1517
|
+
"define custom entity",
|
|
1518
|
+
"define service",
|
|
1519
|
+
"annotate entity",
|
|
1520
|
+
"include directive",
|
|
1521
|
+
"builtin type (abap.char, abap.dec, ...)",
|
|
1522
|
+
"named type reference"
|
|
1523
|
+
]
|
|
1524
|
+
};
|
|
1525
|
+
|
|
1526
|
+
//#endregion
|
|
1527
|
+
//#region src/lib/grammar/dcl.ts
|
|
1528
|
+
const dclCoverage = {
|
|
1529
|
+
topic: "dcl",
|
|
1530
|
+
constructs: [
|
|
1531
|
+
"define role <name> { ... }",
|
|
1532
|
+
"grant select on <entity>",
|
|
1533
|
+
"grant select on <entity> where <expression>"
|
|
1534
|
+
]
|
|
1535
|
+
};
|
|
1536
|
+
|
|
1537
|
+
//#endregion
|
|
1538
|
+
//#region src/lib/grammar/annotations.ts
|
|
1539
|
+
const annotationsCoverage = {
|
|
1540
|
+
topic: "annotations",
|
|
1541
|
+
constructs: [
|
|
1542
|
+
"@Key : value",
|
|
1543
|
+
"@Key.path : value",
|
|
1544
|
+
"array value",
|
|
1545
|
+
"object value",
|
|
1546
|
+
"nested objects",
|
|
1547
|
+
"bare boolean (@Key)"
|
|
1548
|
+
]
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
//#endregion
|
|
1552
|
+
//#region src/lib/grammar/associations.ts
|
|
1553
|
+
const associationsCoverage = {
|
|
1554
|
+
topic: "associations",
|
|
1555
|
+
constructs: [
|
|
1556
|
+
"association to <target>",
|
|
1557
|
+
"association[cardinality] to <target>",
|
|
1558
|
+
"association[L..U] to <target>",
|
|
1559
|
+
"association of (many|one) to <target>",
|
|
1560
|
+
"composition to <target>",
|
|
1561
|
+
"association to redirected to <target>",
|
|
1562
|
+
"association ... as <alias>",
|
|
1563
|
+
"association ... on <expression>"
|
|
1564
|
+
]
|
|
1565
|
+
};
|
|
1566
|
+
|
|
1567
|
+
//#endregion
|
|
1568
|
+
//#region src/lib/grammar/parameters.ts
|
|
1569
|
+
const parametersCoverage = {
|
|
1570
|
+
topic: "parameters",
|
|
1571
|
+
constructs: [
|
|
1572
|
+
"with parameters <p>: <type>",
|
|
1573
|
+
"with parameters <p>: <type> default <literal>",
|
|
1574
|
+
"annotated parameters"
|
|
1575
|
+
]
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
//#endregion
|
|
1579
|
+
//#region src/lib/grammar/projections.ts
|
|
1580
|
+
const projectionsCoverage = {
|
|
1581
|
+
topic: "projections",
|
|
1582
|
+
constructs: [
|
|
1583
|
+
"view element (qualified name)",
|
|
1584
|
+
"view element with alias",
|
|
1585
|
+
"view element with key modifier",
|
|
1586
|
+
"view element with virtual modifier",
|
|
1587
|
+
"typed field (abstract/custom entity)",
|
|
1588
|
+
"view where clause",
|
|
1589
|
+
"projection view (as projection on <source>)"
|
|
1590
|
+
]
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
//#endregion
|
|
1594
|
+
//#region src/lib/grammar/actions.ts
|
|
1595
|
+
const actionsCoverage = {
|
|
1596
|
+
topic: "actions",
|
|
1597
|
+
constructs: []
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
//#endregion
|
|
1601
|
+
//#region src/lib/grammar/index.ts
|
|
1602
|
+
const GRAMMAR_COVERAGE = [
|
|
1603
|
+
ddlCoverage,
|
|
1604
|
+
dclCoverage,
|
|
1605
|
+
annotationsCoverage,
|
|
1606
|
+
associationsCoverage,
|
|
1607
|
+
parametersCoverage,
|
|
1608
|
+
projectionsCoverage,
|
|
1609
|
+
actionsCoverage
|
|
1610
|
+
];
|
|
1611
|
+
|
|
1612
|
+
//#endregion
|
|
1613
|
+
//#region src/index.ts
|
|
1614
|
+
/**
|
|
1615
|
+
* @abapify/acds — ABAP CDS Source Parser
|
|
1616
|
+
*
|
|
1617
|
+
* Parses ABAP CDS DDL source (.acds) files into a typed AST.
|
|
1618
|
+
*
|
|
1619
|
+
* @example
|
|
1620
|
+
* ```typescript
|
|
1621
|
+
* import { parse } from '@abapify/acds';
|
|
1622
|
+
*
|
|
1623
|
+
* const result = parse(`
|
|
1624
|
+
* @AbapCatalog.tableCategory : #TRANSPARENT
|
|
1625
|
+
* define table ztable {
|
|
1626
|
+
* key field1 : abap.char(10) not null;
|
|
1627
|
+
* field2 : some_data_element;
|
|
1628
|
+
* }
|
|
1629
|
+
* `);
|
|
1630
|
+
*
|
|
1631
|
+
* if (result.errors.length === 0) {
|
|
1632
|
+
* const table = result.ast.definitions[0]; // TableDefinition
|
|
1633
|
+
* }
|
|
1634
|
+
* ```
|
|
1635
|
+
*/
|
|
1636
|
+
/**
|
|
1637
|
+
* Parse an ABAP CDS source string into a typed AST.
|
|
1638
|
+
*
|
|
1639
|
+
* @param source - The CDS source code to parse
|
|
1640
|
+
* @returns Parse result with AST and any errors
|
|
1641
|
+
*/
|
|
1642
|
+
function parse(source) {
|
|
1643
|
+
const lexResult = CdsLexer.tokenize(source);
|
|
1644
|
+
const errors = lexResult.errors.map(fromLexError);
|
|
1645
|
+
cdsParser.input = lexResult.tokens;
|
|
1646
|
+
const cst = cdsParser.sourceFile();
|
|
1647
|
+
errors.push(...cdsParser.errors.map(fromParseError));
|
|
1648
|
+
let ast = { definitions: [] };
|
|
1649
|
+
if (cst) try {
|
|
1650
|
+
ast = cdsVisitor.visit(cst);
|
|
1651
|
+
} catch {}
|
|
1652
|
+
return {
|
|
1653
|
+
ast,
|
|
1654
|
+
errors
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
//#endregion
|
|
1659
|
+
export { GRAMMAR_COVERAGE, actionsCoverage, annotationsCoverage, associationsCoverage, dclCoverage, ddlCoverage, findAnnotation, hasFields, hasMembers, hasParameters, isAssociation, parametersCoverage, parse, projectionsCoverage, validate, validateCardinality, validateViewElements, walkAnnotations, walkAssociations, walkDefinitions, walkFields, walkParameters, walkViewElements };
|
|
1660
|
+
//# sourceMappingURL=index.mjs.map
|