2 * Copyright (c) 2018 The xterm.js authors. All rights reserved.
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';
16 * Table values are generated like this:
17 * index: currentState << TableValue.INDEX_STATE_SHIFT | charCode
18 * value: action << TableValue.TRANSITION_ACTION_SHIFT | nextState
20 const enum TableAccess {
21 TRANSITION_ACTION_SHIFT = 4,
22 TRANSITION_STATE_MASK = 15,
27 * Transition table for EscapeSequenceParser.
29 export class TransitionTable {
30 public table: Uint8Array;
32 constructor(length: number) {
33 this.table = new Uint8Array(length);
37 * Set default transition.
38 * @param action default action
39 * @param next default next state
41 public setDefault(action: ParserAction, next: ParserState): void {
42 fill(this.table, action << TableAccess.TRANSITION_ACTION_SHIFT | next);
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
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;
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
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;
71 // Pseudo-character placeholder for printable non-ascii characters (unicode).
72 const NON_ASCII_PRINTABLE = 0xA0;
76 * VT500 compatible transition table.
77 * Taken from https://vt100.net/emu/dec_ansi_parser.
79 export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
80 const table: TransitionTable = new TransitionTable(4095);
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);
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));
93 const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);
96 // set default transition
97 table.setDefault(ParserAction.ERROR, ParserState.GROUND);
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
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);
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);
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);
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);
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);
202 * EscapeSequenceParser.
203 * This class implements the ANSI/DEC compatible parser described by
204 * Paul Williams (https://vt100.net/emu/dec_ansi_parser).
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.
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).
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).
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.
229 * TODO: implement error recovery hook via error handler return values
231 export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {
232 public initialState: number;
233 public currentState: number;
234 public precedingCodepoint: number;
236 // buffers over several parse calls
237 protected _params: Params;
238 protected _collect: number;
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;
250 protected _printHandlerFb: PrintFallbackHandlerType;
251 protected _executeHandlerFb: ExecuteFallbackHandlerType;
252 protected _csiHandlerFb: CsiFallbackHandlerType;
253 protected _escHandlerFb: EscFallbackHandlerType;
254 protected _errorHandlerFb: (state: IParsingState) => IParsingState;
256 constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
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
264 this.precedingCodepoint = 0;
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;
280 // swallow 7bit ST (ESC+\)
281 this.setEscHandler({final: '\\'}, () => {});
284 protected _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number {
287 if (id.prefix.length > 1) {
288 throw new Error('only one byte as prefix supported');
290 res = id.prefix.charCodeAt(0);
291 if (res && 0x3c > res || res > 0x3f) {
292 throw new Error('prefix must be in range 0x3c .. 0x3f');
295 if (id.intermediates) {
296 if (id.intermediates.length > 2) {
297 throw new Error('only two bytes as intermediates are supported');
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');
308 if (id.final.length !== 1) {
309 throw new Error('final must be a single byte');
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]}`);
321 public identToString(ident: number): string {
322 const res: string[] = [];
324 res.push(String.fromCharCode(ident & 0xFF));
327 return res.reverse().join('');
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();
338 public setPrintHandler(handler: PrintHandlerType): void {
339 this._printHandler = handler;
341 public clearPrintHandler(): void {
342 this._printHandler = this._printHandlerFb;
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] = [];
350 const handlerList = this._escHandlers[ident];
351 handlerList.push(handler);
354 const handlerIndex = handlerList.indexOf(handler);
355 if (handlerIndex !== -1) {
356 handlerList.splice(handlerIndex, 1);
361 public setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void {
362 this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [handler];
364 public clearEscHandler(id: IFunctionIdentifier): void {
365 if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
367 public setEscHandlerFallback(handler: EscFallbackHandlerType): void {
368 this._escHandlerFb = handler;
371 public setExecuteHandler(flag: string, handler: ExecuteHandlerType): void {
372 this._executeHandlers[flag.charCodeAt(0)] = handler;
374 public clearExecuteHandler(flag: string): void {
375 if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
377 public setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void {
378 this._executeHandlerFb = handler;
381 public addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable {
382 const ident = this._identifier(id);
383 if (this._csiHandlers[ident] === undefined) {
384 this._csiHandlers[ident] = [];
386 const handlerList = this._csiHandlers[ident];
387 handlerList.push(handler);
390 const handlerIndex = handlerList.indexOf(handler);
391 if (handlerIndex !== -1) {
392 handlerList.splice(handlerIndex, 1);
397 public setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void {
398 this._csiHandlers[this._identifier(id)] = [handler];
400 public clearCsiHandler(id: IFunctionIdentifier): void {
401 if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];
403 public setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {
404 this._csiHandlerFb = callback;
407 public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
408 return this._dcsParser.addHandler(this._identifier(id), handler);
410 public setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void {
411 this._dcsParser.setHandler(this._identifier(id), handler);
413 public clearDcsHandler(id: IFunctionIdentifier): void {
414 this._dcsParser.clearHandler(this._identifier(id));
416 public setDcsHandlerFallback(handler: DcsFallbackHandlerType): void {
417 this._dcsParser.setHandlerFallback(handler);
420 public addOscHandler(ident: number, handler: IOscHandler): IDisposable {
421 return this._oscParser.addHandler(ident, handler);
423 public setOscHandler(ident: number, handler: IOscHandler): void {
424 this._oscParser.setHandler(ident, handler);
426 public clearOscHandler(ident: number): void {
427 this._oscParser.clearHandler(ident);
429 public setOscHandlerFallback(handler: OscFallbackHandlerType): void {
430 this._oscParser.setHandlerFallback(handler);
433 public setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
434 this._errorHandler = callback;
436 public clearErrorHandler(): void {
437 this._errorHandler = this._errorHandlerFb;
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
447 this.precedingCodepoint = 0;
453 * Parse UTF32 codepoints in `data` up to `length`.
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:
463 * - OSC_STRING:OSC_PUT
464 * - DCS_PASSTHROUGH:DCS_PUT
466 public parse(data: Uint32Array, length: number): void {
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;
476 // process input string
477 for (let i = 0; i < length; ++i) {
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);
492 if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
493 this._printHandler(data, i, j);
497 if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
498 this._printHandler(data, i, j);
502 if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
503 this._printHandler(data, i, j);
509 case ParserAction.EXECUTE:
510 if (this._executeHandlers[code]) this._executeHandlers[code]();
511 else this._executeHandlerFb(code);
512 this.precedingCodepoint = 0;
514 case ParserAction.IGNORE:
516 case ParserAction.ERROR:
517 const inject: IParsingState = this._errorHandler(
526 if (inject.abort) return;
527 // inject values: currently not implemented
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) {
540 this._csiHandlerFb(collect << 8 | code, params);
542 this.precedingCodepoint = 0;
544 case ParserAction.PARAM:
545 // inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
549 params.addParam(0); // ZDM
552 params.addSubParam(-1);
554 default: // 0x30 - 0x39
555 params.addDigit(code - 48);
557 } while (++i < length && (code = data[i]) > 0x2f && code < 0x3c);
560 case ParserAction.COLLECT:
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) {
574 this._escHandlerFb(collect << 8 | code);
576 this.precedingCodepoint = 0;
578 case ParserAction.CLEAR:
580 params.addParam(0); // ZDM
583 case ParserAction.DCS_HOOK:
584 dcs.hook(collect << 8 | code, params);
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)) {
597 case ParserAction.DCS_UNHOOK:
598 dcs.unhook(code !== 0x18 && code !== 0x1a);
599 if (code === 0x1b) transition |= ParserState.ESCAPE;
601 params.addParam(0); // ZDM
603 this.precedingCodepoint = 0;
605 case ParserAction.OSC_START:
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)) {
618 case ParserAction.OSC_END:
619 osc.end(code !== 0x18 && code !== 0x1a);
620 if (code === 0x1b) transition |= ParserState.ESCAPE;
622 params.addParam(0); // ZDM
624 this.precedingCodepoint = 0;
627 currentState = transition & TableAccess.TRANSITION_STATE_MASK;
630 // save collected intermediates
631 this._collect = collect;
634 this.currentState = currentState;