blob: 18d49966d8873d84612540d6c4c1e68b9f8b31de [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
Philipp Schradere625ba22020-11-16 20:11:37 -08009import {reflection, aos} from 'org_frc971/aos/configuration_generated';
10import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
James Kuszmaulf8355ff2021-12-19 22:08:45 -080011import {Long} from 'org_frc971/external/com_github_google_flatbuffers/ts/long';
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.
Philipp Schrader87277f42022-01-01 07:45:12 -0800239 getType(typeIndex: number): reflection.ObjectGenerated {
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 {
Philipp Schrader87277f42022-01-01 07:45:12 -0800252 const schema: reflection.ObjectGenerated = 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):
272 number|Long|null {
273 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,
282 readDefaults: boolean = false): (t: Table) => number | Long | null {
283 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())) {
304 if (isLong(fieldType.baseType())) {
305 return field.defaultInteger();
306 } else {
307 if (field.defaultInteger().high != 0) {
308 throw new Error(
309 '<=4 byte integer types should not use 64-bit default values.');
310 }
311 return field.defaultInteger().low;
312 }
313 } else {
314 return field.defaultReal();
315 }
James Kuszmaulabb77132020-08-01 19:56:16 -0700316 }
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800317 return t.readScalar(fieldType.baseType(), offset);
318 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700319 }
320 // Reads a string with the given field name from the provided Table.
321 // If the field is unset, returns null.
322 readString(table: Table, fieldName: string): string|null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800323 return this.readStringLambda(table.typeIndex, fieldName)(table);
324 }
325
326 readStringLambda(typeIndex: number, fieldName: string):
327 (t: Table) => string | null {
328 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700329 const fieldType = field.type();
Austin Schuh7c75e582020-11-14 16:41:18 -0800330 if (fieldType.baseType() !== reflection.BaseType.String) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700331 throw new Error('Field ' + fieldName + ' is not a string.');
332 }
333
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800334
335 return (t: Table) => {
336 const offsetToOffset =
337 t.offset + t.bb.__offset(t.offset, field.offset());
338 if (offsetToOffset === t.offset) {
339 return null;
340 }
341 return t.bb.__string(offsetToOffset) as string;
342 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700343 }
344 // Reads a sub-message from the given Table. The sub-message may either be
345 // a struct or a Table. Returns null if the sub-message is not set.
346 readTable(table: Table, fieldName: string): Table|null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800347 return this.readTableLambda(table.typeIndex, fieldName)(table);
348 }
349 readTableLambda(typeIndex: number, fieldName: string): (t: Table) => Table|null {
350 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700351 const fieldType = field.type();
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800352 const parentIsStruct = this.getType(typeIndex).isStruct();
Austin Schuh7c75e582020-11-14 16:41:18 -0800353 if (fieldType.baseType() !== reflection.BaseType.Obj) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700354 throw new Error('Field ' + fieldName + ' is not an object type.');
355 }
356
357 if (parentIsStruct) {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800358 return (t: Table) => {
359 return new Table(t.bb, fieldType.index(), t.offset + field.offset());
360 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700361 }
362
363 const elementIsStruct = this.getType(fieldType.index()).isStruct();
364
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800365 return (table: Table) => {
366 const offsetToOffset =
367 table.offset + table.bb.__offset(table.offset, field.offset());
368 if (offsetToOffset === table.offset) {
369 return null;
370 }
371
372 const objectStart = elementIsStruct ? offsetToOffset :
373 table.bb.__indirect(offsetToOffset);
374 return new Table(table.bb, fieldType.index(), objectStart);
375 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700376 }
377 // Reads a vector of scalars (like readScalar, may return a vector of Long's
378 // instead). Also, will return null if the vector is not set.
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800379 readVectorOfScalars(table: Table, fieldName: string): number[]|Long[]|null {
380 return this.readVectorOfScalarsLambda(table.typeIndex, fieldName)(table);
381 }
382
383 readVectorOfScalarsLambda(typeIndex: number, fieldName: string):
384 (t: Table) => number[] | Long[] | null {
385 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700386 const fieldType = field.type();
Austin Schuh7c75e582020-11-14 16:41:18 -0800387 if (fieldType.baseType() !== reflection.BaseType.Vector) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700388 throw new Error('Field ' + fieldName + ' is not an vector.');
389 }
390 if (!isScalar(fieldType.element())) {
391 throw new Error('Field ' + fieldName + ' is not an vector of scalars.');
392 }
393
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800394 return (table: Table) => {
395 const offsetToOffset =
396 table.offset + table.bb.__offset(table.offset, field.offset());
397 if (offsetToOffset === table.offset) {
398 return null;
399 }
400 const numElements = table.bb.__vector_len(offsetToOffset);
401 const result = [];
402 const baseOffset = table.bb.__vector(offsetToOffset);
403 const scalarSize = typeSize(fieldType.element());
404 for (let ii = 0; ii < numElements; ++ii) {
405 result.push(table.readScalar(
406 fieldType.element(), baseOffset + scalarSize * ii));
407 }
408 return result;
409 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700410 }
411 // Reads a vector of tables. Returns null if vector is not set.
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800412 readVectorOfTables(table: Table, fieldName: string): Table[]|null {
413 return this.readVectorOfTablesLambda(table.typeIndex, fieldName)(table);
414 }
415 readVectorOfTablesLambda(typeIndex: number, fieldName: string):
416 (t: Table) => Table[] | null {
417 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700418 const fieldType = field.type();
Austin Schuh7c75e582020-11-14 16:41:18 -0800419 if (fieldType.baseType() !== reflection.BaseType.Vector) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700420 throw new Error('Field ' + fieldName + ' is not an vector.');
421 }
Austin Schuh7c75e582020-11-14 16:41:18 -0800422 if (fieldType.element() !== reflection.BaseType.Obj) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700423 throw new Error('Field ' + fieldName + ' is not an vector of objects.');
424 }
425
James Kuszmaulabb77132020-08-01 19:56:16 -0700426 const elementSchema = this.getType(fieldType.index());
427 const elementIsStruct = elementSchema.isStruct();
428 const elementSize = elementIsStruct ? elementSchema.bytesize() :
429 typeSize(fieldType.element());
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800430
431 return (table: Table) => {
432 const offsetToOffset =
433 table.offset + table.bb.__offset(table.offset, field.offset());
434 if (offsetToOffset === table.offset) {
435 return null;
436 }
437 const numElements = table.bb.__vector_len(offsetToOffset);
438 const result = [];
439 const baseOffset = table.bb.__vector(offsetToOffset);
440 for (let ii = 0; ii < numElements; ++ii) {
441 const elementOffset = baseOffset + elementSize * ii;
442 result.push(new Table(
443 table.bb, fieldType.index(),
444 elementIsStruct ? elementOffset :
445 table.bb.__indirect(elementOffset)));
446 }
447 return result;
448 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700449 }
450 // Reads a vector of strings. Returns null if not set.
451 readVectorOfStrings(table: Table, fieldName: string): string[]|null {
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800452 return this.readVectorOfStringsLambda(table.typeIndex, fieldName)(table);
453 }
454 readVectorOfStringsLambda(typeIndex: number, fieldName: string):
455 (t: Table) => string[] | null {
456 const field = this.getField(fieldName, typeIndex);
James Kuszmaulabb77132020-08-01 19:56:16 -0700457 const fieldType = field.type();
Austin Schuh7c75e582020-11-14 16:41:18 -0800458 if (fieldType.baseType() !== reflection.BaseType.Vector) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700459 throw new Error('Field ' + fieldName + ' is not an vector.');
460 }
Austin Schuh7c75e582020-11-14 16:41:18 -0800461 if (fieldType.element() !== reflection.BaseType.String) {
James Kuszmaulabb77132020-08-01 19:56:16 -0700462 throw new Error('Field ' + fieldName + ' is not an vector of strings.');
463 }
464
James Kuszmaulf8355ff2021-12-19 22:08:45 -0800465 return (table: Table) => {
466 const offsetToOffset =
467 table.offset + table.bb.__offset(table.offset, field.offset());
468 if (offsetToOffset === table.offset) {
469 return null;
470 }
471 const numElements = table.bb.__vector_len(offsetToOffset);
472 const result = [];
473 const baseOffset = table.bb.__vector(offsetToOffset);
474 const offsetSize = typeSize(fieldType.element());
475 for (let ii = 0; ii < numElements; ++ii) {
476 result.push(table.bb.__string(baseOffset + offsetSize * ii));
477 }
478 return result;
479 };
James Kuszmaulabb77132020-08-01 19:56:16 -0700480 }
481}