@sap/cds-compiler 2.11.4 → 2.12.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 (80) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +9 -10
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/doc/CHANGELOG_BETA.md +12 -0
  6. package/lib/api/main.js +2 -0
  7. package/lib/api/options.js +2 -2
  8. package/lib/base/message-registry.js +31 -2
  9. package/lib/base/model.js +1 -0
  10. package/lib/base/optionProcessorHelper.js +97 -69
  11. package/lib/checks/.eslintrc.json +2 -0
  12. package/lib/checks/actionsFunctions.js +2 -1
  13. package/lib/checks/foreignKeys.js +4 -4
  14. package/lib/checks/managedInType.js +4 -4
  15. package/lib/checks/queryNoDbArtifacts.js +1 -3
  16. package/lib/checks/sql-snippets.js +93 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +5 -3
  19. package/lib/compiler/base.js +0 -1
  20. package/lib/compiler/checks.js +32 -9
  21. package/lib/compiler/definer.js +25 -4
  22. package/lib/compiler/index.js +1 -1
  23. package/lib/compiler/propagator.js +3 -2
  24. package/lib/compiler/resolver.js +97 -6
  25. package/lib/compiler/shared.js +12 -1
  26. package/lib/compiler/utils.js +7 -0
  27. package/lib/edm/annotations/genericTranslation.js +34 -17
  28. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  29. package/lib/edm/csn2edm.js +1 -1
  30. package/lib/edm/edm.js +8 -8
  31. package/lib/edm/edmPreprocessor.js +30 -23
  32. package/lib/edm/edmUtils.js +11 -12
  33. package/lib/gen/Dictionary.json +82 -40
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +3 -1
  36. package/lib/gen/language.tokens +15 -14
  37. package/lib/gen/languageLexer.interp +9 -1
  38. package/lib/gen/languageLexer.js +830 -779
  39. package/lib/gen/languageLexer.tokens +7 -6
  40. package/lib/gen/languageParser.js +2401 -2282
  41. package/lib/json/from-csn.js +47 -16
  42. package/lib/json/to-csn.js +17 -5
  43. package/lib/language/antlrParser.js +3 -3
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/genericAntlrParser.js +68 -51
  46. package/lib/language/language.g4 +128 -74
  47. package/lib/language/multiLineStringParser.js +536 -0
  48. package/lib/main.d.ts +5 -3
  49. package/lib/main.js +3 -2
  50. package/lib/model/csnRefs.js +116 -68
  51. package/lib/model/csnUtils.js +40 -48
  52. package/lib/model/enrichCsn.js +30 -14
  53. package/lib/optionProcessor.js +3 -3
  54. package/lib/render/DuplicateChecker.js +1 -1
  55. package/lib/render/manageConstraints.js +1 -1
  56. package/lib/render/toCdl.js +193 -79
  57. package/lib/render/toHdbcds.js +179 -95
  58. package/lib/render/toRename.js +7 -10
  59. package/lib/render/toSql.js +57 -40
  60. package/lib/render/utils/common.js +24 -5
  61. package/lib/render/utils/sql.js +6 -4
  62. package/lib/transform/braceExpression.js +4 -2
  63. package/lib/transform/db/associations.js +389 -0
  64. package/lib/transform/db/cdsPersistence.js +150 -0
  65. package/lib/transform/db/constraints.js +6 -4
  66. package/lib/transform/db/draft.js +3 -2
  67. package/lib/transform/db/expansion.js +4 -5
  68. package/lib/transform/db/flattening.js +5 -6
  69. package/lib/transform/db/temporal.js +236 -0
  70. package/lib/transform/db/transformExists.js +36 -23
  71. package/lib/transform/forHanaNew.js +35 -626
  72. package/lib/transform/forOdataNew.js +5 -4
  73. package/lib/transform/localized.js +3 -14
  74. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  75. package/lib/transform/transformUtilsNew.js +13 -13
  76. package/lib/transform/translateAssocsToJoins.js +8 -8
  77. package/lib/transform/universalCsnEnricher.js +217 -47
  78. package/lib/utils/file.js +2 -1
  79. package/lib/utils/timetrace.js +8 -2
  80. package/package.json +1 -1
@@ -490,7 +490,7 @@ projectionSpec returns[ query ] locals[ src ]
490
490
  // now a simplified `tableTerm`:
491
491
  {
492
492
  $src = { path: [], scope: 0 };
493
- $query = { op: this.tokenLocation( $proj, undefined, 'SELECT' ), from: $src, location: this.startLocation() };
493
+ $query = { op: this.valueWithTokenLocation( 'SELECT', $proj ), from: $src, location: this.startLocation() };
494
494
  }
495
495
  fromPath[ $src, 'artref']
496
496
  ( ':'
@@ -983,6 +983,7 @@ mixinElementDef[ outer ] locals[ art ]
983
983
 
984
984
  misplacedAnnotations[ annos, messageId ]
985
985
  :
986
+ // No docComment() here
986
987
  annotationAssignment_ll1[ $annos ]+
987
988
  { if ($messageId) // issue specified in central registry
988
989
  this.message( messageId, this.tokenLocation( $ctx.start, this.getCurrentToken() ) );
@@ -1078,7 +1079,7 @@ elementDefInner[ outer, loc, annos, allowEq ] returns[ art ]
1078
1079
  annotationAssignment_ll1[ $annos ]*
1079
1080
  requiredSemi // also req after foreign key spec
1080
1081
  |
1081
- l=LOCALIZED { $art.localized = this.tokenLocation( $l, undefined, true ); }
1082
+ l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
1082
1083
  typeRefOptArgs[ $art ]
1083
1084
  { this.docComment( $annos ); }
1084
1085
  annotationAssignment_ll1[ $annos ]*
@@ -1146,7 +1147,7 @@ selectItemDef[ outer ] locals[ annos = [] ]
1146
1147
  @after{ if ($ctx.art) this.attachLocation($art.art); }
1147
1148
  :
1148
1149
  star='*'
1149
- { $outer.push( this.tokenLocation( $star, undefined, '*' ) ); }
1150
+ { $outer.push( this.valueWithTokenLocation( '*', $star ) ); }
1150
1151
  |
1151
1152
  { this.docComment( $annos ); }
1152
1153
  annotationAssignment_atn[ $annos ]*
@@ -1156,8 +1157,8 @@ selectItemDef[ outer ] locals[ annos = [] ]
1156
1157
  key=KEY?
1157
1158
  art=selectItemDefBody[ $outer, $annos ]
1158
1159
  {
1159
- if ($virtual) $art.art.virtual = this.tokenLocation( $virtual, undefined, true );
1160
- if ($key) $art.art.key = this.tokenLocation( $key, undefined, true );
1160
+ if ($virtual) $art.art.virtual = this.valueWithTokenLocation( true, $virtual );
1161
+ if ($key) $art.art.key = this.valueWithTokenLocation( true, $key );
1161
1162
  }
1162
1163
  ;
1163
1164
 
@@ -1190,7 +1191,7 @@ selectItemDefBody[ outer, annos ] returns[ art = {} ]
1190
1191
  excludingClause[ $art ]?
1191
1192
  |
1192
1193
  star='*'
1193
- { $art.inline = [ this.tokenLocation( $star, undefined, '*' ) ]; }
1194
+ { $art.inline = [ this.valueWithTokenLocation( '*', $star ) ]; }
1194
1195
  )
1195
1196
  )?
1196
1197
  |
@@ -1239,7 +1240,7 @@ selectItemInlineDef[ outer ] locals[ annos = [] ]
1239
1240
  @after{ if ($ctx.art) this.attachLocation($art.art); }
1240
1241
  :
1241
1242
  star='*'
1242
- { $outer.push( this.tokenLocation( $star, undefined, '*' ) ); }
1243
+ { $outer.push( this.valueWithTokenLocation( '*', $star ) ); }
1243
1244
  |
1244
1245
  { this.docComment( $annos ); }
1245
1246
  annotationAssignment_atn[ $annos ]*
@@ -1300,15 +1301,17 @@ entityParameterDef[ outer ] locals[ art, annos = [] ]
1300
1301
  annotationAssignment_fix[ $annos ]*
1301
1302
  typeSpec[ $art ]
1302
1303
  ( DEFAULT expr=expression { $art.default = $expr.expr; } )?
1304
+ { this.docComment( $annos ); }
1305
+ annotationAssignment_ll1[ $annos ]*
1303
1306
  ;
1304
1307
 
1305
1308
  nullability[ art ]
1306
1309
  :
1307
1310
  not=NOT n1=NULL
1308
- { $art.notNull = this.tokenLocation($not,$n1,true); }
1311
+ { $art.notNull = this.valueWithTokenLocation( true, $not, $n1 ); }
1309
1312
  |
1310
1313
  n2=NULL
1311
- { $art.notNull = this.tokenLocation($n2,undefined,false); }
1314
+ { $art.notNull = this.valueWithTokenLocation( false, $n2 ); }
1312
1315
  ;
1313
1316
 
1314
1317
  elementProperties[ elem ]
@@ -1465,7 +1468,7 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1465
1468
  { this.docComment( $annos ); }
1466
1469
  annotationAssignment_ll1[ $annos ]* requiredSemi
1467
1470
  |
1468
- l=LOCALIZED { $art.localized = this.tokenLocation( $l, undefined, true ); }
1471
+ l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
1469
1472
  typeRefOptArgs[ $art ]
1470
1473
  { this.docComment( $annos ); }
1471
1474
  annotationAssignment_ll1[ $annos ]*
@@ -1769,21 +1772,21 @@ orderByClause[ inQuery ] returns [ query ]
1769
1772
 
1770
1773
  overOrderByClause returns [ expr ]
1771
1774
  :
1772
- o=ORDER b=BY { $expr = { op: this.tokenLocation($o, $b, 'orderBy' ) , args: [] }}
1775
+ o=ORDER b=BY { $expr = { op: this.valueWithTokenLocation( 'orderBy', $o, $b ) , args: [] }}
1773
1776
  ob1=orderBySpec { $expr.args.push( $ob1.ob ); }
1774
1777
  ( ',' obn=orderBySpec { $expr.args.push( $obn.ob ); } )*
1775
1778
  ;
1776
1779
 
1777
1780
  partitionByClause returns [ expr ]
1778
1781
  :
1779
- p=PARTITION b=BY { $expr = { op: this.tokenLocation($p, $b, 'partitionBy' ) , args: [] }}
1782
+ p=PARTITION b=BY { $expr = { op: this.valueWithTokenLocation( 'partitionBy', $p, $b ) , args: [] }}
1780
1783
  e1=expression { $expr.args.push( $e1.expr ); }
1781
1784
  ( ',' en=expression { $expr.args.push( $en.expr ); } )*
1782
1785
  ;
1783
1786
 
1784
1787
  windowFrameClause returns [ wf ]
1785
1788
  :
1786
- r=ROWS { $wf = { op: this.tokenLocation($r, null, 'rows' ) , args: [] }}
1789
+ r=ROWS { $wf = { op: this.valueWithTokenLocation( 'rows', $r ) , args: [] }}
1787
1790
  wfe=windowFrameExtentSpec { $wf.args.push( $wfe.wfe ); }
1788
1791
  ;
1789
1792
 
@@ -1793,7 +1796,7 @@ windowFrameExtentSpec returns[ wfe ]
1793
1796
  windowFrameStartSpec [ $wfe ]
1794
1797
  |
1795
1798
  b=BETWEEN
1796
- { $wfe = { op: this.tokenLocation( $b, null, 'frameBetween' ), args: [] } }
1799
+ { $wfe = { op: this.valueWithTokenLocation( 'frameBetween', $b ), args: [] } }
1797
1800
  wfb1=windowFrameBoundSpec { $wfe.args.push( $wfb1.wfb ); }
1798
1801
  AND
1799
1802
  wfb2=windowFrameBoundSpec { $wfe.args.push( $wfb2.wfb ); }
@@ -1805,12 +1808,12 @@ windowFrameBoundSpec returns [ wfb ]
1805
1808
  // #ATN: Not ll1 because `UNBOUNDED` could also be part of the windowFrameStartSpec
1806
1809
  // `UNBOUNDED` would then be immediately followed by `PRECEDING`
1807
1810
  u=UNBOUNDED f=FOLLOWING
1808
- { $wfb = { op: this.tokenLocation($u, $f, 'unboundedFollowing' ), args: []} }
1811
+ { $wfb = { op: this.valueWithTokenLocation( 'unboundedFollowing', $u, $f ), args: []} }
1809
1812
  |
1810
1813
  // #ATN: Not ll1 because `Number` could also be part of the windowFrameStartSpec
1811
1814
  // `Number` would then be immediately followed by `PRECEDING`
1812
1815
  n=Number f=FOLLOWING
1813
- { $wfb = { op: this.tokenLocation($n, $f, 'following' ), args: [ this.numberLiteral( $n ) ]} }
1816
+ { $wfb = { op: this.valueWithTokenLocation( 'following', $n, $f ), args: [ this.numberLiteral( $n ) ]} }
1814
1817
  |
1815
1818
  { $wfb = {} }
1816
1819
  windowFrameStartSpec [ $wfb ]
@@ -1820,19 +1823,19 @@ windowFrameStartSpec [ wf ]
1820
1823
  :
1821
1824
  u=UNBOUNDED p=PRECEDING
1822
1825
  {
1823
- $wf.op = this.tokenLocation($u, $p, 'unboundedPreceding' );
1826
+ $wf.op = this.valueWithTokenLocation( 'unboundedPreceding', $u, $p );
1824
1827
  $wf.args = [];
1825
1828
  }
1826
1829
  |
1827
1830
  n=Number p=PRECEDING
1828
1831
  {
1829
- $wf.op = this.tokenLocation($p, null, 'preceding' );
1832
+ $wf.op = this.valueWithTokenLocation( 'preceding', $p );
1830
1833
  $wf.args = [ this.numberLiteral( $n ) ];
1831
1834
  }
1832
1835
  |
1833
1836
  c=CURRENT r=ROW
1834
1837
  {
1835
- $wf.op = this.tokenLocation($c, $r, 'currentRow' );
1838
+ $wf.op = this.valueWithTokenLocation( 'currentRow', $c, $r );
1836
1839
  $wf.args = [];
1837
1840
  }
1838
1841
  ;
@@ -1840,7 +1843,7 @@ windowFrameStartSpec [ wf ]
1840
1843
  overClause returns [ over ]
1841
1844
  @after { this.attachLocation($over); }
1842
1845
  :
1843
- o=OVER { $over = { op: this.tokenLocation( $o, null, 'over' ) , args: [] } }
1846
+ o=OVER { $over = { op: this.valueWithTokenLocation( 'over', $o ) , args: [] } }
1844
1847
  '('
1845
1848
  ( pb=partitionByClause { $over.args.push( $pb.expr ); } )?
1846
1849
  ( ob=overOrderByClause { $over.args.push( $ob.expr ); } )?
@@ -1861,11 +1864,11 @@ limitClause[ inQuery ] returns [ query ]
1861
1864
  orderBySpec returns[ ob ]
1862
1865
  :
1863
1866
  e=expression { $ob = $e.expr; }
1864
- ( asc=ASC { $ob.sort = this.tokenLocation( $asc, undefined, 'asc' ); }
1865
- | desc=DESC { $ob.sort = this.tokenLocation( $desc, undefined, 'desc' ); }
1867
+ ( asc=ASC { $ob.sort = this.valueWithTokenLocation( 'asc', $asc ); }
1868
+ | desc=DESC { $ob.sort = this.valueWithTokenLocation( 'desc', $desc ); }
1866
1869
  )?
1867
1870
  ( nb=NULLS ne=( FIRST | LAST )
1868
- { $ob.nulls = this.tokenLocation( $nb, $ne, $ne.text.toLowerCase() ); }
1871
+ { $ob.nulls = this.valueWithTokenLocation( $ne.text.toLowerCase(), $nb, $ne ); }
1869
1872
  )?
1870
1873
  ;
1871
1874
 
@@ -1888,7 +1891,7 @@ queryPrimary returns[ query = {} ]
1888
1891
  { $query = this.surroundByParens( $qe.query, $open, $close ); }
1889
1892
  |
1890
1893
  select=SELECT
1891
- { $query = { op: this.tokenLocation( $select, undefined, 'SELECT' ), location: this.startLocation() }; }
1894
+ { $query = { op: this.valueWithTokenLocation( 'SELECT', $select ), location: this.startLocation() }; }
1892
1895
  (
1893
1896
  FROM querySource[ $query ]
1894
1897
  (
@@ -1897,13 +1900,13 @@ queryPrimary returns[ query = {} ]
1897
1900
  '}' INTO
1898
1901
  )?
1899
1902
  ( ad=( ALL | DISTINCT ) // TODO: or directly after SELECT ?
1900
- { $query.quantifier = this.tokenLocation( $ad, undefined, $ad.text.toLowerCase() ); }
1903
+ { $query.quantifier = this.valueWithTokenLocation( $ad.text.toLowerCase(), $ad ); }
1901
1904
  )?
1902
1905
  bracedSelectItemListDef[ $query ]?
1903
1906
  excludingClause[ $query ]?
1904
1907
  |
1905
1908
  ( ad=( ALL | DISTINCT ) // TODO: or directly after SELECT ?
1906
- { $query.quantifier = this.tokenLocation( $ad, undefined, $ad.text.toLowerCase() ); }
1909
+ { $query.quantifier = this.valueWithTokenLocation( $ad.text.toLowerCase(), $ad ); }
1907
1910
  )?
1908
1911
  { $query.columns = []; } // set it early to avoid "wildcard" errors
1909
1912
  selectItemDef[ $query.columns ]
@@ -1957,8 +1960,8 @@ joinOp[ left ] returns[ table ] locals [ join ]
1957
1960
  | t1=RIGHT t2=OUTER? c=joinCardinality? op=JOIN { $join = 'right' }
1958
1961
  | t1=FULL t2=OUTER? c=joinCardinality? op=JOIN { $join = 'full' }
1959
1962
  )
1960
- { $table = { op: this.tokenLocation( $op, undefined, 'join' ),
1961
- join: this.tokenLocation( $t1 || $op, $t2, $join ),
1963
+ { $table = { op: this.valueWithTokenLocation( 'join', $op ),
1964
+ join: this.valueWithTokenLocation( $join, $t1 || $op, $t2 ),
1962
1965
  args: ($left ? [$left] : []),
1963
1966
  location: $left && $left.location };
1964
1967
  if ($ctx.c) $table.cardinality = $c.joinCard; }
@@ -2065,24 +2068,24 @@ fromPath[ qp, idkind ]
2065
2068
 
2066
2069
  condition returns [ cond ] locals [ args = [], orl = [] ]
2067
2070
  @after{
2068
- $cond = ($args.length == 1)
2071
+ $cond = ($args.length === 1)
2069
2072
  ? this.attachLocation( $args[0] )
2070
2073
  : this.attachLocation({ op: $orl[0], args: $args });
2071
2074
  }
2072
2075
  :
2073
2076
  c1=conditionAnd { $args.push($c1.cond); }
2074
- ( or=OR c2=conditionAnd { $args.push($c2.cond); $orl.push(this.tokenLocation( $or, undefined, 'or' ))} )*
2077
+ ( or=OR c2=conditionAnd { $args.push($c2.cond); $orl.push(this.valueWithTokenLocation( 'or', $or ))} )*
2075
2078
  ;
2076
2079
 
2077
2080
  conditionAnd returns [ cond ] locals [ args = [], andl = [] ]
2078
2081
  @after{
2079
- $cond = ($args.length == 1)
2082
+ $cond = ($args.length === 1)
2080
2083
  ? $args[0]
2081
2084
  : this.attachLocation({ op: $andl[0], args: $args });
2082
2085
  }
2083
2086
  :
2084
2087
  c1=conditionTerm { $args.push($c1.cond); }
2085
- ( and=AND c2=conditionTerm { $args.push($c2.cond); $andl.push(this.tokenLocation( $and, undefined, 'and' )) } )*
2088
+ ( and=AND c2=conditionTerm { $args.push($c2.cond); $andl.push(this.valueWithTokenLocation( 'and', $and )) } )*
2086
2089
  ;
2087
2090
 
2088
2091
  conditionTerm returns [ cond ]
@@ -2091,38 +2094,38 @@ conditionTerm returns [ cond ]
2091
2094
  }
2092
2095
  :
2093
2096
  nt=NOT ct=conditionTerm
2094
- { $cond = { op: this.tokenLocation( $nt, undefined, 'not' ), args: [ $ct.cond ] }; }
2097
+ { $cond = { op: this.valueWithTokenLocation( 'not', $nt ), args: [ $ct.cond ] }; }
2095
2098
  |
2096
2099
  ex=EXISTS
2097
2100
  (
2098
2101
  open='(' qe=queryExpression close=')'
2099
- { $cond = { op: this.tokenLocation( $ex, undefined, 'exists' ),
2102
+ { $cond = { op: this.valueWithTokenLocation( 'exists', $ex ),
2100
2103
  args: [ this.surroundByParens( $qe.query, $open, $close, true ) ] }; }
2101
2104
  |
2102
2105
  qm=( HideAlternatives | '?' )
2103
- { $cond = { op: this.tokenLocation( $ex, undefined, 'exists' ), args: [
2104
- { param: this.tokenLocation( $qm, undefined, '?' ), scope: 'param' }
2106
+ { $cond = { op: this.valueWithTokenLocation( 'exists', $ex ), args: [
2107
+ { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' }
2105
2108
  ] };
2106
2109
  this.csnParseOnly( 'Dynamic parameter "?" is not supported', $qm );
2107
2110
  }
2108
2111
  |
2109
2112
  ep=valuePath[ 'ref' ]
2110
2113
  { $ep.qp['$'+'expected'] = 'exists';
2111
- $cond = { op: this.tokenLocation( $ex, undefined, 'exists' ), args: [ $ep.qp ] };
2114
+ $cond = { op: this.valueWithTokenLocation( 'exists', $ex ), args: [ $ep.qp ] };
2112
2115
  }
2113
2116
  )
2114
2117
  |
2115
2118
  expr=expression // see @after
2116
2119
  (
2117
2120
  rel=( '=' | '<>' | '>' | '>=' | '<' | '<=' | '!=' )
2118
- { $cond = { op: this.tokenLocation( $rel, undefined, $rel.text), args: [ $expr.expr ] }; }
2121
+ { $cond = { op: this.valueWithTokenLocation( $rel.text, $rel ), args: [ $expr.expr ] }; }
2119
2122
  ( asa=( ANY | SOME | ALL )
2120
- { $cond.quantifier = this.tokenLocation($asa, undefined, $asa.text.toLowerCase()); }
2123
+ { $cond.quantifier = this.valueWithTokenLocation( $asa.text.toLowerCase(), $asa ); }
2121
2124
  )?
2122
2125
  e2=expression { $cond.args.push($e2.expr); }
2123
2126
  |
2124
2127
  IS ( inn=NOT NULL | innu=NULL )
2125
- { $cond = { op: $inn ? this.tokenLocation( $inn, undefined, 'isNotNull' ) : this.tokenLocation( $innu, undefined, 'isNull' ), args: [ $expr.expr ] }; }
2128
+ { $cond = { op: $inn ? this.valueWithTokenLocation( 'isNotNull', $inn ) : this.valueWithTokenLocation( 'isNull', $innu ), args: [ $expr.expr ] }; }
2126
2129
  |
2127
2130
  { $cond = { args: [ $expr.expr ] }; }
2128
2131
  NOT predicate[ $cond, true ]
@@ -2138,14 +2141,14 @@ predicate[ cond, negated ]
2138
2141
  // NOT (a BETWEEN b AND c)
2139
2142
  :
2140
2143
  ino=IN e1=expression // including ExpressionList
2141
- { $cond.op = this.tokenLocation( $ino, undefined, (negated) ? 'notIn' : 'in'); $cond.args.push( $e1.expr ); }
2144
+ { $cond.op = this.valueWithTokenLocation( (negated) ? 'notIn' : 'in', $ino ); $cond.args.push( $e1.expr ); }
2142
2145
  |
2143
2146
  bw=BETWEEN e2=expression
2144
- { $cond.op = this.tokenLocation( $bw, undefined, (negated) ? 'notBetween' : 'between' ); $cond.args.push( $e2.expr ); }
2147
+ { $cond.op = this.valueWithTokenLocation( (negated) ? 'notBetween' : 'between', $bw ); $cond.args.push( $e2.expr ); }
2145
2148
  AND e3=expression { $cond.args.push( $e3.expr ); }
2146
2149
  |
2147
2150
  lk=LIKE e4=expression
2148
- { $cond.op = this.tokenLocation( $lk, undefined, (negated) ? 'notLike' : 'like' ); $cond.args.push( $e4.expr ); }
2151
+ { $cond.op = this.valueWithTokenLocation( (negated) ? 'notLike' : 'like', $lk ); $cond.args.push( $e4.expr ); }
2149
2152
  ( ESCAPE e5=expression { $cond.args.push( $e5.expr ); } )?
2150
2153
  ;
2151
2154
 
@@ -2157,7 +2160,7 @@ expression returns [ expr ]
2157
2160
  or='||' e2=expressionSum
2158
2161
  {
2159
2162
  $expr = {
2160
- op: this.tokenLocation( $or, undefined, '||' ), args: [$expr, $e2.expr],
2163
+ op: this.valueWithTokenLocation( '||', $or ), args: [$expr, $e2.expr],
2161
2164
  location: this.combinedLocation( $expr, $e2.expr ) };
2162
2165
  }
2163
2166
  )*
@@ -2171,7 +2174,7 @@ expressionSum returns [ expr ]
2171
2174
  op=( '+' | '-' ) e2=expressionFactor
2172
2175
  {
2173
2176
  $expr = {
2174
- op: this.tokenLocation($op, undefined, $op.text), args: [$expr, $e2.expr],
2177
+ op: this.valueWithTokenLocation( $op.text, $op ), args: [$expr, $e2.expr],
2175
2178
  location: this.combinedLocation( $expr, $e2.expr ) };
2176
2179
  }
2177
2180
  )*
@@ -2185,7 +2188,7 @@ expressionFactor returns [ expr ]
2185
2188
  op=( '*' | '/' ) e2=expressionTerm
2186
2189
  {
2187
2190
  $expr = {
2188
- op: this.tokenLocation($op, undefined, $op.text), args: [$expr, $e2.expr],
2191
+ op: this.valueWithTokenLocation( $op.text, $op ), args: [$expr, $e2.expr],
2189
2192
  location: this.combinedLocation( $expr, $e2.expr ) };
2190
2193
  }
2191
2194
  )*
@@ -2205,7 +2208,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2205
2208
  { $expr = $sf.ret; }
2206
2209
  |
2207
2210
  ca=CASE
2208
- { $expr = { op : this.tokenLocation( $ca, undefined, 'case' ), args: [] }; }
2211
+ { $expr = { op : this.valueWithTokenLocation( 'case', $ca ), args: [] }; }
2209
2212
  (
2210
2213
  e2=expression { $expr.args.push($e2.expr); }
2211
2214
  ( ow=WHEN ew=expression THEN e3=expression
@@ -2223,7 +2226,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2223
2226
  |
2224
2227
  ne=NEW nqp=valuePath[ 'ref', null] // token rewrite for NEW
2225
2228
  // please note: there will be no compiler-supported code completion after NEW
2226
- { $expr = { op: this.tokenLocation( $ne, undefined, 'new' ), args: [] };
2229
+ { $expr = { op: this.valueWithTokenLocation( 'new', $ne ), args: [] };
2227
2230
  this.notSupportedYet( $ne ); }
2228
2231
  |
2229
2232
  vp=valuePath[ 'ref', null ] { $expr = this.valuePathAst( $vp.qp ); }
@@ -2242,7 +2245,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2242
2245
  )
2243
2246
  |
2244
2247
  qm=( HideAlternatives | '?' )
2245
- { $expr = { param: this.tokenLocation( $qm, undefined, '?' ), scope: 'param' };
2248
+ { $expr = { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' };
2246
2249
  this.csnParseOnly( 'Dynamic parameter "?" is not supported', $qm );
2247
2250
  }
2248
2251
  |
@@ -2260,7 +2263,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2260
2263
  close=')'
2261
2264
  {
2262
2265
  if ($expr.length > 1)
2263
- $expr = { op: this.tokenLocation( $open, undefined, ',' ), args: $expr };
2266
+ $expr = { op: this.valueWithTokenLocation( ',', $open ), args: $expr };
2264
2267
  else if ($expr[0]) // can be `null` if condition failed to parse
2265
2268
  $expr = this.surroundByParens( $expr[0], $open, $close );
2266
2269
  }
@@ -2300,7 +2303,7 @@ specialFunction returns [ ret = { } ] locals[ art = {} ]
2300
2303
  ca=CAST open='('
2301
2304
  {
2302
2305
  $ret = {
2303
- op: this.tokenLocation( $ca, undefined, 'cast' ),
2306
+ op: this.valueWithTokenLocation( 'cast', $ca ),
2304
2307
  args: [ ],
2305
2308
  location: this.tokenLocation( $ca )
2306
2309
  };
@@ -2370,15 +2373,15 @@ pathArguments[ pathStep, considerSpecial ]
2370
2373
  funcExpression[ $pathStep, $considerSpecial ]
2371
2374
  )*
2372
2375
  |
2373
- a=ALL { $pathStep.quantifier = this.tokenLocation( $a, undefined, 'all' ); }
2376
+ a=ALL { $pathStep.quantifier = this.valueWithTokenLocation( 'all', $a ); }
2374
2377
  e1=expression { $pathStep.args = [ $e1.expr ]; }
2375
2378
  |
2376
- d=DISTINCT { $pathStep.quantifier = this.tokenLocation( $d, undefined, 'distinct' ); }
2379
+ d=DISTINCT { $pathStep.quantifier = this.valueWithTokenLocation( 'distinct', $d ); }
2377
2380
  e1=expression { $pathStep.args = [ $e1.expr ]; }
2378
2381
  ( ',' e2=expression { $pathStep.args.push( $e2.expr ); } )*
2379
2382
  |
2380
2383
  star='*'
2381
- { $pathStep.args = [ { location: this.tokenLocation($star), val: '*', literal: 'token' } ]; }
2384
+ { $pathStep.args = [ { location: this.tokenLocation( $star ), val: '*', literal: 'token' } ]; }
2382
2385
  |
2383
2386
  { $pathStep.args = []; }
2384
2387
  )
@@ -2452,7 +2455,7 @@ optionalWhereForFilter
2452
2455
 
2453
2456
  // Simple paths and values ---------------------------------------------------
2454
2457
 
2455
- annoValueBase returns[ val ] locals [ hasEllipsis=0 ]
2458
+ annoValueBase returns[ val ] locals [ seenEllipsis = false ]
2456
2459
  @after { this.attachLocation($val); }
2457
2460
  :
2458
2461
  { $val = { literal: 'struct', location: this.startLocation() }; }
@@ -2472,30 +2475,43 @@ annoValueBase returns[ val ] locals [ hasEllipsis=0 ]
2472
2475
  '['
2473
2476
  (
2474
2477
  (
2475
- head=arrayValue { $val.val.push( $head.val ); }
2476
- |
2477
- e='...'
2478
- {
2479
- $val.val.push( { literal: 'token', val: '...', location: this.tokenLocation($e) } );
2480
- $hasEllipsis++;
2481
- }
2478
+ head=arrayValue { $val.val.push( $head.val ); }
2479
+ |
2480
+ e='...' ( UP TO upTo=arrayValue )?
2481
+ {{
2482
+ const item = { literal: 'token', val: '...', location: this.tokenLocation($e) };
2483
+ $val.val.push( item );
2484
+ if ($ctx.upTo) item.upTo = $upTo.val;
2485
+ $seenEllipsis = !$ctx.upTo || 'upTo';
2486
+ }}
2482
2487
  )
2483
2488
  (
2484
2489
  ',' { if (this.isStraightBefore(']')) break; } // allow ',' before ']'
2485
2490
  (
2486
- tail=arrayValue { $val.val.push( $tail.val ); }
2487
- |
2488
- e='...'
2489
- {
2490
- $val.val.push( { literal: 'token', val: '...', location: this.tokenLocation($e) } );
2491
- if(++$hasEllipsis > 1)
2492
- this.error( 'syntax-unexpected-ellipsis', $e, { code: '...' },
2493
- 'Expected no more than one $(CODE)' );
2494
- }
2491
+ tail=arrayValue { $val.val.push( $tail.val ); }
2492
+ |
2493
+ { $ctx.upTo = null; } // is not reset
2494
+ e='...' ( UP TO upTo=arrayValue )?
2495
+ {{
2496
+ const item = { literal: 'token', val: '...', location: this.tokenLocation($e) };
2497
+ if ($ctx.upTo) item.upTo = $upTo.val;
2498
+ $val.val.push( item );
2499
+ if ($seenEllipsis === true) // TODO: adapt msg to UP TO
2500
+ this.error( 'syntax-unexpected-ellipsis', $e, { code: '...' },
2501
+ 'Expected no more than one $(CODE)' );
2502
+ else
2503
+ $seenEllipsis = !$ctx.upTo || 'upTo';
2504
+ }}
2495
2505
  )
2496
2506
  )*
2497
2507
  )?
2498
- ']'
2508
+ cb=']'
2509
+ {
2510
+ if ($seenEllipsis === 'upTo')
2511
+ this.error( 'syntax-expecting-ellipsis', $cb, // at closing bracket
2512
+ { code: '... up to', newcode: '...' },
2513
+ 'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
2514
+ }
2499
2515
  |
2500
2516
  v1=literalValue { $val = $v1.val; }
2501
2517
  |
@@ -2728,6 +2744,7 @@ ident[ category ] returns[ id ]
2728
2744
  | THEN
2729
2745
  | TRAILING
2730
2746
  | UNION
2747
+ | UP
2731
2748
  | TO
2732
2749
  | TYPE
2733
2750
  | USING
@@ -2752,9 +2769,40 @@ LineComment : '//' ~[\r\n\u2028\u2029]* -> channel(HIDDEN);
2752
2769
 
2753
2770
  // Values --------------------------------------------------------------------
2754
2771
 
2755
- String
2772
+ // for syntactic code-completion: Combine all three string styles
2773
+ // Note: Use rule `string` instead as that also parses escape sequences!
2774
+ String : SingleLineString
2775
+ | MultiLineString
2776
+ | MutlLineStringBlock;
2777
+
2778
+ fragment SingleLineString
2756
2779
  :
2757
- ( '\'' ~[\u0027\n\r\u2028\u2029]* '\'' )+ // \u0027 = '\''
2780
+ // \u0027 = '\''
2781
+ // \u2028 = LS (Line Separator)
2782
+ // \u2029 = PS (Paragraph Separator)
2783
+ ( '\'' ~[\u0027\n\r\u2028\u2029]* '\'' )+ //
2784
+ ;
2785
+
2786
+ fragment MultiLineString
2787
+ :
2788
+ ('`' ( MultiLineStringContentChar | EscapeSequence )* '`' )
2789
+ ;
2790
+
2791
+ fragment MutlLineStringBlock
2792
+ :
2793
+ ('```' ( MultiLineStringContentChar | EscapeSequence )* '```')
2794
+ ;
2795
+
2796
+ fragment EscapeSequence
2797
+ :
2798
+ // we could list each escape sequence explicitly, but we already
2799
+ // decode them in genericAntlrParser.js, so no need to do work twice.
2800
+ '\\' .
2801
+ ;
2802
+
2803
+ fragment MultiLineStringContentChar
2804
+ :
2805
+ (~[\u0060\\]) // \u0060 = '`'
2758
2806
  ;
2759
2807
 
2760
2808
  QuotedLiteral
@@ -2763,10 +2811,15 @@ QuotedLiteral
2763
2811
  ( '\'' ~[\u0027\n\r\u2028\u2029]* '\'' )+ // \u0027 = '\''
2764
2812
  ;
2765
2813
 
2814
+ // This literal improves error messages for unterminated literals.
2766
2815
  UnterminatedLiteral
2767
2816
  :
2768
2817
  ( [xX] | [dD][aA][tT][eE] | [tT][iI][mM][eE] ( [sS][tT][aA][mM][pP] )? )?
2769
2818
  '\'' ~[\u0027\n\r\u2028\u2029]* // \u0027 = '\''
2819
+ |
2820
+ ('`' ( MultiLineStringContentChar | EscapeSequence )* )
2821
+ |
2822
+ ('```' ( MultiLineStringContentChar | EscapeSequence )* )
2770
2823
  ;
2771
2824
 
2772
2825
  UnterminatedDelimitedIdentifier
@@ -2899,6 +2952,7 @@ TO : [tT][oO] ; // or make reserved? (is in SQL-92)
2899
2952
  TYPE : [tT][yY][pP][eE] ;
2900
2953
  UNION : [uU][nN][iI][oO][nN] ;
2901
2954
  UNBOUNDED : [uU][nN][bB][oO][uU][nN][dD][eE][dD] ;
2955
+ UP : [uU][pP] ;
2902
2956
  USING : [uU][sS][iI][nN][gG] ;
2903
2957
  VARIABLE : [vV][aA][rR][iI][aA][bB][lL][eE] ;
2904
2958
  VIEW : [vV][iI][eE][wW] ;