@sap/cds-compiler 6.0.14 → 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 +61 -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/api/options.js +2 -0
- 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 +3 -3
- package/lib/base/model.js +1 -0
- package/lib/base/node-helpers.js +10 -2
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/assocOutsideService.js +3 -1
- 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/index.js +10 -1
- 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 -7
- 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 +43 -37
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1424 -1433
- package/lib/gen/Dictionary.json +1 -7
- 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 +9 -5
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +10 -2
- package/lib/model/cloneCsn.js +1 -0
- package/lib/optionProcessor.js +13 -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/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +14 -4
- 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/transformUtils.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +2 -26
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
package/lib/compiler/generate.js
CHANGED
|
@@ -104,7 +104,7 @@ function generate( model ) {
|
|
|
104
104
|
const lang = textsAspect.elements.language;
|
|
105
105
|
error( 'def-unexpected-element', [ lang.name.location, lang ],
|
|
106
106
|
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
107
|
-
// eslint-disable-next-line @stylistic/
|
|
107
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
108
108
|
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
109
109
|
hasError = true;
|
|
110
110
|
}
|
|
@@ -209,7 +209,7 @@ function generate( model ) {
|
|
|
209
209
|
conflictingElements.push( elem );
|
|
210
210
|
|
|
211
211
|
const isKey = elem.key && elem.key.val;
|
|
212
|
-
const isLocalized = hasTruthyProp( elem, 'localized' );
|
|
212
|
+
const isLocalized = elem.$syntax !== 'calc' && hasTruthyProp( elem, 'localized' );
|
|
213
213
|
|
|
214
214
|
if (isKey) {
|
|
215
215
|
keys += 1;
|
|
@@ -245,7 +245,7 @@ function generate( model ) {
|
|
|
245
245
|
(fioriEnabled && art.elements.ID_texts)) {
|
|
246
246
|
// TODO if we have too much time: check all elements of texts entity for safety
|
|
247
247
|
warning( null, [ art.name.location, art ], { art: textsEntity },
|
|
248
|
-
// eslint-disable-next-line @stylistic/
|
|
248
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
249
249
|
'Texts entity $(ART) can\'t be created as there is another definition with that name' );
|
|
250
250
|
info( null, [ textsEntity.name.location, textsEntity ], { art },
|
|
251
251
|
'Texts entity for $(ART) can\'t be created with this definition' );
|
|
@@ -640,7 +640,7 @@ function generate( model ) {
|
|
|
640
640
|
}
|
|
641
641
|
if (model.definitions[entityName]) {
|
|
642
642
|
error( null, [ location, elem ], { art: entityName },
|
|
643
|
-
// eslint-disable-next-line @stylistic/
|
|
643
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
644
644
|
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
645
645
|
return false;
|
|
646
646
|
}
|
package/lib/compiler/index.js
CHANGED
|
@@ -148,6 +148,8 @@ function parserForFile( source, ext, options ) {
|
|
|
148
148
|
// - { realname: fs.realpath(filename) }: if filename is not canonicalized
|
|
149
149
|
//
|
|
150
150
|
function compileX( filenames, dir = '', options = {}, fileCache = Object.create( null ) ) {
|
|
151
|
+
options.abortSignal?.throwIfAborted();
|
|
152
|
+
|
|
151
153
|
// A non-proper dictionary (i.e. with prototype) is safe if the keys are
|
|
152
154
|
// absolute file names - they start with `/` or `\` or similar
|
|
153
155
|
// if (Object.getPrototypeOf( fileCache ))
|
|
@@ -163,7 +165,10 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
163
165
|
input = processedInput;
|
|
164
166
|
model.sources = input.sources;
|
|
165
167
|
} )
|
|
166
|
-
.then( () => promiseAllDoNotRejectImmediately(
|
|
168
|
+
.then( () => promiseAllDoNotRejectImmediately(
|
|
169
|
+
input.files.map( readAndParse ),
|
|
170
|
+
e => e?.name === 'AbortError' // reject immediately if user wants to abort
|
|
171
|
+
))
|
|
167
172
|
.then( testInvocation, (reason) => {
|
|
168
173
|
// do not reject with PromiseAllError, use InvocationError:
|
|
169
174
|
const errs = reason.valuesOrErrors?.filter( e => e instanceof Error ) || [ reason ];
|
|
@@ -176,6 +181,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
176
181
|
all = all.then( readDependencies );
|
|
177
182
|
|
|
178
183
|
return all.then( () => {
|
|
184
|
+
options.abortSignal?.throwIfAborted();
|
|
179
185
|
moduleLayers.setLayers( input.sources );
|
|
180
186
|
return compileDoX( model );
|
|
181
187
|
} );
|
|
@@ -193,6 +199,9 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
193
199
|
sources[filename] = { location: new Location( rel ) };
|
|
194
200
|
|
|
195
201
|
const source = await cdsFs( fileCache, options.traceFs ).readFileAsync( filename, 'utf8' );
|
|
202
|
+
// before running our compute-heavy parsing, check if user aborted
|
|
203
|
+
options.abortSignal?.throwIfAborted();
|
|
204
|
+
|
|
196
205
|
const ast = parseX( source, rel, options, model.$messageFunctions );
|
|
197
206
|
sources[filename] = ast;
|
|
198
207
|
ast.location = new Location( rel );
|
package/lib/compiler/lsp-api.js
CHANGED
|
@@ -397,6 +397,8 @@ function* nameAsReference( ref, hint = null ) {
|
|
|
397
397
|
function* definitionNameTokens( name, art ) {
|
|
398
398
|
if (!art.kind)
|
|
399
399
|
return null; // e.g. parameter references
|
|
400
|
+
if (!name)
|
|
401
|
+
return null; // e.g. column that couldn't be populated
|
|
400
402
|
if (art.kind === '$annotation')
|
|
401
403
|
return null; // annotation name, e.g. in `@anno: (elem)`
|
|
402
404
|
|
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
|
@@ -527,7 +527,6 @@ function fns( model ) {
|
|
|
527
527
|
if (traverseTypedExpr( args[0], exprCtx, user, null, callback ) === traverseExpr.STOP)
|
|
528
528
|
return null;
|
|
529
529
|
return args.slice( 1 );
|
|
530
|
-
// TODO: adopt if we extend this to ?:?:…
|
|
531
530
|
}
|
|
532
531
|
|
|
533
532
|
function traverseCaseWhen( args, exprCtx, user, type, callback ) {
|
|
@@ -992,7 +991,7 @@ function fns( model ) {
|
|
|
992
991
|
}
|
|
993
992
|
else if ($extended && art.elements) {
|
|
994
993
|
warning( 'ref-deprecated-in-extend', [ head.location, user ], { id: head.id },
|
|
995
|
-
// eslint-disable-next-line @stylistic/
|
|
994
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
996
995
|
'In an added column, do not use the table alias $(ID) to refer to source elements' );
|
|
997
996
|
}
|
|
998
997
|
}
|
|
@@ -1490,7 +1489,7 @@ function fns( model ) {
|
|
|
1490
1489
|
// of invisible table aliases; at least one stakeholder uses this,
|
|
1491
1490
|
// so it can't be an error (yet).
|
|
1492
1491
|
message( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
|
|
1493
|
-
// eslint-disable-next-line @stylistic/
|
|
1492
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1494
1493
|
'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
|
|
1495
1494
|
return false;
|
|
1496
1495
|
default:
|
|
@@ -1899,7 +1898,7 @@ function fns( model ) {
|
|
|
1899
1898
|
else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
|
|
1900
1899
|
const last = path[path.length - 1];
|
|
1901
1900
|
warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
|
|
1902
|
-
// eslint-disable-next-line @stylistic/
|
|
1901
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1903
1902
|
'The target $(ART) of the association is not the current entity represented by $(ID)' );
|
|
1904
1903
|
}
|
|
1905
1904
|
}
|
|
@@ -1989,11 +1988,11 @@ function fns( model ) {
|
|
|
1989
1988
|
std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1990
1989
|
keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1991
1990
|
complete: 'The reference must cover a full foreign key reference of association $(ART)',
|
|
1992
|
-
// eslint-disable-next-line @stylistic/
|
|
1991
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1993
1992
|
'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1994
|
-
// eslint-disable-next-line @stylistic/
|
|
1993
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1995
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)',
|
|
1996
|
-
// eslint-disable-next-line @stylistic/
|
|
1995
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1997
1996
|
'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
|
|
1998
1997
|
} );
|
|
1999
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
|
|
@@ -433,13 +436,13 @@ class BaseParser {
|
|
|
433
436
|
this.s = cmd[1];
|
|
434
437
|
if (cmd[0] !== (lk1 ? 'ck' : 'ci')) { // make the std case fast
|
|
435
438
|
// TODO: also not with lean condition
|
|
436
|
-
let match1 = this._pred_next( 'Id', lk1, 'P' ); //
|
|
439
|
+
let match1 = this._pred_next( 'Id', lk1, 'P' ); // first step of `K`/`I` prediction
|
|
437
440
|
if (!match1) {
|
|
438
441
|
if (lk1 || match1 === false) // assert for correct code generation
|
|
439
442
|
throw Error( `Cannot match first prediction token in rule at state ${ saved.s }` );
|
|
440
|
-
if (match1 == null) {
|
|
443
|
+
if (match1 == null) { // TODO: just return true, rule exit prediction will do it
|
|
441
444
|
this._traceSubPush( 0 ); // TODO: make _pred_next push this
|
|
442
|
-
match1 = this._matchesInFollow( 'Id', lk1, 'I' );
|
|
445
|
+
match1 = this._matchesInFollow( 'Id', lk1, 'I' );
|
|
443
446
|
}
|
|
444
447
|
else {
|
|
445
448
|
this._traceSubPush( false );
|
|
@@ -452,10 +455,12 @@ class BaseParser {
|
|
|
452
455
|
|
|
453
456
|
this._traceSubPush( '' ); // between the two tokens
|
|
454
457
|
++this.tokenIdx; // for user lookahead fns and conditions
|
|
455
|
-
|
|
458
|
+
const mode = lk1 ? 'K' : 'I';
|
|
459
|
+
let match2 = this._pred_next( lt2, lk2, mode );
|
|
456
460
|
if (match2 == null) {
|
|
457
461
|
this._traceSubPush( 0 ); // TODO: make _pred_next push this
|
|
458
|
-
match2 = !!this._matchesInFollow( lt2, lk2,
|
|
462
|
+
match2 = !!this._matchesInFollow( lt2, lk2, mode );
|
|
463
|
+
// TODO: we might use mode 'E' in _matchesInFollow (depends on caching)
|
|
459
464
|
}
|
|
460
465
|
else {
|
|
461
466
|
this._traceSubPush( match2 );
|
|
@@ -485,8 +490,6 @@ class BaseParser {
|
|
|
485
490
|
* condition is listed in `this.leanConditions`.
|
|
486
491
|
*/
|
|
487
492
|
_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
|
|
490
493
|
const properCall = (mode === 'P');
|
|
491
494
|
const lean = (mode !== 'M'); // TODO: extra method with conditions ?
|
|
492
495
|
// TODO: if false, use condition in this.leanConditions
|
|
@@ -609,6 +612,7 @@ class BaseParser {
|
|
|
609
612
|
this._traceSubPush( match == null ? 0 : match === (mode !== 'R') );
|
|
610
613
|
// successfully matching a keyword in giR() means unsuccessful match as
|
|
611
614
|
// reserved identifer
|
|
615
|
+
// TODO: this.stack ?
|
|
612
616
|
}
|
|
613
617
|
this.dynamic_ = dynamic_;
|
|
614
618
|
this.s = savedState;
|
|
@@ -761,26 +765,26 @@ class BaseParser {
|
|
|
761
765
|
const { keyword, type } = token;
|
|
762
766
|
if (keyword && set[keyword] === true)
|
|
763
767
|
delete set[keyword];
|
|
764
|
-
else if (set[type] === true && !(keyword && this.keywords[keyword]))
|
|
765
|
-
delete set[type]; // delete
|
|
768
|
+
else if (set[type] === true && !(keyword && this.keywords[keyword] != null))
|
|
769
|
+
delete set[type]; // delete if not keyword
|
|
766
770
|
|
|
767
771
|
this._trace( 'collect tokens for message' );
|
|
768
772
|
const { trace } = this;
|
|
769
773
|
const saved = this._saveForWalk();
|
|
774
|
+
saved.fixKeywordTokenIdx = this.fixKeywordTokenIdx; // changed by confirmExpected
|
|
770
775
|
const expecting = Object.keys( set )
|
|
771
776
|
.filter( tok => this._confirmExpected( tok, saved ) );
|
|
772
777
|
token.type = type; // overwritten by _confirmExpected
|
|
773
778
|
token.keyword = keyword;
|
|
774
779
|
Object.assign( this, saved );
|
|
775
780
|
this.trace = trace;
|
|
776
|
-
// TODO: also trace M(…) collection, extra line for each token, with condition
|
|
777
781
|
return expecting;
|
|
778
782
|
}
|
|
779
783
|
|
|
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
|