@sap/cds-compiler 5.5.2 → 5.7.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 +43 -1
- package/bin/cdsse.js +4 -0
- package/bin/cdsv2m.js +2 -1
- package/doc/Versioning.md +4 -4
- package/lib/api/options.js +1 -0
- package/lib/base/builtins.js +2 -2
- package/lib/base/dictionaries.js +1 -2
- package/lib/base/keywords.js +3 -1
- package/lib/base/lazyload.js +1 -1
- package/lib/base/message-registry.js +170 -143
- package/lib/base/messages.js +69 -59
- package/lib/base/model.js +3 -3
- package/lib/base/node-helpers.js +17 -16
- package/lib/base/optionProcessorHelper.js +13 -14
- package/lib/base/shuffle.js +4 -1
- package/lib/checks/structuredAnnoExpressions.js +1 -1
- package/lib/compiler/assert-consistency.js +1 -1
- package/lib/compiler/builtins.js +4 -1
- package/lib/compiler/extend.js +20 -5
- package/lib/compiler/resolve.js +45 -9
- package/lib/compiler/shared.js +1 -0
- package/lib/edm/annotations/edmJson.js +3 -3
- package/lib/edm/annotations/genericTranslation.js +5 -1
- package/lib/edm/annotations/vocabularyDefinitions.js +8 -2
- package/lib/edm/edmUtils.js +2 -1
- package/lib/gen/BaseParser.js +142 -103
- package/lib/gen/CdlParser.js +2240 -2201
- package/lib/gen/Dictionary.json +185 -6
- package/lib/json/from-csn.js +2 -0
- package/lib/json/to-csn.js +13 -4
- package/lib/language/docCommentParser.js +11 -5
- package/lib/language/errorStrategy.js +3 -3
- package/lib/language/genericAntlrParser.js +2 -0
- package/lib/model/csnUtils.js +6 -1
- package/lib/optionProcessor.js +5 -1
- package/lib/parsers/AstBuildingParser.js +200 -86
- package/lib/parsers/CdlGrammar.g4 +142 -86
- package/lib/parsers/Lexer.js +5 -3
- package/lib/parsers/index.js +1 -1
- package/lib/render/toCdl.js +6 -5
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +5 -3
- package/lib/render/utils/common.js +19 -6
- package/lib/render/utils/standardDatabaseFunctions.js +576 -0
- package/lib/transform/addTenantFields.js +2 -1
- package/lib/transform/db/expansion.js +3 -0
- package/lib/transform/db/flattening.js +18 -77
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/rewriteCalculatedElements.js +14 -19
- package/lib/transform/db/temporal.js +2 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
- package/lib/transform/odata/createForeignKeys.js +25 -9
- package/lib/transform/odata/flattening.js +11 -1
- package/lib/transform/transformUtils.js +20 -85
- package/package.json +2 -1
- package/bin/cds_update_annotations.js +0 -180
package/lib/gen/BaseParser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Base class for generated parser, for redepage v0.1
|
|
1
|
+
// Base class for generated parser, for redepage v0.2.1
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -32,6 +32,7 @@ class BaseParser {
|
|
|
32
32
|
dynamic_ = {}; // TODO: extra class
|
|
33
33
|
prec_ = null;
|
|
34
34
|
$hasErrors = null;
|
|
35
|
+
leanConditions = {};
|
|
35
36
|
// trace:
|
|
36
37
|
trace = [];
|
|
37
38
|
|
|
@@ -163,6 +164,7 @@ class BaseParser {
|
|
|
163
164
|
this._tracePush( [ 'E', true ] );
|
|
164
165
|
return true;
|
|
165
166
|
}
|
|
167
|
+
this._tracePush( [ 'E', 0 ] );
|
|
166
168
|
const match = this._matchesInFollow( type, keyword, 'E' );
|
|
167
169
|
// If the parser reaches this point with match = null, even the top-level rule
|
|
168
170
|
// does not have a required token (typically `EOF`) at the end → the parser
|
|
@@ -203,8 +205,10 @@ class BaseParser {
|
|
|
203
205
|
// cases
|
|
204
206
|
giR( state, follow ) { // go to state (after trying to test again as identifier)
|
|
205
207
|
const { keyword } = this.tokens[this.tokenIdx];
|
|
206
|
-
if (!keyword || this.keywords[keyword]
|
|
207
|
-
|
|
208
|
+
if (!keyword || this.keywords[keyword])
|
|
209
|
+
return this.g( state, follow );
|
|
210
|
+
this._tracePush( [ 'R', 0 ] );
|
|
211
|
+
if (this._matchesInFollow( 'Id', keyword, 'R' ))
|
|
208
212
|
return this.g( state, follow );
|
|
209
213
|
this.nextTokenAsId = true;
|
|
210
214
|
return false; // do not execute action after it
|
|
@@ -256,7 +260,11 @@ class BaseParser {
|
|
|
256
260
|
|
|
257
261
|
// instead of c() for identifiers, used both with l() and lk()
|
|
258
262
|
ci( state, ident = 'ident' ) { // consume identifier token
|
|
263
|
+
if (this.tokenIdx === this.fixKeywordTokenIdx)
|
|
264
|
+
return this.e();
|
|
259
265
|
const la = this.tokens[this.tokenIdx];
|
|
266
|
+
// TODO: consider this like a failed condition? Will be relevant if we try
|
|
267
|
+
// different error recovery possibilities.
|
|
260
268
|
if (this.keywords[la.keyword])
|
|
261
269
|
this.reportReservedWord_();
|
|
262
270
|
// with error recovery: use that (consider this having a good score)
|
|
@@ -308,7 +316,7 @@ class BaseParser {
|
|
|
308
316
|
}
|
|
309
317
|
// calling the condition might have side effects (precendence conditions have)
|
|
310
318
|
// → call tracing “name” before
|
|
311
|
-
const fail =
|
|
319
|
+
const fail = this[cond]( true, arg );
|
|
312
320
|
if (this.constructor.tracingParser)
|
|
313
321
|
this._traceSubPush( !fail );
|
|
314
322
|
// The default case must not have actions. If written in grammar with action,
|
|
@@ -328,6 +336,10 @@ class BaseParser {
|
|
|
328
336
|
this.fixKeywordTokenIdx = this.tokenIdx;
|
|
329
337
|
this.conditionTokenIdx = this.tokenIdx;
|
|
330
338
|
this.conditionStackLength = this.stack.length;
|
|
339
|
+
this.conditionName = cond;
|
|
340
|
+
// we also set the failure here, because the reporting might have a
|
|
341
|
+
// different context (consider immediate exit)
|
|
342
|
+
this.conditionFailure = fail;
|
|
331
343
|
}
|
|
332
344
|
return !fail || this.g( state ) && false;
|
|
333
345
|
}
|
|
@@ -385,95 +397,104 @@ class BaseParser {
|
|
|
385
397
|
return true;
|
|
386
398
|
}
|
|
387
399
|
|
|
388
|
-
// predicate used before rule call
|
|
389
|
-
|
|
400
|
+
// predicate used before rule call (and called by `ckP` and `gP`) on keyword
|
|
401
|
+
// branch if with weak LL(1) conflict, i.e. there is an 'Id' branch or the
|
|
402
|
+
// default branch has `Id` in its first-set (TODO: or rule end, and `Id` is in
|
|
403
|
+
// follow-union)
|
|
404
|
+
lP( first2 ) {
|
|
390
405
|
// nothing to check if not a non-reserved keyword:
|
|
391
406
|
const { keyword: lk1 } = this.tokens[this.tokenIdx];
|
|
392
|
-
if (!lk1 || this.keywords[lk1] !== 0)
|
|
407
|
+
if (!lk1 || this.keywords[lk1] !== 0 || this.fixKeywordTokenIdx === this.tokenIdx)
|
|
393
408
|
return true;
|
|
394
409
|
|
|
410
|
+
this._tracePush( [ 'K' ] );
|
|
395
411
|
const { type: lt2, keyword: lk2 } = this.tokens[this.tokenIdx + 1];
|
|
396
|
-
|
|
412
|
+
if (lt2 === 'IllegalToken')
|
|
413
|
+
return true
|
|
414
|
+
// Argument first2 is just a performance hint:
|
|
397
415
|
if (lk2 && first2?.[0] === 'Id' && !this.keywords[lk2] ||
|
|
398
416
|
first2?.includes( lk2 || lt2 )) {
|
|
399
|
-
this.
|
|
417
|
+
this._traceSubPush( true );
|
|
400
418
|
return true;
|
|
401
419
|
}
|
|
402
|
-
this._tracePush( [ 'K' ] );
|
|
403
420
|
// now check it dynamically:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
421
|
+
if (this._walkPred( this.table[this.s][lk1], lk1, lt2, lk2 ))
|
|
422
|
+
return true;
|
|
423
|
+
this._tracePush( [ 'I' ] );
|
|
424
|
+
const choice = this.table[this.s];
|
|
425
|
+
if (!this._walkPred( choice.Id || choice[''], null, lt2, lk2 ))
|
|
426
|
+
return true;
|
|
427
|
+
this.nextTokenAsId = true;
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
407
430
|
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
431
|
+
_walkPred( cmd, lk1, lt2, lk2 ) {
|
|
432
|
+
const saved = this._saveForWalk();
|
|
433
|
+
const { length } = this.stack;
|
|
434
|
+
if (typeof cmd[0] !== 'number') // don't skip push to state with rule call
|
|
435
|
+
this.s = cmd[1];
|
|
436
|
+
if (cmd[0] !== (lk1 ? 'ck' : 'ci')) { // make the std case fast
|
|
437
|
+
let match1 = this._pred_next( 'Id', lk1, 'P' ); // TODO: really P for I?
|
|
438
|
+
if (!match1) {
|
|
439
|
+
if (lk1 || match1 === false) // assert for correct code generation
|
|
440
|
+
throw Error( `Cannot match first prediction token in rule at state ${ saved.s }` );
|
|
441
|
+
if (match1 == null) {
|
|
442
|
+
this._traceSubPush( 0 ); // TODO: make _pred_next push this
|
|
443
|
+
match1 = this._matchesInFollow( 'Id', lk1, 'I' );
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
this._traceSubPush( false );
|
|
447
|
+
}
|
|
448
|
+
Object.assign( this, saved );
|
|
449
|
+
this.stack.length = length;
|
|
450
|
+
return !!match1;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
411
453
|
|
|
454
|
+
this._traceSubPush( '' ); // between the two tokens
|
|
412
455
|
++this.tokenIdx; // for user lookahead fns and conditions
|
|
413
|
-
|
|
456
|
+
let match2 = this._pred_next( lt2, lk2, (lk1 ? 'K' : 'I') );
|
|
457
|
+
if (match2 == null) {
|
|
458
|
+
this._traceSubPush( 0 ); // TODO: make _pred_next push this
|
|
459
|
+
match2 = !!this._matchesInFollow( lt2, lk2, (lk1 ? 'K' : 'I') );
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
this._traceSubPush( match2 );
|
|
463
|
+
}
|
|
464
|
+
Object.assign( this, saved );
|
|
465
|
+
this.stack.length = length;
|
|
414
466
|
--this.tokenIdx;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const r = match ?? true;
|
|
418
|
-
if (match == null)
|
|
419
|
-
this._traceSubPush( 0 );
|
|
420
|
-
if (lt2 === 'IllegalToken')
|
|
421
|
-
return true
|
|
422
|
-
// TODO: instead of this IllegalToken test, implement a “confirm unreserved
|
|
423
|
-
// keyword as Id” prediction which tests whether the token after the then-Id
|
|
424
|
-
// matches.
|
|
425
|
-
this._traceSubPush( r );
|
|
426
|
-
if (!r)
|
|
427
|
-
this.nextTokenAsId = true;
|
|
428
|
-
return r;
|
|
467
|
+
return match2;
|
|
429
468
|
}
|
|
430
469
|
|
|
431
470
|
// Now the helper methods =====================================================
|
|
432
471
|
|
|
433
472
|
// Standard weak-conflict predicate -------------------------------------------
|
|
434
473
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (!Array.isArray( cmd ))
|
|
441
|
-
cmd = cmd[keyword] || cmd.Id || cmd[''];
|
|
442
|
-
switch (cmd[0]) {
|
|
443
|
-
case 'ck': case 'mk':
|
|
444
|
-
return cmd[1]; // state after token consumption
|
|
445
|
-
case 'g': // TODO: another rule call?
|
|
446
|
-
break;
|
|
447
|
-
default:
|
|
448
|
-
if (typeof cmd[0] !== 'number')
|
|
449
|
-
throw Error( `Unexpected command ${ cmd[0] } at state ${ this.s }` );
|
|
450
|
-
}
|
|
451
|
-
state = cmd[1];
|
|
452
|
-
}
|
|
453
|
-
// reached end of rule without having consumed a token
|
|
454
|
-
throw Error( 'Not supported: option for unreserved keywords in follow set' );
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
_pred_next( type, keyword, mode ) { // mode = K | E | R | M
|
|
458
|
-
const useConditions = (mode === 'M'); // TODO: extra method with conditions ?
|
|
459
|
-
let hasEnteredRule = false;
|
|
474
|
+
_pred_next( type, keyword, mode ) { // mode = P | K | I | E | R | M
|
|
475
|
+
const properCall = (mode === 'P');
|
|
476
|
+
const lean = (mode !== 'M'); // TODO: extra method with conditions ?
|
|
477
|
+
// TODO: if false, use condition in this.leanConditions
|
|
478
|
+
let hasMatchedToken = null; // undecided yet → calculate on demand
|
|
460
479
|
while (this.s) {
|
|
461
|
-
if (
|
|
462
|
-
this._tracePush( this.s );
|
|
463
|
-
else
|
|
480
|
+
if (lean)
|
|
464
481
|
this._traceSubPush( this.s );
|
|
482
|
+
else
|
|
483
|
+
this._tracePush( this.s ); // TODO: push new state instead
|
|
465
484
|
let cmd = this.table[this.s];
|
|
466
485
|
if (!Array.isArray( cmd )) {
|
|
467
486
|
const lookahead = cmd[' lookahead'];
|
|
468
487
|
const c = lookahead // TODO: call with { keyword, type } ?
|
|
469
488
|
? cmd[this[lookahead]( mode )]
|
|
470
489
|
: keyword && cmd[keyword] || cmd[type];
|
|
471
|
-
cmd = !(c &&
|
|
490
|
+
cmd = !(c && this._rejectCondition( c, mode, lean )) && c || cmd[''];
|
|
472
491
|
}
|
|
492
|
+
const state = this.s;
|
|
493
|
+
this.s = cmd[1];
|
|
473
494
|
switch (cmd[0]) {
|
|
474
495
|
case 'c': case 'ck': case 'ckA': // TODO: re-check ckA
|
|
475
496
|
return true;
|
|
476
|
-
case 'ciA':
|
|
497
|
+
case 'ciA': // TODO: fixKeywordTokenIdx ?
|
|
477
498
|
return mode !== 'R';
|
|
478
499
|
// in the R prediction for optional `Id<reserved>` at rule end, only
|
|
479
500
|
// alternative keyword matches are preferred, not identifier matches
|
|
@@ -481,7 +502,8 @@ class BaseParser {
|
|
|
481
502
|
if (!keyword ||
|
|
482
503
|
!this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx)
|
|
483
504
|
return mode !== 'R';
|
|
484
|
-
cmd = this.table[
|
|
505
|
+
cmd = this.table[state]['']; // is currently always 'g' or 'e'
|
|
506
|
+
this.s = cmd[1];
|
|
485
507
|
break;
|
|
486
508
|
case 'm':
|
|
487
509
|
return type === cmd[2];
|
|
@@ -497,17 +519,27 @@ class BaseParser {
|
|
|
497
519
|
break;
|
|
498
520
|
default:
|
|
499
521
|
if (typeof cmd[0] !== 'number')
|
|
500
|
-
throw Error( `Unexpected command ${ cmd[0] } at state ${
|
|
522
|
+
throw Error( `Unexpected command ${ cmd[0] } at state ${ state }` );
|
|
501
523
|
// If the parser enters a rule, reaching the rule end (can happen with
|
|
502
524
|
// option `minTokensMatched`) means "no match".
|
|
503
|
-
|
|
525
|
+
hasMatchedToken = false;
|
|
504
526
|
// If we want to support conditions before matching the first token in a
|
|
505
527
|
// rule, we would have to handle `this.stack` and `this.dynamically_`.
|
|
528
|
+
if (properCall) {
|
|
529
|
+
// rule_() - TODO: also w/ conditions before matching first token
|
|
530
|
+
this.stack.push( {
|
|
531
|
+
ruleState: cmd[1],
|
|
532
|
+
followState: cmd[0],
|
|
533
|
+
tokenIdx: this.tokenIdx,
|
|
534
|
+
prec: this.prec_,
|
|
535
|
+
} );
|
|
536
|
+
this.dynamic_ = Object.create( this.dynamic_ );
|
|
537
|
+
this.prec_ = null;
|
|
538
|
+
}
|
|
506
539
|
}
|
|
507
540
|
// We could optimize with rule call - only 'Id' must be further investigated
|
|
508
541
|
// TODO: actually also with `g`
|
|
509
542
|
// in both cases if no condition is evaluated
|
|
510
|
-
this.s = cmd[1];
|
|
511
543
|
// TODO <prepare=…, arg=…> for real trial run also before all returns
|
|
512
544
|
// if (cmd[5])
|
|
513
545
|
// this.cmd[5]( cmd[4], mode );
|
|
@@ -522,28 +554,31 @@ class BaseParser {
|
|
|
522
554
|
// prediction, the tool can normally omit the prediction (and output a
|
|
523
555
|
// message), no so with `ruleStartingWithUnreserved`. We will rather look
|
|
524
556
|
// behind the current rule _after_ having decided that the token is to be
|
|
525
|
-
|
|
526
|
-
return
|
|
557
|
+
// matched as identifier.
|
|
558
|
+
return (hasMatchedToken ?? this.tokenIdx > this.stack.at( -1 ).tokenIdx)
|
|
559
|
+
&& null; // let caller decide how to interpret this
|
|
527
560
|
}
|
|
528
561
|
|
|
529
|
-
_rejectCondition( cmd, mode ) {
|
|
562
|
+
_rejectCondition( cmd, mode, lean ) {
|
|
530
563
|
const cond = cmd[3];
|
|
531
|
-
if (!cond)
|
|
564
|
+
if (!cond || lean && !this.leanConditions[cond])
|
|
532
565
|
return false;
|
|
533
566
|
if (!this.constructor.tracingParser)
|
|
534
|
-
return
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
567
|
+
return !!this[cond]( mode, cmd[4] );
|
|
568
|
+
// TODO: let this[cond]( true ) return recovery badness in error case
|
|
569
|
+
if (!lean) {
|
|
570
|
+
const { traceName } = this[cond];
|
|
571
|
+
this._tracePush( [ 'C', traceName?.call( this, cmd[4] ) ?? cond ] );
|
|
572
|
+
// calling the condition might have side effects (precendence conditions have)
|
|
573
|
+
// → call tracing “name” before
|
|
574
|
+
}
|
|
575
|
+
const succeed = !this[cond]( mode, cmd[4] );
|
|
576
|
+
this._traceSubPush( lean ? { true: 'C✔', false: 'C✖' }[succeed] : succeed );
|
|
577
|
+
return !succeed;
|
|
543
578
|
}
|
|
544
579
|
|
|
545
580
|
_matchesInFollow( type, keyword, mode ) { // mode = E | R
|
|
546
|
-
|
|
581
|
+
// TODO: now also set stack!
|
|
547
582
|
const savedState = this.s;
|
|
548
583
|
// TODO: caching
|
|
549
584
|
const { dynamic_ } = this;
|
|
@@ -564,9 +599,11 @@ class BaseParser {
|
|
|
564
599
|
}
|
|
565
600
|
|
|
566
601
|
_confirmExpected( token, saved ) { // mode = M
|
|
567
|
-
const
|
|
602
|
+
const fix = /^[_a-z]/.test( token );
|
|
603
|
+
const [ type, keyword ] = (fix) ? [ 'Id', token ] : [ token ];
|
|
568
604
|
Object.assign( this.la(), { type, keyword } );
|
|
569
605
|
this._cloneFromSaved( saved );
|
|
606
|
+
this.fixKeywordTokenIdx = fix && this.tokenIdx;
|
|
570
607
|
this.trace = [];
|
|
571
608
|
let match;
|
|
572
609
|
while (this.stack.length) {
|
|
@@ -627,7 +664,7 @@ class BaseParser {
|
|
|
627
664
|
// ( <prefer, guard=fail> 'foo' | rule ) with
|
|
628
665
|
// rule : 'foo' | Id ;
|
|
629
666
|
// doing it already here would list `foo` as expected token
|
|
630
|
-
_tokenSetInRule( expecting, val, cmd ) {
|
|
667
|
+
_tokenSetInRule( expecting, val, cmd, collectKeywordsAndIdOnly = false ) {
|
|
631
668
|
const savedDynamic = this.dynamic_;
|
|
632
669
|
const savedState = this.s;
|
|
633
670
|
let enteredRules = 0;
|
|
@@ -638,25 +675,19 @@ class BaseParser {
|
|
|
638
675
|
const dict = cmd;
|
|
639
676
|
for (const prop in dict) {
|
|
640
677
|
if (prop && Object.hasOwn( dict, prop ) && prop !== 'Id' &&
|
|
641
|
-
!Object.hasOwn( expecting, prop ) && prop.charAt(0) !== ' ')
|
|
642
|
-
|
|
643
|
-
for (const p of this.translateParserToken_( prop, lookahead ))
|
|
644
|
-
expecting[p] = val;
|
|
645
|
-
}
|
|
646
|
-
else {
|
|
647
|
-
expecting[prop] = val;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
678
|
+
!Object.hasOwn( expecting, prop ) && prop.charAt(0) !== ' ')
|
|
679
|
+
this.addTokenToSet_( expecting, prop, val, collectKeywordsAndIdOnly, lookahead );
|
|
650
680
|
}
|
|
651
681
|
cmd = dict[''];
|
|
652
682
|
if (dict.Id) {
|
|
653
683
|
// recursive call only if Id branch with non-error default branch
|
|
654
684
|
if (cmd[0] === 'e') {
|
|
685
|
+
collectKeywordsAndIdOnly = true;
|
|
655
686
|
cmd = dict.Id;
|
|
656
687
|
}
|
|
657
688
|
else { // Id branch never leads to rule exit:
|
|
658
689
|
this._tracePush( [ '[' ] );
|
|
659
|
-
this._tokenSetInRule( expecting, val, dict.Id );
|
|
690
|
+
this._tokenSetInRule( expecting, val, dict.Id, true );
|
|
660
691
|
this._tracePush( [ ']' ] );
|
|
661
692
|
}
|
|
662
693
|
}
|
|
@@ -664,10 +695,10 @@ class BaseParser {
|
|
|
664
695
|
this._traceSubPush( this.s );
|
|
665
696
|
switch (cmd[0]) {
|
|
666
697
|
case 'm': case 'mk':
|
|
667
|
-
expecting
|
|
698
|
+
this.addTokenToSet_( expecting, cmd[2], val, collectKeywordsAndIdOnly );
|
|
668
699
|
break loop;
|
|
669
700
|
case 'ci': case 'ciA': case 'mi': case 'miA':
|
|
670
|
-
expecting
|
|
701
|
+
this.addTokenToSet_( expecting, 'Id', val, false );
|
|
671
702
|
// TODO: should we do s/th special, such that a reserved word is a sync
|
|
672
703
|
// token for Id<all>? Probably not, see also comment in
|
|
673
704
|
// _findSyncToken()
|
|
@@ -698,8 +729,10 @@ class BaseParser {
|
|
|
698
729
|
return inspectOuterRules;
|
|
699
730
|
}
|
|
700
731
|
|
|
701
|
-
|
|
702
|
-
|
|
732
|
+
// Remark: when called for `Id` token, `collectKeywordsOnly` is `false`
|
|
733
|
+
addTokenToSet_( set, token, val, collectKeywordsOnly, _lookahead ) {
|
|
734
|
+
if (!collectKeywordsOnly || /^[_a-z]/.test( token ))
|
|
735
|
+
set[token] ??= val;
|
|
703
736
|
}
|
|
704
737
|
|
|
705
738
|
// Error reporting and recovery -----------------------------------------------
|
|
@@ -715,12 +748,14 @@ class BaseParser {
|
|
|
715
748
|
delete set[type]; // delete Id if Id token or non-reserved keyword
|
|
716
749
|
|
|
717
750
|
this._trace( 'collect tokens for message,' );
|
|
751
|
+
const { trace } = this;
|
|
718
752
|
const saved = this._saveForWalk();
|
|
719
753
|
const expecting = Object.keys( set )
|
|
720
754
|
.filter( tok => this._confirmExpected( tok, saved ) );
|
|
721
755
|
token.type = type; // overwritten by _confirmExpected
|
|
722
756
|
token.keyword = keyword;
|
|
723
757
|
Object.assign( this, saved );
|
|
758
|
+
this.trace = trace;
|
|
724
759
|
// TODO: also trace M(…) collection, extra line for each token, with condition
|
|
725
760
|
return expecting;
|
|
726
761
|
}
|
|
@@ -761,6 +796,7 @@ class BaseParser {
|
|
|
761
796
|
// Continue parsing: ignore next predicate (TODO: except some specified ones?)
|
|
762
797
|
this.conditionTokenIdx = this.tokenIdx;
|
|
763
798
|
this.conditionStackLength = null;
|
|
799
|
+
this.fixKeywordTokenIdx = -1; // was set when collecting expecting-set
|
|
764
800
|
|
|
765
801
|
// TODO: re-check for rule calls which are at the optional rule end:
|
|
766
802
|
// x: 'x not'; b: 'b'? x {console.log('x→b')} 'b'?; a: b {console.log('b→a')} 'a'
|
|
@@ -887,35 +923,38 @@ class BaseParser {
|
|
|
887
923
|
|
|
888
924
|
// Predefined conditions with extra option names:
|
|
889
925
|
|
|
926
|
+
hide_( mode ) {
|
|
927
|
+
return mode === 'M';
|
|
928
|
+
}
|
|
890
929
|
precLeft_( _test, prec ) { // <prec=…>, <…,assoc=left>, <…,prefix=once>
|
|
891
930
|
const parentPrec = this.stack.at( -1 ).prec;
|
|
892
931
|
if (parentPrec != null && parentPrec >= prec)
|
|
893
|
-
return
|
|
932
|
+
return true;
|
|
894
933
|
this.prec_ = prec;
|
|
895
|
-
return
|
|
934
|
+
return false;
|
|
896
935
|
}
|
|
897
936
|
precRight_( _test, prec ) { // <…,assoc=right>, <…,prefix>
|
|
898
937
|
const parentPrec = this.stack.at( -1 ).prec;
|
|
899
938
|
if (parentPrec != null && parentPrec >= prec)
|
|
900
|
-
return
|
|
939
|
+
return true;
|
|
901
940
|
this.prec_ = prec - 1;
|
|
902
|
-
return
|
|
941
|
+
return false;
|
|
903
942
|
}
|
|
904
943
|
precNone_( _test, prec ) { // <…,assoc=none>, <…,postfix=once>
|
|
905
944
|
const parentPrec = this.stack.at( -1 ).prec;
|
|
906
945
|
if (parentPrec != null && parentPrec >= prec ||
|
|
907
946
|
this.prec_ != null && this.prec_ <= prec)
|
|
908
|
-
return
|
|
947
|
+
return true;
|
|
909
948
|
this.prec_ = prec;
|
|
910
|
-
return
|
|
949
|
+
return false;
|
|
911
950
|
}
|
|
912
951
|
precPost_( _test, prec ) { // <…,postfix>
|
|
913
952
|
const parentPrec = this.stack.at( -1 ).prec;
|
|
914
953
|
if (parentPrec != null && parentPrec >= prec ||
|
|
915
954
|
this.prec_ != null && this.prec_ < prec)
|
|
916
|
-
return
|
|
955
|
+
return true;
|
|
917
956
|
this.prec_ = prec;
|
|
918
|
-
return
|
|
957
|
+
return false;
|
|
919
958
|
}
|
|
920
959
|
}
|
|
921
960
|
const members = BaseParser.prototype;
|