@sap/cds-compiler 6.1.0 → 6.2.2
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 +43 -0
- package/bin/cdsc.js +6 -2
- package/bin/cdsse.js +1 -1
- package/bin/cdsv2m.js +1 -1
- package/lib/api/main.js +29 -7
- package/lib/base/builtins.js +9 -0
- package/lib/base/keywords.js +1 -1
- package/lib/base/message-registry.js +5 -3
- package/lib/base/messages.js +2 -2
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/featureFlags.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +38 -21
- package/lib/compiler/define.js +24 -5
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/finalize-parse-cdl.js +9 -1
- package/lib/compiler/generate.js +4 -4
- package/lib/compiler/lsp-api.js +2 -0
- package/lib/compiler/populate.js +8 -8
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +15 -14
- package/lib/compiler/shared.js +6 -6
- package/lib/compiler/tweak-assocs.js +6 -6
- package/lib/compiler/utils.js +9 -16
- package/lib/compiler/xpr-rewrite.js +2 -2
- package/lib/gen/BaseParser.js +35 -29
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1423 -1432
- package/lib/gen/Dictionary.json +1 -0
- package/lib/gen/cdlKeywords.json +26 -0
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +2 -2
- package/lib/json/to-csn.js +1 -1
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/model/cloneCsn.js +1 -0
- package/lib/optionProcessor.js +8 -7
- package/lib/parsers/AstBuildingParser.js +24 -21
- package/lib/parsers/identifiers.js +2 -30
- package/lib/render/toCdl.js +63 -9
- package/lib/render/toSql.js +127 -108
- package/lib/render/utils/sql.js +67 -0
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +20 -2
- package/lib/transform/forOdata.js +91 -2
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/flattening.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +2 -26
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
package/lib/compiler/populate.js
CHANGED
|
@@ -813,7 +813,7 @@ function populate( model ) {
|
|
|
813
813
|
// invent a name for code completion in expression, see also #10596
|
|
814
814
|
col.name = {
|
|
815
815
|
id: '',
|
|
816
|
-
location: col.value
|
|
816
|
+
location: col.value?.location || col.location,
|
|
817
817
|
$inferred: 'none',
|
|
818
818
|
};
|
|
819
819
|
return '';
|
|
@@ -979,14 +979,14 @@ function populate( model ) {
|
|
|
979
979
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
980
980
|
info( 'wildcard-excluding-many', [ sibling.name.location, query ],
|
|
981
981
|
{ id, keyword: 'excluding' },
|
|
982
|
-
// eslint-disable-next-line @stylistic/
|
|
982
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
983
983
|
'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
|
|
984
984
|
}
|
|
985
985
|
else {
|
|
986
986
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
987
987
|
info( 'wildcard-excluding-one', [ sibling.name.location, query ],
|
|
988
988
|
{ id, alias: navElem._parent.name.id, keyword: 'excluding' },
|
|
989
|
-
// eslint-disable-next-line @stylistic/
|
|
989
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
990
990
|
'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
|
|
991
991
|
}
|
|
992
992
|
}
|
|
@@ -1112,9 +1112,9 @@ function populate( model ) {
|
|
|
1112
1112
|
// art: definitionScope( target ), - TODO extra debug info in message
|
|
1113
1113
|
sorted_arts: exposed,
|
|
1114
1114
|
}, {
|
|
1115
|
-
// eslint-disable-next-line @stylistic/
|
|
1115
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1116
1116
|
std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
1117
|
-
// eslint-disable-next-line @stylistic/
|
|
1117
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1118
1118
|
two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
1119
1119
|
} );
|
|
1120
1120
|
// continuation semantics: no auto-redirection
|
|
@@ -1136,11 +1136,11 @@ function populate( model ) {
|
|
|
1136
1136
|
anno: 'cds.redirection.target',
|
|
1137
1137
|
sorted_arts: exposed,
|
|
1138
1138
|
}, {
|
|
1139
|
-
// eslint-disable-next-line @stylistic/
|
|
1139
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1140
1140
|
std: 'Add $(ANNO) to one of $(SORTED_ARTS) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1141
|
-
// eslint-disable-next-line @stylistic/
|
|
1141
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1142
1142
|
two: 'Add $(ANNO) to either $(SORTED_ARTS) or $(SECOND) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1143
|
-
// eslint-disable-next-line @stylistic/
|
|
1143
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1144
1144
|
justOne: 'Remove $(ANNO) from all but one of $(SORTED_ARTS) to have a unique redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1145
1145
|
} );
|
|
1146
1146
|
}
|
|
@@ -354,7 +354,7 @@ function propagate( model ) {
|
|
|
354
354
|
const art = item && item._artifact;
|
|
355
355
|
if (art?.virtual?.val) {
|
|
356
356
|
message( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
|
|
357
|
-
// eslint-disable-next-line @stylistic/
|
|
357
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
358
358
|
'Prepend $(KEYWORD) to current select item - containing element $(ART) is virtual' );
|
|
359
359
|
return;
|
|
360
360
|
}
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -326,7 +326,7 @@ function resolve( model ) {
|
|
|
326
326
|
propagateKeys = false;
|
|
327
327
|
info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
|
|
328
328
|
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
329
|
-
// eslint-disable-next-line @stylistic/
|
|
329
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
330
330
|
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
331
331
|
} );
|
|
332
332
|
}
|
|
@@ -367,9 +367,9 @@ function resolve( model ) {
|
|
|
367
367
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
368
368
|
info( 'query-navigate-many', [ art.location, user || query ], { art }, {
|
|
369
369
|
std: 'Navigating along to-many association $(ART) - key properties are not propagated',
|
|
370
|
-
// eslint-disable-next-line @stylistic/
|
|
370
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
371
371
|
element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
|
|
372
|
-
// eslint-disable-next-line @stylistic/
|
|
372
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
373
373
|
alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
|
|
374
374
|
} );
|
|
375
375
|
}
|
|
@@ -490,9 +490,9 @@ function resolve( model ) {
|
|
|
490
490
|
resolveTypeExpr( obj, art );
|
|
491
491
|
// typeOf unmanaged assoc? TODO: is this the right place to check this?
|
|
492
492
|
// (probably better in rewriteAssociations)
|
|
493
|
-
const
|
|
494
|
-
if (
|
|
495
|
-
const assocType = getAssocSpec(
|
|
493
|
+
const elemType = obj.type._artifact;
|
|
494
|
+
if (elemType && effectiveType( elemType )) {
|
|
495
|
+
const assocType = getAssocSpec( elemType ) || {};
|
|
496
496
|
if ((assocType.on || assocType.$assocFilter) && !obj.on)
|
|
497
497
|
obj.on = { $inferred: 'rewrite' }; // TODO: no extra rewrite here
|
|
498
498
|
if (assocType.targetAspect) {
|
|
@@ -506,11 +506,10 @@ function resolve( model ) {
|
|
|
506
506
|
}
|
|
507
507
|
|
|
508
508
|
// Check if relational type is missing its target or if it's used directly.
|
|
509
|
-
if (
|
|
510
|
-
!obj.target && !obj.targetAspect) {
|
|
509
|
+
if (elemType.category === 'relation' && !obj.target && !obj.targetAspect) {
|
|
511
510
|
const isCsn = (obj._block && obj._block.$frontend === 'json');
|
|
512
511
|
error( 'type-missing-target', [ obj.type.location, obj ],
|
|
513
|
-
{ '#': isCsn ? 'csn' : 'std', type:
|
|
512
|
+
{ '#': isCsn ? 'csn' : 'std', type: elemType }, {
|
|
514
513
|
// We don't say "use 'association to <target>" because the type could be used
|
|
515
514
|
// in action parameters, etc. as well.
|
|
516
515
|
std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
|
|
@@ -920,7 +919,7 @@ function resolve( model ) {
|
|
|
920
919
|
} );
|
|
921
920
|
}
|
|
922
921
|
else {
|
|
923
|
-
const noTruthyAllowed = [ '
|
|
922
|
+
const noTruthyAllowed = [ 'key', 'virtual' ];
|
|
924
923
|
for (const prop of noTruthyAllowed) {
|
|
925
924
|
if (art[prop]?.val) {
|
|
926
925
|
// probably better than a parse error (which is good for DEFAULT vs calc),
|
|
@@ -1274,7 +1273,7 @@ function resolve( model ) {
|
|
|
1274
1273
|
const text = (item !== last) ? 'sub' : 'std';
|
|
1275
1274
|
error( 'duplicate-key-ref', [ item.location, key ], { '#': text, name }, {
|
|
1276
1275
|
std: 'Foreign key $(NAME) already refers to the same target element',
|
|
1277
|
-
// eslint-disable-next-line @stylistic/
|
|
1276
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1278
1277
|
sub: 'Foreign key $(NAME) already refers to the target element whose sub element is again referred to here',
|
|
1279
1278
|
// TODO: please add ideas for a better text, e.g. to (closed) PR #11325
|
|
1280
1279
|
} );
|
|
@@ -1594,10 +1593,12 @@ function resolve( model ) {
|
|
|
1594
1593
|
const art = type && (type.kind === 'entity' ? type : type.target?._artifact);
|
|
1595
1594
|
if (!art)
|
|
1596
1595
|
return ref; // error already reported via resolvePathItem()
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1596
|
+
if (last.args || last.where || last.cardinality) {
|
|
1597
|
+
const unexpectedFilter = (expected !== 'annotation' && expected !== 'column' &&
|
|
1598
|
+
expected !== 'calc' && 'std') ||
|
|
1599
|
+
isQuasiVirtualAssociation( type ) && 'model-only';
|
|
1600
1600
|
reportUnexpectedArgsAndFilter( last, expected, user, art, unexpectedFilter );
|
|
1601
|
+
}
|
|
1601
1602
|
// TODO: we should have different message-ids for the "last" stuff: adding
|
|
1602
1603
|
// `.item` likely corrects the ref, probably with location at end of ref
|
|
1603
1604
|
return ref;
|
package/lib/compiler/shared.js
CHANGED
|
@@ -991,7 +991,7 @@ function fns( model ) {
|
|
|
991
991
|
}
|
|
992
992
|
else if ($extended && art.elements) {
|
|
993
993
|
warning( 'ref-deprecated-in-extend', [ head.location, user ], { id: head.id },
|
|
994
|
-
// eslint-disable-next-line @stylistic/
|
|
994
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
995
995
|
'In an added column, do not use the table alias $(ID) to refer to source elements' );
|
|
996
996
|
}
|
|
997
997
|
}
|
|
@@ -1489,7 +1489,7 @@ function fns( model ) {
|
|
|
1489
1489
|
// of invisible table aliases; at least one stakeholder uses this,
|
|
1490
1490
|
// so it can't be an error (yet).
|
|
1491
1491
|
message( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
|
|
1492
|
-
// eslint-disable-next-line @stylistic/
|
|
1492
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1493
1493
|
'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
|
|
1494
1494
|
return false;
|
|
1495
1495
|
default:
|
|
@@ -1898,7 +1898,7 @@ function fns( model ) {
|
|
|
1898
1898
|
else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
|
|
1899
1899
|
const last = path[path.length - 1];
|
|
1900
1900
|
warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
|
|
1901
|
-
// eslint-disable-next-line @stylistic/
|
|
1901
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1902
1902
|
'The target $(ART) of the association is not the current entity represented by $(ID)' );
|
|
1903
1903
|
}
|
|
1904
1904
|
}
|
|
@@ -1988,11 +1988,11 @@ function fns( model ) {
|
|
|
1988
1988
|
std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1989
1989
|
keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1990
1990
|
complete: 'The reference must cover a full foreign key reference of association $(ART)',
|
|
1991
|
-
// eslint-disable-next-line @stylistic/
|
|
1991
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1992
1992
|
'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1993
|
-
// eslint-disable-next-line @stylistic/
|
|
1993
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1994
1994
|
'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1995
|
-
// eslint-disable-next-line @stylistic/
|
|
1995
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1996
1996
|
'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
|
|
1997
1997
|
} );
|
|
1998
1998
|
// TODO later: mention allowed ones
|
|
@@ -129,7 +129,7 @@ function tweakAssocs( model ) {
|
|
|
129
129
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
130
130
|
info( 'assoc-outside-service', loc, { '#': text, target, service: main._service }, {
|
|
131
131
|
std: 'Association target $(TARGET) is outside any service',
|
|
132
|
-
// eslint-disable-next-line @stylistic/
|
|
132
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
133
133
|
exposed: 'If association is published in service $(SERVICE), its target $(TARGET) is outside any service',
|
|
134
134
|
} );
|
|
135
135
|
}
|
|
@@ -146,7 +146,7 @@ function tweakAssocs( model ) {
|
|
|
146
146
|
if (assoc && assoc.foreignKeys) {
|
|
147
147
|
error( 'rewrite-key-for-unmanaged', [ elem.on.location, elem ],
|
|
148
148
|
{ keyword: 'on', art: assocWithExplicitSpec( assoc ) },
|
|
149
|
-
// eslint-disable-next-line @stylistic/
|
|
149
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
150
150
|
'Do not specify an $(KEYWORD) condition when redirecting the managed association $(ART)' );
|
|
151
151
|
}
|
|
152
152
|
checkIgnoredFilter( elem );
|
|
@@ -714,7 +714,7 @@ function tweakAssocs( model ) {
|
|
|
714
714
|
if (assoc.$errorReported !== 'assoc-unexpected-scope') {
|
|
715
715
|
error( 'assoc-unexpected-scope', [ assoc.value.location, assoc ],
|
|
716
716
|
{ id: assoc.value._artifact.name.id },
|
|
717
|
-
// eslint-disable-next-line @stylistic/
|
|
717
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
718
718
|
'Association $(ID) can\'t be projected because its ON-condition refers to a parameter' );
|
|
719
719
|
assoc.$errorReported = 'assoc-unexpected-scope';
|
|
720
720
|
}
|
|
@@ -747,11 +747,11 @@ function tweakAssocs( model ) {
|
|
|
747
747
|
if (!(elem === item._artifact || // redirection for explicit def
|
|
748
748
|
elem._origin === item._artifact)) {
|
|
749
749
|
const art = assoc._origin;
|
|
750
|
-
// eslint-disable-next-line @stylistic/
|
|
750
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
751
751
|
warning( 'rewrite-shadowed', [ elem.name.location, elem ], { art: art && effectiveType( art ) }, {
|
|
752
|
-
// eslint-disable-next-line @stylistic/
|
|
752
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
753
753
|
std: 'This element is not originally referred to in the ON-condition of association $(ART)',
|
|
754
|
-
// eslint-disable-next-line @stylistic/
|
|
754
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
755
755
|
element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
|
|
756
756
|
} );
|
|
757
757
|
}
|
package/lib/compiler/utils.js
CHANGED
|
@@ -150,7 +150,7 @@ function setMemberParent( elem, name, parent, prop ) {
|
|
|
150
150
|
p[prop] = Object.create( null );
|
|
151
151
|
dictAdd( p[prop], name, elem );
|
|
152
152
|
}
|
|
153
|
-
if (parent._outer
|
|
153
|
+
if (parent._outer?.items) // TODO: remove for items, too
|
|
154
154
|
parent = parent._outer;
|
|
155
155
|
setLink( elem, '_parent', parent );
|
|
156
156
|
setLink( elem, '_main', parent._main || parent );
|
|
@@ -484,11 +484,13 @@ function traverseQueryPost( query, simpleOnly, callback ) {
|
|
|
484
484
|
// else: with parse error (`select from <EOF>`, `select distinct from;`)
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
487
|
+
/**
|
|
488
|
+
* Call callback on all queries in dependency order, i.e. starting with query Q
|
|
489
|
+
* 1. sub queries in FROM sources of Q
|
|
490
|
+
* 2. Q itself, ALSO if non-referred query
|
|
491
|
+
* 3. sub queries in ON in FROM of Q
|
|
492
|
+
* 4. sub queries in columns, WHERE, HAVING
|
|
493
|
+
*/
|
|
492
494
|
function traverseQueryExtra( main, callback ) {
|
|
493
495
|
if (!main.$queries)
|
|
494
496
|
return;
|
|
@@ -501,16 +503,7 @@ function traverseQueryExtra( main, callback ) {
|
|
|
501
503
|
if (query._status === 'extra' || query._parent.kind === '$tableAlias')
|
|
502
504
|
continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
|
|
503
505
|
// we are now in the top-level (parent is entity) or a non-referred query (parent is query)
|
|
504
|
-
|
|
505
|
-
// console.log( 'A:', query.name,query._status)
|
|
506
|
-
traverseQueryPost( query, null, (q) => {
|
|
507
|
-
if (q._status !== 'extra') {
|
|
508
|
-
// console.log( 'T:', q.name)
|
|
509
|
-
setLink( q, '_status', 'extra' );
|
|
510
|
-
callback( q );
|
|
511
|
-
}
|
|
512
|
-
// else console.log( 'E:', q.name)
|
|
513
|
-
} );
|
|
506
|
+
traverseQueryPost( query, null, callback );
|
|
514
507
|
}
|
|
515
508
|
}
|
|
516
509
|
|
|
@@ -266,7 +266,7 @@ function xprRewriteFns( model ) {
|
|
|
266
266
|
config.tokenExpr = expr;
|
|
267
267
|
return traverseExpr.STOP === traverseExpr(
|
|
268
268
|
expr, 'annoRewrite', config.target,
|
|
269
|
-
// eslint-disable-next-line @stylistic/
|
|
269
|
+
// eslint-disable-next-line @stylistic/max-len, @stylistic/function-paren-newline
|
|
270
270
|
(e, refCtx) => (rewriteAnnoExpr( e, config, refCtx ) ? traverseExpr.STOP : traverseExpr.SKIP) );
|
|
271
271
|
}
|
|
272
272
|
return false;
|
|
@@ -323,7 +323,7 @@ function xprRewriteFns( model ) {
|
|
|
323
323
|
const filterConfig = { ...config, target: assocTarget, isInFilter: true };
|
|
324
324
|
if (traverseExpr.STOP === traverseExpr(
|
|
325
325
|
step.where, 'filter', step,
|
|
326
|
-
// eslint-disable-next-line @stylistic/
|
|
326
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
327
327
|
(e, ctx) => expr.path && (rewriteGenericAnnoPath( e, filterConfig, ctx ) ? traverseExpr.STOP : traverseExpr.SKIP)
|
|
328
328
|
))
|
|
329
329
|
return true;
|
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.7
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -17,7 +17,6 @@ class BaseParser {
|
|
|
17
17
|
lexer;
|
|
18
18
|
|
|
19
19
|
tokens = undefined;
|
|
20
|
-
eofIndex = undefined;
|
|
21
20
|
tokenIdx = 0;
|
|
22
21
|
recoverTokenIdx = -1;
|
|
23
22
|
conditionTokenIdx = -1; // TODO: can we use recoverTokenIdx ?
|
|
@@ -44,7 +43,6 @@ class BaseParser {
|
|
|
44
43
|
|
|
45
44
|
init() {
|
|
46
45
|
this.lexer.tokenize( this );
|
|
47
|
-
this.eofIndex = this.tokens.length - 1;
|
|
48
46
|
return this;
|
|
49
47
|
}
|
|
50
48
|
|
|
@@ -53,7 +51,7 @@ class BaseParser {
|
|
|
53
51
|
s: this.s,
|
|
54
52
|
stack: this.stack,
|
|
55
53
|
dynamic_: this.dynamic_,
|
|
56
|
-
prec_: this.prec_
|
|
54
|
+
prec_: this.prec_, // TODO: necessary?
|
|
57
55
|
};
|
|
58
56
|
}
|
|
59
57
|
|
|
@@ -85,8 +83,8 @@ class BaseParser {
|
|
|
85
83
|
la() { // lookahead: complete token
|
|
86
84
|
return this.tokens[this.tokenIdx];
|
|
87
85
|
}
|
|
88
|
-
lb() {
|
|
89
|
-
return this.tokens[this.tokenIdx -
|
|
86
|
+
lb( k = 1 ) { // look back: complete token
|
|
87
|
+
return this.tokens[this.tokenIdx - k];
|
|
90
88
|
}
|
|
91
89
|
lr() { // return the first token matched by current rule
|
|
92
90
|
return this.tokens[this.stack[this.stack.length - 1].tokenIdx];
|
|
@@ -95,12 +93,12 @@ class BaseParser {
|
|
|
95
93
|
// lookahead, error: ----------------------------------------------------------
|
|
96
94
|
|
|
97
95
|
l() { // lookahead: token type
|
|
98
|
-
return this.
|
|
96
|
+
return this.la().type;
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
// instead of l() if keyword (reserved and/or unreserved) is in one of the cases
|
|
102
100
|
lk() { // keyword lookahead
|
|
103
|
-
const la = this.
|
|
101
|
+
const la = this.la();
|
|
104
102
|
if (!this.nextTokenAsId)
|
|
105
103
|
return la.keyword || la.type;
|
|
106
104
|
// return la.keyword && this.table[this.s][la.keyword] && la.keyword || la.type;
|
|
@@ -109,7 +107,7 @@ class BaseParser {
|
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
e() { // error: report and recover
|
|
112
|
-
const la = this.
|
|
110
|
+
const la = this.la();
|
|
113
111
|
this._trace( 'detect parsing error' );
|
|
114
112
|
if (this.errorTokenIdx === this.tokenIdx)
|
|
115
113
|
throw Error( `Already reported error for ${ tokenFullName( la ) } at ${ la.location }`);
|
|
@@ -141,7 +139,7 @@ class BaseParser {
|
|
|
141
139
|
|
|
142
140
|
// instead of e() in default if lk() had been used and 'Id' is in a non-default case
|
|
143
141
|
ei() { // error (after trying to test again as identifier)
|
|
144
|
-
if (!this.
|
|
142
|
+
if (!this.la().keyword) // lk() had directly returned the type
|
|
145
143
|
return this.e();
|
|
146
144
|
this.nextTokenAsId = true;
|
|
147
145
|
return false; // do not execute action after it
|
|
@@ -200,18 +198,6 @@ class BaseParser {
|
|
|
200
198
|
return false; // do not execute action after it
|
|
201
199
|
}
|
|
202
200
|
|
|
203
|
-
// instead of gi() at rule end (RuleEnd_ in follow-set) for `Id<weak>`, TODO: delete
|
|
204
|
-
giR( state, follow ) { // go to state (after trying to test again as identifier)
|
|
205
|
-
const { keyword } = this.tokens[this.tokenIdx];
|
|
206
|
-
if (!keyword || this.keywords[keyword])
|
|
207
|
-
return this.g( state, follow );
|
|
208
|
-
this._tracePush( [ 'R', 0 ] );
|
|
209
|
-
if (this._matchesInFollow( 'Id', keyword, 'R' ))
|
|
210
|
-
return this.g( state, follow );
|
|
211
|
-
this.nextTokenAsId = true;
|
|
212
|
-
return false; // do not execute action after it
|
|
213
|
-
}
|
|
214
|
-
|
|
215
201
|
// instead of g() in a non-default case if there is a LL1 conflict
|
|
216
202
|
gP( state, follow ) { // goto state with standard weak-conflict prediction
|
|
217
203
|
return this.lP( follow ) && this.g( state );
|
|
@@ -303,7 +289,8 @@ class BaseParser {
|
|
|
303
289
|
// “go if user condition fails”
|
|
304
290
|
gc( state, cond, arg ) {
|
|
305
291
|
if (this.conditionTokenIdx === this.tokenIdx && // tested on same
|
|
306
|
-
this.conditionStackLength == null
|
|
292
|
+
this.conditionStackLength == null && // after error recovery
|
|
293
|
+
!this[cond].afterError) {
|
|
307
294
|
this._tracePush( [ 'C' ] );
|
|
308
295
|
return true;
|
|
309
296
|
}
|
|
@@ -314,12 +301,11 @@ class BaseParser {
|
|
|
314
301
|
}
|
|
315
302
|
// calling the condition might have side effects (precendence conditions have)
|
|
316
303
|
// → call tracing “name” before
|
|
317
|
-
const fail = this[cond]( arg, true ); // TODO: use single-letter for run
|
|
304
|
+
const fail = this[cond]( arg, true ); // TODO: use single-letter for run! → 'X'
|
|
318
305
|
if (this.constructor.tracingParser)
|
|
319
306
|
this._traceSubPush( !fail );
|
|
320
307
|
// The default case must not have actions. If written in grammar with action,
|
|
321
|
-
// the default must have <default=
|
|
322
|
-
|
|
308
|
+
// the default must currently have <default=true>
|
|
323
309
|
|
|
324
310
|
if (fail) { // TODO: extra gcK() method instead of check below
|
|
325
311
|
// TODO: probably remove the following (and `conditionStackLength` tests)
|
|
@@ -346,6 +332,23 @@ class BaseParser {
|
|
|
346
332
|
return this.gc( null, cond, arg );
|
|
347
333
|
}
|
|
348
334
|
|
|
335
|
+
// predefined guard:
|
|
336
|
+
isNoKeywordInRuleFollow( _arg, mode ) {
|
|
337
|
+
const { keyword } = this.la();
|
|
338
|
+
if (this.constructor.tracingParser && (mode === true || mode === 'M')) {
|
|
339
|
+
// TODO: mode === 'X' || mode === 'M'
|
|
340
|
+
--this.trace.at(-1).length; // do not show guard name in trace
|
|
341
|
+
if (!keyword || this.keywords[keyword] == null)
|
|
342
|
+
return false; // ok
|
|
343
|
+
const r = this._matchesInFollow( 'Id', keyword, 'R' );
|
|
344
|
+
--this.trace.at(-1).length; // this.gc() also traces result
|
|
345
|
+
return r;
|
|
346
|
+
}
|
|
347
|
+
if (!keyword || this.keywords[keyword] == null)
|
|
348
|
+
return false; // ok
|
|
349
|
+
return this._matchesInFollow( 'Id', keyword, 'R' );
|
|
350
|
+
}
|
|
351
|
+
|
|
349
352
|
// rule start, end and call: --------------------------------------------------
|
|
350
353
|
|
|
351
354
|
rule_( state, followState = -1 ) { // start rule
|
|
@@ -762,12 +765,13 @@ class BaseParser {
|
|
|
762
765
|
const { keyword, type } = token;
|
|
763
766
|
if (keyword && set[keyword] === true)
|
|
764
767
|
delete set[keyword];
|
|
765
|
-
else if (set[type] === true && !(keyword && this.keywords[keyword]))
|
|
766
|
-
delete set[type]; // delete
|
|
768
|
+
else if (set[type] === true && !(keyword && this.keywords[keyword] != null))
|
|
769
|
+
delete set[type]; // delete if not keyword
|
|
767
770
|
|
|
768
771
|
this._trace( 'collect tokens for message' );
|
|
769
772
|
const { trace } = this;
|
|
770
773
|
const saved = this._saveForWalk();
|
|
774
|
+
saved.fixKeywordTokenIdx = this.fixKeywordTokenIdx; // changed by confirmExpected
|
|
771
775
|
const expecting = Object.keys( set )
|
|
772
776
|
.filter( tok => this._confirmExpected( tok, saved ) );
|
|
773
777
|
token.type = type; // overwritten by _confirmExpected
|
|
@@ -780,7 +784,7 @@ class BaseParser {
|
|
|
780
784
|
_findSyncToken( syncSet ) {
|
|
781
785
|
const rewindDepth = this.stack.length
|
|
782
786
|
this.recoverTokenIdx = this.tokenIdx;
|
|
783
|
-
while (this.recoverTokenIdx
|
|
787
|
+
while (this.recoverTokenIdx < this.tokens.length) {
|
|
784
788
|
const { keyword, type } = this.tokens[this.recoverTokenIdx];
|
|
785
789
|
let recoverDepth = keyword ? syncSet[keyword] : null;
|
|
786
790
|
if (recoverDepth != null)
|
|
@@ -971,6 +975,8 @@ class BaseParser {
|
|
|
971
975
|
const members = BaseParser.prototype;
|
|
972
976
|
// functions below are to be called with `call` to set `this`
|
|
973
977
|
|
|
978
|
+
members.isNoKeywordInRuleFollow.afterError = true;
|
|
979
|
+
|
|
974
980
|
members.precLeft_.traceName = function( prec ) {
|
|
975
981
|
const parentPrec = this.stack.at( -1 ).prec;
|
|
976
982
|
return `${ parentPrec ?? '-∞' }<${ prec }`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7489417512fa82b33fb6799686ce2917
|