cleanup parser rules and docs

This commit is contained in:
Jörg Breitbart
2019-07-31 14:20:36 +02:00
parent 83a1340cc4
commit 7252a5921d
6 changed files with 102 additions and 81 deletions
+1 -2
View File
@@ -231,8 +231,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._parser.setExecuteHandler(C0.SO, () => this.shiftOut());
this._parser.setExecuteHandler(C0.SI, () => this.shiftIn());
// FIXME: What do to with missing? Old code just added those to print.
// some C1 control codes - FIXME: should those be enabled by default?
this._parser.setExecuteHandler(C1.IND, () => this.index());
this._parser.setExecuteHandler(C1.NEL, () => this.nextLine());
this._parser.setExecuteHandler(C1.HTS, () => this.tabSet());
+7 -4
View File
@@ -12,7 +12,8 @@ import { PAYLOAD_LIMIT } from 'common/parser/Constants';
export class DcsParser implements IDcsParser {
private _handlers: IHandlerCollection<IDcsHandler> = Object.create(null);
private _active: IDcsHandler[] = [];
private _empty: IDcsHandler[] = [];
private _active: IDcsHandler[] = this._empty;
private _ident: number = 0;
private _handlerFb: DcsFallbackHandler = () => {};
@@ -53,13 +54,15 @@ export class DcsParser implements IDcsParser {
if (this._active.length) {
this.unhook(false);
}
this._active = [];
this._active = this._empty;
this._ident = 0;
}
public hook(ident: number, params: IParams): void {
// always reset leftover handlers
this.reset();
this._ident = ident;
this._active = this._handlers[ident] || [];
this._active = this._handlers[ident] || this._empty;
if (!this._active.length) {
this._handlerFb(this._ident, 'HOOK', params);
} else {
@@ -95,7 +98,7 @@ export class DcsParser implements IDcsParser {
this._active[j].unhook(false);
}
}
this._active = [];
this._active = this._empty;
this._ident = 0;
}
}
@@ -279,12 +279,10 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
for (state in states) {
parser.currentState = state;
parser.osc = '#';
parser.params = [23];
parser.collect = '#';
parse(parser, '\x1b');
chai.expect(parser.currentState).equal(ParserState.ESCAPE);
chai.expect(parser.osc).equal('');
chai.expect(parser.params).eql([0]);
chai.expect(parser.collect).equal('');
parser.reset();
@@ -396,24 +394,20 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
// C0
parser.currentState = ParserState.ESCAPE;
parser.osc = '#';
parser.params = [123];
parser.collect = '#';
parse(parser, '[');
chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY);
chai.expect(parser.osc).equal('');
chai.expect(parser.params).eql([0]);
chai.expect(parser.collect).equal('');
parser.reset();
// C1
for (state in states) {
parser.currentState = state;
parser.osc = '#';
parser.params = [123];
parser.collect = '#';
parse(parser, '\x9b');
chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY);
chai.expect(parser.osc).equal('');
chai.expect(parser.params).eql([0]);
chai.expect(parser.collect).equal('');
parser.reset();
+27 -32
View File
@@ -334,24 +334,24 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._dcsParser.dispose();
}
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
public setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
this._printHandler = callback;
}
clearPrintHandler(): void {
public clearPrintHandler(): void {
this._printHandler = this._printHandlerFb;
}
setExecuteHandler(flag: string, callback: () => void): void {
public setExecuteHandler(flag: string, callback: () => void): void {
this._executeHandlers[flag.charCodeAt(0)] = callback;
}
clearExecuteHandler(flag: string): void {
public clearExecuteHandler(flag: string): void {
if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
}
setExecuteHandlerFallback(callback: (code: number) => void): void {
public setExecuteHandlerFallback(callback: (code: number) => void): void {
this._executeHandlerFb = callback;
}
addCsiHandler(id: IFunctionIdentifier, callback: CsiHandler): IDisposable {
public addCsiHandler(id: IFunctionIdentifier, callback: CsiHandler): IDisposable {
const ident = this._identifier(id);
if (this._csiHandlers[ident] === undefined) {
this._csiHandlers[ident] = [];
@@ -367,17 +367,17 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
};
}
setCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => void): void {
public setCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => void): void {
this._csiHandlers[this._identifier(id)] = [callback];
}
clearCsiHandler(id: IFunctionIdentifier): void {
public clearCsiHandler(id: IFunctionIdentifier): void {
if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];
}
setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {
public setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {
this._csiHandlerFb = callback;
}
addEscHandler(id: IFunctionIdentifier, callback: EscHandler): IDisposable {
public addEscHandler(id: IFunctionIdentifier, callback: EscHandler): IDisposable {
const ident = this._identifier(id, [0x30, 0x7e]);
if (this._escHandlers[ident] === undefined) {
this._escHandlers[ident] = [];
@@ -393,50 +393,50 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
};
}
setEscHandler(id: IFunctionIdentifier, callback: () => void): void {
public setEscHandler(id: IFunctionIdentifier, callback: () => void): void {
this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [callback];
}
clearEscHandler(id: IFunctionIdentifier): void {
public clearEscHandler(id: IFunctionIdentifier): void {
if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
}
setEscHandlerFallback(callback: (ident: number) => void): void {
public setEscHandlerFallback(callback: (ident: number) => void): void {
this._escHandlerFb = callback;
}
addOscHandler(ident: number, handler: IOscHandler): IDisposable {
public addOscHandler(ident: number, handler: IOscHandler): IDisposable {
return this._oscParser.addOscHandler(ident, handler);
}
setOscHandler(ident: number, handler: IOscHandler): void {
public setOscHandler(ident: number, handler: IOscHandler): void {
this._oscParser.setOscHandler(ident, handler);
}
clearOscHandler(ident: number): void {
public clearOscHandler(ident: number): void {
this._oscParser.clearOscHandler(ident);
}
setOscHandlerFallback(handler: OscFallbackHandler): void {
public setOscHandlerFallback(handler: OscFallbackHandler): void {
this._oscParser.setOscHandlerFallback(handler);
}
addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
return this._dcsParser.addDcsHandler(this._identifier(id), handler);
}
setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void {
public setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void {
this._dcsParser.setDcsHandler(this._identifier(id), handler);
}
clearDcsHandler(id: IFunctionIdentifier): void {
public clearDcsHandler(id: IFunctionIdentifier): void {
this._dcsParser.clearDcsHandler(this._identifier(id));
}
setDcsHandlerFallback(handler: DcsFallbackHandler): void {
public setDcsHandlerFallback(handler: DcsFallbackHandler): void {
this._dcsParser.setDcsHandlerFallback(handler);
}
setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
public setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
this._errorHandler = callback;
}
clearErrorHandler(): void {
public clearErrorHandler(): void {
this._errorHandler = this._errorHandlerFb;
}
reset(): void {
public reset(): void {
this.currentState = this.initialState;
this._oscParser.reset();
this._dcsParser.reset();
@@ -463,7 +463,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
* - OSC_STRING:OSC_PUT
* - DCS_PASSTHROUGH:DCS_PUT
*/
parse(data: Uint32Array, length: number): void {
public parse(data: Uint32Array, length: number): void {
let code = 0;
let transition = 0;
let currentState = this.currentState;
@@ -472,7 +472,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
let collect = this._collect;
const params = this._params;
const table: Uint8Array = this.TRANSITIONS.table;
let callback: Function | null = null;
// process input string
for (let i = 0; i < length; ++i) {
@@ -508,8 +507,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
break;
case ParserAction.EXECUTE:
callback = this._executeHandlers[code];
if (callback) callback();
if (this._executeHandlers[code]) this._executeHandlers[code]();
else this._executeHandlerFb(code);
this.precedingCodepoint = 0;
break;
@@ -581,7 +579,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this.precedingCodepoint = 0;
break;
case ParserAction.CLEAR:
osc.reset();
params.reset();
params.addParam(0); // ZDM
collect = 0;
@@ -603,7 +600,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
case ParserAction.DCS_UNHOOK:
dcs.unhook(code !== 0x18 && code !== 0x1a);
if (code === 0x1b) transition |= ParserState.ESCAPE;
osc.reset();
params.reset();
params.addParam(0); // ZDM
collect = 0;
@@ -625,7 +621,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
case ParserAction.OSC_END:
osc.end(code !== 0x18 && code !== 0x1a);
if (code === 0x1b) transition |= ParserState.ESCAPE;
osc.reset();
params.reset();
params.addParam(0); // ZDM
collect = 0;
@@ -635,7 +630,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
currentState = transition & TableAccess.TRANSITION_STATE_MASK;
}
// save non pushable buffers
// save collected intermediates
this._collect = collect;
// save state
+6 -4
View File
@@ -16,7 +16,7 @@ export class OscParser extends Disposable {
private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);
private _handlerFb: OscFallbackHandler = () => { };
addOscHandler(ident: number, handler: IOscHandler): IDisposable {
public addOscHandler(ident: number, handler: IOscHandler): IDisposable {
if (this._handlers[ident] === undefined) {
this._handlers[ident] = [];
}
@@ -31,13 +31,13 @@ export class OscParser extends Disposable {
}
};
}
setOscHandler(ident: number, handler: IOscHandler): void {
public setOscHandler(ident: number, handler: IOscHandler): void {
this._handlers[ident] = [handler];
}
clearOscHandler(ident: number): void {
public clearOscHandler(ident: number): void {
if (this._handlers[ident]) delete this._handlers[ident];
}
setOscHandlerFallback(handler: OscFallbackHandler): void {
public setOscHandlerFallback(handler: OscFallbackHandler): void {
this._handlerFb = handler;
}
@@ -100,6 +100,8 @@ export class OscParser extends Disposable {
}
public start(): void {
// always reset leftover handlers
this.reset();
this._id = -1;
this._state = OscState.ID;
}
+61 -33
View File
@@ -502,14 +502,13 @@ declare module 'xterm' {
/**
* Adds a handler for CSI escape sequences.
* @param id Specifies the function identifier under which the callback gets registered,
* e.g. {final: 'm'} for SGR.
* @param callback The function to handle the escape sequence. The callback
* is called with the numerical params, as well as the special characters
* (e.g. "$" for DECSCPP). If the sequence has subparams the array will
* contain subarrays with their numercial values.
* Return true if the sequence was handled; false if
* we should try a previous handler (set by addCsiHandler or setCsiHandler).
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {final: 'm'} for SGR.
* @param callback The function to handle the sequence. The callback is
* called with the numerical params. If the sequence has subparams the
* array will contain subarrays with their numercial values.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addCsiHandler or setCsiHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
@@ -517,17 +516,18 @@ declare module 'xterm' {
/**
* Adds a handler for DCS escape sequences.
* @param id Specifies the function identifier under which the callback gets registered,
* e.g. {intermediates: '$' final: 'q'} for DECRQSS.
* @param callback The function to handle the escape sequence. Note that the
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.
* @param callback The function to handle the sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, those will be stored up
* until the sequence is finished. Since DCS sequences are not limited by the amount
* of data this might impose a problem for big payloads. Currently xterm.js limits
* DCS payload to 10 MB which should give enough room for most use cases.
* The function gets numerical parameter and the data as arguments.
* Return true if the sequence was handled; false if
* we should try a previous handler (set by addDcsHandler or setDcsHandler).
* There is currently no way to intercept smaller data chunks, data chunks
* will be stored up until the sequence is finished. Since DCS sequences
* are not limited by the amount of data this might impose a problem for
* big payloads. Currently xterm.js limits DCS payload to 10 MB
* which should give enough room for most use cases.
* The function gets the payload and numerical parameters as arguments.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addDcsHandler or setDcsHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
@@ -535,11 +535,12 @@ declare module 'xterm' {
/**
* Adds a handler for ESC escape sequences.
* @param id Specifies the function identifier under which the callback gets registered,
* e.g. {intermediates: '%' final: 'G'} for default charset selection.
* @param callback The function to handle the escape sequence.
* Return true if the sequence was handled; false if
* we should try a previous handler (set by addEscHandler or setEscHandler).
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {intermediates: '%' final: 'G'} for
* default charset selection.
* @param callback The function to handle the sequence.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addEscHandler or setEscHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
@@ -548,15 +549,17 @@ declare module 'xterm' {
/**
* Adds a handler for OSC escape sequences.
* @param ident The number (first parameter) of the sequence.
* @param callback The function to handle the escape sequence. Note that the
* @param callback The function to handle the sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, those will be stored up
* until the sequence is finished. Since OSC sequences are not limited by the amount
* of data this might impose a problem for big payloads. Currently xterm.js limits
* OSC payload to 10 MB which should give enough room for most use cases.
* The callback is called with OSC data string. Return true if the sequence was handled;
* false if we should try a previous handler (set by addOscHandler or
* setOscHandler). The most recently-added handler is tried first.
* There is currently no way to intercept smaller data chunks, data chunks
* will be stored up until the sequence is finished. Since OSC sequences
* are not limited by the amount of data this might impose a problem for
* big payloads. Currently xterm.js limits OSC payload to 10 MB
* which should give enough room for most use cases.
* The callback is called with OSC data string.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addOscHandler or setOscHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
@@ -988,19 +991,44 @@ declare module 'xterm' {
}
/**
* Data type to register a CSI, DCS or ESC callback in the parser.
* Data type to register a CSI, DCS or ESC callback in the parser in the form:
* ESC I..I F
* CSI Prefix P..P I..I F
* DCS Prefix P..P I..I F data_bytes ST
*
* with these rules/restrictions:
* - prefix can only be used with CSI and DCS
* - only one leading prefix byte is recognized by the parser
* before any other parameter bytes (P..P)
* - intermediate bytes are recognized up to 2
*
* For custom sequences make sure to read ECMA-48 and the resources at
* vt100.net to not clash with existing sequences or reserved address space.
* General recommendations:
* - use private address space (see ECMA-48)
* - use max one intermediate byte (technically not limited by the spec,
* in practice there are no sequences with more than one intermediate byte,
* thus parsers might get confused with more intermediates)
* - test against other common emulators to check whether they escape/ignore
* the sequence correctly
*
* Notes: OSC command registration is handled differently (see addOscHandler)
* APC, PM or SOS is currently not supported
*/
export interface IFunctionIdentifier {
/**
* Optional prefix byte, must be in range \x3c .. \x3f.
* Usable in CSI and DCS.
*/
prefix?: string;
/**
* Optional intermediate bytes, must be in range \x20 .. \x2f.
* Usable in CSI, DCS and ESC.
*/
intermediates?: string;
/**
* Final byte, must be in range \x40 .. \x7e (\x30 .. \x7e for ESC).
* Final byte, must be in range \x40 .. \x7e for CSI and DCS,
* \x30 .. \x7e for ESC.
*/
final: string;
}