Params type implementation
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { Params } from 'common/parser/Params';
|
||||
|
||||
/** `Params` parser shim */
|
||||
function parse(params: Params, s: string): void {
|
||||
params.reset();
|
||||
params.addParam(0);
|
||||
let isSub = false;
|
||||
for (let i = 0; i < s.length; ++i) {
|
||||
let code = s.charCodeAt(i);
|
||||
do {
|
||||
switch (code) {
|
||||
case 0x3b:
|
||||
params.addParam(0);
|
||||
isSub = false;
|
||||
break;
|
||||
case 0x3a:
|
||||
params.addSubParam(-1);
|
||||
isSub = true;
|
||||
break;
|
||||
default: // 0x30 - 0x39
|
||||
if (isSub) params.addSubParamDigit(code - 48);
|
||||
else params.addParamDigit(code - 48);
|
||||
}
|
||||
} while (++i < s.length && (code = s.charCodeAt(i)) > 0x2f && code < 0x3c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
describe('Params', () => {
|
||||
it('should respect ctor args', () => {
|
||||
const params = new Params(12, 23);
|
||||
assert.equal(params.params.length, 12);
|
||||
assert.equal(params.subParams.length, 23);
|
||||
assert.deepEqual(params.toArray(), []);
|
||||
});
|
||||
it('addParam', () => {
|
||||
const params = new Params();
|
||||
params.addParam(1);
|
||||
assert.equal(params.length, 1);
|
||||
assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1]);
|
||||
assert.deepEqual(params.toArray(), [1]);
|
||||
params.addParam(23);
|
||||
assert.equal(params.length, 2);
|
||||
assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1, 23]);
|
||||
assert.deepEqual(params.toArray(), [1, 23]);
|
||||
assert.equal(params.subParamsLength, 0);
|
||||
});
|
||||
it('addSubParam', () => {
|
||||
const params = new Params();
|
||||
params.addParam(1);
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
assert.equal(params.length, 1);
|
||||
assert.equal(params.subParamsLength, 2);
|
||||
assert.deepEqual(params.toArray(), [1, [2, 3]]);
|
||||
params.addParam(12345);
|
||||
params.addSubParam(-1);
|
||||
assert.equal(params.length, 2);
|
||||
assert.equal(params.subParamsLength, 3);
|
||||
assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]);
|
||||
});
|
||||
it('should not add sub params without previous param', () => {
|
||||
const params = new Params();
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
assert.equal(params.length, 0);
|
||||
assert.equal(params.subParamsLength, 0);
|
||||
assert.deepEqual(params.toArray(), []);
|
||||
params.addParam(1);
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
assert.equal(params.length, 1);
|
||||
assert.equal(params.subParamsLength, 2);
|
||||
assert.deepEqual(params.toArray(), [1, [2, 3]]);
|
||||
});
|
||||
it('reset', () => {
|
||||
const params = new Params();
|
||||
params.addParam(1);
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
params.addParam(12345);
|
||||
params.addSubParam(-1);
|
||||
params.reset();
|
||||
assert.equal(params.length, 0);
|
||||
assert.equal(params.subParamsLength, 0);
|
||||
assert.deepEqual(params.toArray(), []);
|
||||
params.addParam(1);
|
||||
params.addSubParam(2);
|
||||
params.addSubParam(3);
|
||||
params.addParam(12345);
|
||||
params.addSubParam(-1);
|
||||
assert.equal(params.length, 2);
|
||||
assert.equal(params.subParamsLength, 3);
|
||||
assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]);
|
||||
});
|
||||
it('Params.fromArray --> toArray', () => {
|
||||
let data: (number | number[])[] = [];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
data = [1, [2, 3], 12345, [-1]];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
data = [38, 2, 50, 100, 150];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
data = [38, 2, 50, 100, [150]];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
data = [38, [2, 50, 100, 150]];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), data);
|
||||
// strip empty sub params
|
||||
data = [38, [2, 50, 100, 150], 5, [], 6];
|
||||
assert.deepEqual(Params.fromArray(data).toArray(), [38, [2, 50, 100, 150], 5, 6]);
|
||||
});
|
||||
it('hasSubParams / getSubParams', () => {
|
||||
const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6]);
|
||||
assert.equal(params.hasSubParams(0), true);
|
||||
assert.deepEqual(params.getSubParams(0), new Int16Array([2, 50, 100, 150]));
|
||||
assert.equal(params.hasSubParams(1), false);
|
||||
assert.deepEqual(params.getSubParams(1), null);
|
||||
assert.equal(params.hasSubParams(2), false);
|
||||
assert.deepEqual(params.getSubParams(2), null);
|
||||
});
|
||||
it('getSubParamsAll', () => {
|
||||
const params = Params.fromArray([1, [2, 3], 7, 12345, [-1]]);
|
||||
assert.deepEqual(params.getSubParamsAll(), {0: new Int16Array([2, 3]), 2: new Int16Array([-1])});
|
||||
});
|
||||
describe('parse tests', () => {
|
||||
it('param defaults to 0 (ZDM - zero default mode)', () => {
|
||||
const params = new Params();
|
||||
parse(params, '');
|
||||
assert.deepEqual(params.toArray(), [0]);
|
||||
});
|
||||
it('sub param defaults to -1', () => {
|
||||
const params = new Params();
|
||||
parse(params, ':');
|
||||
assert.deepEqual(params.toArray(), [0, [-1]]);
|
||||
});
|
||||
it('should correctly reset on new sequence', () => {
|
||||
const params = new Params();
|
||||
parse(params, '1;2;3');
|
||||
assert.deepEqual(params.toArray(), [1, 2, 3]);
|
||||
parse(params, '4');
|
||||
assert.deepEqual(params.toArray(), [4]);
|
||||
parse(params, '4::123:5;6;7');
|
||||
assert.deepEqual(params.toArray(), [4, [-1, 123, 5], 6, 7]);
|
||||
parse(params, '');
|
||||
assert.deepEqual(params.toArray(), [0]);
|
||||
});
|
||||
it('should handle length restrictions correctly', () => {
|
||||
// restrict to 3 params and 3 sub params
|
||||
const params = new Params(3, 3);
|
||||
parse(params, '1;2;3');
|
||||
assert.deepEqual(params.toArray(), [1, 2, 3]);
|
||||
parse(params, '4');
|
||||
assert.deepEqual(params.toArray(), [4]);
|
||||
parse(params, '4::123:5;6;7');
|
||||
assert.deepEqual(params.toArray(), [4, [-1, 123, 5], 6, 7]);
|
||||
parse(params, '');
|
||||
assert.deepEqual(params.toArray(), [0]);
|
||||
// overlong params
|
||||
parse(params, '1;2;3;4;5;6;7');
|
||||
assert.deepEqual(params.toArray(), [1, 2, 3]);
|
||||
// overlong sub params
|
||||
parse(params, '4;38:2::50:100:150;48:5:22');
|
||||
assert.deepEqual(params.toArray(), [4, 38, [2, -1, 50], 48]);
|
||||
});
|
||||
it('typical sequences', () => {
|
||||
const params = new Params();
|
||||
// SGR with semicolon syntax
|
||||
parse(params, '0;4;38;2;50;100;150;48;5;22');
|
||||
assert.deepEqual(params.toArray(), [0, 4, 38, 2, 50, 100, 150, 48, 5, 22]);
|
||||
// SGR mixed style (partly wrong)
|
||||
parse(params, '0;4;38;2;50:100:150;48;5:22');
|
||||
assert.deepEqual(params.toArray(), [0, 4, 38, 2, 50, [100, 150], 48, 5, [22]]);
|
||||
// SGR colon style
|
||||
parse(params, '0;4;38:2::50:100:150;48:5:22');
|
||||
assert.deepEqual(params.toArray(), [0, 4, 38, [2, -1, 50, 100, 150], 48, [5, 22]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { StringToUtf32 } from 'common/input/TextDecoder';
|
||||
|
||||
// TODO: move to Types
|
||||
interface IParamsConstructor {
|
||||
new(maxLength: number, maxSubParamsLength: number): IParams;
|
||||
fromArray(values: (number | number[])[]): IParams;
|
||||
}
|
||||
|
||||
interface IParams {
|
||||
/** from ctor */
|
||||
maxLength: number;
|
||||
maxSubParamsLength: number;
|
||||
|
||||
/** param values and its length */
|
||||
params: Int16Array;
|
||||
length: number;
|
||||
|
||||
/** sub params and its length */
|
||||
subParams: Int16Array;
|
||||
subParamsLength: number;
|
||||
|
||||
/** methods */
|
||||
clone(): IParams;
|
||||
toArray(): (number | number[])[];
|
||||
reset(): void;
|
||||
addParam(value: number): void;
|
||||
addSubParam(value: number): void;
|
||||
hasSubParams(idx: number): boolean;
|
||||
getSubParams(idx: number): Int16Array | null;
|
||||
getSubParamsAll(): {[idx: number]: Int16Array};
|
||||
}
|
||||
|
||||
/**
|
||||
* Params storage class.
|
||||
* This type is used by the parser to acuumulate sequence parameters and sub parameters
|
||||
* and transmit them to the input handler actions.
|
||||
* Note: The params object for the handler actions is borrowed from the parser
|
||||
* and will be lost after the handler exits. Use either `toArray` or `clone` to get
|
||||
* a stable copy of the data.
|
||||
*/
|
||||
export class Params implements IParams {
|
||||
// params store and length
|
||||
public params: Int16Array;
|
||||
public length: number;
|
||||
|
||||
// sub params store and length
|
||||
public subParams: Int16Array;
|
||||
public subParamsLength: number;
|
||||
|
||||
// sub params offsets from param: param idx --> [start, end] offset
|
||||
private _subParamsIdx: Uint16Array;
|
||||
private _rejectDigits: boolean;
|
||||
private _rejectSubDigits: boolean;
|
||||
|
||||
/**
|
||||
* Create a `Params` type from JS array representation.
|
||||
*/
|
||||
public static fromArray(values: (number | number[])[]): Params {
|
||||
const params = new Params();
|
||||
if (!values.length) {
|
||||
return params;
|
||||
}
|
||||
// skip leading sub params
|
||||
for (let i = (values[0] instanceof Array) ? 1 : 0; i < values.length; ++i) {
|
||||
const value = values[i];
|
||||
if (value instanceof Array) {
|
||||
for (let k = 0; k < value.length; ++k) {
|
||||
params.addSubParam(value[k]);
|
||||
}
|
||||
} else {
|
||||
params.addParam(value);
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxLength max length of storable parameters
|
||||
* @param maxSubParamsLength max length of storable sub parameters
|
||||
*/
|
||||
constructor(public maxLength: number = 32, public maxSubParamsLength: number = 32) {
|
||||
// precondition: subparams cannot be more than 256
|
||||
if (maxSubParamsLength > 256) {
|
||||
throw new Error('maxSubParamsLength must not be greater than 256');
|
||||
}
|
||||
this.params = new Int16Array(maxLength);
|
||||
this.length = 0;
|
||||
this.subParams = new Int16Array(maxSubParamsLength);
|
||||
this.subParamsLength = 0;
|
||||
this._subParamsIdx = new Uint16Array(maxLength);
|
||||
this._rejectDigits = false;
|
||||
this._rejectSubDigits = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone object to its own copy.
|
||||
*/
|
||||
public clone(): Params {
|
||||
const newParams = new Params(this.maxLength, this.maxSubParamsLength);
|
||||
newParams.params.set(this.params);
|
||||
newParams.length = this.length;
|
||||
newParams.subParams.set(this.subParams);
|
||||
newParams.subParamsLength = this.subParamsLength;
|
||||
newParams._subParamsIdx.set(this._subParamsIdx);
|
||||
return newParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JS array representation of the current parameters and sub parameters.
|
||||
* The array is structured as follows:
|
||||
* sequence: "1;2:3:4;5::6"
|
||||
* array : [1, 2, [3, 4], 5, [-1, 6]]
|
||||
*/
|
||||
public toArray(): (number | number[])[] {
|
||||
const res: (number | number[])[] = [];
|
||||
for (let i = 0; i < this.length; ++i) {
|
||||
res.push(this.params[i]);
|
||||
const start = this._subParamsIdx[i] >> 8;
|
||||
const end = this._subParamsIdx[i] & 0xFF;
|
||||
if (end - start > 0) {
|
||||
res.push(Array.prototype.slice.call(this.subParams, start, end));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to initial empty state.
|
||||
*/
|
||||
public reset(): void {
|
||||
this.length = 0;
|
||||
this.subParamsLength = 0;
|
||||
this._rejectDigits = false;
|
||||
this._rejectSubDigits = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a parameter value.
|
||||
* `Params` only stores up to `maxLength` parameters, any later
|
||||
* parameter will be ignored.
|
||||
* Note: VT devices only stored up to 16 values, xterm seems to
|
||||
* store up to 30.
|
||||
*/
|
||||
public addParam(value: number): void {
|
||||
if (this.length >= this.maxLength) {
|
||||
this._rejectDigits = true;
|
||||
return;
|
||||
}
|
||||
this._subParamsIdx[this.length] = this.subParamsLength << 8 | this.subParamsLength;
|
||||
this.params[this.length++] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sub parameter value.
|
||||
* The sub parameter is automatically associated with the last parameter value.
|
||||
* If there is no parameter yet the sub parameter is ingored.
|
||||
* `Params` only stores up to `subParamsLength` sub parameters, any later
|
||||
* sub parameter will be ignored.
|
||||
*/
|
||||
public addSubParam(value: number): void {
|
||||
if (!this.length || this.subParamsLength >= this.maxSubParamsLength) {
|
||||
this._rejectSubDigits = true;
|
||||
return;
|
||||
}
|
||||
this.subParams[this.subParamsLength++] = value;
|
||||
this._subParamsIdx[this.length - 1]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether parameter at index `idx` has sub parameters.
|
||||
*/
|
||||
public hasSubParams(idx: number): boolean {
|
||||
return ((this._subParamsIdx[idx] & 0xFF) - (this._subParamsIdx[idx] >> 8) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sub parameters for parameter at index `idx`.
|
||||
* Note: The values are borrowed, thus you need to copy
|
||||
* the values if you need to hold them in nonlocal scope.
|
||||
*/
|
||||
public getSubParams(idx: number): Int16Array | null {
|
||||
const start = this._subParamsIdx[idx] >> 8;
|
||||
const end = this._subParamsIdx[idx] & 0xFF;
|
||||
if (end - start > 0) {
|
||||
return this.subParams.subarray(start, end);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all kown sub parameters as {idx: subparams} mapping.
|
||||
* Note: The values are not borrowed, thus it is safe to hold
|
||||
* them without copying.
|
||||
*/
|
||||
public getSubParamsAll(): {[idx: number]: Int16Array} {
|
||||
const result: {[idx: number]: Int16Array} = {};
|
||||
for (let i = 0; i < this.length; ++i) {
|
||||
const start = this._subParamsIdx[i] >> 8;
|
||||
const end = this._subParamsIdx[i] & 0xFF;
|
||||
if (end - start > 0) {
|
||||
result[i] = this.subParams.slice(start, end);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single digit value to current parameter.
|
||||
* This is used by the parser to account digits on a char by char basis.
|
||||
* Do not use this method directly, consider using `addParam` instead.
|
||||
*/
|
||||
public addParamDigit(value: number): void {
|
||||
if (this._rejectDigits) {
|
||||
return;
|
||||
}
|
||||
this.params[this.length - 1] = this.params[this.length - 1] * 10 + value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single digit value to current sub parameter.
|
||||
* This is used by the parser to account digits on a char by char basis.
|
||||
* Do not use this method directly, consider using `addSubParam` instead.
|
||||
*/
|
||||
public addSubParamDigit(value: number): void {
|
||||
if (!this.subParamsLength || this._rejectDigits || this._rejectSubDigits) {
|
||||
return;
|
||||
}
|
||||
if (this.subParams[this.subParamsLength - 1] === -1) {
|
||||
this.subParams[this.subParamsLength - 1] = value;
|
||||
} else {
|
||||
this.subParams[this.subParamsLength - 1] = this.subParams[this.subParamsLength - 1] * 10 + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user