@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.
- package/CHANGELOG.md +109 -319
- package/README.md +1 -1
- package/bin/cds_update_identifiers.js +3 -5
- package/bin/cdsc.js +22 -8
- package/bin/cdshi.js +1 -1
- package/bin/cdsse.js +4 -4
- package/doc/CHANGELOG_BETA.md +11 -0
- package/doc/CHANGELOG_DEPRECATED.md +29 -0
- package/lib/api/main.js +8 -5
- package/lib/api/options.js +12 -10
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +190 -96
- package/lib/base/messages.js +29 -20
- package/lib/base/model.js +14 -24
- package/lib/checks/actionsFunctions.js +10 -20
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +30 -10
- package/lib/checks/enums.js +31 -0
- package/lib/checks/foreignKeys.js +2 -2
- package/lib/checks/hasPersistedElements.js +5 -0
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/managedWithoutKeys.js +5 -4
- package/lib/checks/queryNoDbArtifacts.js +10 -8
- package/lib/checks/types.js +5 -5
- package/lib/checks/validator.js +6 -4
- package/lib/compiler/assert-consistency.js +12 -9
- package/lib/compiler/checks.js +18 -50
- package/lib/compiler/define.js +6 -6
- package/lib/compiler/extend.js +2 -1
- package/lib/compiler/generate.js +14 -17
- package/lib/compiler/populate.js +8 -31
- package/lib/compiler/propagator.js +21 -35
- package/lib/compiler/resolve.js +35 -22
- package/lib/compiler/shared.js +7 -1
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +1 -1
- package/lib/edm/annotations/edmJson.js +20 -15
- package/lib/edm/annotations/genericTranslation.js +7 -8
- package/lib/edm/csn2edm.js +46 -50
- package/lib/edm/edm.js +8 -7
- package/lib/edm/edmPreprocessor.js +37 -85
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +55 -44
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1133 -1150
- package/lib/json/from-csn.js +70 -43
- package/lib/json/to-csn.js +6 -8
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/main.d.ts +58 -24
- package/lib/model/csnUtils.js +28 -39
- package/lib/model/xprAsTree.js +23 -9
- package/lib/modelCompare/compare.js +5 -4
- package/lib/optionProcessor.js +21 -17
- package/lib/parsers/AstBuildingParser.js +63 -11
- package/lib/parsers/XprTree.js +57 -3
- package/lib/parsers/identifiers.js +1 -1
- package/lib/parsers/index.js +0 -3
- package/lib/render/manageConstraints.js +25 -25
- package/lib/render/toCdl.js +173 -170
- package/lib/render/toHdbcds.js +126 -128
- package/lib/render/toRename.js +7 -7
- package/lib/render/toSql.js +128 -125
- package/lib/render/utils/common.js +47 -22
- package/lib/render/utils/delta.js +25 -25
- package/lib/render/utils/operators.js +2 -2
- package/lib/render/utils/pretty.js +5 -5
- package/lib/render/utils/sql.js +13 -13
- package/lib/render/utils/standardDatabaseFunctions.js +115 -103
- package/lib/render/utils/unique.js +4 -4
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +2 -2
- package/lib/transform/db/associations.js +6 -7
- package/lib/transform/db/assocsToQueries/utils.js +4 -5
- package/lib/transform/db/backlinks.js +12 -9
- package/lib/transform/db/cdsPersistence.js +8 -7
- package/lib/transform/db/constraints.js +13 -10
- package/lib/transform/db/expansion.js +7 -3
- package/lib/transform/db/flattening.js +4 -14
- package/lib/transform/db/processSqlServices.js +2 -1
- package/lib/transform/db/temporal.js +5 -7
- package/lib/transform/db/views.js +2 -4
- package/lib/transform/draft/db.js +8 -8
- package/lib/transform/draft/odata.js +10 -7
- package/lib/transform/forOdata.js +10 -5
- package/lib/transform/forRelationalDB.js +5 -75
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +11 -10
- package/lib/transform/odata/flattening.js +8 -4
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/transformUtils.js +4 -8
- package/lib/transform/translateAssocsToJoins.js +14 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
- package/lib/utils/objectUtils.js +0 -17
- package/package.json +10 -13
- package/share/messages/def-upcoming-virtual-change.md +1 -1
- package/LICENSE +0 -37
- package/bin/cds_remove_invalid_whitespace.js +0 -138
- package/doc/CHANGELOG_ARCHIVE.md +0 -3604
- package/lib/gen/genericAntlrParser.js +0 -3
- package/lib/gen/language.checksum +0 -1
- package/lib/gen/language.interp +0 -456
- package/lib/gen/language.tokens +0 -180
- package/lib/gen/languageLexer.interp +0 -439
- package/lib/gen/languageLexer.js +0 -1483
- package/lib/gen/languageLexer.tokens +0 -167
- package/lib/gen/languageParser.js +0 -24941
- package/lib/language/antlrParser.js +0 -205
- package/lib/language/errorStrategy.js +0 -646
- package/lib/language/genericAntlrParser.js +0 -1572
- 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,
|
|
4
|
+
const { setProp, isBetaEnabled } = require('../base/model');
|
|
5
5
|
const {
|
|
6
6
|
forEachDefinition, forEachGeneric, forEachMemberRecursively,
|
|
7
7
|
isEdmPropertyRendered, getUtils,
|
|
8
|
-
applyTransformations,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
745
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1059
|
+
function muteNavProp( elt ) {
|
|
1076
1060
|
edmUtils.assignAnnotation(elt, '@odata.navigable', false);
|
|
1077
|
-
if (elt._target['@cds.autoexpose'] !== false)
|
|
1078
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
1513
|
-
{
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
package/lib/edm/edmUtils.js
CHANGED
|
@@ -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-
|
|
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-
|
|
382
|
+
info('odata-incomplete-constraints',
|
|
383
383
|
[ 'definitions', assocCsn._parent.name, 'elements', assocCsn.name ], { version: '2.0' } );
|
|
384
384
|
}
|
|
385
385
|
else {
|
package/lib/gen/BaseParser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Base class for generated parser, for redepage v0.2.
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 ' ) }
|
|
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 `
|
|
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,
|
|
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<
|
|
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<
|
|
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
|
-
|
|
769
|
-
if (
|
|
770
|
-
return
|
|
771
|
-
|
|
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 (
|
|
790
|
+
if (recoverDepth != null &&
|
|
774
791
|
(type !== 'Id' || (!keyword || !this.keywords[keyword]) &&
|
|
775
792
|
// reserved words do not match Id in expected-set
|
|
776
|
-
(
|
|
777
|
-
|
|
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( `
|
|
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,
|
|
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 = (
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
539f33c85849442e96168a85adec09fb
|