@sap/cds-compiler 3.4.2 → 3.5.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 (143) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +15 -16
  5. package/bin/cdshi.js +19 -6
  6. package/doc/CHANGELOG_ARCHIVE.md +2 -2
  7. package/doc/CHANGELOG_BETA.md +9 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  9. package/lib/api/main.js +61 -59
  10. package/lib/api/options.js +4 -2
  11. package/lib/api/validate.js +2 -2
  12. package/lib/base/cleanSymbols.js +2 -3
  13. package/lib/base/dictionaries.js +6 -6
  14. package/lib/base/error.js +2 -2
  15. package/lib/base/keywords.js +6 -6
  16. package/lib/base/location.js +11 -12
  17. package/lib/base/message-registry.js +177 -58
  18. package/lib/base/messages.js +252 -180
  19. package/lib/base/model.js +14 -11
  20. package/lib/base/node-helpers.js +9 -10
  21. package/lib/base/optionProcessorHelper.js +138 -129
  22. package/lib/checks/.eslintrc.json +2 -0
  23. package/lib/checks/actionsFunctions.js +5 -5
  24. package/lib/checks/annotationsOData.js +4 -4
  25. package/lib/checks/arrayOfs.js +1 -1
  26. package/lib/checks/cdsPersistence.js +1 -1
  27. package/lib/checks/checkForTypes.js +3 -3
  28. package/lib/checks/defaultValues.js +3 -3
  29. package/lib/checks/elements.js +7 -7
  30. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  31. package/lib/checks/foreignKeys.js +1 -1
  32. package/lib/checks/invalidTarget.js +4 -4
  33. package/lib/checks/managedInType.js +1 -1
  34. package/lib/checks/managedWithoutKeys.js +1 -1
  35. package/lib/checks/nonexpandableStructured.js +5 -3
  36. package/lib/checks/nullableKeys.js +1 -1
  37. package/lib/checks/onConditions.js +5 -6
  38. package/lib/checks/parameters.js +1 -1
  39. package/lib/checks/queryNoDbArtifacts.js +2 -2
  40. package/lib/checks/selectItems.js +4 -4
  41. package/lib/checks/sql-snippets.js +4 -4
  42. package/lib/checks/types.js +7 -7
  43. package/lib/checks/utils.js +4 -4
  44. package/lib/checks/validator.js +16 -13
  45. package/lib/compiler/.eslintrc.json +4 -1
  46. package/lib/compiler/assert-consistency.js +8 -7
  47. package/lib/compiler/builtins.js +14 -14
  48. package/lib/compiler/checks.js +123 -48
  49. package/lib/compiler/define.js +12 -13
  50. package/lib/compiler/extend.js +266 -60
  51. package/lib/compiler/finalize-parse-cdl.js +10 -5
  52. package/lib/compiler/index.js +17 -14
  53. package/lib/compiler/populate.js +14 -6
  54. package/lib/compiler/propagator.js +2 -0
  55. package/lib/compiler/resolve.js +2 -15
  56. package/lib/compiler/shared.js +27 -16
  57. package/lib/compiler/tweak-assocs.js +5 -6
  58. package/lib/compiler/utils.js +20 -0
  59. package/lib/edm/annotations/genericTranslation.js +604 -358
  60. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  61. package/lib/edm/csn2edm.js +275 -222
  62. package/lib/edm/edm.js +17 -3
  63. package/lib/edm/edmAnnoPreprocessor.js +6 -6
  64. package/lib/edm/edmInboundChecks.js +2 -2
  65. package/lib/edm/edmPreprocessor.js +107 -77
  66. package/lib/edm/edmUtils.js +44 -5
  67. package/lib/gen/Dictionary.json +210 -8
  68. package/lib/gen/language.checksum +1 -1
  69. package/lib/gen/language.interp +67 -63
  70. package/lib/gen/language.tokens +81 -81
  71. package/lib/gen/languageLexer.interp +4 -10
  72. package/lib/gen/languageLexer.js +854 -869
  73. package/lib/gen/languageLexer.tokens +79 -81
  74. package/lib/gen/languageParser.js +14309 -13832
  75. package/lib/inspect/inspectModelStatistics.js +2 -2
  76. package/lib/inspect/inspectPropagation.js +6 -6
  77. package/lib/inspect/inspectUtils.js +2 -2
  78. package/lib/json/from-csn.js +102 -55
  79. package/lib/json/to-csn.js +119 -198
  80. package/lib/language/antlrParser.js +5 -2
  81. package/lib/language/docCommentParser.js +6 -6
  82. package/lib/language/errorStrategy.js +43 -23
  83. package/lib/language/genericAntlrParser.js +113 -133
  84. package/lib/language/language.g4 +1550 -1506
  85. package/lib/language/multiLineStringParser.js +3 -3
  86. package/lib/language/textUtils.js +2 -2
  87. package/lib/main.js +3 -3
  88. package/lib/model/csnRefs.js +5 -0
  89. package/lib/model/csnUtils.js +130 -122
  90. package/lib/model/revealInternalProperties.js +1 -1
  91. package/lib/model/sortViews.js +4 -6
  92. package/lib/modelCompare/compare.js +2 -2
  93. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  94. package/lib/modelCompare/utils/filter.js +100 -0
  95. package/lib/optionProcessor.js +5 -0
  96. package/lib/render/.eslintrc.json +1 -0
  97. package/lib/render/DuplicateChecker.js +1 -1
  98. package/lib/render/manageConstraints.js +12 -12
  99. package/lib/render/toCdl.js +311 -276
  100. package/lib/render/toHdbcds.js +97 -94
  101. package/lib/render/toRename.js +5 -5
  102. package/lib/render/toSql.js +127 -223
  103. package/lib/render/utils/common.js +141 -108
  104. package/lib/render/utils/delta.js +227 -0
  105. package/lib/render/utils/sql.js +22 -6
  106. package/lib/render/utils/stringEscapes.js +3 -3
  107. package/lib/transform/db/.eslintrc.json +2 -0
  108. package/lib/transform/db/applyTransformations.js +3 -3
  109. package/lib/transform/db/assertUnique.js +13 -12
  110. package/lib/transform/db/associations.js +5 -5
  111. package/lib/transform/db/cdsPersistence.js +10 -8
  112. package/lib/transform/db/constraints.js +14 -14
  113. package/lib/transform/db/expansion.js +20 -22
  114. package/lib/transform/db/flattening.js +24 -42
  115. package/lib/transform/db/groupByOrderBy.js +3 -3
  116. package/lib/transform/db/temporal.js +6 -6
  117. package/lib/transform/db/transformExists.js +23 -23
  118. package/lib/transform/db/views.js +16 -16
  119. package/lib/transform/draft/.eslintrc.json +1 -35
  120. package/lib/transform/draft/db.js +10 -10
  121. package/lib/transform/draft/odata.js +2 -2
  122. package/lib/transform/forOdataNew.js +8 -29
  123. package/lib/transform/forRelationalDB.js +16 -6
  124. package/lib/transform/localized.js +11 -10
  125. package/lib/transform/odata/toFinalBaseType.js +41 -27
  126. package/lib/transform/odata/typesExposure.js +113 -47
  127. package/lib/transform/parseExpr.js +209 -106
  128. package/lib/transform/transformUtilsNew.js +17 -10
  129. package/lib/transform/translateAssocsToJoins.js +24 -19
  130. package/lib/transform/universalCsn/coreComputed.js +10 -10
  131. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  132. package/lib/transform/universalCsn/utils.js +3 -3
  133. package/lib/utils/file.js +5 -5
  134. package/lib/utils/moduleResolve.js +13 -13
  135. package/lib/utils/objectUtils.js +6 -6
  136. package/lib/utils/term.js +5 -2
  137. package/lib/utils/timetrace.js +51 -24
  138. package/package.json +5 -8
  139. package/share/messages/check-proper-type-of.md +1 -1
  140. package/share/messages/message-explanations.json +1 -1
  141. package/share/messages/redirected-to-complex.md +4 -4
  142. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
  143. package/lib/modelCompare/filter.js +0 -83
@@ -120,6 +120,8 @@ options {
120
120
  superClass = genericAntlrParser;
121
121
  }
122
122
  tokens {
123
+ ELEMENT, // used with setLocalToken()
124
+ MASKED, // used with setLocalToken()
123
125
  VIRTUAL, // used with setLocalToken()
124
126
  OVER, // used with setLocalTokenIfBefore()
125
127
  HelperToken1, // used with setLocalToken(), does not appear in messages
@@ -132,6 +134,20 @@ tokens {
132
134
  COMPOSITIONofBRACE // via token rewrite in rule typeAssociationBase
133
135
  }
134
136
 
137
+ // Content:
138
+ // - top-level: USING, NAMESPACE, artifactDefOrExtend (start rule: start)
139
+ // - main definitions and annotation def
140
+ // - member definitions
141
+ // - EXTEND and ANNOTATE
142
+ // - type expressions
143
+ // - queries: the main query hierarchy (start rule: queryEOF)
144
+ // - queries: columns and other clauses
145
+ // - conditions and expressions (start rule: conditionEOF)
146
+ // - paths and functions
147
+ // - annotation assignments
148
+ // - literal values and identifiers
149
+ // - Lexer: spaces, literal values, reserved keywords, unreserved keywords, identifier
150
+
135
151
  // Top-Level -----------------------------------------------------------------
136
152
 
137
153
  start returns [ source ] locals [ _sync = 'recover' ]
@@ -140,24 +156,14 @@ start returns [ source ] locals [ _sync = 'recover' ]
140
156
  usingDeclaration[$source]*
141
157
  (
142
158
  namespaceDeclaration[$source]
143
- ( usingDeclaration[$source] | artifactDef[$source] )*
159
+ ( usingDeclaration[$source] | artifactDefOrExtend[$source] )*
144
160
  |
145
- artifactDef[$source]
146
- ( usingDeclaration[$source] | artifactDef[$source] )*
161
+ artifactDefOrExtend[$source]
162
+ ( usingDeclaration[$source] | artifactDefOrExtend[$source] )*
147
163
  )?
148
164
  EOF
149
165
  ;
150
166
 
151
- queryEOF returns [ query ]
152
- :
153
- q=queryExpression { $query = $q.query; } EOF
154
- ;
155
-
156
- conditionEOF returns [ cond ]
157
- :
158
- c=condition { $cond = $c.cond; } EOF
159
- ;
160
-
161
167
  namespaceDeclaration[ source ] locals[ decl = {} ]
162
168
  @after { $source.namespace = this.attachLocation($decl); }
163
169
  :
@@ -173,13 +179,9 @@ usingDeclaration[ source ] locals[ decl = {} ]
173
179
  FROM str=String
174
180
  { $source.dependencies.push( this.quotedLiteral( $str, 'string' ) ); }
175
181
  |
176
- path=externalPath
177
- { this.addItem( $decl, $source, 'usings', 'using' );; $decl.extern = $path.extern; }
178
- ( AS name=ident['Using'] { $decl.name = $name.id; }
179
- | { this.classifyImplicitName( 'Using' ); }
180
- )
182
+ usingProxy[ $source, $decl ]
181
183
  ( FROM str=String
182
- { $source.dependencies.push( $decl.fileDep = this.quotedLiteral( $str, 'string' ) ); }
184
+ { $source.dependencies.push( $decl.fileDep = this.quotedLiteral( $str, 'string' ) ); }
183
185
  )?
184
186
  |
185
187
  { this.addItem( $decl, $source, 'usings', 'using' ); }
@@ -187,9 +189,9 @@ usingDeclaration[ source ] locals[ decl = {} ]
187
189
  // to have some check in the future whether the external artifacts are
188
190
  // really in the FROM source...
189
191
  '{' { $decl.usings = this.createArray(); }
190
- innerUsing[ $decl ]
192
+ usingProxy[ $decl, {} ]
191
193
  ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
192
- innerUsing[ $decl ] )*
194
+ usingProxy[ $decl, {} ] )*
193
195
  '}' { this.finalizeDictOrArray( $decl.usings ); }
194
196
  ( FROM str=String
195
197
  { $source.dependencies.push( $decl.fileDep = this.quotedLiteral( $str, 'string' ) ); }
@@ -198,175 +200,27 @@ usingDeclaration[ source ] locals[ decl = {} ]
198
200
  ';'
199
201
  ;
200
202
 
201
- innerUsing[ using ] locals[ decl = {} ]
202
- @after { this.attachLocation($decl); }
203
+ usingProxy[ outer, proxy ]
204
+ @after { this.attachLocation($proxy); }
203
205
  :
204
- { $decl.location = this.startLocation(); }
205
- path=externalPath
206
- { this.addItem( $decl, $using, 'usings', 'using' );; $decl.extern = $path.extern; }
207
- ( AS name=ident['Using'] { $decl.name = $name.id; }
206
+ { if (!$proxy.location) $proxy.location = this.startLocation();
207
+ $proxy.extern = {}; }
208
+ simplePath[ $proxy.extern, 'global' ]
209
+ { this.addItem( $proxy, $outer, 'usings', 'using' ); }
210
+ ( AS name=ident['Using'] { $proxy.name = $name.id; }
208
211
  | { this.classifyImplicitName( 'Using' ); }
209
212
  )
210
213
  ;
211
214
 
212
- externalPath returns [ extern = {} ]
213
- :
214
- simplePath[ $extern, 'global' ]
215
- ;
216
-
217
- // We have two versions of the annotation assignment rule, because we do not
218
- // want to let the ambiguity in select items (solution: "either" possibility)
219
- // creep into all annotation assignments:
220
- // view V(p) as select from E { // either: anno value "ref p", select item -x
221
- // @anno :p - x as x; // or: anno value true, select item :p-x
222
- // }
223
-
224
- annotationAssignment_1[ art ] locals[ assignment = { name: {} } ]
225
- @after { this.assignAnnotation( $art, $assignment ); }
226
- :
227
- annotationPath[ $assignment.name, 'anno' ]
228
- annotationPathVariant[ $assignment.name ]?
229
- (
230
- ':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
231
- val=annoValue[ $assignment ]
232
- )?
233
- ;
234
-
235
- annotationAssignment_paren[ art ]
236
- :
237
- '('
238
- // allow completely useless `@()` with a warning, do not offer it for completion
239
- {
240
- this.meltKeywordToIdentifier();
241
- if (this.isStraightBefore(')')) {
242
- this.warning( 'syntax-anno-useless',
243
- this.tokenLocation( this._input.LT(-2), this.getCurrentToken() ),
244
- { code: '@()' },
245
- 'Ignored useless $(CODE)' );
246
- this.matchWildcard(); // we know it is the ')' - we do not reach the final match
247
- return $ctx;
248
- }
249
- }
250
- annotationAssignment_1[ $art ]
251
- ( ','
252
- {
253
- this.meltKeywordToIdentifier();
254
- if (this.isStraightBefore(')')) break; // allow ',' before ')'
255
- }
256
- annotationAssignment_1[ $art ]
257
- )*
258
- ')'
259
- ;
260
-
261
- annotationAssignment_fix[ art ] locals[ assignment ]
262
- // value outside @(...)
263
- @after {
264
- if ($assignment) {
265
- this.assignAnnotation( $art, $assignment );
266
- this.docComment( $art );
267
- }
268
- } :
269
- '@'
270
- (
271
- annotationAssignment_paren[ $art ]
272
- |
273
- { $assignment = { name: {} }; }
274
- annotationPath[ $assignment.name, 'anno' ]
275
- annotationPathVariant[ $assignment.name ]?
276
- {
277
- var t = this.getCurrentToken();
278
- if (t.text === ':')
279
- this.warning( 'syntax-anno-short', $assignment.name.location,
280
- { code: '@(...)' },
281
- 'Better use $(CODE) for annotation assignments here' );
282
- }
283
- )
284
- ;
285
-
286
- annotationAssignment_ll1[ art ] locals[ assignment ]
287
- @after {
288
- if ($assignment) {
289
- this.assignAnnotation( $art, $assignment );
290
- this.docComment( $art );
291
- }
292
- } :
293
- '@'
294
- (
295
- annotationAssignment_paren[ $art ]
296
- |
297
- { $assignment = { name: {} }; }
298
- annotationPath[ $assignment.name, 'anno' ]
299
- annotationPathVariant[ $assignment.name ]?
300
- (
301
- ':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
302
- val=annoValue[ $assignment ]
303
- )?
304
- )
305
- ;
306
-
307
- // Has previously used ATN, now via local token rewrite
308
- annotationAssignment_atn[ art ] locals[ assignment ]
309
- @after {
310
- if ($assignment) {
311
- this.assignAnnotation( $art, $assignment );
312
- this.docComment( $art );
313
- }
314
- } :
315
- '@'
316
- (
317
- annotationAssignment_paren[ $art ]
318
- |
319
- { $assignment = { name: {} }; }
320
- annotationPath[ $assignment.name, 'anno' ]
321
- // '#' is in the follow set of this rule, as it is used in rule "selectItemDef"
322
- // before an "expression" which can start with a '#' for an enum value
323
- // -> used to introduce variant name if and only if in same line as previous token
324
- { this.setLocalToken( '#', 'HelperToken1', null, true ); }
325
- (
326
- HelperToken1 { this.meltKeywordToIdentifier(); }
327
- variant=ident['variant'] { $assignment.name.variant = $variant.id; }
328
- )?
329
- // ':' is in the follow set of this rule, as it is used in rule "selectItemDef"
330
- // before an "expression" which can start with a ':' for a parameter reference
331
- // -> used to introduce assignment value if and only if in same line as previous token
332
- { this.setLocalToken( ':', 'HelperToken2', null, true ); }
333
- ( HelperToken2 // ':'
334
- { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
335
- (
336
- val=annoValueBase[ $assignment ]
337
- |
338
- at='@'? annotationPath[ $assignment, 'ref', $at ]
339
- { this.setLocalToken( '#', 'HelperToken1', null, true ); } // see above
340
- (
341
- HelperToken1 { this.meltKeywordToIdentifier(); }
342
- variant=ident['variant'] { $assignment.variant = $variant.id; }
343
- )?
344
- )
345
- )?
346
- )
347
- ;
348
-
349
- // Main artifact definitions -------------------------------------------------
350
-
351
- requiredSemi
352
- : ';'
353
- | { return $ctx; } // do not actually parse the closing brace
354
- '}'
355
- ;
356
-
357
- optionalSemi
358
- : { this.noAssignmentInSameLine(); } // issue warning for } @Anno \n? NextDef
359
- ';'?
360
- ;
361
-
362
- artifactDef[ outer, defOnly = false ] locals[ art = {} ] // cannot use `parent` as parameter name!
215
+ artifactDefOrExtend[ outer, defOnly = false ] locals[ art = {} ] // cannot use `parent` as parameter name!
363
216
  @after{ /* #ATN 1 */ }
364
217
  :
365
218
  { $art.location = this.startLocation(); this.docComment( $art ); }
366
219
  annotationAssignment_ll1[ $art ]*
367
220
  (
368
221
  DEFINE?
369
- ( contextDef[ $art, $outer, defOnly ]
222
+ ( serviceDef[ $art, $outer, defOnly ]
223
+ | contextDef[ $art, $outer, defOnly ]
370
224
  | entityDef[ $art, $outer ]
371
225
  | typeDef[ $art, $outer ]
372
226
  | aspectDef[ $art, $outer ]
@@ -377,26 +231,27 @@ artifactDef[ outer, defOnly = false ] locals[ art = {} ] // cannot use `parent`
377
231
  )
378
232
  |
379
233
  extend=EXTEND
380
- { if (defOnly)
234
+ { if (defOnly) // TODO: nicer message text
381
235
  this.error( 'syntax-extend-context', $extend,
382
- { code: 'EXTEND artifact', kind: defOnly },
383
- 'No $(CODE) within $(KIND) extensions' );
236
+ { code: 'EXTEND artifact', meta: defOnly },
237
+ 'No $(CODE) within $(META) extensions' );
384
238
  if (!$outer.extensions) $outer.extensions = [];
385
239
  }
386
- // #ATN: EXTEND elem, while CONTEXT, ENTITY etc are not reserved
387
- ( extendContext[ $art, $outer ]
388
- | extendEntity[ $art, $outer ] // or aspect
240
+ // #ATN: EXTEND art, while CONTEXT, ENTITY etc are not reserved
241
+ ( extendService[ $art, $outer ]
242
+ | extendContext[ $art, $outer ]
243
+ | extendEntityOrAspect[ $art, $outer ] // or aspect
389
244
  | extendProjection[ $art, $outer ]
390
245
  | extendType[ $art, $outer ]
391
- // Streamlined Syntax
246
+ // Streamlined Syntax; we won't add more kinds of the non-streamlined variants:
392
247
  | extendArtifact[ $art, $outer ]
393
248
  )
394
249
  |
395
250
  annotate=ANNOTATE
396
- { if (defOnly)
251
+ { if (defOnly) // TODO: improve message text
397
252
  this.error( 'syntax-extend-context', $annotate,
398
- { code: 'ANNOTATE artifact', kind: defOnly },
399
- 'No $(CODE) within $(KIND) extensions' );
253
+ { code: 'ANNOTATE artifact', meta: defOnly },
254
+ 'No $(CODE) within $(META) extensions' );
400
255
  if (!$outer.extensions) $outer.extensions = [];
401
256
  this.meltKeywordToIdentifier();
402
257
  }
@@ -404,16 +259,14 @@ artifactDef[ outer, defOnly = false ] locals[ art = {} ] // cannot use `parent`
404
259
  )
405
260
  ;
406
261
 
407
- contextDef[ art, outer, defOnly = false ] locals[ name = {} ]
262
+ optArtifactsBlock[ art, defOnly = false ]
408
263
  @after { this.attachLocation( $art ); }
409
264
  :
410
- ( CONTEXT | service=SERVICE ) simplePath[ $name, $service ? 'Service' : 'Context' ]
411
- { this.addDef( $art, $outer, 'artifacts', $service ? 'service' : 'context', $name );
412
- this.docComment( $art ); }
265
+ { this.docComment( $art ); }
413
266
  annotationAssignment_fix[ $art ]*
414
267
  (
415
268
  '{' { $art.artifacts = this.createDict(); $art.extensions = []; }
416
- artifactDef[ $art, defOnly ]*
269
+ artifactDefOrExtend[ $art, defOnly ]*
417
270
  '}' { this.finalizeDictOrArray( $art.artifacts ); }
418
271
  optionalSemi
419
272
  |
@@ -421,25 +274,110 @@ contextDef[ art, outer, defOnly = false ] locals[ name = {} ]
421
274
  )
422
275
  ;
423
276
 
424
- extendContext[ art, outer ] locals[ name = {} ]
277
+ requiredSemi
278
+ : ';'
279
+ | { return $ctx; } // do not actually parse the closing brace
280
+ '}'
281
+ ;
282
+
283
+ optionalSemi
284
+ : { this.noAssignmentInSameLine(); } // issue warning for } @Anno \n? NextDef
285
+ ';'?
286
+ ;
287
+
288
+ // Annotation def and main definitions ------------------------------------------
289
+
290
+ annotationDef[ art, outer ] locals[ name = {} ]
425
291
  @after { this.attachLocation( $art ); }
426
292
  :
427
- ( CONTEXT { $art.expectedKind = 'context'; } | service=SERVICE { $art.expectedKind = 'service'; })
428
- simplePath[ $name, $service ? 'Service' : 'Context' ] // not 'Extend' here
429
- { $art.name = $name; this.addItem( $art, $outer, 'extensions', 'extend' ); }
430
- ( WITH { this.noSemicolonHere(); } )?
431
- { this.docComment( $art ); }
432
- annotationAssignment_ll1[ $art ]*
293
+ annotation=ANNOTATION simplePath[ $name, 'AnnoDef' ]
294
+ { if ($outer.kind !== 'source') { // this is a syntax restriction to avoid confusion
295
+ this.error( 'syntax-unexpected-vocabulary', $annotation, { '#': $outer.kind } );
296
+ $art = {}; }
297
+ else {
298
+ if (!$outer.vocabularies) $outer.vocabularies = Object.create(null);
299
+ this.addDef( $art, $outer, 'vocabularies', 'annotation', $name );
300
+ }
301
+ this.docComment( $art ); }
302
+ annotationAssignment_fix[ $art ]*
303
+ typeSpecSemi[ $art ] // also 'includes'...
304
+ ;
305
+
306
+ serviceDef[ art, outer, defOnly = false ] locals[ name = {} ]
307
+ @after { this.attachLocation( $art ); }
308
+ :
309
+ SERVICE simplePath[ $name, 'Service' ]
310
+ { this.addDef( $art, $outer, 'artifacts', 'service', $name ); }
311
+ optArtifactsBlock[ $art, defOnly ]
312
+ ;
313
+
314
+ contextDef[ art, outer, defOnly = false ] locals[ name = {} ]
315
+ @after { this.attachLocation( $art ); }
316
+ :
317
+ CONTEXT simplePath[ $name, 'Context' ]
318
+ { this.addDef( $art, $outer, 'artifacts', 'context', $name ); }
319
+ optArtifactsBlock[ $art, defOnly ]
320
+ ;
321
+
322
+ eventDef[ art, outer ] locals[ name = {} ]
323
+ @after { /* #ATN 1 */ this.attachLocation( $art ); }
324
+ :
325
+ EVENT simplePath[ $name, 'Event' ]
326
+ { this.addDef( $art, $outer, 'artifacts', 'event', $name );
327
+ this.docComment( $art ); }
328
+ annotationAssignment_fix[ $art ]*
433
329
  (
434
- '{' { $art.artifacts = this.createDict(); }
435
- artifactDef[ $art, $service ? 'SERVICE' : 'CONTEXT' ]*
436
- '}' { this.finalizeDictOrArray( $art.artifacts ); }
437
- optionalSemi
330
+ typeStruct[ $art ] optionalSemi
438
331
  |
439
- requiredSemi
332
+ ':'
333
+ // #ATN: includeRef can be / start with PROJECTION
334
+ (
335
+ { $art.type = {}; }
336
+ simplePath[ $art.type, 'artref' ]
337
+ (
338
+ { $art.includes = [ $art.type ]; delete $art.type; }
339
+ ( ',' { if (this.isStraightBefore('{')) break; } // allow ',' before '{' // }}
340
+ includeRef[ $art ]
341
+ )*
342
+ typeStruct[ $art ] optionalSemi
343
+ |
344
+ { this.docComment( $art ); }
345
+ annotationAssignment_ll1[ $art ]*
346
+ requiredSemi
347
+ )
348
+ |
349
+ typeStruct[ $art ] optionalSemi
350
+ |
351
+ qp=projectionSpec
352
+ { $art.query = $qp.query; $art['$'+'syntax'] = 'projection'; }
353
+ optionalSemi // TODO: not fully correct without columns or excluding
354
+ )
440
355
  )
441
356
  ;
442
357
 
358
+ viewDef[ art, outer ] locals[ name = {} ]
359
+ @after { this.attachLocation( $art ); }
360
+ :
361
+ v=VIEW simplePath[ $name, 'Entity' ]
362
+ { $art['$'+'syntax'] = 'view';
363
+ this.addDef( $art, $outer, 'artifacts', 'entity', $name );
364
+ this.docComment( $art ); }
365
+ annotationAssignment_fix[ $art ]*
366
+ (
367
+ parameterListDef[ $art ]
368
+ |
369
+ // TODO: warning deprecated?
370
+ ( HideAlternatives | WITH ) { $art.params = this.createDict(); }
371
+ PARAMETERS
372
+ parameterDef[ $art ]
373
+ ( ',' parameterDef[ $art ] )* // no optional final ',' here
374
+ { this.finalizeDictOrArray( $art.params ); }
375
+ )?
376
+ AS qe=queryExpression { $art.query = $qe.query; }
377
+ // TODO check ANTLR: bad msg with 'view V as'<eof> but 'view V as FOO' is fine
378
+ requiredSemi
379
+ ;
380
+
443
381
  entityDef[ art, outer ] locals[ name = {} ]
444
382
  @after { this.attachLocation( $art ); }
445
383
  :
@@ -458,7 +396,6 @@ entityDef[ art, outer ] locals[ name = {} ]
458
396
  '{' { $art.elements = this.createDict(); }
459
397
  elementDef[ $art ]*
460
398
  '}' { this.finalizeDictOrArray( $art.elements ); }
461
- // TODO: action definitions in a specific section?
462
399
  (
463
400
  ACTIONS '{' { $art.actions = this.createDict(); }
464
401
  actionFunctionDef[ $art ]*
@@ -489,161 +426,48 @@ entityDef[ art, outer ] locals[ name = {} ]
489
426
  )
490
427
  ;
491
428
 
492
- projectionSpec returns[ query ] locals[ src ]
493
- @after { this.attachLocation($query); }
429
+ aspectDef[ art, outer ] locals[ name = {} ]
430
+ @after { this.attachLocation( $art ); }
494
431
  :
495
- proj=PROJECTION ON // FIXME: First draft only, details unclear/unspecified
496
- // now a simplified `tableTerm`:
497
- {
498
- $src = { path: [], scope: 0 };
499
- $query = { op: this.valueWithTokenLocation( 'SELECT', $proj ), from: $src, location: this.startLocation() };
500
- }
501
- fromPath[ $src, 'artref']
432
+ ( ASPECT | ( abs=ABSTRACT | HideAlternatives ) ent=ENTITY )
433
+ simplePath[ $name, 'Type' ]
434
+ { this.addDef( $art, $outer, 'artifacts', 'aspect', $name );
435
+ if ($ent)
436
+ this.warning( 'syntax-deprecated-abstract', this.tokenLocation( $abs, $ent ) );
437
+ this.docComment( $art ); }
438
+ annotationAssignment_fix[ $art ]*
502
439
  ( ':'
503
- { $src.scope = $src.path.length; }
504
- fromPath[ $src, 'ref']
505
- )?
506
- ( AS aliasName=ident['FromAlias'] { $src.name = $aliasName.id } )?
507
- // ANTLR errors are better if we use ( A )? instead of ( A | ):
508
- { if (!$src.name) this.classifyImplicitName( $src.scope ? 'FromAlias' : 'Without' ); }
509
- bracedSelectItemListDef[ $query, 'columns' ]?
510
- excludingClause[ $query ]?
511
- ;
512
-
513
- projectionClauses[ query ]
514
- @after { this.attachLocation($query); }
515
- :
516
- ( WHERE cond=condition { $query.where = $cond.cond; } )?
517
- (
518
- GROUP BY
519
- e1=expression { $query.groupBy = [ $e1.expr ]; }
520
- ( ',' en=expression { $query.groupBy.push( $en.expr ); } )*
521
- )?
522
- ( HAVING having=condition { $query.having = $having.cond; } )?
523
- ( ob=orderByClause[ $query ] { $query = $ob.query; } ) ?
524
- ( lc=limitClause[ $query ] { $query = $lc.query; } ) ?
525
- ;
526
-
527
- excludingClause[ query ]
528
- :
529
- // syntax is less than ideal - EXCLUDING is only useful for `*` - with
530
- // this syntax, people wonder what happens with explicit select items
531
- EXCLUDING '{' { $query.excludingDict = this.createDict(); }
532
- projectionExclusion[ $query ]
533
- ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
534
- projectionExclusion[ $query ]
535
- )*
536
- '}' { this.finalizeDictOrArray( $query.excludingDict ); }
537
- ;
538
-
539
- projectionExclusion[ outer ] locals[ art = {} ]
540
- @after { this.attachLocation($art); }
541
- :
542
- name=ident['ref']
543
- { this.addDef( $art, $outer, 'excludingDict', '', $name.id ); }
544
- ;
545
-
546
- // also used for aspect
547
- extendEntity[ art, outer ] locals[ name = {} ]
548
- @after { /* #ATN 1 */ this.attachLocation( $art ); }
549
- :
550
- kind=(ASPECT | ENTITY) simplePath[ $name, 'Extend' ]
551
- { $art.expectedKind = $kind.text.toLowerCase(); $art.name = $name;
552
- this.addItem( $art, $outer, 'extensions', 'extend' );
553
- }
554
- (
555
- WITH { this.noSemicolonHere(); this.docComment( $art ); }
556
- annotationAssignment_ll1[ $art ]*
557
- // ATN: the ref can start with ACTIONS
558
- (
559
- includeRef[ $art ] ( ',' includeRef[ $art ] )*
560
- requiredSemi
561
- |
562
- extendForEntity[ $art ]
563
- )
564
- |
565
- { this.docComment( $art ); }
566
- annotationAssignment_ll1[ $art ]*
567
- extendForEntity[ $art ]
568
- )
569
- ;
570
-
571
- extendForEntity[ art ]
572
- :
573
- '{' { $art.elements = this.createDict(); }
574
- elementDefOrExtend[ $art ]*
575
- '}' { this.finalizeDictOrArray( $art.elements ); }
576
- (
577
- ACTIONS '{' { $art.actions = this.createDict(); }
578
- actionFunctionDef[ $art ]*
579
- '}' { this.finalizeDictOrArray( $art.actions ); }
580
- )?
581
- optionalSemi
582
- |
583
- ACTIONS '{' { $art.actions = this.createDict(); }
584
- actionFunctionDef[ $art ]*
585
- '}' { this.finalizeDictOrArray( $art.actions ); }
586
- optionalSemi
587
- |
588
- requiredSemi
589
- ;
590
-
591
- extendProjection[ art, outer ] locals[ name = {} ]
592
- @after { this.attachLocation( $art ); }
593
- :
594
- expected=PROJECTION simplePath[ $name, 'Extend' ]
595
- { $art.expectedKind = 'entity'; $art.name = $name;
596
- this.addItem( $art, $outer, 'extensions', 'extend' );
597
- }
598
- ( WITH { this.noSemicolonHere(); } )?
599
- { this.docComment( $art ); }
600
- annotationAssignment_ll1[ $art ]*
601
- (
602
- '{' { $art.columns = []; }
603
440
  (
604
- selectItemDef[ $art.columns ]
605
- ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
606
- selectItemDef[ $art.columns ]
441
+ includeRef[ $art ]
442
+ ( ',' { if (this.isStraightBefore('{')) break; } // allow ',' before '{' // }}
443
+ includeRef[ $art ]
607
444
  )*
608
445
  )?
609
- '}'
446
+ )?
447
+ ( // `aspect MyAspect {};`
448
+ '{' { $art.elements = this.createDict(); }
449
+ ( elementDef[ $art ]* )
450
+ '}' { this.finalizeDictOrArray( $art.elements ); }
610
451
  (
611
452
  ACTIONS '{' { $art.actions = this.createDict(); }
612
453
  actionFunctionDef[ $art ]*
613
454
  '}' { this.finalizeDictOrArray( $art.actions ); }
614
455
  )?
615
456
  optionalSemi
616
- |
617
- ACTIONS '{' { $art.actions = this.createDict(); }
618
- actionFunctionDef[ $art ]*
619
- '}' { this.finalizeDictOrArray( $art.actions ); }
620
- optionalSemi
621
- |
457
+ | // `aspect MyAspect;`, e.g. for annotation aspects.
458
+ { this.aspectWithoutElements( $art ); }
622
459
  requiredSemi
623
460
  )
624
461
  ;
625
462
 
626
- // TODO: no action extension?
627
- actionFunctionDef[ outer ] locals[ art = {} ]
463
+ typeDef[ art, outer ] locals[ name = {} ]
628
464
  @after { this.attachLocation( $art ); }
629
465
  :
630
- { $art.location = this.startLocation();; this.docComment( $art ); }
631
- annotationAssignment_ll1[ $art ]*
632
- (
633
- ACTION name=ident['BoundAction']
634
- { this.addDef( $art, $outer, 'actions', 'action', $name.id );
635
- this.docComment( $art ); }
636
- annotationAssignment_fix[ $art ]*
637
- parameterListDef[ $art ]
638
- ( returnTypeSpec[ $art ] | requiredSemi )
639
- |
640
- FUNCTION name=ident['BoundAction']
641
- { this.addDef( $art, $outer, 'actions', 'function', $name.id );
642
- this.docComment( $art ); }
643
- annotationAssignment_fix[ $art ]*
644
- parameterListDef[ $art ]
645
- returnTypeSpec[ $art ]
646
- )
466
+ TYPE simplePath[ $name, 'Type' ]
467
+ { this.addDef( $art, $outer, 'artifacts', 'type', $name );
468
+ this.docComment( $art ); }
469
+ annotationAssignment_fix[ $art ]*
470
+ typeSpecSemi[ $art ]
647
471
  ;
648
472
 
649
473
  actionFunctionMainDef[ art, outer ] locals[ name = {} ]
@@ -664,127 +488,273 @@ actionFunctionMainDef[ art, outer ] locals[ name = {} ]
664
488
  returnTypeSpec[ $art ]
665
489
  ;
666
490
 
667
- eventDef[ art, outer ] locals[ name = {} ]
668
- @after { /* #ATN 1 */ this.attachLocation( $art ); }
491
+ // Member definitions: actions, elements, enums, parameters: --------------------
492
+
493
+ actionFunctionDef[ outer ] locals[ art = {} ]
494
+ @after { this.attachLocation( $art ); }
669
495
  :
670
- EVENT simplePath[ $name, 'Event' ]
671
- { this.addDef( $art, $outer, 'artifacts', 'event', $name );
672
- this.docComment( $art ); }
673
- annotationAssignment_fix[ $art ]*
496
+ { $art.location = this.startLocation();; this.docComment( $art ); }
497
+ annotationAssignment_ll1[ $art ]*
674
498
  (
675
- typeStruct[ $art ] optionalSemi
499
+ ACTION name=ident['BoundAction']
500
+ { this.addDef( $art, $outer, 'actions', 'action', $name.id );
501
+ this.docComment( $art ); }
502
+ annotationAssignment_fix[ $art ]*
503
+ parameterListDef[ $art ]
504
+ ( returnTypeSpec[ $art ] | requiredSemi )
676
505
  |
677
- ':'
678
- // #ATN: includeRef can be / start with PROJECTION
679
- (
680
- { $art.type = {}; }
681
- simplePath[ $art.type, 'artref' ]
682
- (
683
- { $art.includes = [ $art.type ]; delete $art.type; }
684
- ( ',' { if (this.isStraightBefore('{')) break; } // allow ',' before '{' // }}
685
- includeRef[ $art ]
686
- )*
687
- typeStruct[ $art ] optionalSemi
688
- |
689
- { this.docComment( $art ); }
690
- annotationAssignment_ll1[ $art ]*
691
- requiredSemi
692
- )
693
- |
694
- typeStruct[ $art ] optionalSemi
695
- |
696
- qp=projectionSpec
697
- { $art.query = $qp.query; $art['$'+'syntax'] = 'projection'; }
698
- optionalSemi // TODO: not fully correct without columns or excluding
699
-
700
- )
506
+ FUNCTION name=ident['BoundAction']
507
+ { this.addDef( $art, $outer, 'actions', 'function', $name.id );
508
+ this.docComment( $art ); }
509
+ annotationAssignment_fix[ $art ]*
510
+ parameterListDef[ $art ]
511
+ returnTypeSpec[ $art ]
701
512
  )
702
513
  ;
703
514
 
704
- aspectDef[ art, outer ] locals[ name = {} ]
515
+ parameterDef[ outer ] locals[ art = {} ]
705
516
  @after { this.attachLocation( $art ); }
706
517
  :
707
- ( ASPECT | ( abs=ABSTRACT | HideAlternatives ) ent=ENTITY )
708
- simplePath[ $name, 'Type' ]
709
- { this.addDef( $art, $outer, 'artifacts', 'aspect', $name );
710
- // backends do not like ['$'+'syntax']: ($ent ? 'entity' : 'aspect')
711
- if ($ent)
712
- this.warning( 'syntax-deprecated-abstract', this.tokenLocation( $abs, $ent ), {},
713
- 'Abstract entity definitions are deprecated; use aspect definitions instead' );
518
+ { this.docComment( $art ); }
519
+ annotationAssignment_ll1[ $art ]*
520
+ name=ident['Param']
521
+ { this.addDef( $art, $outer, 'params', 'param', $name.id );
714
522
  this.docComment( $art ); }
715
523
  annotationAssignment_fix[ $art ]*
716
- ( ':'
717
- (
718
- includeRef[ $art ]
719
- ( ',' { if (this.isStraightBefore('{')) break; } // allow ',' before '{' // }}
720
- includeRef[ $art ]
721
- )*
722
- )?
723
- )?
724
- ( // `aspect MyAspect {};`
725
- '{' { $art.elements = this.createDict(); }
726
- ( elementDef[ $art ]* )
727
- '}' { this.finalizeDictOrArray( $art.elements ); }
728
- // TODO: action definitions in a specific section?
729
- (
730
- ACTIONS '{' { $art.actions = this.createDict(); }
731
- actionFunctionDef[ $art ]*
732
- '}' { this.finalizeDictOrArray( $art.actions ); }
733
- )?
734
- optionalSemi
735
- | // `aspect MyAspect;`, e.g. for annotation aspects.
736
- { this.aspectWithoutElements( $art ); }
737
- requiredSemi
738
- )
524
+ typeSpec[ $art ]
525
+ defaultValue[ $art ]?
526
+ { this.docComment( $art ); }
527
+ annotationAssignment_ll1[ $art ]*
739
528
  ;
740
529
 
741
- typeDef[ art, outer ] locals[ name = {} ]
742
- @after { this.attachLocation( $art ); }
530
+ parameterListDef[ art ]
743
531
  :
744
- TYPE simplePath[ $name, 'Type' ]
745
- { this.addDef( $art, $outer, 'artifacts', 'type', $name );
746
- this.docComment( $art ); }
747
- annotationAssignment_fix[ $art ]*
748
- typeSpecSemi[ $art ]
532
+ '(' { $art.params = this.createDict(); }
533
+ // also empty param list (we might do some hacking later to allow reserved words)
534
+ // see annotationAssignment_paren
535
+ (
536
+ parameterDef[ $art ]
537
+ ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
538
+ parameterDef[ $art ]
539
+ )*
540
+ )?
541
+ ')' { this.finalizeDictOrArray( $art.params ); }
749
542
  ;
750
543
 
751
- extendType[ art, outer ] locals[ name = {} ]
544
+ enumSymbolDef[ outer ] locals[ art = {} ]
752
545
  @after { this.attachLocation( $art ); }
753
546
  :
754
- // aspects are types, i.e. kind is 'type' for aspects
755
- TYPE simplePath[ $name, 'Extend' ]
756
- { $art.expectedKind = 'type'; $art.name = $name;
757
- this.addItem( $art, $outer, 'extensions', 'extend' );
758
- }
759
- extendWithOptElementsOrType[ $art, $art ]
547
+ { $art.location = this.startLocation();; this.docComment( $art ); }
548
+ annotationAssignment_ll1[ $art ]*
549
+ name=ident['Enum']
550
+ { this.addDef( $art, $outer, 'enum', 'enum', $name.id );
551
+ this.docComment( $art ); }
552
+ annotationAssignment_ll1[ $art ]*
553
+ ( '='
554
+ { this.excludeExpected( ['Boolean', 'QuotedLiteral', "'#'", 'NULL'] ); }
555
+ (
556
+ val=literalValue
557
+ { $art.value = $val.val; }
558
+ |
559
+ ( plus='+' | min='-' ) num=Number
560
+ { $art.value = this.numberLiteral( $num, $plus||$min ); }
561
+ )
562
+ { this.docComment( $art ); }
563
+ annotationAssignment_ll1[ $art ]*
564
+ )?
565
+ requiredSemi
760
566
  ;
761
567
 
762
- annotationDef[ art, outer ] locals[ name = {} ]
763
- @after { this.attachLocation( $art ); }
568
+ elementDef[ outer ] locals[ $art = {} ]
764
569
  :
765
- annotation=ANNOTATION simplePath[ $name, 'AnnoDef' ]
766
- { if ($outer.kind !== 'source') { // this is a syntax restriction to avoid confusion
767
- this.error( 'syntax-unexpected-vocabulary', $annotation, { '#': $outer.kind } );
768
- $art = {}; }
769
- else {
770
- if (!$outer.vocabularies) $outer.vocabularies = Object.create(null);
771
- this.addDef( $art, $outer, 'vocabularies', 'annotation', $name );
570
+ { $art.location = this.startLocation();; this.docComment( $art ); }
571
+ annotationAssignment_ll1[ $art ]*
572
+ elementDefInner[ $art, $outer, false ]
573
+ ;
574
+
575
+ elementDefInner[ art, outer, allowEq ]
576
+ @after{ this.attachLocation( $art ); }
577
+ :
578
+ // VIRTUAL is keyword, except if before the following tokens texts:
579
+ { this.setLocalToken( 'VIRTUAL', 'VIRTUAL', /^[:{@=}]$/ ); }
580
+ ( virtual=VIRTUAL { $art.virtual = this.valueWithTokenLocation( true, $virtual ); } )?
581
+ ( key=KEY { $art.key = this.valueWithTokenLocation( true, $key ); } )?
582
+ { this.setLocalToken( 'MASKED', 'MASKED', /^[:{@=}]$/ ); }
583
+ ( masked=MASKED
584
+ {
585
+ $art.masked = this.valueWithTokenLocation( true, $masked ) ;
586
+ this.message( 'syntax-unsupported-masked', $masked, { keyword: 'masked' } );
772
587
  }
588
+ )?
589
+ { this.setLocalToken( 'ELEMENT', 'ELEMENT', /^[:{@=}]$/ ); }
590
+ ELEMENT?
591
+ name=ident['Element']
592
+ { this.addDef( $art, $outer, 'elements', 'element', $name.id );
773
593
  this.docComment( $art ); }
774
594
  annotationAssignment_fix[ $art ]*
775
- typeSpecSemi[ $art ] // also 'includes'...
595
+ // TODO: we can think of making the typeSpec optional and do checks instead:
596
+ // type optional with '=', type required otherwise
597
+ (
598
+ typeStruct[ $art ]
599
+ ( nullability[ $art ]
600
+ requiredSemi
601
+ | optionalSemi // NOT and NULL are reserved...
602
+ )
603
+ |
604
+ ':'
605
+ elementType[ $art ]
606
+ |
607
+ // this is also called for enum symbols (in EXTEND)
608
+ eq='=' e=expression // never introduce AS as syntax variant of '='
609
+ {
610
+ if (!$allowEq || $e.expr && !$e.expr.literal )
611
+ this.error( 'syntax-unsupported-calc-elem', $eq );
612
+ else if ($e.expr)
613
+ $art.value = $e.expr;
614
+ }
615
+ { this.docComment( $art ); }
616
+ annotationAssignment_ll1[ $art ]* // for enum symbol def via EXTEND
617
+ requiredSemi
618
+ )
619
+ ;
620
+
621
+ elementType[ art ] // TODO: split this monster rule
622
+ @after{ /* #ATN 3 */ this.attachLocation( $art ); }
623
+ :
624
+ // #ATN: referenced type name can be ASSOCIATION or ARRAY or TYPE or LOCALIZED
625
+ typeStruct[ $art ]
626
+ nullability[ $art ]?
627
+ requiredSemi
628
+ |
629
+ typeAssociationBase[ $art, true ]
630
+ // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
631
+ (
632
+ typeStruct[ $art.target, true ] optionalSemi
633
+ |
634
+ one=ONE
635
+ { this.setMaxCardinality( $art, $one, this.numberLiteral( $one, null, '1' ) ); }
636
+ typeCompoStruct[ $art.target ] optionalSemi
637
+ |
638
+ many=MANY
639
+ { this.setMaxCardinality( $art, $many, { literal: 'string', val: '*' } ); }
640
+ typeCompoStruct[ $art.target ] optionalSemi
641
+ |
642
+ // we do not support `Composition of many { e }` - ambiguity ad-hoc target versus foreign keys!
643
+ typeToMany[ $art ] typeAssociationElementCont[ $art ]
644
+ |
645
+ typeToOne[ $art ] typeAssociationElementCont[ $art ]
646
+ |
647
+ simplePath[ $art.target, 'artref' ] typeAssociationElementCont[ $art ]
648
+ )
649
+ |
650
+ (
651
+ array=ARRAY of=OF
652
+ { $art.items = { location: this.tokenLocation( $array, $of ) }; }
653
+ | many=MANY
654
+ { $art.items = { location: this.tokenLocation( $many ) };}
655
+ )
656
+ ( typeStruct[ $art.items ]
657
+ nullability[ $art.items ]?
658
+ | // #ATN: typeRefOptArgs/typeTypeOf can start with TYPE
659
+ ( typeTypeOf[ $art.items ] | typeRefOptArgs[ $art.items ] )
660
+ nullability[ $art.items ]? // only if not followed by `enum`
661
+ { this.docComment( $art ); }
662
+ annotationAssignment_ll1[ $art ]*
663
+ (
664
+ { if ($art.items.notNull) {
665
+ this.message( 'syntax-unexpected-null', $art.items.notNull.location,
666
+ { keyword: $art.items.notNull.val ? 'not null' : 'null' } );
667
+ }
668
+ }
669
+ ENUM '{' { $art.items.enum = this.createDict(); }
670
+ enumSymbolDef[ $art.items ]*
671
+ '}' { this.finalizeDictOrArray( $art.items.enum ); }
672
+ nullability[ $art.items ]?
673
+ )?
674
+ )
675
+ requiredSemi // also req after struct/enum
676
+ |
677
+ l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
678
+ typeRefOptArgs[ $art ]
679
+ { this.docComment( $art ); }
680
+ annotationAssignment_ll1[ $art ]*
681
+ ( elementProperties[ $art ]
682
+ { this.docComment( $art ); }
683
+ annotationAssignment_ll1[ $art ]*
684
+ )?
685
+ requiredSemi
686
+ |
687
+ typeTypeOf[ $art ] // Note: Same as the typeRefOptArgs rule below
688
+ { this.docComment( $art ); }
689
+ annotationAssignment_ll1[ $art ]*
690
+ (
691
+ ENUM '{' { $art.enum = this.createDict(); }
692
+ enumSymbolDef[ $art ]*
693
+ '}' { this.finalizeDictOrArray( $art.enum ); }
694
+ elementProperties[ $art ]?
695
+ |
696
+ elementProperties[ $art ]
697
+ { this.docComment( $art ); }
698
+ annotationAssignment_ll1[ $art ]*
699
+ )?
700
+ requiredSemi // also req after foreign key spec
701
+ |
702
+ typeRefOptArgs[ $art ] // Note: Same as the typeTypeOf rule above
703
+ { this.docComment( $art ); }
704
+ annotationAssignment_ll1[ $art ]*
705
+ (
706
+ ENUM '{' { $art.enum = this.createDict(); }
707
+ enumSymbolDef[ $art ]*
708
+ '}' { this.finalizeDictOrArray( $art.enum ); }
709
+ elementProperties[ $art ]?
710
+ |
711
+ elementProperties[ $art ]
712
+ { this.docComment( $art ); }
713
+ annotationAssignment_ll1[ $art ]*
714
+ )?
715
+ requiredSemi // also req after enum spec
716
+ ;
717
+
718
+ elementProperties[ elem ]
719
+ :
720
+ nullability[ $elem ]
721
+ defaultValue[ $elem ]?
722
+ |
723
+ defaultValue[ $elem ]
724
+ nullability[ $elem ]?
725
+ |
726
+ eq='='
727
+ { this.error( 'syntax-unsupported-calc-elem', $eq ); }
728
+ e=expression { $elem.value = $e.expr; }
729
+ ;
730
+
731
+ defaultValue[ art ] locals[ elem, elements = {} ]
732
+ :
733
+ // TODO: We may support structured default values here.
734
+ DEFAULT expr=expression { $art.default = $expr.expr; }
776
735
  ;
777
736
 
737
+ // Extend and annotate ----------------------------------------------------------
738
+
778
739
  extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
779
740
  @after{ /* #ATN 1 */ this.attachLocation( $art ); }
780
741
  :
781
742
  simplePath[ $name, 'Extend' ]
782
- ( ':' simplePath[ $elemName, 'Element'] )?
783
- { this.addExtension( $art, $outer, 'extend', $name, $elemName.path ); }
784
743
  (
785
- { this.docComment( $art ); }
744
+ ':' simplePath[ $elemName, 'Element']
745
+ { this.addExtension( $art, $outer, 'extend', $name, $elemName.path ); }
746
+ extendWithOptElementsOrType[ art ]
747
+ |
748
+ { this.addExtension( $art, $outer, 'extend', $name ); }
749
+ extendWithOptElementsNoWith[ art ]
750
+ |
751
+ { this.addExtension( $art, $outer, 'extend', $name ); }
752
+ WITH { this.noSemicolonHere(); this.docComment( $art ); }
786
753
  annotationAssignment_ll1[ $art ]*
754
+ // #ATN: ELEMENTS, ENUM, DEFINITIONS, COLUMNS, ACTIONS are not reserved and
755
+ // could be includeRef
787
756
  (
757
+ // all the alternatives from `extendWithOptElementsOrType` --------------
788
758
  '{' { $art.elements = this.createDict(); }
789
759
  elementDefOrExtend[ $art ]*
790
760
  '}' { this.finalizeDictOrArray( $art.elements ); }
@@ -792,32 +762,34 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
792
762
  optionalSemi
793
763
  |
794
764
  requiredSemi
795
- )
796
- |
797
- WITH { this.noSemicolonHere(); this.docComment( $art ); }
798
- annotationAssignment_ll1[ $art ]*
799
- // #ATN: DEFINITIONS, COLUMNS, ACTIONS etc are not reserved and could be identifiers (ref).
800
- // TODO: exclude "expected" according to disallowElementExtension()
801
- (
802
- includeRef[ $art ] ( ',' includeRef[ $art ] )*
803
- requiredSemi
804
765
  |
805
- '{' { $art.elements = this.createDict(); }
766
+ ELEMENTS '{' { $art.elements = this.createDict(); }
806
767
  elementDefOrExtend[ $art ]*
807
768
  '}' { this.finalizeDictOrArray( $art.elements ); }
808
- { this.checkExtensionDict( $art.elements ); }
769
+ { this.checkExtensionDict( $art.elements ); }
809
770
  optionalSemi
810
771
  |
772
+ ENUM '{' { $art.enum = this.createDict(); }
773
+ enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
774
+ '}' { this.finalizeDictOrArray( $art.enum ); }
775
+ optionalSemi
776
+ |
777
+ // extend Art with (length: 10);
778
+ // `with` is required, or we could have `extend String(length:10);`.
779
+ // future `extend Action with (param: Type)` now has ambiguity
780
+ typeNamedArgList[ $art ]
781
+ requiredSemi
782
+ |
783
+ // extension alternatives for main definitions --------------------------
784
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
811
785
  requiredSemi
812
786
  |
813
- { this.disallowElementExtension( $elemName, $outer, 'definitions' ); }
814
787
  DEFINITIONS
815
788
  '{' { $art.artifacts = this.createDict(); }
816
- artifactDef[ $art, true ]*
789
+ artifactDefOrExtend[ $art, true ]*
817
790
  '}' { this.finalizeDictOrArray( $art.artifacts ); }
818
791
  optionalSemi
819
792
  |
820
- { this.disallowElementExtension( $elemName, $outer, 'columns' ); }
821
793
  COLUMNS
822
794
  '{' { $art.columns = []; }
823
795
  (
@@ -829,85 +801,239 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
829
801
  '}'
830
802
  optionalSemi
831
803
  |
832
- { this.disallowElementExtension( $elemName, $outer, 'actions' ); }
833
804
  ACTIONS '{' { $art.actions = this.createDict(); }
834
805
  actionFunctionDef[ $art ]* // TODO: no EXTEND in actions? (ok, would just allow annos)
835
806
  '}' { this.finalizeDictOrArray( $art.actions ); }
836
807
  optionalSemi
837
- |
838
- ELEMENTS '{' { $art.elements = this.createDict(); }
839
- elementDefOrExtend[ $art ]*
840
- '}' { this.finalizeDictOrArray( $art.elements ); }
841
- { this.checkExtensionDict( $art.elements ); }
842
- optionalSemi
843
- |
844
- ENUM '{' { $art.enum = this.createDict(); }
845
- enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
846
- '}' { this.finalizeDictOrArray( $art.enum ); }
847
- optionalSemi
848
- |
849
- // extend Art with (length: 10);
850
- // `with` is required, or we could have `extend String(length:10);`.
851
- typeNamedArgList[ $art ]
852
- requiredSemi
853
808
  )
809
+ // TODO: what about adding both ELEMENTS and ACTIONS? (TODO: csn input test & to-cdl)
854
810
  )
855
811
  ;
856
812
 
857
- extendWithOptElementsOrType[ art ]
813
+ extendService[ art, outer ] locals[ name = {} ]
814
+ @after { this.attachLocation( $art ); }
858
815
  :
859
- WITH { this.noSemicolonHere(); this.docComment( $art ); }
860
- annotationAssignment_ll1[ $art ]*
861
- (
862
- includeRef[ $art ] ( ',' includeRef[ $art ] )*
863
- requiredSemi
864
- |
865
- '{' { $art.elements = this.createDict(); }
866
- elementDefOrExtend[ $art ]*
867
- '}' { this.finalizeDictOrArray( $art.elements ); }
868
- { this.checkExtensionDict( $art.elements ); }
869
- optionalSemi
870
- |
871
- // extend type|element Art with (length: 10);
872
- typeNamedArgList[ $art ]
873
- requiredSemi
874
- |
875
- requiredSemi
876
- )
877
- |
878
- { this.docComment( $art ); }
879
- annotationAssignment_ll1[ $art ]*
880
- (
881
- '{' { $art.elements = this.createDict(); }
882
- elementDefOrExtend[ $art ]*
883
- '}' { this.finalizeDictOrArray( $art.elements ); }
884
- { this.checkExtensionDict( $art.elements ); }
885
- optionalSemi
886
- |
887
- requiredSemi
888
- )
816
+ SERVICE { $art.expectedKind = 'service'; }
817
+ simplePath[ $name, 'Service' ]
818
+ { $art.name = $name; this.addItem( $art, $outer, 'extensions', 'extend' ); }
819
+ ( WITH { this.noSemicolonHere(); } )?
820
+ optArtifactsBlock[ art, 'SERVICE' ]
889
821
  ;
890
822
 
891
- annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
823
+ extendContext[ art, outer ] locals[ name = {} ]
892
824
  @after { this.attachLocation( $art ); }
893
825
  :
894
- simplePath[ $name, 'Annotate' ]
895
- ( ':' simplePath[ $elemName, 'Element'] )?
896
- { this.addExtension( $art, $outer, 'annotate', $name, $elemName.path ); }
826
+ CONTEXT { $art.expectedKind = 'context'; }
827
+ simplePath[ $name, 'Context' ]
828
+ { $art.name = $name; this.addItem( $art, $outer, 'extensions', 'extend' ); }
897
829
  ( WITH { this.noSemicolonHere(); } )?
898
- { this.docComment( $art ); }
899
- annotationAssignment_ll1[ $art ]*
830
+ optArtifactsBlock[ art, 'CONTEXT' ]
831
+ ;
832
+
833
+ extendEntityOrAspect[ art, outer ] locals[ name = {} ]
834
+ @after { /* #ATN 1 */ this.attachLocation( $art ); }
835
+ :
836
+ kind=(ASPECT | ENTITY) simplePath[ $name, 'Extend' ]
837
+ { $art.expectedKind = $kind.text.toLowerCase(); $art.name = $name;
838
+ this.addItem( $art, $outer, 'extensions', 'extend' );
839
+ }
900
840
  (
901
- '{' { $art.elements = this.createDict(); }
902
- annotateElement[ $art ]*
903
- '}' { this.finalizeDictOrArray( $art.elements ); }
904
- { this.checkExtensionDict( $art.elements ); }
841
+ WITH { this.noSemicolonHere(); this.docComment( $art ); }
842
+ annotationAssignment_ll1[ $art ]*
843
+ // ATN: the ref can start with ACTIONS
905
844
  (
906
- ACTIONS
907
- '{' { $art.actions = this.createDict(); }
908
- annotateAction[ $art ]*
909
- '}' { this.finalizeDictOrArray( $art.actions ); }
910
- { this.checkExtensionDict( $art.actions ); }
845
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
846
+ requiredSemi
847
+ |
848
+ extendForEntity[ $art ]
849
+ )
850
+ |
851
+ { this.docComment( $art ); }
852
+ annotationAssignment_ll1[ $art ]*
853
+ extendForEntity[ $art ]
854
+ )
855
+ ;
856
+
857
+ extendForEntity[ art ]
858
+ :
859
+ '{' { $art.elements = this.createDict(); }
860
+ elementDefOrExtend[ $art ]*
861
+ '}' { this.finalizeDictOrArray( $art.elements ); }
862
+ (
863
+ ACTIONS '{' { $art.actions = this.createDict(); }
864
+ actionFunctionDef[ $art ]*
865
+ '}' { this.finalizeDictOrArray( $art.actions ); }
866
+ )?
867
+ optionalSemi
868
+ |
869
+ ACTIONS '{' { $art.actions = this.createDict(); }
870
+ actionFunctionDef[ $art ]*
871
+ '}' { this.finalizeDictOrArray( $art.actions ); }
872
+ optionalSemi
873
+ |
874
+ requiredSemi
875
+ ;
876
+
877
+ extendProjection[ art, outer ] locals[ name = {} ]
878
+ @after { this.attachLocation( $art ); }
879
+ :
880
+ expected=PROJECTION simplePath[ $name, 'Extend' ]
881
+ { $art.expectedKind = 'entity'; $art.name = $name;
882
+ this.addItem( $art, $outer, 'extensions', 'extend' );
883
+ }
884
+ ( WITH { this.noSemicolonHere(); } )?
885
+ { this.docComment( $art ); }
886
+ annotationAssignment_ll1[ $art ]*
887
+ (
888
+ '{' { $art.columns = []; }
889
+ (
890
+ selectItemDef[ $art.columns ]
891
+ ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
892
+ selectItemDef[ $art.columns ]
893
+ )*
894
+ )?
895
+ '}'
896
+ (
897
+ ACTIONS '{' { $art.actions = this.createDict(); }
898
+ actionFunctionDef[ $art ]*
899
+ '}' { this.finalizeDictOrArray( $art.actions ); }
900
+ )?
901
+ optionalSemi
902
+ |
903
+ ACTIONS '{' { $art.actions = this.createDict(); }
904
+ actionFunctionDef[ $art ]*
905
+ '}' { this.finalizeDictOrArray( $art.actions ); }
906
+ optionalSemi
907
+ |
908
+ requiredSemi
909
+ )
910
+ ;
911
+
912
+ extendType[ art, outer ] locals[ name = {} ]
913
+ @after { this.attachLocation( $art ); }
914
+ :
915
+ TYPE simplePath[ $name, 'Extend' ]
916
+ { $art.expectedKind = 'type'; $art.name = $name;
917
+ this.addItem( $art, $outer, 'extensions', 'extend' );
918
+ }
919
+ // extendWithOptElementsOrType + includeRef:
920
+ (
921
+ extendWithOptElementsNoWith[ art ]
922
+ |
923
+ WITH { this.noSemicolonHere(); this.docComment( $art ); }
924
+ annotationAssignment_ll1[ $art ]*
925
+ (
926
+ '{' { $art.elements = this.createDict(); }
927
+ elementDefOrExtend[ $art ]*
928
+ '}' { this.finalizeDictOrArray( $art.elements ); }
929
+ { this.checkExtensionDict( $art.elements ); }
930
+ optionalSemi
931
+ |
932
+ // extend type|element Art with (length: 10);
933
+ typeNamedArgList[ $art ]
934
+ requiredSemi
935
+ |
936
+ requiredSemi
937
+ |
938
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
939
+ requiredSemi
940
+ )
941
+ )
942
+ ;
943
+
944
+ extendWithOptElementsOrType[ art ]
945
+ :
946
+ extendWithOptElementsNoWith[ art ]
947
+ |
948
+ WITH { this.noSemicolonHere(); this.docComment( $art ); }
949
+ annotationAssignment_ll1[ $art ]*
950
+ (
951
+ '{' { $art.elements = this.createDict(); }
952
+ elementDefOrExtend[ $art ]*
953
+ '}' { this.finalizeDictOrArray( $art.elements ); }
954
+ { this.checkExtensionDict( $art.elements ); }
955
+ optionalSemi
956
+ |
957
+ ELEMENTS '{' { $art.elements = this.createDict(); }
958
+ elementDefOrExtend[ $art ]*
959
+ '}' { this.finalizeDictOrArray( $art.elements ); }
960
+ { this.checkExtensionDict( $art.elements ); }
961
+ optionalSemi
962
+ |
963
+ ENUM '{' { $art.enum = this.createDict(); }
964
+ enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
965
+ '}' { this.finalizeDictOrArray( $art.enum ); }
966
+ optionalSemi
967
+ |
968
+ // extend type|element Art with (length: 10);
969
+ typeNamedArgList[ $art ]
970
+ requiredSemi
971
+ |
972
+ requiredSemi
973
+ )
974
+ ;
975
+
976
+ extendWithOptElementsNoWith[ art ]
977
+ :
978
+ { this.docComment( $art ); }
979
+ annotationAssignment_ll1[ $art ]*
980
+ (
981
+ '{' { $art.elements = this.createDict(); }
982
+ elementDefOrExtend[ $art ]*
983
+ '}' { this.finalizeDictOrArray( $art.elements ); }
984
+ { this.checkExtensionDict( $art.elements ); }
985
+ optionalSemi
986
+ |
987
+ requiredSemi
988
+ )
989
+ ;
990
+
991
+ // For `extend … with elements` or `extend entity … with`, `extend aspect … with`,
992
+ // i.e. definitions in { … } are never enums
993
+ elementDefOrExtend[ outer ] locals[ art = {} ]
994
+ @after { /* #ATN 1 */ } // if ($art) this.attachLocation( $art ); }
995
+ :
996
+ { $art.location = this.startLocation();; this.docComment( $art ); }
997
+ annotationAssignment_ll1[ $art ]*
998
+ // #ATN: element name for definition can be EXTEND
999
+ (
1000
+ EXTEND
1001
+ extendElement[ $art, $outer ]
1002
+ |
1003
+ elementDefInner[ $art, $outer, true ]
1004
+ )
1005
+ ;
1006
+
1007
+ extendElement[ art, outer ]
1008
+ @after{ this.attachLocation( $art ); }
1009
+ :
1010
+ { this.setLocalToken( 'ELEMENT', 'ELEMENT', /^([:{@=}()]|WITH)$/i ); }
1011
+ ( expected=ELEMENT { $art.expectedKind = 'element'; } )?
1012
+ name=ident['Element']
1013
+ { this.addDef( $art, $outer, 'elements', 'extend', $name.id ); }
1014
+ extendWithOptElementsOrType[ $art, $art ]
1015
+ ;
1016
+
1017
+ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
1018
+ @after { this.attachLocation( $art ); }
1019
+ :
1020
+ simplePath[ $name, 'Annotate' ]
1021
+ ( ':' simplePath[ $elemName, 'Element'] )?
1022
+ { this.addExtension( $art, $outer, 'annotate', $name, $elemName.path ); }
1023
+ ( WITH { this.noSemicolonHere(); } )?
1024
+ { this.docComment( $art ); }
1025
+ annotationAssignment_ll1[ $art ]*
1026
+ (
1027
+ '{' { $art.elements = this.createDict(); }
1028
+ annotateElement[ $art ]*
1029
+ '}' { this.finalizeDictOrArray( $art.elements ); }
1030
+ { this.checkExtensionDict( $art.elements ); }
1031
+ (
1032
+ ACTIONS
1033
+ '{' { $art.actions = this.createDict(); }
1034
+ annotateAction[ $art ]*
1035
+ '}' { this.finalizeDictOrArray( $art.actions ); }
1036
+ { this.checkExtensionDict( $art.actions ); }
911
1037
  )?
912
1038
  optionalSemi
913
1039
  |
@@ -1008,458 +1134,44 @@ annotateParam [ outer ] locals [ art = {} ]
1008
1134
  annotationAssignment_ll1[ $art ]*
1009
1135
  ;
1010
1136
 
1011
- // Element definition and its helpers ----------------------------------------
1012
-
1013
- enumSymbolDef[ outer ] locals[ art = {} ]
1014
- @after { this.attachLocation( $art ); }
1015
- :
1016
- { $art.location = this.startLocation();; this.docComment( $art ); }
1017
- annotationAssignment_ll1[ $art ]*
1018
- name=ident['Enum']
1019
- { this.addDef( $art, $outer, 'enum', 'enum', $name.id );
1020
- this.docComment( $art ); }
1021
- annotationAssignment_ll1[ $art ]*
1022
- ( '='
1023
- { this.excludeExpected( ['Boolean', 'QuotedLiteral', "'#'", 'NULL'] ); }
1024
- (
1025
- val=literalValue
1026
- { $art.value = $val.val; }
1027
- |
1028
- ( plus='+' | min='-' ) num=Number
1029
- { $art.value = this.numberLiteral( $num, $plus||$min ); }
1030
- )
1031
- { this.docComment( $art ); }
1032
- annotationAssignment_ll1[ $art ]*
1033
- )?
1034
- requiredSemi
1035
- ;
1137
+ // Type expressions -------------------------------------------------------------
1036
1138
 
1037
- defaultValue[ art ] locals[ elem, elements = {} ]
1139
+ includeRef[ art ] locals[ incl = {} ]
1038
1140
  :
1039
- // TODO: We may support structured default values here.
1040
- DEFAULT expr=expression { $art.default = $expr.expr; }
1141
+ simplePath[ $incl, 'artref' ]
1142
+ { if ($art.includes) $art.includes.push($incl); else $art.includes = [$incl]; }
1041
1143
  ;
1042
1144
 
1043
- elementDefOrExtend[ outer ] locals[ art = {} ]
1044
- @after { /* #ATN 1 */ } // if ($art) this.attachLocation( $art ); }
1145
+ typeSpec[ art ] // for parameterDef
1146
+ @after{ /* #ATN 1 */ }
1045
1147
  :
1046
- { $art.location = this.startLocation();; this.docComment( $art ); }
1047
- annotationAssignment_ll1[ $art ]*
1048
- // #ATN: element name for definition can be EXTEND
1049
- (
1050
- EXTEND
1051
- extendElement[ $art, $outer ]
1052
- |
1053
- elementDefInner[ $art, $outer, true ]
1148
+ typeStruct[ $art ]
1149
+ |
1150
+ ':'
1151
+ // #ATN: typeSimple can start with ARRAY or TYPE
1152
+ ( typeStruct[ $art ]
1153
+ nullability[ $art ]?
1154
+ | typeArray[ $art ] // nullability is set in typeArray
1155
+ | typeTypeOf[ $art ]
1156
+ nullability[ $art ]?
1157
+ (
1158
+ ENUM '{' { $art.enum = this.createDict(); }
1159
+ enumSymbolDef[ $art ]*
1160
+ '}' { this.finalizeDictOrArray( $art.enum ); }
1161
+ )?
1162
+ // TODO: no LOCALIZED ?
1163
+ | typeRefOptArgs[ $art ]
1164
+ nullability[ $art ]?
1165
+ (
1166
+ ENUM '{' { $art.enum = this.createDict(); }
1167
+ enumSymbolDef[ $art ]*
1168
+ '}' { this.finalizeDictOrArray( $art.enum ); }
1169
+ )?
1054
1170
  )
1055
1171
  ;
1056
1172
 
1057
- elementDef[ outer ] locals[ $art = {} ]
1058
- :
1059
- { $art.location = this.startLocation();; this.docComment( $art ); }
1060
- annotationAssignment_ll1[ $art ]*
1061
- elementDefInner[ $art, $outer, false ]
1062
- ;
1063
-
1064
- // Actually, this is a subset if elementDefInner...
1065
- // TODO: the corresponding restrictions must also be checked in the core
1066
- // compiler, as the mixin element could come via CSN
1067
- mixinElementDef[ outer ] locals[ art = {} ]
1068
- @after { /* #ATN 2 */ this.attachLocation($art); }
1069
- :
1070
- name=ident['Mixin']
1071
- { this.addDef( $art, $outer, 'mixin', 'mixin', $name.id ); }
1072
- (
1073
- ':'
1074
- // #ATN: referenced type name can be ASSOCIATION or COMPOSITION
1075
- (
1076
- typeAssociationBase[ $art, false ]
1077
- // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
1078
- ( typeToMany[ $art ] | typeToOne[ $art ] | simplePath[ $art.target, 'artref' ] )
1079
- typeAssociationCont[ $art ]?
1080
- |
1081
- typeRefOptArgs[ $art ]
1082
- ( as='=' expression
1083
- { this.error( 'syntax-unsupported-field', $as ); }
1084
- )?
1085
- )
1086
- |
1087
- as='=' expression
1088
- { this.error( 'syntax-unsupported-field', $as ); }
1089
- )
1090
- requiredSemi
1091
- ;
1092
-
1093
- elementDefInner[ art, outer, allowEq ]
1094
- @after{ /* #ATN 5 */ this.attachLocation( $art ); }
1095
- :
1096
- // TODO: it would be excellent to remove ELEMENT...
1097
- // or have a special ident rule without the ELEMENT
1098
- // Reason: it would be good for error recovery to start a major block without LL1 ambiguity
1099
- // VIRTUAL is keyword, except if before the following tokens texts:
1100
- { this.setLocalToken( 'VIRTUAL', 'VIRTUAL', /^[:{@=}]$/ ); }
1101
- ( virtual=VIRTUAL { $art.virtual = this.valueWithTokenLocation( true, $virtual ); } )?
1102
- ( key=KEY { $art.key = this.valueWithTokenLocation( true, $key ); } )?
1103
- // #ATN: element name can be MASKED or ELEMENT (2x)
1104
- ( masked=MASKED
1105
- {
1106
- $art.masked = this.valueWithTokenLocation( true, $masked ) ;
1107
- this.message( 'syntax-unsupported-masked', $masked, { keyword: 'masked' } );
1108
- }
1109
- )?
1110
- // TODO: order?
1111
- ELEMENT?
1112
- name=ident['Element']
1113
- { this.addDef( $art, $outer, 'elements', 'element', $name.id );
1114
- this.docComment( $art ); }
1115
- annotationAssignment_fix[ $art ]*
1116
- // TODO: we can think of making the typeSpec optional and do checks instead:
1117
- // type optional with '=', type required otherwise
1118
- (
1119
- typeStruct[ $art ]
1120
- ( nullability[ $art ]
1121
- requiredSemi
1122
- | optionalSemi // NOT and NULL are reserved...
1123
- )
1124
- |
1125
- ':'
1126
- // #ATN: referenced type name can be ASSOCIATION or ARRAY or TYPE or LOCALIZED
1127
- (
1128
- typeStruct[ $art ]
1129
- nullability[ $art ]?
1130
- requiredSemi
1131
- |
1132
- typeAssociationBase[ $art, true ]
1133
- // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
1134
- (
1135
- typeStruct[ $art.target, true ] optionalSemi
1136
- |
1137
- one=ONE
1138
- { this.setMaxCardinality( $art, $one, this.numberLiteral( $one, null, '1' ) ); }
1139
- typeCompoStruct[ $art.target ] optionalSemi
1140
- |
1141
- many=MANY
1142
- { this.setMaxCardinality( $art, $many, { literal: 'string', val: '*' } ); }
1143
- typeCompoStruct[ $art.target ] optionalSemi
1144
- |
1145
- // we do not support `Composition of many { e }` - ambiguity ad-hoc target versus foreign keys!
1146
- typeToMany[ $art ] typeAssociationElementCont[ $art ]
1147
- |
1148
- typeToOne[ $art ] typeAssociationElementCont[ $art ]
1149
- |
1150
- simplePath[ $art.target, 'artref' ] typeAssociationElementCont[ $art ]
1151
- )
1152
- |
1153
- (
1154
- array=ARRAY of=OF
1155
- { $art.items = { location: this.tokenLocation( $array, $of ) }; }
1156
- | many=MANY
1157
- { $art.items = { location: this.tokenLocation( $many ) };}
1158
- )
1159
- // #ATN: typeRefOptArgs can start with TYPE
1160
- ( typeStruct[ $art.items ]
1161
- nullability[ $art.items ]?
1162
- | typeTypeOf[ $art.items ]
1163
- nullability[ $art.items ]?
1164
- { this.docComment( $art ); }
1165
- annotationAssignment_ll1[ $art ]*
1166
- | typeRefOptArgs[ $art.items ]
1167
- nullability[ $art.items ]? // only if not followed by `enum`
1168
- { this.docComment( $art ); }
1169
- annotationAssignment_ll1[ $art ]*
1170
- (
1171
- { if ($art.items.notNull) {
1172
- this.message( 'syntax-unexpected-null', $art.items.notNull.location,
1173
- { keyword: $art.items.notNull.val ? 'not null' : 'null' } );
1174
- }
1175
- }
1176
- ENUM '{' { $art.items.enum = this.createDict(); }
1177
- enumSymbolDef[ $art.items ]*
1178
- '}' { this.finalizeDictOrArray( $art.items.enum ); }
1179
- nullability[ $art.items ]?
1180
- )?
1181
- )
1182
- requiredSemi // also req after struct/enum
1183
- |
1184
- typeTypeOf[ $art ] elementProperties[ $art ]?
1185
- { this.docComment( $art ); }
1186
- annotationAssignment_ll1[ $art ]*
1187
- requiredSemi // also req after foreign key spec
1188
- |
1189
- l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
1190
- typeRefOptArgs[ $art ]
1191
- { this.docComment( $art ); }
1192
- annotationAssignment_ll1[ $art ]*
1193
- ( elementProperties[ $art ]
1194
- { this.docComment( $art ); }
1195
- annotationAssignment_ll1[ $art ]*
1196
- )?
1197
- requiredSemi
1198
- |
1199
- typeRefOptArgs[ $art ]
1200
- { this.docComment( $art ); }
1201
- annotationAssignment_ll1[ $art ]*
1202
- (
1203
- ENUM '{' { $art.enum = this.createDict(); }
1204
- enumSymbolDef[ $art ]*
1205
- '}' { this.finalizeDictOrArray( $art.enum ); }
1206
- elementProperties[ $art ]?
1207
- |
1208
- elementProperties[ $art ]
1209
- { this.docComment( $art ); }
1210
- annotationAssignment_ll1[ $art ]*
1211
- )?
1212
- requiredSemi // also req after enum spec
1213
- )
1214
- |
1215
- // this is also called for enum symbols (in EXTEND)
1216
- eq='=' e=expression // never introduce AS as syntax variant of '='
1217
- {
1218
- if (!$allowEq || $e.expr && !$e.expr.literal )
1219
- this.error( 'syntax-unsupported-field', $eq );
1220
- else if ($e.expr)
1221
- $art.value = $e.expr;
1222
- }
1223
- { this.docComment( $art ); }
1224
- annotationAssignment_ll1[ $art ]* // for enum symbol def via EXTEND
1225
- requiredSemi
1226
- )
1227
- ;
1228
-
1229
- extendElement[ art, outer ]
1230
- @after{ /* #ATN 1 */ this.attachLocation( $art ); }
1231
- :
1232
- // #ATN: element name can be ELEMENT
1233
- ( expected=ELEMENT { $art.expectedKind = 'element'; } )?
1234
- name=ident['Element']
1235
- { this.addDef( $art, $outer, 'elements', 'extend', $name.id ); }
1236
- extendWithOptElementsOrType[ $art, $art ]
1237
- ;
1238
-
1239
- selectItemDef[ outer ] locals[ art ]
1240
- @after{ if ($art) this.attachLocation( $art ); }
1241
- :
1242
- star='*'
1243
- { $outer.push( this.valueWithTokenLocation( '*', $star ) ); }
1244
- |
1245
- { $art = {};; this.docComment( $art ); }
1246
- annotationAssignment_atn[ $art ]*
1247
- // VIRTUAL is keyword, except if before the following tokens texts:
1248
- { this.setLocalToken( 'VIRTUAL', 'VIRTUAL', /^([,.:\[@]|as)$/i ) ; } // not '{'
1249
- ( virtual=VIRTUAL { $art.virtual = this.valueWithTokenLocation( true, $virtual ); } )?
1250
- ( key=KEY { $art.key = this.valueWithTokenLocation( true, $key ); } )?
1251
- selectItemDefBody[ $art, $outer ]
1252
- ;
1253
-
1254
- selectItemDefBody[ art, outer ]
1255
- @after{ /* #ATN 2 */ }
1256
- :
1257
- { $outer.push( $art ); }
1258
- (
1259
- e=expression { $art.value = $e.expr; }
1260
- // we cannot use 'condition' instead, as long as we allow aliases without
1261
- // AS (using rule 'ident' instead of 'identNoKeyword') -> ambiguities
1262
- ( as=AS n1=ident['Item'] { $art.name = $n1.id }
1263
- | n2=ident['Item'] { $art.name = this.fragileAlias( $n2.id, true ); }
1264
- | { if (this.getCurrentToken().text !== '.') this.classifyImplicitName( 'Item', $e.expr ); }
1265
- )
1266
- { if ($art.value && !$art.value.path) this.excludeExpected( ["'.'", "'{'"] );
1267
- else if ($art.name) this.excludeExpected( ["'.'"] );
1268
- }
1269
- (
1270
- { this.reportExpandInline( $art, false ); }
1271
- selectItemInlineList[ $art, 'expand' ]
1272
- excludingClause[ $art ]?
1273
- // TODO: we might alternatively allow AS here
1274
- |
1275
- { this.reportExpandInline( $art, $as || this._input.LT(-1) ); }
1276
- DOTbeforeBRACE // ...orASTERISK
1277
- (
1278
- selectItemInlineList[ $art, 'inline' ]
1279
- excludingClause[ $art ]?
1280
- |
1281
- star='*'
1282
- { $art.inline = [ this.valueWithTokenLocation( '*', $star ) ]; }
1283
- )
1284
- )?
1285
- |
1286
- selectItemInlineList[ $art, 'expand' ]
1287
- excludingClause[ $art ]?
1288
- AS n1=ident['Item'] { $art.name = $n1.id }
1289
- )
1290
- { this.docComment( $art ); }
1291
- annotationAssignment_fix[ $art ]*
1292
- ( ':'
1293
- // #ATN: typeRefOptArgs can start with TYPE, REDIRECTED
1294
- ( re=REDIRECTED to=TO
1295
- { $art.target = {}; }
1296
- simplePath[ $art.target, 'artref' ]
1297
- (
1298
- typeAssociationCont[ $art ]
1299
- |
1300
- { this.docComment( $art ); }
1301
- annotationAssignment_ll1[ $art ]*
1302
- )
1303
- | typeTypeOf[ $art ]
1304
- { this.docComment( $art ); }
1305
- annotationAssignment_ll1[ $art ]*
1306
- | typeRefOptArgs[ $art ]
1307
- { this.docComment( $art ); }
1308
- annotationAssignment_ll1[ $art ]*
1309
- |
1310
- typeAssociationBase[ $art, false ]
1311
- // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
1312
- ( typeToMany[ $art ] | typeToOne[ $art ] | simplePath[ $art.target, 'artref' ] )
1313
- typeAssociationCont[ $art ]?
1314
- { this.associationInSelectItem( $art ); }
1315
- )
1316
- )?
1317
- ;
1318
-
1319
- bracedSelectItemListDef[ query ]
1320
- :
1321
- '{' { $query.columns = this.createArray(); }
1322
- (
1323
- selectItemDef[ $query.columns ]
1324
- ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1325
- selectItemDef[ $query.columns ]
1326
- )*
1327
- )?
1328
- '}' { this.finalizeDictOrArray( $query.columns ); }
1329
- ;
1330
-
1331
- selectItemInlineList[ art, clause ]
1332
- :
1333
- '{' { $art[$clause] = this.createArray(); }
1334
- (
1335
- selectItemInlineDef[ $art[$clause] ]
1336
- ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1337
- selectItemInlineDef[ $art[$clause] ]
1338
- )*
1339
- )?
1340
- '}' { this.finalizeDictOrArray( $art[$clause] ); }
1341
- ;
1342
-
1343
- selectItemInlineDef[ outer ] locals[ art ]
1344
- @after{ if ($art) this.attachLocation( $art ); }
1345
- :
1346
- star='*'
1347
- { $outer.push( this.valueWithTokenLocation( '*', $star ) ); }
1348
- |
1349
- { $art = {};; this.docComment( $art ); }
1350
- annotationAssignment_atn[ $art ]*
1351
- selectItemDefBody[ $art, $outer ]
1352
- ;
1353
-
1354
- parameterListDef[ art ]
1355
- :
1356
- '(' { $art.params = this.createDict(); }
1357
- // also empty param list (we might do some hacking later to allow reserved words)
1358
- // see annotationAssignment_paren
1359
- (
1360
- parameterDef[ $art ]
1361
- ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1362
- parameterDef[ $art ]
1363
- )*
1364
- )?
1365
- ')' { this.finalizeDictOrArray( $art.params ); }
1366
- ;
1367
-
1368
- parameterDef[ outer ] locals[ art = {} ]
1369
- @after { this.attachLocation( $art ); }
1370
- :
1371
- { this.docComment( $art ); }
1372
- annotationAssignment_ll1[ $art ]*
1373
- name=ident['Param']
1374
- { this.addDef( $art, $outer, 'params', 'param', $name.id );
1375
- this.docComment( $art ); }
1376
- annotationAssignment_fix[ $art ]*
1377
- typeSpec[ $art ]
1378
- defaultValue[ $art ]?
1379
- { this.docComment( $art ); }
1380
- annotationAssignment_ll1[ $art ]*
1381
- ;
1382
-
1383
- nullability[ art ]
1384
- :
1385
- not=NOT n1=NULL
1386
- { $art.notNull = this.valueWithTokenLocation( true, $not, $n1 ); }
1387
- |
1388
- n2=NULL
1389
- { $art.notNull = this.valueWithTokenLocation( false, $n2 ); }
1390
- ;
1391
-
1392
- elementProperties[ elem ]
1393
- :
1394
- nullability[ $elem ]
1395
- defaultValue[ $elem ]?
1396
- |
1397
- defaultValue[ $elem ]
1398
- nullability[ $elem ]?
1399
- |
1400
- eq='='
1401
- { this.error( 'syntax-unsupported-field', $eq ); }
1402
- expression
1403
- ;
1404
-
1405
- // View definitions ----------------------------------------------------------
1406
-
1407
- viewDef[ art, outer ] locals[ name = {} ]
1408
- @after { this.attachLocation( $art ); }
1409
- :
1410
- v=VIEW simplePath[ $name, 'Entity' ]
1411
- { $art['$'+'syntax'] = 'view';
1412
- this.addDef( $art, $outer, 'artifacts', 'entity', $name );
1413
- this.docComment( $art ); }
1414
- annotationAssignment_fix[ $art ]*
1415
- (
1416
- parameterListDef[ $art ]
1417
- |
1418
- // TODO: warning deprecated?
1419
- ( HideAlternatives | WITH ) { $art.params = this.createDict(); }
1420
- PARAMETERS
1421
- parameterDef[ $art ]
1422
- ( ',' parameterDef[ $art ] )* // no optional final ',' here
1423
- { this.finalizeDictOrArray( $art.params ); }
1424
- )?
1425
- AS qe=queryExpression { $art.query = $qe.query; }
1426
- // TODO check ANTLR: bad msg with 'view V as'<eof> but 'view V as FOO' is fine
1427
- requiredSemi
1428
- ;
1429
-
1430
- // Type references -----------------------------------------------------------
1431
-
1432
- includeRef[ art ] locals[ incl = {} ]
1433
- :
1434
- simplePath[ $incl, 'artref' ]
1435
- { if ($art.includes) $art.includes.push($incl); else $art.includes = [$incl]; }
1436
- ;
1437
-
1438
- typeSpec[ art ] // for params
1439
- @after{ /* #ATN 1 */ }
1440
- :
1441
- typeStruct[ $art ]
1442
- |
1443
- ':'
1444
- // #ATN: typeSimple can start with ARRAY or TYPE
1445
- ( typeStruct[ $art ]
1446
- nullability[ $art ]?
1447
- | typeArray[ $art ] // nullability is set in typeArray
1448
- | typeTypeOf[ $art ]
1449
- nullability[ $art ]?
1450
- // TODO: no LOCALIZED ?
1451
- | typeRefOptArgs[ $art ]
1452
- nullability[ $art ]?
1453
- (
1454
- ENUM '{' { $art.enum = this.createDict(); }
1455
- enumSymbolDef[ $art ]*
1456
- '}' { this.finalizeDictOrArray( $art.enum ); }
1457
- )?
1458
- )
1459
- ;
1460
-
1461
- returnTypeSpec[ art ]
1462
- @after{ /* #ATN 1 */ }
1173
+ returnTypeSpec[ art ]
1174
+ @after{ /* #ATN 1 */ }
1463
1175
  :
1464
1176
  ret=RETURNS { $art.returns = { location: this.tokenLocation( $ret ), kind: 'param' }; }
1465
1177
  // #ATN: typeSimple can start with ARRAY or TYPE
@@ -1468,6 +1180,11 @@ returnTypeSpec[ art ]
1468
1180
  | typeArray[ $art.returns ] // nullability is set in typeArray
1469
1181
  | typeTypeOf[ $art.returns ]
1470
1182
  nullability[ $art.returns ]?
1183
+ (
1184
+ ENUM '{' { $art.returns.enum = this.createDict(); }
1185
+ enumSymbolDef[ $art.returns ]*
1186
+ '}' { this.finalizeDictOrArray( $art.returns.enum ); }
1187
+ )?
1471
1188
  // TODO: no LOCALIZED ?
1472
1189
  | typeRefOptArgs[ $art.returns ]
1473
1190
  nullability[ $art.returns ]?
@@ -1627,6 +1344,11 @@ typeArray[ art ]
1627
1344
  nullability[ $art.items ]?
1628
1345
  | typeTypeOf[ $art.items ]
1629
1346
  nullability[ $art.items ]?
1347
+ (
1348
+ ENUM '{' { $art.items.enum = this.createDict(); }
1349
+ enumSymbolDef[ $art.items ]*
1350
+ '}' { this.finalizeDictOrArray( $art.items.enum ); }
1351
+ )?
1630
1352
  | typeRefOptArgs[ $art.items ]
1631
1353
  nullability[ $art.items ]?
1632
1354
  (
@@ -1742,235 +1464,499 @@ cardinality[ art ] locals[ card = {} ]
1742
1464
  location: this.tokenLocation($trgMaxStar) }; }
1743
1465
  )
1744
1466
  )?
1745
- ']'
1746
- ;
1747
-
1748
- foreignKey[ outer ] locals[ art = {}, elem = {} ]
1749
- @after { this.attachLocation($art); }
1750
- :
1751
- simplePath[ $elem, 'ref' ] { $art.targetElement = $elem; }
1752
- ( AS name=ident['Key'] )?
1753
- { this.addDef( $art, $outer, 'foreignKeys', 'key', ($ctx.name) ? $name.id : $elem.path ); }
1467
+ ']'
1468
+ ;
1469
+
1470
+ nullability[ art ]
1471
+ :
1472
+ not=NOT n1=NULL
1473
+ { $art.notNull = this.valueWithTokenLocation( true, $not, $n1 ); }
1474
+ |
1475
+ n2=NULL
1476
+ { $art.notNull = this.valueWithTokenLocation( false, $n2 ); }
1477
+ ;
1478
+
1479
+ foreignKey[ outer ] locals[ art = {}, elem = {} ]
1480
+ @after { this.attachLocation($art); }
1481
+ :
1482
+ simplePath[ $elem, 'ref' ] { $art.targetElement = $elem; }
1483
+ ( AS name=ident['Key'] )?
1484
+ { this.addDef( $art, $outer, 'foreignKeys', 'key', ($ctx.name) ? $name.id : $elem.path ); }
1485
+ ;
1486
+
1487
+ typeTypeOf[ art ] locals[ _sync = 'nop' ]
1488
+ @after { this.attachLocation($art.type); }
1489
+ :
1490
+ TYPE OF
1491
+ { $art.type = { scope: 'typeOf' }; }
1492
+ simplePath[ $art.type, 'ref' ]
1493
+ ( ':'
1494
+ // If we have too much time, we could set the category of the simple path
1495
+ // before to 'artref'
1496
+ { $art.type.scope = $art.type.path.length; }
1497
+ simplePath[ $art.type, 'ref']
1498
+ )?
1499
+ ;
1500
+
1501
+ typeRefOptArgs[ art ]
1502
+ @init { $art.type = {}; }
1503
+ :
1504
+ simplePath[ $art.type, 'artref' ]
1505
+ (
1506
+ typeRefArgs[ $art ]
1507
+ |
1508
+ ':'
1509
+ { $art.type.scope = $art.type.path.length; }
1510
+ simplePath[ $art.type, 'ref']
1511
+ )?
1512
+ ;
1513
+
1514
+ typeRefArgs[ art ]
1515
+ :
1516
+ paren='(' { $art['$'+'typeArgs'] = this.createArray(); }
1517
+ (
1518
+ // unnamed arguments
1519
+ head=Number
1520
+ { $art['$'+'typeArgs'].push( this.numberLiteral( $head ) ); }
1521
+ ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1522
+ (
1523
+ v=VARIABLE
1524
+ { $art['$'+'typeArgs'].push(
1525
+ { literal: 'string', val: 'variable', location: this.tokenLocation($v) } );
1526
+ }
1527
+ |
1528
+ f=FLOATING
1529
+ { $art['$'+'typeArgs'].push(
1530
+ { literal: 'string', val: 'floating', location: this.tokenLocation($f) } );
1531
+ }
1532
+ |
1533
+ tail=Number
1534
+ { $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
1535
+ )
1536
+ )*
1537
+ |
1538
+ // named arguments
1539
+ typeNamedArg[ $art ]
1540
+ ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1541
+ typeNamedArg[ $art ]
1542
+ )*
1543
+ )
1544
+ ')'{ this.finalizeDictOrArray( $art['$'+'typeArgs']); }
1545
+ ;
1546
+
1547
+ typeNamedArgList[ art ]
1548
+ :
1549
+ paren='('
1550
+ typeNamedArg[ $art ]
1551
+ ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1552
+ typeNamedArg[ $art ]
1553
+ )*
1554
+ ')'
1555
+ ;
1556
+
1557
+ typeNamedArg[ art ] locals[ arg = '' ]
1558
+ :
1559
+ name=ident['paramname']
1560
+ ':'
1561
+ { if ($name.id && this.checkTypeFacet( $art, $name.id ))
1562
+ $arg = $name.id.id;
1563
+ }
1564
+ (
1565
+ val=Number
1566
+ { if ($arg && $art && $name.id) {
1567
+ $art[$arg] = this.numberLiteral( $val );
1568
+ }
1569
+ }
1570
+ |
1571
+ v=VARIABLE
1572
+ { if ($arg && $art && $name.id) {
1573
+ $art[$arg] = { literal: 'string', val: 'variable', location: this.tokenLocation($v) };
1574
+ }
1575
+ }
1576
+ |
1577
+ f=FLOATING
1578
+ { if ($arg && $art && $name.id) {
1579
+ $art[$arg] = { literal: 'string', val: 'floating', location: this.tokenLocation($f) };
1580
+ }
1581
+ }
1582
+ )
1583
+ ;
1584
+
1585
+ // Queries: the main query structure --------------------------------------------
1586
+
1587
+ queryEOF returns [ query ]
1588
+ :
1589
+ q=queryExpression { $query = $q.query; } EOF
1590
+ ;
1591
+
1592
+ projectionSpec returns[ query ] locals[ src ]
1593
+ @after { this.attachLocation($query); }
1594
+ :
1595
+ proj=PROJECTION ON
1596
+ // now a simplified `tableTerm`:
1597
+ {
1598
+ $src = { path: [], scope: 0 };
1599
+ $query = { op: this.valueWithTokenLocation( 'SELECT', $proj ), from: $src, location: this.startLocation() };
1600
+ }
1601
+ fromPath[ $src, 'artref']
1602
+ ( ':'
1603
+ { $src.scope = $src.path.length; }
1604
+ fromPath[ $src, 'ref']
1605
+ )?
1606
+ ( AS aliasName=ident['FromAlias'] { $src.name = $aliasName.id } )?
1607
+ // ANTLR errors are better if we use ( A )? instead of ( A | ):
1608
+ { if (!$src.name) this.classifyImplicitName( $src.scope ? 'FromAlias' : 'Without' ); }
1609
+ bracedSelectItemListDef[ $query, 'columns' ]?
1610
+ excludingClause[ $query ]?
1611
+ ;
1612
+
1613
+ projectionClauses[ query ]
1614
+ @after { this.attachLocation($query); }
1615
+ :
1616
+ ( WHERE cond=condition { $query.where = $cond.cond; } )?
1617
+ (
1618
+ GROUP BY
1619
+ e1=expression { $query.groupBy = [ $e1.expr ]; }
1620
+ ( ',' en=expression { $query.groupBy.push( $en.expr ); } )*
1621
+ )?
1622
+ ( HAVING having=condition { $query.having = $having.cond; } )?
1623
+ ( ob=orderByClause[ $query ] { $query = $ob.query; } ) ?
1624
+ ( lc=limitClause[ $query ] { $query = $lc.query; } ) ?
1625
+ ;
1626
+
1627
+ queryExpression returns[ query ] // QLSubqueryComplex, SubqueryComplex
1628
+ @after{ this.attachLocation($query); }
1629
+ :
1630
+ qt1=queryTerm { $query = $qt1.query; }
1631
+ (
1632
+ ( op=UNION q=( DISTINCT | ALL )?
1633
+ | op=EXCEPT q=DISTINCT?
1634
+ | op=MINUS q=DISTINCT?
1635
+ )
1636
+ qt=queryTerm
1637
+ { if ($qt.query) $query = this.leftAssocBinaryOp( $query, $op, $q, $qt.query );; $ctx.q = null; }
1638
+ )*
1639
+ ( ob=orderByClause[ $query ] { if ($ob.query) $query = $ob.query; } ) ?
1640
+ ( lc=limitClause[ $query ] { if ($lc.query) $query = $lc.query; } ) ?
1641
+ ;
1642
+
1643
+ queryTerm returns[ query ]
1644
+ @after{ this.attachLocation($query); }
1645
+ :
1646
+ qt1=queryPrimary { $query = $qt1.query; }
1647
+ (
1648
+ intersect=INTERSECT quantifier=DISTINCT?
1649
+ qt=queryPrimary
1650
+ { $query = this.leftAssocBinaryOp( $query, $intersect, $quantifier, $qt.query );
1651
+ $ctx.quantifier = null; } // reset for loop
1652
+ )*
1653
+ ;
1654
+
1655
+ queryPrimary returns[ query = {} ]
1656
+ @after { this.attachLocation($query); }
1657
+ :
1658
+ open='(' qe=queryExpression close=')'
1659
+ { $query = this.surroundByParens( $qe.query, $open, $close ); }
1660
+ |
1661
+ select=SELECT
1662
+ { $query = { op: this.valueWithTokenLocation( 'SELECT', $select ), location: this.startLocation() }; }
1663
+ (
1664
+ FROM querySource[ $query ]
1665
+ (
1666
+ mixin=MIXIN '{' { $query.mixin = this.createDict(); }
1667
+ mixinElementDef[ $query ]*
1668
+ '}' { this.finalizeDictOrArray( $query.mixin ); }
1669
+ INTO
1670
+ )?
1671
+ ( ad=( ALL | DISTINCT ) // TODO: or directly after SELECT ?
1672
+ { $query.quantifier = this.valueWithTokenLocation( $ad.text.toLowerCase(), $ad ); }
1673
+ )?
1674
+ bracedSelectItemListDef[ $query, 'columns' ]?
1675
+ excludingClause[ $query ]?
1676
+ |
1677
+ ( ad=( ALL | DISTINCT ) // TODO: or directly after SELECT ?
1678
+ { $query.quantifier = this.valueWithTokenLocation( $ad.text.toLowerCase(), $ad ); }
1679
+ )?
1680
+ { $query.columns = []; } // set it early to avoid "wildcard" errors
1681
+ selectItemDef[ $query.columns ]
1682
+ ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1683
+ selectItemDef[ $query.columns ]
1684
+ )*
1685
+ FROM querySource[ $query ]
1686
+ )
1687
+ ( WHERE cond=condition { $query.where = $cond.cond; } )?
1688
+ (
1689
+ GROUP BY
1690
+ e1=expression { $query.groupBy = [ $e1.expr ]; }
1691
+ ( ',' en=expression { $query.groupBy.push( $en.expr ); } )*
1692
+ )?
1693
+ ( HAVING having=condition { $query.having = $having.cond; } )?
1754
1694
  ;
1755
1695
 
1756
- typeTypeOf[ art ] locals[ _sync = 'nop' ]
1757
- @after { this.attachLocation($art.type); }
1696
+ querySource[ query ]
1697
+ @after { this.attachLocation($query.from); }
1758
1698
  :
1759
- TYPE OF
1760
- { $art.type = { scope: 'typeOf' }; }
1761
- simplePath[ $art.type, 'ref' ]
1762
- ( ':'
1763
- // If we have too much time, we could set the category of the simple path
1764
- // before to 'artref'
1765
- { $art.type.scope = $art.type.path.length; }
1766
- simplePath[ $art.type, 'ref']
1699
+ t1=tableExpression { $query.from = $t1.table; }
1700
+ (
1701
+ { const location = this.tokenLocation( this.getCurrentToken() );
1702
+ $query.from = { op: { val: 'join', location },
1703
+ join: { val: 'cross', location },
1704
+ args: [$t1.table] }; }
1705
+ ( ',' tn=tableExpression { if ($tn.table) $query.from.args.push( $tn.table ); } )+
1767
1706
  )?
1768
1707
  ;
1769
1708
 
1770
- typeRefOptArgs[ art ]
1771
- @init { $art.type = {}; }
1709
+ tableExpression returns[ table ] // TableOrJoin
1710
+ @after { this.attachLocation($table); }
1772
1711
  :
1773
- simplePath[ $art.type, 'artref' ]
1712
+ qt=tableTerm { $table = $qt.table; }
1774
1713
  (
1775
- typeRefArgs[ $art ]
1714
+ join=joinOp[ $table ] { $table = $join.table; }
1715
+ te=tableExpression
1716
+ { if (!$table) { $table = {}; } else if ($te.table) $table.args.push( $te.table ); }
1717
+ ON cond=condition { $table.on = $cond.cond; }
1776
1718
  |
1777
- ':'
1778
- { $art.type.scope = $art.type.path.length; }
1779
- simplePath[ $art.type, 'ref']
1780
- )?
1719
+ crj=CROSS jn=JOIN tt=tableTerm
1720
+ { if (!$table) { $table = {}; } $table = this.leftAssocBinaryOp( $table, $jn, $crj, $tt.table, 'join' ); }
1721
+ )*
1781
1722
  ;
1782
1723
 
1783
- typeRefArgs[ art ]
1724
+ tableTerm returns [ table ]
1725
+ @after{ /* #ATN 1 */ this.attachLocation($table); }
1784
1726
  :
1785
- paren='(' { $art['$'+'typeArgs'] = this.createArray(); }
1727
+ { $table = { path: [], scope: 0 }; }
1728
+ fromPath[ $table, 'artref']
1729
+ ( ':'
1730
+ { $table.scope = $table.path.length; }
1731
+ fromPath[ $table, 'ref']
1732
+ )?
1733
+ ( AS n1=ident['FromAlias'] { $table.name = $n1.id }
1734
+ | n2=identNoKeyword['FromAlias'] { $table.name = this.fragileAlias( $n2.id ); }
1735
+ // if we would use rule `ident`, we would either had to make all JOIN
1736
+ // kinds reserved or introduce ATN
1737
+ )?
1738
+ // ANTLR errors are better if we use ( A | B )? instead of ( A | B | ):
1739
+ { if (!$table.name) this.classifyImplicitName( $table.scope ? 'FromAlias' : 'Without' ); }
1740
+ |
1741
+ open='('
1742
+ // #ATN: The following alternative is not LL1, because both can start with
1743
+ // left-paren, but queryExpression has SELECT after initial left-parens
1786
1744
  (
1787
- // unnamed arguments
1788
- head=Number
1789
- { $art['$'+'typeArgs'].push( this.numberLiteral( $head ) ); }
1790
- ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1791
- (
1792
- v=VARIABLE
1793
- { $art['$'+'typeArgs'].push(
1794
- { literal: 'string', val: 'variable', location: this.tokenLocation($v) } );
1795
- }
1796
- |
1797
- f=FLOATING
1798
- { $art['$'+'typeArgs'].push(
1799
- { literal: 'string', val: 'floating', location: this.tokenLocation($f) } );
1800
- }
1801
- |
1802
- tail=Number
1803
- { $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
1804
- )
1805
- )*
1745
+ qe=queryExpression close=')'
1746
+ { $table = this.surroundByParens( $qe.query, $open, $close, true ); }
1747
+ ( AS a1=ident['FromAlias'] { $table.name = $a1.id } // for defining table aliass
1748
+ | a2=identNoKeyword['FromAlias'] { $table.name = this.fragileAlias( $a2.id, true ); }
1749
+ // not using ident` to have a similar behavior to above
1750
+ )
1806
1751
  |
1807
- // named arguments
1808
- typeNamedArg[ $art ]
1809
- ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1810
- typeNamedArg[ $art ]
1811
- )*
1752
+ te=tableExpression close=')'
1753
+ { $table = this.surroundByParens( $te.table, $open, $close ); }
1812
1754
  )
1813
- ')'{ this.finalizeDictOrArray( $art['$'+'typeArgs']); }
1814
1755
  ;
1815
1756
 
1816
- typeNamedArgList[ art ]
1757
+ fromPath[ qp, idkind ]
1758
+ @after{ this.attachLocation($qp); }
1817
1759
  :
1818
- paren='('
1819
- typeNamedArg[ $art ]
1820
- ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1821
- typeNamedArg[ $art ]
1760
+ id=ident[$idkind] { this.pushIdent( $qp.path, $id.id ); }
1761
+ ( fromArguments[ $id.id ] cardinalityAndFilter[ $id.id ]?
1762
+ | cardinalityAndFilter[ $id.id ]
1763
+ )?
1764
+ (
1765
+ '.' id=ident[$idkind] { this.pushIdent( $qp.path, $id.id ); }
1766
+ ( fromArguments[ $id.id ] cardinalityAndFilter[ $id.id ]?
1767
+ | cardinalityAndFilter[ $id.id ]
1768
+ )?
1822
1769
  )*
1823
- ')'
1824
1770
  ;
1825
1771
 
1826
- typeNamedArg[ art ] locals[ arg = '' ]
1772
+ fromArguments[ pathStep ]
1773
+ @init{ if (!$pathStep) $pathStep = {}; } // grammar robustness, see test/negative/parser/NamedExpression.cds
1827
1774
  :
1828
- name=ident['paramname']
1829
- ':'
1830
- { if ($name.id && this.checkTypeFacet( $art, $name.id ))
1831
- $arg = $name.id.id;
1832
- }
1833
- (
1834
- val=Number
1835
- { if ($arg && $art && $name.id) {
1836
- $art[$arg] = this.numberLiteral( $val );
1837
- }
1838
- }
1839
- |
1840
- v=VARIABLE
1841
- { if ($arg && $art && $name.id) {
1842
- $art[$arg] = { literal: 'string', val: 'variable', location: this.tokenLocation($v) };
1843
- }
1844
- }
1845
- |
1846
- f=FLOATING
1847
- { if ($arg && $art && $name.id) {
1848
- $art[$arg] = { literal: 'string', val: 'floating', location: this.tokenLocation($f) };
1849
- }
1850
- }
1851
- )
1775
+ '(' { $pathStep.args = this.createDict(); $pathStep['$'+'syntax'] = ':'; } // necessary?
1776
+ name=ident['paramname'] ':'
1777
+ namedExpression[ $pathStep, $name.id ]
1778
+ ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1779
+ name=ident['paramname'] ':'
1780
+ namedExpression[ $pathStep, $name.id ]
1781
+ )*
1782
+ ')' { this.finalizeDictOrArray( $pathStep.args ); }
1852
1783
  ;
1853
1784
 
1785
+ // Queries: columns and other clauses -------------------------------------------
1854
1786
 
1855
- // Queries -------------------------------------------------------------------
1856
-
1857
- queryExpression returns[ query ] // QLSubqueryComplex, SubqueryComplex
1858
- @after{ this.attachLocation($query); }
1787
+ excludingClause[ query ]
1859
1788
  :
1860
- qt1=queryTerm { $query = $qt1.query; }
1861
- (
1862
- ( op=UNION q=( DISTINCT | ALL )?
1863
- | op=EXCEPT q=DISTINCT?
1864
- | op=MINUS q=DISTINCT?
1865
- )
1866
- qt=queryTerm
1867
- { if ($qt.query) $query = this.leftAssocBinaryOp( $query, $op, $q, $qt.query );; $ctx.q = null; }
1789
+ // syntax is less than ideal - EXCLUDING is only useful for `*` - with
1790
+ // this syntax, people wonder what happens with explicit select items
1791
+ EXCLUDING '{' { $query.excludingDict = this.createDict(); }
1792
+ projectionExclusion[ $query ]
1793
+ ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1794
+ projectionExclusion[ $query ]
1868
1795
  )*
1869
- ( ob=orderByClause[ $query ] { if ($ob.query) $query = $ob.query; } ) ?
1870
- ( lc=limitClause[ $query ] { if ($lc.query) $query = $lc.query; } ) ?
1871
- ;
1872
-
1873
- orderByClause[ inQuery ] returns [ query ]
1874
- :
1875
- ORDER BY { $query = this.unaryOpForParens( $inQuery, '$'+'query' ); }
1876
- ob1=orderBySpec { $query.orderBy = [ $ob1.ob ]; }
1877
- ( ',' obn=orderBySpec { $query.orderBy.push( $obn.ob ); } )*
1796
+ '}' { this.finalizeDictOrArray( $query.excludingDict ); }
1878
1797
  ;
1879
1798
 
1880
- // Generic function ORDER BY clause, e.g. `first_value(id order by name)`
1881
- // lhsExpr is the left expression of the ORDER BY clause.
1882
- functionOrderByClause[ lhsExpr ] returns [ expr ]
1883
- @after { this.attachLocation( $expr ); }
1799
+ projectionExclusion[ outer ] locals[ art = {} ]
1800
+ @after { this.attachLocation($art); }
1884
1801
  :
1885
- o=ORDER b=BY { $expr = { op: this.valueWithTokenLocation( 'orderBy', $o, $b ) , args: [ $lhsExpr ] }}
1886
- ob1=orderBySpec { $expr.args.push( $ob1.ob ); }
1887
- ( ',' obn=orderBySpec { $expr.args.push( $obn.ob ); } )*
1802
+ name=ident['ref']
1803
+ { this.addDef( $art, $outer, 'excludingDict', '', $name.id ); }
1888
1804
  ;
1889
1805
 
1890
- overOrderByClause returns [ expr ]
1891
- @after { this.attachLocation( $expr ); }
1806
+ // Actually, this is a subset if elementDefInner...
1807
+ // TODO: the corresponding restrictions must also be checked in the core
1808
+ // compiler, as the mixin element could come via CSN
1809
+ mixinElementDef[ outer ] locals[ art = {} ]
1810
+ @after { /* #ATN 2 */ this.attachLocation($art); }
1892
1811
  :
1893
- o=ORDER b=BY { $expr = { op: this.valueWithTokenLocation( 'overOrderBy', $o, $b ) , args: [] }}
1894
- ob1=orderBySpec { $expr.args.push( $ob1.ob ); }
1895
- ( ',' obn=orderBySpec { $expr.args.push( $obn.ob ); } )*
1812
+ name=ident['Mixin']
1813
+ { this.addDef( $art, $outer, 'mixin', 'mixin', $name.id ); }
1814
+ (
1815
+ ':'
1816
+ // #ATN: referenced type name can be ASSOCIATION or COMPOSITION
1817
+ (
1818
+ typeAssociationBase[ $art, false ]
1819
+ // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
1820
+ ( typeToMany[ $art ] | typeToOne[ $art ] | simplePath[ $art.target, 'artref' ] )
1821
+ typeAssociationCont[ $art ]?
1822
+ |
1823
+ typeRefOptArgs[ $art ]
1824
+ ( as='=' expression
1825
+ { this.error( 'syntax-unsupported-calc-elem', $as ); }
1826
+ )?
1827
+ )
1828
+ |
1829
+ as='=' expression
1830
+ { this.error( 'syntax-unsupported-calc-elem', $as ); }
1831
+ )
1832
+ requiredSemi
1896
1833
  ;
1897
1834
 
1898
- partitionByClause returns [ expr ]
1899
- @after { this.attachLocation( $expr ); }
1835
+ selectItemDef[ outer ] locals[ art ]
1836
+ @after{ if ($art) this.attachLocation( $art ); }
1900
1837
  :
1901
- p=PARTITION b=BY { $expr = { op: this.valueWithTokenLocation( 'partitionBy', $p, $b ) , args: [] }}
1902
- e1=expression { $expr.args.push( $e1.expr ); }
1903
- ( ',' en=expression { $expr.args.push( $en.expr ); } )*
1838
+ star='*'
1839
+ { $outer.push( this.valueWithTokenLocation( '*', $star ) ); }
1840
+ |
1841
+ { $art = {};; this.docComment( $art ); }
1842
+ annotationAssignment_atn[ $art ]*
1843
+ // VIRTUAL is keyword, except if before the following tokens texts:
1844
+ { this.setLocalToken( 'VIRTUAL', 'VIRTUAL', /^([,.:\[@]|as)$/i ) ; } // not '{'
1845
+ ( virtual=VIRTUAL { $art.virtual = this.valueWithTokenLocation( true, $virtual ); } )?
1846
+ ( key=KEY { $art.key = this.valueWithTokenLocation( true, $key ); } )?
1847
+ selectItemDefBody[ $art, $outer ]
1904
1848
  ;
1905
1849
 
1906
- windowFrameClause returns [ wf ]
1907
- @after { this.attachLocation( $wf ); }
1850
+ selectItemDefBody[ art, outer ] locals[ assoc ]
1851
+ @after{ /* #ATN 2 */ }
1908
1852
  :
1909
- r=ROWS { $wf = { op: this.valueWithTokenLocation( 'rows', $r ) , args: [] }}
1910
- wfe=windowFrameExtentSpec { $wf.args.push( $wfe.wfe ); }
1853
+ { $outer.push( $art ); }
1854
+ (
1855
+ e=expression { $art.value = $e.expr; }
1856
+ // we cannot use 'condition' instead, as long as we allow aliases without
1857
+ // AS (using rule 'ident' instead of 'identNoKeyword') -> ambiguities
1858
+ ( as=AS n1=ident['Item'] { $art.name = $n1.id }
1859
+ | n2=ident['Item'] { $art.name = this.fragileAlias( $n2.id, true ); }
1860
+ | { this.classifyImplicitName( 'Item', $e.expr ); }
1861
+ )
1862
+ { if ($art.value && !$art.value.path) this.excludeExpected( ["'.'", "'{'"] );
1863
+ else if ($art.name) this.excludeExpected( ["'.'"] );
1864
+ }
1865
+ (
1866
+ { this.reportExpandInline( $art, false ); }
1867
+ selectItemInlineList[ $art, 'expand' ]
1868
+ excludingClause[ $art ]?
1869
+ // TODO: we might alternatively allow AS here
1870
+ |
1871
+ { this.reportExpandInline( $art, $as || this._input.LT(-1) ); }
1872
+ DOTbeforeBRACE // ...orASTERISK
1873
+ (
1874
+ selectItemInlineList[ $art, 'inline' ]
1875
+ excludingClause[ $art ]?
1876
+ |
1877
+ star='*'
1878
+ { $art.inline = [ this.valueWithTokenLocation( '*', $star ) ]; }
1879
+ )
1880
+ )?
1881
+ |
1882
+ selectItemInlineList[ $art, 'expand' ]
1883
+ excludingClause[ $art ]?
1884
+ AS n1=ident['Item'] { $art.name = $n1.id }
1885
+ )
1886
+ { this.docComment( $art ); }
1887
+ annotationAssignment_fix[ $art ]*
1888
+ ( ':'
1889
+ // #ATN: typeRefOptArgs can start with TYPE, REDIRECTED, ASSOCIATION
1890
+ ( re=REDIRECTED to=TO
1891
+ { $art.target = {}; }
1892
+ simplePath[ $art.target, 'artref' ]
1893
+ (
1894
+ typeAssociationCont[ $art ]
1895
+ |
1896
+ { this.docComment( $art ); }
1897
+ annotationAssignment_ll1[ $art ]*
1898
+ )
1899
+ | typeTypeOf[ $art ]
1900
+ { this.docComment( $art ); }
1901
+ annotationAssignment_ll1[ $art ]*
1902
+ | l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
1903
+ typeRefOptArgs[ $art ]
1904
+ { this.docComment( $art ); }
1905
+ annotationAssignment_ll1[ $art ]*
1906
+ | typeRefOptArgs[ $art ]
1907
+ { this.docComment( $art ); }
1908
+ annotationAssignment_ll1[ $art ]*
1909
+ |
1910
+ { $assoc = this.associationInSelectItem( $art ); }
1911
+ typeAssociationBase[ $assoc, false ]
1912
+ // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
1913
+ ( typeToMany[ $assoc ] | typeToOne[ $assoc ] | simplePath[ $assoc.target, 'artref' ] )
1914
+ ON cond=condition
1915
+ { $assoc.on=$cond.cond; }
1916
+ )
1917
+ )?
1911
1918
  ;
1912
1919
 
1913
- windowFrameExtentSpec returns[ wfe ]
1914
- @after { this.attachLocation( $wfe ); }
1920
+ bracedSelectItemListDef[ query ]
1915
1921
  :
1916
- { $wfe = {} }
1917
- windowFrameStartSpec [ $wfe ]
1918
- |
1919
- b=BETWEEN
1920
- { $wfe = { op: this.valueWithTokenLocation( 'frameBetween', $b ), args: [] } }
1921
- wfb1=windowFrameBoundSpec { $wfe.args.push( $wfb1.wfb ); }
1922
- AND
1923
- wfb2=windowFrameBoundSpec { $wfe.args.push( $wfb2.wfb ); }
1922
+ '{' { $query.columns = this.createArray(); }
1923
+ (
1924
+ selectItemDef[ $query.columns ]
1925
+ ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1926
+ selectItemDef[ $query.columns ]
1927
+ )*
1928
+ )?
1929
+ '}' { this.finalizeDictOrArray( $query.columns ); }
1924
1930
  ;
1925
1931
 
1926
- windowFrameBoundSpec returns [ wfb ]
1927
- @after{ /* #ATN 1 */ }
1932
+ selectItemInlineList[ art, clause ]
1928
1933
  :
1929
- // #ATN: Not ll1 because `UNBOUNDED` could also be part of the windowFrameStartSpec
1930
- // `UNBOUNDED` would then be immediately followed by `PRECEDING`
1931
- u=UNBOUNDED f=FOLLOWING
1932
- { $wfb = { op: this.valueWithTokenLocation( 'unboundedFollowing', $u, $f ), args: []} }
1933
- |
1934
- // #ATN: Not ll1 because `Number` could also be part of the windowFrameStartSpec
1935
- // `Number` would then be immediately followed by `PRECEDING`
1936
- n=Number f=FOLLOWING
1937
- { $wfb = { op: this.valueWithTokenLocation( 'following', $n, $f ), args: [ this.numberLiteral( $n ) ]} }
1938
- |
1939
- { $wfb = {} }
1940
- windowFrameStartSpec [ $wfb ]
1934
+ '{' { $art[$clause] = this.createArray(); }
1935
+ (
1936
+ selectItemInlineDef[ $art[$clause] ]
1937
+ ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
1938
+ selectItemInlineDef[ $art[$clause] ]
1939
+ )*
1940
+ )?
1941
+ '}' { this.finalizeDictOrArray( $art[$clause] ); }
1941
1942
  ;
1942
1943
 
1943
- windowFrameStartSpec [ wf ]
1944
- @after { this.attachLocation( $wf ); }
1944
+ selectItemInlineDef[ outer ] locals[ art ]
1945
+ @after{ if ($art) this.attachLocation( $art ); }
1945
1946
  :
1946
- u=UNBOUNDED p=PRECEDING
1947
- {
1948
- $wf.op = this.valueWithTokenLocation( 'unboundedPreceding', $u, $p );
1949
- $wf.args = [];
1950
- }
1951
- |
1952
- n=Number p=PRECEDING
1953
- {
1954
- $wf.op = this.valueWithTokenLocation( 'preceding', $p );
1955
- $wf.args = [ this.numberLiteral( $n ) ];
1956
- }
1947
+ star='*'
1948
+ { $outer.push( this.valueWithTokenLocation( '*', $star ) ); }
1957
1949
  |
1958
- c=CURRENT r=ROW
1959
- {
1960
- $wf.op = this.valueWithTokenLocation( 'currentRow', $c, $r );
1961
- $wf.args = [];
1962
- }
1950
+ { $art = {};; this.docComment( $art ); }
1951
+ annotationAssignment_atn[ $art ]*
1952
+ selectItemDefBody[ $art, $outer ]
1963
1953
  ;
1964
1954
 
1965
- overClause returns [ over ]
1966
- @after { this.attachLocation( $over ); }
1955
+ orderByClause[ inQuery ] returns [ query ]
1967
1956
  :
1968
- o=OVER { $over = { op: this.valueWithTokenLocation( 'over', $o ) , args: [] } }
1969
- '(' // TODO: check whether an extra location could be useful
1970
- ( pb=partitionByClause { $over.args.push( $pb.expr ); } )?
1971
- ( ob=overOrderByClause { $over.args.push( $ob.expr ); } )?
1972
- ( wf=windowFrameClause { $over.args.push( $wf.wf ); } )?
1973
- ')'
1957
+ ORDER BY { $query = this.unaryOpForParens( $inQuery, '$'+'query' ); }
1958
+ ob1=orderBySpec { $query.orderBy = [ $ob1.ob ]; }
1959
+ ( ',' obn=orderBySpec { $query.orderBy.push( $obn.ob ); } )*
1974
1960
  ;
1975
1961
 
1976
1962
  limitClause[ inQuery ] returns [ query ]
@@ -1994,87 +1980,6 @@ orderBySpec returns[ ob ]
1994
1980
  )?
1995
1981
  ;
1996
1982
 
1997
- queryTerm returns[ query ]
1998
- @after{ this.attachLocation($query); }
1999
- :
2000
- qt1=queryPrimary { $query = $qt1.query; }
2001
- (
2002
- intersect=INTERSECT quantifier=DISTINCT?
2003
- qt=queryPrimary
2004
- { $query = this.leftAssocBinaryOp( $query, $intersect, $quantifier, $qt.query );
2005
- $ctx.quantifier = null; } // reset for loop
2006
- )*
2007
- ;
2008
-
2009
- queryPrimary returns[ query = {} ]
2010
- @after { this.attachLocation($query); }
2011
- :
2012
- open='(' qe=queryExpression close=')'
2013
- { $query = this.surroundByParens( $qe.query, $open, $close ); }
2014
- |
2015
- select=SELECT
2016
- { $query = { op: this.valueWithTokenLocation( 'SELECT', $select ), location: this.startLocation() }; }
2017
- (
2018
- FROM querySource[ $query ]
2019
- (
2020
- mixin=MIXIN '{' { $query.mixin = this.createDict(); }
2021
- mixinElementDef[ $query ]*
2022
- '}' { this.finalizeDictOrArray( $query.mixin ); }
2023
- INTO
2024
- )?
2025
- ( ad=( ALL | DISTINCT ) // TODO: or directly after SELECT ?
2026
- { $query.quantifier = this.valueWithTokenLocation( $ad.text.toLowerCase(), $ad ); }
2027
- )?
2028
- bracedSelectItemListDef[ $query, 'columns' ]?
2029
- excludingClause[ $query ]?
2030
- |
2031
- ( ad=( ALL | DISTINCT ) // TODO: or directly after SELECT ?
2032
- { $query.quantifier = this.valueWithTokenLocation( $ad.text.toLowerCase(), $ad ); }
2033
- )?
2034
- { $query.columns = []; } // set it early to avoid "wildcard" errors
2035
- selectItemDef[ $query.columns ]
2036
- ( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
2037
- selectItemDef[ $query.columns ]
2038
- )*
2039
- FROM querySource[ $query ]
2040
- )
2041
- ( WHERE cond=condition { $query.where = $cond.cond; } )?
2042
- (
2043
- GROUP BY
2044
- e1=expression { $query.groupBy = [ $e1.expr ]; }
2045
- ( ',' en=expression { $query.groupBy.push( $en.expr ); } )*
2046
- )?
2047
- ( HAVING having=condition { $query.having = $having.cond; } )?
2048
- ;
2049
-
2050
- querySource[ query ]
2051
- @after { this.attachLocation($query.from); }
2052
- :
2053
- t1=tableExpression { $query.from = $t1.table; }
2054
- (
2055
- { const location = this.tokenLocation( this.getCurrentToken() );
2056
- $query.from = { op: { val: 'join', location },
2057
- join: { val: 'cross', location },
2058
- args: [$t1.table] }; }
2059
- ( ',' tn=tableExpression { if ($tn.table) $query.from.args.push( $tn.table ); } )+
2060
- )?
2061
- ;
2062
-
2063
- tableExpression returns[ table ] // TableOrJoin
2064
- @after { this.attachLocation($table); }
2065
- :
2066
- qt=tableTerm { $table = $qt.table; }
2067
- (
2068
- join=joinOp[ $table ] { $table = $join.table; }
2069
- te=tableExpression
2070
- { if (!$table) { $table = {}; } else if ($te.table) $table.args.push( $te.table ); }
2071
- ON cond=condition { $table.on = $cond.cond; }
2072
- |
2073
- crj=CROSS jn=JOIN tt=tableTerm
2074
- { if (!$table) { $table = {}; } $table = this.leftAssocBinaryOp( $table, $jn, $crj, $tt.table, 'join' ); }
2075
- )*
2076
- ;
2077
-
2078
1983
  joinOp[ left ] returns[ table ] locals [ join ]
2079
1984
  :
2080
1985
  ( op=JOIN { $join = 'inner'; }
@@ -2122,55 +2027,7 @@ joinCardinality returns [ joinCard ]
2122
2027
  )
2123
2028
  ;
2124
2029
 
2125
- tableTerm returns [ table ]
2126
- @after{ /* #ATN 1 */ this.attachLocation($table); }
2127
- :
2128
- { $table = { path: [], scope: 0 }; }
2129
- fromPath[ $table, 'artref']
2130
- ( ':'
2131
- { $table.scope = $table.path.length; }
2132
- fromPath[ $table, 'ref']
2133
- )?
2134
- ( AS n1=ident['FromAlias'] { $table.name = $n1.id }
2135
- | n2=identNoKeyword['FromAlias'] { $table.name = this.fragileAlias( $n2.id ); }
2136
- // if we would use rule `ident`, we would either had to make all JOIN
2137
- // kinds reserved or introduce ATN
2138
- )?
2139
- // ANTLR errors are better if we use ( A | B )? instead of ( A | B | ):
2140
- { if (!$table.name) this.classifyImplicitName( $table.scope ? 'FromAlias' : 'Without' ); }
2141
- |
2142
- open='('
2143
- // #ATN: The following alternative is not LL1, because both can start with
2144
- // left-paren, but queryExpression has SELECT after initial left-parens
2145
- (
2146
- qe=queryExpression close=')'
2147
- { $table = this.surroundByParens( $qe.query, $open, $close, true ); }
2148
- ( AS a1=ident['FromAlias'] { $table.name = $a1.id } // for defining table aliass
2149
- | a2=identNoKeyword['FromAlias'] { $table.name = this.fragileAlias( $a2.id, true ); }
2150
- // not using ident` to have a similar behavior to above
2151
- )
2152
- |
2153
- te=tableExpression close=')'
2154
- { $table = this.surroundByParens( $te.table, $open, $close ); }
2155
- )
2156
- ;
2157
-
2158
- fromPath[ qp, idkind ]
2159
- @after{ this.attachLocation($qp); }
2160
- :
2161
- id=ident[$idkind] { this.pushIdent( $qp.path, $id.id ); }
2162
- ( fromArguments[ $id.id ] cardinalityAndFilter[ $id.id ]?
2163
- | cardinalityAndFilter[ $id.id ]
2164
- )?
2165
- (
2166
- '.' id=ident[$idkind] { this.pushIdent( $qp.path, $id.id ); }
2167
- ( fromArguments[ $id.id ] cardinalityAndFilter[ $id.id ]?
2168
- | cardinalityAndFilter[ $id.id ]
2169
- )?
2170
- )*
2171
- ;
2172
-
2173
- // Conditions and expressions ------------------------------------------------
2030
+ // Conditions and expressions ---------------------------------------------------
2174
2031
 
2175
2032
  // With "separate" `condition` and `expression` rules, we have long LL
2176
2033
  // ambiguities (not so with LALR used in Bison) with initial left parentheses:
@@ -2186,221 +2043,214 @@ fromPath[ qp, idkind ]
2186
2043
  // be then ensured by code (either in actions of the grammar or in a check
2187
2044
  // phase - to be discussed).
2188
2045
  //
2046
+ // We cannot generally allow `condition` where `expression` is written:
2047
+ // - IN is also used in non-standard function `args` and as predicate:
2048
+ // - AND is boolean operator and also used for BETWEEN…AND
2049
+ //
2189
2050
  // ANTLR4s left-recursion feature cannot be used as we will have rule
2190
2051
  // arguments.
2191
2052
 
2192
- condition returns [ cond ] locals [ args = [], orl = [] ]
2193
- @after{
2194
- $cond = ($args.length === 1)
2195
- ? this.attachLocation( $args[0] )
2196
- : this.attachLocation({ op: $orl[0], args: $args });
2197
- }
2053
+ conditionEOF returns [ cond ]
2054
+ :
2055
+ c=condition { $cond = $c.cond; } EOF
2056
+ ;
2057
+
2058
+ condition returns [ cond ] locals[ args = [] ]
2059
+ @after{ $cond = this.argsExpression( $args ); }
2198
2060
  :
2199
2061
  c1=conditionAnd { $args.push($c1.cond); }
2200
- ( or=OR c2=conditionAnd { $args.push($c2.cond); $orl.push(this.valueWithTokenLocation( 'or', $or ))} )*
2062
+ (
2063
+ or=OR { this.pushXprToken( $args ); }
2064
+ c2=conditionAnd { $args.push($c2.cond); }
2065
+ )*
2201
2066
  ;
2202
2067
 
2203
- conditionAnd returns [ cond ] locals [ args = [], andl = [] ]
2204
- @after{
2205
- $cond = ($args.length === 1)
2206
- ? $args[0]
2207
- : this.attachLocation({ op: $andl[0], args: $args });
2208
- }
2068
+ conditionAnd returns [ cond ] locals[ args = [] ]
2069
+ @after{ $cond = this.argsExpression( $args, null, 'and' ); } // 'and' used by A2J and checks
2209
2070
  :
2210
2071
  c1=conditionTerm { $args.push($c1.cond); }
2211
- ( and=AND c2=conditionTerm { $args.push($c2.cond); $andl.push(this.valueWithTokenLocation( 'and', $and )) } )*
2072
+ (
2073
+ and=AND { this.pushXprToken( $args ); }
2074
+ c2=conditionTerm { $args.push($c2.cond); }
2075
+ )*
2212
2076
  ;
2213
2077
 
2214
2078
  // Note: New operators need to be added to functionExpressionOperatorsRequireParentheses[] in toCdl.js.
2215
- conditionTerm returns [ cond ]
2216
- @after{
2217
- if ($cond) { this.attachLocation($cond); } else { $cond = $expr.expr; }
2218
- }
2079
+ conditionTerm returns [ cond ] locals[ args = [] ]
2080
+ @after{ $cond = this.argsExpression( $args, null, '=' ); }// op: '=' used by A2J and checks
2219
2081
  :
2220
- nt=NOT ct=conditionTerm
2221
- { $cond = { op: this.valueWithTokenLocation( 'not', $nt ), args: [ $ct.cond ] }; }
2082
+ nt=NOT { this.pushXprToken( $args ); }
2083
+ ct=conditionTerm { $args.push( $ct.cond ); }
2222
2084
  |
2223
- ex=EXISTS
2085
+ EXISTS { this.pushXprToken( $args ); }
2224
2086
  (
2225
2087
  open='(' qe=queryExpression close=')'
2226
- { $cond = { op: this.valueWithTokenLocation( 'exists', $ex ),
2227
- args: [ this.surroundByParens( $qe.query, $open, $close, true ) ] }; }
2088
+ { $args.push( this.surroundByParens( $qe.query, $open, $close, true ) ); }
2228
2089
  |
2229
2090
  qm=( HideAlternatives | '?' )
2230
- { $cond = { op: this.valueWithTokenLocation( 'exists', $ex ), args: [
2231
- { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' }
2232
- ] };
2233
- this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
2091
+ { $args.push( { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' } );
2092
+ this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', code: '?' } );
2234
2093
  }
2235
2094
  |
2236
- ep=valuePath[ 'ref' ]
2237
- { $ep.qp['$'+'expected'] = 'exists';
2238
- $cond = { op: this.valueWithTokenLocation( 'exists', $ex ), args: [ $ep.qp ] };
2239
- }
2095
+ ep=valuePath[ 'ref' ] { $args.push( $ep.qp ); }
2096
+ { $ep.qp['$'+'expected'] = 'exists'; } // hm, really in parser? what about CSN input?
2240
2097
  )
2241
2098
  |
2242
- expr=expression // see @after
2099
+ expr=expression { $args.push( $expr.expr ); }
2243
2100
  (
2244
- rel=( '=' | '<>' | '>' | '>=' | '<' | '<=' | '!=' )
2245
- { $cond = { op: this.valueWithTokenLocation( $rel.text, $rel ), args: [ $expr.expr ] }; }
2246
- ( asa=( ANY | SOME | ALL )
2247
- { $cond.quantifier = this.valueWithTokenLocation( $asa.text.toLowerCase(), $asa ); }
2248
- )?
2249
- e2=expression { $cond.args.push($e2.expr); }
2101
+ ( '=' | '<>' | '>' | '>=' | '<' | '<=' | '!=' ) { this.pushXprToken( $args ); }
2102
+ ( ( ANY | SOME | ALL ) { this.pushXprToken( $args ); } )?
2103
+ e2=expression { $args.push( $e2.expr ); }
2250
2104
  |
2251
- IS ( inn=NOT NULL | innu=NULL )
2252
- { $cond = { op: $inn ? this.valueWithTokenLocation( 'isNotNull', $inn ) : this.valueWithTokenLocation( 'isNull', $innu ), args: [ $expr.expr ] }; }
2105
+ IS { this.pushXprToken( $args ); }
2106
+ ( NOT { this.pushXprToken( $args ); } )?
2107
+ NULL { this.pushXprToken( $args ); }
2253
2108
  |
2254
- { $cond = { args: [ $expr.expr ] }; }
2255
- NOT predicate[ $cond, true ]
2256
- { if (!$cond.op) $cond = null; } // predicate failed to parse, avoid subseqential errors
2109
+ NOT { this.pushXprToken( $args ); }
2110
+ predicate[ $args ]
2257
2111
  |
2258
- { $cond = { args: [ $expr.expr ] }; }
2259
- predicate[ $cond, false ]
2260
- { if (!$cond.op) $cond = null; } // predicate failed to parse, avoid subseqential errors
2112
+ predicate[ $args ]
2261
2113
  )? // optional: for conditions in parentheses
2262
2114
  ;
2263
2115
 
2264
2116
  // Note: New operators need to be added to functionExpressionOperatorsRequireParentheses[] in toCdl.js.
2265
- predicate[ cond, negated ]
2266
- // As an alternative, we could have a `negated` properties for the operations
2267
- // `isNull`(!), `in`, `between` and `like` (or produce the same AST as for
2268
- // NOT (a BETWEEN b AND c)
2117
+ predicate[ args ]
2269
2118
  :
2270
- ino=IN e1=expression // including ExpressionList
2271
- { $cond.op = this.valueWithTokenLocation( (negated) ? 'notIn' : 'in', $ino ); $cond.args.push( $e1.expr ); }
2119
+ IN { this.pushXprToken( $args ); }
2120
+ // TODO: do we really allow it not to start with `(`? - TODO: try with warning
2121
+ e1=expression { $args.push( this.secureParens( $e1.expr ) ); } // including ExpressionList
2272
2122
  |
2273
- bw=BETWEEN e2=expression
2274
- { $cond.op = this.valueWithTokenLocation( (negated) ? 'notBetween' : 'between', $bw ); $cond.args.push( $e2.expr ); }
2275
- AND e3=expression { $cond.args.push( $e3.expr ); }
2123
+ BETWEEN { this.pushXprToken( $args ); }
2124
+ e2=expression { $args.push( $e2.expr ); }
2125
+ AND { this.pushXprToken( $args ); }
2126
+ e3=expression { $args.push( $e3.expr ); }
2276
2127
  |
2277
- lk=LIKE e4=expression
2278
- { $cond.op = this.valueWithTokenLocation( (negated) ? 'notLike' : 'like', $lk ); $cond.args.push( $e4.expr ); }
2279
- ( ESCAPE e5=expression { $cond.args.push( $e5.expr ); } )?
2128
+ LIKE { this.pushXprToken( $args ); }
2129
+ e4=expression { $args.push( $e4.expr ); }
2130
+ ( ESCAPE { this.pushXprToken( $args ); }
2131
+ e5=expression { $args.push( $e5.expr ); }
2132
+ )?
2280
2133
  ;
2281
2134
 
2282
- expression returns [ expr ]
2283
- @after{ if ($expr) { this.attachLocation($expr); } else { $expr = this.attachLocation({});} }
2135
+ expression returns [ expr ] locals [ args = [] ]
2136
+ @after{ $expr = this.argsExpression( $args ); }
2284
2137
  :
2285
- e1=expressionSum { $expr = $e1.expr; }
2138
+ e1=expressionSum { $args.push( $e1.expr ); }
2286
2139
  (
2287
- or='||' e2=expressionSum
2288
- {
2289
- $expr = {
2290
- op: this.valueWithTokenLocation( '||', $or ), args: [$expr, $e2.expr],
2291
- location: this.combinedLocation( $expr, $e2.expr ) };
2292
- }
2140
+ '||' { this.pushXprToken( $args ); }
2141
+ e2=expressionSum { $args.push( $e2.expr ); }
2293
2142
  )*
2294
2143
  ;
2295
2144
 
2296
- expressionSum returns [ expr ]
2297
- @after{ if ($expr) this.attachLocation($expr); }
2145
+ expressionSum returns [ expr ] locals [ args = [] ]
2146
+ @after{ $expr = this.argsExpression( $args ); }
2298
2147
  :
2299
- e1=expressionFactor { $expr = $e1.expr; }
2148
+ e1=expressionFactor { $args.push( $e1.expr ); }
2300
2149
  (
2301
- op=( '+' | '-' ) e2=expressionFactor
2302
- {
2303
- $expr = {
2304
- op: this.valueWithTokenLocation( $op.text, $op ), args: [$expr, $e2.expr],
2305
- location: this.combinedLocation( $expr, $e2.expr ) };
2306
- }
2150
+ ( '+' | '-' ) { this.pushXprToken( $args ); }
2151
+ e2=expressionFactor { $args.push( $e2.expr ); }
2307
2152
  )*
2308
2153
  ;
2309
2154
 
2310
- expressionFactor returns [ expr ]
2311
- @after{ if ($expr) this.attachLocation($expr); }
2155
+ expressionFactor returns [ expr ] locals [ args = [] ]
2156
+ @after{ $expr = this.argsExpression( $args ); }
2312
2157
  :
2313
- e1=expressionTerm { $expr = $e1.expr; }
2158
+ e1=expressionTerm { $args.push( $e1.expr ); }
2314
2159
  (
2315
- op=( '*' | '/' ) e2=expressionTerm
2316
- {
2317
- $expr = {
2318
- op: this.valueWithTokenLocation( $op.text, $op ), args: [$expr, $e2.expr],
2319
- location: this.combinedLocation( $expr, $e2.expr ) };
2320
- }
2160
+ ( '*' | '/' ) { this.pushXprToken( $args ); }
2161
+ e2=expressionTerm { $args.push( $e2.expr ); }
2321
2162
  )*
2322
2163
  ;
2323
2164
 
2324
- expressionTerm returns [ expr ] locals [ op, args = [] ]
2325
- @after{ /* #ATN 1 */ this.attachLocation($expr); }
2165
+ expressionTerm returns [ expr ] locals [ args = [] ]
2166
+ @after{ /* #ATN 1 */ $expr = this.argsExpression( $args ); }
2326
2167
  :
2327
- unary=( '+' | '-' ) e1=expressionTerm // prefix op or part of the number
2328
- { $expr = this.signedExpression( $unary, $e1.expr ); }
2168
+ ( '+' | '-' ) { this.pushXprToken( $args ); }
2169
+ e1=expressionTerm // prefix op or part of the number
2170
+ { this.signedExpression( $args, $e1.expr ); }
2329
2171
  |
2172
+ val=literalValue { $args.push( $val.val ); }
2173
+ |
2174
+ sf=specialFunction { $args.push( $sf.ret ); }
2175
+ |
2176
+ CASE { this.pushXprToken( $args ); }
2330
2177
  (
2331
- val=literalValue
2332
- { $expr = $val.val; }
2333
- |
2334
- sf=specialFunction
2335
- { $expr = $sf.ret; }
2336
- |
2337
- ca=CASE
2338
- { $expr = { op : this.valueWithTokenLocation( 'case', $ca ), args: [] }; }
2339
- (
2340
- e2=expression { $expr.args.push($e2.expr); }
2341
- ( ow=WHEN ew=expression THEN e3=expression
2342
- { $expr.args.push( this.createPrefixOp( $ow, [ $ew.expr, $e3.expr ] ) ); }
2343
- )+
2344
- |
2345
- ( ow=WHEN c=condition THEN e3=expression
2346
- { $expr.args.push( this.createPrefixOp( $ow, [ $c.cond, $e3.expr ] ) ); }
2347
- )+
2348
- )
2349
- ( el=ELSE e4=expression
2350
- { $expr.args.push( this.createPrefixOp( $el, [ $e4.expr ] ) ); }
2351
- )?
2352
- END
2353
- |
2354
- ne=NEW nqp=valuePath[ 'ref', null] // token rewrite for NEW
2355
- // please note: there will be no compiler-supported code completion after NEW
2356
- { $expr = { op: this.valueWithTokenLocation( 'new', $ne ), args: [] };
2357
- this.error( 'syntax-unsupported-new', $ne, { keyword: $ne.text }, '$(KEYWORD) is not supported' ); }
2178
+ e2=expression { $args.push( $e2.expr ); }
2179
+ ( WHEN { this.pushXprToken( $args ); }
2180
+ ew=expression { $args.push( $ew.expr ); }
2181
+ THEN { this.pushXprToken( $args ); }
2182
+ e3=expression { $args.push( $e3.expr ); }
2183
+ )+
2358
2184
  |
2359
- vp=valuePath[ 'ref', null ] { $expr = this.valuePathAst( $vp.qp ); }
2360
- { this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
2361
2185
  (
2362
- over=overClause { $expr.suffix = [ $over.over ] }
2363
- )?
2364
- |
2365
- ':'
2366
- ( vp=valuePath[ 'paramref', this.startLocation() ]
2367
- { $expr = $vp.qp;; $expr.scope = 'param'; }
2368
- | pp=Number
2369
- { $expr = { param: this.numberLiteral( $pp ), scope: 'param' };
2370
- this.csnParseOnly( 'syntax-unsupported-param', [ $pp ], { '#': 'positional', name: ':' + $pp.text } );
2371
- }
2372
- )
2186
+ WHEN { this.pushXprToken( $args ); }
2187
+ c=condition { $args.push( $c.cond ); }
2188
+ THEN { this.pushXprToken( $args ); }
2189
+ e3=expression { $args.push( $e3.expr ); }
2190
+ )+
2191
+ )
2192
+ (
2193
+ ELSE { this.pushXprToken( $args ); }
2194
+ e4=expression { $args.push( $e4.expr ); }
2195
+ )?
2196
+ END { this.pushXprToken( $args ); }
2197
+ |
2198
+ ne=NEW { this.pushXprToken( $args ); } // token rewrite for NEW
2199
+ // please note: there will be no compiler-supported code completion after NEW
2200
+ { $expr = { op: this.valueWithTokenLocation( 'new', $ne ), args: [] };
2201
+ this.error( 'syntax-unsupported-new', $ne, { keyword: $ne.text }, '$(KEYWORD) is not supported' ); }
2202
+ nqp=valuePath[ 'ref', null ]
2203
+ |
2204
+ vp=valuePath[ 'ref', null ] { $args.push( this.valuePathAst( $vp.qp ) ); }
2205
+ { this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
2206
+ (
2207
+ { $args[0].suffix = []; }
2208
+ OVER { this.pushXprToken( $args[0].suffix ); }
2209
+ open='(' over=overClause close=')'
2210
+ { $args[0].suffix.push( this.surroundByParens( $over.over, $open, $close ) ); }
2211
+ )?
2212
+ |
2213
+ ':'
2214
+ (
2215
+ vp=valuePath[ 'paramref', this.startLocation() ]
2216
+ {{ const par = $vp.qp;; par.scope = 'param';; $args.push( par ); }}
2373
2217
  |
2374
- qm= '?' // is automatically not mentioned as CC candidate
2375
- // if we have an HideAlternatives here, we would block it to use it in
2376
- // parallel to an expression (would produce adaptivePredict() otherwise)
2377
- { $expr = { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' };
2378
- this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
2218
+ pp=Number
2219
+ { $args.push( { param: this.numberLiteral( $pp ), scope: 'param' } );
2220
+ this.csnParseOnly( 'syntax-unsupported-param', [ $pp ], { '#': 'positional', code: ':' + $pp.text } );
2379
2221
  }
2222
+ )
2223
+ |
2224
+ qm= '?' // is automatically not mentioned as CC candidate
2225
+ // if we have an HideAlternatives here, we would block it to use it in
2226
+ // parallel to an expression (would produce adaptivePredict() otherwise)
2227
+ { $args.push( { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' } );
2228
+ this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', code: '?' } );
2229
+ }
2230
+ |
2231
+ open='('
2232
+ // #ATN: The following alternative is not LL1, because both can start with
2233
+ // left-paren, but queryExpression has SELECT after initial left-parens
2234
+ (
2235
+ qe=queryExpression close=')'
2236
+ { $args.push( this.surroundByParens( $qe.query, $open, $close, true ) ); }
2380
2237
  |
2381
- open='('
2382
- // #ATN: The following alternative is not LL1, because both can start with
2383
- // left-paren, but queryExpression has SELECT after initial left-parens
2384
- (
2385
- qe=queryExpression close=')'
2386
- { $expr = this.surroundByParens( $qe.query, $open, $close, true ); }
2387
- |
2388
- c1=condition { $expr = [ $c1.cond ]; }
2389
- ( ',' { if ($expr.length > 1 && this.isStraightBefore(')')) break; } // allow ',' before ')'
2390
- cn=expression { if ($cn.expr) $expr.push($cn.expr); }
2391
- )*
2392
- close=')'
2393
- {
2394
- if ($expr.length > 1)
2395
- $expr = { op: this.valueWithTokenLocation( ',', $open ), args: $expr };
2396
- else if ($expr[0]) // can be `null` if condition failed to parse
2397
- $expr = this.surroundByParens( $expr[0], $open, $close );
2398
- }
2399
- )
2238
+ c1=condition { $args = [ $c1.cond ]; }
2239
+ ( ',' { if ($args.length > 1 && this.isStraightBefore(')')) break; } // allow ',' before ')'
2240
+ cn=expression { if ($cn.expr) $args.push($cn.expr); }
2241
+ )*
2242
+ close=')'
2243
+ {
2244
+ if ($args.length > 1)
2245
+ $args = [ { op: this.valueWithTokenLocation( 'list', $open ), args: $args,
2246
+ location: this.tokenLocation( $open, $close ) } ];
2247
+ else if ($args[0]) // can be `null` if condition failed to parse
2248
+ $args = [ this.surroundByParens( $args[0], $open, $close ) ];
2249
+ }
2400
2250
  )
2401
2251
  ;
2402
2252
 
2403
- specialFunction returns [ ret = { } ] locals[ art = {} ]
2253
+ specialFunction returns [ ret = {} ] locals[ art = {} ]
2404
2254
  :
2405
2255
  ca=CAST '(' // see createArray() in action
2406
2256
  {
@@ -2417,9 +2267,21 @@ specialFunction returns [ ret = { } ] locals[ art = {} ]
2417
2267
  ')' { this.finalizeDictOrArray( $ret.args ); }
2418
2268
  ;
2419
2269
 
2420
- // query path includes aggregation:
2421
- // ( COUNT | MIN | MAX | SUM | AVG | STDDEV | VAR )
2422
- // '(' ( '*' | expression | ALL expression | DISTINCT expression_list ) ')'
2270
+ // Paths and functions: ---------------------------------------------------------
2271
+
2272
+ simplePath[ art, category ] locals[ _sync = 'nop' ]
2273
+ @after { this.attachLocation($art); }
2274
+ // Due to error recovery, rule `ident` can return with value `null`. Set the
2275
+ // path as broken in this case.
2276
+ :
2277
+ head=ident[ $category ]
2278
+ { if (!$art.path) $art.path = []; this.pushIdent( $art.path, $head.id );
2279
+ if ($category === 'artref') $art.scope = 0;
2280
+ }
2281
+ (
2282
+ '.' tail=ident[ $category ] { this.pushIdent( $art.path, $tail.id ); }
2283
+ )*
2284
+ ;
2423
2285
 
2424
2286
  valuePath[ category, location = null ] returns[ qp = { path: [] } ] locals[ _sync = 'nop' ]
2425
2287
  @init { $qp.location = location || this.startLocation(); }
@@ -2439,19 +2301,6 @@ valuePath[ category, location = null ] returns[ qp = { path: [] } ] locals[ _syn
2439
2301
  )*
2440
2302
  ;
2441
2303
 
2442
- fromArguments[ pathStep ]
2443
- @init{ if (!$pathStep) $pathStep = {}; } // grammar robustness, see test/negative/parser/NamedExpression.cds
2444
- :
2445
- '(' { $pathStep.args = this.createDict(); $pathStep['$'+'syntax'] = ':'; } // necessary?
2446
- name=ident['paramname'] ':'
2447
- namedExpression[ $pathStep, $name.id ]
2448
- ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
2449
- name=ident['paramname'] ':'
2450
- namedExpression[ $pathStep, $name.id ]
2451
- )*
2452
- ')' { this.finalizeDictOrArray( $pathStep.args ); }
2453
- ;
2454
-
2455
2304
  pathArguments[ pathStep, considerSpecial ]
2456
2305
  @init{
2457
2306
  if (!$pathStep) $pathStep = {}; // grammar robustness, see test/negative/parser/NamedExpression.cds
@@ -2491,11 +2340,9 @@ pathArguments[ pathStep, considerSpecial ]
2491
2340
  funcExpression[ $pathStep, $considerSpecial ]
2492
2341
  )*
2493
2342
  // Note: We can't move this into funcExpression, or we would increase the ATN count because of `,` amiguity.
2494
- ( ob=functionOrderByClause[ $pathStep.args[$pathStep.args.length - 1] ]
2495
- {
2496
- // Remove the last entry which was copied to $ob.expr and push $ob.expr.
2497
- $pathStep.args[$pathStep.args.length - 1] = $ob.expr;
2498
- }
2343
+ ( ob=funcOrderByClause[ [ $pathStep.args[$pathStep.args.length - 1] ] ]
2344
+ // Remove the last entry which was copied to $ob.expr and push $ob.expr:
2345
+ { $pathStep.args[$pathStep.args.length - 1] = $ob.expr; }
2499
2346
  )?
2500
2347
  |
2501
2348
  { $pathStep.args = this.createArray(); }
@@ -2517,32 +2364,29 @@ namedExpression[ pathStep, id ]
2517
2364
  }
2518
2365
  ;
2519
2366
 
2520
- funcExpression[ pathStep, considerSpecial ] locals[ args ]
2367
+ funcExpression[ pathStep, considerSpecial ] locals[ args = [] ]
2521
2368
  @init { this.prepareGenericKeywords( $considerSpecial ); }
2369
+ @after{ $pathStep.args.push( this.argsExpression( $args, true ) ); }
2522
2370
  :
2523
2371
  (
2524
2372
  expr=expression
2525
- { $pathStep.args.push( $expr.expr ); }
2373
+ { $args.push( $expr.expr ); }
2526
2374
  |
2527
2375
  GenericExpr // keyword as replacement for expression, like '*'
2528
- { $pathStep.args.push( this.xprToken() ); }
2376
+ { this.pushXprToken( $args ); }
2529
2377
  |
2530
2378
  GenericIntro // keyword as introduction of expression, like DISTINCT
2531
- { $pathStep.args.push( this.xprToken() ); }
2532
- expr=expression
2533
- { $args = this.setLastAsXpr( $pathStep.args );
2534
- $args.push( $expr.expr ); }
2379
+ { this.pushXprToken( $args ); }
2380
+ expr=expression { $args.push( $expr.expr ); }
2535
2381
  |
2536
2382
  // Rule 'pathArguments' makes a decision based on the first two lookahead
2537
2383
  // tokens of this rule → we need to list tokens which would be changed to
2538
2384
  // GenericExpr or GenericIntro, and are not already covered by 'expression'
2539
2385
  { this.reportErrorForGenericKeyword(); }
2540
- ( HideAlternatives | '*' | ALL | DISTINCT )
2386
+ ( HideAlternatives | '*' | ALL | DISTINCT ) { this.pushXprToken( $args ); }
2541
2387
  // now continue parsing like GenericExpr:
2542
- { $pathStep.args.push( this.xprToken() ); }
2543
2388
  )
2544
2389
  (
2545
- { if (!$args) $args = this.setLastAsXpr( $pathStep.args ); }
2546
2390
  (
2547
2391
  { this.prepareGenericKeywords( $considerSpecial, 'separator' ); }
2548
2392
  (
@@ -2556,26 +2400,108 @@ funcExpression[ pathStep, considerSpecial ] locals[ args ]
2556
2400
  { this.reportErrorForGenericKeyword(); }
2557
2401
  ( HideAlternatives | Identifier | FROM | IN | WITH | GROUP )
2558
2402
  )
2559
- { $args.push( this.xprToken() );
2560
- this.prepareGenericKeywords( $considerSpecial, 'expr' );
2561
- }
2403
+ { this.pushXprToken( $args );
2404
+ this.prepareGenericKeywords( $considerSpecial, 'expr' ); }
2562
2405
  (
2563
- expr=expression
2564
- { $args.push( $expr.expr ); }
2406
+ expr=expression { $args.push( $expr.expr ); }
2565
2407
  |
2566
- GenericExpr
2567
- { $args.push( this.xprToken() ); }
2408
+ GenericExpr { this.pushXprToken( $args ); }
2568
2409
  |
2569
2410
  { this.reportErrorForGenericKeyword(); }
2570
2411
  // Again, we need to list tokens which could make it to GenericExpr
2571
2412
  // and which do not start an expression
2572
- ( HideAlternatives | ALL )
2573
- { $args.push( this.xprToken() ); }
2413
+ ( HideAlternatives | ALL ) { this.pushXprToken( $args ); }
2574
2414
  )
2575
2415
  )+
2576
2416
  )?
2577
2417
  ;
2578
2418
 
2419
+ overClause returns[ over ] locals[ args = [] ]
2420
+ @after{ $over = this.argsExpression( $args ); }
2421
+ :
2422
+ // TODO: empty really allowed?
2423
+ ( PARTITION { this.pushXprToken( $args ); } BY { this.pushXprToken( $args ); }
2424
+ pb=partitionByClause { $args.push( $pb.expr ); }
2425
+ )?
2426
+ ( ORDER { this.pushXprToken( $args ); } BY { this.pushXprToken( $args ); }
2427
+ ob=exprOrderByClause { $args.push( $ob.expr ); }
2428
+ )?
2429
+ ( ROWS { this.pushXprToken( $args ); }
2430
+ wf=windowFrameClause { $args.push( $wf.wf ); }
2431
+ )?
2432
+ ;
2433
+
2434
+ partitionByClause returns [ expr ] locals[ args = [] ]
2435
+ @after{ $expr = this.argsExpression( $args ); }
2436
+ :
2437
+ e1=expression { $args.push( $e1.expr ); }
2438
+ ( ',' { this.pushXprToken( $args ); }
2439
+ en=expression { $args.push( $en.expr ); }
2440
+ )*
2441
+ ;
2442
+
2443
+ // ORDER BY clause in generic functions, e.g. `first_value(id order by name)`
2444
+ funcOrderByClause[ args ] returns[ expr ]
2445
+ @after{ $expr = this.argsExpression( args, true ); }
2446
+ :
2447
+ ORDER { this.pushXprToken( $args ); } BY { this.pushXprToken( $args ); }
2448
+ ob=exprOrderByClause { $args.push( $ob.expr ); }
2449
+ ;
2450
+
2451
+ // ORDER BY clause in generic functions or OVER clause
2452
+ exprOrderByClause returns[ expr ] locals[ args = [] ]
2453
+ @after{ $expr = this.argsExpression( $args ); }
2454
+ :
2455
+ orderBySpecInExpr[ $args ]
2456
+ ( ',' { this.pushXprToken( $args ); } orderBySpecInExpr[ $args ] )*
2457
+ ;
2458
+
2459
+ orderBySpecInExpr[ args ]
2460
+ :
2461
+ e=expression { $args.push( $e.expr ); }
2462
+ ( ASC { this.pushXprToken( $args ); }
2463
+ | DESC { this.pushXprToken( $args ); }
2464
+ )?
2465
+ ( NULLS { this.pushXprToken( $args ); }
2466
+ ( FIRST | LAST ) { this.pushXprToken( $args ); }
2467
+ )?
2468
+ ;
2469
+
2470
+ windowFrameClause returns[ wf ] locals[ args = [] ]
2471
+ @after{ $wf = this.argsExpression( $args ); }
2472
+ :
2473
+ (
2474
+ windowFrameStartSpec[ $args ]
2475
+ |
2476
+ BETWEEN { this.pushXprToken( $args ); }
2477
+ windowFrameBoundSpec[ $args ]
2478
+ AND { this.pushXprToken( $args ); }
2479
+ windowFrameBoundSpec[ $args ]
2480
+ )
2481
+ ;
2482
+
2483
+ windowFrameBoundSpec[ args ]
2484
+ :
2485
+ UNBOUNDED { this.pushXprToken( $args ); }
2486
+ ( FOLLOWING | PRECEDING ) { this.pushXprToken( $args ); }
2487
+ |
2488
+ n=Number { $args.push( this.numberLiteral( $n ) ); }
2489
+ ( FOLLOWING | PRECEDING ) { this.pushXprToken( $args ); }
2490
+ |
2491
+ CURRENT { this.pushXprToken( $args ); } ROW { this.pushXprToken( $args ); }
2492
+ ;
2493
+
2494
+ windowFrameStartSpec[ args = [] ]
2495
+ :
2496
+ UNBOUNDED { this.pushXprToken( $args ); }
2497
+ PRECEDING { this.pushXprToken( $args ); }
2498
+ |
2499
+ n=Number { $args.push( this.numberLiteral( $n ) ); }
2500
+ PRECEDING { this.pushXprToken( $args ); }
2501
+ |
2502
+ CURRENT { this.pushXprToken( $args ); } ROW { this.pushXprToken( $args ); }
2503
+ ;
2504
+
2579
2505
  cardinalityAndFilter[ pathStep ] locals [ _sync = 'nop' ]
2580
2506
  :
2581
2507
  '['
@@ -2607,13 +2533,171 @@ optionalWhereForFilter
2607
2533
  // one of the expected ones.
2608
2534
  {
2609
2535
  var text = this.getCurrentToken().text.toUpperCase();
2610
- if (!['WHERE','GROUP','ORDER','LIMIT'].includes( text )) return;
2536
+ if (!['WHERE','GROUP','ORDER','LIMIT'].includes( text )) return $ctx;
2611
2537
  // TODO: should we somehow add those keywords to $(EXPECTED)?
2612
2538
  }
2613
2539
  WHERE
2614
2540
  ;
2615
2541
 
2616
- // Simple paths and values ---------------------------------------------------
2542
+ // Annotation assignments -------------------------------------------------------
2543
+
2544
+ // We have three versions of the annotation assignment rules:
2545
+ // - "fix": typically after a name if a ':' could follow
2546
+ // - "ll1": typically before keyword+name, after a name if no ':' could follow
2547
+ // - "atn": at the beginning of a column definition
2548
+ //
2549
+ // want to let the ambiguity in select items (solution: "either" possibility)
2550
+ //
2551
+ // entity E @Anno: Base { … }; // Base is include (chosen w/ warning) or @Anno value?
2552
+ // entity V(p) as select from E { // either: anno value "ref p", select item -x
2553
+ // @anno :p - x as x; // or: anno value true, select item :p-x
2554
+ // }
2555
+
2556
+ annotationAssignment_fix[ art ] locals[ assignment ]
2557
+ // value outside @(...)
2558
+ @after {
2559
+ if ($assignment) {
2560
+ this.assignAnnotation( $art, $assignment );
2561
+ this.docComment( $art );
2562
+ }
2563
+ } :
2564
+ '@'
2565
+ (
2566
+ annotationAssignment_paren[ $art ]
2567
+ |
2568
+ { $assignment = { name: {} }; }
2569
+ annotationPath[ $assignment.name, 'anno' ]
2570
+ annotationPathVariant[ $assignment.name ]?
2571
+ { this.warnIfColonFollows( $assignment ); }
2572
+ )
2573
+ ;
2574
+
2575
+ annotationAssignment_ll1[ art ] locals[ assignment ]
2576
+ @after {
2577
+ if ($assignment) {
2578
+ this.assignAnnotation( $art, $assignment );
2579
+ this.docComment( $art );
2580
+ }
2581
+ } :
2582
+ '@'
2583
+ (
2584
+ annotationAssignment_paren[ $art ]
2585
+ |
2586
+ { $assignment = { name: {} }; }
2587
+ annotationPath[ $assignment.name, 'anno' ]
2588
+ annotationPathVariant[ $assignment.name ]?
2589
+ (
2590
+ ':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
2591
+ val=annoValue[ $assignment ]
2592
+ )?
2593
+ )
2594
+ ;
2595
+
2596
+ // Has previously used ATN, now via local token rewrite
2597
+ annotationAssignment_atn[ art ] locals[ assignment ]
2598
+ @after {
2599
+ if ($assignment) {
2600
+ this.assignAnnotation( $art, $assignment );
2601
+ this.docComment( $art );
2602
+ }
2603
+ } :
2604
+ '@'
2605
+ (
2606
+ annotationAssignment_paren[ $art ]
2607
+ |
2608
+ { $assignment = { name: {} }; }
2609
+ annotationPath[ $assignment.name, 'anno' ]
2610
+ // '#' is in the follow set of this rule, as it is used in rule "selectItemDef"
2611
+ // before an "expression" which can start with a '#' for an enum value
2612
+ // -> used to introduce variant name if and only if in same line as previous token
2613
+ { this.setLocalToken( '#', 'HelperToken1', null, true ); }
2614
+ (
2615
+ HelperToken1 { this.meltKeywordToIdentifier(); }
2616
+ variant=ident['variant'] { $assignment.name.variant = $variant.id; }
2617
+ )?
2618
+ // ':' is in the follow set of this rule, as it is used in rule "selectItemDef"
2619
+ // before an "expression" which can start with a ':' for a parameter reference
2620
+ // -> used to introduce assignment value if and only if in same line as previous token
2621
+ { this.setLocalToken( ':', 'HelperToken2', null, true ); }
2622
+ ( HelperToken2 // ':'
2623
+ { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
2624
+ (
2625
+ val=annoValueBase[ $assignment ]
2626
+ |
2627
+ at='@'? annotationPath[ $assignment, 'ref', $at ]
2628
+ { this.setLocalToken( '#', 'HelperToken1', null, true ); } // see above
2629
+ (
2630
+ HelperToken1 { this.meltKeywordToIdentifier(); }
2631
+ variant=ident['variant'] { $assignment.variant = $variant.id; }
2632
+ )?
2633
+ )
2634
+ )?
2635
+ )
2636
+ ;
2637
+
2638
+ annotationAssignment_paren[ art ]
2639
+ :
2640
+ '('
2641
+ // allow completely useless `@()` with a warning, do not offer it for completion
2642
+ {
2643
+ this.meltKeywordToIdentifier();
2644
+ if (this.isStraightBefore(')')) {
2645
+ // TODO: or should we simple accept this without warning? (but still no CC)
2646
+ this.warning( 'syntax-unexpected-right-paren',
2647
+ this.tokenLocation( this.getCurrentToken() ),
2648
+ { offending: "')'", expecting: ['Identifier'], code: '@()' },
2649
+ 'Unexpected $(OFFENDING), expecting $(EXPECTING); ignoring $(CODE)' );
2650
+ this.matchWildcard(); // we know it is the ')' - we do not reach the final match
2651
+ return $ctx;
2652
+ }
2653
+ }
2654
+ annotationAssignment_1[ $art ]
2655
+ ( ','
2656
+ {
2657
+ this.meltKeywordToIdentifier();
2658
+ if (this.isStraightBefore(')')) break; // allow ',' before ')'
2659
+ }
2660
+ annotationAssignment_1[ $art ]
2661
+ )*
2662
+ ')'
2663
+ ;
2664
+
2665
+ annotationAssignment_1[ art ] locals[ assignment = { name: {} } ]
2666
+ @after { this.assignAnnotation( $art, $assignment ); }
2667
+ :
2668
+ annotationPath[ $assignment.name, 'anno' ]
2669
+ annotationPathVariant[ $assignment.name ]?
2670
+ (
2671
+ ':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
2672
+ val=annoValue[ $assignment ]
2673
+ )?
2674
+ ;
2675
+
2676
+ annotationPath[ art, category, headat = null ] locals[ _sync = 'nop' ]
2677
+ @after { this.attachLocation($art); }
2678
+ // Due to error recovery, rule `ident` can return with value `null`. Set the
2679
+ // path as broken in this case.
2680
+ :
2681
+ head=ident[ $category ]
2682
+ { $art.path = []; this.pushIdent( $art.path, $head.id, $headat );
2683
+ if ($category === 'artref') $art.scope = 0;
2684
+ }
2685
+ (
2686
+ '.' at='@'? tail=ident[ $category ]
2687
+ { this.pushIdent( $art.path, $tail.id, $at );
2688
+ // Otherwise, $at may continue to be set after one `.@anno` segment.
2689
+ $ctx.at = null;
2690
+ }
2691
+ )*
2692
+ ;
2693
+
2694
+ annotationPathVariant[ art ] locals[ variant = {} ]
2695
+ @after { this.attachLocation($art); }
2696
+ :
2697
+ // TODO: warning for space after '#'
2698
+ '#' { this.meltKeywordToIdentifier(); }
2699
+ simplePath[ $variant, 'variant' ] { $art.variant = $variant; }
2700
+ ;
2617
2701
 
2618
2702
  annoValue[ assignment ]
2619
2703
  :
@@ -2742,6 +2826,8 @@ annoSubValue returns[ val = {} ]
2742
2826
  ( annotationPathVariant[ $val ] )?
2743
2827
  ;
2744
2828
 
2829
+ // Literal values and identifiers -----------------------------------------------
2830
+
2745
2831
  literalValue returns[ val ] locals[ tok ]
2746
2832
  @init{ $tok = this.getCurrentToken(); }
2747
2833
  @after { this.attachLocation($val); }
@@ -2765,47 +2851,7 @@ literalValue returns[ val ] locals[ tok ]
2765
2851
  { $val = this.quotedLiteral( $tok ); }
2766
2852
  ;
2767
2853
 
2768
- simplePath[ art, category ] locals[ _sync = 'nop' ]
2769
- @after { this.attachLocation($art); }
2770
- // Due to error recovery, rule `ident` can return with value `null`. Set the
2771
- // path as broken in this case.
2772
- :
2773
- head=ident[ $category ]
2774
- { if (!$art.path) $art.path = []; this.pushIdent( $art.path, $head.id );
2775
- if ($category === 'artref') $art.scope = 0;
2776
- }
2777
- (
2778
- '.' tail=ident[ $category ] { this.pushIdent( $art.path, $tail.id ); }
2779
- )*
2780
- ;
2781
-
2782
- annotationPath[ art, category, headat = null ] locals[ _sync = 'nop' ]
2783
- @after { this.attachLocation($art); }
2784
- // Due to error recovery, rule `ident` can return with value `null`. Set the
2785
- // path as broken in this case.
2786
- :
2787
- head=ident[ $category ]
2788
- { $art.path = []; this.pushIdent( $art.path, $head.id, $headat );
2789
- if ($category === 'artref') $art.scope = 0;
2790
- }
2791
- (
2792
- '.' at='@'? tail=ident[ $category ]
2793
- { this.pushIdent( $art.path, $tail.id, $at );
2794
- // Otherwise, $at may continue to be set after one `.@anno` segment.
2795
- $ctx.at = null;
2796
- }
2797
- )*
2798
- ;
2799
-
2800
- annotationPathVariant[ art ] locals[ variant = {} ]
2801
- @after { this.attachLocation($art); }
2802
- :
2803
- // TODO: warning for space after '#'
2804
- '#' { this.meltKeywordToIdentifier(); }
2805
- simplePath[ $variant, 'variant' ] { $art.variant = $variant; }
2806
- ;
2807
-
2808
- // Identifier and non-reserved keywords --------------------------------------
2854
+ // #IDENT - keep this comment here, used in scripts/linter/lintGrammar.js
2809
2855
 
2810
2856
  identNoKeyword[ category ] returns[ id ] // for aliases without AS
2811
2857
  @after{ $id = this.identAst( $stop, $category ); }
@@ -2840,7 +2886,6 @@ ident[ category ] returns[ id ]
2840
2886
  | DEFINE
2841
2887
  | DEFINITIONS
2842
2888
  | DESC
2843
- | ELEMENT
2844
2889
  | ELEMENTS
2845
2890
  | ELSE
2846
2891
  | END
@@ -2870,7 +2915,6 @@ ident[ category ] returns[ id ]
2870
2915
  | LIMIT
2871
2916
  | LOCALIZED
2872
2917
  | MANY
2873
- | MASKED
2874
2918
  | MINUS
2875
2919
  | MIXIN
2876
2920
  | NAMESPACE
@@ -2901,7 +2945,7 @@ ident[ category ] returns[ id ]
2901
2945
  | VIEW
2902
2946
  ;
2903
2947
 
2904
- //----------------------------------------------------------------------------
2948
+ // LEXER ------------------------------------------------------------------------
2905
2949
 
2906
2950
  WhiteSpace // like \s in JavaScript RegExp
2907
2951
  : // LineTerminator | [\t\f\v\u00A0\uFEFF] | Zs
@@ -2914,10 +2958,10 @@ Comment : '/*' .*? '*/' -> channel(HIDDEN);
2914
2958
 
2915
2959
  LineComment : '//' ~[\r\n\u2028\u2029]* -> channel(HIDDEN);
2916
2960
 
2917
- // Values --------------------------------------------------------------------
2961
+ // Literal values ---------------------------------------------------------------
2918
2962
 
2919
2963
  // for syntactic code-completion: Combine all three string styles
2920
- // Note: Use rule `string` instead as that also parses escape sequences!
2964
+ // Note: Use rule `string` instead as that also parses escape sequences! (TODO: ???)
2921
2965
  String : SingleLineString
2922
2966
  | MultiLineString
2923
2967
  | MutlLineStringBlock;
@@ -3037,7 +3081,7 @@ DEFAULT : [dD][eE][fF][aA][uU][lL][tT] ;
3037
3081
  DEFINE : [dD][eE][fF][iI][nN][eE] ;
3038
3082
  DEFINITIONS : [dD][eE][fF][iI][nN][iI][tT][iI][oO][nN][sS] ;
3039
3083
  DESC : [dD][eE][sS][cC] ;
3040
- ELEMENT : [eE][lL][eE][mM][eE][nN][tT] ;
3084
+ // ELEMENT : [eE][lL][eE][mM][eE][nN][tT] ;
3041
3085
  ELEMENTS : [eE][lL][eE][mM][eE][nN][tT][sS] ;
3042
3086
  ELSE : [eE][lL][sS][eE] ;
3043
3087
  END : [eE][nN][dD] ;
@@ -3067,7 +3111,7 @@ LIKE : [lL][iI][kK][eE] ;
3067
3111
  LIMIT : [lL][iI][mM][iI][tT] ;
3068
3112
  LOCALIZED: [lL][oO][cC][aA][lL][iI][zZ][eE][dD];
3069
3113
  MANY : [mM][aA][nN][yY] ;
3070
- MASKED : [mM][aA][sS][kK][eE][dD] ;
3114
+ // MASKED : [mM][aA][sS][kK][eE][dD] ;
3071
3115
  MINUS : [mM][iI][nN][uU][sS] ;
3072
3116
  MIXIN : [mM][iI][xX][iI][nN] ;
3073
3117
  NAMESPACE : [nN][aA][mM][eE][sS][pP][aA][cC][eE] ;