@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.
Files changed (57) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/bin/cdsse.js +4 -0
  3. package/bin/cdsv2m.js +2 -1
  4. package/doc/Versioning.md +4 -4
  5. package/lib/api/options.js +1 -0
  6. package/lib/base/builtins.js +2 -2
  7. package/lib/base/dictionaries.js +1 -2
  8. package/lib/base/keywords.js +3 -1
  9. package/lib/base/lazyload.js +1 -1
  10. package/lib/base/message-registry.js +170 -143
  11. package/lib/base/messages.js +69 -59
  12. package/lib/base/model.js +3 -3
  13. package/lib/base/node-helpers.js +17 -16
  14. package/lib/base/optionProcessorHelper.js +13 -14
  15. package/lib/base/shuffle.js +4 -1
  16. package/lib/checks/structuredAnnoExpressions.js +1 -1
  17. package/lib/compiler/assert-consistency.js +1 -1
  18. package/lib/compiler/builtins.js +4 -1
  19. package/lib/compiler/extend.js +20 -5
  20. package/lib/compiler/resolve.js +45 -9
  21. package/lib/compiler/shared.js +1 -0
  22. package/lib/edm/annotations/edmJson.js +3 -3
  23. package/lib/edm/annotations/genericTranslation.js +5 -1
  24. package/lib/edm/annotations/vocabularyDefinitions.js +8 -2
  25. package/lib/edm/edmUtils.js +2 -1
  26. package/lib/gen/BaseParser.js +142 -103
  27. package/lib/gen/CdlParser.js +2240 -2201
  28. package/lib/gen/Dictionary.json +185 -6
  29. package/lib/json/from-csn.js +2 -0
  30. package/lib/json/to-csn.js +13 -4
  31. package/lib/language/docCommentParser.js +11 -5
  32. package/lib/language/errorStrategy.js +3 -3
  33. package/lib/language/genericAntlrParser.js +2 -0
  34. package/lib/model/csnUtils.js +6 -1
  35. package/lib/optionProcessor.js +5 -1
  36. package/lib/parsers/AstBuildingParser.js +200 -86
  37. package/lib/parsers/CdlGrammar.g4 +142 -86
  38. package/lib/parsers/Lexer.js +5 -3
  39. package/lib/parsers/index.js +1 -1
  40. package/lib/render/toCdl.js +6 -5
  41. package/lib/render/toHdbcds.js +1 -1
  42. package/lib/render/toSql.js +5 -3
  43. package/lib/render/utils/common.js +19 -6
  44. package/lib/render/utils/standardDatabaseFunctions.js +576 -0
  45. package/lib/transform/addTenantFields.js +2 -1
  46. package/lib/transform/db/expansion.js +3 -0
  47. package/lib/transform/db/flattening.js +18 -77
  48. package/lib/transform/db/groupByOrderBy.js +2 -2
  49. package/lib/transform/db/rewriteCalculatedElements.js +14 -19
  50. package/lib/transform/db/temporal.js +2 -1
  51. package/lib/transform/forOdata.js +1 -1
  52. package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
  53. package/lib/transform/odata/createForeignKeys.js +25 -9
  54. package/lib/transform/odata/flattening.js +11 -1
  55. package/lib/transform/transformUtils.js +20 -85
  56. package/package.json +2 -1
  57. package/bin/cds_update_annotations.js +0 -180
@@ -1,4 +1,4 @@
1
- // Base class for generated parser, for redepage v0.1.18
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
- this._matchesInFollow( 'Id', keyword, 'R' ))
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 = !this[cond]( true, arg );
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 if with LL(1) conflict, 'Id' in other case
389
- lP( first2 ) { // only start rule if this predicate returns true
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
- // Argument first2 is just a performance hint with ckP():
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._tracePush( [ 'K', true ] );
417
+ this._traceSubPush( true );
400
418
  return true;
401
419
  }
402
- this._tracePush( [ 'K' ] );
403
420
  // now check it dynamically:
404
- let cmd = this.table[this.s][lk1];
405
- if (cmd[2] !== 1)
406
- throw Error( `Unexpected command '${ cmd?.[0] }' without prediction at state ${ this.s } for ‘${ lk1 }’` );
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
- // if not the keyword match, the command is “goto” or “rule call”
409
- const savedState = this.s;
410
- this.s = (cmd[0] === 'ck') ? cmd[1] : this._pred_keyword( cmd[1], lk1 );
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
- const match = this._pred_next( lt2, lk2, 'K' );
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
- this.s = savedState;
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
- _pred_keyword( state, keyword ) {
436
- // returns state after matching the first token as keyword, for lP()
437
- while (state) {
438
- this._traceSubPush( state );
439
- let cmd = this.table[state];
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 (useConditions)
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 && useConditions && this._rejectCondition( c, mode )) && c || cmd[''];
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[this.s]['']; // is currently always 'g' or 'e'
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 ${ this.s }` );
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
- hasEnteredRule = true;
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
- // matched as identifier.
526
- return !hasEnteredRule && null; // let caller decide how to interpret this
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 !this[cond]( mode, cmd[4] );
535
- // TODO: let this[cond]( true ) return recovery badness in error case
536
- const { traceName } = this[cond];
537
- this._tracePush( [ 'C', traceName?.call( this, cmd[4] ) ?? cond ] );
538
- // calling the condition might have side effects (precendence conditions have)
539
- // call tracing “name” before
540
- const fail = !this[cond]( mode, cmd[4] );
541
- this._traceSubPush( !fail );
542
- return fail;
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
- this._tracePush( [ mode, 0 ] );
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 [ type, keyword ] = (/^[_a-z]/.test( token )) ? [ 'Id', token ] : [ token ];
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
- if (lookahead) { // yes, independently from ckA()
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[cmd[2]] ??= val;
698
+ this.addTokenToSet_( expecting, cmd[2], val, collectKeywordsAndIdOnly );
668
699
  break loop;
669
700
  case 'ci': case 'ciA': case 'mi': case 'miA':
670
- expecting['Id'] ??= val;
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
- translateParserToken_( token ) {
702
- return [ token ];
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 false;
932
+ return true;
894
933
  this.prec_ = prec;
895
- return true;
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 false;
939
+ return true;
901
940
  this.prec_ = prec - 1;
902
- return true;
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 false;
947
+ return true;
909
948
  this.prec_ = prec;
910
- return true;
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 false;
955
+ return true;
917
956
  this.prec_ = prec;
918
- return true;
957
+ return false;
919
958
  }
920
959
  }
921
960
  const members = BaseParser.prototype;