@sap/cds-compiler 3.3.2 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/bin/cdsc.js +3 -1
- package/doc/CHANGELOG_BETA.md +17 -0
- package/lib/api/main.js +147 -18
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/keywords.js +104 -0
- package/lib/base/message-registry.js +136 -67
- package/lib/base/messages.js +59 -48
- package/lib/base/model.js +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +13 -8
- package/lib/checks/defaultValues.js +3 -1
- package/lib/checks/elements.js +1 -1
- package/lib/checks/parameters.js +4 -2
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/validator.js +14 -4
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/checks.js +30 -20
- package/lib/compiler/define.js +89 -25
- package/lib/compiler/extend.js +21 -18
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/populate.js +30 -8
- package/lib/compiler/propagator.js +4 -2
- package/lib/compiler/resolve.js +11 -5
- package/lib/compiler/shared.js +66 -48
- package/lib/compiler/tweak-assocs.js +2 -3
- package/lib/compiler/utils.js +11 -0
- package/lib/edm/annotations/genericTranslation.js +7 -4
- package/lib/edm/csn2edm.js +1 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3565 -3544
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +140 -158
- package/lib/json/to-csn.js +23 -5
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +7 -10
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +115 -84
- package/lib/language/language.g4 +29 -25
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.js +1 -0
- package/lib/model/csnRefs.js +4 -3
- package/lib/model/csnUtils.js +39 -7
- package/lib/model/sortViews.js +7 -3
- package/lib/modelCompare/compare.js +49 -15
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +5 -1
- package/lib/render/manageConstraints.js +9 -5
- package/lib/render/toCdl.js +120 -62
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +6 -2
- package/lib/render/utils/common.js +7 -0
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +11 -4
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +7 -1
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forRelationalDB.js +12 -6
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +23 -14
- package/lib/transform/translateAssocsToJoins.js +12 -12
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
|
@@ -12,17 +12,21 @@ 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 {
|
|
16
|
-
|
|
15
|
+
const {
|
|
16
|
+
functionsWithoutParens,
|
|
17
|
+
specialFunctions,
|
|
18
|
+
quotedLiteralPatterns,
|
|
19
|
+
} = require('../compiler/builtins');
|
|
20
|
+
const { pathName } = require('../compiler/utils');
|
|
21
|
+
const { isBetaEnabled } = require('../base/model');
|
|
17
22
|
|
|
18
23
|
const $location = Symbol.for('cds.$location');
|
|
19
24
|
|
|
20
25
|
// Push message `msg` with location `loc` to array of errors:
|
|
21
26
|
function _message( parser, severity, id, loc, ...args ) {
|
|
22
27
|
const msg = parser.$messageFunctions[severity]; // set in antlrParser.js
|
|
23
|
-
if (loc instanceof antlr4.CommonToken)
|
|
28
|
+
if (loc instanceof antlr4.CommonToken)
|
|
24
29
|
loc = parser.tokenLocation(loc);
|
|
25
|
-
}
|
|
26
30
|
return msg( id, loc, ...args );
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -57,10 +61,18 @@ class GenericAntlrParser extends antlr4.Parser {
|
|
|
57
61
|
|
|
58
62
|
// TODO: Use actual methods.
|
|
59
63
|
Object.assign(GenericAntlrParser.prototype, {
|
|
60
|
-
message
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
message(...args) {
|
|
65
|
+
return _message( this, 'message', ...args );
|
|
66
|
+
},
|
|
67
|
+
error(...args) {
|
|
68
|
+
return _message( this, 'error', ...args );
|
|
69
|
+
},
|
|
70
|
+
warning(...args) {
|
|
71
|
+
return _message( this, 'warning', ...args );
|
|
72
|
+
},
|
|
73
|
+
info(...args) {
|
|
74
|
+
return _message( this, 'info', ...args );
|
|
75
|
+
},
|
|
64
76
|
attachLocation,
|
|
65
77
|
assignAnnotation,
|
|
66
78
|
addAnnotation,
|
|
@@ -91,6 +103,7 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
91
103
|
addDef,
|
|
92
104
|
addItem,
|
|
93
105
|
addExtension,
|
|
106
|
+
aspectWithoutElements,
|
|
94
107
|
createSource,
|
|
95
108
|
createDict,
|
|
96
109
|
createArray,
|
|
@@ -103,7 +116,6 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
103
116
|
associationInSelectItem,
|
|
104
117
|
reportExpandInline,
|
|
105
118
|
checkTypeFacet,
|
|
106
|
-
notSupportedYet,
|
|
107
119
|
csnParseOnly,
|
|
108
120
|
disallowElementExtension,
|
|
109
121
|
noAssignmentInSameLine,
|
|
@@ -126,30 +138,19 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
126
138
|
// language construct can come from a CSN as source.
|
|
127
139
|
// TODO: this is not completely done this way
|
|
128
140
|
|
|
129
|
-
function notSupportedYet( text, ...tokens ) {
|
|
130
|
-
if (!text)
|
|
131
|
-
return;
|
|
132
|
-
if (typeof text !== 'string') {
|
|
133
|
-
tokens = [ text, ...tokens ];
|
|
134
|
-
text = `${ tokens.map( t => t.text.toUpperCase() ).join(' ') } is not supported`;
|
|
135
|
-
}
|
|
136
|
-
this.error( null, this.tokenLocation( tokens[0], tokens[tokens.length - 1] ), {}, text );
|
|
137
|
-
}
|
|
138
|
-
|
|
139
141
|
// Use the following function for language constructs which we (currently) do
|
|
140
142
|
// not really compile, just use to produce a CSN for functions parse.cql() and
|
|
141
143
|
// parse.expr().
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
+
// This function has a similar interface to our message functions on purpose!
|
|
145
|
+
// (tokens ~= location)
|
|
146
|
+
function csnParseOnly( msgId, tokens, textArgs ) {
|
|
147
|
+
if (!msgId || this.options.parseOnly)
|
|
144
148
|
return;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
text = `${ tokens.map( t => t.text.toUpperCase() ).join(' ') } is not supported`;
|
|
148
|
-
}
|
|
149
|
-
this.error( null, this.tokenLocation( tokens[0], tokens[tokens.length - 1] ), {}, text );
|
|
149
|
+
const loc = this.tokenLocation( tokens[0], tokens[tokens.length - 1] );
|
|
150
|
+
this.error( msgId, loc, textArgs );
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
|
|
153
|
+
/** @this {object} */
|
|
153
154
|
function noSemicolonHere() {
|
|
154
155
|
const handler = this._errHandler;
|
|
155
156
|
const t = this.getCurrentToken();
|
|
@@ -193,7 +194,7 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
|
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
function setLocalTokenForId( tokenNameMap ) {
|
|
196
|
-
const tokenName = tokenNameMap[
|
|
197
|
+
const tokenName = tokenNameMap[this._input.LT(2).text || ''];
|
|
197
198
|
const ll1 = this.getCurrentToken();
|
|
198
199
|
if (tokenName &&
|
|
199
200
|
(ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )))
|
|
@@ -227,8 +228,10 @@ function setLocalTokenForId( tokenNameMap ) {
|
|
|
227
228
|
*/
|
|
228
229
|
function disallowElementExtension(elemName, outer, extensionVariant) {
|
|
229
230
|
if (elemName.path) {
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
const loc = this.tokenLocation(this.getCurrentToken());
|
|
232
|
+
this.error( 'syntax-invalid-extend', loc, { kind: extensionVariant } );
|
|
233
|
+
// remove last, i.e. new extension
|
|
234
|
+
outer.extensions.pop();
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
|
|
@@ -266,7 +269,7 @@ const genericTokenTypes = {
|
|
|
266
269
|
|
|
267
270
|
function prepareGenericKeywords( pathItem, expected = null) {
|
|
268
271
|
const length = pathItem?.args?.length || 0;
|
|
269
|
-
const argPos = (expected ? length -1 : length);
|
|
272
|
+
const argPos = (expected ? length - 1 : length);
|
|
270
273
|
const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
|
|
271
274
|
const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
|
|
272
275
|
this.$genericKeywords = spec;
|
|
@@ -298,7 +301,7 @@ function prepareGenericKeywords( pathItem, expected = null) {
|
|
|
298
301
|
// To be called before having matched ( HideAlternatives | … )
|
|
299
302
|
function reportErrorForGenericKeyword() {
|
|
300
303
|
this._errHandler.reportUnwantedToken( this );
|
|
301
|
-
//this._errHandler.reportInputMismatch( this, { offending: this._input.LT(1) }, null );
|
|
304
|
+
// this._errHandler.reportInputMismatch( this, { offending: this._input.LT(1) }, null );
|
|
302
305
|
}
|
|
303
306
|
|
|
304
307
|
// Attach location matched by current rule to node `art`. If a location is
|
|
@@ -317,11 +320,12 @@ function attachLocation( art ) {
|
|
|
317
320
|
// case we can't rely on `this._ctx.stop.line`.
|
|
318
321
|
if (this.isMultiLineToken(this._ctx.stop)) {
|
|
319
322
|
this.fixMultiLineTokenEndLocation(this._ctx.stop, art.location);
|
|
320
|
-
|
|
321
|
-
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
322
325
|
const { stop } = this._ctx;
|
|
323
326
|
art.location.endLine = stop.line;
|
|
324
|
-
|
|
327
|
+
// after the last char (special for EOF?)
|
|
328
|
+
art.location.endCol = stop.stop - stop.start + stop.column + 2;
|
|
325
329
|
}
|
|
326
330
|
|
|
327
331
|
return art;
|
|
@@ -355,7 +359,7 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
|
|
|
355
359
|
}
|
|
356
360
|
else {
|
|
357
361
|
name.absolute = absolute;
|
|
358
|
-
this.addAnnotation( art,
|
|
362
|
+
this.addAnnotation( art, `@${ absolute }`, anno );
|
|
359
363
|
}
|
|
360
364
|
if (!prefix) { // set deprecated $annotations for cds-lsp
|
|
361
365
|
if (!art.$annotations)
|
|
@@ -366,6 +370,7 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
|
|
|
366
370
|
}
|
|
367
371
|
|
|
368
372
|
function addAnnotation( art, prop, anno ) {
|
|
373
|
+
// TODO: just overwrite after having reported the error
|
|
369
374
|
dictAddArray( art, prop, anno, (n, location, a) => {
|
|
370
375
|
// if we would make it a warning, we would still need to keep it an error
|
|
371
376
|
// with '...'; otherwise parse.cdl would have to split annotate statements
|
|
@@ -376,7 +381,9 @@ function addAnnotation( art, prop, anno ) {
|
|
|
376
381
|
} );
|
|
377
382
|
}
|
|
378
383
|
|
|
379
|
-
const extensionDicts = {
|
|
384
|
+
const extensionDicts = {
|
|
385
|
+
elements: true, enum: true, params: true, returns: true,
|
|
386
|
+
};
|
|
380
387
|
|
|
381
388
|
function checkExtensionDict( dict ) {
|
|
382
389
|
for (const name in dict) {
|
|
@@ -385,8 +392,8 @@ function checkExtensionDict( dict ) {
|
|
|
385
392
|
continue;
|
|
386
393
|
|
|
387
394
|
if (def.kind !== 'annotate') {
|
|
388
|
-
const numDefines
|
|
389
|
-
def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
|
|
395
|
+
const numDefines
|
|
396
|
+
= def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
|
|
390
397
|
this.handleDuplicateExtension( def, name, numDefines );
|
|
391
398
|
for (const dup of def.$duplicates)
|
|
392
399
|
this.handleDuplicateExtension( dup, name, numDefines );
|
|
@@ -396,12 +403,13 @@ function checkExtensionDict( dict ) {
|
|
|
396
403
|
for (const dup of def.$duplicates) {
|
|
397
404
|
for (const prop of Object.keys( dup )) {
|
|
398
405
|
if (prop.charAt(0) === '@') {
|
|
399
|
-
this.addAnnotation( def, prop, dup[prop] )
|
|
406
|
+
this.addAnnotation( def, prop, dup[prop] );
|
|
400
407
|
}
|
|
401
408
|
else if (prop === 'doc') {
|
|
402
|
-
if (def.doc)
|
|
409
|
+
if (def.doc) {
|
|
403
410
|
this.warning( 'syntax-duplicate-doc-comment', def.doc.location, {},
|
|
404
411
|
'Doc comment is overwritten by another one below' );
|
|
412
|
+
}
|
|
405
413
|
def.doc = dup.doc;
|
|
406
414
|
}
|
|
407
415
|
else if (extensionDicts[prop]) {
|
|
@@ -427,11 +435,13 @@ function addOneForDefinition( count, ext ) {
|
|
|
427
435
|
* @param {number} numDefines
|
|
428
436
|
*/
|
|
429
437
|
function handleDuplicateExtension( ext, name, numDefines ) {
|
|
430
|
-
if (ext.kind === 'extend')
|
|
438
|
+
if (ext.kind === 'extend') {
|
|
431
439
|
this.error( 'syntax-duplicate-extend', [ ext.name.location ],
|
|
432
440
|
{ name, '#': (numDefines ? 'define' : 'extend') } );
|
|
433
|
-
|
|
434
|
-
|
|
441
|
+
}
|
|
442
|
+
else if (numDefines === 1) {
|
|
443
|
+
ext.$errorReported = 'syntax-duplicate-extend';
|
|
444
|
+
} // a definition, but not duplicate
|
|
435
445
|
}
|
|
436
446
|
|
|
437
447
|
|
|
@@ -470,8 +480,9 @@ function tokenLocation( token, endToken = null ) {
|
|
|
470
480
|
col: token.column + 1,
|
|
471
481
|
// Default for single line tokens
|
|
472
482
|
endLine: endToken.line,
|
|
473
|
-
|
|
474
|
-
|
|
483
|
+
// after the last char (special for EOF?)
|
|
484
|
+
endCol: endToken.stop - endToken.start + endToken.column + 2,
|
|
485
|
+
};
|
|
475
486
|
|
|
476
487
|
// This check is done for performance reason. No need to access a token's
|
|
477
488
|
// data if we know that it spans only one single line.
|
|
@@ -514,9 +525,11 @@ function fixMultiLineTokenEndLocation( token, location ) {
|
|
|
514
525
|
if (newLineCount > 0) {
|
|
515
526
|
location.endLine = token.line + newLineCount;
|
|
516
527
|
location.endCol = token.stop - lastNewlineIndex + 1;
|
|
517
|
-
}
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
518
530
|
location.endLine = token.line;
|
|
519
|
-
|
|
531
|
+
// after the last char (special for EOF?)
|
|
532
|
+
location.endCol = token.stop - token.start + token.column + 2;
|
|
520
533
|
}
|
|
521
534
|
}
|
|
522
535
|
|
|
@@ -568,7 +581,7 @@ function unaryOpForParens( query, val ) {
|
|
|
568
581
|
if (!parens)
|
|
569
582
|
return query;
|
|
570
583
|
const location = parens[parens.length - 1];
|
|
571
|
-
return { op: { val, location }, location, args: [query] };
|
|
584
|
+
return { op: { val, location }, location, args: [ query ] };
|
|
572
585
|
}
|
|
573
586
|
|
|
574
587
|
// If the token before the current one is a doc comment (ignoring other tokens
|
|
@@ -609,12 +622,14 @@ function classifyImplicitName( category, ref ) {
|
|
|
609
622
|
function fragileAlias( ast, safe = false ) {
|
|
610
623
|
if (this.getCurrentToken().text === '.')
|
|
611
624
|
return ast;
|
|
612
|
-
if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id ))
|
|
625
|
+
if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id )) {
|
|
613
626
|
this.warning( 'syntax-sloppy-alias', ast.location, { keyword: 'as' },
|
|
614
627
|
'Please add the keyword $(KEYWORD) in front of the alias name' );
|
|
615
|
-
|
|
628
|
+
}
|
|
629
|
+
else { // configurable error
|
|
616
630
|
this.message( 'syntax-fragile-alias', ast.location, { keyword: 'as' },
|
|
617
631
|
'Please add the keyword $(KEYWORD) in front of the alias name' );
|
|
632
|
+
}
|
|
618
633
|
return ast;
|
|
619
634
|
}
|
|
620
635
|
|
|
@@ -645,6 +660,7 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
645
660
|
}
|
|
646
661
|
else {
|
|
647
662
|
this.message( 'syntax-deprecated-ident', token, { delimited: id },
|
|
663
|
+
// eslint-disable-next-line max-len
|
|
648
664
|
'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
|
|
649
665
|
}
|
|
650
666
|
return { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
@@ -676,7 +692,7 @@ function setLastAsXpr( args ) {
|
|
|
676
692
|
|
|
677
693
|
function xprToken() {
|
|
678
694
|
const token = this._input.LT(-1);
|
|
679
|
-
return { location: this.tokenLocation( token ), val: token.text, literal: 'token' }
|
|
695
|
+
return { location: this.tokenLocation( token ), val: token.text, literal: 'token' };
|
|
680
696
|
}
|
|
681
697
|
|
|
682
698
|
function valuePathAst( ref ) {
|
|
@@ -690,7 +706,7 @@ function valuePathAst( ref ) {
|
|
|
690
706
|
if (!item)
|
|
691
707
|
return ref;
|
|
692
708
|
this.error( 'syntax-not-supported', item.location, {},
|
|
693
|
-
|
|
709
|
+
'Methods in expressions are not supported yet' );
|
|
694
710
|
path.broken = true;
|
|
695
711
|
path.length = 1;
|
|
696
712
|
}
|
|
@@ -705,7 +721,9 @@ function valuePathAst( ref ) {
|
|
|
705
721
|
implicit.isIdentifier = 'func';
|
|
706
722
|
const op = { location, val: 'call' };
|
|
707
723
|
return (args)
|
|
708
|
-
? {
|
|
724
|
+
? {
|
|
725
|
+
op, func: ref, location: ref.location, args,
|
|
726
|
+
}
|
|
709
727
|
: { op, func: ref, location: ref.location };
|
|
710
728
|
}
|
|
711
729
|
|
|
@@ -713,15 +731,15 @@ function valuePathAst( ref ) {
|
|
|
713
731
|
// otherwise (including for '+'), represent it as extra unary prefix operator.
|
|
714
732
|
function signedExpression( signToken, expr ) {
|
|
715
733
|
const sign = this.valueWithTokenLocation( signToken.text, signToken );
|
|
716
|
-
const nval
|
|
717
|
-
(signToken.text === '-' &&
|
|
734
|
+
const nval
|
|
735
|
+
= (signToken.text === '-' &&
|
|
718
736
|
expr && // expr may be null if `-` rule can't be parsed
|
|
719
737
|
expr.literal === 'number' &&
|
|
720
738
|
sign.location.endLine === expr.location.line &&
|
|
721
739
|
sign.location.endCol === expr.location.col &&
|
|
722
740
|
(typeof expr.val === 'number'
|
|
723
|
-
|
|
724
|
-
|
|
741
|
+
? expr.val >= 0 && -expr.val
|
|
742
|
+
: !expr.val.startsWith('-') && `-${ expr.val }`)) || false;
|
|
725
743
|
if (nval === false)
|
|
726
744
|
return { op: sign, args: expr ? [ expr ] : [] };
|
|
727
745
|
expr.val = nval;
|
|
@@ -747,9 +765,11 @@ function numberLiteral( token, sign, text = token.text ) {
|
|
|
747
765
|
const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
|
|
748
766
|
if (!Number.isSafeInteger(num)) {
|
|
749
767
|
if (sign == null) {
|
|
750
|
-
this.error( 'syntax-
|
|
768
|
+
this.error( 'syntax-expecting-integer', token,
|
|
769
|
+
{ '#': !text.match(/^[0-9]*$/) ? 'normal' : 'unsafe' } );
|
|
751
770
|
}
|
|
752
|
-
|
|
771
|
+
|
|
772
|
+
else if (text !== `${ num }`) {
|
|
753
773
|
return { literal: 'number', val: text, location };
|
|
754
774
|
}
|
|
755
775
|
}
|
|
@@ -768,7 +788,8 @@ function quotedLiteral( token, literal ) {
|
|
|
768
788
|
if (token.text.startsWith('`')) {
|
|
769
789
|
val = this.parseMultiLineStringLiteral(token);
|
|
770
790
|
literal = 'string';
|
|
771
|
-
}
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
772
793
|
pos = token.text.search( '\'' ) + 1; // pos of char after quote
|
|
773
794
|
val = token.text.slice( pos, -1 ).replace( /''/g, '\'' );
|
|
774
795
|
}
|
|
@@ -782,7 +803,7 @@ function quotedLiteral( token, literal ) {
|
|
|
782
803
|
|
|
783
804
|
if (p.unexpected_char) {
|
|
784
805
|
const idx = val.search(p.unexpected_char);
|
|
785
|
-
if (
|
|
806
|
+
if (idx > -1) {
|
|
786
807
|
this.warning( 'syntax-invalid-literal', {
|
|
787
808
|
file: location.file,
|
|
788
809
|
line: location.line,
|
|
@@ -934,6 +955,15 @@ function addExtension( ext, parent, kind, artName, elemPath ) {
|
|
|
934
955
|
}
|
|
935
956
|
}
|
|
936
957
|
|
|
958
|
+
function aspectWithoutElements( art ) {
|
|
959
|
+
// Empty dictionary to allow element extensions.
|
|
960
|
+
art.elements = this.createDict();
|
|
961
|
+
if (!isBetaEnabled( this.options, 'aspectWithoutElements' )) {
|
|
962
|
+
this.error( null, [ art.name.location ], {},
|
|
963
|
+
'Aspects without elements are not supported, yet' );
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
937
967
|
// must be in action directly after having parsed '{' or '(`
|
|
938
968
|
function createDict() {
|
|
939
969
|
const dict = Object.create(null);
|
|
@@ -977,10 +1007,10 @@ function createPrefixOp( token, args ) {
|
|
|
977
1007
|
|
|
978
1008
|
// Create AST node for binary operator `op` and arguments `args`
|
|
979
1009
|
function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifier' ) {
|
|
980
|
-
const op = this.valueWithTokenLocation( opToken.text.toLowerCase()
|
|
1010
|
+
const op = this.valueWithTokenLocation( opToken.text.toLowerCase(), opToken);
|
|
981
1011
|
const extra = eToken
|
|
982
|
-
|
|
983
|
-
|
|
1012
|
+
? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
|
|
1013
|
+
: undefined;
|
|
984
1014
|
if (!left.$parens &&
|
|
985
1015
|
(left.op && left.op.val) === (op && op.val) &&
|
|
986
1016
|
(left[extraProp] && left[extraProp].val) === (extra && extra.val)) {
|
|
@@ -988,11 +1018,12 @@ function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifie
|
|
|
988
1018
|
return left;
|
|
989
1019
|
}
|
|
990
1020
|
else if (extra) {
|
|
991
|
-
return {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
return { op, args: [ left, right ], location: left.location };
|
|
1021
|
+
return {
|
|
1022
|
+
op, [extraProp]: extra, args: [ left, right ], location: left.location,
|
|
1023
|
+
};
|
|
995
1024
|
}
|
|
1025
|
+
|
|
1026
|
+
return { op, args: [ left, right ], location: left.location };
|
|
996
1027
|
}
|
|
997
1028
|
|
|
998
1029
|
// Set property `prop` of `target` to value `value`. Issue error if that
|
|
@@ -1003,7 +1034,7 @@ function setOnce( target, prop, value, ...tokens ) {
|
|
|
1003
1034
|
const prev = target[prop];
|
|
1004
1035
|
if (prev) {
|
|
1005
1036
|
this.error( 'syntax-repeated-option', loc, { option: prev.option },
|
|
1006
|
-
|
|
1037
|
+
'Option $(OPTION) has already been specified' );
|
|
1007
1038
|
}
|
|
1008
1039
|
if (typeof value === 'boolean') {
|
|
1009
1040
|
if (!value)
|
|
@@ -1041,25 +1072,27 @@ function associationInSelectItem( art ) {
|
|
|
1041
1072
|
if (!art.value) // e.g. `expand` without value (for new structures)
|
|
1042
1073
|
return;
|
|
1043
1074
|
|
|
1044
|
-
const isPath = art.value.path && art.value.path.length
|
|
1075
|
+
const isPath = art.value.path && art.value.path.length;
|
|
1045
1076
|
const isIdentifier = isPath && art.value.path.length === 1;
|
|
1046
1077
|
if (isIdentifier) {
|
|
1047
1078
|
if (!art.name) {
|
|
1048
1079
|
art.name = art.value.path[0];
|
|
1049
|
-
}
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1050
1082
|
// Use alias if provided, i.e. ignore art.value.path.
|
|
1051
1083
|
this.error( 'query-unexpected-alias', art.name.location, {},
|
|
1052
|
-
|
|
1084
|
+
'Unexpected alias for association' );
|
|
1053
1085
|
}
|
|
1054
1086
|
delete art.value;
|
|
1055
|
-
}
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1056
1089
|
const loc = isPath ? art.value.path[1].location : art.value.location;
|
|
1057
1090
|
// If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
|
|
1058
1091
|
if (isPath || art.name) {
|
|
1059
1092
|
this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
|
|
1060
|
-
if (isPath)
|
|
1093
|
+
if (isPath)
|
|
1061
1094
|
art.name = art.value.path[art.value.path.length - 1];
|
|
1062
|
-
|
|
1095
|
+
|
|
1063
1096
|
delete art.value;
|
|
1064
1097
|
}
|
|
1065
1098
|
}
|
|
@@ -1091,21 +1124,19 @@ function reportExpandInline( column, isInline ) {
|
|
|
1091
1124
|
}
|
|
1092
1125
|
|
|
1093
1126
|
function checkTypeFacet( art, argIdent ) {
|
|
1094
|
-
const id = argIdent
|
|
1127
|
+
const { id } = argIdent;
|
|
1095
1128
|
if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
|
|
1096
1129
|
if (art[id] !== undefined) {
|
|
1097
1130
|
this.error( 'syntax-duplicate-argument', argIdent.location,
|
|
1098
|
-
|
|
1131
|
+
{ '#': 'duplicate', code: id } );
|
|
1099
1132
|
this.error( 'syntax-duplicate-argument', art[id].location,
|
|
1100
|
-
|
|
1133
|
+
{ '#': 'duplicate', code: id } );
|
|
1101
1134
|
}
|
|
1102
1135
|
return true;
|
|
1103
|
-
|
|
1104
|
-
} else {
|
|
1105
|
-
this.error( 'syntax-duplicate-argument', argIdent.location,
|
|
1106
|
-
{ '#': 'unknown', code: id } );
|
|
1107
|
-
return false;
|
|
1108
1136
|
}
|
|
1137
|
+
this.error( 'syntax-duplicate-argument', argIdent.location,
|
|
1138
|
+
{ '#': 'unknown', code: id } );
|
|
1139
|
+
return false;
|
|
1109
1140
|
}
|
|
1110
1141
|
|
|
1111
1142
|
module.exports = GenericAntlrParser;
|
package/lib/language/language.g4
CHANGED
|
@@ -721,16 +721,21 @@ aspectDef[ art, outer ] locals[ name = {} ]
|
|
|
721
721
|
)*
|
|
722
722
|
)?
|
|
723
723
|
)?
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
724
|
+
( // `aspect MyAspect {};`
|
|
725
|
+
'{' { $art.elements = this.createDict(); }
|
|
726
|
+
( elementDef[ $art ]* )
|
|
727
|
+
'}' { this.finalizeDictOrArray( $art.elements ); }
|
|
728
|
+
// TODO: action definitions in a specific section?
|
|
729
|
+
(
|
|
730
|
+
ACTIONS '{' { $art.actions = this.createDict(); }
|
|
731
|
+
actionFunctionDef[ $art ]*
|
|
732
|
+
'}' { this.finalizeDictOrArray( $art.actions ); }
|
|
733
|
+
)?
|
|
734
|
+
optionalSemi
|
|
735
|
+
| // `aspect MyAspect;`, e.g. for annotation aspects.
|
|
736
|
+
{ this.aspectWithoutElements( $art ); }
|
|
737
|
+
requiredSemi
|
|
738
|
+
)
|
|
734
739
|
;
|
|
735
740
|
|
|
736
741
|
typeDef[ art, outer ] locals[ name = {} ]
|
|
@@ -1075,12 +1080,12 @@ mixinElementDef[ outer ] locals[ art = {} ]
|
|
|
1075
1080
|
|
|
|
1076
1081
|
typeRefOptArgs[ $art ]
|
|
1077
1082
|
( as='=' expression
|
|
1078
|
-
{ this.
|
|
1083
|
+
{ this.error( 'syntax-unsupported-field', $as ); }
|
|
1079
1084
|
)?
|
|
1080
1085
|
)
|
|
1081
1086
|
|
|
|
1082
1087
|
as='=' expression
|
|
1083
|
-
{ this.
|
|
1088
|
+
{ this.error( 'syntax-unsupported-field', $as ); }
|
|
1084
1089
|
)
|
|
1085
1090
|
requiredSemi
|
|
1086
1091
|
;
|
|
@@ -1099,8 +1104,7 @@ elementDefInner[ art, outer, allowEq ]
|
|
|
1099
1104
|
( masked=MASKED
|
|
1100
1105
|
{
|
|
1101
1106
|
$art.masked = this.valueWithTokenLocation( true, $masked ) ;
|
|
1102
|
-
this.message( 'syntax-
|
|
1103
|
-
'Keyword $(KEYWORD) not supported' );
|
|
1107
|
+
this.message( 'syntax-unsupported-masked', $masked, { keyword: 'masked' } );
|
|
1104
1108
|
}
|
|
1105
1109
|
)?
|
|
1106
1110
|
// TODO: order?
|
|
@@ -1212,7 +1216,7 @@ elementDefInner[ art, outer, allowEq ]
|
|
|
1212
1216
|
eq='=' e=expression // never introduce AS as syntax variant of '='
|
|
1213
1217
|
{
|
|
1214
1218
|
if (!$allowEq || $e.expr && !$e.expr.literal )
|
|
1215
|
-
this.
|
|
1219
|
+
this.error( 'syntax-unsupported-field', $eq );
|
|
1216
1220
|
else if ($e.expr)
|
|
1217
1221
|
$art.value = $e.expr;
|
|
1218
1222
|
}
|
|
@@ -1394,7 +1398,7 @@ elementProperties[ elem ]
|
|
|
1394
1398
|
nullability[ $elem ]?
|
|
1395
1399
|
|
|
|
1396
1400
|
eq='='
|
|
1397
|
-
{ this.
|
|
1401
|
+
{ this.error( 'syntax-unsupported-field', $eq ); }
|
|
1398
1402
|
expression
|
|
1399
1403
|
;
|
|
1400
1404
|
|
|
@@ -2226,7 +2230,7 @@ conditionTerm returns [ cond ]
|
|
|
2226
2230
|
{ $cond = { op: this.valueWithTokenLocation( 'exists', $ex ), args: [
|
|
2227
2231
|
{ param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' }
|
|
2228
2232
|
] };
|
|
2229
|
-
this.csnParseOnly( '
|
|
2233
|
+
this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
|
|
2230
2234
|
}
|
|
2231
2235
|
|
|
|
2232
2236
|
ep=valuePath[ 'ref' ]
|
|
@@ -2350,7 +2354,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
|
|
|
2350
2354
|
ne=NEW nqp=valuePath[ 'ref', null] // token rewrite for NEW
|
|
2351
2355
|
// please note: there will be no compiler-supported code completion after NEW
|
|
2352
2356
|
{ $expr = { op: this.valueWithTokenLocation( 'new', $ne ), args: [] };
|
|
2353
|
-
this.
|
|
2357
|
+
this.error( 'syntax-unsupported-new', $ne, { keyword: $ne.text }, '$(KEYWORD) is not supported' ); }
|
|
2354
2358
|
|
|
|
2355
2359
|
vp=valuePath[ 'ref', null ] { $expr = this.valuePathAst( $vp.qp ); }
|
|
2356
2360
|
{ this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
|
|
@@ -2363,7 +2367,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
|
|
|
2363
2367
|
{ $expr = $vp.qp;; $expr.scope = 'param'; }
|
|
2364
2368
|
| pp=Number
|
|
2365
2369
|
{ $expr = { param: this.numberLiteral( $pp ), scope: 'param' };
|
|
2366
|
-
this.csnParseOnly( '
|
|
2370
|
+
this.csnParseOnly( 'syntax-unsupported-param', [ $pp ], { '#': 'positional', name: ':' + $pp.text } );
|
|
2367
2371
|
}
|
|
2368
2372
|
)
|
|
2369
2373
|
|
|
|
@@ -2371,7 +2375,7 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
|
|
|
2371
2375
|
// if we have an HideAlternatives here, we would block it to use it in
|
|
2372
2376
|
// parallel to an expression (would produce adaptivePredict() otherwise)
|
|
2373
2377
|
{ $expr = { param: this.valueWithTokenLocation( '?', $qm ), scope: 'param' };
|
|
2374
|
-
this.csnParseOnly( '
|
|
2378
|
+
this.csnParseOnly( 'syntax-unsupported-param', [ $qm ], { '#': 'dynamic', name: '?' } );
|
|
2375
2379
|
}
|
|
2376
2380
|
|
|
|
2377
2381
|
open='('
|
|
@@ -2661,8 +2665,9 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
|
|
|
2661
2665
|
const item = { literal: 'token', val: '...', location: this.tokenLocation($e) };
|
|
2662
2666
|
if ($ctx.upTo) item.upTo = $upTo.val;
|
|
2663
2667
|
$assignment.val.push( item );
|
|
2664
|
-
if ($seenEllipsis === true)
|
|
2665
|
-
this.error( 'syntax-unexpected-ellipsis', $e,
|
|
2668
|
+
if ($seenEllipsis === true)
|
|
2669
|
+
this.error( 'syntax-unexpected-ellipsis', $e,
|
|
2670
|
+
{ '#': 'duplicate', code: '...', keyword: 'up to' } );
|
|
2666
2671
|
else
|
|
2667
2672
|
$seenEllipsis = !$ctx.upTo || 'upTo';
|
|
2668
2673
|
}}
|
|
@@ -2672,9 +2677,8 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
|
|
|
2672
2677
|
cb=']'
|
|
2673
2678
|
{
|
|
2674
2679
|
if ($seenEllipsis === 'upTo')
|
|
2675
|
-
this.error( 'syntax-
|
|
2676
|
-
{ code: '... up to', newcode: '...' }
|
|
2677
|
-
'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
|
|
2680
|
+
this.error( 'syntax-missing-ellipsis', $cb, // at closing bracket
|
|
2681
|
+
{ code: '... up to', newcode: '...' } );
|
|
2678
2682
|
}
|
|
2679
2683
|
|
|
|
2680
2684
|
v1=literalValue { Object.assign( $assignment, $v1.val ); }
|