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