@sap/cds-compiler 5.2.0 → 5.3.2

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 (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/cdsc.js +5 -0
  3. package/bin/cdshi.js +8 -8
  4. package/doc/CHANGELOG_BETA.md +9 -4
  5. package/lib/api/validate.js +5 -0
  6. package/lib/base/message-registry.js +25 -1
  7. package/lib/base/messages.js +1 -1
  8. package/lib/base/model.js +0 -1
  9. package/lib/compiler/assert-consistency.js +2 -2
  10. package/lib/compiler/builtins.js +1 -1
  11. package/lib/compiler/checks.js +25 -6
  12. package/lib/compiler/define.js +24 -28
  13. package/lib/compiler/extend.js +11 -13
  14. package/lib/compiler/generate.js +3 -3
  15. package/lib/compiler/populate.js +13 -7
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +58 -60
  18. package/lib/compiler/shared.js +5 -5
  19. package/lib/compiler/tweak-assocs.js +247 -34
  20. package/lib/compiler/utils.js +40 -32
  21. package/lib/compiler/xpr-rewrite.js +44 -58
  22. package/lib/edm/annotations/genericTranslation.js +4 -4
  23. package/lib/edm/csn2edm.js +2 -2
  24. package/lib/edm/edm.js +46 -21
  25. package/lib/edm/edmInboundChecks.js +0 -1
  26. package/lib/edm/edmPreprocessor.js +40 -27
  27. package/lib/edm/edmUtils.js +1 -1
  28. package/lib/gen/BaseParser.js +180 -122
  29. package/lib/gen/CdlParser.js +2226 -2170
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +3820 -3777
  33. package/lib/inspect/inspectPropagation.js +1 -1
  34. package/lib/json/from-csn.js +5 -3
  35. package/lib/json/to-csn.js +7 -10
  36. package/lib/language/antlrParser.js +38 -4
  37. package/lib/language/errorStrategy.js +1 -1
  38. package/lib/language/genericAntlrParser.js +4 -4
  39. package/lib/language/multiLineStringParser.js +1 -1
  40. package/lib/main.d.ts +23 -0
  41. package/lib/model/cloneCsn.js +22 -13
  42. package/lib/optionProcessor.js +7 -7
  43. package/lib/parsers/AstBuildingParser.js +155 -37
  44. package/lib/parsers/CdlGrammar.g4 +154 -81
  45. package/lib/parsers/Lexer.js +20 -10
  46. package/lib/render/toCdl.js +23 -18
  47. package/lib/transform/addTenantFields.js +4 -4
  48. package/lib/transform/db/rewriteCalculatedElements.js +11 -5
  49. package/lib/transform/db/transformExists.js +43 -18
  50. package/lib/transform/effective/main.js +1 -1
  51. package/lib/transform/forRelationalDB.js +8 -7
  52. package/lib/utils/moduleResolve.js +1 -1
  53. package/package.json +1 -1
  54. package/share/messages/redirected-to-complex.md +6 -3
@@ -42,9 +42,9 @@ tokens{ // reserved words
42
42
  start returns[ source = new XsnSource( 'cdl' ) ]
43
43
  :
44
44
  (
45
- ( <cond=fileSection> namespaceDeclaration[ $source ]
45
+ ( <cond=namespaceRestriction> namespaceDeclaration[ $source ]
46
46
  | usingDeclaration[ $source ]
47
- | artifactDefOrExtend[ $source ] <setCondition=fileSection>
47
+ | artifactDefOrExtend[ $source ] <setCondition=namespaceRestriction>
48
48
  )
49
49
  ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
50
50
  )*
@@ -53,7 +53,8 @@ start returns[ source = new XsnSource( 'cdl' ) ]
53
53
 
54
54
  artifactsBlock[ art, start = undefined ]
55
55
  :
56
- '{' { $art.artifacts = this.createDict( $start ); $art.extensions = []; }
56
+ '{'<setCondition=vocabularyRestriction>
57
+ { $art.artifacts = this.createDict( $start ); $art.extensions = []; }
57
58
  (
58
59
  artifactDefOrExtend[ $art ]
59
60
  ( ';' | <exitLoop> | <repeatLoop=afterBrace> { this.noAssignmentInSameLine(); } )
@@ -70,7 +71,7 @@ artifactDefOrExtend[ outer ] locals[ art = new XsnArtifact() ]
70
71
  DEFINE?
71
72
  ( serviceDef[ $art, $outer ]
72
73
  | contextDef[ $art, $outer ]
73
- | annotationDef[ $art, $outer ] // TODO: <cond> for syntax-unexpected-vocabulary
74
+ | <cond=vocabularyRestriction>annotationDef[ $art, $outer ]
74
75
  | typeDef[ $art, $outer ]
75
76
  | aspectDef[ $art, $outer ]
76
77
  | entityDef[ $art, $outer ]
@@ -79,9 +80,8 @@ artifactDefOrExtend[ outer ] locals[ art = new XsnArtifact() ]
79
80
  | actionMainDef[ $art, $outer ]
80
81
  | functionMainDef[ $art, $outer ]
81
82
  )
82
- // TODO: condition to disable ANNOTATE/EXTEND in EXTEND … WITH DEFINITION
83
83
  |
84
- EXTEND { $art.kind = 'extend'; }
84
+ <cond=extensionRestriction> EXTEND { $art.kind = 'extend'; }
85
85
  ( extendArtifact[ $art, $outer ]
86
86
  | extendService[ $art, $outer ]
87
87
  // Non-streamlined Syntax; we would neither add new clauses to them, nor
@@ -92,7 +92,7 @@ artifactDefOrExtend[ outer ] locals[ art = new XsnArtifact() ]
92
92
  | <hide> extendProjection[ $art, $outer ]
93
93
  )
94
94
  |
95
- ANNOTATE annotateArtifact[ $art, $outer ]
95
+ <cond=extensionRestriction> ANNOTATE annotateArtifact[ $art, $outer ]
96
96
  )
97
97
  ;
98
98
 
@@ -384,16 +384,16 @@ paramDef[ outer ] locals[ art = new XsnArtifact() ]
384
384
  elementsBlock[ $art ]
385
385
  nullability[ $art ]?
386
386
  |
387
- ':' // s/th like <setCondition=allowDefaultOrCalc>
387
+ ':'
388
388
  typeExpression[ $art ] // was elementType, with NOT? NULL / DEFAULT
389
- { this.docComment( $art ); } annoAssignStd[ $art ]*
390
389
  )
391
390
  ;
392
391
 
393
392
  returnsSpec[ outer ] locals[ art = new XsnArtifact() ]
394
393
  @finally{ this.attachLocation( $art ); }
395
394
  :
396
- RETURNS { $art.kind = 'param'; $outer.returns = $art; }
395
+ RETURNS<setCondition=elementRestriction>
396
+ { $art.kind = 'param'; $outer.returns = $art; }
397
397
  { this.docComment( $art ); } annoAssignStd[ $art ]*
398
398
  typeExpression[ $art ]
399
399
  ;
@@ -428,18 +428,22 @@ elementDef[ outer, art = undefined ]
428
428
  elementsBlock[ $art ]
429
429
  nullability[ $art ]?
430
430
  |
431
- ':' // s/th like <setCondition=allowDefaultOrCalc>
431
+ ':'<setCondition=elementRestriction>
432
432
  typeExpression[ $art ] // was elementType, with NOT? NULL / DEFAULT
433
- )?
433
+ |
434
+ // <setCondition=elementRestriction> // TODO TOOL: allow this
435
+ { this.elementRestriction(); }<always> // workaround
436
+ )
434
437
  (
435
- // <cond=allowDefaultOrCalc>
436
- '='
438
+ <cond=calcOrDefaultRestriction> '='
439
+ // TODO TOOL: add to "expected set" if failing here? Or have some "do not
440
+ // consider for rule exit if condition failure on `=`"?
437
441
  expr=expression { $art.value = $expr; }
438
442
  ( STORED { $art.value.stored = this.valueWithLocation( true ); } )?
439
443
  // TODO: why have `stored` as property of the value?
444
+ { this.docComment( $art ); } // TODO: also restricted
445
+ ( <cond=elementRestriction> annoAssignStd[ $art ] )*
440
446
  )?
441
- { this.docComment( $art ); }
442
- ( <cond=allowFinalAnnoAssign> annoAssignStd[ $art ] )*
443
447
  ;
444
448
 
445
449
  enumSymbolsBlock[ art ]
@@ -583,7 +587,7 @@ extendArtifact[ art, outer ]
583
587
  |
584
588
  typeNamedArgsList[ $art ]
585
589
  |
586
- COLUMNS selectItemsList[ $art, 'columns', this.lb() ]
590
+ COLUMNS selectItemsList[ $art, this.lb() ]
587
591
  |
588
592
  DEFINITIONS artifactsBlock[ $art, this.lb() ]
589
593
  )?
@@ -789,20 +793,7 @@ elementDefOrExtend[ outer ] locals[ art = new XsnArtifact() ]
789
793
 
790
794
  // Type expressions -------------------------------------------------------------
791
795
 
792
- // in language.g4:
793
- // - elementType: full stuff + enum + localized + null + default ( + calc ), managed-compo
794
- // - in type def: typeSpecSemi: elementsBlock, : elementsBlock, assoc, localized
795
- // - param def = default + typeSpec: elementsBlock + :typeSpecCont (no assoc!)
796
- // - returns = typeSpecCont: elementsBlock + null, typeArray, typeOf (+ enum), ref+null+enum,
797
- // - typeArray: +enum + null + typeof
798
-
799
- // - select item: redirected to + type of / localized, ref, assoc (published assoc)
800
- // - mixin: only assoc
801
- // - cast: just refoptargs
802
- // - extend: just the type args
803
-
804
- // -> typeExpression, typeForColumn, typeForMixin, typeRefOptArgs
805
-
796
+ // For `type` and `annotation` definitions:
806
797
  typeOrIncludesSpec[ art ]
807
798
  :
808
799
  elementsBlock[ $art ]
@@ -815,11 +806,15 @@ typeOrIncludesSpec[ art ]
815
806
  <priority>
816
807
  ref=simplePath { $art.type = $ref; }
817
808
  (
818
- ( typeRefOptArgs[ $art ]<atAltStart> )?
819
- nullability[ $art ]?
820
- { this.docComment( $art ); } annoAssignStd[ $art ]*
821
- enumSymbolsBlock[ $art ]?
822
- nullabilityAndDefault[ $art ]?
809
+ // <default> does not work here
810
+ typeRefOptArgs[ $art ]<atAltStart>
811
+ ( typeExpression[ $art ]<atAltStart>
812
+ | { this.docComment( $art ); }
813
+ )
814
+ |
815
+ typeExpression[ $art ]<atAltStart>
816
+ |
817
+ { this.docComment( $art ); }
823
818
  |
824
819
  { $art.includes = [ $art.type ]; delete $art.type; }
825
820
  (
@@ -835,36 +830,82 @@ typeOrIncludesSpec[ art ]
835
830
  )
836
831
  ;
837
832
 
833
+ // Type expression (after the `:`) and “final” annotation assignment. Type
834
+ // expressions include `null`/`not null` and `default`, the latter is forbidden in
835
+ // the `returns` type.
836
+ //
837
+ // If used in a definition with additional clauses (currently just `= expr` for
838
+ // elements), these clauses must be guarded with <cond=…>.
839
+ //
840
+ // No annotation assignments are allowed after element and enum blocks, because
841
+ // these would conflict with the optional `;` after those blocks. To lower the
842
+ // impact with enums, the “final” annotation assignments are actually moved before
843
+ // the keyword `enum` (only allowed if not after `many`/`array of`).
844
+ //
845
+ // This rule is not used when the type expression is restricted: CDL-style cast in
846
+ // `select` items, `cast` function, `mixin` definition.
847
+
838
848
  typeExpression[ art ]
849
+ // TODO: really introduce <exitRule>
839
850
  :
840
851
  elementsBlock[ $art ]
841
852
  nullability[ $art ]?
842
853
  |
843
854
  ( typeRefOptArgs[ $art ] | typeTypeOf[ $art ] )
855
+ (<altRuleStart>)
844
856
  nullability[ $art ]?
845
- { this.docComment( $art ); } annoAssignStd[ $art ]*
846
- enumSymbolsBlock[ $art ]?
847
- nullabilityAndDefault[ $art ]?
857
+ // <setCondition=calcOrDefaultRestriction> // TODO TOOL: allow this
858
+ { this.calcOrDefaultRestriction(); }<always> // workaround
859
+ (
860
+ nullabilityAndDefault[ $art ]
861
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
862
+ |
863
+ enumSymbolsBlock[ $art ]
864
+ nullabilityAndDefault[ $art ]?
865
+ |
866
+ { this.docComment( $art ); } annoAssignStd[ $art ]+
867
+ ( enumSymbolsBlock[ $art ]
868
+ // <setCondition=calcOrDefaultRestriction> // TODO TOOL: allow this
869
+ { this.calcOrDefaultRestriction(); }<always> // workaround
870
+ nullabilityAndDefault[ $art ]?
871
+ )?
872
+ |
873
+ { this.docComment( $art ); }
874
+ )
848
875
  |
849
- LOCALIZED { $art.localized = this.valueWithLocation( true ); }
850
- typeRefOptArgs[ $art ]
851
- { this.docComment( $art ); } annoAssignStd[ $art ]*
876
+ LOCALIZED
877
+ { $art.localized = this.valueWithLocation( true ); }
878
+ typeRefOptArgs[ $art ] // TODO: why no TYPE OF ?
879
+ // <setCondition=calcOrDefaultRestriction> // TODO TOOL: allow this
880
+ { this.calcOrDefaultRestriction(); }<always> // workaround
852
881
  nullabilityAndDefault[ $art ]?
882
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
853
883
  |
854
884
  assoc=ASSOCIATION cardinality[ $art ]? TO card=ONE/MANY?
855
885
  target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
886
+ // final anno assignments allowed also with fkeys (also in ANTLR-based parser)
887
+ // <prepare=calcOrDefaultRestriction,arg=true> // TODO TOOL: allow this
888
+ { this.calcOrDefaultRestriction(false,true); }<always> // workaround
889
+
856
890
  ( ON cond=condition { $art.on = $cond; }
857
- | foreignKeysBlock[ $art ]? nullabilityAndDefault[ $art ]?
891
+ | foreignKeysBlock[ $art ]?
892
+ nullabilityAndDefault[ $art ]?
893
+ // scalar default - hm..., what about calc expression?
858
894
  )
895
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
859
896
  |
860
897
  assoc=COMPOSITION cardinality[ $art ]? OF card=ONE/MANY?
861
898
  (
862
899
  target=simplePath { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
900
+ // final anno assignments allowed also with fkeys (also in ANTLR-based parser) - TODO: really?
901
+ // <prepare=calcOrDefaultRestriction,arg=true> // TODO TOOL: allow this
902
+ { this.calcOrDefaultRestriction(false,true); }<always> // workaround
863
903
  ( ON cond=condition { $art.on = $cond; }
864
- | foreignKeysBlock[ $art ]? nullabilityAndDefault[ $art ]?
904
+ | foreignKeysBlock[ $art ]?
905
+ nullabilityAndDefault[ $art ]?
865
906
  )
907
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
866
908
  |
867
- // TODO: really do SyntaxOnly/GrammarAmbiguities/CompositionOfMany.cds ?
868
909
  { $target = {}; this.setAssocAndComposition( $art, $assoc, $card, $target ); }
869
910
  elementsBlock[ $target ]
870
911
  { $target.location = $target.elements[Symbol.for('cds.$location')]; }
@@ -879,9 +920,11 @@ typeExpression[ art ]
879
920
  |
880
921
  ( typeRefOptArgs[ $art.items ] | typeTypeOf[ $art.items ] )
881
922
  nullability[ $art.items ]?
882
- { this.docComment( $art.items ); } annoAssignStd[ $art.items ]*
883
- enumSymbolsBlock[ $art.items ]?
884
- nullability[ $art.items ]?
923
+ ( enumSymbolsBlock[ $art.items ]
924
+ nullability[ $art.items ]? // TODO: only with enum
925
+ |
926
+ { this.docComment( $art ); } annoAssignStd[ $art ]*
927
+ )
885
928
  )
886
929
  ;
887
930
 
@@ -1000,9 +1043,12 @@ targetCardinality[ card, atAlt = false ]
1000
1043
  nullabilityAndDefault[ art ]
1001
1044
  :
1002
1045
  nullability[ $art ]
1003
- ( DEFAULT expr=expression { $art.default = $expr; } )?
1046
+ ( <cond=calcOrDefaultRestriction> DEFAULT expr=expression
1047
+ { $art.default = $expr; }
1048
+ )?
1004
1049
  |
1005
- DEFAULT expr=expression { $art.default = $expr; }
1050
+ <cond=calcOrDefaultRestriction> DEFAULT expr=expression
1051
+ { $art.default = $expr; }
1006
1052
  nullability[ $art ]?
1007
1053
  // TODO TOOL: when `followUnion` does not contain `Id`, `RuleEnd_` does not
1008
1054
  // need to induce prediction (here for `default`).
@@ -1161,7 +1207,7 @@ tableOrQueryParens returns[ default expr ]
1161
1207
  { $expr = this.taggedIfQuery( $expr ); $expr.name = this.fragileAlias(); }
1162
1208
  |
1163
1209
  // <setCondition=setPrecInCallingRule> // TODO TOOL: allow this
1164
- { this.setPrecInCallingRule(); } // workaround
1210
+ { this.setPrecInCallingRule(); }<always> // workaround
1165
1211
  )
1166
1212
  ; // change #10799 for ANTLR-based parser
1167
1213
 
@@ -1194,10 +1240,15 @@ fromRefWithOptAlias returns[ default expr = { path: [] } ]
1194
1240
  } }
1195
1241
  fromPath[ $expr, 'ref']
1196
1242
  )?
1197
- ( AS Id['FromAlias'] { $expr.name = this.identAst(); }
1198
- | <hide, cond=tableAlias> Id_restricted['FromAlias']
1243
+ (
1244
+ AS Id['FromAlias'] { $expr.name = this.identAst(); }
1245
+ |
1246
+ <hide, cond=tableAlias>
1247
+ Id_restricted['FromAlias']
1199
1248
  { $expr.name = this.fragileAlias(); }
1200
- | { this.classifyImplicitName( $expr.scope ? 'FromElemImplicit' : 'FromImplicit', $expr ); }
1249
+ |
1250
+ <default=fallback>
1251
+ { this.classifyImplicitName( $expr.scope ? 'FromElemImplicit' : 'FromImplicit', $expr ); }
1201
1252
  )
1202
1253
  ;
1203
1254
 
@@ -1288,9 +1339,24 @@ excludingClause[ query ]
1288
1339
  { this.finalizeDictOrArray( $query.excludingDict ); }
1289
1340
  ;
1290
1341
 
1291
- selectItemsList[ query, clause = 'columns', start = undefined ]
1342
+ selectItemsList[ query, start = undefined ]
1343
+ :
1344
+ '{'<setCondition=notInExpandInline>
1345
+ { $query.columns = this.createArray( $start ); }
1346
+ (
1347
+ ( '*' { $query.columns.push( this.valueWithLocation() ); }
1348
+ | selectItemDef[ $query.columns ]
1349
+ )
1350
+ ( ',' | <exitLoop> )
1351
+ )*
1352
+ '}'<setCondition=afterBrace>
1353
+ { this.finalizeDictOrArray( $query.columns ); }
1354
+ ;
1355
+
1356
+ nestedSelectItemsList[ query, clause ]
1292
1357
  :
1293
- '{' { $query[$clause] = this.createArray( $start ); }
1358
+ '{'<setCondition=inExpandInline>
1359
+ { $query[$clause] = this.createArray(); }
1294
1360
  (
1295
1361
  ( '*' { $query[$clause].push( this.valueWithLocation() ); }
1296
1362
  | selectItemDef[ $query[$clause] ]
@@ -1306,32 +1372,34 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1306
1372
  :
1307
1373
  { $columns.push( $art ); } // TODO: probably too early
1308
1374
  { this.docComment( $art ); } annoAssignCol[ $art ]*
1309
- // TODO: <cond> for VIRTUAL/KEY only top-level
1310
- ( VIRTUAL { $art.virtual = this.valueWithLocation( true ); } )?
1311
- ( KEY { $art.key = this.valueWithLocation( true ); } )?
1375
+ ( <cond=notInExpandInline> VIRTUAL
1376
+ { $art.virtual = this.valueWithLocation( true ); } )?
1377
+ ( <cond=notInExpandInline> KEY
1378
+ { $art.key = this.valueWithLocation( true ); } )?
1312
1379
  (
1313
1380
  expr=expression { $art.value = $expr; }
1314
1381
  ( as=AS Id['ItemAlias'] { $art.name = this.identAst(); }
1315
1382
  | Id_restricted['ItemAlias'] { $art.name = this.fragileAlias( true ); }
1316
1383
  | { $alias = this.classifyImplicitName( 'ItemImplicit', $expr ); }
1317
1384
  )
1318
- // TODO: <cond> for expand/inline only with ref,
1385
+ // TODO: <cond> instead `reportExpandInline` for "expand/inline only w/ ref"
1319
1386
  (
1320
1387
  { this.reportExpandInline( $art, false ); }
1321
- selectItemsList[ $art, 'expand' ]
1388
+ nestedSelectItemsList[ $art, 'expand' ]
1322
1389
  excludingClause[ $art ]?
1323
1390
  |
1324
- '.' { this.reportExpandInline( $art, $as || true ); }
1325
- { if ($alias) $alias.token.parsed = $alias.parsed; }
1391
+ '.'
1392
+ { this.reportExpandInline( $art, $as || true ); }
1393
+ { if ($alias) $alias.token.parsedAs = $alias.parsedAs; }
1326
1394
  (
1327
- selectItemsList[ $art, 'inline' ]
1395
+ nestedSelectItemsList[ $art, 'inline' ]
1328
1396
  excludingClause[ $art ]?
1329
1397
  |
1330
1398
  '*' { $art.inline = [ this.valueWithLocation() ]; }
1331
1399
  )
1332
1400
  )?
1333
1401
  |
1334
- selectItemsList[ $art, 'expand' ]
1402
+ nestedSelectItemsList[ $art, 'expand' ]
1335
1403
  excludingClause[ $art ]?
1336
1404
  AS Id['ItemAlias'] { $art.name = this.identAst(); }
1337
1405
  )
@@ -1442,7 +1510,7 @@ expression returns[ default expr ]
1442
1510
  (
1443
1511
  Id_all['paramref']
1444
1512
  { $expr = { path: [ this.identAst() ], location: this.startLocation(), scope: 'param' }; }
1445
- ( <cond=isDotForPath> valuePath[ ...$ ]<atAltStart>
1513
+ ( valuePath[ ...$ ]<atAltStart>
1446
1514
  { $expr = this.valuePathAst( $expr ); }
1447
1515
  | { this.attachLocation( $expr ); }
1448
1516
  )
@@ -1597,6 +1665,7 @@ castFunction returns[ default expr = {} ]
1597
1665
  ;
1598
1666
 
1599
1667
  argumentsAndFilter[ pathStep ]
1668
+ // TODO: what about valuePath with EXISTS, after `:` etc (also in ANTLR-based parser)?
1600
1669
  options{ minTokensMatched = 1 }
1601
1670
  :
1602
1671
  (
@@ -1606,7 +1675,6 @@ options{ minTokensMatched = 1 }
1606
1675
  // TOOL? at least msg)
1607
1676
  (
1608
1677
  <default=fallback> // TODO TOOL: allow in loop
1609
- { ; } // TODO TOOL: we need an idle action here, for <default>
1610
1678
  (
1611
1679
  expr=funcExpression { $pathStep.args.push( $expr ); }
1612
1680
  (
@@ -1623,6 +1691,9 @@ options{ minTokensMatched = 1 }
1623
1691
  )?
1624
1692
  )?
1625
1693
  |
1694
+ // TODO: if we want perfect CC and error recovery, `isNamedArg` would work
1695
+ // like keyword prediction and also do something similar to the (future)
1696
+ // "identifier confirmation prediction" (consider argument `(par ‡ : 42)`
1626
1697
  <cond=isNamedArg> id=Id_all['paramname']
1627
1698
  (
1628
1699
  ':' { $pathStep.args = this.createDict( $open ); $pathStep.$syntax = ':'; }
@@ -1679,11 +1750,11 @@ funcExpression returns[ default expr ] locals[ args ]
1679
1750
  )*
1680
1751
  ;
1681
1752
 
1682
- GenericExpr // options { representingTokens = representingExpr; }
1753
+ GenericExpr
1683
1754
  : Id_all | '*' ;
1684
- GenericIntro // options { representingTokens = representingIntro; }
1755
+ GenericIntro
1685
1756
  : Id_all ;
1686
- GenericSeparator // options { representingTokens = representingSeparator; }
1757
+ GenericSeparator
1687
1758
  : Id_all ;
1688
1759
 
1689
1760
  overClause[ outer ] locals[ over = [] ]
@@ -1791,7 +1862,7 @@ literalValue returns[ default expr = {} ]
1791
1862
  annoAssignStd[ art ]
1792
1863
  @finally{ this.docComment( $art ); }
1793
1864
  :
1794
- '@'<setCondition=inSameLine> { this.reportUnexpectedSpace(); }
1865
+ '@'<setCondition=annoInSameLine> { this.reportUnexpectedSpace(); }
1795
1866
  ( annoAssignParen[ ...$ ]
1796
1867
  | annoAssignBase[ ...$ ]
1797
1868
  )
@@ -1809,16 +1880,16 @@ annoAssignCol[ art ]
1809
1880
  annoAssignMid[ art ]
1810
1881
  @finally{ this.docComment( $art ); }
1811
1882
  :
1812
- '@'<setCondition=inSameLine> { this.reportUnexpectedSpace(); }
1883
+ '@'<setCondition=annoInSameLine> { this.reportUnexpectedSpace(); }
1813
1884
  ( annoAssignParen[ ...$ ]
1814
- | name=annoNamePath
1885
+ | name=annoNamePath // !
1815
1886
  { this.assignAnnotation( $art, {}, $name ); this.warnIfColonFollows( $name ); }
1816
1887
  )
1817
1888
  ;
1818
1889
 
1819
1890
  annoAssignParen[ art ]
1820
1891
  :
1821
- '('<setCondition=inSameLine>
1892
+ '('<setCondition=annoInSameLine>
1822
1893
  ( annoAssignBase[ $art ]
1823
1894
  ( ',' | <exitLoop> )
1824
1895
  )*
@@ -1829,7 +1900,7 @@ annoAssignBase[ art ] locals[ value = {} ]
1829
1900
  @finally{ this.assignAnnotation( $art, $value, $name || {} ); }
1830
1901
  :
1831
1902
  name=annoNamePath
1832
- ( <cond=inSameLine> ':' value=annoValue )?
1903
+ ( <cond=annoInSameLine> ':' value=annoValue )?
1833
1904
  ;
1834
1905
 
1835
1906
  annoNamePath[ category = 'anno' ] returns[ default name = new XsnName() ]
@@ -1844,7 +1915,7 @@ annoNamePath[ category = 'anno' ] returns[ default name = new XsnName() ]
1844
1915
  Id_all[ $category ] { $name.path.push( this.identAstWithPrefix( $at ) ); }
1845
1916
  )
1846
1917
  )*
1847
- ( <cond=inSameLine> annoPathVariant[ $name ] )?
1918
+ ( <cond=annoInSameLine> annoPathVariant[ $name ] )?
1848
1919
  ;
1849
1920
 
1850
1921
  annoPath[ nameOrRef, category = 'annoref' ]
@@ -1878,7 +1949,7 @@ annoStructValue returns[ default value = {} ] locals[ name = new XsnName() ]
1878
1949
  @finally{ $value.name = $name; }
1879
1950
  :
1880
1951
  annoPath[ $name, 'name' ] { this.attachLocation( $name ); }
1881
- ( ':'<setCondition=annoTopValue> // make it inherit “non-top” status
1952
+ ( ':'
1882
1953
  $value=annoValue
1883
1954
  |
1884
1955
  { this.attachLocation( $value ); }
@@ -1895,13 +1966,15 @@ annoValue returns[ default value = {} ]
1895
1966
  |
1896
1967
  annoPath[ $value, 'annoref' ]
1897
1968
  |
1898
- '{'<setCondition=annoTopValue>
1969
+ '{'
1899
1970
  {
1900
- if (this.annoTopValue(true)) $value.$flatten = [];
1971
+ if (!this.dynamic_.arrayAnno) $value.$flatten = [];
1901
1972
  else { $value.struct = Object.create(null); $value.literal = 'struct'; }
1902
1973
  }
1903
1974
  (
1904
1975
  // TODO: complain about empty loop if top-level as before
1976
+ // TOOL TODO → allow `<cond=…> '}'` below where the condition rejects `}`
1977
+ // after `{` if top-level
1905
1978
  sub=annoStructValue
1906
1979
  {
1907
1980
  if ($value.$flatten) $value.$flatten.push( $sub );
@@ -1911,13 +1984,13 @@ annoValue returns[ default value = {} ]
1911
1984
  )*
1912
1985
  '}' // Do NOT use <setCondition=afterBrace> here!
1913
1986
  |
1914
- '['<setCondition=annoTopValue>
1987
+ '['<setCondition=ellipsisRestriction>
1915
1988
  { $value.val = []; $value.literal = 'array' }
1916
1989
  // no need for createArray() here, $value.location is set above
1917
1990
  (
1918
1991
  ( sub=annoValue { $value.val.push( $sub ) }
1919
1992
  |
1920
- ellipsis='...'
1993
+ <cond=ellipsisRestriction> ellipsis='...'
1921
1994
  ( UP TO upTo=annoValue | { $upTo = undefined; } )
1922
1995
  { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1923
1996
  )
@@ -42,9 +42,9 @@ class Token {
42
42
  text;
43
43
  keyword;
44
44
  location;
45
- parsed;
45
+ parsedAs;
46
46
  get isIdentifier() { // compatibility method
47
- return this.parsed !== 'keyword' && this.parsed !== 'token' && this.parsed;
47
+ return this.parsedAs !== 'keyword' && this.parsedAs !== 'token' && this.parsedAs;
48
48
  }
49
49
  get tokenIndex() {
50
50
  return this.location.tokenIndex;
@@ -92,7 +92,7 @@ class Lexer {
92
92
  endLine: line,
93
93
  endCol: col + text.length,
94
94
  // remark: end positions of multi-line tokens must be set by function
95
- tokenIndex: parser.tokens.length,
95
+ tokenIndex: parser.tokens.length + parser.docComments.length + parser.comments.length,
96
96
  };
97
97
  let keyword;
98
98
  if (typeof type !== 'function' ||
@@ -103,7 +103,7 @@ class Lexer {
103
103
  text,
104
104
  keyword,
105
105
  location: this.location,
106
- parsed: undefined,
106
+ parsedAs: undefined,
107
107
  } );
108
108
  }
109
109
  }
@@ -116,7 +116,7 @@ class Lexer {
116
116
  col: endCol,
117
117
  endLine: line,
118
118
  endCol,
119
- tokenIndex: parser.tokens.length,
119
+ tokenIndex: parser.tokens.length + parser.docComments.length + parser.comments.length,
120
120
  };
121
121
  parser.tokens.push( {
122
122
  __proto__: Token.prototype,
@@ -124,7 +124,7 @@ class Lexer {
124
124
  text: '',
125
125
  keyword: false,
126
126
  location,
127
- parsed: undefined,
127
+ parsedAs: undefined,
128
128
  } );
129
129
  }
130
130
  }
@@ -141,14 +141,24 @@ function comment( text, lexer, parser, beg ) {
141
141
  }
142
142
  else if (text === '/*' && lexer.input.charAt( rulesRegexp.lastIndex ) === '*' &&
143
143
  rulesRegexp.lastIndex + 2 < re.lastIndex) { // not just `/**/`
144
- lexer.location.tokenIndex = parser.docComments.length;
145
144
  parser.docComments.push( {
146
145
  __proto__: Token.prototype,
147
146
  type: 'DocComment',
148
147
  text: lexer.input.substring( beg, re.lastIndex ),
149
148
  keyword: false,
150
149
  location: lexer.location,
151
- parsed: undefined,
150
+ parsedAs: undefined,
151
+ } );
152
+ adaptEndLocation( lexer, re.lastIndex ); // also works after push ?
153
+ }
154
+ else { // TODO: only attach with option `attachTokens` ?
155
+ parser.comments.push( {
156
+ __proto__: Token.prototype,
157
+ type: 'Comment',
158
+ text: lexer.input.substring( beg, re.lastIndex ),
159
+ keyword: false,
160
+ location: lexer.location,
161
+ parsedAs: undefined,
152
162
  } );
153
163
  adaptEndLocation( lexer, re.lastIndex ); // also works after push ?
154
164
  }
@@ -187,7 +197,7 @@ function string( text, lexer, parser, beg ) {
187
197
  multi: 'The multi-line string literal must end with $(NEWCODE)',
188
198
  } );
189
199
  keyword = 0;
190
- // TODO: set parsed to 0 → no further error if string is not expected?
200
+ // TODO: set parsedAs to 0 → no further error if string is not expected?
191
201
  prefix = null; // no combination with date/time/…
192
202
  }
193
203
  adaptEndLocation( lexer, (rulesRegexp.lastIndex = lastIndex || lexer.input.length) );
@@ -221,7 +231,7 @@ function ident( text, lexer, parser, beg ) {
221
231
  ident: 'The delimited id must end with $(NEWCODE) before the end of line',
222
232
  } );
223
233
  keyword = 0;
224
- // TODO: set parsed to 0 → no further error if string is not expected?
234
+ // TODO: set parsedAs to 0 → no further error if string is not expected?
225
235
  }
226
236
  adaptEndLocation( lexer, (rulesRegexp.lastIndex = lastIndex || lexer.input.length) );
227
237
  return [ 'Id', lexer.input.substring( beg, rulesRegexp.lastIndex ), keyword ];