@sap/cds-compiler 2.5.2 → 2.11.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 +235 -9
- package/bin/cdsc.js +44 -27
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +37 -3
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +37 -123
- package/lib/api/options.js +27 -15
- package/lib/api/validate.js +34 -9
- package/lib/backends.js +9 -89
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +73 -11
- package/lib/base/messages.js +86 -30
- package/lib/base/model.js +6 -6
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +25 -7
- package/lib/checks/selectItems.js +29 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +23 -7
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +8 -5
- package/lib/compiler/definer.js +157 -133
- package/lib/compiler/index.js +89 -31
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +375 -185
- package/lib/compiler/shared.js +49 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +104 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +388 -146
- package/lib/edm/edmUtils.js +104 -34
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5330 -4300
- package/lib/json/from-csn.js +110 -52
- package/lib/json/to-csn.js +434 -120
- package/lib/language/antlrParser.js +15 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +93 -26
- package/lib/language/language.g4 +172 -31
- package/lib/main.d.ts +216 -19
- package/lib/main.js +32 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +413 -149
- package/lib/model/csnUtils.js +286 -75
- package/lib/model/enrichCsn.js +50 -6
- package/lib/model/revealInternalProperties.js +22 -5
- package/lib/modelCompare/compare.js +39 -21
- package/lib/optionProcessor.js +35 -18
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +9 -6
- package/lib/render/toCdl.js +121 -36
- package/lib/render/toHdbcds.js +148 -98
- package/lib/render/toSql.js +114 -43
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +281 -106
- package/lib/transform/db/draft.js +11 -8
- package/lib/transform/db/expansion.js +584 -0
- package/lib/transform/db/flattening.js +341 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +345 -65
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +131 -793
- package/lib/transform/forOdataNew.js +30 -24
- package/lib/transform/localized.js +39 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +144 -78
- package/lib/transform/translateAssocsToJoins.js +22 -27
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -14
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- package/lib/utils/string.js +0 -17
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const antlr4 = require('antlr4');
|
|
12
12
|
|
|
13
|
-
const {
|
|
13
|
+
const { CompileMessage } = require('../base/messages');
|
|
14
14
|
const errorStrategy = require('./errorStrategy');
|
|
15
15
|
|
|
16
16
|
const Parser = require('../gen/languageParser').languageParser;
|
|
@@ -112,7 +112,7 @@ const rules = {
|
|
|
112
112
|
expr: { func: 'conditionEOF', returns: 'cond' }, // yes, condition
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
function parse( source, filename = '<undefined>.cds', options = {}, rule = 'cdl' ) {
|
|
115
|
+
function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions, rule = 'cdl' ) {
|
|
116
116
|
const lexer = new Lexer( new antlr4.InputStream(source) );
|
|
117
117
|
const tokenStream = new RewriteTypeTokenStream(lexer);
|
|
118
118
|
/** @type {object} */
|
|
@@ -121,7 +121,7 @@ function parse( source, filename = '<undefined>.cds', options = {}, rule = 'cdl'
|
|
|
121
121
|
|
|
122
122
|
parser.filename = filename;
|
|
123
123
|
parser.options = options;
|
|
124
|
-
parser.$
|
|
124
|
+
parser.$messageFunctions = messageFunctions;
|
|
125
125
|
|
|
126
126
|
initTokenRewrite( parser, tokenStream );
|
|
127
127
|
// comment the following 2 lines if you want to output the parser errors directly:
|
|
@@ -162,6 +162,18 @@ function parse( source, filename = '<undefined>.cds', options = {}, rule = 'cdl'
|
|
|
162
162
|
if (rulespec.$frontend)
|
|
163
163
|
ast.$frontend = rulespec.$frontend;
|
|
164
164
|
|
|
165
|
+
// Warn about unused doc-comments.
|
|
166
|
+
// Do not warn if docComments are explicitly disabled.
|
|
167
|
+
if (options.docComment !== false) {
|
|
168
|
+
for (const token of tokenStream.tokens) {
|
|
169
|
+
if (token.channel === antlr4.Token.HIDDEN_CHANNEL && token.type === parser.constructor.DocComment && !token.isUsed) {
|
|
170
|
+
messageFunctions.info('syntax-ignoring-doc-comment', parser.multiLineTokenLocation(token), {},
|
|
171
|
+
"Ignoring doc-comment as it does not belong to any artifact");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// TODO: clarify with LSP colleagues: still necessary?
|
|
165
177
|
if (parser.messages) {
|
|
166
178
|
Object.defineProperty( ast, 'messages',
|
|
167
179
|
{ value: parser.messages, configurable: true, writable: true } );
|
|
@@ -13,6 +13,14 @@ const locUtils = require('../base/location');
|
|
|
13
13
|
const { parseDocComment } = require('./docCommentParser');
|
|
14
14
|
const { functionsWithoutParens, specialFunctions } = require('../compiler/builtins');
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
// Push message `msg` with location `loc` to array of errors:
|
|
18
|
+
function _message( parser, severity, id, loc, ...args ) {
|
|
19
|
+
const msg = parser.$messageFunctions[severity]; // set in antlrParser.js
|
|
20
|
+
return msg( id,
|
|
21
|
+
(loc instanceof antlr4.CommonToken) ? parser.tokenLocation(loc) : loc, ...args );
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
// Class which is to be used as grammar option with
|
|
17
25
|
// grammar <name> options { superclass = genericAntlrParser; }
|
|
18
26
|
//
|
|
@@ -34,13 +42,15 @@ function GenericAntlrParser( ...args ) {
|
|
|
34
42
|
|
|
35
43
|
GenericAntlrParser.prototype = Object.assign(
|
|
36
44
|
Object.create( antlr4.Parser.prototype ), {
|
|
37
|
-
message: function(...args) { return
|
|
38
|
-
error: function(...args) { return
|
|
39
|
-
warning: function(...args) { return
|
|
40
|
-
info: function(...args) { return
|
|
45
|
+
message: function(...args) { return _message( this, 'message', ...args ); },
|
|
46
|
+
error: function(...args) { return _message( this, 'error', ...args ); },
|
|
47
|
+
warning: function(...args) { return _message( this, 'warning', ...args ); },
|
|
48
|
+
info: function(...args) { return _message( this, 'info', ...args ); },
|
|
41
49
|
attachLocation,
|
|
42
50
|
startLocation,
|
|
43
51
|
tokenLocation,
|
|
52
|
+
multiLineTokenLocation,
|
|
53
|
+
previousTokenAtLocation,
|
|
44
54
|
combinedLocation,
|
|
45
55
|
surroundByParens,
|
|
46
56
|
unaryOpForParens,
|
|
@@ -69,6 +79,7 @@ GenericAntlrParser.prototype = Object.assign(
|
|
|
69
79
|
noAssignmentInSameLine,
|
|
70
80
|
noSemicolonHere,
|
|
71
81
|
setLocalToken,
|
|
82
|
+
setLocalTokenIfBefore,
|
|
72
83
|
excludeExpected,
|
|
73
84
|
isStraightBefore,
|
|
74
85
|
meltKeywordToIdentifier,
|
|
@@ -110,14 +121,6 @@ const quotedLiteralPatterns = {
|
|
|
110
121
|
},
|
|
111
122
|
};
|
|
112
123
|
|
|
113
|
-
|
|
114
|
-
// Push message `msg` with location `loc` to array of errors:
|
|
115
|
-
function message( severity, id, loc, ...args ) {
|
|
116
|
-
const msg = this.$message[severity];
|
|
117
|
-
return msg( id, // function $message is set in antlrParser.js
|
|
118
|
-
(loc instanceof antlr4.CommonToken) ? this.tokenLocation(loc) : loc, ...args );
|
|
119
|
-
}
|
|
120
|
-
|
|
121
124
|
// Use the following function for language constructs which we (currently)
|
|
122
125
|
// just being able to parse, in able to run tests from HANA CDS. As soon as we
|
|
123
126
|
// create ASTs for the language construct and put it into a CSN, a
|
|
@@ -183,6 +186,14 @@ function setLocalToken( string, tokenName, notBefore, inSameLine ) {
|
|
|
183
186
|
ll1.type = this.constructor[tokenName];
|
|
184
187
|
}
|
|
185
188
|
|
|
189
|
+
function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
|
|
190
|
+
const ll1 = this.getCurrentToken();
|
|
191
|
+
if (ll1.text.toUpperCase() === string &&
|
|
192
|
+
(!inSameLine || this._input.LT(-1).line === ll1.line) &&
|
|
193
|
+
(!before || before && before.test( this._input.LT(2).text )))
|
|
194
|
+
ll1.type = this.constructor[tokenName];
|
|
195
|
+
}
|
|
196
|
+
|
|
186
197
|
// // Special function for rule `requiredSemi` before return $ctx
|
|
187
198
|
// function braceForSemi() {
|
|
188
199
|
// if (RBRACE == null)
|
|
@@ -236,9 +247,12 @@ function prepareGenericKeywords( pathItem ) {
|
|
|
236
247
|
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
|
|
237
248
|
// as we can have nested special functions
|
|
238
249
|
this.$genericKeywords.argFull = Object.keys( spec );
|
|
250
|
+
// @ts-ignore
|
|
239
251
|
const token = this.getCurrentToken() || { text: '' };
|
|
240
|
-
if (spec[token.text.toUpperCase()] === 'argFull')
|
|
252
|
+
if (spec[token.text.toUpperCase()] === 'argFull') {
|
|
253
|
+
// @ts-ignore
|
|
241
254
|
token.type = this.constructor.GenericArgFull;
|
|
255
|
+
}
|
|
242
256
|
}
|
|
243
257
|
|
|
244
258
|
// Attach location matched by current rule to node `art`. If a location is
|
|
@@ -288,7 +302,7 @@ function tokenLocation( token, endToken, val ) {
|
|
|
288
302
|
file: this.filename,
|
|
289
303
|
line: token.line,
|
|
290
304
|
col: token.column + 1,
|
|
291
|
-
// we only have single-line tokens
|
|
305
|
+
// we only have single-line tokens, except for docComments
|
|
292
306
|
endLine: endToken.line,
|
|
293
307
|
endCol: endToken.stop - endToken.start + endToken.column + 2, // after the last char (special for EOF?)
|
|
294
308
|
};
|
|
@@ -297,6 +311,51 @@ function tokenLocation( token, endToken, val ) {
|
|
|
297
311
|
return r;
|
|
298
312
|
}
|
|
299
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Return location of `token`. In contrast to `tokenLocation()`, this function
|
|
316
|
+
* can handle multiline tokens.
|
|
317
|
+
*/
|
|
318
|
+
function multiLineTokenLocation(token, val) {
|
|
319
|
+
if (!token)
|
|
320
|
+
return undefined;
|
|
321
|
+
|
|
322
|
+
// Count the number of newlines in the token.
|
|
323
|
+
// TODO: I want to avoid a substring, that's why I don't use RegEx here
|
|
324
|
+
const source = token.source[1].data;
|
|
325
|
+
let newLineCount = 0;
|
|
326
|
+
let lastNewlineIndex = token.start;
|
|
327
|
+
for (let i = token.start; i < token.stop; i++) {
|
|
328
|
+
if (source[i] === 10) { // ASCII code for '\n'
|
|
329
|
+
newLineCount++;
|
|
330
|
+
lastNewlineIndex = i;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (newLineCount === 0)
|
|
334
|
+
// endCol calculation below requires at least one newLine.
|
|
335
|
+
return this.tokenLocation(token, token, val);
|
|
336
|
+
|
|
337
|
+
/** @type {CSN.Location} */
|
|
338
|
+
const r = {
|
|
339
|
+
file: this.filename,
|
|
340
|
+
line: token.line,
|
|
341
|
+
col: token.column + 1,
|
|
342
|
+
endLine: token.line + newLineCount,
|
|
343
|
+
endCol: token.stop - lastNewlineIndex + 1, // after the last char (special for EOF?)
|
|
344
|
+
};
|
|
345
|
+
if (val !== undefined)
|
|
346
|
+
return { location: r, val };
|
|
347
|
+
return r;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function previousTokenAtLocation( location ) {
|
|
351
|
+
let k = -1;
|
|
352
|
+
let token = this._input.LT(k);
|
|
353
|
+
while (token.line > location.line ||
|
|
354
|
+
token.line === location.line && token.column >= location.col)
|
|
355
|
+
token = this._input.LT(--k);
|
|
356
|
+
return (token.line === location.line && token.column + 1 === location.col) && token;
|
|
357
|
+
}
|
|
358
|
+
|
|
300
359
|
// Create a location with location properties `filename` and `start` from
|
|
301
360
|
// argument `start`, and location property `end` from argument `end`.
|
|
302
361
|
function combinedLocation( start, end ) {
|
|
@@ -333,16 +392,20 @@ function unaryOpForParens( query, val ) {
|
|
|
333
392
|
// - would influence the prediction, probably even induce adaptivePredict() calls,
|
|
334
393
|
// - is only slightly "more declarative" in the grammar.
|
|
335
394
|
function docComment( node ) {
|
|
336
|
-
if (!this.options.docComment)
|
|
337
|
-
return;
|
|
338
395
|
const token = this._input.getHiddenTokenToLeft( this.constructor.DocComment );
|
|
339
396
|
if (!token)
|
|
340
397
|
return;
|
|
398
|
+
|
|
399
|
+
// This token is actually used by / assigned to an artifact.
|
|
400
|
+
token.isUsed = true;
|
|
401
|
+
|
|
402
|
+
if (!this.options.docComment)
|
|
403
|
+
return;
|
|
341
404
|
if (node.doc) {
|
|
342
405
|
this.warning( 'syntax-duplicate-doc-comment', token, {},
|
|
343
406
|
'Repeated doc comment - previous doc is replaced' );
|
|
344
407
|
}
|
|
345
|
-
node.doc = this.
|
|
408
|
+
node.doc = this.multiLineTokenLocation( token, parseDocComment( token.text ) );
|
|
346
409
|
}
|
|
347
410
|
|
|
348
411
|
// Classify token (identifier category) for implicit names,
|
|
@@ -426,14 +489,18 @@ function valuePathAst( ref ) {
|
|
|
426
489
|
path.length = 1;
|
|
427
490
|
}
|
|
428
491
|
const { args, id, location } = path[0];
|
|
429
|
-
if (args
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
492
|
+
if (args
|
|
493
|
+
? path[0].$syntax === ':'
|
|
494
|
+
: path[0].$delimited || !functionsWithoutParens.includes( id.toUpperCase() ))
|
|
495
|
+
return ref;
|
|
496
|
+
|
|
497
|
+
const implicit = this.previousTokenAtLocation( location );
|
|
498
|
+
if (implicit && implicit.isIdentifier)
|
|
499
|
+
implicit.isIdentifier = 'func';
|
|
500
|
+
const op = { location, val: 'call' };
|
|
501
|
+
return (args)
|
|
502
|
+
? { op, func: ref, location: ref.location, args }
|
|
503
|
+
: { op, func: ref, location: ref.location };
|
|
437
504
|
}
|
|
438
505
|
|
|
439
506
|
// If a '-' is directly before an unsigned number, consider it part of the number;
|
|
@@ -592,7 +659,7 @@ function addDef( parent, env, kind, name, annos, props, location ) {
|
|
|
592
659
|
// which could be tested in name search (then no undefined-ref error)
|
|
593
660
|
return art;
|
|
594
661
|
}
|
|
595
|
-
else if (env === 'artifacts') {
|
|
662
|
+
else if (env === 'artifacts' || env === 'vocabularies') {
|
|
596
663
|
dictAddArray( parent[env], art.name.id, art );
|
|
597
664
|
}
|
|
598
665
|
else if (kind || this.options.parseOnly) {
|
package/lib/language/language.g4
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// allow integers at certain places), one token type for non-quoted and
|
|
21
21
|
// quoted identifiers.
|
|
22
22
|
//
|
|
23
|
-
// * Keep the number of keywords as small as
|
|
23
|
+
// * Keep the number of keywords as small as possible. Thus, built-ins is a
|
|
24
24
|
// topic for the semantic analysis, not the grammar. Examples: no keywords
|
|
25
25
|
// for built-in types or built-in SQL functions. This also avoids noise in
|
|
26
26
|
// the grammar and a huge/slow generated parser.
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
// is not enough for a decision), write a comment starting with `#ATN:`
|
|
44
44
|
// which describes the ambiguity. Additionally, put a comment `/* #ATN n
|
|
45
45
|
// */` INSIDE an (`@after`) action of a rule if the corresponding function
|
|
46
|
-
// in '../gen/
|
|
46
|
+
// in '../gen/languageParser.js' contains `n` occurrences of
|
|
47
47
|
// `adaptivePredict` calls. This is checked in 'test/testCompiler.js',
|
|
48
48
|
// which also counts the total number of `adaptivePredict` occurrences.
|
|
49
49
|
// └─────────────────────────────────────────────────────────────────────────┘
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
// good idea anyway to avoid calls to `adaptivePredict`, see the rules
|
|
56
56
|
// starting with `annotationAssignment_`.
|
|
57
57
|
//
|
|
58
|
+
// * Factoring out a sub rule into a named rule influences the error recovery:
|
|
59
|
+
// the parser tries to consume all tokens which are neither in the follow
|
|
60
|
+
// set of loops and named rules. So be careful.
|
|
61
|
+
//
|
|
58
62
|
// * Do not use actions in the lexer. Examples: de-quote string literals not
|
|
59
63
|
// in the lexer, but in the parser; do not throw errors, but produce error
|
|
60
64
|
// tokens if necessary.
|
|
@@ -106,6 +110,9 @@
|
|
|
106
110
|
//
|
|
107
111
|
// * The ANTLR error "missing attribute access on rule reference c in $c" can
|
|
108
112
|
// be solved with using $ctx.c instead of $c
|
|
113
|
+
//
|
|
114
|
+
// * If you want to set a property starting with '$' like $syntax, use
|
|
115
|
+
// obj['$'+'syntax'] as the ANTLR tool would replace $syntax by $ctx.syntax
|
|
109
116
|
|
|
110
117
|
grammar language;
|
|
111
118
|
options {
|
|
@@ -114,6 +121,7 @@ options {
|
|
|
114
121
|
}
|
|
115
122
|
tokens {
|
|
116
123
|
VIRTUAL, // used with setLocalToken()
|
|
124
|
+
OVER, // used with setLocalTokenIfBefore()
|
|
117
125
|
HelperToken1, // used with setLocalToken(), does not appear in messages
|
|
118
126
|
HelperToken2, // used with setLocalToken(), does not appear in messages
|
|
119
127
|
HideAlternatives, // hide alternative tokens (no token seq!)
|
|
@@ -490,6 +498,8 @@ projectionSpec returns[ query ] locals[ src ]
|
|
|
490
498
|
fromPath[ $src, 'ref']
|
|
491
499
|
)?
|
|
492
500
|
( AS aliasName=ident['FromAlias'] { $src.name = $aliasName.id } )?
|
|
501
|
+
// ANTLR errors are better if we use ( A )? instead of ( A | ):
|
|
502
|
+
{ if (!$src.name) this.classifyImplicitName( $src.scope ? 'FromAlias' : 'Without' ); }
|
|
493
503
|
bracedSelectItemListDef[ $query ]?
|
|
494
504
|
excludingClause[ $query ]?
|
|
495
505
|
;
|
|
@@ -818,7 +828,7 @@ annotateArtifact[ outer, loc, annos ] locals[ art, name = {} ]
|
|
|
818
828
|
)*
|
|
819
829
|
')'
|
|
820
830
|
(
|
|
821
|
-
RETURNS '{'
|
|
831
|
+
RETURNS '{' { $art['$'+'syntax'] = 'returns'; }
|
|
822
832
|
annotateElement[ $art ]*
|
|
823
833
|
'}'
|
|
824
834
|
optionalSemi
|
|
@@ -826,7 +836,7 @@ annotateArtifact[ outer, loc, annos ] locals[ art, name = {} ]
|
|
|
826
836
|
requiredSemi
|
|
827
837
|
)
|
|
828
838
|
|
|
|
829
|
-
RETURNS '{'
|
|
839
|
+
RETURNS '{' { $art['$'+'syntax'] = 'returns'; }
|
|
830
840
|
annotateElement[ $art ]*
|
|
831
841
|
'}'
|
|
832
842
|
optionalSemi
|
|
@@ -1124,13 +1134,7 @@ bracedSelectItemListDef[ query ]
|
|
|
1124
1134
|
'{'
|
|
1125
1135
|
{ if (!$query.columns) $query.columns = []; } // set it early to avoid "wildcard" errors
|
|
1126
1136
|
(
|
|
1127
|
-
|
|
1128
|
-
{
|
|
1129
|
-
$query.columns = [ this.tokenLocation( $star, undefined, '*' ) ];
|
|
1130
|
-
}
|
|
1131
|
-
|
|
|
1132
|
-
selectItemDef[ $query.columns ]
|
|
1133
|
-
)
|
|
1137
|
+
selectItemDef[ $query.columns ]
|
|
1134
1138
|
( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
|
|
1135
1139
|
selectItemDef[ $query.columns ]
|
|
1136
1140
|
)*
|
|
@@ -1139,8 +1143,11 @@ bracedSelectItemListDef[ query ]
|
|
|
1139
1143
|
;
|
|
1140
1144
|
|
|
1141
1145
|
selectItemDef[ outer ] locals[ annos = [] ]
|
|
1142
|
-
@after{ this.attachLocation($art.art); }
|
|
1146
|
+
@after{ if ($ctx.art) this.attachLocation($art.art); }
|
|
1143
1147
|
:
|
|
1148
|
+
star='*'
|
|
1149
|
+
{ $outer.push( this.tokenLocation( $star, undefined, '*' ) ); }
|
|
1150
|
+
|
|
|
1144
1151
|
{ this.docComment( $annos ); }
|
|
1145
1152
|
annotationAssignment_atn[ $annos ]*
|
|
1146
1153
|
// VIRTUAL is keyword, except if before the following tokens texts:
|
|
@@ -1159,6 +1166,8 @@ selectItemDefBody[ outer, annos ] returns[ art = {} ]
|
|
|
1159
1166
|
:
|
|
1160
1167
|
(
|
|
1161
1168
|
e=expression
|
|
1169
|
+
// we cannot use 'condition' instead, as long as we allow aliases without
|
|
1170
|
+
// AS (using rule 'ident' instead of 'identNoKeyword') -> ambiguities
|
|
1162
1171
|
{
|
|
1163
1172
|
$art = this.addItem( $outer, null, null, $annos, { value: $e.expr } );
|
|
1164
1173
|
}
|
|
@@ -1218,11 +1227,7 @@ selectItemInlineList[ art, clause ]
|
|
|
1218
1227
|
'{'
|
|
1219
1228
|
{ $art[$clause] = []; }
|
|
1220
1229
|
(
|
|
1221
|
-
|
|
1222
|
-
{ $art[$clause].push( this.tokenLocation( $star, undefined, '*' ) ); }
|
|
1223
|
-
|
|
|
1224
|
-
selectItemInlineDef[ $art[$clause] ]
|
|
1225
|
-
)
|
|
1230
|
+
selectItemInlineDef[ $art[$clause] ]
|
|
1226
1231
|
( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
|
|
1227
1232
|
selectItemInlineDef[ $art[$clause] ]
|
|
1228
1233
|
)*
|
|
@@ -1231,8 +1236,11 @@ selectItemInlineList[ art, clause ]
|
|
|
1231
1236
|
;
|
|
1232
1237
|
|
|
1233
1238
|
selectItemInlineDef[ outer ] locals[ annos = [] ]
|
|
1234
|
-
@after{ this.attachLocation($art.art); }
|
|
1239
|
+
@after{ if ($ctx.art) this.attachLocation($art.art); }
|
|
1235
1240
|
:
|
|
1241
|
+
star='*'
|
|
1242
|
+
{ $outer.push( this.tokenLocation( $star, undefined, '*' ) ); }
|
|
1243
|
+
|
|
|
1236
1244
|
{ this.docComment( $annos ); }
|
|
1237
1245
|
annotationAssignment_atn[ $annos ]*
|
|
1238
1246
|
art=selectItemDefBody[ $outer, $annos ]
|
|
@@ -1407,6 +1415,8 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
|
|
|
1407
1415
|
|
|
|
1408
1416
|
':'
|
|
1409
1417
|
// #ATN: typeRefOptArgs can start with ARRAY or MANY or ASSOCIATION or TYPE or LOCALIZED
|
|
1418
|
+
// Nevertheless, MANY '{' is handled by local token rewrite:
|
|
1419
|
+
{ this.setLocalToken( 'MANY', 'HelperToken1', /^[^\{]/ ); }
|
|
1410
1420
|
(
|
|
1411
1421
|
typeStruct[ $art ]
|
|
1412
1422
|
optionalSemi
|
|
@@ -1416,6 +1426,12 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
|
|
|
1416
1426
|
( typeToMany[ $art ] | typeToOne[ $art ] | simplePath[ $art.target, 'artref' ] )
|
|
1417
1427
|
typeAssociationCont[ $art ]?
|
|
1418
1428
|
requiredSemi // and if its the ';'...
|
|
1429
|
+
|
|
|
1430
|
+
many=HelperToken1 // rewritten MANY before '{'
|
|
1431
|
+
{ $art.items = { location: this.tokenLocation( $many ) };}
|
|
1432
|
+
typeStruct[ $art.items ]
|
|
1433
|
+
nullability[ $art.items ]?
|
|
1434
|
+
optionalSemi
|
|
1419
1435
|
|
|
|
1420
1436
|
(
|
|
1421
1437
|
array=ARRAY of=OF
|
|
@@ -1455,6 +1471,7 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
|
|
|
1455
1471
|
annotationAssignment_ll1[ $annos ]*
|
|
1456
1472
|
requiredSemi
|
|
1457
1473
|
|
|
|
1474
|
+
// alt lookahead includes MANY '{'
|
|
1458
1475
|
{ $art.type = {}; }
|
|
1459
1476
|
simplePath[ $art.type, 'artref' ]
|
|
1460
1477
|
(
|
|
@@ -1462,8 +1479,20 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
|
|
|
1462
1479
|
head=Number
|
|
1463
1480
|
{ $art['$'+'typeArgs'] = [ this.numberLiteral( $head ) ]; }
|
|
1464
1481
|
( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
|
|
1465
|
-
|
|
1466
|
-
|
|
1482
|
+
(
|
|
1483
|
+
v=VARIABLE
|
|
1484
|
+
{ $art['$'+'typeArgs'].push(
|
|
1485
|
+
{ literal: 'string', val: 'variable', location: this.tokenLocation($v) } );
|
|
1486
|
+
}
|
|
1487
|
+
|
|
|
1488
|
+
f=FLOATING
|
|
1489
|
+
{ $art['$'+'typeArgs'].push(
|
|
1490
|
+
{ literal: 'string', val: 'floating', location: this.tokenLocation($f) } );
|
|
1491
|
+
}
|
|
1492
|
+
|
|
|
1493
|
+
tail=Number
|
|
1494
|
+
{ $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
|
|
1495
|
+
)
|
|
1467
1496
|
)*
|
|
1468
1497
|
')'
|
|
1469
1498
|
{ this.docComment( $annos ); }
|
|
@@ -1687,8 +1716,20 @@ typeRefOptArgs[ art ]
|
|
|
1687
1716
|
head=Number
|
|
1688
1717
|
{ $art['$'+'typeArgs'] = [ this.numberLiteral( $head ) ]; }
|
|
1689
1718
|
( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
|
|
1690
|
-
|
|
1691
|
-
|
|
1719
|
+
(
|
|
1720
|
+
v=VARIABLE
|
|
1721
|
+
{ $art['$'+'typeArgs'].push(
|
|
1722
|
+
{ literal: 'string', val: 'variable', location: this.tokenLocation($v) } );
|
|
1723
|
+
}
|
|
1724
|
+
|
|
|
1725
|
+
f=FLOATING
|
|
1726
|
+
{ $art['$'+'typeArgs'].push(
|
|
1727
|
+
{ literal: 'string', val: 'floating', location: this.tokenLocation($f) } );
|
|
1728
|
+
}
|
|
1729
|
+
|
|
|
1730
|
+
tail=Number
|
|
1731
|
+
{ $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
|
|
1732
|
+
)
|
|
1692
1733
|
)*
|
|
1693
1734
|
')'
|
|
1694
1735
|
|
|
|
@@ -1723,6 +1764,87 @@ orderByClause[ inQuery ] returns [ query ]
|
|
|
1723
1764
|
( ',' obn=orderBySpec { $query.orderBy.push( $obn.ob ); } )*
|
|
1724
1765
|
;
|
|
1725
1766
|
|
|
1767
|
+
overOrderByClause returns [ expr ]
|
|
1768
|
+
:
|
|
1769
|
+
o=ORDER b=BY { $expr = { op: this.tokenLocation($o, $b, 'orderBy' ) , args: [] }}
|
|
1770
|
+
ob1=orderBySpec { $expr.args.push( $ob1.ob ); }
|
|
1771
|
+
( ',' obn=orderBySpec { $expr.args.push( $obn.ob ); } )*
|
|
1772
|
+
;
|
|
1773
|
+
|
|
1774
|
+
partitionByClause returns [ expr ]
|
|
1775
|
+
:
|
|
1776
|
+
p=PARTITION b=BY { $expr = { op: this.tokenLocation($p, $b, 'partitionBy' ) , args: [] }}
|
|
1777
|
+
e1=expression { $expr.args.push( $e1.expr ); }
|
|
1778
|
+
( ',' en=expression { $expr.args.push( $en.expr ); } )*
|
|
1779
|
+
;
|
|
1780
|
+
|
|
1781
|
+
windowFrameClause returns [ wf ]
|
|
1782
|
+
:
|
|
1783
|
+
r=ROWS { $wf = { op: this.tokenLocation($r, null, 'rows' ) , args: [] }}
|
|
1784
|
+
wfe=windowFrameExtentSpec { $wf.args.push( $wfe.wfe ); }
|
|
1785
|
+
;
|
|
1786
|
+
|
|
1787
|
+
windowFrameExtentSpec returns[ wfe ]
|
|
1788
|
+
:
|
|
1789
|
+
{ $wfe = {} }
|
|
1790
|
+
windowFrameStartSpec [ $wfe ]
|
|
1791
|
+
|
|
|
1792
|
+
b=BETWEEN
|
|
1793
|
+
{ $wfe = { op: this.tokenLocation( $b, null, 'frameBetween' ), args: [] } }
|
|
1794
|
+
wfb1=windowFrameBoundSpec { $wfe.args.push( $wfb1.wfb ); }
|
|
1795
|
+
AND
|
|
1796
|
+
wfb2=windowFrameBoundSpec { $wfe.args.push( $wfb2.wfb ); }
|
|
1797
|
+
;
|
|
1798
|
+
|
|
1799
|
+
windowFrameBoundSpec returns [ wfb ]
|
|
1800
|
+
@after{ /* #ATN 1 */ }
|
|
1801
|
+
:
|
|
1802
|
+
// #ATN: Not ll1 because `UNBOUNDED` could also be part of the windowFrameStartSpec
|
|
1803
|
+
// `UNBOUNDED` would then be immediately followed by `PRECEDING`
|
|
1804
|
+
u=UNBOUNDED f=FOLLOWING
|
|
1805
|
+
{ $wfb = { op: this.tokenLocation($u, $f, 'unboundedFollowing' ), args: []} }
|
|
1806
|
+
|
|
|
1807
|
+
// #ATN: Not ll1 because `Number` could also be part of the windowFrameStartSpec
|
|
1808
|
+
// `Number` would then be immediately followed by `PRECEDING`
|
|
1809
|
+
n=Number f=FOLLOWING
|
|
1810
|
+
{ $wfb = { op: this.tokenLocation($n, $f, 'following' ), args: [ this.numberLiteral( $n ) ]} }
|
|
1811
|
+
|
|
|
1812
|
+
{ $wfb = {} }
|
|
1813
|
+
windowFrameStartSpec [ $wfb ]
|
|
1814
|
+
;
|
|
1815
|
+
|
|
1816
|
+
windowFrameStartSpec [ wf ]
|
|
1817
|
+
:
|
|
1818
|
+
u=UNBOUNDED p=PRECEDING
|
|
1819
|
+
{
|
|
1820
|
+
$wf.op = this.tokenLocation($u, $p, 'unboundedPreceding' );
|
|
1821
|
+
$wf.args = [];
|
|
1822
|
+
}
|
|
1823
|
+
|
|
|
1824
|
+
n=Number p=PRECEDING
|
|
1825
|
+
{
|
|
1826
|
+
$wf.op = this.tokenLocation($p, null, 'preceding' );
|
|
1827
|
+
$wf.args = [ this.numberLiteral( $n ) ];
|
|
1828
|
+
}
|
|
1829
|
+
|
|
|
1830
|
+
c=CURRENT r=ROW
|
|
1831
|
+
{
|
|
1832
|
+
$wf.op = this.tokenLocation($c, $r, 'currentRow' );
|
|
1833
|
+
$wf.args = [];
|
|
1834
|
+
}
|
|
1835
|
+
;
|
|
1836
|
+
|
|
1837
|
+
overClause returns [ over ]
|
|
1838
|
+
@after { this.attachLocation($over); }
|
|
1839
|
+
:
|
|
1840
|
+
o=OVER { $over = { op: this.tokenLocation( $o, null, 'over' ) , args: [] } }
|
|
1841
|
+
'('
|
|
1842
|
+
( pb=partitionByClause { $over.args.push( $pb.expr ); } )?
|
|
1843
|
+
( ob=overOrderByClause { $over.args.push( $ob.expr ); } )?
|
|
1844
|
+
( wf=windowFrameClause { $over.args.push( $wf.wf ); } )?
|
|
1845
|
+
')'
|
|
1846
|
+
;
|
|
1847
|
+
|
|
1726
1848
|
limitClause[ inQuery ] returns [ query ]
|
|
1727
1849
|
:
|
|
1728
1850
|
limkw=LIMIT { $query = this.unaryOpForParens( $inQuery, '$'+'query' ); }
|
|
@@ -1781,13 +1903,7 @@ queryPrimary returns[ query = {} ]
|
|
|
1781
1903
|
{ $query.quantifier = this.tokenLocation( $ad, undefined, $ad.text.toLowerCase() ); }
|
|
1782
1904
|
)?
|
|
1783
1905
|
{ $query.columns = []; } // set it early to avoid "wildcard" errors
|
|
1784
|
-
|
|
1785
|
-
{
|
|
1786
|
-
$query.columns = [ this.tokenLocation( $star, undefined, '*' ) ];
|
|
1787
|
-
}
|
|
1788
|
-
|
|
|
1789
|
-
selectItemDef[ $query.columns ]
|
|
1790
|
-
)
|
|
1906
|
+
selectItemDef[ $query.columns ]
|
|
1791
1907
|
( ',' { if (this.isStraightBefore("}")) break; } // allow ',' before '}'
|
|
1792
1908
|
selectItemDef[ $query.columns ]
|
|
1793
1909
|
)*
|
|
@@ -1891,6 +2007,8 @@ tableTerm returns [ table ]
|
|
|
1891
2007
|
// if we would use rule `ident`, we would either had to make all JOIN
|
|
1892
2008
|
// kinds reserved or introduce ATN
|
|
1893
2009
|
)?
|
|
2010
|
+
// ANTLR errors are better if we use ( A | B )? instead of ( A | B | ):
|
|
2011
|
+
{ if (!$table.name) this.classifyImplicitName( $table.scope ? 'FromAlias' : 'Without' ); }
|
|
1894
2012
|
|
|
|
1895
2013
|
open='('
|
|
1896
2014
|
// #ATN: The following alternative is not LL1, because both can start with
|
|
@@ -1945,7 +2063,7 @@ fromPath[ qp, idkind ]
|
|
|
1945
2063
|
condition returns [ cond ] locals [ args = [], orl = [] ]
|
|
1946
2064
|
@after{
|
|
1947
2065
|
$cond = ($args.length == 1)
|
|
1948
|
-
? $args[0]
|
|
2066
|
+
? this.attachLocation( $args[0] )
|
|
1949
2067
|
: this.attachLocation({ op: $orl[0], args: $args });
|
|
1950
2068
|
}
|
|
1951
2069
|
:
|
|
@@ -2106,6 +2224,10 @@ expressionTerm returns [ expr ] locals [ op, args = [] ]
|
|
|
2106
2224
|
this.notSupportedYet( $ne ); }
|
|
2107
2225
|
|
|
|
2108
2226
|
vp=valuePath[ 'ref', null ] { $expr = this.valuePathAst( $vp.qp ); }
|
|
2227
|
+
{ this.setLocalTokenIfBefore( 'OVER', 'OVER', /^\($/i ); }
|
|
2228
|
+
(
|
|
2229
|
+
over=overClause { $expr.suffix = [ $over.over ] }
|
|
2230
|
+
)?
|
|
2109
2231
|
|
|
|
2110
2232
|
':'
|
|
2111
2233
|
( vp=valuePath[ 'paramref', this.startLocation() ]
|
|
@@ -2306,7 +2428,7 @@ optionalCardinality[ pathStep ]
|
|
|
2306
2428
|
// completion just produces `:`after having inserted a Number - TODO.
|
|
2307
2429
|
{ if (this._input.LT(2).text !== ':') return $ctx; }
|
|
2308
2430
|
( trgMax=Number ':'
|
|
2309
|
-
{ if ($pathStep) $pathStep.cardinality = { targetMax: this.numberLiteral( $trgMax ) } }
|
|
2431
|
+
{ if ($pathStep) $pathStep.cardinality = { targetMax: this.numberLiteral( $trgMax ), location: this.startLocation() }; }
|
|
2310
2432
|
)
|
|
2311
2433
|
;
|
|
2312
2434
|
|
|
@@ -2541,6 +2663,7 @@ ident[ category ] returns[ id ]
|
|
|
2541
2663
|
| COMPOSITION
|
|
2542
2664
|
| CONTEXT
|
|
2543
2665
|
| CROSS
|
|
2666
|
+
| CURRENT
|
|
2544
2667
|
| DAY
|
|
2545
2668
|
| DEFAULT
|
|
2546
2669
|
| DEFINE
|
|
@@ -2557,6 +2680,8 @@ ident[ category ] returns[ id ]
|
|
|
2557
2680
|
| EXCLUDING
|
|
2558
2681
|
| EXTEND
|
|
2559
2682
|
| FIRST
|
|
2683
|
+
| FLOATING
|
|
2684
|
+
| FOLLOWING
|
|
2560
2685
|
| FULL
|
|
2561
2686
|
| FUNCTION
|
|
2562
2687
|
| GROUP
|
|
@@ -2587,10 +2712,14 @@ ident[ category ] returns[ id ]
|
|
|
2587
2712
|
| ORDER
|
|
2588
2713
|
| OUTER
|
|
2589
2714
|
| PARAMETERS
|
|
2715
|
+
| PARTITION
|
|
2716
|
+
| PRECEDING
|
|
2590
2717
|
| PROJECTION
|
|
2591
2718
|
| REDIRECTED
|
|
2592
2719
|
| RETURNS
|
|
2593
2720
|
| RIGHT
|
|
2721
|
+
| ROW
|
|
2722
|
+
| ROWS
|
|
2594
2723
|
| SECOND
|
|
2595
2724
|
| SERVICE
|
|
2596
2725
|
| THEN
|
|
@@ -2599,6 +2728,8 @@ ident[ category ] returns[ id ]
|
|
|
2599
2728
|
| TO
|
|
2600
2729
|
| TYPE
|
|
2601
2730
|
| USING
|
|
2731
|
+
| UNBOUNDED
|
|
2732
|
+
| VARIABLE
|
|
2602
2733
|
| VIEW
|
|
2603
2734
|
| YEAR
|
|
2604
2735
|
;
|
|
@@ -2699,6 +2830,7 @@ BOTH : [bB][oO][tT][hH] ;
|
|
|
2699
2830
|
COMPOSITION : [cC][oO][mM][pP][oO][sS][iI][tT][iI][oO][nN] ;
|
|
2700
2831
|
CONTEXT : [cC][oO][nN][tT][eE][xX][tT] ;
|
|
2701
2832
|
CROSS : [cC][rR][oO][sS][sS] ;
|
|
2833
|
+
CURRENT : [cC][uU][rR][rR][eE][nN][tT] ;
|
|
2702
2834
|
DAY : [dD][aA][yY] ;
|
|
2703
2835
|
DEFAULT : [dD][eE][fF][aA][uU][lL][tT] ;
|
|
2704
2836
|
DEFINE : [dD][eE][fF][iI][nN][eE] ;
|
|
@@ -2715,6 +2847,8 @@ EXCEPT : [eE][xX][cC][eE][pP][tT] ;
|
|
|
2715
2847
|
EXCLUDING : [eE][xX][cC][lL][uU][dD][iI][nN][gG] ;
|
|
2716
2848
|
EXTEND : [eE][xX][tT][eE][nN][dD] ;
|
|
2717
2849
|
FIRST : [fF][iI][rR][sS][tT] ;
|
|
2850
|
+
FLOATING : [fF][lL][oO][aA][tT][iI][nN][gG] ;
|
|
2851
|
+
FOLLOWING : [fF][oO][lL][lL][oO][wW][iI][nN][gG] ;
|
|
2718
2852
|
FULL : [fF][uU][lL][lL] ;
|
|
2719
2853
|
FUNCTION : [fF][uU][nN][cC][tT][iI][oO][nN] ;
|
|
2720
2854
|
GROUP : [gG][rR][oO][uU][pP] ;
|
|
@@ -2744,11 +2878,16 @@ ONE : [oO][nN][eE] ;
|
|
|
2744
2878
|
OR : [oO][rR] ;
|
|
2745
2879
|
ORDER : [oO][rR][dD][eE][rR] ;
|
|
2746
2880
|
OUTER : [oO][uU][tT][eE][rR] ;
|
|
2881
|
+
// OVER : [oO][vV][eE][rR] ;
|
|
2747
2882
|
PARAMETERS : [pP][aA][rR][aA][mM][eE][tT][eE][rR][sS] ;
|
|
2883
|
+
PARTITION: [pP][aA][rR][tT][iI][tT][iI][oO][nN] ;
|
|
2884
|
+
PRECEDING: [pP][rR][eE][cC][eE][dD][iI][nN][gG] ;
|
|
2748
2885
|
PROJECTION : [pP][rR][oO][jJ][eE][cC][tT][iI][oO][nN] ;
|
|
2749
2886
|
REDIRECTED : [rR][eE][dD][iI][rR][eE][cC][tT][eE][dD] ;
|
|
2750
2887
|
RETURNS : [rR][eE][tT][uU][rR][nN][sS] ;
|
|
2751
2888
|
RIGHT : [rR][iI][gG][hH][tT] ;
|
|
2889
|
+
ROW : [rR][oO][wW] ;
|
|
2890
|
+
ROWS : [rR][oO][wW][sS] ;
|
|
2752
2891
|
SECOND : [sS][eE][cC][oO][nN][dD] ;
|
|
2753
2892
|
SERVICE : [sS][eE][rR][vV][iI][cC][eE] ;
|
|
2754
2893
|
THEN : [tT][hH][eE][nN] ;
|
|
@@ -2756,7 +2895,9 @@ TRAILING : [tT][rR][aA][iI][lL][iI][nN][gG] ;
|
|
|
2756
2895
|
TO : [tT][oO] ; // or make reserved? (is in SQL-92)
|
|
2757
2896
|
TYPE : [tT][yY][pP][eE] ;
|
|
2758
2897
|
UNION : [uU][nN][iI][oO][nN] ;
|
|
2898
|
+
UNBOUNDED : [uU][nN][bB][oO][uU][nN][dD][eE][dD] ;
|
|
2759
2899
|
USING : [uU][sS][iI][nN][gG] ;
|
|
2900
|
+
VARIABLE : [vV][aA][rR][iI][aA][bB][lL][eE] ;
|
|
2760
2901
|
VIEW : [vV][iI][eE][wW] ;
|
|
2761
2902
|
// VIRTUAL: [vV][iI][rR][tT][uU][aA][lL] ; see tokens {}
|
|
2762
2903
|
YEAR : [yY][eE][aA][rR] ;
|