xref: /aosp_15_r20/external/flatbuffers/ts/flexbuffers/reference.ts (revision 890232f25432b36107d06881e0a25aaa6b473652)
1import { fromByteWidth } from './bit-width-util.js'
2import { ValueType } from './value-type.js'
3import { isNumber, isIndirectNumber, isAVector, fixedTypedVectorElementSize, isFixedTypedVector, isTypedVector, typedVectorElementType, packedType, fixedTypedVectorElementType } from './value-type-util.js'
4import { indirect, keyForIndex, keyIndex, readFloat, readInt, readUInt } from './reference-util.js'
5import { fromUTF8Array } from './flexbuffers-util.js';
6import { BitWidth } from './bit-width.js';
7
8export function toReference(buffer: ArrayBuffer): Reference {
9  const len = buffer.byteLength;
10
11  if (len < 3) {
12    throw "Buffer needs to be bigger than 3";
13  }
14
15  const dataView = new DataView(buffer);
16  const byteWidth = dataView.getUint8(len - 1);
17  const packedType = dataView.getUint8(len - 2);
18  const parentWidth = fromByteWidth(byteWidth);
19  const offset = len - byteWidth - 2;
20
21  return new Reference(dataView, offset, parentWidth, packedType, "/")
22}
23
24function valueForIndexWithKey(index: number, key: string, dataView: DataView, offset: number, parentWidth: number, byteWidth: number, length: number, path: string): Reference {
25  const _indirect = indirect(dataView, offset, parentWidth);
26  const elementOffset = _indirect + index * byteWidth;
27  const packedType = dataView.getUint8(_indirect + length * byteWidth + index);
28  return new Reference(dataView, elementOffset, fromByteWidth(byteWidth), packedType, `${path}/${key}`)
29}
30
31export class Reference {
32  private readonly byteWidth: number
33  private readonly valueType: ValueType
34  private _length = -1
35  constructor(private dataView: DataView, private offset: number, private parentWidth: number, private packedType: ValueType, private path: string) {
36    this.byteWidth = 1 << (packedType & 3)
37    this.valueType = packedType >> 2
38  }
39
40  isNull(): boolean { return this.valueType === ValueType.NULL; }
41  isNumber(): boolean { return isNumber(this.valueType) || isIndirectNumber(this.valueType); }
42  isFloat(): boolean { return ValueType.FLOAT === this.valueType || ValueType.INDIRECT_FLOAT === this.valueType; }
43  isInt(): boolean { return this.isNumber() && !this.isFloat(); }
44  isString(): boolean { return ValueType.STRING === this.valueType || ValueType.KEY === this.valueType; }
45  isBool(): boolean { return ValueType.BOOL === this.valueType; }
46  isBlob(): boolean { return ValueType.BLOB === this.valueType; }
47  isVector(): boolean { return isAVector(this.valueType); }
48  isMap(): boolean { return ValueType.MAP === this.valueType; }
49
50  boolValue(): boolean | null {
51    if (this.isBool()) {
52      return readInt(this.dataView, this.offset, this.parentWidth) > 0;
53    }
54    return null;
55  }
56
57  intValue(): number | bigint | null {
58    if (this.valueType === ValueType.INT) {
59      return readInt(this.dataView, this.offset, this.parentWidth);
60    }
61    if (this.valueType === ValueType.UINT) {
62      return readUInt(this.dataView, this.offset, this.parentWidth);
63    }
64    if (this.valueType === ValueType.INDIRECT_INT) {
65      return readInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth));
66    }
67    if (this.valueType === ValueType.INDIRECT_UINT) {
68      return readUInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth));
69    }
70    return null;
71  }
72
73  floatValue(): number | null {
74    if (this.valueType === ValueType.FLOAT) {
75      return readFloat(this.dataView, this.offset, this.parentWidth);
76    }
77    if (this.valueType === ValueType.INDIRECT_FLOAT) {
78      return readFloat(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth));
79    }
80    return null;
81  }
82
83  numericValue(): number | bigint | null { return this.floatValue() || this.intValue()}
84
85  stringValue(): string | null {
86    if (this.valueType === ValueType.STRING || this.valueType === ValueType.KEY) {
87      const begin = indirect(this.dataView, this.offset, this.parentWidth);
88      return fromUTF8Array(new Uint8Array(this.dataView.buffer, begin, this.length()));
89    }
90    return null;
91  }
92
93  blobValue(): Uint8Array | null {
94    if (this.isBlob()) {
95      const begin = indirect(this.dataView, this.offset, this.parentWidth);
96      return new Uint8Array(this.dataView.buffer, begin, this.length());
97    }
98    return null;
99  }
100
101  get(key: number): Reference {
102    const length = this.length();
103    if (Number.isInteger(key) && isAVector(this.valueType)) {
104      if (key >= length || key < 0) {
105        throw `Key: [${key}] is not applicable on ${this.path} of ${this.valueType} length: ${length}`;
106      }
107      const _indirect = indirect(this.dataView, this.offset, this.parentWidth);
108      const elementOffset = _indirect + key * this.byteWidth;
109      let _packedType = this.dataView.getUint8(_indirect + length * this.byteWidth + key);
110      if (isTypedVector(this.valueType)) {
111        const _valueType = typedVectorElementType(this.valueType);
112        _packedType = packedType(_valueType, BitWidth.WIDTH8);
113      } else if (isFixedTypedVector(this.valueType)) {
114        const _valueType = fixedTypedVectorElementType(this.valueType);
115        _packedType = packedType(_valueType, BitWidth.WIDTH8);
116      }
117      return new Reference(this.dataView, elementOffset, fromByteWidth(this.byteWidth), _packedType, `${this.path}[${key}]`);
118    }
119    if (typeof key === 'string') {
120      const index = keyIndex(key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length);
121      if (index !== null) {
122        return valueForIndexWithKey(index, key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length, this.path)
123      }
124    }
125    throw `Key [${key}] is not applicable on ${this.path} of ${this.valueType}`;
126  }
127
128  length(): number {
129    let size;
130    if (this._length > -1) {
131      return this._length;
132    }
133    if (isFixedTypedVector(this.valueType)) {
134      this._length = fixedTypedVectorElementSize(this.valueType);
135    } else if (this.valueType === ValueType.BLOB
136      || this.valueType === ValueType.MAP
137      || isAVector(this.valueType)) {
138      this._length = readUInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth) - this.byteWidth, fromByteWidth(this.byteWidth)) as number
139    } else if (this.valueType === ValueType.NULL) {
140      this._length = 0;
141    } else if (this.valueType === ValueType.STRING) {
142      const _indirect = indirect(this.dataView, this.offset, this.parentWidth);
143      let sizeByteWidth = this.byteWidth;
144      size = readUInt(this.dataView, _indirect - sizeByteWidth, fromByteWidth(this.byteWidth));
145      while (this.dataView.getInt8(_indirect + (size as number)) !== 0) {
146        sizeByteWidth <<= 1;
147        size = readUInt(this.dataView, _indirect - sizeByteWidth, fromByteWidth(this.byteWidth));
148      }
149      this._length = size as number;
150    } else if (this.valueType === ValueType.KEY) {
151      const _indirect = indirect(this.dataView, this.offset, this.parentWidth);
152      size = 1;
153      while (this.dataView.getInt8(_indirect + size) !== 0) {
154        size++;
155      }
156      this._length = size;
157    } else {
158      this._length = 1;
159    }
160    return Number(this._length);
161  }
162
163  toObject(): unknown {
164    const length = this.length();
165    if (this.isVector()) {
166      const result = [];
167      for (let i = 0; i < length; i++) {
168        result.push(this.get(i).toObject());
169      }
170      return result;
171    }
172    if (this.isMap()) {
173      const result: Record<string, unknown> = {};
174      for (let i = 0; i < length; i++) {
175        const key = keyForIndex(i, this.dataView, this.offset, this.parentWidth, this.byteWidth);
176        result[key] = valueForIndexWithKey(i, key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length, this.path).toObject();
177      }
178      return result;
179    }
180    if (this.isNull()) {
181      return null;
182    }
183    if (this.isBool()) {
184      return this.boolValue();
185    }
186    if (this.isNumber()) {
187      return this.numericValue();
188    }
189    return this.blobValue() || this.stringValue();
190  }
191}
192