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