blob: a765eea87f7819e61e51bc3f041c36508ff33c6b [file] [log] [blame]
James Kuszmaulabb77132020-08-01 19:56:16 -07001// This library provides a few basic reflection utilities for Flatbuffers.
2// Currently, this only really supports the level of reflection that would
3// be necessary to convert a serialized flatbuffer to JSON using just a
4// reflection.Schema flatbuffer describing the type.
5// The current implementation is also not necessarily robust to invalidly
6// constructed flatbuffers.
7// See reflection_test_main.ts for sample usage.
8
James Kuszmauldac091f2022-03-22 09:35:06 -07009import * as aos from 'org_frc971/aos/configuration_generated';
10import * as reflection from '../../../external/com_github_google_flatbuffers/reflection/reflection_generated';
11import {ByteBuffer} from 'flatbuffers';
James Kuszmaulabb77132020-08-01 19:56:16 -070012
13// Returns the size, in bytes, of the given type. For vectors/strings/etc.
14// returns the size of the offset.
Austin Schuh7c75e582020-11-14 16:41:18 -080015function typeSize(baseType: reflection.BaseType): number {
James Kuszmaulabb77132020-08-01 19:56:16 -070016 switch (baseType) {
Austin Schuh7c75e582020-11-14 16:41:18 -080017 case reflection.BaseType.None:
18 case reflection.BaseType.UType:
19 case reflection.BaseType.Bool:
20 case reflection.BaseType.Byte:
21 case reflection.BaseType.UByte:
James Kuszmaulabb77132020-08-01 19:56:16 -070022 return 1;
Austin Schuh7c75e582020-11-14 16:41:18 -080023 case reflection.BaseType.Short:
24 case reflection.BaseType.UShort:
James Kuszmaulabb77132020-08-01 19:56:16 -070025 return 2;
Austin Schuh7c75e582020-11-14 16:41:18 -080026 case reflection.BaseType.Int:
27 case reflection.BaseType.UInt:
James Kuszmaulabb77132020-08-01 19:56:16 -070028 return 4;
Austin Schuh7c75e582020-11-14 16:41:18 -080029 case reflection.BaseType.Long:
30 case reflection.BaseType.ULong:
James Kuszmaulabb77132020-08-01 19:56:16 -070031 return 8;
Austin Schuh7c75e582020-11-14 16:41:18 -080032 case reflection.BaseType.Float:
James Kuszmaulabb77132020-08-01 19:56:16 -070033 return 4;
Austin Schuh7c75e582020-11-14 16:41:18 -080034 case reflection.BaseType.Double:
James Kuszmaulabb77132020-08-01 19:56:16 -070035 return 8;
Austin Schuh7c75e582020-11-14 16:41:18 -080036 case reflection.BaseType.String:
37 case reflection.BaseType.Vector:
38 case reflection.BaseType.Obj:
39 case reflection.BaseType.Union:
40 case reflection.BaseType.Array:
James Kuszmaulabb77132020-08-01 19:56:16 -070041 return 4;
42 }
43}
44
45// Returns whether the given type is a scalar type.
Austin Schuh7c75e582020-11-14 16:41:18 -080046function isScalar(baseType: reflection.BaseType): boolean {
James Kuszmaulabb77132020-08-01 19:56:16 -070047 switch (baseType) {
Austin Schuh7c75e582020-11-14 16:41:18 -080048 case reflection.BaseType.UType:
49 case reflection.BaseType.Bool:
50 case reflection.BaseType.Byte:
51 case reflection.BaseType.UByte:
52 case reflection.BaseType.Short:
53 case reflection.BaseType.UShort:
54 case reflection.BaseType.Int:
55 case reflection.BaseType.UInt:
56 case reflection.BaseType.Long:
57 case reflection.BaseType.ULong:
58 case reflection.BaseType.Float:
59 case reflection.BaseType.Double:
James Kuszmaulabb77132020-08-01 19:56:16 -070060 return true;
Austin Schuh7c75e582020-11-14 16:41:18 -080061 case reflection.BaseType.None:
62 case reflection.BaseType.String:
63 case reflection.BaseType.Vector:
64 case reflection.BaseType.Obj:
65 case reflection.BaseType.Union:
66 case reflection.BaseType.Array:
James Kuszmaulabb77132020-08-01 19:56:16 -070067 return false;
68 }
69}
70
71// Returns whether the given type is integer or not.
Austin Schuh7c75e582020-11-14 16:41:18 -080072function isInteger(baseType: reflection.BaseType): boolean {
James Kuszmaulabb77132020-08-01 19:56:16 -070073 switch (baseType) {
Austin Schuh7c75e582020-11-14 16:41:18 -080074 case reflection.BaseType.UType:
75 case reflection.BaseType.Bool:
76 case reflection.BaseType.Byte:
77 case reflection.BaseType.UByte:
78 case reflection.BaseType.Short:
79 case reflection.BaseType.UShort:
80 case reflection.BaseType.Int:
81 case reflection.BaseType.UInt:
82 case reflection.BaseType.Long:
83 case reflection.BaseType.ULong:
James Kuszmaulabb77132020-08-01 19:56:16 -070084 return true;
Austin Schuh7c75e582020-11-14 16:41:18 -080085 case reflection.BaseType.Float:
86 case reflection.BaseType.Double:
87 case reflection.BaseType.None:
88 case reflection.BaseType.String:
89 case reflection.BaseType.Vector:
90 case reflection.BaseType.Obj:
91 case reflection.BaseType.Union:
92 case reflection.BaseType.Array:
James Kuszmaulabb77132020-08-01 19:56:16 -070093 return false;
94 }
95}
96
97// Returns whether the given type is a long--this is needed to know whether it
98// can be represented by the normal javascript number (8-byte integers require a
99// special type, since the native number type is an 8-byte double, which won't
100// represent 8-byte integers to full precision).
Austin Schuh7c75e582020-11-14 16:41:18 -0800101function isLong(baseType: reflection.BaseType): boolean {
James Kuszmaulabb77132020-08-01 19:56:16 -0700102 return isInteger(baseType) && (typeSize(baseType) > 4);
103}
104
James Kuszmaulabb77132020-08-01 19:56:16 -0700105// Stores the data associated with a Table within a given buffer.
106export class Table {
107 // Wrapper to represent an object (Table or Struct) within a ByteBuffer.
108 // The ByteBuffer is the raw data associated with the object.
109 // typeIndex is an index into the schema object vector for the parser
110 // object that this is associated with.
111 // offset is the absolute location within the buffer where the root of the
112 // object is.
113 // Note that a given Table assumes that it is being used with a particular
114 // Schema object.
115 // External users should generally not be using this constructor directly.
116 constructor(
117 public readonly bb: ByteBuffer,
118 public readonly typeIndex: number, public readonly offset: number) {}
119 // Constructs a Table object for the root of a ByteBuffer--this assumes that
120 // the type of the Table is the root table of the Parser that you are using.
121 static getRootTable(bb: ByteBuffer): Table {
122 return new Table(bb, -1, bb.readInt32(bb.position()) + bb.position());
123 }
James Kuszmaul5f5e1232020-12-22 20:58:00 -0800124 static getNamedTable(
125 bb: ByteBuffer, schema: reflection.Schema, type: string,
126 offset: number): Table {
127 for (let ii = 0; ii < schema.objectsLength(); ++ii) {
128 if (schema.objects(ii).name() == type) {
129 return new Table(bb, ii, offset);
130 }
131 }
132 throw new Error('Unable to find type ' + type + ' in schema.');
133 }
James Kuszmaulabb77132020-08-01 19:56:16 -0700134 // Reads a scalar of a given type at a given offset.
Austin Schuh7c75e582020-11-14 16:41:18 -0800135 readScalar(fieldType: reflection.BaseType, offset: number) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700136 switch (fieldType) {
Austin Schuh7c75e582020-11-14 16:41:18 -0800137 case reflection.BaseType.UType:
138 case reflection.BaseType.Bool:
James Kuszmaulabb77132020-08-01 19:56:16 -0700139 return this.bb.readUint8(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800140 case reflection.BaseType.Byte:
James Kuszmaulabb77132020-08-01 19:56:16 -0700141 return this.bb.readInt8(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800142 case reflection.BaseType.UByte:
James Kuszmaulabb77132020-08-01 19:56:16 -0700143 return this.bb.readUint8(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800144 case reflection.BaseType.Short:
James Kuszmaulabb77132020-08-01 19:56:16 -0700145 return this.bb.readInt16(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800146 case reflection.BaseType.UShort:
James Kuszmaulabb77132020-08-01 19:56:16 -0700147 return this.bb.readUint16(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800148 case reflection.BaseType.Int:
James Kuszmaulabb77132020-08-01 19:56:16 -0700149 return this.bb.readInt32(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800150 case reflection.BaseType.UInt:
James Kuszmaulabb77132020-08-01 19:56:16 -0700151 return this.bb.readUint32(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800152 case reflection.BaseType.Long:
James Kuszmaulabb77132020-08-01 19:56:16 -0700153 return this.bb.readInt64(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800154 case reflection.BaseType.ULong:
James Kuszmaulabb77132020-08-01 19:56:16 -0700155 return this.bb.readUint64(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800156 case reflection.BaseType.Float:
James Kuszmaulabb77132020-08-01 19:56:16 -0700157 return this.bb.readFloat32(offset);
Austin Schuh7c75e582020-11-14 16:41:18 -0800158 case reflection.BaseType.Double:
James Kuszmaulabb77132020-08-01 19:56:16 -0700159 return this.bb.readFloat64(offset);
160 }
Philipp Schrader47445a02020-11-14 17:31:04 -0800161 throw new Error('Unsupported message type ' + fieldType);
James Kuszmaulabb77132020-08-01 19:56:16 -0700162 }
163};
164
165// The Parser class uses a Schema to provide all the utilities required to
166// parse flatbuffers that have a type that is the same as the root_type defined
167// by the Schema.
168// The classical usage would be to, e.g., be reading a channel with a type of
169// "aos.FooBar". At startup, you would construct a Parser from the channel's
170// Schema. When a message is received on the channel , you would then use
171// Table.getRootTable() on the received buffer to construct the Table, and
172// then access the members using the various methods of the Parser (or just
173// convert the entire object to a javascript Object/JSON using toObject()).
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800174// There are three basic ways to access fields in a Table:
175// 1) Call toObject(), which turns the entire table into a javascript object.
176// This is not meant to be particularly fast, but is useful to, e.g.,
177// convert something to JSON, or as a debugging tool.
178// 2) Use the read*Lambda() accessors: These return a function that lets you
179// access the specified field given a table. This is used by the plotter
180// to repeatedly access the same field on a bunch of tables of the same type,
181// without having to redo all the reflection-related work on every access.
182// 3) Use the read*() accessors: These just call the lambda returned by
183// read*Lambda() for you, as a convenience. This is cleaner to use, but for
184// repeated lookups on tables of the same type, this may be inefficient.
James Kuszmaulabb77132020-08-01 19:56:16 -0700185export class Parser {
Austin Schuh7c75e582020-11-14 16:41:18 -0800186 constructor(private readonly schema: reflection.Schema) {}
James Kuszmaulabb77132020-08-01 19:56:16 -0700187
188 // Parse a Table to a javascript object. This is can be used, e.g., to convert
189 // a flatbuffer Table to JSON.
190 // If readDefaults is set to true, then scalar fields will be filled out with
191 // their default values if not populated; if readDefaults is false and the
192 // field is not populated, the resulting object will not populate the field.
193 toObject(table: Table, readDefaults: boolean = false) {
194 const result = {};
195 const schema = this.getType(table.typeIndex);
196 const numFields = schema.fieldsLength();
197 for (let ii = 0; ii < numFields; ++ii) {
198 const field = schema.fields(ii);
199 const baseType = field.type().baseType();
200 let fieldValue = null;
201 if (isScalar(baseType)) {
202 fieldValue = this.readScalar(table, field.name(), readDefaults);
Austin Schuh7c75e582020-11-14 16:41:18 -0800203 } else if (baseType === reflection.BaseType.String) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700204 fieldValue = this.readString(table, field.name());
Austin Schuh7c75e582020-11-14 16:41:18 -0800205 } else if (baseType === reflection.BaseType.Obj) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700206 const subTable = this.readTable(table, field.name());
207 if (subTable !== null) {
208 fieldValue = this.toObject(subTable, readDefaults);
209 }
Austin Schuh7c75e582020-11-14 16:41:18 -0800210 } else if (baseType === reflection.BaseType.Vector) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700211 const elementType = field.type().element();
212 if (isScalar(elementType)) {
213 fieldValue = this.readVectorOfScalars(table, field.name());
Austin Schuh7c75e582020-11-14 16:41:18 -0800214 } else if (elementType === reflection.BaseType.String) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700215 fieldValue = this.readVectorOfStrings(table, field.name());
Austin Schuh7c75e582020-11-14 16:41:18 -0800216 } else if (elementType === reflection.BaseType.Obj) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700217 const tables = this.readVectorOfTables(table, field.name());
218 if (tables !== null) {
219 fieldValue = [];
220 for (const table of tables) {
221 fieldValue.push(this.toObject(table, readDefaults));
222 }
223 }
224 } else {
225 throw new Error('Vectors of Unions and Arrays are not supported.');
226 }
227 } else {
228 throw new Error(
229 'Unions and Arrays are not supported in field ' + field.name());
230 }
231 if (fieldValue !== null) {
232 result[field.name()] = fieldValue;
233 }
234 }
235 return result;
236 }
237
238 // Returns the Object definition associated with the given type index.
James Kuszmauldac091f2022-03-22 09:35:06 -0700239 getType(typeIndex: number): reflection.Object_ {
James Kuszmaulabb77132020-08-01 19:56:16 -0700240 if (typeIndex === -1) {
241 return this.schema.rootTable();
242 }
243 if (typeIndex < 0 || typeIndex > this.schema.objectsLength()) {
244 throw new Error("Type index out-of-range.");
245 }
246 return this.schema.objects(typeIndex);
247 }
248
249 // Retrieves the Field schema for the given field name within a given
250 // type index.
Austin Schuh7c75e582020-11-14 16:41:18 -0800251 getField(fieldName: string, typeIndex: number): reflection.Field {
James Kuszmauldac091f2022-03-22 09:35:06 -0700252 const schema: reflection.Object_ = this.getType(typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700253 const numFields = schema.fieldsLength();
254 for (let ii = 0; ii < numFields; ++ii) {
255 const field = schema.fields(ii);
256 const name = field.name();
257 if (fieldName === name) {
258 return field;
259 }
260 }
James Kuszmaulc4ae11c2020-12-26 16:26:58 -0800261 throw new Error(
262 'Couldn\'t find field ' + fieldName + ' in object ' + schema.name() +
263 '.');
James Kuszmaulabb77132020-08-01 19:56:16 -0700264 }
265
266 // Reads a scalar with the given field name from a Table. If readDefaults
267 // is set to false and the field is unset, we will return null. If
268 // readDefaults is true and the field is unset, we will look-up the default
269 // value for the field and return that.
270 // For 64-bit fields, returns a flatbuffer Long rather than a standard number.
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800271 readScalar(table: Table, fieldName: string, readDefaults: boolean = false):
James Kuszmauldac091f2022-03-22 09:35:06 -0700272 number|BigInt|null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800273 return this.readScalarLambda(
274 table.typeIndex, fieldName, readDefaults)(table);
275 }
276 // Like readScalar(), except that this returns an accessor for the specified
277 // field, rather than the value of the field itself.
278 // Note that the *Lambda() methods take a typeIndex instead of a Table, which
279 // can be obtained using table.typeIndex.
280 readScalarLambda(
281 typeIndex: number, fieldName: string,
James Kuszmauldac091f2022-03-22 09:35:06 -0700282 readDefaults: boolean = false): (t: Table) => number | BigInt | null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800283 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700284 const fieldType = field.type();
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800285 const isStruct = this.getType(typeIndex).isStruct();
James Kuszmaulabb77132020-08-01 19:56:16 -0700286 if (!isScalar(fieldType.baseType())) {
287 throw new Error('Field ' + fieldName + ' is not a scalar type.');
288 }
289
290 if (isStruct) {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800291 const baseType = fieldType.baseType();
292 return (t: Table) => {
293 return t.readScalar(baseType, t.offset + field.offset());
294 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700295 }
296
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800297 return (t: Table) => {
298 const offset = t.offset + t.bb.__offset(t.offset, field.offset());
299 if (offset === t.offset) {
300 if (!readDefaults) {
301 return null;
James Kuszmaulabb77132020-08-01 19:56:16 -0700302 }
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800303 if (isInteger(fieldType.baseType())) {
James Kuszmauldac091f2022-03-22 09:35:06 -0700304 return field.defaultInteger();
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800305 } else {
306 return field.defaultReal();
307 }
James Kuszmaulabb77132020-08-01 19:56:16 -0700308 }
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800309 return t.readScalar(fieldType.baseType(), offset);
310 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700311 }
312 // Reads a string with the given field name from the provided Table.
313 // If the field is unset, returns null.
314 readString(table: Table, fieldName: string): string|null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800315 return this.readStringLambda(table.typeIndex, fieldName)(table);
316 }
317
318 readStringLambda(typeIndex: number, fieldName: string):
319 (t: Table) => string | null {
320 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700321 const fieldType = field.type();
Austin Schuh7c75e582020-11-14 16:41:18 -0800322 if (fieldType.baseType() !== reflection.BaseType.String) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700323 throw new Error('Field ' + fieldName + ' is not a string.');
324 }
325
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800326
327 return (t: Table) => {
328 const offsetToOffset =
329 t.offset + t.bb.__offset(t.offset, field.offset());
330 if (offsetToOffset === t.offset) {
331 return null;
332 }
333 return t.bb.__string(offsetToOffset) as string;
334 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700335 }
336 // Reads a sub-message from the given Table. The sub-message may either be
337 // a struct or a Table. Returns null if the sub-message is not set.
338 readTable(table: Table, fieldName: string): Table|null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800339 return this.readTableLambda(table.typeIndex, fieldName)(table);
340 }
341 readTableLambda(typeIndex: number, fieldName: string): (t: Table) => Table|null {
342 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700343 const fieldType = field.type();
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800344 const parentIsStruct = this.getType(typeIndex).isStruct();
Austin Schuh7c75e582020-11-14 16:41:18 -0800345 if (fieldType.baseType() !== reflection.BaseType.Obj) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700346 throw new Error('Field ' + fieldName + ' is not an object type.');
347 }
348
349 if (parentIsStruct) {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800350 return (t: Table) => {
351 return new Table(t.bb, fieldType.index(), t.offset + field.offset());
352 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700353 }
354
355 const elementIsStruct = this.getType(fieldType.index()).isStruct();
356
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800357 return (table: Table) => {
358 const offsetToOffset =
359 table.offset + table.bb.__offset(table.offset, field.offset());
360 if (offsetToOffset === table.offset) {
361 return null;
362 }
363
364 const objectStart = elementIsStruct ? offsetToOffset :
365 table.bb.__indirect(offsetToOffset);
366 return new Table(table.bb, fieldType.index(), objectStart);
367 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700368 }
James Kuszmauldac091f2022-03-22 09:35:06 -0700369 // Reads a vector of scalars (like readScalar, may return a vector of BigInt's
James Kuszmaulabb77132020-08-01 19:56:16 -0700370 // instead). Also, will return null if the vector is not set.
James Kuszmauldac091f2022-03-22 09:35:06 -0700371 readVectorOfScalars(table: Table, fieldName: string): number[]|BigInt[]|null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800372 return this.readVectorOfScalarsLambda(table.typeIndex, fieldName)(table);
373 }
374
375 readVectorOfScalarsLambda(typeIndex: number, fieldName: string):
James Kuszmauldac091f2022-03-22 09:35:06 -0700376 (t: Table) => number[] | BigInt[] | null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800377 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700378 const fieldType = field.type();
Austin Schuh7c75e582020-11-14 16:41:18 -0800379 if (fieldType.baseType() !== reflection.BaseType.Vector) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700380 throw new Error('Field ' + fieldName + ' is not an vector.');
381 }
382 if (!isScalar(fieldType.element())) {
383 throw new Error('Field ' + fieldName + ' is not an vector of scalars.');
384 }
385
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800386 return (table: Table) => {
387 const offsetToOffset =
388 table.offset + table.bb.__offset(table.offset, field.offset());
389 if (offsetToOffset === table.offset) {
390 return null;
391 }
392 const numElements = table.bb.__vector_len(offsetToOffset);
393 const result = [];
394 const baseOffset = table.bb.__vector(offsetToOffset);
395 const scalarSize = typeSize(fieldType.element());
396 for (let ii = 0; ii < numElements; ++ii) {
397 result.push(table.readScalar(
398 fieldType.element(), baseOffset + scalarSize * ii));
399 }
400 return result;
401 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700402 }
403 // Reads a vector of tables. Returns null if vector is not set.
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800404 readVectorOfTables(table: Table, fieldName: string): Table[]|null {
405 return this.readVectorOfTablesLambda(table.typeIndex, fieldName)(table);
406 }
407 readVectorOfTablesLambda(typeIndex: number, fieldName: string):
408 (t: Table) => Table[] | null {
409 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700410 const fieldType = field.type();
Austin Schuh7c75e582020-11-14 16:41:18 -0800411 if (fieldType.baseType() !== reflection.BaseType.Vector) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700412 throw new Error('Field ' + fieldName + ' is not an vector.');
413 }
Austin Schuh7c75e582020-11-14 16:41:18 -0800414 if (fieldType.element() !== reflection.BaseType.Obj) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700415 throw new Error('Field ' + fieldName + ' is not an vector of objects.');
416 }
417
James Kuszmaulabb77132020-08-01 19:56:16 -0700418 const elementSchema = this.getType(fieldType.index());
419 const elementIsStruct = elementSchema.isStruct();
420 const elementSize = elementIsStruct ? elementSchema.bytesize() :
421 typeSize(fieldType.element());
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800422
423 return (table: Table) => {
424 const offsetToOffset =
425 table.offset + table.bb.__offset(table.offset, field.offset());
426 if (offsetToOffset === table.offset) {
427 return null;
428 }
429 const numElements = table.bb.__vector_len(offsetToOffset);
430 const result = [];
431 const baseOffset = table.bb.__vector(offsetToOffset);
432 for (let ii = 0; ii < numElements; ++ii) {
433 const elementOffset = baseOffset + elementSize * ii;
434 result.push(new Table(
435 table.bb, fieldType.index(),
436 elementIsStruct ? elementOffset :
437 table.bb.__indirect(elementOffset)));
438 }
439 return result;
440 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700441 }
442 // Reads a vector of strings. Returns null if not set.
443 readVectorOfStrings(table: Table, fieldName: string): string[]|null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800444 return this.readVectorOfStringsLambda(table.typeIndex, fieldName)(table);
445 }
446 readVectorOfStringsLambda(typeIndex: number, fieldName: string):
447 (t: Table) => string[] | null {
448 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700449 const fieldType = field.type();
Austin Schuh7c75e582020-11-14 16:41:18 -0800450 if (fieldType.baseType() !== reflection.BaseType.Vector) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700451 throw new Error('Field ' + fieldName + ' is not an vector.');
452 }
Austin Schuh7c75e582020-11-14 16:41:18 -0800453 if (fieldType.element() !== reflection.BaseType.String) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700454 throw new Error('Field ' + fieldName + ' is not an vector of strings.');
455 }
456
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800457 return (table: Table) => {
458 const offsetToOffset =
459 table.offset + table.bb.__offset(table.offset, field.offset());
460 if (offsetToOffset === table.offset) {
461 return null;
462 }
463 const numElements = table.bb.__vector_len(offsetToOffset);
464 const result = [];
465 const baseOffset = table.bb.__vector(offsetToOffset);
466 const offsetSize = typeSize(fieldType.element());
467 for (let ii = 0; ii < numElements; ++ii) {
468 result.push(table.bb.__string(baseOffset + offsetSize * ii));
469 }
470 return result;
471 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700472 }
473}