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}
View as plain text