@sap/cds-compiler 4.3.2 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/lib/api/main.js +14 -24
- package/lib/api/options.js +1 -0
- package/lib/api/trace.js +38 -0
- package/lib/base/location.js +46 -1
- package/lib/base/message-registry.js +68 -16
- package/lib/base/messages.js +8 -3
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/selectItems.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +25 -1
- package/lib/compiler/checks.js +6 -5
- package/lib/compiler/define.js +12 -10
- package/lib/compiler/extend.js +22 -22
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +70 -53
- package/lib/compiler/kick-start.js +7 -5
- package/lib/compiler/populate.js +31 -22
- package/lib/compiler/propagator.js +6 -2
- package/lib/compiler/resolve.js +52 -17
- package/lib/compiler/shared.js +74 -38
- package/lib/compiler/tweak-assocs.js +64 -23
- package/lib/compiler/utils.js +40 -23
- package/lib/edm/.eslintrc.json +2 -0
- package/lib/edm/EdmPrimitiveTypeDefinitions.js +252 -0
- package/lib/edm/annotations/edmJson.js +994 -0
- package/lib/edm/annotations/genericTranslation.js +75 -421
- package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
- package/lib/edm/csn2edm.js +12 -5
- package/lib/edm/edm.js +14 -73
- package/lib/edm/edmPreprocessor.js +6 -0
- package/lib/gen/Dictionary.json +187 -16
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageLexer.interp +1 -1
- package/lib/gen/languageLexer.js +1129 -671
- package/lib/gen/languageParser.js +4285 -4283
- package/lib/json/from-csn.js +13 -18
- package/lib/json/to-csn.js +11 -6
- package/lib/language/antlrParser.js +0 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +95 -30
- package/lib/language/genericAntlrParser.js +21 -1
- package/lib/main.js +13 -3
- package/lib/model/csnRefs.js +42 -8
- package/lib/model/csnUtils.js +14 -2
- package/lib/model/enrichCsn.js +33 -5
- package/lib/model/revealInternalProperties.js +5 -0
- package/lib/modelCompare/compare.js +76 -14
- package/lib/modelCompare/utils/filter.js +19 -12
- package/lib/optionProcessor.js +2 -0
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toHdbcds.js +3 -0
- package/lib/render/toRename.js +3 -1
- package/lib/render/toSql.js +46 -92
- package/lib/render/utils/common.js +76 -0
- package/lib/render/utils/delta.js +17 -3
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/db/.eslintrc.json +1 -0
- package/lib/transform/db/applyTransformations.js +30 -4
- package/lib/transform/db/associations.js +22 -10
- package/lib/transform/db/backlinks.js +6 -2
- package/lib/transform/db/expansion.js +2 -2
- package/lib/transform/db/transformExists.js +13 -39
- package/lib/transform/draft/db.js +14 -3
- package/lib/transform/draft/odata.js +5 -18
- package/lib/transform/effective/associations.js +46 -15
- package/lib/transform/effective/main.js +7 -2
- package/lib/transform/effective/misc.js +43 -24
- package/lib/transform/effective/queries.js +20 -22
- package/lib/transform/effective/types.js +6 -2
- package/lib/transform/forOdata.js +5 -2
- package/lib/transform/localized.js +1 -1
- package/lib/transform/parseExpr.js +73 -21
- package/lib/transform/translateAssocsToJoins.js +22 -15
- package/lib/utils/term.js +2 -2
- package/package.json +2 -1
package/lib/json/from-csn.js
CHANGED
|
@@ -44,9 +44,11 @@
|
|
|
44
44
|
* Use this message variant instead of the default one.
|
|
45
45
|
* Allows more precise and detailed error messages.
|
|
46
46
|
*
|
|
47
|
-
* @property {string|string[]|false} [requires]
|
|
48
|
-
* If the value is a string, then the given sub-property is required.
|
|
49
|
-
*
|
|
47
|
+
* @property {string|string[]|Function|false} [requires]
|
|
48
|
+
* If the value is a(n array of) string, then (one of) the given sub-property is required.
|
|
49
|
+
* If a function, that function issues its own message.
|
|
50
|
+
* If `undefined` (default), then at least one property is required.
|
|
51
|
+
* If false, then no sub-properties are required.
|
|
50
52
|
*
|
|
51
53
|
* @property {boolean} [noPrefix]
|
|
52
54
|
* Only used for '#' at the moment. Signals that the entry should not be used for keys
|
|
@@ -117,7 +119,8 @@
|
|
|
117
119
|
*/
|
|
118
120
|
|
|
119
121
|
const { dictAdd } = require('../base/dictionaries');
|
|
120
|
-
|
|
122
|
+
// TODO: move parts of lib/compiler/builtins to some lib/base/…:
|
|
123
|
+
const { isAnnotationExpression, quotedLiteralPatterns } = require('../compiler/builtins');
|
|
121
124
|
const { CompilerAssertion } = require('../base/error');
|
|
122
125
|
const { XsnSource, CsnLocation } = require('../compiler/classes');
|
|
123
126
|
|
|
@@ -171,17 +174,6 @@ const xorGroups = {
|
|
|
171
174
|
// quantifiers 'some' and 'any are 'xpr' token strings in CSN v1.0
|
|
172
175
|
};
|
|
173
176
|
|
|
174
|
-
/**
|
|
175
|
-
* Properties that are required next to `=` to make an annotation value an actual expression
|
|
176
|
-
* and not some foreign structure.
|
|
177
|
-
*
|
|
178
|
-
* @type {string[]}
|
|
179
|
-
*/
|
|
180
|
-
const xprInAnnoProperties = [
|
|
181
|
-
'ref', 'xpr', 'list', 'literal', 'val',
|
|
182
|
-
'#', 'func', 'args', 'SELECT', 'SET',
|
|
183
|
-
];
|
|
184
|
-
|
|
185
177
|
// Functions reading properties which do not count for the message
|
|
186
178
|
// 'Object in $(PROP) must have at least one property'
|
|
187
179
|
const functionsOfIrrelevantProps = [ ignore, extra, explicitName ];
|
|
@@ -661,7 +653,11 @@ const schema = compileSchema( {
|
|
|
661
653
|
'-expr': { // '-expr' and '-' must not exist top-level
|
|
662
654
|
prop: '@‹anno›',
|
|
663
655
|
type: object,
|
|
664
|
-
optional: [
|
|
656
|
+
optional: [
|
|
657
|
+
'=', '#', 'xpr', 'ref', 'val', 'list',
|
|
658
|
+
'literal', 'func', 'args', 'param',
|
|
659
|
+
'cast',
|
|
660
|
+
],
|
|
665
661
|
schema: {
|
|
666
662
|
'=': {
|
|
667
663
|
type: renameTo( '$tokenTexts', string ),
|
|
@@ -1440,7 +1436,6 @@ function annoValue( val, spec ) {
|
|
|
1440
1436
|
// An object with `=` is an expression if and only if:
|
|
1441
1437
|
// - there is exactly one property ('=')
|
|
1442
1438
|
// - there is at least one other expression property (e.g. "xpr")
|
|
1443
|
-
// TODO: Have xprInAnnoProperties centrally for other backends to use as well (toCdl)
|
|
1444
1439
|
const valKeys = Object.keys( val );
|
|
1445
1440
|
if (valKeys.length === 1) {
|
|
1446
1441
|
++virtualLine;
|
|
@@ -1448,7 +1443,7 @@ function annoValue( val, spec ) {
|
|
|
1448
1443
|
++virtualLine;
|
|
1449
1444
|
return r;
|
|
1450
1445
|
}
|
|
1451
|
-
else if (
|
|
1446
|
+
else if (isAnnotationExpression( val )) {
|
|
1452
1447
|
const s = schema['@'].schema['-expr'];
|
|
1453
1448
|
const r = { location: location() };
|
|
1454
1449
|
Object.assign( r, object( val, s ) );
|
package/lib/json/to-csn.js
CHANGED
|
@@ -45,7 +45,8 @@ const $inferred = Symbol.for('cds.$inferred');
|
|
|
45
45
|
const inferredAsGenerated = {
|
|
46
46
|
autoexposed: 'exposed',
|
|
47
47
|
'localized-entity': 'localized',
|
|
48
|
-
localized: 'localized', // on elements (texts, localized)
|
|
48
|
+
localized: 'localized', // on elements (texts, localized, language)
|
|
49
|
+
// remark: not on 'localize-origin' = other elements of inferred base entity
|
|
49
50
|
'composition-entity': 'composed', // ('aspect-composition' on element not in CSN)
|
|
50
51
|
};
|
|
51
52
|
|
|
@@ -675,9 +676,9 @@ function dollarSyntax( node, csn ) {
|
|
|
675
676
|
function ignore() { /* no-op: ignore property */ }
|
|
676
677
|
|
|
677
678
|
function location( loc, csn, xsn ) {
|
|
678
|
-
if (xsn.kind && xsn.kind.charAt(0) !== '$' && xsn.kind !== 'select' &&
|
|
679
|
-
(!xsn.$inferred || !xsn._main)) {
|
|
680
|
-
// Also include $location for elements in queries (if not via '*')
|
|
679
|
+
if (xsn.kind && xsn.kind.charAt(0) !== '$' && xsn.kind !== 'select' && // TODO: also for 'select'
|
|
680
|
+
(!xsn.$inferred || !xsn._main)) {
|
|
681
|
+
// Also include $location for elements in queries (if not via '*' except for autoexposed)
|
|
681
682
|
addLocation( xsn.name && xsn.name.location || loc, csn );
|
|
682
683
|
}
|
|
683
684
|
}
|
|
@@ -768,7 +769,8 @@ function definition( art, _csn, _node, prop ) {
|
|
|
768
769
|
if (art.kind === 'key') { // foreignkey
|
|
769
770
|
const key = addExplicitAs( { ref: art.targetElement.path.map( pathItem ) },
|
|
770
771
|
art.name, neqPath( art.targetElement ) );
|
|
771
|
-
|
|
772
|
+
if (!art.$inferred)
|
|
773
|
+
addLocation( art.targetElement.location, key );
|
|
772
774
|
return extra( key, art );
|
|
773
775
|
}
|
|
774
776
|
const c = standard( art );
|
|
@@ -1191,8 +1193,9 @@ function value( node ) {
|
|
|
1191
1193
|
if (!node)
|
|
1192
1194
|
return true; // `@aBool` short for `@aBool: true`
|
|
1193
1195
|
if (universalCsn && node.$inferred) {
|
|
1196
|
+
// TODO: return undefined for all values of node.$inferred (except 'NULL')?
|
|
1194
1197
|
if (node.$inferred === 'prop' || node.$inferred === '$generated' || // via propagator.js
|
|
1195
|
-
|
|
1198
|
+
node.$inferred === 'parent-origin')
|
|
1196
1199
|
return undefined;
|
|
1197
1200
|
else if (node.$inferred === 'NULL')
|
|
1198
1201
|
return null;
|
|
@@ -1250,6 +1253,8 @@ function condition( node ) {
|
|
|
1250
1253
|
}
|
|
1251
1254
|
|
|
1252
1255
|
function expression( node ) {
|
|
1256
|
+
if (node?.$inferred && (gensrcFlavor || universalCsn || node.$inferred === 'NULL'))
|
|
1257
|
+
return undefined; // Note: No `null` for universal CSN at the moment
|
|
1253
1258
|
const expr = exprInternal( node, 'no' );
|
|
1254
1259
|
return (Array.isArray( expr ))
|
|
1255
1260
|
? { xpr: flattenInternalXpr( expr, node.op?.val ) }
|
|
@@ -126,7 +126,6 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
126
126
|
// comment the following 2 lines if you want to output the parser errors directly:
|
|
127
127
|
parser.messageErrorListener = errorListener;
|
|
128
128
|
parser._errHandler = new errorStrategy.KeywordErrorStrategy();
|
|
129
|
-
parser.match = errorStrategy.match;
|
|
130
129
|
parser._interp.predictionMode = antlr4.atn.PredictionMode.SLL;
|
|
131
130
|
// parser._interp.predictionMode = antlr4.atn.PredictionMode.LL_EXACT_AMBIG_DETECTION;
|
|
132
131
|
|
|
@@ -39,7 +39,7 @@ function parseDocComment( comment ) {
|
|
|
39
39
|
// Remove "/***/" and trim white space and asterisks.
|
|
40
40
|
const content = lines[0]
|
|
41
41
|
.replace(/^\/[*]{2,}/, '')
|
|
42
|
-
.replace(
|
|
42
|
+
.replace(/\**\/$/, '') // for `/*****/`, only `/` remains
|
|
43
43
|
.replace('*\\/', '*/') // escape sequence
|
|
44
44
|
.trim();
|
|
45
45
|
return isWhitespaceOrNewLineOnly(content) ? null : content;
|
|
@@ -45,27 +45,6 @@ const keywordRegexp = /^[a-zA-Z]+$/; // we don't have keywords with underscore
|
|
|
45
45
|
let SEMI = null;
|
|
46
46
|
let RBRACE = null;
|
|
47
47
|
|
|
48
|
-
// Match current token against token type `ttype` and consume it if successful.
|
|
49
|
-
// Also allow to match keywords as identifiers. This function should be set as
|
|
50
|
-
// property `match` to the parser (prototype). See also `recoverInline()`.
|
|
51
|
-
function match( ttype ) {
|
|
52
|
-
const identType = this.constructor.Identifier;
|
|
53
|
-
if (ttype !== identType)
|
|
54
|
-
return antlr4.Parser.prototype.match.call( this, ttype );
|
|
55
|
-
|
|
56
|
-
const token = this.getCurrentToken();
|
|
57
|
-
if (token.type === identType || !keywordRegexp.test( token.text ))
|
|
58
|
-
return antlr4.Parser.prototype.match.call( this, ttype );
|
|
59
|
-
// This is very likely to be dead code: we do not use a simple Identifier
|
|
60
|
-
// without alternatives in the grammar. With alternatives, recoverInline() is
|
|
61
|
-
// the place to go. (But this code should work with a changed grammar…)
|
|
62
|
-
this.message( 'syntax-unexpected-reserved-word', token,
|
|
63
|
-
{ code: token.text, delimited: token.text } );
|
|
64
|
-
this._errHandler.reportMatch(this);
|
|
65
|
-
this.consume();
|
|
66
|
-
return token;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
48
|
// Class which adapts ANTLR4s standard error strategy: do something special
|
|
70
49
|
// with (non-reserved) keywords.
|
|
71
50
|
//
|
|
@@ -85,6 +64,7 @@ class KeywordErrorStrategy extends DefaultErrorStrategy {
|
|
|
85
64
|
// TODO: Use actual methods
|
|
86
65
|
Object.assign( KeywordErrorStrategy.prototype, {
|
|
87
66
|
sync,
|
|
67
|
+
singleTokenDeletion,
|
|
88
68
|
reportNoViableAlternative,
|
|
89
69
|
reportInputMismatch,
|
|
90
70
|
reportUnwantedToken,
|
|
@@ -102,6 +82,7 @@ Object.assign( KeywordErrorStrategy.prototype, {
|
|
|
102
82
|
// Attempt to recover from problems in subrules, except if rule has defined a
|
|
103
83
|
// local variable `_sync` with value 'nop'
|
|
104
84
|
// TODO: consider performance - see #8800
|
|
85
|
+
// See DefaultErrorStrategy#sync
|
|
105
86
|
function sync( recognizer ) {
|
|
106
87
|
// If already recovering, don't try to sync
|
|
107
88
|
if (this.inErrorRecoveryMode(recognizer))
|
|
@@ -176,6 +157,8 @@ function sync( recognizer ) {
|
|
|
176
157
|
this.consumeUntil( recognizer, nextTokens );
|
|
177
158
|
return;
|
|
178
159
|
}
|
|
160
|
+
// TODO: at least with STAR_LOOP_ENTRY, we might want to do s/th similar as
|
|
161
|
+
// with LOOP_BACK (syncing to “expected tokens” -> the separator)
|
|
179
162
|
throw new InputMismatchException(recognizer);
|
|
180
163
|
|
|
181
164
|
case ATNState.PLUS_LOOP_BACK: // 11
|
|
@@ -184,15 +167,93 @@ function sync( recognizer ) {
|
|
|
184
167
|
this.reportUnwantedToken(recognizer);
|
|
185
168
|
const expecting = new IntervalSet();
|
|
186
169
|
expecting.addSet(recognizer.getExpectedTokens());
|
|
170
|
+
|
|
171
|
+
// First try some ',' insertion (TODO does not work yet):
|
|
172
|
+
if (trySeparatorInsertion( recognizer, expecting, "','" ))
|
|
173
|
+
return;
|
|
174
|
+
|
|
175
|
+
// We then try syncing only to the loop-cont (`,`) / loop-end (`}`) token set,
|
|
176
|
+
// but only for the current or next line (and not consuming `;`s):
|
|
177
|
+
const prevToken = recognizer.getTokenStream().LT(-1);
|
|
178
|
+
if (token.line <= prevToken.line + 1 && // in same or next line
|
|
179
|
+
this.consumeAndMarkUntil( recognizer, expecting, true ))
|
|
180
|
+
break;
|
|
181
|
+
// console.log(token.text,JSON.stringify(intervalSetToArray(recognizer,expecting)))
|
|
182
|
+
|
|
183
|
+
// If that fails, we also sync to all tokens which are in the follow set of
|
|
184
|
+
// the current rule and all outer rules
|
|
187
185
|
const whatFollowsLoopIterationOrRule = expecting.addSet(this.getErrorRecoverySet(recognizer));
|
|
188
186
|
this.consumeUntil(recognizer, whatFollowsLoopIterationOrRule);
|
|
189
|
-
|
|
187
|
+
// console.log(JSON.stringify(intervalSetToArray(recognizer,expecting)))
|
|
188
|
+
if (recognizer._ctx._sync === 'recover' || // in start rule: no exception
|
|
189
|
+
nextTokens.contains( recognizer.getTokenStream().LA(1) ))
|
|
190
|
+
return;
|
|
191
|
+
throw new InputMismatchException(recognizer);
|
|
190
192
|
}
|
|
191
193
|
default:
|
|
192
194
|
// do nothing if we can't identify the exact kind of ATN state
|
|
193
195
|
}
|
|
194
196
|
}
|
|
195
197
|
|
|
198
|
+
|
|
199
|
+
function trySeparatorInsertion( recognizer, expecting, separatorName ) {
|
|
200
|
+
// Remark: this function does not really work, because it is based on
|
|
201
|
+
// singleTokenInsertion, which also does not really work… (see below).
|
|
202
|
+
// But we might improve it in the future…
|
|
203
|
+
const separator = recognizer.literalNames.indexOf( separatorName );
|
|
204
|
+
if (!expecting.contains( separator ))
|
|
205
|
+
return false;
|
|
206
|
+
|
|
207
|
+
const currentSymbolType = recognizer.getTokenStream().LA(1);
|
|
208
|
+
// if current token is consistent with what could come after current
|
|
209
|
+
// ATN state, then we know we're missing a token; error recovery
|
|
210
|
+
// is free to conjure up and insert the missing token
|
|
211
|
+
const { atn } = recognizer._interp;
|
|
212
|
+
const currentState = atn.states[recognizer.state];
|
|
213
|
+
const next = separatorTransition( currentState.transitions, separator ).target;
|
|
214
|
+
// While this is an improvement to the default ANTLR code for
|
|
215
|
+
// singleTokenInsertion(), it still does not help, as we navigate along an
|
|
216
|
+
// epsilon transition, i.e. we still see ',', etc
|
|
217
|
+
const expectingAtLL2 = atn.nextTokens(next, recognizer._ctx);
|
|
218
|
+
if (!expectingAtLL2.contains(currentSymbolType))
|
|
219
|
+
return false;
|
|
220
|
+
|
|
221
|
+
this.reportMissingToken(recognizer);
|
|
222
|
+
return getMissingSymbol( recognizer, separator );
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function separatorTransition( transitions, separator ) {
|
|
226
|
+
for (const tr of transitions) {
|
|
227
|
+
if (tr.matches( separator ))
|
|
228
|
+
return tr;
|
|
229
|
+
}
|
|
230
|
+
return transitions[0];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function singleTokenDeletion( recognizer ) {
|
|
234
|
+
const token = recognizer.getCurrentToken();
|
|
235
|
+
if (!token || token.text === '}')
|
|
236
|
+
return null;
|
|
237
|
+
|
|
238
|
+
const nextTokenType = recognizer.getTokenStream().LA(2);
|
|
239
|
+
const { Number } = recognizer.constructor;
|
|
240
|
+
if (nextTokenType > Number && // next token is Id|Unreserved|IllegalToken
|
|
241
|
+
token.type <= Number) // current token is not
|
|
242
|
+
return null;
|
|
243
|
+
|
|
244
|
+
const expecting = this.getExpectedTokens(recognizer);
|
|
245
|
+
if (!expecting.contains(nextTokenType))
|
|
246
|
+
return null;
|
|
247
|
+
|
|
248
|
+
this.reportUnwantedToken(recognizer);
|
|
249
|
+
recognizer.consume(); // simply delete extra token
|
|
250
|
+
// we want to return the token we're actually matching
|
|
251
|
+
const matchedSymbol = recognizer.getCurrentToken();
|
|
252
|
+
this.reportMatch( recognizer ); // we know current token is correct
|
|
253
|
+
return matchedSymbol;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
196
257
|
// singleTokenInsertion called by recoverInline (called by match / in else),
|
|
197
258
|
// calls reportMissingToken
|
|
198
259
|
|
|
@@ -234,14 +295,14 @@ function reportInputMismatch( recognizer, e, deadEnds ) {
|
|
|
234
295
|
}
|
|
235
296
|
|
|
236
297
|
// Report unwanted token when the parser `recognizer` tries to recover/sync
|
|
237
|
-
function reportUnwantedToken( recognizer ) {
|
|
298
|
+
function reportUnwantedToken( recognizer, expecting ) {
|
|
238
299
|
if (this.inErrorRecoveryMode(recognizer))
|
|
239
300
|
return;
|
|
240
301
|
this.beginErrorCondition(recognizer);
|
|
241
302
|
|
|
242
303
|
const token = recognizer.getCurrentToken();
|
|
243
304
|
token.$isSkipped = 'offending';
|
|
244
|
-
|
|
305
|
+
expecting ??= this.getExpectedTokensForMessage( recognizer, token );
|
|
245
306
|
const offending = this.getTokenDisplay( token, recognizer );
|
|
246
307
|
// Just text variant, no other message id! Would depend on ANTLR-internals
|
|
247
308
|
const err = recognizer.error( 'syntax-unexpected-token', token,
|
|
@@ -323,14 +384,19 @@ function consumeUntil( recognizer, set ) {
|
|
|
323
384
|
}
|
|
324
385
|
}
|
|
325
386
|
|
|
326
|
-
function consumeAndMarkUntil( recognizer, set ) {
|
|
327
|
-
|
|
387
|
+
function consumeAndMarkUntil( recognizer, set, onlyInSameLine ) {
|
|
388
|
+
const stream = recognizer.getTokenStream();
|
|
389
|
+
let t = stream.LT(1);
|
|
390
|
+
const { line } = t;
|
|
328
391
|
while (t.type !== antlr4.Token.EOF && !set.contains( t.type )) {
|
|
392
|
+
if (onlyInSameLine && (t.line !== line || t.text === ';' || t.text === '}' ))
|
|
393
|
+
return false; // early exit
|
|
329
394
|
if (!t.$isSkipped)
|
|
330
395
|
t.$isSkipped = true;
|
|
331
396
|
recognizer.consume();
|
|
332
|
-
t =
|
|
397
|
+
t = stream.LT(1);
|
|
333
398
|
}
|
|
399
|
+
return true;
|
|
334
400
|
}
|
|
335
401
|
|
|
336
402
|
// As the `match` function of the parser `recognizer` does not allow to check
|
|
@@ -363,8 +429,8 @@ function recoverInline( recognizer ) {
|
|
|
363
429
|
// Conjure up a missing token during error recovery in parser `recognizer`. If
|
|
364
430
|
// an identifier is expected, create one.
|
|
365
431
|
// Think about: we might want to prefer one of '}]);,'.
|
|
366
|
-
function getMissingSymbol( recognizer ) {
|
|
367
|
-
|
|
432
|
+
function getMissingSymbol( recognizer, expectedTokenType ) {
|
|
433
|
+
expectedTokenType ??= this.getExpectedTokens(recognizer).first(); // get any element
|
|
368
434
|
const current = recognizer.getCurrentToken();
|
|
369
435
|
return recognizer.getTokenFactory().create(
|
|
370
436
|
current.source, // do s/th special if EOF like in DefaultErrorStrategy ?
|
|
@@ -556,6 +622,5 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
|
|
|
556
622
|
}
|
|
557
623
|
|
|
558
624
|
module.exports = {
|
|
559
|
-
match,
|
|
560
625
|
KeywordErrorStrategy,
|
|
561
626
|
};
|
|
@@ -130,6 +130,7 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
130
130
|
checkTypeFacet,
|
|
131
131
|
checkTypeArgs,
|
|
132
132
|
csnParseOnly,
|
|
133
|
+
markAsSkippedUntilEOF,
|
|
133
134
|
noAssignmentInSameLine,
|
|
134
135
|
noSemicolonHere,
|
|
135
136
|
setLocalToken,
|
|
@@ -249,6 +250,25 @@ function setLocalTokenForId( offset, tokenNameMap ) {
|
|
|
249
250
|
// // throw new antlr4.error.InputMismatchException(this);
|
|
250
251
|
// }
|
|
251
252
|
|
|
253
|
+
function markAsSkippedUntilEOF() {
|
|
254
|
+
let t = this.getCurrentToken();
|
|
255
|
+
if (t.type === antlr4.Token.EOF)
|
|
256
|
+
return;
|
|
257
|
+
if (!t.$isSkipped && !this._errHandler.inErrorRecoveryMode( this )) {
|
|
258
|
+
// If not already done, we should report an error if we do not see EOF. We cannot
|
|
259
|
+
// use match() here, because these would consume tokens without marking them.
|
|
260
|
+
this._errHandler.reportUnwantedToken( this, [ '<EOF>' ] );
|
|
261
|
+
t.$isSkipped = 'offending';
|
|
262
|
+
this.consume();
|
|
263
|
+
t = this.getCurrentToken();
|
|
264
|
+
}
|
|
265
|
+
while (t.type !== antlr4.Token.EOF) {
|
|
266
|
+
t.$isSkipped = true;
|
|
267
|
+
this.consume();
|
|
268
|
+
t = this.getCurrentToken();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
252
272
|
function noAssignmentInSameLine() {
|
|
253
273
|
const t = this.getCurrentToken();
|
|
254
274
|
if (t.text === '@' && t.line <= this._input.LT(-1).line) {
|
|
@@ -928,12 +948,12 @@ function signedExpression( args, expr ) {
|
|
|
928
948
|
function numberLiteral( token, sign, text = token.text ) {
|
|
929
949
|
let location = this.tokenLocation( token );
|
|
930
950
|
if (sign) {
|
|
931
|
-
// TODO: warning for space in between
|
|
932
951
|
const { endLine, endCol } = location;
|
|
933
952
|
location = this.startLocation( sign );
|
|
934
953
|
location.endLine = endLine;
|
|
935
954
|
location.endCol = endCol;
|
|
936
955
|
text = sign.text + text;
|
|
956
|
+
this.reportUnexpectedSpace( sign, this.tokenLocation( token ) );
|
|
937
957
|
}
|
|
938
958
|
|
|
939
959
|
const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
|
package/lib/main.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
+
const { traceApi } = require('./api/trace');
|
|
16
17
|
const snapi = lazyload('./api/main');
|
|
17
18
|
const csnUtils = lazyload('./model/csnUtils');
|
|
18
19
|
const model_api = lazyload('./model/api');
|
|
@@ -75,9 +76,18 @@ function parseExpr( cdlSource, filename = '<expr>.cds', options = {} ) {
|
|
|
75
76
|
module.exports = {
|
|
76
77
|
// Compiler
|
|
77
78
|
version,
|
|
78
|
-
compile: (
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
compile: (filenames, dir, options, fileCache) => { // main function
|
|
80
|
+
traceApi( 'compile', options );
|
|
81
|
+
return compiler.compileX(filenames, dir, options, fileCache).then(toCsn.compactModel);
|
|
82
|
+
},
|
|
83
|
+
compileSync: (filenames, dir, options, fileCache) => { // main function
|
|
84
|
+
traceApi('compileSync', options);
|
|
85
|
+
return toCsn.compactModel(compiler.compileSyncX(filenames, dir, options, fileCache));
|
|
86
|
+
},
|
|
87
|
+
compileSources: (sourcesDict, options) => { // main function
|
|
88
|
+
traceApi('compileSources', options);
|
|
89
|
+
return toCsn.compactModel(compiler.compileSourcesX(sourcesDict, options));
|
|
90
|
+
},
|
|
81
91
|
compactModel: csn => csn, // for easy v2 migration
|
|
82
92
|
get CompilationError() {
|
|
83
93
|
Object.defineProperty(this, 'CompilationError', {
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -194,6 +194,7 @@
|
|
|
194
194
|
const BUILTIN_TYPE = {};
|
|
195
195
|
const { locationString } = require('../base/location');
|
|
196
196
|
const { ModelError, CompilerAssertion } = require('../base/error');
|
|
197
|
+
const { isAnnotationExpression } = require('../compiler/builtins');
|
|
197
198
|
|
|
198
199
|
// Properties in which artifact or members are defined - next property in the
|
|
199
200
|
// "csnPath" is the name or index of that property; 'args' (its value can be a
|
|
@@ -221,6 +222,8 @@ const referenceSemantics = {
|
|
|
221
222
|
inline: { lexical: justDollar, dynamic: 'inline' }, // ...using baseEnv
|
|
222
223
|
ref_where: { lexical: justDollar, dynamic: 'ref-target' }, // ...using baseEnv
|
|
223
224
|
on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
|
|
225
|
+
annotation: { lexical: justDollar, dynamic: 'query' }, // anno top-level `ref`
|
|
226
|
+
annotationExpr: { lexical: justDollar, dynamic: 'query' }, // annotation assignment
|
|
224
227
|
// there are also 'on_join' and 'on_mixin' with default semantics
|
|
225
228
|
orderBy_ref: { lexical: query => query, dynamic: 'query' },
|
|
226
229
|
orderBy_expr: { lexical: query => query, dynamic: 'source' }, // ref in ORDER BY expression
|
|
@@ -467,6 +470,16 @@ function csnRefs( csn, universalReady ) {
|
|
|
467
470
|
return art[property];
|
|
468
471
|
}
|
|
469
472
|
|
|
473
|
+
function boundActionOrMain( art ) {
|
|
474
|
+
while (art.kind !== 'action' && art.kind !== 'function') {
|
|
475
|
+
const p = getCache( art, '_parent' );
|
|
476
|
+
if (!p)
|
|
477
|
+
return art;
|
|
478
|
+
art = p;
|
|
479
|
+
}
|
|
480
|
+
return art;
|
|
481
|
+
}
|
|
482
|
+
|
|
470
483
|
function initDefinition( main ) {
|
|
471
484
|
// TODO: some --test-mode check that the argument is in ‹csn›.definitions ?
|
|
472
485
|
if (getCache( main, '$queries' ) !== undefined) // already computed
|
|
@@ -572,13 +585,16 @@ function csnRefs( csn, universalReady ) {
|
|
|
572
585
|
const path = (typeof ref === 'string') ? [ ref ] : ref.ref;
|
|
573
586
|
if (!Array.isArray( path ))
|
|
574
587
|
throw new ModelError( 'References must look like {ref:[...]}' );
|
|
575
|
-
|
|
576
|
-
const head = pathId( path[0] );
|
|
577
588
|
if (main) // TODO: improve, for csnpath starting with art
|
|
578
589
|
initDefinition( main );
|
|
579
|
-
if (ref.param)
|
|
580
|
-
return resolvePath( path, main.params[head], main, 'param' );
|
|
581
590
|
|
|
591
|
+
const head = pathId( path[0] );
|
|
592
|
+
if (ref.param) {
|
|
593
|
+
const boundOrMain = (query || !main.actions || parent === main)
|
|
594
|
+
? main // shortcut (would also have been return by function)
|
|
595
|
+
: boundActionOrMain( parent );
|
|
596
|
+
return resolvePath( path, boundOrMain.params[head], boundOrMain, 'param' );
|
|
597
|
+
}
|
|
582
598
|
const semantics = referenceSemantics[refCtx] || {};
|
|
583
599
|
if (semantics.$initOnly)
|
|
584
600
|
return undefined;
|
|
@@ -644,6 +660,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
644
660
|
|
|
645
661
|
if (semantics.dynamic === 'query') {
|
|
646
662
|
// TODO: for ON condition in expand, would need to use cached _element
|
|
663
|
+
// TODO: test and implement - Issue #11792!
|
|
647
664
|
return resolvePath( path, qcache.elements[head], null, 'query' );
|
|
648
665
|
}
|
|
649
666
|
for (const name in qcache.$aliases) {
|
|
@@ -1002,7 +1019,7 @@ function startCsnPath( csnPath, csn ) {
|
|
|
1002
1019
|
throw new CompilerAssertion( 'References outside definitions and vocabularies not supported yet');
|
|
1003
1020
|
const art = csn[csnPath[0]][csnPath[1]];
|
|
1004
1021
|
return {
|
|
1005
|
-
index: 2, main: art, parent:
|
|
1022
|
+
index: 2, main: art, parent: art, art,
|
|
1006
1023
|
};
|
|
1007
1024
|
}
|
|
1008
1025
|
|
|
@@ -1033,10 +1050,23 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1033
1050
|
break;
|
|
1034
1051
|
|
|
1035
1052
|
const prop = csnPath[index];
|
|
1053
|
+
if (refCtx === 'annotation' && typeof obj === 'object') {
|
|
1054
|
+
// we do not know yet whether the annotation value is a expression or not →
|
|
1055
|
+
// loop over outer array and records (structure values):
|
|
1056
|
+
if (Array.isArray( obj ) || !isAnnotationExpression( obj )) {
|
|
1057
|
+
obj = obj[prop];
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
refCtx = 'annotationExpr';
|
|
1061
|
+
}
|
|
1036
1062
|
// array item, name/index of artifact/member, (named) argument
|
|
1037
1063
|
if (isName || Array.isArray( obj ) || prop === 'returns') {
|
|
1038
1064
|
// TODO: call some kind of resolve.setOrigin()
|
|
1039
|
-
if (
|
|
1065
|
+
if (isName === 'actions') {
|
|
1066
|
+
art = obj[prop];
|
|
1067
|
+
parent = art; // param refs in annos for actions are based on the action, not the entity
|
|
1068
|
+
}
|
|
1069
|
+
else if (typeof isName === 'string' || prop === 'returns') {
|
|
1040
1070
|
parent = art;
|
|
1041
1071
|
art = obj[prop];
|
|
1042
1072
|
}
|
|
@@ -1058,7 +1088,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1058
1088
|
resolve( '', '$init', main );
|
|
1059
1089
|
main = obj;
|
|
1060
1090
|
art = obj;
|
|
1061
|
-
parent =
|
|
1091
|
+
parent = obj;
|
|
1062
1092
|
}
|
|
1063
1093
|
isName = prop;
|
|
1064
1094
|
// if we want to allow auto-redirect of user-provided target with renamed keys:
|
|
@@ -1106,7 +1136,11 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1106
1136
|
else if (prop === 'orderBy') {
|
|
1107
1137
|
refCtx = 'orderBy';
|
|
1108
1138
|
}
|
|
1109
|
-
else if (prop
|
|
1139
|
+
else if (prop.charAt(0) === '@') {
|
|
1140
|
+
refCtx = 'annotation';
|
|
1141
|
+
}
|
|
1142
|
+
else if (prop !== 'xpr' && prop !== 'list') {
|
|
1143
|
+
// 'xpr' and 'list' do not change the ref context, all other props do:
|
|
1110
1144
|
refCtx = prop;
|
|
1111
1145
|
}
|
|
1112
1146
|
obj = obj[prop];
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -1162,18 +1162,20 @@ function moveAnnotationsAndDoc( sourceNode, targetNode, overwrite = false ) {
|
|
|
1162
1162
|
* @todo Does _not_ apply param/action/... annotations.
|
|
1163
1163
|
*
|
|
1164
1164
|
* @param {CSN.Model} csn
|
|
1165
|
-
* @param {{notFound?: (name: string, index: number) => void, override?: boolean, filter?: (name: string) => boolean}} config
|
|
1165
|
+
* @param {{notFound?: (name: string, index: number) => void, override?: boolean, filter?: (name: string) => boolean, applyToElements?: boolean}} config
|
|
1166
1166
|
* notFound: Function that is called if the referenced definition can't be found.
|
|
1167
1167
|
* Second argument is index in `csn.extensions` array.
|
|
1168
1168
|
* override: Whether to ignore existing annotations.
|
|
1169
1169
|
* filter: Positive filter. If it returns true, annotations for the referenced artifact
|
|
1170
1170
|
* will be applied.
|
|
1171
|
+
* applyToElements: Wether to apply annotations to elements or only to artifacts
|
|
1171
1172
|
*/
|
|
1172
1173
|
function applyAnnotationsFromExtensions( csn, config ) {
|
|
1173
1174
|
if (!csn.extensions)
|
|
1174
1175
|
return;
|
|
1175
1176
|
|
|
1176
1177
|
const filter = config.filter || (_name => true);
|
|
1178
|
+
const applyToElements = config.applyToElements ?? true;
|
|
1177
1179
|
for (let i = 0; i < csn.extensions.length; ++i) {
|
|
1178
1180
|
const ext = csn.extensions[i];
|
|
1179
1181
|
const name = ext.annotate || ext.extend;
|
|
@@ -1181,7 +1183,10 @@ function applyAnnotationsFromExtensions( csn, config ) {
|
|
|
1181
1183
|
const def = csn.definitions[name];
|
|
1182
1184
|
if (def) {
|
|
1183
1185
|
moveAnnotationsAndDoc(ext, def, config.override);
|
|
1184
|
-
|
|
1186
|
+
if (applyToElements)
|
|
1187
|
+
applyAnnotationsToElements(ext, def);
|
|
1188
|
+
if (Object.keys(ext).length <= 1)
|
|
1189
|
+
csn.extensions[i] = undefined;
|
|
1185
1190
|
}
|
|
1186
1191
|
else if (config.notFound) {
|
|
1187
1192
|
config.notFound(name, i);
|
|
@@ -1189,6 +1194,8 @@ function applyAnnotationsFromExtensions( csn, config ) {
|
|
|
1189
1194
|
}
|
|
1190
1195
|
}
|
|
1191
1196
|
|
|
1197
|
+
csn.extensions = csn.extensions.filter(ext => ext);
|
|
1198
|
+
|
|
1192
1199
|
function applyAnnotationsToElements( ext, def ) {
|
|
1193
1200
|
// Only the definition is arrayed but the extension is not since
|
|
1194
1201
|
// `items` is not expected in `extensions` by the CSN frontend and not
|
|
@@ -1204,8 +1211,13 @@ function applyAnnotationsFromExtensions( csn, config ) {
|
|
|
1204
1211
|
if (targetElem) {
|
|
1205
1212
|
moveAnnotationsAndDoc(sourceElem, targetElem, config.override);
|
|
1206
1213
|
applyAnnotationsToElements(sourceElem, targetElem);
|
|
1214
|
+
if (Object.keys(sourceElem).length === 0)
|
|
1215
|
+
delete ext.elements[key];
|
|
1207
1216
|
}
|
|
1208
1217
|
});
|
|
1218
|
+
|
|
1219
|
+
if (Object.keys(ext.elements).length === 0)
|
|
1220
|
+
delete ext.elements;
|
|
1209
1221
|
}
|
|
1210
1222
|
}
|
|
1211
1223
|
|