@sap/cds-compiler 2.15.4 → 3.0.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 +33 -1590
- package/bin/cdsc.js +36 -33
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +220 -103
- package/lib/api/options.js +15 -85
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +60 -20
- package/lib/base/messages.js +65 -24
- package/lib/base/model.js +44 -2
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +2 -1
- package/lib/compiler/assert-consistency.js +15 -10
- package/lib/compiler/builtins.js +87 -9
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/extend.js +59 -11
- package/lib/compiler/finalize-parse-cdl.js +20 -9
- package/lib/compiler/index.js +25 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +13 -13
- package/lib/compiler/propagator.js +3 -3
- package/lib/compiler/resolve.js +193 -218
- package/lib/compiler/shared.js +47 -76
- package/lib/compiler/tweak-assocs.js +9 -10
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/csn2edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +25 -30
- package/lib/edm/edmUtils.js +10 -24
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20632 -22313
- package/lib/json/from-csn.js +56 -49
- package/lib/json/to-csn.js +10 -8
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +303 -229
- package/lib/language/language.g4 +573 -629
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +27 -42
- package/lib/main.js +104 -81
- package/lib/model/csnRefs.js +1 -1
- package/lib/model/csnUtils.js +170 -283
- package/lib/model/revealInternalProperties.js +28 -8
- package/lib/model/sortViews.js +32 -31
- package/lib/optionProcessor.js +12 -21
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +334 -339
- package/lib/render/toHdbcds.js +19 -15
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +53 -51
- package/lib/render/utils/common.js +15 -1
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +18 -19
- package/lib/transform/db/views.js +3 -3
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +19 -22
- package/lib/transform/forOdataNew.js +10 -12
- package/lib/transform/localized.js +22 -16
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +3 -3
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
const antlr4 = require('antlr4');
|
|
10
|
-
const { ATNState } = require('antlr4/atn/ATNState');
|
|
10
|
+
const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
|
|
11
11
|
const { dictAdd, dictAddArray } = require('../base/dictionaries');
|
|
12
12
|
const locUtils = require('../base/location');
|
|
13
13
|
const { parseDocComment } = require('./docCommentParser');
|
|
@@ -32,85 +32,86 @@ function _message( parser, severity, id, loc, ...args ) {
|
|
|
32
32
|
// this.<function>(...)
|
|
33
33
|
// in the actions inside the grammar.
|
|
34
34
|
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
35
|
+
class GenericAntlrParser extends antlr4.Parser {
|
|
36
|
+
constructor( ...args ) {
|
|
37
|
+
// ANTLR restriction: we cannot add parameters to the constructor.
|
|
38
|
+
super( ...args );
|
|
39
|
+
this.buildParseTrees = false;
|
|
40
|
+
|
|
41
|
+
// Common properties.
|
|
42
|
+
// We set them here so that they are available in the prototype.
|
|
43
|
+
// This improved performance by 25% for certain scenario tests.
|
|
44
|
+
// Probably because there was no need to look up the prototype chain anymore.
|
|
45
|
+
this.$adaptExpectedToken = null;
|
|
46
|
+
this.$adaptExpectedExcludes = [ ];
|
|
47
|
+
this.$nextTokensToken = null;
|
|
48
|
+
this.$nextTokensContext = null;
|
|
49
|
+
|
|
50
|
+
this.options = {};
|
|
51
|
+
|
|
52
|
+
this.genericFunctionsStack = [];
|
|
53
|
+
this.$genericKeywords = specialFunctions[''][1];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// TODO: Use actual methods.
|
|
58
|
+
Object.assign(GenericAntlrParser.prototype, {
|
|
59
|
+
message: function(...args) { return _message( this, 'message', ...args ); },
|
|
60
|
+
error: function(...args) { return _message( this, 'error', ...args ); },
|
|
61
|
+
warning: function(...args) { return _message( this, 'warning', ...args ); },
|
|
62
|
+
info: function(...args) { return _message( this, 'info', ...args ); },
|
|
63
|
+
attachLocation,
|
|
64
|
+
assignAnnotation,
|
|
65
|
+
startLocation,
|
|
66
|
+
tokenLocation,
|
|
67
|
+
valueWithTokenLocation,
|
|
68
|
+
previousTokenAtLocation,
|
|
69
|
+
combinedLocation,
|
|
70
|
+
surroundByParens,
|
|
71
|
+
unaryOpForParens,
|
|
72
|
+
leftAssocBinaryOp,
|
|
73
|
+
classifyImplicitName,
|
|
74
|
+
fragileAlias,
|
|
75
|
+
identAst,
|
|
76
|
+
functionAst,
|
|
77
|
+
setLastAsXpr,
|
|
78
|
+
xprToken,
|
|
79
|
+
valuePathAst,
|
|
80
|
+
signedExpression,
|
|
81
|
+
numberLiteral,
|
|
82
|
+
quotedLiteral,
|
|
83
|
+
pathName,
|
|
84
|
+
docComment,
|
|
85
|
+
addDef,
|
|
86
|
+
addItem,
|
|
87
|
+
addExtension,
|
|
88
|
+
createSource,
|
|
89
|
+
createDict,
|
|
90
|
+
createArray,
|
|
91
|
+
finalizeDictOrArray,
|
|
92
|
+
createPrefixOp,
|
|
93
|
+
setOnce,
|
|
94
|
+
setMaxCardinality,
|
|
95
|
+
pushIdent,
|
|
96
|
+
handleComposition,
|
|
97
|
+
associationInSelectItem,
|
|
98
|
+
reportExpandInline,
|
|
99
|
+
checkTypeFacet,
|
|
100
|
+
notSupportedYet,
|
|
101
|
+
csnParseOnly,
|
|
102
|
+
disallowElementExtension,
|
|
103
|
+
noAssignmentInSameLine,
|
|
104
|
+
noSemicolonHere,
|
|
105
|
+
setLocalToken,
|
|
106
|
+
setLocalTokenIfBefore,
|
|
107
|
+
setLocalTokenForId,
|
|
108
|
+
excludeExpected,
|
|
109
|
+
isStraightBefore,
|
|
110
|
+
meltKeywordToIdentifier,
|
|
111
|
+
prepareGenericKeywords,
|
|
112
|
+
reportErrorForGenericKeyword,
|
|
113
|
+
parseMultiLineStringLiteral,
|
|
114
|
+
});
|
|
114
115
|
|
|
115
116
|
// Patterns for literal token tests and creation. The value is a map from the
|
|
116
117
|
// `prefix` argument of function `quotedliteral` to the following properties:
|
|
@@ -121,26 +122,27 @@ GenericAntlrParser.prototype = Object.assign(
|
|
|
121
122
|
// - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
122
123
|
// the error location is only correct for a literal <prefix>'<value>'
|
|
123
124
|
// - `literal`: the value which is used instead of `prefix` in the AST
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
// TODO:
|
|
125
|
+
// TODO: we might do a range check (consider leap seconds, i.e. max value 60),
|
|
126
|
+
// but always allow Feb 29 (no leap year computation)
|
|
127
|
+
// TODO: make it a configurable error (syntax-invalid-literal)
|
|
128
|
+
// TODO: also use for CSN input
|
|
127
129
|
const quotedLiteralPatterns = {
|
|
128
130
|
x: {
|
|
129
|
-
|
|
131
|
+
test_variant: 'uneven-hex',
|
|
130
132
|
test_fn: (str => Number.isInteger(str.length / 2)),
|
|
131
|
-
|
|
133
|
+
unexpected_variant: 'invalid-hex',
|
|
132
134
|
unexpected_char: /[^0-9a-f]/i,
|
|
133
135
|
},
|
|
134
136
|
time: {
|
|
135
|
-
|
|
137
|
+
test_variant: 'time',
|
|
136
138
|
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
|
|
137
139
|
},
|
|
138
140
|
date: {
|
|
139
|
-
|
|
140
|
-
test_re: /^[0-9]{
|
|
141
|
+
test_variant: 'date',
|
|
142
|
+
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
|
|
141
143
|
},
|
|
142
144
|
timestamp: {
|
|
143
|
-
|
|
145
|
+
test_variant: 'timestamp',
|
|
144
146
|
test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
|
|
145
147
|
},
|
|
146
148
|
};
|
|
@@ -163,8 +165,8 @@ function notSupportedYet( text, ...tokens ) {
|
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
// Use the following function for language constructs which we (currently) do
|
|
166
|
-
// not really compile, just use to produce a CSN for functions
|
|
167
|
-
//
|
|
168
|
+
// not really compile, just use to produce a CSN for functions parse.cql() and
|
|
169
|
+
// parse.expr().
|
|
168
170
|
function csnParseOnly( text, ...tokens ) {
|
|
169
171
|
if (!text || this.options.parseOnly)
|
|
170
172
|
return;
|
|
@@ -218,6 +220,15 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
|
|
|
218
220
|
ll1.type = this.constructor[tokenName];
|
|
219
221
|
}
|
|
220
222
|
|
|
223
|
+
function setLocalTokenForId( tokenNameMap ) {
|
|
224
|
+
const ll1 = this.getCurrentToken();
|
|
225
|
+
if (ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )) {
|
|
226
|
+
const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
|
|
227
|
+
if (tokenName)
|
|
228
|
+
ll1.type = this.constructor[tokenName];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
221
232
|
// // Special function for rule `requiredSemi` before return $ctx
|
|
222
233
|
// function braceForSemi() {
|
|
223
234
|
// if (RBRACE == null)
|
|
@@ -275,24 +286,50 @@ function meltKeywordToIdentifier( exceptTrueFalseNull = false ) {
|
|
|
275
286
|
token.type = Identifier;
|
|
276
287
|
}
|
|
277
288
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
289
|
+
const genericTokenTypes = {
|
|
290
|
+
expr: 'GenericExpr',
|
|
291
|
+
separator: 'GenericSeparator',
|
|
292
|
+
intro: 'GenericIntro',
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
function prepareGenericKeywords( pathItem, expected = null) {
|
|
296
|
+
const length = pathItem?.args?.length || 0;
|
|
297
|
+
const argPos = (expected ? length -1 : length);
|
|
298
|
+
const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
|
|
299
|
+
const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
|
|
300
|
+
this.$genericKeywords = spec;
|
|
301
|
+
// currently, we only have 'TODO', i.e. a keyword which is alternative to expression
|
|
287
302
|
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
|
|
288
303
|
// as we can have nested special functions
|
|
289
|
-
this.$genericKeywords.argFull = Object.keys( spec );
|
|
290
304
|
// @ts-ignore
|
|
291
305
|
const token = this.getCurrentToken() || { text: '' };
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
306
|
+
const text = token.text.toUpperCase();
|
|
307
|
+
let generic = spec[text];
|
|
308
|
+
// console.log('PGK:',token.text,generic,expected,spec,func,argPos)
|
|
309
|
+
if (expected) { // 'separator' or 'expr' (after 'separator')
|
|
310
|
+
if (generic !== expected)
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
else if (!generic || generic === 'separator') {
|
|
314
|
+
// Mismatch at beginning (or just an expression): keep token type
|
|
315
|
+
// (if not expression, issue error and consider the token to be an
|
|
316
|
+
// expression replacement, like ALL)
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
else if (generic === 'expr' && spec.intro && spec.intro.includes( text )) {
|
|
320
|
+
// token is both an intro and an expression, like LEADING for TRIM
|
|
321
|
+
const next = this._input.LT(2).text;
|
|
322
|
+
if (!next || // followed by EOF -> consider it to be 'intro', better for CC
|
|
323
|
+
next !== ',' && next !== ')' && spec[next.toUpperCase()] !== 'separator')
|
|
324
|
+
generic = 'intro'; // is intro if next token is not separator, not ',', ')'
|
|
295
325
|
}
|
|
326
|
+
// @ts-ignore
|
|
327
|
+
token.type = this.constructor[genericTokenTypes[generic]];
|
|
328
|
+
}
|
|
329
|
+
// To be called before having matched ( HideAlternatives | … )
|
|
330
|
+
function reportErrorForGenericKeyword() {
|
|
331
|
+
this._errHandler.reportUnwantedToken( this );
|
|
332
|
+
//this._errHandler.reportInputMismatch( this, { offending: this._input.LT(1) }, null );
|
|
296
333
|
}
|
|
297
334
|
|
|
298
335
|
// Attach location matched by current rule to node `art`. If a location is
|
|
@@ -310,6 +347,52 @@ function attachLocation( art ) {
|
|
|
310
347
|
return art;
|
|
311
348
|
}
|
|
312
349
|
|
|
350
|
+
function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
351
|
+
const { name, $flatten } = anno;
|
|
352
|
+
const { path } = name;
|
|
353
|
+
if (path.broken || !path[path.length - 1].id)
|
|
354
|
+
return;
|
|
355
|
+
const pathname = pathName( path );
|
|
356
|
+
let absolute = '';
|
|
357
|
+
if (name.variant) {
|
|
358
|
+
absolute = `${ prefix }${ pathname }#${ name.variant.id }`;
|
|
359
|
+
if (iHaveVariant) { // TODO: do we really care in the parser / core compiler?
|
|
360
|
+
this.error( 'anno-duplicate-variant', [ name.variant.location ],
|
|
361
|
+
{}, // TODO: params
|
|
362
|
+
'Annotation variant has been already provided' );
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else if (!prefix || pathname !== '$value') {
|
|
366
|
+
absolute = `${ prefix }${ pathname }`;
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
absolute = prefix.slice( 0, -1 );
|
|
370
|
+
}
|
|
371
|
+
if ($flatten) {
|
|
372
|
+
for (const a of $flatten)
|
|
373
|
+
this.assignAnnotation( art, a, `${ absolute }.`, iHaveVariant || name.variant);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
name.absolute = absolute;
|
|
377
|
+
const prop = '@' + absolute;
|
|
378
|
+
const old = art[prop];
|
|
379
|
+
if (old && old.$inferred)
|
|
380
|
+
art[prop] = anno;
|
|
381
|
+
else
|
|
382
|
+
dictAddArray( art, prop, anno, (n, location, a) => {
|
|
383
|
+
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
|
|
384
|
+
'Duplicate assignment with $(ANNO)' );
|
|
385
|
+
a.$errorReported = true; // do not report again later as anno-duplicate-xyz
|
|
386
|
+
} );
|
|
387
|
+
}
|
|
388
|
+
if (!prefix) { // set deprecated $annnotations for cds-lsp
|
|
389
|
+
if (!art.$annotations)
|
|
390
|
+
art.$annotations = [];
|
|
391
|
+
const location = locUtils.combinedLocation( anno.name, anno );
|
|
392
|
+
art.$annotations.push( { value: anno, location } );
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
313
396
|
/**
|
|
314
397
|
* Return start location of `token`, or the first token matched by the current
|
|
315
398
|
* rule if `token` is undefined
|
|
@@ -409,20 +492,6 @@ function combinedLocation( start, end ) {
|
|
|
409
492
|
return locUtils.combinedLocation( start, end );
|
|
410
493
|
}
|
|
411
494
|
|
|
412
|
-
function createDict( location = null ) {
|
|
413
|
-
const dict = Object.create(null);
|
|
414
|
-
dict[$location] = location || this.startLocation( this._input.LT(-1) );
|
|
415
|
-
return dict;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function setDictEndLocation( dict ) {
|
|
419
|
-
const stop = this._input.LT(-1);
|
|
420
|
-
Object.assign( dict[$location], {
|
|
421
|
-
endLine: stop.line,
|
|
422
|
-
endCol: stop.stop - stop.start + stop.column + 2,
|
|
423
|
-
} );
|
|
424
|
-
}
|
|
425
|
-
|
|
426
495
|
function surroundByParens( expr, open, close, asQuery = false ) {
|
|
427
496
|
if (!expr)
|
|
428
497
|
return expr;
|
|
@@ -488,10 +557,11 @@ function fragileAlias( ast, safe = false ) {
|
|
|
488
557
|
}
|
|
489
558
|
|
|
490
559
|
// Return AST for identifier token `token`. Also check that identifer is not empty.
|
|
491
|
-
function identAst( token, category ) {
|
|
560
|
+
function identAst( token, category, noTokenTypeCheck = false ) {
|
|
492
561
|
token.isIdentifier = category;
|
|
493
562
|
let id = token.text;
|
|
494
|
-
if (
|
|
563
|
+
if (!noTokenTypeCheck &&
|
|
564
|
+
token.type !== this.constructor.Identifier && !/^[a-zA-Z_]+$/.test( id ))
|
|
495
565
|
id = '';
|
|
496
566
|
if (token.text[0] === '!') {
|
|
497
567
|
id = id.slice( 2, -1 ).replace( /]]/g, ']' );
|
|
@@ -518,11 +588,11 @@ function identAst( token, category ) {
|
|
|
518
588
|
return { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
519
589
|
}
|
|
520
590
|
|
|
521
|
-
function functionAst( token,
|
|
591
|
+
function functionAst( token, xpr ) { // only for special function TRIM and EXTRACT
|
|
522
592
|
// TODO: XSN func cleanup
|
|
523
593
|
const location = this.tokenLocation( token );
|
|
524
|
-
const args =
|
|
525
|
-
? [ { op: { location, val: '
|
|
594
|
+
const args = xpr
|
|
595
|
+
? [ { op: { location, val: 'ixpr' }, args: [], location: this.tokenLocation( xpr ) } ]
|
|
526
596
|
: [];
|
|
527
597
|
return {
|
|
528
598
|
op: { location, val: 'call' },
|
|
@@ -532,6 +602,21 @@ function functionAst( token, xprToken ) {
|
|
|
532
602
|
};
|
|
533
603
|
}
|
|
534
604
|
|
|
605
|
+
function setLastAsXpr( args ) {
|
|
606
|
+
const pos = args.length - 1;
|
|
607
|
+
const last = args[pos];
|
|
608
|
+
if (!last || last?.op?.val === 'ixpr') // actuall an internal xpr, which is always flattened
|
|
609
|
+
return last;
|
|
610
|
+
const location = { ...last.location };
|
|
611
|
+
args[pos] = { op: { location, val: 'ixpr' }, args: [ last ], location };
|
|
612
|
+
return args[pos].args;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function xprToken() {
|
|
616
|
+
const token = this._input.LT(-1);
|
|
617
|
+
return { location: this.tokenLocation( token ), val: token.text, literal: 'token' }
|
|
618
|
+
}
|
|
619
|
+
|
|
535
620
|
function valuePathAst( ref ) {
|
|
536
621
|
// TODO: XSN representation of functions is a bit strange - rework if methods
|
|
537
622
|
// are introduced
|
|
@@ -630,21 +715,20 @@ function quotedLiteral( token, literal ) {
|
|
|
630
715
|
literal = token.text.slice( 0, pos - 1 ).toLowerCase();
|
|
631
716
|
const p = quotedLiteralPatterns[literal] || {};
|
|
632
717
|
|
|
633
|
-
// TODO: make tests available for CSN parser
|
|
634
718
|
if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) &&
|
|
635
719
|
!this.options.parseOnly)
|
|
636
|
-
this.
|
|
720
|
+
this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );
|
|
637
721
|
|
|
638
722
|
if (p.unexpected_char) {
|
|
639
723
|
const idx = val.search(p.unexpected_char);
|
|
640
724
|
if (~idx) {
|
|
641
|
-
this.
|
|
725
|
+
this.warning( 'syntax-invalid-literal', {
|
|
642
726
|
file: location.file,
|
|
643
727
|
line: location.line,
|
|
644
728
|
endLine: location.line,
|
|
645
729
|
col: atChar(idx),
|
|
646
730
|
endCol: atChar( idx + (val[idx] === '\'' ? 2 : 1) ),
|
|
647
|
-
}, p.
|
|
731
|
+
}, { '#': p.unexpected_variant } );
|
|
648
732
|
}
|
|
649
733
|
}
|
|
650
734
|
return {
|
|
@@ -691,61 +775,62 @@ function pushIdent( path, ident, prefix ) {
|
|
|
691
775
|
}
|
|
692
776
|
}
|
|
693
777
|
|
|
694
|
-
// Add new definition to dictionary property `env` of node `parent
|
|
695
|
-
//
|
|
696
|
-
// - `name`: argument `name`, which is used as key in the dictionary
|
|
697
|
-
// - `kind`: argument `kind` if that is truthy
|
|
698
|
-
// - `location`: argument `location` or the start location of source matched by
|
|
699
|
-
// current rule
|
|
700
|
-
// - properties in argument `props` which are no empty (undefined, null, {},
|
|
701
|
-
// []), ANTLR tokens are replaced by their locations
|
|
778
|
+
// Add new definition `art` to dictionary property `env` of node `parent`.
|
|
779
|
+
// Return `art`.
|
|
702
780
|
//
|
|
703
|
-
//
|
|
704
|
-
//
|
|
705
|
-
// `
|
|
706
|
-
|
|
781
|
+
// If argument `kind` is provided, set `art.kind` to that value.
|
|
782
|
+
// If argument `name` is provided, set `art.name`:
|
|
783
|
+
// - if `name` is an array, the name consist of the ID of the last path item
|
|
784
|
+
// (for elements via columns, foreign keys, table aliases)
|
|
785
|
+
// - if `name` is an object, the name consist of the IDs of all path items
|
|
786
|
+
// (for main artifact definitions)
|
|
787
|
+
function addDef( art, parent, env, kind, name ) {
|
|
707
788
|
if (Array.isArray(name)) {
|
|
708
789
|
// XSN TODO: clearly say: definitions have name.path, members have name.id
|
|
709
790
|
const last = name.length && name[name.length - 1];
|
|
710
791
|
if (last && last.id) { // // A.B.C -> 'C'
|
|
711
|
-
name = {
|
|
792
|
+
art.name = {
|
|
712
793
|
id: last.id, location: last.location, $inferred: 'as',
|
|
713
794
|
};
|
|
714
795
|
}
|
|
715
796
|
}
|
|
716
|
-
else if (name
|
|
717
|
-
name
|
|
797
|
+
else if (name) {
|
|
798
|
+
art.name = name;
|
|
799
|
+
if (name.id == null)
|
|
800
|
+
name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
|
|
801
|
+
// TODO: get rid of setting `id`, only use for named values in structs
|
|
718
802
|
}
|
|
719
|
-
|
|
803
|
+
|
|
720
804
|
if (kind)
|
|
721
805
|
art.kind = kind;
|
|
722
|
-
if (!parent[env]) // TODO: dump with --test-mode, env !== 'artifacts'
|
|
723
|
-
parent[env] = env === 'args' ? Object.create(null) : this.createDict( { ...location } );
|
|
724
806
|
if (!art.name || art.name.id == null) {
|
|
725
807
|
// no id was parsed, but with error recovery: no further error
|
|
726
808
|
// TODO: add to parent[env]['']
|
|
727
809
|
// which could be tested in name search (then no undefined-ref error)
|
|
810
|
+
// console.log( kind+': !!', art, parent, env, name )
|
|
728
811
|
return art;
|
|
729
812
|
}
|
|
730
|
-
|
|
813
|
+
// console.log( kind+':', art, parent, env, name )
|
|
814
|
+
|
|
815
|
+
if (env === 'artifacts' || env === 'vocabularies') {
|
|
731
816
|
dictAddArray( parent[env], art.name.id, art );
|
|
732
817
|
}
|
|
733
818
|
else if (kind || this.options.parseOnly) {
|
|
734
819
|
dictAdd( parent[env], art.name.id, art );
|
|
735
820
|
}
|
|
736
821
|
else {
|
|
737
|
-
dictAdd( parent[env], art.name.id, art, (
|
|
822
|
+
dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
|
|
738
823
|
// do not use function(), otherwise `this` is wrong:
|
|
739
824
|
if (kind === 0) {
|
|
740
|
-
this.error( 'duplicate-argument', loc, { name },
|
|
825
|
+
this.error( 'duplicate-argument', loc, { name: duplicateName },
|
|
741
826
|
'Duplicate value for parameter $(NAME)' );
|
|
742
827
|
}
|
|
743
828
|
else if (kind === '') {
|
|
744
|
-
this.error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
|
|
829
|
+
this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' },
|
|
745
830
|
'Duplicate $(NAME) in the $(KEYWORD) clause' );
|
|
746
831
|
}
|
|
747
832
|
else {
|
|
748
|
-
this.error( 'duplicate-prop', loc, { name },
|
|
833
|
+
this.error( 'duplicate-prop', loc, { name: duplicateName },
|
|
749
834
|
'Duplicate value for structure property $(NAME)' );
|
|
750
835
|
}
|
|
751
836
|
} );
|
|
@@ -753,85 +838,78 @@ function addDef( parent, env, kind, name, annos, props, location ) {
|
|
|
753
838
|
return art;
|
|
754
839
|
}
|
|
755
840
|
|
|
756
|
-
// Add new definition to array property `env` of node `parent
|
|
757
|
-
//
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
// - properties in argument `props` which are no empty (undefined, null, {},
|
|
762
|
-
// []); ANTLR tokens are replaced by their locations
|
|
763
|
-
//
|
|
764
|
-
// Hack: if argument `location` is exactly `true`, do not set `location`
|
|
765
|
-
// (except if part of `props`), but also include the empty properties of
|
|
766
|
-
// `props`.
|
|
767
|
-
function addItem( parent, env, kind, annos, props, location ) {
|
|
768
|
-
const art = this.assignProps( {}, annos, props, location );
|
|
769
|
-
if (kind)
|
|
770
|
-
art.kind = kind;
|
|
771
|
-
if (!env)
|
|
772
|
-
parent.push( art );
|
|
773
|
-
else if (!parent[env])
|
|
774
|
-
parent[env] = [ art ];
|
|
775
|
-
else
|
|
776
|
-
parent[env].push( art );
|
|
841
|
+
// Add new definition `art` to array property `env` of node `parent`.
|
|
842
|
+
// Also set `kind`. Returns `art`.
|
|
843
|
+
function addItem( art, parent, env, kind ) {
|
|
844
|
+
art.kind = kind;
|
|
845
|
+
parent[env].push( art );
|
|
777
846
|
return art;
|
|
778
847
|
}
|
|
779
|
-
|
|
780
848
|
/**
|
|
781
|
-
*
|
|
782
|
-
*
|
|
849
|
+
* Add `annotate/extend Main.Artifact:elem.sub` to `‹xsn›.extensions`:
|
|
850
|
+
* - the array item is an extend/annotate for `Main.Artifact`,
|
|
851
|
+
* - for each path item in `elem.sub`, we add an `elements` property containing
|
|
852
|
+
* one extend/annotate for the corresponding element
|
|
853
|
+
* - The deepest extend/annotate is the object which is to be extended
|
|
783
854
|
*
|
|
784
|
-
* @param {
|
|
785
|
-
* @param {object}
|
|
786
|
-
* @param {
|
|
787
|
-
* @param {object
|
|
788
|
-
* @param {XSN.
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (!Array.isArray(
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
855
|
+
* @param {object} ext The object containing the location and annotations for the extension.
|
|
856
|
+
* @param {object} parent The parent containing the `extensions` property, i.e. the source.
|
|
857
|
+
* @param {string} kind Either `annotate` or `extend`.
|
|
858
|
+
* @param {object} artName The "name object" for `Main.Artifact`.
|
|
859
|
+
* @param {XSN.Path} elemPath Path as returned by `simplePath` rule.
|
|
860
|
+
*/
|
|
861
|
+
function addExtension( ext, parent, kind, artName, elemPath ) {
|
|
862
|
+
const { location } = ext;
|
|
863
|
+
if (!Array.isArray( elemPath ) || !elemPath.length || elemPath.broken) {
|
|
864
|
+
ext.name = artName;
|
|
865
|
+
this.addItem( ext, parent, 'extensions', kind );
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
// Note: the element extensions share a common `location`, also with the
|
|
869
|
+
// extension of the main artifact; its end location will usually set later
|
|
870
|
+
parent = this.addItem( { name: artName, location }, parent, 'extensions', kind );
|
|
871
|
+
|
|
872
|
+
const last = elemPath[elemPath.length - 1];
|
|
873
|
+
for (const seg of elemPath) {
|
|
874
|
+
parent.elements = Object.create(null); // no dict location → no createDict()
|
|
875
|
+
parent = this.addDef( (seg === last ? ext : { location }),
|
|
876
|
+
parent, 'elements', kind, seg );
|
|
798
877
|
}
|
|
799
|
-
const last = elementPath[elementPath.length - 1];
|
|
800
|
-
artifact = this.addDef( artifact, 'elements', kind,
|
|
801
|
-
{ path: [ last ], location: last.location }, annos, {}, artifactLocation );
|
|
802
|
-
return artifact;
|
|
803
878
|
}
|
|
804
879
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
880
|
+
// must be in action directly after having parsed '{' or '(`
|
|
881
|
+
function createDict() {
|
|
882
|
+
const dict = Object.create(null);
|
|
883
|
+
dict[$location] = this.startLocation( this._input.LT(-1) );
|
|
884
|
+
return dict;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// must be in action directly after having parsed '[' or '(` or `{`
|
|
888
|
+
function createArray() {
|
|
889
|
+
const array = [];
|
|
890
|
+
array[$location] = this.startLocation( this._input.LT(-1) );
|
|
891
|
+
return array;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// must be in action directly after having parsed '}' or ')`
|
|
895
|
+
function finalizeDictOrArray( dict ) {
|
|
896
|
+
const loc = dict[$location];
|
|
897
|
+
if (!loc)
|
|
898
|
+
return;
|
|
899
|
+
const stop = this._input.LT(-1);
|
|
900
|
+
loc.endLine = stop.line;
|
|
901
|
+
loc.endCol = stop.stop - stop.start + stop.column + 2;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function createSource() {
|
|
905
|
+
return {
|
|
906
|
+
kind: 'source',
|
|
907
|
+
usings: [],
|
|
908
|
+
dependencies: [],
|
|
909
|
+
artifacts: Object.create(null),
|
|
910
|
+
// vocabularies: Object.create(null),
|
|
911
|
+
extensions: [],
|
|
912
|
+
};
|
|
835
913
|
}
|
|
836
914
|
|
|
837
915
|
// Create AST node for prefix operator `op` and arguments `args`
|
|
@@ -879,14 +957,12 @@ function setOnce( target, prop, value, ...tokens ) {
|
|
|
879
957
|
target[prop] = value;
|
|
880
958
|
}
|
|
881
959
|
|
|
882
|
-
function setMaxCardinality( art, token, max
|
|
960
|
+
function setMaxCardinality( art, token, max ) {
|
|
883
961
|
const location = this.tokenLocation( token );
|
|
884
962
|
if (!art.cardinality) {
|
|
885
963
|
art.cardinality = { targetMax: Object.assign( { location }, max ), location };
|
|
886
|
-
if (inferred)
|
|
887
|
-
art.cardinality.$inferred = inferred;
|
|
888
964
|
}
|
|
889
|
-
else
|
|
965
|
+
else {
|
|
890
966
|
this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text },
|
|
891
967
|
'The target cardinality has already been specified - ignored $(KEYWORD)' );
|
|
892
968
|
}
|
|
@@ -958,6 +1034,4 @@ function checkTypeFacet( art, argIdent ) {
|
|
|
958
1034
|
}
|
|
959
1035
|
}
|
|
960
1036
|
|
|
961
|
-
module.exports =
|
|
962
|
-
genericAntlrParser: GenericAntlrParser,
|
|
963
|
-
};
|
|
1037
|
+
module.exports = GenericAntlrParser;
|