mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-01 19:58:15 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea57dfe897 | ||
|
|
9f506f57c0 | ||
|
|
57b614587c | ||
|
|
15dc1a86cd | ||
|
|
766d0df797 | ||
|
|
4507594812 | ||
|
|
8e40902d52 | ||
|
|
bc5fa9d52f | ||
|
|
11b743688c | ||
|
|
ffb3dec573 | ||
|
|
51ba48ae40 | ||
|
|
c553b6b950 | ||
|
|
541b06759f | ||
|
|
30af866e5a | ||
|
|
ebac1e1940 | ||
|
|
f7b0d130b6 | ||
|
|
c2ba7fd251 | ||
|
|
be894f09df | ||
|
|
293a8110c4 | ||
|
|
8c5d7f7dea |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,3 +35,7 @@ flatsampletext
|
||||
snapshot.sh
|
||||
tests/go_gen
|
||||
CMakeLists.txt.user
|
||||
CMakeScripts/**
|
||||
build/Xcode/FlatBuffers.xcodeproj/project.xcworkspace/**
|
||||
build/Xcode/FlatBuffers.xcodeproj/xcuserdata/**
|
||||
|
||||
|
||||
@@ -52,13 +52,13 @@ set(CMAKE_BUILD_TYPE Debug)
|
||||
# source_group(Tests FILES ${FlatBuffers_Tests_SRCS})
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++")
|
||||
set(CMAKE_CXX_FLAGS
|
||||
"${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++ -Wall -pedantic -Werror -Wextra")
|
||||
elseif(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
|
||||
set(CMAKE_CXX_FLAGS
|
||||
"${CMAKE_CXX_FLAGS} -std=c++0x -Wall -pedantic -Werror -Wextra")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Werror -Wextra")
|
||||
|
||||
if(FLATBUFFERS_CODE_COVERAGE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fprofile-arcs -ftest-coverage")
|
||||
set(CMAKE_EXE_LINKER_FLAGS
|
||||
|
||||
@@ -53,7 +53,8 @@ $(document).ready(function(){initNavTree('md__grammar.html','');});
|
||||
<div class="title">Formal Grammar of the schema language </div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
<div class="textblock"><p>schema = namespace_decl | type_decl | enum_decl | root_decl | object</p>
|
||||
<div class="textblock"><p>schema = include* ( namespace_decl | type_decl | enum_decl | root_decl | object )*</p>
|
||||
<p>include = <code>include</code> string_constant <code>;</code></p>
|
||||
<p>namespace_decl = <code>namespace</code> ident ( <code>.</code> ident )* <code>;</code></p>
|
||||
<p>type_decl = ( <code>table</code> | <code>struct</code> ) ident metadata <code>{</code> field_decl+ <code>}</code></p>
|
||||
<p>enum_decl = ( <code>enum</code> | <code>union</code> ) ident [ <code>:</code> type ] metadata <code>{</code> commasep( enumval_decl ) <code>}</code></p>
|
||||
|
||||
@@ -107,7 +107,7 @@ inline const char **EnumNamesAny() {
|
||||
inline const char *EnumNameAny(int e) { return EnumNamesAny()[e]; }
|
||||
</pre><p>Unions share a lot with enums. </p><pre class="fragment">struct Vec3;
|
||||
struct Monster;
|
||||
</pre><p>Predeclare all datatypes since there may be circular references. </p><pre class="fragment">MANUALLY_ALIGNED_STRUCT(4) Vec3 {
|
||||
</pre><p>Predeclare all data types since circular references between types are allowed (circular references between object are not, though). </p><pre class="fragment">MANUALLY_ALIGNED_STRUCT(4) Vec3 {
|
||||
private:
|
||||
float x_;
|
||||
float y_;
|
||||
|
||||
@@ -111,6 +111,10 @@ root_type Monster;
|
||||
<p>Unions share a lot of properties with enums, but instead of new names for constants, you use names of tables. You can then declare a union field which can hold a reference to any of those types, and additionally a hidden field with the suffix <code>_type</code> is generated that holds the corresponding enum value, allowing you to know which type to cast to at runtime.</p>
|
||||
<h3>Namespaces</h3>
|
||||
<p>These will generate the corresponding namespace in C++ for all helper code, and packages in Java. You can use <code>.</code> to specify nested namespaces / packages.</p>
|
||||
<h3>Includes</h3>
|
||||
<p>You can include other schemas files in your current one, e.g.: </p><pre class="fragment">include "mydefinitions.fbs"
|
||||
</pre><p>This makes it easier to refer to types defined elsewhere. <code>include</code> automatically ensures each file is parsed just once, even when referred to more than once.</p>
|
||||
<p>When using the <code>flatc</code> compiler to generate code for schema definitions, only definitions in the current file will be generated, not those from the included files (those you still generate separately).</p>
|
||||
<h3>Root type</h3>
|
||||
<p>This declares what you consider to be the root table (or struct) of the serialized data. This is particular important for parsing JSON data, which doesn't include object type information.</p>
|
||||
<h3>File identification and extension</h3>
|
||||
@@ -140,6 +144,20 @@ root_type Monster;
|
||||
<li>It accepts field names with and without quotes, like many JSON parsers already do. It outputs them without quotes as well, though can be made to output them using the <code>strict_json</code> flag.</li>
|
||||
<li>If a field has an enum type, the parser will recognize symbolic enum values (with or without quotes) instead of numbers, e.g. <code>field: EnumVal</code>. If a field is of integral type, you can still use symbolic names, but values need to be prefixed with their type and need to be quoted, e.g. <code>field: "Enum.EnumVal"</code>. For enums representing flags, you may place multiple inside a string separated by spaces to OR them, e.g. <code>field: "EnumVal1 EnumVal2"</code> or <code>field: "Enum.EnumVal1 Enum.EnumVal2"</code>.</li>
|
||||
</ul>
|
||||
<p>When parsing JSON, it recognizes the following escape codes in strings:</p>
|
||||
<ul>
|
||||
<li><code>\n</code> - linefeed.</li>
|
||||
<li><code>\t</code> - tab.</li>
|
||||
<li><code>\r</code> - carriage return.</li>
|
||||
<li><code>\b</code> - backspace.</li>
|
||||
<li><code>\f</code> - form feed.</li>
|
||||
<li><code>\"</code> - double quote.</li>
|
||||
<li><code>\\</code> - backslash.</li>
|
||||
<li><code>\/</code> - forward slash.</li>
|
||||
<li><code>\uXXXX</code> - 16-bit unicode code point, converted to the equivalent UTF-8 representation.</li>
|
||||
<li><code>\xXX</code> - 8-bit binary hexadecimal number XX. This is the only one that is not in the JSON spec (see <a href="http://json.org/">http://json.org/</a>), but is needed to be able to encode arbitrary binary in strings to text and back without losing information (e.g. the byte 0xFF can't be represented in standard JSON).</li>
|
||||
</ul>
|
||||
<p>It also generates these escape codes back again when generating JSON from a binary representation.</p>
|
||||
<h2>Gotchas</h2>
|
||||
<h3>Schemas and version control</h3>
|
||||
<p>FlatBuffers relies on new field declarations being added at the end, and earlier declarations to not be removed, but be marked deprecated when needed. We think this is an improvement over the manual number assignment that happens in Protocol Buffers (and which is still an option using the <code>id</code> attribute mentioned above).</p>
|
||||
|
||||
@@ -34,3 +34,6 @@ be generated for each file processed:
|
||||
|
||||
- `-S` : Generate strict JSON (field names are enclosed in quotes).
|
||||
By default, no quotes are generated.
|
||||
|
||||
- `-P` : Don't prefix enum values in generated C++ by their enum type.
|
||||
|
||||
|
||||
@@ -188,6 +188,11 @@ a full traversal (since any scalar data is not actually touched),
|
||||
and since it may cause the buffer to be brought into cache before
|
||||
reading, the actual overhead may be even lower than expected.
|
||||
|
||||
In specialized cases where a denial of service attack is possible,
|
||||
the verifier has two additional constructor arguments that allow
|
||||
you to limit the nesting depth and total amount of tables the
|
||||
verifier may encounter before declaring the buffer malformed.
|
||||
|
||||
## Text & schema parsing
|
||||
|
||||
Using binary buffers with the generated header provides a super low
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Formal Grammar of the schema language
|
||||
|
||||
schema = namespace\_decl | type\_decl | enum\_decl | root\_decl | object
|
||||
schema = include*
|
||||
( namespace\_decl | type\_decl | enum\_decl | root\_decl | object )*
|
||||
|
||||
include = `include` string\_constant `;`
|
||||
|
||||
namespace\_decl = `namespace` ident ( `.` ident )* `;`
|
||||
|
||||
|
||||
@@ -159,7 +159,8 @@ Unions share a lot with enums.
|
||||
struct Vec3;
|
||||
struct Monster;
|
||||
|
||||
Predeclare all datatypes since there may be circular references.
|
||||
Predeclare all data types since circular references between types are allowed
|
||||
(circular references between object are not, though).
|
||||
|
||||
MANUALLY_ALIGNED_STRUCT(4) Vec3 {
|
||||
private:
|
||||
|
||||
@@ -141,6 +141,20 @@ These will generate the corresponding namespace in C++ for all helper
|
||||
code, and packages in Java. You can use `.` to specify nested namespaces /
|
||||
packages.
|
||||
|
||||
### Includes
|
||||
|
||||
You can include other schemas files in your current one, e.g.:
|
||||
|
||||
include "mydefinitions.fbs"
|
||||
|
||||
This makes it easier to refer to types defined elsewhere. `include`
|
||||
automatically ensures each file is parsed just once, even when referred to
|
||||
more than once.
|
||||
|
||||
When using the `flatc` compiler to generate code for schema definitions,
|
||||
only definitions in the current file will be generated, not those from the
|
||||
included files (those you still generate separately).
|
||||
|
||||
### Root type
|
||||
|
||||
This declares what you consider to be the root table (or struct) of the
|
||||
@@ -254,6 +268,26 @@ JSON:
|
||||
separated by spaces to OR them, e.g.
|
||||
`field: "EnumVal1 EnumVal2"` or `field: "Enum.EnumVal1 Enum.EnumVal2"`.
|
||||
|
||||
When parsing JSON, it recognizes the following escape codes in strings:
|
||||
|
||||
- `\n` - linefeed.
|
||||
- `\t` - tab.
|
||||
- `\r` - carriage return.
|
||||
- `\b` - backspace.
|
||||
- `\f` - form feed.
|
||||
- `\"` - double quote.
|
||||
- `\\` - backslash.
|
||||
- `\/` - forward slash.
|
||||
- `\uXXXX` - 16-bit unicode code point, converted to the equivalent UTF-8
|
||||
representation.
|
||||
- `\xXX` - 8-bit binary hexadecimal number XX. This is the only one that is
|
||||
not in the JSON spec (see http://json.org/), but is needed to be able to
|
||||
encode arbitrary binary in strings to text and back without losing
|
||||
information (e.g. the byte 0xFF can't be represented in standard JSON).
|
||||
|
||||
It also generates these escape codes back again when generating JSON from a
|
||||
binary representation.
|
||||
|
||||
## Gotchas
|
||||
|
||||
### Schemas and version control
|
||||
|
||||
@@ -669,8 +669,10 @@ inline bool BufferHasIdentifier(const void *buf, const char *identifier) {
|
||||
// Helper class to verify the integrity of a FlatBuffer
|
||||
class Verifier {
|
||||
public:
|
||||
Verifier(const uint8_t *buf, size_t buf_len)
|
||||
: buf_(buf), end_(buf + buf_len)
|
||||
Verifier(const uint8_t *buf, size_t buf_len, size_t _max_depth = 64,
|
||||
size_t _max_tables = 1000000)
|
||||
: buf_(buf), end_(buf + buf_len), depth_(0), max_depth_(_max_depth),
|
||||
num_tables_(0), max_tables_(_max_tables)
|
||||
{}
|
||||
|
||||
// Verify any range within the buffer.
|
||||
@@ -688,7 +690,7 @@ class Verifier {
|
||||
}
|
||||
|
||||
// Verify a pointer (may be NULL) of a table type.
|
||||
template<typename T> bool VerifyTable(const T *table) const {
|
||||
template<typename T> bool VerifyTable(const T *table) {
|
||||
return !table || table->Verify(*this);
|
||||
}
|
||||
|
||||
@@ -733,8 +735,7 @@ class Verifier {
|
||||
}
|
||||
|
||||
// Special case for table contents, after the above has been called.
|
||||
template<typename T> bool VerifyVectorOfTables(const Vector<Offset<T>> *vec)
|
||||
const {
|
||||
template<typename T> bool VerifyVectorOfTables(const Vector<Offset<T>> *vec) {
|
||||
if (vec) {
|
||||
for (uoffset_t i = 0; i < vec->Length(); i++) {
|
||||
if (!vec->Get(i)->Verify(*this)) return false;
|
||||
@@ -744,16 +745,40 @@ class Verifier {
|
||||
}
|
||||
|
||||
// Verify this whole buffer, starting with root type T.
|
||||
template<typename T> bool VerifyBuffer() const {
|
||||
template<typename T> bool VerifyBuffer() {
|
||||
// Call T::Verify, which must be in the generated code for this type.
|
||||
return Verify<uoffset_t>(buf_) &&
|
||||
reinterpret_cast<const T *>(buf_ + ReadScalar<uoffset_t>(buf_))->
|
||||
Verify(*this);
|
||||
}
|
||||
|
||||
// Called at the start of a table to increase counters measuring data
|
||||
// structure depth and amount, and possibly bails out with false if
|
||||
// limits set by the constructor have been hit. Needs to be balanced
|
||||
// with EndTable().
|
||||
bool VerifyComplexity() {
|
||||
depth_++;
|
||||
num_tables_++;
|
||||
bool too_complex = depth_ > max_depth_ || num_tables_ > max_tables_;
|
||||
#ifdef FLATBUFFERS_DEBUG_VERIFICATION_FAILURE
|
||||
assert(!too_complex);
|
||||
#endif
|
||||
return !too_complex;
|
||||
}
|
||||
|
||||
// Called at the end of a table to pop the depth count.
|
||||
bool EndTable() {
|
||||
depth_--;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t *buf_;
|
||||
const uint8_t *end_;
|
||||
size_t depth_;
|
||||
size_t max_depth_;
|
||||
size_t num_tables_;
|
||||
size_t max_tables_;
|
||||
};
|
||||
|
||||
// "structs" are flat structures that do not have an offset table, thus
|
||||
@@ -828,12 +853,13 @@ class Table {
|
||||
|
||||
// Verify the vtable of this table.
|
||||
// Call this once per table, followed by VerifyField once per field.
|
||||
bool VerifyTable(const Verifier &verifier) const {
|
||||
bool VerifyTableStart(Verifier &verifier) const {
|
||||
// Check the vtable offset.
|
||||
if (!verifier.Verify<soffset_t>(data_)) return false;
|
||||
auto vtable = data_ - ReadScalar<soffset_t>(data_);
|
||||
// Check the vtable size field, then check vtable fits in its entirety.
|
||||
return verifier.Verify<voffset_t>(vtable) &&
|
||||
return verifier.VerifyComplexity() &&
|
||||
verifier.Verify<voffset_t>(vtable) &&
|
||||
verifier.Verify(vtable, ReadScalar<voffset_t>(vtable));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include "flatbuffers/flatbuffers.h"
|
||||
|
||||
@@ -159,6 +160,11 @@ template<typename T> class SymbolTable {
|
||||
std::vector<T *> vec; // Used to iterate in order of insertion
|
||||
};
|
||||
|
||||
// A name space, as set in the schema.
|
||||
struct Namespace {
|
||||
std::vector<std::string> components;
|
||||
};
|
||||
|
||||
// Base class for all definition types (fields, structs_, enums_).
|
||||
struct Definition {
|
||||
Definition() : generated(false) {}
|
||||
@@ -183,7 +189,8 @@ struct StructDef : public Definition {
|
||||
predecl(true),
|
||||
sortbysize(true),
|
||||
minalign(1),
|
||||
bytesize(0)
|
||||
bytesize(0),
|
||||
defined_namespace(nullptr)
|
||||
{}
|
||||
|
||||
void PadLastField(size_t minalign) {
|
||||
@@ -198,6 +205,7 @@ struct StructDef : public Definition {
|
||||
bool sortbysize; // Whether fields come in the declaration or size order.
|
||||
size_t minalign; // What the whole object needs to be aligned to.
|
||||
size_t bytesize; // Size if fixed.
|
||||
Namespace *defined_namespace; // Where it was defined.
|
||||
};
|
||||
|
||||
inline bool IsStruct(const Type &type) {
|
||||
@@ -245,16 +253,31 @@ class Parser {
|
||||
root_struct_def(nullptr),
|
||||
source_(nullptr),
|
||||
cursor_(nullptr),
|
||||
line_(1) {}
|
||||
line_(1) {
|
||||
// Just in case none are declared:
|
||||
namespaces_.push_back(new Namespace());
|
||||
}
|
||||
|
||||
~Parser() {
|
||||
for (auto it = namespaces_.begin(); it != namespaces_.end(); ++it) {
|
||||
delete *it;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the string containing either schema or JSON data, which will
|
||||
// populate the SymbolTable's or the FlatBufferBuilder above.
|
||||
bool Parse(const char *_source);
|
||||
// filepath indicates the file that _source was loaded from, it is
|
||||
// used to resolve any include statements.
|
||||
bool Parse(const char *_source, const char *filepath);
|
||||
|
||||
// Set the root type. May override the one set in the schema.
|
||||
bool SetRootType(const char *name);
|
||||
|
||||
// Mark all definitions as already having code generated.
|
||||
void MarkGenerated();
|
||||
|
||||
private:
|
||||
int64_t ParseHexNum(int nibbles);
|
||||
void Next();
|
||||
bool IsNext(int t);
|
||||
void Expect(int t);
|
||||
@@ -279,7 +302,7 @@ class Parser {
|
||||
public:
|
||||
SymbolTable<StructDef> structs_;
|
||||
SymbolTable<EnumDef> enums_;
|
||||
std::vector<std::string> name_space_; // As set in the schema.
|
||||
std::vector<Namespace *> namespaces_;
|
||||
std::string error_; // User readable error_ if Parse() == false
|
||||
|
||||
FlatBufferBuilder builder_; // any data contained in the file
|
||||
@@ -295,6 +318,7 @@ class Parser {
|
||||
|
||||
std::vector<std::pair<Value, FieldDef *>> field_stack_;
|
||||
std::vector<uint8_t> struct_stack_;
|
||||
std::map<std::string, bool> included_files_;
|
||||
};
|
||||
|
||||
// Utility functions for generators:
|
||||
@@ -319,9 +343,10 @@ struct GeneratorOptions {
|
||||
bool strict_json;
|
||||
int indent_step;
|
||||
bool output_enum_identifiers;
|
||||
bool prefixed_enums;
|
||||
|
||||
GeneratorOptions() : strict_json(false), indent_step(2),
|
||||
output_enum_identifiers(true) {}
|
||||
output_enum_identifiers(true), prefixed_enums(true) {}
|
||||
};
|
||||
|
||||
// Generate text (JSON) from a given FlatBuffer, and a given Parser
|
||||
@@ -338,7 +363,8 @@ extern void GenerateText(const Parser &parser,
|
||||
// Generate a C++ header from the definitions in the Parser object.
|
||||
// See idl_gen_cpp.
|
||||
extern std::string GenerateCPP(const Parser &parser,
|
||||
const std::string &include_guard_ident);
|
||||
const std::string &include_guard_ident,
|
||||
const GeneratorOptions &opts);
|
||||
extern bool GenerateCPP(const Parser &parser,
|
||||
const std::string &path,
|
||||
const std::string &file_name,
|
||||
|
||||
@@ -22,16 +22,14 @@
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
namespace flatbuffers {
|
||||
|
||||
static const char kPosixPathSeparator = '/';
|
||||
#ifdef _WIN32
|
||||
static const char kPathSeparator = '\\';
|
||||
#else
|
||||
static const char kPathSeparator = kPosixPathSeparator;
|
||||
#endif // _WIN32
|
||||
|
||||
// Convert an integer or floating point value to a string.
|
||||
// In contrast to std::stringstream, "char" values are
|
||||
// converted to a string of digits.
|
||||
@@ -51,13 +49,11 @@ template<> inline std::string NumToString<unsigned char>(unsigned char t) {
|
||||
}
|
||||
|
||||
// Convert an integer value to a hexadecimal string.
|
||||
// The returned string length is the number of nibbles in
|
||||
// the supplied value prefixed by 0 digits. For example,
|
||||
// IntToStringHex(static_cast<int>(0x23)) returns the
|
||||
// string "00000023".
|
||||
template<typename T> std::string IntToStringHex(T i) {
|
||||
// The returned string length is always xdigits long, prefixed by 0 digits.
|
||||
// For example, IntToStringHex(0x23, 8) returns the string "00000023".
|
||||
inline std::string IntToStringHex(int i, int xdigits) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(sizeof(T) * 2)
|
||||
ss << std::setw(xdigits)
|
||||
<< std::setfill('0')
|
||||
<< std::hex
|
||||
<< std::uppercase
|
||||
@@ -66,11 +62,11 @@ template<typename T> std::string IntToStringHex(T i) {
|
||||
}
|
||||
|
||||
// Portable implementation of strtoull().
|
||||
inline int64_t StringToInt(const char *str) {
|
||||
inline int64_t StringToInt(const char *str, int base = 10) {
|
||||
#ifdef _MSC_VER
|
||||
return _strtoui64(str, nullptr, 10);
|
||||
return _strtoui64(str, nullptr, base);
|
||||
#else
|
||||
return strtoull(str, nullptr, 10);
|
||||
return strtoull(str, nullptr, base);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -107,6 +103,101 @@ inline bool SaveFile(const char *name, const std::string &buf, bool binary) {
|
||||
return SaveFile(name, buf.c_str(), buf.size(), binary);
|
||||
}
|
||||
|
||||
// Functionality for minimalistic portable path handling:
|
||||
|
||||
static const char kPosixPathSeparator = '/';
|
||||
#ifdef _WIN32
|
||||
static const char kPathSeparator = '\\';
|
||||
static const char *PathSeparatorSet = "\\/"; // Intentionally no ':'
|
||||
#else
|
||||
static const char kPathSeparator = kPosixPathSeparator;
|
||||
static const char *PathSeparatorSet = "/";
|
||||
#endif // _WIN32
|
||||
|
||||
// Returns the path with the extension, if any, removed.
|
||||
inline std::string StripExtension(const std::string &filepath) {
|
||||
size_t i = filepath.find_last_of(".");
|
||||
return i != std::string::npos ? filepath.substr(0, i) : filepath;
|
||||
}
|
||||
|
||||
// Return the last component of the path, after the last separator.
|
||||
inline std::string StripPath(const std::string &filepath) {
|
||||
size_t i = filepath.find_last_of(PathSeparatorSet);
|
||||
return i != std::string::npos ? filepath.substr(i + 1) : filepath;
|
||||
}
|
||||
|
||||
// Strip the last component of the path + separator.
|
||||
inline std::string StripFileName(const std::string &filepath) {
|
||||
size_t i = filepath.find_last_of(PathSeparatorSet);
|
||||
return i != std::string::npos ? filepath.substr(0, i) : "";
|
||||
}
|
||||
|
||||
// This function ensure a directory exists, by recursively
|
||||
// creating dirs for any parts of the path that don't exist yet.
|
||||
inline void EnsureDirExists(const std::string &filepath) {
|
||||
auto parent = StripFileName(filepath);
|
||||
if (parent.length()) EnsureDirExists(parent);
|
||||
#ifdef _WIN32
|
||||
_mkdir(filepath.c_str());
|
||||
#else
|
||||
mkdir(filepath.c_str(), S_IRWXU|S_IRGRP|S_IXGRP);
|
||||
#endif
|
||||
}
|
||||
|
||||
// To and from UTF-8 unicode conversion functions
|
||||
|
||||
// Convert a unicode code point into a UTF-8 representation by appending it
|
||||
// to a string. Returns the number of bytes generated.
|
||||
inline int ToUTF8(uint32_t ucc, std::string *out) {
|
||||
assert(!(ucc & 0x80000000)); // Top bit can't be set.
|
||||
// 6 possible encodings: http://en.wikipedia.org/wiki/UTF-8
|
||||
for (int i = 0; i < 6; i++) {
|
||||
// Max bits this encoding can represent.
|
||||
uint32_t max_bits = 6 + i * 5 + static_cast<int>(!i);
|
||||
if (ucc < (1u << max_bits)) { // does it fit?
|
||||
// Remaining bits not encoded in the first byte, store 6 bits each
|
||||
uint32_t remain_bits = i * 6;
|
||||
// Store first byte:
|
||||
(*out) += static_cast<char>((0xFE << (max_bits - remain_bits)) |
|
||||
(ucc >> remain_bits));
|
||||
// Store remaining bytes:
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
(*out) += static_cast<char>(((ucc >> (j * 6)) & 0x3F) | 0x80);
|
||||
}
|
||||
return i + 1; // Return the number of bytes added.
|
||||
}
|
||||
}
|
||||
assert(0); // Impossible to arrive here.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Converts whatever prefix of the incoming string corresponds to a valid
|
||||
// UTF-8 sequence into a unicode code. The incoming pointer will have been
|
||||
// advanced past all bytes parsed.
|
||||
// returns -1 upon corrupt UTF-8 encoding (ignore the incoming pointer in
|
||||
// this case).
|
||||
inline int FromUTF8(const char **in) {
|
||||
int len = 0;
|
||||
// Count leading 1 bits.
|
||||
for (int mask = 0x80; mask >= 0x04; mask >>= 1) {
|
||||
if (**in & mask) {
|
||||
len++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((**in << len) & 0x80) return -1; // Bit after leading 1's must be 0.
|
||||
if (!len) return *(*in)++;
|
||||
// Grab initial bits of the code.
|
||||
int ucc = *(*in)++ & ((1 << (7 - len)) - 1);
|
||||
for (int i = 0; i < len - 1; i++) {
|
||||
if ((**in & 0xC0) != 0x80) return -1; // Upper bits must 1 0.
|
||||
ucc <<= 6;
|
||||
ucc |= *(*in)++ & 0x3F; // Grab 6 more bits of the code.
|
||||
}
|
||||
return ucc;
|
||||
}
|
||||
|
||||
} // namespace flatbuffers
|
||||
|
||||
#endif // FLATBUFFERS_UTIL_H_
|
||||
|
||||
@@ -30,7 +30,7 @@ public class FlatBufferBuilder {
|
||||
int space; // Remaining space in the ByteBuffer.
|
||||
static final Charset utf8charset = Charset.forName("UTF-8");
|
||||
int minalign = 1; // Minimum alignment encountered so far.
|
||||
int[] vtable; // The vtable for the current table, null otherwise.
|
||||
int[] vtable = null; // The vtable for the current table, null otherwise.
|
||||
int object_start; // Starting offset of the current struct/table.
|
||||
int[] vtables = new int[16]; // List of offsets of all vtables.
|
||||
int num_vtables = 0; // Number of entries in `vtables` in use.
|
||||
@@ -47,6 +47,14 @@ public class FlatBufferBuilder {
|
||||
bb = newByteBuffer(new byte[initial_size]);
|
||||
}
|
||||
|
||||
// Alternative constructor allowing reuse of ByteBuffers
|
||||
public FlatBufferBuilder(ByteBuffer existing_bb) {
|
||||
bb = existing_bb;
|
||||
bb.clear();
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN);
|
||||
space = bb.capacity();
|
||||
}
|
||||
|
||||
ByteBuffer newByteBuffer(byte[] buf) {
|
||||
ByteBuffer newbb = ByteBuffer.wrap(buf);
|
||||
newbb.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
@@ -42,7 +42,17 @@ public class Table {
|
||||
// Create a java String from UTF-8 data stored inside the flatbuffer.
|
||||
protected String __string(int offset) {
|
||||
offset += bb.getInt(offset);
|
||||
return new String(bb.array(), offset + SIZEOF_INT, bb.getInt(offset), Charset.forName("UTF-8"));
|
||||
if (bb.hasArray()) {
|
||||
return new String(bb.array(), offset + SIZEOF_INT, bb.getInt(offset), Charset.forName("UTF-8"));
|
||||
} else {
|
||||
// We can't access .array(), since the ByteBuffer is read-only.
|
||||
// We're forced to make an extra copy:
|
||||
bb.position(offset + SIZEOF_INT);
|
||||
byte[] copy = new byte[bb.getInt(offset)];
|
||||
bb.get(copy);
|
||||
bb.position(0);
|
||||
return new String(copy, 0, copy.length, Charset.forName("UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the length of a vector whose offset is stored at "offset" in this object.
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
|
||||
#include "flatbuffers/flatbuffers.h"
|
||||
|
||||
|
||||
namespace MyGame {
|
||||
namespace Sample {
|
||||
|
||||
struct Vec3;
|
||||
struct Monster;
|
||||
|
||||
enum {
|
||||
Color_Red = 0,
|
||||
Color_Green = 1,
|
||||
@@ -33,10 +37,7 @@ inline const char **EnumNamesAny() {
|
||||
|
||||
inline const char *EnumNameAny(int e) { return EnumNamesAny()[e]; }
|
||||
|
||||
bool VerifyAny(const flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type);
|
||||
|
||||
struct Vec3;
|
||||
struct Monster;
|
||||
bool VerifyAny(flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type);
|
||||
|
||||
MANUALLY_ALIGNED_STRUCT(4) Vec3 {
|
||||
private:
|
||||
@@ -46,7 +47,7 @@ MANUALLY_ALIGNED_STRUCT(4) Vec3 {
|
||||
|
||||
public:
|
||||
Vec3(float x, float y, float z)
|
||||
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), z_(flatbuffers::EndianScalar(z)) {}
|
||||
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), z_(flatbuffers::EndianScalar(z)) { }
|
||||
|
||||
float x() const { return flatbuffers::EndianScalar(x_); }
|
||||
float y() const { return flatbuffers::EndianScalar(y_); }
|
||||
@@ -61,8 +62,8 @@ struct Monster : private flatbuffers::Table {
|
||||
const flatbuffers::String *name() const { return GetPointer<const flatbuffers::String *>(10); }
|
||||
const flatbuffers::Vector<uint8_t> *inventory() const { return GetPointer<const flatbuffers::Vector<uint8_t> *>(14); }
|
||||
int8_t color() const { return GetField<int8_t>(16, 2); }
|
||||
bool Verify(const flatbuffers::Verifier &verifier) const {
|
||||
return VerifyTable(verifier) &&
|
||||
bool Verify(flatbuffers::Verifier &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
VerifyField<Vec3>(verifier, 4 /* pos */) &&
|
||||
VerifyField<int16_t>(verifier, 6 /* mana */) &&
|
||||
VerifyField<int16_t>(verifier, 8 /* hp */) &&
|
||||
@@ -70,7 +71,8 @@ struct Monster : private flatbuffers::Table {
|
||||
verifier.Verify(name()) &&
|
||||
VerifyField<flatbuffers::uoffset_t>(verifier, 14 /* inventory */) &&
|
||||
verifier.Verify(inventory()) &&
|
||||
VerifyField<int8_t>(verifier, 16 /* color */);
|
||||
VerifyField<int8_t>(verifier, 16 /* color */) &&
|
||||
verifier.EndTable();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,7 +107,7 @@ inline flatbuffers::Offset<Monster> CreateMonster(flatbuffers::FlatBufferBuilder
|
||||
return builder_.Finish();
|
||||
}
|
||||
|
||||
bool VerifyAny(const flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type) {
|
||||
bool VerifyAny(flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type) {
|
||||
switch (type) {
|
||||
case Any_NONE: return true;
|
||||
case Any_Monster: return verifier.VerifyTable(reinterpret_cast<const Monster *>(union_obj));
|
||||
@@ -115,7 +117,9 @@ bool VerifyAny(const flatbuffers::Verifier &verifier, const void *union_obj, uin
|
||||
|
||||
inline const Monster *GetMonster(const void *buf) { return flatbuffers::GetRoot<Monster>(buf); }
|
||||
|
||||
inline bool VerifyMonsterBuffer(const flatbuffers::Verifier &verifier) { return verifier.VerifyBuffer<Monster>(); }
|
||||
inline bool VerifyMonsterBuffer(flatbuffers::Verifier &verifier) { return verifier.VerifyBuffer<Monster>(); }
|
||||
|
||||
inline void FinishMonsterBuffer(flatbuffers::FlatBufferBuilder &fbb, flatbuffers::Offset<Monster> root) { fbb.Finish(root); }
|
||||
|
||||
} // namespace Sample
|
||||
} // namespace MyGame
|
||||
|
||||
@@ -54,9 +54,11 @@ int main(int /*argc*/, const char * /*argv*/[]) {
|
||||
auto pos = monster->pos();
|
||||
assert(pos);
|
||||
assert(pos->z() == 3);
|
||||
(void)pos;
|
||||
|
||||
auto inv = monster->inventory();
|
||||
assert(inv);
|
||||
assert(inv->Get(9) == 9);
|
||||
(void)inv;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ int main(int /*argc*/, const char * /*argv*/[]) {
|
||||
|
||||
// parse schema first, so we can use it to parse the data after
|
||||
flatbuffers::Parser parser;
|
||||
ok = parser.Parse(schemafile.c_str()) &&
|
||||
parser.Parse(jsonfile.c_str());
|
||||
ok = parser.Parse(schemafile.c_str(), "samples/") &&
|
||||
parser.Parse(jsonfile.c_str(), "samples/");
|
||||
assert(ok);
|
||||
|
||||
// here, parser.builder_ contains a binary buffer that is the parsed data.
|
||||
|
||||
@@ -90,6 +90,7 @@ static void Error(const char *err, const char *obj, bool usage) {
|
||||
printf(" -%s %s.\n", generators[i].extension, generators[i].help);
|
||||
printf(" -o PATH Prefix PATH to all generated files.\n"
|
||||
" -S Strict JSON: add quotes to field names.\n"
|
||||
" -P Don\'t prefix enum values with the enum name in C++.\n"
|
||||
"FILEs may depend on declarations in earlier files.\n"
|
||||
"FILEs after the -- must be binary flatbuffer format files.\n"
|
||||
"Output files are named using the base file name of the input,"
|
||||
@@ -100,22 +101,6 @@ static void Error(const char *err, const char *obj, bool usage) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string StripExtension(const std::string &filename) {
|
||||
size_t i = filename.find_last_of(".");
|
||||
return i != std::string::npos ? filename.substr(0, i) : filename;
|
||||
}
|
||||
|
||||
std::string StripPath(const std::string &filename) {
|
||||
size_t i = filename.find_last_of(
|
||||
#ifdef _WIN32
|
||||
"\\:"
|
||||
#else
|
||||
"/"
|
||||
#endif
|
||||
);
|
||||
return i != std::string::npos ? filename.substr(i + 1) : filename;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
program_name = argv[0];
|
||||
flatbuffers::Parser parser;
|
||||
@@ -145,6 +130,9 @@ int main(int argc, const char *argv[]) {
|
||||
case 'S':
|
||||
opts.strict_json = true;
|
||||
break;
|
||||
case 'P':
|
||||
opts.prefixed_enums = false;
|
||||
break;
|
||||
case '-': // Separator between text and binary input files.
|
||||
binary_files_from = filenames.size();
|
||||
break;
|
||||
@@ -187,14 +175,16 @@ int main(int argc, const char *argv[]) {
|
||||
reinterpret_cast<const uint8_t *>(contents.c_str()),
|
||||
contents.length());
|
||||
} else {
|
||||
if (!parser.Parse(contents.c_str()))
|
||||
Error(parser.error_.c_str());
|
||||
if (!parser.Parse(contents.c_str(), file_it->c_str()))
|
||||
Error((*file_it + ": " + parser.error_).c_str());
|
||||
}
|
||||
|
||||
std::string filebase = StripPath(StripExtension(*file_it));
|
||||
std::string filebase = flatbuffers::StripPath(
|
||||
flatbuffers::StripExtension(*file_it));
|
||||
|
||||
for (size_t i = 0; i < num_generators; ++i) {
|
||||
if (generator_enabled[i]) {
|
||||
flatbuffers::EnsureDirExists(output_path);
|
||||
if (!generators[i].generate(parser, output_path, filebase, opts)) {
|
||||
Error((std::string("Unable to generate ") +
|
||||
generators[i].name +
|
||||
@@ -204,17 +194,9 @@ int main(int argc, const char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// Since the Parser object retains definitions across files, we must
|
||||
// ensure we only output code for these once, in the file they are first
|
||||
// declared:
|
||||
for (auto it = parser.enums_.vec.begin();
|
||||
it != parser.enums_.vec.end(); ++it) {
|
||||
(*it)->generated = true;
|
||||
}
|
||||
for (auto it = parser.structs_.vec.begin();
|
||||
it != parser.structs_.vec.end(); ++it) {
|
||||
(*it)->generated = true;
|
||||
}
|
||||
// We do not want to generate code for the definitions in this file
|
||||
// in any files coming up next.
|
||||
parser.MarkGenerated();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -33,18 +33,31 @@ static std::string GenTypeBasic(const Type &type) {
|
||||
return ctypename[type.base_type];
|
||||
}
|
||||
|
||||
static std::string GenTypeWire(const Type &type, const char *postfix);
|
||||
static std::string GenTypeWire(const Parser &parser, const Type &type,
|
||||
const char *postfix);
|
||||
|
||||
// Return a C++ pointer type, specialized to the actual struct/table types,
|
||||
// and vector element types.
|
||||
static std::string GenTypePointer(const Type &type) {
|
||||
static std::string GenTypePointer(const Parser &parser, const Type &type) {
|
||||
switch (type.base_type) {
|
||||
case BASE_TYPE_STRING:
|
||||
return "flatbuffers::String";
|
||||
case BASE_TYPE_VECTOR:
|
||||
return "flatbuffers::Vector<" + GenTypeWire(type.VectorType(), "") + ">";
|
||||
case BASE_TYPE_STRUCT:
|
||||
return type.struct_def->name;
|
||||
return "flatbuffers::Vector<" +
|
||||
GenTypeWire(parser, type.VectorType(), "") + ">";
|
||||
case BASE_TYPE_STRUCT: {
|
||||
auto ns = type.struct_def->defined_namespace;
|
||||
if (parser.namespaces_.back() != ns) {
|
||||
std::string qualified_name;
|
||||
for (auto it = ns->components.begin();
|
||||
it != ns->components.end(); ++it) {
|
||||
qualified_name += *it + "::";
|
||||
}
|
||||
return qualified_name + type.struct_def->name;
|
||||
} else {
|
||||
return type.struct_def->name;
|
||||
}
|
||||
}
|
||||
case BASE_TYPE_UNION:
|
||||
// fall through
|
||||
default:
|
||||
@@ -54,31 +67,33 @@ static std::string GenTypePointer(const Type &type) {
|
||||
|
||||
// Return a C++ type for any type (scalar/pointer) specifically for
|
||||
// building a flatbuffer.
|
||||
static std::string GenTypeWire(const Type &type, const char *postfix) {
|
||||
static std::string GenTypeWire(const Parser &parser, const Type &type,
|
||||
const char *postfix) {
|
||||
return IsScalar(type.base_type)
|
||||
? GenTypeBasic(type) + postfix
|
||||
: IsStruct(type)
|
||||
? "const " + GenTypePointer(type) + " *"
|
||||
: "flatbuffers::Offset<" + GenTypePointer(type) + ">" + postfix;
|
||||
? "const " + GenTypePointer(parser, type) + " *"
|
||||
: "flatbuffers::Offset<" + GenTypePointer(parser, type) + ">" + postfix;
|
||||
}
|
||||
|
||||
// Return a C++ type for any type (scalar/pointer) that reflects its
|
||||
// serialized size.
|
||||
static std::string GenTypeSize(const Type &type) {
|
||||
static std::string GenTypeSize(const Parser &parser, const Type &type) {
|
||||
return IsScalar(type.base_type)
|
||||
? GenTypeBasic(type)
|
||||
: IsStruct(type)
|
||||
? GenTypePointer(type)
|
||||
? GenTypePointer(parser, type)
|
||||
: "flatbuffers::uoffset_t";
|
||||
}
|
||||
|
||||
// Return a C++ type for any type (scalar/pointer) specifically for
|
||||
// using a flatbuffer.
|
||||
static std::string GenTypeGet(const Type &type, const char *afterbasic,
|
||||
const char *beforeptr, const char *afterptr) {
|
||||
static std::string GenTypeGet(const Parser &parser, const Type &type,
|
||||
const char *afterbasic, const char *beforeptr,
|
||||
const char *afterptr) {
|
||||
return IsScalar(type.base_type)
|
||||
? GenTypeBasic(type) + afterbasic
|
||||
: beforeptr + GenTypePointer(type) + afterptr;
|
||||
: beforeptr + GenTypePointer(parser, type) + afterptr;
|
||||
}
|
||||
|
||||
// Generate a documentation comment, if available.
|
||||
@@ -91,9 +106,16 @@ static void GenComment(const std::string &dc,
|
||||
}
|
||||
}
|
||||
|
||||
static std::string GenEnumVal(const EnumDef &enum_def, const EnumVal &enum_val,
|
||||
const GeneratorOptions &opts) {
|
||||
return opts.prefixed_enums ? enum_def.name + "_" + enum_val.name
|
||||
: enum_val.name;
|
||||
}
|
||||
|
||||
// Generate an enum declaration and an enum string lookup table.
|
||||
static void GenEnum(EnumDef &enum_def, std::string *code_ptr,
|
||||
std::string *code_ptr_post) {
|
||||
static void GenEnum(EnumDef &enum_def, std::string *code_ptr,
|
||||
std::string *code_ptr_post,
|
||||
const GeneratorOptions &opts) {
|
||||
if (enum_def.generated) return;
|
||||
std::string &code = *code_ptr;
|
||||
std::string &code_post = *code_ptr_post;
|
||||
@@ -104,7 +126,7 @@ static void GenComment(const std::string &dc,
|
||||
++it) {
|
||||
auto &ev = **it;
|
||||
GenComment(ev.doc_comment, code_ptr, " ");
|
||||
code += " " + enum_def.name + "_" + ev.name + " = ";
|
||||
code += " " + GenEnumVal(enum_def, ev, opts) + " = ";
|
||||
code += NumToString(ev.value);
|
||||
code += (it + 1) != enum_def.vals.vec.end() ? ",\n" : "\n";
|
||||
}
|
||||
@@ -133,7 +155,7 @@ static void GenComment(const std::string &dc,
|
||||
code += "inline const char *EnumName" + enum_def.name;
|
||||
code += "(int e) { return EnumNames" + enum_def.name + "()[e";
|
||||
if (enum_def.vals.vec.front()->value)
|
||||
code += " - " + enum_def.name + "_" + enum_def.vals.vec.front()->name;
|
||||
code += " - " + GenEnumVal(enum_def, *enum_def.vals.vec.front(), opts);
|
||||
code += "]; }\n\n";
|
||||
}
|
||||
|
||||
@@ -144,7 +166,7 @@ static void GenComment(const std::string &dc,
|
||||
// has been corrupted, since the verifiers will simply fail when called
|
||||
// on the wrong type.
|
||||
auto signature = "bool Verify" + enum_def.name +
|
||||
"(const flatbuffers::Verifier &verifier, " +
|
||||
"(flatbuffers::Verifier &verifier, " +
|
||||
"const void *union_obj, uint8_t type)";
|
||||
code += signature + ";\n\n";
|
||||
code_post += signature + " {\n switch (type) {\n";
|
||||
@@ -152,7 +174,7 @@ static void GenComment(const std::string &dc,
|
||||
it != enum_def.vals.vec.end();
|
||||
++it) {
|
||||
auto &ev = **it;
|
||||
code_post += " case " + enum_def.name + "_" + ev.name;
|
||||
code_post += " case " + GenEnumVal(enum_def, ev, opts);
|
||||
if (!ev.value) {
|
||||
code_post += ": return true;\n"; // "NONE" enum value.
|
||||
} else {
|
||||
@@ -181,13 +203,13 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
|
||||
auto &field = **it;
|
||||
if (!field.deprecated) { // Deprecated fields won't be accessible.
|
||||
GenComment(field.doc_comment, code_ptr, " ");
|
||||
code += " " + GenTypeGet(field.value.type, " ", "const ", " *");
|
||||
code += " " + GenTypeGet(parser, field.value.type, " ", "const ", " *");
|
||||
code += field.name + "() const { return ";
|
||||
// Call a different accessor for pointers, that indirects.
|
||||
code += IsScalar(field.value.type.base_type)
|
||||
? "GetField<"
|
||||
: (IsStruct(field.value.type) ? "GetStruct<" : "GetPointer<");
|
||||
code += GenTypeGet(field.value.type, "", "const ", " *") + ">(";
|
||||
code += GenTypeGet(parser, field.value.type, "", "const ", " *") + ">(";
|
||||
code += NumToString(field.value.offset);
|
||||
// Default value as second arg for non-pointer types.
|
||||
if (IsScalar(field.value.type.base_type))
|
||||
@@ -205,15 +227,15 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
|
||||
}
|
||||
// Generate a verifier function that can check a buffer from an untrusted
|
||||
// source will never cause reads outside the buffer.
|
||||
code += " bool Verify(const flatbuffers::Verifier &verifier) const {\n";
|
||||
code += " return VerifyTable(verifier)";
|
||||
code += " bool Verify(flatbuffers::Verifier &verifier) const {\n";
|
||||
code += " return VerifyTableStart(verifier)";
|
||||
std::string prefix = " &&\n ";
|
||||
for (auto it = struct_def.fields.vec.begin();
|
||||
it != struct_def.fields.vec.end();
|
||||
++it) {
|
||||
auto &field = **it;
|
||||
if (!field.deprecated) {
|
||||
code += prefix + "VerifyField<" + GenTypeSize(field.value.type);
|
||||
code += prefix + "VerifyField<" + GenTypeSize(parser, field.value.type);
|
||||
code += ">(verifier, " + NumToString(field.value.offset);
|
||||
code += " /* " + field.name + " */)";
|
||||
switch (field.value.type.base_type) {
|
||||
@@ -254,6 +276,7 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
|
||||
}
|
||||
}
|
||||
}
|
||||
code += prefix + "verifier.EndTable()";
|
||||
code += ";\n }\n";
|
||||
code += "};\n\n";
|
||||
|
||||
@@ -268,9 +291,10 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
|
||||
auto &field = **it;
|
||||
if (!field.deprecated) {
|
||||
code += " void add_" + field.name + "(";
|
||||
code += GenTypeWire(field.value.type, " ") + field.name + ") { fbb_.Add";
|
||||
code += GenTypeWire(parser, field.value.type, " ") + field.name;
|
||||
code += ") { fbb_.Add";
|
||||
if (IsScalar(field.value.type.base_type))
|
||||
code += "Element<" + GenTypeWire(field.value.type, "") + ">";
|
||||
code += "Element<" + GenTypeWire(parser, field.value.type, "") + ">";
|
||||
else if (IsStruct(field.value.type))
|
||||
code += "Struct";
|
||||
else
|
||||
@@ -301,8 +325,8 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
|
||||
++it) {
|
||||
auto &field = **it;
|
||||
if (!field.deprecated) {
|
||||
code += ",\n " + GenTypeWire(field.value.type, " ") + field.name;
|
||||
code += " = " + field.value.constant;
|
||||
code += ",\n " + GenTypeWire(parser, field.value.type, " ");
|
||||
code += field.name + " = " + field.value.constant;
|
||||
}
|
||||
}
|
||||
code += ") {\n " + struct_def.name + "Builder builder_(_fbb);\n";
|
||||
@@ -323,8 +347,18 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
|
||||
code += " return builder_.Finish();\n}\n\n";
|
||||
}
|
||||
|
||||
static void GenPadding(const FieldDef &field, const std::function<void (int bits)> &f) {
|
||||
if (field.padding) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (static_cast<int>(field.padding) & (1 << i))
|
||||
f((1 << i) * 8);
|
||||
assert(!(field.padding & ~0xF));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate an accessor struct with constructor for a flatbuffers struct.
|
||||
static void GenStruct(StructDef &struct_def, std::string *code_ptr) {
|
||||
static void GenStruct(const Parser &parser, StructDef &struct_def,
|
||||
std::string *code_ptr) {
|
||||
if (struct_def.generated) return;
|
||||
std::string &code = *code_ptr;
|
||||
|
||||
@@ -341,15 +375,12 @@ static void GenStruct(StructDef &struct_def, std::string *code_ptr) {
|
||||
it != struct_def.fields.vec.end();
|
||||
++it) {
|
||||
auto &field = **it;
|
||||
code += " " + GenTypeGet(field.value.type, " ", "", " ");
|
||||
code += " " + GenTypeGet(parser, field.value.type, " ", "", " ");
|
||||
code += field.name + "_;\n";
|
||||
if (field.padding) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (static_cast<int>(field.padding) & (1 << i))
|
||||
code += " int" + NumToString((1 << i) * 8) +
|
||||
"_t __padding" + NumToString(padding_id++) + ";\n";
|
||||
assert(!(field.padding & ~0xF));
|
||||
}
|
||||
GenPadding(field, [&code, &padding_id](int bits) {
|
||||
code += " int" + NumToString(bits) +
|
||||
"_t __padding" + NumToString(padding_id++) + ";\n";
|
||||
});
|
||||
}
|
||||
|
||||
// Generate a constructor that takes all fields as arguments.
|
||||
@@ -359,7 +390,8 @@ static void GenStruct(StructDef &struct_def, std::string *code_ptr) {
|
||||
++it) {
|
||||
auto &field = **it;
|
||||
if (it != struct_def.fields.vec.begin()) code += ", ";
|
||||
code += GenTypeGet(field.value.type, " ", "const ", " &") + field.name;
|
||||
code += GenTypeGet(parser, field.value.type, " ", "const ", " &");
|
||||
code += field.name;
|
||||
}
|
||||
code += ")\n : ";
|
||||
padding_id = 0;
|
||||
@@ -373,10 +405,23 @@ static void GenStruct(StructDef &struct_def, std::string *code_ptr) {
|
||||
code += "flatbuffers::EndianScalar(" + field.name + "))";
|
||||
else
|
||||
code += field.name + ")";
|
||||
if (field.padding)
|
||||
GenPadding(field, [&code, &padding_id](int bits) {
|
||||
(void)bits;
|
||||
code += ", __padding" + NumToString(padding_id++) + "(0)";
|
||||
});
|
||||
}
|
||||
code += " {}\n\n";
|
||||
code += " {";
|
||||
padding_id = 0;
|
||||
for (auto it = struct_def.fields.vec.begin();
|
||||
it != struct_def.fields.vec.end();
|
||||
++it) {
|
||||
auto &field = **it;
|
||||
GenPadding(field, [&code, &padding_id](int bits) {
|
||||
(void)bits;
|
||||
code += " (void)__padding" + NumToString(padding_id++) + ";";
|
||||
});
|
||||
}
|
||||
code += " }\n\n";
|
||||
|
||||
// Generate accessor methods of the form:
|
||||
// type name() const { return flatbuffers::EndianScalar(name_); }
|
||||
@@ -385,7 +430,7 @@ static void GenStruct(StructDef &struct_def, std::string *code_ptr) {
|
||||
++it) {
|
||||
auto &field = **it;
|
||||
GenComment(field.doc_comment, code_ptr, " ");
|
||||
code += " " + GenTypeGet(field.value.type, " ", "const ", " &");
|
||||
code += " " + GenTypeGet(parser, field.value.type, " ", "const ", " &");
|
||||
code += field.name + "() const { return ";
|
||||
if (IsScalar(field.value.type.base_type))
|
||||
code += "flatbuffers::EndianScalar(" + field.name + "_)";
|
||||
@@ -397,34 +442,73 @@ static void GenStruct(StructDef &struct_def, std::string *code_ptr) {
|
||||
code += NumToString(struct_def.bytesize) + ");\n\n";
|
||||
}
|
||||
|
||||
void GenerateNestedNameSpaces(Namespace *ns, std::string *code_ptr) {
|
||||
for (auto it = ns->components.begin(); it != ns->components.end(); ++it) {
|
||||
*code_ptr += "namespace " + *it + " {\n";
|
||||
}
|
||||
}
|
||||
|
||||
void CloseNestedNameSpaces(Namespace *ns, std::string *code_ptr) {
|
||||
for (auto it = ns->components.rbegin(); it != ns->components.rend(); ++it) {
|
||||
*code_ptr += "} // namespace " + *it + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cpp
|
||||
|
||||
// Iterate through all definitions we haven't generate code for (enums, structs,
|
||||
// and tables) and output them to a single file.
|
||||
std::string GenerateCPP(const Parser &parser, const std::string &include_guard_ident) {
|
||||
std::string GenerateCPP(const Parser &parser,
|
||||
const std::string &include_guard_ident,
|
||||
const GeneratorOptions &opts) {
|
||||
using namespace cpp;
|
||||
|
||||
// Generate code for all the enum declarations.
|
||||
std::string enum_code, enum_code_post;
|
||||
for (auto it = parser.enums_.vec.begin();
|
||||
it != parser.enums_.vec.end(); ++it) {
|
||||
GenEnum(**it, &enum_code, &enum_code_post);
|
||||
GenEnum(**it, &enum_code, &enum_code_post, opts);
|
||||
}
|
||||
|
||||
// Generate forward declarations for all structs/tables, since they may
|
||||
// have circular references.
|
||||
std::string forward_decl_code;
|
||||
std::string forward_decl_code_same_namespace;
|
||||
std::string forward_decl_code_other_namespace;
|
||||
Namespace *cur_name_space = nullptr;
|
||||
for (auto it = parser.structs_.vec.begin();
|
||||
it != parser.structs_.vec.end(); ++it) {
|
||||
if (!(*it)->generated)
|
||||
forward_decl_code += "struct " + (*it)->name + ";\n";
|
||||
auto &struct_def = **it;
|
||||
auto decl = "struct " + struct_def.name + ";\n";
|
||||
if (struct_def.defined_namespace == parser.namespaces_.back()) {
|
||||
forward_decl_code_same_namespace += decl;
|
||||
} else {
|
||||
// Wrap this decl in the correct namespace. Only open a namespace if
|
||||
// the adjacent one is different.
|
||||
// TODO: this could be done more intelligently, by sorting to
|
||||
// namespace path and only opening/closing what is necessary, but that's
|
||||
// quite a bit more complexity.
|
||||
if (cur_name_space != struct_def.defined_namespace) {
|
||||
if (cur_name_space) {
|
||||
CloseNestedNameSpaces(cur_name_space,
|
||||
&forward_decl_code_other_namespace);
|
||||
}
|
||||
GenerateNestedNameSpaces(struct_def.defined_namespace,
|
||||
&forward_decl_code_other_namespace);
|
||||
cur_name_space = struct_def.defined_namespace;
|
||||
}
|
||||
forward_decl_code_other_namespace += decl;
|
||||
}
|
||||
}
|
||||
if (cur_name_space) {
|
||||
CloseNestedNameSpaces(cur_name_space,
|
||||
&forward_decl_code_other_namespace);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if ((**it).fixed) GenStruct(**it, &decl_code);
|
||||
if ((**it).fixed) GenStruct(parser, **it, &decl_code);
|
||||
}
|
||||
for (auto it = parser.structs_.vec.begin();
|
||||
it != parser.structs_.vec.end(); ++it) {
|
||||
@@ -432,7 +516,7 @@ std::string GenerateCPP(const Parser &parser, const std::string &include_guard_i
|
||||
}
|
||||
|
||||
// Only output file-level code if there were any declarations.
|
||||
if (enum_code.length() || forward_decl_code.length() || decl_code.length()) {
|
||||
if (enum_code.length() || decl_code.length()) {
|
||||
std::string code;
|
||||
code = "// automatically generated by the FlatBuffers compiler,"
|
||||
" do not modify\n\n";
|
||||
@@ -440,8 +524,9 @@ std::string GenerateCPP(const Parser &parser, const std::string &include_guard_i
|
||||
// Generate include guard.
|
||||
std::string include_guard = "FLATBUFFERS_GENERATED_" + include_guard_ident;
|
||||
include_guard += "_";
|
||||
for (auto it = parser.name_space_.begin();
|
||||
it != parser.name_space_.end(); ++it) {
|
||||
auto name_space = parser.namespaces_.back();
|
||||
for (auto it = name_space->components.begin();
|
||||
it != name_space->components.end(); ++it) {
|
||||
include_guard += *it + "_";
|
||||
}
|
||||
include_guard += "H_";
|
||||
@@ -452,17 +537,17 @@ std::string GenerateCPP(const Parser &parser, const std::string &include_guard_i
|
||||
|
||||
code += "#include \"flatbuffers/flatbuffers.h\"\n\n";
|
||||
|
||||
// Generate nested namespaces.
|
||||
for (auto it = parser.name_space_.begin();
|
||||
it != parser.name_space_.end(); ++it) {
|
||||
code += "namespace " + *it + " {\n";
|
||||
}
|
||||
code += forward_decl_code_other_namespace;
|
||||
code += "\n";
|
||||
|
||||
GenerateNestedNameSpaces(name_space, &code);
|
||||
code += "\n";
|
||||
|
||||
code += forward_decl_code_same_namespace;
|
||||
code += "\n";
|
||||
|
||||
// Output the main declaration code from above.
|
||||
code += "\n";
|
||||
code += enum_code;
|
||||
code += forward_decl_code;
|
||||
code += "\n";
|
||||
code += decl_code;
|
||||
code += enum_code_post;
|
||||
|
||||
@@ -477,7 +562,7 @@ std::string GenerateCPP(const Parser &parser, const std::string &include_guard_i
|
||||
// The root verifier:
|
||||
code += "inline bool Verify";
|
||||
code += parser.root_struct_def->name;
|
||||
code += "Buffer(const flatbuffers::Verifier &verifier) { "
|
||||
code += "Buffer(flatbuffers::Verifier &verifier) { "
|
||||
"return verifier.VerifyBuffer<";
|
||||
code += parser.root_struct_def->name + ">(); }\n\n";
|
||||
|
||||
@@ -498,11 +583,7 @@ std::string GenerateCPP(const Parser &parser, const std::string &include_guard_i
|
||||
}
|
||||
}
|
||||
|
||||
// Close the namespaces.
|
||||
for (auto it = parser.name_space_.rbegin();
|
||||
it != parser.name_space_.rend(); ++it) {
|
||||
code += "} // namespace " + *it + "\n";
|
||||
}
|
||||
CloseNestedNameSpaces(name_space, &code);
|
||||
|
||||
// Close the include guard.
|
||||
code += "\n#endif // " + include_guard + "\n";
|
||||
@@ -516,8 +597,8 @@ std::string GenerateCPP(const Parser &parser, const std::string &include_guard_i
|
||||
bool GenerateCPP(const Parser &parser,
|
||||
const std::string &path,
|
||||
const std::string &file_name,
|
||||
const GeneratorOptions & /*opts*/) {
|
||||
auto code = GenerateCPP(parser, file_name);
|
||||
const GeneratorOptions &opts) {
|
||||
auto code = GenerateCPP(parser, file_name, opts);
|
||||
return !code.length() ||
|
||||
SaveFile((path + file_name + "_generated.h").c_str(), code, false);
|
||||
}
|
||||
|
||||
@@ -585,24 +585,24 @@ static bool SaveType(const Parser &parser, const Definition &def,
|
||||
bool needs_imports) {
|
||||
if (!classcode.length()) return true;
|
||||
|
||||
std::string name_space_name;
|
||||
std::string name_space_dir = path;
|
||||
for (auto it = parser.name_space_.begin();
|
||||
it != parser.name_space_.end(); ++it) {
|
||||
if (name_space_name.length()) {
|
||||
name_space_name += ".";
|
||||
name_space_dir += PATH_SEPARATOR;
|
||||
std::string namespace_name;
|
||||
std::string namespace_dir = path;
|
||||
auto &namespaces = parser.namespaces_.back()->components;
|
||||
for (auto it = namespaces.begin(); it != namespaces.end(); ++it) {
|
||||
if (namespace_name.length()) {
|
||||
namespace_name += ".";
|
||||
namespace_dir += PATH_SEPARATOR;
|
||||
}
|
||||
name_space_name = *it;
|
||||
name_space_dir += *it;
|
||||
mkdir(name_space_dir.c_str(), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
|
||||
namespace_name = *it;
|
||||
namespace_dir += *it;
|
||||
mkdir(namespace_dir.c_str(), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
|
||||
}
|
||||
|
||||
|
||||
std::string code = "";
|
||||
BeginFile(name_space_name, needs_imports, &code);
|
||||
BeginFile(namespace_name, needs_imports, &code);
|
||||
code += classcode;
|
||||
std::string filename = name_space_dir + PATH_SEPARATOR + def.name + ".go";
|
||||
std::string filename = namespace_dir + PATH_SEPARATOR + def.name + ".go";
|
||||
return SaveFile(filename.c_str(), code, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,6 @@
|
||||
#include "flatbuffers/idl.h"
|
||||
#include "flatbuffers/util.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#define mkdir(n, m) _mkdir(n)
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
namespace flatbuffers {
|
||||
namespace java {
|
||||
|
||||
@@ -161,9 +154,7 @@ static void GenStructBody(const StructDef &struct_def, std::string *code_ptr,
|
||||
}
|
||||
}
|
||||
|
||||
static void GenStruct(StructDef &struct_def,
|
||||
std::string *code_ptr,
|
||||
StructDef *root_struct_def) {
|
||||
static void GenStruct(StructDef &struct_def, std::string *code_ptr) {
|
||||
if (struct_def.generated) return;
|
||||
std::string &code = *code_ptr;
|
||||
|
||||
@@ -177,9 +168,9 @@ static void GenStruct(StructDef &struct_def,
|
||||
code += "public class " + struct_def.name + " extends ";
|
||||
code += struct_def.fixed ? "Struct" : "Table";
|
||||
code += " {\n";
|
||||
if (&struct_def == root_struct_def) {
|
||||
// Generate a special accessor for the table that has been declared as
|
||||
// the root type.
|
||||
if (!struct_def.fixed) {
|
||||
// Generate a special accessor for the table that when used as the root
|
||||
// of a FlatBuffer
|
||||
code += " public static " + struct_def.name + " getRootAs";
|
||||
code += struct_def.name;
|
||||
code += "(ByteBuffer _bb, int offset) { ";
|
||||
@@ -341,27 +332,27 @@ static bool SaveClass(const Parser &parser, const Definition &def,
|
||||
bool needs_imports) {
|
||||
if (!classcode.length()) return true;
|
||||
|
||||
std::string name_space_java;
|
||||
std::string name_space_dir = path;
|
||||
for (auto it = parser.name_space_.begin();
|
||||
it != parser.name_space_.end(); ++it) {
|
||||
if (name_space_java.length()) {
|
||||
name_space_java += ".";
|
||||
name_space_dir += kPathSeparator;
|
||||
std::string namespace_java;
|
||||
std::string namespace_dir = path;
|
||||
auto &namespaces = parser.namespaces_.back()->components;
|
||||
for (auto it = namespaces.begin(); it != namespaces.end(); ++it) {
|
||||
if (namespace_java.length()) {
|
||||
namespace_java += ".";
|
||||
namespace_dir += kPathSeparator;
|
||||
}
|
||||
name_space_java += *it;
|
||||
name_space_dir += *it;
|
||||
mkdir(name_space_dir.c_str(), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
|
||||
namespace_java += *it;
|
||||
namespace_dir += *it;
|
||||
}
|
||||
EnsureDirExists(namespace_dir);
|
||||
|
||||
std::string code = "// automatically generated, do not modify\n\n";
|
||||
code += "package " + name_space_java + ";\n\n";
|
||||
code += "package " + namespace_java + ";\n\n";
|
||||
if (needs_imports) {
|
||||
code += "import java.nio.*;\nimport java.lang.*;\nimport java.util.*;\n";
|
||||
code += "import flatbuffers.*;\n\n";
|
||||
}
|
||||
code += classcode;
|
||||
auto filename = name_space_dir + kPathSeparator + def.name + ".java";
|
||||
auto filename = namespace_dir + kPathSeparator + def.name + ".java";
|
||||
return SaveFile(filename.c_str(), code, false);
|
||||
}
|
||||
|
||||
@@ -384,7 +375,7 @@ bool GenerateJava(const Parser &parser,
|
||||
for (auto it = parser.structs_.vec.begin();
|
||||
it != parser.structs_.vec.end(); ++it) {
|
||||
std::string declcode;
|
||||
GenStruct(**it, &declcode, parser.root_struct_def);
|
||||
GenStruct(**it, &declcode);
|
||||
if (!SaveClass(parser, **it, declcode, path, true))
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,12 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
|
||||
|
||||
// If indentation is less than 0, that indicates we don't want any newlines
|
||||
// either.
|
||||
const char *NewLine(int indent_step) {
|
||||
return indent_step >= 0 ? "\n" : "";
|
||||
const char *NewLine(const GeneratorOptions &opts) {
|
||||
return opts.indent_step >= 0 ? "\n" : "";
|
||||
}
|
||||
|
||||
int Indent(const GeneratorOptions &opts) {
|
||||
return std::max(opts.indent_step, 0);
|
||||
}
|
||||
|
||||
// Output an identifier with or without quotes depending on strictness.
|
||||
@@ -65,21 +69,21 @@ template<typename T> void PrintVector(const Vector<T> &v, Type type,
|
||||
std::string *_text) {
|
||||
std::string &text = *_text;
|
||||
text += "[";
|
||||
text += NewLine(opts.indent_step);
|
||||
text += NewLine(opts);
|
||||
for (uoffset_t i = 0; i < v.Length(); i++) {
|
||||
if (i) {
|
||||
text += ",";
|
||||
text += NewLine(opts.indent_step);
|
||||
text += NewLine(opts);
|
||||
}
|
||||
text.append(indent + opts.indent_step, ' ');
|
||||
text.append(indent + Indent(opts), ' ');
|
||||
if (IsStruct(type))
|
||||
Print(v.GetStructFromOffset(i * type.struct_def->bytesize), type,
|
||||
indent + opts.indent_step, nullptr, opts, _text);
|
||||
indent + Indent(opts), nullptr, opts, _text);
|
||||
else
|
||||
Print(v.Get(i), type, indent + opts.indent_step, nullptr,
|
||||
Print(v.Get(i), type, indent + Indent(opts), nullptr,
|
||||
opts, _text);
|
||||
}
|
||||
text += NewLine(opts.indent_step);
|
||||
text += NewLine(opts);
|
||||
text.append(indent, ' ');
|
||||
text += "]";
|
||||
}
|
||||
@@ -93,15 +97,29 @@ static void EscapeString(const String &s, std::string *_text) {
|
||||
case '\n': text += "\\n"; break;
|
||||
case '\t': text += "\\t"; break;
|
||||
case '\r': text += "\\r"; break;
|
||||
case '\b': text += "\\b"; break;
|
||||
case '\f': text += "\\f"; break;
|
||||
case '\"': text += "\\\""; break;
|
||||
case '\\': text += "\\\\"; break;
|
||||
default:
|
||||
if (c >= ' ' && c <= '~') {
|
||||
text += c;
|
||||
} else {
|
||||
auto u = static_cast<unsigned char>(c);
|
||||
text += "\\x";
|
||||
text += IntToStringHex(u);
|
||||
// Not printable ASCII data. Let's see if it's valid UTF-8 first:
|
||||
const char *utf8 = s.c_str() + i;
|
||||
int ucc = FromUTF8(&utf8);
|
||||
if (ucc >= 0x80 && ucc <= 0xFFFF) {
|
||||
// Parses as Unicode within JSON's \uXXXX range, so use that.
|
||||
text += "\\u";
|
||||
text += IntToStringHex(ucc, 4);
|
||||
// Skip past characters recognized.
|
||||
i = static_cast<uoffset_t>(utf8 - s.c_str() - 1);
|
||||
} else {
|
||||
// It's either unprintable ASCII, arbitrary binary, or Unicode data
|
||||
// that doesn't fit \uXXXX, so use \xXX escape code instead.
|
||||
text += "\\x";
|
||||
text += IntToStringHex(static_cast<uint8_t>(c), 2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -202,15 +220,15 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
|
||||
if (fieldout++) {
|
||||
text += ",";
|
||||
}
|
||||
text += NewLine(opts.indent_step);
|
||||
text.append(indent + opts.indent_step, ' ');
|
||||
text += NewLine(opts);
|
||||
text.append(indent + Indent(opts), ' ');
|
||||
OutputIdentifier(fd.name, opts, _text);
|
||||
text += ": ";
|
||||
switch (fd.value.type.base_type) {
|
||||
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE) \
|
||||
case BASE_TYPE_ ## ENUM: \
|
||||
GenField<CTYPE>(fd, table, struct_def.fixed, \
|
||||
opts, indent + opts.indent_step, _text); \
|
||||
opts, indent + Indent(opts), _text); \
|
||||
break;
|
||||
FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD)
|
||||
#undef FLATBUFFERS_TD
|
||||
@@ -219,7 +237,7 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
|
||||
case BASE_TYPE_ ## ENUM:
|
||||
FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD)
|
||||
#undef FLATBUFFERS_TD
|
||||
GenFieldOffset(fd, table, struct_def.fixed, indent + opts.indent_step,
|
||||
GenFieldOffset(fd, table, struct_def.fixed, indent + Indent(opts),
|
||||
union_sd, opts, _text);
|
||||
break;
|
||||
}
|
||||
@@ -231,7 +249,7 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
|
||||
}
|
||||
}
|
||||
}
|
||||
text += NewLine(opts.indent_step);
|
||||
text += NewLine(opts);
|
||||
text.append(indent, ' ');
|
||||
text += "}";
|
||||
}
|
||||
@@ -247,7 +265,7 @@ void GenerateText(const Parser &parser, const void *flatbuffer,
|
||||
0,
|
||||
opts,
|
||||
_text);
|
||||
text += NewLine(opts.indent_step);
|
||||
text += NewLine(opts);
|
||||
}
|
||||
|
||||
} // namespace flatbuffers
|
||||
|
||||
@@ -83,7 +83,8 @@ template<> inline Offset<void> atot<Offset<void>>(const char *s) {
|
||||
TD(NameSpace, 265, "namespace") \
|
||||
TD(RootType, 266, "root_type") \
|
||||
TD(FileIdentifier, 267, "file_identifier") \
|
||||
TD(FileExtension, 268, "file_extension")
|
||||
TD(FileExtension, 268, "file_extension") \
|
||||
TD(Include, 269, "include")
|
||||
#ifdef __GNUC__
|
||||
__extension__ // Stop GCC complaining about trailing comma with -Wpendantic.
|
||||
#endif
|
||||
@@ -107,13 +108,24 @@ static std::string TokenToString(int t) {
|
||||
};
|
||||
if (t < 256) { // A single ascii char token.
|
||||
std::string s;
|
||||
s.append(1, t);
|
||||
s.append(1, static_cast<char>(t));
|
||||
return s;
|
||||
} else { // Other tokens.
|
||||
return tokens[t - 256];
|
||||
}
|
||||
}
|
||||
|
||||
// Parses exactly nibbles worth of hex digits into a number, or error.
|
||||
int64_t Parser::ParseHexNum(int nibbles) {
|
||||
for (int i = 0; i < nibbles; i++)
|
||||
if (!isxdigit(cursor_[i]))
|
||||
Error("escape code must be followed by " + NumToString(nibbles) +
|
||||
" hex digits");
|
||||
auto val = StringToInt(cursor_, 16);
|
||||
cursor_ += nibbles;
|
||||
return val;
|
||||
}
|
||||
|
||||
void Parser::Next() {
|
||||
doc_comment_.clear();
|
||||
bool seen_newline = false;
|
||||
@@ -141,8 +153,21 @@ void Parser::Next() {
|
||||
case 'n': attribute_ += '\n'; cursor_++; break;
|
||||
case 't': attribute_ += '\t'; cursor_++; break;
|
||||
case 'r': attribute_ += '\r'; cursor_++; break;
|
||||
case 'b': attribute_ += '\b'; cursor_++; break;
|
||||
case 'f': attribute_ += '\f'; cursor_++; break;
|
||||
case '\"': attribute_ += '\"'; cursor_++; break;
|
||||
case '\\': attribute_ += '\\'; cursor_++; break;
|
||||
case '/': attribute_ += '/'; cursor_++; break;
|
||||
case 'x': { // Not in the JSON standard
|
||||
cursor_++;
|
||||
attribute_ += static_cast<char>(ParseHexNum(2));
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
cursor_++;
|
||||
ToUTF8(static_cast<int>(ParseHexNum(4)), &attribute_);
|
||||
break;
|
||||
}
|
||||
default: Error("unknown escape code in string constant"); break;
|
||||
}
|
||||
} else { // printable chars + UTF-8 bytes
|
||||
@@ -196,6 +221,7 @@ void Parser::Next() {
|
||||
if (attribute_ == "union") { token_ = kTokenUnion; return; }
|
||||
if (attribute_ == "namespace") { token_ = kTokenNameSpace; return; }
|
||||
if (attribute_ == "root_type") { token_ = kTokenRootType; return; }
|
||||
if (attribute_ == "include") { token_ = kTokenInclude; return; }
|
||||
if (attribute_ == "file_identifier") {
|
||||
token_ = kTokenFileIdentifier;
|
||||
return;
|
||||
@@ -338,6 +364,8 @@ void Parser::ParseField(StructDef &struct_def) {
|
||||
|
||||
if (token_ == '=') {
|
||||
Next();
|
||||
if (!IsScalar(type.base_type))
|
||||
Error("default values currently only supported for scalars");
|
||||
ParseSingleValue(field.value);
|
||||
}
|
||||
|
||||
@@ -429,6 +457,10 @@ uoffset_t Parser::ParseTable(const StructDef &struct_def) {
|
||||
|| struct_def.fields.vec[fieldn] != field)) {
|
||||
Error("struct field appearing out of order: " + name);
|
||||
}
|
||||
for (auto it = field_stack_.rbegin();
|
||||
it != field_stack_.rbegin() + fieldn; ++it) {
|
||||
if (it->second == field) Error("field already set: " + name);
|
||||
}
|
||||
Expect(':');
|
||||
Value val = field->value;
|
||||
ParseAnyValue(val, field);
|
||||
@@ -645,6 +677,7 @@ StructDef *Parser::LookupCreateStruct(const std::string &name) {
|
||||
structs_.Add(name, struct_def);
|
||||
struct_def->name = name;
|
||||
struct_def->predecl = true;
|
||||
struct_def->defined_namespace = namespaces_.back();
|
||||
}
|
||||
return struct_def;
|
||||
}
|
||||
@@ -698,7 +731,7 @@ void Parser::ParseEnum(bool is_union) {
|
||||
if (prevsize && enum_def.vals.vec[prevsize - 1]->value >= ev.value)
|
||||
Error("enum values must be specified in ascending order");
|
||||
}
|
||||
} while (IsNext(','));
|
||||
} while (IsNext(',') && token_ != '}');
|
||||
Expect('}');
|
||||
if (enum_def.attributes.Lookup("bit_flags")) {
|
||||
for (auto it = enum_def.vals.vec.begin(); it != enum_def.vals.vec.end();
|
||||
@@ -773,6 +806,31 @@ void Parser::ParseDecl() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check that no identifiers clash with auto generated fields.
|
||||
// This is not an ideal situation, but should occur very infrequently,
|
||||
// and allows us to keep using very readable names for type & length fields
|
||||
// without inducing compile errors.
|
||||
auto CheckClash = [&fields, &struct_def](const char *suffix,
|
||||
BaseType basetype) {
|
||||
auto len = strlen(suffix);
|
||||
for (auto it = fields.begin(); it != fields.end(); ++it) {
|
||||
auto &name = (*it)->name;
|
||||
if (name.length() > len &&
|
||||
name.compare(name.length() - len, len, suffix) == 0 &&
|
||||
(*it)->value.type.base_type != BASE_TYPE_UTYPE) {
|
||||
auto field = struct_def.fields.Lookup(
|
||||
name.substr(0, name.length() - len));
|
||||
if (field && field->value.type.base_type == basetype)
|
||||
Error("Field " + name +
|
||||
" would clash with generated functions for field " +
|
||||
field->name);
|
||||
}
|
||||
}
|
||||
};
|
||||
CheckClash("_type", BASE_TYPE_UNION);
|
||||
CheckClash("Type", BASE_TYPE_UNION);
|
||||
CheckClash("_length", BASE_TYPE_VECTOR);
|
||||
CheckClash("Length", BASE_TYPE_VECTOR);
|
||||
Expect('}');
|
||||
}
|
||||
|
||||
@@ -781,19 +839,66 @@ bool Parser::SetRootType(const char *name) {
|
||||
return root_struct_def != nullptr;
|
||||
}
|
||||
|
||||
bool Parser::Parse(const char *source) {
|
||||
void Parser::MarkGenerated() {
|
||||
// Since the Parser object retains definitions across files, we must
|
||||
// ensure we only output code for definitions once, in the file they are first
|
||||
// declared. This function marks all existing definitions as having already
|
||||
// been generated.
|
||||
for (auto it = enums_.vec.begin();
|
||||
it != enums_.vec.end(); ++it) {
|
||||
(*it)->generated = true;
|
||||
}
|
||||
for (auto it = structs_.vec.begin();
|
||||
it != structs_.vec.end(); ++it) {
|
||||
(*it)->generated = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Parser::Parse(const char *source, const char *filepath) {
|
||||
included_files_[filepath] = true;
|
||||
// This is the starting point to reset to if we interrupted our parsing
|
||||
// to deal with an include:
|
||||
restart_parse_after_include:
|
||||
source_ = cursor_ = source;
|
||||
line_ = 1;
|
||||
error_.clear();
|
||||
builder_.Clear();
|
||||
try {
|
||||
Next();
|
||||
// Includes must come first:
|
||||
while (IsNext(kTokenInclude)) {
|
||||
auto name = attribute_;
|
||||
Expect(kTokenStringConstant);
|
||||
auto path = StripFileName(filepath);
|
||||
if (path.length()) name = path + kPathSeparator + name;
|
||||
if (included_files_.find(name) == included_files_.end()) {
|
||||
// We found an include file that we have not parsed yet.
|
||||
// Load it and parse it.
|
||||
std::string contents;
|
||||
if (!LoadFile(name.c_str(), true, &contents))
|
||||
Error("unable to load include file: " + name);
|
||||
Parse(contents.c_str(), name.c_str());
|
||||
// Any errors, we're done.
|
||||
if (error_.length()) return false;
|
||||
// We do not want to output code for any included files:
|
||||
MarkGenerated();
|
||||
// This is the easiest way to continue this file after an include:
|
||||
// instead of saving and restoring all the state, we simply start the
|
||||
// file anew. This will cause it to encounter the same include statement
|
||||
// again, but this time it will skip it, because it was entered into
|
||||
// included_files_.
|
||||
goto restart_parse_after_include;
|
||||
}
|
||||
Expect(';');
|
||||
}
|
||||
// Now parse all other kinds of declarations:
|
||||
while (token_ != kTokenEof) {
|
||||
if (token_ == kTokenNameSpace) {
|
||||
Next();
|
||||
name_space_.clear();
|
||||
auto ns = new Namespace();
|
||||
namespaces_.push_back(ns);
|
||||
for (;;) {
|
||||
name_space_.push_back(attribute_);
|
||||
ns->components.push_back(attribute_);
|
||||
Expect(kTokenIdentifier);
|
||||
if (!IsNext('.')) break;
|
||||
}
|
||||
@@ -832,6 +937,8 @@ bool Parser::Parse(const char *source) {
|
||||
file_extension_ = attribute_;
|
||||
Expect(kTokenStringConstant);
|
||||
Expect(';');
|
||||
} else if(token_ == kTokenInclude) {
|
||||
Error("includes must come before declarations");
|
||||
} else {
|
||||
ParseDecl();
|
||||
}
|
||||
|
||||
@@ -103,9 +103,12 @@ class JavaTest {
|
||||
}
|
||||
|
||||
// Test it:
|
||||
|
||||
TestBuffer(fbb.dataBuffer(), fbb.dataStart());
|
||||
|
||||
// Make sure it also works with read only ByteBuffers. This is slower, since
|
||||
// creating strings incurs an additional copy (see Table.__string).
|
||||
TestBuffer(fbb.dataBuffer().asReadOnlyBuffer(), fbb.dataStart());
|
||||
|
||||
System.out.println("FlatBuffers test: completed successfully");
|
||||
}
|
||||
|
||||
|
||||
5
tests/include_test1.fbs
Normal file
5
tests/include_test1.fbs
Normal file
@@ -0,0 +1,5 @@
|
||||
include "include_test2.fbs";
|
||||
include "include_test2.fbs"; // should be skipped
|
||||
include "include_test1.fbs"; // should be skipped
|
||||
|
||||
|
||||
9
tests/include_test2.fbs
Normal file
9
tests/include_test2.fbs
Normal file
@@ -0,0 +1,9 @@
|
||||
include "include_test2.fbs"; // should be skipped
|
||||
|
||||
namespace MyGame.OtherNameSpace;
|
||||
|
||||
enum FromInclude:long { IncludeVal }
|
||||
|
||||
struct Unused {}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// example IDL file
|
||||
|
||||
include "include_test1.fbs";
|
||||
|
||||
namespace MyGame.Example;
|
||||
|
||||
enum Color:byte (bit_flags) { Red = 0, Green, Blue = 3 }
|
||||
enum Color:byte (bit_flags) { Red = 0, Green, Blue = 3, }
|
||||
|
||||
union Any { Monster } // TODO: add more elements
|
||||
|
||||
|
||||
@@ -5,9 +5,19 @@
|
||||
|
||||
#include "flatbuffers/flatbuffers.h"
|
||||
|
||||
namespace MyGame {
|
||||
namespace OtherNameSpace {
|
||||
struct Unused;
|
||||
} // namespace OtherNameSpace
|
||||
} // namespace MyGame
|
||||
|
||||
namespace MyGame {
|
||||
namespace Example {
|
||||
|
||||
struct Test;
|
||||
struct Vec3;
|
||||
struct Monster;
|
||||
|
||||
enum {
|
||||
Color_Red = 1,
|
||||
Color_Green = 2,
|
||||
@@ -33,11 +43,7 @@ inline const char **EnumNamesAny() {
|
||||
|
||||
inline const char *EnumNameAny(int e) { return EnumNamesAny()[e]; }
|
||||
|
||||
bool VerifyAny(const flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type);
|
||||
|
||||
struct Test;
|
||||
struct Vec3;
|
||||
struct Monster;
|
||||
bool VerifyAny(flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type);
|
||||
|
||||
MANUALLY_ALIGNED_STRUCT(2) Test {
|
||||
private:
|
||||
@@ -47,7 +53,7 @@ MANUALLY_ALIGNED_STRUCT(2) Test {
|
||||
|
||||
public:
|
||||
Test(int16_t a, int8_t b)
|
||||
: a_(flatbuffers::EndianScalar(a)), b_(flatbuffers::EndianScalar(b)), __padding0(0) {}
|
||||
: a_(flatbuffers::EndianScalar(a)), b_(flatbuffers::EndianScalar(b)), __padding0(0) { (void)__padding0; }
|
||||
|
||||
int16_t a() const { return flatbuffers::EndianScalar(a_); }
|
||||
int8_t b() const { return flatbuffers::EndianScalar(b_); }
|
||||
@@ -68,7 +74,7 @@ MANUALLY_ALIGNED_STRUCT(16) Vec3 {
|
||||
|
||||
public:
|
||||
Vec3(float x, float y, float z, double test1, int8_t test2, const Test &test3)
|
||||
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), z_(flatbuffers::EndianScalar(z)), __padding0(0), test1_(flatbuffers::EndianScalar(test1)), test2_(flatbuffers::EndianScalar(test2)), __padding1(0), test3_(test3), __padding2(0) {}
|
||||
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), z_(flatbuffers::EndianScalar(z)), __padding0(0), test1_(flatbuffers::EndianScalar(test1)), test2_(flatbuffers::EndianScalar(test2)), __padding1(0), test3_(test3), __padding2(0) { (void)__padding0; (void)__padding1; (void)__padding2; }
|
||||
|
||||
float x() const { return flatbuffers::EndianScalar(x_); }
|
||||
float y() const { return flatbuffers::EndianScalar(y_); }
|
||||
@@ -96,8 +102,8 @@ struct Monster : private flatbuffers::Table {
|
||||
const flatbuffers::Vector<uint8_t> *testnestedflatbuffer() const { return GetPointer<const flatbuffers::Vector<uint8_t> *>(30); }
|
||||
const Monster *testnestedflatbuffer_nested_root() { return flatbuffers::GetRoot<Monster>(testnestedflatbuffer()->Data()); }
|
||||
const Monster *testempty() const { return GetPointer<const Monster *>(32); }
|
||||
bool Verify(const flatbuffers::Verifier &verifier) const {
|
||||
return VerifyTable(verifier) &&
|
||||
bool Verify(flatbuffers::Verifier &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
VerifyField<Vec3>(verifier, 4 /* pos */) &&
|
||||
VerifyField<int16_t>(verifier, 6 /* mana */) &&
|
||||
VerifyField<int16_t>(verifier, 8 /* hp */) &&
|
||||
@@ -122,7 +128,8 @@ struct Monster : private flatbuffers::Table {
|
||||
VerifyField<flatbuffers::uoffset_t>(verifier, 30 /* testnestedflatbuffer */) &&
|
||||
verifier.Verify(testnestedflatbuffer()) &&
|
||||
VerifyField<flatbuffers::uoffset_t>(verifier, 32 /* testempty */) &&
|
||||
verifier.VerifyTable(testempty());
|
||||
verifier.VerifyTable(testempty()) &&
|
||||
verifier.EndTable();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -181,7 +188,7 @@ inline flatbuffers::Offset<Monster> CreateMonster(flatbuffers::FlatBufferBuilder
|
||||
return builder_.Finish();
|
||||
}
|
||||
|
||||
bool VerifyAny(const flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type) {
|
||||
bool VerifyAny(flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type) {
|
||||
switch (type) {
|
||||
case Any_NONE: return true;
|
||||
case Any_Monster: return verifier.VerifyTable(reinterpret_cast<const Monster *>(union_obj));
|
||||
@@ -191,7 +198,7 @@ bool VerifyAny(const flatbuffers::Verifier &verifier, const void *union_obj, uin
|
||||
|
||||
inline const Monster *GetMonster(const void *buf) { return flatbuffers::GetRoot<Monster>(buf); }
|
||||
|
||||
inline bool VerifyMonsterBuffer(const flatbuffers::Verifier &verifier) { return verifier.VerifyBuffer<Monster>(); }
|
||||
inline bool VerifyMonsterBuffer(flatbuffers::Verifier &verifier) { return verifier.VerifyBuffer<Monster>(); }
|
||||
|
||||
inline void FinishMonsterBuffer(flatbuffers::FlatBufferBuilder &fbb, flatbuffers::Offset<Monster> root) { fbb.Finish(root, "MONS"); }
|
||||
|
||||
|
||||
@@ -192,8 +192,8 @@ void ParseAndGenerateTextTest() {
|
||||
|
||||
// parse schema first, so we can use it to parse the data after
|
||||
flatbuffers::Parser parser;
|
||||
TEST_EQ(parser.Parse(schemafile.c_str()), true);
|
||||
TEST_EQ(parser.Parse(jsonfile.c_str()), true);
|
||||
TEST_EQ(parser.Parse(schemafile.c_str(), "tests/"), true);
|
||||
TEST_EQ(parser.Parse(jsonfile.c_str(), "tests/"), true);
|
||||
|
||||
// here, parser.builder_ contains a binary buffer that is the parsed data.
|
||||
|
||||
@@ -406,12 +406,12 @@ void FuzzTest2() {
|
||||
|
||||
// Parse the schema, parse the generated data, then generate text back
|
||||
// from the binary and compare against the original.
|
||||
TEST_EQ(parser.Parse(schema.c_str()), true);
|
||||
TEST_EQ(parser.Parse(schema.c_str(), ""), true);
|
||||
|
||||
const std::string &json =
|
||||
definitions[num_definitions - 1].instances[0] + "\n";
|
||||
|
||||
TEST_EQ(parser.Parse(json.c_str()), true);
|
||||
TEST_EQ(parser.Parse(json.c_str(), ""), true);
|
||||
|
||||
std::string jsongen;
|
||||
flatbuffers::GeneratorOptions opts;
|
||||
@@ -443,7 +443,7 @@ void FuzzTest2() {
|
||||
// Test that parser errors are actually generated.
|
||||
void TestError(const char *src, const char *error_substr) {
|
||||
flatbuffers::Parser parser;
|
||||
TEST_EQ(parser.Parse(src), false); // Must signal error
|
||||
TEST_EQ(parser.Parse(src, ""), false); // Must signal error
|
||||
// Must be the error we're expecting
|
||||
TEST_NOTNULL(strstr(parser.error_.c_str(), error_substr));
|
||||
}
|
||||
@@ -488,6 +488,9 @@ void ErrorTest() {
|
||||
TestError("struct X { Y:int; } root_type X;", "a table");
|
||||
TestError("union X { Y }", "referenced");
|
||||
TestError("union Z { X } struct X { Y:int; }", "only tables");
|
||||
TestError("table X { Y:[int]; YLength:int; }", "clash");
|
||||
TestError("table X { Y:string = 1; }", "scalar");
|
||||
TestError("table X { Y:byte; } root_type X; { Y:1, Y:2 }", "already set");
|
||||
}
|
||||
|
||||
// Additional parser testing not covered elsewhere.
|
||||
@@ -495,10 +498,10 @@ void ScientificTest() {
|
||||
flatbuffers::Parser parser;
|
||||
|
||||
// Simple schema.
|
||||
TEST_EQ(parser.Parse("table X { Y:float; } root_type X;"), true);
|
||||
TEST_EQ(parser.Parse("table X { Y:float; } root_type X;", ""), true);
|
||||
|
||||
// Test scientific notation numbers.
|
||||
TEST_EQ(parser.Parse("{ Y:0.0314159e+2 }"), true);
|
||||
TEST_EQ(parser.Parse("{ Y:0.0314159e+2 }", ""), true);
|
||||
auto root = flatbuffers::GetRoot<float>(parser.builder_.GetBufferPointer());
|
||||
// root will point to the table, which is a 32bit vtable offset followed
|
||||
// by a float:
|
||||
@@ -509,14 +512,26 @@ void EnumStringsTest() {
|
||||
flatbuffers::Parser parser1;
|
||||
TEST_EQ(parser1.Parse("enum E:byte { A, B, C } table T { F:[E]; }"
|
||||
"root_type T;"
|
||||
"{ F:[ A, B, \"C\", \"A B C\" ] }"), true);
|
||||
"{ F:[ A, B, \"C\", \"A B C\" ] }", ""), true);
|
||||
flatbuffers::Parser parser2;
|
||||
TEST_EQ(parser2.Parse("enum E:byte { A, B, C } table T { F:[int]; }"
|
||||
"root_type T;"
|
||||
"{ F:[ \"E.C\", \"E.A E.B E.C\" ] }"), true);
|
||||
"{ F:[ \"E.C\", \"E.A E.B E.C\" ] }", ""), true);
|
||||
}
|
||||
|
||||
|
||||
void UnicodeTest() {
|
||||
flatbuffers::Parser parser;
|
||||
TEST_EQ(parser.Parse("table T { F:string; }"
|
||||
"root_type T;"
|
||||
"{ F:\"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC"
|
||||
"\\u5225\\u30B5\\u30A4\\u30C8\\x01\\x80\" }", ""), true);
|
||||
std::string jsongen;
|
||||
flatbuffers::GeneratorOptions opts;
|
||||
opts.indent_step = -1;
|
||||
GenerateText(parser, parser.builder_.GetBufferPointer(), opts, &jsongen);
|
||||
TEST_EQ(jsongen == "{F: \"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC"
|
||||
"\\u5225\\u30B5\\u30A4\\u30C8\\x01\\x80\"}", true);
|
||||
}
|
||||
|
||||
int main(int /*argc*/, const char * /*argv*/[]) {
|
||||
// Run our various test suites:
|
||||
@@ -534,6 +549,7 @@ int main(int /*argc*/, const char * /*argv*/[]) {
|
||||
ErrorTest();
|
||||
ScientificTest();
|
||||
EnumStringsTest();
|
||||
UnicodeTest();
|
||||
|
||||
if (!testing_fails) {
|
||||
TEST_OUTPUT_LINE("ALL TESTS PASSED");
|
||||
|
||||
Reference in New Issue
Block a user