From 224e33ed09235628938970f6c672d6e2a69f5c6c Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Mon, 17 Aug 2015 00:56:54 -0700 Subject: [PATCH] Add support for JavaScript code generation This adds a JavaScript language target. The generated JavaScript uses Google Closure Compiler type annotations and can be compiled using the advanced compilation mode, which performs type checking and optimizations such as inlining and dead code elimination. The generated JavaScript also exports all generated symbols for use with Node.js and RequireJS. This export behavior can be turned off with the --no-js-exports flag for use with Google Closure Compiler. --- .gitignore | 3 +- CMakeLists.txt | 1 + include/flatbuffers/idl.h | 18 + js/flatbuffers.js | 1021 +++++++++++++++++++++++++++++++ src/flatc.cpp | 6 + src/idl_gen_js.cpp | 712 +++++++++++++++++++++ tests/JavaScriptTest.js | 116 ++++ tests/JavaScriptTest.sh | 5 + tests/generate_code.sh | 2 +- tests/monster_test_generated.js | 936 ++++++++++++++++++++++++++++ 10 files changed, 2818 insertions(+), 2 deletions(-) create mode 100644 js/flatbuffers.js create mode 100644 src/idl_gen_js.cpp create mode 100644 tests/JavaScriptTest.js create mode 100755 tests/JavaScriptTest.sh create mode 100644 tests/monster_test_generated.js diff --git a/.gitignore b/.gitignore index c8415516a..69e7586a1 100755 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ snapshot.sh tests/go_gen tests/monsterdata_java_wire.mon tests/monsterdata_go_wire.mon +tests/monsterdata_javascript_wire.mon CMakeLists.txt.user CMakeScripts/** CTestTestfile.cmake @@ -55,4 +56,4 @@ java/.idea java/*.iml java/target **/*.pyc -.idea \ No newline at end of file +.idea diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e8cdee40..f0691dd90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ set(FlatBuffers_Compiler_SRCS src/idl_gen_cpp.cpp src/idl_gen_general.cpp src/idl_gen_go.cpp + src/idl_gen_js.cpp src/idl_gen_python.cpp src/idl_gen_fbs.cpp src/flatc.cpp diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 4b0dd818c..82fb18bed 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -449,6 +449,7 @@ extern void GenComment(const std::vector &dc, // Container of options that may apply to any of the source/text generators. struct GeneratorOptions { bool strict_json; + bool skip_js_exports; bool output_default_scalars_in_json; int indent_step; bool output_enum_identifiers; @@ -464,6 +465,7 @@ struct GeneratorOptions { Language lang; GeneratorOptions() : strict_json(false), + skip_js_exports(false), output_default_scalars_in_json(false), indent_step(2), output_enum_identifiers(true), prefixed_enums(true), scoped_enums(false), @@ -506,6 +508,15 @@ extern bool GenerateCPP(const Parser &parser, const std::string &file_name, const GeneratorOptions &opts); +// Generate JavaScript code from the definitions in the Parser object. +// See idl_gen_js. +extern std::string GenerateJS(const Parser &parser, + const GeneratorOptions &opts); +extern bool GenerateJS(const Parser &parser, + const std::string &path, + const std::string &file_name, + const GeneratorOptions &opts); + // Generate Go files from the definitions in the Parser object. // See idl_gen_go.cpp. extern bool GenerateGo(const Parser &parser, @@ -551,6 +562,13 @@ extern bool GenerateFBS(const Parser &parser, const std::string &file_name, const GeneratorOptions &opts); +// Generate a make rule for the generated JavaScript code. +// See idl_gen_js.cpp. +extern std::string JSMakeRule(const Parser &parser, + const std::string &path, + const std::string &file_name, + const GeneratorOptions &opts); + // Generate a make rule for the generated C++ header. // See idl_gen_cpp.cpp. extern std::string CPPMakeRule(const Parser &parser, diff --git a/js/flatbuffers.js b/js/flatbuffers.js new file mode 100644 index 000000000..37995c9c8 --- /dev/null +++ b/js/flatbuffers.js @@ -0,0 +1,1021 @@ +var flatbuffers = {}; + +/** + * @typedef {number} + */ +flatbuffers.Offset; + +/** + * @typedef {{ + * bb: flatbuffers.ByteBuffer, + * bb_pos: number + * }} + */ +flatbuffers.Table; + +/** + * @type {number} + * @const + */ +flatbuffers.SIZEOF_SHORT = 2; + +/** + * @type {number} + * @const + */ +flatbuffers.SIZEOF_INT = 4; + +/** + * @type {number} + * @const + */ +flatbuffers.FILE_IDENTIFIER_LENGTH = 4; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * @constructor + * @param {number} high + * @param {number} low + */ +flatbuffers.Long = function(low, high) { + /** + * @type {number} + * @const + */ + this.low = low | 0; + + /** + * @type {number} + * @const + */ + this.high = high | 0; +}; + +/** + * @returns {number} + */ +flatbuffers.Long.prototype.toFloat64 = function() { + return this.low + this.high * 0x100000000; +}; + +/** + * @param {flatbuffers.Long} other + * @returns {boolean} + */ +flatbuffers.Long.prototype.equals = function(other) { + return this.low == other.low && this.high == other.high; +}; + +/** + * @type {flatbuffers.Long} + * @const + */ +flatbuffers.Long.ZERO = new flatbuffers.Long(0, 0); + +//////////////////////////////////////////////////////////////////////////////// + +/** + * @constructor + * @param {number=} initial_size + */ +flatbuffers.Builder = function(initial_size) { + if (!initial_size) { + initial_size = 1024; + } + + /** + * @type {flatbuffers.ByteBuffer} + * @private + */ + this.bb = flatbuffers.ByteBuffer.allocate(initial_size); + + /** + * Remaining space in the ByteBuffer. + * + * @type {number} + * @private + */ + this.space = initial_size; + + /** + * Minimum alignment encountered so far. + * + * @type {number} + * @private + */ + this.minalign = 1; + + /** + * The vtable for the current table. + * + * @type {Array.} + * @private + */ + this.vtable = null; + + /** + * The amount of fields we're actually using. + * + * @type {number} + * @private + */ + this.vtable_in_use = 0; + + /** + * Whether we are currently serializing a table. + * + * @type {boolean} + * @private + */ + this.isNested = false; + + /** + * Starting offset of the current struct/table. + * + * @type {number} + * @private + */ + this.object_start = 0; + + /** + * List of offsets of all vtables. + * + * @type {Array.} + * @private + */ + this.vtables = []; + + /** + * For the current vector being built. + * + * @type {number} + * @private + */ + this.vector_num_elems = 0; + + /** + * False omits default values from the serialized data + * + * @type {boolean} + * @private + */ + this.force_defaults = false; +}; + +/** + * 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 {boolean} forceDefaults true always serializes default values + */ +flatbuffers.Builder.prototype.forceDefaults = function(forceDefaults) { + 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. + * + * @returns {flatbuffers.ByteBuffer} + */ +flatbuffers.Builder.prototype.dataBuffer = function() { + return this.bb; +}; + +/** + * 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. + * + * @returns {Uint8Array} + */ +flatbuffers.Builder.prototype.asUint8Array = function() { + 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 {number} size This is the of the new element to write + * @param {number} additional_bytes The padding size + */ +flatbuffers.Builder.prototype.prep = function(size, additional_bytes) { + // 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` + var 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) { + var old_buf_size = this.bb.capacity(); + this.bb = flatbuffers.Builder.growByteBuffer(this.bb); + this.space += this.bb.capacity() - old_buf_size; + } + + this.pad(align_size); +}; + +/** + * @param {number} byte_size + */ +flatbuffers.Builder.prototype.pad = function(byte_size) { + for (var i = 0; i < byte_size; i++) { + this.bb.writeInt8(--this.space, 0); + } +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.writeInt8 = function(value) { + this.bb.writeInt8(this.space -= 1, value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.writeInt16 = function(value) { + this.bb.writeInt16(this.space -= 2, value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.writeInt32 = function(value) { + this.bb.writeInt32(this.space -= 4, value); +}; + +/** + * @param {flatbuffers.Long} value + */ +flatbuffers.Builder.prototype.writeInt64 = function(value) { + this.bb.writeInt64(this.space -= 8, value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.writeFloat32 = function(value) { + this.bb.writeFloat32(this.space -= 4, value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.writeFloat64 = function(value) { + this.bb.writeFloat64(this.space -= 8, value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.addInt8 = function(value) { + this.prep(1, 0); + this.writeInt8(value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.addInt16 = function(value) { + this.prep(2, 0); + this.writeInt16(value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.addInt32 = function(value) { + this.prep(4, 0); + this.writeInt32(value); +}; + +/** + * @param {flatbuffers.Long} value + */ +flatbuffers.Builder.prototype.addInt64 = function(value) { + this.prep(8, 0); + this.writeInt64(value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.addFloat32 = function(value) { + this.prep(4, 0); + this.writeFloat32(value); +}; + +/** + * @param {number} value + */ +flatbuffers.Builder.prototype.addFloat64 = function(value) { + this.prep(8, 0); + this.writeFloat64(value); +}; + +/** + * @param {number} voffset + * @param {number} value + * @param {number} defaultValue + */ +flatbuffers.Builder.prototype.addFieldInt8 = function(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addInt8(value); + this.slot(voffset); + } +}; + +/** + * @param {number} voffset + * @param {number} value + * @param {number} defaultValue + */ +flatbuffers.Builder.prototype.addFieldInt16 = function(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addInt16(value); + this.slot(voffset); + } +}; + +/** + * @param {number} voffset + * @param {number} value + * @param {number} defaultValue + */ +flatbuffers.Builder.prototype.addFieldInt32 = function(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addInt32(value); + this.slot(voffset); + } +}; + +/** + * @param {number} voffset + * @param {flatbuffers.Long} value + * @param {flatbuffers.Long} defaultValue + */ +flatbuffers.Builder.prototype.addFieldInt64 = function(voffset, value, defaultValue) { + if (this.force_defaults || !value.equals(defaultValue)) { + this.addInt64(value); + this.slot(voffset); + } +}; + +/** + * @param {number} voffset + * @param {number} value + * @param {number} defaultValue + */ +flatbuffers.Builder.prototype.addFieldFloat32 = function(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addFloat32(value); + this.slot(voffset); + } +}; + +/** + * @param {number} voffset + * @param {number} value + * @param {number} defaultValue + */ +flatbuffers.Builder.prototype.addFieldFloat64 = function(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addFloat64(value); + this.slot(voffset); + } +}; + +/** + * @param {number} voffset + * @param {flatbuffers.Offset} value + * @param {flatbuffers.Offset} defaultValue + */ +flatbuffers.Builder.prototype.addFieldOffset = function(voffset, value, defaultValue) { + 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. + * + * @param {number} voffset + * @param {flatbuffers.Offset} value + * @param {flatbuffers.Offset} defaultValue + */ +flatbuffers.Builder.prototype.addFieldStruct = function(voffset, value, defaultValue) { + 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. + * + * @param {flatbuffers.Offset} obj The offset of the created object + */ +flatbuffers.Builder.prototype.nested = function(obj) { + 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 + */ +flatbuffers.Builder.prototype.notNested = function() { + 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. + * + * @param {number} voffset + */ +flatbuffers.Builder.prototype.slot = function(voffset) { + this.vtable[voffset] = this.offset(); +}; + +/** + * @returns {flatbuffers.Offset} Offset relative to the end of the buffer. + */ +flatbuffers.Builder.prototype.offset = function() { + 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 {flatbuffers.ByteBuffer} bb The current buffer with the existing data + * @returns {flatbuffers.ByteBuffer} A new byte buffer with the old data copied + * to it. The data is located at the end of the buffer. + */ +flatbuffers.Builder.growByteBuffer = function(bb) { + var 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.'); + } + + var new_buf_size = old_buf_size << 1; + var nbb = flatbuffers.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 {flatbuffers.Offset} offset The offset to add + */ +flatbuffers.Builder.prototype.addOffset = function(offset) { + this.prep(flatbuffers.SIZEOF_INT, 0); // Ensure alignment is already done. + this.writeInt32(this.offset() - offset + flatbuffers.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. + * + * @param {number} numfields + */ +flatbuffers.Builder.prototype.startObject = function(numfields) { + this.notNested(); + if (this.vtable == null) { + this.vtable = []; + } + this.vtable_in_use = numfields; + for (var i = 0; i < numfields; i++) { + this.vtable[i] = 0; // This will push additional elements as needed + } + this.isNested = true; + this.object_start = this.offset(); +}; + +/** + * Finish off writing the object that is under construction. + * + * @returns {flatbuffers.Offset} The offset to the object inside `dataBuffer` + */ +flatbuffers.Builder.prototype.endObject = function() { + if (this.vtable == null || !this.isNested) { + throw new Error('FlatBuffers: endObject called without startObject'); + } + + this.addInt32(0); + var vtableloc = this.offset(); + + // Write out the current vtable. + for (var i = this.vtable_in_use - 1; i >= 0; i--) { + // Offset relative to the start of the table. + this.addInt16(this.vtable[i] != 0 ? vtableloc - this.vtable[i] : 0); + } + + var standard_fields = 2; // The fields below: + this.addInt16(vtableloc - this.object_start); + this.addInt16((this.vtable_in_use + standard_fields) * flatbuffers.SIZEOF_SHORT); + + // Search for an existing vtable that matches the current one. + var existing_vtable = 0; +outer_loop: + for (var i = 0; i < this.vtables.length; i++) { + var vt1 = this.bb.capacity() - this.vtables[i]; + var vt2 = this.space; + var len = this.bb.readInt16(vt1); + if (len == this.bb.readInt16(vt2)) { + for (var j = flatbuffers.SIZEOF_SHORT; j < len; j += flatbuffers.SIZEOF_SHORT) { + if (this.bb.readInt16(vt1 + j) != this.bb.readInt16(vt2 + j)) { + continue outer_loop; + } + } + 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; +}; + +/** + * @param {flatbuffers.Offset} root_table + * @param {string=} file_identifier + */ +flatbuffers.Builder.prototype.finish = function(root_table, file_identifier) { + if (file_identifier) { + this.prep(this.minalign, flatbuffers.SIZEOF_INT + + flatbuffers.FILE_IDENTIFIER_LENGTH); + if (file_identifier.length != flatbuffers.FILE_IDENTIFIER_LENGTH) { + throw new Error('FlatBuffers: file identifier must be length ' + + flatbuffers.FILE_IDENTIFIER_LENGTH); + } + for (var i = flatbuffers.FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) { + this.writeInt8(file_identifier.charCodeAt(i)); + } + } + this.prep(this.minalign, flatbuffers.SIZEOF_INT); + this.addOffset(root_table); + this.bb.setPosition(this.space); +}; + +/** + * This checks a required field has been set in a given table that has + * just been constructed. + * + * @param {flatbuffers.Offset} table + * @param {number} field + */ +flatbuffers.Builder.prototype.requiredField = function(table, field) { + var table_start = this.bb.capacity() - table; + var vtable_start = table_start - this.bb.readInt32(table_start); + var 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 {number} elem_size The size of each element in the array + * @param {number} num_elems The number of elements in the array + * @param {number} alignment The alignment of the array + */ +flatbuffers.Builder.prototype.startVector = function(elem_size, num_elems, alignment) { + this.notNested(); + this.vector_num_elems = num_elems; + this.prep(flatbuffers.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 {flatbuffers.Offset} The offset at which the newly created array + * starts. + */ +flatbuffers.Builder.prototype.endVector = function() { + this.writeInt32(this.vector_num_elems); + return this.offset(); +}; + +/** + * Encode the string `s` in the buffer using UTF-8. + * + * @param {string} s The string to encode + * @return {flatbuffers.Offset} The offset in the buffer where the encoded string starts + */ +flatbuffers.Builder.prototype.createString = function(s) { + var utf8 = []; + var i = 0; + + while (i < s.length) { + var codePoint; + + // Decode UTF-16 + var a = s.charCodeAt(i++); + if (a < 0xD800 || a >= 0xDC00) { + codePoint = a; + } else { + var 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, + ((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 (var i = 0, offset = this.space, bytes = this.bb.bytes(); i < utf8.length; i++) { + bytes[offset++] = utf8[i]; + } + return this.endVector(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * @constructor + * @param {Uint8Array} bytes + */ +flatbuffers.ByteBuffer = function(bytes) { + /** + * @type {Uint8Array} + * @private + */ + this.bytes_ = bytes; + + /** + * @type {DataView} + * @private + */ + this.view_ = new DataView(bytes.buffer, bytes.byteOffset, bytes.length); + + /** + * @type {number} + * @private + */ + this.position_ = 0; +}; + +/** + * @param {number} byte_size + * @returns {flatbuffers.ByteBuffer} + */ +flatbuffers.ByteBuffer.allocate = function(byte_size) { + return new flatbuffers.ByteBuffer(new Uint8Array(byte_size)); +}; + +/** + * @returns {Uint8Array} + */ +flatbuffers.ByteBuffer.prototype.bytes = function() { + return this.bytes_; +}; + +/** + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.position = function() { + return this.position_; +}; + +/** + * @param {number} position + */ +flatbuffers.ByteBuffer.prototype.setPosition = function(position) { + this.position_ = position; +}; + +/** + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.capacity = function() { + return this.bytes_.length; +}; + +/** + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.readInt8 = function(offset) { + return this.view_.getInt8(offset); +}; + +/** + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.readUint8 = function(offset) { + return this.view_.getUint8(offset); +}; + +/** + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.readInt16 = function(offset) { + return this.view_.getInt16(offset, true); +}; + +/** + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.readUint16 = function(offset) { + return this.view_.getUint16(offset, true); +}; + +/** + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.readInt32 = function(offset) { + return this.view_.getInt32(offset, true); +}; + +/** + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.readUint32 = function(offset) { + return this.view_.getUint32(offset, true); +}; + +/** + * @param {number} offset + * @returns {flatbuffers.Long} + */ +flatbuffers.ByteBuffer.prototype.readInt64 = function(offset) { + return new flatbuffers.Long(this.readInt32(offset), this.readInt32(offset + 4)); +}; + +/** + * @param {number} offset + * @returns {flatbuffers.Long} + */ +flatbuffers.ByteBuffer.prototype.readUint64 = function(offset) { + return new flatbuffers.Long(this.readInt32(offset), this.readInt32(offset + 4)); +}; + +/** + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.readFloat32 = function(offset) { + return this.view_.getFloat32(offset, true); +}; + +/** + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.readFloat64 = function(offset) { + return this.view_.getFloat64(offset, true); +}; + +/** + * @param {number} offset + * @param {number} value + */ +flatbuffers.ByteBuffer.prototype.writeInt8 = function(offset, value) { + this.view_.setInt8(offset, value); +}; + +/** + * @param {number} offset + * @param {number} value + */ +flatbuffers.ByteBuffer.prototype.writeInt16 = function(offset, value) { + this.view_.setInt16(offset, value, true); +}; + +/** + * @param {number} offset + * @param {number} value + */ +flatbuffers.ByteBuffer.prototype.writeInt32 = function(offset, value) { + this.view_.setInt32(offset, value, true); +}; + +/** + * @param {number} offset + * @param {flatbuffers.Long} value + */ +flatbuffers.ByteBuffer.prototype.writeInt64 = function(offset, value) { + this.view_.setInt32(offset, value.low); + this.view_.setInt32(offset + 4, value.high); +}; + +/** + * @param {number} offset + * @param {number} value + */ +flatbuffers.ByteBuffer.prototype.writeFloat32 = function(offset, value) { + this.view_.setFloat32(offset, value, true); +}; + +/** + * @param {number} offset + * @param {number} value + */ +flatbuffers.ByteBuffer.prototype.writeFloat64 = function(offset, value) { + this.view_.setFloat64(offset, value, true); +}; + +/** + * Look up a field in the vtable, return an offset into the object, or 0 if the + * field is not present. + * + * @param {number} bb_pos + * @param {number} vtable_offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.__offset = function(bb_pos, vtable_offset) { + var 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. + * + * @param {flatbuffers.Table} t + * @param {number} offset + * @returns {flatbuffers.Table} + */ +flatbuffers.ByteBuffer.prototype.__union = function(t, offset) { + 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. + * + * @param {number} offset + * @returns {string} + */ +flatbuffers.ByteBuffer.prototype.__string = function(offset) { + offset += this.readInt32(offset); + + var length = this.readInt32(offset); + var result = ''; + var i = 0; + + offset += flatbuffers.SIZEOF_INT; + + while (i < length) { + var codePoint; + + // Decode UTF-8 + var a = this.readUint8(offset + i++); + if (a < 0xC0) { + codePoint = a; + } else { + var b = this.readUint8(offset + i++); + if (a < 0xE0) { + codePoint = + ((a & 0x1F) << 6) | + (b & 0x3F); + } else { + var c = this.readUint8(offset + i++); + if (a < 0xF0) { + codePoint = + ((a & 0x0F) << 12) | + ((b & 0x3F) << 6) | + (c & 0x3F); + } else { + var 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; +}; + +/** + * Retrieve the relative offset stored at "offset" + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.__indirect = function(offset) { + return offset + this.readInt32(offset); +}; + +/** + * Get the start of data of a vector whose offset is stored at "offset" in this object. + * + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.__vector = function(offset) { + return offset + this.readInt32(offset) + flatbuffers.SIZEOF_INT; // data starts after the length +}; + +/** + * Get the length of a vector whose offset is stored at "offset" in this object. + * + * @param {number} offset + * @returns {number} + */ +flatbuffers.ByteBuffer.prototype.__vector_len = function(offset) { + return this.readInt32(offset + this.readInt32(offset)); +}; + +/** + * @param {string} ident + * @returns {boolean} + */ +flatbuffers.ByteBuffer.prototype.__has_identifier = function(ident) { + if (ident.length != flatbuffers.FILE_IDENTIFIER_LENGTH) { + throw new Error('FlatBuffers: file identifier must be length ' + + flatbuffers.FILE_IDENTIFIER_LENGTH); + } + for (var i = 0; i < flatbuffers.FILE_IDENTIFIER_LENGTH; i++) { + if (ident.charCodeAt(i) != this.readInt8(this.position_ + flatbuffers.SIZEOF_INT + i)) { + return false; + } + } + return true; +}; + +// Exports for Node.js and RequireJS +this.flatbuffers = flatbuffers; diff --git a/src/flatc.cpp b/src/flatc.cpp index 7f654b0ba..e3c74d2e2 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -60,6 +60,10 @@ const Generator generators[] = { flatbuffers::GeneratorOptions::kJava, "Generate Java classes for tables/structs", flatbuffers::GeneralMakeRule }, + { flatbuffers::GenerateJS, "-s", "JavaScript", + flatbuffers::GeneratorOptions::kMAX, + "Generate JavaScript code for tables/structs", + flatbuffers::JSMakeRule }, { flatbuffers::GenerateGeneral, "-n", "C#", flatbuffers::GeneratorOptions::kCSharp, "Generate C# classes for tables/structs", @@ -140,6 +144,8 @@ int main(int argc, const char *argv[]) { include_directories.push_back(argv[argi]); } else if(arg == "--strict-json") { opts.strict_json = true; + } else if(arg == "--no-js-exports") { + opts.skip_js_exports = true; } else if(arg == "--defaults-json") { opts.output_default_scalars_in_json = true; } else if(arg == "--no-prefix") { diff --git a/src/idl_gen_js.cpp b/src/idl_gen_js.cpp new file mode 100644 index 000000000..40726f1b3 --- /dev/null +++ b/src/idl_gen_js.cpp @@ -0,0 +1,712 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// independent from idl_parser, since this code is not needed for most clients + +#include "flatbuffers/flatbuffers.h" +#include "flatbuffers/idl.h" +#include "flatbuffers/util.h" + +namespace flatbuffers { +namespace js { + +static void GenNamespaces(const Parser &parser, std::string *code_ptr, + std::string *exports_ptr) { + std::set namespaces; + + for (auto it = parser.namespaces_.begin(); + it != parser.namespaces_.end(); ++it) { + std::string namespace_so_far; + + // Gather all parent namespaces for this namespace + for (auto component = (*it)->components.begin(); + component != (*it)->components.end(); ++component) { + if (!namespace_so_far.empty()) { + namespace_so_far += '.'; + } + namespace_so_far += *component; + namespaces.insert(namespace_so_far); + } + } + + // Make sure parent namespaces come before child namespaces + std::vector sorted_namespaces( + namespaces.begin(), namespaces.end()); + std::sort(sorted_namespaces.begin(), sorted_namespaces.end()); + + // Emit namespaces in a form that Closure Compiler can optimize + std::string &code = *code_ptr; + std::string &exports = *exports_ptr; + for (auto it = sorted_namespaces.begin(); + it != sorted_namespaces.end(); it++) { + code += "/**\n * @const\n*/\n"; + if (it->find('.') == std::string::npos) { + code += "var "; + exports += "this." + *it + " = " + *it + ";\n"; + } + code += *it + " = " + *it + " || {};\n\n"; + } +} + +// Ensure that a type is prefixed with its namespace whenever it is used +// outside of its namespace. +static std::string WrapInNameSpace(const Namespace *ns, + const std::string &name) { + std::string qualified_name; + for (auto it = ns->components.begin(); + it != ns->components.end(); ++it) { + qualified_name += *it + "."; + } + return qualified_name + name; +} + +static std::string WrapInNameSpace(const Definition &def) { + return WrapInNameSpace(def.defined_namespace, def.name); +} + +// Generate a documentation comment, if available. +static void GenDocComment(const std::vector &dc, + std::string *code_ptr, + const std::string &extra_lines, + const char *indent = nullptr) { + if (dc.empty() && extra_lines.empty()) { + // Don't output empty comment blocks with 0 lines of comment content. + return; + } + + std::string &code = *code_ptr; + if (indent) code += indent; + code += "/**\n"; + for (auto it = dc.begin(); it != dc.end(); ++it) { + if (indent) code += indent; + code += " *" + *it + "\n"; + } + if (!extra_lines.empty()) { + if (!dc.empty()) { + if (indent) code += indent; + code += " *\n"; + } + if (indent) code += indent; + std::string::size_type start = 0; + while (true) { + auto end = extra_lines.find('\n', start); + if (end != std::string::npos) { + code += " * " + extra_lines.substr(start, end - start) + "\n"; + start = end + 1; + } else { + code += " * " + extra_lines.substr(start) + "\n"; + break; + } + } + } + if (indent) code += indent; + code += " */\n"; +} + +static void GenDocComment(std::string *code_ptr, + const std::string &extra_lines) { + GenDocComment(std::vector(), code_ptr, extra_lines); +} + +// Generate an enum declaration and an enum string lookup table. +static void GenEnum(EnumDef &enum_def, std::string *code_ptr, + std::string *exports_ptr) { + if (enum_def.generated) return; + std::string &code = *code_ptr; + std::string &exports = *exports_ptr; + GenDocComment(enum_def.doc_comment, code_ptr, "@enum"); + if (enum_def.defined_namespace->components.empty()) { + code += "var "; + exports += "this." + enum_def.name + " = " + enum_def.name + ";\n"; + } + code += WrapInNameSpace(enum_def) + " = {\n"; + for (auto it = enum_def.vals.vec.begin(); + it != enum_def.vals.vec.end(); ++it) { + auto &ev = **it; + if (!ev.doc_comment.empty()) { + if (it != enum_def.vals.vec.begin()) { + code += '\n'; + } + GenDocComment(ev.doc_comment, code_ptr, "", " "); + } + code += " " + ev.name + ": " + NumToString(ev.value); + code += (it + 1) != enum_def.vals.vec.end() ? ",\n" : "\n"; + } + code += "};\n\n"; +} + +static std::string GenType(const Type &type) { + switch (type.base_type) { + case BASE_TYPE_BOOL: + case BASE_TYPE_CHAR: return "Int8"; + case BASE_TYPE_UTYPE: + case BASE_TYPE_UCHAR: return "Uint8"; + case BASE_TYPE_SHORT: return "Int16"; + case BASE_TYPE_USHORT: return "Uint16"; + case BASE_TYPE_INT: return "Int32"; + case BASE_TYPE_UINT: return "Uint32"; + case BASE_TYPE_LONG: return "Int64"; + case BASE_TYPE_ULONG: return "Uint64"; + case BASE_TYPE_FLOAT: return "Float32"; + case BASE_TYPE_DOUBLE: return "Float64"; + case BASE_TYPE_STRING: return "String"; + case BASE_TYPE_VECTOR: return GenType(type.VectorType()); + case BASE_TYPE_STRUCT: return type.struct_def->name; + default: return "Table"; + } +} + +static std::string GenGetter(const Type &type, const std::string &arguments) { + switch (type.base_type) { + case BASE_TYPE_STRING: return "this.bb.__string" + arguments; + case BASE_TYPE_STRUCT: return "this.bb.__struct" + arguments; + case BASE_TYPE_UNION: return "this.bb.__union" + arguments; + case BASE_TYPE_VECTOR: return GenGetter(type.VectorType(), arguments); + default: { + auto getter = "this.bb.read" + MakeCamel(GenType(type)) + arguments; + if (type.base_type == BASE_TYPE_BOOL) { + getter = "!!" + getter; + } + if (type.enum_def) { + getter = "/** @type {" + WrapInNameSpace(*type.enum_def) + "} */ (" + + getter + ")"; + } + return getter; + } + } +} + +static std::string GenDefaultValue(const Value &value) { + if (value.type.enum_def) { + if (auto val = value.type.enum_def->ReverseLookup( + atoi(value.constant.c_str()), false)) { + return WrapInNameSpace(*value.type.enum_def) + "." + val->name; + } + } + + switch (value.type.base_type) { + case BASE_TYPE_BOOL: + return value.constant == "0" ? "false" : "true"; + + case BASE_TYPE_STRING: + return "null"; + + case BASE_TYPE_LONG: + case BASE_TYPE_ULONG: + if (value.constant != "0") { + int64_t constant = std::atoll(value.constant.c_str()); + return "new flatbuffers.Long(" + NumToString((int32_t)constant) + + ", " + NumToString((int32_t)(constant >> 32)) + ")"; + } + return "flatbuffers.Long.ZERO"; + + default: + return value.constant; + } +} + +enum struct InOut { + IN, + OUT, +}; + +static std::string GenTypeName(const Type &type, InOut inOut) { + if (inOut == InOut::OUT) { + if (type.base_type == BASE_TYPE_STRING) { + return "?string"; + } + if (type.base_type == BASE_TYPE_STRUCT) { + return WrapInNameSpace(*type.struct_def); + } + } + + switch (type.base_type) { + case BASE_TYPE_BOOL: return "boolean"; + case BASE_TYPE_LONG: + case BASE_TYPE_ULONG: return "flatbuffers.Long"; + default: + if (IsScalar(type.base_type)) { + if (type.enum_def) { + return WrapInNameSpace(*type.enum_def); + } + return "number"; + } + return "flatbuffers.Offset"; + } +} + +// Returns the method name for use with add/put calls. +static std::string GenWriteMethod(const Type &type) { + // Forward to signed versions since unsigned versions don't exist + switch (type.base_type) { + case BASE_TYPE_UTYPE: + case BASE_TYPE_UCHAR: return GenWriteMethod(Type(BASE_TYPE_CHAR)); + case BASE_TYPE_USHORT: return GenWriteMethod(Type(BASE_TYPE_SHORT)); + case BASE_TYPE_UINT: return GenWriteMethod(Type(BASE_TYPE_INT)); + case BASE_TYPE_ULONG: return GenWriteMethod(Type(BASE_TYPE_LONG)); + default: break; + } + + return IsScalar(type.base_type) + ? MakeCamel(GenType(type)) + : (IsStruct(type) ? "Struct" : "Offset"); +} + +template +static std::string MaybeAdd(T value) { + return value != 0 ? " + " + NumToString(value) : ""; +} + +template +static std::string MaybeScale(T value) { + return value != 1 ? " * " + NumToString(value) : ""; +} + +static void GenStructArgs(const StructDef &struct_def, + std::string *annotations, + std::string *arguments, + const std::string &nameprefix) { + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); ++it) { + auto &field = **it; + if (IsStruct(field.value.type)) { + // Generate arguments for a struct inside a struct. To ensure names + // don't clash, and to make it obvious these arguments are constructing + // a nested struct, prefix the name with the field name. + GenStructArgs(*field.value.type.struct_def, annotations, arguments, + nameprefix + field.name + "_"); + } else { + *annotations += "@param {" + GenTypeName(field.value.type, InOut::IN); + *annotations += "} " + nameprefix + field.name + "\n"; + *arguments += ", " + nameprefix + field.name; + } + } +} + +static void GenStructBody(const StructDef &struct_def, + std::string *body, + const std::string &nameprefix) { + *body += " builder.prep("; + *body += NumToString(struct_def.minalign) + ", "; + *body += NumToString(struct_def.bytesize) + ");\n"; + + for (auto it = struct_def.fields.vec.rbegin(); + it != struct_def.fields.vec.rend(); ++it) { + auto &field = **it; + if (field.padding) { + *body += " builder.pad(" + NumToString(field.padding) + ");\n"; + } + if (IsStruct(field.value.type)) { + // Generate arguments for a struct inside a struct. To ensure names + // don't clash, and to make it obvious these arguments are constructing + // a nested struct, prefix the name with the field name. + GenStructBody(*field.value.type.struct_def, body, + nameprefix + field.name + "_"); + } else { + *body += " builder.write" + GenWriteMethod(field.value.type) + "("; + if (field.value.type.base_type == BASE_TYPE_BOOL) { + *body += "+"; + } + *body += nameprefix + field.name + ");\n"; + } + } +} + +// Generate an accessor struct with constructor for a flatbuffers struct. +static void GenStruct(const Parser &parser, StructDef &struct_def, + std::string *code_ptr, std::string *exports_ptr) { + if (struct_def.generated) return; + std::string &code = *code_ptr; + std::string &exports = *exports_ptr; + + // Emit constructor + bool isStatement = struct_def.defined_namespace->components.empty(); + std::string object_name = WrapInNameSpace(struct_def); + GenDocComment(struct_def.doc_comment, code_ptr, "@constructor"); + if (isStatement) { + exports += "this." + struct_def.name + " = " + struct_def.name + ";\n"; + code += "function " + object_name; + } else { + code += object_name + " = function"; + } + code += "() {\n"; + code += " /**\n"; + code += " * @type {flatbuffers.ByteBuffer}\n"; + code += " */\n"; + code += " this.bb = null;\n"; + code += "\n"; + code += " /**\n"; + code += " * @type {number}\n"; + code += " */\n"; + code += " this.bb_pos = 0;\n"; + code += isStatement ? "}\n\n" : "};\n\n"; + + // Generate the __init method that sets the field in a pre-existing + // accessor object. This is to allow object reuse. + code += "/**\n"; + code += " * @param {number} i\n"; + code += " * @param {flatbuffers.ByteBuffer} bb\n"; + code += " * @returns {" + object_name + "}\n"; + code += " */\n"; + code += object_name + ".prototype.__init = function(i, bb) {\n"; + code += " this.bb_pos = i;\n"; + code += " this.bb = bb;\n"; + code += " return this;\n"; + code += "};\n\n"; + + // Generate a special accessor for the table that when used as the root of a + // FlatBuffer + if (!struct_def.fixed) { + GenDocComment(code_ptr, + "@param {flatbuffers.ByteBuffer} bb\n" + "@param {" + object_name + "=} obj\n" + "@returns {" + object_name + "}"); + code += object_name + ".getRootAs" + struct_def.name; + code += " = function(bb, obj) {\n"; + code += " return (obj || new " + object_name; + code += ").__init(bb.readInt32(bb.position()) + bb.position(), bb);\n"; + code += "};\n\n"; + + // Generate the identifier check method + if (parser.root_struct_def_ == &struct_def && + !parser.file_identifier_.empty()) { + GenDocComment(code_ptr, + "@param {flatbuffers.ByteBuffer} bb\n" + "@returns {boolean}"); + code += object_name + ".bufferHasIdentifier = function(bb) {\n"; + code += " return bb.__has_identifier('" + parser.file_identifier_; + code += "');\n};\n\n"; + } + } + + // Emit field accessors + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); ++it) { + auto &field = **it; + if (field.deprecated) continue; + auto offset_prefix = " var offset = this.bb.__offset(this.bb_pos, " + + NumToString(field.value.offset) + ");\n return offset ? "; + + // Emit a scalar field + if (IsScalar(field.value.type.base_type) || + field.value.type.base_type == BASE_TYPE_STRING) { + GenDocComment(field.doc_comment, code_ptr, + "@returns {" + GenTypeName(field.value.type, InOut::OUT) + "}"); + code += object_name + ".prototype." + MakeCamel(field.name, false); + code += " = function() {\n"; + if (struct_def.fixed) { + code += " return " + GenGetter(field.value.type, "(this.bb_pos" + + MaybeAdd(field.value.offset) + ")") + ";\n"; + } else { + code += offset_prefix + GenGetter(field.value.type, + "(this.bb_pos + offset)") + " : " + GenDefaultValue(field.value); + code += ";\n"; + } + } + + // Emit an object field + else { + switch (field.value.type.base_type) { + case BASE_TYPE_STRUCT: { + auto type = WrapInNameSpace(*field.value.type.struct_def); + GenDocComment(field.doc_comment, code_ptr, + "@param {" + type + "=} obj\n@returns {" + type + "}"); + code += object_name + ".prototype." + MakeCamel(field.name, false); + code += " = function(obj) {\n"; + if (struct_def.fixed) { + code += " return (obj || new " + type; + code += ").__init(this.bb_pos"; + code += MaybeAdd(field.value.offset) + ", this.bb);\n"; + } else { + code += offset_prefix + "(obj || new " + type + ").__init("; + code += field.value.type.struct_def->fixed + ? "this.bb_pos + offset" + : "this.bb.__indirect(this.bb_pos + offset)"; + code += ", this.bb) : null;\n"; + } + break; + } + + case BASE_TYPE_VECTOR: { + auto vectortype = field.value.type.VectorType(); + auto vectortypename = GenTypeName(vectortype, InOut::OUT); + auto inline_size = InlineSize(vectortype); + auto index = "this.bb.__vector(this.bb_pos + offset) + index" + + MaybeScale(inline_size); + GenDocComment(field.doc_comment, code_ptr, + "@param {number} index\n" + + std::string(vectortype.base_type == BASE_TYPE_STRUCT ? + "@param {" + vectortypename + "=} obj\n" : + "") + + "@returns {" + vectortypename + "}"); + code += object_name + ".prototype." + MakeCamel(field.name, false); + code += " = function(index"; + if (vectortype.base_type == BASE_TYPE_STRUCT) { + code += ", obj"; + } + code += ") {\n"; + if (vectortype.base_type == BASE_TYPE_STRUCT) { + code += offset_prefix + "(obj || new " + vectortypename; + code += ").__init("; + code += vectortype.struct_def->fixed + ? index + : "this.bb.__indirect(" + index + ")"; + code += ", this.bb)"; + } else { + code += offset_prefix + GenGetter(vectortype, "(" + index + ")"); + } + code += " : "; + if (field.value.type.element == BASE_TYPE_BOOL) { + code += "false"; + } else if (field.value.type.element == BASE_TYPE_LONG || + field.value.type.element == BASE_TYPE_ULONG) { + code += "flatbuffers.Long.ZERO"; + } else if (IsScalar(field.value.type.element)) { + code += "0"; + } else { + code += "null"; + } + code += ";\n"; + break; + } + + case BASE_TYPE_UNION: + GenDocComment(field.doc_comment, code_ptr, + "@param {flatbuffers.Table} obj\n" + "@returns {?flatbuffers.Table}"); + code += object_name + ".prototype." + MakeCamel(field.name, false); + code += " = function(obj) {\n"; + code += offset_prefix + GenGetter(field.value.type, + "(obj, this.bb_pos + offset)") + " : null;\n"; + break; + + default: + assert(0); + } + } + code += "};\n\n"; + + // Emit a length helper + if (field.value.type.base_type == BASE_TYPE_VECTOR) { + GenDocComment(code_ptr, "@returns {number}"); + code += object_name + ".prototype." + MakeCamel(field.name, false); + code += "Length = function() {\n" + offset_prefix; + code += "this.bb.__vector_len(this.bb_pos + offset) : 0;\n};\n\n"; + } + } + + // Emit a factory constructor + if (struct_def.fixed) { + std::string annotations = "@param {flatbuffers.Builder} builder\n"; + std::string arguments; + GenStructArgs(struct_def, &annotations, &arguments, ""); + GenDocComment(code_ptr, annotations + + "@returns {flatbuffers.Offset}"); + code += object_name + ".create" + struct_def.name + " = function(builder"; + code += arguments + ") {\n"; + GenStructBody(struct_def, &code, ""); + code += " return builder.offset();\n};\n\n"; + } else { + // Generate a method to start building a new object + GenDocComment(code_ptr, + "@param {flatbuffers.Builder} builder"); + code += object_name + ".start" + struct_def.name; + code += " = function(builder) {\n"; + code += " builder.startObject(" + NumToString( + struct_def.fields.vec.size()) + ");\n"; + code += "};\n\n"; + + // Generate a set of static methods that allow table construction + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); ++it) { + auto &field = **it; + if (field.deprecated) continue; + auto argname = MakeCamel(field.name, false); + if (!IsScalar(field.value.type.base_type)) { + argname += "Offset"; + } + + // Generate the field insertion method + GenDocComment(code_ptr, + "@param {flatbuffers.Builder} builder\n" + "@param {" + GenTypeName(field.value.type, InOut::IN) + "} " + + argname); + code += object_name + ".add" + MakeCamel(field.name); + code += " = function(builder, " + argname + ") {\n"; + code += " builder.addField" + GenWriteMethod(field.value.type) + "("; + code += NumToString(it - struct_def.fields.vec.begin()) + ", "; + if (field.value.type.base_type == BASE_TYPE_BOOL) { + code += "+"; + } + code += argname + ", "; + if (!IsScalar(field.value.type.base_type)) { + code += "0"; + } else { + if (field.value.type.base_type == BASE_TYPE_BOOL) { + code += "+"; + } + code += GenDefaultValue(field.value); + } + code += ");\n};\n\n"; + + if (field.value.type.base_type == BASE_TYPE_VECTOR) { + auto vector_type = field.value.type.VectorType(); + auto alignment = InlineAlignment(vector_type); + auto elem_size = InlineSize(vector_type); + + // Generate a method to create a vector from a JavaScript array + if (!IsStruct(vector_type)) { + GenDocComment(code_ptr, + "@param {flatbuffers.Builder} builder\n" + "@param {Array.<" + GenTypeName(vector_type, InOut::IN) + + ">} data\n" + "@returns {flatbuffers.Offset}"); + code += object_name + ".create" + MakeCamel(field.name); + code += "Vector = function(builder, data) {\n"; + code += " builder.startVector(" + NumToString(elem_size); + code += ", data.length, " + NumToString(alignment) + ");\n"; + code += " for (var i = data.length - 1; i >= 0; i--) {\n"; + code += " builder.add" + GenWriteMethod(vector_type); + code += "(data[i]);\n"; + code += " }\n"; + code += " return builder.endVector();\n"; + code += "};\n\n"; + } + + // Generate a method to start a vector, data to be added manually after + GenDocComment(code_ptr, + "@param {flatbuffers.Builder} builder\n" + "@param {number} numElems"); + code += object_name + ".start" + MakeCamel(field.name); + code += "Vector = function(builder, numElems) {\n"; + code += " builder.startVector(" + NumToString(elem_size); + code += ", numElems, " + NumToString(alignment) + ");\n"; + code += "};\n\n"; + } + } + + // Generate a method to stop building a new object + GenDocComment(code_ptr, + "@param {flatbuffers.Builder} builder\n" + "@returns {flatbuffers.Offset}"); + code += object_name + ".end" + struct_def.name; + code += " = function(builder) {\n"; + code += " var offset = builder.endObject();\n"; + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); ++it) { + auto &field = **it; + if (!field.deprecated && field.required) { + code += " builder.requiredField(offset, "; + code += NumToString(field.value.offset); + code += "); // " + field.name + "\n"; + } + } + code += " return offset;\n"; + code += "};\n\n"; + + // Generate the method to complete buffer construction + if (parser.root_struct_def_ == &struct_def) { + GenDocComment(code_ptr, + "@param {flatbuffers.Builder} builder\n" + "@param {flatbuffers.Offset} offset"); + code += object_name + ".finish" + struct_def.name + "Buffer"; + code += " = function(builder, offset) {\n"; + code += " return builder.finish(offset"; + if (!parser.file_identifier_.empty()) { + code += ", '" + parser.file_identifier_ + "'"; + } + code += ");\n"; + code += "};\n\n"; + } + } +} + +} // namespace js + +// Iterate through all definitions we haven't generate code for (enums, structs, +// and tables) and output them to a single file. +std::string GenerateJS(const Parser &parser, + const GeneratorOptions &opts) { + using namespace js; + + // Generate code for all the enum declarations. + std::string enum_code, exports_code; + for (auto it = parser.enums_.vec.begin(); + it != parser.enums_.vec.end(); ++it) { + GenEnum(**it, &enum_code, &exports_code); + } + + // Generate code for all structs, then all tables. + std::string decl_code; + for (auto it = parser.structs_.vec.begin(); + it != parser.structs_.vec.end(); ++it) { + GenStruct(parser, **it, &decl_code, &exports_code); + } + + // Only output file-level code if there were any declarations. + if (enum_code.length() || decl_code.length()) { + std::string code; + code = "// automatically generated by the FlatBuffers compiler," + " do not modify\n\n"; + + // Generate code for all the namespace declarations. + GenNamespaces(parser, &code, &exports_code); + + // Output the main declaration code from above. + code += enum_code; + code += decl_code; + + if (!exports_code.empty() && !opts.skip_js_exports) { + code += "// Exports for Node.js and RequireJS\n"; + code += exports_code; + } + + return code; + } + + return std::string(); +} + +static std::string GeneratedFileName(const std::string &path, + const std::string &file_name) { + return path + file_name + "_generated.js"; +} + +bool GenerateJS(const Parser &parser, + const std::string &path, + const std::string &file_name, + const GeneratorOptions &opts) { + auto code = GenerateJS(parser, opts); + return !code.length() || + SaveFile(GeneratedFileName(path, file_name).c_str(), code, false); +} + +std::string JSMakeRule(const Parser &parser, + const std::string &path, + const std::string &file_name, + const GeneratorOptions & /*opts*/) { + std::string filebase = flatbuffers::StripPath( + flatbuffers::StripExtension(file_name)); + std::string make_rule = GeneratedFileName(path, filebase) + ": "; + auto included_files = parser.GetIncludedFilesRecursive(file_name); + for (auto it = included_files.begin(); + it != included_files.end(); ++it) { + make_rule += " " + *it; + } + return make_rule; +} + +} // namespace flatbuffers diff --git a/tests/JavaScriptTest.js b/tests/JavaScriptTest.js new file mode 100644 index 000000000..af62a4ee6 --- /dev/null +++ b/tests/JavaScriptTest.js @@ -0,0 +1,116 @@ +var assert = require('assert'); +var fs = require('fs'); + +var flatbuffers = require('../js/flatbuffers').flatbuffers; +var MyGame = require('./monster_test_generated').MyGame; + +function main() { + + // First, let's test reading a FlatBuffer generated by C++ code: + // This file was generated from monsterdata_test.json + var data = new Uint8Array(fs.readFileSync('monsterdata_test.mon')); + + // Now test it: + + var bb = new flatbuffers.ByteBuffer(data); + testBuffer(bb); + + // Second, let's create a FlatBuffer from scratch in JavaScript, and test it also. + // We use an initial size of 1 to exercise the reallocation algorithm, + // normally a size larger than the typical FlatBuffer you generate would be + // better for performance. + var fbb = new flatbuffers.Builder(1); + + // We set up the same values as monsterdata.json: + + var str = fbb.createString('MyMonster'); + + var inv = MyGame.Example.Monster.createInventoryVector(fbb, [0, 1, 2, 3, 4]); + + var fred = fbb.createString('Fred'); + MyGame.Example.Monster.startMonster(fbb); + MyGame.Example.Monster.addName(fbb, fred); + var mon2 = MyGame.Example.Monster.endMonster(fbb); + + MyGame.Example.Monster.startTest4Vector(fbb, 2); + MyGame.Example.Test.createTest(fbb, 10, 20); + MyGame.Example.Test.createTest(fbb, 30, 40); + var test4 = fbb.endVector(); + + var testArrayOfString = MyGame.Example.Monster.createTestarrayofstringVector(fbb, [ + fbb.createString('test1'), + fbb.createString('test2') + ]); + + MyGame.Example.Monster.startMonster(fbb); + MyGame.Example.Monster.addPos(fbb, MyGame.Example.Vec3.createVec3(fbb, 1, 2, 3, 3, MyGame.Example.Color.Green, 5, 6)); + MyGame.Example.Monster.addHp(fbb, 80); + MyGame.Example.Monster.addName(fbb, str); + MyGame.Example.Monster.addInventory(fbb, inv); + MyGame.Example.Monster.addTestType(fbb, MyGame.Example.Any.Monster); + MyGame.Example.Monster.addTest(fbb, mon2); + MyGame.Example.Monster.addTest4(fbb, test4); + MyGame.Example.Monster.addTestarrayofstring(fbb, testArrayOfString); + MyGame.Example.Monster.addTestbool(fbb, false); + var mon = MyGame.Example.Monster.endMonster(fbb); + + MyGame.Example.Monster.finishMonsterBuffer(fbb, mon); + + // Write the result to a file for debugging purposes: + // Note that the binaries are not necessarily identical, since the JSON + // parser may serialize in a slightly different order than the above + // JavaScript code. They are functionally equivalent though. + + fs.writeFileSync('monsterdata_javascript_wire.mon', new Buffer(fbb.asUint8Array())); + + // Test it: + testBuffer(fbb.dataBuffer()); + + console.log('FlatBuffers test: completed successfully'); +} + +function testBuffer(bb) { + assert.ok(MyGame.Example.Monster.bufferHasIdentifier(bb)); + + var monster = MyGame.Example.Monster.getRootAsMonster(bb); + + assert.strictEqual(monster.hp(), 80); + assert.strictEqual(monster.mana(), 150); // default + + assert.strictEqual(monster.name(), 'MyMonster'); + + var pos = monster.pos(); + assert.strictEqual(pos.x(), 1); + assert.strictEqual(pos.y(), 2); + assert.strictEqual(pos.z(), 3); + assert.strictEqual(pos.test1(), 3); + assert.strictEqual(pos.test2(), MyGame.Example.Color.Green); + var t = pos.test3(); + assert.strictEqual(t.a(), 5); + assert.strictEqual(t.b(), 6); + + assert.strictEqual(monster.testType(), MyGame.Example.Any.Monster); + var monster2 = new MyGame.Example.Monster(); + assert.strictEqual(monster.test(monster2) != null, true); + assert.strictEqual(monster2.name(), 'Fred'); + + assert.strictEqual(monster.inventoryLength(), 5); + var invsum = 0; + for (var i = 0; i < monster.inventoryLength(); i++) { + invsum += monster.inventory(i); + } + assert.strictEqual(invsum, 10); + + var test_0 = monster.test4(0); + var test_1 = monster.test4(1); + assert.strictEqual(monster.test4Length(), 2); + assert.strictEqual(test_0.a() + test_0.b() + test_1.a() + test_1.b(), 100); + + assert.strictEqual(monster.testarrayofstringLength(), 2); + assert.strictEqual(monster.testarrayofstring(0), 'test1'); + assert.strictEqual(monster.testarrayofstring(1), 'test2'); + + assert.strictEqual(monster.testbool(), false); +} + +main(); diff --git a/tests/JavaScriptTest.sh b/tests/JavaScriptTest.sh new file mode 100755 index 000000000..40dba2be1 --- /dev/null +++ b/tests/JavaScriptTest.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +pushd "$(dirname $0)" >/dev/null +test_dir="$(pwd)" +node ${test_dir}/JavaScriptTest diff --git a/tests/generate_code.sh b/tests/generate_code.sh index 5b768197f..3e96712f7 100644 --- a/tests/generate_code.sh +++ b/tests/generate_code.sh @@ -1,2 +1,2 @@ -../flatc -c -j -n -g -b -p --gen-mutable --no-includes monster_test.fbs monsterdata_test.json +../flatc -c -j -n -g -b -p -s --gen-mutable --no-includes monster_test.fbs monsterdata_test.json ../flatc -b --schema monster_test.fbs diff --git a/tests/monster_test_generated.js b/tests/monster_test_generated.js new file mode 100644 index 000000000..25617cada --- /dev/null +++ b/tests/monster_test_generated.js @@ -0,0 +1,936 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/** + * @const +*/ +var MyGame = MyGame || {}; + +/** + * @const +*/ +MyGame.Example = MyGame.Example || {}; + +/** + * @const +*/ +MyGame.OtherNameSpace = MyGame.OtherNameSpace || {}; + +/** + * @enum + */ +MyGame.Example.Color = { + Red: 1, + Green: 2, + Blue: 8 +}; + +/** + * @enum + */ +MyGame.Example.Any = { + NONE: 0, + Monster: 1, + TestSimpleTableWithEnum: 2 +}; + +/** + * @constructor + */ +MyGame.Example.Test = function() { + /** + * @type {flatbuffers.ByteBuffer} + */ + this.bb = null; + + /** + * @type {number} + */ + this.bb_pos = 0; +}; + +/** + * @param {number} i + * @param {flatbuffers.ByteBuffer} bb + * @returns {MyGame.Example.Test} + */ +MyGame.Example.Test.prototype.__init = function(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; +}; + +/** + * @returns {number} + */ +MyGame.Example.Test.prototype.a = function() { + return this.bb.readInt16(this.bb_pos); +}; + +/** + * @returns {number} + */ +MyGame.Example.Test.prototype.b = function() { + return this.bb.readInt8(this.bb_pos + 2); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} a + * @param {number} b + * @returns {flatbuffers.Offset} + */ +MyGame.Example.Test.createTest = function(builder, a, b) { + builder.prep(2, 4); + builder.pad(1); + builder.writeInt8(b); + builder.writeInt16(a); + return builder.offset(); +}; + +/** + * @constructor + */ +MyGame.Example.TestSimpleTableWithEnum = function() { + /** + * @type {flatbuffers.ByteBuffer} + */ + this.bb = null; + + /** + * @type {number} + */ + this.bb_pos = 0; +}; + +/** + * @param {number} i + * @param {flatbuffers.ByteBuffer} bb + * @returns {MyGame.Example.TestSimpleTableWithEnum} + */ +MyGame.Example.TestSimpleTableWithEnum.prototype.__init = function(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; +}; + +/** + * @param {flatbuffers.ByteBuffer} bb + * @param {MyGame.Example.TestSimpleTableWithEnum=} obj + * @returns {MyGame.Example.TestSimpleTableWithEnum} + */ +MyGame.Example.TestSimpleTableWithEnum.getRootAsTestSimpleTableWithEnum = function(bb, obj) { + return (obj || new MyGame.Example.TestSimpleTableWithEnum).__init(bb.readInt32(bb.position()) + bb.position(), bb); +}; + +/** + * @returns {MyGame.Example.Color} + */ +MyGame.Example.TestSimpleTableWithEnum.prototype.color = function() { + var offset = this.bb.__offset(this.bb_pos, 4); + return offset ? /** @type {MyGame.Example.Color} */ (this.bb.readInt8(this.bb_pos + offset)) : MyGame.Example.Color.Green; +}; + +/** + * @param {flatbuffers.Builder} builder + */ +MyGame.Example.TestSimpleTableWithEnum.startTestSimpleTableWithEnum = function(builder) { + builder.startObject(1); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {MyGame.Example.Color} color + */ +MyGame.Example.TestSimpleTableWithEnum.addColor = function(builder, color) { + builder.addFieldInt8(0, color, MyGame.Example.Color.Green); +}; + +/** + * @param {flatbuffers.Builder} builder + * @returns {flatbuffers.Offset} + */ +MyGame.Example.TestSimpleTableWithEnum.endTestSimpleTableWithEnum = function(builder) { + var offset = builder.endObject(); + return offset; +}; + +/** + * @constructor + */ +MyGame.Example.Vec3 = function() { + /** + * @type {flatbuffers.ByteBuffer} + */ + this.bb = null; + + /** + * @type {number} + */ + this.bb_pos = 0; +}; + +/** + * @param {number} i + * @param {flatbuffers.ByteBuffer} bb + * @returns {MyGame.Example.Vec3} + */ +MyGame.Example.Vec3.prototype.__init = function(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; +}; + +/** + * @returns {number} + */ +MyGame.Example.Vec3.prototype.x = function() { + return this.bb.readFloat32(this.bb_pos); +}; + +/** + * @returns {number} + */ +MyGame.Example.Vec3.prototype.y = function() { + return this.bb.readFloat32(this.bb_pos + 4); +}; + +/** + * @returns {number} + */ +MyGame.Example.Vec3.prototype.z = function() { + return this.bb.readFloat32(this.bb_pos + 8); +}; + +/** + * @returns {number} + */ +MyGame.Example.Vec3.prototype.test1 = function() { + return this.bb.readFloat64(this.bb_pos + 16); +}; + +/** + * @returns {MyGame.Example.Color} + */ +MyGame.Example.Vec3.prototype.test2 = function() { + return /** @type {MyGame.Example.Color} */ (this.bb.readInt8(this.bb_pos + 24)); +}; + +/** + * @param {MyGame.Example.Test=} obj + * @returns {MyGame.Example.Test} + */ +MyGame.Example.Vec3.prototype.test3 = function(obj) { + return (obj || new MyGame.Example.Test).__init(this.bb_pos + 26, this.bb); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} x + * @param {number} y + * @param {number} z + * @param {number} test1 + * @param {MyGame.Example.Color} test2 + * @param {number} test3_a + * @param {number} test3_b + * @returns {flatbuffers.Offset} + */ +MyGame.Example.Vec3.createVec3 = function(builder, x, y, z, test1, test2, test3_a, test3_b) { + builder.prep(16, 32); + builder.pad(2); + builder.prep(2, 4); + builder.pad(1); + builder.writeInt8(test3_b); + builder.writeInt16(test3_a); + builder.pad(1); + builder.writeInt8(test2); + builder.writeFloat64(test1); + builder.pad(4); + builder.writeFloat32(z); + builder.writeFloat32(y); + builder.writeFloat32(x); + return builder.offset(); +}; + +/** + * @constructor + */ +MyGame.Example.Stat = function() { + /** + * @type {flatbuffers.ByteBuffer} + */ + this.bb = null; + + /** + * @type {number} + */ + this.bb_pos = 0; +}; + +/** + * @param {number} i + * @param {flatbuffers.ByteBuffer} bb + * @returns {MyGame.Example.Stat} + */ +MyGame.Example.Stat.prototype.__init = function(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; +}; + +/** + * @param {flatbuffers.ByteBuffer} bb + * @param {MyGame.Example.Stat=} obj + * @returns {MyGame.Example.Stat} + */ +MyGame.Example.Stat.getRootAsStat = function(bb, obj) { + return (obj || new MyGame.Example.Stat).__init(bb.readInt32(bb.position()) + bb.position(), bb); +}; + +/** + * @returns {?string} + */ +MyGame.Example.Stat.prototype.id = function() { + var offset = this.bb.__offset(this.bb_pos, 4); + return offset ? this.bb.__string(this.bb_pos + offset) : null; +}; + +/** + * @returns {flatbuffers.Long} + */ +MyGame.Example.Stat.prototype.val = function() { + var offset = this.bb.__offset(this.bb_pos, 6); + return offset ? this.bb.readInt64(this.bb_pos + offset) : flatbuffers.Long.ZERO; +}; + +/** + * @returns {number} + */ +MyGame.Example.Stat.prototype.count = function() { + var offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.readUint16(this.bb_pos + offset) : 0; +}; + +/** + * @param {flatbuffers.Builder} builder + */ +MyGame.Example.Stat.startStat = function(builder) { + builder.startObject(3); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} idOffset + */ +MyGame.Example.Stat.addId = function(builder, idOffset) { + builder.addFieldOffset(0, idOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Long} val + */ +MyGame.Example.Stat.addVal = function(builder, val) { + builder.addFieldInt64(1, val, flatbuffers.Long.ZERO); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} count + */ +MyGame.Example.Stat.addCount = function(builder, count) { + builder.addFieldInt16(2, count, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @returns {flatbuffers.Offset} + */ +MyGame.Example.Stat.endStat = function(builder) { + var offset = builder.endObject(); + return offset; +}; + +/** + * @constructor + */ +MyGame.Example.Monster = function() { + /** + * @type {flatbuffers.ByteBuffer} + */ + this.bb = null; + + /** + * @type {number} + */ + this.bb_pos = 0; +}; + +/** + * @param {number} i + * @param {flatbuffers.ByteBuffer} bb + * @returns {MyGame.Example.Monster} + */ +MyGame.Example.Monster.prototype.__init = function(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; +}; + +/** + * @param {flatbuffers.ByteBuffer} bb + * @param {MyGame.Example.Monster=} obj + * @returns {MyGame.Example.Monster} + */ +MyGame.Example.Monster.getRootAsMonster = function(bb, obj) { + return (obj || new MyGame.Example.Monster).__init(bb.readInt32(bb.position()) + bb.position(), bb); +}; + +/** + * @param {flatbuffers.ByteBuffer} bb + * @returns {boolean} + */ +MyGame.Example.Monster.bufferHasIdentifier = function(bb) { + return bb.__has_identifier('MONS'); +}; + +/** + * @param {MyGame.Example.Vec3=} obj + * @returns {MyGame.Example.Vec3} + */ +MyGame.Example.Monster.prototype.pos = function(obj) { + var offset = this.bb.__offset(this.bb_pos, 4); + return offset ? (obj || new MyGame.Example.Vec3).__init(this.bb_pos + offset, this.bb) : null; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.mana = function() { + var offset = this.bb.__offset(this.bb_pos, 6); + return offset ? this.bb.readInt16(this.bb_pos + offset) : 150; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.hp = function() { + var offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.readInt16(this.bb_pos + offset) : 100; +}; + +/** + * @returns {?string} + */ +MyGame.Example.Monster.prototype.name = function() { + var offset = this.bb.__offset(this.bb_pos, 10); + return offset ? this.bb.__string(this.bb_pos + offset) : null; +}; + +/** + * @param {number} index + * @returns {number} + */ +MyGame.Example.Monster.prototype.inventory = function(index) { + var offset = this.bb.__offset(this.bb_pos, 14); + return offset ? this.bb.readUint8(this.bb.__vector(this.bb_pos + offset) + index) : 0; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.inventoryLength = function() { + var offset = this.bb.__offset(this.bb_pos, 14); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; +}; + +/** + * @returns {MyGame.Example.Color} + */ +MyGame.Example.Monster.prototype.color = function() { + var offset = this.bb.__offset(this.bb_pos, 16); + return offset ? /** @type {MyGame.Example.Color} */ (this.bb.readInt8(this.bb_pos + offset)) : MyGame.Example.Color.Blue; +}; + +/** + * @returns {MyGame.Example.Any} + */ +MyGame.Example.Monster.prototype.testType = function() { + var offset = this.bb.__offset(this.bb_pos, 18); + return offset ? /** @type {MyGame.Example.Any} */ (this.bb.readUint8(this.bb_pos + offset)) : MyGame.Example.Any.NONE; +}; + +/** + * @param {flatbuffers.Table} obj + * @returns {?flatbuffers.Table} + */ +MyGame.Example.Monster.prototype.test = function(obj) { + var offset = this.bb.__offset(this.bb_pos, 20); + return offset ? this.bb.__union(obj, this.bb_pos + offset) : null; +}; + +/** + * @param {number} index + * @param {MyGame.Example.Test=} obj + * @returns {MyGame.Example.Test} + */ +MyGame.Example.Monster.prototype.test4 = function(index, obj) { + var offset = this.bb.__offset(this.bb_pos, 22); + return offset ? (obj || new MyGame.Example.Test).__init(this.bb.__vector(this.bb_pos + offset) + index * 4, this.bb) : null; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.test4Length = function() { + var offset = this.bb.__offset(this.bb_pos, 22); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; +}; + +/** + * @param {number} index + * @returns {?string} + */ +MyGame.Example.Monster.prototype.testarrayofstring = function(index) { + var offset = this.bb.__offset(this.bb_pos, 24); + return offset ? this.bb.__string(this.bb.__vector(this.bb_pos + offset) + index * 4) : null; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.testarrayofstringLength = function() { + var offset = this.bb.__offset(this.bb_pos, 24); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; +}; + +/** + * an example documentation comment: this will end up in the generated code + * multiline too + * + * @param {number} index + * @param {MyGame.Example.Monster=} obj + * @returns {MyGame.Example.Monster} + */ +MyGame.Example.Monster.prototype.testarrayoftables = function(index, obj) { + var offset = this.bb.__offset(this.bb_pos, 26); + return offset ? (obj || new MyGame.Example.Monster).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos + offset) + index * 4), this.bb) : null; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.testarrayoftablesLength = function() { + var offset = this.bb.__offset(this.bb_pos, 26); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; +}; + +/** + * @param {MyGame.Example.Monster=} obj + * @returns {MyGame.Example.Monster} + */ +MyGame.Example.Monster.prototype.enemy = function(obj) { + var offset = this.bb.__offset(this.bb_pos, 28); + return offset ? (obj || new MyGame.Example.Monster).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; +}; + +/** + * @param {number} index + * @returns {number} + */ +MyGame.Example.Monster.prototype.testnestedflatbuffer = function(index) { + var offset = this.bb.__offset(this.bb_pos, 30); + return offset ? this.bb.readUint8(this.bb.__vector(this.bb_pos + offset) + index) : 0; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.testnestedflatbufferLength = function() { + var offset = this.bb.__offset(this.bb_pos, 30); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; +}; + +/** + * @param {MyGame.Example.Stat=} obj + * @returns {MyGame.Example.Stat} + */ +MyGame.Example.Monster.prototype.testempty = function(obj) { + var offset = this.bb.__offset(this.bb_pos, 32); + return offset ? (obj || new MyGame.Example.Stat).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; +}; + +/** + * @returns {boolean} + */ +MyGame.Example.Monster.prototype.testbool = function() { + var offset = this.bb.__offset(this.bb_pos, 34); + return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.testhashs32Fnv1 = function() { + var offset = this.bb.__offset(this.bb_pos, 36); + return offset ? this.bb.readInt32(this.bb_pos + offset) : 0; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.testhashu32Fnv1 = function() { + var offset = this.bb.__offset(this.bb_pos, 38); + return offset ? this.bb.readUint32(this.bb_pos + offset) : 0; +}; + +/** + * @returns {flatbuffers.Long} + */ +MyGame.Example.Monster.prototype.testhashs64Fnv1 = function() { + var offset = this.bb.__offset(this.bb_pos, 40); + return offset ? this.bb.readInt64(this.bb_pos + offset) : flatbuffers.Long.ZERO; +}; + +/** + * @returns {flatbuffers.Long} + */ +MyGame.Example.Monster.prototype.testhashu64Fnv1 = function() { + var offset = this.bb.__offset(this.bb_pos, 42); + return offset ? this.bb.readUint64(this.bb_pos + offset) : flatbuffers.Long.ZERO; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.testhashs32Fnv1a = function() { + var offset = this.bb.__offset(this.bb_pos, 44); + return offset ? this.bb.readInt32(this.bb_pos + offset) : 0; +}; + +/** + * @returns {number} + */ +MyGame.Example.Monster.prototype.testhashu32Fnv1a = function() { + var offset = this.bb.__offset(this.bb_pos, 46); + return offset ? this.bb.readUint32(this.bb_pos + offset) : 0; +}; + +/** + * @returns {flatbuffers.Long} + */ +MyGame.Example.Monster.prototype.testhashs64Fnv1a = function() { + var offset = this.bb.__offset(this.bb_pos, 48); + return offset ? this.bb.readInt64(this.bb_pos + offset) : flatbuffers.Long.ZERO; +}; + +/** + * @returns {flatbuffers.Long} + */ +MyGame.Example.Monster.prototype.testhashu64Fnv1a = function() { + var offset = this.bb.__offset(this.bb_pos, 50); + return offset ? this.bb.readUint64(this.bb_pos + offset) : flatbuffers.Long.ZERO; +}; + +/** + * @param {flatbuffers.Builder} builder + */ +MyGame.Example.Monster.startMonster = function(builder) { + builder.startObject(24); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} posOffset + */ +MyGame.Example.Monster.addPos = function(builder, posOffset) { + builder.addFieldStruct(0, posOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} mana + */ +MyGame.Example.Monster.addMana = function(builder, mana) { + builder.addFieldInt16(1, mana, 150); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} hp + */ +MyGame.Example.Monster.addHp = function(builder, hp) { + builder.addFieldInt16(2, hp, 100); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} nameOffset + */ +MyGame.Example.Monster.addName = function(builder, nameOffset) { + builder.addFieldOffset(3, nameOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} inventoryOffset + */ +MyGame.Example.Monster.addInventory = function(builder, inventoryOffset) { + builder.addFieldOffset(5, inventoryOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {Array.} data + * @returns {flatbuffers.Offset} + */ +MyGame.Example.Monster.createInventoryVector = function(builder, data) { + builder.startVector(1, data.length, 1); + for (var i = data.length - 1; i >= 0; i--) { + builder.addInt8(data[i]); + } + return builder.endVector(); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} numElems + */ +MyGame.Example.Monster.startInventoryVector = function(builder, numElems) { + builder.startVector(1, numElems, 1); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {MyGame.Example.Color} color + */ +MyGame.Example.Monster.addColor = function(builder, color) { + builder.addFieldInt8(6, color, MyGame.Example.Color.Blue); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {MyGame.Example.Any} testType + */ +MyGame.Example.Monster.addTestType = function(builder, testType) { + builder.addFieldInt8(7, testType, MyGame.Example.Any.NONE); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} testOffset + */ +MyGame.Example.Monster.addTest = function(builder, testOffset) { + builder.addFieldOffset(8, testOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} test4Offset + */ +MyGame.Example.Monster.addTest4 = function(builder, test4Offset) { + builder.addFieldOffset(9, test4Offset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} numElems + */ +MyGame.Example.Monster.startTest4Vector = function(builder, numElems) { + builder.startVector(4, numElems, 2); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} testarrayofstringOffset + */ +MyGame.Example.Monster.addTestarrayofstring = function(builder, testarrayofstringOffset) { + builder.addFieldOffset(10, testarrayofstringOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {Array.} data + * @returns {flatbuffers.Offset} + */ +MyGame.Example.Monster.createTestarrayofstringVector = function(builder, data) { + builder.startVector(4, data.length, 4); + for (var i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]); + } + return builder.endVector(); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} numElems + */ +MyGame.Example.Monster.startTestarrayofstringVector = function(builder, numElems) { + builder.startVector(4, numElems, 4); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} testarrayoftablesOffset + */ +MyGame.Example.Monster.addTestarrayoftables = function(builder, testarrayoftablesOffset) { + builder.addFieldOffset(11, testarrayoftablesOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {Array.} data + * @returns {flatbuffers.Offset} + */ +MyGame.Example.Monster.createTestarrayoftablesVector = function(builder, data) { + builder.startVector(4, data.length, 4); + for (var i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]); + } + return builder.endVector(); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} numElems + */ +MyGame.Example.Monster.startTestarrayoftablesVector = function(builder, numElems) { + builder.startVector(4, numElems, 4); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} enemyOffset + */ +MyGame.Example.Monster.addEnemy = function(builder, enemyOffset) { + builder.addFieldOffset(12, enemyOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} testnestedflatbufferOffset + */ +MyGame.Example.Monster.addTestnestedflatbuffer = function(builder, testnestedflatbufferOffset) { + builder.addFieldOffset(13, testnestedflatbufferOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {Array.} data + * @returns {flatbuffers.Offset} + */ +MyGame.Example.Monster.createTestnestedflatbufferVector = function(builder, data) { + builder.startVector(1, data.length, 1); + for (var i = data.length - 1; i >= 0; i--) { + builder.addInt8(data[i]); + } + return builder.endVector(); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} numElems + */ +MyGame.Example.Monster.startTestnestedflatbufferVector = function(builder, numElems) { + builder.startVector(1, numElems, 1); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} testemptyOffset + */ +MyGame.Example.Monster.addTestempty = function(builder, testemptyOffset) { + builder.addFieldOffset(14, testemptyOffset, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {boolean} testbool + */ +MyGame.Example.Monster.addTestbool = function(builder, testbool) { + builder.addFieldInt8(15, +testbool, +false); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} testhashs32Fnv1 + */ +MyGame.Example.Monster.addTesthashs32Fnv1 = function(builder, testhashs32Fnv1) { + builder.addFieldInt32(16, testhashs32Fnv1, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} testhashu32Fnv1 + */ +MyGame.Example.Monster.addTesthashu32Fnv1 = function(builder, testhashu32Fnv1) { + builder.addFieldInt32(17, testhashu32Fnv1, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Long} testhashs64Fnv1 + */ +MyGame.Example.Monster.addTesthashs64Fnv1 = function(builder, testhashs64Fnv1) { + builder.addFieldInt64(18, testhashs64Fnv1, flatbuffers.Long.ZERO); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Long} testhashu64Fnv1 + */ +MyGame.Example.Monster.addTesthashu64Fnv1 = function(builder, testhashu64Fnv1) { + builder.addFieldInt64(19, testhashu64Fnv1, flatbuffers.Long.ZERO); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} testhashs32Fnv1a + */ +MyGame.Example.Monster.addTesthashs32Fnv1a = function(builder, testhashs32Fnv1a) { + builder.addFieldInt32(20, testhashs32Fnv1a, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {number} testhashu32Fnv1a + */ +MyGame.Example.Monster.addTesthashu32Fnv1a = function(builder, testhashu32Fnv1a) { + builder.addFieldInt32(21, testhashu32Fnv1a, 0); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Long} testhashs64Fnv1a + */ +MyGame.Example.Monster.addTesthashs64Fnv1a = function(builder, testhashs64Fnv1a) { + builder.addFieldInt64(22, testhashs64Fnv1a, flatbuffers.Long.ZERO); +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Long} testhashu64Fnv1a + */ +MyGame.Example.Monster.addTesthashu64Fnv1a = function(builder, testhashu64Fnv1a) { + builder.addFieldInt64(23, testhashu64Fnv1a, flatbuffers.Long.ZERO); +}; + +/** + * @param {flatbuffers.Builder} builder + * @returns {flatbuffers.Offset} + */ +MyGame.Example.Monster.endMonster = function(builder) { + var offset = builder.endObject(); + builder.requiredField(offset, 10); // name + return offset; +}; + +/** + * @param {flatbuffers.Builder} builder + * @param {flatbuffers.Offset} offset + */ +MyGame.Example.Monster.finishMonsterBuffer = function(builder, offset) { + return builder.finish(offset, 'MONS'); +}; + +// Exports for Node.js and RequireJS +this.MyGame = MyGame;