xref: /aosp_15_r20/external/flatbuffers/ts/flexbuffers/builder.ts (revision 890232f25432b36107d06881e0a25aaa6b473652)
1*890232f2SAndroid Build Coastguard Workerimport { BitWidth } from './bit-width.js'
2*890232f2SAndroid Build Coastguard Workerimport { paddingSize, iwidth, uwidth, fwidth, toByteWidth, fromByteWidth } from './bit-width-util.js'
3*890232f2SAndroid Build Coastguard Workerimport { toUTF8Array } from './flexbuffers-util.js'
4*890232f2SAndroid Build Coastguard Workerimport { ValueType } from './value-type.js'
5*890232f2SAndroid Build Coastguard Workerimport { isNumber, isTypedVectorElement, toTypedVector } from './value-type-util.js'
6*890232f2SAndroid Build Coastguard Workerimport { StackValue } from './stack-value.js'
7*890232f2SAndroid Build Coastguard Worker
8*890232f2SAndroid Build Coastguard Workerinterface StackPointer {
9*890232f2SAndroid Build Coastguard Worker  stackPosition: number,
10*890232f2SAndroid Build Coastguard Worker  isVector: boolean
11*890232f2SAndroid Build Coastguard Worker  presorted?: boolean
12*890232f2SAndroid Build Coastguard Worker}
13*890232f2SAndroid Build Coastguard Worker
14*890232f2SAndroid Build Coastguard Workerexport class Builder {
15*890232f2SAndroid Build Coastguard Worker  buffer: ArrayBuffer
16*890232f2SAndroid Build Coastguard Worker  view: DataView
17*890232f2SAndroid Build Coastguard Worker
18*890232f2SAndroid Build Coastguard Worker  readonly stack: Array<StackValue> = [];
19*890232f2SAndroid Build Coastguard Worker  readonly stackPointers: Array<StackPointer> = [];
20*890232f2SAndroid Build Coastguard Worker  offset = 0;
21*890232f2SAndroid Build Coastguard Worker  finished = false;
22*890232f2SAndroid Build Coastguard Worker  readonly stringLookup: Record<string, StackValue> = {};
23*890232f2SAndroid Build Coastguard Worker  readonly keyLookup: Record<string, StackValue> = {};
24*890232f2SAndroid Build Coastguard Worker  readonly keyVectorLookup: Record<string, StackValue> = {};
25*890232f2SAndroid Build Coastguard Worker  readonly indirectIntLookup: Record<number, StackValue> = {};
26*890232f2SAndroid Build Coastguard Worker  readonly indirectUIntLookup: Record<number, StackValue> = {};
27*890232f2SAndroid Build Coastguard Worker  readonly indirectFloatLookup: Record<number, StackValue> = {};
28*890232f2SAndroid Build Coastguard Worker
29*890232f2SAndroid Build Coastguard Worker  constructor(size = 2048, private dedupStrings = true, private dedupKeys = true, private dedupKeyVectors = true) {
30*890232f2SAndroid Build Coastguard Worker    this.buffer = new ArrayBuffer(size > 0 ? size : 2048);
31*890232f2SAndroid Build Coastguard Worker    this.view = new DataView(this.buffer);
32*890232f2SAndroid Build Coastguard Worker  }
33*890232f2SAndroid Build Coastguard Worker
34*890232f2SAndroid Build Coastguard Worker  private align(width: BitWidth) {
35*890232f2SAndroid Build Coastguard Worker    const byteWidth = toByteWidth(width);
36*890232f2SAndroid Build Coastguard Worker    this.offset += paddingSize(this.offset, byteWidth);
37*890232f2SAndroid Build Coastguard Worker    return byteWidth;
38*890232f2SAndroid Build Coastguard Worker  }
39*890232f2SAndroid Build Coastguard Worker
40*890232f2SAndroid Build Coastguard Worker  computeOffset(newValueSize: number): number {
41*890232f2SAndroid Build Coastguard Worker    const targetOffset = this.offset + newValueSize;
42*890232f2SAndroid Build Coastguard Worker    let size = this.buffer.byteLength;
43*890232f2SAndroid Build Coastguard Worker    const prevSize = size;
44*890232f2SAndroid Build Coastguard Worker    while (size < targetOffset) {
45*890232f2SAndroid Build Coastguard Worker      size <<= 1;
46*890232f2SAndroid Build Coastguard Worker    }
47*890232f2SAndroid Build Coastguard Worker    if (prevSize < size) {
48*890232f2SAndroid Build Coastguard Worker      const prevBuffer = this.buffer;
49*890232f2SAndroid Build Coastguard Worker      this.buffer = new ArrayBuffer(size);
50*890232f2SAndroid Build Coastguard Worker      this.view = new DataView(this.buffer);
51*890232f2SAndroid Build Coastguard Worker      new Uint8Array(this.buffer).set(new Uint8Array(prevBuffer), 0);
52*890232f2SAndroid Build Coastguard Worker    }
53*890232f2SAndroid Build Coastguard Worker    return targetOffset;
54*890232f2SAndroid Build Coastguard Worker  }
55*890232f2SAndroid Build Coastguard Worker
56*890232f2SAndroid Build Coastguard Worker  pushInt(value: number, width: BitWidth): void {
57*890232f2SAndroid Build Coastguard Worker    if (width === BitWidth.WIDTH8) {
58*890232f2SAndroid Build Coastguard Worker      this.view.setInt8(this.offset, value);
59*890232f2SAndroid Build Coastguard Worker    } else if (width === BitWidth.WIDTH16) {
60*890232f2SAndroid Build Coastguard Worker      this.view.setInt16(this.offset, value, true);
61*890232f2SAndroid Build Coastguard Worker    } else if (width === BitWidth.WIDTH32) {
62*890232f2SAndroid Build Coastguard Worker      this.view.setInt32(this.offset, value, true);
63*890232f2SAndroid Build Coastguard Worker    } else if (width === BitWidth.WIDTH64) {
64*890232f2SAndroid Build Coastguard Worker      this.view.setBigInt64(this.offset, BigInt(value), true);
65*890232f2SAndroid Build Coastguard Worker    } else {
66*890232f2SAndroid Build Coastguard Worker      throw `Unexpected width: ${width} for value: ${value}`;
67*890232f2SAndroid Build Coastguard Worker    }
68*890232f2SAndroid Build Coastguard Worker  }
69*890232f2SAndroid Build Coastguard Worker
70*890232f2SAndroid Build Coastguard Worker  pushUInt(value: number, width: BitWidth): void {
71*890232f2SAndroid Build Coastguard Worker    if (width === BitWidth.WIDTH8) {
72*890232f2SAndroid Build Coastguard Worker      this.view.setUint8(this.offset, value);
73*890232f2SAndroid Build Coastguard Worker    } else if (width === BitWidth.WIDTH16) {
74*890232f2SAndroid Build Coastguard Worker      this.view.setUint16(this.offset, value, true);
75*890232f2SAndroid Build Coastguard Worker    } else if (width === BitWidth.WIDTH32) {
76*890232f2SAndroid Build Coastguard Worker      this.view.setUint32(this.offset, value, true);
77*890232f2SAndroid Build Coastguard Worker    } else if (width === BitWidth.WIDTH64) {
78*890232f2SAndroid Build Coastguard Worker      this.view.setBigUint64(this.offset, BigInt(value), true);
79*890232f2SAndroid Build Coastguard Worker    } else {
80*890232f2SAndroid Build Coastguard Worker      throw `Unexpected width: ${width} for value: ${value}`;
81*890232f2SAndroid Build Coastguard Worker    }
82*890232f2SAndroid Build Coastguard Worker  }
83*890232f2SAndroid Build Coastguard Worker
84*890232f2SAndroid Build Coastguard Worker  private writeInt(value: number, byteWidth: number) {
85*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(byteWidth);
86*890232f2SAndroid Build Coastguard Worker    this.pushInt(value, fromByteWidth(byteWidth));
87*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
88*890232f2SAndroid Build Coastguard Worker  }
89*890232f2SAndroid Build Coastguard Worker
90*890232f2SAndroid Build Coastguard Worker  private writeUInt(value: number, byteWidth: number) {
91*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(byteWidth);
92*890232f2SAndroid Build Coastguard Worker    this.pushUInt(value, fromByteWidth(byteWidth));
93*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
94*890232f2SAndroid Build Coastguard Worker  }
95*890232f2SAndroid Build Coastguard Worker
96*890232f2SAndroid Build Coastguard Worker  private writeBlob(arrayBuffer: ArrayBuffer) {
97*890232f2SAndroid Build Coastguard Worker    const length = arrayBuffer.byteLength;
98*890232f2SAndroid Build Coastguard Worker    const bitWidth = uwidth(length);
99*890232f2SAndroid Build Coastguard Worker    const byteWidth = this.align(bitWidth);
100*890232f2SAndroid Build Coastguard Worker    this.writeUInt(length, byteWidth);
101*890232f2SAndroid Build Coastguard Worker    const blobOffset = this.offset;
102*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(length);
103*890232f2SAndroid Build Coastguard Worker    new Uint8Array(this.buffer).set(new Uint8Array(arrayBuffer), blobOffset);
104*890232f2SAndroid Build Coastguard Worker    this.stack.push(this.offsetStackValue(blobOffset, ValueType.BLOB, bitWidth));
105*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
106*890232f2SAndroid Build Coastguard Worker  }
107*890232f2SAndroid Build Coastguard Worker
108*890232f2SAndroid Build Coastguard Worker  private writeString(str: string): void {
109*890232f2SAndroid Build Coastguard Worker    if (this.dedupStrings && Object.prototype.hasOwnProperty.call(this.stringLookup, str)) {
110*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.stringLookup[str]);
111*890232f2SAndroid Build Coastguard Worker      return;
112*890232f2SAndroid Build Coastguard Worker    }
113*890232f2SAndroid Build Coastguard Worker    const utf8 = toUTF8Array(str);
114*890232f2SAndroid Build Coastguard Worker    const length = utf8.length;
115*890232f2SAndroid Build Coastguard Worker    const bitWidth = uwidth(length);
116*890232f2SAndroid Build Coastguard Worker    const byteWidth = this.align(bitWidth);
117*890232f2SAndroid Build Coastguard Worker    this.writeUInt(length, byteWidth);
118*890232f2SAndroid Build Coastguard Worker    const stringOffset = this.offset;
119*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(length + 1);
120*890232f2SAndroid Build Coastguard Worker    new Uint8Array(this.buffer).set(utf8, stringOffset);
121*890232f2SAndroid Build Coastguard Worker    const stackValue = this.offsetStackValue(stringOffset, ValueType.STRING, bitWidth);
122*890232f2SAndroid Build Coastguard Worker    this.stack.push(stackValue);
123*890232f2SAndroid Build Coastguard Worker    if (this.dedupStrings) {
124*890232f2SAndroid Build Coastguard Worker      this.stringLookup[str] = stackValue;
125*890232f2SAndroid Build Coastguard Worker    }
126*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
127*890232f2SAndroid Build Coastguard Worker  }
128*890232f2SAndroid Build Coastguard Worker
129*890232f2SAndroid Build Coastguard Worker  private writeKey(str: string): void {
130*890232f2SAndroid Build Coastguard Worker    if (this.dedupKeys && Object.prototype.hasOwnProperty.call(this.keyLookup, str)) {
131*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.keyLookup[str]);
132*890232f2SAndroid Build Coastguard Worker      return;
133*890232f2SAndroid Build Coastguard Worker    }
134*890232f2SAndroid Build Coastguard Worker    const utf8 = toUTF8Array(str);
135*890232f2SAndroid Build Coastguard Worker    const length = utf8.length;
136*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(length + 1);
137*890232f2SAndroid Build Coastguard Worker    new Uint8Array(this.buffer).set(utf8, this.offset);
138*890232f2SAndroid Build Coastguard Worker    const stackValue = this.offsetStackValue(this.offset, ValueType.KEY, BitWidth.WIDTH8);
139*890232f2SAndroid Build Coastguard Worker    this.stack.push(stackValue);
140*890232f2SAndroid Build Coastguard Worker    if (this.dedupKeys) {
141*890232f2SAndroid Build Coastguard Worker      this.keyLookup[str] = stackValue;
142*890232f2SAndroid Build Coastguard Worker    }
143*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
144*890232f2SAndroid Build Coastguard Worker  }
145*890232f2SAndroid Build Coastguard Worker
146*890232f2SAndroid Build Coastguard Worker  private writeStackValue(value: StackValue, byteWidth: number): void {
147*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(byteWidth);
148*890232f2SAndroid Build Coastguard Worker    if (value.isOffset()) {
149*890232f2SAndroid Build Coastguard Worker      const relativeOffset = this.offset - value.offset;
150*890232f2SAndroid Build Coastguard Worker      if (byteWidth === 8 || BigInt(relativeOffset) < (BigInt(1) << BigInt(byteWidth * 8))) {
151*890232f2SAndroid Build Coastguard Worker        this.writeUInt(relativeOffset, byteWidth);
152*890232f2SAndroid Build Coastguard Worker      } else {
153*890232f2SAndroid Build Coastguard Worker        throw `Unexpected size ${byteWidth}. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new`
154*890232f2SAndroid Build Coastguard Worker      }
155*890232f2SAndroid Build Coastguard Worker    } else {
156*890232f2SAndroid Build Coastguard Worker      value.writeToBuffer(byteWidth);
157*890232f2SAndroid Build Coastguard Worker    }
158*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
159*890232f2SAndroid Build Coastguard Worker  }
160*890232f2SAndroid Build Coastguard Worker
161*890232f2SAndroid Build Coastguard Worker  private integrityCheckOnValueAddition() {
162*890232f2SAndroid Build Coastguard Worker    if (this.finished) {
163*890232f2SAndroid Build Coastguard Worker      throw "Adding values after finish is prohibited";
164*890232f2SAndroid Build Coastguard Worker    }
165*890232f2SAndroid Build Coastguard Worker    if (this.stackPointers.length !== 0 && this.stackPointers[this.stackPointers.length - 1].isVector === false) {
166*890232f2SAndroid Build Coastguard Worker      if (this.stack[this.stack.length - 1].type !== ValueType.KEY) {
167*890232f2SAndroid Build Coastguard Worker        throw "Adding value to a map before adding a key is prohibited";
168*890232f2SAndroid Build Coastguard Worker      }
169*890232f2SAndroid Build Coastguard Worker    }
170*890232f2SAndroid Build Coastguard Worker  }
171*890232f2SAndroid Build Coastguard Worker
172*890232f2SAndroid Build Coastguard Worker  private integrityCheckOnKeyAddition() {
173*890232f2SAndroid Build Coastguard Worker    if (this.finished) {
174*890232f2SAndroid Build Coastguard Worker      throw "Adding values after finish is prohibited";
175*890232f2SAndroid Build Coastguard Worker    }
176*890232f2SAndroid Build Coastguard Worker    if (this.stackPointers.length === 0 || this.stackPointers[this.stackPointers.length - 1].isVector) {
177*890232f2SAndroid Build Coastguard Worker      throw "Adding key before starting a map is prohibited";
178*890232f2SAndroid Build Coastguard Worker    }
179*890232f2SAndroid Build Coastguard Worker  }
180*890232f2SAndroid Build Coastguard Worker
181*890232f2SAndroid Build Coastguard Worker  startVector(): void {
182*890232f2SAndroid Build Coastguard Worker    this.stackPointers.push({ stackPosition: this.stack.length, isVector: true });
183*890232f2SAndroid Build Coastguard Worker  }
184*890232f2SAndroid Build Coastguard Worker
185*890232f2SAndroid Build Coastguard Worker  startMap(presorted = false): void {
186*890232f2SAndroid Build Coastguard Worker    this.stackPointers.push({ stackPosition: this.stack.length, isVector: false, presorted: presorted });
187*890232f2SAndroid Build Coastguard Worker  }
188*890232f2SAndroid Build Coastguard Worker
189*890232f2SAndroid Build Coastguard Worker  private endVector(stackPointer: StackPointer) {
190*890232f2SAndroid Build Coastguard Worker    const vecLength = this.stack.length - stackPointer.stackPosition;
191*890232f2SAndroid Build Coastguard Worker    const vec = this.createVector(stackPointer.stackPosition, vecLength, 1);
192*890232f2SAndroid Build Coastguard Worker    this.stack.splice(stackPointer.stackPosition, vecLength);
193*890232f2SAndroid Build Coastguard Worker    this.stack.push(vec);
194*890232f2SAndroid Build Coastguard Worker  }
195*890232f2SAndroid Build Coastguard Worker
196*890232f2SAndroid Build Coastguard Worker  private endMap(stackPointer: StackPointer) {
197*890232f2SAndroid Build Coastguard Worker    if (!stackPointer.presorted) {
198*890232f2SAndroid Build Coastguard Worker      this.sort(stackPointer);
199*890232f2SAndroid Build Coastguard Worker    }
200*890232f2SAndroid Build Coastguard Worker    let keyVectorHash = "";
201*890232f2SAndroid Build Coastguard Worker    for (let i = stackPointer.stackPosition; i < this.stack.length; i += 2) {
202*890232f2SAndroid Build Coastguard Worker      keyVectorHash += `,${this.stack[i].offset}`;
203*890232f2SAndroid Build Coastguard Worker    }
204*890232f2SAndroid Build Coastguard Worker    const vecLength = (this.stack.length - stackPointer.stackPosition) >> 1;
205*890232f2SAndroid Build Coastguard Worker
206*890232f2SAndroid Build Coastguard Worker    if (this.dedupKeyVectors && !Object.prototype.hasOwnProperty.call(this.keyVectorLookup, keyVectorHash)) {
207*890232f2SAndroid Build Coastguard Worker      this.keyVectorLookup[keyVectorHash] = this.createVector(stackPointer.stackPosition, vecLength, 2);
208*890232f2SAndroid Build Coastguard Worker    }
209*890232f2SAndroid Build Coastguard Worker    const keysStackValue = this.dedupKeyVectors ? this.keyVectorLookup[keyVectorHash] : this.createVector(stackPointer.stackPosition, vecLength, 2);
210*890232f2SAndroid Build Coastguard Worker    const valuesStackValue = this.createVector(stackPointer.stackPosition + 1, vecLength, 2, keysStackValue);
211*890232f2SAndroid Build Coastguard Worker    this.stack.splice(stackPointer.stackPosition, vecLength << 1);
212*890232f2SAndroid Build Coastguard Worker    this.stack.push(valuesStackValue);
213*890232f2SAndroid Build Coastguard Worker  }
214*890232f2SAndroid Build Coastguard Worker
215*890232f2SAndroid Build Coastguard Worker  private sort(stackPointer: StackPointer) {
216*890232f2SAndroid Build Coastguard Worker    const view = this.view
217*890232f2SAndroid Build Coastguard Worker    const stack = this.stack
218*890232f2SAndroid Build Coastguard Worker
219*890232f2SAndroid Build Coastguard Worker    function shouldFlip(v1: StackValue, v2: StackValue) {
220*890232f2SAndroid Build Coastguard Worker      if (v1.type !== ValueType.KEY || v2.type !== ValueType.KEY) {
221*890232f2SAndroid Build Coastguard Worker        throw `Stack values are not keys ${v1} | ${v2}. Check if you combined [addKey] with add... method calls properly.`
222*890232f2SAndroid Build Coastguard Worker      }
223*890232f2SAndroid Build Coastguard Worker      let c1, c2;
224*890232f2SAndroid Build Coastguard Worker      let index = 0;
225*890232f2SAndroid Build Coastguard Worker      do {
226*890232f2SAndroid Build Coastguard Worker        c1 = view.getUint8(v1.offset + index);
227*890232f2SAndroid Build Coastguard Worker        c2 = view.getUint8(v2.offset + index);
228*890232f2SAndroid Build Coastguard Worker        if (c2 < c1) return true;
229*890232f2SAndroid Build Coastguard Worker        if (c1 < c2) return false;
230*890232f2SAndroid Build Coastguard Worker        index += 1;
231*890232f2SAndroid Build Coastguard Worker      } while (c1 !== 0 && c2 !== 0);
232*890232f2SAndroid Build Coastguard Worker      return false;
233*890232f2SAndroid Build Coastguard Worker    }
234*890232f2SAndroid Build Coastguard Worker
235*890232f2SAndroid Build Coastguard Worker    function swap(stack: Array<StackValue>, flipIndex: number, i: number) {
236*890232f2SAndroid Build Coastguard Worker      if (flipIndex === i) return;
237*890232f2SAndroid Build Coastguard Worker      const k = stack[flipIndex];
238*890232f2SAndroid Build Coastguard Worker      const v = stack[flipIndex + 1];
239*890232f2SAndroid Build Coastguard Worker      stack[flipIndex] = stack[i];
240*890232f2SAndroid Build Coastguard Worker      stack[flipIndex + 1] = stack[i + 1];
241*890232f2SAndroid Build Coastguard Worker      stack[i] = k;
242*890232f2SAndroid Build Coastguard Worker      stack[i + 1] = v;
243*890232f2SAndroid Build Coastguard Worker    }
244*890232f2SAndroid Build Coastguard Worker
245*890232f2SAndroid Build Coastguard Worker    function selectionSort() {
246*890232f2SAndroid Build Coastguard Worker      for (let i = stackPointer.stackPosition; i < stack.length; i += 2) {
247*890232f2SAndroid Build Coastguard Worker        let flipIndex = i;
248*890232f2SAndroid Build Coastguard Worker        for (let j = i + 2; j < stack.length; j += 2) {
249*890232f2SAndroid Build Coastguard Worker          if (shouldFlip(stack[flipIndex], stack[j])) {
250*890232f2SAndroid Build Coastguard Worker            flipIndex = j;
251*890232f2SAndroid Build Coastguard Worker          }
252*890232f2SAndroid Build Coastguard Worker        }
253*890232f2SAndroid Build Coastguard Worker        if (flipIndex !== i) {
254*890232f2SAndroid Build Coastguard Worker          swap(stack, flipIndex, i);
255*890232f2SAndroid Build Coastguard Worker        }
256*890232f2SAndroid Build Coastguard Worker      }
257*890232f2SAndroid Build Coastguard Worker    }
258*890232f2SAndroid Build Coastguard Worker
259*890232f2SAndroid Build Coastguard Worker    function smaller(v1: StackValue, v2: StackValue) {
260*890232f2SAndroid Build Coastguard Worker      if (v1.type !== ValueType.KEY || v2.type !== ValueType.KEY) {
261*890232f2SAndroid Build Coastguard Worker        throw `Stack values are not keys ${v1} | ${v2}. Check if you combined [addKey] with add... method calls properly.`
262*890232f2SAndroid Build Coastguard Worker      }
263*890232f2SAndroid Build Coastguard Worker      if (v1.offset === v2.offset) {
264*890232f2SAndroid Build Coastguard Worker        return false;
265*890232f2SAndroid Build Coastguard Worker      }
266*890232f2SAndroid Build Coastguard Worker      let c1, c2;
267*890232f2SAndroid Build Coastguard Worker      let index = 0;
268*890232f2SAndroid Build Coastguard Worker      do {
269*890232f2SAndroid Build Coastguard Worker        c1 = view.getUint8(v1.offset + index);
270*890232f2SAndroid Build Coastguard Worker        c2 = view.getUint8(v2.offset + index);
271*890232f2SAndroid Build Coastguard Worker        if (c1 < c2) return true;
272*890232f2SAndroid Build Coastguard Worker        if (c2 < c1) return false;
273*890232f2SAndroid Build Coastguard Worker        index += 1;
274*890232f2SAndroid Build Coastguard Worker      } while (c1 !== 0 && c2 !== 0);
275*890232f2SAndroid Build Coastguard Worker      return false;
276*890232f2SAndroid Build Coastguard Worker    }
277*890232f2SAndroid Build Coastguard Worker
278*890232f2SAndroid Build Coastguard Worker    function quickSort(left: number, right: number) {
279*890232f2SAndroid Build Coastguard Worker
280*890232f2SAndroid Build Coastguard Worker      if (left < right) {
281*890232f2SAndroid Build Coastguard Worker        const mid = left + (((right - left) >> 2)) * 2;
282*890232f2SAndroid Build Coastguard Worker        const pivot = stack[mid];
283*890232f2SAndroid Build Coastguard Worker        let left_new = left;
284*890232f2SAndroid Build Coastguard Worker        let right_new = right;
285*890232f2SAndroid Build Coastguard Worker
286*890232f2SAndroid Build Coastguard Worker        do {
287*890232f2SAndroid Build Coastguard Worker          while (smaller(stack[left_new], pivot)) {
288*890232f2SAndroid Build Coastguard Worker            left_new += 2;
289*890232f2SAndroid Build Coastguard Worker          }
290*890232f2SAndroid Build Coastguard Worker          while (smaller(pivot, stack[right_new])) {
291*890232f2SAndroid Build Coastguard Worker            right_new -= 2;
292*890232f2SAndroid Build Coastguard Worker          }
293*890232f2SAndroid Build Coastguard Worker          if (left_new <= right_new) {
294*890232f2SAndroid Build Coastguard Worker            swap(stack, left_new, right_new);
295*890232f2SAndroid Build Coastguard Worker            left_new += 2;
296*890232f2SAndroid Build Coastguard Worker            right_new -= 2;
297*890232f2SAndroid Build Coastguard Worker          }
298*890232f2SAndroid Build Coastguard Worker        } while (left_new <= right_new);
299*890232f2SAndroid Build Coastguard Worker
300*890232f2SAndroid Build Coastguard Worker        quickSort(left, right_new);
301*890232f2SAndroid Build Coastguard Worker        quickSort(left_new, right);
302*890232f2SAndroid Build Coastguard Worker
303*890232f2SAndroid Build Coastguard Worker      }
304*890232f2SAndroid Build Coastguard Worker    }
305*890232f2SAndroid Build Coastguard Worker
306*890232f2SAndroid Build Coastguard Worker    let sorted = true;
307*890232f2SAndroid Build Coastguard Worker    for (let i = stackPointer.stackPosition; i < this.stack.length - 2; i += 2) {
308*890232f2SAndroid Build Coastguard Worker      if (shouldFlip(this.stack[i], this.stack[i + 2])) {
309*890232f2SAndroid Build Coastguard Worker        sorted = false;
310*890232f2SAndroid Build Coastguard Worker        break;
311*890232f2SAndroid Build Coastguard Worker      }
312*890232f2SAndroid Build Coastguard Worker    }
313*890232f2SAndroid Build Coastguard Worker
314*890232f2SAndroid Build Coastguard Worker    if (!sorted) {
315*890232f2SAndroid Build Coastguard Worker      if (this.stack.length - stackPointer.stackPosition > 40) {
316*890232f2SAndroid Build Coastguard Worker        quickSort(stackPointer.stackPosition, this.stack.length - 2);
317*890232f2SAndroid Build Coastguard Worker      } else {
318*890232f2SAndroid Build Coastguard Worker        selectionSort();
319*890232f2SAndroid Build Coastguard Worker      }
320*890232f2SAndroid Build Coastguard Worker    }
321*890232f2SAndroid Build Coastguard Worker  }
322*890232f2SAndroid Build Coastguard Worker
323*890232f2SAndroid Build Coastguard Worker  end(): void {
324*890232f2SAndroid Build Coastguard Worker    if (this.stackPointers.length < 1) return;
325*890232f2SAndroid Build Coastguard Worker    const pointer = this.stackPointers.pop() as StackPointer;
326*890232f2SAndroid Build Coastguard Worker    if (pointer.isVector) {
327*890232f2SAndroid Build Coastguard Worker      this.endVector(pointer);
328*890232f2SAndroid Build Coastguard Worker    } else {
329*890232f2SAndroid Build Coastguard Worker      this.endMap(pointer);
330*890232f2SAndroid Build Coastguard Worker    }
331*890232f2SAndroid Build Coastguard Worker  }
332*890232f2SAndroid Build Coastguard Worker
333*890232f2SAndroid Build Coastguard Worker  private createVector(start: number, vecLength: number, step: number, keys: StackValue | null = null) {
334*890232f2SAndroid Build Coastguard Worker    let bitWidth = uwidth(vecLength);
335*890232f2SAndroid Build Coastguard Worker    let prefixElements = 1;
336*890232f2SAndroid Build Coastguard Worker    if (keys !== null) {
337*890232f2SAndroid Build Coastguard Worker      const elementWidth = keys.elementWidth(this.offset, 0);
338*890232f2SAndroid Build Coastguard Worker      if (elementWidth > bitWidth) {
339*890232f2SAndroid Build Coastguard Worker        bitWidth = elementWidth;
340*890232f2SAndroid Build Coastguard Worker      }
341*890232f2SAndroid Build Coastguard Worker      prefixElements += 2;
342*890232f2SAndroid Build Coastguard Worker    }
343*890232f2SAndroid Build Coastguard Worker    let vectorType = ValueType.KEY;
344*890232f2SAndroid Build Coastguard Worker    let typed = keys === null;
345*890232f2SAndroid Build Coastguard Worker    for (let i = start; i < this.stack.length; i += step) {
346*890232f2SAndroid Build Coastguard Worker      const elementWidth = this.stack[i].elementWidth(this.offset, i + prefixElements);
347*890232f2SAndroid Build Coastguard Worker      if (elementWidth > bitWidth) {
348*890232f2SAndroid Build Coastguard Worker        bitWidth = elementWidth;
349*890232f2SAndroid Build Coastguard Worker      }
350*890232f2SAndroid Build Coastguard Worker      if (i === start) {
351*890232f2SAndroid Build Coastguard Worker        vectorType = this.stack[i].type;
352*890232f2SAndroid Build Coastguard Worker        typed = typed && isTypedVectorElement(vectorType);
353*890232f2SAndroid Build Coastguard Worker      } else {
354*890232f2SAndroid Build Coastguard Worker        if (vectorType !== this.stack[i].type) {
355*890232f2SAndroid Build Coastguard Worker          typed = false;
356*890232f2SAndroid Build Coastguard Worker        }
357*890232f2SAndroid Build Coastguard Worker      }
358*890232f2SAndroid Build Coastguard Worker    }
359*890232f2SAndroid Build Coastguard Worker    const byteWidth = this.align(bitWidth);
360*890232f2SAndroid Build Coastguard Worker    const fix = typed && isNumber(vectorType) && vecLength >= 2 && vecLength <= 4;
361*890232f2SAndroid Build Coastguard Worker    if (keys !== null) {
362*890232f2SAndroid Build Coastguard Worker      this.writeStackValue(keys, byteWidth);
363*890232f2SAndroid Build Coastguard Worker      this.writeUInt(1 << keys.width, byteWidth);
364*890232f2SAndroid Build Coastguard Worker    }
365*890232f2SAndroid Build Coastguard Worker    if (!fix) {
366*890232f2SAndroid Build Coastguard Worker      this.writeUInt(vecLength, byteWidth);
367*890232f2SAndroid Build Coastguard Worker    }
368*890232f2SAndroid Build Coastguard Worker    const vecOffset = this.offset;
369*890232f2SAndroid Build Coastguard Worker    for (let i = start; i < this.stack.length; i += step) {
370*890232f2SAndroid Build Coastguard Worker      this.writeStackValue(this.stack[i], byteWidth);
371*890232f2SAndroid Build Coastguard Worker    }
372*890232f2SAndroid Build Coastguard Worker    if (!typed) {
373*890232f2SAndroid Build Coastguard Worker      for (let i = start; i < this.stack.length; i += step) {
374*890232f2SAndroid Build Coastguard Worker        this.writeUInt(this.stack[i].storedPackedType(), 1);
375*890232f2SAndroid Build Coastguard Worker      }
376*890232f2SAndroid Build Coastguard Worker    }
377*890232f2SAndroid Build Coastguard Worker    if (keys !== null) {
378*890232f2SAndroid Build Coastguard Worker      return this.offsetStackValue(vecOffset, ValueType.MAP, bitWidth);
379*890232f2SAndroid Build Coastguard Worker    }
380*890232f2SAndroid Build Coastguard Worker    if (typed) {
381*890232f2SAndroid Build Coastguard Worker      const vType = toTypedVector(vectorType, fix ? vecLength : 0);
382*890232f2SAndroid Build Coastguard Worker      return this.offsetStackValue(vecOffset, vType, bitWidth);
383*890232f2SAndroid Build Coastguard Worker    }
384*890232f2SAndroid Build Coastguard Worker    return this.offsetStackValue(vecOffset, ValueType.VECTOR, bitWidth);
385*890232f2SAndroid Build Coastguard Worker  }
386*890232f2SAndroid Build Coastguard Worker
387*890232f2SAndroid Build Coastguard Worker  private nullStackValue() {
388*890232f2SAndroid Build Coastguard Worker    return new StackValue(this, ValueType.NULL, BitWidth.WIDTH8);
389*890232f2SAndroid Build Coastguard Worker  }
390*890232f2SAndroid Build Coastguard Worker
391*890232f2SAndroid Build Coastguard Worker  private boolStackValue(value: boolean) {
392*890232f2SAndroid Build Coastguard Worker    return new StackValue(this, ValueType.BOOL, BitWidth.WIDTH8, value);
393*890232f2SAndroid Build Coastguard Worker  }
394*890232f2SAndroid Build Coastguard Worker
395*890232f2SAndroid Build Coastguard Worker  private intStackValue(value: number | bigint) {
396*890232f2SAndroid Build Coastguard Worker    return new StackValue(this, ValueType.INT, iwidth(value), value as number);
397*890232f2SAndroid Build Coastguard Worker  }
398*890232f2SAndroid Build Coastguard Worker
399*890232f2SAndroid Build Coastguard Worker  private uintStackValue(value: number) {
400*890232f2SAndroid Build Coastguard Worker    return new StackValue(this, ValueType.UINT, uwidth(value), value);
401*890232f2SAndroid Build Coastguard Worker  }
402*890232f2SAndroid Build Coastguard Worker
403*890232f2SAndroid Build Coastguard Worker  private floatStackValue(value: number) {
404*890232f2SAndroid Build Coastguard Worker    return new StackValue(this, ValueType.FLOAT, fwidth(value), value);
405*890232f2SAndroid Build Coastguard Worker  }
406*890232f2SAndroid Build Coastguard Worker
407*890232f2SAndroid Build Coastguard Worker  private offsetStackValue(offset: number, valueType: ValueType, bitWidth: BitWidth): StackValue {
408*890232f2SAndroid Build Coastguard Worker    return new StackValue(this, valueType, bitWidth, null, offset);
409*890232f2SAndroid Build Coastguard Worker  }
410*890232f2SAndroid Build Coastguard Worker
411*890232f2SAndroid Build Coastguard Worker  private finishBuffer() {
412*890232f2SAndroid Build Coastguard Worker    if (this.stack.length !== 1) {
413*890232f2SAndroid Build Coastguard Worker      throw `Stack has to be exactly 1, but it is ${this.stack.length}. You have to end all started vectors and maps before calling [finish]`;
414*890232f2SAndroid Build Coastguard Worker    }
415*890232f2SAndroid Build Coastguard Worker    const value = this.stack[0];
416*890232f2SAndroid Build Coastguard Worker    const byteWidth = this.align(value.elementWidth(this.offset, 0));
417*890232f2SAndroid Build Coastguard Worker    this.writeStackValue(value, byteWidth);
418*890232f2SAndroid Build Coastguard Worker    this.writeUInt(value.storedPackedType(), 1);
419*890232f2SAndroid Build Coastguard Worker    this.writeUInt(byteWidth, 1);
420*890232f2SAndroid Build Coastguard Worker    this.finished = true;
421*890232f2SAndroid Build Coastguard Worker  }
422*890232f2SAndroid Build Coastguard Worker
423*890232f2SAndroid Build Coastguard Worker  add(value: undefined | null | boolean | bigint | number | DataView | string | Array<unknown> | Record<string, unknown> | unknown): void {
424*890232f2SAndroid Build Coastguard Worker    this.integrityCheckOnValueAddition();
425*890232f2SAndroid Build Coastguard Worker    if (typeof value === 'undefined') {
426*890232f2SAndroid Build Coastguard Worker      throw "You need to provide a value";
427*890232f2SAndroid Build Coastguard Worker    }
428*890232f2SAndroid Build Coastguard Worker    if (value === null) {
429*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.nullStackValue());
430*890232f2SAndroid Build Coastguard Worker    } else if (typeof value === "boolean") {
431*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.boolStackValue(value));
432*890232f2SAndroid Build Coastguard Worker    } else if (typeof value === "bigint") {
433*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.intStackValue(value));
434*890232f2SAndroid Build Coastguard Worker    } else if (typeof value == 'number') {
435*890232f2SAndroid Build Coastguard Worker      if (Number.isInteger(value)) {
436*890232f2SAndroid Build Coastguard Worker        this.stack.push(this.intStackValue(value));
437*890232f2SAndroid Build Coastguard Worker      } else {
438*890232f2SAndroid Build Coastguard Worker        this.stack.push(this.floatStackValue(value));
439*890232f2SAndroid Build Coastguard Worker      }
440*890232f2SAndroid Build Coastguard Worker    } else if (ArrayBuffer.isView(value)) {
441*890232f2SAndroid Build Coastguard Worker      this.writeBlob(value.buffer);
442*890232f2SAndroid Build Coastguard Worker    } else if (typeof value === 'string' || value instanceof String) {
443*890232f2SAndroid Build Coastguard Worker      this.writeString(value as string);
444*890232f2SAndroid Build Coastguard Worker    } else if (Array.isArray(value)) {
445*890232f2SAndroid Build Coastguard Worker      this.startVector();
446*890232f2SAndroid Build Coastguard Worker      for (let i = 0; i < value.length; i++) {
447*890232f2SAndroid Build Coastguard Worker        this.add(value[i]);
448*890232f2SAndroid Build Coastguard Worker      }
449*890232f2SAndroid Build Coastguard Worker      this.end();
450*890232f2SAndroid Build Coastguard Worker    } else if (typeof value === 'object') {
451*890232f2SAndroid Build Coastguard Worker      const properties = Object.getOwnPropertyNames(value).sort();
452*890232f2SAndroid Build Coastguard Worker      this.startMap(true);
453*890232f2SAndroid Build Coastguard Worker      for (let i = 0; i < properties.length; i++) {
454*890232f2SAndroid Build Coastguard Worker        const key = properties[i];
455*890232f2SAndroid Build Coastguard Worker        this.addKey(key);
456*890232f2SAndroid Build Coastguard Worker        this.add((value as Record<string, unknown>)[key]);
457*890232f2SAndroid Build Coastguard Worker      }
458*890232f2SAndroid Build Coastguard Worker      this.end();
459*890232f2SAndroid Build Coastguard Worker    } else {
460*890232f2SAndroid Build Coastguard Worker      throw `Unexpected value input ${value}`;
461*890232f2SAndroid Build Coastguard Worker    }
462*890232f2SAndroid Build Coastguard Worker  }
463*890232f2SAndroid Build Coastguard Worker
464*890232f2SAndroid Build Coastguard Worker  finish(): Uint8Array {
465*890232f2SAndroid Build Coastguard Worker    if (!this.finished) {
466*890232f2SAndroid Build Coastguard Worker      this.finishBuffer();
467*890232f2SAndroid Build Coastguard Worker    }
468*890232f2SAndroid Build Coastguard Worker    const result = this.buffer.slice(0, this.offset);
469*890232f2SAndroid Build Coastguard Worker    return new Uint8Array(result);
470*890232f2SAndroid Build Coastguard Worker  }
471*890232f2SAndroid Build Coastguard Worker
472*890232f2SAndroid Build Coastguard Worker  isFinished(): boolean {
473*890232f2SAndroid Build Coastguard Worker    return this.finished;
474*890232f2SAndroid Build Coastguard Worker  }
475*890232f2SAndroid Build Coastguard Worker
476*890232f2SAndroid Build Coastguard Worker  addKey(key: string): void {
477*890232f2SAndroid Build Coastguard Worker    this.integrityCheckOnKeyAddition();
478*890232f2SAndroid Build Coastguard Worker    this.writeKey(key);
479*890232f2SAndroid Build Coastguard Worker  }
480*890232f2SAndroid Build Coastguard Worker
481*890232f2SAndroid Build Coastguard Worker  addInt(value: number, indirect = false, deduplicate = false): void {
482*890232f2SAndroid Build Coastguard Worker    this.integrityCheckOnValueAddition();
483*890232f2SAndroid Build Coastguard Worker    if (!indirect) {
484*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.intStackValue(value));
485*890232f2SAndroid Build Coastguard Worker      return;
486*890232f2SAndroid Build Coastguard Worker    }
487*890232f2SAndroid Build Coastguard Worker    if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectIntLookup, value)) {
488*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.indirectIntLookup[value]);
489*890232f2SAndroid Build Coastguard Worker      return;
490*890232f2SAndroid Build Coastguard Worker    }
491*890232f2SAndroid Build Coastguard Worker    const stackValue = this.intStackValue(value);
492*890232f2SAndroid Build Coastguard Worker    const byteWidth = this.align(stackValue.width);
493*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(byteWidth);
494*890232f2SAndroid Build Coastguard Worker    const valueOffset = this.offset;
495*890232f2SAndroid Build Coastguard Worker    stackValue.writeToBuffer(byteWidth);
496*890232f2SAndroid Build Coastguard Worker    const stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_INT, stackValue.width);
497*890232f2SAndroid Build Coastguard Worker    this.stack.push(stackOffset);
498*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
499*890232f2SAndroid Build Coastguard Worker    if (deduplicate) {
500*890232f2SAndroid Build Coastguard Worker      this.indirectIntLookup[value] = stackOffset;
501*890232f2SAndroid Build Coastguard Worker    }
502*890232f2SAndroid Build Coastguard Worker  }
503*890232f2SAndroid Build Coastguard Worker
504*890232f2SAndroid Build Coastguard Worker  addUInt(value: number, indirect = false, deduplicate = false): void {
505*890232f2SAndroid Build Coastguard Worker    this.integrityCheckOnValueAddition();
506*890232f2SAndroid Build Coastguard Worker    if (!indirect) {
507*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.uintStackValue(value));
508*890232f2SAndroid Build Coastguard Worker      return;
509*890232f2SAndroid Build Coastguard Worker    }
510*890232f2SAndroid Build Coastguard Worker    if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectUIntLookup, value)) {
511*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.indirectUIntLookup[value]);
512*890232f2SAndroid Build Coastguard Worker      return;
513*890232f2SAndroid Build Coastguard Worker    }
514*890232f2SAndroid Build Coastguard Worker    const stackValue = this.uintStackValue(value);
515*890232f2SAndroid Build Coastguard Worker    const byteWidth = this.align(stackValue.width);
516*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(byteWidth);
517*890232f2SAndroid Build Coastguard Worker    const valueOffset = this.offset;
518*890232f2SAndroid Build Coastguard Worker    stackValue.writeToBuffer(byteWidth);
519*890232f2SAndroid Build Coastguard Worker    const stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_UINT, stackValue.width);
520*890232f2SAndroid Build Coastguard Worker    this.stack.push(stackOffset);
521*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
522*890232f2SAndroid Build Coastguard Worker    if (deduplicate) {
523*890232f2SAndroid Build Coastguard Worker      this.indirectUIntLookup[value] = stackOffset;
524*890232f2SAndroid Build Coastguard Worker    }
525*890232f2SAndroid Build Coastguard Worker  }
526*890232f2SAndroid Build Coastguard Worker
527*890232f2SAndroid Build Coastguard Worker  addFloat(value: number, indirect = false, deduplicate = false): void {
528*890232f2SAndroid Build Coastguard Worker    this.integrityCheckOnValueAddition();
529*890232f2SAndroid Build Coastguard Worker    if (!indirect) {
530*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.floatStackValue(value));
531*890232f2SAndroid Build Coastguard Worker      return;
532*890232f2SAndroid Build Coastguard Worker    }
533*890232f2SAndroid Build Coastguard Worker    if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectFloatLookup, value)) {
534*890232f2SAndroid Build Coastguard Worker      this.stack.push(this.indirectFloatLookup[value]);
535*890232f2SAndroid Build Coastguard Worker      return;
536*890232f2SAndroid Build Coastguard Worker    }
537*890232f2SAndroid Build Coastguard Worker    const stackValue = this.floatStackValue(value);
538*890232f2SAndroid Build Coastguard Worker    const byteWidth = this.align(stackValue.width);
539*890232f2SAndroid Build Coastguard Worker    const newOffset = this.computeOffset(byteWidth);
540*890232f2SAndroid Build Coastguard Worker    const valueOffset = this.offset;
541*890232f2SAndroid Build Coastguard Worker    stackValue.writeToBuffer(byteWidth);
542*890232f2SAndroid Build Coastguard Worker    const stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_FLOAT, stackValue.width);
543*890232f2SAndroid Build Coastguard Worker    this.stack.push(stackOffset);
544*890232f2SAndroid Build Coastguard Worker    this.offset = newOffset;
545*890232f2SAndroid Build Coastguard Worker    if (deduplicate) {
546*890232f2SAndroid Build Coastguard Worker      this.indirectFloatLookup[value] = stackOffset;
547*890232f2SAndroid Build Coastguard Worker    }
548*890232f2SAndroid Build Coastguard Worker  }
549*890232f2SAndroid Build Coastguard Worker}
550