@sap/cds-compiler 5.1.2 → 5.3.0

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/bin/cdsc.js +7 -2
  3. package/bin/cdshi.js +24 -17
  4. package/bin/cdsse.js +17 -18
  5. package/doc/CHANGELOG_BETA.md +9 -4
  6. package/lib/api/main.js +19 -2
  7. package/lib/api/options.js +4 -1
  8. package/lib/api/validate.js +5 -0
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/message-registry.js +40 -3
  11. package/lib/base/messages.js +1 -1
  12. package/lib/base/model.js +0 -11
  13. package/lib/checks/actionsFunctions.js +0 -12
  14. package/lib/checks/structuredAnnoExpressions.js +10 -14
  15. package/lib/compiler/assert-consistency.js +21 -13
  16. package/lib/compiler/builtins.js +2 -2
  17. package/lib/compiler/checks.js +25 -6
  18. package/lib/compiler/define.js +27 -31
  19. package/lib/compiler/extend.js +16 -18
  20. package/lib/compiler/generate.js +3 -3
  21. package/lib/compiler/populate.js +22 -16
  22. package/lib/compiler/propagator.js +3 -2
  23. package/lib/compiler/resolve.js +87 -94
  24. package/lib/compiler/shared.js +12 -13
  25. package/lib/compiler/tweak-assocs.js +390 -86
  26. package/lib/compiler/utils.js +41 -33
  27. package/lib/compiler/xpr-rewrite.js +45 -58
  28. package/lib/edm/annotations/genericTranslation.js +17 -13
  29. package/lib/edm/csn2edm.js +28 -4
  30. package/lib/edm/edm.js +68 -28
  31. package/lib/edm/edmInboundChecks.js +5 -8
  32. package/lib/edm/edmPreprocessor.js +66 -40
  33. package/lib/edm/edmUtils.js +1 -1
  34. package/lib/gen/BaseParser.js +778 -0
  35. package/lib/gen/CdlParser.js +4477 -0
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +1 -1
  38. package/lib/gen/languageParser.js +4072 -4024
  39. package/lib/inspect/inspectPropagation.js +1 -1
  40. package/lib/json/from-csn.js +5 -3
  41. package/lib/json/to-csn.js +7 -10
  42. package/lib/language/antlrParser.js +96 -0
  43. package/lib/language/errorStrategy.js +1 -1
  44. package/lib/language/genericAntlrParser.js +32 -4
  45. package/lib/language/multiLineStringParser.js +1 -1
  46. package/lib/main.d.ts +23 -0
  47. package/lib/model/cloneCsn.js +22 -13
  48. package/lib/model/csnUtils.js +2 -0
  49. package/lib/model/revealInternalProperties.js +2 -0
  50. package/lib/modelCompare/utils/filter.js +70 -42
  51. package/lib/optionProcessor.js +16 -10
  52. package/lib/parsers/AstBuildingParser.js +1290 -0
  53. package/lib/parsers/CdlGrammar.g4 +2013 -0
  54. package/lib/parsers/Lexer.js +249 -0
  55. package/lib/render/toCdl.js +46 -45
  56. package/lib/render/toSql.js +5 -5
  57. package/lib/transform/addTenantFields.js +4 -4
  58. package/lib/transform/db/applyTransformations.js +54 -16
  59. package/lib/transform/draft/odata.js +10 -11
  60. package/lib/transform/effective/flattening.js +10 -14
  61. package/lib/transform/forRelationalDB.js +7 -6
  62. package/lib/transform/odata/flattening.js +42 -31
  63. package/lib/transform/odata/toFinalBaseType.js +7 -6
  64. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  65. package/lib/utils/moduleResolve.js +1 -1
  66. package/package.json +2 -2
  67. package/share/messages/redirected-to-ambiguous.md +5 -4
  68. package/share/messages/redirected-to-complex.md +6 -3
@@ -0,0 +1,2013 @@
1
+ // Grammar for CDS
2
+
3
+ parser grammar Cdl;
4
+ options {
5
+ language = JavaScript;
6
+ superClass = AstBuildingParser;
7
+ }
8
+ @header{
9
+ const { XsnSource, XsnArtifact, XsnName } = require( '../compiler/xsn-model' );
10
+ const AstBuildingParser = require('../parsers/AstBuildingParser');
11
+ }
12
+
13
+ // Content:
14
+ // - top-level: USING, NAMESPACE, artifactDefOrExtend (start rule: start)
15
+ // - main definitions and annotation def
16
+ // - member definitions
17
+ // - EXTEND and ANNOTATE
18
+ // - type expressions
19
+ // - queries: the main query hierarchy (start rule: queryEOF)
20
+ // - queries: columns and other clauses
21
+ // - conditions and expressions (start rule: conditionEOF)
22
+ // - paths and functions
23
+ // - annotation assignments
24
+
25
+ tokens{ // reserved words
26
+ ALL, ANY, AS,
27
+ BY,
28
+ CASE, CAST,
29
+ EXISTS,
30
+ FALSE, FROM,
31
+ IN,
32
+ KEY,
33
+ NOT, NULL,
34
+ OF, ON,
35
+ SELECT, SOME,
36
+ TRUE,
37
+ WHERE, WITH,
38
+ }
39
+
40
+ // Top-level: USING, NAMESPACE, artifactDefOrExtend (start rule: start) ---------
41
+
42
+ start returns[ source = new XsnSource( 'cdl' ) ]
43
+ :
44
+ (
45
+ ( <cond=namespaceRestriction> namespaceDeclaration[ $source ]
46
+ | usingDeclaration[ $source ]
47
+ | artifactDefOrExtend[ $source ] <setCondition=namespaceRestriction>
48
+ )
49
+ ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
50
+ )*
51
+ EOF { this.docComment( null ); }
52
+ ;
53
+
54
+ artifactsBlock[ art, start = undefined ]
55
+ :
56
+ '{'<setCondition=vocabularyRestriction>
57
+ { $art.artifacts = this.createDict( $start ); $art.extensions = []; }
58
+ (
59
+ artifactDefOrExtend[ $art ]
60
+ ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
61
+ )*
62
+ '}'<setCondition=afterBrace>
63
+ { this.finalizeDictOrArray( $art.artifacts ); }
64
+ ;
65
+
66
+ artifactDefOrExtend[ outer ] locals[ art = new XsnArtifact() ]
67
+ :
68
+ { $art.location = this.startLocation(); }
69
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
70
+ (
71
+ DEFINE?
72
+ ( serviceDef[ $art, $outer ]
73
+ | contextDef[ $art, $outer ]
74
+ | <cond=vocabularyRestriction>annotationDef[ $art, $outer ]
75
+ | typeDef[ $art, $outer ]
76
+ | aspectDef[ $art, $outer ]
77
+ | entityDef[ $art, $outer ]
78
+ | <hide> viewDef[ $art, $outer ]
79
+ | eventDef[ $art, $outer ]
80
+ | actionMainDef[ $art, $outer ]
81
+ | functionMainDef[ $art, $outer ]
82
+ )
83
+ |
84
+ <cond=extensionRestriction> EXTEND { $art.kind = 'extend'; }
85
+ ( extendArtifact[ $art, $outer ]
86
+ | extendService[ $art, $outer ]
87
+ // Non-streamlined Syntax; we would neither add new clauses to them, nor
88
+ // add more of them (for further `kind`s):
89
+ | <hide> extendContext[ $art, $outer ]
90
+ | <hide> extendType[ $art, $outer ]
91
+ | <hide> extendEntityOrAspect[ $art, $outer ]
92
+ | <hide> extendProjection[ $art, $outer ]
93
+ )
94
+ |
95
+ <cond=extensionRestriction> ANNOTATE annotateArtifact[ $art, $outer ]
96
+ )
97
+ ;
98
+
99
+ namespaceDeclaration[ source ]
100
+ @finally{ this.attachLocation( $source.namespace ); }
101
+ :
102
+ NAMESPACE name=namePath[ 'Namespace' ]
103
+ { $source.namespace ??= { kind: 'namespace', name: $name }; }
104
+ // TODO: XsnArtifact ?
105
+ ;
106
+
107
+ usingDeclaration[ source ] locals[ decl = { kind: 'using' } ] // TODO: XsnArtifact ?
108
+ @finally{ this.attachLocation( $decl ); }
109
+ :
110
+ USING
111
+ (
112
+ FROM String
113
+ { $source.dependencies.push( this.quotedLiteral() ); }
114
+ |
115
+ usingProxy[ $source, $decl ]
116
+ ( FROM String
117
+ { $source.dependencies.push( $decl.fileDep = this.quotedLiteral() ); }
118
+ )?
119
+ |
120
+ { $source.usings.push( $decl ); }
121
+ // We could just create "independent" USING declaration, but if we want
122
+ // to have some check in the future whether the external artifacts are
123
+ // really in the FROM source...
124
+ '{' { $decl.usings = this.createArray(); }
125
+ ( usingProxy[ $decl, { kind: 'using' } ]
126
+ ( ',' | <exitLoop> )
127
+ )+
128
+ '}'<setCondition=afterBrace>
129
+ { this.finalizeDictOrArray( $decl.usings ); }
130
+ ( FROM String
131
+ { $source.dependencies.push( $decl.fileDep = this.quotedLiteral() ); }
132
+ )?
133
+ )
134
+ ;
135
+
136
+ usingProxy[ outer, proxy ]
137
+ @finally{ this.attachLocation( $proxy ); }
138
+ :
139
+ extern=simplePath[ 'global' ]
140
+ { $proxy.extern = $extern; $outer.usings.push( $proxy ); }
141
+ ( AS Id['UsingAlias'] { $proxy.name = this.identAst(); } // TODO: XsnName ?
142
+ | { this.classifyImplicitName( 'Using' ); }
143
+ )
144
+ ;
145
+
146
+ namePath[ category ] returns[ default name = new XsnName() ]
147
+ @finally{ this.attachLocation( $name ); }
148
+ :
149
+ Id[ $category ] { $name.path = [ this.identAst() ]; }
150
+ (
151
+ '.' Id_all[ $category ] { $name.path.push( this.identAst() ); }
152
+ )*
153
+ ;
154
+
155
+ simplePath[ category = 'artref' ] returns[ default ref = {} ]
156
+ @finally{ this.attachLocation( $ref ); }
157
+ :
158
+ Id[ $category ]
159
+ { $ref.path = [ this.identAst() ]; }
160
+ (
161
+ '.' Id_all[ $category ] { $ref.path.push( this.identAst() ); }
162
+ )*
163
+ ;
164
+
165
+ // Annotation def and main definitions ------------------------------------------
166
+
167
+ serviceDef[ art, outer ]
168
+ @finally{ this.attachLocation( $art ); }
169
+ :
170
+ SERVICE name=namePath[ 'Service' ]
171
+ { this.addDef( $art, $outer, 'artifacts', 'service', $name ); }
172
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
173
+ artifactsBlock[ $art ]?
174
+ ;
175
+
176
+ contextDef[ art, outer ]
177
+ @finally{ this.attachLocation( $art ); }
178
+ :
179
+ CONTEXT name=namePath[ 'Context' ]
180
+ { this.addDef( $art, $outer, 'artifacts', 'context', $name ); }
181
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
182
+ artifactsBlock[ $art ]?
183
+ ;
184
+
185
+ annotationDef[ art, outer ]
186
+ @finally{ this.attachLocation( $art ); }
187
+ :
188
+ ANNOTATION name=namePath[ 'AnnoDef' ]
189
+ { this.addDef( $art, $outer, 'vocabularies', 'annotation', $name ); }
190
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
191
+ typeOrIncludesSpec[ $art ]
192
+ ;
193
+
194
+ typeDef[ art, outer ]
195
+ @finally{ this.attachLocation( $art ); }
196
+ :
197
+ TYPE name=namePath[ 'Type' ]
198
+ { this.addDef( $art, $outer, 'artifacts', 'type', $name ); }
199
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
200
+ typeOrIncludesSpec[ $art ] // TODO: optional
201
+ ;
202
+
203
+ aspectDef[ art, outer ]
204
+ @finally{ this.attachLocation( $art ); }
205
+ :
206
+ ( ASPECT
207
+ | <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.lb().location ); }
208
+ ENTITY
209
+ )
210
+ name=namePath[ 'Type' ] // TODO: Type?
211
+ { this.addDef( $art, $outer, 'artifacts', 'aspect', $name ); }
212
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
213
+ (
214
+ elementsBlock[ $art ]
215
+ actionsBlock[ $art ]?
216
+ |
217
+ ':' aspectColonSpec[ $art ]
218
+ )?
219
+ ;
220
+
221
+ aspectColonSpec[ art ]
222
+ options{ minTokensMatched = 1 }
223
+ :
224
+ (
225
+ incl=simplePath { $art.includes ??= []; $art.includes.push( $incl ); }
226
+ ( ',' | <exitLoop> )
227
+ )*
228
+ (
229
+ elementsBlock[ $art ]
230
+ actionsBlock[ $art ]?
231
+ )? // TODO: no rule end after ','
232
+ ;
233
+
234
+ entityDef[ art, outer ]
235
+ @finally{ this.attachLocation( $art ); }
236
+ :
237
+ ENTITY name=namePath[ 'Entity' ]
238
+ { this.addDef( $art, $outer, 'artifacts', 'entity', $name ); }
239
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
240
+ paramsList[ $art ]?
241
+ (
242
+ ( ':' { $art.includes ??= []; }
243
+ (
244
+ incl=simplePath { $art.includes.push( $incl ); }
245
+ ( ',' | <exitLoop> )
246
+ )+
247
+ )?
248
+ elementsBlock[ $art ]
249
+ |
250
+ AS
251
+ (
252
+ query=queryExpression
253
+ { $art.query = $query; $art.$syntax = 'entity'; }
254
+ |
255
+ query=projectionSpec
256
+ { $art.query = $query; $art.$syntax = 'projection'; }
257
+ whereGroupByHaving[ $query ]?
258
+ orderByLimitOffset[ $query ]?
259
+ )
260
+ )
261
+ actionsBlock[ $art ]?
262
+ ;
263
+
264
+ viewDef[ art, outer ]
265
+ @finally{ this.attachLocation( $art ); }
266
+ :
267
+ VIEW name=namePath[ 'Entity' ]
268
+ { this.addDef( $art, $outer, 'artifacts', 'entity', $name ); }
269
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
270
+ (
271
+ paramsList[ $art ]
272
+ |
273
+ <hide> WITH PARAMETERS { $art.params = this.createDict(); }
274
+ paramDef[ $art ]
275
+ ( ',' paramDef[ $art ] )* // no optional final ',' here
276
+ { this.finalizeDictOrArray( $art.params ); }
277
+ )?
278
+ AS query=queryExpression
279
+ { $art.query = $query; $art.$syntax = 'view'; }
280
+ ;
281
+
282
+ eventDef[ art, outer ]
283
+ @finally{ this.attachLocation( $art ); }
284
+ :
285
+ EVENT
286
+ name=namePath[ 'Event' ]
287
+ { this.addDef( $art, $outer, 'artifacts', 'event', $name ); }
288
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
289
+ (
290
+ elementsBlock[ $art ]
291
+ |
292
+ ':'
293
+ (
294
+ elementsBlock[ $art ]
295
+ |
296
+ incl=simplePath { $art.type = $incl; }
297
+ (
298
+ { $art.includes = [ $art.type ]; delete $art.type; }
299
+ ( ','
300
+ ( incl=simplePath { $art.includes.push( $incl ); }
301
+ ( ',' | <exitLoop> )
302
+ )*
303
+ )?
304
+ elementsBlock[ $art ]
305
+ |
306
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
307
+ )
308
+ |
309
+ query=projectionSpec { $art.query = $query; $art.$syntax = 'projection'; }
310
+ )
311
+ )
312
+ ;
313
+
314
+ actionMainDef[ art, outer ]
315
+ @finally{ this.attachLocation( $art ); }
316
+ :
317
+ ACTION name=namePath[ 'Action' ]
318
+ { this.addDef( $art, $outer, 'artifacts', 'action', $name ); }
319
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
320
+ paramsList[ $art ]
321
+ returnsSpec[ $art ]?
322
+ ;
323
+
324
+ functionMainDef[ art, outer ]
325
+ @finally{ this.attachLocation( $art ); }
326
+ :
327
+ FUNCTION name=namePath[ 'Action' ]
328
+ { this.addDef( $art, $outer, 'artifacts', 'function', $name ); }
329
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
330
+ paramsList[ $art ]
331
+ returnsSpec[ $art ]
332
+ ;
333
+
334
+ // Member definitions: actions, parameters, elements, enums: --------------------
335
+
336
+ actionsBlock[ art ]
337
+ :
338
+ ACTIONS { $art.actions = this.createDict(); } '{'
339
+ (
340
+ boundActionFunctionDef[ $art ]
341
+ ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
342
+ )*
343
+ '}'<setCondition=afterBrace>
344
+ { this.finalizeDictOrArray( $art.actions ); }
345
+ ;
346
+
347
+ boundActionFunctionDef[ outer ] locals[ art = new XsnArtifact() ]
348
+ @finally{ this.attachLocation( $art ); }
349
+ :
350
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
351
+ (
352
+ ACTION Id['BoundAction']
353
+ { this.addDef( $art, $outer, 'actions', 'action', this.identAst() ); }
354
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
355
+ paramsList[ $art ]
356
+ returnsSpec[ $art ]?
357
+ |
358
+ FUNCTION Id['BoundAction']
359
+ { this.addDef( $art, $outer, 'actions', 'function', this.identAst() ); }
360
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
361
+ paramsList[ $art ]
362
+ returnsSpec[ $art ]
363
+ )
364
+ ;
365
+
366
+ paramsList[ art ]
367
+ :
368
+ '(' { $art.params = this.createDict(); }
369
+ (
370
+ paramDef[ $art ]
371
+ ( ',' | <exitLoop> )
372
+ )*
373
+ ')' { this.finalizeDictOrArray( $art.params ); }
374
+ ;
375
+
376
+ paramDef[ outer ] locals[ art = new XsnArtifact() ]
377
+ @finally{ this.attachLocation( $art ); }
378
+ :
379
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
380
+ Id_all['Param']
381
+ { this.addDef( $art, $outer, 'params', 'param', this.identAst() ); }
382
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
383
+ (
384
+ elementsBlock[ $art ]
385
+ nullability[ $art ]?
386
+ |
387
+ ':'
388
+ typeExpression[ $art ] // was elementType, with NOT? NULL / DEFAULT
389
+ )
390
+ ;
391
+
392
+ returnsSpec[ outer ] locals[ art = new XsnArtifact() ]
393
+ @finally{ this.attachLocation( $art ); }
394
+ :
395
+ RETURNS<setCondition=elementRestriction>
396
+ { $art.kind = 'param'; $outer.returns = $art; }
397
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
398
+ typeExpression[ $art ]
399
+ ;
400
+
401
+ elementsBlock[ art ]
402
+ :
403
+ '{' { $art.elements = this.createDict(); }
404
+ ( elementDef[ $art ]
405
+ ( ';'
406
+ | <exitLoop>
407
+ | <repeatLoop=afterBrace, restrict=Id> { this.noAssignmentInSameLine(); }
408
+ )
409
+ )*
410
+ '}'<setCondition=afterBrace>
411
+ { this.finalizeDictOrArray( $art.elements ); }
412
+ ;
413
+
414
+ elementDef[ outer, art = undefined ]
415
+ @finally{ this.attachLocation( $art ); }
416
+ :
417
+ { $art ??= new XsnArtifact(); }
418
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
419
+ ( VIRTUAL { $art.virtual = this.valueWithLocation( true ); } )?
420
+ ( KEY { $art.key = this.valueWithLocation( true ); } )?
421
+ ( MASKED { $art.masked = this.valueWithLocation( true ); }
422
+ { this.message( 'syntax-unsupported-masked', this.lb(), { keyword: 'masked' } ); } )?
423
+ ( ELEMENT { $art.$syntax = 'element'; } )?
424
+ Id['Element']
425
+ { this.addDef( $art, $outer, 'elements', 'element', this.identAst() ); }
426
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
427
+ (
428
+ elementsBlock[ $art ]
429
+ nullability[ $art ]?
430
+ |
431
+ ':'<setCondition=elementRestriction>
432
+ typeExpression[ $art ] // was elementType, with NOT? NULL / DEFAULT
433
+ |
434
+ // <setCondition=elementRestriction> // TODO TOOL: allow this
435
+ { this.elementRestriction(); }<always> // workaround
436
+ )
437
+ (
438
+ <cond=calcOrDefaultRestriction> '='
439
+ // TODO TOOL: add to "expected set" if failing here? Or have some "do not
440
+ // consider for rule exit if condition failure on `=`"?
441
+ expr=expression { $art.value = $expr; }
442
+ ( STORED { $art.value.stored = this.valueWithLocation( true ); } )?
443
+ // TODO: why have `stored` as property of the value?
444
+ { this.docComment( $art ); } // TODO: also restricted
445
+ ( <cond=elementRestriction> annoAssignStd[ $art ] )*
446
+ )?
447
+ ;
448
+
449
+ enumSymbolsBlock[ art ]
450
+ :
451
+ ENUM { $art.enum = this.createDict(); } '{'
452
+ ( enumSymbolDef[ $art ]
453
+ ( ';' | <exitLoop> )
454
+ )*
455
+ '}'<setCondition=afterBrace>
456
+ { this.finalizeDictOrArray( $art.enum ); }
457
+ ;
458
+
459
+ enumSymbolDef[ outer ] locals[ art = new XsnArtifact() ]
460
+ @finally{ this.attachLocation( $art ); }
461
+ :
462
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
463
+ Id['Enum']
464
+ { this.addDef( $art, $outer, 'enum', 'enum', this.identAst() ); }
465
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
466
+ ( '='
467
+ (
468
+ <priority> String
469
+ { $art.value = this.quotedLiteral(); }
470
+ |
471
+ <priority> Number
472
+ { $art.value = this.numberLiteral(); }
473
+ |
474
+ sign='+'/'-' Number
475
+ { $art.value = this.numberLiteral( $sign ); }
476
+ |
477
+ <hide> value=literalValue
478
+ { $art.value = $value; }
479
+ )
480
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
481
+ )?
482
+ ;
483
+
484
+ foreignKeysBlock[ art ]
485
+ :
486
+ '{' { $art.foreignKeys = this.createDict(); }
487
+ ( foreignKeyDef[ $art ]
488
+ ( ',' | <exitLoop> )
489
+ )*
490
+ '}' // DOES NOT SET afterBrace, because we allow annos after { …fks… }
491
+ { this.finalizeDictOrArray( $art.foreignKeys ); }
492
+ ;
493
+
494
+ foreignKeyDef[ outer ] locals[ art = new XsnArtifact(), name ]
495
+ @finally{ this.attachLocation($art); }
496
+ :
497
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
498
+ ref=simplePath[ 'ref' ] { $art.targetElement = $ref; }
499
+ ( AS name=Id['Key'] { $name = this.identAst(); }
500
+ | { this.classifyImplicitName( 'KeyImplicit', $ref ); $name = $ref.path; }
501
+ )
502
+ { this.addDef( $art, $outer, 'foreignKeys', 'key', $name ); }
503
+ // TODO: for a more uniform syntax, we'd allow:
504
+ // { this.docComment( $art ); } annoAssignMid[ $art ]*
505
+ ;
506
+
507
+ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
508
+ @finally{ this.attachLocation($art); }
509
+ :
510
+ Id['Mixin']
511
+ { this.addDef( $art, $outer, 'mixin', 'mixin', this.identAst() ); }
512
+ ':'
513
+ ( assoc=ASSOCIATION cardinality[ $art ]? TO
514
+ | assoc=COMPOSITION cardinality[ $art ]? OF
515
+ )
516
+ card=ONE/MANY? target=simplePath
517
+ { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
518
+ ON expr=condition { $art.on = $expr; }
519
+ ;
520
+
521
+ // Annotate and Extend: main definitions ----------------------------------------
522
+
523
+ annotateArtifact[ art, outer ]
524
+ @finally{ this.attachLocation( $art ); }
525
+ :
526
+ name=namePath[ 'Ext' ]
527
+ ( // direct element annotation:
528
+ ':' elemName=namePath[ 'ExtElement']
529
+ { this.addExtension( $art, $outer, 'annotate', $name, $elemName.path ); }
530
+ WITH?
531
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
532
+ annotateElementsBlock[ $art ]?
533
+ | // definition annotation
534
+ WITH?
535
+ // <cond=noRuleExitAfterWith>), or as rule option,
536
+ // this.noSemicolonHere() had the issues: DocComment, before `}`/EOF
537
+ { this.addExtension( $art, $outer, 'annotate', $name ); }
538
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
539
+ annotateParamsBlock[ $art ]?
540
+ (
541
+ annotateReturns[ $art ]
542
+ |
543
+ annotateElementsBlock[ $art ]?
544
+ annotateActionsBlock[ $art ]?
545
+ )
546
+ )
547
+ ;
548
+
549
+ extendArtifact[ art, outer ]
550
+ @finally{ this.attachLocation( $art ); }
551
+ :
552
+ name=namePath[ 'Ext' ]
553
+ ( // direct element annotation:
554
+ ':' elemName=namePath[ 'ExtElement']
555
+ { this.addExtension( $art, $outer, 'extend', $name, $elemName.path ); }
556
+ WITH?
557
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
558
+ (
559
+ elements=ELEMENTS? extendElementsBlock[ $art, $elements ]
560
+ |
561
+ enumSymbolsBlock[ $art ] // ENUM …, just define, no extend
562
+ |
563
+ typeNamedArgsList[ $art ]
564
+ )?
565
+ |
566
+ { this.addExtension( $art, $outer, 'extend', $name ); }
567
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
568
+ ( extendElementsBlock[ $art ]
569
+ actionsBlock[ $art ]?
570
+ )?
571
+ |
572
+ WITH
573
+ { this.addExtension( $art, $outer, 'extend', $name ); }
574
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
575
+ (
576
+ incl=simplePath { $art.includes = [ $incl ]; }
577
+ ( ',' incl=simplePath { $art.includes.push( $incl ); } )*
578
+ extendElementsBlock[ $art ]?
579
+ actionsBlock[ $art ]?
580
+ |
581
+ elements=ELEMENTS? extendElementsBlock[ $art, $elements ]
582
+ actionsBlock[ $art ]?
583
+ |
584
+ actionsBlock[ $art ]
585
+ |
586
+ enumSymbolsBlock[ $art ] // ENUM …, just define, no extend
587
+ |
588
+ typeNamedArgsList[ $art ]
589
+ |
590
+ COLUMNS selectItemsList[ $art, this.lb() ]
591
+ |
592
+ DEFINITIONS artifactsBlock[ $art, this.lb() ]
593
+ )?
594
+ )
595
+ ;
596
+
597
+ extendService[ art, outer ]
598
+ @finally{ this.attachLocation( $art ); }
599
+ :
600
+ SERVICE { $art.expectedKind = this.valueWithLocation(); }
601
+ name=namePath[ 'ExtService' ]
602
+ { $art.name = $name; $outer.extensions.push( $art ); }
603
+ WITH?
604
+ // <cond=noRuleExitAfterWith>), or as rule option,
605
+ // this.noSemicolonHere() had the issues: DocComment, before `}`/EOF
606
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
607
+ artifactsBlock[ $art ]?
608
+ ;
609
+
610
+ extendContext[ art, outer ]
611
+ @finally{ this.attachLocation( $art ); }
612
+ :
613
+ CONTEXT { $art.expectedKind = this.valueWithLocation(); }
614
+ name=namePath[ 'ExtContext' ]
615
+ { $art.name = $name; $outer.extensions.push( $art ); }
616
+ WITH?
617
+ // <cond=noRuleExitAfterWith>), or as rule option,
618
+ // this.noSemicolonHere() had the issues: DocComment, before `}`/EOF
619
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
620
+ artifactsBlock[ $art ]?
621
+ ;
622
+
623
+ extendType[ art, outer ]
624
+ @finally{ this.attachLocation( $art ); }
625
+ :
626
+ TYPE { $art.expectedKind = this.valueWithLocation(); }
627
+ name=namePath[ 'Ext' ]
628
+ { $art.name = $name; $outer.extensions.push( $art ); }
629
+ (
630
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
631
+ extendElementsBlock[ $art ]?
632
+ |
633
+ WITH
634
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
635
+ (
636
+ incl=simplePath { $art.includes = [ $incl ]; }
637
+ ( ',' incl=simplePath { $art.includes.push( $incl ); } )*
638
+ extendElementsBlock[ $art ]?
639
+ |
640
+ elements=ELEMENTS? extendElementsBlock[ $art, $elements ]
641
+ |
642
+ enumSymbolsBlock[ $art ]
643
+ |
644
+ typeNamedArgsList[ $art ]
645
+ )?
646
+ )
647
+ ;
648
+
649
+ extendEntityOrAspect[ art, outer ]
650
+ @finally{ this.attachLocation( $art ); }
651
+ :
652
+ ASPECT/ENTITY { $art.expectedKind = this.valueWithLocation(); }
653
+ name=namePath[ 'Ext' ]
654
+ { $art.name = $name; $outer.extensions.push( $art ); }
655
+ (
656
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
657
+ |
658
+ WITH
659
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
660
+ (
661
+ incl=simplePath { $art.includes = [ $incl ]; }
662
+ ( ',' incl=simplePath { $art.includes.push( $incl ); } )*
663
+ )?
664
+ )
665
+ extendElementsBlock[ $art ]?
666
+ actionsBlock[ $art ]?
667
+ ;
668
+
669
+ extendProjection[ art, outer ]
670
+ @finally{ this.attachLocation( $art ); }
671
+ :
672
+ PROJECTION { $art.expectedKind = this.valueWithLocation(); }
673
+ name=namePath[ 'Ext' ]
674
+ { $art.name = $name; $outer.extensions.push( $art ); }
675
+ WITH ?
676
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
677
+ selectItemsList[ $art ]?
678
+ actionsBlock[ $art ]?
679
+ ;
680
+
681
+ // Extend and annotate on members: bound actions, parameters, elements ----------
682
+
683
+ annotateActionsBlock[ art ]
684
+ :
685
+ ACTIONS { $art.actions = this.createDict(); } '{'
686
+ ( annotateBoundAction[ $art ]
687
+ ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
688
+ )*
689
+ '}'<setCondition=afterBrace>
690
+ { this.finalizeExtensionsDict( $art.actions ); }
691
+ ;
692
+
693
+ annotateBoundAction[ outer ] locals[ art = new XsnArtifact() ]
694
+ @finally{ this.attachLocation( $art ); }
695
+ :
696
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
697
+ Id['ExtBoundAction']
698
+ { this.addDef( $art, $outer, 'actions', 'annotate', this.identAst() ); }
699
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
700
+ annotateParamsBlock[ $art ]?
701
+ annotateReturns[ $art ]?
702
+ ;
703
+
704
+ annotateParamsBlock[ art ]
705
+ :
706
+ '(' { $art.params = this.createDict(); }
707
+ ( annotateParam[ $art ]
708
+ ( ',' | <exitLoop> )
709
+ )*
710
+ ')'
711
+ { this.finalizeExtensionsDict( $art.params ); }
712
+ ;
713
+
714
+ annotateParam[ outer ] locals[ art = new XsnArtifact() ]
715
+ @finally{ this.attachLocation( $art ); }
716
+ :
717
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
718
+ Id['ExtParam']
719
+ { this.addDef( $art, $outer, 'params', 'annotate', this.identAst() ); }
720
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
721
+ // annotateElementsBlock[ $art ]? // TODO: why not
722
+ ;
723
+
724
+ annotateReturns[ outer ] locals[ art = new XsnArtifact() ]
725
+ @finally{ this.attachLocation( $art ); }
726
+ :
727
+ RETURNS { $outer.returns = $art; $art.kind = 'annotate'; } // TODO: tokenIndex necessary?
728
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
729
+ annotateElementsBlock[ $art ]?
730
+ ;
731
+
732
+ annotateElementsBlock[ art ]
733
+ :
734
+ '{' { $art.elements = this.createDict(); }
735
+ ( annotateElement[ $art ]
736
+ ( ';'
737
+ | <exitLoop>
738
+ | <repeatLoop=afterBrace, restrict=Id> { this.noAssignmentInSameLine(); }
739
+ )
740
+ )*
741
+ '}'<setCondition=afterBrace>
742
+ { this.finalizeExtensionsDict( $art.elements ); }
743
+ ;
744
+
745
+ annotateElement[ outer ] locals[ art = new XsnArtifact() ]
746
+ @finally{ this.attachLocation( $art ); }
747
+ :
748
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
749
+ Id['ExtElement']
750
+ { this.addDef( $art, $outer, 'elements', 'annotate', this.identAst() ); }
751
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
752
+ annotateElementsBlock[ $art ]?
753
+ ;
754
+
755
+ extendElementsBlock[ art, start = undefined ]
756
+ :
757
+ '{' { $art.elements = this.createDict( $start ); }
758
+ ( elementDefOrExtend[ $art ]
759
+ ( ';'
760
+ | <exitLoop>
761
+ | <repeatLoop=afterBrace, restrict=Id> { this.noAssignmentInSameLine(); } )
762
+ )*
763
+ '}'<setCondition=afterBrace>
764
+ { this.finalizeExtensionsDict( $art.elements ); }
765
+ ;
766
+
767
+ elementDefOrExtend[ outer ] locals[ art = new XsnArtifact() ]
768
+ :
769
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
770
+ (
771
+ elementDef[ $outer, $art ]
772
+ |
773
+ EXTEND
774
+ ( ELEMENT { $art.expectedKind = this.valueWithLocation(); } )?
775
+ Id['ExtElement']
776
+ { this.addDef( $art, $outer, 'elements', 'extend', this.identAst() ); }
777
+ (
778
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
779
+ extendElementsBlock[ $art ]?
780
+ |
781
+ WITH
782
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
783
+ (
784
+ elements=ELEMENTS? extendElementsBlock[ art, $elements ]
785
+ |
786
+ enumSymbolsBlock[ $art ] // ENUM …, just define, no extend
787
+ |
788
+ typeNamedArgsList[ $art ]
789
+ )?
790
+ )
791
+ )
792
+ ;
793
+
794
+ // Type expressions -------------------------------------------------------------
795
+
796
+ // For `type` and `annotation` definitions:
797
+ typeOrIncludesSpec[ art ]
798
+ :
799
+ elementsBlock[ $art ]
800
+ nullability[ $art ]?
801
+ |
802
+ ':'
803
+ (
804
+ typeExpression[ $art ]
805
+ |
806
+ <priority>
807
+ ref=simplePath { $art.type = $ref; }
808
+ (
809
+ // <default> does not work here
810
+ typeRefOptArgs[ $art ]<atAltStart>
811
+ ( typeExpression[ $art ]<atAltStart>
812
+ | { this.docComment( $art ); }
813
+ )
814
+ |
815
+ typeExpression[ $art ]<atAltStart>
816
+ |
817
+ { this.docComment( $art ); }
818
+ |
819
+ { $art.includes = [ $art.type ]; delete $art.type; }
820
+ (
821
+ ','
822
+ (
823
+ ref=simplePath { $art.includes.push( $ref ); }
824
+ ( ',' | <exitLoop> )
825
+ )*
826
+ )?
827
+ elementsBlock[ $art ]
828
+ nullability[ $art ]?
829
+ )
830
+ )
831
+ ;
832
+
833
+ // Type expression (after the `:`) and “final” annotation assignment. Type
834
+ // expressions include `null`/`not null` and `default`, the latter is forbidden in
835
+ // the `returns` type.
836
+ //
837
+ // If used in a definition with additional clauses (currently just `= expr` for
838
+ // elements), these clauses must be guarded with <cond=…>.
839
+ //
840
+ // No annotation assignments are allowed after element and enum blocks, because
841
+ // these would conflict with the optional `;` after those blocks. To lower the
842
+ // impact with enums, the “final” annotation assignments are actually moved before
843
+ // the keyword `enum` (only allowed if not after `many`/`array of`).
844
+ //
845
+ // This rule is not used when the type expression is restricted: CDL-style cast in
846
+ // `select` items, `cast` function, `mixin` definition.
847
+
848
+ typeExpression[ art ]
849
+ // TODO: really introduce <exitRule>
850
+ :
851
+ elementsBlock[ $art ]
852
+ nullability[ $art ]?
853
+ |
854
+ ( typeRefOptArgs[ $art ] | typeTypeOf[ $art ] )
855
+ (<altRuleStart>)
856
+ nullability[ $art ]?
857
+ // <setCondition=calcOrDefaultRestriction> // TODO TOOL: allow this
858
+ { this.calcOrDefaultRestriction(); }<always> // workaround
859
+ (
860
+ nullabilityAndDefault[ $art ]
861
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
862
+ |
863
+ enumSymbolsBlock[ $art ]
864
+ nullabilityAndDefault[ $art ]?
865
+ |
866
+ { this.docComment( $art ); } annoAssignStd[ $art ]+
867
+ ( enumSymbolsBlock[ $art ]
868
+ // <setCondition=calcOrDefaultRestriction> // TODO TOOL: allow this
869
+ { this.calcOrDefaultRestriction(); }<always> // workaround
870
+ nullabilityAndDefault[ $art ]?
871
+ )?
872
+ |
873
+ { this.docComment( $art ); }
874
+ )
875
+ |
876
+ LOCALIZED
877
+ { $art.localized = this.valueWithLocation( true ); }
878
+ typeRefOptArgs[ $art ] // TODO: why no TYPE OF ?
879
+ // <setCondition=calcOrDefaultRestriction> // TODO TOOL: allow this
880
+ { this.calcOrDefaultRestriction(); }<always> // workaround
881
+ nullabilityAndDefault[ $art ]?
882
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
883
+ |
884
+ assoc=ASSOCIATION cardinality[ $art ]? TO card=ONE/MANY?
885
+ target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
886
+ // final anno assignments allowed also with fkeys (also in ANTLR-based parser)
887
+ // <prepare=calcOrDefaultRestriction,arg=true> // TODO TOOL: allow this
888
+ { this.calcOrDefaultRestriction(false,true); }<always> // workaround
889
+
890
+ ( ON cond=condition { $art.on = $cond; }
891
+ | foreignKeysBlock[ $art ]?
892
+ nullabilityAndDefault[ $art ]?
893
+ // scalar default - hm..., what about calc expression?
894
+ )
895
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
896
+ |
897
+ assoc=COMPOSITION cardinality[ $art ]? OF card=ONE/MANY?
898
+ (
899
+ target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
900
+ // final anno assignments allowed also with fkeys (also in ANTLR-based parser) - TODO: really?
901
+ // <prepare=calcOrDefaultRestriction,arg=true> // TODO TOOL: allow this
902
+ { this.calcOrDefaultRestriction(false,true); }<always> // workaround
903
+ ( ON cond=condition { $art.on = $cond; }
904
+ | foreignKeysBlock[ $art ]?
905
+ nullabilityAndDefault[ $art ]?
906
+ )
907
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
908
+ |
909
+ { $target = {}; this.setAssocAndComposition( $art, $assoc, $card, $target ); }
910
+ elementsBlock[ $target ]
911
+ { $target.location = $target.elements[Symbol.for('cds.$location')]; }
912
+ )
913
+ |
914
+ ( ARRAY OF { $art.items = { location: this.locationOfPrevTokens( 2 ) }; }
915
+ | MANY { $art.items = { location: this.lb().location }; }
916
+ )
917
+ (
918
+ elementsBlock[ $art.items ]
919
+ nullability[ $art.items ]?
920
+ |
921
+ ( typeRefOptArgs[ $art.items ] | typeTypeOf[ $art.items ] )
922
+ nullability[ $art.items ]?
923
+ ( enumSymbolsBlock[ $art.items ]
924
+ nullability[ $art.items ]? // TODO: only with enum
925
+ |
926
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
927
+ )
928
+ )
929
+ ;
930
+
931
+ typeTypeOf[ art ] locals[ location ]
932
+ :
933
+ TYPE OF { location = this.locationOfPrevTokens( 2 ); }
934
+ type=simplePath[ 'ref' ] { $art.type = $type; }
935
+ (
936
+ { $type.scope = 'typeOf'; $type.path.unshift( { id: 'type of', location } ); }
937
+ |
938
+ ':' { $type.scope = $type.path.length; }
939
+ // If we have too much time, we could set the category of the simple path
940
+ // before to 'artref'; but why use TYPE OF before `Art:elem` anyway?
941
+ Id_all['ref'] { $type.path.push( this.identAst() ); }
942
+ (
943
+ '.' Id_all['ref'] { $type.path.push( this.identAst() ); }
944
+ )*
945
+ )
946
+ ;
947
+
948
+ typeRefOptArgs[ art ] locals[ type = $art.type ]
949
+ :
950
+ type=simplePath { $art.type = $type; }
951
+ (<altRuleStart>)
952
+ (
953
+ ':' { $type.scope = $type.path.length; }
954
+ Id_all['ref'] { $type.path.push( this.identAst() ); }
955
+ (
956
+ '.' Id_all['ref'] { $type.path.push( this.identAst() ); }
957
+ )*
958
+ |
959
+ open='('
960
+ (
961
+ Number { $art.$typeArgs = this.createArray( $open ); }
962
+ { $art.$typeArgs.push( this.unsignedIntegerLiteral() ); }
963
+ (
964
+ ','
965
+ ( Number
966
+ { $art.$typeArgs.push( this.unsignedIntegerLiteral() ); }
967
+ | tok=VARIABLE/FLOATING
968
+ { $art.$typeArgs.push( { literal: 'string', val: $tok.keyword, location: $tok.location } ); }
969
+ | <exitLoop>
970
+ )
971
+ )* // TODO: really as loop?
972
+ |
973
+ { $art.$typeArgs = this.createDict( $open ); }
974
+ (
975
+ typeNamedArg[ $art ]
976
+ ( ',' | <exitLoop> )
977
+ )+ // TODO: really as loop?
978
+ )
979
+ ')' { this.finalizeDictOrArray( $art.$typeArgs ); }
980
+ )?
981
+ ;
982
+
983
+ typeNamedArgsList[ art ]
984
+ :
985
+ '(' { $art.$typeArgs = this.createDict(); }
986
+ (
987
+ typeNamedArg[ $art ]
988
+ ( ',' | <exitLoop> )
989
+ )* // TODO: really as loop?
990
+ ')' { this.finalizeDictOrArray( $art.$typeArgs ); }
991
+ ;
992
+
993
+ typeNamedArg[ art ]
994
+ :
995
+ name=Id['typeparamname']
996
+ ':'
997
+ ( Number
998
+ { this.setTypeFacet( $art, $name, this.unsignedIntegerLiteral() ); }
999
+ | tok=VARIABLE/FLOATING
1000
+ { this.setTypeFacet( $art, $name, { literal: 'string', val: $tok.keyword, location: $tok.location } ); }
1001
+ )
1002
+ ;
1003
+
1004
+ cardinality[ art ] locals[ card = {} ]
1005
+ @finally{ $art.cardinality = this.attachLocation($card); }
1006
+ :
1007
+ '['
1008
+ (
1009
+ '*' { $card.targetMax = this.valueWithLocation(); }
1010
+ ( ',' targetCardinality[ $card ] )?
1011
+ |
1012
+ Number { $card.targetMax = this.unsignedIntegerLiteral(); }
1013
+ (
1014
+ ',' targetCardinality[ $card ]
1015
+ |
1016
+ targetCardinality[ $card, true ]<atAltStart>
1017
+ )?
1018
+ |
1019
+ { $card.targetMax = this.valueWithLocation( '*' ); }
1020
+ ) // TODO: really optional?
1021
+ ']'
1022
+ ;
1023
+
1024
+ targetCardinality[ card, atAlt = false ]
1025
+ :
1026
+ // TODO TOOL: the following action should not be executed when called
1027
+ // <atAltStart> → we can then remove param `atAlt`
1028
+ { if (!$atAlt) $card.sourceMax = $card.targetMax; }
1029
+ (
1030
+ '*' { $card.targetMax = this.valueWithLocation(); }
1031
+ |
1032
+ Number { $card.targetMax = this.unsignedIntegerLiteral(); }
1033
+ ( <altRuleStart> ) // TODO TOOL: robust error when moved to after '('
1034
+ (
1035
+ '..' { $card.targetMin = $card.targetMax; }
1036
+ ( '*' { $card.targetMax = this.valueWithLocation(); }
1037
+ | Number { $card.targetMax = this.unsignedIntegerLiteral(); }
1038
+ )
1039
+ )?
1040
+ )
1041
+ ;
1042
+
1043
+ nullabilityAndDefault[ art ]
1044
+ :
1045
+ nullability[ $art ]
1046
+ ( <cond=calcOrDefaultRestriction> DEFAULT expr=expression
1047
+ { $art.default = $expr; }
1048
+ )?
1049
+ |
1050
+ <cond=calcOrDefaultRestriction> DEFAULT expr=expression
1051
+ { $art.default = $expr; }
1052
+ nullability[ $art ]?
1053
+ // TODO TOOL: when `followUnion` does not contain `Id`, `RuleEnd_` does not
1054
+ // need to induce prediction (here for `default`).
1055
+ ;
1056
+
1057
+ nullability[ art ]
1058
+ :
1059
+ NULL { this.setNullability( $art, false ); }
1060
+ |
1061
+ NOT NULL { this.setNullability( $art, true, this.locationOfPrevTokens( 2 ) ); }
1062
+ ;
1063
+
1064
+ // Queries: projections and SELECTs ---------------------------------------------
1065
+
1066
+ queryEOF returns[ query ]
1067
+ :
1068
+ $query=queryExpression ';'? EOF
1069
+ ;
1070
+
1071
+ projectionSpec returns[ default query = {} ]
1072
+ @finally{ this.attachLocation($query); }
1073
+ :
1074
+ // TODO, currently just with simple ref
1075
+ PROJECTION { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1076
+ ON
1077
+ // TODO: this <setCondition=afterBrace> is extremely strange... v6 forbid.
1078
+ // Deliberately set this via action.
1079
+ tab=fromRefWithOptAlias { this.afterBrace(); }<always>
1080
+ { $query.from = tab; }
1081
+ selectItemsList[ $query ]?
1082
+ excludingClause[ $query ]?
1083
+ ;
1084
+
1085
+ queryExpression returns[ default expr = {} ] locals[ op, quantifier ]
1086
+ @finally{ this.attachLocation( $expr ); }
1087
+ :
1088
+ ( '(' queryExpression[ ...$ ] ')' { this.surroundByParens( $expr ); }
1089
+ | $expr=selectQuery
1090
+ )
1091
+ (<altRuleStart>)
1092
+ (
1093
+ // See also `taggedIfQuery`/`queryOps` in AstBuildingParser.js
1094
+ ( ( <prec=4> INTERSECT { $op = this.valueWithLocation(); }
1095
+ | <prec=2> EXCEPT/MINUS { $op = this.valueWithLocation(); }
1096
+ )
1097
+ ( DISTINCT { $quantifier = this.valueWithLocation(); } )?
1098
+ | <prec=2> UNION { $op = this.valueWithLocation(); }
1099
+ ( DISTINCT/ALL { $quantifier = this.valueWithLocation(); } )?
1100
+ )
1101
+ query=queryExpression
1102
+ // with same op/quantifier: make left-assoc binary to nary:
1103
+ { if ($expr.$parens || $op.val !== $expr.op.val || $quantifier?.val !== $expr.quantifier?.val) $expr = { op, args: [$expr], quantifier }; }
1104
+ { $quantifier = undefined; }
1105
+ { $expr.args.push( $query ); this.attachLocation( $expr ); }
1106
+ )*
1107
+ ( <prec=0, postfix>
1108
+ // TODO: the following action is a bit stupid, but compatible to ANTLR-based
1109
+ // parser:
1110
+ { if ($expr.$parens) { this.attachLocation( $expr ); $expr = { op: this.valueWithLocation( '$query', this.la() ), args: [ $expr ] }; } }
1111
+ orderByLimitOffset[ $expr ]
1112
+ )?
1113
+ ;
1114
+
1115
+ selectQuery returns[ default query = {} ]
1116
+ @finally{ this.attachLocation($query); }
1117
+ :
1118
+ SELECT { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1119
+ (
1120
+ FROM querySource[ $query ]
1121
+ (
1122
+ MIXIN '{' { $query.mixin = this.createDict(); }
1123
+ (
1124
+ mixinElementDef[ $query ]
1125
+ ( ';' | <exitLoop> )
1126
+ )*
1127
+ '}' { this.finalizeDictOrArray( $query.mixin ); }
1128
+ INTO
1129
+ )?
1130
+ ( ALL/DISTINCT { $query.quantifier = this.valueWithLocation(); } )?
1131
+ // TODO: or directly after SELECT ?
1132
+ selectItemsList[ $query ]?
1133
+ excludingClause[ $query ]?
1134
+ |
1135
+ ( ALL/DISTINCT { $query.quantifier = this.valueWithLocation(); } )?
1136
+ ( '*' { $query.columns = [ this.valueWithLocation() ]; }
1137
+ | selectItemDef[ ($query.columns = []) ]
1138
+ )
1139
+ ( ','
1140
+ ( '*' { $query.columns.push( this.valueWithLocation() ); }
1141
+ | selectItemDef[ $query.columns ]
1142
+ )
1143
+ )*
1144
+ FROM querySource[ $query ]
1145
+ )
1146
+ whereGroupByHaving[ $query ]?
1147
+ ;
1148
+
1149
+ querySource[ query ]
1150
+ @after { this.attachLocation($query.from); }
1151
+ :
1152
+ tab=tableExpression { $query.from = $tab; }
1153
+ (
1154
+ { const { location } = this.la();
1155
+ $query.from = { op: { val: 'join', location }, join: { val: 'cross', location }, args: [$tab] };
1156
+ }
1157
+ ( ',' tab=tableExpression { $query.from.args.push( $tab ); } )+
1158
+ )?
1159
+ ;
1160
+
1161
+ tableExpression returns[ default expr ] // TableOrJoin
1162
+ :
1163
+ ( tableOrQueryParens[ ...$ ]
1164
+ (<altRuleStart> { $expr = this.taggedIfQuery( $expr ); } )
1165
+ | fromRefWithOptAlias[ ...$ ]
1166
+ )
1167
+ (
1168
+ join=CROSS JOIN
1169
+ { if ($expr?.join?.val !== 'cross' || $expr.$parens) $expr = { op: this.valueWithLocation(), join: this.valueWithLocation( undefined, $join ), args: [ $expr ] }; }
1170
+ ( tab=tableOrQueryParens { $expr.args.push( this.taggedIfQuery( $tab ) ); }
1171
+ { this.attachLocation( $expr ); }
1172
+ | tab=fromRefWithOptAlias { $expr.args.push( $tab ); }
1173
+ { this.attachLocation( $expr ); }
1174
+ )
1175
+ |
1176
+ ( ( join=INNER | join=LEFT/RIGHT/FULL OUTER? )
1177
+ card=joinCardinality?
1178
+ | { $join = undefined; } // TODO TOOL: action is lost
1179
+ )
1180
+ JOIN
1181
+ { $expr = { op: this.valueWithLocation(), join: this.valueWithLocation( $join?.keyword || 'inner', $join ), args: [ $expr ] }; if ($card) $expr.cardinality = $card; $card = undefined; }
1182
+ { $join = undefined; } // TODO TOOL bug workaround, see above
1183
+ tab=tableExpression
1184
+ { $expr.args.push( $tab ); this.attachLocation( $expr ); }
1185
+ ON cond=condition { $expr.on = $cond; }
1186
+ { this.attachLocation( $expr ); }
1187
+ )*
1188
+ ;
1189
+
1190
+ tableOrQueryParens returns[ default expr ]
1191
+ :
1192
+ '('
1193
+ ( <priority> tableOrQueryParens[ ...$ ]
1194
+ ( <prec=-2, postfix> tableExpression[ ...$ ]<atAltStart>
1195
+ | <prec=-1, postfix> queryExpression[ ...$ ]<atAltStart>
1196
+ )?
1197
+ | <prec=-2> tableExpression[ ...$ ]
1198
+ | <prec=-1> queryExpression[ ...$ ]
1199
+ )
1200
+ ')'
1201
+ { this.surroundByParens( $expr ); }
1202
+ ( <prec=-2, postfix=once> AS Id['FromAlias']
1203
+ <setCondition=setPrecInCallingRule>
1204
+ { $expr = this.taggedIfQuery( $expr ); $expr.name = this.identAst(); }
1205
+ | <hide, cond=tableAlias> Id_restricted['FromAlias']
1206
+ <setCondition=setPrecInCallingRule>
1207
+ { $expr = this.taggedIfQuery( $expr ); $expr.name = this.fragileAlias(); }
1208
+ |
1209
+ // <setCondition=setPrecInCallingRule> // TODO TOOL: allow this
1210
+ { this.setPrecInCallingRule(); }<always> // workaround
1211
+ )
1212
+ ; // change #10799 for ANTLR-based parser
1213
+
1214
+ joinCardinality returns[ sourceMax, targetMax ]
1215
+ @finally{ this.attachLocation( $ ); }
1216
+ :
1217
+ (
1218
+ ( EXACT { $.sourceMin = this.valueWithLocation( 1 ); } )?
1219
+ ONE { $sourceMax = this.valueWithLocation( 1 ); }
1220
+ |
1221
+ MANY { $sourceMax = this.valueWithLocation( '*' ); }
1222
+ )
1223
+ TO
1224
+ (
1225
+ ( EXACT { $.targetMin = this.valueWithLocation( 1 ); } )?
1226
+ ONE { $targetMax = this.valueWithLocation( 1 ); }
1227
+ |
1228
+ MANY { $targetMax = this.valueWithLocation( '*' ); }
1229
+ )
1230
+ ;
1231
+
1232
+ fromRefWithOptAlias returns[ default expr = { path: [] } ]
1233
+ @finally{ this.attachLocation( $expr ); }
1234
+ :
1235
+ fromPath[ $expr, 'artref' ]
1236
+ (
1237
+ ':' { if (!$expr.scope) $expr.scope = $expr.path.length; else {
1238
+ this.warning( 'syntax-invalid-path-separator', this.lb(),
1239
+ { '#': 'colon', code: ':', newcode: '.' } );
1240
+ } }
1241
+ fromPath[ $expr, 'ref']
1242
+ )?
1243
+ (
1244
+ AS Id['FromAlias'] { $expr.name = this.identAst(); }
1245
+ |
1246
+ <hide, cond=tableAlias>
1247
+ Id_restricted['FromAlias']
1248
+ { $expr.name = this.fragileAlias(); }
1249
+ |
1250
+ <default=fallback>
1251
+ { this.classifyImplicitName( $expr.scope ? 'FromElemImplicit' : 'FromImplicit', $expr ); }
1252
+ )
1253
+ ;
1254
+
1255
+ fromPath[ table, category ] locals[ pathItem ]
1256
+ @finally{ this.attachLocation( $table.path ); }
1257
+ :
1258
+ Id[ $category ] { $table.path.push( $pathItem = this.identAst() ); }
1259
+ ( fromArgumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
1260
+ (
1261
+ '.' { if (!$pathItem && !$table.scope) {
1262
+ $table.scope = $table.path.length; $category = 'ref';
1263
+ this.warning( 'syntax-invalid-path-separator', this.lb(),
1264
+ { '#': 'dot', code: '.', newcode: ':' } );
1265
+ } }
1266
+ Id_all[ $category ] { $table.path.push( $pathItem = this.identAst() ); }
1267
+ ( fromArgumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
1268
+ )*
1269
+ ;
1270
+
1271
+ fromArgumentsAndFilter[ pathStep ]
1272
+ options{ minTokensMatched = 1 }
1273
+ :
1274
+ (
1275
+ '(' { $pathStep.args = this.createDict(); $pathStep.$syntax = ':'; }
1276
+ ( fromNamedArgument[ ...$ ]
1277
+ ( ',' | <exitLoop> )
1278
+ )+
1279
+ ')'
1280
+ )?
1281
+ cardinalityAndFilter[ ...$ ]?
1282
+ ;
1283
+
1284
+ fromNamedArgument[ pathStep ]
1285
+ :
1286
+ name=Id['paramname'] ':' expr=expression
1287
+ { this.addDef( $expr, $pathStep, 'args', 0, this.identAst( $name ) ); }
1288
+ // TODO: or add argument directly after having parsed the name? (for CC)
1289
+ ;
1290
+
1291
+ cardinalityAndFilter[ pathStep ]
1292
+ :
1293
+ '['
1294
+ ( <cond=beforeColon> Number // TODO: only allow `1`?
1295
+ { $pathStep.cardinality = { targetMax: this.unsignedIntegerLiteral(), location: this.lb().location }; }
1296
+ ':'
1297
+ )?
1298
+ filterClauses[ $pathStep ]
1299
+ // TODO: why not allowing all clauses to be optional? (then inline rule `filterClauses`)
1300
+ ']'
1301
+ ;
1302
+
1303
+ filterClauses[ pathStep ]
1304
+ options{ minTokensMatched = 1 }
1305
+ :
1306
+ ( WHERE?
1307
+ // compare GROUP/HAVING/ORDER/LIMIT w/o prediction (reserved WHERE anyway):
1308
+ // <restrict=Id> // TODO TOOL: not yet supported here
1309
+ // BTW, why? not necessary anymore…
1310
+ cond=condition { $pathStep.where = $cond; }
1311
+ )?
1312
+ ( <hide>
1313
+ { this.csnParseOnly('syntax-unexpected-sql-clause', 1, { keyword: 'group by' }); }
1314
+ groupByClause[ $pathStep ]
1315
+ )?
1316
+ ( <hide> HAVING
1317
+ { this.csnParseOnly('syntax-unexpected-sql-clause', -1, { keyword: 'having' }); }
1318
+ cond=condition { $pathStep.having = $cond; }
1319
+ )?
1320
+ ( <hide>
1321
+ { if (this.lk() === 'limit') this.csnParseOnly('syntax-unexpected-sql-clause', 0, { keyword: 'limit' } ); else this.csnParseOnly('syntax-unexpected-sql-clause', 1, { keyword: 'order by' } ); }
1322
+ // I do not care that there is now only one error msg for both ORDER BY … LIMIT …
1323
+ orderByLimitOffset[ $pathStep ]
1324
+ )?
1325
+ ;
1326
+
1327
+ excludingClause[ query ]
1328
+ :
1329
+ // syntax is less than ideal - EXCLUDING is only useful for `*` - with
1330
+ // this syntax, people wonder what happens with explicit select items
1331
+ EXCLUDING '{' { $query.excludingDict = this.createDict(); }
1332
+ // TODO: better move '{' to after action, but → diff to ANTLR-based parser
1333
+ (
1334
+ Id_all['ref'] // TODO: different category?
1335
+ { this.addDef( { location: this.lb().location }, $query, 'excludingDict', '', this.identAst() ); }
1336
+ ( ',' | <exitLoop> )
1337
+ )+
1338
+ '}'<setCondition=afterBrace>
1339
+ { this.finalizeDictOrArray( $query.excludingDict ); }
1340
+ ;
1341
+
1342
+ selectItemsList[ query, start = undefined ]
1343
+ :
1344
+ '{'<setCondition=notInExpandInline>
1345
+ { $query.columns = this.createArray( $start ); }
1346
+ (
1347
+ ( '*' { $query.columns.push( this.valueWithLocation() ); }
1348
+ | selectItemDef[ $query.columns ]
1349
+ )
1350
+ ( ',' | <exitLoop> )
1351
+ )*
1352
+ '}'<setCondition=afterBrace>
1353
+ { this.finalizeDictOrArray( $query.columns ); }
1354
+ ;
1355
+
1356
+ nestedSelectItemsList[ query, clause ]
1357
+ :
1358
+ '{'<setCondition=inExpandInline>
1359
+ { $query[$clause] = this.createArray(); }
1360
+ (
1361
+ ( '*' { $query[$clause].push( this.valueWithLocation() ); }
1362
+ | selectItemDef[ $query[$clause] ]
1363
+ )
1364
+ ( ',' | <exitLoop> )
1365
+ )*
1366
+ '}'<setCondition=afterBrace>
1367
+ { this.finalizeDictOrArray( $query[$clause] ); }
1368
+ ;
1369
+
1370
+ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1371
+ @finally{ this.attachLocation( $art ); }
1372
+ :
1373
+ { $columns.push( $art ); } // TODO: probably too early
1374
+ { this.docComment( $art ); } annoAssignCol[ $art ]*
1375
+ ( <cond=notInExpandInline> VIRTUAL
1376
+ { $art.virtual = this.valueWithLocation( true ); } )?
1377
+ ( <cond=notInExpandInline> KEY
1378
+ { $art.key = this.valueWithLocation( true ); } )?
1379
+ (
1380
+ expr=expression { $art.value = $expr; }
1381
+ ( as=AS Id['ItemAlias'] { $art.name = this.identAst(); }
1382
+ | Id_restricted['ItemAlias'] { $art.name = this.fragileAlias( true ); }
1383
+ | { $alias = this.classifyImplicitName( 'ItemImplicit', $expr ); }
1384
+ )
1385
+ // TODO: <cond> instead `reportExpandInline` for "expand/inline only w/ ref"
1386
+ (
1387
+ { this.reportExpandInline( $art, false ); }
1388
+ nestedSelectItemsList[ $art, 'expand' ]
1389
+ excludingClause[ $art ]?
1390
+ |
1391
+ '.'
1392
+ { this.reportExpandInline( $art, $as || true ); }
1393
+ { if ($alias) $alias.token.parsedAs = $alias.parsedAs; }
1394
+ (
1395
+ nestedSelectItemsList[ $art, 'inline' ]
1396
+ excludingClause[ $art ]?
1397
+ |
1398
+ '*' { $art.inline = [ this.valueWithLocation() ]; }
1399
+ )
1400
+ )?
1401
+ |
1402
+ nestedSelectItemsList[ $art, 'expand' ]
1403
+ excludingClause[ $art ]?
1404
+ AS Id['ItemAlias'] { $art.name = this.identAst(); }
1405
+ )
1406
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
1407
+ (
1408
+ ':'
1409
+ (
1410
+ ( typeTypeOf[ $art ]
1411
+ | ( LOCALIZED { $art.localized = this.valueWithLocation( true ); } )?
1412
+ typeRefOptArgs[ $art ]
1413
+ )
1414
+ |
1415
+ REDIRECTED TO target=simplePath { $art.target = $target; }
1416
+ ( ON cond=condition { $art.on = $cond; }
1417
+ | foreignKeysBlock[ $art ]
1418
+ )?
1419
+ |
1420
+ // TODO: condition for this
1421
+ ( assoc=ASSOCIATION { this.associationInSelectItem( $art ); }
1422
+ cardinality[ $art ]? TO
1423
+ | assoc=COMPOSITION { this.associationInSelectItem( $art ); }
1424
+ cardinality[ $art ]? OF
1425
+ )
1426
+ // { this.classifyImplicitName( 'ItemAssoc', $art.value ); } TODO: do we need this?
1427
+ card=ONE/MANY? target=simplePath
1428
+ { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
1429
+ ON expr=condition { $art.on = $expr; }
1430
+ )
1431
+ // TODO: no nullability here ?
1432
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
1433
+ )?
1434
+ ;
1435
+
1436
+ whereGroupByHaving[ query ]
1437
+ options{ minTokensMatched = 1 }
1438
+ :
1439
+ ( WHERE cond=condition { $query.where = $cond; } )?
1440
+ groupByClause[ ...$ ]?
1441
+ ( HAVING cond=condition { $query.having = $cond; } )?
1442
+ ;
1443
+
1444
+ groupByClause[ query ]
1445
+ :
1446
+ GROUP BY expr=expression { $query.groupBy = [ $expr ]; }
1447
+ ( ',' expr=expression { $query.groupBy.push( $expr ); } )*
1448
+ ;
1449
+
1450
+ orderByLimitOffset[ query ]
1451
+ options{ minTokensMatched = 1 }
1452
+ :
1453
+ orderByClause[ ...$ ]?
1454
+ ( LIMIT expr=expression { $query.limit = { rows: $expr }; }
1455
+ ( OFFSET expr=expression { $query.limit.offset = $expr; } )?
1456
+ )?
1457
+ ;
1458
+
1459
+ orderByClause[ query ]
1460
+ :
1461
+ ORDER BY expr=orderByExpression { $query.orderBy = [ $expr ]; }
1462
+ ( ',' expr=orderByExpression { $query.orderBy.push( $expr ); } )*
1463
+ ;
1464
+
1465
+ orderByExpression returns[ default expr ]
1466
+ :
1467
+ expression[ ...$ ]
1468
+ ( ASC/DESC { $expr.sort = this.valueWithLocation(); } )?
1469
+ ( NULLS FIRST/LAST { $expr.nulls = this.valueWithLocation(); } )?
1470
+ ;
1471
+
1472
+ // Conditions and expressions ---------------------------------------------------
1473
+
1474
+ conditionEOF returns[ cond ]
1475
+ :
1476
+ $cond=expression EOF
1477
+ ;
1478
+
1479
+ condition returns[ default expr ]
1480
+ :
1481
+ expression[ ...$ ]
1482
+ ;
1483
+
1484
+ // TODO TOOL: sort rules if a rule with <altRuleStart> uses rule with <altRuleStart>
1485
+ // at least issue error if no user-sorted
1486
+ valuePath returns[ default expr = { path: [] } ] locals[ pathItem ]
1487
+ @finally{ this.attachLocation( $expr ); }
1488
+ :
1489
+ Id['ref'] { $expr.path.push( $pathItem = this.identAst() ); }
1490
+ ( argumentsAndFilter[ $pathItem ] )?
1491
+ (<altRuleStart>)
1492
+ (
1493
+ <cond=isDotForPath> '.'
1494
+ Id_all['ref'] { $expr.path.push( $pathItem = this.identAst() ); }
1495
+ ( argumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
1496
+ )*
1497
+ ;
1498
+
1499
+ // TODO: ? params
1500
+ expression returns[ default expr ]
1501
+ //@finally{ if (!$expr?.$parens) this.attachLocation( $expr ); }
1502
+ :
1503
+ (
1504
+ expressionOrQueryParens[ ...$ ]
1505
+ (<altRuleStart> { $expr = this.taggedIfQuery( $expr ); })
1506
+ |
1507
+ literalValue[ ...$ ]
1508
+ |
1509
+ ':' { this.reportUnexpectedSpace(); }
1510
+ (
1511
+ Id_all['paramref']
1512
+ { $expr = { path: [ this.identAst() ], location: this.startLocation(), scope: 'param' }; }
1513
+ ( valuePath[ ...$ ]<atAltStart>
1514
+ { $expr = this.valuePathAst( $expr ); }
1515
+ | { this.attachLocation( $expr ); }
1516
+ )
1517
+ |
1518
+ <hide> Number // TODO: as user condition
1519
+ { this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'positional', code: ':' + this.lb().text } ); }
1520
+ { $expr = this.attachLocation({ param: this.unsignedIntegerLiteral(), scope: 'param' }); }
1521
+ )
1522
+ |
1523
+ <hide> '?' // TODO: do as user condition
1524
+ {this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'dynamic', code: '?' } );
1525
+ }
1526
+ { $expr = this.attachLocation({ param: this.valueWithLocation(), scope: 'param' }); }
1527
+ |
1528
+ e=valuePath { $expr = this.valuePathAst( $e ); }
1529
+ (
1530
+ OVER { this.pushXprToken( $expr.suffix = [] ); }
1531
+ e=overClause[ $expr.suffix ]
1532
+ )?
1533
+ { this.attachLocation( $expr ); }
1534
+ |
1535
+ newAndValuePath[ ...$ ]
1536
+ |
1537
+ EXISTS { $expr = this.applyOpToken(); }
1538
+ ( open='(' e=queryExpression ')'
1539
+ { $expr.args.push( this.taggedIfQuery( this.surroundByParens( $e, $open ) ) ); }
1540
+ { this.attachLocation( $expr ); }
1541
+ | e=valuePath { $e = this.valuePathAst( $e ); $e.$expected = 'exists'; }
1542
+ // TODO: re-check whether to really set $expected in parser
1543
+ { $expr.args.push( $e ); this.attachLocation( $expr ); }
1544
+ | <hide> '?' // TODO: do as user condition
1545
+ {this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'dynamic', code: '?' } ); }
1546
+ { $expr.args.push( { param: this.valueWithLocation(), scope: 'param' } ); this.attachLocation( $expr ); }
1547
+ )
1548
+ |
1549
+ caseExpression[ ...$ ]
1550
+ |
1551
+ castFunction[ ...$ ]
1552
+ |
1553
+ ( <prec=30, prefix> '+'/'-' { $expr = this.applyOpToken(); }
1554
+ | <prec=8, prefix> NOT { $expr = this.applyOpToken(); }
1555
+ )
1556
+ e=expression { $expr = this.signedExpression( $expr, $e ); }
1557
+ )
1558
+ // binary + postfix
1559
+ ( (
1560
+ ( <prec=24> '*'/'/' { $expr = this.applyOpToken( $expr, 'nary' ); }
1561
+ | <prec=22> '+'/'-' { $expr = this.applyOpToken( $expr, 'nary' ); }
1562
+ | <prec=20> '||' { $expr = this.applyOpToken( $expr, 'nary' ); }
1563
+ | <prec=4> AND { $expr = this.applyOpToken( $expr, 'nary' ); }
1564
+ | <prec=2> OR { $expr = this.applyOpToken( $expr, 'nary' ); }
1565
+ | <prec=0> '?' { $expr = this.applyOpToken( $expr, '?:' ); }
1566
+ e=expression { $expr.args.push( $e ); }
1567
+ ':' { this.pushXprToken( $expr ); }
1568
+ // -> createXprForOp vs createAstForOp
1569
+
1570
+ | <prec=10, assoc=none> '='/'<>'/'>'/'>='/'<'/'<='/'!='
1571
+ { $expr = this.applyOpToken( $expr ); }
1572
+ ( ANY/SOME/ALL { this.pushXprToken( $expr ); } )?
1573
+ )
1574
+ e=expression { $expr.args.push( $e ); }
1575
+
1576
+ | <prec=10, postfix=once> IS { $expr = this.applyOpToken( $expr ); }
1577
+ ( NOT { this.pushXprToken( $expr ); } )?
1578
+ NULL { this.pushXprToken( $expr ); }
1579
+ | ( <cond=isNegatedRelation> NOT { $expr = this.applyOpToken( $expr ); }
1580
+ // TODO: condition, because there might be NOT NULL after DEFAULT expression
1581
+ | <prec=10, postfix=once>
1582
+ { $expr = { op: { val: 'ixpr', location: this.la().location }, args: [ $expr ] }; }
1583
+ )
1584
+ (
1585
+ BETWEEN { this.pushXprToken( $expr ); }
1586
+ e=expression { $expr.args?.push( $e ); }
1587
+ AND { this.pushXprToken( $expr ); }
1588
+ e=expression { $expr.args?.push( $e ); }
1589
+ | IN { this.pushXprToken( $expr ); }
1590
+ e=expression { $expr.args?.push( this.secureParens( $e ) ); }
1591
+ | LIKE { this.pushXprToken( $expr ); }
1592
+ e=expression { $expr.args?.push( $e ); }
1593
+ ( ESCAPE { this.pushXprToken( $expr ); }
1594
+ e=expression { $expr.args?.push( $e ); }
1595
+ )?
1596
+ )
1597
+ )
1598
+ { this.attachLocation( $expr ); }
1599
+ )*
1600
+ ;
1601
+
1602
+ expressionOrQueryParens returns[ default expr ]
1603
+ :
1604
+ '('
1605
+ ( <priority> expressionOrQueryParens[ ...$ ]
1606
+ ( <prec=-2, postfix> expression[ ...$ ]<atAltStart>
1607
+ continueExpressionslist[ ...$ ]?
1608
+ | <prec=-2, postfix>
1609
+ continueExpressionslist[ ...$ ]
1610
+ | <prec=-1, postfix> queryExpression[ ...$ ]<atAltStart>
1611
+ )?
1612
+ | <prec=-2> expression[ ...$ ]
1613
+ continueExpressionslist[ ...$ ]?
1614
+ | <prec=-1> queryExpression[ ...$ ]
1615
+ )
1616
+ ')' <setCondition=setPrecInCallingRule>
1617
+ { this.surroundByParens( $expr ); }
1618
+ ;
1619
+
1620
+ continueExpressionslist[ expr ]
1621
+ @finally{ this.attachLocation( $expr ); }
1622
+ :
1623
+ ',' { $expr = { op: this.valueWithLocation( 'list' ), args: [ $expr ], location: { ... $expr.$parens?.at( -1 ) ?? $expr.location } }; }
1624
+ (
1625
+ e=expression { $expr.args.push( $e ); }
1626
+ ( ',' | <exitLoop> )
1627
+ )+
1628
+ { this.attachLocation( $expr ); }
1629
+ ;
1630
+
1631
+ newAndValuePath returns[ default expr ]
1632
+ @finally{ this.attachLocation( $expr ); }
1633
+ :
1634
+ NEW { $expr = this.applyOpToken(); }
1635
+ e=valuePath { $e = this.valuePathAst( $e ); }
1636
+ { if ($e.op?.val !== 'ixpr') $expr.args.push( $e ); else $expr.args.push( ...e.args ); }
1637
+ ;
1638
+
1639
+ caseExpression returns[ default expr = { op: { val: 'ixpr' }, args: [] } ]
1640
+ @finally{ this.attachLocation( $expr ); }
1641
+ :
1642
+ CASE { this.pushXprToken( $expr ); $expr.op.location = $expr.args[0].location; }
1643
+ ( e=expression { $expr.args.push( $e ); } )?
1644
+ (
1645
+ WHEN { this.pushXprToken( $expr ); }
1646
+ e=expression { $expr.args.push( $e ); }
1647
+ THEN { this.pushXprToken( $expr ); }
1648
+ e=expression { $expr.args.push( $e ); }
1649
+ )+
1650
+ (
1651
+ ELSE { this.pushXprToken( $expr ); }
1652
+ e=expression { $expr.args.push( $e ); }
1653
+ )?
1654
+ END { this.pushXprToken( $expr ); }
1655
+ ;
1656
+
1657
+ castFunction returns[ default expr = {} ]
1658
+ @finally{ this.attachLocation( $expr ); }
1659
+ :
1660
+ CAST { $expr.op = this.valueWithLocation(); }
1661
+ '(' { $expr.args = this.createArray(); }
1662
+ arg=expression { $expr.args?.push( $arg ); }
1663
+ AS typeRefOptArgs[ $expr ]
1664
+ ')' { this.finalizeDictOrArray( $expr.args ); }
1665
+ ;
1666
+
1667
+ argumentsAndFilter[ pathStep ]
1668
+ // TODO: what about valuePath with EXISTS, after `:` etc (also in ANTLR-based parser)?
1669
+ options{ minTokensMatched = 1 }
1670
+ :
1671
+ (
1672
+ open='(' <setCondition=prepareSpecialFunction>
1673
+ { $pathStep.args = this.createArray(); }
1674
+ // action here, default action won't be executed with failed condition (TODO
1675
+ // TOOL? at least msg)
1676
+ (
1677
+ <default=fallback> // TODO TOOL: allow in loop
1678
+ (
1679
+ expr=funcExpression { $pathStep.args.push( $expr ); }
1680
+ (
1681
+ ','<setCondition=nextFunctionArgument>
1682
+ ( expr=funcExpression { $pathStep.args.push( $expr ); }
1683
+ | <exitLoop> // <cond>: only before `)`
1684
+ )
1685
+ )*
1686
+ ( // ORDER BY in generic functions, e.g. `first_value(id order by name)`
1687
+ ORDER { $expr = $pathStep.args[$pathStep.args.length - 1] = this.applyOpToken( $expr ); }
1688
+ BY { this.pushXprToken( $expr ); }
1689
+ orderByClauseAsXpr[ $expr.args ]
1690
+ { this.attachLocation( $expr ); }
1691
+ )?
1692
+ )?
1693
+ |
1694
+ // TODO: if we want perfect CC and error recovery, `isNamedArg` would work
1695
+ // like keyword prediction and also do something similar to the (future)
1696
+ // "identifier confirmation prediction" (consider argument `(par ‡ : 42)`
1697
+ <cond=isNamedArg> id=Id_all['paramname']
1698
+ (
1699
+ ':' { $pathStep.args = this.createDict( $open ); $pathStep.$syntax = ':'; }
1700
+ expr=expression { this.addNamedArg( $pathStep, $id, $expr ); }
1701
+ ( ','
1702
+ ( id=Id_all['paramname'] ':'
1703
+ expr=expression { this.addNamedArg( $pathStep, $id, $expr ); }
1704
+ | <exitLoop>
1705
+ )
1706
+ )*
1707
+ |
1708
+ '=>' { $pathStep.args = this.createDict(); }
1709
+ // TODO: potentially special expressions for special functions
1710
+ expr=expression { this.addNamedArg( $pathStep, $id, $expr ); }
1711
+ ( ','
1712
+ ( id=Id_all['paramname'] '=>'
1713
+ expr=expression { this.addNamedArg( $pathStep, $id, $expr ); }
1714
+ | <exitLoop>
1715
+ )
1716
+ )*
1717
+ )
1718
+ )
1719
+ ')'
1720
+ )?
1721
+ // TODO: not with function!
1722
+ cardinalityAndFilter[ ...$ ]?
1723
+ ;
1724
+
1725
+ funcExpression returns[ default expr ] locals[ args ]
1726
+ @finally{ this.attachLocation( $expr ); }
1727
+ :
1728
+ ( options{ lookahead = lGenericIntroOrExpr; }
1729
+ :
1730
+ $expr=expression
1731
+ |
1732
+ tok=GenericExpr // keyword as replacement for expression, like '*'
1733
+ { $expr = { val: $tok.keyword ?? $tok.type, location: $tok.location, literal: 'token' }; }
1734
+ |
1735
+ GenericIntro // keyword as introduction of expression, like DISTINCT
1736
+ { $expr = this.applyOpToken(); $args = $expr.args; }
1737
+ e=expression { $expr.args.push( $e ); }
1738
+ )
1739
+ ( options{ lookahead = lGenericSeparator; }
1740
+ :
1741
+ GenericSeparator
1742
+ { if ($args) this.pushXprToken( $args ); else { $expr= this.applyOpToken( $expr ); $args = $expr.args; } }
1743
+
1744
+ ( options{ lookahead = lGenericExpr; }
1745
+ :
1746
+ e=expression { $args.push( $e ); }
1747
+ |
1748
+ GenericExpr { this.pushXprToken( $args ); }
1749
+ )
1750
+ )*
1751
+ ;
1752
+
1753
+ GenericExpr
1754
+ : Id_all | '*' ;
1755
+ GenericIntro
1756
+ : Id_all ;
1757
+ GenericSeparator
1758
+ : Id_all ;
1759
+
1760
+ overClause[ outer ] locals[ over = [] ]
1761
+ @finally{ $outer.push( this.surroundByParens( this.ixprAst( $over ) ) ); }
1762
+ :
1763
+ '('
1764
+ ( PARTITION { this.pushXprToken( $over ); } BY { this.pushXprToken( $over ); }
1765
+ expressionsAsXpr[ $over ]
1766
+ )?
1767
+ ( ORDER { this.pushXprToken( $over ); } BY { this.pushXprToken( $over ); }
1768
+ orderByClauseAsXpr[ $over ]
1769
+ )?
1770
+ ( ROWS { this.pushXprToken( $over ); }
1771
+ windowFrameClause[ $over ]
1772
+ )?
1773
+ ')'
1774
+ ;
1775
+
1776
+ expressionsAsXpr[ outer ] locals[ args = [] ]
1777
+ @finally{ $outer.push( this.ixprAst( $args ) ); }
1778
+ :
1779
+ expr=expression { $args.push( $expr ); }
1780
+ ( ',' { this.pushXprToken( $args ); }
1781
+ expr=expression { $args.push( $expr ); }
1782
+ )*
1783
+ ;
1784
+
1785
+ orderByClauseAsXpr[ outer ] locals[ args = [] ]
1786
+ @finally{ $outer.push( this.ixprAst( $args ) ); }
1787
+ :
1788
+ orderBySpecAsXpr[ $args ]
1789
+ ( ',' { this.pushXprToken( $args ); }
1790
+ orderBySpecAsXpr[ $args ]
1791
+ )*
1792
+ ;
1793
+
1794
+ orderBySpecAsXpr[ args ]
1795
+ :
1796
+ expr=expression { $args.push( $expr ); }
1797
+ ( ASC/DESC { this.pushXprToken( $args ); } )?
1798
+ ( NULLS { this.pushXprToken( $args ); }
1799
+ FIRST/LAST { this.pushXprToken( $args ); }
1800
+ )?
1801
+ ;
1802
+
1803
+ windowFrameClause[ outer ] locals[ args = [] ]
1804
+ @finally{ $outer.push( this.ixprAst( $args ) ); }
1805
+ :
1806
+ ( UNBOUNDED { this.pushXprToken( $args ); }
1807
+ | Number { $args.push( this.unsignedIntegerLiteral() ); }
1808
+ )
1809
+ PRECEDING { this.pushXprToken( $args ); }
1810
+ |
1811
+ CURRENT { this.pushXprToken( $args ); }
1812
+ ROW { this.pushXprToken( $args ); }
1813
+ |
1814
+ BETWEEN { this.pushXprToken( $args ); }
1815
+ windowFrameBoundSpec[ $args ]
1816
+ AND { this.pushXprToken( $args ); }
1817
+ windowFrameBoundSpec[ $args ]
1818
+ ;
1819
+
1820
+ windowFrameBoundSpec[ args ]
1821
+ :
1822
+ ( UNBOUNDED { this.pushXprToken( $args ); }
1823
+ | Number { $args.push( this.unsignedIntegerLiteral() ); }
1824
+ )
1825
+ ( FOLLOWING | PRECEDING ) { this.pushXprToken( $args ); }
1826
+ |
1827
+ CURRENT { this.pushXprToken( $args ); }
1828
+ ROW { this.pushXprToken( $args ); }
1829
+ ;
1830
+
1831
+ literalValue returns[ default expr = {} ]
1832
+ @finally{ this.attachLocation( $expr ); }
1833
+ :
1834
+ // TODO: remove from this rule (not in enum! `String enum { foo = #bar }`) ?
1835
+ '#' { this.reportUnexpectedSpace(); }
1836
+ Id['enumref']
1837
+ { $expr = { literal: 'enum', sym: this.identAst() } }
1838
+ |
1839
+ NULL
1840
+ { $expr = { literal: 'null', val: null }; }
1841
+ |
1842
+ TRUE/FALSE
1843
+ { $expr = { literal: 'boolean', val: this.lb().keyword === 'true' }; }
1844
+ |
1845
+ Number
1846
+ { $expr = this.numberLiteral(); } // allow float and large number
1847
+ |
1848
+ String
1849
+ { $expr = this.quotedLiteral(); }
1850
+ |
1851
+ QuotedLiteral // x'12', date'...', time'...', timestamp'...'
1852
+ { $expr = this.quotedLiteral(); }
1853
+ ;
1854
+
1855
+ // Annotation assignments -------------------------------------------------------
1856
+
1857
+ // We have three versions of the annotation assignment rules:
1858
+ // - "…Std": typically before keyword+name, after a name if no ':' could follow
1859
+ // - "…Col": at the beginning of a column def, which can start with ':' or '#'
1860
+ // - "…Mid": typically after a name if a ':' could follow
1861
+
1862
+ annoAssignStd[ art ]
1863
+ @finally{ this.docComment( $art ); }
1864
+ :
1865
+ '@'<setCondition=annoInSameLine> { this.reportUnexpectedSpace(); }
1866
+ ( annoAssignParen[ ...$ ]
1867
+ | annoAssignBase[ ...$ ]
1868
+ )
1869
+ ;
1870
+
1871
+ annoAssignCol[ art ]
1872
+ @finally{ this.docComment( $art ); }
1873
+ :
1874
+ '@' { this.reportUnexpectedSpace(); }
1875
+ ( annoAssignParen[ ...$ ]
1876
+ | annoAssignBase[ ...$ ]
1877
+ )
1878
+ ;
1879
+
1880
+ annoAssignMid[ art ]
1881
+ @finally{ this.docComment( $art ); }
1882
+ :
1883
+ '@'<setCondition=annoInSameLine> { this.reportUnexpectedSpace(); }
1884
+ ( annoAssignParen[ ...$ ]
1885
+ | name=annoNamePath // !
1886
+ { this.assignAnnotation( $art, {}, $name ); this.warnIfColonFollows( $name ); }
1887
+ )
1888
+ ;
1889
+
1890
+ annoAssignParen[ art ]
1891
+ :
1892
+ '('<setCondition=annoInSameLine>
1893
+ ( annoAssignBase[ $art ]
1894
+ ( ',' | <exitLoop> )
1895
+ )*
1896
+ ')'
1897
+ ;
1898
+
1899
+ annoAssignBase[ art ] locals[ value = {} ]
1900
+ @finally{ this.assignAnnotation( $art, $value, $name || {} ); }
1901
+ :
1902
+ name=annoNamePath
1903
+ ( <cond=annoInSameLine> ':' value=annoValue )?
1904
+ ;
1905
+
1906
+ annoNamePath[ category = 'anno' ] returns[ default name = new XsnName() ]
1907
+ @finally{ this.attachLocation( $name ); }
1908
+ :
1909
+ Id_all[ $category ]
1910
+ { $name.path = [ this.identAst() ]; }
1911
+ (
1912
+ '.'
1913
+ ( Id_all[ $category ] { $name.path.push( this.identAst() ); }
1914
+ | at='@' // TODO: complain about spaces after?
1915
+ Id_all[ $category ] { $name.path.push( this.identAstWithPrefix( $at ) ); }
1916
+ )
1917
+ )*
1918
+ ( <cond=annoInSameLine> annoPathVariant[ $name ] )?
1919
+ ;
1920
+
1921
+ annoPath[ nameOrRef, category = 'annoref' ]
1922
+ @finally{ this.attachLocation( $nameOrRef ); }
1923
+ :
1924
+ ( Id_all[ $category ] { $nameOrRef.path = [ this.identAst() ]; }
1925
+ | at='@' // TODO: complain about spaces after?
1926
+ Id_all[ $category ] { $nameOrRef.path = [ this.identAstWithPrefix( $at ) ]; }
1927
+ )
1928
+ (
1929
+ '.'
1930
+ ( Id_all[ $category ] { $nameOrRef.path.push( this.identAst() ); }
1931
+ | at='@' // TODO: complain about spaces after?
1932
+ Id_all[ $category ] { $nameOrRef.path.push( this.identAstWithPrefix( $at ) ); }
1933
+ )
1934
+ )*
1935
+ annoPathVariant[ $nameOrRef ]?
1936
+ ;
1937
+
1938
+ annoPathVariant[ nameOrRef ]
1939
+ @finally{ this.attachLocation( $nameOrRef.variant ); }
1940
+ :
1941
+ '#' { this.reportUnexpectedSpace(); }
1942
+ Id_all['variant'] { $nameOrRef.variant = { path: [ this.identAst() ] }; }
1943
+ (
1944
+ '.' Id_all['variant'] { $nameOrRef.variant.path.push( this.identAst() ); }
1945
+ )*
1946
+ ;
1947
+
1948
+ annoStructValue returns[ default value = {} ] locals[ name = new XsnName() ]
1949
+ @finally{ $value.name = $name; }
1950
+ :
1951
+ annoPath[ $name, 'name' ] { this.attachLocation( $name ); }
1952
+ ( ':'
1953
+ $value=annoValue
1954
+ |
1955
+ { this.attachLocation( $value ); }
1956
+ )
1957
+ ;
1958
+
1959
+ annoValue returns[ default value = {} ]
1960
+ @finally{ this.attachLocation( $value ); }
1961
+ :
1962
+ $value=literalValue { this.adjustAnnoNumber( $value ); }
1963
+ |
1964
+ sign='+'/'-' Number
1965
+ { this.adjustAnnoNumber( $value = this.numberLiteral( $sign ) ); }
1966
+ |
1967
+ annoPath[ $value, 'annoref' ]
1968
+ |
1969
+ '{'
1970
+ {
1971
+ if (!this.dynamic_.arrayAnno) $value.$flatten = [];
1972
+ else { $value.struct = Object.create(null); $value.literal = 'struct'; }
1973
+ }
1974
+ (
1975
+ // TODO: complain about empty loop if top-level as before
1976
+ // TOOL TODO → allow `<cond=…> '}'` below where the condition rejects `}`
1977
+ // after `{` if top-level
1978
+ sub=annoStructValue
1979
+ {
1980
+ if ($value.$flatten) $value.$flatten.push( $sub );
1981
+ else this.addDef( $sub, $value, 'struct', null, $sub.name );
1982
+ }
1983
+ ( ',' | <exitLoop> )
1984
+ )*
1985
+ '}' // Do NOT use <setCondition=afterBrace> here!
1986
+ |
1987
+ '['<setCondition=ellipsisRestriction>
1988
+ { $value.val = []; $value.literal = 'array' }
1989
+ // no need for createArray() here, $value.location is set above
1990
+ (
1991
+ ( sub=annoValue { $value.val.push( $sub ) }
1992
+ |
1993
+ <cond=ellipsisRestriction> ellipsis='...'
1994
+ ( UP TO upTo=annoValue | { $upTo = undefined; } )
1995
+ { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1996
+ )
1997
+ ( ',' | <exitLoop> )
1998
+ )*
1999
+ ']'
2000
+ |
2001
+ '(' $value=condition ')' { $value.$tokenTexts = this.ruleTokensText(); }
2002
+ // TODO: (1,2,3) not supported, yet, only ((1,2,3))
2003
+ ;
2004
+
2005
+ // Shorten tokens array in this.gr() calls in top-level rules by reducing
2006
+ // intersection follow set:
2007
+ ignoredRule
2008
+ options{ excludeRuleFrom = Parser }
2009
+ :
2010
+ usingDeclaration ';'
2011
+ artifactDefOrExtend ';'
2012
+ ;
2013
+