Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame^] | 1 | import { fromByteWidth } from './bit-width-util' |
| 2 | import { ValueType } from './value-type' |
| 3 | import { isNumber, isIndirectNumber, isAVector, fixedTypedVectorElementSize, isFixedTypedVector, isTypedVector, typedVectorElementType, packedType, fixedTypedVectorElementType } from './value-type-util' |
| 4 | import { indirect, keyForIndex, keyIndex, readFloat, readInt, readUInt, valueForIndexWithKey } from './reference-util' |
| 5 | import { Long } from '../long'; |
| 6 | import { fromUTF8Array } from './flexbuffers-util'; |
| 7 | import { BitWidth } from './bit-width'; |
| 8 | |
| 9 | export 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 | |
| 25 | export 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 | } |