xterm
[VSoRC/.git] / node_modules / xterm / src / common / parser / EscapeSequenceParser.ts
1 /**
2  * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3  * @license MIT
4  */
5
6 import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType } from 'common/parser/Types';
7 import { ParserState, ParserAction } from 'common/parser/Constants';
8 import { Disposable } from 'common/Lifecycle';
9 import { IDisposable } from 'common/Types';
10 import { fill } from 'common/TypedArrayUtils';
11 import { Params } from 'common/parser/Params';
12 import { OscParser } from 'common/parser/OscParser';
13 import { DcsParser } from 'common/parser/DcsParser';
14
15 /**
16  * Table values are generated like this:
17  *    index:  currentState << TableValue.INDEX_STATE_SHIFT | charCode
18  *    value:  action << TableValue.TRANSITION_ACTION_SHIFT | nextState
19  */
20 const enum TableAccess {
21   TRANSITION_ACTION_SHIFT = 4,
22   TRANSITION_STATE_MASK = 15,
23   INDEX_STATE_SHIFT = 8
24 }
25
26 /**
27  * Transition table for EscapeSequenceParser.
28  */
29 export class TransitionTable {
30   public table: Uint8Array;
31
32   constructor(length: number) {
33     this.table = new Uint8Array(length);
34   }
35
36   /**
37    * Set default transition.
38    * @param action default action
39    * @param next default next state
40    */
41   public setDefault(action: ParserAction, next: ParserState): void {
42     fill(this.table, action << TableAccess.TRANSITION_ACTION_SHIFT | next);
43   }
44
45   /**
46    * Add a transition to the transition table.
47    * @param code input character code
48    * @param state current parser state
49    * @param action parser action to be done
50    * @param next next parser state
51    */
52   public add(code: number, state: ParserState, action: ParserAction, next: ParserState): void {
53     this.table[state << TableAccess.INDEX_STATE_SHIFT | code] = action << TableAccess.TRANSITION_ACTION_SHIFT | next;
54   }
55
56   /**
57    * Add transitions for multiple input character codes.
58    * @param codes input character code array
59    * @param state current parser state
60    * @param action parser action to be done
61    * @param next next parser state
62    */
63   public addMany(codes: number[], state: ParserState, action: ParserAction, next: ParserState): void {
64     for (let i = 0; i < codes.length; i++) {
65       this.table[state << TableAccess.INDEX_STATE_SHIFT | codes[i]] = action << TableAccess.TRANSITION_ACTION_SHIFT | next;
66     }
67   }
68 }
69
70
71 // Pseudo-character placeholder for printable non-ascii characters (unicode).
72 const NON_ASCII_PRINTABLE = 0xA0;
73
74
75 /**
76  * VT500 compatible transition table.
77  * Taken from https://vt100.net/emu/dec_ansi_parser.
78  */
79 export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
80   const table: TransitionTable = new TransitionTable(4095);
81
82   // range macro for byte
83   const BYTE_VALUES = 256;
84   const blueprint = Array.apply(null, Array(BYTE_VALUES)).map((unused: any, i: number) => i);
85   const r = (start: number, end: number) => blueprint.slice(start, end);
86
87   // Default definitions.
88   const PRINTABLES = r(0x20, 0x7f); // 0x20 (SP) included, 0x7F (DEL) excluded
89   const EXECUTABLES = r(0x00, 0x18);
90   EXECUTABLES.push(0x19);
91   EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));
92
93   const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);
94   let state: any;
95
96   // set default transition
97   table.setDefault(ParserAction.ERROR, ParserState.GROUND);
98   // printables
99   table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
100   // global anywhere rules
101   for (state in states) {
102     table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND);
103     table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND);
104     table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND);
105     table.add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND); // ST as terminator
106     table.add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE);  // ESC
107     table.add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING);  // OSC
108     table.addMany([0x98, 0x9e, 0x9f], state, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
109     table.add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY);  // CSI
110     table.add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY);  // DCS
111   }
112   // rules for executables and 7f
113   table.addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND);
114   table.addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE);
115   table.add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE);
116   table.addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);
117   table.addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY);
118   table.add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY);
119   table.addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM);
120   table.add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM);
121   table.addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE);
122   table.addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE);
123   table.add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE);
124   table.addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE);
125   table.add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE);
126   // osc
127   table.add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING);
128   table.addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
129   table.add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
130   table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND);
131   table.addMany(r(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);
132   // sos/pm/apc does nothing
133   table.addMany([0x58, 0x5e, 0x5f], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
134   table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
135   table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
136   table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND);
137   table.add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
138   // csi entries
139   table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);
140   table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);
141   table.addMany(r(0x30, 0x3c), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);
142   table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM);
143   table.addMany(r(0x30, 0x3c), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);
144   table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND);
145   table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE);
146   table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
147   table.add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
148   table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND);
149   table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
150   table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
151   table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
152   table.addMany(r(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND);
153   table.addMany(r(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
154   // esc_intermediate
155   table.addMany(r(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);
156   table.addMany(r(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);
157   table.addMany(r(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
158   table.addMany(r(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
159   table.addMany(r(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
160   table.addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
161   table.addMany(r(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
162   // dcs entry
163   table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY);
164   table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
165   table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
166   table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
167   table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
168   table.addMany(r(0x30, 0x3c), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
169   table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM);
170   table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
171   table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
172   table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
173   table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
174   table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
175   table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
176   table.addMany(r(0x30, 0x3c), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
177   table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);
178   table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
179   table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
180   table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
181   table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
182   table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
183   table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
184   table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
185   table.addMany(r(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
186   table.addMany(r(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
187   table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
188   table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
189   table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);
190   table.addMany([0x1b, 0x9c, 0x18, 0x1a], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
191   // special handling of unicode chars
192   table.add(NON_ASCII_PRINTABLE, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
193   table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
194   table.add(NON_ASCII_PRINTABLE, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
195   table.add(NON_ASCII_PRINTABLE, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
196   table.add(NON_ASCII_PRINTABLE, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
197   return table;
198 })();
199
200
201 /**
202  * EscapeSequenceParser.
203  * This class implements the ANSI/DEC compatible parser described by
204  * Paul Williams (https://vt100.net/emu/dec_ansi_parser).
205  *
206  * To implement custom ANSI compliant escape sequences it is not needed to
207  * alter this parser, instead consider registering a custom handler.
208  * For non ANSI compliant sequences change the transition table with
209  * the optional `transitions` constructor argument and
210  * reimplement the `parse` method.
211  *
212  * This parser is currently hardcoded to operate in ZDM (Zero Default Mode)
213  * as suggested by the original parser, thus empty parameters are set to 0.
214  * This this is not in line with the latest ECMA-48 specification
215  * (ZDM was part of the early specs and got completely removed later on).
216  *
217  * Other than the original parser from vt100.net this parser supports
218  * sub parameters in digital parameters separated by colons. Empty sub parameters
219  * are set to -1 (no ZDM for sub parameters).
220  *
221  * About prefix and intermediate bytes:
222  * This parser follows the assumptions of the vt100.net parser with these restrictions:
223  * - only one prefix byte is allowed as first parameter byte, byte range 0x3c .. 0x3f
224  * - max. two intermediates are respected, byte range 0x20 .. 0x2f
225  * Note that this is not in line with ECMA-48 which does not limit either of those.
226  * Furthermore ECMA-48 allows the prefix byte range at any param byte position. Currently
227  * there are no known sequences that follow the broader definition of the specification.
228  *
229  * TODO: implement error recovery hook via error handler return values
230  */
231 export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {
232   public initialState: number;
233   public currentState: number;
234   public precedingCodepoint: number;
235
236   // buffers over several parse calls
237   protected _params: Params;
238   protected _collect: number;
239
240   // handler lookup containers
241   protected _printHandler: PrintHandlerType;
242   protected _executeHandlers: {[flag: number]: ExecuteHandlerType};
243   protected _csiHandlers: IHandlerCollection<CsiHandlerType>;
244   protected _escHandlers: IHandlerCollection<EscHandlerType>;
245   protected _oscParser: IOscParser;
246   protected _dcsParser: IDcsParser;
247   protected _errorHandler: (state: IParsingState) => IParsingState;
248
249   // fallback handlers
250   protected _printHandlerFb: PrintFallbackHandlerType;
251   protected _executeHandlerFb: ExecuteFallbackHandlerType;
252   protected _csiHandlerFb: CsiFallbackHandlerType;
253   protected _escHandlerFb: EscFallbackHandlerType;
254   protected _errorHandlerFb: (state: IParsingState) => IParsingState;
255
256   constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
257     super();
258
259     this.initialState = ParserState.GROUND;
260     this.currentState = this.initialState;
261     this._params = new Params(); // defaults to 32 storable params/subparams
262     this._params.addParam(0);    // ZDM
263     this._collect = 0;
264     this.precedingCodepoint = 0;
265
266     // set default fallback handlers and handler lookup containers
267     this._printHandlerFb = (data, start, end): void => { };
268     this._executeHandlerFb = (code: number): void => { };
269     this._csiHandlerFb = (ident: number, params: IParams): void => { };
270     this._escHandlerFb = (ident: number): void => { };
271     this._errorHandlerFb = (state: IParsingState): IParsingState => state;
272     this._printHandler = this._printHandlerFb;
273     this._executeHandlers = Object.create(null);
274     this._csiHandlers = Object.create(null);
275     this._escHandlers = Object.create(null);
276     this._oscParser = new OscParser();
277     this._dcsParser = new DcsParser();
278     this._errorHandler = this._errorHandlerFb;
279
280     // swallow 7bit ST (ESC+\)
281     this.setEscHandler({final: '\\'}, () => {});
282   }
283
284   protected _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number {
285     let res = 0;
286     if (id.prefix) {
287       if (id.prefix.length > 1) {
288         throw new Error('only one byte as prefix supported');
289       }
290       res = id.prefix.charCodeAt(0);
291       if (res && 0x3c > res || res > 0x3f) {
292         throw new Error('prefix must be in range 0x3c .. 0x3f');
293       }
294     }
295     if (id.intermediates) {
296       if (id.intermediates.length > 2) {
297         throw new Error('only two bytes as intermediates are supported');
298       }
299       for (let i = 0; i < id.intermediates.length; ++i) {
300         const intermediate = id.intermediates.charCodeAt(i);
301         if (0x20 > intermediate || intermediate > 0x2f) {
302           throw new Error('intermediate must be in range 0x20 .. 0x2f');
303         }
304         res <<= 8;
305         res |= intermediate;
306       }
307     }
308     if (id.final.length !== 1) {
309       throw new Error('final must be a single byte');
310     }
311     const finalCode = id.final.charCodeAt(0);
312     if (finalRange[0] > finalCode || finalCode > finalRange[1]) {
313       throw new Error(`final must be in range ${finalRange[0]} .. ${finalRange[1]}`);
314     }
315     res <<= 8;
316     res |= finalCode;
317
318     return res;
319   }
320
321   public identToString(ident: number): string {
322     const res: string[] = [];
323     while (ident) {
324       res.push(String.fromCharCode(ident & 0xFF));
325       ident >>= 8;
326     }
327     return res.reverse().join('');
328   }
329
330   public dispose(): void {
331     this._csiHandlers = Object.create(null);
332     this._executeHandlers = Object.create(null);
333     this._escHandlers = Object.create(null);
334     this._oscParser.dispose();
335     this._dcsParser.dispose();
336   }
337
338   public setPrintHandler(handler: PrintHandlerType): void {
339     this._printHandler = handler;
340   }
341   public clearPrintHandler(): void {
342     this._printHandler = this._printHandlerFb;
343   }
344
345   public addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable {
346     const ident = this._identifier(id, [0x30, 0x7e]);
347     if (this._escHandlers[ident] === undefined) {
348       this._escHandlers[ident] = [];
349     }
350     const handlerList = this._escHandlers[ident];
351     handlerList.push(handler);
352     return {
353       dispose: () => {
354         const handlerIndex = handlerList.indexOf(handler);
355         if (handlerIndex !== -1) {
356           handlerList.splice(handlerIndex, 1);
357         }
358       }
359     };
360   }
361   public setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void {
362     this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [handler];
363   }
364   public clearEscHandler(id: IFunctionIdentifier): void {
365     if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
366   }
367   public setEscHandlerFallback(handler: EscFallbackHandlerType): void {
368     this._escHandlerFb = handler;
369   }
370
371   public setExecuteHandler(flag: string, handler: ExecuteHandlerType): void {
372     this._executeHandlers[flag.charCodeAt(0)] = handler;
373   }
374   public clearExecuteHandler(flag: string): void {
375     if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
376   }
377   public setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void {
378     this._executeHandlerFb = handler;
379   }
380
381   public addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable {
382     const ident = this._identifier(id);
383     if (this._csiHandlers[ident] === undefined) {
384       this._csiHandlers[ident] = [];
385     }
386     const handlerList = this._csiHandlers[ident];
387     handlerList.push(handler);
388     return {
389       dispose: () => {
390         const handlerIndex = handlerList.indexOf(handler);
391         if (handlerIndex !== -1) {
392           handlerList.splice(handlerIndex, 1);
393         }
394       }
395     };
396   }
397   public setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void {
398     this._csiHandlers[this._identifier(id)] = [handler];
399   }
400   public clearCsiHandler(id: IFunctionIdentifier): void {
401     if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];
402   }
403   public setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {
404     this._csiHandlerFb = callback;
405   }
406
407   public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
408     return this._dcsParser.addHandler(this._identifier(id), handler);
409   }
410   public setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void {
411     this._dcsParser.setHandler(this._identifier(id), handler);
412   }
413   public clearDcsHandler(id: IFunctionIdentifier): void {
414     this._dcsParser.clearHandler(this._identifier(id));
415   }
416   public setDcsHandlerFallback(handler: DcsFallbackHandlerType): void {
417     this._dcsParser.setHandlerFallback(handler);
418   }
419
420   public addOscHandler(ident: number, handler: IOscHandler): IDisposable {
421     return this._oscParser.addHandler(ident, handler);
422   }
423   public setOscHandler(ident: number, handler: IOscHandler): void {
424     this._oscParser.setHandler(ident, handler);
425   }
426   public clearOscHandler(ident: number): void {
427     this._oscParser.clearHandler(ident);
428   }
429   public setOscHandlerFallback(handler: OscFallbackHandlerType): void {
430     this._oscParser.setHandlerFallback(handler);
431   }
432
433   public setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
434     this._errorHandler = callback;
435   }
436   public clearErrorHandler(): void {
437     this._errorHandler = this._errorHandlerFb;
438   }
439
440   public reset(): void {
441     this.currentState = this.initialState;
442     this._oscParser.reset();
443     this._dcsParser.reset();
444     this._params.reset();
445     this._params.addParam(0); // ZDM
446     this._collect = 0;
447     this.precedingCodepoint = 0;
448   }
449
450
451
452   /**
453    * Parse UTF32 codepoints in `data` up to `length`.
454    *
455    * Note: For several actions with high data load the parsing is optimized
456    * by using local read ahead loops with hardcoded conditions to
457    * avoid costly table lookups. Make sure that any change of table values
458    * will be reflected in the loop conditions as well and vice versa.
459    * Affected states/actions:
460    * - GROUND:PRINT
461    * - CSI_PARAM:PARAM
462    * - DCS_PARAM:PARAM
463    * - OSC_STRING:OSC_PUT
464    * - DCS_PASSTHROUGH:DCS_PUT
465    */
466   public parse(data: Uint32Array, length: number): void {
467     let code = 0;
468     let transition = 0;
469     let currentState = this.currentState;
470     const osc = this._oscParser;
471     const dcs = this._dcsParser;
472     let collect = this._collect;
473     const params = this._params;
474     const table: Uint8Array = this.TRANSITIONS.table;
475
476     // process input string
477     for (let i = 0; i < length; ++i) {
478       code = data[i];
479
480       // normal transition & action lookup
481       transition = table[currentState << TableAccess.INDEX_STATE_SHIFT | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)];
482       switch (transition >> TableAccess.TRANSITION_ACTION_SHIFT) {
483         case ParserAction.PRINT:
484           // read ahead with loop unrolling
485           // Note: 0x20 (SP) is included, 0x7F (DEL) is excluded
486           for (let j = i + 1; ; ++j) {
487             if (j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
488               this._printHandler(data, i, j);
489               i = j - 1;
490               break;
491             }
492             if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
493               this._printHandler(data, i, j);
494               i = j - 1;
495               break;
496             }
497             if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
498               this._printHandler(data, i, j);
499               i = j - 1;
500               break;
501             }
502             if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
503               this._printHandler(data, i, j);
504               i = j - 1;
505               break;
506             }
507           }
508           break;
509         case ParserAction.EXECUTE:
510           if (this._executeHandlers[code]) this._executeHandlers[code]();
511           else this._executeHandlerFb(code);
512           this.precedingCodepoint = 0;
513           break;
514         case ParserAction.IGNORE:
515           break;
516         case ParserAction.ERROR:
517           const inject: IParsingState = this._errorHandler(
518             {
519               position: i,
520               code,
521               currentState,
522               collect,
523               params,
524               abort: false
525             });
526           if (inject.abort) return;
527           // inject values: currently not implemented
528           break;
529         case ParserAction.CSI_DISPATCH:
530           // Trigger CSI Handler
531           const handlers = this._csiHandlers[collect << 8 | code];
532           let j = handlers ? handlers.length - 1 : -1;
533           for (; j >= 0; j--) {
534             // undefined or true means success and to stop bubbling
535             if (handlers[j](params) !== false) {
536               break;
537             }
538           }
539           if (j < 0) {
540             this._csiHandlerFb(collect << 8 | code, params);
541           }
542           this.precedingCodepoint = 0;
543           break;
544         case ParserAction.PARAM:
545           // inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
546           do {
547             switch (code) {
548               case 0x3b:
549                 params.addParam(0);  // ZDM
550                 break;
551               case 0x3a:
552                 params.addSubParam(-1);
553                 break;
554               default:  // 0x30 - 0x39
555                 params.addDigit(code - 48);
556             }
557           } while (++i < length && (code = data[i]) > 0x2f && code < 0x3c);
558           i--;
559           break;
560         case ParserAction.COLLECT:
561           collect <<= 8;
562           collect |= code;
563           break;
564         case ParserAction.ESC_DISPATCH:
565           const handlersEsc = this._escHandlers[collect << 8 | code];
566           let jj = handlersEsc ? handlersEsc.length - 1 : -1;
567           for (; jj >= 0; jj--) {
568             // undefined or true means success and to stop bubbling
569             if (handlersEsc[jj]() !== false) {
570               break;
571             }
572           }
573           if (jj < 0) {
574             this._escHandlerFb(collect << 8 | code);
575           }
576           this.precedingCodepoint = 0;
577           break;
578         case ParserAction.CLEAR:
579           params.reset();
580           params.addParam(0); // ZDM
581           collect = 0;
582           break;
583         case ParserAction.DCS_HOOK:
584           dcs.hook(collect << 8 | code, params);
585           break;
586         case ParserAction.DCS_PUT:
587           // inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f
588           // unhook triggered by: 0x1b, 0x9c (success) and 0x18, 0x1a (abort)
589           for (let j = i + 1; ; ++j) {
590             if (j >= length || (code = data[j]) === 0x18 || code === 0x1a || code === 0x1b || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {
591               dcs.put(data, i, j);
592               i = j - 1;
593               break;
594             }
595           }
596           break;
597         case ParserAction.DCS_UNHOOK:
598           dcs.unhook(code !== 0x18 && code !== 0x1a);
599           if (code === 0x1b) transition |= ParserState.ESCAPE;
600           params.reset();
601           params.addParam(0); // ZDM
602           collect = 0;
603           this.precedingCodepoint = 0;
604           break;
605         case ParserAction.OSC_START:
606           osc.start();
607           break;
608         case ParserAction.OSC_PUT:
609           // inner loop: 0x20 (SP) included, 0x7F (DEL) included
610           for (let j = i + 1; ; j++) {
611             if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code <= 0x9f)) {
612               osc.put(data, i, j);
613               i = j - 1;
614               break;
615             }
616           }
617           break;
618         case ParserAction.OSC_END:
619           osc.end(code !== 0x18 && code !== 0x1a);
620           if (code === 0x1b) transition |= ParserState.ESCAPE;
621           params.reset();
622           params.addParam(0); // ZDM
623           collect = 0;
624           this.precedingCodepoint = 0;
625           break;
626       }
627       currentState = transition & TableAccess.TRANSITION_STATE_MASK;
628     }
629
630     // save collected intermediates
631     this._collect = collect;
632
633     // save state
634     this.currentState = currentState;
635   }
636 }