blob: a93c7431b8bb949998e1cc51e8efe43fa8f5027d [file] [log] [blame]
Austin Schuh272c6132020-11-14 16:37:52 -08001import { 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}