aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/xterm/src/common/parser/Params.ts
blob: 7071453d0a6de26a429f6922b41796c25438b81d (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
/**
 * Copyright (c) 2019 The xterm.js authors. All rights reserved.
 * @license MIT
 */
import { IParams, ParamsArray } from 'common/parser/Types';

// max value supported for a single param/subparam (clamped to positive int32 range)
const MAX_VALUE = 0x7FFFFFFF;
// max allowed subparams for a single sequence (hardcoded limitation)
const MAX_SUBPARAMS = 256;

/**
 * Params storage class.
 * This type is used by the parser to accumulate sequence parameters and sub parameters
 * and transmit them to the input handler actions.
 *
 * NOTES:
 *  - params object for action handlers is borrowed, use `.toArray` or `.clone` to get a copy
 *  - never read beyond `params.length - 1` (likely to contain arbitrary data)
 *  - `.getSubParams` returns a borrowed typed array, use `.getSubParamsAll` for cloned sub params
 *  - hardcoded limitations:
 *    - max. value for a single (sub) param is 2^31 - 1 (greater values are clamped to that)
 *    - max. 256 sub params possible
 *    - negative values are not allowed beside -1 (placeholder for default value)
 *
 * About ZDM (Zero Default Mode):
 * ZDM is not orchestrated by this class. If the parser is in ZDM,
 * it should add 0 for empty params, otherwise -1. This does not apply
 * to subparams, empty subparams should always be added with -1.
 */
export class Params implements IParams {
  // params store and length
  public params: Int32Array;
  public length: number;

  // sub params store and length
  protected _subParams: Int32Array;
  protected _subParamsLength: number;

  // sub params offsets from param: param idx --> [start, end] offset
  private _subParamsIdx: Uint16Array;
  private _rejectDigits: boolean;
  private _rejectSubDigits: boolean;
  private _digitIsSub: boolean;

  /**
   * Create a `Params` type from JS array representation.
   */
  public static fromArray(values: ParamsArray): Params {
    const params = new Params();
    if (!values.length) {
      return params;
    }
    // skip leading sub params
    for (let i = (Array.isArray(values[0])) ? 1 : 0; i < values.length; ++i) {
      const value = values[i];
      if (Array.isArray(value)) {
        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) {
    if (maxSubParamsLength > MAX_SUBPARAMS) {
      throw new Error('maxSubParamsLength must not be greater than 256');
    }
    this.params = new Int32Array(maxLength);
    this.length = 0;
    this._subParams = new Int32Array(maxSubParamsLength);
    this._subParamsLength = 0;
    this._subParamsIdx = new Uint16Array(maxLength);
    this._rejectDigits = false;
    this._rejectSubDigits = false;
    this._digitIsSub = false;
  }

  /**
   * Clone object.
   */
  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);
    newParams._rejectDigits = this._rejectDigits;
    newParams._rejectSubDigits = this._rejectSubDigits;
    newParams._digitIsSub = this._digitIsSub;
    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(): ParamsArray {
    const res: ParamsArray = [];
    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;
    this._digitIsSub = 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 {
    this._digitIsSub = false;
    if (this.length >= this.maxLength) {
      this._rejectDigits = true;
      return;
    }
    if (value < -1) {
      throw new Error('values lesser than -1 are not allowed');
    }
    this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength;
    this.params[this.length++] = value > MAX_VALUE ? MAX_VALUE : value;
  }

  /**
   * Add a sub parameter value.
   * The sub parameter is automatically associated with the last parameter value.
   * Thus it is not possible to add a subparameter without any parameter added yet.
   * `Params` only stores up to `subParamsLength` sub parameters, any later
   * sub parameter will be ignored.
   */
  public addSubParam(value: number): void {
    this._digitIsSub = true;
    if (!this.length) {
      return;
    }
    if (this._rejectDigits || this._subParamsLength >= this.maxSubParamsLength) {
      this._rejectSubDigits = true;
      return;
    }
    if (value < -1) {
      throw new Error('values lesser than -1 are not allowed');
    }
    this._subParams[this._subParamsLength++] = value > MAX_VALUE ? MAX_VALUE : 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): Int32Array | 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 sub parameters as {idx: subparams} mapping.
   * Note: The values are not borrowed.
   */
  public getSubParamsAll(): {[idx: number]: Int32Array} {
    const result: {[idx: number]: Int32Array} = {};
    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.
   */
  public addDigit(value: number): void {
    let length;
    if (this._rejectDigits
      || !(length = this._digitIsSub ? this._subParamsLength : this.length)
      || (this._digitIsSub && this._rejectSubDigits)
    ) {
      return;
    }

    const store = this._digitIsSub ? this._subParams : this.params;
    const cur = store[length - 1];
    store[length - 1] = ~cur ? Math.min(cur * 10 + value, MAX_VALUE) : value;
  }
}