@sap/cds-compiler 5.9.4 → 6.0.10
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 +104 -319
- package/README.md +1 -1
- package/bin/cds_update_identifiers.js +3 -5
- package/bin/cdsc.js +22 -8
- package/bin/cdshi.js +1 -1
- package/bin/cdsse.js +4 -4
- package/doc/CHANGELOG_BETA.md +11 -0
- package/doc/CHANGELOG_DEPRECATED.md +29 -0
- package/lib/api/main.js +8 -5
- package/lib/api/options.js +12 -10
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +190 -96
- package/lib/base/messages.js +29 -20
- package/lib/base/model.js +14 -24
- package/lib/checks/actionsFunctions.js +10 -20
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +30 -10
- package/lib/checks/enums.js +31 -0
- package/lib/checks/foreignKeys.js +2 -2
- package/lib/checks/hasPersistedElements.js +5 -0
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/managedWithoutKeys.js +5 -4
- package/lib/checks/queryNoDbArtifacts.js +10 -8
- package/lib/checks/types.js +5 -5
- package/lib/checks/validator.js +6 -4
- package/lib/compiler/assert-consistency.js +12 -9
- package/lib/compiler/checks.js +18 -50
- package/lib/compiler/define.js +6 -6
- package/lib/compiler/extend.js +2 -1
- package/lib/compiler/generate.js +14 -17
- package/lib/compiler/populate.js +8 -31
- package/lib/compiler/propagator.js +21 -35
- package/lib/compiler/resolve.js +35 -22
- package/lib/compiler/shared.js +7 -1
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +1 -1
- package/lib/edm/annotations/edmJson.js +20 -15
- package/lib/edm/annotations/genericTranslation.js +7 -8
- package/lib/edm/csn2edm.js +46 -50
- package/lib/edm/edm.js +8 -7
- package/lib/edm/edmPreprocessor.js +33 -83
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +55 -44
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1133 -1150
- package/lib/json/from-csn.js +70 -43
- package/lib/json/to-csn.js +6 -8
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/main.d.ts +58 -24
- package/lib/model/csnUtils.js +28 -39
- package/lib/model/xprAsTree.js +23 -9
- package/lib/modelCompare/compare.js +5 -4
- package/lib/optionProcessor.js +21 -17
- package/lib/parsers/AstBuildingParser.js +63 -11
- package/lib/parsers/XprTree.js +57 -3
- package/lib/parsers/identifiers.js +1 -1
- package/lib/parsers/index.js +0 -3
- package/lib/render/manageConstraints.js +25 -25
- package/lib/render/toCdl.js +173 -170
- package/lib/render/toHdbcds.js +126 -128
- package/lib/render/toRename.js +7 -7
- package/lib/render/toSql.js +128 -125
- package/lib/render/utils/common.js +47 -22
- package/lib/render/utils/delta.js +25 -25
- package/lib/render/utils/operators.js +2 -2
- package/lib/render/utils/pretty.js +5 -5
- package/lib/render/utils/sql.js +13 -13
- package/lib/render/utils/standardDatabaseFunctions.js +115 -103
- package/lib/render/utils/unique.js +4 -4
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +2 -2
- package/lib/transform/db/associations.js +6 -7
- package/lib/transform/db/assocsToQueries/utils.js +4 -5
- package/lib/transform/db/backlinks.js +12 -9
- package/lib/transform/db/cdsPersistence.js +8 -7
- package/lib/transform/db/constraints.js +13 -10
- package/lib/transform/db/expansion.js +7 -3
- package/lib/transform/db/flattening.js +4 -14
- package/lib/transform/db/processSqlServices.js +2 -1
- package/lib/transform/db/temporal.js +5 -7
- package/lib/transform/db/views.js +2 -4
- package/lib/transform/draft/db.js +8 -8
- package/lib/transform/draft/odata.js +10 -7
- package/lib/transform/forOdata.js +10 -5
- package/lib/transform/forRelationalDB.js +5 -75
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +11 -10
- package/lib/transform/odata/flattening.js +8 -4
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/transformUtils.js +4 -8
- package/lib/transform/translateAssocsToJoins.js +14 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
- package/lib/utils/objectUtils.js +0 -17
- package/package.json +10 -13
- package/share/messages/def-upcoming-virtual-change.md +1 -1
- package/LICENSE +0 -37
- package/bin/cds_remove_invalid_whitespace.js +0 -138
- package/doc/CHANGELOG_ARCHIVE.md +0 -3604
- package/lib/gen/genericAntlrParser.js +0 -3
- package/lib/gen/language.checksum +0 -1
- package/lib/gen/language.interp +0 -456
- package/lib/gen/language.tokens +0 -180
- package/lib/gen/languageLexer.interp +0 -439
- package/lib/gen/languageLexer.js +0 -1483
- package/lib/gen/languageLexer.tokens +0 -167
- package/lib/gen/languageParser.js +0 -24941
- package/lib/language/antlrParser.js +0 -205
- package/lib/language/errorStrategy.js +0 -646
- package/lib/language/genericAntlrParser.js +0 -1572
- package/lib/parsers/CdlGrammar.g4 +0 -2070
|
@@ -1,646 +0,0 @@
|
|
|
1
|
-
// Error strategy with special handling for (non-reserved) keywords
|
|
2
|
-
|
|
3
|
-
// If a language has non-reserved keywords, any such keyword can be used at
|
|
4
|
-
// places where just a identifier is expected. For doing so, we define a rule
|
|
5
|
-
// ident : Identifier | NONRESERVED_1 | ... NONRESERVED_n ;
|
|
6
|
-
//
|
|
7
|
-
// Now consider another rule:
|
|
8
|
-
// expected : RESERVED_j | NONRESERVED_k | ident ;
|
|
9
|
-
// If parsing fails at this place, you expect to see an message like
|
|
10
|
-
// Mismatched input '?', expecting RESERVED_j, NONRESERVED_k, or Identifier
|
|
11
|
-
// With ANTLR's default error strategy, you unfortunately also see all other
|
|
12
|
-
// n-1 non-reserved keyword after "expecting"...
|
|
13
|
-
//
|
|
14
|
-
// The error strategy provided by this file gives you the expected message.
|
|
15
|
-
// The example above shows that it is not enough to just remove all
|
|
16
|
-
// non-reserved keywords from the expected-set. The error strategy also allows
|
|
17
|
-
// you to match reserved keywords as identifiers at certain places (when there
|
|
18
|
-
// are no alternatives).
|
|
19
|
-
|
|
20
|
-
// For using this error strategy, the grammar for the parser/lexer must have a
|
|
21
|
-
// lexer rule `Number`, then rules for unreserved keywords, and finally a rule
|
|
22
|
-
// `Identifier`. No tokens (which are used in parser rules) must be defined
|
|
23
|
-
// after that, no other rules must be defined in between those rules.
|
|
24
|
-
|
|
25
|
-
// This file is actually very ANTLR4 specific and should be checked against
|
|
26
|
-
// future versions of the ANTLR4-js runtime. There is no need to look at this
|
|
27
|
-
// file if you just want to understand the rest of this compiler project.
|
|
28
|
-
|
|
29
|
-
'use strict';
|
|
30
|
-
|
|
31
|
-
const antlr4 = require('antlr4');
|
|
32
|
-
const Antlr4LL1Analyzer = require('antlr4/src/antlr4/LL1Analyzer');
|
|
33
|
-
const { DefaultErrorStrategy } = require('antlr4/src/antlr4/error/ErrorStrategy');
|
|
34
|
-
const { InputMismatchException } = require('antlr4/src/antlr4/error/Errors');
|
|
35
|
-
const {
|
|
36
|
-
predictionContextFromRuleContext: predictionContext,
|
|
37
|
-
} = require('antlr4/src/antlr4/PredictionContext');
|
|
38
|
-
const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
|
|
39
|
-
const { IntervalSet, Interval } = require('antlr4/src/antlr4/IntervalSet');
|
|
40
|
-
const { CompilerAssertion } = require('../base/error');
|
|
41
|
-
|
|
42
|
-
const keywordRegexp = /^[a-zA-Z]+$/; // we don't have keywords with underscore
|
|
43
|
-
|
|
44
|
-
let SEMI = null;
|
|
45
|
-
let RBRACE = null;
|
|
46
|
-
|
|
47
|
-
// Class which adapts ANTLR4s standard error strategy: do something special
|
|
48
|
-
// with (non-reserved) keywords.
|
|
49
|
-
//
|
|
50
|
-
// An instance of this class should be set as property `_errHandler` to the
|
|
51
|
-
// parser (prototype).
|
|
52
|
-
class KeywordErrorStrategy extends DefaultErrorStrategy {
|
|
53
|
-
constructor( ...args ) {
|
|
54
|
-
super( ...args );
|
|
55
|
-
|
|
56
|
-
this._super = {
|
|
57
|
-
recoverInline: super.recoverInline,
|
|
58
|
-
getExpectedTokens: super.getExpectedTokens,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// TODO: Use actual methods
|
|
64
|
-
Object.assign( KeywordErrorStrategy.prototype, {
|
|
65
|
-
sync,
|
|
66
|
-
singleTokenDeletion,
|
|
67
|
-
reportNoViableAlternative,
|
|
68
|
-
reportInputMismatch,
|
|
69
|
-
reportUnwantedToken,
|
|
70
|
-
reportMissingToken,
|
|
71
|
-
reportIgnoredWith,
|
|
72
|
-
// getErrorRecoverySet,
|
|
73
|
-
consumeUntil,
|
|
74
|
-
consumeAndMarkUntil,
|
|
75
|
-
recoverInline,
|
|
76
|
-
getMissingSymbol,
|
|
77
|
-
getExpectedTokensForMessage,
|
|
78
|
-
getTokenDisplay,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Attempt to recover from problems in subrules, except if rule has defined a
|
|
82
|
-
// local variable `_sync` with value 'nop'
|
|
83
|
-
// TODO: consider performance - see #8800
|
|
84
|
-
// See DefaultErrorStrategy#sync
|
|
85
|
-
function sync( recognizer ) {
|
|
86
|
-
// If already recovering, don't try to sync
|
|
87
|
-
if (this.inErrorRecoveryMode(recognizer))
|
|
88
|
-
return;
|
|
89
|
-
|
|
90
|
-
const token = recognizer.getCurrentToken();
|
|
91
|
-
if (!token)
|
|
92
|
-
return;
|
|
93
|
-
|
|
94
|
-
const s = recognizer._interp.atn.states[recognizer.state];
|
|
95
|
-
// try cheaper subset first; might get lucky. seems to shave a wee bit off
|
|
96
|
-
const nextTokens = recognizer.atn.nextTokens(s);
|
|
97
|
-
// console.log('SYNC:', recognizer._ctx._sync, s.stateType, token.text,
|
|
98
|
-
// intervalSetToArray( recognizer, nextTokens ))
|
|
99
|
-
|
|
100
|
-
if (nextTokens.contains(token.type)) { // we are sure the token matches
|
|
101
|
-
if (token.text === '}' && recognizer.$nextTokensToken !== token &&
|
|
102
|
-
nextTokens.contains(SEMI)) {
|
|
103
|
-
// if the '}' could be matched alternative to ';', we had an opt ';' (rule requiredSemi)
|
|
104
|
-
recognizer.$nextTokensToken = token;
|
|
105
|
-
recognizer.$nextTokensState = recognizer.state;
|
|
106
|
-
recognizer.$nextTokensContext = recognizer._ctx;
|
|
107
|
-
}
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (nextTokens.contains(antlr4.Token.EPSILON)) {
|
|
112
|
-
// when exiting a (innermost) rule, remember the state to make
|
|
113
|
-
// getExpectedTokensForMessage() calculate the full "expected set"
|
|
114
|
-
if (recognizer.$nextTokensToken !== token) {
|
|
115
|
-
// console.log('SET:',token.type,recognizer.state,recognizer.$nextTokensToken &&
|
|
116
|
-
// recognizer.$nextTokensToken.type)
|
|
117
|
-
recognizer.$nextTokensToken = token;
|
|
118
|
-
recognizer.$nextTokensState = recognizer.state;
|
|
119
|
-
recognizer.$nextTokensContext = recognizer._ctx;
|
|
120
|
-
}
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Expected token is identifier, current is (reserved) KEYWORD:
|
|
125
|
-
// TODO: do not use this if "close enough" (1 char diff or prefix)
|
|
126
|
-
// to a keyword in nextTokens
|
|
127
|
-
//
|
|
128
|
-
// NOTE: it is important to do this only if EPSILON is not in `nextTokens`,
|
|
129
|
-
// which means that we cannot bring the better special syntax-unexpected-reserved
|
|
130
|
-
// in all cases. Reason: high performance impact of the alternative,
|
|
131
|
-
// i.e. calling method Parser#isExpectedToken() = invoking the ATN
|
|
132
|
-
// interpreter to see behind EPSILON.
|
|
133
|
-
const identType = recognizer.constructor.Identifier;
|
|
134
|
-
if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) {
|
|
135
|
-
recognizer.message( 'syntax-unexpected-reserved-word', token,
|
|
136
|
-
{ code: token.text, delimited: token.text } );
|
|
137
|
-
// TODO: attach tokens like for 'syntax-unexpected-token'
|
|
138
|
-
token.type = identType; // make next ANTLR decision assume identifier
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (recognizer._ctx._sync === 'nop')
|
|
143
|
-
return;
|
|
144
|
-
switch (s.stateType) {
|
|
145
|
-
case ATNState.BLOCK_START: // 3
|
|
146
|
-
case ATNState.STAR_BLOCK_START: // 5
|
|
147
|
-
case ATNState.PLUS_BLOCK_START: // 4
|
|
148
|
-
case ATNState.STAR_LOOP_ENTRY: // 10
|
|
149
|
-
// report error and recover if possible
|
|
150
|
-
if ( token.text !== '}' && // do not just delete a '}'
|
|
151
|
-
this.singleTokenDeletion(recognizer) !== null) { // also calls reportUnwantedToken
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
else if (recognizer._ctx._sync === 'recover') {
|
|
155
|
-
this.reportInputMismatch( recognizer, new InputMismatchException(recognizer) );
|
|
156
|
-
this.consumeUntil( recognizer, nextTokens );
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
// TODO: at least with STAR_LOOP_ENTRY, we might want to do s/th similar as
|
|
160
|
-
// with LOOP_BACK (syncing to “expected tokens” -> the separator)
|
|
161
|
-
throw new InputMismatchException(recognizer);
|
|
162
|
-
|
|
163
|
-
case ATNState.PLUS_LOOP_BACK: // 11
|
|
164
|
-
case ATNState.STAR_LOOP_BACK: { // 9
|
|
165
|
-
// TODO: do not delete a '}', ')', ',', ';'
|
|
166
|
-
this.reportUnwantedToken(recognizer);
|
|
167
|
-
const expecting = new IntervalSet();
|
|
168
|
-
expecting.addSet(recognizer.getExpectedTokens());
|
|
169
|
-
|
|
170
|
-
// First try some ',' insertion (TODO does not work yet):
|
|
171
|
-
if (trySeparatorInsertion( recognizer, expecting, "','" ))
|
|
172
|
-
return;
|
|
173
|
-
|
|
174
|
-
// We then try syncing only to the loop-cont (`,`) / loop-end (`}`) token set,
|
|
175
|
-
// but only for the current or next line (and not consuming `;`s):
|
|
176
|
-
const prevToken = recognizer.getTokenStream().LT(-1);
|
|
177
|
-
if (token.line <= prevToken.line + 1 && // in same or next line
|
|
178
|
-
this.consumeAndMarkUntil( recognizer, expecting, true ))
|
|
179
|
-
break;
|
|
180
|
-
// console.log(token.text,JSON.stringify(intervalSetToArray(recognizer,expecting)))
|
|
181
|
-
|
|
182
|
-
// If that fails, we also sync to all tokens which are in the follow set of
|
|
183
|
-
// the current rule and all outer rules
|
|
184
|
-
const whatFollowsLoopIterationOrRule = expecting.addSet(this.getErrorRecoverySet(recognizer));
|
|
185
|
-
this.consumeUntil(recognizer, whatFollowsLoopIterationOrRule);
|
|
186
|
-
// console.log(JSON.stringify(intervalSetToArray(recognizer,expecting)))
|
|
187
|
-
if (recognizer._ctx._sync === 'recover' || // in start rule: no exception
|
|
188
|
-
nextTokens.contains( recognizer.getTokenStream().LA(1) ))
|
|
189
|
-
return;
|
|
190
|
-
throw new InputMismatchException(recognizer);
|
|
191
|
-
}
|
|
192
|
-
default:
|
|
193
|
-
// do nothing if we can't identify the exact kind of ATN state
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
function trySeparatorInsertion( recognizer, expecting, separatorName ) {
|
|
199
|
-
// Remark: this function does not really work, because it is based on
|
|
200
|
-
// singleTokenInsertion, which also does not really work… (see below).
|
|
201
|
-
// But we might improve it in the future…
|
|
202
|
-
const separator = recognizer.literalNames.indexOf( separatorName );
|
|
203
|
-
if (!expecting.contains( separator ))
|
|
204
|
-
return false;
|
|
205
|
-
|
|
206
|
-
const currentSymbolType = recognizer.getTokenStream().LA(1);
|
|
207
|
-
// if current token is consistent with what could come after current
|
|
208
|
-
// ATN state, then we know we're missing a token; error recovery
|
|
209
|
-
// is free to conjure up and insert the missing token
|
|
210
|
-
const { atn } = recognizer._interp;
|
|
211
|
-
const currentState = atn.states[recognizer.state];
|
|
212
|
-
const next = separatorTransition( currentState.transitions, separator ).target;
|
|
213
|
-
// While this is an improvement to the default ANTLR code for
|
|
214
|
-
// singleTokenInsertion(), it still does not help, as we navigate along an
|
|
215
|
-
// epsilon transition, i.e. we still see ',', etc
|
|
216
|
-
const expectingAtLL2 = atn.nextTokens(next, recognizer._ctx);
|
|
217
|
-
if (!expectingAtLL2.contains(currentSymbolType))
|
|
218
|
-
return false;
|
|
219
|
-
|
|
220
|
-
this.reportMissingToken(recognizer);
|
|
221
|
-
return getMissingSymbol( recognizer, separator );
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function separatorTransition( transitions, separator ) {
|
|
225
|
-
for (const tr of transitions) {
|
|
226
|
-
if (tr.matches( separator ))
|
|
227
|
-
return tr;
|
|
228
|
-
}
|
|
229
|
-
return transitions[0];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function singleTokenDeletion( recognizer ) {
|
|
233
|
-
const token = recognizer.getCurrentToken();
|
|
234
|
-
if (!token || token.text === '}')
|
|
235
|
-
return null;
|
|
236
|
-
|
|
237
|
-
const nextTokenType = recognizer.getTokenStream().LA(2);
|
|
238
|
-
const { Number: num } = recognizer.constructor;
|
|
239
|
-
if (nextTokenType > num && // next token is Id|Unreserved|IllegalToken
|
|
240
|
-
token.type <= num) // current token is not
|
|
241
|
-
return null;
|
|
242
|
-
|
|
243
|
-
const expecting = this.getExpectedTokens(recognizer);
|
|
244
|
-
if (!expecting.contains(nextTokenType))
|
|
245
|
-
return null;
|
|
246
|
-
|
|
247
|
-
this.reportUnwantedToken(recognizer);
|
|
248
|
-
recognizer.consume(); // simply delete extra token
|
|
249
|
-
// we want to return the token we're actually matching
|
|
250
|
-
const matchedSymbol = recognizer.getCurrentToken();
|
|
251
|
-
this.reportMatch( recognizer ); // we know current token is correct
|
|
252
|
-
return matchedSymbol;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// singleTokenInsertion called by recoverInline (called by match / in else),
|
|
257
|
-
// calls reportMissingToken
|
|
258
|
-
|
|
259
|
-
// Report `NoViableAltException e` signalled by parser `recognizer`
|
|
260
|
-
function reportNoViableAlternative( recognizer, e ) {
|
|
261
|
-
// console.log('NOV:',this.getTokenErrorDisplay(e.startToken),
|
|
262
|
-
// this.getTokenErrorDisplay(e.offendingToken))
|
|
263
|
-
if (e.startToken === e.offendingToken) { // mismatch at LA(1)
|
|
264
|
-
this.reportInputMismatch( recognizer, e );
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
this.reportInputMismatch( recognizer, e, !e.deadEndConfigs || e.deadEndConfigs.configs );
|
|
268
|
-
do {
|
|
269
|
-
// console.log('CONSUME-NOVIA:',this.getTokenErrorDisplay(recognizer.getCurrentToken()));
|
|
270
|
-
recognizer.consume();
|
|
271
|
-
} while (recognizer.getCurrentToken() !== e.offendingToken);
|
|
272
|
-
// this.lastErrorIndex = e.startToken.tokenIndex; // avoid another consume()
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Report `InputMismatchException e` signalled by parser `recognizer``
|
|
277
|
-
function reportInputMismatch( recognizer, e, deadEnds ) {
|
|
278
|
-
const expecting = deadEnds !== true && // true: cannot compute expecting
|
|
279
|
-
this.getExpectedTokensForMessage( recognizer, e.offendingToken, deadEnds );
|
|
280
|
-
const offending = this.getTokenDisplay( e.offendingToken, recognizer );
|
|
281
|
-
e.offendingToken.$isSkipped = 'offending';
|
|
282
|
-
let err;
|
|
283
|
-
if (expecting && expecting.length) {
|
|
284
|
-
err = recognizer.error( 'syntax-unexpected-token', e.offendingToken,
|
|
285
|
-
{ offending, expecting } );
|
|
286
|
-
err.expectedTokens = expecting;
|
|
287
|
-
}
|
|
288
|
-
else { // should not really happen anymore... -> no messageId !
|
|
289
|
-
err = recognizer.error( null, e.offendingToken, { offending },
|
|
290
|
-
'Mismatched $(OFFENDING)' );
|
|
291
|
-
}
|
|
292
|
-
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig
|
|
293
|
-
recognizer.notifyErrorListeners( err.message, e.offendingToken, err );
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Report unwanted token when the parser `recognizer` tries to recover/sync
|
|
297
|
-
function reportUnwantedToken( recognizer, expecting ) {
|
|
298
|
-
if (this.inErrorRecoveryMode(recognizer))
|
|
299
|
-
return;
|
|
300
|
-
this.beginErrorCondition(recognizer);
|
|
301
|
-
|
|
302
|
-
const token = recognizer.getCurrentToken();
|
|
303
|
-
token.$isSkipped = 'offending';
|
|
304
|
-
expecting ??= this.getExpectedTokensForMessage( recognizer, token );
|
|
305
|
-
const offending = this.getTokenDisplay( token, recognizer );
|
|
306
|
-
// Just text variant, no other message id! Would depend on ANTLR-internals
|
|
307
|
-
const err = recognizer.error( 'syntax-unexpected-token', token,
|
|
308
|
-
{ '#': 'unwanted', offending, expecting } );
|
|
309
|
-
err.expectedTokens = expecting; // TODO: remove next token?
|
|
310
|
-
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig
|
|
311
|
-
recognizer.notifyErrorListeners( err.message, token, err );
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Report missing token when the parser `recognizer` tries to recover/sync
|
|
315
|
-
function reportMissingToken( recognizer ) {
|
|
316
|
-
if ( this.inErrorRecoveryMode(recognizer))
|
|
317
|
-
return;
|
|
318
|
-
this.beginErrorCondition(recognizer);
|
|
319
|
-
|
|
320
|
-
const token = recognizer.getCurrentToken();
|
|
321
|
-
token.$isSkipped = 'offending';
|
|
322
|
-
const expecting = this.getExpectedTokensForMessage( recognizer, token );
|
|
323
|
-
const offending = this.getTokenDisplay( token, recognizer );
|
|
324
|
-
// TODO: if non-reserved keyword will not been parsed as keyword, use Identifier for offending
|
|
325
|
-
// Hopefully not too ANTLR-specific, so extra message id is ok:
|
|
326
|
-
const err = recognizer.error( 'syntax-missing-token', token,
|
|
327
|
-
{ offending, expecting },
|
|
328
|
-
'Missing $(EXPECTING) before $(OFFENDING)' );
|
|
329
|
-
err.expectedTokens = expecting;
|
|
330
|
-
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig
|
|
331
|
-
recognizer.notifyErrorListeners( err.message, token, err );
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function reportIgnoredWith( recognizer, t ) {
|
|
335
|
-
const next = recognizer._interp.atn.states[recognizer.state].transitions[0].target;
|
|
336
|
-
recognizer.state = next.stateNumber; // previous match() does not set the state
|
|
337
|
-
const expecting = this.getExpectedTokensForMessage( recognizer, t );
|
|
338
|
-
const m = recognizer.warning( 'syntax-unexpected-semicolon', t,
|
|
339
|
-
{ offending: "';'", expecting, keyword: 'with' },
|
|
340
|
-
// eslint-disable-next-line @stylistic/js/max-len
|
|
341
|
-
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
|
|
342
|
-
m.expectedTokens = expecting;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function consumeUntil( recognizer, set ) {
|
|
346
|
-
// TODO: add trace
|
|
347
|
-
if (SEMI == null)
|
|
348
|
-
SEMI = recognizer.literalNames.indexOf( "';'" );
|
|
349
|
-
if (RBRACE == null)
|
|
350
|
-
RBRACE = recognizer.literalNames.indexOf( "'}'" );
|
|
351
|
-
|
|
352
|
-
// let s=this.getTokenDisplay( recognizer.getCurrentToken(), recognizer );
|
|
353
|
-
if (SEMI < 1 || RBRACE < 1) {
|
|
354
|
-
this.consumeAndMarkUntil( recognizer, set );
|
|
355
|
-
}
|
|
356
|
-
else if (set.contains(SEMI)) { // do not check for RBRACE here!
|
|
357
|
-
this.consumeAndMarkUntil( recognizer, set );
|
|
358
|
-
// console.log('CONSUMED-ORIG:',s,this.getTokenDisplay( recognizer.getCurrentToken(),
|
|
359
|
-
// recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set ));
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
// DO NOT modify input param `set`, as the set might be cached in the ATN
|
|
363
|
-
const stop = new IntervalSet();
|
|
364
|
-
stop.addSet( set );
|
|
365
|
-
stop.removeOne( recognizer.constructor.Identifier );
|
|
366
|
-
stop.addOne( SEMI );
|
|
367
|
-
// I am not that sure whether to add RBRACE...
|
|
368
|
-
stop.addOne( RBRACE );
|
|
369
|
-
this.consumeAndMarkUntil( recognizer, stop );
|
|
370
|
-
const ttype = recognizer.getTokenStream().LA(1);
|
|
371
|
-
if (ttype === SEMI || ttype === RBRACE && !set.contains(RBRACE)) {
|
|
372
|
-
recognizer.consume();
|
|
373
|
-
this.reportMatch(recognizer); // we know current token is correct
|
|
374
|
-
}
|
|
375
|
-
// if matched '}', also try to match next ';' (also matches double ';')
|
|
376
|
-
if (recognizer.getTokenStream().LA(1) === SEMI) {
|
|
377
|
-
recognizer.consume();
|
|
378
|
-
this.reportMatch(recognizer); // we know current token is correct
|
|
379
|
-
}
|
|
380
|
-
// console.log('CONSUMED:',s,this.getTokenDisplay( recognizer.getCurrentToken(),
|
|
381
|
-
// recognizer ),recognizer.getCurrentToken().line);
|
|
382
|
-
// throw new CompilerAssertion('Sync')
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function consumeAndMarkUntil( recognizer, set, onlyInSameLine ) {
|
|
387
|
-
const stream = recognizer.getTokenStream();
|
|
388
|
-
let t = stream.LT(1);
|
|
389
|
-
const { line } = t;
|
|
390
|
-
while (t.type !== antlr4.Token.EOF && !set.contains( t.type )) {
|
|
391
|
-
if (onlyInSameLine && (t.line !== line || t.text === ';' || t.text === '}' ))
|
|
392
|
-
return false; // early exit
|
|
393
|
-
if (!t.$isSkipped)
|
|
394
|
-
t.$isSkipped = true;
|
|
395
|
-
recognizer.consume();
|
|
396
|
-
t = stream.LT(1);
|
|
397
|
-
}
|
|
398
|
-
return true;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// As the `match` function of the parser `recognizer` does not allow to check
|
|
402
|
-
// against a set of token types, the generated parser code checks against that
|
|
403
|
-
// set itself and calls this function if not successful.
|
|
404
|
-
// We now also allow keywords if the Identifier is expected.
|
|
405
|
-
// Called by match() and in generated parser in "else part" before consume()
|
|
406
|
-
// for ( TOKEN1 | TOKEN2 )
|
|
407
|
-
function recoverInline( recognizer ) {
|
|
408
|
-
const identType = recognizer.constructor.Identifier;
|
|
409
|
-
if (!identType || !recognizer.isExpectedToken( identType ))
|
|
410
|
-
return this._super.recoverInline.call( this, recognizer );
|
|
411
|
-
|
|
412
|
-
const token = recognizer.getCurrentToken();
|
|
413
|
-
// TODO: do not delete `)`, `}`,
|
|
414
|
-
|
|
415
|
-
// TODO: overwrite singleTokenDeletion do not delete parens etc for identifier
|
|
416
|
-
// or non-reserved keywords
|
|
417
|
-
if (!keywordRegexp.test( token.text ))
|
|
418
|
-
return this._super.recoverInline.call( this, recognizer );
|
|
419
|
-
|
|
420
|
-
// TODO: attach `Identifier` as valid name to message?
|
|
421
|
-
recognizer.message( 'syntax-unexpected-reserved-word', token,
|
|
422
|
-
{ code: token.text, delimited: token.text } );
|
|
423
|
-
this.reportMatch(recognizer); // we know current token is correct
|
|
424
|
-
recognizer.consume();
|
|
425
|
-
return token;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Conjure up a missing token during error recovery in parser `recognizer`. If
|
|
429
|
-
// an identifier is expected, create one.
|
|
430
|
-
// Think about: we might want to prefer one of '}]);,'.
|
|
431
|
-
function getMissingSymbol( recognizer, expectedTokenType ) {
|
|
432
|
-
expectedTokenType ??= this.getExpectedTokens(recognizer).first(); // get any element
|
|
433
|
-
const current = recognizer.getCurrentToken();
|
|
434
|
-
return recognizer.getTokenFactory().create(
|
|
435
|
-
current.source, // do s/th special if EOF like in DefaultErrorStrategy ?
|
|
436
|
-
expectedTokenType, '', antlr4.Token.DEFAULT_CHANNEL, // empty string as token text
|
|
437
|
-
-1, -1, current.line, current.column
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function intervalSetToArray( recognizer, expected, excludesForNextToken ) {
|
|
442
|
-
// similar to `IntervalSet#toTokenString`
|
|
443
|
-
let names = [];
|
|
444
|
-
const pc = recognizer.constructor;
|
|
445
|
-
for (const v of expected.intervals) {
|
|
446
|
-
for (let j = v.start; j < v.stop; j++) {
|
|
447
|
-
// a generic keyword as such does not appear in messages, only its replacements,
|
|
448
|
-
// which are function name and argument position dependent:
|
|
449
|
-
if (j === pc.GenericExpr) {
|
|
450
|
-
names.push( ...recognizer.$genericKeywords.expr );
|
|
451
|
-
}
|
|
452
|
-
else if (j === pc.GenericSeparator) {
|
|
453
|
-
names.push( ...recognizer.$genericKeywords.separator );
|
|
454
|
-
}
|
|
455
|
-
else if (j === pc.GenericIntro) {
|
|
456
|
-
names.push( ...recognizer.$genericKeywords.introMsg );
|
|
457
|
-
}
|
|
458
|
-
else if (j === pc.SemicolonTopLevel) {
|
|
459
|
-
// We only insert a semikolon (i.e. make it optional) after a closing brace.
|
|
460
|
-
// If the previous token is not `}`, don't propose these keywords, as ';' is required.
|
|
461
|
-
if (recognizer._input.LA(-1) === recognizer._input.BRACE_CLOSE) {
|
|
462
|
-
const name = recognizer.topLevelKeywords.map(i => expected
|
|
463
|
-
.elementName(recognizer.literalNames, recognizer.symbolicNames, i));
|
|
464
|
-
names.push(...name);
|
|
465
|
-
if (recognizer._ctx.outer?.kind !== 'source') {
|
|
466
|
-
if (names.includes('<EOF>'))
|
|
467
|
-
names.splice(names.indexOf('<EOF>'), 1);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
// other expected tokens usually appear in messages, except the helper tokens
|
|
472
|
-
// which are used to solve ambiguities via the parser method setLocalToken():
|
|
473
|
-
else if (j !== pc.HelperToken1 && j !== pc.HelperToken2) {
|
|
474
|
-
names.push( expected.elementName(recognizer.literalNames, recognizer.symbolicNames, j ) );
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
// The parser method excludeExpected() additionally removes some tokens from the message:
|
|
479
|
-
if (recognizer.$adaptExpectedToken &&
|
|
480
|
-
recognizer.$nextTokensToken === recognizer.$adaptExpectedToken) {
|
|
481
|
-
const excludes = (excludesForNextToken && Array.isArray(recognizer.$adaptExpectedExcludes[0]))
|
|
482
|
-
? recognizer.$adaptExpectedExcludes[0]
|
|
483
|
-
: recognizer.$adaptExpectedExcludes;
|
|
484
|
-
names = names.filter( n => !excludes.includes( n ) );
|
|
485
|
-
}
|
|
486
|
-
else if (names.includes("';'")) {
|
|
487
|
-
names = names.filter( n => n !== "'}'" );
|
|
488
|
-
}
|
|
489
|
-
else if (names.includes("'?'")) {
|
|
490
|
-
names = names.filter( n => n !== "'?'" );
|
|
491
|
-
}
|
|
492
|
-
names.sort( (a, b) => (tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1) );
|
|
493
|
-
return names;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Used for sorting in messages
|
|
497
|
-
const token1sort = {
|
|
498
|
-
// 0: Identifier, Number, ...
|
|
499
|
-
// 1: separators:
|
|
500
|
-
',': 1,
|
|
501
|
-
'.': 1,
|
|
502
|
-
':': 1,
|
|
503
|
-
';': 1,
|
|
504
|
-
// 2: parentheses:
|
|
505
|
-
'(': 2,
|
|
506
|
-
')': 2,
|
|
507
|
-
'[': 2,
|
|
508
|
-
']': 2,
|
|
509
|
-
'{': 2,
|
|
510
|
-
'}': 2,
|
|
511
|
-
// 3: special:
|
|
512
|
-
'!': 3,
|
|
513
|
-
'#': 3,
|
|
514
|
-
$: 3,
|
|
515
|
-
'?': 3,
|
|
516
|
-
'@': 3,
|
|
517
|
-
// 4: operators:
|
|
518
|
-
'*': 4,
|
|
519
|
-
'+': 4,
|
|
520
|
-
'-': 4,
|
|
521
|
-
'/': 4,
|
|
522
|
-
'<': 4,
|
|
523
|
-
'=': 4,
|
|
524
|
-
'>': 4,
|
|
525
|
-
'|': 4,
|
|
526
|
-
// 8: KEYWORD
|
|
527
|
-
// 9: <EOF>
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
function tokenPrecedence( name ) {
|
|
531
|
-
if (name.length < 2 || name === '<EOF>')
|
|
532
|
-
return `9${ name }`;
|
|
533
|
-
const prec = token1sort[name.charAt(1)];
|
|
534
|
-
if (prec)
|
|
535
|
-
return `${ prec }${ name }`;
|
|
536
|
-
return (name.charAt(1) < 'a' ? '8' : '0') + name;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function getTokenDisplay( token, recognizer ) {
|
|
540
|
-
if (!token)
|
|
541
|
-
return '<EOF>';
|
|
542
|
-
const t = token.type;
|
|
543
|
-
if (t === antlr4.Token.EOF || t === antlr4.Token.EPSILON ) {
|
|
544
|
-
return '<EOF>';
|
|
545
|
-
}
|
|
546
|
-
else if (t === recognizer.constructor.DOTbeforeBRACE) {
|
|
547
|
-
if (recognizer.getTokenStream().LT(2).text === '{')
|
|
548
|
-
return "'.{'";
|
|
549
|
-
return "'.*'";
|
|
550
|
-
}
|
|
551
|
-
return recognizer.literalNames[t] || recognizer.symbolicNames[t];
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Return an IntervalSet of token types which the parser had expected. Do not
|
|
555
|
-
// include non-reserved keywords if not mentioned explicitly (i.e. other than
|
|
556
|
-
// from rule `ident`).
|
|
557
|
-
//
|
|
558
|
-
// We actually define something like a corrected version of function
|
|
559
|
-
// `LL1Analyzer.prototype.getDecisionLookahead`. We cannot just redefine
|
|
560
|
-
// `getExpectedTokens`, because that function is also used to decide whether
|
|
561
|
-
// to consume in `DefaultErrorStrategy#singleTokenDeletion`.
|
|
562
|
-
function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
|
|
563
|
-
const { atn } = recognizer._interp;
|
|
564
|
-
if (recognizer.state < 0)
|
|
565
|
-
return [];
|
|
566
|
-
if (recognizer.state >= atn.states.length) {
|
|
567
|
-
throw new CompilerAssertion( `Invalid state number ${ recognizer.state } for ${
|
|
568
|
-
this.getTokenErrorDisplay( offendingToken ) }`);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
const identType = recognizer.constructor.Identifier;
|
|
572
|
-
const hideAltsType = recognizer.constructor.HideAlternatives;
|
|
573
|
-
const beforeUnreserved = recognizer.constructor.Number;
|
|
574
|
-
if (!identType || !beforeUnreserved || beforeUnreserved + 2 > identType)
|
|
575
|
-
return intervalSetToArray( recognizer, this._super.getExpectedTokens.call( this, recognizer ) );
|
|
576
|
-
|
|
577
|
-
const ll1 = new Antlr4LL1Analyzer(atn);
|
|
578
|
-
const expected = new IntervalSet();
|
|
579
|
-
const origAddInterval = expected.addInterval;
|
|
580
|
-
const origAddSet = expected.addSet;
|
|
581
|
-
expected.addInterval = addInterval;
|
|
582
|
-
expected.addSet = addSet;
|
|
583
|
-
const lookBusy = new antlr4.Utils.Set();
|
|
584
|
-
const calledRules = new antlr4.Utils.BitSet();
|
|
585
|
-
|
|
586
|
-
if (deadEnds) {
|
|
587
|
-
// "No viable alternative" by adaptivePredict() not on first token
|
|
588
|
-
for (const trans of deadEnds) {
|
|
589
|
-
ll1._LOOK( trans.state, null, predictionContext( atn, recognizer._ctx ),
|
|
590
|
-
expected, lookBusy, calledRules, true, true );
|
|
591
|
-
}
|
|
592
|
-
return intervalSetToArray( recognizer, expected, true );
|
|
593
|
-
}
|
|
594
|
-
else if (offendingToken && recognizer.$nextTokensContext &&
|
|
595
|
-
offendingToken === recognizer.$nextTokensToken) {
|
|
596
|
-
// Before exiting a rule, we had a state (via sync()) with a bigger
|
|
597
|
-
// "expecting set" for the same token
|
|
598
|
-
ll1._LOOK( atn.states[recognizer.$nextTokensState], null,
|
|
599
|
-
predictionContext( atn, recognizer.$nextTokensContext ),
|
|
600
|
-
expected, lookBusy, calledRules, true, true );
|
|
601
|
-
}
|
|
602
|
-
else {
|
|
603
|
-
// Use current state to compute "expecting"
|
|
604
|
-
ll1._LOOK( atn.states[recognizer.state], null,
|
|
605
|
-
predictionContext( atn, recognizer._ctx ),
|
|
606
|
-
expected, lookBusy, calledRules, true, true );
|
|
607
|
-
}
|
|
608
|
-
// console.log(state, recognizer.$nextTokensState,
|
|
609
|
-
// expected.toString(recognizer.literalNames, recognizer.symbolicNames));
|
|
610
|
-
return intervalSetToArray( recognizer, expected );
|
|
611
|
-
|
|
612
|
-
function addSet( other ) {
|
|
613
|
-
if (!other.contains( hideAltsType ))
|
|
614
|
-
origAddSet.call( this, other );
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Add an interval `v` to the IntervalSet `this`. If `v` contains the token
|
|
618
|
-
// type `Identifier`, do not add non-reserved keywords in `v`.
|
|
619
|
-
function addInterval( v ) {
|
|
620
|
-
if (v.stop <= identType) {
|
|
621
|
-
origAddInterval.call(this, v);
|
|
622
|
-
}
|
|
623
|
-
else if (v.start >= identType) {
|
|
624
|
-
if (v.stop === identType + 1 || !recognizer.tokenRewrite) {
|
|
625
|
-
origAddInterval.call(this, v);
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
for (let j = v.start; j < v.stop; j++)
|
|
629
|
-
addRange( this, recognizer.tokenRewrite[j - identType] || j );
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
else {
|
|
633
|
-
if (v.start <= beforeUnreserved)
|
|
634
|
-
addRange( this, v.start, beforeUnreserved + 1 );
|
|
635
|
-
addRange( this, identType );
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function addRange( interval, start, stop ) {
|
|
640
|
-
origAddInterval.call( interval, new Interval( start, stop || start + 1 ) );
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
module.exports = {
|
|
645
|
-
KeywordErrorStrategy,
|
|
646
|
-
};
|