@sap/cds-compiler 6.6.2 → 6.7.1

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 (44) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/bin/cdsc.js +2 -0
  3. package/bin/cdsse.js +1 -1
  4. package/lib/base/message-registry.js +6 -7
  5. package/lib/base/model.js +0 -72
  6. package/lib/checks/elements.js +1 -1
  7. package/lib/checks/featureFlags.js +2 -2
  8. package/lib/compiler/assert-consistency.js +3 -4
  9. package/lib/compiler/base.js +8 -0
  10. package/lib/compiler/builtins.js +8 -9
  11. package/lib/compiler/checks.js +27 -6
  12. package/lib/compiler/cycle-detector.js +4 -4
  13. package/lib/compiler/define.js +65 -83
  14. package/lib/compiler/extend.js +357 -325
  15. package/lib/compiler/finalize-parse-cdl.js +3 -4
  16. package/lib/compiler/generate.js +205 -203
  17. package/lib/compiler/kick-start.js +34 -49
  18. package/lib/compiler/populate.js +95 -28
  19. package/lib/compiler/propagator.js +3 -5
  20. package/lib/compiler/resolve.js +17 -13
  21. package/lib/compiler/shared.js +47 -19
  22. package/lib/compiler/tweak-assocs.js +2 -4
  23. package/lib/compiler/utils.js +84 -31
  24. package/lib/gen/BaseParser.js +924 -1055
  25. package/lib/gen/CdlGrammar.checksum +1 -1
  26. package/lib/gen/CdlParser.js +5 -2
  27. package/lib/json/from-csn.js +25 -16
  28. package/lib/main.d.ts +13 -0
  29. package/lib/model/revealInternalProperties.js +18 -0
  30. package/lib/parsers/AstBuildingParser.js +22 -5
  31. package/lib/render/toHdbcds.js +2 -2
  32. package/lib/render/utils/sql.js +2 -2
  33. package/lib/render/utils/standardDatabaseFunctions.js +2 -2
  34. package/lib/transform/db/constraints.js +3 -4
  35. package/lib/transform/db/killAnnotations.js +1 -1
  36. package/lib/transform/db/processSqlServices.js +10 -11
  37. package/lib/transform/forOdata.js +7 -124
  38. package/lib/transform/odata/fioriTreeViews.js +173 -0
  39. package/lib/transform/odata/flattening.js +2 -2
  40. package/lib/transform/translateAssocsToJoins.js +7 -4
  41. package/package.json +1 -1
  42. package/share/messages/message-explanations.json +0 -2
  43. package/share/messages/type-unexpected-foreign-keys.md +0 -52
  44. package/share/messages/type-unexpected-on-condition.md +0 -52
@@ -1,1072 +1,941 @@
1
- // Base class for generated parser, for redepage v0.3.1
1
+ //Base class for generated parser, for redepage v0.3.2
2
2
 
3
3
  'use strict';
4
4
 
5
- // TODO: instance method
6
- // name → true, list of predicates which are tested for rule exit
7
- // const ruleExitPredicates = {};
8
5
 
9
- // list of predicates which are tested when continue parsing after error starts,
10
- // i.e. there is a predicate on the first token to match after recover example
11
- // `afterBrace` or just method which by default just sets this.conditionTokenIdx
12
- // and this.conditionStackLength and returns true?
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
13
 
14
14
  class BaseParser {
15
- keywords;
16
- table;
17
- lexer;
18
-
19
- tokens = undefined;
20
- tokenIdx = 0;
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”
25
- fixKeywordTokenIdx = -1;
26
- conditionStackLength = -1;
27
- nextTokenAsId = false;
28
-
29
- s = null;
30
- errorState = null;
31
- stack = [];
32
- dynamic_ = {}; // TODO: extra class
33
- prec_ = null;
34
- $hasErrors = null;
35
- leanConditions = {};
36
- // trace:
37
- trace = [];
38
-
39
- constructor( lexer, keywords, table ) {
40
- this.keywords = { __proto__: null, ...keywords };
41
- this.table = compileTable( table );
42
- this.lexer = lexer;
43
- }
44
-
45
- init() {
46
- this.lexer.tokenize( this );
47
- return this;
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
-
61
- _saveForWalk() {
62
- return {
63
- s: this.s,
64
- stack: this.stack,
65
- dynamic_: this.dynamic_,
66
- prec_: this.prec_, // TODO: necessary?
67
- };
68
- }
69
-
70
- _cloneFromSaved( saved ) { // non-deep: Object.assign
71
- this.s = saved.s;
72
- this.stack = saved.stack.map( obj => ({ ...obj }) );
73
- this.dynamic_ = this._cloneDynamic( saved.dynamic_ );
74
- this.prec_ = saved.prec_;
75
- }
76
-
77
- _cloneDynamic( dynamic_ ) {
78
- let chain = [];
79
- while (dynamic_ !== Object.prototype) {
80
- const obj = {};
81
- for (const [ prop, val ] of Object.entries( dynamic_ ))
82
- obj[prop] = Array.isArray( val ) ? [ ...val ] : val;
83
- chain.push( obj );
84
- dynamic_ = Object.getPrototypeOf( dynamic_ );
85
- }
86
- let copy = Object.prototype;
87
- let { length } = chain;
88
- while (--length >= 0)
89
- copy = { __proto__: copy, ...chain[length] };
90
- return copy;
91
- }
92
-
93
- // methods for actions --------------------------------------------------------
94
-
95
- la() { // lookahead: complete token
96
- return this.tokens[this.tokenIdx];
97
- }
98
- lb( k = 1 ) { // look back: complete token
99
- return this.tokens[this.tokenIdx - k];
100
- }
101
- lr() { // return the first token matched by current rule
102
- return this.tokens[this.stack[this.stack.length - 1].tokenIdx];
103
- }
104
-
105
- // lookahead, error: ----------------------------------------------------------
106
-
107
- l() { // lookahead: token type
108
- return this.la().type;
109
- }
110
-
111
- // instead of l() if keyword (reserved and/or unreserved) is in one of the cases
112
- lk() { // keyword lookahead
113
- const la = this.la();
114
- if (!this.nextTokenAsId)
115
- return la.keyword || la.type;
116
- // return la.keyword && this.table[this.s][la.keyword] && la.keyword || la.type;
117
- this.nextTokenAsId = false;
118
- return la.type;
119
- }
120
-
121
- e() { // error: report and recover
122
- const la = this.la();
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))
126
- throw Error( `Already reported error for ${ tokenFullName( la ) } at ${ la.location }`);
127
-
128
- la.parsedAs = ''; // current token is erroneous
129
- this.errorTokenIdx = this.tokenIdx;
130
- this.conditionStackLength = null;
131
-
132
- let { length } = this.stack;
133
- while (--length && this.tokenIdx === this.stack[length].tokenIdx)
134
- this.stack[length].followState = null;
135
- if (++length === this.stack.length) // last good state in current rule
136
- return this._reportAndRecover(); // otherwise report+recovery after unwind in exit_()
137
-
138
- this.stack[length].followState = this.errorState;
139
- this.s = null;
140
- return false;
141
- }
142
-
143
- // instead of e() in default if lk() had been used and 'Id' is in a non-default case
144
- ei() { // error (after trying to test again as identifier)
145
- if (!this.la().keyword) // lk() had directly returned the type
146
- return this.e();
147
- this.nextTokenAsId = true;
148
- return false; // do not execute action after it
149
- }
150
-
151
- // goto state: ----------------------------------------------------------------
152
-
153
- // go to end of the rule, in tracing parser: g(0)
154
- gr( follow ) { // intersection follow set for fast exit
155
- if (this.stack[this.stack.length - 1].tokenIdx === this.tokenIdx)
156
- //this.tokenIdx >= this.errorTokenIdx) not necessary
157
- return this.e(); // match at least one token
158
- this.s = 0;
159
- // TODO: also have recursive flag in stack: was rule was called recursively?
160
- // extra val 'gr' when rule was called when it could reach the rule end
161
- const { type, keyword } = this.tokens[this.tokenIdx];
162
- if (keyword && // Id also for unreserved, except after condition failure
163
- follow?.[0] === 'Id' && !this.keywords[keyword] &&
164
- this.fixKeywordTokenIdx !== this.tokenIdx ||
165
- follow?.includes( keyword || type )) {
166
- this._tracePush( [ 'E', true ] );
167
- return true;
168
- }
169
- this._tracePush( [ 'E', 0 ] );
170
- const match = this._matchesInFollow( type, keyword, 'E' );
171
- // If the parser reaches this point with match = null, even the top-level rule
172
- // does not have a required token (typically `EOF`) at the end → the parser
173
- // must accept any token → rule exit possible (but no output '✔' in trace).
174
- return (match ?? true) || this.e();
175
- }
176
-
177
- // go to state; non-tracing parser: `this.s=‹state›` or `this.gr()`
178
- g( state, follow ) {
179
- if (!(state == null ? this.e() : state || this.gr( follow )))
180
- return false;
181
- this.s = state; // is just `this.s=‹state›` in non-trace parser
182
- this._tracePush( this.s );
183
- return true;
184
- }
185
-
186
- // instead of gi() for `Id<greedy>`
187
- giA( state, follow ) { // go to state (after trying to test again as identifier)
188
- if (!this.tokens[this.tokenIdx].keyword) // lk() had directly returned the type
189
- return this.g( state, follow );
190
- this.nextTokenAsId = true;
191
- return false; // do not execute action after it
192
- }
193
-
194
- // instead of g() in default if lk() had been used and 'Id' is in a non-default case
195
- gi( state, follow ) { // go to state (after trying to test again as identifier)
196
- const lk = this.tokens[this.tokenIdx].keyword;
197
- // As opposed to ei(), we also check for reserved keywords here; this way, we
198
- // do not have to add reserved keywords from the follow-set to the `switch`.
199
- if (!lk || this.keywords[lk]) // TODO: consider fixKeywordTokenIdx ?
200
- return this.g( state, follow );
201
- this.nextTokenAsId = true;
202
- return false; // do not execute action after it
203
- }
204
-
205
- // instead of g() in a non-default case if there is a LL1 conflict
206
- gP( state, follow ) { // goto state with standard weak-conflict prediction
207
- return this.lP( follow ) && this.g( state );
208
- }
209
-
210
- // match and consume token: ---------------------------------------------------
211
-
212
- m( state, token ) { // match token = compare and consume
213
- return (this.tokens[this.tokenIdx].type === token)
214
- ? this.c( state )
215
- : this.e();
216
- }
217
-
218
- // instead of m() for identifiers via `Id` or `Id<weak>`
219
- mi( state, ident = true ) { // match identifier token
220
- return (this.tokens[this.tokenIdx].type === 'Id')
221
- ? this.ci( state, ident )
222
- : this.e();
223
- }
224
-
225
- // instead of mi() for `Id<greedy>`
226
- miA( state, ident = true ) { // match identifier token
227
- return (this.tokens[this.tokenIdx].type === 'Id')
228
- ? this.ciA( state, ident )
229
- : this.e();
230
- }
231
-
232
- // instead of m() for reserved keywords or unreserved without conflict:
233
- mk( state, token ) { // match keyword token
234
- return (this.tokens[this.tokenIdx].keyword === token)
235
- ? this.ck( state )
236
- : this.e();
237
- }
238
-
239
- c( state, parsedAs = 'token' ) { // consume token
240
- const la = this.tokens[this.tokenIdx++]; // ++ now also for EOF
241
- la.parsedAs = parsedAs;
242
- this.s = state;
243
- this.errorState = state;
244
- if (this.constructor.tracingParser)
245
- this._trace( `consume ${ tokenFullName( la, ' as ' ) }`, la );
246
- return true;
247
- }
248
-
249
- // instead of c() for identifiers, used both with l() and lk()
250
- ci( state, ident = 'ident' ) { // consume identifier token
251
- if (this.tokenIdx === this.fixKeywordTokenIdx)
252
- return this.e();
253
- const la = this.tokens[this.tokenIdx];
254
- // TODO: consider this like a failed condition? Will be relevant if we try
255
- // different error recovery possibilities.
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();
266
- this.reportReservedWord_();
267
- // with error recovery: use that (consider this having a good score)
268
- }
269
- return this.c( state, ident )
270
- }
271
-
272
- // instead of ci() for `Id<greedy>`, used both with l() and lk()
273
- ciA( state, ident = 'ident' ) { // consume identifier token, the "All" variant
274
- return this.c( state, ident )
275
- }
276
-
277
- // instead of c() for reserved or unreserved without conflict, requires lk()
278
- ck( state ) { // consume keyword token
279
- return this.c( state, 'keyword' )
280
- }
281
-
282
- // instead of ck() if there is a LL1 conflict
283
- ckP( state, first2 ) { // consume unreserved keyword with weak conflict
284
- return this.lP( first2 ) && this.ck( state );
285
- }
286
-
287
- // for parser token or token set via `/` -> cx() ?
288
- ckA( state ) {
289
- // if it really should be considered an Id, `set this.la().parsedAs` yourself
290
- return this.c( state, (this.l() === 'Id' ? 'keyword' : 'token') );
291
- }
292
-
293
- skipToken_() {
294
- ++this.tokenIdx;
295
- }
296
-
297
- reuseToken_() {
298
- --this.tokenIdx;
299
- }
300
-
301
- // condition and precedence handling ------------------------------------------
302
-
303
- // state must match the goto-state of the default (there must be no default
304
- // action), or null for error, lP() must have been used before. There is no
305
- // “or Id” behavior other than via gpP()
306
-
307
- // “go if user condition fails”
308
- gc( state, cond, arg ) {
309
- if (this.conditionTokenIdx === this.tokenIdx && // tested on same
310
- this.conditionStackLength == null && // after error recovery
311
- !this[cond].afterError) {
312
- this._tracePush( [ 'C' ] );
313
- return true;
314
- }
315
- // TODO: let this[cond]( true ) return recovery badness in error case
316
- if (this.constructor.tracingParser) {
317
- const { traceName } = this[cond];
318
- this._tracePush( [ 'C', traceName?.call( this, arg ) ?? cond ] );
319
- }
320
- // calling the condition might have side effects (precendence conditions have)
321
- // → call tracing “name” before
322
- const fail = this[cond]( arg, true ); // TODO: use single-letter for run! → 'X'
323
- if (this.constructor.tracingParser)
324
- this._traceSubPush( !fail );
325
- // The default case must not have actions. If written in grammar with action,
326
- // the default must currently have <default=true>
327
-
328
- if (fail) { // TODO: extra gcK() method instead of check below
329
- // TODO: probably remove the following (and `conditionStackLength` tests)
330
- // altogether, error with gr() should be enough
331
- // if (this.conditionTokenIdx === this.tokenIdx &&
332
- // this.conditionStackLength == this.stack.length)
333
- // return this.e(); // already failed on same token in same rule
334
- // TODO: extra method necessary for academic case
335
- // ( 'unreserved' 'foo' | <cond> Id 'bar' )` with input `unreserved bar`
336
- const { keyword } = this.la();
337
- if (keyword && this.table[this.s][keyword])
338
- this.fixKeywordTokenIdx = this.tokenIdx;
339
- this.conditionTokenIdx = this.tokenIdx;
340
- this.conditionStackLength = this.stack.length;
341
- this.conditionName = cond;
342
- // we also set the failure here, because the reporting might have a
343
- // different context (consider immediate exit)
344
- this.conditionFailure = fail;
345
- }
346
- return !fail || this.g( state ) && false;
347
- }
348
-
349
- ec( cond, arg ) {
350
- return this.gc( null, cond, arg );
351
- }
352
-
353
- // predefined guard:
354
- isNoKeywordInRuleFollow( _arg, mode ) {
355
- const { keyword } = this.la();
356
- if (this.constructor.tracingParser && (mode === true || mode === 'M')) {
357
- // TODO: mode === 'X' || mode === 'M'
358
- --this.trace.at(-1).length; // do not show guard name in trace
359
- if (!keyword || this.keywords[keyword] == null)
360
- return false; // ok
361
- const r = this._matchesInFollow( 'Id', keyword, 'F' );
362
- --this.trace.at(-1).length; // this.gc() also traces result
363
- return r;
364
- }
365
- if (!keyword || this.keywords[keyword] == null)
366
- return false; // ok
367
- return this._matchesInFollow( 'Id', keyword, 'F' );
368
- // TODO: still extra tests for 'F' in _pred_next()?
369
- }
370
-
371
- // rule start, end and call: --------------------------------------------------
372
-
373
- rule_( state, followState = -1 ) { // start rule
374
- this.s = state;
375
- this._trace( [ 'call rule', state, ' at alt start' ] );
376
- this.stack.push( {
377
- ruleState: state,
378
- followState,
379
- tokenIdx: this.tokenIdx,
380
- prec: this.prec_,
381
- } );
382
- this.dynamic_ = Object.create( this.dynamic_ );
383
- this.prec_ = null;
384
- this.errorState ??= state;
385
- }
386
-
387
- exit_() { // exit rule
388
- if (this.s)
389
- throw Error( `this.s === ${ this.s } // illegally set by action, or runtime/generator bug` );
390
- this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
391
- const caller = this.stack.pop();
392
- const immediately = this.tokenIdx === caller.tokenIdx && this.tokenIdx >= this.errorTokenIdx;
393
- if (this.constructor.tracingParser) {
394
- const post = this.s == null &&
395
- (immediately
396
- ? ' immediately' // no token matched (further unwind or to last good state)
397
- : caller.followState == null
398
- ? ' unsuccessfully' // further error rewind
399
- : ' prematurely'); // continue at sync state after exit
400
- const text = immediately ? '⚠ exit rule' : '⏎ exit rule';
401
- this.s = caller.followState; // for trace
402
- this._trace( [ text, caller.ruleState, post, 'back to' ] )
403
- if (immediately && this.stack.at(-1)?.followState != null)
404
- this.trace = [ this.errorState ]; // show last good state in trace
405
- }
406
- this.prec_ = caller.prec;
407
- this.s = caller.followState;
408
- if (immediately) // this.gr() has already called this.e() if no token is matched
409
- return this.s != null && this._reportAndRecover();
410
-
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"
414
- }
415
-
416
- // predicate used before rule call (and called by `ckP` and `gP`) on keyword
417
- // branch if with weak LL(1) conflict, i.e. there is an 'Id' branch or the
418
- // default branch has `Id` in its first-set (TODO: or rule end, and `Id` is in
419
- // follow-union)
420
- lP( first2 ) {
421
- // nothing to check if not a non-reserved keyword:
422
- const { keyword: lk1 } = this.tokens[this.tokenIdx];
423
- if (!lk1 || this.keywords[lk1] !== 0 || this.fixKeywordTokenIdx === this.tokenIdx)
424
- return true;
425
-
426
- this._tracePush( [ 'K' ] );
427
- const { type: lt2, keyword: lk2 } = this.tokens[this.tokenIdx + 1];
428
- if (lt2 === 'IllegalToken')
429
- return true
430
- // Argument first2 is just a performance hint:
431
- if (lk2 && first2?.[0] === 'Id' && !this.keywords[lk2] ||
432
- first2?.includes( lk2 || lt2 )) {
433
- this._traceSubPush( true );
434
- return true;
435
- }
436
- // now check it dynamically:
437
- if (this._walkPred( this.table[this.s][lk1], lk1, lt2, lk2 ))
438
- return true;
439
- this._tracePush( [ 'I' ] );
440
- const choice = this.table[this.s];
441
- if (!this._walkPred( choice.Id || choice[''], null, lt2, lk2 ))
442
- return true;
443
- this.nextTokenAsId = true;
444
- return false;
445
- }
446
-
447
- _walkPred( cmd, lk1, lt2, lk2 ) {
448
- const saved = this._saveForWalk();
449
- const { length } = this.stack;
450
- if (typeof cmd[0] !== 'number') // don't skip push to state with rule call
451
- this.s = cmd[1];
452
- if (cmd[0] !== (lk1 ? 'ck' : 'ci')) { // make the std case fast
453
- // TODO: also not with lean condition
454
- let match1 = this._pred_next( 'Id', lk1, 'P' ); // first step of `K`/`I` prediction
455
- if (!match1) {
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
- }
461
- if (match1 == null) { // TODO: just return true, rule exit prediction will do it
462
- this._traceSubPush( 0 ); // TODO: make _pred_next push this
463
- match1 = this._matchesInFollow( 'Id', lk1, 'I' );
464
- }
465
- else {
466
- this._traceSubPush( false );
467
- }
468
- Object.assign( this, saved );
469
- this.stack.length = length;
470
- return !!match1;
471
- }
472
- }
473
-
474
- this._traceSubPush( '' ); // between the two tokens
475
- ++this.tokenIdx; // for user lookahead fns and conditions
476
- const mode = lk1 ? 'K' : 'I';
477
- let match2 = this._pred_next( lt2, lk2, mode );
478
- if (match2 == null) {
479
- this._traceSubPush( 0 ); // TODO: make _pred_next push this
480
- match2 = !!this._matchesInFollow( lt2, lk2, mode );
481
- // TODO: we might use mode 'E' in _matchesInFollow (depends on caching)
482
- }
483
- else {
484
- this._traceSubPush( match2 );
485
- }
486
- Object.assign( this, saved );
487
- this.stack.length = length;
488
- --this.tokenIdx;
489
- return match2;
490
- }
491
-
492
- // Now the helper methods =====================================================
493
-
494
- // Standard weak-conflict predicate -------------------------------------------
495
-
496
- /**
497
- * Return whether current token (its type and keyword are args - TODO delete?)
498
- * would be matched when starting at the current state:
499
- * - true/false are definite answers,
500
- * - null: reached end-of-rule (let caller decide what to do).
501
- *
502
- * Changes by side-effect:
503
- * - this.s
504
- * - with mode='P' (first step in keyword prediction) if a rule is called:
505
- * this.stack, this.dynamic_, this.prec_
506
- *
507
- * Conditions are only evaluated with mode='M' (expected set in msgs) or if
508
- * condition is listed in `this.leanConditions`.
509
- */
510
- _pred_next( type, keyword, mode ) { // mode = P | K | I | E | R | M
511
- const properCall = (mode === 'P');
512
- const lean = (mode !== 'M'); // TODO: extra method with conditions ?
513
- // TODO: if false, use condition in this.leanConditions
514
- let hasMatchedToken = null; // undecided yet calculate on demand
515
- while (this.s) {
516
- if (lean)
517
- this._traceSubPush( this.s );
518
- else
519
- this._tracePush( this.s ); // TODO: push new state instead
520
- let cmd = this.table[this.s];
521
- if (!Array.isArray( cmd )) {
522
- const lookahead = cmd[' lookahead'];
523
- const c = lookahead // TODO: call with { keyword, type } ?
524
- ? cmd[this[lookahead]( mode )]
525
- : keyword && cmd[keyword] || cmd[type];
526
- cmd = !(c && this._rejectCondition( c, mode, lean )) && c || cmd[''];
527
- }
528
- const state = this.s;
529
- this.s = cmd[1];
530
- switch (cmd[0]) {
531
- case 'c': case 'ck': case 'ckA': // TODO: re-check ckA
532
- return true;
533
- case 'ciA': // TODO: fixKeywordTokenIdx ?
534
- return mode !== 'F';
535
- // in the R prediction for optional `Id<weak>` at rule end, only
536
- // alternative keyword matches are preferred, not identifier matches
537
- // TODO: delete this prediction
538
- case 'ci':
539
- if (!keyword ||
540
- !this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx)
541
- return mode !== 'F';
542
- cmd = this.table[state]['']; // is currently always 'g' or 'e'
543
- this.s = cmd[1];
544
- break;
545
- case 'm':
546
- return type === cmd[2];
547
- case 'mi':
548
- return type === 'Id' && mode !== 'F' &&
549
- (!keyword ||
550
- !this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx);
551
- case 'miA':
552
- return type === 'Id' && mode !== 'F';
553
- case 'mk':
554
- return keyword === cmd[2];
555
- case 'g': case 'e':
556
- break;
557
- default:
558
- if (typeof cmd[0] !== 'number')
559
- throw Error( `Unexpected command ${ cmd[0] } at state ${ state }` );
560
- // If the parser enters a rule, reaching the rule end (can happen with
561
- // option `minTokensMatched`) means "no match".
562
- hasMatchedToken = false;
563
- // If we want to support conditions before matching the first token in a
564
- // rule, we would have to handle `this.stack` and `this.dynamically_`.
565
- if (properCall) {
566
- // rule_() - TODO: also w/ conditions before matching first token
567
- this.stack.push( {
568
- ruleState: cmd[1],
569
- followState: cmd[0],
570
- tokenIdx: this.tokenIdx,
571
- prec: this.prec_,
572
- } );
573
- this.dynamic_ = Object.create( this.dynamic_ );
574
- this.prec_ = null;
575
- }
576
- }
577
- // We could optimize with rule call - only 'Id' must be further investigated
578
- // TODO: actually also with `g`
579
- // in both cases if no condition is evaluated
580
- // TODO <prepare=…, arg=…> for real trial run also before all returns
581
- // if (cmd[5])
582
- // this.cmd[5]( cmd[4], mode );
583
- }
584
- // If invalid state, the second token does not match, e.g. for `VIRTUAL +`
585
- // or `VIRTUAL ⎀` (with IllegalToken):
586
- if (this.s == null)
587
- return false;
588
-
589
- // Otherwise, the parser could end the rule after having matched the keyword
590
- // with prediction. TODO: as we do not look behind the current rule for the
591
- // prediction, the tool can normally omit the prediction (and output a
592
- // message), no so with `ruleStartingWithUnreserved`. We will rather look
593
- // behind the current rule _after_ having decided that the token is to be
594
- // matched as identifier.
595
- return (hasMatchedToken ?? this.tokenIdx > this.stack.at( -1 ).tokenIdx)
596
- && null; // let caller decide how to interpret this
597
- }
598
-
599
- _rejectCondition( cmd, mode, lean ) {
600
- const cond = cmd[3];
601
- if (!cond || lean && !this.leanConditions[cond])
602
- return false;
603
- if (!this.constructor.tracingParser)
604
- return !!this[cond]( cmd[4], mode );
605
- // TODO: let this[cond]( true ) return recovery badness in error case
606
- if (!lean) {
607
- const { traceName } = this[cond];
608
- this._tracePush( [ 'C', traceName?.call( this, cmd[4] ) ?? cond ] );
609
- // calling the condition might have side effects (precendence conditions have)
610
- // → call tracing “name” before
611
- }
612
- const succeed = !this[cond]( cmd[4], mode );
613
- this._traceSubPush( lean ? { true: 'C✔', false: 'C✖' }[succeed] : succeed );
614
- return !succeed;
615
- }
616
-
617
- _matchesInFollow( type, keyword, mode ) { // mode = E | R and K | I
618
- // TODO: now also set stack!
619
- const savedState = this.s;
620
- // TODO: caching
621
- const { dynamic_ } = this;
622
- let match;
623
- let depth = this.stack.length;
624
- // TODO: currently assumes that lookahead does not use stack.at()
625
- while (match == null && --depth) {
626
- this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
627
- this.s = this.stack[depth].followState;
628
- // TODO: this.prec_ ?
629
- match = this._pred_next( type, keyword, mode );
630
- this._traceSubPush( match == null ? 0 : match === (mode !== 'F') );
631
- // successfully matching a keyword in giR() means unsuccessful match as
632
- // reserved identifer
633
- // TODO: this.stack ?
634
- }
635
- this.dynamic_ = dynamic_;
636
- this.s = savedState;
637
- return match;
638
- }
639
-
640
- _confirmExpected( token, saved ) { // mode = M
641
- const fix = /^[_a-z]/.test( token );
642
- const [ type, keyword ] = (fix) ? [ 'Id', token ] : [ token ];
643
- Object.assign( this.la(), { type, keyword } );
644
- this._cloneFromSaved( saved );
645
- this.fixKeywordTokenIdx = fix && this.tokenIdx;
646
- this.trace = [];
647
- let match;
648
- while (this.stack.length) {
649
- match = this._pred_next( type, keyword, 'M' );
650
- if (match != null) {
651
- this._tracePush( { true: '✔', false: '✖' }[match] );
652
- break;
653
- }
654
- this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
655
- this.s = this.stack.pop().followState;
656
- }
657
- if (this.constructor.tracingParser) {
658
- this.stack = saved.stack; // influences indentation
659
- this._trace( tokenName( token ), 2 );
660
- }
661
- return match ?? true;
662
- }
663
-
664
- // Set of expected and sync tokens: for error reporting and recovery ----------
665
-
666
- // Calculate array of expected tokens / error sync set
667
- _calculateTokenSet( mode ) { // mode = M | Y
668
- this._tracePush( [ mode ] );
669
- // TODO later (after trying different synchronization tokens), we could use
670
- // one set for both M and Y, the latter just adds more tokens to it
671
- const savedState = this.s;
672
- const savedDynamic = this.dynamic_;
673
- const savedStack = this.stack;
674
- this.stack = [ ...savedStack ];
675
- this.s = this.errorState;
676
-
677
- const set = Object.create(null);
678
- // Add follow sets of outer rules if at potential rule end
679
- if (mode === 'M') { // for messages
680
- while (this.stack.length && this._tokenSetInRule( set, true )) {
681
- this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
682
- this.s = this.stack.pop().followState;
683
- }
684
- }
685
- else { // or always when calculating the sync-set
686
- let val = this.stack.length + 1;
687
- while (this.stack.length) {
688
- this._tokenSetInRule( set, val ); // TODO: use if Y-M unification
689
- val = this.stack.length;
690
- // TODO: use new _tracePush if `val` changes, probably also use Y‹val›(…)
691
- this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
692
- this.s = this.stack.pop().followState;
693
- }
694
- set.EOF ??= 0; // TODO: really necessary, see also _findSyncToken()
695
- }
696
- this.stack = savedStack;
697
- this.s = savedState; // should be the errorState anyway - TODO: confirm
698
- this.dynamic_ = savedDynamic;
699
- return set;
700
- }
701
-
702
- // Filter after this fn for conditions via interpreter call after: consider
703
- // ( <prefer, guard=fail> 'foo' | rule ) with
704
- // rule : 'foo' | Id ;
705
- // doing it already here would list `foo` as expected token
706
- _tokenSetInRule( expecting, val, cmd, collectKeywordsAndIdOnly = false ) {
707
- const savedDynamic = this.dynamic_;
708
- const savedState = this.s;
709
- let enteredRules = 0;
710
- loop: while (this.s) {
711
- cmd ??= this.table[this.s];
712
- if (!Array.isArray( cmd )) {
713
- const lookahead = cmd[' lookahead'];
714
- const dict = cmd;
715
- for (const prop in dict) {
716
- if (prop && Object.hasOwn( dict, prop ) && prop !== 'Id' &&
717
- !Object.hasOwn( expecting, prop ) && prop.charAt(0) !== ' ')
718
- this.addTokenToSet_( expecting, prop, val, collectKeywordsAndIdOnly, lookahead );
719
- }
720
- cmd = dict[''];
721
- if (dict.Id) {
722
- // recursive call only if Id branch with non-error default branch
723
- if (cmd[0] === 'e') {
724
- collectKeywordsAndIdOnly = true;
725
- cmd = dict.Id;
726
- }
727
- else { // Id branch never leads to rule exit (really?):
728
- this._tracePush( [ '[' ] );
729
- this._tokenSetInRule( expecting, val, dict.Id, true );
730
- this._tracePush( [ ']' ] );
731
- }
732
- }
733
- }
734
- this._traceSubPush( this.s );
735
- switch (cmd[0]) {
736
- case 'm': case 'mk':
737
- this.addTokenToSet_( expecting, cmd[2], val, collectKeywordsAndIdOnly );
738
- break loop;
739
- case 'ci': case 'ciA': case 'mi': case 'miA':
740
- this.addTokenToSet_( expecting, 'Id', val, false );
741
- // TODO: should we do s/th special, such that a reserved word is a sync
742
- // token for Id<greedy>? Probably not, see also comment in
743
- // _findSyncToken()
744
- break loop;
745
- case 'g': case 'gi': case 'e':
746
- break;
747
- default:
748
- if (typeof cmd[0] !== 'number')
749
- throw Error( `Unexpected command ${ cmd[0] } at state ${ this.s }` );
750
- ++enteredRules; // conditions might use stack/dynamic_
751
- // core rule_():
752
- this.stack.push( {
753
- ruleState: cmd[1],
754
- followState: cmd[0],
755
- tokenIdx: this.tokenIdx,
756
- prec: this.prec_,
757
- } );
758
- this.dynamic_ = Object.create( this.dynamic_ );
759
- this.prec_ = null;
760
- }
761
- this.s = cmd[1];
762
- cmd = null;
763
- }
764
- const inspectOuterRules = (this.s === 0 && !enteredRules);
765
- this.s = savedState;
766
- this.dynamic_ = savedDynamic;
767
- this.stack.length -= enteredRules;
768
- return inspectOuterRules;
769
- }
770
-
771
- // Remark: when called for `Id` token, `collectKeywordsOnly` is `false`
772
- addTokenToSet_( set, token, val, collectKeywordsOnly, _lookahead ) {
773
- if (!collectKeywordsOnly || /^[_a-z]/.test( token ))
774
- set[token] ??= val;
775
- }
776
-
777
- // Error reporting and recovery -----------------------------------------------
778
-
779
- expectingArray_() {
780
- const token = this.la();
781
- const set = this._calculateTokenSet( 'M' );
782
- // Speed-up: delete current token
783
- const { keyword, type } = token;
784
- if (keyword && set[keyword] === true)
785
- delete set[keyword];
786
- else if (set[type] === true && !(keyword && this.keywords[keyword] != null))
787
- delete set[type]; // delete if not keyword
788
-
789
- this._trace( 'collect tokens for message' );
790
- const { trace } = this;
791
- const saved = this._saveForWalk();
792
- saved.fixKeywordTokenIdx = this.fixKeywordTokenIdx; // changed by confirmExpected
793
- const expecting = Object.keys( set )
794
- .filter( tok => this._confirmExpected( tok, saved ) );
795
- token.type = type; // overwritten by _confirmExpected
796
- token.keyword = keyword;
797
- Object.assign( this, saved );
798
- this.trace = trace;
799
- return expecting;
800
- }
801
-
802
- _findSyncToken( syncSet ) { // only called from _reportAndRecover()
803
- const rewindDepth = this.stack.length
804
- this.recoverTokenIdx = this.tokenIdx; // TODO: make it part of the return value?
805
- while (this.recoverTokenIdx < this.tokens.length) {
806
- const { keyword, type } = this.tokens[this.recoverTokenIdx];
807
- let recoverDepth = keyword ? syncSet[keyword] : null;
808
- if (recoverDepth != null)
809
- return recoverDepth;
810
- recoverDepth = syncSet[type];
811
- // sync to Id only if in intra-rule expected set of last good state or if after ';'/`}`
812
- if (recoverDepth != null &&
813
- (type !== 'Id' || (!keyword || !this.keywords[keyword]) &&
814
- // reserved words do not match Id in expected-set → as method
815
- (recoverDepth > rewindDepth ||
816
- [ ';', '}' ].includes( this.tokens[this.recoverTokenIdx - 1].type ))))
817
- // if (recoverDepth != null &&
818
- // (this.recoverTokenIdx > this.tokenIdx ||
819
- return recoverDepth;
820
- ++this.recoverTokenIdx;
821
- }
822
- throw Error( 'EOF must be last in `tokens`' ); // TODO: really necessary?
823
- }
824
-
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 );
845
- this.s = null;
846
- let depth = this.stack.length;
847
- if (recoverDepth > depth) { // no rewind, no rule exit
848
- this.trace = [ this.errorState ]; // show last good state in trace
849
- this.s = this.errorState;
850
- }
851
- while (depth > recoverDepth)
852
- this.stack[--depth].followState = null;
853
- // TODO: when the error is due to failed rule exit prediction, try to keep
854
- // existing followState (if that reaches RuleEnd_)
855
- // Continue parsing: ignore next predicate (TODO: except some specified ones?)
856
-
857
- // TODO: re-check for rule calls which are at the optional rule end:
858
- // x: 'x not'; b: 'b'? x {console.log('x→b')} 'b'?; a: b {console.log('b→a')} 'a'
859
- // with start rule `a` and input `x a`: output should be x→b + b→a
860
- // with start rule `a` and input `b a`: output should be b→a
861
-
862
- if (this.constructor.tracingParser) {
863
- this._trace( `skipped ${ this.recoverTokenIdx - this.tokenIdx } tokens to recover from error`,
864
- this.tokens[this.recoverTokenIdx] );
865
- }
866
- if (this.tokenIdx > this.recoverTokenIdx)
867
- this.reuseToken_();
868
- else while (this.tokenIdx < this.recoverTokenIdx)
869
- this.skipToken_();
870
- this.conditionTokenIdx = this.tokenIdx;
871
- this.conditionStackLength = null;
872
- return false;
873
- }
874
-
875
- // small methods --------------------------------------------------------------
876
-
877
- log( ...args ) {
878
- console.log( ...args );
879
- }
880
-
881
- reportError_( location, text ) {
882
- this.$hasErrors = true;
883
- this.log( `${ location }:`, text );
884
- }
885
-
886
- reportUnexpectedToken_( msg ) {
887
- const token = (msg === 'reuse') ? this.lb() : this.la();
888
- msg ??= `Unexpected token ${ tokenFullName( token, ': ' ) }`;
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 );
893
- }
894
-
895
- reportReservedWord_() {
896
- this.reportUnexpectedToken_( `Unexpected reserved word ‘${ this.la().text }’` );
897
- }
898
-
899
- errorAndRecoverOutside( token, text ) { // TODO: re-check
900
- // TODO: TMP
901
- this.reportError_( token.location, text );
902
- while (this.l() !== ';')
903
- this.skipToken_();
904
- this.s = null;
905
- return false;
906
- }
907
-
908
- _tracePush( state ) {
909
- if (this.constructor.tracingParser)
910
- this.trace.push( state ?? '⚠' );
911
- }
912
- _traceSubPush( state ) {
913
- if (this.constructor.tracingParser)
914
- this.trace.at(-1).push( state );
915
- }
916
- traceAction( location ) { // TODO: remove
917
- this._trace( 1, location );
918
- }
919
-
920
- _trace( msg, la = this.la() ?? this.lb() ) {
921
- if (!this.constructor.tracingParser)
922
- return;
923
- // indentation according to rule call depth is nice, but only if without
924
- // excessive spaces → truncate:
925
- const indent = ' '.repeat( this.stack.length % 32 );
926
- if (msg === 1) {
927
- let line = ' execute action'; // align with non-action messages
928
- if (this.trace.length > 1) { // i.e. with some 'g' command
929
- line += ', states: ' + this.trace.map( traceStep ).join( ' → ' );
930
- this.trace = [ this.s ?? '⚠' ];
931
- }
932
- this.log( indent, line, `(${ la })` );
933
- return;
934
- }
935
- else if (la === 2) { // confirming tokens in expected set
936
- this.log( indent, ' ', msg + ':',
937
- this.trace.map( traceStep ).join( ' → ' ) );
938
- this.trace = [ this.s ?? '⚠' ];
939
- return;
940
- }
941
- const { location } = la;
942
- if (!this.trace.length) {
943
- this.log( `In ${ location.file }:` );
944
- this.trace = [ -1 ];
945
- }
946
- this.trace.push( this.s ?? '⚠' );
947
- if (Array.isArray( msg )) { // rule call and exit
948
- const [ intro, state, finale, exit ] = msg;
949
- let start = state;
950
- while (typeof this.table[--start] !== 'string')
951
- ;
952
- const post = (exit || start + 1 < state) && finale;
953
- msg = `${ intro } “${ this.table[start] }”${ post || '' } ${ exit || 'from' } stack level ${ this.stack.length }`;
954
- }
955
- // Yes, I know util.format, but do not want to have a `require` in this file
956
- const line = location.line < 1e5 ? ` ${ location.line }`.slice(-5) : `${ location.line }`;
957
- const col = location.col < 1e4 ? `:${ location.col } `.slice(0,5) : `:${location.col }`;
958
- this.log( line + col + indent + msg + ', states:',
959
- this.trace.map( traceStep ).join( ' → ' ) );
960
- this.trace = [ this.s ?? '⚠' ];
961
- }
962
-
963
- inSameRule_( lowState = this.s, highState = this.stack.at(-1).followState ) {
964
- if (lowState > highState)
965
- [ lowState, highState ] = [ highState, lowState ];
966
- while (lowState < highState) {
967
- if (typeof this.table[++lowState] === 'string') // rule boundary
968
- return false;
969
- }
970
- return true;
971
- }
972
-
973
- // Predefined conditions with extra option names:
974
-
975
- hide_( _arg, mode ) {
976
- return mode === 'M';
977
- }
978
- precLeft_( prec ) { // <prec=…>, <…,assoc=left>, <…,prefix=once>
979
- const parentPrec = this.stack.at( -1 ).prec;
980
- if (parentPrec != null && parentPrec >= prec)
981
- return true;
982
- this.prec_ = prec;
983
- return false;
984
- }
985
- precRight_( prec ) { // <…,assoc=right>, <…,prefix>
986
- const parentPrec = this.stack.at( -1 ).prec;
987
- if (parentPrec != null && parentPrec >= prec)
988
- return true;
989
- this.prec_ = prec - 1;
990
- return false;
991
- }
992
- precNone_( prec ) { // <…,assoc=none>, <…,postfix=once>
993
- const parentPrec = this.stack.at( -1 ).prec;
994
- if (parentPrec != null && parentPrec >= prec ||
995
- this.prec_ != null && this.prec_ <= prec)
996
- return true;
997
- this.prec_ = prec;
998
- return false;
999
- }
1000
- precPost_( prec ) { // <…,postfix>
1001
- const parentPrec = this.stack.at( -1 ).prec;
1002
- if (parentPrec != null && parentPrec >= prec ||
1003
- this.prec_ != null && this.prec_ < prec)
1004
- return true;
1005
- this.prec_ = prec;
1006
- return false;
1007
- }
1008
- }
1009
- const members = BaseParser.prototype;
1010
- // functions below are to be called with `call` to set `this`
1011
-
1012
- members.isNoKeywordInRuleFollow.afterError = true;
1013
-
1014
- members.precLeft_.traceName = function( prec ) {
1015
- const parentPrec = this.stack.at( -1 ).prec;
1016
- return `${ parentPrec ?? '-∞' }<${ prec }`;
1017
- }
1018
- members.precRight_.traceName = function( prec ) {
1019
- const left = this.precLeft_.traceName.call( this, prec );
1020
- return `${ left },↓`;
1021
- }
1022
- members.precNone_.traceName = function( prec ) {
1023
- const left = this.precLeft_.traceName.call( this, prec );
1024
- return `${ left }<${ this.prec_ == null ? '∞' : this.prec_ }`;
1025
- }
1026
- members.precPost_.traceName = function( prec ) {
1027
- const left = this.precLeft_.traceName.call( this, prec );
1028
- return `${ left }≤${ this.prec_ == null ? '∞' : this.prec_ }`;
1029
- }
1030
-
1031
- function traceStep( step ) {
1032
- if (!Array.isArray( step ))
1033
- return step;
1034
- const result = { true: '✔', false: '✖' }[step.at( -1 )] ?? '';
1035
- const intro = (typeof step[1] === 'number') ? '→' : '';
1036
- const arg = step.slice( 1, result ? -1 : undefined ).join( '→' );
1037
- return `${ step[0] }(${ intro }${ arg })${ result }`;
15
+ keywords;
16
+ table;
17
+ lexer;
18
+
19
+ tokens = undefined;
20
+ tokenIdx = 0;
21
+ conditionTokenIdx = -1;
22
+ errorTokenIdx = -1;
23
+ recoverTokenIdx = -1;
24
+ reuseErrorTokenIdx = null;
25
+ fixKeywordTokenIdx = -1;
26
+ conditionStackLength = -1;
27
+ nextTokenAsId = false;
28
+
29
+ s = null;
30
+ errorState = null;
31
+ stack = [];
32
+ dynamic_ = {};
33
+ prec_ = null;
34
+ $hasErrors = null;
35
+ leanConditions = {};
36
+ _trace = [];
37
+
38
+ constructor( lexer, keywords, table ) {
39
+ this.keywords = { __proto__: null, ...keywords };
40
+ this.table = compileTable( table );
41
+ this.lexer = lexer;
42
+ }
43
+
44
+ init() {
45
+ this.lexer.tokenize( this );
46
+ return this;
47
+ }
48
+
49
+ _runTransparently( callback ) {
50
+ const { tokenIdx } = this;
51
+ const saved = this._saveForWalk();
52
+ const { length } = this.stack;
53
+ const r = callback();
54
+ this.stack.length = length;
55
+ Object.assign( this, saved );
56
+ this.tokenIdx = tokenIdx;
57
+ return r;
58
+ }
59
+
60
+ _saveForWalk() {
61
+ return {
62
+ s: this.s,
63
+ stack: this.stack,
64
+ dynamic_: this.dynamic_,
65
+ prec_: this.prec_,
66
+ };
67
+ }
68
+
69
+ _cloneFromSaved( saved ) {
70
+ this.s = saved.s;
71
+ this.stack = saved.stack.map( obj => ({ ...obj }) );
72
+ this.dynamic_ = this._cloneDynamic( saved.dynamic_ );
73
+ this.prec_ = saved.prec_;
74
+ }
75
+
76
+ _cloneDynamic( dynamic_ ) {
77
+ let chain = [];
78
+ while (dynamic_ !== Object.prototype) {
79
+ const obj = {};
80
+ for (const [ prop, val ] of Object.entries( dynamic_ ))
81
+ obj[prop] = Array.isArray( val ) ? [ ...val ] : val;
82
+ chain.push( obj );
83
+ dynamic_ = Object.getPrototypeOf( dynamic_ );
84
+ }
85
+ let copy = Object.prototype;
86
+ let { length } = chain;
87
+ while (--length >= 0)
88
+ copy = { __proto__: copy, ...chain[length] };
89
+ return copy;
90
+ }
91
+
92
+
93
+
94
+ la() {
95
+ return this.tokens[this.tokenIdx];
96
+ }
97
+ lb( k = 1 ) {
98
+ return this.tokens[this.tokenIdx - k];
99
+ }
100
+ lr() {
101
+ return this.tokens[this.stack[this.stack.length - 1].tokenIdx];
102
+ }
103
+
104
+
105
+
106
+ l() {
107
+ return this.la().type;
108
+ }
109
+
110
+
111
+ lk() {
112
+ const la = this.la();
113
+ if (!this.nextTokenAsId)
114
+ return la.keyword || la.type;
115
+
116
+ this.nextTokenAsId = false;
117
+ return la.type;
118
+ }
119
+
120
+ e() {
121
+ const la = this.la();
122
+
123
+ if (this.errorTokenIdx === this.tokenIdx &&
124
+ (this.reuseErrorTokenIdx == null && this.reuseErrorTokenIdx < this.tokenIdx))
125
+ throw Error( `Already reported error for ${ tokenFullName( la ) } at ${ la.location }`);
126
+
127
+ la.parsedAs = '';
128
+ this.errorTokenIdx = this.tokenIdx;
129
+ this.conditionStackLength = null;
130
+
131
+ let { length } = this.stack;
132
+ while (--length && this.tokenIdx === this.stack[length].tokenIdx)
133
+ this.stack[length].followState = null;
134
+ if (++length === this.stack.length)
135
+ return this._reportAndRecover();
136
+
137
+ this.stack[length].followState = this.errorState;
138
+ this.s = null;
139
+ return false;
140
+ }
141
+
142
+
143
+ ei() {
144
+ if (!this.la().keyword)
145
+ return this.e();
146
+ this.nextTokenAsId = true;
147
+ return false;
148
+ }
149
+
150
+
151
+
152
+
153
+ gr( follow ) {
154
+ if (this.stack[this.stack.length - 1].tokenIdx === this.tokenIdx)
155
+
156
+ return this.e();
157
+ this.s = 0;
158
+
159
+
160
+ const { type, keyword } = this.tokens[this.tokenIdx];
161
+ if (keyword &&
162
+ follow?.[0] === 'Id' && !this.keywords[keyword] &&
163
+ this.fixKeywordTokenIdx !== this.tokenIdx ||
164
+ follow?.includes( keyword || type ))
165
+ return true
166
+
167
+ const match = this._matchesInFollow( type, keyword, 'E' );
168
+
169
+
170
+
171
+ return (match ?? true) || this.e();
172
+ }
173
+
174
+
175
+ g( state, follow ) {
176
+ if (!(state == null ? this.e() : state || this.gr( follow )))
177
+ return false;
178
+ this.s = state;
179
+ return true
180
+ }
181
+
182
+
183
+ giA( state, follow ) {
184
+ if (!this.tokens[this.tokenIdx].keyword)
185
+ return this.g( state, follow );
186
+ this.nextTokenAsId = true;
187
+ return false;
188
+ }
189
+
190
+
191
+ gi( state, follow ) {
192
+ const lk = this.tokens[this.tokenIdx].keyword;
193
+
194
+
195
+ if (!lk || this.keywords[lk])
196
+ return this.g( state, follow );
197
+ this.nextTokenAsId = true;
198
+ return false;
199
+ }
200
+
201
+
202
+ gP( state, follow ) {
203
+ return this.lP( follow ) && this.g( state );
204
+ }
205
+
206
+
207
+
208
+ m( state, token ) {
209
+ return (this.tokens[this.tokenIdx].type === token)
210
+ ? this.c( state )
211
+ : this.e();
212
+ }
213
+
214
+
215
+ mi( state, ident = true ) {
216
+ return (this.tokens[this.tokenIdx].type === 'Id')
217
+ ? this.ci( state, ident )
218
+ : this.e();
219
+ }
220
+
221
+
222
+ miA( state, ident = true ) {
223
+ return (this.tokens[this.tokenIdx].type === 'Id')
224
+ ? this.ciA( state, ident )
225
+ : this.e();
226
+ }
227
+
228
+
229
+ mk( state, token ) {
230
+ return (this.tokens[this.tokenIdx].keyword === token)
231
+ ? this.ck( state )
232
+ : this.e();
233
+ }
234
+
235
+ c( state, parsedAs = 'token' ) {
236
+ const la = this.tokens[this.tokenIdx++];
237
+ la.parsedAs = parsedAs;
238
+ this.s = state;
239
+ this.errorState = state;
240
+ return true
241
+ }
242
+
243
+
244
+ ci( state, ident = 'ident' ) {
245
+ if (this.tokenIdx === this.fixKeywordTokenIdx)
246
+ return this.e();
247
+ const la = this.tokens[this.tokenIdx];
248
+
249
+
250
+ if (this.keywords[la.keyword]) {
251
+
252
+ if (this._runTransparently( () => {
253
+ ++this.tokenIdx;
254
+ this.s = state;
255
+ const { type, keyword } = this.la();
256
+ return !(this._pred_next( type, keyword, 'R' ) ??
257
+ this._matchesInFollow( type, keyword, 'R' ));
258
+ } ))
259
+ return this.e();
260
+ this.reportReservedWord_();
261
+
262
+ }
263
+ return this.c( state, ident )
264
+ }
265
+
266
+
267
+ ciA( state, ident = 'ident' ) {
268
+ return this.c( state, ident )
269
+ }
270
+
271
+
272
+ ck( state ) {
273
+ return this.c( state, 'keyword' )
274
+ }
275
+
276
+
277
+ ckP( state, first2 ) {
278
+ return this.lP( first2 ) && this.ck( state );
279
+ }
280
+
281
+
282
+ ckA( state ) {
283
+
284
+ return this.c( state, (this.l() === 'Id' ? 'keyword' : 'token') );
285
+ }
286
+
287
+ skipToken_() {
288
+ ++this.tokenIdx;
289
+ }
290
+
291
+ reuseToken_() {
292
+ --this.tokenIdx;
293
+ }
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+ gc( state, cond, arg ) {
303
+ if (this.conditionTokenIdx === this.tokenIdx &&
304
+ this.conditionStackLength == null &&
305
+ !this[cond].afterError)
306
+ return true
307
+
308
+
309
+
310
+
311
+
312
+ const fail = this[cond]( arg, true );
313
+
314
+
315
+
316
+
317
+ if (fail) {
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+ const { keyword } = this.la();
326
+ if (keyword && this.table[this.s][keyword])
327
+ this.fixKeywordTokenIdx = this.tokenIdx;
328
+ this.conditionTokenIdx = this.tokenIdx;
329
+ this.conditionStackLength = this.stack.length;
330
+ this.conditionName = cond;
331
+
332
+
333
+ this.conditionFailure = fail;
334
+ }
335
+ return !fail || this.g( state ) && false;
336
+ }
337
+
338
+ ec( cond, arg ) {
339
+ return this.gc( null, cond, arg );
340
+ }
341
+
342
+
343
+ isNoKeywordInRuleFollow() {
344
+ const { keyword } = this.la();
345
+ if (!keyword || this.keywords[keyword] == null)
346
+ return false;
347
+ return this._matchesInFollow( 'Id', keyword, 'F' );
348
+
349
+ }
350
+
351
+
352
+
353
+ rule_( state, followState = -1 ) {
354
+ this.s = state;
355
+
356
+ this.stack.push( {
357
+ ruleState: state,
358
+ followState,
359
+ tokenIdx: this.tokenIdx,
360
+ prec: this.prec_,
361
+ } );
362
+ this.dynamic_ = Object.create( this.dynamic_ );
363
+ this.prec_ = null;
364
+ this.errorState ??= state;
365
+ }
366
+
367
+ exit_() {
368
+ if (this.s)
369
+ throw Error( `this.s === ${ this.s }; illegally set by action, or runtime/generator bug` );
370
+ this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
371
+ const caller = this.stack.pop();
372
+ const immediately = this.tokenIdx === caller.tokenIdx && this.tokenIdx >= this.errorTokenIdx;
373
+ if (this.constructor.tracingParser) {
374
+ const post = this.s == null &&
375
+ (immediately
376
+ ? ' immediately'
377
+ : caller.followState == null
378
+ ? ' unsuccessfully'
379
+ : ' prematurely');
380
+ const text = immediately ? '⚠ exit rule' : '⏎ exit rule';
381
+ this.s = caller.followState;
382
+
383
+
384
+ }
385
+ this.prec_ = caller.prec;
386
+ this.s = caller.followState;
387
+ if (immediately)
388
+ return this.s != null && this._reportAndRecover();
389
+
390
+ if (this.s != null)
391
+ this.errorState = this.s;
392
+ return true;
393
+ }
394
+
395
+
396
+
397
+
398
+
399
+ lP( first2 ) {
400
+
401
+ const { keyword: lk1 } = this.tokens[this.tokenIdx];
402
+ if (!lk1 || this.keywords[lk1] !== 0 || this.fixKeywordTokenIdx === this.tokenIdx)
403
+ return true;
404
+
405
+
406
+ const { type: lt2, keyword: lk2 } = this.tokens[this.tokenIdx + 1];
407
+ if (lt2 === 'IllegalToken')
408
+ return true
409
+
410
+ if (lk2 && first2?.[0] === 'Id' && !this.keywords[lk2] ||
411
+ first2?.includes( lk2 || lt2 ))
412
+ return true
413
+
414
+ if (this._walkPred( this.table[this.s][lk1], lk1, lt2, lk2 ))
415
+ return true;
416
+
417
+ const choice = this.table[this.s];
418
+ if (!this._walkPred( choice.Id || choice[''], null, lt2, lk2 ))
419
+ return true;
420
+ this.nextTokenAsId = true;
421
+ return false;
422
+ }
423
+
424
+ _walkPred( cmd, lk1, lt2, lk2 ) {
425
+ const saved = this._saveForWalk();
426
+ const { length } = this.stack;
427
+ if (typeof cmd[0] !== 'number')
428
+ this.s = cmd[1];
429
+ if (cmd[0] !== (lk1 ? 'ck' : 'ci')) {
430
+
431
+ let match1 = this._pred_next( 'Id', lk1, 'P' );
432
+ if (!match1) {
433
+ if (lk1) {
434
+
435
+ const { location } = this.la();
436
+ throw Error( `Cannot match first prediction token at ${ location.line }:${ location.col } in rule at state ${ saved.s }` );
437
+ }
438
+
439
+ if (match1 == null)
440
+ match1 = this._matchesInFollow( 'Id', lk1, 'I' );
441
+ Object.assign( this, saved );
442
+ this.stack.length = length;
443
+ return !!match1;
444
+ }
445
+ }
446
+
447
+ ++this.tokenIdx;
448
+ const mode = lk1 ? 'K' : 'I';
449
+ let match2 = this._pred_next( lt2, lk2, mode );
450
+
451
+ if (match2 == null)
452
+ match2 = !!this._matchesInFollow( lt2, lk2, mode );
453
+
454
+ Object.assign( this, saved );
455
+ this.stack.length = length;
456
+ --this.tokenIdx;
457
+ return match2;
458
+ }
459
+
460
+
461
+
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+
470
+
471
+
472
+
473
+
474
+
475
+
476
+
477
+
478
+ _pred_next( type, keyword, mode ) {
479
+ const properCall = (mode === 'P');
480
+ const lean = (mode !== 'M');
481
+
482
+ let hasMatchedToken = null;
483
+ while (this.s) {
484
+
485
+
486
+ let cmd = this.table[this.s];
487
+ if (!Array.isArray( cmd )) {
488
+ const lookahead = cmd[' lookahead'];
489
+ const c = lookahead
490
+ ? cmd[this[lookahead]( mode )]
491
+ : keyword && cmd[keyword] || cmd[type];
492
+ cmd = !(c && this._rejectCondition( c, mode, lean )) && c || cmd[''];
493
+ }
494
+ const state = this.s;
495
+ this.s = cmd[1];
496
+ switch (cmd[0]) {
497
+ case 'c': case 'ck': case 'ckA':
498
+ return true;
499
+ case 'ciA':
500
+ return mode !== 'F';
501
+
502
+
503
+
504
+ case 'ci':
505
+ if (!keyword ||
506
+ !this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx)
507
+ return mode !== 'F';
508
+ cmd = this.table[state][''];
509
+ this.s = cmd[1];
510
+ break;
511
+ case 'm':
512
+ return type === cmd[2];
513
+ case 'mi':
514
+ return type === 'Id' && mode !== 'F' &&
515
+ (!keyword ||
516
+ !this.keywords[keyword] && this.fixKeywordTokenIdx !== this.tokenIdx);
517
+ case 'miA':
518
+ return type === 'Id' && mode !== 'F';
519
+ case 'mk':
520
+ return keyword === cmd[2];
521
+ case 'g': case 'e':
522
+ break;
523
+ default:
524
+ if (typeof cmd[0] !== 'number')
525
+ throw Error( `Unexpected command ${ cmd[0] } at state ${ state }` );
526
+
527
+
528
+ hasMatchedToken = false;
529
+
530
+
531
+ if (properCall) {
532
+
533
+ this.stack.push( {
534
+ ruleState: cmd[1],
535
+ followState: cmd[0],
536
+ tokenIdx: this.tokenIdx,
537
+ prec: this.prec_,
538
+ } );
539
+ this.dynamic_ = Object.create( this.dynamic_ );
540
+ this.prec_ = null;
541
+ }
542
+ }
543
+
544
+
545
+
546
+
547
+
548
+
549
+ }
550
+
551
+
552
+ if (this.s == null)
553
+ return false;
554
+
555
+
556
+
557
+
558
+
559
+
560
+
561
+ return (hasMatchedToken ?? this.tokenIdx > this.stack.at( -1 ).tokenIdx)
562
+ && null;
563
+ }
564
+
565
+ _rejectCondition( cmd, mode, lean ) {
566
+ const cond = cmd[3];
567
+ if (!cond || lean && !this.leanConditions[cond])
568
+ return false;
569
+ if (!this.constructor.tracingParser)
570
+ return !!this[cond]( cmd[4], mode );
571
+
572
+
573
+
574
+
575
+ const succeed = !this[cond]( cmd[4], mode );
576
+
577
+ return !succeed;
578
+ }
579
+
580
+ _matchesInFollow( type, keyword, mode ) {
581
+
582
+ const savedState = this.s;
583
+
584
+ const { dynamic_ } = this;
585
+ let match;
586
+ let depth = this.stack.length;
587
+
588
+ while (match == null && --depth) {
589
+ this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
590
+ this.s = this.stack[depth].followState;
591
+
592
+ match = this._pred_next( type, keyword, mode );
593
+
594
+
595
+
596
+
597
+ }
598
+ this.dynamic_ = dynamic_;
599
+ this.s = savedState;
600
+ return match;
601
+ }
602
+
603
+ _confirmExpected( token, saved ) {
604
+ const fix = /^[_a-z]/.test( token );
605
+ const [ type, keyword ] = (fix) ? [ 'Id', token ] : [ token ];
606
+ Object.assign( this.la(), { type, keyword } );
607
+ this._cloneFromSaved( saved );
608
+ this.fixKeywordTokenIdx = fix && this.tokenIdx;
609
+
610
+ let match;
611
+ while (this.stack.length) {
612
+ match = this._pred_next( type, keyword, 'M' );
613
+
614
+ if (match != null)
615
+ break;
616
+ this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
617
+ this.s = this.stack.pop().followState;
618
+ }
619
+
620
+ return match ?? true;
621
+ }
622
+
623
+
624
+
625
+
626
+ _calculateTokenSet( mode ) {
627
+
628
+
629
+
630
+ const savedState = this.s;
631
+ const savedDynamic = this.dynamic_;
632
+ const savedStack = this.stack;
633
+ this.stack = [ ...savedStack ];
634
+ this.s = this.errorState;
635
+
636
+ const set = Object.create(null);
637
+
638
+ if (mode === 'M') {
639
+ while (this.stack.length && this._tokenSetInRule( set, true )) {
640
+ this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
641
+ this.s = this.stack.pop().followState;
642
+ }
643
+ }
644
+ else {
645
+ let val = this.stack.length + 1;
646
+ while (this.stack.length) {
647
+ this._tokenSetInRule( set, val );
648
+ val = this.stack.length;
649
+
650
+ this.dynamic_ = Object.getPrototypeOf( this.dynamic_ );
651
+ this.s = this.stack.pop().followState;
652
+ }
653
+ set.EOF ??= 0;
654
+ }
655
+ this.stack = savedStack;
656
+ this.s = savedState;
657
+ this.dynamic_ = savedDynamic;
658
+ return set;
659
+ }
660
+
661
+
662
+
663
+
664
+
665
+ _tokenSetInRule( expecting, val, cmd, collectKeywordsAndIdOnly = false ) {
666
+ const savedDynamic = this.dynamic_;
667
+ const savedState = this.s;
668
+ let enteredRules = 0;
669
+ loop: while (this.s) {
670
+ cmd ??= this.table[this.s];
671
+ if (!Array.isArray( cmd )) {
672
+ const lookahead = cmd[' lookahead'];
673
+ const dict = cmd;
674
+ for (const prop in dict) {
675
+ if (prop && Object.hasOwn( dict, prop ) && prop !== 'Id' &&
676
+ !Object.hasOwn( expecting, prop ) && prop.charAt(0) !== ' ')
677
+ this.addTokenToSet_( expecting, prop, val, collectKeywordsAndIdOnly, lookahead );
678
+ }
679
+ cmd = dict[''];
680
+ if (dict.Id) {
681
+
682
+ if (cmd[0] === 'e') {
683
+ collectKeywordsAndIdOnly = true;
684
+ cmd = dict.Id;
685
+ }
686
+ else {
687
+
688
+ this._tokenSetInRule( expecting, val, dict.Id, true );
689
+
690
+ }
691
+ }
692
+ }
693
+
694
+ switch (cmd[0]) {
695
+ case 'm': case 'mk':
696
+ this.addTokenToSet_( expecting, cmd[2], val, collectKeywordsAndIdOnly );
697
+ break loop;
698
+ case 'ci': case 'ciA': case 'mi': case 'miA':
699
+ this.addTokenToSet_( expecting, 'Id', val, false );
700
+
701
+
702
+
703
+ break loop;
704
+ case 'g': case 'gi': case 'e':
705
+ break;
706
+ default:
707
+ if (typeof cmd[0] !== 'number')
708
+ throw Error( `Unexpected command ${ cmd[0] } at state ${ this.s }` );
709
+ ++enteredRules;
710
+
711
+ this.stack.push( {
712
+ ruleState: cmd[1],
713
+ followState: cmd[0],
714
+ tokenIdx: this.tokenIdx,
715
+ prec: this.prec_,
716
+ } );
717
+ this.dynamic_ = Object.create( this.dynamic_ );
718
+ this.prec_ = null;
719
+ }
720
+ this.s = cmd[1];
721
+ cmd = null;
722
+ }
723
+ const inspectOuterRules = (this.s === 0 && !enteredRules);
724
+ this.s = savedState;
725
+ this.dynamic_ = savedDynamic;
726
+ this.stack.length -= enteredRules;
727
+ return inspectOuterRules;
728
+ }
729
+
730
+
731
+ addTokenToSet_( set, token, val, collectKeywordsOnly, _lookahead ) {
732
+ if (!collectKeywordsOnly || /^[_a-z]/.test( token ))
733
+ set[token] ??= val;
734
+ }
735
+
736
+
737
+
738
+ expectingArray_() {
739
+ const token = this.la();
740
+ const set = this._calculateTokenSet( 'M' );
741
+
742
+ const { keyword, type } = token;
743
+ if (keyword && set[keyword] === true)
744
+ delete set[keyword];
745
+ else if (set[type] === true && !(keyword && this.keywords[keyword] != null))
746
+ delete set[type];
747
+
748
+
749
+ const saved = this._saveForWalk();
750
+ saved._trace = this._trace;
751
+ saved.fixKeywordTokenIdx = this.fixKeywordTokenIdx;
752
+ const expecting = Object.keys( set )
753
+ .filter( tok => this._confirmExpected( tok, saved ) );
754
+ token.type = type;
755
+ token.keyword = keyword;
756
+ Object.assign( this, saved );
757
+ return expecting;
758
+ }
759
+
760
+ _findSyncToken( syncSet ) {
761
+ const rewindDepth = this.stack.length
762
+ this.recoverTokenIdx = this.tokenIdx;
763
+ while (this.recoverTokenIdx < this.tokens.length) {
764
+ const { keyword, type } = this.tokens[this.recoverTokenIdx];
765
+ let recoverDepth = keyword ? syncSet[keyword] : null;
766
+ if (recoverDepth != null)
767
+ return recoverDepth;
768
+ recoverDepth = syncSet[type];
769
+
770
+ if (recoverDepth != null &&
771
+ (type !== 'Id' || (!keyword || !this.keywords[keyword]) &&
772
+
773
+ (recoverDepth > rewindDepth ||
774
+ [ ';', '}' ].includes( this.tokens[this.recoverTokenIdx - 1].type ))))
775
+
776
+
777
+ return recoverDepth;
778
+ ++this.recoverTokenIdx;
779
+ }
780
+ throw Error( 'EOF must be last in `tokens`' );
781
+ }
782
+
783
+ _reportAndRecover() {
784
+ this.s = this.errorState;
785
+ const syncSet = this._calculateTokenSet( 'Y' );
786
+
787
+
788
+ this.recoverTokenIdx = this.tokenIdx;;
789
+ const prev = this.lb();
790
+ const reuseRecoverDepth = this.reuseErrorTokenIdx != null &&
791
+ prev?.keyword && prev.parsedAs !== 'keyword' &&
792
+ this.reuseErrorTokenIdx < this.tokenIdx && syncSet[prev.keyword];
793
+
794
+
795
+
796
+ this.reportUnexpectedToken_( reuseRecoverDepth ? 'reuse' : null );
797
+
798
+ if (this.reuseErrorTokenIdx != null)
799
+ this.reuseErrorTokenIdx = (reuseRecoverDepth) ? this.tokenIdx : -1;
800
+ this.fixKeywordTokenIdx = (reuseRecoverDepth) ? --this.recoverTokenIdx : -1;
801
+
802
+ const recoverDepth = reuseRecoverDepth || this._findSyncToken( syncSet );
803
+ this.s = null;
804
+ let depth = this.stack.length;
805
+ if (recoverDepth > depth)
806
+ this.s = this.errorState;
807
+
808
+ while (depth > recoverDepth)
809
+ this.stack[--depth].followState = null;
810
+
811
+
812
+
813
+
814
+
815
+
816
+
817
+
818
+
819
+
820
+ if (this.tokenIdx > this.recoverTokenIdx)
821
+ this.reuseToken_();
822
+ else while (this.tokenIdx < this.recoverTokenIdx)
823
+ this.skipToken_();
824
+ this.conditionTokenIdx = this.tokenIdx;
825
+ this.conditionStackLength = null;
826
+ return false;
827
+ }
828
+
829
+
830
+
831
+ log( ...args ) {
832
+ console.log( ...args );
833
+ }
834
+
835
+ reportError_( location, text ) {
836
+ this.$hasErrors = true;
837
+ this.log( `${ location }:`, text );
838
+ }
839
+
840
+ reportUnexpectedToken_( msg ) {
841
+ const token = (msg === 'reuse') ? this.lb() : this.la();
842
+ msg ??= `Unexpected token ${ tokenFullName( token, ': ' ) }`;
843
+ msg = (msg === 'reuse')
844
+ ? `Missing input before keyword ${ tokenFullName( token, ': ' ) }`
845
+ : msg + ' - expecting: ' + this.expectingArray_().map( tokenName ).sort().join( ', ' );
846
+ this.reportError_( token.location, msg );
847
+ }
848
+
849
+ reportReservedWord_() {
850
+ this.reportUnexpectedToken_( `Unexpected reserved word ‘${ this.la().text }’` );
851
+ }
852
+
853
+ errorAndRecoverOutside( token, text ) {
854
+
855
+ this.reportError_( token.location, text );
856
+ while (this.l() !== ';')
857
+ this.skipToken_();
858
+ this.s = null;
859
+ return false;
860
+ }
861
+
862
+ inSameRule_( lowState = this.s, highState = this.stack.at(-1).followState ) {
863
+ if (lowState > highState)
864
+ [ lowState, highState ] = [ highState, lowState ];
865
+ while (lowState < highState) {
866
+ if (typeof this.table[++lowState] === 'string')
867
+ return false;
868
+ }
869
+ return true;
870
+ }
871
+
872
+
873
+
874
+ hide_( _arg, mode ) {
875
+ return mode === 'M';
876
+ }
877
+ precLeft_( prec ) {
878
+ const parentPrec = this.stack.at( -1 ).prec;
879
+ if (parentPrec != null && parentPrec >= prec)
880
+ return true;
881
+ this.prec_ = prec;
882
+ return false;
883
+ }
884
+ precRight_( prec ) {
885
+ const parentPrec = this.stack.at( -1 ).prec;
886
+ if (parentPrec != null && parentPrec >= prec)
887
+ return true;
888
+ this.prec_ = prec - 1;
889
+ return false;
890
+ }
891
+ precNone_( prec ) {
892
+ const parentPrec = this.stack.at( -1 ).prec;
893
+ if (parentPrec != null && parentPrec >= prec ||
894
+ this.prec_ != null && this.prec_ <= prec)
895
+ return true;
896
+ this.prec_ = prec;
897
+ return false;
898
+ }
899
+ precPost_( prec ) {
900
+ const parentPrec = this.stack.at( -1 ).prec;
901
+ if (parentPrec != null && parentPrec >= prec ||
902
+ this.prec_ != null && this.prec_ < prec)
903
+ return true;
904
+ this.prec_ = prec;
905
+ return false;
906
+ }
1038
907
  }
1039
908
 
1040
909
  function tokenName( type ) {
1041
- if (typeof type !== 'string')
1042
- type = (!type.parsedAs || type.parsedAs === 'keyword') && type.keyword || type.type;
1043
- return (/^[A-Z]+/.test( type )) ? `‹${ type }›` : `‘${ type }’`;
910
+ if (typeof type !== 'string')
911
+ type = (!type.parsedAs || type.parsedAs === 'keyword') && type.keyword || type.type;
912
+ return (/^[A-Z]+/.test( type )) ? `‹${ type }›` : `‘${ type }’`;
1044
913
  }
1045
914
 
1046
915
  function tokenFullName( token, sep ) {
1047
- return (token.parsedAs && token.parsedAs !== 'keyword' && token.parsedAs !== 'token' ||
1048
- token.type !== 'Id' && token.type !== token.text && token.text)
1049
- ? `‘${ token.text }’${ sep }${ tokenName( token ) }`
1050
- : tokenName( token );
916
+ return (token.parsedAs && token.parsedAs !== 'keyword' && token.parsedAs !== 'token' ||
917
+ token.type !== 'Id' && token.type !== token.text && token.text)
918
+ ? `‘${ token.text }’${ sep }${ tokenName( token ) }`
919
+ : tokenName( token );
1051
920
  }
1052
921
 
1053
922
  function compileTable( table ) {
1054
- if (table.$compiled)
1055
- return table;
1056
- for (const line of table) {
1057
- if (typeof line !== 'object' || Array.isArray( line ))
1058
- continue;
1059
- const cache = Object.create( null ); // very sparse array
1060
- for (const prop of Object.keys( line )) {
1061
- const alt = line[prop];
1062
- if (!Array.isArray( alt ) && prop.charAt(0) !== ' ') // string or number
1063
- line[prop] = (typeof alt === 'string') ? line[alt] : (cache[alt] ??= [ 'g', alt ]);
1064
- }
1065
- if (!line[''])
1066
- line[''] = [ 'e' ];
1067
- }
1068
- table.$compiled = true;
1069
- return table;
923
+ if (table.$compiled)
924
+ return table;
925
+ for (const line of table) {
926
+ if (typeof line !== 'object' || Array.isArray( line ))
927
+ continue;
928
+ const cache = Object.create( null );
929
+ for (const prop of Object.keys( line )) {
930
+ const alt = line[prop];
931
+ if (!Array.isArray( alt ) && prop.charAt(0) !== ' ')
932
+ line[prop] = (typeof alt === 'string') ? line[alt] : (cache[alt] ??= [ 'g', alt ]);
1070
933
  }
1071
-
934
+ if (!line[''])
935
+ line[''] = [ 'e' ];
936
+ }
937
+ table.$compiled = true;
938
+ return table;
939
+ }
940
+ BaseParser.prototype.isNoKeywordInRuleFollow.afterError = true;
1072
941
  module.exports = BaseParser;