@sap/cds-compiler 6.3.4 → 6.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/LICENSE +32 -0
- package/README.md +14 -2
- package/bin/cdsse.js +0 -3
- package/doc/CHANGELOG_BETA.md +1 -1
- package/doc/CHANGELOG_DEPRECATED.md +1 -1
- package/lib/base/message-registry.js +7 -0
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/checks.js +37 -26
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +39 -50
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +29 -6
- package/lib/compiler/resolve.js +13 -3
- package/lib/compiler/shared.js +31 -25
- package/lib/compiler/tweak-assocs.js +86 -28
- package/lib/compiler/xpr-rewrite.js +70 -38
- package/lib/edm/annotations/edmJson.js +206 -37
- package/lib/edm/csn2edm.js +13 -0
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +106 -72
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1500 -1509
- package/lib/json/to-csn.js +8 -5
- package/lib/language/genericAntlrParser.js +0 -0
- package/lib/main.js +19 -16
- package/lib/model/csnRefs.js +589 -521
- package/lib/model/csnUtils.js +26 -7
- package/lib/model/enrichCsn.js +1 -0
- package/lib/parsers/AstBuildingParser.js +72 -27
- package/lib/render/toCdl.js +2 -1
- package/lib/render/toHdbcds.js +6 -3
- package/lib/render/toSql.js +5 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +4 -1
- package/lib/transform/db/cdsPersistence.js +17 -18
- package/lib/transform/db/expansion.js +179 -3
- package/lib/transform/db/flattening.js +16 -5
- package/lib/transform/db/rewriteCalculatedElements.js +79 -283
- package/lib/transform/effective/main.js +8 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/forRelationalDB.js +21 -80
- package/lib/transform/localized.js +65 -110
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
- package/lib/transform/transformUtils.js +23 -21
- package/lib/transform/translateAssocsToJoins.js +7 -5
- package/lib/transform/tupleExpansion.js +16 -3
- package/package.json +1 -1
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- package/lib/transform/parseExpr.js +0 -415
package/lib/gen/BaseParser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Base class for generated parser, for redepage v0.3.
|
|
1
|
+
// Base class for generated parser, for redepage v0.3.1
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -18,9 +18,10 @@ class BaseParser {
|
|
|
18
18
|
|
|
19
19
|
tokens = undefined;
|
|
20
20
|
tokenIdx = 0;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
conditionTokenIdx = -1;
|
|
22
|
+
errorTokenIdx = -1; // token index where error is detected
|
|
23
|
+
recoverTokenIdx = -1; // token index where error recovery resumes
|
|
24
|
+
reuseErrorTokenIdx = null; // tmp special for error recovery “token-reuse”
|
|
24
25
|
fixKeywordTokenIdx = -1;
|
|
25
26
|
conditionStackLength = -1;
|
|
26
27
|
nextTokenAsId = false;
|
|
@@ -46,6 +47,17 @@ class BaseParser {
|
|
|
46
47
|
return this;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
_runTransparently( callback ) {
|
|
51
|
+
const { tokenIdx } = this;
|
|
52
|
+
const saved = this._saveForWalk();
|
|
53
|
+
const { length } = this.stack;
|
|
54
|
+
const r = callback();
|
|
55
|
+
this.stack.length = length;
|
|
56
|
+
Object.assign( this, saved );
|
|
57
|
+
this.tokenIdx = tokenIdx;
|
|
58
|
+
return r;
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
_saveForWalk() {
|
|
50
62
|
return {
|
|
51
63
|
s: this.s,
|
|
@@ -108,8 +120,9 @@ class BaseParser {
|
|
|
108
120
|
|
|
109
121
|
e() { // error: report and recover
|
|
110
122
|
const la = this.la();
|
|
111
|
-
this._trace( 'detect parsing error' );
|
|
112
|
-
if (this.errorTokenIdx === this.tokenIdx
|
|
123
|
+
this._trace( 'detect parsing error' ); // TODO: write reason (la, no tokens matched, …)
|
|
124
|
+
if (this.errorTokenIdx === this.tokenIdx &&
|
|
125
|
+
(this.reuseErrorTokenIdx == null && this.reuseErrorTokenIdx < this.tokenIdx))
|
|
113
126
|
throw Error( `Already reported error for ${ tokenFullName( la ) } at ${ la.location }`);
|
|
114
127
|
|
|
115
128
|
la.parsedAs = ''; // current token is erroneous
|
|
@@ -120,23 +133,13 @@ class BaseParser {
|
|
|
120
133
|
while (--length && this.tokenIdx === this.stack[length].tokenIdx)
|
|
121
134
|
this.stack[length].followState = null;
|
|
122
135
|
if (++length === this.stack.length) // last good state in current rule
|
|
123
|
-
return this._reportAndRecover();
|
|
136
|
+
return this._reportAndRecover(); // otherwise report+recovery after unwind in exit_()
|
|
124
137
|
|
|
125
138
|
this.stack[length].followState = this.errorState;
|
|
126
139
|
this.s = null;
|
|
127
140
|
return false;
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
_reportAndRecover() {
|
|
131
|
-
this.s = this.errorState;
|
|
132
|
-
this.reportUnexpectedToken_();
|
|
133
|
-
const syncSet = this._calculateTokenSet( 'Y' );
|
|
134
|
-
const recoverDepth = this._findSyncToken( syncSet );
|
|
135
|
-
this._trace( 'recover from error' );
|
|
136
|
-
this._recoverFromError( recoverDepth );
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
143
|
// instead of e() in default if lk() had been used and 'Id' is in a non-default case
|
|
141
144
|
ei() { // error (after trying to test again as identifier)
|
|
142
145
|
if (!this.la().keyword) // lk() had directly returned the type
|
|
@@ -150,6 +153,7 @@ class BaseParser {
|
|
|
150
153
|
// go to end of the rule, in tracing parser: g(0)
|
|
151
154
|
gr( follow ) { // intersection follow set for fast exit
|
|
152
155
|
if (this.stack[this.stack.length - 1].tokenIdx === this.tokenIdx)
|
|
156
|
+
//this.tokenIdx >= this.errorTokenIdx) not necessary
|
|
153
157
|
return this.e(); // match at least one token
|
|
154
158
|
this.s = 0;
|
|
155
159
|
// TODO: also have recursive flag in stack: was rule was called recursively?
|
|
@@ -249,9 +253,19 @@ class BaseParser {
|
|
|
249
253
|
const la = this.tokens[this.tokenIdx];
|
|
250
254
|
// TODO: consider this like a failed condition? Will be relevant if we try
|
|
251
255
|
// different error recovery possibilities.
|
|
252
|
-
if (this.keywords[la.keyword])
|
|
256
|
+
if (this.keywords[la.keyword]) {
|
|
257
|
+
this._tracePush( [ 'R' ] );
|
|
258
|
+
if (this._runTransparently( () => {
|
|
259
|
+
++this.tokenIdx;
|
|
260
|
+
this.s = state;
|
|
261
|
+
const { type, keyword } = this.la();
|
|
262
|
+
return !(this._pred_next( type, keyword, 'R' ) ??
|
|
263
|
+
this._matchesInFollow( type, keyword, 'R' ));
|
|
264
|
+
} ))
|
|
265
|
+
return this.e();
|
|
253
266
|
this.reportReservedWord_();
|
|
254
|
-
|
|
267
|
+
// with error recovery: use that (consider this having a good score)
|
|
268
|
+
}
|
|
255
269
|
return this.c( state, ident )
|
|
256
270
|
}
|
|
257
271
|
|
|
@@ -280,6 +294,10 @@ class BaseParser {
|
|
|
280
294
|
++this.tokenIdx;
|
|
281
295
|
}
|
|
282
296
|
|
|
297
|
+
reuseToken_() {
|
|
298
|
+
--this.tokenIdx;
|
|
299
|
+
}
|
|
300
|
+
|
|
283
301
|
// condition and precedence handling ------------------------------------------
|
|
284
302
|
|
|
285
303
|
// state must match the goto-state of the default (there must be no default
|
|
@@ -340,13 +358,14 @@ class BaseParser {
|
|
|
340
358
|
--this.trace.at(-1).length; // do not show guard name in trace
|
|
341
359
|
if (!keyword || this.keywords[keyword] == null)
|
|
342
360
|
return false; // ok
|
|
343
|
-
const r = this._matchesInFollow( 'Id', keyword, '
|
|
361
|
+
const r = this._matchesInFollow( 'Id', keyword, 'F' );
|
|
344
362
|
--this.trace.at(-1).length; // this.gc() also traces result
|
|
345
363
|
return r;
|
|
346
364
|
}
|
|
347
365
|
if (!keyword || this.keywords[keyword] == null)
|
|
348
366
|
return false; // ok
|
|
349
|
-
return this._matchesInFollow( 'Id', keyword, '
|
|
367
|
+
return this._matchesInFollow( 'Id', keyword, 'F' );
|
|
368
|
+
// TODO: still extra tests for 'F' in _pred_next()?
|
|
350
369
|
}
|
|
351
370
|
|
|
352
371
|
// rule start, end and call: --------------------------------------------------
|
|
@@ -370,32 +389,28 @@ class BaseParser {
|
|
|
370
389
|
throw Error( `this.s === ${ this.s } // illegally set by action, or runtime/generator bug` );
|
|
371
390
|
this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
|
|
372
391
|
const caller = this.stack.pop();
|
|
373
|
-
const immediately = this.tokenIdx === caller.tokenIdx;
|
|
392
|
+
const immediately = this.tokenIdx === caller.tokenIdx && this.tokenIdx >= this.errorTokenIdx;
|
|
374
393
|
if (this.constructor.tracingParser) {
|
|
375
394
|
const post = this.s == null &&
|
|
376
395
|
(immediately
|
|
377
|
-
? ' immediately'
|
|
396
|
+
? ' immediately' // no token matched (further unwind or to last good state)
|
|
378
397
|
: caller.followState == null
|
|
379
|
-
? ' unsuccessfully'
|
|
380
|
-
: ' prematurely');
|
|
398
|
+
? ' unsuccessfully' // further error rewind
|
|
399
|
+
: ' prematurely'); // continue at sync state after exit
|
|
381
400
|
const text = immediately ? '⚠ exit rule' : '⏎ exit rule';
|
|
382
401
|
this.s = caller.followState; // for trace
|
|
383
402
|
this._trace( [ text, caller.ruleState, post, 'back to' ] )
|
|
384
403
|
if (immediately && this.stack.at(-1)?.followState != null)
|
|
385
404
|
this.trace = [ this.errorState ]; // show last good state in trace
|
|
386
405
|
}
|
|
406
|
+
this.prec_ = caller.prec;
|
|
387
407
|
this.s = caller.followState;
|
|
388
|
-
if (immediately)
|
|
408
|
+
if (immediately) // this.gr() has already called this.e() if no token is matched
|
|
389
409
|
return this.s != null && this._reportAndRecover();
|
|
390
410
|
|
|
391
|
-
this.
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
else if (this.s == null)
|
|
395
|
-
return true; // attached actions are executed even with "unsuccessful exit"
|
|
396
|
-
|
|
397
|
-
this.errorState = this.s;
|
|
398
|
-
return true;
|
|
411
|
+
if (this.s != null)
|
|
412
|
+
this.errorState = this.s; // last good state is now after rule call
|
|
413
|
+
return true; // attached actions are executed even with "unsuccessful exit"
|
|
399
414
|
}
|
|
400
415
|
|
|
401
416
|
// predicate used before rule call (and called by `ckP` and `gP`) on keyword
|
|
@@ -438,8 +453,11 @@ class BaseParser {
|
|
|
438
453
|
// TODO: also not with lean condition
|
|
439
454
|
let match1 = this._pred_next( 'Id', lk1, 'P' ); // first step of `K`/`I` prediction
|
|
440
455
|
if (!match1) {
|
|
441
|
-
if (lk1
|
|
442
|
-
|
|
456
|
+
if (lk1) { // assert for correct code generation
|
|
457
|
+
// Remark: this._pred_next() returns false also if rule has not matched any token
|
|
458
|
+
const { location } = this.la();
|
|
459
|
+
throw Error( `Cannot match first prediction token at ${ location.line }:${ location.col } in rule at state ${ saved.s }` );
|
|
460
|
+
}
|
|
443
461
|
if (match1 == null) { // TODO: just return true, rule exit prediction will do it
|
|
444
462
|
this._traceSubPush( 0 ); // TODO: make _pred_next push this
|
|
445
463
|
match1 = this._matchesInFollow( 'Id', lk1, 'I' );
|
|
@@ -513,25 +531,25 @@ class BaseParser {
|
|
|
513
531
|
case 'c': case 'ck': case 'ckA': // TODO: re-check ckA
|
|
514
532
|
return true;
|
|
515
533
|
case 'ciA': // TODO: fixKeywordTokenIdx ?
|
|
516
|
-
return mode !== '
|
|
534
|
+
return mode !== 'F';
|
|
517
535
|
// in the R prediction for optional `Id<weak>` at rule end, only
|
|
518
536
|
// alternative keyword matches are preferred, not identifier matches
|
|
519
537
|
// TODO: delete this prediction
|
|
520
538
|
case 'ci':
|
|
521
539
|
if (!keyword ||
|
|
522
540
|
!this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx)
|
|
523
|
-
return mode !== '
|
|
541
|
+
return mode !== 'F';
|
|
524
542
|
cmd = this.table[state]['']; // is currently always 'g' or 'e'
|
|
525
543
|
this.s = cmd[1];
|
|
526
544
|
break;
|
|
527
545
|
case 'm':
|
|
528
546
|
return type === cmd[2];
|
|
529
547
|
case 'mi':
|
|
530
|
-
return type === 'Id' && mode !== '
|
|
548
|
+
return type === 'Id' && mode !== 'F' &&
|
|
531
549
|
(!keyword ||
|
|
532
550
|
!this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx);
|
|
533
551
|
case 'miA':
|
|
534
|
-
return type === 'Id' && mode !== '
|
|
552
|
+
return type === 'Id' && mode !== 'F';
|
|
535
553
|
case 'mk':
|
|
536
554
|
return keyword === cmd[2];
|
|
537
555
|
case 'g': case 'e':
|
|
@@ -573,7 +591,7 @@ class BaseParser {
|
|
|
573
591
|
// prediction, the tool can normally omit the prediction (and output a
|
|
574
592
|
// message), no so with `ruleStartingWithUnreserved`. We will rather look
|
|
575
593
|
// behind the current rule _after_ having decided that the token is to be
|
|
576
|
-
|
|
594
|
+
// matched as identifier.
|
|
577
595
|
return (hasMatchedToken ?? this.tokenIdx > this.stack.at( -1 ).tokenIdx)
|
|
578
596
|
&& null; // let caller decide how to interpret this
|
|
579
597
|
}
|
|
@@ -609,7 +627,7 @@ class BaseParser {
|
|
|
609
627
|
this.s = this.stack[depth].followState;
|
|
610
628
|
// TODO: this.prec_ ?
|
|
611
629
|
match = this._pred_next( type, keyword, mode );
|
|
612
|
-
this._traceSubPush( match == null ? 0 : match === (mode !== '
|
|
630
|
+
this._traceSubPush( match == null ? 0 : match === (mode !== 'F') );
|
|
613
631
|
// successfully matching a keyword in giR() means unsuccessful match as
|
|
614
632
|
// reserved identifer
|
|
615
633
|
// TODO: this.stack ?
|
|
@@ -667,13 +685,13 @@ class BaseParser {
|
|
|
667
685
|
else { // or always when calculating the sync-set
|
|
668
686
|
let val = this.stack.length + 1;
|
|
669
687
|
while (this.stack.length) {
|
|
670
|
-
|
|
671
|
-
|
|
688
|
+
this._tokenSetInRule( set, val ); // TODO: use if Y-M unification
|
|
689
|
+
val = this.stack.length;
|
|
672
690
|
// TODO: use new _tracePush if `val` changes, probably also use Y‹val›(…)
|
|
673
691
|
this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
|
|
674
692
|
this.s = this.stack.pop().followState;
|
|
675
693
|
}
|
|
676
|
-
set.EOF ??= 0;
|
|
694
|
+
set.EOF ??= 0; // TODO: really necessary, see also _findSyncToken()
|
|
677
695
|
}
|
|
678
696
|
this.stack = savedStack;
|
|
679
697
|
this.s = savedState; // should be the errorState anyway - TODO: confirm
|
|
@@ -706,7 +724,7 @@ class BaseParser {
|
|
|
706
724
|
collectKeywordsAndIdOnly = true;
|
|
707
725
|
cmd = dict.Id;
|
|
708
726
|
}
|
|
709
|
-
else { // Id branch never leads to rule exit:
|
|
727
|
+
else { // Id branch never leads to rule exit (really?):
|
|
710
728
|
this._tracePush( [ '[' ] );
|
|
711
729
|
this._tokenSetInRule( expecting, val, dict.Id, true );
|
|
712
730
|
this._tracePush( [ ']' ] );
|
|
@@ -722,7 +740,7 @@ class BaseParser {
|
|
|
722
740
|
this.addTokenToSet_( expecting, 'Id', val, false );
|
|
723
741
|
// TODO: should we do s/th special, such that a reserved word is a sync
|
|
724
742
|
// token for Id<greedy>? Probably not, see also comment in
|
|
725
|
-
|
|
743
|
+
// _findSyncToken()
|
|
726
744
|
break loop;
|
|
727
745
|
case 'g': case 'gi': case 'e':
|
|
728
746
|
break;
|
|
@@ -781,62 +799,77 @@ class BaseParser {
|
|
|
781
799
|
return expecting;
|
|
782
800
|
}
|
|
783
801
|
|
|
784
|
-
_findSyncToken( syncSet ) {
|
|
802
|
+
_findSyncToken( syncSet ) { // only called from _reportAndRecover()
|
|
785
803
|
const rewindDepth = this.stack.length
|
|
786
|
-
this.recoverTokenIdx = this.tokenIdx;
|
|
804
|
+
this.recoverTokenIdx = this.tokenIdx; // TODO: make it part of the return value?
|
|
787
805
|
while (this.recoverTokenIdx < this.tokens.length) {
|
|
788
806
|
const { keyword, type } = this.tokens[this.recoverTokenIdx];
|
|
789
807
|
let recoverDepth = keyword ? syncSet[keyword] : null;
|
|
790
808
|
if (recoverDepth != null)
|
|
791
809
|
return recoverDepth;
|
|
792
810
|
recoverDepth = syncSet[type];
|
|
793
|
-
// sync to Id only if in expected set of last good state or if after ';'
|
|
811
|
+
// sync to Id only if in intra-rule expected set of last good state or if after ';'/`}`
|
|
794
812
|
if (recoverDepth != null &&
|
|
795
813
|
(type !== 'Id' || (!keyword || !this.keywords[keyword]) &&
|
|
796
|
-
// reserved words do not match Id in expected-set
|
|
797
|
-
(recoverDepth > rewindDepth ||
|
|
814
|
+
// reserved words do not match Id in expected-set → as method
|
|
815
|
+
(recoverDepth > rewindDepth ||
|
|
816
|
+
[ ';', '}' ].includes( this.tokens[this.recoverTokenIdx - 1].type ))))
|
|
798
817
|
// if (recoverDepth != null &&
|
|
799
818
|
// (this.recoverTokenIdx > this.tokenIdx ||
|
|
800
819
|
return recoverDepth;
|
|
801
820
|
++this.recoverTokenIdx;
|
|
802
821
|
}
|
|
803
|
-
throw Error( 'EOF must be last in `tokens`' );
|
|
822
|
+
throw Error( 'EOF must be last in `tokens`' ); // TODO: really necessary?
|
|
804
823
|
}
|
|
805
824
|
|
|
806
|
-
|
|
825
|
+
_reportAndRecover() { // called from this.e() and this.exit_() after rewind
|
|
826
|
+
this.s = this.errorState;
|
|
827
|
+
const syncSet = this._calculateTokenSet( 'Y' );
|
|
828
|
+
// TODO: use (extended) syncSet also for “expecting” in messages
|
|
829
|
+
this._trace( 'investigate how to recover' );
|
|
830
|
+
this.recoverTokenIdx = this.tokenIdx;;
|
|
831
|
+
const prev = this.lb();
|
|
832
|
+
const reuseRecoverDepth = this.reuseErrorTokenIdx != null && // only if specially switched on
|
|
833
|
+
prev?.keyword && prev.parsedAs !== 'keyword' &&
|
|
834
|
+
this.reuseErrorTokenIdx < this.tokenIdx && syncSet[prev.keyword];
|
|
835
|
+
// TODO: this reuse-token will only properly work if we can check that the token at
|
|
836
|
+
// this.la() will definitely match after having re-used this.lb()
|
|
837
|
+
// → if we have done that, we can again remove this.reuseErrorTokenIdx
|
|
838
|
+
this.reportUnexpectedToken_( reuseRecoverDepth ? 'reuse' : null );
|
|
839
|
+
|
|
840
|
+
if (this.reuseErrorTokenIdx != null)
|
|
841
|
+
this.reuseErrorTokenIdx = (reuseRecoverDepth) ? this.tokenIdx : -1;
|
|
842
|
+
this.fixKeywordTokenIdx = (reuseRecoverDepth) ? --this.recoverTokenIdx : -1;
|
|
843
|
+
|
|
844
|
+
const recoverDepth = reuseRecoverDepth || this._findSyncToken( syncSet );
|
|
807
845
|
this.s = null;
|
|
808
846
|
let depth = this.stack.length;
|
|
809
847
|
if (recoverDepth > depth) { // no rewind, no rule exit
|
|
810
848
|
this.trace = [ this.errorState ]; // show last good state in trace
|
|
811
849
|
this.s = this.errorState;
|
|
812
|
-
if (this.s)
|
|
813
|
-
this._skipErrorTokens();
|
|
814
850
|
}
|
|
815
851
|
while (depth > recoverDepth)
|
|
816
852
|
this.stack[--depth].followState = null;
|
|
817
853
|
// TODO: when the error is due to failed rule exit prediction, try to keep
|
|
818
854
|
// existing followState (if that reaches RuleEnd_)
|
|
819
855
|
// Continue parsing: ignore next predicate (TODO: except some specified ones?)
|
|
820
|
-
this.conditionTokenIdx = this.tokenIdx;
|
|
821
|
-
this.conditionStackLength = null;
|
|
822
|
-
this.fixKeywordTokenIdx = -1; // was set when collecting expecting-set
|
|
823
856
|
|
|
824
857
|
// TODO: re-check for rule calls which are at the optional rule end:
|
|
825
858
|
// x: 'x not'; b: 'b'? x {console.log('x→b')} 'b'?; a: b {console.log('b→a')} 'a'
|
|
826
859
|
// with start rule `a` and input `x a`: output should be x→b + b→a
|
|
827
860
|
// with start rule `a` and input `b a`: output should be b→a
|
|
828
|
-
//
|
|
829
|
-
// → the rule is: if a rule can continue at the specified state and has
|
|
830
|
-
// matched at least one token, then its action is executed, otherwise not
|
|
831
|
-
}
|
|
832
861
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
this._trace( `skip ${ this.recoverTokenIdx - this.tokenIdx } tokens to recover from error`,
|
|
862
|
+
if (this.constructor.tracingParser) {
|
|
863
|
+
this._trace( `skipped ${ this.recoverTokenIdx - this.tokenIdx } tokens to recover from error`,
|
|
836
864
|
this.tokens[this.recoverTokenIdx] );
|
|
837
|
-
|
|
838
|
-
|
|
865
|
+
}
|
|
866
|
+
if (this.tokenIdx > this.recoverTokenIdx)
|
|
867
|
+
this.reuseToken_();
|
|
868
|
+
else while (this.tokenIdx < this.recoverTokenIdx)
|
|
839
869
|
this.skipToken_();
|
|
870
|
+
this.conditionTokenIdx = this.tokenIdx;
|
|
871
|
+
this.conditionStackLength = null;
|
|
872
|
+
return false;
|
|
840
873
|
}
|
|
841
874
|
|
|
842
875
|
// small methods --------------------------------------------------------------
|
|
@@ -851,11 +884,12 @@ class BaseParser {
|
|
|
851
884
|
}
|
|
852
885
|
|
|
853
886
|
reportUnexpectedToken_( msg ) {
|
|
854
|
-
const token = this.la();
|
|
887
|
+
const token = (msg === 'reuse') ? this.lb() : this.la();
|
|
855
888
|
msg ??= `Unexpected token ${ tokenFullName( token, ': ' ) }`;
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
889
|
+
msg = (msg === 'reuse')
|
|
890
|
+
? `Missing input before keyword ${ tokenFullName( token, ': ' ) }`
|
|
891
|
+
: msg + ' - expecting: ' + this.expectingArray_().map( tokenName ).sort().join( ', ' );
|
|
892
|
+
this.reportError_( token.location, msg );
|
|
859
893
|
}
|
|
860
894
|
|
|
861
895
|
reportReservedWord_() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2f488eae5a0a809b97737edac6e2a42d
|