@sap/cds-compiler 5.9.2 → 6.0.10

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 (111) hide show
  1. package/CHANGELOG.md +109 -319
  2. package/README.md +1 -1
  3. package/bin/cds_update_identifiers.js +3 -5
  4. package/bin/cdsc.js +22 -8
  5. package/bin/cdshi.js +1 -1
  6. package/bin/cdsse.js +4 -4
  7. package/doc/CHANGELOG_BETA.md +11 -0
  8. package/doc/CHANGELOG_DEPRECATED.md +29 -0
  9. package/lib/api/main.js +8 -5
  10. package/lib/api/options.js +12 -10
  11. package/lib/base/builtins.js +1 -0
  12. package/lib/base/message-registry.js +190 -96
  13. package/lib/base/messages.js +29 -20
  14. package/lib/base/model.js +14 -24
  15. package/lib/checks/actionsFunctions.js +10 -20
  16. package/lib/checks/annotationsOData.js +1 -1
  17. package/lib/checks/elements.js +30 -10
  18. package/lib/checks/enums.js +31 -0
  19. package/lib/checks/foreignKeys.js +2 -2
  20. package/lib/checks/hasPersistedElements.js +5 -0
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/managedWithoutKeys.js +5 -4
  23. package/lib/checks/queryNoDbArtifacts.js +10 -8
  24. package/lib/checks/types.js +5 -5
  25. package/lib/checks/validator.js +6 -4
  26. package/lib/compiler/assert-consistency.js +12 -9
  27. package/lib/compiler/checks.js +18 -50
  28. package/lib/compiler/define.js +6 -6
  29. package/lib/compiler/extend.js +2 -1
  30. package/lib/compiler/generate.js +14 -17
  31. package/lib/compiler/populate.js +8 -31
  32. package/lib/compiler/propagator.js +21 -35
  33. package/lib/compiler/resolve.js +35 -22
  34. package/lib/compiler/shared.js +7 -1
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +1 -1
  37. package/lib/edm/annotations/edmJson.js +20 -15
  38. package/lib/edm/annotations/genericTranslation.js +7 -8
  39. package/lib/edm/csn2edm.js +46 -50
  40. package/lib/edm/edm.js +8 -7
  41. package/lib/edm/edmPreprocessor.js +37 -85
  42. package/lib/edm/edmUtils.js +2 -2
  43. package/lib/gen/BaseParser.js +55 -44
  44. package/lib/gen/CdlGrammar.checksum +1 -1
  45. package/lib/gen/CdlParser.js +1133 -1150
  46. package/lib/json/from-csn.js +70 -43
  47. package/lib/json/to-csn.js +6 -8
  48. package/lib/language/multiLineStringParser.js +3 -2
  49. package/lib/main.d.ts +58 -24
  50. package/lib/model/csnUtils.js +28 -39
  51. package/lib/model/xprAsTree.js +23 -9
  52. package/lib/modelCompare/compare.js +5 -4
  53. package/lib/optionProcessor.js +21 -17
  54. package/lib/parsers/AstBuildingParser.js +63 -11
  55. package/lib/parsers/XprTree.js +57 -3
  56. package/lib/parsers/identifiers.js +1 -1
  57. package/lib/parsers/index.js +0 -3
  58. package/lib/render/manageConstraints.js +25 -25
  59. package/lib/render/toCdl.js +173 -170
  60. package/lib/render/toHdbcds.js +126 -128
  61. package/lib/render/toRename.js +7 -7
  62. package/lib/render/toSql.js +128 -125
  63. package/lib/render/utils/common.js +47 -22
  64. package/lib/render/utils/delta.js +25 -25
  65. package/lib/render/utils/operators.js +2 -2
  66. package/lib/render/utils/pretty.js +5 -5
  67. package/lib/render/utils/sql.js +13 -13
  68. package/lib/render/utils/standardDatabaseFunctions.js +115 -103
  69. package/lib/render/utils/unique.js +4 -4
  70. package/lib/transform/db/applyTransformations.js +1 -1
  71. package/lib/transform/db/assertUnique.js +2 -2
  72. package/lib/transform/db/associations.js +6 -7
  73. package/lib/transform/db/assocsToQueries/utils.js +4 -5
  74. package/lib/transform/db/backlinks.js +12 -9
  75. package/lib/transform/db/cdsPersistence.js +8 -7
  76. package/lib/transform/db/constraints.js +13 -10
  77. package/lib/transform/db/expansion.js +7 -3
  78. package/lib/transform/db/flattening.js +4 -14
  79. package/lib/transform/db/processSqlServices.js +2 -1
  80. package/lib/transform/db/temporal.js +5 -7
  81. package/lib/transform/db/views.js +2 -4
  82. package/lib/transform/draft/db.js +8 -8
  83. package/lib/transform/draft/odata.js +10 -7
  84. package/lib/transform/forOdata.js +10 -5
  85. package/lib/transform/forRelationalDB.js +5 -75
  86. package/lib/transform/localized.js +1 -1
  87. package/lib/transform/odata/createForeignKeys.js +11 -10
  88. package/lib/transform/odata/flattening.js +8 -4
  89. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
  90. package/lib/transform/odata/typesExposure.js +3 -3
  91. package/lib/transform/transformUtils.js +4 -8
  92. package/lib/transform/translateAssocsToJoins.js +14 -7
  93. package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
  94. package/lib/utils/objectUtils.js +0 -17
  95. package/package.json +10 -13
  96. package/share/messages/def-upcoming-virtual-change.md +1 -1
  97. package/LICENSE +0 -37
  98. package/bin/cds_remove_invalid_whitespace.js +0 -138
  99. package/doc/CHANGELOG_ARCHIVE.md +0 -3604
  100. package/lib/gen/genericAntlrParser.js +0 -3
  101. package/lib/gen/language.checksum +0 -1
  102. package/lib/gen/language.interp +0 -456
  103. package/lib/gen/language.tokens +0 -180
  104. package/lib/gen/languageLexer.interp +0 -439
  105. package/lib/gen/languageLexer.js +0 -1483
  106. package/lib/gen/languageLexer.tokens +0 -167
  107. package/lib/gen/languageParser.js +0 -24941
  108. package/lib/language/antlrParser.js +0 -205
  109. package/lib/language/errorStrategy.js +0 -646
  110. package/lib/language/genericAntlrParser.js +0 -1572
  111. package/lib/parsers/CdlGrammar.g4 +0 -2070
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  /* eslint max-statements-per-line:off */
4
- const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
4
+ const { setProp, isBetaEnabled } = require('../base/model');
5
5
  const {
6
6
  forEachDefinition, forEachGeneric, forEachMemberRecursively,
7
7
  isEdmPropertyRendered, getUtils,
8
- applyTransformations, transformAnnotationExpression, findAnnotationExpression,
8
+ applyTransformations, applyTransformationsOnNonDictionary,
9
+ transformAnnotationExpression, findAnnotationExpression,
9
10
  cardinality2str,
10
11
  } = require('../model/csnUtils');
11
12
  const { isBuiltinType, isMagicVariable } = require('../base/builtins');
@@ -322,6 +323,15 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
322
323
  if (node.$path && dotEntityNameMap[node.$path[1]])
323
324
  node.$path[1] = dotEntityNameMap[node.$path[1]];
324
325
 
326
+ if (node.projection || node.query) {
327
+ applyTransformationsOnNonDictionary(node.projection || node.query, undefined, {
328
+ ref: (parent, prop, ref) => {
329
+ if (dotEntityNameMap[ref[0]])
330
+ parent.ref[0] = dotEntityNameMap[ref[0]];
331
+ },
332
+ }, { directDict: true });
333
+ }
334
+
325
335
  rewriteReferencesInActions(node);
326
336
  };
327
337
 
@@ -518,7 +528,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
518
528
  // create the Type Definition
519
529
  // modify the original parameter entity with backlink and new name
520
530
  if (csn.definitions[typeEntityName])
521
- error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: typeEntityName });
531
+ error('odata-duplicate-definition', [ 'definitions', entityName ], { '#': 'std', name: typeEntityName });
522
532
  else
523
533
  entityCsn.name = typeEntityName;
524
534
  setProp(entityCsn, '$entitySetName', typeEntitySetName);
@@ -649,7 +659,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
649
659
  // proxies are registered in model separately
650
660
  if (!isProxy) {
651
661
  if (csn.definitions[parameterCsn.name]) {
652
- error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
662
+ error('odata-duplicate-definition', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
653
663
  }
654
664
  else {
655
665
  csn.definitions[parameterCsn.name] = parameterCsn;
@@ -694,7 +704,8 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
694
704
  if (!edmUtils.isStructuredArtifact(def))
695
705
  return;
696
706
 
697
- let keys = Object.create(null);
707
+ const keys = Object.create(null);
708
+ // eslint-disable-next-line
698
709
  const validFrom = [];
699
710
  const validKey = [];
700
711
 
@@ -741,38 +752,11 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
741
752
  edmAnnoPreproc.applyAppSpecificLateCsnTransformationOnElement(options, element, def, error);
742
753
  }, [], true, { elementsOnly: true });
743
754
 
744
- if (!isDeprecatedEnabled(options, '_v1KeysForTemporal')) {
745
- // if artifact has a cds.valid.key mention it as @Core.AlternateKey
746
- if (validKey.length) {
747
- const altKeys = [ { Key: [] } ];
748
- validKey.forEach(vk => altKeys[0].Key.push( { Name: vk.name, Alias: vk.name } ) );
749
- edmUtils.assignAnnotation(def, '@Core.AlternateKeys', altKeys);
750
- }
751
- }
752
- else if (validKey.length) {
753
- // if artifact has a cds.valid.key make this the only primary key and
754
- // add all @cds.valid.from + original primary keys as alternate keys
755
- // @Core.AlternateKeys: [{ Key: [ { Name: 'slID', Alias: 'slID' }, { Name: 'validFrom', Alias: 'validFrom'} ] }]
755
+ // if artifact has a cds.valid.key mention it as @Core.AlternateKey
756
+ if (validKey.length) {
756
757
  const altKeys = [ { Key: [] } ];
757
- Object.entries(([ kn, k ]) => {
758
- altKeys[0].Key.push( { Name: kn, Alias: kn } );
759
- delete k.key;
760
- });
761
- validFrom.forEach((e) => {
762
- altKeys[0].Key.push( { Name: e.name, Alias: e.name } );
763
- });
758
+ validKey.forEach(vk => altKeys[0].Key.push( { Name: vk.name, Alias: vk.name } ) );
764
759
  edmUtils.assignAnnotation(def, '@Core.AlternateKeys', altKeys);
765
- keys = Object.create(null);
766
- validKey.forEach((e) => {
767
- e.key = true;
768
- keys[e.name] = e;
769
- });
770
- }
771
- else {
772
- validFrom.forEach((e) => {
773
- e.key = true;
774
- keys[e.name] = e;
775
- });
776
760
  }
777
761
 
778
762
  // prepare the structure itself
@@ -1040,7 +1024,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1040
1024
  else if (!assocOK) {
1041
1025
  // if there is already a proxy (generated by a 'good' association)
1042
1026
  // and this association is not a good one, don't expose this association.
1043
- muteNavProp(element, 'oncond');
1027
+ muteNavProp(element);
1044
1028
  return;
1045
1029
  }
1046
1030
  if (proxy) {
@@ -1060,7 +1044,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1060
1044
  }
1061
1045
  }
1062
1046
  else {
1063
- muteNavProp(element, assocOK ? 'std' : 'oncond');
1047
+ muteNavProp(element);
1064
1048
  return;
1065
1049
  }
1066
1050
  }
@@ -1072,12 +1056,10 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1072
1056
  });
1073
1057
  }
1074
1058
 
1075
- function muteNavProp( elt, msg = 'std' ) {
1059
+ function muteNavProp( elt ) {
1076
1060
  edmUtils.assignAnnotation(elt, '@odata.navigable', false);
1077
- if (elt._target['@cds.autoexpose'] !== false) {
1078
- warning('odata-navigation', elt.$path,
1079
- { target: elt._target.name, service: globalSchemaPrefix, '#': msg });
1080
- }
1061
+ if (elt._target['@cds.autoexpose'] !== false)
1062
+ info('odata-navigation', elt.$path, { target: elt._target.name, service: globalSchemaPrefix });
1081
1063
  }
1082
1064
 
1083
1065
  function createSchemaRefFor( targetSchemaName ) {
@@ -1152,10 +1134,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1152
1134
  if (!newElt) {
1153
1135
  if (csnUtils.isAssocOrComposition(e)) {
1154
1136
  if (!e.on && e.keys) {
1155
- if (options.odataNoTransitiveProxies)
1156
- newElt = convertManagedAssocIntoStruct(e);
1157
- else
1158
- newElt = createProxyOrSchemaRefForManagedAssoc(e);
1137
+ newElt = createProxyOrSchemaRefForManagedAssoc(e);
1159
1138
  }
1160
1139
  else {
1161
1140
  info(null, [ 'definitions', struct.name, 'elements', assoc.name ],
@@ -1306,7 +1285,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1306
1285
  });
1307
1286
  }
1308
1287
  else if (elem.keys && !elem.on) {
1309
- // a primary key can never be an unmanaged association
1288
+ // a primary key can never be an unmanaged association
1310
1289
  type.elements[elemName] = createProxyOrSchemaRefForManagedAssoc(elem);
1311
1290
  }
1312
1291
  if (forceToNotNull) {
@@ -1316,7 +1295,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1316
1295
  newElt.cardinality = {};
1317
1296
  newElt.cardinality.min = 1;
1318
1297
  }
1319
- // if odata-spec-violation-key-null is checking on min>1, this can be an else
1298
+ // if odata-unexpected-nullable-key is checking on min>1, this can be an else
1320
1299
  newElt.notNull = true;
1321
1300
  }
1322
1301
  setProp(type.elements[elemName], 'name', elem.name);
@@ -1326,35 +1305,6 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1326
1305
  }
1327
1306
  }
1328
1307
 
1329
- // Convert a managed association into a structured type and
1330
- // eliminate nested foreign key associations
1331
- function convertManagedAssocIntoStruct( e ) {
1332
- const newElt = cloneCsnNonDict(e, options);
1333
- newElt.elements = Object.create(null);
1334
- // remove all unwanted garbage
1335
- delete newElt.keys;
1336
- delete newElt.target;
1337
- delete newElt.type;
1338
- // if this association has no keys or if it is a redirected parameterized entity,
1339
- // use the primary keys of the target
1340
- const keys = (!e._target.$isParamEntity && e.keys) ||
1341
- Object.keys(e._target.$keys).map(k => ({ ref: [ k ] }));
1342
- keys.forEach((k) => {
1343
- let art = e._target || csnUtils.getCsnDef(e.target);
1344
- for (const ps of k.ref)
1345
- art = art.elements[ps];
1346
-
1347
- // art is in the target side, clone it and remove key property
1348
- const cloneArt = cloneCsnNonDict(art, options);
1349
- setProp(cloneArt, 'name', art.name);
1350
- if (e.key)
1351
- cloneArt.notNull = true;
1352
- delete cloneArt.key;
1353
- newElt.elements[art.name] = cloneArt;
1354
- });
1355
- return newElt;
1356
- }
1357
-
1358
1308
  // create a new element and wire the proxy as new target.
1359
1309
  // Create a new proxy if:
1360
1310
  // 1) source and target schema names are different (otherwise)
@@ -1509,8 +1459,8 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1509
1459
  }
1510
1460
  else if (alreadyRegistered && !alreadyRegistered.$proxy &&
1511
1461
  alreadyRegistered.kind !== 'entity') {
1512
- warning('odata-definition-exists', [ 'definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name ],
1513
- { '#': 'proxy', name: fqProxyName, kind: alreadyRegistered.kind });
1462
+ warning('odata-duplicate-proxy', [ 'definitions', proxy.$origin._parent.name, 'elements', proxy.$origin.name ],
1463
+ { name: fqProxyName, kind: alreadyRegistered.kind });
1514
1464
  }
1515
1465
  }
1516
1466
  else if (!schemas[fqSchemaName]) {
@@ -1639,7 +1589,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1639
1589
  else if (eltCsn.target && !eltCsn.on) {
1640
1590
  // if this association has no keys or if it is a redirected parameterized entity,
1641
1591
  // use the primary keys of the target
1642
- const keys = (!eltCsn._target.$isParamEntity && eltCsn.keys) ||
1592
+ const keys = (!eltCsn._target.$isParamEntity && !eltCsn.on && (eltCsn.keys ?? [])) ||
1643
1593
  Object.keys(eltCsn._target.$keys).map(k => ({ ref: [ k ] }));
1644
1594
  let pathSegment = prefix;
1645
1595
  keys.forEach((k) => {
@@ -1668,7 +1618,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1668
1618
  const eltDef = elt.items || elt;
1669
1619
  if ((!elt.key && (eltDef.notNull === undefined || eltDef.notNull === false)) ||
1670
1620
  elt.key && (eltDef.notNull !== undefined && eltDef.notNull === false)) {
1671
- message('odata-spec-violation-key-null', location,
1621
+ message('odata-unexpected-nullable-key', location,
1672
1622
  { name: pathSegment || elt.name, '#': !pathSegment ? 'std' : 'scalar' });
1673
1623
  }
1674
1624
  // many is either directly on elements or on the type
@@ -1682,7 +1632,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1682
1632
  (options.odataFormat !== 'flat' && !options.odataForeignKeys) &&
1683
1633
  elt.cardinality?.max && elt.cardinality.max !== 1) {
1684
1634
  // many primary key can be induced by a many parameter of a view
1685
- message('odata-spec-violation-key-array', location,
1635
+ message('odata-unexpected-arrayed-key', location,
1686
1636
  {
1687
1637
  name: pathSegment || elt.name,
1688
1638
  value: cardinality2str(elt),
@@ -1714,7 +1664,7 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
1714
1664
  'Edm.TimeOfDay': 1,
1715
1665
  };
1716
1666
  if (!(edmType in legalEdmTypes)) {
1717
- message('odata-spec-violation-key-type', location,
1667
+ message('odata-invalid-key-type', location,
1718
1668
  {
1719
1669
  name: pathSegment, type: type.type, id: edmType, '#': pathSegment ? 'std' : 'scalar',
1720
1670
  });
@@ -2152,9 +2102,11 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
2152
2102
  edmUtils.assignAnnotation(p, '@Core.OptionalParameter.DefaultValue', p.default.val);
2153
2103
  optPns.push(p);
2154
2104
  }
2155
- // nullable action params are optional (implicit default null)
2156
- else if (!p.notNull && action.kind === 'action') {
2105
+ // nullable action/function params are optional (implicit default null)
2106
+ else if (!p.notNull) {
2157
2107
  optPns.push(p);
2108
+ if (action.kind === 'function')
2109
+ p['@Core.OptionalParameter'] = { $Type: '' };
2158
2110
  }
2159
2111
  else if (action.kind === 'function') {
2160
2112
  // this is a mandatory parameter, warn about all previously collected optional parameters
@@ -344,7 +344,7 @@ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
344
344
  const renderedKeys = Object.values(principalEntity.$keys).filter(isConstraintCandidate).map(v => v.name);
345
345
  if (options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length) {
346
346
  if (options.odataV2PartialConstr) {
347
- info('odata-spec-violation-constraints',
347
+ info('odata-incomplete-constraints',
348
348
  [ 'definitions', assocCsn._parent.name, 'elements', assocCsn.name ], { version: '2.0' });
349
349
  }
350
350
  else {
@@ -379,7 +379,7 @@ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
379
379
  const renderedKeys = Object.values(assocCsn._target.$keys).filter(isConstraintCandidate).map(v => v.name);
380
380
  if (options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length) {
381
381
  if (options.odataV2PartialConstr) {
382
- info('odata-spec-violation-constraints',
382
+ info('odata-incomplete-constraints',
383
383
  [ 'definitions', assocCsn._parent.name, 'elements', assocCsn.name ], { version: '2.0' } );
384
384
  }
385
385
  else {
@@ -1,4 +1,4 @@
1
- // Base class for generated parser, for redepage v0.2.1
1
+ // Base class for generated parser, for redepage v0.2.3
2
2
 
3
3
  'use strict';
4
4
 
@@ -110,7 +110,7 @@ class BaseParser {
110
110
 
111
111
  e() { // error: report and recover
112
112
  const la = this.tokens[this.tokenIdx];
113
- this._trace( 'detect parsing error,' );
113
+ this._trace( 'detect parsing error' );
114
114
  if (this.errorTokenIdx === this.tokenIdx)
115
115
  throw Error( `Already reported error for ${ tokenFullName( la ) } at ${ la.location }`);
116
116
 
@@ -134,7 +134,7 @@ class BaseParser {
134
134
  this.reportUnexpectedToken_();
135
135
  const syncSet = this._calculateTokenSet( 'Y' );
136
136
  const recoverDepth = this._findSyncToken( syncSet );
137
- this._trace( 'recover from error,' );
137
+ this._trace( 'recover from error' );
138
138
  this._recoverFromError( recoverDepth );
139
139
  return false;
140
140
  }
@@ -181,7 +181,7 @@ class BaseParser {
181
181
  return true;
182
182
  }
183
183
 
184
- // instead of gi() for `Id_all`
184
+ // instead of gi() for `Id<greedy>`
185
185
  giA( state, follow ) { // go to state (after trying to test again as identifier)
186
186
  if (!this.tokens[this.tokenIdx].keyword) // lk() had directly returned the type
187
187
  return this.g( state, follow );
@@ -200,9 +200,7 @@ class BaseParser {
200
200
  return false; // do not execute action after it
201
201
  }
202
202
 
203
- // instead of gi() at rule end (RuleEnd_ in follow-set) for `Id_restricted`
204
- // TODO: investigate why this is just used once - should be for all no-`as`
205
- // cases
203
+ // instead of gi() at rule end (RuleEnd_ in follow-set) for `Id<weak>`, TODO: delete
206
204
  giR( state, follow ) { // go to state (after trying to test again as identifier)
207
205
  const { keyword } = this.tokens[this.tokenIdx];
208
206
  if (!keyword || this.keywords[keyword])
@@ -227,14 +225,14 @@ class BaseParser {
227
225
  : this.e();
228
226
  }
229
227
 
230
- // instead of m() for identifiers via `Id` or `Id_restricted`
228
+ // instead of m() for identifiers via `Id` or `Id<weak>`
231
229
  mi( state, ident = true ) { // match identifier token
232
230
  return (this.tokens[this.tokenIdx].type === 'Id')
233
231
  ? this.ci( state, ident )
234
232
  : this.e();
235
233
  }
236
234
 
237
- // instead of mi() for `Id_all`
235
+ // instead of mi() for `Id<greedy>`
238
236
  miA( state, ident = true ) { // match identifier token
239
237
  return (this.tokens[this.tokenIdx].type === 'Id')
240
238
  ? this.ciA( state, ident )
@@ -254,7 +252,7 @@ class BaseParser {
254
252
  this.s = state;
255
253
  this.errorState = state;
256
254
  if (this.constructor.tracingParser)
257
- this._trace( `consume ${ tokenFullName( la, ' as ' ) },`, la );
255
+ this._trace( `consume ${ tokenFullName( la, ' as ' ) }`, la );
258
256
  return true;
259
257
  }
260
258
 
@@ -271,7 +269,7 @@ class BaseParser {
271
269
  return this.c( state, ident )
272
270
  }
273
271
 
274
- // instead of ci() for `Id_all`, used both with l() and lk()
272
+ // instead of ci() for `Id<greedy>`, used both with l() and lk()
275
273
  ciA( state, ident = 'ident' ) { // consume identifier token, the "All" variant
276
274
  return this.c( state, ident )
277
275
  }
@@ -286,7 +284,7 @@ class BaseParser {
286
284
  return this.lP( first2 ) && this.ck( state );
287
285
  }
288
286
 
289
- // for parser token or token set via `/`
287
+ // for parser token or token set via `/` -> cx() ?
290
288
  ckA( state ) {
291
289
  // if it really should be considered an Id, `set this.la().parsedAs` yourself
292
290
  return this.c( state, (this.l() === 'Id' ? 'keyword' : 'token') );
@@ -316,7 +314,7 @@ class BaseParser {
316
314
  }
317
315
  // calling the condition might have side effects (precendence conditions have)
318
316
  // → call tracing “name” before
319
- const fail = this[cond]( true, arg );
317
+ const fail = this[cond]( true, arg ); // TODO: use single-letter for run?
320
318
  if (this.constructor.tracingParser)
321
319
  this._traceSubPush( !fail );
322
320
  // The default case must not have actions. If written in grammar with action,
@@ -379,7 +377,7 @@ class BaseParser {
379
377
  : ' prematurely');
380
378
  const text = immediately ? '⚠ exit rule' : '⏎ exit rule';
381
379
  this.s = caller.followState; // for trace
382
- this._trace( [ text, caller.ruleState, post, this.stack.length + 1 ] )
380
+ this._trace( [ text, caller.ruleState, post, 'back to' ] )
383
381
  if (immediately && this.stack.at(-1)?.followState != null)
384
382
  this.trace = [ this.errorState ]; // show last good state in trace
385
383
  }
@@ -434,13 +432,14 @@ class BaseParser {
434
432
  if (typeof cmd[0] !== 'number') // don't skip push to state with rule call
435
433
  this.s = cmd[1];
436
434
  if (cmd[0] !== (lk1 ? 'ck' : 'ci')) { // make the std case fast
435
+ // TODO: also not with lean condition
437
436
  let match1 = this._pred_next( 'Id', lk1, 'P' ); // TODO: really P for I?
438
437
  if (!match1) {
439
438
  if (lk1 || match1 === false) // assert for correct code generation
440
439
  throw Error( `Cannot match first prediction token in rule at state ${ saved.s }` );
441
440
  if (match1 == null) {
442
441
  this._traceSubPush( 0 ); // TODO: make _pred_next push this
443
- match1 = this._matchesInFollow( 'Id', lk1, 'I' );
442
+ match1 = this._matchesInFollow( 'Id', lk1, 'I' ); // TODO: 'I'?
444
443
  }
445
444
  else {
446
445
  this._traceSubPush( false );
@@ -471,7 +470,23 @@ class BaseParser {
471
470
 
472
471
  // Standard weak-conflict predicate -------------------------------------------
473
472
 
473
+ /**
474
+ * Return whether current token (its type and keyword are args - TODO delete?)
475
+ * would be matched when starting at the current state:
476
+ * - true/false are definite answers,
477
+ * - null: reached end-of-rule (let caller decide what to do).
478
+ *
479
+ * Changes by side-effect:
480
+ * - this.s
481
+ * - with mode='P' (first step in keyword prediction) if a rule is called:
482
+ * this.stack, this.dynamic_, this.prec_
483
+ *
484
+ * Conditions are only evaluated with mode='M' (expected set in msgs) or if
485
+ * condition is listed in `this.leanConditions`.
486
+ */
474
487
  _pred_next( type, keyword, mode ) { // mode = P | K | I | E | R | M
488
+ // TODO mode: really distinguish between K | I | E | R ?
489
+ // Probably not: would not work with caching? → P, P -> F
475
490
  const properCall = (mode === 'P');
476
491
  const lean = (mode !== 'M'); // TODO: extra method with conditions ?
477
492
  // TODO: if false, use condition in this.leanConditions
@@ -496,8 +511,9 @@ class BaseParser {
496
511
  return true;
497
512
  case 'ciA': // TODO: fixKeywordTokenIdx ?
498
513
  return mode !== 'R';
499
- // in the R prediction for optional `Id<reserved>` at rule end, only
514
+ // in the R prediction for optional `Id<weak>` at rule end, only
500
515
  // alternative keyword matches are preferred, not identifier matches
516
+ // TODO: delete this prediction
501
517
  case 'ci':
502
518
  if (!keyword ||
503
519
  !this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx)
@@ -577,7 +593,7 @@ class BaseParser {
577
593
  return !succeed;
578
594
  }
579
595
 
580
- _matchesInFollow( type, keyword, mode ) { // mode = E | R
596
+ _matchesInFollow( type, keyword, mode ) { // mode = E | R and K | I
581
597
  // TODO: now also set stack!
582
598
  const savedState = this.s;
583
599
  // TODO: caching
@@ -588,6 +604,7 @@ class BaseParser {
588
604
  while (match == null && --depth) {
589
605
  this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
590
606
  this.s = this.stack[depth].followState;
607
+ // TODO: this.prec_ ?
591
608
  match = this._pred_next( type, keyword, mode );
592
609
  this._traceSubPush( match == null ? 0 : match === (mode !== 'R') );
593
610
  // successfully matching a keyword in giR() means unsuccessful match as
@@ -700,7 +717,7 @@ class BaseParser {
700
717
  case 'ci': case 'ciA': case 'mi': case 'miA':
701
718
  this.addTokenToSet_( expecting, 'Id', val, false );
702
719
  // TODO: should we do s/th special, such that a reserved word is a sync
703
- // token for Id<all>? Probably not, see also comment in
720
+ // token for Id<greedy>? Probably not, see also comment in
704
721
  // _findSyncToken()
705
722
  break loop;
706
723
  case 'g': case 'gi': case 'e':
@@ -747,7 +764,7 @@ class BaseParser {
747
764
  else if (set[type] === true && !(keyword && this.keywords[keyword]))
748
765
  delete set[type]; // delete Id if Id token or non-reserved keyword
749
766
 
750
- this._trace( 'collect tokens for message,' );
767
+ this._trace( 'collect tokens for message' );
751
768
  const { trace } = this;
752
769
  const saved = this._saveForWalk();
753
770
  const expecting = Object.keys( set )
@@ -765,16 +782,18 @@ class BaseParser {
765
782
  this.recoverTokenIdx = this.tokenIdx;
766
783
  while (this.recoverTokenIdx <= this.eofIndex) {
767
784
  const { keyword, type } = this.tokens[this.recoverTokenIdx];
768
- const tryKeyw = keyword ? syncSet[keyword] : null;
769
- if (tryKeyw != null)
770
- return tryKeyw;
771
- const tryType = syncSet[type];
785
+ let recoverDepth = keyword ? syncSet[keyword] : null;
786
+ if (recoverDepth != null)
787
+ return recoverDepth;
788
+ recoverDepth = syncSet[type];
772
789
  // sync to Id only if in expected set of last good state or if after ';'
773
- if (tryType != null &&
790
+ if (recoverDepth != null &&
774
791
  (type !== 'Id' || (!keyword || !this.keywords[keyword]) &&
775
792
  // reserved words do not match Id in expected-set
776
- (tryType > rewindDepth || this.tokens[this.recoverTokenIdx - 1].type === ';')))
777
- return tryType;
793
+ (recoverDepth > rewindDepth || this.tokens[this.recoverTokenIdx - 1].type === ';')))
794
+ // if (recoverDepth != null &&
795
+ // (this.recoverTokenIdx > this.tokenIdx ||
796
+ return recoverDepth;
778
797
  ++this.recoverTokenIdx;
779
798
  }
780
799
  throw Error( 'EOF must be last in `tokens`' );
@@ -809,7 +828,7 @@ class BaseParser {
809
828
 
810
829
  _skipErrorTokens() {
811
830
  if (this.constructor.tracingParser && this.tokenIdx <= this.recoverTokenIdx) {
812
- this._trace( `skipped ${ this.recoverTokenIdx - this.tokenIdx } tokens to recover from error,`,
831
+ this._trace( `skip ${ this.recoverTokenIdx - this.tokenIdx } tokens to recover from error`,
813
832
  this.tokens[this.recoverTokenIdx] );
814
833
  }
815
834
  while (this.tokenIdx < this.recoverTokenIdx)
@@ -832,7 +851,7 @@ class BaseParser {
832
851
  msg ??= `Unexpected token ${ tokenFullName( token, ': ' ) }`;
833
852
  this.reportError_(
834
853
  token.location, msg + ' - expecting: ' +
835
- this.expectingArray_().map( tokenName ).sort().join( ',' ) );
854
+ this.expectingArray_().map( tokenName ).sort().join( ', ' ) );
836
855
  }
837
856
 
838
857
  reportReservedWord_() {
@@ -875,7 +894,7 @@ class BaseParser {
875
894
  this.log( indent, line, `(${ la })` );
876
895
  return;
877
896
  }
878
- else if (la === 2) {
897
+ else if (la === 2) { // confirming tokens in expected set
879
898
  this.log( indent, ' ', msg + ':',
880
899
  this.trace.map( traceStep ).join( ' → ' ) );
881
900
  this.trace = [ this.s ?? '⚠' ];
@@ -887,31 +906,23 @@ class BaseParser {
887
906
  this.trace = [ -1 ];
888
907
  }
889
908
  this.trace.push( this.s ?? '⚠' );
890
- if (Array.isArray( msg )) {
891
- const [ intro, state, finale, exitLength ] = msg;
892
- let depth = (exitLength) ? exitLength - 1 : this.stack.length + 1;
893
- let length = this.trace.length - 1;
894
- this.trace[length] = `${ this.trace[length] }(${ depth })`;
895
- depth = exitLength || this.stack.length;
896
- while (length && typeof this.trace[--length] !== 'number')
897
- ;
898
- this.trace[length] = `${ this.trace[length] }(${ depth })`;
899
-
909
+ if (Array.isArray( msg )) { // rule call and exit
910
+ const [ intro, state, finale, exit ] = msg;
900
911
  let start = state;
901
912
  while (typeof this.table[--start] !== 'string')
902
913
  ;
903
- const post = (exitLength || start + 1 < state) && finale;
904
- msg = `${ intro } “${ this.table[start] }”${ post || '' },`;
914
+ const post = (exit || start + 1 < state) && finale;
915
+ msg = `${ intro } “${ this.table[start] }”${ post || '' } ${ exit || 'from' } stack level ${ this.stack.length }`;
905
916
  }
906
917
  // Yes, I know util.format, but do not want to have a `require` in this file
907
918
  const line = location.line < 1e5 ? ` ${ location.line }`.slice(-5) : `${ location.line }`;
908
919
  const col = location.col < 1e4 ? `:${ location.col } `.slice(0,5) : `:${location.col }`;
909
- this.log( line + col + indent + msg,
910
- 'states:', this.trace.map( traceStep ).join( ' → ' ) );
920
+ this.log( line + col + indent + msg + ', states:',
921
+ this.trace.map( traceStep ).join( ' → ' ) );
911
922
  this.trace = [ this.s ?? '⚠' ];
912
923
  }
913
924
 
914
- inSameRule_( lowState, highState ) {
925
+ inSameRule_( lowState = this.s, highState = this.stack.at(-1).followState ) {
915
926
  if (lowState > highState)
916
927
  [ lowState, highState ] = [ highState, lowState ];
917
928
  while (lowState < highState) {
@@ -1 +1 @@
1
- 226c8854e08665b3eaae3ae1a1276db7
1
+ 539f33c85849442e96168a85adec09fb