From 3608863ec3e67cc584f6ca1256aa36c328599588 Mon Sep 17 00:00:00 2001 From: Lucas Switzer Date: Tue, 19 Jan 2021 11:45:17 -0500 Subject: [PATCH] [Assembly Script] Add basic Assembly Script support --- as/builder.ts | 671 ++++++++++++++++++++++++++++++++++++++ as/byte-buffer.ts | 365 +++++++++++++++++++++ as/constants.ts | 4 + as/encoding.ts | 4 + as/flatbuffers.ts | 29 ++ as/long.ts | 26 ++ as/types.ts | 14 + as/utils.ts | 9 + include/flatbuffers/idl.h | 1 + src/flatc_main.cpp | 4 + src/idl_gen_ts.cpp | 378 +++++++++++++++++---- 11 files changed, 1433 insertions(+), 72 deletions(-) create mode 100644 as/builder.ts create mode 100644 as/byte-buffer.ts create mode 100644 as/constants.ts create mode 100644 as/encoding.ts create mode 100644 as/flatbuffers.ts create mode 100644 as/long.ts create mode 100644 as/types.ts create mode 100644 as/utils.ts diff --git a/as/builder.ts b/as/builder.ts new file mode 100644 index 000000000000..3c6b333bc01f --- /dev/null +++ b/as/builder.ts @@ -0,0 +1,671 @@ +import { ByteBuffer } from './byte-buffer'; +import { + SIZEOF_SHORT, + SIZE_PREFIX_LENGTH, + SIZEOF_INT, + FILE_IDENTIFIER_LENGTH, +} from './constants'; +import { Offset, IGeneratedObject } from './types'; +import { Long } from './long'; + +export class Builder { + private bb: ByteBuffer; + /** Remaining space in the ByteBuffer. */ + private space: i32; + /** Minimum alignment encountered so far. */ + private minalign: i32 = 1; + /** The vtable for the current table. */ + private vtable: i32[] | null = null; + /** The amount of fields we're actually using. */ + private vtable_in_use: i32 = 0; + /** Whether we are currently serializing a table. */ + private isNested: boolean = false; + /** Starting offset of the current struct/table. */ + private object_start: i32 = 0; + /** List of offsets of all vtables. */ + private vtables: i32[] = []; + /** For the current vector being built. */ + private vector_num_elems: i32 = 0; + /** False omits default values from the serialized data */ + private force_defaults: boolean = false; + + private string_maps: Map | null = null; + + /** + * Create a FlatBufferBuilder. + */ + constructor(opt_initial_size: i32) { + let initial_size: i32; + + if (!opt_initial_size) { + initial_size = 1024; + } else { + initial_size = opt_initial_size; + } + + /** + * @type {ByteBuffer} + * @private + */ + this.bb = ByteBuffer.allocate(initial_size); + this.space = initial_size; + } + + clear(): void { + this.bb.clear(); + this.space = this.bb.capacity(); + this.minalign = 1; + this.vtable = null; + this.vtable_in_use = 0; + this.isNested = false; + this.object_start = 0; + this.vtables = []; + this.vector_num_elems = 0; + this.force_defaults = false; + this.string_maps = null; + } + + /** + * In order to save space, fields that are set to their default value + * don't get serialized into the buffer. Forcing defaults provides a + * way to manually disable this optimization. + * + * @param forceDefaults true always serializes default values + */ + forceDefaults(forceDefaults: boolean): void { + this.force_defaults = forceDefaults; + } + + /** + * Get the ByteBuffer representing the FlatBuffer. Only call this after you've + * called finish(). The actual data starts at the ByteBuffer's current position, + * not necessarily at 0. + */ + dataBuffer(): ByteBuffer { + return this.bb; + } + + /** + * Get the bytes representing the FlatBuffer. Only call this after you've + * called finish(). + */ + asUint8Array(): Uint8Array { + return this.bb + .bytes() + .subarray(this.bb.position(), this.bb.position() + this.offset()); + } + + /** + * Prepare to write an element of `size` after `additional_bytes` have been + * written, e.g. if you write a string, you need to align such the int length + * field is aligned to 4 bytes, and the string data follows it directly. If all + * you need to do is alignment, `additional_bytes` will be 0. + * + * @param size This is the of the new element to write + * @param additional_bytes The padding size + */ + prep(size: i32, additional_bytes: i32): void { + // Track the biggest thing we've ever aligned to. + if (size > this.minalign) { + this.minalign = size; + } + + // Find the amount of alignment needed such that `size` is properly + // aligned after `additional_bytes` + const align_size = + (~(this.bb.capacity() - this.space + additional_bytes) + 1) & (size - 1); + + // Reallocate the buffer if needed. + while (this.space < align_size + size + additional_bytes) { + const old_buf_size = this.bb.capacity(); + this.bb = Builder.growByteBuffer(this.bb); + this.space += this.bb.capacity() - old_buf_size; + } + + this.pad(align_size); + } + + pad(byte_size: i32): void { + for (let i = 0; i < byte_size; i++) { + this.bb.writeInt8(--this.space, 0); + } + } + + writeInt8(value: i32): void { + this.bb.writeInt8((this.space -= 1), value); + } + + writeInt16(value: i32): void { + this.bb.writeInt16((this.space -= 2), value); + } + + writeInt32(value: i32): void { + this.bb.writeInt32((this.space -= 4), value); + } + + writeInt64(value: Long): void { + this.bb.writeInt64((this.space -= 8), value); + } + + writeFloat32(value: f32): void { + this.bb.writeFloat32((this.space -= 4), value); + } + + writeFloat64(value: f64): void { + this.bb.writeFloat64((this.space -= 8), value); + } + + /** + * Add an `int8` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `int8` to add the the buffer. + */ + addInt8(value: i32): void { + this.prep(1, 0); + this.writeInt8(value); + } + + /** + * Add an `int16` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `int16` to add the the buffer. + */ + addInt16(value: i32): void { + this.prep(2, 0); + this.writeInt16(value); + } + + /** + * Add an `int32` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `int32` to add the the buffer. + */ + addInt32(value: i32): void { + this.prep(4, 0); + this.writeInt32(value); + } + + /** + * Add an `int64` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `int64` to add the the buffer. + */ + addInt64(value: Long): void { + this.prep(8, 0); + this.writeInt64(value); + } + + /** + * Add a `float32` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `float32` to add the the buffer. + */ + addFloat32(value: f32): void { + this.prep(4, 0); + this.writeFloat32(value); + } + + /** + * Add a `float64` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `float64` to add the the buffer. + */ + addFloat64(value: f64): void { + this.prep(8, 0); + this.writeFloat64(value); + } + + addFieldInt8(voffset: i32, value: i32, defaultValue: i32): void { + if (this.force_defaults || value != defaultValue) { + this.addInt8(value); + this.slot(voffset); + } + } + + addFieldInt16(voffset: i32, value: i32, defaultValue: i32): void { + if (this.force_defaults || value != defaultValue) { + this.addInt16(value); + this.slot(voffset); + } + } + + addFieldInt32(voffset: i32, value: i32, defaultValue: i32): void { + if (this.force_defaults || value != defaultValue) { + this.addInt32(value); + this.slot(voffset); + } + } + + addFieldInt64(voffset: i32, value: Long, defaultValue: Long): void { + if (this.force_defaults || !value.equals(defaultValue)) { + this.addInt64(value); + this.slot(voffset); + } + } + + addFieldFloat32(voffset: i32, value: f32, defaultValue: f32): void { + if (this.force_defaults || value != defaultValue) { + this.addFloat32(value); + this.slot(voffset); + } + } + + addFieldFloat64(voffset: i32, value: f64, defaultValue: f64): void { + if (this.force_defaults || value != defaultValue) { + this.addFloat64(value); + this.slot(voffset); + } + } + + addFieldOffset(voffset: i32, value: Offset, defaultValue: Offset): void { + if (this.force_defaults || value != defaultValue) { + this.addOffset(value); + this.slot(voffset); + } + } + + /** + * Structs are stored inline, so nothing additional is being added. `d` is always 0. + */ + addFieldStruct(voffset: i32, value: Offset, defaultValue: Offset): void { + if (value != defaultValue) { + this.nested(value); + this.slot(voffset); + } + } + + /** + * Structures are always stored inline, they need to be created right + * where they're used. You'll get this assertion failure if you + * created it elsewhere. + */ + nested(obj: Offset): void { + if (obj != this.offset()) { + throw new Error('FlatBuffers: struct must be serialized inline.'); + } + } + + /** + * Should not be creating any other object, string or vector + * while an object is being constructed + */ + notNested(): void { + if (this.isNested) { + throw new Error('FlatBuffers: object serialization must not be nested.'); + } + } + + /** + * Set the current vtable at `voffset` to the current location in the buffer. + */ + slot(voffset: i32): void { + const l_vtable = this.vtable; + if (l_vtable !== null) l_vtable[voffset] = this.offset(); + } + + /** + * @returns Offset relative to the end of the buffer. + */ + offset(): Offset { + return this.bb.capacity() - this.space; + } + + /** + * Doubles the size of the backing ByteBuffer and copies the old data towards + * the end of the new buffer (since we build the buffer backwards). + * + * @param bb The current buffer with the existing data + * @returns A new byte buffer with the old data copied + * to it. The data is located at the end of the buffer. + * + * uint8Array.set() formally takes {Array|ArrayBufferView}, so to pass + * it a uint8Array we need to suppress the type check: + * @suppress {checkTypes} + */ + static growByteBuffer(bb: ByteBuffer): ByteBuffer { + const old_buf_size = bb.capacity(); + + // Ensure we don't grow beyond what fits in an int. + if (old_buf_size & 0xc0000000) { + throw new Error('FlatBuffers: cannot grow buffer beyond 2 gigabytes.'); + } + + const new_buf_size = old_buf_size << 1; + const nbb = ByteBuffer.allocate(new_buf_size); + nbb.setPosition(new_buf_size - old_buf_size); + nbb.bytes().set(bb.bytes(), new_buf_size - old_buf_size); + return nbb; + } + + /** + * Adds on offset, relative to where it will be written. + * + * @param offset The offset to add. + */ + addOffset(offset: Offset): void { + this.prep(SIZEOF_INT, 0); // Ensure alignment is already done. + this.writeInt32(this.offset() - offset + SIZEOF_INT); + } + + /** + * Start encoding a new object in the buffer. Users will not usually need to + * call this directly. The FlatBuffers compiler will generate helper methods + * that call this method internally. + */ + startObject(numfields: i32): void { + this.notNested(); + let l_vtable = this.vtable; + if (l_vtable == null) { + l_vtable = []; + } + this.vtable_in_use = numfields; + for (let i = 0; i < numfields; i++) { + l_vtable[i] = 0; // This will push additional elements as needed + } + this.isNested = true; + this.object_start = this.offset(); + this.vtable = l_vtable; + } + + /** + * Finish off writing the object that is under construction. + * + * @returns The offset to the object inside `dataBuffer` + */ + endObject(): Offset { + const l_vtable = this.vtable; + if (l_vtable === null || !this.isNested) { + throw new Error('FlatBuffers: endObject called without startObject'); + } + + this.addInt32(0); + const vtableloc = this.offset(); + + // Trim trailing zeroes. + let i = this.vtable_in_use - 1; + // eslint-disable-next-line no-empty + for (; i >= 0 && l_vtable[i] == 0; i--) {} + const trimmed_size = i + 1; + + // Write out the current vtable. + for (; i >= 0; i--) { + // Offset relative to the start of the table. + this.addInt16(l_vtable[i] != 0 ? vtableloc - l_vtable[i] : 0); + } + + const standard_fields = 2; // The fields below: + this.addInt16(vtableloc - this.object_start); + const len = (trimmed_size + standard_fields) * SIZEOF_SHORT; + this.addInt16(len); + + // Search for an existing vtable that matches the current one. + let existing_vtable = 0; + const vt1 = this.space; + for (i = 0; i < this.vtables.length; i++) { + const vt2 = this.bb.capacity() - this.vtables[i]; + if (len == this.bb.readInt16(vt2)) { + let continueOuterLoop = false; + for (let j = SIZEOF_SHORT; j < len; j += SIZEOF_SHORT) { + if (this.bb.readInt16(vt1 + j) != this.bb.readInt16(vt2 + j)) { + continueOuterLoop = true; + break; + } + } + if (continueOuterLoop) { + continue; + } + existing_vtable = this.vtables[i]; + break; + } + } + + if (existing_vtable) { + // Found a match: + // Remove the current vtable. + this.space = this.bb.capacity() - vtableloc; + + // Point table to existing vtable. + this.bb.writeInt32(this.space, existing_vtable - vtableloc); + } else { + // No match: + // Add the location of the current vtable to the list of vtables. + this.vtables.push(this.offset()); + + // Point table to current vtable. + this.bb.writeInt32( + this.bb.capacity() - vtableloc, + this.offset() - vtableloc, + ); + } + + this.isNested = false; + return vtableloc as Offset; + } + + /** + * Finalize a buffer, poiting to the given `root_table`. + */ + finish( + root_table: Offset, + opt_file_identifier: string | null = null, + opt_size_prefix: bool = false, + ): void { + const size_prefix = opt_size_prefix ? SIZE_PREFIX_LENGTH : 0; + if (opt_file_identifier) { + const file_identifier = opt_file_identifier; + this.prep( + this.minalign, + SIZEOF_INT + FILE_IDENTIFIER_LENGTH + size_prefix, + ); + if (file_identifier.length != FILE_IDENTIFIER_LENGTH) { + throw new Error( + 'FlatBuffers: file identifier must be length ' + + FILE_IDENTIFIER_LENGTH.toString(), + ); + } + for (let i = FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) { + this.writeInt8(file_identifier.charCodeAt(i)); + } + } + this.prep(this.minalign, SIZEOF_INT + size_prefix); + this.addOffset(root_table); + if (size_prefix) { + this.addInt32(this.bb.capacity() - this.space); + } + this.bb.setPosition(this.space); + } + + /** + * Finalize a size prefixed buffer, pointing to the given `root_table`. + */ + finishSizePrefixed( + this: Builder, + root_table: Offset, + opt_file_identifier?: string, + ): void { + this.finish(root_table, opt_file_identifier, true); + } + + /** + * This checks a required field has been set in a given table that has + * just been constructed. + */ + requiredField(table: Offset, field: i32): void { + const table_start = this.bb.capacity() - table; + const vtable_start = table_start - this.bb.readInt32(table_start); + const ok = this.bb.readInt16(vtable_start + field) != 0; + + // If this fails, the caller will show what field needs to be set. + if (!ok) { + throw new Error('FlatBuffers: field ' + field + ' must be set'); + } + } + + /** + * Start a new array/vector of objects. Users usually will not call + * this directly. The FlatBuffers compiler will create a start/end + * method for vector types in generated code. + * + * @param elem_size The size of each element in the array + * @param num_elems The number of elements in the array + * @param alignment The alignment of the array + */ + startVector(elem_size: i32, num_elems: i32, alignment: i32): void { + this.notNested(); + this.vector_num_elems = num_elems; + this.prep(SIZEOF_INT, elem_size * num_elems); + this.prep(alignment, elem_size * num_elems); // Just in case alignment > int. + } + + /** + * Finish off the creation of an array and all its elements. The array must be + * created with `startVector`. + * + * @returns The offset at which the newly created array + * starts. + */ + endVector(): Offset { + this.writeInt32(this.vector_num_elems); + return this.offset(); + } + + /** + * Encode the string `s` in the buffer using UTF-8. If the string passed has + * already been seen, we return the offset of the already written string + * + * @param s The string to encode + * @return The offset in the buffer where the encoded string starts + */ + createSharedString(s: string): Offset { + if (!s) { + return 0; + } + + if (!this.string_maps) { + this.string_maps = new Map(); + } + + if (this.string_maps.has(s)) { + return this.string_maps.get(s) as Offset; + } + const offset = this.createString(s); + this.string_maps.set(s, offset); + return offset; + } + + /** + * Encode the string `s` in the buffer using UTF-8. If a Uint8Array is passed + * instead of a string, it is assumed to contain valid UTF-8 encoded data. + * + * @param s The string to encode + * @return The offset in the buffer where the encoded string starts + */ + createString(s: string): Offset { + if (!s) { + return 0; + } + const utf8: i32[] = []; + let i = 0; + + while (i < s.length) { + let codePoint: i32; + + // Decode UTF-16 + const a = s.charCodeAt(i++); + if (a < 0xd800 || a >= 0xdc00) { + codePoint = a; + } else { + const b = s.charCodeAt(i++); + codePoint = (a << 10) + b + (0x10000 - (0xd800 << 10) - 0xdc00); + } + + // Encode UTF-8 + if (codePoint < 0x80) { + utf8.push(codePoint); + } else { + if (codePoint < 0x800) { + utf8.push(((codePoint >> 6) & 0x1f) | 0xc0); + } else { + if (codePoint < 0x10000) { + utf8.push(((codePoint >> 12) & 0x0f) | 0xe0); + } else { + utf8.push(((codePoint >> 18) & 0x07) | 0xf0); + utf8.push(((codePoint >> 12) & 0x3f) | 0x80); + } + utf8.push(((codePoint >> 6) & 0x3f) | 0x80); + } + utf8.push((codePoint & 0x3f) | 0x80); + } + } + + this.addInt8(0); + this.startVector(1, utf8.length, 1); + this.bb.setPosition((this.space -= utf8.length)); + for ( + let i = 0, offset = this.space, bytes = this.bb.bytes(); + i < utf8.length; + i++ + ) { + bytes[offset++] = utf8[i]; + } + return this.endVector(); + } + + /** + * A helper function to avoid generated code depending on this file directly. + */ + createLong(low: i32, high: i32): Long { + return Long.create(low, high); + } + + /** + * A helper function to pack an object + * + * @returns offset of obj + */ + createObjectOffset(obj: IGeneratedObject): Offset { + if (obj === null) { + return 0; + } + + return obj.pack(this); + } + + /** + * A helper function to pack an object + * + * @returns offset of obj + */ + createStringOffset(obj: string): Offset { + if (obj === null) { + return 0; + } + return this.createString(obj); + } + + /** + * A helper function to pack a list of object + * + * @returns list of offsets of each non null object + */ + createObjectOffsetList(list: string[]): Offset[] { + const ret = []; + + for (let i = 0; i < list.length; ++i) { + const val = list[i]; + + if (val !== null) { + ret.push(this.createObjectOffset(val)); + } else { + throw new Error( + 'FlatBuffers: Argument for createObjectOffsetList cannot contain null.', + ); + } + } + + return ret; + } + + createStructOffsetList( + list: string[], + startFunc: (builder: Builder, length: i32) => void, + ): Offset { + startFunc(this, list.length); + this.createObjectOffsetList(list); + return this.endVector(); + } +} diff --git a/as/byte-buffer.ts b/as/byte-buffer.ts new file mode 100644 index 000000000000..a076016742a9 --- /dev/null +++ b/as/byte-buffer.ts @@ -0,0 +1,365 @@ +import { FILE_IDENTIFIER_LENGTH, SIZEOF_INT } from './constants'; +import { Long } from './long'; +import { int32, isLittleEndian, float32, float64 } from './utils'; +import { Offset, Table, IGeneratedObject } from './types'; + +export class ByteBuffer { + private position_: i32 = 0; + + /** + * Create a new ByteBuffer with a given array of bytes (`Uint8Array`) + */ + constructor(private bytes_: Uint8Array) {} + + /** + * Create and allocate a new ByteBuffer with a given size. + */ + static allocate(byte_size: i32): ByteBuffer { + return new ByteBuffer(new Uint8Array(byte_size)); + } + + clear(): void { + this.position_ = 0; + } + + /** + * Get the underlying `Uint8Array`. + */ + bytes(): Uint8Array { + return this.bytes_; + } + + /** + * Get the buffer's position. + */ + position(): i32 { + return this.position_; + } + + /** + * Set the buffer's position. + */ + setPosition(position: i32): void { + this.position_ = position; + } + + /** + * Get the buffer's capacity. + */ + capacity(): i32 { + return this.bytes_.length; + } + + readInt8(offset: i32): i32 { + return (i32(this.readUint8(offset)) << 24) >> 24; + } + + readUint8(offset: i32): i32 { + return this.bytes_[offset]; + } + + readInt16(offset: i32): i32 { + return (i32(this.readUint16(offset)) << 16) >> 16; + } + + readUint16(offset: i32): i32 { + return i32(this.bytes_[offset]) | (i32(this.bytes_[offset + 1]) << 8); + } + + readInt32(offset: i32): i32 { + return ( + i32(this.bytes_[offset]) | + (i32(this.bytes_[offset + 1]) << 8) | + (i32(this.bytes_[offset + 2]) << 16) | + (i32(this.bytes_[offset + 3]) << 24) + ); + } + + readUint32(offset: i32): i32 { + return this.readInt32(offset) >>> 0; + } + + readInt64(offset: i32): Long { + return new Long(this.readInt32(offset), this.readInt32(offset + 4)); + } + + readUint64(offset: i32): Long { + return new Long(this.readUint32(offset), this.readUint32(offset + 4)); + } + + readFloat32(offset: i32): f32 { + int32[0] = this.readInt32(offset); + return float32[0]; + } + + readFloat64(offset: i32): f64 { + int32[isLittleEndian ? 0 : 1] = this.readInt32(offset); + int32[isLittleEndian ? 1 : 0] = this.readInt32(offset + 4); + return float64[0]; + } + + writeInt8(offset: i32, value: i32): void { + this.bytes_[offset] = value; + } + + writeUint8(offset: i32, value: i32): void { + this.bytes_[offset] = value; + } + + writeInt16(offset: i32, value: i32): void { + this.bytes_[offset] = value; + this.bytes_[offset + 1] = value >> 8; + } + + writeUint16(offset: i32, value: i32): void { + this.bytes_[offset] = value; + this.bytes_[offset + 1] = value >> 8; + } + + writeInt32(offset: i32, value: i32): void { + this.bytes_[offset] = value; + this.bytes_[offset + 1] = value >> 8; + this.bytes_[offset + 2] = value >> 16; + this.bytes_[offset + 3] = value >> 24; + } + + writeUint32(offset: i32, value: i32): void { + this.bytes_[offset] = value; + this.bytes_[offset + 1] = value >> 8; + this.bytes_[offset + 2] = value >> 16; + this.bytes_[offset + 3] = value >> 24; + } + + writeInt64(offset: i32, value: Long): void { + this.writeInt32(offset, value.low); + this.writeInt32(offset + 4, value.high); + } + + writeUint64(offset: i32, value: Long): void { + this.writeUint32(offset, value.low); + this.writeUint32(offset + 4, value.high); + } + + writeFloat32(offset: i32, value: f32): void { + float32[0] = value; + this.writeInt32(offset, int32[0]); + } + + writeFloat64(offset: i32, value: f64): void { + float64[0] = value; + this.writeInt32(offset, int32[isLittleEndian ? 0 : 1]); + this.writeInt32(offset + 4, int32[isLittleEndian ? 1 : 0]); + } + + /** + * Return the file identifier. Behavior is undefined for FlatBuffers whose + * schema does not include a file_identifier (likely points at padding or the + * start of a the root vtable). + */ + getBufferIdentifier(): string { + if ( + this.bytes_.length < + this.position_ + SIZEOF_INT + FILE_IDENTIFIER_LENGTH + ) { + throw new Error( + 'FlatBuffers: ByteBuffer is too short to contain an identifier.' + ); + } + let result = ''; + for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { + result += String.fromCharCode( + this.readInt8(this.position_ + SIZEOF_INT + i) + ); + } + return result; + } + + /** + * Look up a field in the vtable, return an offset into the object, or 0 if the + * field is not present. + */ + __offset(bb_pos: i32, vtable_offset: i32): Offset { + const vtable = bb_pos - this.readInt32(bb_pos); + return vtable_offset < this.readInt16(vtable) + ? this.readInt16(vtable + vtable_offset) + : 0; + } + + /** + * Initialize any Table-derived type to point to the union at the given offset. + */ + __union(t: Table, offset: i32): Table { + t.bb_pos = offset + this.readInt32(offset); + t.bb = this; + return t; + } + + /** + * Create a JavaScript string from UTF-8 data stored inside the FlatBuffer. + * This allocates a new string and converts to wide chars upon each access. + * + * To avoid the conversion to UTF-16, pass Encoding.UTF8_BYTES as + * the "optionalEncoding" argument. This is useful for avoiding conversion to + * and from UTF-16 when the data will just be packaged back up in another + * FlatBuffer later on. + * + * @param offset + * @param opt_encoding Defaults to UTF16_STRING + */ + __string(offset: i32): string { + offset += this.readInt32(offset); + + const length = this.readInt32(offset); + let result = ''; + let i = 0; + + offset += SIZEOF_INT; + + while (i < length) { + let codePoint: i32; + + // Decode UTF-8 + const a = this.readUint8(offset + i++); + if (a < 0xc0) { + codePoint = a; + } else { + const b = this.readUint8(offset + i++); + if (a < 0xe0) { + codePoint = ((a & 0x1f) << 6) | (b & 0x3f); + } else { + const c = this.readUint8(offset + i++); + if (a < 0xf0) { + codePoint = ((a & 0x0f) << 12) | ((b & 0x3f) << 6) | (c & 0x3f); + } else { + const d = this.readUint8(offset + i++); + codePoint = + ((a & 0x07) << 18) | + ((b & 0x3f) << 12) | + ((c & 0x3f) << 6) | + (d & 0x3f); + } + } + } + + // Encode UTF-16 + if (codePoint < 0x10000) { + result += String.fromCharCode(codePoint); + } else { + codePoint -= 0x10000; + result += String.fromCharCode( + (codePoint >> 10) + 0xd800, + (codePoint & ((1 << 10) - 1)) + 0xdc00 + ); + } + } + + return result; + } + + /** + * Handle unions that can contain string as its member, if a Table-derived type then initialize it, + * if a string then return a new one + * + * WARNING: strings are immutable in JS so we can't change the string that the user gave us, this + * makes the behaviour of __union_with_string different compared to __union + */ + __union_with_string(offset: i32): string { + return this.__string(offset) as string; + } + + __union_with_table(o: Table, offset: i32): Table { + return this.__union(o, offset); + } + + /** + * Retrieve the relative offset stored at "offset" + */ + __indirect(offset: Offset): Offset { + return offset + this.readInt32(offset); + } + + /** + * Get the start of data of a vector whose offset is stored at "offset" in this object. + */ + __vector(offset: Offset): Offset { + return offset + this.readInt32(offset) + SIZEOF_INT; // data starts after the length + } + + /** + * Get the length of a vector whose offset is stored at "offset" in this object. + */ + __vector_len(offset: Offset): Offset { + return this.readInt32(offset + this.readInt32(offset)); + } + + __has_identifier(ident: string): boolean { + if (ident.length != FILE_IDENTIFIER_LENGTH) { + throw new Error( + 'FlatBuffers: file identifier must be length ' + FILE_IDENTIFIER_LENGTH + ); + } + for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { + if ( + ident.charCodeAt(i) != this.readInt8(this.position() + SIZEOF_INT + i) + ) { + return false; + } + } + return true; + } + + /** + * A helper function to avoid generated code depending on this file directly. + */ + createLong(low: i32, high: i32): Long { + return Long.create(low, high); + } + + /** + * A helper function for generating list for obj api + */ + createScalarList( + listAccessor: (i: i32) => unknown, + listLength: i32 + ): unknown[] { + const ret: unknown[] = []; + for (let i = 0; i < listLength; ++i) { + if (listAccessor(i) !== null) { + ret.push(listAccessor(i)); + } + } + + return ret; + } + + /** + * This function is here only to get around typescript type system + */ + createStringList( + listAccessor: (i: i32) => unknown, + listLength: i32 + ): unknown[] { + return this.createScalarList(listAccessor, listLength); + } + + /** + * A helper function for generating list for obj api + * @param listAccessor function that accepts an index and return data at that index + * @param listLength listLength + * @param res result list + */ + createObjList( + listAccessor: (i: i32) => IGeneratedObject, + listLength: i32 + ): IGeneratedObject[] { + const ret: IGeneratedObject[] = []; + for (let i = 0; i < listLength; ++i) { + const val = listAccessor(i); + if (val !== null) { + ret.push(val.unpack()); + } + } + + return ret; + } +} diff --git a/as/constants.ts b/as/constants.ts new file mode 100644 index 000000000000..cb70ceddb597 --- /dev/null +++ b/as/constants.ts @@ -0,0 +1,4 @@ +export const SIZEOF_SHORT = 2; +export const SIZEOF_INT = 4; +export const FILE_IDENTIFIER_LENGTH = 4; +export const SIZE_PREFIX_LENGTH = 4; diff --git a/as/encoding.ts b/as/encoding.ts new file mode 100644 index 000000000000..90e35fe071fa --- /dev/null +++ b/as/encoding.ts @@ -0,0 +1,4 @@ +export enum Encoding { + UTF8_BYTES = 1, + UTF16_STRING = 2, +} diff --git a/as/flatbuffers.ts b/as/flatbuffers.ts new file mode 100644 index 000000000000..1e71ecc1571d --- /dev/null +++ b/as/flatbuffers.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +import * as constants from './constants'; +import * as types from './types'; +import * as utils from './utils'; + +import { Long as LongClass } from './long'; +import { Encoding as EncodingEnum } from './encoding'; +import { Builder as BuilderClass } from './builder'; +import { ByteBuffer as ByteBufferClass } from './byte-buffer'; + +export type Offset = types.Offset; + +export type Table = types.Table; + +export const SIZEOF_SHORT = constants.SIZEOF_SHORT; +export const SIZEOF_INT = constants.SIZEOF_INT; +export const FILE_IDENTIFIER_LENGTH = constants.FILE_IDENTIFIER_LENGTH; +export const SIZE_PREFIX_LENGTH = constants.SIZE_PREFIX_LENGTH; + +export type Encoding = EncodingEnum; + +export const int32 = utils.int32; +export const float32 = utils.float32; +export const float64 = utils.float64; +export const isLittleEndian = utils.isLittleEndian; + +export type Long = LongClass; +export type Builder = BuilderClass; +export type ByteBuffer = ByteBufferClass; diff --git a/as/long.ts b/as/long.ts new file mode 100644 index 000000000000..c86d6b18af6f --- /dev/null +++ b/as/long.ts @@ -0,0 +1,26 @@ +export function createLong(low: i32, high: i32): Long { + return Long.create(low, high); +} + +export class Long { + static readonly ZERO: Long = new Long(0, 0); + low: i32; + high: i32; + constructor(low: i32, high: i32) { + this.low = low | 0; + this.high = high | 0; + } + static create(low: i32, high: i32): Long { + // Special-case zero to avoid GC overhead for default values + return low == 0 && high == 0 ? Long.ZERO : new Long(low, high); + } + toFloat64(): i32 { + return (this.low >>> 0) + this.high * 0x100000000; + } + toInt64(): i64 { + return (this.low >>> 0) + this.high * 0x100000000; + } + equals(other: Long): boolean { + return this.low == other.low && this.high == other.high; + } +} diff --git a/as/types.ts b/as/types.ts new file mode 100644 index 000000000000..ea7926e14fd1 --- /dev/null +++ b/as/types.ts @@ -0,0 +1,14 @@ +import { ByteBuffer } from './byte-buffer'; +import { Builder } from './builder'; + +export type Offset = i32; + +export class Table { + bb: ByteBuffer; + bb_pos: i32; +} + +export interface IGeneratedObject { + pack(builder: Builder): Offset; + unpack(): IGeneratedObject; +} diff --git a/as/utils.ts b/as/utils.ts new file mode 100644 index 000000000000..1750090a662d --- /dev/null +++ b/as/utils.ts @@ -0,0 +1,9 @@ +export const int32 = new Int32Array(2); +export const float32 = Float32Array.wrap(int32.buffer); +export const float64 = Float64Array.wrap(int32.buffer); +// export const isLittleEndian = +// new Uint16Array(new Uint8Array([1, 0]).buffer)[0] === 1; + +// TODO: need to figure out how to convert above logic into AS compatible one. +// however, in our current use cases we're always little endian based. +export const isLittleEndian = true; diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 1d57ba16410e..c600af519ef0 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -593,6 +593,7 @@ struct IDLOptions { kRust = 1 << 14, kKotlin = 1 << 15, kSwift = 1 << 16, + kAs = 1 << 17, kMAX }; diff --git a/src/flatc_main.cpp b/src/flatc_main.cpp index c942bdacb2ca..c26dc0cf2f8a 100644 --- a/src/flatc_main.cpp +++ b/src/flatc_main.cpp @@ -82,6 +82,10 @@ int main(int argc, const char *argv[]) { flatbuffers::GenerateTSGRPC, flatbuffers::IDLOptions::kTs, "Generate TypeScript code for tables/structs", flatbuffers::JSTSMakeRule }, + { flatbuffers::GenerateJSTS, "-A", "--as", "AssemblyScript", true, + flatbuffers::GenerateTSGRPC, flatbuffers::IDLOptions::kAs, + "Generate AssemblyScript code for tables/structs", + flatbuffers::JSTSMakeRule }, { flatbuffers::GenerateCSharp, "-n", "--csharp", "C#", true, nullptr, flatbuffers::IDLOptions::kCSharp, "Generate C# classes for tables/structs", diff --git a/src/idl_gen_ts.cpp b/src/idl_gen_ts.cpp index 0240f513059d..488cb34bc0df 100644 --- a/src/idl_gen_ts.cpp +++ b/src/idl_gen_ts.cpp @@ -50,13 +50,19 @@ const JsTsLanguageParameters &GetJsLangParams(IDLOptions::Language lang) { IDLOptions::kTs, ".ts", }, + { + IDLOptions::kAs, + ".ts", + }, }; if (lang == IDLOptions::kJs) { return js_language_parameters[0]; - } else { - FLATBUFFERS_ASSERT(lang == IDLOptions::kTs); + } else if (lang == IDLOptions::kTs) { return js_language_parameters[1]; + } else { + FLATBUFFERS_ASSERT(lang == IDLOptions::kAs); + return js_language_parameters[2]; } } @@ -81,6 +87,31 @@ class JsTsGenerator : public BaseGenerator { reexport_map reexports; std::string enum_code, struct_code, import_code, exports_code, code; + int closing_brackets = 0; + + // HACK: Assembly script does not currently support dot notation + // namespaces. Removing namepsaces from each component for this reason. + if (lang_.language == IDLOptions::kAs) { + for (size_t i = 0; i < parser_.structs_.vec.size(); i++) { + parser_.structs_.vec[i]->defined_namespace->components.clear(); + for (size_t j = 0; j < parser_.structs_.vec[i]->fields.vec.size(); j++) { + if (parser_.structs_.vec[i]->fields.vec[j]->value.type.struct_def) { + if (parser_.structs_.vec[i] + ->fields.vec[j] + ->value.type.struct_def->defined_namespace) { + parser_.structs_.vec[i] + ->fields.vec[j] + ->value.type.struct_def->defined_namespace->components + .clear(); + } + } + } + } + for (size_t i = 0; i < parser_.enums_.vec.size(); i++) { + parser_.enums_.vec[i]->defined_namespace->components.clear(); + } + } + generateEnums(&enum_code, &exports_code, reexports, imported_files); generateStructs(&struct_code, &exports_code, imported_files); generateImportDependencies(&import_code, imported_files); @@ -93,12 +124,14 @@ class JsTsGenerator : public BaseGenerator { // Output the main declaration code from above. code += import_code; - code += enum_code; code += struct_code; - if (lang_.language == IDLOptions::kJs && !exports_code.empty() && - !parser_.opts.skip_js_exports) { + while (closing_brackets--) { code += "}\n"; } + + if ((lang_.language == IDLOptions::kJs || + lang_.language == IDLOptions::kAs) && + !exports_code.empty() && !parser_.opts.skip_js_exports) { if (parser_.opts.use_ES6_js_export_format) code += "// Exports for ECMAScript6 Modules\n"; else @@ -190,7 +223,8 @@ class JsTsGenerator : public BaseGenerator { } } void GenNamespaces(std::string *code_ptr, std::string *exports_ptr) { - if (lang_.language == IDLOptions::kTs && + if ((lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) && parser_.opts.skip_flatbuffers_import) { return; } @@ -219,13 +253,15 @@ class JsTsGenerator : public BaseGenerator { std::string &code = *code_ptr; std::string &exports = *exports_ptr; - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += "import * as flatbuffers from 'flatbuffers';\n"; } for (auto it = sorted_namespaces.begin(); it != sorted_namespaces.end(); ++it) { - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { if (it->find('.') == std::string::npos) { break; } } else { code += "/**\n * @const\n * @namespace\n */\n"; @@ -331,7 +367,9 @@ class JsTsGenerator : public BaseGenerator { std::string *exports_ptr, reexport_map &reexports, imported_fileset &imported_files, bool reverse) { if (enum_def.generated) return; - if (reverse && lang_.language == IDLOptions::kTs) return; // FIXME. + if (reverse && (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs)) + return; // FIXME. std::string &code = *code_ptr; std::string &exports = *exports_ptr; GenDocComment(enum_def.doc_comment, code_ptr, @@ -341,6 +379,8 @@ class JsTsGenerator : public BaseGenerator { if (lang_.language == IDLOptions::kTs) { if (!ns.empty()) { code += "export namespace " + ns + "{\n"; } code += "export enum " + enum_def.name + "{\n"; + } else if (lang_.language == IDLOptions::kAs) { + code += "export enum " + enum_def.name + "{\n"; } else { if (enum_def.defined_namespace->components.empty()) { code += "var "; @@ -365,11 +405,17 @@ class JsTsGenerator : public BaseGenerator { // Generate mapping between EnumName: EnumValue(int) if (reverse) { code += " '" + enum_def.ToString(ev) + "'"; - code += lang_.language == IDLOptions::kTs ? "= " : ": "; + code += lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs + ? "= " + : ": "; code += "'" + ev.name + "'"; } else { code += " " + ev.name; - code += lang_.language == IDLOptions::kTs ? "= " : ": "; + code += lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs + ? "= " + : ": "; code += enum_def.ToString(ev); } @@ -383,13 +429,14 @@ class JsTsGenerator : public BaseGenerator { std::make_pair(ev.union_type.struct_def->file, std::move(desc))); } } - code += "};"; + + code += "};\n"; if (lang_.language == IDLOptions::kTs) { if (enum_def.is_union) { code += GenUnionConvFunc(enum_def.underlying_type, imported_files); } - if (!ns.empty()) { code += "\n}"; } + if (!ns.empty() && lang_.language != IDLOptions::kAs) { code += "}"; } } code += "\n\n"; @@ -443,7 +490,10 @@ class JsTsGenerator : public BaseGenerator { } std::string GenBBAccess() const { - return lang_.language == IDLOptions::kTs ? "this.bb!" : "this.bb"; + return lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs + ? "this.bb!" + : "this.bb"; } std::string GenDefaultValue(const FieldDef &field, const std::string &context) { @@ -455,7 +505,8 @@ class JsTsGenerator : public BaseGenerator { if (value.type.enum_def && value.type.base_type != BASE_TYPE_UNION && value.type.base_type != BASE_TYPE_VECTOR) { if (auto val = value.type.enum_def->FindByValue(value.constant)) { - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { return GenPrefixedTypeName(WrapInNameSpace(*value.type.enum_def), value.type.enum_def->file) + "." + val->name; @@ -511,14 +562,17 @@ class JsTsGenerator : public BaseGenerator { case BASE_TYPE_BOOL: return (allowNull) ? ("boolean|null") : ("boolean"); case BASE_TYPE_LONG: case BASE_TYPE_ULONG: return (allowNull) ? ("flatbuffers.Long|null") : ("flatbuffers.Long"); + case BASE_TYPE_FLOAT: + if (lang_.language == IDLOptions::kAs) { return "f32"; } + return (allowNull) ? ("number|null") : ("number"); default: if (IsScalar(type.base_type)) { - if (type.enum_def) { - const auto enum_name = WrapInNameSpace(*type.enum_def); - return (allowNull) ? (enum_name + "|null") : (enum_name); + if (type.enum_def) { return WrapInNameSpace(*type.enum_def); } + if (lang_.language == IDLOptions::kAs) { + return "i32"; + } else { + return (allowNull) ? ("number|null") : ("number"); } - - return (allowNull) ? ("number|null") : ("number"); } return "flatbuffers.Offset"; } @@ -600,7 +654,8 @@ class JsTsGenerator : public BaseGenerator { *annotations += GenTypeAnnotation(kParam, GenTypeName(field.value.type, true, field.optional), nameprefix + field.name); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { *arguments += ", " + nameprefix + field.name + ": " + GenTypeName(field.value.type, true, field.optional); } else { @@ -638,7 +693,10 @@ class JsTsGenerator : public BaseGenerator { std::string GenerateNewExpression(const std::string &object_name) { return "new " + object_name + - (lang_.language == IDLOptions::kTs ? "()" : ""); + (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs + ? "()" + : ""); } void GenerateRootAccessor(StructDef &struct_def, std::string *code_ptr, @@ -655,6 +713,11 @@ class JsTsGenerator : public BaseGenerator { Verbose(struct_def, "As"); code += "(bb:flatbuffers.ByteBuffer, obj?:" + object_name + "):" + object_name + " {\n"; + } else if (lang_.language == IDLOptions::kAs) { + code += "static get" + (size_prefixed ? sizePrefixed : "") + "Root" + + Verbose(struct_def, "As"); + code += "(bb:flatbuffers.ByteBuffer, obj:" + object_name + + "| null = null):" + object_name + " {\n"; } else { code += object_name + ".get" + (size_prefixed ? sizePrefixed : "") + "Root" + Verbose(struct_def, "As"); @@ -665,9 +728,20 @@ class JsTsGenerator : public BaseGenerator { " bb.setPosition(bb.position() + " "flatbuffers.SIZE_PREFIX_LENGTH);\n"; } - code += " return (obj || " + GenerateNewExpression(object_name); - code += ").__init(bb.readInt32(bb.position()) + bb.position(), bb);\n"; - code += "};\n\n"; + if (lang_.language == IDLOptions::kAs) { + code += " let local = obj;\n"; + code += " if (local === null) {\n"; + code += " local = new " + object_name + "()\n"; + code += " }\n"; + code += + " return local.__init(bb.readInt32(bb.position()) + " + "bb.position(), bb);\n"; + code += "};\n\n"; + } else { + code += " return (obj || " + GenerateNewExpression(object_name); + code += ").__init(bb.readInt32(bb.position()) + bb.position(), bb);\n"; + code += "};\n\n"; + } } } @@ -685,6 +759,11 @@ class JsTsGenerator : public BaseGenerator { code += "static finish" + (size_prefixed ? sizePrefixed : "") + Verbose(struct_def) + "Buffer"; code += "(builder:flatbuffers.Builder, offset:flatbuffers.Offset) {\n"; + } else if (lang_.language == IDLOptions::kAs) { + code += "static finish" + (size_prefixed ? sizePrefixed : "") + + Verbose(struct_def) + "Buffer"; + code += + "(builder:flatbuffers.Builder, offset:flatbuffers.Offset):void {\n"; } else { code += object_name + ".finish" + (size_prefixed ? sizePrefixed : "") + Verbose(struct_def) + "Buffer"; @@ -855,13 +934,23 @@ class JsTsGenerator : public BaseGenerator { union_enum_loop("accessor("); ret += "}"; - ret += "\n\nexport function " + GenUnionListConvFuncName(enum_def) + - "(\n type: " + enum_def.name + - ", \n accessor: (index: number, obj:" + valid_union_type + - ") => " + valid_union_type_with_null + - ", \n index: number\n): " + valid_union_type_with_null + " {\n"; - union_enum_loop("accessor(index, "); - ret += "}"; + if (lang_.language == IDLOptions::kAs) { + ret += "\n\nexport function " + GenUnionListConvFuncName(enum_def) + + "(\n type: " + enum_def.name + + ", \n accessor: (index: i32, obj:" + valid_union_type + + ") => " + valid_union_type_with_null + + ", \n index: i32\n): " + valid_union_type_with_null + " {\n"; + union_enum_loop("accessor(index, "); + ret += "}"; + } else { + ret += "\n\nexport function " + GenUnionListConvFuncName(enum_def) + + "(\n type: " + enum_def.name + + ", \n accessor: (index: number, obj:" + valid_union_type + + ") => " + valid_union_type_with_null + + ", \n index: number\n): " + valid_union_type_with_null + " {\n"; + union_enum_loop("accessor(index, "); + ret += "}"; + } return ret; } @@ -1320,7 +1409,8 @@ class JsTsGenerator : public BaseGenerator { } code += "export class " + struct_def.name; code += " {\n"; - if (lang_.language != IDLOptions::kTs) { + if (lang_.language != IDLOptions::kTs && + lang_.language != IDLOptions::kAs) { code += " /**\n"; code += " * " + GenTypeAnnotation(kType, "flatbuffers.ByteBuffer", ""); @@ -1328,12 +1418,21 @@ class JsTsGenerator : public BaseGenerator { } code += " bb: flatbuffers.ByteBuffer|null = null;\n"; code += "\n"; - if (lang_.language != IDLOptions::kTs) { + if (lang_.language != IDLOptions::kTs && + lang_.language != IDLOptions::kAs) { code += " /**\n"; code += " * " + GenTypeAnnotation(kType, "number", ""); code += " */\n"; } code += " bb_pos:number = 0;\n"; + } else if (lang_.language == IDLOptions::kAs) { + object_name = struct_def.name; + GenDocComment(struct_def.doc_comment, code_ptr, "@constructor"); + code += "export class " + struct_def.name; + code += " {\n"; + code += " bb: flatbuffers.ByteBuffer|null = null;\n"; + code += "\n"; + code += " bb_pos:i32 = 0;\n"; } else { bool isStatement = struct_def.defined_namespace->components.empty(); object_name = WrapInNameSpace(struct_def); @@ -1376,6 +1475,9 @@ class JsTsGenerator : public BaseGenerator { if (lang_.language == IDLOptions::kTs) { code += "__init(i:number, bb:flatbuffers.ByteBuffer):" + object_name + " {\n"; + } else if (lang_.language == IDLOptions::kAs) { + code += + "__init(i:i32, bb:flatbuffers.ByteBuffer):" + object_name + " {\n"; } else { code += object_name + ".prototype.__init = function(i, bb) {\n"; } @@ -1396,7 +1498,8 @@ class JsTsGenerator : public BaseGenerator { GenDocComment(code_ptr, GenTypeAnnotation(kParam, "flatbuffers.ByteBuffer", "bb") + GenTypeAnnotation(kReturns, "boolean", "", false)); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += "static bufferHasIdentifier(bb:flatbuffers.ByteBuffer):boolean " "{\n"; @@ -1454,6 +1557,30 @@ class JsTsGenerator : public BaseGenerator { } else { code += "):" + GenTypeName(field.value.type, false, has_null_default) + " {\n"; } + } else if (lang_.language == IDLOptions::kAs) { + std::string prefix = MakeCamel(field.name, false) + "("; + if (field.value.type.base_type == BASE_TYPE_STRING) { + code += prefix + "):string|null{\n"; + } else { + code += prefix; + } + if (field.value.type.base_type != BASE_TYPE_STRING) { + if (field.value.type.enum_def) { + code += "):" + + GenPrefixedTypeName( + GenTypeName(field.value.type, false, true), + field.value.type.enum_def->file) + + " {\n"; + + if (!parser_.opts.generate_all) { + imported_files.insert(field.value.type.enum_def->file); + } + } else { + bool can_be_null = field.value.type.base_type != BASE_TYPE_BOOL; + code += "):" + GenTypeName(field.value.type, false, can_be_null) + + " {\n"; + } + } } else { code += object_name + ".prototype." + MakeCamel(field.name, false); code += " = function("; @@ -1471,7 +1598,7 @@ class JsTsGenerator : public BaseGenerator { ";\n"; } else { std::string index = "this.bb_pos + offset"; - if (is_string) { + if (is_string && lang_.language != IDLOptions::kAs) { index += ", optionalEncoding"; } code += offset_prefix + @@ -1495,27 +1622,57 @@ class JsTsGenerator : public BaseGenerator { GenPrefixedTypeName(type, field.value.type.struct_def->file); code += MakeCamel(field.name, false); code += "(obj?:" + type + "):" + type + "|null {\n"; + } else if (lang_.language == IDLOptions::kAs) { + type = + GenPrefixedTypeName(type, field.value.type.struct_def->file); + code += MakeCamel(field.name, false); + code += "(obj:" + type + "|null):" + type + "|null {\n"; } else { code += object_name + ".prototype." + MakeCamel(field.name, false); code += " = function(obj) {\n"; } - if (struct_def.fixed) { - code += " return (obj || " + GenerateNewExpression(type); - code += ").__init(this.bb_pos"; - code += - MaybeAdd(field.value.offset) + ", " + GenBBAccess() + ");\n"; + if (lang_.language == IDLOptions::kAs) { + if (struct_def.fixed) { + code += " let local = obj;\n"; + code += " if (local === null) {\n"; + code += " local = new " + type + "()\n"; + code += " }\n"; + code += " return local.__init(this.bb_pos"; + code += MaybeAdd(field.value.offset) + ", " + GenBBAccess() + + ");\n"; + } else { + code += " let local = obj;\n"; + code += " if (local === null) {\n"; + code += " local = new " + type + "()\n"; + code += " }\n"; + code += offset_prefix + " local.__init("; + code += + field.value.type.struct_def->fixed + ? "this.bb_pos + offset" + : GenBBAccess() + ".__indirect(this.bb_pos + offset)"; + code += ", " + GenBBAccess() + ") : null;\n"; + } } else { - code += offset_prefix + "(obj || " + GenerateNewExpression(type) + - ").__init("; - code += field.value.type.struct_def->fixed - ? "this.bb_pos + offset" - : GenBBAccess() + ".__indirect(this.bb_pos + offset)"; - code += ", " + GenBBAccess() + ") : null;\n"; + if (struct_def.fixed) { + code += " return (obj || " + GenerateNewExpression(type); + code += ").__init(this.bb_pos"; + code += MaybeAdd(field.value.offset) + ", " + GenBBAccess() + + ");\n"; + } else { + code += offset_prefix + "(obj || " + + GenerateNewExpression(type) + ").__init("; + code += + field.value.type.struct_def->fixed + ? "this.bb_pos + offset" + : GenBBAccess() + ".__indirect(this.bb_pos + offset)"; + code += ", " + GenBBAccess() + ") : null;\n"; + } } - if (lang_.language == IDLOptions::kTs && + if ((lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) && !parser_.opts.generate_all) { imported_files.insert(field.value.type.struct_def->file); } @@ -1590,6 +1747,35 @@ class JsTsGenerator : public BaseGenerator { } } code += "):" + vectortypename + "|null {\n"; + } else if (lang_.language == IDLOptions::kAs) { + std::string prefix = MakeCamel(field.name, false); + if (is_union) { prefix += ""; } + prefix += "(index: i32"; + if (is_union) { + const auto union_type = + GenUnionGenericTypeTS(*(field.value.type.enum_def)); + + vectortypename = union_type; + code += prefix + ", obj:" + union_type; + } else if (vectortype.base_type == BASE_TYPE_STRUCT) { + vectortypename = GenPrefixedTypeName( + vectortypename, vectortype.struct_def->file); + code += prefix + ", obj:" + vectortypename + "|null=null"; + + if (!parser_.opts.generate_all) { + imported_files.insert(vectortype.struct_def->file); + } + } else if (vectortype.base_type == BASE_TYPE_STRING) { + code += prefix + "):string|null {\n"; + } else { + code += prefix; + if (vectortype.enum_def && !parser_.opts.generate_all) { + imported_files.insert(vectortype.enum_def->file); + } + } + if (vectortype.base_type != BASE_TYPE_STRING) { + code += "):" + vectortypename + "|null {\n"; + } } else { code += object_name + ".prototype." + MakeCamel(field.name, false); @@ -1603,13 +1789,27 @@ class JsTsGenerator : public BaseGenerator { } if (vectortype.base_type == BASE_TYPE_STRUCT) { - code += offset_prefix + "(obj || " + - GenerateNewExpression(vectortypename); - code += ").__init("; - code += vectortype.struct_def->fixed - ? index - : GenBBAccess() + ".__indirect(" + index + ")"; - code += ", " + GenBBAccess() + ")"; + if (lang_.language == IDLOptions::kAs) { + code += " let local = obj;\n"; + code += " if (local === null) {\n"; + code += " local = " + GenerateNewExpression(vectortypename) + + ";\n"; + code += " }\n"; + code += offset_prefix + "local"; + code += ".__init("; + code += vectortype.struct_def->fixed + ? index + : GenBBAccess() + ".__indirect(" + index + ")"; + code += ", " + GenBBAccess() + ")"; + } else { + code += offset_prefix + "(obj || " + + GenerateNewExpression(vectortypename); + code += ").__init("; + code += vectortype.struct_def->fixed + ? index + : GenBBAccess() + ".__indirect(" + index + ")"; + code += ", " + GenBBAccess() + ")"; + } } else { if (is_union) { index = "obj, " + index; @@ -1647,7 +1847,8 @@ class JsTsGenerator : public BaseGenerator { GenTypeAnnotation(kParam, "flatbuffers.Table", "obj") + GenTypeAnnotation(kReturns, "?flatbuffers.Table", "", false)); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += MakeCamel(field.name, false); const auto &union_enum = *(field.value.type.enum_def); @@ -1687,7 +1888,8 @@ class JsTsGenerator : public BaseGenerator { code_ptr, annotations + GenTypeAnnotation(kReturns, "boolean", "", false)); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { std::string type; if (field.value.type.enum_def) { if (!parser_.opts.generate_all) { @@ -1747,6 +1949,9 @@ class JsTsGenerator : public BaseGenerator { if (lang_.language == IDLOptions::kTs) { code += MakeCamel(field.name, false); code += "Length():number {\n" + offset_prefix; + } else if (lang_.language == IDLOptions::kAs) { + code += MakeCamel(field.name, false); + code += "Length():i32 {\n" + offset_prefix; } else { code += object_name + ".prototype." + MakeCamel(field.name, false); code += "Length = function() {\n" + offset_prefix; @@ -1769,7 +1974,8 @@ class JsTsGenerator : public BaseGenerator { kReturns, GenType(vectorType) + "Array", "", false)); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += MakeCamel(field.name, false); code += "Array():" + GenType(vectorType) + "Array|null {\n" + offset_prefix; @@ -1797,7 +2003,8 @@ class JsTsGenerator : public BaseGenerator { // Emit the fully qualified name if (parser_.opts.generate_name_strings) { GenDocComment(code_ptr, GenTypeAnnotation(kReturns, "string", "", false)); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += "static getFullyQualifiedName():string {\n"; } else { code += object_name + ".getFullyQualifiedName = function() {\n"; @@ -1811,6 +2018,8 @@ class JsTsGenerator : public BaseGenerator { GenDocComment(code_ptr, GenTypeAnnotation(kReturns, "number", "", false)); if (lang_.language == IDLOptions::kTs) { code += "static sizeOf():number {\n"; + } else if (lang_.language == IDLOptions::kAs) { + code += "static sizeOf():i32 {\n"; } else { code += object_name + ".sizeOf = function() {\n"; } @@ -1828,7 +2037,8 @@ class JsTsGenerator : public BaseGenerator { kReturns, "flatbuffers.Offset", "", false)); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += "static create" + Verbose(struct_def) + "(builder:flatbuffers.Builder"; code += arguments + "):flatbuffers.Offset {\n"; @@ -1848,6 +2058,9 @@ class JsTsGenerator : public BaseGenerator { if (lang_.language == IDLOptions::kTs) { code += "static start" + Verbose(struct_def) + "(builder:flatbuffers.Builder) {\n"; + } else if (lang_.language == IDLOptions::kAs) { + code += "static start" + Verbose(struct_def) + + "(builder:flatbuffers.Builder):void {\n"; } else { code += object_name + ".start" + Verbose(struct_def); code += " = function(builder) {\n"; @@ -1875,6 +2088,10 @@ class JsTsGenerator : public BaseGenerator { code += "static add" + MakeCamel(field.name); code += "(builder:flatbuffers.Builder, " + argname + ":" + GetArgType(field, false) + ") {\n"; + } else if (lang_.language == IDLOptions::kAs) { + code += "static add" + MakeCamel(field.name); + code += "(builder:flatbuffers.Builder, " + argname + ":" + + GetArgType(field, false) + "):void {\n"; } else { code += object_name + ".add" + MakeCamel(field.name); code += " = function(builder, " + argname + ") {\n"; @@ -1915,7 +2132,8 @@ class JsTsGenerator : public BaseGenerator { GenTypeAnnotation(kReturns, "flatbuffers.Offset", "", false)); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { const std::string sig_begin = "static create" + MakeCamel(field.name) + "Vector(builder:flatbuffers.Builder, data:"; @@ -1973,6 +2191,10 @@ class JsTsGenerator : public BaseGenerator { if (lang_.language == IDLOptions::kTs) { code += "static start" + MakeCamel(field.name); code += "Vector(builder:flatbuffers.Builder, numElems:number) {\n"; + } else if (lang_.language == IDLOptions::kAs) { + code += "static start" + MakeCamel(field.name); + code += + "Vector(builder:flatbuffers.Builder, numElems:i32):void {\n"; } else { code += object_name + ".start" + MakeCamel(field.name); code += "Vector = function(builder, numElems) {\n"; @@ -1990,7 +2212,8 @@ class JsTsGenerator : public BaseGenerator { GenTypeAnnotation(kParam, "flatbuffers.Builder", "builder") + GenTypeAnnotation(kReturns, "flatbuffers.Offset", "", false)); - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += "static end" + Verbose(struct_def); code += "(builder:flatbuffers.Builder):flatbuffers.Offset {\n"; } else { @@ -2033,7 +2256,8 @@ class JsTsGenerator : public BaseGenerator { GenDocComment(code_ptr, paramDoc); } - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += "static create" + Verbose(struct_def); code += "(builder:flatbuffers.Builder"; } else { @@ -2045,14 +2269,16 @@ class JsTsGenerator : public BaseGenerator { const auto &field = **it; if (field.deprecated) continue; - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += ", " + GetArgName(field) + ":" + GetArgType(field, true); } else { code += ", " + GetArgName(field); } } - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { code += "):flatbuffers.Offset {\n"; code += " " + struct_def.name + ".start" + Verbose(struct_def) + "(builder);\n"; @@ -2062,8 +2288,10 @@ class JsTsGenerator : public BaseGenerator { "(builder);\n"; } - std::string methodPrefix = - lang_.language == IDLOptions::kTs ? struct_def.name : object_name; + std::string methodPrefix = (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) + ? struct_def.name + : object_name; for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { const auto &field = **it; @@ -2082,12 +2310,15 @@ class JsTsGenerator : public BaseGenerator { code += " return " + methodPrefix + ".end" + Verbose(struct_def) + "(builder);\n"; code += "}\n"; - if (lang_.language == IDLOptions::kJs) code += "\n"; + if (lang_.language == IDLOptions::kJs || + lang_.language == IDLOptions::kAs) + code += "\n"; } } if (!struct_def.fixed && parser_.services_.vec.size() != 0 && - lang_.language == IDLOptions::kTs) { + (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs)) { auto name = Verbose(struct_def, ""); code += "\n"; code += "serialize():Uint8Array {\n"; @@ -2101,7 +2332,8 @@ class JsTsGenerator : public BaseGenerator { code += "}\n"; } - if (lang_.language == IDLOptions::kTs) { + if (lang_.language == IDLOptions::kTs || + lang_.language == IDLOptions::kAs) { if (parser_.opts.generate_object_based_api) { std::string obj_api_class; std::string obj_api_unpack_func; @@ -2112,10 +2344,12 @@ class JsTsGenerator : public BaseGenerator { } else { code += "}\n"; } - if (!object_namespace.empty()) { code += "}\n"; } + if (!object_namespace.empty() && lang_.language != IDLOptions::kAs) { + code += "}\n"; + } } } - + static bool HasNullDefault(const FieldDef &field) { return field.optional && field.value.constant == "null"; }