@sap/cds-compiler 2.15.2 → 3.0.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 +66 -1590
- package/bin/cdsc.js +42 -46
- 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 +312 -143
- package/lib/api/options.js +15 -85
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +280 -110
- package/lib/base/message-registry.js +80 -24
- package/lib/base/messages.js +103 -52
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +53 -21
- 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 +127 -10
- package/lib/compiler/define.js +6 -4
- package/lib/compiler/extend.js +63 -12
- 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 +16 -14
- package/lib/compiler/propagator.js +3 -3
- package/lib/compiler/resolve.js +194 -222
- package/lib/compiler/shared.js +56 -76
- package/lib/compiler/tweak-assocs.js +9 -10
- package/lib/compiler/utils.js +7 -2
- package/lib/edm/annotations/genericTranslation.js +60 -6
- package/lib/edm/annotations/preprocessAnnotations.js +10 -11
- package/lib/edm/csn2edm.js +39 -41
- package/lib/edm/edm.js +22 -15
- package/lib/edm/edmPreprocessor.js +66 -69
- package/lib/edm/edmUtils.js +12 -62
- package/lib/gen/Dictionary.json +8 -6
- 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 +20717 -22376
- package/lib/json/from-csn.js +73 -68
- package/lib/json/to-csn.js +13 -10
- 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 +333 -259
- package/lib/language/language.g4 +600 -645
- 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 +2 -1
- package/lib/model/csnUtils.js +183 -285
- package/lib/model/revealInternalProperties.js +32 -9
- package/lib/model/sortViews.js +32 -31
- package/lib/optionProcessor.js +64 -57
- 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 +20 -16
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +60 -54
- 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 +13 -15
- package/lib/transform/localized.js +35 -25
- package/lib/transform/odata/toFinalBaseType.js +11 -9
- 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 +6 -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 +31 -21
- package/lib/utils/timetrace.js +20 -21
- package/package.json +34 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- 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,12 +7,13 @@
|
|
|
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');
|
|
14
14
|
const { parseMultiLineStringLiteral } = require('./multiLineStringParser');
|
|
15
|
-
const { functionsWithoutParens, specialFunctions } = require('../compiler/builtins');
|
|
15
|
+
const { functionsWithoutParens, specialFunctions, quotedLiteralPatterns } = require('../compiler/builtins');
|
|
16
|
+
const { pathName } = require("../compiler/utils");
|
|
16
17
|
|
|
17
18
|
const $location = Symbol.for('cds.$location');
|
|
18
19
|
|
|
@@ -32,118 +33,88 @@ function _message( parser, severity, id, loc, ...args ) {
|
|
|
32
33
|
// this.<function>(...)
|
|
33
34
|
// in the actions inside the grammar.
|
|
34
35
|
//
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
|
|
118
|
-
// - `test_fn`: function called with argument `value`, fails falsy return value
|
|
119
|
-
// - `test_re`: regular expression, fails if it does not match argument `value`
|
|
120
|
-
// - `unexpected_msg`: error message which is issued if `unexpected_char` matches
|
|
121
|
-
// - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
122
|
-
// the error location is only correct for a literal <prefix>'<value>'
|
|
123
|
-
// - `literal`: the value which is used instead of `prefix` in the AST
|
|
124
|
-
// - `normalized`: function called with argument `value`, return value is used
|
|
125
|
-
// instead of `value` in the AST
|
|
126
|
-
// TODO: think about laxer regexp for date/time/timestamp - normalization?
|
|
127
|
-
const quotedLiteralPatterns = {
|
|
128
|
-
x: {
|
|
129
|
-
test_msg: 'A binary literal must have an even number of characters',
|
|
130
|
-
test_fn: (str => Number.isInteger(str.length / 2)),
|
|
131
|
-
unexpected_msg: 'A binary literal must only contain characters 0-9, a-f and A-F',
|
|
132
|
-
unexpected_char: /[^0-9a-f]/i,
|
|
133
|
-
},
|
|
134
|
-
time: {
|
|
135
|
-
test_msg: 'Expected time\'HH:MM:SS\' where H, M and S are numbers and \':SS\' is optional',
|
|
136
|
-
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
|
|
137
|
-
},
|
|
138
|
-
date: {
|
|
139
|
-
test_msg: 'Expected date\'YYYY-MM-DD\' where Y, M and D are numbers',
|
|
140
|
-
test_re: /^[0-9]{1,4}-[0-9]{1,2}-[0-9]{1,2}$/,
|
|
141
|
-
},
|
|
142
|
-
timestamp: {
|
|
143
|
-
test_msg: 'Expected timestamp\'YYYY-MM-DD HH:MM:SS.u…u\' where Y, M, D, H, S and u are numbers (optional 1-7×u)',
|
|
144
|
-
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
|
-
},
|
|
146
|
-
};
|
|
36
|
+
class GenericAntlrParser extends antlr4.Parser {
|
|
37
|
+
constructor( ...args ) {
|
|
38
|
+
// ANTLR restriction: we cannot add parameters to the constructor.
|
|
39
|
+
super( ...args );
|
|
40
|
+
this.buildParseTrees = false;
|
|
41
|
+
|
|
42
|
+
// Common properties.
|
|
43
|
+
// We set them here so that they are available in the prototype.
|
|
44
|
+
// This improved performance by 25% for certain scenario tests.
|
|
45
|
+
// Probably because there was no need to look up the prototype chain anymore.
|
|
46
|
+
this.$adaptExpectedToken = null;
|
|
47
|
+
this.$adaptExpectedExcludes = [ ];
|
|
48
|
+
this.$nextTokensToken = null;
|
|
49
|
+
this.$nextTokensContext = null;
|
|
50
|
+
|
|
51
|
+
this.options = {};
|
|
52
|
+
|
|
53
|
+
this.genericFunctionsStack = [];
|
|
54
|
+
this.$genericKeywords = specialFunctions[''][1];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// TODO: Use actual methods.
|
|
59
|
+
Object.assign(GenericAntlrParser.prototype, {
|
|
60
|
+
message: function(...args) { return _message( this, 'message', ...args ); },
|
|
61
|
+
error: function(...args) { return _message( this, 'error', ...args ); },
|
|
62
|
+
warning: function(...args) { return _message( this, 'warning', ...args ); },
|
|
63
|
+
info: function(...args) { return _message( this, 'info', ...args ); },
|
|
64
|
+
attachLocation,
|
|
65
|
+
assignAnnotation,
|
|
66
|
+
checkExtensionDict,
|
|
67
|
+
handleExtension,
|
|
68
|
+
startLocation,
|
|
69
|
+
tokenLocation,
|
|
70
|
+
valueWithTokenLocation,
|
|
71
|
+
previousTokenAtLocation,
|
|
72
|
+
combinedLocation,
|
|
73
|
+
surroundByParens,
|
|
74
|
+
unaryOpForParens,
|
|
75
|
+
leftAssocBinaryOp,
|
|
76
|
+
classifyImplicitName,
|
|
77
|
+
fragileAlias,
|
|
78
|
+
identAst,
|
|
79
|
+
functionAst,
|
|
80
|
+
setLastAsXpr,
|
|
81
|
+
xprToken,
|
|
82
|
+
valuePathAst,
|
|
83
|
+
signedExpression,
|
|
84
|
+
numberLiteral,
|
|
85
|
+
quotedLiteral,
|
|
86
|
+
pathName,
|
|
87
|
+
docComment,
|
|
88
|
+
addDef,
|
|
89
|
+
addItem,
|
|
90
|
+
addExtension,
|
|
91
|
+
createSource,
|
|
92
|
+
createDict,
|
|
93
|
+
createArray,
|
|
94
|
+
finalizeDictOrArray,
|
|
95
|
+
createPrefixOp,
|
|
96
|
+
setOnce,
|
|
97
|
+
setMaxCardinality,
|
|
98
|
+
pushIdent,
|
|
99
|
+
handleComposition,
|
|
100
|
+
associationInSelectItem,
|
|
101
|
+
reportExpandInline,
|
|
102
|
+
checkTypeFacet,
|
|
103
|
+
notSupportedYet,
|
|
104
|
+
csnParseOnly,
|
|
105
|
+
disallowElementExtension,
|
|
106
|
+
noAssignmentInSameLine,
|
|
107
|
+
noSemicolonHere,
|
|
108
|
+
setLocalToken,
|
|
109
|
+
setLocalTokenIfBefore,
|
|
110
|
+
setLocalTokenForId,
|
|
111
|
+
excludeExpected,
|
|
112
|
+
isStraightBefore,
|
|
113
|
+
meltKeywordToIdentifier,
|
|
114
|
+
prepareGenericKeywords,
|
|
115
|
+
reportErrorForGenericKeyword,
|
|
116
|
+
parseMultiLineStringLiteral,
|
|
117
|
+
});
|
|
147
118
|
|
|
148
119
|
// Use the following function for language constructs which we (currently)
|
|
149
120
|
// just being able to parse, in able to run tests from HANA CDS. As soon as we
|
|
@@ -163,8 +134,8 @@ function notSupportedYet( text, ...tokens ) {
|
|
|
163
134
|
}
|
|
164
135
|
|
|
165
136
|
// Use the following function for language constructs which we (currently) do
|
|
166
|
-
// not really compile, just use to produce a CSN for functions
|
|
167
|
-
//
|
|
137
|
+
// not really compile, just use to produce a CSN for functions parse.cql() and
|
|
138
|
+
// parse.expr().
|
|
168
139
|
function csnParseOnly( text, ...tokens ) {
|
|
169
140
|
if (!text || this.options.parseOnly)
|
|
170
141
|
return;
|
|
@@ -218,6 +189,15 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
|
|
|
218
189
|
ll1.type = this.constructor[tokenName];
|
|
219
190
|
}
|
|
220
191
|
|
|
192
|
+
function setLocalTokenForId( tokenNameMap ) {
|
|
193
|
+
const ll1 = this.getCurrentToken();
|
|
194
|
+
if (ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )) {
|
|
195
|
+
const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
|
|
196
|
+
if (tokenName)
|
|
197
|
+
ll1.type = this.constructor[tokenName];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
221
201
|
// // Special function for rule `requiredSemi` before return $ctx
|
|
222
202
|
// function braceForSemi() {
|
|
223
203
|
// if (RBRACE == null)
|
|
@@ -275,24 +255,50 @@ function meltKeywordToIdentifier( exceptTrueFalseNull = false ) {
|
|
|
275
255
|
token.type = Identifier;
|
|
276
256
|
}
|
|
277
257
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
258
|
+
const genericTokenTypes = {
|
|
259
|
+
expr: 'GenericExpr',
|
|
260
|
+
separator: 'GenericSeparator',
|
|
261
|
+
intro: 'GenericIntro',
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
function prepareGenericKeywords( pathItem, expected = null) {
|
|
265
|
+
const length = pathItem?.args?.length || 0;
|
|
266
|
+
const argPos = (expected ? length -1 : length);
|
|
267
|
+
const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
|
|
268
|
+
const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
|
|
269
|
+
this.$genericKeywords = spec;
|
|
270
|
+
// currently, we only have 'TODO', i.e. a keyword which is alternative to expression
|
|
287
271
|
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
|
|
288
272
|
// as we can have nested special functions
|
|
289
|
-
this.$genericKeywords.argFull = Object.keys( spec );
|
|
290
273
|
// @ts-ignore
|
|
291
274
|
const token = this.getCurrentToken() || { text: '' };
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
275
|
+
const text = token.text.toUpperCase();
|
|
276
|
+
let generic = spec[text];
|
|
277
|
+
// console.log('PGK:',token.text,generic,expected,spec,func,argPos)
|
|
278
|
+
if (expected) { // 'separator' or 'expr' (after 'separator')
|
|
279
|
+
if (generic !== expected)
|
|
280
|
+
return;
|
|
295
281
|
}
|
|
282
|
+
else if (!generic || generic === 'separator') {
|
|
283
|
+
// Mismatch at beginning (or just an expression): keep token type
|
|
284
|
+
// (if not expression, issue error and consider the token to be an
|
|
285
|
+
// expression replacement, like ALL)
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
else if (generic === 'expr' && spec.intro && spec.intro.includes( text )) {
|
|
289
|
+
// token is both an intro and an expression, like LEADING for TRIM
|
|
290
|
+
const next = this._input.LT(2).text;
|
|
291
|
+
if (!next || // followed by EOF -> consider it to be 'intro', better for CC
|
|
292
|
+
next !== ',' && next !== ')' && spec[next.toUpperCase()] !== 'separator')
|
|
293
|
+
generic = 'intro'; // is intro if next token is not separator, not ',', ')'
|
|
294
|
+
}
|
|
295
|
+
// @ts-ignore
|
|
296
|
+
token.type = this.constructor[genericTokenTypes[generic]];
|
|
297
|
+
}
|
|
298
|
+
// To be called before having matched ( HideAlternatives | … )
|
|
299
|
+
function reportErrorForGenericKeyword() {
|
|
300
|
+
this._errHandler.reportUnwantedToken( this );
|
|
301
|
+
//this._errHandler.reportInputMismatch( this, { offending: this._input.LT(1) }, null );
|
|
296
302
|
}
|
|
297
303
|
|
|
298
304
|
// Attach location matched by current rule to node `art`. If a location is
|
|
@@ -310,6 +316,84 @@ function attachLocation( art ) {
|
|
|
310
316
|
return art;
|
|
311
317
|
}
|
|
312
318
|
|
|
319
|
+
function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
|
|
320
|
+
const { name, $flatten } = anno;
|
|
321
|
+
const { path } = name;
|
|
322
|
+
if (path.broken || !path[path.length - 1].id)
|
|
323
|
+
return;
|
|
324
|
+
const pathname = pathName( path );
|
|
325
|
+
let absolute = '';
|
|
326
|
+
if (name.variant) {
|
|
327
|
+
const variant = pathName( name.variant.path );
|
|
328
|
+
absolute = `${ prefix }${ pathname }#${ variant }`;
|
|
329
|
+
if (iHaveVariant) { // TODO: do we really care in the parser / core compiler?
|
|
330
|
+
this.error( 'anno-duplicate-variant', [ name.variant.location ],
|
|
331
|
+
{}, // TODO: params
|
|
332
|
+
'Annotation variant has been already provided' );
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else if (!prefix || pathname !== '$value') {
|
|
336
|
+
absolute = `${ prefix }${ pathname }`;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
absolute = prefix.slice( 0, -1 );
|
|
340
|
+
}
|
|
341
|
+
if ($flatten) {
|
|
342
|
+
for (const a of $flatten)
|
|
343
|
+
this.assignAnnotation( art, a, `${ absolute }.`, iHaveVariant || name.variant);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
name.absolute = absolute;
|
|
347
|
+
const prop = '@' + absolute;
|
|
348
|
+
const old = art[prop];
|
|
349
|
+
if (old && old.$inferred)
|
|
350
|
+
art[prop] = anno;
|
|
351
|
+
else
|
|
352
|
+
dictAddArray( art, prop, anno, (n, location, a) => {
|
|
353
|
+
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
|
|
354
|
+
'Duplicate assignment with $(ANNO)' );
|
|
355
|
+
a.$errorReported = 'syntax-duplicate-anno';
|
|
356
|
+
// do not report again later as anno-duplicate-xyz
|
|
357
|
+
} );
|
|
358
|
+
}
|
|
359
|
+
if (!prefix) { // set deprecated $annnotations for cds-lsp
|
|
360
|
+
if (!art.$annotations)
|
|
361
|
+
art.$annotations = [];
|
|
362
|
+
const location = locUtils.combinedLocation( anno.name, anno );
|
|
363
|
+
art.$annotations.push( { value: anno, location } );
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function checkExtensionDict( dict ) {
|
|
368
|
+
for (const name in dict) {
|
|
369
|
+
const def = dict[name];
|
|
370
|
+
if (!def.$duplicates)
|
|
371
|
+
continue;
|
|
372
|
+
|
|
373
|
+
const numDefines = (def.kind === 'annotate')
|
|
374
|
+
? 0
|
|
375
|
+
: def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
|
|
376
|
+
this.handleExtension( def, name, numDefines );
|
|
377
|
+
for (const dup of def.$duplicates)
|
|
378
|
+
this.handleExtension( dup, name, numDefines );
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function addOneForDefinition( count, ext ) {
|
|
383
|
+
return (ext.kind === 'extend') ? count : count + 1;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function handleExtension( ext, name, numDefines ) {
|
|
387
|
+
if (ext.kind === 'annotate')
|
|
388
|
+
this.warning( 'syntax-duplicate-annotate', [ ext.name.location ], { name } );
|
|
389
|
+
else if (ext.kind === 'extend')
|
|
390
|
+
this.error( 'syntax-duplicate-extend', [ ext.name.location ],
|
|
391
|
+
{ name, '#': (numDefines ? 'define' : 'extend') } );
|
|
392
|
+
else if (numDefines === 1)
|
|
393
|
+
ext.$errorReported = 'syntax-duplicate-extend'; // a definition, but not duplicate
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
313
397
|
/**
|
|
314
398
|
* Return start location of `token`, or the first token matched by the current
|
|
315
399
|
* rule if `token` is undefined
|
|
@@ -409,20 +493,6 @@ function combinedLocation( start, end ) {
|
|
|
409
493
|
return locUtils.combinedLocation( start, end );
|
|
410
494
|
}
|
|
411
495
|
|
|
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
496
|
function surroundByParens( expr, open, close, asQuery = false ) {
|
|
427
497
|
if (!expr)
|
|
428
498
|
return expr;
|
|
@@ -435,7 +505,7 @@ function surroundByParens( expr, open, close, asQuery = false ) {
|
|
|
435
505
|
}
|
|
436
506
|
|
|
437
507
|
function unaryOpForParens( query, val ) {
|
|
438
|
-
const parens = query
|
|
508
|
+
const parens = query?.$parens;
|
|
439
509
|
if (!parens)
|
|
440
510
|
return query;
|
|
441
511
|
const location = parens[parens.length - 1];
|
|
@@ -488,10 +558,11 @@ function fragileAlias( ast, safe = false ) {
|
|
|
488
558
|
}
|
|
489
559
|
|
|
490
560
|
// Return AST for identifier token `token`. Also check that identifer is not empty.
|
|
491
|
-
function identAst( token, category ) {
|
|
561
|
+
function identAst( token, category, noTokenTypeCheck = false ) {
|
|
492
562
|
token.isIdentifier = category;
|
|
493
563
|
let id = token.text;
|
|
494
|
-
if (
|
|
564
|
+
if (!noTokenTypeCheck &&
|
|
565
|
+
token.type !== this.constructor.Identifier && !/^[a-zA-Z_]+$/.test( id ))
|
|
495
566
|
id = '';
|
|
496
567
|
if (token.text[0] === '!') {
|
|
497
568
|
id = id.slice( 2, -1 ).replace( /]]/g, ']' );
|
|
@@ -518,11 +589,11 @@ function identAst( token, category ) {
|
|
|
518
589
|
return { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
519
590
|
}
|
|
520
591
|
|
|
521
|
-
function functionAst( token,
|
|
592
|
+
function functionAst( token, xpr ) { // only for special function TRIM and EXTRACT
|
|
522
593
|
// TODO: XSN func cleanup
|
|
523
594
|
const location = this.tokenLocation( token );
|
|
524
|
-
const args =
|
|
525
|
-
? [ { op: { location, val: '
|
|
595
|
+
const args = xpr
|
|
596
|
+
? [ { op: { location, val: 'ixpr' }, args: [], location: this.tokenLocation( xpr ) } ]
|
|
526
597
|
: [];
|
|
527
598
|
return {
|
|
528
599
|
op: { location, val: 'call' },
|
|
@@ -532,6 +603,21 @@ function functionAst( token, xprToken ) {
|
|
|
532
603
|
};
|
|
533
604
|
}
|
|
534
605
|
|
|
606
|
+
function setLastAsXpr( args ) {
|
|
607
|
+
const pos = args.length - 1;
|
|
608
|
+
const last = args[pos];
|
|
609
|
+
if (!last || last?.op?.val === 'ixpr') // actuall an internal xpr, which is always flattened
|
|
610
|
+
return last;
|
|
611
|
+
const location = { ...last.location };
|
|
612
|
+
args[pos] = { op: { location, val: 'ixpr' }, args: [ last ], location };
|
|
613
|
+
return args[pos].args;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function xprToken() {
|
|
617
|
+
const token = this._input.LT(-1);
|
|
618
|
+
return { location: this.tokenLocation( token ), val: token.text, literal: 'token' }
|
|
619
|
+
}
|
|
620
|
+
|
|
535
621
|
function valuePathAst( ref ) {
|
|
536
622
|
// TODO: XSN representation of functions is a bit strange - rework if methods
|
|
537
623
|
// are introduced
|
|
@@ -630,21 +716,20 @@ function quotedLiteral( token, literal ) {
|
|
|
630
716
|
literal = token.text.slice( 0, pos - 1 ).toLowerCase();
|
|
631
717
|
const p = quotedLiteralPatterns[literal] || {};
|
|
632
718
|
|
|
633
|
-
// TODO: make tests available for CSN parser
|
|
634
719
|
if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) &&
|
|
635
720
|
!this.options.parseOnly)
|
|
636
|
-
this.
|
|
721
|
+
this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );
|
|
637
722
|
|
|
638
723
|
if (p.unexpected_char) {
|
|
639
724
|
const idx = val.search(p.unexpected_char);
|
|
640
725
|
if (~idx) {
|
|
641
|
-
this.
|
|
726
|
+
this.warning( 'syntax-invalid-literal', {
|
|
642
727
|
file: location.file,
|
|
643
728
|
line: location.line,
|
|
644
729
|
endLine: location.line,
|
|
645
730
|
col: atChar(idx),
|
|
646
731
|
endCol: atChar( idx + (val[idx] === '\'' ? 2 : 1) ),
|
|
647
|
-
}, p.
|
|
732
|
+
}, { '#': p.unexpected_variant } );
|
|
648
733
|
}
|
|
649
734
|
}
|
|
650
735
|
return {
|
|
@@ -659,10 +744,6 @@ function quotedLiteral( token, literal ) {
|
|
|
659
744
|
}
|
|
660
745
|
}
|
|
661
746
|
|
|
662
|
-
function pathName( path, brokenName ) {
|
|
663
|
-
return (path && !path.broken) ? path.map( id => id.id ).join('.') : brokenName;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
747
|
function pushIdent( path, ident, prefix ) {
|
|
667
748
|
if (!ident) {
|
|
668
749
|
path.broken = true;
|
|
@@ -691,61 +772,62 @@ function pushIdent( path, ident, prefix ) {
|
|
|
691
772
|
}
|
|
692
773
|
}
|
|
693
774
|
|
|
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
|
|
775
|
+
// Add new definition `art` to dictionary property `env` of node `parent`.
|
|
776
|
+
// Return `art`.
|
|
702
777
|
//
|
|
703
|
-
//
|
|
704
|
-
//
|
|
705
|
-
// `
|
|
706
|
-
|
|
778
|
+
// If argument `kind` is provided, set `art.kind` to that value.
|
|
779
|
+
// If argument `name` is provided, set `art.name`:
|
|
780
|
+
// - if `name` is an array, the name consist of the ID of the last path item
|
|
781
|
+
// (for elements via columns, foreign keys, table aliases)
|
|
782
|
+
// - if `name` is an object, the name consist of the IDs of all path items
|
|
783
|
+
// (for main artifact definitions)
|
|
784
|
+
function addDef( art, parent, env, kind, name ) {
|
|
707
785
|
if (Array.isArray(name)) {
|
|
708
786
|
// XSN TODO: clearly say: definitions have name.path, members have name.id
|
|
709
787
|
const last = name.length && name[name.length - 1];
|
|
710
788
|
if (last && last.id) { // // A.B.C -> 'C'
|
|
711
|
-
name = {
|
|
789
|
+
art.name = {
|
|
712
790
|
id: last.id, location: last.location, $inferred: 'as',
|
|
713
791
|
};
|
|
714
792
|
}
|
|
715
793
|
}
|
|
716
|
-
else if (name
|
|
717
|
-
name
|
|
794
|
+
else if (name) {
|
|
795
|
+
art.name = name;
|
|
796
|
+
if (name.id == null)
|
|
797
|
+
name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
|
|
798
|
+
// TODO: get rid of setting `id`, only use for named values in structs
|
|
718
799
|
}
|
|
719
|
-
|
|
800
|
+
|
|
720
801
|
if (kind)
|
|
721
802
|
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
803
|
if (!art.name || art.name.id == null) {
|
|
725
804
|
// no id was parsed, but with error recovery: no further error
|
|
726
805
|
// TODO: add to parent[env]['']
|
|
727
806
|
// which could be tested in name search (then no undefined-ref error)
|
|
807
|
+
// console.log( kind+': !!', art, parent, env, name )
|
|
728
808
|
return art;
|
|
729
809
|
}
|
|
730
|
-
|
|
810
|
+
// console.log( kind+':', art, parent, env, name )
|
|
811
|
+
|
|
812
|
+
if (env === 'artifacts' || env === 'vocabularies') {
|
|
731
813
|
dictAddArray( parent[env], art.name.id, art );
|
|
732
814
|
}
|
|
733
815
|
else if (kind || this.options.parseOnly) {
|
|
734
816
|
dictAdd( parent[env], art.name.id, art );
|
|
735
817
|
}
|
|
736
818
|
else {
|
|
737
|
-
dictAdd( parent[env], art.name.id, art, (
|
|
819
|
+
dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
|
|
738
820
|
// do not use function(), otherwise `this` is wrong:
|
|
739
821
|
if (kind === 0) {
|
|
740
|
-
this.error( 'duplicate-argument', loc, { name },
|
|
822
|
+
this.error( 'duplicate-argument', loc, { name: duplicateName },
|
|
741
823
|
'Duplicate value for parameter $(NAME)' );
|
|
742
824
|
}
|
|
743
825
|
else if (kind === '') {
|
|
744
|
-
this.error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
|
|
826
|
+
this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' },
|
|
745
827
|
'Duplicate $(NAME) in the $(KEYWORD) clause' );
|
|
746
828
|
}
|
|
747
829
|
else {
|
|
748
|
-
this.error( 'duplicate-prop', loc, { name },
|
|
830
|
+
this.error( 'duplicate-prop', loc, { name: duplicateName },
|
|
749
831
|
'Duplicate value for structure property $(NAME)' );
|
|
750
832
|
}
|
|
751
833
|
} );
|
|
@@ -753,85 +835,78 @@ function addDef( parent, env, kind, name, annos, props, location ) {
|
|
|
753
835
|
return art;
|
|
754
836
|
}
|
|
755
837
|
|
|
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 );
|
|
838
|
+
// Add new definition `art` to array property `env` of node `parent`.
|
|
839
|
+
// Also set `kind`. Returns `art`.
|
|
840
|
+
function addItem( art, parent, env, kind ) {
|
|
841
|
+
art.kind = kind;
|
|
842
|
+
parent[env].push( art );
|
|
777
843
|
return art;
|
|
778
844
|
}
|
|
779
|
-
|
|
780
845
|
/**
|
|
781
|
-
*
|
|
782
|
-
*
|
|
846
|
+
* Add `annotate/extend Main.Artifact:elem.sub` to `‹xsn›.extensions`:
|
|
847
|
+
* - the array item is an extend/annotate for `Main.Artifact`,
|
|
848
|
+
* - for each path item in `elem.sub`, we add an `elements` property containing
|
|
849
|
+
* one extend/annotate for the corresponding element
|
|
850
|
+
* - The deepest extend/annotate is the object which is to be extended
|
|
783
851
|
*
|
|
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
|
-
|
|
852
|
+
* @param {object} ext The object containing the location and annotations for the extension.
|
|
853
|
+
* @param {object} parent The parent containing the `extensions` property, i.e. the source.
|
|
854
|
+
* @param {string} kind Either `annotate` or `extend`.
|
|
855
|
+
* @param {object} artName The "name object" for `Main.Artifact`.
|
|
856
|
+
* @param {XSN.Path} elemPath Path as returned by `simplePath` rule.
|
|
857
|
+
*/
|
|
858
|
+
function addExtension( ext, parent, kind, artName, elemPath ) {
|
|
859
|
+
const { location } = ext;
|
|
860
|
+
if (!Array.isArray( elemPath ) || !elemPath.length || elemPath.broken) {
|
|
861
|
+
ext.name = artName;
|
|
862
|
+
this.addItem( ext, parent, 'extensions', kind );
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
// Note: the element extensions share a common `location`, also with the
|
|
866
|
+
// extension of the main artifact; its end location will usually set later
|
|
867
|
+
parent = this.addItem( { name: artName, location }, parent, 'extensions', kind );
|
|
868
|
+
|
|
869
|
+
const last = elemPath[elemPath.length - 1];
|
|
870
|
+
for (const seg of elemPath) {
|
|
871
|
+
parent.elements = Object.create(null); // no dict location → no createDict()
|
|
872
|
+
parent = this.addDef( (seg === last ? ext : { location }),
|
|
873
|
+
parent, 'elements', kind, seg );
|
|
798
874
|
}
|
|
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
875
|
}
|
|
804
876
|
|
|
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
|
-
|
|
877
|
+
// must be in action directly after having parsed '{' or '(`
|
|
878
|
+
function createDict() {
|
|
879
|
+
const dict = Object.create(null);
|
|
880
|
+
dict[$location] = this.startLocation( this._input.LT(-1) );
|
|
881
|
+
return dict;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// must be in action directly after having parsed '[' or '(` or `{`
|
|
885
|
+
function createArray() {
|
|
886
|
+
const array = [];
|
|
887
|
+
array[$location] = this.startLocation( this._input.LT(-1) );
|
|
888
|
+
return array;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// must be in action directly after having parsed '}' or ')`
|
|
892
|
+
function finalizeDictOrArray( dict ) {
|
|
893
|
+
const loc = dict[$location];
|
|
894
|
+
if (!loc)
|
|
895
|
+
return;
|
|
896
|
+
const stop = this._input.LT(-1);
|
|
897
|
+
loc.endLine = stop.line;
|
|
898
|
+
loc.endCol = stop.stop - stop.start + stop.column + 2;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function createSource() {
|
|
902
|
+
return {
|
|
903
|
+
kind: 'source',
|
|
904
|
+
usings: [],
|
|
905
|
+
dependencies: [],
|
|
906
|
+
artifacts: Object.create(null),
|
|
907
|
+
// vocabularies: Object.create(null),
|
|
908
|
+
extensions: [],
|
|
909
|
+
};
|
|
835
910
|
}
|
|
836
911
|
|
|
837
912
|
// Create AST node for prefix operator `op` and arguments `args`
|
|
@@ -879,14 +954,12 @@ function setOnce( target, prop, value, ...tokens ) {
|
|
|
879
954
|
target[prop] = value;
|
|
880
955
|
}
|
|
881
956
|
|
|
882
|
-
function setMaxCardinality( art, token, max
|
|
957
|
+
function setMaxCardinality( art, token, max ) {
|
|
883
958
|
const location = this.tokenLocation( token );
|
|
884
959
|
if (!art.cardinality) {
|
|
885
960
|
art.cardinality = { targetMax: Object.assign( { location }, max ), location };
|
|
886
|
-
if (inferred)
|
|
887
|
-
art.cardinality.$inferred = inferred;
|
|
888
961
|
}
|
|
889
|
-
else
|
|
962
|
+
else {
|
|
890
963
|
this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text },
|
|
891
964
|
'The target cardinality has already been specified - ignored $(KEYWORD)' );
|
|
892
965
|
}
|
|
@@ -905,6 +978,9 @@ function handleComposition( cardinality, isComposition ) {
|
|
|
905
978
|
}
|
|
906
979
|
|
|
907
980
|
function associationInSelectItem( art ) {
|
|
981
|
+
if (!art.value) // e.g. `expand` without value (for new structures)
|
|
982
|
+
return;
|
|
983
|
+
|
|
908
984
|
const isPath = art.value.path && art.value.path.length
|
|
909
985
|
const isIdentifier = isPath && art.value.path.length === 1;
|
|
910
986
|
if (isIdentifier) {
|
|
@@ -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;
|