aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/xterm/src/common/parser/EscapeSequenceParser.ts
blob: f20a7e914fcb687a1f9349280b9e57409f6a7724 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
/**
 * Copyright (c) 2018 The xterm.js authors. All rights reserved.
 * @license MIT
 */

import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types';
import { ParserState, ParserAction } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { IDisposable } from 'common/Types';
import { fill } from 'common/TypedArrayUtils';
import { Params } from 'common/parser/Params';
import { OscParser } from 'common/parser/OscParser';
import { DcsParser } from 'common/parser/DcsParser';

/**
 * Table values are generated like this:
 *    index:  currentState << TableValue.INDEX_STATE_SHIFT | charCode
 *    value:  action << TableValue.TRANSITION_ACTION_SHIFT | nextState
 */
const enum TableAccess {
  TRANSITION_ACTION_SHIFT = 4,
  TRANSITION_STATE_MASK = 15,
  INDEX_STATE_SHIFT = 8
}

/**
 * Transition table for EscapeSequenceParser.
 */
export class TransitionTable {
  public table: Uint8Array;

  constructor(length: number) {
    this.table = new Uint8Array(length);
  }

  /**
   * Set default transition.
   * @param action default action
   * @param next default next state
   */
  public setDefault(action: ParserAction, next: ParserState): void {
    fill(this.table, action << TableAccess.TRANSITION_ACTION_SHIFT | next);
  }

  /**
   * Add a transition to the transition table.
   * @param code input character code
   * @param state current parser state
   * @param action parser action to be done
   * @param next next parser state
   */
  public add(code: number, state: ParserState, action: ParserAction, next: ParserState): void {
    this.table[state << TableAccess.INDEX_STATE_SHIFT | code] = action << TableAccess.TRANSITION_ACTION_SHIFT | next;
  }

  /**
   * Add transitions for multiple input character codes.
   * @param codes input character code array
   * @param state current parser state
   * @param action parser action to be done
   * @param next next parser state
   */
  public addMany(codes: number[], state: ParserState, action: ParserAction, next: ParserState): void {
    for (let i = 0; i < codes.length; i++) {
      this.table[state << TableAccess.INDEX_STATE_SHIFT | codes[i]] = action << TableAccess.TRANSITION_ACTION_SHIFT | next;
    }
  }
}


// Pseudo-character placeholder for printable non-ascii characters (unicode).
const NON_ASCII_PRINTABLE = 0xA0;


/**
 * VT500 compatible transition table.
 * Taken from https://vt100.net/emu/dec_ansi_parser.
 */
export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
  const table: TransitionTable = new TransitionTable(4095);

  // range macro for byte
  const BYTE_VALUES = 256;
  const blueprint = Array.apply(null, Array(BYTE_VALUES)).map((unused: any, i: number) => i);
  const r = (start: number, end: number): number[] => blueprint.slice(start, end);

  // Default definitions.
  const PRINTABLES = r(0x20, 0x7f); // 0x20 (SP) included, 0x7F (DEL) excluded
  const EXECUTABLES = r(0x00, 0x18);
  EXECUTABLES.push(0x19);
  EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));

  const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);
  let state: any;

  // set default transition
  table.setDefault(ParserAction.ERROR, ParserState.GROUND);
  // printables
  table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
  // global anywhere rules
  for (state in states) {
    table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND);
    table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND);
    table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND);
    table.add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND); // ST as terminator
    table.add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE);  // ESC
    table.add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING);  // OSC
    table.addMany([0x98, 0x9e, 0x9f], state, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
    table.add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY);  // CSI
    table.add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY);  // DCS
  }
  // rules for executables and 7f
  table.addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND);
  table.addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE);
  table.add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE);
  table.addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);
  table.addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY);
  table.add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY);
  table.addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM);
  table.add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM);
  table.addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE);
  table.addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE);
  table.add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE);
  table.addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE);
  table.add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE);
  // osc
  table.add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING);
  table.addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
  table.add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
  table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND);
  table.addMany(r(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);
  // sos/pm/apc does nothing
  table.addMany([0x58, 0x5e, 0x5f], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
  table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
  table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
  table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND);
  table.add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
  // csi entries
  table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);
  table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);
  table.addMany(r(0x30, 0x3c), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);
  table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM);
  table.addMany(r(0x30, 0x3c), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);
  table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND);
  table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE);
  table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
  table.add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
  table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND);
  table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
  table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
  table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
  table.addMany(r(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND);
  table.addMany(r(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
  // esc_intermediate
  table.addMany(r(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);
  table.addMany(r(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);
  table.addMany(r(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
  table.addMany(r(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
  table.addMany(r(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
  table.addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
  table.addMany(r(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
  // dcs entry
  table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY);
  table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
  table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
  table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
  table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
  table.addMany(r(0x30, 0x3c), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
  table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM);
  table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
  table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
  table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
  table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
  table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
  table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
  table.addMany(r(0x30, 0x3c), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
  table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);
  table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
  table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
  table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
  table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
  table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
  table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
  table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
  table.addMany(r(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
  table.addMany(r(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
  table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
  table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
  table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);
  table.addMany([0x1b, 0x9c, 0x18, 0x1a], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
  // special handling of unicode chars
  table.add(NON_ASCII_PRINTABLE, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
  table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
  table.add(NON_ASCII_PRINTABLE, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
  table.add(NON_ASCII_PRINTABLE, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
  table.add(NON_ASCII_PRINTABLE, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
  return table;
})();


/**
 * EscapeSequenceParser.
 * This class implements the ANSI/DEC compatible parser described by
 * Paul Williams (https://vt100.net/emu/dec_ansi_parser).
 *
 * To implement custom ANSI compliant escape sequences it is not needed to
 * alter this parser, instead consider registering a custom handler.
 * For non ANSI compliant sequences change the transition table with
 * the optional `transitions` constructor argument and
 * reimplement the `parse` method.
 *
 * This parser is currently hardcoded to operate in ZDM (Zero Default Mode)
 * as suggested by the original parser, thus empty parameters are set to 0.
 * This this is not in line with the latest ECMA-48 specification
 * (ZDM was part of the early specs and got completely removed later on).
 *
 * Other than the original parser from vt100.net this parser supports
 * sub parameters in digital parameters separated by colons. Empty sub parameters
 * are set to -1 (no ZDM for sub parameters).
 *
 * About prefix and intermediate bytes:
 * This parser follows the assumptions of the vt100.net parser with these restrictions:
 * - only one prefix byte is allowed as first parameter byte, byte range 0x3c .. 0x3f
 * - max. two intermediates are respected, byte range 0x20 .. 0x2f
 * Note that this is not in line with ECMA-48 which does not limit either of those.
 * Furthermore ECMA-48 allows the prefix byte range at any param byte position. Currently
 * there are no known sequences that follow the broader definition of the specification.
 *
 * TODO: implement error recovery hook via error handler return values
 */
export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {
  public initialState: number;
  public currentState: number;
  public precedingCodepoint: number;

  // buffers over several parse calls
  protected _params: Params;
  protected _collect: number;

  // handler lookup containers
  protected _printHandler: PrintHandlerType;
  protected _executeHandlers: { [flag: number]: ExecuteHandlerType };
  protected _csiHandlers: IHandlerCollection<CsiHandlerType>;
  protected _escHandlers: IHandlerCollection<EscHandlerType>;
  protected _oscParser: IOscParser;
  protected _dcsParser: IDcsParser;
  protected _errorHandler: (state: IParsingState) => IParsingState;

  // fallback handlers
  protected _printHandlerFb: PrintFallbackHandlerType;
  protected _executeHandlerFb: ExecuteFallbackHandlerType;
  protected _csiHandlerFb: CsiFallbackHandlerType;
  protected _escHandlerFb: EscFallbackHandlerType;
  protected _errorHandlerFb: (state: IParsingState) => IParsingState;

  // parser stack save for async handler support
  protected _parseStack: IParserStackState = {
    state: ParserStackType.NONE,
    handlers: [],
    handlerPos: 0,
    transition: 0,
    chunkPos: 0
  };

  constructor(
    protected readonly _transitions: TransitionTable = VT500_TRANSITION_TABLE
  ) {
    super();

    this.initialState = ParserState.GROUND;
    this.currentState = this.initialState;
    this._params = new Params(); // defaults to 32 storable params/subparams
    this._params.addParam(0);    // ZDM
    this._collect = 0;
    this.precedingCodepoint = 0;

    // set default fallback handlers and handler lookup containers
    this._printHandlerFb = (data, start, end): void => { };
    this._executeHandlerFb = (code: number): void => { };
    this._csiHandlerFb = (ident: number, params: IParams): void => { };
    this._escHandlerFb = (ident: number): void => { };
    this._errorHandlerFb = (state: IParsingState): IParsingState => state;
    this._printHandler = this._printHandlerFb;
    this._executeHandlers = Object.create(null);
    this._csiHandlers = Object.create(null);
    this._escHandlers = Object.create(null);
    this._oscParser = new OscParser();
    this._dcsParser = new DcsParser();
    this._errorHandler = this._errorHandlerFb;

    // swallow 7bit ST (ESC+\)
    this.registerEscHandler({ final: '\\' }, () => true);
  }

  protected _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number {
    let res = 0;
    if (id.prefix) {
      if (id.prefix.length > 1) {
        throw new Error('only one byte as prefix supported');
      }
      res = id.prefix.charCodeAt(0);
      if (res && 0x3c > res || res > 0x3f) {
        throw new Error('prefix must be in range 0x3c .. 0x3f');
      }
    }
    if (id.intermediates) {
      if (id.intermediates.length > 2) {
        throw new Error('only two bytes as intermediates are supported');
      }
      for (let i = 0; i < id.intermediates.length; ++i) {
        const intermediate = id.intermediates.charCodeAt(i);
        if (0x20 > intermediate || intermediate > 0x2f) {
          throw new Error('intermediate must be in range 0x20 .. 0x2f');
        }
        res <<= 8;
        res |= intermediate;
      }
    }
    if (id.final.length !== 1) {
      throw new Error('final must be a single byte');
    }
    const finalCode = id.final.charCodeAt(0);
    if (finalRange[0] > finalCode || finalCode > finalRange[1]) {
      throw new Error(`final must be in range ${finalRange[0]} .. ${finalRange[1]}`);
    }
    res <<= 8;
    res |= finalCode;

    return res;
  }

  public identToString(ident: number): string {
    const res: string[] = [];
    while (ident) {
      res.push(String.fromCharCode(ident & 0xFF));
      ident >>= 8;
    }
    return res.reverse().join('');
  }

  public dispose(): void {
    this._csiHandlers = Object.create(null);
    this._executeHandlers = Object.create(null);
    this._escHandlers = Object.create(null);
    this._oscParser.dispose();
    this._dcsParser.dispose();
  }

  public setPrintHandler(handler: PrintHandlerType): void {
    this._printHandler = handler;
  }
  public clearPrintHandler(): void {
    this._printHandler = this._printHandlerFb;
  }

  public registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable {
    const ident = this._identifier(id, [0x30, 0x7e]);
    if (this._escHandlers[ident] === undefined) {
      this._escHandlers[ident] = [];
    }
    const handlerList = this._escHandlers[ident];
    handlerList.push(handler);
    return {
      dispose: () => {
        const handlerIndex = handlerList.indexOf(handler);
        if (handlerIndex !== -1) {
          handlerList.splice(handlerIndex, 1);
        }
      }
    };
  }
  public clearEscHandler(id: IFunctionIdentifier): void {
    if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
  }
  public setEscHandlerFallback(handler: EscFallbackHandlerType): void {
    this._escHandlerFb = handler;
  }

  public setExecuteHandler(flag: string, handler: ExecuteHandlerType): void {
    this._executeHandlers[flag.charCodeAt(0)] = handler;
  }
  public clearExecuteHandler(flag: string): void {
    if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
  }
  public setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void {
    this._executeHandlerFb = handler;
  }

  public registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable {
    const ident = this._identifier(id);
    if (this._csiHandlers[ident] === undefined) {
      this._csiHandlers[ident] = [];
    }
    const handlerList = this._csiHandlers[ident];
    handlerList.push(handler);
    return {
      dispose: () => {
        const handlerIndex = handlerList.indexOf(handler);
        if (handlerIndex !== -1) {
          handlerList.splice(handlerIndex, 1);
        }
      }
    };
  }
  public clearCsiHandler(id: IFunctionIdentifier): void {
    if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];
  }
  public setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {
    this._csiHandlerFb = callback;
  }

  public registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
    return this._dcsParser.registerHandler(this._identifier(id), handler);
  }
  public clearDcsHandler(id: IFunctionIdentifier): void {
    this._dcsParser.clearHandler(this._identifier(id));
  }
  public setDcsHandlerFallback(handler: DcsFallbackHandlerType): void {
    this._dcsParser.setHandlerFallback(handler);
  }

  public registerOscHandler(ident: number, handler: IOscHandler): IDisposable {
    return this._oscParser.registerHandler(ident, handler);
  }
  public clearOscHandler(ident: number): void {
    this._oscParser.clearHandler(ident);
  }
  public setOscHandlerFallback(handler: OscFallbackHandlerType): void {
    this._oscParser.setHandlerFallback(handler);
  }

  public setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
    this._errorHandler = callback;
  }
  public clearErrorHandler(): void {
    this._errorHandler = this._errorHandlerFb;
  }

  /**
   * Reset parser to initial values.
   *
   * This can also be used to lift the improper continuation error condition
   * when dealing with async handlers. Use this only as a last resort to silence
   * that error when the terminal has no pending data to be processed. Note that
   * the interrupted async handler might continue its work in the future messing
   * up the terminal state even further.
   */
  public reset(): void {
    this.currentState = this.initialState;
    this._oscParser.reset();
    this._dcsParser.reset();
    this._params.reset();
    this._params.addParam(0); // ZDM
    this._collect = 0;
    this.precedingCodepoint = 0;
    // abort pending continuation from async handler
    // Here the RESET type indicates, that the next parse call will
    // ignore any saved stack, instead continues sync with next codepoint from GROUND
    if (this._parseStack.state !== ParserStackType.NONE) {
      this._parseStack.state = ParserStackType.RESET;
      this._parseStack.handlers = []; // also release handlers ref
    }
  }

  /**
   * Async parse support.
   */
  protected _preserveStack(
    state: ParserStackType,
    handlers: ResumableHandlersType,
    handlerPos: number,
    transition: number,
    chunkPos: number
  ): void {
    this._parseStack.state = state;
    this._parseStack.handlers = handlers;
    this._parseStack.handlerPos = handlerPos;
    this._parseStack.transition = transition;
    this._parseStack.chunkPos = chunkPos;
  }

  /**
   * Parse UTF32 codepoints in `data` up to `length`.
   *
   * Note: For several actions with high data load the parsing is optimized
   * by using local read ahead loops with hardcoded conditions to
   * avoid costly table lookups. Make sure that any change of table values
   * will be reflected in the loop conditions as well and vice versa.
   * Affected states/actions:
   * - GROUND:PRINT
   * - CSI_PARAM:PARAM
   * - DCS_PARAM:PARAM
   * - OSC_STRING:OSC_PUT
   * - DCS_PASSTHROUGH:DCS_PUT
   *
   * Note on asynchronous handler support:
   * Any handler returning a promise will be treated as asynchronous.
   * To keep the in-band blocking working for async handlers, `parse` pauses execution,
   * creates a stack save and returns the promise to the caller.
   * For proper continuation of the paused state it is important
   * to await the promise resolving. On resolve the parse must be repeated
   * with the same chunk of data and the resolved value in `promiseResult`
   * until no promise is returned.
   *
   * Important: With only sync handlers defined, parsing is completely synchronous as well.
   * As soon as an async handler is involved, synchronous parsing is not possible anymore.
   *
   * Boilerplate for proper parsing of multiple chunks with async handlers:
   *
   * ```typescript
   * async function parseMultipleChunks(chunks: Uint32Array[]): Promise<void> {
   *   for (const chunk of chunks) {
   *     let result: void | Promise<boolean>;
   *     let prev: boolean | undefined;
   *     while (result = parser.parse(chunk, chunk.length, prev)) {
   *       prev = await result;
   *     }
   *   }
   *   // finished parsing all chunks...
   * }
   * ```
   */
  public parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise<boolean> {
    let code = 0;
    let transition = 0;
    let start = 0;
    let handlerResult: void | boolean | Promise<boolean>;

    // resume from async handler
    if (this._parseStack.state) {
      // allow sync parser reset even in continuation mode
      // Note: can be used to recover parser from improper continuation error below
      if (this._parseStack.state === ParserStackType.RESET) {
        this._parseStack.state = ParserStackType.NONE;
        start = this._parseStack.chunkPos + 1; // continue with next codepoint in GROUND
      } else {
        if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) {
          /**
           * Reject further parsing on improper continuation after pausing.
           * This is a really bad condition with screwed up execution order and prolly messed up
           * terminal state, therefore we exit hard with an exception and reject any further parsing.
           *
           * Note: With `Terminal.write` usage this exception should never occur, as the top level
           * calls are guaranteed to handle async conditions properly. If you ever encounter this
           * exception in your terminal integration it indicates, that you injected data chunks to
           * `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for
           * continuation of a running async handler.
           *
           * It is possible to get rid of this error by calling `reset`. But dont rely on that,
           * as the pending async handler still might mess up the terminal later. Instead fix the faulty
           * async handling, so this error will not be thrown anymore.
           */
          this._parseStack.state = ParserStackType.FAIL;
          throw new Error('improper continuation due to previous async handler, giving up parsing');
        }

        // we have to resume the old handler loop if:
        // - return value of the promise was `false`
        // - handlers are not exhausted yet
        const handlers = this._parseStack.handlers;
        let handlerPos = this._parseStack.handlerPos - 1;
        switch (this._parseStack.state) {
          case ParserStackType.CSI:
            if (promiseResult === false && handlerPos > -1) {
              for (; handlerPos >= 0; handlerPos--) {
                handlerResult = (handlers as CsiHandlerType[])[handlerPos](this._params);
                if (handlerResult === true) {
                  break;
                } else if (handlerResult instanceof Promise) {
                  this._parseStack.handlerPos = handlerPos;
                  return handlerResult;
                }
              }
            }
            this._parseStack.handlers = [];
            break;
          case ParserStackType.ESC:
            if (promiseResult === false && handlerPos > -1) {
              for (; handlerPos >= 0; handlerPos--) {
                handlerResult = (handlers as EscHandlerType[])[handlerPos]();
                if (handlerResult === true) {
                  break;
                } else if (handlerResult instanceof Promise) {
                  this._parseStack.handlerPos = handlerPos;
                  return handlerResult;
                }
              }
            }
            this._parseStack.handlers = [];
            break;
          case ParserStackType.DCS:
            code = data[this._parseStack.chunkPos];
            handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a, promiseResult);
            if (handlerResult) {
              return handlerResult;
            }
            if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
            this._params.reset();
            this._params.addParam(0); // ZDM
            this._collect = 0;
            break;
          case ParserStackType.OSC:
            code = data[this._parseStack.chunkPos];
            handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult);
            if (handlerResult) {
              return handlerResult;
            }
            if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
            this._params.reset();
            this._params.addParam(0); // ZDM
            this._collect = 0;
            break;
        }
        // cleanup before continuing with the main sync loop
        this._parseStack.state = ParserStackType.NONE;
        start = this._parseStack.chunkPos + 1;
        this.precedingCodepoint = 0;
        this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK;
      }
    }

    // continue with main sync loop

    // process input string
    for (let i = start; i < length; ++i) {
      code = data[i];

      // normal transition & action lookup
      transition = this._transitions.table[this.currentState << TableAccess.INDEX_STATE_SHIFT | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)];
      switch (transition >> TableAccess.TRANSITION_ACTION_SHIFT) {
        case ParserAction.PRINT:
          // read ahead with loop unrolling
          // Note: 0x20 (SP) is included, 0x7F (DEL) is excluded
          for (let j = i + 1; ; ++j) {
            if (j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
              this._printHandler(data, i, j);
              i = j - 1;
              break;
            }
            if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
              this._printHandler(data, i, j);
              i = j - 1;
              break;
            }
            if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
              this._printHandler(data, i, j);
              i = j - 1;
              break;
            }
            if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
              this._printHandler(data, i, j);
              i = j - 1;
              break;
            }
          }
          break;
        case ParserAction.EXECUTE:
          if (this._executeHandlers[code]) this._executeHandlers[code]();
          else this._executeHandlerFb(code);
          this.precedingCodepoint = 0;
          break;
        case ParserAction.IGNORE:
          break;
        case ParserAction.ERROR:
          const inject: IParsingState = this._errorHandler(
            {
              position: i,
              code,
              currentState: this.currentState,
              collect: this._collect,
              params: this._params,
              abort: false
            });
          if (inject.abort) return;
          // inject values: currently not implemented
          break;
        case ParserAction.CSI_DISPATCH:
          // Trigger CSI Handler
          const handlers = this._csiHandlers[this._collect << 8 | code];
          let j = handlers ? handlers.length - 1 : -1;
          for (; j >= 0; j--) {
            // true means success and to stop bubbling
            // a promise indicates an async handler that needs to finish before progressing
            handlerResult = handlers[j](this._params);
            if (handlerResult === true) {
              break;
            } else if (handlerResult instanceof Promise) {
              this._preserveStack(ParserStackType.CSI, handlers, j, transition, i);
              return handlerResult;
            }
          }
          if (j < 0) {
            this._csiHandlerFb(this._collect << 8 | code, this._params);
          }
          this.precedingCodepoint = 0;
          break;
        case ParserAction.PARAM:
          // inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
          do {
            switch (code) {
              case 0x3b:
                this._params.addParam(0);  // ZDM
                break;
              case 0x3a:
                this._params.addSubParam(-1);
                break;
              default:  // 0x30 - 0x39
                this._params.addDigit(code - 48);
            }
          } while (++i < length && (code = data[i]) > 0x2f && code < 0x3c);
          i--;
          break;
        case ParserAction.COLLECT:
          this._collect <<= 8;
          this._collect |= code;
          break;
        case ParserAction.ESC_DISPATCH:
          const handlersEsc = this._escHandlers[this._collect << 8 | code];
          let jj = handlersEsc ? handlersEsc.length - 1 : -1;
          for (; jj >= 0; jj--) {
            // true means success and to stop bubbling
            // a promise indicates an async handler that needs to finish before progressing
            handlerResult = handlersEsc[jj]();
            if (handlerResult === true) {
              break;
            } else if (handlerResult instanceof Promise) {
              this._preserveStack(ParserStackType.ESC, handlersEsc, jj, transition, i);
              return handlerResult;
            }
          }
          if (jj < 0) {
            this._escHandlerFb(this._collect << 8 | code);
          }
          this.precedingCodepoint = 0;
          break;
        case ParserAction.CLEAR:
          this._params.reset();
          this._params.addParam(0); // ZDM
          this._collect = 0;
          break;
        case ParserAction.DCS_HOOK:
          this._dcsParser.hook(this._collect << 8 | code, this._params);
          break;
        case ParserAction.DCS_PUT:
          // inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f
          // unhook triggered by: 0x1b, 0x9c (success) and 0x18, 0x1a (abort)
          for (let j = i + 1; ; ++j) {
            if (j >= length || (code = data[j]) === 0x18 || code === 0x1a || code === 0x1b || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {
              this._dcsParser.put(data, i, j);
              i = j - 1;
              break;
            }
          }
          break;
        case ParserAction.DCS_UNHOOK:
          handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a);
          if (handlerResult) {
            this._preserveStack(ParserStackType.DCS, [], 0, transition, i);
            return handlerResult;
          }
          if (code === 0x1b) transition |= ParserState.ESCAPE;
          this._params.reset();
          this._params.addParam(0); // ZDM
          this._collect = 0;
          this.precedingCodepoint = 0;
          break;
        case ParserAction.OSC_START:
          this._oscParser.start();
          break;
        case ParserAction.OSC_PUT:
          // inner loop: 0x20 (SP) included, 0x7F (DEL) included
          for (let j = i + 1; ; j++) {
            if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {
              this._oscParser.put(data, i, j);
              i = j - 1;
              break;
            }
          }
          break;
        case ParserAction.OSC_END:
          handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a);
          if (handlerResult) {
            this._preserveStack(ParserStackType.OSC, [], 0, transition, i);
            return handlerResult;
          }
          if (code === 0x1b) transition |= ParserState.ESCAPE;
          this._params.reset();
          this._params.addParam(0); // ZDM
          this._collect = 0;
          this.precedingCodepoint = 0;
          break;
      }
      this.currentState = transition & TableAccess.TRANSITION_STATE_MASK;
    }
  }
}