@sap/cds-compiler 3.4.0 → 3.4.4

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 (60) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/bin/cdsc.js +3 -4
  3. package/bin/cdshi.js +19 -6
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/lib/api/main.js +6 -3
  6. package/lib/base/message-registry.js +61 -38
  7. package/lib/base/messages.js +7 -3
  8. package/lib/checks/.eslintrc.json +2 -0
  9. package/lib/compiler/.eslintrc.json +4 -1
  10. package/lib/compiler/assert-consistency.js +8 -6
  11. package/lib/compiler/builtins.js +13 -13
  12. package/lib/compiler/checks.js +50 -33
  13. package/lib/compiler/define.js +9 -6
  14. package/lib/compiler/extend.js +83 -55
  15. package/lib/compiler/finalize-parse-cdl.js +3 -3
  16. package/lib/compiler/populate.js +16 -5
  17. package/lib/compiler/propagator.js +22 -29
  18. package/lib/compiler/resolve.js +2 -15
  19. package/lib/compiler/shared.js +4 -4
  20. package/lib/compiler/utils.js +14 -0
  21. package/lib/edm/annotations/genericTranslation.js +68 -56
  22. package/lib/edm/csn2edm.js +214 -174
  23. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  24. package/lib/edm/edmInboundChecks.js +2 -2
  25. package/lib/edm/edmPreprocessor.js +1 -1
  26. package/lib/edm/edmUtils.js +3 -3
  27. package/lib/gen/Dictionary.json +176 -8
  28. package/lib/gen/language.checksum +1 -1
  29. package/lib/gen/language.interp +2 -1
  30. package/lib/gen/languageParser.js +4776 -4513
  31. package/lib/json/from-csn.js +21 -16
  32. package/lib/json/to-csn.js +37 -41
  33. package/lib/language/.eslintrc.json +4 -1
  34. package/lib/language/antlrParser.js +5 -2
  35. package/lib/language/docCommentParser.js +6 -6
  36. package/lib/language/errorStrategy.js +43 -23
  37. package/lib/language/genericAntlrParser.js +54 -95
  38. package/lib/language/language.g4 +92 -66
  39. package/lib/language/multiLineStringParser.js +2 -2
  40. package/lib/language/textUtils.js +2 -2
  41. package/lib/model/csnRefs.js +5 -0
  42. package/lib/modelCompare/compare.js +2 -2
  43. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  44. package/lib/modelCompare/utils/filter.js +99 -0
  45. package/lib/render/.eslintrc.json +1 -0
  46. package/lib/render/toCdl.js +96 -127
  47. package/lib/render/toHdbcds.js +38 -35
  48. package/lib/render/toSql.js +75 -161
  49. package/lib/render/utils/common.js +133 -83
  50. package/lib/render/utils/delta.js +227 -0
  51. package/lib/transform/db/.eslintrc.json +2 -0
  52. package/lib/transform/draft/.eslintrc.json +1 -35
  53. package/lib/transform/forOdataNew.js +33 -22
  54. package/lib/transform/forRelationalDB.js +1 -1
  55. package/lib/transform/localized.js +9 -8
  56. package/lib/transform/odata/typesExposure.js +26 -4
  57. package/lib/transform/transformUtilsNew.js +15 -8
  58. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  59. package/package.json +2 -3
  60. package/lib/modelCompare/filter.js +0 -83
@@ -89,6 +89,7 @@ Object.assign(GenericAntlrParser.prototype, {
89
89
  unaryOpForParens,
90
90
  leftAssocBinaryOp,
91
91
  classifyImplicitName,
92
+ warnIfColonFollows,
92
93
  fragileAlias,
93
94
  identAst,
94
95
  functionAst,
@@ -109,7 +110,6 @@ Object.assign(GenericAntlrParser.prototype, {
109
110
  createArray,
110
111
  finalizeDictOrArray,
111
112
  createPrefixOp,
112
- setOnce,
113
113
  setMaxCardinality,
114
114
  pushIdent,
115
115
  handleComposition,
@@ -117,7 +117,6 @@ Object.assign(GenericAntlrParser.prototype, {
117
117
  reportExpandInline,
118
118
  checkTypeFacet,
119
119
  csnParseOnly,
120
- disallowElementExtension,
121
120
  noAssignmentInSameLine,
122
121
  noSemicolonHere,
123
122
  setLocalToken,
@@ -217,29 +216,12 @@ function setLocalTokenForId( tokenNameMap ) {
217
216
  // // throw new antlr4.error.InputMismatchException(this);
218
217
  // }
219
218
 
220
- /**
221
- * For element extensions (`extend E:elem` syntax).
222
- * If `elemName.path` is set, remove the last extension from `$outer` and
223
- * emit an error that the extension is invalid.
224
- *
225
- * @param {object} elemName
226
- * @param {object} outer
227
- * @param {string} extensionVariant
228
- */
229
- function disallowElementExtension(elemName, outer, extensionVariant) {
230
- if (elemName.path) {
231
- const loc = this.tokenLocation(this.getCurrentToken());
232
- this.error( 'syntax-invalid-extend', loc, { kind: extensionVariant } );
233
- // remove last, i.e. new extension
234
- outer.extensions.pop();
235
- }
236
- }
237
-
238
219
  function noAssignmentInSameLine() {
239
220
  const t = this.getCurrentToken();
240
221
  if (t.text === '@' && t.line <= this._input.LT(-1).line) {
241
- this.warning( 'syntax-anno-same-line', t, {},
242
- 'Annotation assignment belongs to next statement' );
222
+ this.warning( 'syntax-missing-newline', t, { anno: '‹anno›' }, // TODO: single quotes, @()
223
+ // eslint-disable-next-line max-len
224
+ 'Add a newline before $(ANNO) to indicate that it belongs to the next statement' );
243
225
  }
244
226
  }
245
227
 
@@ -267,7 +249,7 @@ const genericTokenTypes = {
267
249
  intro: 'GenericIntro',
268
250
  };
269
251
 
270
- function prepareGenericKeywords( pathItem, expected = null) {
252
+ function prepareGenericKeywords( pathItem, expected = null ) {
271
253
  const length = pathItem?.args?.length || 0;
272
254
  const argPos = (expected ? length - 1 : length);
273
255
  const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
@@ -492,7 +474,7 @@ function tokenLocation( token, endToken = null ) {
492
474
  return loc;
493
475
  }
494
476
 
495
- function isMultiLineToken(token) {
477
+ function isMultiLineToken( token ) {
496
478
  return (
497
479
  token.type === this.constructor.DocComment ||
498
480
  token.type === this.constructor.String ||
@@ -584,6 +566,17 @@ function unaryOpForParens( query, val ) {
584
566
  return { op: { val, location }, location, args: [ query ] };
585
567
  }
586
568
 
569
+ // ANTLR on some OS might corrupt non-ASCII chars for messages
570
+ function warnIfColonFollows( anno ) {
571
+ const t = this.getCurrentToken();
572
+ if (t.text === ':') {
573
+ this.warning( 'syntax-missing-parens', anno.name.location,
574
+ { code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
575
+ // eslint-disable-next-line max-len
576
+ 'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
577
+ }
578
+ }
579
+
587
580
  // If the token before the current one is a doc comment (ignoring other tokens
588
581
  // on the hidden channel), put its "cleaned-up" text as value of property `doc`
589
582
  // of arg `node` (which could be an array). Complain if `doc` is already set.
@@ -611,9 +604,9 @@ function docComment( node ) {
611
604
 
612
605
  // Classify token (identifier category) for implicit names,
613
606
  // to be used in the empty alternative to AS <explicitName>.
614
- function classifyImplicitName( category, ref ) {
607
+ function classifyImplicitName( category, ref, tokpos = 1 ) {
615
608
  if (!ref || ref.path && this.getCurrentToken().text !== '.') {
616
- const implicit = this._input.LT(-1);
609
+ const implicit = this._input.LT( tokpos - 1 || -1 );
617
610
  if (implicit.isIdentifier)
618
611
  implicit.isIdentifier = category;
619
612
  }
@@ -623,11 +616,11 @@ function fragileAlias( ast, safe = false ) {
623
616
  if (this.getCurrentToken().text === '.')
624
617
  return ast;
625
618
  if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id )) {
626
- this.warning( 'syntax-sloppy-alias', ast.location, { keyword: 'as' },
619
+ this.warning( 'syntax-deprecated-auto-as', ast.location, { keyword: 'as' },
627
620
  'Please add the keyword $(KEYWORD) in front of the alias name' );
628
621
  }
629
622
  else { // configurable error
630
- this.message( 'syntax-fragile-alias', ast.location, { keyword: 'as' },
623
+ this.message( 'syntax-missing-as', ast.location, { keyword: 'as' },
631
624
  'Please add the keyword $(KEYWORD) in front of the alias name' );
632
625
  }
633
626
  return ast;
@@ -642,10 +635,9 @@ function identAst( token, category, noTokenTypeCheck = false ) {
642
635
  id = '';
643
636
  if (token.text[0] === '!') {
644
637
  id = id.slice( 2, -1 ).replace( /]]/g, ']' );
645
- if (!id) {
646
- this.error( 'syntax-empty-ident', token, {},
647
- 'Delimited identifier must contain at least one character' );
648
- }
638
+ if (!id)
639
+ this.message( 'syntax-invalid-name', token, {} );
640
+
649
641
  // $delimited is used to complain about ![$self] and other magic vars usage;
650
642
  // we might complain about that already here via @arg{category}
651
643
  return { id, $delimited: true, location: this.tokenLocation( token ) };
@@ -655,8 +647,7 @@ function identAst( token, category, noTokenTypeCheck = false ) {
655
647
  // delimited:
656
648
  id = id.slice( 1, -1 ).replace( /""/g, '"' );
657
649
  if (!id) {
658
- this.error( 'syntax-empty-ident', token, {},
659
- 'Delimited identifier must contain at least one character' );
650
+ this.message( 'syntax-invalid-name', token, {} );
660
651
  }
661
652
  else {
662
653
  this.message( 'syntax-deprecated-ident', token, { delimited: id },
@@ -705,7 +696,7 @@ function valuePathAst( ref ) {
705
696
  const item = path.find( i => i.args && i.$syntax !== ':' );
706
697
  if (!item)
707
698
  return ref;
708
- this.error( 'syntax-not-supported', item.location, {},
699
+ this.error( 'syntax-unsupported-method', item.location, {},
709
700
  'Methods in expressions are not supported yet' );
710
701
  path.broken = true;
711
702
  path.length = 1;
@@ -819,7 +810,7 @@ function quotedLiteral( token, literal ) {
819
810
  location,
820
811
  };
821
812
 
822
- function atChar(i) {
813
+ function atChar( i ) {
823
814
  // Is only used with single-line strings.
824
815
  return location.col + pos + i;
825
816
  }
@@ -843,7 +834,7 @@ function pushIdent( path, ident, prefix ) {
843
834
  endLine: ident.location.line,
844
835
  endCol: ident.location.col,
845
836
  };
846
- this.error( 'syntax-anno-space', wsLocation, {}, // TODO: really Error?
837
+ this.error( 'syntax-unexpected-space', wsLocation, {}, // TODO: really Error?
847
838
  'Expected identifier after \'@\' but found whitespace' );
848
839
  }
849
840
  ident.location.line = tokenLoc.line;
@@ -900,15 +891,15 @@ function addDef( art, parent, env, kind, name ) {
900
891
  dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
901
892
  // do not use function(), otherwise `this` is wrong:
902
893
  if (kind === 0) {
903
- this.error( 'duplicate-argument', loc, { name: duplicateName },
894
+ this.error( 'syntax-duplicate-argument', loc, { name: duplicateName },
904
895
  'Duplicate value for parameter $(NAME)' );
905
896
  }
906
897
  else if (kind === '') {
907
- this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' },
908
- 'Duplicate $(NAME) in the $(KEYWORD) clause' );
898
+ this.error( 'syntax-duplicate-excluding', loc,
899
+ { name: duplicateName, keyword: 'excluding' } );
909
900
  }
910
901
  else {
911
- this.error( 'duplicate-prop', loc, { name: duplicateName },
902
+ this.error( 'syntax-duplicate-property', loc, { name: duplicateName },
912
903
  'Duplicate value for structure property $(NAME)' );
913
904
  }
914
905
  } );
@@ -1026,33 +1017,14 @@ function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifie
1026
1017
  return { op, args: [ left, right ], location: left.location };
1027
1018
  }
1028
1019
 
1029
- // Set property `prop` of `target` to value `value`. Issue error if that
1030
- // property has been set before, while mentioning the keywords previously
1031
- // provided (as arguments `tokens`).
1032
- function setOnce( target, prop, value, ...tokens ) {
1033
- const loc = this.tokenLocation( tokens[0], tokens[tokens.length - 1] );
1034
- const prev = target[prop];
1035
- if (prev) {
1036
- this.error( 'syntax-repeated-option', loc, { option: prev.option },
1037
- 'Option $(OPTION) has already been specified' );
1038
- }
1039
- if (typeof value === 'boolean') {
1040
- if (!value)
1041
- loc.value = false;
1042
- value = loc;
1043
- }
1044
- value.option = tokens.map( t => t.text.toUpperCase() ).join(' ');
1045
- target[prop] = value;
1046
- }
1047
-
1048
1020
  function setMaxCardinality( art, token, max ) {
1049
1021
  const location = this.tokenLocation( token );
1050
1022
  if (!art.cardinality) {
1051
1023
  art.cardinality = { targetMax: Object.assign( { location }, max ), location };
1052
1024
  }
1053
1025
  else {
1054
- this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text },
1055
- 'The target cardinality has already been specified - ignored $(KEYWORD)' );
1026
+ this.warning( 'syntax-duplicate-cardinality', location, { keyword: token.text },
1027
+ 'The target cardinality has already been specified - ignoring $(KEYWORD)' );
1056
1028
  }
1057
1029
  }
1058
1030
 
@@ -1069,33 +1041,20 @@ function handleComposition( cardinality, isComposition ) {
1069
1041
  }
1070
1042
 
1071
1043
  function associationInSelectItem( art ) {
1072
- if (!art.value) // e.g. `expand` without value (for new structures)
1073
- return;
1074
-
1075
- const isPath = art.value.path && art.value.path.length;
1076
- const isIdentifier = isPath && art.value.path.length === 1;
1077
- if (isIdentifier) {
1078
- if (!art.name) {
1079
- art.name = art.value.path[0];
1080
- }
1081
- else {
1082
- // Use alias if provided, i.e. ignore art.value.path.
1083
- this.error( 'query-unexpected-alias', art.name.location, {},
1084
- 'Unexpected alias for association' );
1085
- }
1086
- delete art.value;
1087
- }
1088
- else {
1089
- const loc = isPath ? art.value.path[1].location : art.value.location;
1090
- // If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
1091
- if (isPath || art.name) {
1092
- this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
1093
- if (isPath)
1094
- art.name = art.value.path[art.value.path.length - 1];
1095
-
1044
+ const { value } = art;
1045
+ const path = value?.path;
1046
+ // we cannot compare "just one token before `:`" because there might be annos
1047
+ if (path && path.length === 1 && !art.name && !art.expand && !art.inline) {
1048
+ const name = value.path[0];
1049
+ if (path.length === 1 && !name.args && !name.cardinality && !name.where) {
1050
+ art.name = name;
1096
1051
  delete art.value;
1052
+ return art;
1097
1053
  }
1098
1054
  }
1055
+ this.error( 'syntax-unexpected-assoc', this.getCurrentToken(), {},
1056
+ 'Unexpected association definition in select item' );
1057
+ return {}; // result of the association rules are written into /dev/null
1099
1058
  }
1100
1059
 
1101
1060
  function reportExpandInline( column, isInline ) {
@@ -1108,8 +1067,8 @@ function reportExpandInline( column, isInline ) {
1108
1067
  if (isInline && !name && this._input.LT(-1).type >= this.constructor.Identifier)
1109
1068
  token = this._input.LT(2);
1110
1069
  this.error( 'syntax-unexpected-nested-proj', token,
1111
- { prop: isInline ? 'inline' : 'expand' },
1112
- { std: 'Unexpected nested $(PROP), can only be used after a reference' } );
1070
+ { code: isInline ? '.{ ‹inline› }' : '{ ‹expand› }' },
1071
+ 'Unexpected $(CODE); nested projections can only be used after a reference' );
1113
1072
  // continuation semantics:
1114
1073
  // - add elements anyway (could lead to duplicate errors as usual)
1115
1074
  // - no errors for refs inside expand/inline, but for refs in sibling expr
@@ -1117,25 +1076,25 @@ function reportExpandInline( column, isInline ) {
1117
1076
  }
1118
1077
  if (isInline && name) {
1119
1078
  const location = this.tokenLocation( isInline, this._input.LT(-1) );
1120
- this.error( 'syntax-unexpected-alias', location, { prop: 'inline' },
1121
- 'Unexpected alias name before nested $(PROP)' );
1079
+ this.error( 'syntax-unexpected-alias', location, { code: '.{ ‹inline› }' },
1080
+ 'Unexpected alias name before $(CODE)' );
1122
1081
  // continuation semantics: ignore AS
1123
1082
  }
1124
1083
  }
1125
1084
 
1126
1085
  function checkTypeFacet( art, argIdent ) {
1086
+ // TODO: use dictAddArray or dictAdd?
1127
1087
  const { id } = argIdent;
1128
1088
  if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
1129
1089
  if (art[id] !== undefined) {
1130
- this.error( 'syntax-duplicate-argument', argIdent.location,
1131
- { '#': 'duplicate', code: id } );
1132
1090
  this.error( 'syntax-duplicate-argument', art[id].location,
1133
- { '#': 'duplicate', code: id } );
1091
+ { '#': 'type', name: id } );
1092
+ // continuation semantics: use last
1134
1093
  }
1135
1094
  return true;
1136
1095
  }
1137
- this.error( 'syntax-duplicate-argument', argIdent.location,
1138
- { '#': 'unknown', code: id } );
1096
+ this.error( 'syntax-undefined-param', argIdent.location, { name: id },
1097
+ 'There is no type parameter called $(NAME)');
1139
1098
  return false;
1140
1099
  }
1141
1100
 
@@ -239,10 +239,11 @@ annotationAssignment_paren[ art ]
239
239
  {
240
240
  this.meltKeywordToIdentifier();
241
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)' );
242
+ // TODO: or should we simple accept this without warning? (but still no CC)
243
+ this.warning( 'syntax-unexpected-right-paren',
244
+ this.tokenLocation( this.getCurrentToken() ),
245
+ { offending: "')'", expecting: ['Identifier'], code: '@()' },
246
+ 'Unexpected $(OFFENDING), expecting $(EXPECTING); ignoring $(CODE)' );
246
247
  this.matchWildcard(); // we know it is the ')' - we do not reach the final match
247
248
  return $ctx;
248
249
  }
@@ -273,13 +274,7 @@ annotationAssignment_fix[ art ] locals[ assignment ]
273
274
  { $assignment = { name: {} }; }
274
275
  annotationPath[ $assignment.name, 'anno' ]
275
276
  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
- }
277
+ { this.warnIfColonFollows( $assignment ); }
283
278
  )
284
279
  ;
285
280
 
@@ -709,8 +704,7 @@ aspectDef[ art, outer ] locals[ name = {} ]
709
704
  { this.addDef( $art, $outer, 'artifacts', 'aspect', $name );
710
705
  // backends do not like ['$'+'syntax']: ($ent ? 'entity' : 'aspect')
711
706
  if ($ent)
712
- this.warning( 'syntax-deprecated-abstract', this.tokenLocation( $abs, $ent ), {},
713
- 'Abstract entity definitions are deprecated; use aspect definitions instead' );
707
+ this.warning( 'syntax-deprecated-abstract', this.tokenLocation( $abs, $ent ) );
714
708
  this.docComment( $art ); }
715
709
  annotationAssignment_fix[ $art ]*
716
710
  ( ':'
@@ -751,12 +745,33 @@ typeDef[ art, outer ] locals[ name = {} ]
751
745
  extendType[ art, outer ] locals[ name = {} ]
752
746
  @after { this.attachLocation( $art ); }
753
747
  :
754
- // aspects are types, i.e. kind is 'type' for aspects
755
748
  TYPE simplePath[ $name, 'Extend' ]
756
749
  { $art.expectedKind = 'type'; $art.name = $name;
757
750
  this.addItem( $art, $outer, 'extensions', 'extend' );
758
751
  }
759
- extendWithOptElementsOrType[ $art, $art ]
752
+ // extendWithOptElementsOrType + includeRef:
753
+ (
754
+ extendWithOptElementsNoWith[ art ]
755
+ |
756
+ WITH { this.noSemicolonHere(); this.docComment( $art ); }
757
+ annotationAssignment_ll1[ $art ]*
758
+ (
759
+ '{' { $art.elements = this.createDict(); }
760
+ elementDefOrExtend[ $art ]*
761
+ '}' { this.finalizeDictOrArray( $art.elements ); }
762
+ { this.checkExtensionDict( $art.elements ); }
763
+ optionalSemi
764
+ |
765
+ // extend type|element Art with (length: 10);
766
+ typeNamedArgList[ $art ]
767
+ requiredSemi
768
+ |
769
+ requiredSemi
770
+ |
771
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
772
+ requiredSemi
773
+ )
774
+ )
760
775
  ;
761
776
 
762
777
  annotationDef[ art, outer ] locals[ name = {} ]
@@ -779,12 +794,21 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
779
794
  @after{ /* #ATN 1 */ this.attachLocation( $art ); }
780
795
  :
781
796
  simplePath[ $name, 'Extend' ]
782
- ( ':' simplePath[ $elemName, 'Element'] )?
783
- { this.addExtension( $art, $outer, 'extend', $name, $elemName.path ); }
784
797
  (
785
- { this.docComment( $art ); }
798
+ ':' simplePath[ $elemName, 'Element']
799
+ { this.addExtension( $art, $outer, 'extend', $name, $elemName.path ); }
800
+ extendWithOptElementsOrType[ art ]
801
+ |
802
+ { this.addExtension( $art, $outer, 'extend', $name ); }
803
+ extendWithOptElementsNoWith[ art ]
804
+ |
805
+ { this.addExtension( $art, $outer, 'extend', $name ); }
806
+ WITH { this.noSemicolonHere(); this.docComment( $art ); }
786
807
  annotationAssignment_ll1[ $art ]*
808
+ // #ATN: ELEMENTS, ENUM, DEFINITIONS, COLUMNS, ACTIONS are not reserved and
809
+ // could be includeRef
787
810
  (
811
+ // all the alternatives from `extendWithOptElementsOrType` --------------
788
812
  '{' { $art.elements = this.createDict(); }
789
813
  elementDefOrExtend[ $art ]*
790
814
  '}' { this.finalizeDictOrArray( $art.elements ); }
@@ -792,32 +816,33 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
792
816
  optionalSemi
793
817
  |
794
818
  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
819
  |
805
- '{' { $art.elements = this.createDict(); }
820
+ ELEMENTS '{' { $art.elements = this.createDict(); }
806
821
  elementDefOrExtend[ $art ]*
807
822
  '}' { this.finalizeDictOrArray( $art.elements ); }
808
- { this.checkExtensionDict( $art.elements ); }
823
+ { this.checkExtensionDict( $art.elements ); }
824
+ optionalSemi
825
+ |
826
+ ENUM '{' { $art.enum = this.createDict(); }
827
+ enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
828
+ '}' { this.finalizeDictOrArray( $art.enum ); }
809
829
  optionalSemi
810
830
  |
831
+ // extend Art with (length: 10);
832
+ // `with` is required, or we could have `extend String(length:10);`.
833
+ typeNamedArgList[ $art ]
834
+ requiredSemi
835
+ |
836
+ // extension alternatives for main definitions --------------------------
837
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
811
838
  requiredSemi
812
839
  |
813
- { this.disallowElementExtension( $elemName, $outer, 'definitions' ); }
814
840
  DEFINITIONS
815
841
  '{' { $art.artifacts = this.createDict(); }
816
842
  artifactDef[ $art, true ]*
817
843
  '}' { this.finalizeDictOrArray( $art.artifacts ); }
818
844
  optionalSemi
819
845
  |
820
- { this.disallowElementExtension( $elemName, $outer, 'columns' ); }
821
846
  COLUMNS
822
847
  '{' { $art.columns = []; }
823
848
  (
@@ -829,44 +854,37 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
829
854
  '}'
830
855
  optionalSemi
831
856
  |
832
- { this.disallowElementExtension( $elemName, $outer, 'actions' ); }
833
857
  ACTIONS '{' { $art.actions = this.createDict(); }
834
858
  actionFunctionDef[ $art ]* // TODO: no EXTEND in actions? (ok, would just allow annos)
835
859
  '}' { this.finalizeDictOrArray( $art.actions ); }
836
860
  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
861
  )
854
862
  )
855
863
  ;
856
864
 
857
865
  extendWithOptElementsOrType[ art ]
858
866
  :
867
+ extendWithOptElementsNoWith[ art ]
868
+ |
859
869
  WITH { this.noSemicolonHere(); this.docComment( $art ); }
860
870
  annotationAssignment_ll1[ $art ]*
861
871
  (
862
- includeRef[ $art ] ( ',' includeRef[ $art ] )*
863
- requiredSemi
864
- |
865
872
  '{' { $art.elements = this.createDict(); }
866
873
  elementDefOrExtend[ $art ]*
867
874
  '}' { this.finalizeDictOrArray( $art.elements ); }
868
875
  { this.checkExtensionDict( $art.elements ); }
869
876
  optionalSemi
877
+ |
878
+ ELEMENTS '{' { $art.elements = this.createDict(); }
879
+ elementDefOrExtend[ $art ]*
880
+ '}' { this.finalizeDictOrArray( $art.elements ); }
881
+ { this.checkExtensionDict( $art.elements ); }
882
+ optionalSemi
883
+ |
884
+ ENUM '{' { $art.enum = this.createDict(); }
885
+ enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
886
+ '}' { this.finalizeDictOrArray( $art.enum ); }
887
+ optionalSemi
870
888
  |
871
889
  // extend type|element Art with (length: 10);
872
890
  typeNamedArgList[ $art ]
@@ -874,7 +892,10 @@ extendWithOptElementsOrType[ art ]
874
892
  |
875
893
  requiredSemi
876
894
  )
877
- |
895
+ ;
896
+
897
+ extendWithOptElementsNoWith[ art ]
898
+ :
878
899
  { this.docComment( $art ); }
879
900
  annotationAssignment_ll1[ $art ]*
880
901
  (
@@ -1080,12 +1101,12 @@ mixinElementDef[ outer ] locals[ art = {} ]
1080
1101
  |
1081
1102
  typeRefOptArgs[ $art ]
1082
1103
  ( as='=' expression
1083
- { this.error( 'syntax-unsupported-field', $as ); }
1104
+ { this.error( 'syntax-unsupported-calc-field', $as ); }
1084
1105
  )?
1085
1106
  )
1086
1107
  |
1087
1108
  as='=' expression
1088
- { this.error( 'syntax-unsupported-field', $as ); }
1109
+ { this.error( 'syntax-unsupported-calc-field', $as ); }
1089
1110
  )
1090
1111
  requiredSemi
1091
1112
  ;
@@ -1216,7 +1237,7 @@ elementDefInner[ art, outer, allowEq ]
1216
1237
  eq='=' e=expression // never introduce AS as syntax variant of '='
1217
1238
  {
1218
1239
  if (!$allowEq || $e.expr && !$e.expr.literal )
1219
- this.error( 'syntax-unsupported-field', $eq );
1240
+ this.error( 'syntax-unsupported-calc-field', $eq );
1220
1241
  else if ($e.expr)
1221
1242
  $art.value = $e.expr;
1222
1243
  }
@@ -1251,7 +1272,7 @@ selectItemDef[ outer ] locals[ art ]
1251
1272
  selectItemDefBody[ $art, $outer ]
1252
1273
  ;
1253
1274
 
1254
- selectItemDefBody[ art, outer ]
1275
+ selectItemDefBody[ art, outer ] locals[ assoc ]
1255
1276
  @after{ /* #ATN 2 */ }
1256
1277
  :
1257
1278
  { $outer.push( $art ); }
@@ -1261,7 +1282,7 @@ selectItemDefBody[ art, outer ]
1261
1282
  // AS (using rule 'ident' instead of 'identNoKeyword') -> ambiguities
1262
1283
  ( as=AS n1=ident['Item'] { $art.name = $n1.id }
1263
1284
  | n2=ident['Item'] { $art.name = this.fragileAlias( $n2.id, true ); }
1264
- | { if (this.getCurrentToken().text !== '.') this.classifyImplicitName( 'Item', $e.expr ); }
1285
+ | { this.classifyImplicitName( 'Item', $e.expr ); }
1265
1286
  )
1266
1287
  { if ($art.value && !$art.value.path) this.excludeExpected( ["'.'", "'{'"] );
1267
1288
  else if ($art.name) this.excludeExpected( ["'.'"] );
@@ -1290,7 +1311,7 @@ selectItemDefBody[ art, outer ]
1290
1311
  { this.docComment( $art ); }
1291
1312
  annotationAssignment_fix[ $art ]*
1292
1313
  ( ':'
1293
- // #ATN: typeRefOptArgs can start with TYPE, REDIRECTED
1314
+ // #ATN: typeRefOptArgs can start with TYPE, REDIRECTED, ASSOCIATION
1294
1315
  ( re=REDIRECTED to=TO
1295
1316
  { $art.target = {}; }
1296
1317
  simplePath[ $art.target, 'artref' ]
@@ -1303,15 +1324,20 @@ selectItemDefBody[ art, outer ]
1303
1324
  | typeTypeOf[ $art ]
1304
1325
  { this.docComment( $art ); }
1305
1326
  annotationAssignment_ll1[ $art ]*
1327
+ | l=LOCALIZED { $art.localized = this.valueWithTokenLocation( true, $l ); }
1328
+ typeRefOptArgs[ $art ]
1329
+ { this.docComment( $art ); }
1330
+ annotationAssignment_ll1[ $art ]*
1306
1331
  | typeRefOptArgs[ $art ]
1307
1332
  { this.docComment( $art ); }
1308
1333
  annotationAssignment_ll1[ $art ]*
1309
1334
  |
1310
- typeAssociationBase[ $art, false ]
1335
+ { $assoc = this.associationInSelectItem( $art ); }
1336
+ typeAssociationBase[ $assoc, false ]
1311
1337
  // #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 ); }
1338
+ ( typeToMany[ $assoc ] | typeToOne[ $assoc ] | simplePath[ $assoc.target, 'artref' ] )
1339
+ ON cond=condition
1340
+ { $assoc.on=$cond.cond; }
1315
1341
  )
1316
1342
  )?
1317
1343
  ;
@@ -1398,7 +1424,7 @@ elementProperties[ elem ]
1398
1424
  nullability[ $elem ]?
1399
1425
  |
1400
1426
  eq='='
1401
- { this.error( 'syntax-unsupported-field', $eq ); }
1427
+ { this.error( 'syntax-unsupported-calc-field', $eq ); }
1402
1428
  expression
1403
1429
  ;
1404
1430
 
@@ -2230,7 +2256,7 @@ conditionTerm returns [ cond ]
2230
2256
  { $cond = { op: this.valueWithTokenLocation( 'exists', $ex ), args: [
2231
2257
  { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' }
2232
2258
  ] };
2233
- this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
2259
+ this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', code: '?' } );
2234
2260
  }
2235
2261
  |
2236
2262
  ep=valuePath[ 'ref' ]
@@ -2367,7 +2393,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2367
2393
  { $expr = $vp.qp;; $expr.scope = 'param'; }
2368
2394
  | pp=Number
2369
2395
  { $expr = { param: this.numberLiteral( $pp ), scope: 'param' };
2370
- this.csnParseOnly( 'syntax-unsupported-param', [ $pp ], { '#': 'positional', name: ':' + $pp.text } );
2396
+ this.csnParseOnly( 'syntax-unsupported-param', [ $pp ], { '#': 'positional', code: ':' + $pp.text } );
2371
2397
  }
2372
2398
  )
2373
2399
  |
@@ -2375,7 +2401,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
2375
2401
  // if we have an HideAlternatives here, we would block it to use it in
2376
2402
  // parallel to an expression (would produce adaptivePredict() otherwise)
2377
2403
  { $expr = { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' };
2378
- this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
2404
+ this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', code: '?' } );
2379
2405
  }
2380
2406
  |
2381
2407
  open='('
@@ -24,7 +24,7 @@ const {
24
24
  * @returns {[string, number]} The indentation-stripped string and the number
25
25
  * of whitespace characters removed.
26
26
  */
27
- function stripIndentation(str) {
27
+ function stripIndentation( str ) {
28
28
  if (str === '')
29
29
  return [ '', 0 ];
30
30
 
@@ -510,7 +510,7 @@ class MultiLineStringParser {
510
510
  *
511
511
  * @param {object} token
512
512
  */
513
- function parseMultiLineStringLiteral(token) {
513
+ function parseMultiLineStringLiteral( token ) {
514
514
  const p = new MultiLineStringParser(this, token);
515
515
  return p.parse();
516
516
  }
@@ -11,7 +11,7 @@ const cdlNewLineRegEx = /\r\n?|\n|\u2028|\u2029/u;
11
11
  * @param {string} str
12
12
  * @returns {boolean}
13
13
  */
14
- function isWhitespaceOrNewLineOnly(str) {
14
+ function isWhitespaceOrNewLineOnly( str ) {
15
15
  return /^\s*$/.test(str);
16
16
  }
17
17
 
@@ -33,7 +33,7 @@ function isWhitespaceOrNewLineOnly(str) {
33
33
  * @param char
34
34
  * @returns {boolean}
35
35
  */
36
- function isWhitespaceCharacterNoNewline(char) {
36
+ function isWhitespaceCharacterNoNewline( char ) {
37
37
  return whitespaceRegEx.test(char);
38
38
  }
39
39