@sap/cds-compiler 2.13.8 → 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 +155 -1594
- package/bin/cdsc.js +144 -66
- 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 +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- 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 +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -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 +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- 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 +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- 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 +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- 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
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
|
@@ -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,83 +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
|
-
|
|
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
|
+
});
|
|
112
115
|
|
|
113
116
|
// Patterns for literal token tests and creation. The value is a map from the
|
|
114
117
|
// `prefix` argument of function `quotedliteral` to the following properties:
|
|
@@ -119,26 +122,27 @@ GenericAntlrParser.prototype = Object.assign(
|
|
|
119
122
|
// - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
120
123
|
// the error location is only correct for a literal <prefix>'<value>'
|
|
121
124
|
// - `literal`: the value which is used instead of `prefix` in the AST
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// 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
|
|
125
129
|
const quotedLiteralPatterns = {
|
|
126
130
|
x: {
|
|
127
|
-
|
|
131
|
+
test_variant: 'uneven-hex',
|
|
128
132
|
test_fn: (str => Number.isInteger(str.length / 2)),
|
|
129
|
-
|
|
133
|
+
unexpected_variant: 'invalid-hex',
|
|
130
134
|
unexpected_char: /[^0-9a-f]/i,
|
|
131
135
|
},
|
|
132
136
|
time: {
|
|
133
|
-
|
|
137
|
+
test_variant: 'time',
|
|
134
138
|
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
|
|
135
139
|
},
|
|
136
140
|
date: {
|
|
137
|
-
|
|
138
|
-
test_re: /^[0-9]{
|
|
141
|
+
test_variant: 'date',
|
|
142
|
+
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
|
|
139
143
|
},
|
|
140
144
|
timestamp: {
|
|
141
|
-
|
|
145
|
+
test_variant: 'timestamp',
|
|
142
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})?)?$/,
|
|
143
147
|
},
|
|
144
148
|
};
|
|
@@ -161,8 +165,8 @@ function notSupportedYet( text, ...tokens ) {
|
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
// Use the following function for language constructs which we (currently) do
|
|
164
|
-
// not really compile, just use to produce a CSN for functions
|
|
165
|
-
//
|
|
168
|
+
// not really compile, just use to produce a CSN for functions parse.cql() and
|
|
169
|
+
// parse.expr().
|
|
166
170
|
function csnParseOnly( text, ...tokens ) {
|
|
167
171
|
if (!text || this.options.parseOnly)
|
|
168
172
|
return;
|
|
@@ -216,6 +220,15 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
|
|
|
216
220
|
ll1.type = this.constructor[tokenName];
|
|
217
221
|
}
|
|
218
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
|
+
|
|
219
232
|
// // Special function for rule `requiredSemi` before return $ctx
|
|
220
233
|
// function braceForSemi() {
|
|
221
234
|
// if (RBRACE == null)
|
|
@@ -273,24 +286,50 @@ function meltKeywordToIdentifier( exceptTrueFalseNull = false ) {
|
|
|
273
286
|
token.type = Identifier;
|
|
274
287
|
}
|
|
275
288
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
285
302
|
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
|
|
286
303
|
// as we can have nested special functions
|
|
287
|
-
this.$genericKeywords.argFull = Object.keys( spec );
|
|
288
304
|
// @ts-ignore
|
|
289
305
|
const token = this.getCurrentToken() || { text: '' };
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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 ',', ')'
|
|
293
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 );
|
|
294
333
|
}
|
|
295
334
|
|
|
296
335
|
// Attach location matched by current rule to node `art`. If a location is
|
|
@@ -308,6 +347,52 @@ function attachLocation( art ) {
|
|
|
308
347
|
return art;
|
|
309
348
|
}
|
|
310
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
|
+
|
|
311
396
|
/**
|
|
312
397
|
* Return start location of `token`, or the first token matched by the current
|
|
313
398
|
* rule if `token` is undefined
|
|
@@ -407,20 +492,6 @@ function combinedLocation( start, end ) {
|
|
|
407
492
|
return locUtils.combinedLocation( start, end );
|
|
408
493
|
}
|
|
409
494
|
|
|
410
|
-
function createDict( location = null ) {
|
|
411
|
-
const dict = Object.create(null);
|
|
412
|
-
dict[$location] = location || this.startLocation( this._input.LT(-1) );
|
|
413
|
-
return dict;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function setDictEndLocation( dict ) {
|
|
417
|
-
const stop = this._input.LT(-1);
|
|
418
|
-
Object.assign( dict[$location], {
|
|
419
|
-
endLine: stop.line,
|
|
420
|
-
endCol: stop.stop - stop.start + stop.column + 2,
|
|
421
|
-
} );
|
|
422
|
-
}
|
|
423
|
-
|
|
424
495
|
function surroundByParens( expr, open, close, asQuery = false ) {
|
|
425
496
|
if (!expr)
|
|
426
497
|
return expr;
|
|
@@ -486,10 +557,11 @@ function fragileAlias( ast, safe = false ) {
|
|
|
486
557
|
}
|
|
487
558
|
|
|
488
559
|
// Return AST for identifier token `token`. Also check that identifer is not empty.
|
|
489
|
-
function identAst( token, category ) {
|
|
560
|
+
function identAst( token, category, noTokenTypeCheck = false ) {
|
|
490
561
|
token.isIdentifier = category;
|
|
491
562
|
let id = token.text;
|
|
492
|
-
if (
|
|
563
|
+
if (!noTokenTypeCheck &&
|
|
564
|
+
token.type !== this.constructor.Identifier && !/^[a-zA-Z_]+$/.test( id ))
|
|
493
565
|
id = '';
|
|
494
566
|
if (token.text[0] === '!') {
|
|
495
567
|
id = id.slice( 2, -1 ).replace( /]]/g, ']' );
|
|
@@ -516,11 +588,11 @@ function identAst( token, category ) {
|
|
|
516
588
|
return { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
517
589
|
}
|
|
518
590
|
|
|
519
|
-
function functionAst( token,
|
|
591
|
+
function functionAst( token, xpr ) { // only for special function TRIM and EXTRACT
|
|
520
592
|
// TODO: XSN func cleanup
|
|
521
593
|
const location = this.tokenLocation( token );
|
|
522
|
-
const args =
|
|
523
|
-
? [ { op: { location, val: '
|
|
594
|
+
const args = xpr
|
|
595
|
+
? [ { op: { location, val: 'ixpr' }, args: [], location: this.tokenLocation( xpr ) } ]
|
|
524
596
|
: [];
|
|
525
597
|
return {
|
|
526
598
|
op: { location, val: 'call' },
|
|
@@ -530,6 +602,21 @@ function functionAst( token, xprToken ) {
|
|
|
530
602
|
};
|
|
531
603
|
}
|
|
532
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
|
+
|
|
533
620
|
function valuePathAst( ref ) {
|
|
534
621
|
// TODO: XSN representation of functions is a bit strange - rework if methods
|
|
535
622
|
// are introduced
|
|
@@ -594,11 +681,11 @@ function numberLiteral( token, sign, text = token.text ) {
|
|
|
594
681
|
location.endCol = endCol;
|
|
595
682
|
text = sign.text + text;
|
|
596
683
|
}
|
|
684
|
+
|
|
597
685
|
const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
|
|
598
686
|
if (!Number.isSafeInteger(num)) {
|
|
599
687
|
if (sign == null) {
|
|
600
|
-
this.error( 'syntax-
|
|
601
|
-
'An integer number is expected here' );
|
|
688
|
+
this.error( 'syntax-expected-integer', token, { '#': !text.match(/^[0-9]*$/) ? 'normal' : 'unsafe'} );
|
|
602
689
|
}
|
|
603
690
|
else if (text !== `${num}`) {
|
|
604
691
|
return { literal: 'number', val: text, location };
|
|
@@ -628,21 +715,20 @@ function quotedLiteral( token, literal ) {
|
|
|
628
715
|
literal = token.text.slice( 0, pos - 1 ).toLowerCase();
|
|
629
716
|
const p = quotedLiteralPatterns[literal] || {};
|
|
630
717
|
|
|
631
|
-
// TODO: make tests available for CSN parser
|
|
632
718
|
if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) &&
|
|
633
719
|
!this.options.parseOnly)
|
|
634
|
-
this.
|
|
720
|
+
this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );
|
|
635
721
|
|
|
636
722
|
if (p.unexpected_char) {
|
|
637
723
|
const idx = val.search(p.unexpected_char);
|
|
638
724
|
if (~idx) {
|
|
639
|
-
this.
|
|
725
|
+
this.warning( 'syntax-invalid-literal', {
|
|
640
726
|
file: location.file,
|
|
641
727
|
line: location.line,
|
|
642
728
|
endLine: location.line,
|
|
643
729
|
col: atChar(idx),
|
|
644
730
|
endCol: atChar( idx + (val[idx] === '\'' ? 2 : 1) ),
|
|
645
|
-
}, p.
|
|
731
|
+
}, { '#': p.unexpected_variant } );
|
|
646
732
|
}
|
|
647
733
|
}
|
|
648
734
|
return {
|
|
@@ -689,61 +775,62 @@ function pushIdent( path, ident, prefix ) {
|
|
|
689
775
|
}
|
|
690
776
|
}
|
|
691
777
|
|
|
692
|
-
// Add new definition to dictionary property `env` of node `parent
|
|
693
|
-
//
|
|
694
|
-
// - `name`: argument `name`, which is used as key in the dictionary
|
|
695
|
-
// - `kind`: argument `kind` if that is truthy
|
|
696
|
-
// - `location`: argument `location` or the start location of source matched by
|
|
697
|
-
// current rule
|
|
698
|
-
// - properties in argument `props` which are no empty (undefined, null, {},
|
|
699
|
-
// []), ANTLR tokens are replaced by their locations
|
|
778
|
+
// Add new definition `art` to dictionary property `env` of node `parent`.
|
|
779
|
+
// Return `art`.
|
|
700
780
|
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
// `
|
|
704
|
-
|
|
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 ) {
|
|
705
788
|
if (Array.isArray(name)) {
|
|
706
789
|
// XSN TODO: clearly say: definitions have name.path, members have name.id
|
|
707
790
|
const last = name.length && name[name.length - 1];
|
|
708
791
|
if (last && last.id) { // // A.B.C -> 'C'
|
|
709
|
-
name = {
|
|
792
|
+
art.name = {
|
|
710
793
|
id: last.id, location: last.location, $inferred: 'as',
|
|
711
794
|
};
|
|
712
795
|
}
|
|
713
796
|
}
|
|
714
|
-
else if (name
|
|
715
|
-
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
|
|
716
802
|
}
|
|
717
|
-
|
|
803
|
+
|
|
718
804
|
if (kind)
|
|
719
805
|
art.kind = kind;
|
|
720
|
-
if (!parent[env]) // TODO: dump with --test-mode, env !== 'artifacts'
|
|
721
|
-
parent[env] = env === 'args' ? Object.create(null) : this.createDict( { ...location } );
|
|
722
806
|
if (!art.name || art.name.id == null) {
|
|
723
807
|
// no id was parsed, but with error recovery: no further error
|
|
724
808
|
// TODO: add to parent[env]['']
|
|
725
809
|
// which could be tested in name search (then no undefined-ref error)
|
|
810
|
+
// console.log( kind+': !!', art, parent, env, name )
|
|
726
811
|
return art;
|
|
727
812
|
}
|
|
728
|
-
|
|
813
|
+
// console.log( kind+':', art, parent, env, name )
|
|
814
|
+
|
|
815
|
+
if (env === 'artifacts' || env === 'vocabularies') {
|
|
729
816
|
dictAddArray( parent[env], art.name.id, art );
|
|
730
817
|
}
|
|
731
818
|
else if (kind || this.options.parseOnly) {
|
|
732
819
|
dictAdd( parent[env], art.name.id, art );
|
|
733
820
|
}
|
|
734
821
|
else {
|
|
735
|
-
dictAdd( parent[env], art.name.id, art, (
|
|
822
|
+
dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
|
|
736
823
|
// do not use function(), otherwise `this` is wrong:
|
|
737
824
|
if (kind === 0) {
|
|
738
|
-
this.error( 'duplicate-argument', loc, { name },
|
|
825
|
+
this.error( 'duplicate-argument', loc, { name: duplicateName },
|
|
739
826
|
'Duplicate value for parameter $(NAME)' );
|
|
740
827
|
}
|
|
741
828
|
else if (kind === '') {
|
|
742
|
-
this.error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
|
|
829
|
+
this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' },
|
|
743
830
|
'Duplicate $(NAME) in the $(KEYWORD) clause' );
|
|
744
831
|
}
|
|
745
832
|
else {
|
|
746
|
-
this.error( 'duplicate-prop', loc, { name },
|
|
833
|
+
this.error( 'duplicate-prop', loc, { name: duplicateName },
|
|
747
834
|
'Duplicate value for structure property $(NAME)' );
|
|
748
835
|
}
|
|
749
836
|
} );
|
|
@@ -751,85 +838,78 @@ function addDef( parent, env, kind, name, annos, props, location ) {
|
|
|
751
838
|
return art;
|
|
752
839
|
}
|
|
753
840
|
|
|
754
|
-
// Add new definition to array property `env` of node `parent
|
|
755
|
-
//
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
// - properties in argument `props` which are no empty (undefined, null, {},
|
|
760
|
-
// []); ANTLR tokens are replaced by their locations
|
|
761
|
-
//
|
|
762
|
-
// Hack: if argument `location` is exactly `true`, do not set `location`
|
|
763
|
-
// (except if part of `props`), but also include the empty properties of
|
|
764
|
-
// `props`.
|
|
765
|
-
function addItem( parent, env, kind, annos, props, location ) {
|
|
766
|
-
const art = this.assignProps( {}, annos, props, location );
|
|
767
|
-
if (kind)
|
|
768
|
-
art.kind = kind;
|
|
769
|
-
if (!env)
|
|
770
|
-
parent.push( art );
|
|
771
|
-
else if (!parent[env])
|
|
772
|
-
parent[env] = [ art ];
|
|
773
|
-
else
|
|
774
|
-
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 );
|
|
775
846
|
return art;
|
|
776
847
|
}
|
|
777
|
-
|
|
778
848
|
/**
|
|
779
|
-
*
|
|
780
|
-
*
|
|
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
|
|
781
854
|
*
|
|
782
|
-
* @param {
|
|
783
|
-
* @param {object}
|
|
784
|
-
* @param {
|
|
785
|
-
* @param {object
|
|
786
|
-
* @param {XSN.
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if (!Array.isArray(
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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 );
|
|
796
877
|
}
|
|
797
|
-
const last = elementPath[elementPath.length - 1];
|
|
798
|
-
artifact = this.addDef( artifact, 'elements', kind,
|
|
799
|
-
{ path: [ last ], location: last.location }, annos, {}, artifactLocation );
|
|
800
|
-
return artifact;
|
|
801
878
|
}
|
|
802
879
|
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
833
913
|
}
|
|
834
914
|
|
|
835
915
|
// Create AST node for prefix operator `op` and arguments `args`
|
|
@@ -877,14 +957,12 @@ function setOnce( target, prop, value, ...tokens ) {
|
|
|
877
957
|
target[prop] = value;
|
|
878
958
|
}
|
|
879
959
|
|
|
880
|
-
function setMaxCardinality( art, token, max
|
|
960
|
+
function setMaxCardinality( art, token, max ) {
|
|
881
961
|
const location = this.tokenLocation( token );
|
|
882
962
|
if (!art.cardinality) {
|
|
883
963
|
art.cardinality = { targetMax: Object.assign( { location }, max ), location };
|
|
884
|
-
if (inferred)
|
|
885
|
-
art.cardinality.$inferred = inferred;
|
|
886
964
|
}
|
|
887
|
-
else
|
|
965
|
+
else {
|
|
888
966
|
this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text },
|
|
889
967
|
'The target cardinality has already been specified - ignored $(KEYWORD)' );
|
|
890
968
|
}
|
|
@@ -902,6 +980,31 @@ function handleComposition( cardinality, isComposition ) {
|
|
|
902
980
|
this.excludeExpected( [ [ "'}'", 'COMPOSITIONofBRACE' ], brace1, ...manyOne ] );
|
|
903
981
|
}
|
|
904
982
|
|
|
983
|
+
function associationInSelectItem( art ) {
|
|
984
|
+
const isPath = art.value.path && art.value.path.length
|
|
985
|
+
const isIdentifier = isPath && art.value.path.length === 1;
|
|
986
|
+
if (isIdentifier) {
|
|
987
|
+
if (!art.name) {
|
|
988
|
+
art.name = art.value.path[0];
|
|
989
|
+
} else {
|
|
990
|
+
// Use alias if provided, i.e. ignore art.value.path.
|
|
991
|
+
this.error( 'query-unexpected-alias', art.name.location, {},
|
|
992
|
+
'Unexpected alias for association' );
|
|
993
|
+
}
|
|
994
|
+
delete art.value;
|
|
995
|
+
} else {
|
|
996
|
+
const loc = isPath ? art.value.path[1].location : art.value.location;
|
|
997
|
+
// If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
|
|
998
|
+
if (isPath || art.name) {
|
|
999
|
+
this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
|
|
1000
|
+
if (isPath) {
|
|
1001
|
+
art.name = art.value.path[art.value.path.length - 1];
|
|
1002
|
+
}
|
|
1003
|
+
delete art.value;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
905
1008
|
function reportExpandInline( clauseName ) {
|
|
906
1009
|
let token = this.getCurrentToken();
|
|
907
1010
|
// improve error location when using "inline" `.{…}` after ref (arguments and
|
|
@@ -913,6 +1016,22 @@ function reportExpandInline( clauseName ) {
|
|
|
913
1016
|
'Unexpected nested $(PROP), can only be used after a reference' );
|
|
914
1017
|
}
|
|
915
1018
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1019
|
+
function checkTypeFacet( art, argIdent ) {
|
|
1020
|
+
const id = argIdent.id;
|
|
1021
|
+
if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
|
|
1022
|
+
if (art[id] !== undefined) {
|
|
1023
|
+
this.error( 'syntax-duplicate-argument', argIdent.location,
|
|
1024
|
+
{ '#': 'duplicate', code: id } );
|
|
1025
|
+
this.error( 'syntax-duplicate-argument', art[id].location,
|
|
1026
|
+
{ '#': 'duplicate', code: id } );
|
|
1027
|
+
}
|
|
1028
|
+
return true;
|
|
1029
|
+
|
|
1030
|
+
} else {
|
|
1031
|
+
this.error( 'syntax-duplicate-argument', argIdent.location,
|
|
1032
|
+
{ '#': 'unknown', code: id } );
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
module.exports = GenericAntlrParser;
|