@sap/cds-compiler 5.7.2 → 5.8.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 (73) hide show
  1. package/CHANGELOG.md +62 -2
  2. package/bin/cdsse.js +13 -1
  3. package/doc/CHANGELOG_BETA.md +7 -0
  4. package/lib/api/options.js +2 -1
  5. package/lib/api/validate.js +9 -0
  6. package/lib/base/location.js +1 -1
  7. package/lib/base/message-registry.js +55 -20
  8. package/lib/base/messages.js +5 -2
  9. package/lib/base/model.js +8 -6
  10. package/lib/checks/assocOutsideService.js +40 -0
  11. package/lib/checks/featureFlags.js +4 -1
  12. package/lib/checks/types.js +7 -4
  13. package/lib/checks/validator.js +3 -0
  14. package/lib/compiler/assert-consistency.js +11 -5
  15. package/lib/compiler/checks.js +79 -17
  16. package/lib/compiler/define.js +60 -3
  17. package/lib/compiler/extend.js +1 -2
  18. package/lib/compiler/generate.js +1 -1
  19. package/lib/compiler/populate.js +17 -6
  20. package/lib/compiler/propagator.js +1 -1
  21. package/lib/compiler/resolve.js +181 -150
  22. package/lib/compiler/shared.js +276 -22
  23. package/lib/compiler/tweak-assocs.js +15 -4
  24. package/lib/compiler/xpr-rewrite.js +76 -50
  25. package/lib/edm/annotations/edmJson.js +1 -1
  26. package/lib/edm/annotations/genericTranslation.js +2 -2
  27. package/lib/edm/csn2edm.js +2 -2
  28. package/lib/edm/edmPreprocessor.js +15 -9
  29. package/lib/edm/edmUtils.js +12 -5
  30. package/lib/gen/CdlGrammar.checksum +1 -0
  31. package/lib/gen/CdlParser.js +2239 -2229
  32. package/lib/gen/Dictionary.json +55 -8
  33. package/lib/json/from-csn.js +37 -17
  34. package/lib/json/to-csn.js +4 -0
  35. package/lib/language/genericAntlrParser.js +7 -0
  36. package/lib/main.d.ts +5 -1
  37. package/lib/model/cloneCsn.js +1 -0
  38. package/lib/model/csnRefs.js +1 -0
  39. package/lib/model/csnUtils.js +0 -5
  40. package/lib/modelCompare/utils/filter.js +2 -2
  41. package/lib/optionProcessor.js +2 -0
  42. package/lib/parsers/AstBuildingParser.js +72 -34
  43. package/lib/parsers/CdlGrammar.g4 +20 -19
  44. package/lib/parsers/XprTree.js +206 -0
  45. package/lib/parsers/index.js +1 -1
  46. package/lib/render/toCdl.js +61 -89
  47. package/lib/render/toSql.js +59 -29
  48. package/lib/render/utils/standardDatabaseFunctions.js +252 -15
  49. package/lib/transform/addTenantFields.js +9 -3
  50. package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
  51. package/lib/transform/db/assocsToQueries/utils.js +10 -3
  52. package/lib/transform/db/expansion.js +3 -1
  53. package/lib/transform/db/flattening.js +7 -3
  54. package/lib/transform/db/killAnnotations.js +1 -0
  55. package/lib/transform/db/processSqlServices.js +70 -17
  56. package/lib/transform/draft/db.js +8 -3
  57. package/lib/transform/draft/odata.js +27 -4
  58. package/lib/transform/effective/main.js +37 -10
  59. package/lib/transform/effective/misc.js +4 -9
  60. package/lib/transform/effective/service.js +34 -0
  61. package/lib/transform/effective/types.js +28 -17
  62. package/lib/transform/forOdata.js +36 -10
  63. package/lib/transform/forRelationalDB.js +30 -18
  64. package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
  65. package/lib/transform/odata/createForeignKeys.js +120 -116
  66. package/lib/transform/odata/flattening.js +10 -8
  67. package/lib/transform/transformUtils.js +58 -25
  68. package/lib/transform/translateAssocsToJoins.js +10 -6
  69. package/lib/transform/universalCsn/coreComputed.js +5 -1
  70. package/package.json +1 -1
  71. package/share/messages/message-explanations.json +1 -0
  72. package/share/messages/rewrite-not-supported.md +5 -0
  73. package/share/messages/rewrite-undefined-key.md +94 -0
@@ -132,6 +132,13 @@
132
132
  "Collection"
133
133
  ]
134
134
  },
135
+ "Analytics.LevelInformation": {
136
+ "Type": "Hierarchy.HierarchyType",
137
+ "AppliesTo": [
138
+ "EntityType"
139
+ ],
140
+ "$experimental": true
141
+ },
135
142
  "Authorization.SecuritySchemes": {
136
143
  "Type": "Collection(Authorization.SecurityScheme)",
137
144
  "AppliesTo": [
@@ -1121,6 +1128,13 @@
1121
1128
  ],
1122
1129
  "$experimental": true
1123
1130
  },
1131
+ "Common.AddressViaNavigationPath": {
1132
+ "Type": "Core.Tag",
1133
+ "AppliesTo": [
1134
+ "EntityContainer"
1135
+ ],
1136
+ "$experimental": true
1137
+ },
1124
1138
  "Communication.Contact": {
1125
1139
  "Type": "Communication.ContactType",
1126
1140
  "AppliesTo": [
@@ -1519,7 +1533,7 @@
1519
1533
  "$experimental": true
1520
1534
  },
1521
1535
  "EntityRelationship.referencesWithConstantIds": {
1522
- "Type": "Collection(EntityRelationship.referencesWithConstantId)",
1536
+ "Type": "Collection(EntityRelationship.referenceWithConstantId)",
1523
1537
  "AppliesTo": [
1524
1538
  "EntityType"
1525
1539
  ],
@@ -1575,8 +1589,7 @@
1575
1589
  ]
1576
1590
  },
1577
1591
  "HTML5.LinkTarget": {
1578
- "Type": "HTML5.LinkTargetType",
1579
- "$experimental": true
1592
+ "Type": "HTML5.LinkTargetType"
1580
1593
  },
1581
1594
  "HTML5.RowSpanForDuplicateValues": {
1582
1595
  "Type": "Core.Tag",
@@ -1894,8 +1907,7 @@
1894
1907
  "Function",
1895
1908
  "ActionImport",
1896
1909
  "FunctionImport"
1897
- ],
1898
- "$experimental": true
1910
+ ]
1899
1911
  },
1900
1912
  "UI.SelectionPresentationVariant": {
1901
1913
  "Type": "UI.SelectionPresentationVariantType",
@@ -2525,6 +2537,30 @@
2525
2537
  "AccumulativeMeasure": "Core.Tag"
2526
2538
  }
2527
2539
  },
2540
+ "Analytics.MultiLevelExpandLevel": {
2541
+ "$kind": "ComplexType",
2542
+ "Properties": {
2543
+ "DimensionProperties": "Collection(Edm.String)",
2544
+ "AdditionalProperties": "Collection(Edm.String)"
2545
+ },
2546
+ "$experimental": true
2547
+ },
2548
+ "Analytics.MultiLevelExpandSiblingOrder": {
2549
+ "$kind": "ComplexType",
2550
+ "Properties": {
2551
+ "Property": "Edm.String",
2552
+ "Descending": "Edm.Boolean"
2553
+ },
2554
+ "$experimental": true
2555
+ },
2556
+ "Analytics.MultiLevelExpandEntry": {
2557
+ "$kind": "ComplexType",
2558
+ "Properties": {
2559
+ "Entry": "Collection(Edm.String)",
2560
+ "Levels": "Edm.Int64"
2561
+ },
2562
+ "$experimental": true
2563
+ },
2528
2564
  "Authorization.SecurityScheme": {
2529
2565
  "$kind": "ComplexType",
2530
2566
  "Properties": {
@@ -3964,16 +4000,26 @@
3964
4000
  },
3965
4001
  "$experimental": true
3966
4002
  },
4003
+ "Hierarchy.HierarchyType": {
4004
+ "$kind": "ComplexType",
4005
+ "Properties": {
4006
+ "LimitedDescendantCount": "Edm.Int64",
4007
+ "DrillState": "Edm.String",
4008
+ "DistanceFromRoot": "Edm.Int64"
4009
+ },
4010
+ "$experimental": true
4011
+ },
3967
4012
  "Hierarchy.RecursiveHierarchyType": {
3968
4013
  "$kind": "ComplexType",
4014
+ "BaseType": "Hierarchy.HierarchyType",
3969
4015
  "Properties": {
3970
4016
  "ExternalKey": "Edm.String",
3971
4017
  "NodeType": "Edm.String",
3972
- "ChildCount": "Edm.Int64",
3973
- "DescendantCount": "Edm.Int64",
3974
4018
  "LimitedDescendantCount": "Edm.Int64",
3975
4019
  "DrillState": "Edm.String",
3976
4020
  "DistanceFromRoot": "Edm.Int64",
4021
+ "ChildCount": "Edm.Int64",
4022
+ "DescendantCount": "Edm.Int64",
3977
4023
  "LimitedRank": "Edm.Int64",
3978
4024
  "SiblingRank": "Edm.Int64",
3979
4025
  "Matched": "Edm.Boolean",
@@ -3987,7 +4033,8 @@
3987
4033
  "ChangeNextSiblingAction": "Common.QualifiedName",
3988
4034
  "ChangeSiblingForRootsSupported": "Edm.Boolean",
3989
4035
  "CopyAction": "Common.QualifiedName"
3990
- }
4036
+ },
4037
+ "$experimental": true
3991
4038
  },
3992
4039
  "Hierarchy.TopLevelsExpandType": {
3993
4040
  "$kind": "ComplexType",
@@ -124,6 +124,7 @@ const { isAnnotationExpression } = require('../base/builtins');
124
124
  const { CompilerAssertion } = require('../base/error');
125
125
  const { Location } = require('../base/location');
126
126
  const { XsnSource } = require('../compiler/xsn-model');
127
+ const { xprAsTree } = require('../parsers/XprTree');
127
128
 
128
129
  const $location = Symbol.for('cds.$location');
129
130
 
@@ -529,7 +530,7 @@ const schema = compileSchema( {
529
530
  'from', 'all', 'distinct', 'columns', 'excluding', // no 'mixin'
530
531
  'where', 'groupBy', 'having', 'orderBy', 'limit',
531
532
  ],
532
- inKind: [ 'entity', 'event' ],
533
+ inKind: [ 'entity', 'event', 'type' ],
533
534
  },
534
535
  SELECT: {
535
536
  type: queryTerm,
@@ -664,7 +665,7 @@ const schema = compileSchema( {
664
665
  ],
665
666
  schema: {
666
667
  '=': {
667
- type: renameTo( '$tokenTexts', string ),
668
+ type: renameTo( '$tokenTexts', stringOrBool ),
668
669
  xorGroups: null, // reset xorGroup; allow '=' for all :expr
669
670
  },
670
671
  },
@@ -698,7 +699,8 @@ const schema = compileSchema( {
698
699
  // 2. Inside "xpr" => inside expressions
699
700
  // Because of (1) we have to set this property to false.
700
701
  inValue: false,
701
- optional: typeProperties, // TODO: only in CDL-style cast, otherwise just length,…
702
+ optional: typeProperties,
703
+ // TODO v6: only in CDL-style cast, otherwise just length,…
702
704
  inKind: [ '$column' ],
703
705
  },
704
706
  default: {
@@ -762,6 +764,9 @@ const schema = compileSchema( {
762
764
  options: { // deprecated top-level property
763
765
  type: ignore,
764
766
  },
767
+ csnInteropEffective: {
768
+ type: ignore, // by https://github.com/SAP/csn-interop-specification
769
+ },
765
770
  indexNo: { // CSN v0.1.0, but ignored without message
766
771
  ignore: true, type: ignore,
767
772
  },
@@ -787,6 +792,7 @@ const topLevelSpec = {
787
792
  optional: [
788
793
  'requires', 'definitions', 'vocabularies', 'extensions', 'i18n',
789
794
  'namespace', 'version', 'messages', 'meta', 'options', '@', '$location',
795
+ 'csnInteropEffective',
790
796
  ],
791
797
  requires: false, // empty object OK
792
798
  schema,
@@ -1316,6 +1322,14 @@ function string( val, spec ) {
1316
1322
  return ignore( val );
1317
1323
  }
1318
1324
 
1325
+ function stringOrBool( val, spec ) {
1326
+ if (typeof val === 'string' && val || typeof val === 'boolean')
1327
+ return val;
1328
+ error( 'syntax-expecting-string', location(true),
1329
+ { '#': spec.msgVariant || 'or-bool', prop: spec.msgProp } );
1330
+ return ignore( val );
1331
+ }
1332
+
1319
1333
  function stringVal( val, spec ) {
1320
1334
  if (typeof val === 'string' && val)
1321
1335
  // XSN TODO: do not require literal
@@ -1440,12 +1454,12 @@ function annoValue( val, spec ) {
1440
1454
  }
1441
1455
  return retval;
1442
1456
  }
1443
- else if (typeof val['='] === 'string') {
1457
+ else if (typeof val['='] === 'string' || val['='] === true) {
1444
1458
  // An object with `=` is an expression if and only if:
1445
1459
  // - there is exactly one property ('=')
1446
1460
  // - there is at least one other expression property (e.g. "xpr")
1447
1461
  const valKeys = Object.keys( val );
1448
- if (valKeys.length === 1) {
1462
+ if (valKeys.length === 1 && typeof val['='] === 'string') {
1449
1463
  ++virtualLine;
1450
1464
  const r = refSplit( val['='], '=' ); // i.e. no extra `variant` stuff
1451
1465
  ++virtualLine;
@@ -1566,12 +1580,16 @@ function xpr( exprs, spec, xsn, csn ) {
1566
1580
  { prop: 'xpr', siblingprop: 'func', '#': 'suffix' });
1567
1581
  }
1568
1582
  xsn.suffix = exprArgs( exprs, spec );
1583
+ if (exprs.length > 2)
1584
+ xsn.suffix = xprAsTree( exprs, xsn.suffix, xsn.op.location ).args;
1569
1585
  }
1570
1586
  else {
1571
1587
  // setting $parens here would not always be correct; thus, keep distinction
1572
1588
  // between 'xpr' and 'ixpr' (”internal” `xpr` = without implicit parens)
1573
1589
  xsn.op = { val: 'xpr', location: location() };
1574
1590
  xsn.args = exprArgs( exprs, spec );
1591
+ if (exprs.length > 2)
1592
+ xsn.args = xprAsTree( exprs, xsn.args, xsn.op.location ).args;
1575
1593
  }
1576
1594
  }
1577
1595
 
@@ -1620,11 +1638,10 @@ function expr( e, spec ) {
1620
1638
  if (Array.isArray( e )) {
1621
1639
  if (e.length > 1) { // struct-xpr
1622
1640
  const loc = location();
1623
- return {
1624
- op: { val: 'ixpr', location: loc },
1625
- args: exprArgs( e, spec ),
1626
- location: loc,
1627
- };
1641
+ const xsn = exprArgs( e, spec );
1642
+ return (e.length < 3) // optimization
1643
+ ? { op: { val: 'ixpr', location: loc }, args: xsn, location: loc }
1644
+ : xprAsTree( e, xsn, loc );
1628
1645
  }
1629
1646
  else if (e.length === 1) { // CSN v.0.1.0 way for parentheses
1630
1647
  const loc = location();
@@ -1657,14 +1674,14 @@ function exprOrString( val, spec ) {
1657
1674
  : expr( val, spec );
1658
1675
  }
1659
1676
 
1660
- // mark path argument of 'exits' predicate with $expected:'exists'
1677
+ // mark path argument of 'exists' predicate with $expected:'exists'
1661
1678
  function exprArgs( cond, spec ) {
1662
1679
  const rxsn = arrayOf( exprOrString )( cond, spec );
1663
1680
  // TODO: do that in definer.js, neither here nor in CDL parser
1664
1681
  if (Array.isArray( rxsn )) {
1665
1682
  for (let i = 0; i < rxsn.length - 1; i++) {
1666
1683
  // TODO: disallow param ref - write test
1667
- if (rxsn[i]?.val === 'exists' && rxsn[i].literal === 'token' && rxsn[i + 1].path)
1684
+ if (cond[i] === 'exists' && rxsn[i + 1].path)
1668
1685
  rxsn[++i].$expected = 'exists';
1669
1686
  }
1670
1687
  }
@@ -1673,11 +1690,14 @@ function exprArgs( cond, spec ) {
1673
1690
 
1674
1691
  function condition( cond, spec ) {
1675
1692
  const loc = location();
1676
- return {
1677
- op: { val: 'xpr', location: loc },
1678
- args: exprArgs( cond, spec ),
1679
- location: loc,
1680
- };
1693
+ const xsn = exprArgs( cond, spec );
1694
+ // TODO sql-like backends: with the commented `return`, test3 generated sql
1695
+ // files will not have the unnecessary `(…)` around `on` anymore → extra PR
1696
+ const tree = (cond.length < 3) ? xsn : xprAsTree( cond, xsn, loc ).args;
1697
+ return { op: { val: 'xpr', location: loc }, args: tree, location: loc };
1698
+ // return (cond.length < 3) // optimization
1699
+ // ? { op: { val: 'ixpr', location: loc }, args: xsn, location: loc }
1700
+ // : xprAsTree( cond, xsn, loc );
1681
1701
  }
1682
1702
 
1683
1703
  // Queries (std signature) ---------------------------------------------------
@@ -1147,6 +1147,8 @@ function value( node ) {
1147
1147
  }
1148
1148
 
1149
1149
  function enumValue( node ) {
1150
+ if (node.val !== undefined) // with `val` via CSN input (e.g. recompilation)
1151
+ return extra( { '#': node.sym.id, val: node.val }, node );
1150
1152
  const r = extra( { '#': node.sym.id }, node );
1151
1153
  const sym = node.sym._artifact;
1152
1154
  // add calculated `val`, but not for chained symbols:
@@ -1276,6 +1278,8 @@ function exprInternal( node, xprParens ) {
1276
1278
  function flattenInternalXpr( array, op ) {
1277
1279
  if (!structXpr)
1278
1280
  return array.flat( Infinity );
1281
+ // TODO: do not rely on 'nary' - this dosn't work with CSN input have an
1282
+ // `xpr: [{val:1},'+',{val:2},'+',{val:3}]`.
1279
1283
  if (array.length < 5 || op !== 'nary')
1280
1284
  return array;
1281
1285
  // nary: [ ‹a›, '+', ‹b›, '+', ‹c› ] → [ [ ‹a›, '+', ‹b› ], '+', ‹c› ]
@@ -853,6 +853,10 @@ function valuePathAst( ref ) {
853
853
  const implicit = this.previousTokenAtLocation( location );
854
854
  if (implicit && implicit.isIdentifier)
855
855
  implicit.isIdentifier = 'func';
856
+
857
+ const filter = path[0].cardinality || path[0].where;
858
+ if (filter)
859
+ this.message( 'syntax-unexpected-filter', filter.location, {} );
856
860
  const op = { location, val: 'call' };
857
861
  return (args)
858
862
  ? {
@@ -881,6 +885,9 @@ function valuePathAst( ref ) {
881
885
  }, 'References after function calls can\'t be resolved. Use $(CODE) in function arguments');
882
886
  break;
883
887
  }
888
+ const filter = path[i].cardinality || path[i].where;
889
+ if (filter)
890
+ this.message( 'syntax-unexpected-filter', filter.location, {} );
884
891
  }
885
892
 
886
893
  const args = [];
package/lib/main.d.ts CHANGED
@@ -172,7 +172,6 @@ declare namespace compiler {
172
172
  withLocations?: boolean|string
173
173
  /**
174
174
  * Use the new non-ANTLR based parser for compilation.
175
- * Experimental flag!
176
175
  *
177
176
  * @since v5.2.0
178
177
  */
@@ -934,6 +933,11 @@ declare namespace compiler {
934
933
  /**
935
934
  * Renders the given CSN into a CDL source representation.
936
935
  *
936
+ * The CDL string representation may change between minor @sap/cds-compiler
937
+ * versions, but when compiled, will return the same CSN again.
938
+ * Hence, stylistic changes such as additional whitespace is not considered
939
+ * a breaking change.
940
+ *
937
941
  * @returns Object containing the rendered model.
938
942
  */
939
943
  function cdl(csn: CSN, options?: CdlOptions): CdlResult;
@@ -38,6 +38,7 @@ const internalCsnProps = {
38
38
  $default: shallowCopy, // used for HANA CSN migrations
39
39
  $notNull: shallowCopy, // used for HANA CSN migrations
40
40
  $sqlService: shallowCopy,
41
+ $dummyService: shallowCopy,
41
42
  };
42
43
  const internalEnumerableCsnProps = {
43
44
  __proto__: null,
@@ -170,6 +170,7 @@
170
170
  // any reference in the entity is inspected: with data for the query
171
171
  // hierarchy, query number, table aliases and links from a column to its
172
172
  // respective inferred element.
173
+ // - TODO: some `name` property would also be useful (set with `initDefinition`)
173
174
 
174
175
  // Properties in cache:
175
176
  //
@@ -1179,10 +1179,6 @@ function applyAnnotationsFromExtensions( csn, config ) {
1179
1179
  }
1180
1180
  }
1181
1181
 
1182
- function isAspect( node ) {
1183
- return node && node.kind === 'aspect';
1184
- }
1185
-
1186
1182
  /**
1187
1183
  * Return true if the artifact has a valid, truthy persistence.exists/skip annotation
1188
1184
  *
@@ -1446,7 +1442,6 @@ module.exports = {
1446
1442
  normalizeTypeRef,
1447
1443
  copyAnnotations,
1448
1444
  copyAnnotationsAndDoc,
1449
- isAspect,
1450
1445
  hasValidSkipOrExists,
1451
1446
  getNamespace,
1452
1447
  getServiceNames,
@@ -91,10 +91,10 @@ function getFilterObject( options, dialect, extensionCallback, migrationCallback
91
91
  if (migration.new.type === migration.old.type && migration.new.length < migration.old.length) {
92
92
  raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-length-change', id => message(id, loc, { '#': messageVariant, id: name }));
93
93
  }
94
- else if (migration.new.type === migration.old.type && migration.new.scale !== migration.old.scale) {
94
+ else if (migration.new.type === migration.old.type && migration.new.scale < migration.old.scale || migration.new.precision - migration.old.precision < migration.new.scale - migration.old.scale) {
95
95
  raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-scale-change', id => message(id, loc, { '#': messageVariant, id: name }));
96
96
  }
97
- else if (migration.new.type === migration.old.type && migration.new.precision !== migration.old.scale) {
97
+ else if (migration.new.type === migration.old.type && migration.new.precision < migration.old.precision) {
98
98
  raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-precision-change', id => message(id, loc, { '#': messageVariant, id: name }));
99
99
  }
100
100
  else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type)) {
@@ -580,6 +580,7 @@ optionProcessor.command('forEffective')
580
580
  .option('--resolve-projections <val>', { valid: ['true', 'false'] } )
581
581
  .option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
582
582
  .option('--keep-localized <val>', { valid: ['true', 'false'] } )
583
+ .option('--effective-service-name <name>')
583
584
  .positionalArgument('<files...>')
584
585
  .help(`
585
586
  Usage: cdsc forEffective [options] <files...>
@@ -602,6 +603,7 @@ optionProcessor.command('forEffective')
602
603
  --keep-localized <val> Keep '.localized' property in the CSN:
603
604
  true: property is kept
604
605
  false:(default) property is deleted
606
+ --effective-service-name <name> Filter the output CSN to only contain the given service
605
607
  `);
606
608
 
607
609
  optionProcessor.command('forSeal')
@@ -48,7 +48,7 @@ const PRECEDENCE_OF_IN_PREDICATE = 10;
48
48
  const PRECEDENCE_OF_EQUAL = 10;
49
49
 
50
50
  class AstBuildingParser extends BaseParser {
51
- leanConditions = { afterBrace: true, fail: true };
51
+ leanConditions = { afterBrace: true, atRightParen: true, fail: true };
52
52
 
53
53
  constructor( lexer, keywords, table, options, messageFunctions ) {
54
54
  super( lexer, keywords, table ); // lexer has file
@@ -171,7 +171,7 @@ class AstBuildingParser extends BaseParser {
171
171
  this.dynamic_.generic = spec ? spec[call.argPos] : specialFunctions[''][1];
172
172
  }
173
173
 
174
- lGenericIntroOrExpr( tryGenericIntro = true ) {
174
+ lGenericIntroOrExpr( _mode, tryGenericIntro = true ) {
175
175
  const { keyword, type } = this.la();
176
176
  // TODO: use lower-case in specialFunctions
177
177
  const text = typeof keyword === 'string' ? keyword.toUpperCase() : type;
@@ -180,37 +180,44 @@ class AstBuildingParser extends BaseParser {
180
180
  if (this.dynamic_.generic?.IN === 'separator')
181
181
  this.prec_ = PRECEDENCE_OF_IN_PREDICATE; // only expressions if `in` is separator
182
182
  if (generic !== 'expr')
183
- return (generic === 'intro') ? 'GenericIntro' : 'Id';
183
+ return (generic === 'intro') ? 'GenericIntro' : type;
184
+ // if both intro and expr: specialFunctions[fn][argPos][token] = 'expr'
184
185
  const next = this.tokens[this.tokenIdx + 1];
185
- if (next.type !== ',' && next.type !== ')' &&
186
+ if (next && next.type !== ',' && next.type !== ')' &&
186
187
  this.dynamic_.generic[next.keyword?.toUpperCase?.()] !== 'separator')
187
188
  return 'GenericIntro';
188
189
  }
189
- return (generic === 'expr') ? 'GenericExpr' : 'Id';
190
+ return (generic === 'expr') ? 'GenericExpr' : type;
190
191
  }
191
192
 
192
193
  lGenericExpr() {
193
- return this.lGenericIntroOrExpr( false );
194
+ return this.lGenericIntroOrExpr( null, false );
194
195
  }
195
196
 
196
- lGenericSeparator() { // TODO: { keyword, type } as arg ?
197
+ lGenericSeparator() {
197
198
  const { keyword, type } = this.la();
198
199
  // TODO: use lower-case in specialFunctions
199
200
  const text = typeof keyword === 'string' ? keyword.toUpperCase() : type;
200
201
  const generic = this.dynamic_.generic?.[text];
201
- return (generic === 'separator') ? 'GenericSeparator' : ',';
202
+ return (generic === 'separator') ? 'GenericSeparator' : type;
202
203
  }
203
204
 
204
205
  addTokenToSet_( set, tokenName, val, collectKeywordsOnly ) {
205
- const realTokens = parserTokens[tokenName] && this.dynamic_.generic?.[parserTokens[tokenName]];
206
- // TODO: avoid 2nd parserTokens dict use, use lower-case in specialFunctions
207
- if (!realTokens) {
208
- super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
209
- }
210
- else {
206
+ const token = parserTokens[tokenName];
207
+ // TODO: use lower-case in specialFunctions
208
+ const realTokens = token && this.dynamic_.generic?.[token];
209
+ if (realTokens) {
211
210
  for (const t of realTokens)
212
211
  super.addTokenToSet_( set, t.toLowerCase(), val, collectKeywordsOnly );
213
212
  }
213
+ else if (tokenName === 'DeleteStarFromSet') { // in rule `argumentsAndFilter`
214
+ // TODO: workaround for (`GenericExpr : Id_all | '*'`), see #13485.
215
+ // Works, since `DeleteStarFromSet` comes after `*` (length-sorted):
216
+ delete set['*'];
217
+ }
218
+ else {
219
+ super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
220
+ }
214
221
  }
215
222
 
216
223
  inSelectItem( _test, arg ) { // only as action
@@ -334,7 +341,7 @@ class AstBuildingParser extends BaseParser {
334
341
  }
335
342
 
336
343
  /**
337
- * Prepare element restrictions and check validility of final anno assignments.
344
+ * Prepare element restrictions and check validity of final anno assignments.
338
345
  *
339
346
  * Called as <prepare=…>:
340
347
  *
@@ -432,27 +439,49 @@ class AstBuildingParser extends BaseParser {
432
439
  * `;` between statements is optional only after a `}` (ex braces of structure
433
440
  * values for annotations and foreign key specifications).
434
441
  *
442
+ * Unfortunate exception: always optional after `entity … as projection on`.
443
+ *
435
444
  * Beware: mentioned in leanConditions, i.e. executed in predictions!
436
445
  */
437
446
  afterBrace( test, arg ) {
438
447
  if (!test) {
439
- this.afterBrace$
440
- = (!arg || this.afterBrace$ > 0 && this.tokens[this.afterBrace$ - 1].keyword === arg)
441
- ? this.tokenIdx
442
- : -1;
448
+ if (arg === 'normal' && this.lb().type !== '}') {
449
+ const { type, keyword } = this.la();
450
+ if (type !== ';' && type !== '}' && type !== 'EOF' && keyword !== 'actions') {
451
+ const prev = this.lb().location;
452
+ const loc = new Location( prev.file, prev.endLine, prev.endCol );
453
+ this.warning( 'syntax-missing-proj-semicolon', loc,
454
+ { expecting: [ "';'" ], offending: this.antlrName( this.la() ) },
455
+ 'Missing $(EXPECTING) before $(OFFENDING)');
456
+ }
457
+ }
458
+ // with arg 'init' (used in rule `start`/`artifactsBlock`), and arg 'sloppy'
459
+ // or 'normal' (used in rule `entityDef`), set marker in dynamic context:
460
+ if (!arg)
461
+ this.afterBrace$ = this.tokenIdx;
462
+ else if (arg === 'init')
463
+ this.dynamic_.sloppySemicolon$ = [ false ];
464
+ else if (arg === 'sloppy' || this.la().keyword === 'actions')
465
+ this.dynamic_.sloppySemicolon$[0] = (arg === 'sloppy');
466
+ return null;
443
467
  }
444
468
  // TODO TOOL: the following test belongs to the BaseParser.js:
445
469
  if (this.conditionTokenIdx === this.tokenIdx && // tested on same
446
470
  this.conditionStackLength == null && // after error recover
447
471
  test !== 'M')
448
472
  return false;
449
- // Strange optional `;` after PROJECTION ON source: the rule exit prediction
450
- // for fromRefWithOptAlias etc now checks C(afterBrace):
451
- if (test === 'E' && this.afterBrace$ > 0 &&
452
- this.tokens[this.afterBrace$ - 1]?.keyword === 'projection' &&
453
- this.tokens[this.afterBrace$].keyword === 'on')
454
- return false;
455
- return this.afterBrace$ !== this.tokenIdx;
473
+ const { sloppySemicolon$ } = this.dynamic_;
474
+ if (!sloppySemicolon$?.[0])
475
+ return this.afterBrace$ !== this.tokenIdx;
476
+ if (test === true && sloppySemicolon$) // TODO: single-let mode for running parser
477
+ sloppySemicolon$[0] = false;
478
+ return this.afterBrace$ !== this.tokenIdx && test === 'M';
479
+ // TODO: should we always fail for expected set (test === 'M'), at least if
480
+ // token is not on a new line?
481
+ }
482
+
483
+ atRightParen() {
484
+ return this.l() !== ')';
456
485
  }
457
486
 
458
487
  /**
@@ -731,14 +760,15 @@ class AstBuildingParser extends BaseParser {
731
760
  }
732
761
 
733
762
  taggedIfQuery( query ) {
734
- return (query.op && queryOps[query.op.val])
763
+ // attached actions are run even if rules ends prematurely → query can be
764
+ // undefined
765
+ return (query?.op && queryOps[query.op.val])
735
766
  ? { query, location: query.$parens?.at( -1 ) ?? query.location }
736
767
  : query;
737
768
  }
738
769
 
739
- addNamedArg( args, idToken, expr ) {
740
- expr.name = this.identAst( idToken );
741
- (args.args ?? args)[expr.name.id] = expr;
770
+ addNamedArg( pathItem, idToken, expr ) {
771
+ this.addDef( expr, pathItem, 'args', 0, this.identAst( idToken ) );
742
772
  }
743
773
 
744
774
  ixprAst( args ) {
@@ -1096,7 +1126,6 @@ class AstBuildingParser extends BaseParser {
1096
1126
  }
1097
1127
 
1098
1128
  // make sure that the parens of `IN (…)` do not disappear:
1099
- // TODO: make this a to-csn thing
1100
1129
  secureParens( expr ) {
1101
1130
  const op = expr?.op?.val;
1102
1131
  const $parens = expr?.$parens;
@@ -1155,6 +1184,10 @@ class AstBuildingParser extends BaseParser {
1155
1184
  // TODO: we could have an opt(?) parameter funcToken for speed-up (passing this.lr())
1156
1185
  if (funcToken)
1157
1186
  funcToken.parsedAs = 'func';
1187
+
1188
+ const filter = path[0].cardinality || path[0].where; // XSN TODO: filter$location
1189
+ if (filter) // TODO v7: make this be reported via guard, as error
1190
+ this.message( 'syntax-unexpected-filter', filter.location, {} );
1158
1191
  // TODO: XSN representation of functions is a bit strange - rework
1159
1192
  const op = { location, val: 'call' };
1160
1193
  return this.attachLocation( { op, func: ref, args } );
@@ -1171,13 +1204,18 @@ class AstBuildingParser extends BaseParser {
1171
1204
  // Everything after the first function is also a function, and not a reference.
1172
1205
 
1173
1206
  for (let i = firstFunc; i < path.length; ++i) {
1174
- if (path[i].args && path[i].$syntax === ':') {
1207
+ const item = path[i];
1208
+ if (item.args && item.$syntax === ':') {
1175
1209
  // Error for `a(P => 1).b.c(P: 1)`: no ref after function.
1176
- this.error( 'syntax-invalid-ref', path[i].args[$location], {
1210
+ // TODO v6: make this be reported via guard
1211
+ this.error( 'syntax-invalid-ref', item.args[$location], {
1212
+ // TODO: msg text - huh? → syntax-invalid-named-arg ?
1177
1213
  code: '=>',
1178
1214
  }, 'References after function calls can\'t be resolved. Use $(CODE) in function arguments');
1179
- break;
1180
1215
  }
1216
+ const filter = item.cardinality || item.where; // XSN TODO: filter$location
1217
+ if (filter) // TODO v7: make this be reported via guard, as error
1218
+ this.message( 'syntax-unexpected-filter', filter.location, {} );
1181
1219
  }
1182
1220
 
1183
1221
  const args = [];