The parser and flatc now allow include directories to be specified.

Bug: 17139854
Change-Id: I0eac65d054951e00a8811ad1d80ba8c37012dbf0
Tested: on Linux.
parent cb58fc6f
...@@ -53,7 +53,7 @@ $(document).ready(function(){initNavTree('md__compiler.html','');}); ...@@ -53,7 +53,7 @@ $(document).ready(function(){initNavTree('md__compiler.html','');});
<div class="title">Using the schema compiler </div> </div> <div class="title">Using the schema compiler </div> </div>
</div><!--header--> </div><!--header-->
<div class="contents"> <div class="contents">
<div class="textblock"><p>Usage: </p><pre class="fragment">flatc [ -c ] [ -j ] [ -b ] [ -t ] [ -o PATH ] [ -S ] FILES... <div class="textblock"><p>Usage: </p><pre class="fragment">flatc [ -c ] [ -j ] [ -b ] [ -t ] [ -o PATH ] [ -I PATH ] [ -S ] FILES...
[ -- FILES...] [ -- FILES...]
</pre><p>The files are read and parsed in order, and can contain either schemas or data (see below). Later files can make use of definitions in earlier files.</p> </pre><p>The files are read and parsed in order, and can contain either schemas or data (see below). Later files can make use of definitions in earlier files.</p>
<p><code>--</code> indicates that the following files are binary files in FlatBuffer format conforming to the schema(s) indicated before it. Incompatible binary files currently will give unpredictable results (!)</p> <p><code>--</code> indicates that the following files are binary files in FlatBuffer format conforming to the schema(s) indicated before it. Incompatible binary files currently will give unpredictable results (!)</p>
...@@ -64,6 +64,7 @@ $(document).ready(function(){initNavTree('md__compiler.html','');}); ...@@ -64,6 +64,7 @@ $(document).ready(function(){initNavTree('md__compiler.html','');});
<li><code>-b</code> : If data is contained in this file, generate a <code>filename.bin</code> containing the binary flatbuffer.</li> <li><code>-b</code> : If data is contained in this file, generate a <code>filename.bin</code> containing the binary flatbuffer.</li>
<li><code>-t</code> : If data is contained in this file, generate a <code>filename.json</code> representing the data in the flatbuffer.</li> <li><code>-t</code> : If data is contained in this file, generate a <code>filename.json</code> representing the data in the flatbuffer.</li>
<li><code>-o PATH</code> : Output all generated files to PATH (either absolute, or relative to the current directory). If omitted, PATH will be the current directory. PATH should end in your systems path separator, e.g. <code>/</code> or <code>\</code>.</li> <li><code>-o PATH</code> : Output all generated files to PATH (either absolute, or relative to the current directory). If omitted, PATH will be the current directory. PATH should end in your systems path separator, e.g. <code>/</code> or <code>\</code>.</li>
<li><code>-I PATH</code> : when encountering <code>include</code> statements, attempt to load the files from this path. Paths will be tried in the order given, and if all fail (or none are specified) it will try to load relative to the path of the schema file being parsed.</li>
<li><code>-S</code> : Generate strict JSON (field names are enclosed in quotes). By default, no quotes are generated.</li> <li><code>-S</code> : Generate strict JSON (field names are enclosed in quotes). By default, no quotes are generated.</li>
<li><code>-P</code> : Don't prefix enum values in generated C++ by their enum type. </li> <li><code>-P</code> : Don't prefix enum values in generated C++ by their enum type. </li>
</ul> </ul>
......
...@@ -120,6 +120,7 @@ assert(inv-&gt;Get(9) == 9); ...@@ -120,6 +120,7 @@ assert(inv-&gt;Get(9) == 9);
<p>Load text (either a schema or json) into an in-memory buffer (there is a convenient <code>LoadFile()</code> utility function in <code>flatbuffers/util.h</code> if you wish). Construct a parser: </p><pre class="fragment">flatbuffers::Parser parser; <p>Load text (either a schema or json) into an in-memory buffer (there is a convenient <code>LoadFile()</code> utility function in <code>flatbuffers/util.h</code> if you wish). Construct a parser: </p><pre class="fragment">flatbuffers::Parser parser;
</pre><p>Now you can parse any number of text files in sequence: </p><pre class="fragment">parser.Parse(text_file.c_str()); </pre><p>Now you can parse any number of text files in sequence: </p><pre class="fragment">parser.Parse(text_file.c_str());
</pre><p>This works similarly to how the command-line compiler works: a sequence of files parsed by the same <code>Parser</code> object allow later files to reference definitions in earlier files. Typically this means you first load a schema file (which populates <code>Parser</code> with definitions), followed by one or more JSON files.</p> </pre><p>This works similarly to how the command-line compiler works: a sequence of files parsed by the same <code>Parser</code> object allow later files to reference definitions in earlier files. Typically this means you first load a schema file (which populates <code>Parser</code> with definitions), followed by one or more JSON files.</p>
<p>As optional argument to <code>Parse</code>, you may specify a null-terminated list of include paths. If not specified, any include statements try to resolve from the current directory.</p>
<p>If there were any parsing errors, <code>Parse</code> will return <code>false</code>, and <code>Parser::err</code> contains a human readable error string with a line number etc, which you should present to the creator of that file.</p> <p>If there were any parsing errors, <code>Parse</code> will return <code>false</code>, and <code>Parser::err</code> contains a human readable error string with a line number etc, which you should present to the creator of that file.</p>
<p>After each JSON file, the <code>Parser::fbb</code> member variable is the <code>FlatBufferBuilder</code> that contains the binary buffer version of that file, that you can access as described above.</p> <p>After each JSON file, the <code>Parser::fbb</code> member variable is the <code>FlatBufferBuilder</code> that contains the binary buffer version of that file, that you can access as described above.</p>
<p><code>samples/sample_text.cpp</code> is a code sample showing the above operations.</p> <p><code>samples/sample_text.cpp</code> is a code sample showing the above operations.</p>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Usage: Usage:
flatc [ -c ] [ -j ] [ -b ] [ -t ] [ -o PATH ] [ -S ] FILES... flatc [ -c ] [ -j ] [ -b ] [ -t ] [ -o PATH ] [ -I PATH ] [ -S ] FILES...
[ -- FILES...] [ -- FILES...]
The files are read and parsed in order, and can contain either schemas The files are read and parsed in order, and can contain either schemas
...@@ -32,6 +32,11 @@ be generated for each file processed: ...@@ -32,6 +32,11 @@ be generated for each file processed:
current directory. PATH should end in your systems path separator, current directory. PATH should end in your systems path separator,
e.g. `/` or `\`. e.g. `/` or `\`.
- `-I PATH` : when encountering `include` statements, attempt to load the
files from this path. Paths will be tried in the order given, and if all
fail (or none are specified) it will try to load relative to the path of
the schema file being parsed.
- `-S` : Generate strict JSON (field names are enclosed in quotes). - `-S` : Generate strict JSON (field names are enclosed in quotes).
By default, no quotes are generated. By default, no quotes are generated.
......
...@@ -249,6 +249,10 @@ reference definitions in earlier files. Typically this means you first ...@@ -249,6 +249,10 @@ reference definitions in earlier files. Typically this means you first
load a schema file (which populates `Parser` with definitions), followed load a schema file (which populates `Parser` with definitions), followed
by one or more JSON files. by one or more JSON files.
As optional argument to `Parse`, you may specify a null-terminated list of
include paths. If not specified, any include statements try to resolve from
the current directory.
If there were any parsing errors, `Parse` will return `false`, and If there were any parsing errors, `Parse` will return `false`, and
`Parser::err` contains a human readable error string with a line number `Parser::err` contains a human readable error string with a line number
etc, which you should present to the creator of that file. etc, which you should present to the creator of that file.
......
...@@ -268,9 +268,12 @@ class Parser { ...@@ -268,9 +268,12 @@ class Parser {
// Parse the string containing either schema or JSON data, which will // Parse the string containing either schema or JSON data, which will
// populate the SymbolTable's or the FlatBufferBuilder above. // populate the SymbolTable's or the FlatBufferBuilder above.
// filepath indicates the file that _source was loaded from, it is // include_paths is used to resolve any include statements, and typically
// used to resolve any include statements. // should at least include the project path (where you loaded source_ from).
bool Parse(const char *_source, const char *filepath); // include_paths must be nullptr terminated if specified.
// If include_paths is nullptr, it will attempt to load from the current
// directory.
bool Parse(const char *_source, const char **include_paths = nullptr);
// Set the root type. May override the one set in the schema. // Set the root type. May override the one set in the schema.
bool SetRootType(const char *name); bool SetRootType(const char *name);
...@@ -320,6 +323,7 @@ class Parser { ...@@ -320,6 +323,7 @@ class Parser {
std::vector<std::pair<Value, FieldDef *>> field_stack_; std::vector<std::pair<Value, FieldDef *>> field_stack_;
std::vector<uint8_t> struct_stack_; std::vector<uint8_t> struct_stack_;
std::map<std::string, bool> included_files_; std::map<std::string, bool> included_files_;
}; };
......
...@@ -132,6 +132,18 @@ inline std::string StripFileName(const std::string &filepath) { ...@@ -132,6 +132,18 @@ inline std::string StripFileName(const std::string &filepath) {
return i != std::string::npos ? filepath.substr(0, i) : ""; return i != std::string::npos ? filepath.substr(0, i) : "";
} }
// Concatenates a path with a filename, regardless of wether the path
// ends in a separator or not.
inline std::string ConCatPathFileName(const std::string &path,
const std::string &filename) {
std::string filepath = path;
if (path.length() && path.back() != kPathSeparator &&
path.back() != kPosixPathSeparator)
filepath += kPathSeparator;
filepath += filename;
return filepath;
}
// This function ensure a directory exists, by recursively // This function ensure a directory exists, by recursively
// creating dirs for any parts of the path that don't exist yet. // creating dirs for any parts of the path that don't exist yet.
inline void EnsureDirExists(const std::string &filepath) { inline void EnsureDirExists(const std::string &filepath) {
......
...@@ -37,8 +37,9 @@ int main(int /*argc*/, const char * /*argv*/[]) { ...@@ -37,8 +37,9 @@ int main(int /*argc*/, const char * /*argv*/[]) {
// parse schema first, so we can use it to parse the data after // parse schema first, so we can use it to parse the data after
flatbuffers::Parser parser; flatbuffers::Parser parser;
ok = parser.Parse(schemafile.c_str(), "samples/") && const char *include_directories[] = { "samples", nullptr };
parser.Parse(jsonfile.c_str(), "samples/"); ok = parser.Parse(schemafile.c_str(), include_directories) &&
parser.Parse(jsonfile.c_str(), include_directories);
assert(ok); assert(ok);
// here, parser.builder_ contains a binary buffer that is the parsed data. // here, parser.builder_ contains a binary buffer that is the parsed data.
......
...@@ -91,6 +91,7 @@ static void Error(const char *err, const char *obj, bool usage) { ...@@ -91,6 +91,7 @@ static void Error(const char *err, const char *obj, bool usage) {
for (size_t i = 0; i < sizeof(generators) / sizeof(generators[0]); ++i) for (size_t i = 0; i < sizeof(generators) / sizeof(generators[0]); ++i)
printf(" -%s %s.\n", generators[i].extension, generators[i].help); printf(" -%s %s.\n", generators[i].extension, generators[i].help);
printf(" -o PATH Prefix PATH to all generated files.\n" printf(" -o PATH Prefix PATH to all generated files.\n"
" -I PATH Search for includes in the specified path.\n"
" -S Strict JSON: add quotes to field names.\n" " -S Strict JSON: add quotes to field names.\n"
" -P Don\'t prefix enum values with the enum name in C++.\n" " -P Don\'t prefix enum values with the enum name in C++.\n"
"FILEs may depend on declarations in earlier files.\n" "FILEs may depend on declarations in earlier files.\n"
...@@ -112,6 +113,7 @@ int main(int argc, const char *argv[]) { ...@@ -112,6 +113,7 @@ int main(int argc, const char *argv[]) {
bool generator_enabled[num_generators] = { false }; bool generator_enabled[num_generators] = { false };
bool any_generator = false; bool any_generator = false;
std::vector<std::string> filenames; std::vector<std::string> filenames;
std::vector<const char *> include_directories;
size_t binary_files_from = std::numeric_limits<size_t>::max(); size_t binary_files_from = std::numeric_limits<size_t>::max();
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
const char *arg = argv[i]; const char *arg = argv[i];
...@@ -123,11 +125,11 @@ int main(int argc, const char *argv[]) { ...@@ -123,11 +125,11 @@ int main(int argc, const char *argv[]) {
switch (arg[1]) { switch (arg[1]) {
case 'o': case 'o':
if (++i >= argc) Error("missing path following", arg, true); if (++i >= argc) Error("missing path following", arg, true);
output_path = argv[i]; output_path = flatbuffers::ConCatPathFileName(argv[i], "");
if (!(output_path.back() == flatbuffers::kPathSeparator || break;
output_path.back() == flatbuffers::kPosixPathSeparator)) { case 'I':
output_path += flatbuffers::kPathSeparator; if (++i >= argc) Error("missing path following", arg, true);
} include_directories.push_back(argv[i]);
break; break;
case 'S': case 'S':
opts.strict_json = true; opts.strict_json = true;
...@@ -177,8 +179,13 @@ int main(int argc, const char *argv[]) { ...@@ -177,8 +179,13 @@ int main(int argc, const char *argv[]) {
reinterpret_cast<const uint8_t *>(contents.c_str()), reinterpret_cast<const uint8_t *>(contents.c_str()),
contents.length()); contents.length());
} else { } else {
if (!parser.Parse(contents.c_str(), file_it->c_str())) auto local_include_directory = flatbuffers::StripFileName(*file_it);
include_directories.push_back(local_include_directory.c_str());
include_directories.push_back(nullptr);
if (!parser.Parse(contents.c_str(), &include_directories[0]))
Error((*file_it + ": " + parser.error_).c_str()); Error((*file_it + ": " + parser.error_).c_str());
include_directories.pop_back();
include_directories.pop_back();
} }
std::string filebase = flatbuffers::StripPath( std::string filebase = flatbuffers::StripPath(
......
...@@ -860,11 +860,7 @@ void Parser::MarkGenerated() { ...@@ -860,11 +860,7 @@ void Parser::MarkGenerated() {
} }
} }
bool Parser::Parse(const char *source, const char *filepath) { bool Parser::Parse(const char *source, const char **include_paths) {
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; source_ = cursor_ = source;
line_ = 1; line_ = 1;
error_.clear(); error_.clear();
...@@ -875,17 +871,25 @@ bool Parser::Parse(const char *source, const char *filepath) { ...@@ -875,17 +871,25 @@ bool Parser::Parse(const char *source, const char *filepath) {
while (IsNext(kTokenInclude)) { while (IsNext(kTokenInclude)) {
auto name = attribute_; auto name = attribute_;
Expect(kTokenStringConstant); Expect(kTokenStringConstant);
auto path = StripFileName(filepath);
if (path.length()) name = path + kPathSeparator + name;
if (included_files_.find(name) == included_files_.end()) { if (included_files_.find(name) == included_files_.end()) {
// We found an include file that we have not parsed yet. // We found an include file that we have not parsed yet.
// Load it and parse it. // Load it and parse it.
std::string contents; std::string contents;
if (!LoadFile(name.c_str(), true, &contents)) if (!include_paths) {
const char *current_directory[] = { "", nullptr };
include_paths = current_directory;
}
for (auto paths = include_paths; paths && *paths; paths++) {
auto filepath = flatbuffers::ConCatPathFileName(*paths, name);
if(LoadFile(filepath.c_str(), true, &contents)) break;
}
if (contents.empty())
Error("unable to load include file: " + name); Error("unable to load include file: " + name);
Parse(contents.c_str(), name.c_str()); included_files_[name] = true;
if (!Parse(contents.c_str(), include_paths)) {
// Any errors, we're done. // Any errors, we're done.
if (error_.length()) return false; return false;
}
// We do not want to output code for any included files: // We do not want to output code for any included files:
MarkGenerated(); MarkGenerated();
// This is the easiest way to continue this file after an include: // This is the easiest way to continue this file after an include:
...@@ -893,7 +897,9 @@ bool Parser::Parse(const char *source, const char *filepath) { ...@@ -893,7 +897,9 @@ bool Parser::Parse(const char *source, const char *filepath) {
// file anew. This will cause it to encounter the same include statement // 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 // again, but this time it will skip it, because it was entered into
// included_files_. // included_files_.
goto restart_parse_after_include; // This is recursive, but only go as deep as the number of include
// statements.
return Parse(source, include_paths);
} }
Expect(';'); Expect(';');
} }
......
...@@ -192,8 +192,9 @@ void ParseAndGenerateTextTest() { ...@@ -192,8 +192,9 @@ void ParseAndGenerateTextTest() {
// parse schema first, so we can use it to parse the data after // parse schema first, so we can use it to parse the data after
flatbuffers::Parser parser; flatbuffers::Parser parser;
TEST_EQ(parser.Parse(schemafile.c_str(), "tests/"), true); const char *include_directories[] = { "tests", nullptr };
TEST_EQ(parser.Parse(jsonfile.c_str(), "tests/"), true); TEST_EQ(parser.Parse(schemafile.c_str(), include_directories), true);
TEST_EQ(parser.Parse(jsonfile.c_str(), include_directories), true);
// here, parser.builder_ contains a binary buffer that is the parsed data. // here, parser.builder_ contains a binary buffer that is the parsed data.
...@@ -406,12 +407,12 @@ void FuzzTest2() { ...@@ -406,12 +407,12 @@ void FuzzTest2() {
// Parse the schema, parse the generated data, then generate text back // Parse the schema, parse the generated data, then generate text back
// from the binary and compare against the original. // 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 = const std::string &json =
definitions[num_definitions - 1].instances[0] + "\n"; 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; std::string jsongen;
flatbuffers::GeneratorOptions opts; flatbuffers::GeneratorOptions opts;
...@@ -443,7 +444,7 @@ void FuzzTest2() { ...@@ -443,7 +444,7 @@ void FuzzTest2() {
// Test that parser errors are actually generated. // Test that parser errors are actually generated.
void TestError(const char *src, const char *error_substr) { void TestError(const char *src, const char *error_substr) {
flatbuffers::Parser parser; 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 // Must be the error we're expecting
TEST_NOTNULL(strstr(parser.error_.c_str(), error_substr)); TEST_NOTNULL(strstr(parser.error_.c_str(), error_substr));
} }
...@@ -498,10 +499,10 @@ void ScientificTest() { ...@@ -498,10 +499,10 @@ void ScientificTest() {
flatbuffers::Parser parser; flatbuffers::Parser parser;
// Simple schema. // 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 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()); auto root = flatbuffers::GetRoot<float>(parser.builder_.GetBufferPointer());
// root will point to the table, which is a 32bit vtable offset followed // root will point to the table, which is a 32bit vtable offset followed
// by a float: // by a float:
...@@ -513,11 +514,11 @@ void EnumStringsTest() { ...@@ -513,11 +514,11 @@ void EnumStringsTest() {
flatbuffers::Parser parser1; flatbuffers::Parser parser1;
TEST_EQ(parser1.Parse("enum E:byte { A, B, C } table T { F:[E]; }" TEST_EQ(parser1.Parse("enum E:byte { A, B, C } table T { F:[E]; }"
"root_type T;" "root_type T;"
"{ F:[ A, B, \"C\", \"A B C\" ] }", ""), true); "{ F:[ A, B, \"C\", \"A B C\" ] }"), true);
flatbuffers::Parser parser2; flatbuffers::Parser parser2;
TEST_EQ(parser2.Parse("enum E:byte { A, B, C } table T { F:[int]; }" TEST_EQ(parser2.Parse("enum E:byte { A, B, C } table T { F:[int]; }"
"root_type T;" "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() { void UnicodeTest() {
...@@ -525,7 +526,7 @@ void UnicodeTest() { ...@@ -525,7 +526,7 @@ void UnicodeTest() {
TEST_EQ(parser.Parse("table T { F:string; }" TEST_EQ(parser.Parse("table T { F:string; }"
"root_type T;" "root_type T;"
"{ F:\"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC" "{ F:\"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC"
"\\u5225\\u30B5\\u30A4\\u30C8\\x01\\x80\" }", ""), true); "\\u5225\\u30B5\\u30A4\\u30C8\\x01\\x80\" }"), true);
std::string jsongen; std::string jsongen;
flatbuffers::GeneratorOptions opts; flatbuffers::GeneratorOptions opts;
opts.indent_step = -1; opts.indent_step = -1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment