@sap/cds-compiler 5.9.4 → 6.0.12

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 (114) hide show
  1. package/CHANGELOG.md +117 -319
  2. package/README.md +1 -1
  3. package/bin/cds_update_identifiers.js +3 -5
  4. package/bin/cdsc.js +24 -9
  5. package/bin/cdshi.js +1 -1
  6. package/bin/cdsse.js +4 -4
  7. package/doc/CHANGELOG_BETA.md +11 -0
  8. package/doc/CHANGELOG_DEPRECATED.md +29 -0
  9. package/lib/api/main.js +8 -5
  10. package/lib/api/options.js +12 -10
  11. package/lib/base/builtins.js +1 -0
  12. package/lib/base/message-registry.js +191 -99
  13. package/lib/base/messages.js +35 -21
  14. package/lib/base/model.js +14 -24
  15. package/lib/checks/actionsFunctions.js +10 -20
  16. package/lib/checks/annotationsOData.js +1 -1
  17. package/lib/checks/elements.js +35 -10
  18. package/lib/checks/enums.js +31 -0
  19. package/lib/checks/foreignKeys.js +2 -2
  20. package/lib/checks/hasPersistedElements.js +5 -0
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/managedWithoutKeys.js +5 -4
  23. package/lib/checks/queryNoDbArtifacts.js +10 -8
  24. package/lib/checks/types.js +5 -5
  25. package/lib/checks/validator.js +6 -4
  26. package/lib/compiler/assert-consistency.js +13 -9
  27. package/lib/compiler/checks.js +20 -52
  28. package/lib/compiler/define.js +31 -6
  29. package/lib/compiler/extend.js +5 -1
  30. package/lib/compiler/generate.js +14 -17
  31. package/lib/compiler/populate.js +8 -31
  32. package/lib/compiler/propagator.js +21 -35
  33. package/lib/compiler/resolve.js +64 -29
  34. package/lib/compiler/shared.js +16 -4
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +1 -1
  37. package/lib/edm/annotations/edmJson.js +23 -20
  38. package/lib/edm/annotations/genericTranslation.js +12 -10
  39. package/lib/edm/csn2edm.js +50 -56
  40. package/lib/edm/edm.js +33 -28
  41. package/lib/edm/edmInboundChecks.js +2 -2
  42. package/lib/edm/edmPreprocessor.js +54 -88
  43. package/lib/edm/edmUtils.js +9 -12
  44. package/lib/gen/BaseParser.js +63 -52
  45. package/lib/gen/CdlGrammar.checksum +1 -1
  46. package/lib/gen/CdlParser.js +1153 -1165
  47. package/lib/gen/Dictionary.json +21 -1
  48. package/lib/json/from-csn.js +70 -43
  49. package/lib/json/to-csn.js +6 -8
  50. package/lib/language/multiLineStringParser.js +3 -2
  51. package/lib/main.d.ts +58 -24
  52. package/lib/model/cloneCsn.js +3 -0
  53. package/lib/model/csnUtils.js +28 -39
  54. package/lib/model/xprAsTree.js +23 -9
  55. package/lib/modelCompare/compare.js +5 -4
  56. package/lib/optionProcessor.js +24 -17
  57. package/lib/parsers/AstBuildingParser.js +81 -25
  58. package/lib/parsers/XprTree.js +57 -3
  59. package/lib/parsers/identifiers.js +1 -1
  60. package/lib/parsers/index.js +0 -3
  61. package/lib/render/manageConstraints.js +25 -25
  62. package/lib/render/toCdl.js +173 -170
  63. package/lib/render/toHdbcds.js +126 -128
  64. package/lib/render/toRename.js +7 -7
  65. package/lib/render/toSql.js +128 -125
  66. package/lib/render/utils/common.js +47 -22
  67. package/lib/render/utils/delta.js +25 -25
  68. package/lib/render/utils/operators.js +2 -2
  69. package/lib/render/utils/pretty.js +5 -5
  70. package/lib/render/utils/sql.js +13 -13
  71. package/lib/render/utils/standardDatabaseFunctions.js +115 -103
  72. package/lib/render/utils/unique.js +4 -4
  73. package/lib/transform/db/applyTransformations.js +1 -1
  74. package/lib/transform/db/assertUnique.js +2 -2
  75. package/lib/transform/db/associations.js +6 -7
  76. package/lib/transform/db/assocsToQueries/utils.js +4 -5
  77. package/lib/transform/db/backlinks.js +12 -9
  78. package/lib/transform/db/cdsPersistence.js +8 -7
  79. package/lib/transform/db/constraints.js +13 -10
  80. package/lib/transform/db/expansion.js +7 -3
  81. package/lib/transform/db/flattening.js +4 -14
  82. package/lib/transform/db/processSqlServices.js +2 -1
  83. package/lib/transform/db/temporal.js +5 -7
  84. package/lib/transform/db/views.js +2 -4
  85. package/lib/transform/draft/db.js +8 -8
  86. package/lib/transform/draft/odata.js +10 -7
  87. package/lib/transform/forOdata.js +10 -5
  88. package/lib/transform/forRelationalDB.js +5 -75
  89. package/lib/transform/localized.js +1 -1
  90. package/lib/transform/odata/createForeignKeys.js +11 -10
  91. package/lib/transform/odata/flattening.js +8 -4
  92. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
  93. package/lib/transform/odata/typesExposure.js +3 -3
  94. package/lib/transform/transformUtils.js +4 -8
  95. package/lib/transform/translateAssocsToJoins.js +14 -7
  96. package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
  97. package/lib/utils/objectUtils.js +0 -17
  98. package/package.json +10 -13
  99. package/share/messages/def-upcoming-virtual-change.md +1 -1
  100. package/LICENSE +0 -37
  101. package/bin/cds_remove_invalid_whitespace.js +0 -138
  102. package/doc/CHANGELOG_ARCHIVE.md +0 -3604
  103. package/lib/gen/genericAntlrParser.js +0 -3
  104. package/lib/gen/language.checksum +0 -1
  105. package/lib/gen/language.interp +0 -456
  106. package/lib/gen/language.tokens +0 -180
  107. package/lib/gen/languageLexer.interp +0 -439
  108. package/lib/gen/languageLexer.js +0 -1483
  109. package/lib/gen/languageLexer.tokens +0 -167
  110. package/lib/gen/languageParser.js +0 -24941
  111. package/lib/language/antlrParser.js +0 -205
  112. package/lib/language/errorStrategy.js +0 -646
  113. package/lib/language/genericAntlrParser.js +0 -1572
  114. package/lib/parsers/CdlGrammar.g4 +0 -2070
@@ -1,4 +1,4 @@
1
- // Base class for generated parser, for redepage v0.2.1
1
+ // Base class for generated parser, for redepage v0.2.4
2
2
 
3
3
  'use strict';
4
4
 
@@ -9,7 +9,7 @@
9
9
  // list of predicates which are tested when continue parsing after error starts,
10
10
  // i.e. there is a predicate on the first token to match after recover example
11
11
  // `afterBrace` or just method which by default just sets this.conditionTokenIdx
12
- // and this.conditionStackLength and returns true?
12
+ // and this.conditionStackLength and returns true?
13
13
 
14
14
  class BaseParser {
15
15
  keywords;
@@ -110,7 +110,7 @@ class BaseParser {
110
110
 
111
111
  e() { // error: report and recover
112
112
  const la = this.tokens[this.tokenIdx];
113
- this._trace( 'detect parsing error,' );
113
+ this._trace( 'detect parsing error' );
114
114
  if (this.errorTokenIdx === this.tokenIdx)
115
115
  throw Error( `Already reported error for ${ tokenFullName( la ) } at ${ la.location }`);
116
116
 
@@ -134,7 +134,7 @@ class BaseParser {
134
134
  this.reportUnexpectedToken_();
135
135
  const syncSet = this._calculateTokenSet( 'Y' );
136
136
  const recoverDepth = this._findSyncToken( syncSet );
137
- this._trace( 'recover from error,' );
137
+ this._trace( 'recover from error' );
138
138
  this._recoverFromError( recoverDepth );
139
139
  return false;
140
140
  }
@@ -181,7 +181,7 @@ class BaseParser {
181
181
  return true;
182
182
  }
183
183
 
184
- // instead of gi() for `Id_all`
184
+ // instead of gi() for `Id<greedy>`
185
185
  giA( state, follow ) { // go to state (after trying to test again as identifier)
186
186
  if (!this.tokens[this.tokenIdx].keyword) // lk() had directly returned the type
187
187
  return this.g( state, follow );
@@ -200,9 +200,7 @@ class BaseParser {
200
200
  return false; // do not execute action after it
201
201
  }
202
202
 
203
- // instead of gi() at rule end (RuleEnd_ in follow-set) for `Id_restricted`
204
- // TODO: investigate why this is just used once - should be for all no-`as`
205
- // cases
203
+ // instead of gi() at rule end (RuleEnd_ in follow-set) for `Id<weak>`, TODO: delete
206
204
  giR( state, follow ) { // go to state (after trying to test again as identifier)
207
205
  const { keyword } = this.tokens[this.tokenIdx];
208
206
  if (!keyword || this.keywords[keyword])
@@ -227,14 +225,14 @@ class BaseParser {
227
225
  : this.e();
228
226
  }
229
227
 
230
- // instead of m() for identifiers via `Id` or `Id_restricted`
228
+ // instead of m() for identifiers via `Id` or `Id<weak>`
231
229
  mi( state, ident = true ) { // match identifier token
232
230
  return (this.tokens[this.tokenIdx].type === 'Id')
233
231
  ? this.ci( state, ident )
234
232
  : this.e();
235
233
  }
236
234
 
237
- // instead of mi() for `Id_all`
235
+ // instead of mi() for `Id<greedy>`
238
236
  miA( state, ident = true ) { // match identifier token
239
237
  return (this.tokens[this.tokenIdx].type === 'Id')
240
238
  ? this.ciA( state, ident )
@@ -254,7 +252,7 @@ class BaseParser {
254
252
  this.s = state;
255
253
  this.errorState = state;
256
254
  if (this.constructor.tracingParser)
257
- this._trace( `consume ${ tokenFullName( la, ' as ' ) },`, la );
255
+ this._trace( `consume ${ tokenFullName( la, ' as ' ) }`, la );
258
256
  return true;
259
257
  }
260
258
 
@@ -271,7 +269,7 @@ class BaseParser {
271
269
  return this.c( state, ident )
272
270
  }
273
271
 
274
- // instead of ci() for `Id_all`, used both with l() and lk()
272
+ // instead of ci() for `Id<greedy>`, used both with l() and lk()
275
273
  ciA( state, ident = 'ident' ) { // consume identifier token, the "All" variant
276
274
  return this.c( state, ident )
277
275
  }
@@ -286,7 +284,7 @@ class BaseParser {
286
284
  return this.lP( first2 ) && this.ck( state );
287
285
  }
288
286
 
289
- // for parser token or token set via `/`
287
+ // for parser token or token set via `/` -> cx() ?
290
288
  ckA( state ) {
291
289
  // if it really should be considered an Id, `set this.la().parsedAs` yourself
292
290
  return this.c( state, (this.l() === 'Id' ? 'keyword' : 'token') );
@@ -316,7 +314,7 @@ class BaseParser {
316
314
  }
317
315
  // calling the condition might have side effects (precendence conditions have)
318
316
  // → call tracing “name” before
319
- const fail = this[cond]( true, arg );
317
+ const fail = this[cond]( arg, true ); // TODO: use single-letter for run?
320
318
  if (this.constructor.tracingParser)
321
319
  this._traceSubPush( !fail );
322
320
  // The default case must not have actions. If written in grammar with action,
@@ -379,7 +377,7 @@ class BaseParser {
379
377
  : ' prematurely');
380
378
  const text = immediately ? '⚠ exit rule' : '⏎ exit rule';
381
379
  this.s = caller.followState; // for trace
382
- this._trace( [ text, caller.ruleState, post, this.stack.length + 1 ] )
380
+ this._trace( [ text, caller.ruleState, post, 'back to' ] )
383
381
  if (immediately && this.stack.at(-1)?.followState != null)
384
382
  this.trace = [ this.errorState ]; // show last good state in trace
385
383
  }
@@ -434,13 +432,14 @@ class BaseParser {
434
432
  if (typeof cmd[0] !== 'number') // don't skip push to state with rule call
435
433
  this.s = cmd[1];
436
434
  if (cmd[0] !== (lk1 ? 'ck' : 'ci')) { // make the std case fast
435
+ // TODO: also not with lean condition
437
436
  let match1 = this._pred_next( 'Id', lk1, 'P' ); // TODO: really P for I?
438
437
  if (!match1) {
439
438
  if (lk1 || match1 === false) // assert for correct code generation
440
439
  throw Error( `Cannot match first prediction token in rule at state ${ saved.s }` );
441
440
  if (match1 == null) {
442
441
  this._traceSubPush( 0 ); // TODO: make _pred_next push this
443
- match1 = this._matchesInFollow( 'Id', lk1, 'I' );
442
+ match1 = this._matchesInFollow( 'Id', lk1, 'I' ); // TODO: 'I'?
444
443
  }
445
444
  else {
446
445
  this._traceSubPush( false );
@@ -471,7 +470,23 @@ class BaseParser {
471
470
 
472
471
  // Standard weak-conflict predicate -------------------------------------------
473
472
 
473
+ /**
474
+ * Return whether current token (its type and keyword are args - TODO delete?)
475
+ * would be matched when starting at the current state:
476
+ * - true/false are definite answers,
477
+ * - null: reached end-of-rule (let caller decide what to do).
478
+ *
479
+ * Changes by side-effect:
480
+ * - this.s
481
+ * - with mode='P' (first step in keyword prediction) if a rule is called:
482
+ * this.stack, this.dynamic_, this.prec_
483
+ *
484
+ * Conditions are only evaluated with mode='M' (expected set in msgs) or if
485
+ * condition is listed in `this.leanConditions`.
486
+ */
474
487
  _pred_next( type, keyword, mode ) { // mode = P | K | I | E | R | M
488
+ // TODO mode: really distinguish between K | I | E | R ?
489
+ // Probably not: would not work with caching? → P, P -> F
475
490
  const properCall = (mode === 'P');
476
491
  const lean = (mode !== 'M'); // TODO: extra method with conditions ?
477
492
  // TODO: if false, use condition in this.leanConditions
@@ -496,8 +511,9 @@ class BaseParser {
496
511
  return true;
497
512
  case 'ciA': // TODO: fixKeywordTokenIdx ?
498
513
  return mode !== 'R';
499
- // in the R prediction for optional `Id<reserved>` at rule end, only
514
+ // in the R prediction for optional `Id<weak>` at rule end, only
500
515
  // alternative keyword matches are preferred, not identifier matches
516
+ // TODO: delete this prediction
501
517
  case 'ci':
502
518
  if (!keyword ||
503
519
  !this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx)
@@ -564,7 +580,7 @@ class BaseParser {
564
580
  if (!cond || lean && !this.leanConditions[cond])
565
581
  return false;
566
582
  if (!this.constructor.tracingParser)
567
- return !!this[cond]( mode, cmd[4] );
583
+ return !!this[cond]( cmd[4], mode );
568
584
  // TODO: let this[cond]( true ) return recovery badness in error case
569
585
  if (!lean) {
570
586
  const { traceName } = this[cond];
@@ -572,12 +588,12 @@ class BaseParser {
572
588
  // calling the condition might have side effects (precendence conditions have)
573
589
  // → call tracing “name” before
574
590
  }
575
- const succeed = !this[cond]( mode, cmd[4] );
591
+ const succeed = !this[cond]( cmd[4], mode );
576
592
  this._traceSubPush( lean ? { true: 'C✔', false: 'C✖' }[succeed] : succeed );
577
593
  return !succeed;
578
594
  }
579
595
 
580
- _matchesInFollow( type, keyword, mode ) { // mode = E | R
596
+ _matchesInFollow( type, keyword, mode ) { // mode = E | R and K | I
581
597
  // TODO: now also set stack!
582
598
  const savedState = this.s;
583
599
  // TODO: caching
@@ -588,6 +604,7 @@ class BaseParser {
588
604
  while (match == null && --depth) {
589
605
  this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
590
606
  this.s = this.stack[depth].followState;
607
+ // TODO: this.prec_ ?
591
608
  match = this._pred_next( type, keyword, mode );
592
609
  this._traceSubPush( match == null ? 0 : match === (mode !== 'R') );
593
610
  // successfully matching a keyword in giR() means unsuccessful match as
@@ -700,7 +717,7 @@ class BaseParser {
700
717
  case 'ci': case 'ciA': case 'mi': case 'miA':
701
718
  this.addTokenToSet_( expecting, 'Id', val, false );
702
719
  // TODO: should we do s/th special, such that a reserved word is a sync
703
- // token for Id<all>? Probably not, see also comment in
720
+ // token for Id<greedy>? Probably not, see also comment in
704
721
  // _findSyncToken()
705
722
  break loop;
706
723
  case 'g': case 'gi': case 'e':
@@ -747,7 +764,7 @@ class BaseParser {
747
764
  else if (set[type] === true && !(keyword && this.keywords[keyword]))
748
765
  delete set[type]; // delete Id if Id token or non-reserved keyword
749
766
 
750
- this._trace( 'collect tokens for message,' );
767
+ this._trace( 'collect tokens for message' );
751
768
  const { trace } = this;
752
769
  const saved = this._saveForWalk();
753
770
  const expecting = Object.keys( set )
@@ -765,16 +782,18 @@ class BaseParser {
765
782
  this.recoverTokenIdx = this.tokenIdx;
766
783
  while (this.recoverTokenIdx <= this.eofIndex) {
767
784
  const { keyword, type } = this.tokens[this.recoverTokenIdx];
768
- const tryKeyw = keyword ? syncSet[keyword] : null;
769
- if (tryKeyw != null)
770
- return tryKeyw;
771
- const tryType = syncSet[type];
785
+ let recoverDepth = keyword ? syncSet[keyword] : null;
786
+ if (recoverDepth != null)
787
+ return recoverDepth;
788
+ recoverDepth = syncSet[type];
772
789
  // sync to Id only if in expected set of last good state or if after ';'
773
- if (tryType != null &&
790
+ if (recoverDepth != null &&
774
791
  (type !== 'Id' || (!keyword || !this.keywords[keyword]) &&
775
792
  // reserved words do not match Id in expected-set
776
- (tryType > rewindDepth || this.tokens[this.recoverTokenIdx - 1].type === ';')))
777
- return tryType;
793
+ (recoverDepth > rewindDepth || this.tokens[this.recoverTokenIdx - 1].type === ';')))
794
+ // if (recoverDepth != null &&
795
+ // (this.recoverTokenIdx > this.tokenIdx ||
796
+ return recoverDepth;
778
797
  ++this.recoverTokenIdx;
779
798
  }
780
799
  throw Error( 'EOF must be last in `tokens`' );
@@ -809,7 +828,7 @@ class BaseParser {
809
828
 
810
829
  _skipErrorTokens() {
811
830
  if (this.constructor.tracingParser && this.tokenIdx <= this.recoverTokenIdx) {
812
- this._trace( `skipped ${ this.recoverTokenIdx - this.tokenIdx } tokens to recover from error,`,
831
+ this._trace( `skip ${ this.recoverTokenIdx - this.tokenIdx } tokens to recover from error`,
813
832
  this.tokens[this.recoverTokenIdx] );
814
833
  }
815
834
  while (this.tokenIdx < this.recoverTokenIdx)
@@ -832,7 +851,7 @@ class BaseParser {
832
851
  msg ??= `Unexpected token ${ tokenFullName( token, ': ' ) }`;
833
852
  this.reportError_(
834
853
  token.location, msg + ' - expecting: ' +
835
- this.expectingArray_().map( tokenName ).sort().join( ',' ) );
854
+ this.expectingArray_().map( tokenName ).sort().join( ', ' ) );
836
855
  }
837
856
 
838
857
  reportReservedWord_() {
@@ -875,7 +894,7 @@ class BaseParser {
875
894
  this.log( indent, line, `(${ la })` );
876
895
  return;
877
896
  }
878
- else if (la === 2) {
897
+ else if (la === 2) { // confirming tokens in expected set
879
898
  this.log( indent, ' ', msg + ':',
880
899
  this.trace.map( traceStep ).join( ' → ' ) );
881
900
  this.trace = [ this.s ?? '⚠' ];
@@ -887,31 +906,23 @@ class BaseParser {
887
906
  this.trace = [ -1 ];
888
907
  }
889
908
  this.trace.push( this.s ?? '⚠' );
890
- if (Array.isArray( msg )) {
891
- const [ intro, state, finale, exitLength ] = msg;
892
- let depth = (exitLength) ? exitLength - 1 : this.stack.length + 1;
893
- let length = this.trace.length - 1;
894
- this.trace[length] = `${ this.trace[length] }(${ depth })`;
895
- depth = exitLength || this.stack.length;
896
- while (length && typeof this.trace[--length] !== 'number')
897
- ;
898
- this.trace[length] = `${ this.trace[length] }(${ depth })`;
899
-
909
+ if (Array.isArray( msg )) { // rule call and exit
910
+ const [ intro, state, finale, exit ] = msg;
900
911
  let start = state;
901
912
  while (typeof this.table[--start] !== 'string')
902
913
  ;
903
- const post = (exitLength || start + 1 < state) && finale;
904
- msg = `${ intro } “${ this.table[start] }”${ post || '' },`;
914
+ const post = (exit || start + 1 < state) && finale;
915
+ msg = `${ intro } “${ this.table[start] }”${ post || '' } ${ exit || 'from' } stack level ${ this.stack.length }`;
905
916
  }
906
917
  // Yes, I know util.format, but do not want to have a `require` in this file
907
918
  const line = location.line < 1e5 ? ` ${ location.line }`.slice(-5) : `${ location.line }`;
908
919
  const col = location.col < 1e4 ? `:${ location.col } `.slice(0,5) : `:${location.col }`;
909
- this.log( line + col + indent + msg,
910
- 'states:', this.trace.map( traceStep ).join( ' → ' ) );
920
+ this.log( line + col + indent + msg + ', states:',
921
+ this.trace.map( traceStep ).join( ' → ' ) );
911
922
  this.trace = [ this.s ?? '⚠' ];
912
923
  }
913
924
 
914
- inSameRule_( lowState, highState ) {
925
+ inSameRule_( lowState = this.s, highState = this.stack.at(-1).followState ) {
915
926
  if (lowState > highState)
916
927
  [ lowState, highState ] = [ highState, lowState ];
917
928
  while (lowState < highState) {
@@ -923,24 +934,24 @@ class BaseParser {
923
934
 
924
935
  // Predefined conditions with extra option names:
925
936
 
926
- hide_( mode ) {
937
+ hide_( _arg, mode ) {
927
938
  return mode === 'M';
928
939
  }
929
- precLeft_( _test, prec ) { // <prec=…>, <…,assoc=left>, <…,prefix=once>
940
+ precLeft_( prec ) { // <prec=…>, <…,assoc=left>, <…,prefix=once>
930
941
  const parentPrec = this.stack.at( -1 ).prec;
931
942
  if (parentPrec != null && parentPrec >= prec)
932
943
  return true;
933
944
  this.prec_ = prec;
934
945
  return false;
935
946
  }
936
- precRight_( _test, prec ) { // <…,assoc=right>, <…,prefix>
947
+ precRight_( prec ) { // <…,assoc=right>, <…,prefix>
937
948
  const parentPrec = this.stack.at( -1 ).prec;
938
949
  if (parentPrec != null && parentPrec >= prec)
939
950
  return true;
940
951
  this.prec_ = prec - 1;
941
952
  return false;
942
953
  }
943
- precNone_( _test, prec ) { // <…,assoc=none>, <…,postfix=once>
954
+ precNone_( prec ) { // <…,assoc=none>, <…,postfix=once>
944
955
  const parentPrec = this.stack.at( -1 ).prec;
945
956
  if (parentPrec != null && parentPrec >= prec ||
946
957
  this.prec_ != null && this.prec_ <= prec)
@@ -948,7 +959,7 @@ class BaseParser {
948
959
  this.prec_ = prec;
949
960
  return false;
950
961
  }
951
- precPost_( _test, prec ) { // <…,postfix>
962
+ precPost_( prec ) { // <…,postfix>
952
963
  const parentPrec = this.stack.at( -1 ).prec;
953
964
  if (parentPrec != null && parentPrec >= prec ||
954
965
  this.prec_ != null && this.prec_ < prec)
@@ -1 +1 @@
1
- 226c8854e08665b3eaae3ae1a1276db7
1
+ eb778bab22f4006cfcf64e418dcae71c