| import {fromByteWidth} from './bit-width-util.js'; |
| import {BitWidth} from './bit-width.js'; |
| import {fromUTF8Array} from './flexbuffers-util.js'; |
| import { |
| indirect, |
| keyForIndex, |
| keyIndex, |
| readFloat, |
| readInt, |
| readUInt, |
| } from './reference-util.js'; |
| import { |
| fixedTypedVectorElementSize, |
| fixedTypedVectorElementType, |
| isAVector, |
| isFixedTypedVector, |
| isIndirectNumber, |
| isNumber, |
| isTypedVector, |
| packedType, |
| typedVectorElementType, |
| } from './value-type-util.js'; |
| import {ValueType} from './value-type.js'; |
| |
| export function toReference(buffer: ArrayBuffer): Reference { |
| const len = buffer.byteLength; |
| |
| if (len < 3) { |
| throw 'Buffer needs to be bigger than 3'; |
| } |
| |
| const dataView = new DataView(buffer); |
| const byteWidth = dataView.getUint8(len - 1); |
| const packedType = dataView.getUint8(len - 2); |
| const parentWidth = fromByteWidth(byteWidth); |
| const offset = len - byteWidth - 2; |
| |
| return new Reference(dataView, offset, parentWidth, packedType, '/'); |
| } |
| |
| function valueForIndexWithKey( |
| index: number, |
| key: string, |
| dataView: DataView, |
| offset: number, |
| parentWidth: number, |
| byteWidth: number, |
| length: number, |
| path: string, |
| ): Reference { |
| const _indirect = indirect(dataView, offset, parentWidth); |
| const elementOffset = _indirect + index * byteWidth; |
| const packedType = dataView.getUint8(_indirect + length * byteWidth + index); |
| return new Reference( |
| dataView, |
| elementOffset, |
| fromByteWidth(byteWidth), |
| packedType, |
| `${path}/${key}`, |
| ); |
| } |
| |
| export class Reference { |
| private readonly byteWidth: number; |
| private readonly valueType: ValueType; |
| private _length = -1; |
| constructor( |
| private dataView: DataView, |
| private offset: number, |
| private parentWidth: number, |
| private packedType: ValueType, |
| private path: string, |
| ) { |
| this.byteWidth = 1 << (packedType & 3); |
| this.valueType = packedType >> 2; |
| } |
| |
| isNull(): boolean { |
| return this.valueType === ValueType.NULL; |
| } |
| isNumber(): boolean { |
| return isNumber(this.valueType) || isIndirectNumber(this.valueType); |
| } |
| isFloat(): boolean { |
| return ( |
| ValueType.FLOAT === this.valueType || |
| ValueType.INDIRECT_FLOAT === this.valueType |
| ); |
| } |
| isInt(): boolean { |
| return this.isNumber() && !this.isFloat(); |
| } |
| isString(): boolean { |
| return ( |
| ValueType.STRING === this.valueType || ValueType.KEY === this.valueType |
| ); |
| } |
| isBool(): boolean { |
| return ValueType.BOOL === this.valueType; |
| } |
| isBlob(): boolean { |
| return ValueType.BLOB === this.valueType; |
| } |
| isVector(): boolean { |
| return isAVector(this.valueType); |
| } |
| isMap(): boolean { |
| return ValueType.MAP === this.valueType; |
| } |
| |
| boolValue(): boolean | null { |
| if (this.isBool()) { |
| return readInt(this.dataView, this.offset, this.parentWidth) > 0; |
| } |
| return null; |
| } |
| |
| intValue(): number | bigint | null { |
| if (this.valueType === ValueType.INT) { |
| return readInt(this.dataView, this.offset, this.parentWidth); |
| } |
| if (this.valueType === ValueType.UINT) { |
| return readUInt(this.dataView, this.offset, this.parentWidth); |
| } |
| if (this.valueType === ValueType.INDIRECT_INT) { |
| return readInt( |
| this.dataView, |
| indirect(this.dataView, this.offset, this.parentWidth), |
| fromByteWidth(this.byteWidth), |
| ); |
| } |
| if (this.valueType === ValueType.INDIRECT_UINT) { |
| return readUInt( |
| this.dataView, |
| indirect(this.dataView, this.offset, this.parentWidth), |
| fromByteWidth(this.byteWidth), |
| ); |
| } |
| return null; |
| } |
| |
| floatValue(): number | null { |
| if (this.valueType === ValueType.FLOAT) { |
| return readFloat(this.dataView, this.offset, this.parentWidth); |
| } |
| if (this.valueType === ValueType.INDIRECT_FLOAT) { |
| return readFloat( |
| this.dataView, |
| indirect(this.dataView, this.offset, this.parentWidth), |
| fromByteWidth(this.byteWidth), |
| ); |
| } |
| return null; |
| } |
| |
| numericValue(): number | bigint | null { |
| return this.floatValue() || this.intValue(); |
| } |
| |
| stringValue(): string | null { |
| if ( |
| this.valueType === ValueType.STRING || |
| this.valueType === ValueType.KEY |
| ) { |
| const begin = indirect(this.dataView, this.offset, this.parentWidth); |
| return fromUTF8Array( |
| new Uint8Array(this.dataView.buffer, begin, this.length()), |
| ); |
| } |
| return null; |
| } |
| |
| blobValue(): Uint8Array | null { |
| if (this.isBlob()) { |
| const begin = indirect(this.dataView, this.offset, this.parentWidth); |
| return new Uint8Array(this.dataView.buffer, begin, this.length()); |
| } |
| return null; |
| } |
| |
| get(key: number): Reference { |
| const length = this.length(); |
| if (Number.isInteger(key) && isAVector(this.valueType)) { |
| if (key >= length || key < 0) { |
| throw `Key: [${key}] is not applicable on ${this.path} of ${this.valueType} length: ${length}`; |
| } |
| const _indirect = indirect(this.dataView, this.offset, this.parentWidth); |
| const elementOffset = _indirect + key * this.byteWidth; |
| let _packedType = this.dataView.getUint8( |
| _indirect + length * this.byteWidth + key, |
| ); |
| if (isTypedVector(this.valueType)) { |
| const _valueType = typedVectorElementType(this.valueType); |
| _packedType = packedType(_valueType, BitWidth.WIDTH8); |
| } else if (isFixedTypedVector(this.valueType)) { |
| const _valueType = fixedTypedVectorElementType(this.valueType); |
| _packedType = packedType(_valueType, BitWidth.WIDTH8); |
| } |
| return new Reference( |
| this.dataView, |
| elementOffset, |
| fromByteWidth(this.byteWidth), |
| _packedType, |
| `${this.path}[${key}]`, |
| ); |
| } |
| if (typeof key === 'string') { |
| const index = keyIndex( |
| key, |
| this.dataView, |
| this.offset, |
| this.parentWidth, |
| this.byteWidth, |
| length, |
| ); |
| if (index !== null) { |
| return valueForIndexWithKey( |
| index, |
| key, |
| this.dataView, |
| this.offset, |
| this.parentWidth, |
| this.byteWidth, |
| length, |
| this.path, |
| ); |
| } |
| } |
| throw `Key [${key}] is not applicable on ${this.path} of ${this.valueType}`; |
| } |
| |
| length(): number { |
| let size; |
| if (this._length > -1) { |
| return this._length; |
| } |
| if (isFixedTypedVector(this.valueType)) { |
| this._length = fixedTypedVectorElementSize(this.valueType); |
| } else if ( |
| this.valueType === ValueType.BLOB || |
| this.valueType === ValueType.MAP || |
| isAVector(this.valueType) |
| ) { |
| this._length = readUInt( |
| this.dataView, |
| indirect(this.dataView, this.offset, this.parentWidth) - this.byteWidth, |
| fromByteWidth(this.byteWidth), |
| ) as number; |
| } else if (this.valueType === ValueType.NULL) { |
| this._length = 0; |
| } else if (this.valueType === ValueType.STRING) { |
| const _indirect = indirect(this.dataView, this.offset, this.parentWidth); |
| let sizeByteWidth = this.byteWidth; |
| size = readUInt( |
| this.dataView, |
| _indirect - sizeByteWidth, |
| fromByteWidth(this.byteWidth), |
| ); |
| while (this.dataView.getInt8(_indirect + (size as number)) !== 0) { |
| sizeByteWidth <<= 1; |
| size = readUInt( |
| this.dataView, |
| _indirect - sizeByteWidth, |
| fromByteWidth(this.byteWidth), |
| ); |
| } |
| this._length = size as number; |
| } else if (this.valueType === ValueType.KEY) { |
| const _indirect = indirect(this.dataView, this.offset, this.parentWidth); |
| size = 1; |
| while (this.dataView.getInt8(_indirect + size) !== 0) { |
| size++; |
| } |
| this._length = size; |
| } else { |
| this._length = 1; |
| } |
| return Number(this._length); |
| } |
| |
| toObject(): unknown { |
| const length = this.length(); |
| if (this.isVector()) { |
| const result = []; |
| for (let i = 0; i < length; i++) { |
| result.push(this.get(i).toObject()); |
| } |
| return result; |
| } |
| if (this.isMap()) { |
| const result: Record<string, unknown> = {}; |
| for (let i = 0; i < length; i++) { |
| const key = keyForIndex( |
| i, |
| this.dataView, |
| this.offset, |
| this.parentWidth, |
| this.byteWidth, |
| ); |
| result[key] = valueForIndexWithKey( |
| i, |
| key, |
| this.dataView, |
| this.offset, |
| this.parentWidth, |
| this.byteWidth, |
| length, |
| this.path, |
| ).toObject(); |
| } |
| return result; |
| } |
| if (this.isNull()) { |
| return null; |
| } |
| if (this.isBool()) { |
| return this.boolValue(); |
| } |
| if (this.isNumber()) { |
| return this.numericValue(); |
| } |
| return this.blobValue() || this.stringValue(); |
| } |
| } |