@sap/cds-compiler 5.1.2 → 5.2.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 (51) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/cdsc.js +2 -2
  3. package/bin/cdshi.js +24 -17
  4. package/bin/cdsse.js +17 -18
  5. package/lib/api/main.js +19 -2
  6. package/lib/api/options.js +4 -1
  7. package/lib/base/builtins.js +1 -0
  8. package/lib/base/message-registry.js +16 -3
  9. package/lib/base/model.js +0 -10
  10. package/lib/checks/actionsFunctions.js +0 -12
  11. package/lib/checks/structuredAnnoExpressions.js +10 -14
  12. package/lib/compiler/assert-consistency.js +19 -11
  13. package/lib/compiler/builtins.js +1 -1
  14. package/lib/compiler/define.js +3 -3
  15. package/lib/compiler/extend.js +5 -5
  16. package/lib/compiler/populate.js +9 -9
  17. package/lib/compiler/propagator.js +1 -0
  18. package/lib/compiler/resolve.js +29 -34
  19. package/lib/compiler/shared.js +7 -8
  20. package/lib/compiler/tweak-assocs.js +155 -64
  21. package/lib/compiler/utils.js +1 -1
  22. package/lib/compiler/xpr-rewrite.js +4 -3
  23. package/lib/edm/annotations/genericTranslation.js +13 -9
  24. package/lib/edm/csn2edm.js +26 -2
  25. package/lib/edm/edm.js +23 -8
  26. package/lib/edm/edmInboundChecks.js +5 -7
  27. package/lib/edm/edmPreprocessor.js +43 -30
  28. package/lib/gen/BaseParser.js +720 -0
  29. package/lib/gen/CdlParser.js +4421 -0
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +4006 -4001
  33. package/lib/language/antlrParser.js +62 -0
  34. package/lib/language/genericAntlrParser.js +28 -0
  35. package/lib/model/csnUtils.js +2 -0
  36. package/lib/model/revealInternalProperties.js +2 -0
  37. package/lib/modelCompare/utils/filter.js +70 -42
  38. package/lib/optionProcessor.js +9 -3
  39. package/lib/parsers/AstBuildingParser.js +1172 -0
  40. package/lib/parsers/CdlGrammar.g4 +1940 -0
  41. package/lib/parsers/Lexer.js +239 -0
  42. package/lib/render/toCdl.js +23 -27
  43. package/lib/render/toSql.js +5 -5
  44. package/lib/transform/db/applyTransformations.js +54 -16
  45. package/lib/transform/draft/odata.js +10 -11
  46. package/lib/transform/effective/flattening.js +10 -14
  47. package/lib/transform/odata/flattening.js +42 -31
  48. package/lib/transform/odata/toFinalBaseType.js +7 -6
  49. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  50. package/package.json +2 -2
  51. package/share/messages/redirected-to-ambiguous.md +5 -4
@@ -0,0 +1,1940 @@
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=fileSection> namespaceDeclaration[ $source ]
46
+ | usingDeclaration[ $source ]
47
+ | artifactDefOrExtend[ $source ] <setCondition=fileSection>
48
+ )
49
+ ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
50
+ )*
51
+ EOF { this.docComment( null ); }
52
+ ;
53
+
54
+ artifactsBlock[ art, start = undefined ]
55
+ :
56
+ '{' { $art.artifacts = this.createDict( $start ); $art.extensions = []; }
57
+ (
58
+ artifactDefOrExtend[ $art ]
59
+ ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
60
+ )*
61
+ '}'<setCondition=afterBrace>
62
+ { this.finalizeDictOrArray( $art.artifacts ); }
63
+ ;
64
+
65
+ artifactDefOrExtend[ outer ] locals[ art = new XsnArtifact() ]
66
+ :
67
+ { $art.location = this.startLocation(); }
68
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
69
+ (
70
+ DEFINE?
71
+ ( serviceDef[ $art, $outer ]
72
+ | contextDef[ $art, $outer ]
73
+ | annotationDef[ $art, $outer ] // TODO: <cond> for syntax-unexpected-vocabulary
74
+ | typeDef[ $art, $outer ]
75
+ | aspectDef[ $art, $outer ]
76
+ | entityDef[ $art, $outer ]
77
+ | <hide> viewDef[ $art, $outer ]
78
+ | eventDef[ $art, $outer ]
79
+ | actionMainDef[ $art, $outer ]
80
+ | functionMainDef[ $art, $outer ]
81
+ )
82
+ // TODO: condition to disable ANNOTATE/EXTEND in EXTEND … WITH DEFINITION
83
+ |
84
+ 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
+ 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
+ ':' // s/th like <setCondition=allowDefaultOrCalc>
388
+ typeExpression[ $art ] // was elementType, with NOT? NULL / DEFAULT
389
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
390
+ )
391
+ ;
392
+
393
+ returnsSpec[ outer ] locals[ art = new XsnArtifact() ]
394
+ @finally{ this.attachLocation( $art ); }
395
+ :
396
+ RETURNS { $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
+ ':' // s/th like <setCondition=allowDefaultOrCalc>
432
+ typeExpression[ $art ] // was elementType, with NOT? NULL / DEFAULT
433
+ )?
434
+ (
435
+ // <cond=allowDefaultOrCalc>
436
+ '='
437
+ expr=expression { $art.value = $expr; }
438
+ ( STORED { $art.value.stored = this.valueWithLocation( true ); } )?
439
+ // TODO: why have `stored` as property of the value?
440
+ )?
441
+ { this.docComment( $art ); }
442
+ ( <cond=allowFinalAnnoAssign> annoAssignStd[ $art ] )*
443
+ ;
444
+
445
+ enumSymbolsBlock[ art ]
446
+ :
447
+ ENUM { $art.enum = this.createDict(); } '{'
448
+ ( enumSymbolDef[ $art ]
449
+ ( ';' | <exitLoop> )
450
+ )*
451
+ '}'<setCondition=afterBrace>
452
+ { this.finalizeDictOrArray( $art.enum ); }
453
+ ;
454
+
455
+ enumSymbolDef[ outer ] locals[ art = new XsnArtifact() ]
456
+ @finally{ this.attachLocation( $art ); }
457
+ :
458
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
459
+ Id['Enum']
460
+ { this.addDef( $art, $outer, 'enum', 'enum', this.identAst() ); }
461
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
462
+ ( '='
463
+ (
464
+ <priority> String
465
+ { $art.value = this.quotedLiteral(); }
466
+ |
467
+ <priority> Number
468
+ { $art.value = this.numberLiteral(); }
469
+ |
470
+ sign='+'/'-' Number
471
+ { $art.value = this.numberLiteral( $sign ); }
472
+ |
473
+ <hide> value=literalValue
474
+ { $art.value = $value; }
475
+ )
476
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
477
+ )?
478
+ ;
479
+
480
+ foreignKeysBlock[ art ]
481
+ :
482
+ '{' { $art.foreignKeys = this.createDict(); }
483
+ ( foreignKeyDef[ $art ]
484
+ ( ',' | <exitLoop> )
485
+ )*
486
+ '}' // DOES NOT SET afterBrace, because we allow annos after { …fks… }
487
+ { this.finalizeDictOrArray( $art.foreignKeys ); }
488
+ ;
489
+
490
+ foreignKeyDef[ outer ] locals[ art = new XsnArtifact(), name ]
491
+ @finally{ this.attachLocation($art); }
492
+ :
493
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
494
+ ref=simplePath[ 'ref' ] { $art.targetElement = $ref; }
495
+ ( AS name=Id['Key'] { $name = this.identAst(); }
496
+ | { this.classifyImplicitName( 'KeyImplicit', $ref ); $name = $ref.path; }
497
+ )
498
+ { this.addDef( $art, $outer, 'foreignKeys', 'key', $name ); }
499
+ // TODO: for a more uniform syntax, we'd allow:
500
+ // { this.docComment( $art ); } annoAssignMid[ $art ]*
501
+ ;
502
+
503
+ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
504
+ @finally{ this.attachLocation($art); }
505
+ :
506
+ Id['Mixin']
507
+ { this.addDef( $art, $outer, 'mixin', 'mixin', this.identAst() ); }
508
+ ':'
509
+ ( assoc=ASSOCIATION cardinality[ $art ]? TO
510
+ | assoc=COMPOSITION cardinality[ $art ]? OF
511
+ )
512
+ card=ONE/MANY? target=simplePath
513
+ { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
514
+ ON expr=condition { $art.on = $expr; }
515
+ ;
516
+
517
+ // Annotate and Extend: main definitions ----------------------------------------
518
+
519
+ annotateArtifact[ art, outer ]
520
+ @finally{ this.attachLocation( $art ); }
521
+ :
522
+ name=namePath[ 'Ext' ]
523
+ ( // direct element annotation:
524
+ ':' elemName=namePath[ 'ExtElement']
525
+ { this.addExtension( $art, $outer, 'annotate', $name, $elemName.path ); }
526
+ WITH?
527
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
528
+ annotateElementsBlock[ $art ]?
529
+ | // definition annotation
530
+ WITH?
531
+ // <cond=noRuleExitAfterWith>), or as rule option,
532
+ // this.noSemicolonHere() had the issues: DocComment, before `}`/EOF
533
+ { this.addExtension( $art, $outer, 'annotate', $name ); }
534
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
535
+ annotateParamsBlock[ $art ]?
536
+ (
537
+ annotateReturns[ $art ]
538
+ |
539
+ annotateElementsBlock[ $art ]?
540
+ annotateActionsBlock[ $art ]?
541
+ )
542
+ )
543
+ ;
544
+
545
+ extendArtifact[ art, outer ]
546
+ @finally{ this.attachLocation( $art ); }
547
+ :
548
+ name=namePath[ 'Ext' ]
549
+ ( // direct element annotation:
550
+ ':' elemName=namePath[ 'ExtElement']
551
+ { this.addExtension( $art, $outer, 'extend', $name, $elemName.path ); }
552
+ WITH?
553
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
554
+ (
555
+ elements=ELEMENTS? extendElementsBlock[ $art, $elements ]
556
+ |
557
+ enumSymbolsBlock[ $art ] // ENUM …, just define, no extend
558
+ |
559
+ typeNamedArgsList[ $art ]
560
+ )?
561
+ |
562
+ { this.addExtension( $art, $outer, 'extend', $name ); }
563
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
564
+ ( extendElementsBlock[ $art ]
565
+ actionsBlock[ $art ]?
566
+ )?
567
+ |
568
+ WITH
569
+ { this.addExtension( $art, $outer, 'extend', $name ); }
570
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
571
+ (
572
+ incl=simplePath { $art.includes = [ $incl ]; }
573
+ ( ',' incl=simplePath { $art.includes.push( $incl ); } )*
574
+ extendElementsBlock[ $art ]?
575
+ actionsBlock[ $art ]?
576
+ |
577
+ elements=ELEMENTS? extendElementsBlock[ $art, $elements ]
578
+ actionsBlock[ $art ]?
579
+ |
580
+ actionsBlock[ $art ]
581
+ |
582
+ enumSymbolsBlock[ $art ] // ENUM …, just define, no extend
583
+ |
584
+ typeNamedArgsList[ $art ]
585
+ |
586
+ COLUMNS selectItemsList[ $art, 'columns', this.lb() ]
587
+ |
588
+ DEFINITIONS artifactsBlock[ $art, this.lb() ]
589
+ )?
590
+ )
591
+ ;
592
+
593
+ extendService[ art, outer ]
594
+ @finally{ this.attachLocation( $art ); }
595
+ :
596
+ SERVICE { $art.expectedKind = this.valueWithLocation(); }
597
+ name=namePath[ 'ExtService' ]
598
+ { $art.name = $name; $outer.extensions.push( $art ); }
599
+ WITH?
600
+ // <cond=noRuleExitAfterWith>), or as rule option,
601
+ // this.noSemicolonHere() had the issues: DocComment, before `}`/EOF
602
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
603
+ artifactsBlock[ $art ]?
604
+ ;
605
+
606
+ extendContext[ art, outer ]
607
+ @finally{ this.attachLocation( $art ); }
608
+ :
609
+ CONTEXT { $art.expectedKind = this.valueWithLocation(); }
610
+ name=namePath[ 'ExtContext' ]
611
+ { $art.name = $name; $outer.extensions.push( $art ); }
612
+ WITH?
613
+ // <cond=noRuleExitAfterWith>), or as rule option,
614
+ // this.noSemicolonHere() had the issues: DocComment, before `}`/EOF
615
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
616
+ artifactsBlock[ $art ]?
617
+ ;
618
+
619
+ extendType[ art, outer ]
620
+ @finally{ this.attachLocation( $art ); }
621
+ :
622
+ TYPE { $art.expectedKind = this.valueWithLocation(); }
623
+ name=namePath[ 'Ext' ]
624
+ { $art.name = $name; $outer.extensions.push( $art ); }
625
+ (
626
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
627
+ extendElementsBlock[ $art ]?
628
+ |
629
+ WITH
630
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
631
+ (
632
+ incl=simplePath { $art.includes = [ $incl ]; }
633
+ ( ',' incl=simplePath { $art.includes.push( $incl ); } )*
634
+ extendElementsBlock[ $art ]?
635
+ |
636
+ elements=ELEMENTS? extendElementsBlock[ $art, $elements ]
637
+ |
638
+ enumSymbolsBlock[ $art ]
639
+ |
640
+ typeNamedArgsList[ $art ]
641
+ )?
642
+ )
643
+ ;
644
+
645
+ extendEntityOrAspect[ art, outer ]
646
+ @finally{ this.attachLocation( $art ); }
647
+ :
648
+ ASPECT/ENTITY { $art.expectedKind = this.valueWithLocation(); }
649
+ name=namePath[ 'Ext' ]
650
+ { $art.name = $name; $outer.extensions.push( $art ); }
651
+ (
652
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
653
+ |
654
+ WITH
655
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
656
+ (
657
+ incl=simplePath { $art.includes = [ $incl ]; }
658
+ ( ',' incl=simplePath { $art.includes.push( $incl ); } )*
659
+ )?
660
+ )
661
+ extendElementsBlock[ $art ]?
662
+ actionsBlock[ $art ]?
663
+ ;
664
+
665
+ extendProjection[ art, outer ]
666
+ @finally{ this.attachLocation( $art ); }
667
+ :
668
+ PROJECTION { $art.expectedKind = this.valueWithLocation(); }
669
+ name=namePath[ 'Ext' ]
670
+ { $art.name = $name; $outer.extensions.push( $art ); }
671
+ WITH ?
672
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
673
+ selectItemsList[ $art ]?
674
+ actionsBlock[ $art ]?
675
+ ;
676
+
677
+ // Extend and annotate on members: bound actions, parameters, elements ----------
678
+
679
+ annotateActionsBlock[ art ]
680
+ :
681
+ ACTIONS { $art.actions = this.createDict(); } '{'
682
+ ( annotateBoundAction[ $art ]
683
+ ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
684
+ )*
685
+ '}'<setCondition=afterBrace>
686
+ { this.finalizeExtensionsDict( $art.actions ); }
687
+ ;
688
+
689
+ annotateBoundAction[ outer ] locals[ art = new XsnArtifact() ]
690
+ @finally{ this.attachLocation( $art ); }
691
+ :
692
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
693
+ Id['ExtBoundAction']
694
+ { this.addDef( $art, $outer, 'actions', 'annotate', this.identAst() ); }
695
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
696
+ annotateParamsBlock[ $art ]?
697
+ annotateReturns[ $art ]?
698
+ ;
699
+
700
+ annotateParamsBlock[ art ]
701
+ :
702
+ '(' { $art.params = this.createDict(); }
703
+ ( annotateParam[ $art ]
704
+ ( ',' | <exitLoop> )
705
+ )*
706
+ ')'
707
+ { this.finalizeExtensionsDict( $art.params ); }
708
+ ;
709
+
710
+ annotateParam[ outer ] locals[ art = new XsnArtifact() ]
711
+ @finally{ this.attachLocation( $art ); }
712
+ :
713
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
714
+ Id['ExtParam']
715
+ { this.addDef( $art, $outer, 'params', 'annotate', this.identAst() ); }
716
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
717
+ // annotateElementsBlock[ $art ]? // TODO: why not
718
+ ;
719
+
720
+ annotateReturns[ outer ] locals[ art = new XsnArtifact() ]
721
+ @finally{ this.attachLocation( $art ); }
722
+ :
723
+ RETURNS { $outer.returns = $art; $art.kind = 'annotate'; } // TODO: tokenIndex necessary?
724
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
725
+ annotateElementsBlock[ $art ]?
726
+ ;
727
+
728
+ annotateElementsBlock[ art ]
729
+ :
730
+ '{' { $art.elements = this.createDict(); }
731
+ ( annotateElement[ $art ]
732
+ ( ';'
733
+ | <exitLoop>
734
+ | <repeatLoop=afterBrace, restrict=Id> { this.noAssignmentInSameLine(); }
735
+ )
736
+ )*
737
+ '}'<setCondition=afterBrace>
738
+ { this.finalizeExtensionsDict( $art.elements ); }
739
+ ;
740
+
741
+ annotateElement[ outer ] locals[ art = new XsnArtifact() ]
742
+ @finally{ this.attachLocation( $art ); }
743
+ :
744
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
745
+ Id['ExtElement']
746
+ { this.addDef( $art, $outer, 'elements', 'annotate', this.identAst() ); }
747
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
748
+ annotateElementsBlock[ $art ]?
749
+ ;
750
+
751
+ extendElementsBlock[ art, start = undefined ]
752
+ :
753
+ '{' { $art.elements = this.createDict( $start ); }
754
+ ( elementDefOrExtend[ $art ]
755
+ ( ';'
756
+ | <exitLoop>
757
+ | <repeatLoop=afterBrace, restrict=Id> { this.noAssignmentInSameLine(); } )
758
+ )*
759
+ '}'<setCondition=afterBrace>
760
+ { this.finalizeExtensionsDict( $art.elements ); }
761
+ ;
762
+
763
+ elementDefOrExtend[ outer ] locals[ art = new XsnArtifact() ]
764
+ :
765
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
766
+ (
767
+ elementDef[ $outer, $art ]
768
+ |
769
+ EXTEND
770
+ ( ELEMENT { $art.expectedKind = this.valueWithLocation(); } )?
771
+ Id['ExtElement']
772
+ { this.addDef( $art, $outer, 'elements', 'extend', this.identAst() ); }
773
+ (
774
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
775
+ extendElementsBlock[ $art ]?
776
+ |
777
+ WITH
778
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
779
+ (
780
+ elements=ELEMENTS? extendElementsBlock[ art, $elements ]
781
+ |
782
+ enumSymbolsBlock[ $art ] // ENUM …, just define, no extend
783
+ |
784
+ typeNamedArgsList[ $art ]
785
+ )?
786
+ )
787
+ )
788
+ ;
789
+
790
+ // Type expressions -------------------------------------------------------------
791
+
792
+ // in language.g4:
793
+ // - elementType: full stuff + enum + localized + null + default ( + calc ), managed-compo
794
+ // - in type def: typeSpecSemi: elementsBlock, : elementsBlock, assoc, localized
795
+ // - param def = default + typeSpec: elementsBlock + :typeSpecCont (no assoc!)
796
+ // - returns = typeSpecCont: elementsBlock + null, typeArray, typeOf (+ enum), ref+null+enum,
797
+ // - typeArray: +enum + null + typeof
798
+
799
+ // - select item: redirected to + type of / localized, ref, assoc (published assoc)
800
+ // - mixin: only assoc
801
+ // - cast: just refoptargs
802
+ // - extend: just the type args
803
+
804
+ // -> typeExpression, typeForColumn, typeForMixin, typeRefOptArgs
805
+
806
+ typeOrIncludesSpec[ art ]
807
+ :
808
+ elementsBlock[ $art ]
809
+ nullability[ $art ]?
810
+ |
811
+ ':'
812
+ (
813
+ typeExpression[ $art ]
814
+ |
815
+ <priority>
816
+ ref=simplePath { $art.type = $ref; }
817
+ (
818
+ ( typeRefOptArgs[ $art ]<atAltStart> )?
819
+ nullability[ $art ]?
820
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
821
+ enumSymbolsBlock[ $art ]?
822
+ nullabilityAndDefault[ $art ]?
823
+ |
824
+ { $art.includes = [ $art.type ]; delete $art.type; }
825
+ (
826
+ ','
827
+ (
828
+ ref=simplePath { $art.includes.push( $ref ); }
829
+ ( ',' | <exitLoop> )
830
+ )*
831
+ )?
832
+ elementsBlock[ $art ]
833
+ nullability[ $art ]?
834
+ )
835
+ )
836
+ ;
837
+
838
+ typeExpression[ art ]
839
+ :
840
+ elementsBlock[ $art ]
841
+ nullability[ $art ]?
842
+ |
843
+ ( typeRefOptArgs[ $art ] | typeTypeOf[ $art ] )
844
+ nullability[ $art ]?
845
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
846
+ enumSymbolsBlock[ $art ]?
847
+ nullabilityAndDefault[ $art ]?
848
+ |
849
+ LOCALIZED { $art.localized = this.valueWithLocation( true ); }
850
+ typeRefOptArgs[ $art ]
851
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
852
+ nullabilityAndDefault[ $art ]?
853
+ |
854
+ assoc=ASSOCIATION cardinality[ $art ]? TO card=ONE/MANY?
855
+ target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
856
+ ( ON cond=condition { $art.on = $cond; }
857
+ | foreignKeysBlock[ $art ]? nullabilityAndDefault[ $art ]?
858
+ )
859
+ |
860
+ assoc=COMPOSITION cardinality[ $art ]? OF card=ONE/MANY?
861
+ (
862
+ target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
863
+ ( ON cond=condition { $art.on = $cond; }
864
+ | foreignKeysBlock[ $art ]? nullabilityAndDefault[ $art ]?
865
+ )
866
+ |
867
+ // TODO: really do SyntaxOnly/GrammarAmbiguities/CompositionOfMany.cds ?
868
+ { $target = {}; this.setAssocAndComposition( $art, $assoc, $card, $target ); }
869
+ elementsBlock[ $target ]
870
+ { $target.location = $target.elements[Symbol.for('cds.$location')]; }
871
+ )
872
+ |
873
+ ( ARRAY OF { $art.items = { location: this.locationOfPrevTokens( 2 ) }; }
874
+ | MANY { $art.items = { location: this.lb().location }; }
875
+ )
876
+ (
877
+ elementsBlock[ $art.items ]
878
+ nullability[ $art.items ]?
879
+ |
880
+ ( typeRefOptArgs[ $art.items ] | typeTypeOf[ $art.items ] )
881
+ nullability[ $art.items ]?
882
+ { this.docComment( $art.items ); } annoAssignStd[ $art.items ]*
883
+ enumSymbolsBlock[ $art.items ]?
884
+ nullability[ $art.items ]?
885
+ )
886
+ ;
887
+
888
+ typeTypeOf[ art ] locals[ location ]
889
+ :
890
+ TYPE OF { location = this.locationOfPrevTokens( 2 ); }
891
+ type=simplePath[ 'ref' ] { $art.type = $type; }
892
+ (
893
+ { $type.scope = 'typeOf'; $type.path.unshift( { id: 'type of', location } ); }
894
+ |
895
+ ':' { $type.scope = $type.path.length; }
896
+ // If we have too much time, we could set the category of the simple path
897
+ // before to 'artref'; but why use TYPE OF before `Art:elem` anyway?
898
+ Id_all['ref'] { $type.path.push( this.identAst() ); }
899
+ (
900
+ '.' Id_all['ref'] { $type.path.push( this.identAst() ); }
901
+ )*
902
+ )
903
+ ;
904
+
905
+ typeRefOptArgs[ art ] locals[ type = $art.type ]
906
+ :
907
+ type=simplePath { $art.type = $type; }
908
+ (<altRuleStart>)
909
+ (
910
+ ':' { $type.scope = $type.path.length; }
911
+ Id_all['ref'] { $type.path.push( this.identAst() ); }
912
+ (
913
+ '.' Id_all['ref'] { $type.path.push( this.identAst() ); }
914
+ )*
915
+ |
916
+ open='('
917
+ (
918
+ Number { $art.$typeArgs = this.createArray( $open ); }
919
+ { $art.$typeArgs.push( this.unsignedIntegerLiteral() ); }
920
+ (
921
+ ','
922
+ ( Number
923
+ { $art.$typeArgs.push( this.unsignedIntegerLiteral() ); }
924
+ | tok=VARIABLE/FLOATING
925
+ { $art.$typeArgs.push( { literal: 'string', val: $tok.keyword, location: $tok.location } ); }
926
+ | <exitLoop>
927
+ )
928
+ )* // TODO: really as loop?
929
+ |
930
+ { $art.$typeArgs = this.createDict( $open ); }
931
+ (
932
+ typeNamedArg[ $art ]
933
+ ( ',' | <exitLoop> )
934
+ )+ // TODO: really as loop?
935
+ )
936
+ ')' { this.finalizeDictOrArray( $art.$typeArgs ); }
937
+ )?
938
+ ;
939
+
940
+ typeNamedArgsList[ art ]
941
+ :
942
+ '(' { $art.$typeArgs = this.createDict(); }
943
+ (
944
+ typeNamedArg[ $art ]
945
+ ( ',' | <exitLoop> )
946
+ )* // TODO: really as loop?
947
+ ')' { this.finalizeDictOrArray( $art.$typeArgs ); }
948
+ ;
949
+
950
+ typeNamedArg[ art ]
951
+ :
952
+ name=Id['typeparamname']
953
+ ':'
954
+ ( Number
955
+ { this.setTypeFacet( $art, $name, this.unsignedIntegerLiteral() ); }
956
+ | tok=VARIABLE/FLOATING
957
+ { this.setTypeFacet( $art, $name, { literal: 'string', val: $tok.keyword, location: $tok.location } ); }
958
+ )
959
+ ;
960
+
961
+ cardinality[ art ] locals[ card = {} ]
962
+ @finally{ $art.cardinality = this.attachLocation($card); }
963
+ :
964
+ '['
965
+ (
966
+ '*' { $card.targetMax = this.valueWithLocation(); }
967
+ ( ',' targetCardinality[ $card ] )?
968
+ |
969
+ Number { $card.targetMax = this.unsignedIntegerLiteral(); }
970
+ (
971
+ ',' targetCardinality[ $card ]
972
+ |
973
+ targetCardinality[ $card, true ]<atAltStart>
974
+ )?
975
+ |
976
+ { $card.targetMax = this.valueWithLocation( '*' ); }
977
+ ) // TODO: really optional?
978
+ ']'
979
+ ;
980
+
981
+ targetCardinality[ card, atAlt = false ]
982
+ :
983
+ // TODO TOOL: the following action should not be executed when called
984
+ // <atAltStart> → we can then remove param `atAlt`
985
+ { if (!$atAlt) $card.sourceMax = $card.targetMax; }
986
+ (
987
+ '*' { $card.targetMax = this.valueWithLocation(); }
988
+ |
989
+ Number { $card.targetMax = this.unsignedIntegerLiteral(); }
990
+ ( <altRuleStart> ) // TODO TOOL: robust error when moved to after '('
991
+ (
992
+ '..' { $card.targetMin = $card.targetMax; }
993
+ ( '*' { $card.targetMax = this.valueWithLocation(); }
994
+ | Number { $card.targetMax = this.unsignedIntegerLiteral(); }
995
+ )
996
+ )?
997
+ )
998
+ ;
999
+
1000
+ nullabilityAndDefault[ art ]
1001
+ :
1002
+ nullability[ $art ]
1003
+ ( DEFAULT expr=expression { $art.default = $expr; } )?
1004
+ |
1005
+ DEFAULT expr=expression { $art.default = $expr; }
1006
+ nullability[ $art ]?
1007
+ // TODO TOOL: when `followUnion` does not contain `Id`, `RuleEnd_` does not
1008
+ // need to induce prediction (here for `default`).
1009
+ ;
1010
+
1011
+ nullability[ art ]
1012
+ :
1013
+ NULL { this.setNullability( $art, false ); }
1014
+ |
1015
+ NOT NULL { this.setNullability( $art, true, this.locationOfPrevTokens( 2 ) ); }
1016
+ ;
1017
+
1018
+ // Queries: projections and SELECTs ---------------------------------------------
1019
+
1020
+ queryEOF returns[ query ]
1021
+ :
1022
+ $query=queryExpression ';'? EOF
1023
+ ;
1024
+
1025
+ projectionSpec returns[ default query = {} ]
1026
+ @finally{ this.attachLocation($query); }
1027
+ :
1028
+ // TODO, currently just with simple ref
1029
+ PROJECTION { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1030
+ ON
1031
+ // TODO: this <setCondition=afterBrace> is extremely strange... v6 forbid.
1032
+ // Deliberately set this via action.
1033
+ tab=fromRefWithOptAlias { this.afterBrace(); }<always>
1034
+ { $query.from = tab; }
1035
+ selectItemsList[ $query ]?
1036
+ excludingClause[ $query ]?
1037
+ ;
1038
+
1039
+ queryExpression returns[ default expr = {} ] locals[ op, quantifier ]
1040
+ @finally{ this.attachLocation( $expr ); }
1041
+ :
1042
+ ( '(' queryExpression[ ...$ ] ')' { this.surroundByParens( $expr ); }
1043
+ | $expr=selectQuery
1044
+ )
1045
+ (<altRuleStart>)
1046
+ (
1047
+ // See also `taggedIfQuery`/`queryOps` in AstBuildingParser.js
1048
+ ( ( <prec=4> INTERSECT { $op = this.valueWithLocation(); }
1049
+ | <prec=2> EXCEPT/MINUS { $op = this.valueWithLocation(); }
1050
+ )
1051
+ ( DISTINCT { $quantifier = this.valueWithLocation(); } )?
1052
+ | <prec=2> UNION { $op = this.valueWithLocation(); }
1053
+ ( DISTINCT/ALL { $quantifier = this.valueWithLocation(); } )?
1054
+ )
1055
+ query=queryExpression
1056
+ // with same op/quantifier: make left-assoc binary to nary:
1057
+ { if ($expr.$parens || $op.val !== $expr.op.val || $quantifier?.val !== $expr.quantifier?.val) $expr = { op, args: [$expr], quantifier }; }
1058
+ { $quantifier = undefined; }
1059
+ { $expr.args.push( $query ); this.attachLocation( $expr ); }
1060
+ )*
1061
+ ( <prec=0, postfix>
1062
+ // TODO: the following action is a bit stupid, but compatible to ANTLR-based
1063
+ // parser:
1064
+ { if ($expr.$parens) { this.attachLocation( $expr ); $expr = { op: this.valueWithLocation( '$query', this.la() ), args: [ $expr ] }; } }
1065
+ orderByLimitOffset[ $expr ]
1066
+ )?
1067
+ ;
1068
+
1069
+ selectQuery returns[ default query = {} ]
1070
+ @finally{ this.attachLocation($query); }
1071
+ :
1072
+ SELECT { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1073
+ (
1074
+ FROM querySource[ $query ]
1075
+ (
1076
+ MIXIN '{' { $query.mixin = this.createDict(); }
1077
+ (
1078
+ mixinElementDef[ $query ]
1079
+ ( ';' | <exitLoop> )
1080
+ )*
1081
+ '}' { this.finalizeDictOrArray( $query.mixin ); }
1082
+ INTO
1083
+ )?
1084
+ ( ALL/DISTINCT { $query.quantifier = this.valueWithLocation(); } )?
1085
+ // TODO: or directly after SELECT ?
1086
+ selectItemsList[ $query ]?
1087
+ excludingClause[ $query ]?
1088
+ |
1089
+ ( ALL/DISTINCT { $query.quantifier = this.valueWithLocation(); } )?
1090
+ ( '*' { $query.columns = [ this.valueWithLocation() ]; }
1091
+ | selectItemDef[ ($query.columns = []) ]
1092
+ )
1093
+ ( ','
1094
+ ( '*' { $query.columns.push( this.valueWithLocation() ); }
1095
+ | selectItemDef[ $query.columns ]
1096
+ )
1097
+ )*
1098
+ FROM querySource[ $query ]
1099
+ )
1100
+ whereGroupByHaving[ $query ]?
1101
+ ;
1102
+
1103
+ querySource[ query ]
1104
+ @after { this.attachLocation($query.from); }
1105
+ :
1106
+ tab=tableExpression { $query.from = $tab; }
1107
+ (
1108
+ { const { location } = this.la();
1109
+ $query.from = { op: { val: 'join', location }, join: { val: 'cross', location }, args: [$tab] };
1110
+ }
1111
+ ( ',' tab=tableExpression { $query.from.args.push( $tab ); } )+
1112
+ )?
1113
+ ;
1114
+
1115
+ tableExpression returns[ default expr ] // TableOrJoin
1116
+ :
1117
+ ( tableOrQueryParens[ ...$ ]
1118
+ (<altRuleStart> { $expr = this.taggedIfQuery( $expr ); } )
1119
+ | fromRefWithOptAlias[ ...$ ]
1120
+ )
1121
+ (
1122
+ join=CROSS JOIN
1123
+ { if ($expr?.join?.val !== 'cross' || $expr.$parens) $expr = { op: this.valueWithLocation(), join: this.valueWithLocation( undefined, $join ), args: [ $expr ] }; }
1124
+ ( tab=tableOrQueryParens { $expr.args.push( this.taggedIfQuery( $tab ) ); }
1125
+ { this.attachLocation( $expr ); }
1126
+ | tab=fromRefWithOptAlias { $expr.args.push( $tab ); }
1127
+ { this.attachLocation( $expr ); }
1128
+ )
1129
+ |
1130
+ ( ( join=INNER | join=LEFT/RIGHT/FULL OUTER? )
1131
+ card=joinCardinality?
1132
+ | { $join = undefined; } // TODO TOOL: action is lost
1133
+ )
1134
+ JOIN
1135
+ { $expr = { op: this.valueWithLocation(), join: this.valueWithLocation( $join?.keyword || 'inner', $join ), args: [ $expr ] }; if ($card) $expr.cardinality = $card; $card = undefined; }
1136
+ { $join = undefined; } // TODO TOOL bug workaround, see above
1137
+ tab=tableExpression
1138
+ { $expr.args.push( $tab ); this.attachLocation( $expr ); }
1139
+ ON cond=condition { $expr.on = $cond; }
1140
+ { this.attachLocation( $expr ); }
1141
+ )*
1142
+ ;
1143
+
1144
+ tableOrQueryParens returns[ default expr ]
1145
+ :
1146
+ '('
1147
+ ( <priority> tableOrQueryParens[ ...$ ]
1148
+ ( <prec=-2, postfix> tableExpression[ ...$ ]<atAltStart>
1149
+ | <prec=-1, postfix> queryExpression[ ...$ ]<atAltStart>
1150
+ )?
1151
+ | <prec=-2> tableExpression[ ...$ ]
1152
+ | <prec=-1> queryExpression[ ...$ ]
1153
+ )
1154
+ ')'
1155
+ { this.surroundByParens( $expr ); }
1156
+ ( <prec=-2, postfix=once> AS Id['FromAlias']
1157
+ <setCondition=setPrecInCallingRule>
1158
+ { $expr = this.taggedIfQuery( $expr ); $expr.name = this.identAst(); }
1159
+ | <hide, cond=tableAlias> Id_restricted['FromAlias']
1160
+ <setCondition=setPrecInCallingRule>
1161
+ { $expr = this.taggedIfQuery( $expr ); $expr.name = this.fragileAlias(); }
1162
+ |
1163
+ // <setCondition=setPrecInCallingRule> // TODO TOOL: allow this
1164
+ { this.setPrecInCallingRule(); } // workaround
1165
+ )
1166
+ ; // change #10799 for ANTLR-based parser
1167
+
1168
+ joinCardinality returns[ sourceMax, targetMax ]
1169
+ @finally{ this.attachLocation( $ ); }
1170
+ :
1171
+ (
1172
+ ( EXACT { $.sourceMin = this.valueWithLocation( 1 ); } )?
1173
+ ONE { $sourceMax = this.valueWithLocation( 1 ); }
1174
+ |
1175
+ MANY { $sourceMax = this.valueWithLocation( '*' ); }
1176
+ )
1177
+ TO
1178
+ (
1179
+ ( EXACT { $.targetMin = this.valueWithLocation( 1 ); } )?
1180
+ ONE { $targetMax = this.valueWithLocation( 1 ); }
1181
+ |
1182
+ MANY { $targetMax = this.valueWithLocation( '*' ); }
1183
+ )
1184
+ ;
1185
+
1186
+ fromRefWithOptAlias returns[ default expr = { path: [] } ]
1187
+ @finally{ this.attachLocation( $expr ); }
1188
+ :
1189
+ fromPath[ $expr, 'artref' ]
1190
+ (
1191
+ ':' { if (!$expr.scope) $expr.scope = $expr.path.length; else {
1192
+ this.warning( 'syntax-invalid-path-separator', this.lb(),
1193
+ { '#': 'colon', code: ':', newcode: '.' } );
1194
+ } }
1195
+ fromPath[ $expr, 'ref']
1196
+ )?
1197
+ ( AS Id['FromAlias'] { $expr.name = this.identAst(); }
1198
+ | <hide, cond=tableAlias> Id_restricted['FromAlias']
1199
+ { $expr.name = this.fragileAlias(); }
1200
+ | { this.classifyImplicitName( $expr.scope ? 'FromElemImplicit' : 'FromImplicit', $expr ); }
1201
+ )
1202
+ ;
1203
+
1204
+ fromPath[ table, category ] locals[ pathItem ]
1205
+ @finally{ this.attachLocation( $table.path ); }
1206
+ :
1207
+ Id[ $category ] { $table.path.push( $pathItem = this.identAst() ); }
1208
+ ( fromArgumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
1209
+ (
1210
+ '.' { if (!$pathItem && !$table.scope) {
1211
+ $table.scope = $table.path.length; $category = 'ref';
1212
+ this.warning( 'syntax-invalid-path-separator', this.lb(),
1213
+ { '#': 'dot', code: '.', newcode: ':' } );
1214
+ } }
1215
+ Id_all[ $category ] { $table.path.push( $pathItem = this.identAst() ); }
1216
+ ( fromArgumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
1217
+ )*
1218
+ ;
1219
+
1220
+ fromArgumentsAndFilter[ pathStep ]
1221
+ options{ minTokensMatched = 1 }
1222
+ :
1223
+ (
1224
+ '(' { $pathStep.args = this.createDict(); $pathStep.$syntax = ':'; }
1225
+ ( fromNamedArgument[ ...$ ]
1226
+ ( ',' | <exitLoop> )
1227
+ )+
1228
+ ')'
1229
+ )?
1230
+ cardinalityAndFilter[ ...$ ]?
1231
+ ;
1232
+
1233
+ fromNamedArgument[ pathStep ]
1234
+ :
1235
+ name=Id['paramname'] ':' expr=expression
1236
+ { this.addDef( $expr, $pathStep, 'args', 0, this.identAst( $name ) ); }
1237
+ // TODO: or add argument directly after having parsed the name? (for CC)
1238
+ ;
1239
+
1240
+ cardinalityAndFilter[ pathStep ]
1241
+ :
1242
+ '['
1243
+ ( <cond=beforeColon> Number // TODO: only allow `1`?
1244
+ { $pathStep.cardinality = { targetMax: this.unsignedIntegerLiteral(), location: this.lb().location }; }
1245
+ ':'
1246
+ )?
1247
+ filterClauses[ $pathStep ]
1248
+ // TODO: why not allowing all clauses to be optional? (then inline rule `filterClauses`)
1249
+ ']'
1250
+ ;
1251
+
1252
+ filterClauses[ pathStep ]
1253
+ options{ minTokensMatched = 1 }
1254
+ :
1255
+ ( WHERE?
1256
+ // compare GROUP/HAVING/ORDER/LIMIT w/o prediction (reserved WHERE anyway):
1257
+ // <restrict=Id> // TODO TOOL: not yet supported here
1258
+ // BTW, why? not necessary anymore…
1259
+ cond=condition { $pathStep.where = $cond; }
1260
+ )?
1261
+ ( <hide>
1262
+ { this.csnParseOnly('syntax-unexpected-sql-clause', 1, { keyword: 'group by' }); }
1263
+ groupByClause[ $pathStep ]
1264
+ )?
1265
+ ( <hide> HAVING
1266
+ { this.csnParseOnly('syntax-unexpected-sql-clause', -1, { keyword: 'having' }); }
1267
+ cond=condition { $pathStep.having = $cond; }
1268
+ )?
1269
+ ( <hide>
1270
+ { if (this.lk() === 'limit') this.csnParseOnly('syntax-unexpected-sql-clause', 0, { keyword: 'limit' } ); else this.csnParseOnly('syntax-unexpected-sql-clause', 1, { keyword: 'order by' } ); }
1271
+ // I do not care that there is now only one error msg for both ORDER BY … LIMIT …
1272
+ orderByLimitOffset[ $pathStep ]
1273
+ )?
1274
+ ;
1275
+
1276
+ excludingClause[ query ]
1277
+ :
1278
+ // syntax is less than ideal - EXCLUDING is only useful for `*` - with
1279
+ // this syntax, people wonder what happens with explicit select items
1280
+ EXCLUDING '{' { $query.excludingDict = this.createDict(); }
1281
+ // TODO: better move '{' to after action, but → diff to ANTLR-based parser
1282
+ (
1283
+ Id_all['ref'] // TODO: different category?
1284
+ { this.addDef( { location: this.lb().location }, $query, 'excludingDict', '', this.identAst() ); }
1285
+ ( ',' | <exitLoop> )
1286
+ )+
1287
+ '}'<setCondition=afterBrace>
1288
+ { this.finalizeDictOrArray( $query.excludingDict ); }
1289
+ ;
1290
+
1291
+ selectItemsList[ query, clause = 'columns', start = undefined ]
1292
+ :
1293
+ '{' { $query[$clause] = this.createArray( $start ); }
1294
+ (
1295
+ ( '*' { $query[$clause].push( this.valueWithLocation() ); }
1296
+ | selectItemDef[ $query[$clause] ]
1297
+ )
1298
+ ( ',' | <exitLoop> )
1299
+ )*
1300
+ '}'<setCondition=afterBrace>
1301
+ { this.finalizeDictOrArray( $query[$clause] ); }
1302
+ ;
1303
+
1304
+ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1305
+ @finally{ this.attachLocation( $art ); }
1306
+ :
1307
+ { $columns.push( $art ); } // TODO: probably too early
1308
+ { this.docComment( $art ); } annoAssignCol[ $art ]*
1309
+ // TODO: <cond> for VIRTUAL/KEY only top-level
1310
+ ( VIRTUAL { $art.virtual = this.valueWithLocation( true ); } )?
1311
+ ( KEY { $art.key = this.valueWithLocation( true ); } )?
1312
+ (
1313
+ expr=expression { $art.value = $expr; }
1314
+ ( as=AS Id['ItemAlias'] { $art.name = this.identAst(); }
1315
+ | Id_restricted['ItemAlias'] { $art.name = this.fragileAlias( true ); }
1316
+ | { $alias = this.classifyImplicitName( 'ItemImplicit', $expr ); }
1317
+ )
1318
+ // TODO: <cond> for expand/inline only with ref,
1319
+ (
1320
+ { this.reportExpandInline( $art, false ); }
1321
+ selectItemsList[ $art, 'expand' ]
1322
+ excludingClause[ $art ]?
1323
+ |
1324
+ '.' { this.reportExpandInline( $art, $as || true ); }
1325
+ { if ($alias) $alias.token.parsed = $alias.parsed; }
1326
+ (
1327
+ selectItemsList[ $art, 'inline' ]
1328
+ excludingClause[ $art ]?
1329
+ |
1330
+ '*' { $art.inline = [ this.valueWithLocation() ]; }
1331
+ )
1332
+ )?
1333
+ |
1334
+ selectItemsList[ $art, 'expand' ]
1335
+ excludingClause[ $art ]?
1336
+ AS Id['ItemAlias'] { $art.name = this.identAst(); }
1337
+ )
1338
+ { this.docComment( $art ); } annoAssignMid[ $art ]*
1339
+ (
1340
+ ':'
1341
+ (
1342
+ ( typeTypeOf[ $art ]
1343
+ | ( LOCALIZED { $art.localized = this.valueWithLocation( true ); } )?
1344
+ typeRefOptArgs[ $art ]
1345
+ )
1346
+ |
1347
+ REDIRECTED TO target=simplePath { $art.target = $target; }
1348
+ ( ON cond=condition { $art.on = $cond; }
1349
+ | foreignKeysBlock[ $art ]
1350
+ )?
1351
+ |
1352
+ // TODO: condition for this
1353
+ ( assoc=ASSOCIATION { this.associationInSelectItem( $art ); }
1354
+ cardinality[ $art ]? TO
1355
+ | assoc=COMPOSITION { this.associationInSelectItem( $art ); }
1356
+ cardinality[ $art ]? OF
1357
+ )
1358
+ // { this.classifyImplicitName( 'ItemAssoc', $art.value ); } TODO: do we need this?
1359
+ card=ONE/MANY? target=simplePath
1360
+ { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
1361
+ ON expr=condition { $art.on = $expr; }
1362
+ )
1363
+ // TODO: no nullability here ?
1364
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
1365
+ )?
1366
+ ;
1367
+
1368
+ whereGroupByHaving[ query ]
1369
+ options{ minTokensMatched = 1 }
1370
+ :
1371
+ ( WHERE cond=condition { $query.where = $cond; } )?
1372
+ groupByClause[ ...$ ]?
1373
+ ( HAVING cond=condition { $query.having = $cond; } )?
1374
+ ;
1375
+
1376
+ groupByClause[ query ]
1377
+ :
1378
+ GROUP BY expr=expression { $query.groupBy = [ $expr ]; }
1379
+ ( ',' expr=expression { $query.groupBy.push( $expr ); } )*
1380
+ ;
1381
+
1382
+ orderByLimitOffset[ query ]
1383
+ options{ minTokensMatched = 1 }
1384
+ :
1385
+ orderByClause[ ...$ ]?
1386
+ ( LIMIT expr=expression { $query.limit = { rows: $expr }; }
1387
+ ( OFFSET expr=expression { $query.limit.offset = $expr; } )?
1388
+ )?
1389
+ ;
1390
+
1391
+ orderByClause[ query ]
1392
+ :
1393
+ ORDER BY expr=orderByExpression { $query.orderBy = [ $expr ]; }
1394
+ ( ',' expr=orderByExpression { $query.orderBy.push( $expr ); } )*
1395
+ ;
1396
+
1397
+ orderByExpression returns[ default expr ]
1398
+ :
1399
+ expression[ ...$ ]
1400
+ ( ASC/DESC { $expr.sort = this.valueWithLocation(); } )?
1401
+ ( NULLS FIRST/LAST { $expr.nulls = this.valueWithLocation(); } )?
1402
+ ;
1403
+
1404
+ // Conditions and expressions ---------------------------------------------------
1405
+
1406
+ conditionEOF returns[ cond ]
1407
+ :
1408
+ $cond=expression EOF
1409
+ ;
1410
+
1411
+ condition returns[ default expr ]
1412
+ :
1413
+ expression[ ...$ ]
1414
+ ;
1415
+
1416
+ // TODO TOOL: sort rules if a rule with <altRuleStart> uses rule with <altRuleStart>
1417
+ // at least issue error if no user-sorted
1418
+ valuePath returns[ default expr = { path: [] } ] locals[ pathItem ]
1419
+ @finally{ this.attachLocation( $expr ); }
1420
+ :
1421
+ Id['ref'] { $expr.path.push( $pathItem = this.identAst() ); }
1422
+ ( argumentsAndFilter[ $pathItem ] )?
1423
+ (<altRuleStart>)
1424
+ (
1425
+ <cond=isDotForPath> '.'
1426
+ Id_all['ref'] { $expr.path.push( $pathItem = this.identAst() ); }
1427
+ ( argumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
1428
+ )*
1429
+ ;
1430
+
1431
+ // TODO: ? params
1432
+ expression returns[ default expr ]
1433
+ //@finally{ if (!$expr?.$parens) this.attachLocation( $expr ); }
1434
+ :
1435
+ (
1436
+ expressionOrQueryParens[ ...$ ]
1437
+ (<altRuleStart> { $expr = this.taggedIfQuery( $expr ); })
1438
+ |
1439
+ literalValue[ ...$ ]
1440
+ |
1441
+ ':' { this.reportUnexpectedSpace(); }
1442
+ (
1443
+ Id_all['paramref']
1444
+ { $expr = { path: [ this.identAst() ], location: this.startLocation(), scope: 'param' }; }
1445
+ ( <cond=isDotForPath> valuePath[ ...$ ]<atAltStart>
1446
+ { $expr = this.valuePathAst( $expr ); }
1447
+ | { this.attachLocation( $expr ); }
1448
+ )
1449
+ |
1450
+ <hide> Number // TODO: as user condition
1451
+ { this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'positional', code: ':' + this.lb().text } ); }
1452
+ { $expr = this.attachLocation({ param: this.unsignedIntegerLiteral(), scope: 'param' }); }
1453
+ )
1454
+ |
1455
+ <hide> '?' // TODO: do as user condition
1456
+ {this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'dynamic', code: '?' } );
1457
+ }
1458
+ { $expr = this.attachLocation({ param: this.valueWithLocation(), scope: 'param' }); }
1459
+ |
1460
+ e=valuePath { $expr = this.valuePathAst( $e ); }
1461
+ (
1462
+ OVER { this.pushXprToken( $expr.suffix = [] ); }
1463
+ e=overClause[ $expr.suffix ]
1464
+ )?
1465
+ { this.attachLocation( $expr ); }
1466
+ |
1467
+ newAndValuePath[ ...$ ]
1468
+ |
1469
+ EXISTS { $expr = this.applyOpToken(); }
1470
+ ( open='(' e=queryExpression ')'
1471
+ { $expr.args.push( this.taggedIfQuery( this.surroundByParens( $e, $open ) ) ); }
1472
+ { this.attachLocation( $expr ); }
1473
+ | e=valuePath { $e = this.valuePathAst( $e ); $e.$expected = 'exists'; }
1474
+ // TODO: re-check whether to really set $expected in parser
1475
+ { $expr.args.push( $e ); this.attachLocation( $expr ); }
1476
+ | <hide> '?' // TODO: do as user condition
1477
+ {this.csnParseOnly( 'syntax-unsupported-param', -1, { '#': 'dynamic', code: '?' } ); }
1478
+ { $expr.args.push( { param: this.valueWithLocation(), scope: 'param' } ); this.attachLocation( $expr ); }
1479
+ )
1480
+ |
1481
+ caseExpression[ ...$ ]
1482
+ |
1483
+ castFunction[ ...$ ]
1484
+ |
1485
+ ( <prec=30, prefix> '+'/'-' { $expr = this.applyOpToken(); }
1486
+ | <prec=8, prefix> NOT { $expr = this.applyOpToken(); }
1487
+ )
1488
+ e=expression { $expr = this.signedExpression( $expr, $e ); }
1489
+ )
1490
+ // binary + postfix
1491
+ ( (
1492
+ ( <prec=24> '*'/'/' { $expr = this.applyOpToken( $expr, 'nary' ); }
1493
+ | <prec=22> '+'/'-' { $expr = this.applyOpToken( $expr, 'nary' ); }
1494
+ | <prec=20> '||' { $expr = this.applyOpToken( $expr, 'nary' ); }
1495
+ | <prec=4> AND { $expr = this.applyOpToken( $expr, 'nary' ); }
1496
+ | <prec=2> OR { $expr = this.applyOpToken( $expr, 'nary' ); }
1497
+ | <prec=0> '?' { $expr = this.applyOpToken( $expr, '?:' ); }
1498
+ e=expression { $expr.args.push( $e ); }
1499
+ ':' { this.pushXprToken( $expr ); }
1500
+ // -> createXprForOp vs createAstForOp
1501
+
1502
+ | <prec=10, assoc=none> '='/'<>'/'>'/'>='/'<'/'<='/'!='
1503
+ { $expr = this.applyOpToken( $expr ); }
1504
+ ( ANY/SOME/ALL { this.pushXprToken( $expr ); } )?
1505
+ )
1506
+ e=expression { $expr.args.push( $e ); }
1507
+
1508
+ | <prec=10, postfix=once> IS { $expr = this.applyOpToken( $expr ); }
1509
+ ( NOT { this.pushXprToken( $expr ); } )?
1510
+ NULL { this.pushXprToken( $expr ); }
1511
+ | ( <cond=isNegatedRelation> NOT { $expr = this.applyOpToken( $expr ); }
1512
+ // TODO: condition, because there might be NOT NULL after DEFAULT expression
1513
+ | <prec=10, postfix=once>
1514
+ { $expr = { op: { val: 'ixpr', location: this.la().location }, args: [ $expr ] }; }
1515
+ )
1516
+ (
1517
+ BETWEEN { this.pushXprToken( $expr ); }
1518
+ e=expression { $expr.args?.push( $e ); }
1519
+ AND { this.pushXprToken( $expr ); }
1520
+ e=expression { $expr.args?.push( $e ); }
1521
+ | IN { this.pushXprToken( $expr ); }
1522
+ e=expression { $expr.args?.push( this.secureParens( $e ) ); }
1523
+ | LIKE { this.pushXprToken( $expr ); }
1524
+ e=expression { $expr.args?.push( $e ); }
1525
+ ( ESCAPE { this.pushXprToken( $expr ); }
1526
+ e=expression { $expr.args?.push( $e ); }
1527
+ )?
1528
+ )
1529
+ )
1530
+ { this.attachLocation( $expr ); }
1531
+ )*
1532
+ ;
1533
+
1534
+ expressionOrQueryParens returns[ default expr ]
1535
+ :
1536
+ '('
1537
+ ( <priority> expressionOrQueryParens[ ...$ ]
1538
+ ( <prec=-2, postfix> expression[ ...$ ]<atAltStart>
1539
+ continueExpressionslist[ ...$ ]?
1540
+ | <prec=-2, postfix>
1541
+ continueExpressionslist[ ...$ ]
1542
+ | <prec=-1, postfix> queryExpression[ ...$ ]<atAltStart>
1543
+ )?
1544
+ | <prec=-2> expression[ ...$ ]
1545
+ continueExpressionslist[ ...$ ]?
1546
+ | <prec=-1> queryExpression[ ...$ ]
1547
+ )
1548
+ ')' <setCondition=setPrecInCallingRule>
1549
+ { this.surroundByParens( $expr ); }
1550
+ ;
1551
+
1552
+ continueExpressionslist[ expr ]
1553
+ @finally{ this.attachLocation( $expr ); }
1554
+ :
1555
+ ',' { $expr = { op: this.valueWithLocation( 'list' ), args: [ $expr ], location: { ... $expr.$parens?.at( -1 ) ?? $expr.location } }; }
1556
+ (
1557
+ e=expression { $expr.args.push( $e ); }
1558
+ ( ',' | <exitLoop> )
1559
+ )+
1560
+ { this.attachLocation( $expr ); }
1561
+ ;
1562
+
1563
+ newAndValuePath returns[ default expr ]
1564
+ @finally{ this.attachLocation( $expr ); }
1565
+ :
1566
+ NEW { $expr = this.applyOpToken(); }
1567
+ e=valuePath { $e = this.valuePathAst( $e ); }
1568
+ { if ($e.op?.val !== 'ixpr') $expr.args.push( $e ); else $expr.args.push( ...e.args ); }
1569
+ ;
1570
+
1571
+ caseExpression returns[ default expr = { op: { val: 'ixpr' }, args: [] } ]
1572
+ @finally{ this.attachLocation( $expr ); }
1573
+ :
1574
+ CASE { this.pushXprToken( $expr ); $expr.op.location = $expr.args[0].location; }
1575
+ ( e=expression { $expr.args.push( $e ); } )?
1576
+ (
1577
+ WHEN { this.pushXprToken( $expr ); }
1578
+ e=expression { $expr.args.push( $e ); }
1579
+ THEN { this.pushXprToken( $expr ); }
1580
+ e=expression { $expr.args.push( $e ); }
1581
+ )+
1582
+ (
1583
+ ELSE { this.pushXprToken( $expr ); }
1584
+ e=expression { $expr.args.push( $e ); }
1585
+ )?
1586
+ END { this.pushXprToken( $expr ); }
1587
+ ;
1588
+
1589
+ castFunction returns[ default expr = {} ]
1590
+ @finally{ this.attachLocation( $expr ); }
1591
+ :
1592
+ CAST { $expr.op = this.valueWithLocation(); }
1593
+ '(' { $expr.args = this.createArray(); }
1594
+ arg=expression { $expr.args?.push( $arg ); }
1595
+ AS typeRefOptArgs[ $expr ]
1596
+ ')' { this.finalizeDictOrArray( $expr.args ); }
1597
+ ;
1598
+
1599
+ argumentsAndFilter[ pathStep ]
1600
+ options{ minTokensMatched = 1 }
1601
+ :
1602
+ (
1603
+ open='(' <setCondition=prepareSpecialFunction>
1604
+ { $pathStep.args = this.createArray(); }
1605
+ // action here, default action won't be executed with failed condition (TODO
1606
+ // TOOL? at least msg)
1607
+ (
1608
+ <default=fallback> // TODO TOOL: allow in loop
1609
+ { ; } // TODO TOOL: we need an idle action here, for <default>
1610
+ (
1611
+ expr=funcExpression { $pathStep.args.push( $expr ); }
1612
+ (
1613
+ ','<setCondition=nextFunctionArgument>
1614
+ ( expr=funcExpression { $pathStep.args.push( $expr ); }
1615
+ | <exitLoop> // <cond>: only before `)`
1616
+ )
1617
+ )*
1618
+ ( // ORDER BY in generic functions, e.g. `first_value(id order by name)`
1619
+ ORDER { $expr = $pathStep.args[$pathStep.args.length - 1] = this.applyOpToken( $expr ); }
1620
+ BY { this.pushXprToken( $expr ); }
1621
+ orderByClauseAsXpr[ $expr.args ]
1622
+ { this.attachLocation( $expr ); }
1623
+ )?
1624
+ )?
1625
+ |
1626
+ <cond=isNamedArg> id=Id_all['paramname']
1627
+ (
1628
+ ':' { $pathStep.args = this.createDict( $open ); $pathStep.$syntax = ':'; }
1629
+ expr=expression { this.addNamedArg( $pathStep, $id, $expr ); }
1630
+ ( ','
1631
+ ( id=Id_all['paramname'] ':'
1632
+ expr=expression { this.addNamedArg( $pathStep, $id, $expr ); }
1633
+ | <exitLoop>
1634
+ )
1635
+ )*
1636
+ |
1637
+ '=>' { $pathStep.args = this.createDict(); }
1638
+ // TODO: potentially special expressions for special functions
1639
+ expr=expression { this.addNamedArg( $pathStep, $id, $expr ); }
1640
+ ( ','
1641
+ ( id=Id_all['paramname'] '=>'
1642
+ expr=expression { this.addNamedArg( $pathStep, $id, $expr ); }
1643
+ | <exitLoop>
1644
+ )
1645
+ )*
1646
+ )
1647
+ )
1648
+ ')'
1649
+ )?
1650
+ // TODO: not with function!
1651
+ cardinalityAndFilter[ ...$ ]?
1652
+ ;
1653
+
1654
+ funcExpression returns[ default expr ] locals[ args ]
1655
+ @finally{ this.attachLocation( $expr ); }
1656
+ :
1657
+ ( options{ lookahead = lGenericIntroOrExpr; }
1658
+ :
1659
+ $expr=expression
1660
+ |
1661
+ tok=GenericExpr // keyword as replacement for expression, like '*'
1662
+ { $expr = { val: $tok.keyword ?? $tok.type, location: $tok.location, literal: 'token' }; }
1663
+ |
1664
+ GenericIntro // keyword as introduction of expression, like DISTINCT
1665
+ { $expr = this.applyOpToken(); $args = $expr.args; }
1666
+ e=expression { $expr.args.push( $e ); }
1667
+ )
1668
+ ( options{ lookahead = lGenericSeparator; }
1669
+ :
1670
+ GenericSeparator
1671
+ { if ($args) this.pushXprToken( $args ); else { $expr= this.applyOpToken( $expr ); $args = $expr.args; } }
1672
+
1673
+ ( options{ lookahead = lGenericExpr; }
1674
+ :
1675
+ e=expression { $args.push( $e ); }
1676
+ |
1677
+ GenericExpr { this.pushXprToken( $args ); }
1678
+ )
1679
+ )*
1680
+ ;
1681
+
1682
+ GenericExpr // options { representingTokens = representingExpr; }
1683
+ : Id_all | '*' ;
1684
+ GenericIntro // options { representingTokens = representingIntro; }
1685
+ : Id_all ;
1686
+ GenericSeparator // options { representingTokens = representingSeparator; }
1687
+ : Id_all ;
1688
+
1689
+ overClause[ outer ] locals[ over = [] ]
1690
+ @finally{ $outer.push( this.surroundByParens( this.ixprAst( $over ) ) ); }
1691
+ :
1692
+ '('
1693
+ ( PARTITION { this.pushXprToken( $over ); } BY { this.pushXprToken( $over ); }
1694
+ expressionsAsXpr[ $over ]
1695
+ )?
1696
+ ( ORDER { this.pushXprToken( $over ); } BY { this.pushXprToken( $over ); }
1697
+ orderByClauseAsXpr[ $over ]
1698
+ )?
1699
+ ( ROWS { this.pushXprToken( $over ); }
1700
+ windowFrameClause[ $over ]
1701
+ )?
1702
+ ')'
1703
+ ;
1704
+
1705
+ expressionsAsXpr[ outer ] locals[ args = [] ]
1706
+ @finally{ $outer.push( this.ixprAst( $args ) ); }
1707
+ :
1708
+ expr=expression { $args.push( $expr ); }
1709
+ ( ',' { this.pushXprToken( $args ); }
1710
+ expr=expression { $args.push( $expr ); }
1711
+ )*
1712
+ ;
1713
+
1714
+ orderByClauseAsXpr[ outer ] locals[ args = [] ]
1715
+ @finally{ $outer.push( this.ixprAst( $args ) ); }
1716
+ :
1717
+ orderBySpecAsXpr[ $args ]
1718
+ ( ',' { this.pushXprToken( $args ); }
1719
+ orderBySpecAsXpr[ $args ]
1720
+ )*
1721
+ ;
1722
+
1723
+ orderBySpecAsXpr[ args ]
1724
+ :
1725
+ expr=expression { $args.push( $expr ); }
1726
+ ( ASC/DESC { this.pushXprToken( $args ); } )?
1727
+ ( NULLS { this.pushXprToken( $args ); }
1728
+ FIRST/LAST { this.pushXprToken( $args ); }
1729
+ )?
1730
+ ;
1731
+
1732
+ windowFrameClause[ outer ] locals[ args = [] ]
1733
+ @finally{ $outer.push( this.ixprAst( $args ) ); }
1734
+ :
1735
+ ( UNBOUNDED { this.pushXprToken( $args ); }
1736
+ | Number { $args.push( this.unsignedIntegerLiteral() ); }
1737
+ )
1738
+ PRECEDING { this.pushXprToken( $args ); }
1739
+ |
1740
+ CURRENT { this.pushXprToken( $args ); }
1741
+ ROW { this.pushXprToken( $args ); }
1742
+ |
1743
+ BETWEEN { this.pushXprToken( $args ); }
1744
+ windowFrameBoundSpec[ $args ]
1745
+ AND { this.pushXprToken( $args ); }
1746
+ windowFrameBoundSpec[ $args ]
1747
+ ;
1748
+
1749
+ windowFrameBoundSpec[ args ]
1750
+ :
1751
+ ( UNBOUNDED { this.pushXprToken( $args ); }
1752
+ | Number { $args.push( this.unsignedIntegerLiteral() ); }
1753
+ )
1754
+ ( FOLLOWING | PRECEDING ) { this.pushXprToken( $args ); }
1755
+ |
1756
+ CURRENT { this.pushXprToken( $args ); }
1757
+ ROW { this.pushXprToken( $args ); }
1758
+ ;
1759
+
1760
+ literalValue returns[ default expr = {} ]
1761
+ @finally{ this.attachLocation( $expr ); }
1762
+ :
1763
+ // TODO: remove from this rule (not in enum! `String enum { foo = #bar }`) ?
1764
+ '#' { this.reportUnexpectedSpace(); }
1765
+ Id['enumref']
1766
+ { $expr = { literal: 'enum', sym: this.identAst() } }
1767
+ |
1768
+ NULL
1769
+ { $expr = { literal: 'null', val: null }; }
1770
+ |
1771
+ TRUE/FALSE
1772
+ { $expr = { literal: 'boolean', val: this.lb().keyword === 'true' }; }
1773
+ |
1774
+ Number
1775
+ { $expr = this.numberLiteral(); } // allow float and large number
1776
+ |
1777
+ String
1778
+ { $expr = this.quotedLiteral(); }
1779
+ |
1780
+ QuotedLiteral // x'12', date'...', time'...', timestamp'...'
1781
+ { $expr = this.quotedLiteral(); }
1782
+ ;
1783
+
1784
+ // Annotation assignments -------------------------------------------------------
1785
+
1786
+ // We have three versions of the annotation assignment rules:
1787
+ // - "…Std": typically before keyword+name, after a name if no ':' could follow
1788
+ // - "…Col": at the beginning of a column def, which can start with ':' or '#'
1789
+ // - "…Mid": typically after a name if a ':' could follow
1790
+
1791
+ annoAssignStd[ art ]
1792
+ @finally{ this.docComment( $art ); }
1793
+ :
1794
+ '@'<setCondition=inSameLine> { this.reportUnexpectedSpace(); }
1795
+ ( annoAssignParen[ ...$ ]
1796
+ | annoAssignBase[ ...$ ]
1797
+ )
1798
+ ;
1799
+
1800
+ annoAssignCol[ art ]
1801
+ @finally{ this.docComment( $art ); }
1802
+ :
1803
+ '@' { this.reportUnexpectedSpace(); }
1804
+ ( annoAssignParen[ ...$ ]
1805
+ | annoAssignBase[ ...$ ]
1806
+ )
1807
+ ;
1808
+
1809
+ annoAssignMid[ art ]
1810
+ @finally{ this.docComment( $art ); }
1811
+ :
1812
+ '@'<setCondition=inSameLine> { this.reportUnexpectedSpace(); }
1813
+ ( annoAssignParen[ ...$ ]
1814
+ | name=annoNamePath
1815
+ { this.assignAnnotation( $art, {}, $name ); this.warnIfColonFollows( $name ); }
1816
+ )
1817
+ ;
1818
+
1819
+ annoAssignParen[ art ]
1820
+ :
1821
+ '('<setCondition=inSameLine>
1822
+ ( annoAssignBase[ $art ]
1823
+ ( ',' | <exitLoop> )
1824
+ )*
1825
+ ')'
1826
+ ;
1827
+
1828
+ annoAssignBase[ art ] locals[ value = {} ]
1829
+ @finally{ this.assignAnnotation( $art, $value, $name || {} ); }
1830
+ :
1831
+ name=annoNamePath
1832
+ ( <cond=inSameLine> ':' value=annoValue )?
1833
+ ;
1834
+
1835
+ annoNamePath[ category = 'anno' ] returns[ default name = new XsnName() ]
1836
+ @finally{ this.attachLocation( $name ); }
1837
+ :
1838
+ Id_all[ $category ]
1839
+ { $name.path = [ this.identAst() ]; }
1840
+ (
1841
+ '.'
1842
+ ( Id_all[ $category ] { $name.path.push( this.identAst() ); }
1843
+ | at='@' // TODO: complain about spaces after?
1844
+ Id_all[ $category ] { $name.path.push( this.identAstWithPrefix( $at ) ); }
1845
+ )
1846
+ )*
1847
+ ( <cond=inSameLine> annoPathVariant[ $name ] )?
1848
+ ;
1849
+
1850
+ annoPath[ nameOrRef, category = 'annoref' ]
1851
+ @finally{ this.attachLocation( $nameOrRef ); }
1852
+ :
1853
+ ( Id_all[ $category ] { $nameOrRef.path = [ this.identAst() ]; }
1854
+ | at='@' // TODO: complain about spaces after?
1855
+ Id_all[ $category ] { $nameOrRef.path = [ this.identAstWithPrefix( $at ) ]; }
1856
+ )
1857
+ (
1858
+ '.'
1859
+ ( Id_all[ $category ] { $nameOrRef.path.push( this.identAst() ); }
1860
+ | at='@' // TODO: complain about spaces after?
1861
+ Id_all[ $category ] { $nameOrRef.path.push( this.identAstWithPrefix( $at ) ); }
1862
+ )
1863
+ )*
1864
+ annoPathVariant[ $nameOrRef ]?
1865
+ ;
1866
+
1867
+ annoPathVariant[ nameOrRef ]
1868
+ @finally{ this.attachLocation( $nameOrRef.variant ); }
1869
+ :
1870
+ '#' { this.reportUnexpectedSpace(); }
1871
+ Id_all['variant'] { $nameOrRef.variant = { path: [ this.identAst() ] }; }
1872
+ (
1873
+ '.' Id_all['variant'] { $nameOrRef.variant.path.push( this.identAst() ); }
1874
+ )*
1875
+ ;
1876
+
1877
+ annoStructValue returns[ default value = {} ] locals[ name = new XsnName() ]
1878
+ @finally{ $value.name = $name; }
1879
+ :
1880
+ annoPath[ $name, 'name' ] { this.attachLocation( $name ); }
1881
+ ( ':'<setCondition=annoTopValue> // make it inherit “non-top” status
1882
+ $value=annoValue
1883
+ |
1884
+ { this.attachLocation( $value ); }
1885
+ )
1886
+ ;
1887
+
1888
+ annoValue returns[ default value = {} ]
1889
+ @finally{ this.attachLocation( $value ); }
1890
+ :
1891
+ $value=literalValue { this.adjustAnnoNumber( $value ); }
1892
+ |
1893
+ sign='+'/'-' Number
1894
+ { this.adjustAnnoNumber( $value = this.numberLiteral( $sign ) ); }
1895
+ |
1896
+ annoPath[ $value, 'annoref' ]
1897
+ |
1898
+ '{'<setCondition=annoTopValue>
1899
+ {
1900
+ if (this.annoTopValue(true)) $value.$flatten = [];
1901
+ else { $value.struct = Object.create(null); $value.literal = 'struct'; }
1902
+ }
1903
+ (
1904
+ // TODO: complain about empty loop if top-level as before
1905
+ sub=annoStructValue
1906
+ {
1907
+ if ($value.$flatten) $value.$flatten.push( $sub );
1908
+ else this.addDef( $sub, $value, 'struct', null, $sub.name );
1909
+ }
1910
+ ( ',' | <exitLoop> )
1911
+ )*
1912
+ '}' // Do NOT use <setCondition=afterBrace> here!
1913
+ |
1914
+ '['<setCondition=annoTopValue>
1915
+ { $value.val = []; $value.literal = 'array' }
1916
+ // no need for createArray() here, $value.location is set above
1917
+ (
1918
+ ( sub=annoValue { $value.val.push( $sub ) }
1919
+ |
1920
+ ellipsis='...'
1921
+ ( UP TO upTo=annoValue | { $upTo = undefined; } )
1922
+ { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1923
+ )
1924
+ ( ',' | <exitLoop> )
1925
+ )*
1926
+ ']'
1927
+ |
1928
+ '(' $value=condition ')' { $value.$tokenTexts = this.ruleTokensText(); }
1929
+ // TODO: (1,2,3) not supported, yet, only ((1,2,3))
1930
+ ;
1931
+
1932
+ // Shorten tokens array in this.gr() calls in top-level rules by reducing
1933
+ // intersection follow set:
1934
+ ignoredRule
1935
+ options{ excludeRuleFrom = Parser }
1936
+ :
1937
+ usingDeclaration ';'
1938
+ artifactDefOrExtend ';'
1939
+ ;
1940
+