From b395359b6ef8613b3b156f12f9a9e1d924953b35 Mon Sep 17 00:00:00 2001
From: Wouter van Oortmerssen <aardappel@gmail.com>
Date: Wed, 25 Jan 2017 13:41:34 -0800
Subject: [PATCH] Allow the flatbuffer compiler (flatc) to be built as a
 library.

Change-Id: I71baff427243f96be1596b01ff3405fdf39386d7
---
 CMakeLists.txt              |   1 +
 include/flatbuffers/flatc.h |  96 ++++++++++++++++
 src/flatc.cpp               | 220 +++++++++++++-----------------------
 src/flatc_main.cpp          | 103 +++++++++++++++++
 4 files changed, 276 insertions(+), 144 deletions(-)
 create mode 100644 include/flatbuffers/flatc.h
 create mode 100644 src/flatc_main.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6ac39960..a5d98174 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,6 +48,7 @@ set(FlatBuffers_Compiler_SRCS
   src/idl_gen_fbs.cpp
   src/idl_gen_grpc.cpp
   src/flatc.cpp
+  src/flatc_main.cpp
   grpc/src/compiler/schema_interface.h
   grpc/src/compiler/cpp_generator.h
   grpc/src/compiler/cpp_generator.cc
diff --git a/include/flatbuffers/flatc.h b/include/flatbuffers/flatc.h
new file mode 100644
index 00000000..e7bc835e
--- /dev/null
+++ b/include/flatbuffers/flatc.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flatbuffers/flatbuffers.h"
+#include "flatbuffers/idl.h"
+#include "flatbuffers/util.h"
+#include <functional>
+#include <limits>
+#include <string>
+
+#ifndef FLATC_H_
+#define FLATC_H_
+
+namespace flatbuffers {
+
+class FlatCompiler {
+ public:
+  // Output generator for the various programming languages and formats we
+  // support.
+  struct Generator {
+    typedef bool (*GenerateFn)(const flatbuffers::Parser &parser,
+                               const std::string &path,
+                               const std::string &file_name);
+    typedef std::string (*MakeRuleFn)(const flatbuffers::Parser &parser,
+                                      const std::string &path,
+                                      const std::string &file_name);
+
+    GenerateFn generate;
+    const char *generator_opt_short;
+    const char *generator_opt_long;
+    const char *lang_name;
+    GenerateFn generateGRPC;
+    flatbuffers::IDLOptions::Language lang;
+    const char *generator_help;
+    MakeRuleFn make_rule;
+  };
+
+  typedef void (*WarnFn)(const FlatCompiler *flatc,
+                         const std::string &warn,
+                         bool show_exe_name);
+
+  typedef void (*ErrorFn)(const FlatCompiler *flatc,
+                          const std::string &err,
+                          bool usage, bool show_exe_name);
+
+  // Parameters required to initialize the FlatCompiler.
+  struct InitParams {
+    InitParams()
+        : generators(nullptr),
+          num_generators(0),
+          warn_fn(nullptr),
+          error_fn(nullptr) {}
+
+    const Generator* generators;
+    size_t num_generators;
+    WarnFn warn_fn;
+    ErrorFn error_fn;
+  };
+
+  explicit FlatCompiler(const InitParams& params) : params_(params) {}
+
+  int Compile(int argc, const char** argv);
+
+  std::string GetUsageString(const char* program_name) const;
+
+ private:
+  void ParseFile(flatbuffers::Parser &parser,
+                 const std::string &filename,
+                 const std::string &contents,
+                 std::vector<const char *> &include_directories) const;
+
+  void Warn(const std::string &warn, bool show_exe_name = true) const;
+
+  void Error(const std::string &err, bool usage = true,
+             bool show_exe_name = true) const;
+
+  InitParams params_;
+};
+
+
+}  // namespace flatbuffers
+
+#endif  // FLATC_H_
diff --git a/src/flatc.cpp b/src/flatc.cpp
index f5553e85..5d426cc7 100644
--- a/src/flatc.cpp
+++ b/src/flatc.cpp
@@ -14,105 +14,50 @@
  * limitations under the License.
  */
 
-#include "flatbuffers/flatbuffers.h"
-#include "flatbuffers/idl.h"
-#include "flatbuffers/util.h"
-#include <limits>
+#include "flatbuffers/flatc.h"
 
 #define FLATC_VERSION "1.5.0 (" __DATE__ ")"
 
-static void Error(const std::string &err, bool usage = false,
-                  bool show_exe_name = true);
+namespace flatbuffers {
 
-// This struct allows us to create a table of all possible output generators
-// for the various programming languages and formats we support.
-struct Generator {
-  bool (*generate)(const flatbuffers::Parser &parser,
-                   const std::string &path,
-                   const std::string &file_name);
-  const char *generator_opt_short;
-  const char *generator_opt_long;
-  const char *lang_name;
-  bool (*generateGRPC)(const flatbuffers::Parser &parser,
-                       const std::string &path,
-                       const std::string &file_name);
-  flatbuffers::IDLOptions::Language lang;
-  const char *generator_help;
+void FlatCompiler::ParseFile(
+    flatbuffers::Parser &parser,
+    const std::string &filename,
+    const std::string &contents,
+    std::vector<const char *> &include_directories) const {
+  auto local_include_directory = flatbuffers::StripFileName(filename);
+  include_directories.push_back(local_include_directory.c_str());
+  include_directories.push_back(nullptr);
+  if (!parser.Parse(contents.c_str(), &include_directories[0],
+                    filename.c_str()))
+    Error(parser.error_, false, false);
+  include_directories.pop_back();
+  include_directories.pop_back();
+}
 
-  std::string (*make_rule)(const flatbuffers::Parser &parser,
-                           const std::string &path,
-                           const std::string &file_name);
-};
+void FlatCompiler::Warn(const std::string &warn, bool show_exe_name) const {
+  params_.warn_fn(this, warn, show_exe_name);
+}
 
-const Generator generators[] = {
-  { flatbuffers::GenerateBinary,   "-b", "--binary", "binary",
-    nullptr,
-    flatbuffers::IDLOptions::kBinary,
-    "Generate wire format binaries for any data definitions",
-    flatbuffers::BinaryMakeRule },
-  { flatbuffers::GenerateTextFile, "-t", "--json", "text",
-    nullptr,
-    flatbuffers::IDLOptions::kJson,
-    "Generate text output for any data definitions",
-    flatbuffers::TextMakeRule },
-  { flatbuffers::GenerateCPP,      "-c", "--cpp", "C++",
-    flatbuffers::GenerateCppGRPC,
-    flatbuffers::IDLOptions::kCpp,
-    "Generate C++ headers for tables/structs",
-    flatbuffers::CPPMakeRule },
-  { flatbuffers::GenerateGo,       "-g", "--go", "Go",
-    flatbuffers::GenerateGoGRPC,
-    flatbuffers::IDLOptions::kGo,
-    "Generate Go files for tables/structs",
-    flatbuffers::GeneralMakeRule },
-  { flatbuffers::GenerateGeneral,  "-j", "--java", "Java",
-    nullptr,
-    flatbuffers::IDLOptions::kJava,
-    "Generate Java classes for tables/structs",
-    flatbuffers::GeneralMakeRule },
-  { flatbuffers::GenerateJS,       "-s", "--js", "JavaScript",
-    nullptr,
-    flatbuffers::IDLOptions::kJs,
-    "Generate JavaScript code for tables/structs",
-    flatbuffers::JSMakeRule },
-  { flatbuffers::GenerateGeneral,  "-n", "--csharp", "C#",
-    nullptr,
-    flatbuffers::IDLOptions::kCSharp,
-    "Generate C# classes for tables/structs",
-    flatbuffers::GeneralMakeRule },
-  { flatbuffers::GeneratePython,   "-p", "--python", "Python",
-    nullptr,
-    flatbuffers::IDLOptions::kPython,
-    "Generate Python files for tables/structs",
-    flatbuffers::GeneralMakeRule },
-  { flatbuffers::GeneratePhp, nullptr, "--php", "PHP",
-    nullptr,
-    flatbuffers::IDLOptions::kPhp,
-    "Generate PHP files for tables/structs",
-    flatbuffers::GeneralMakeRule },
-};
+void FlatCompiler::Error(const std::string &err, bool usage,
+                         bool show_exe_name) const {
+  params_.error_fn(this, err, usage, show_exe_name);
+}
 
-const char *g_program_name = nullptr;
-flatbuffers::Parser *g_parser = nullptr;
+std::string FlatCompiler::GetUsageString(const char* program_name) const {
+  std::stringstream ss;
+  ss << "Usageaa: " << program_name << " [OPTION]... FILE... [-- FILE...]\n";
+  for (size_t i = 0; i < params_.num_generators; ++i) {
+    const Generator& g = params_.generators[i];
 
-static void Warn(const std::string &warn, bool show_exe_name = true) {
-  if (show_exe_name) printf("%s: ", g_program_name);
-  printf("warning: %s\n", warn.c_str());
-}
+    std::stringstream full_name;
+    full_name << std::setw(12) << std::left << g.generator_opt_long;
+    const char *name = g.generator_opt_short ? g.generator_opt_short : "  ";
+    const char *help = g.generator_help;
 
-static void Error(const std::string &err, bool usage, bool show_exe_name) {
-  if (show_exe_name) printf("%s: ", g_program_name);
-  printf("error: %s\n", err.c_str());
-  if (usage) {
-    printf("usage: %s [OPTION]... FILE... [-- FILE...]\n", g_program_name);
-    for (size_t i = 0; i < sizeof(generators) / sizeof(generators[0]); ++i)
-      printf("  %-12s %s %s.\n",
-             generators[i].generator_opt_long,
-             generators[i].generator_opt_short
-               ? generators[i].generator_opt_short
-               : "  ",
-             generators[i].generator_help);
-    printf(
+    ss << "  " << full_name.str() << " " << name << "    " << help << ".\n";
+  }
+  ss <<
       "  -o PATH            Prefix PATH to all generated files.\n"
       "  -I PATH            Search for includes in the specified path.\n"
       "  -M                 Print make rules for generated files.\n"
@@ -154,32 +99,18 @@ static void Error(const std::string &err, bool usage, bool show_exe_name) {
       "FILEs after the -- must be binary flatbuffer format files.\n"
       "Output files are named using the base file name of the input,\n"
       "and written to the current directory or the path given by -o.\n"
-      "example: %s -c -b schema1.fbs schema2.fbs data.json\n",
-      g_program_name);
-  }
-  if (g_parser) delete g_parser;
-  exit(1);
+      "example: " << program_name << " -c -b schema1.fbs schema2.fbs data.json\n";
+  return ss.str();
 }
 
-static void ParseFile(flatbuffers::Parser &parser, const std::string &filename,
-                      const std::string &contents,
-                      std::vector<const char *> &include_directories) {
-  auto local_include_directory = flatbuffers::StripFileName(filename);
-  include_directories.push_back(local_include_directory.c_str());
-  include_directories.push_back(nullptr);
-  if (!parser.Parse(contents.c_str(), &include_directories[0],
-                     filename.c_str()))
-    Error(parser.error_, false, false);
-  include_directories.pop_back();
-  include_directories.pop_back();
-}
+int FlatCompiler::Compile(int argc, const char** argv) {
+  if (params_.generators == nullptr || params_.num_generators == 0) {
+    return 0;
+  }
 
-int main(int argc, const char *argv[]) {
-  g_program_name = argv[0];
   flatbuffers::IDLOptions opts;
   std::string output_path;
-  const size_t num_generators = sizeof(generators) / sizeof(generators[0]);
-  bool generator_enabled[num_generators] = { false };
+
   bool any_generator = false;
   bool print_make_rules = false;
   bool raw_binary = false;
@@ -188,9 +119,11 @@ int main(int argc, const char *argv[]) {
   std::vector<std::string> filenames;
   std::vector<const char *> include_directories;
   std::vector<const char *> conform_include_directories;
+  std::vector<bool> generator_enabled(params_.num_generators, false);
   size_t binary_files_from = std::numeric_limits<size_t>::max();
   std::string conform_to_schema;
-  for (int argi = 1; argi < argc; argi++) {
+
+  for (int argi = 0; argi < argc; argi++) {
     std::string arg = argv[argi];
     if (arg[0] == '-') {
       if (filenames.size() && arg[1] != '-')
@@ -261,13 +194,13 @@ int main(int argc, const char *argv[]) {
       } else if(arg == "--grpc") {
         grpc_enabled = true;
       } else {
-        for (size_t i = 0; i < num_generators; ++i) {
-          if (arg == generators[i].generator_opt_long ||
-              (generators[i].generator_opt_short &&
-               arg == generators[i].generator_opt_short)) {
+        for (size_t i = 0; i < params_.num_generators; ++i) {
+          if (arg == params_.generators[i].generator_opt_long ||
+              (params_.generators[i].generator_opt_short &&
+               arg == params_.generators[i].generator_opt_short)) {
             generator_enabled[i] = true;
             any_generator = true;
-            opts.lang_to_generate |= generators[i].lang;
+            opts.lang_to_generate |= params_.generators[i].lang;
             goto found;
           }
         }
@@ -297,8 +230,8 @@ int main(int argc, const char *argv[]) {
               conform_include_directories);
   }
 
-  // Now process the files:
-  g_parser = new flatbuffers::Parser(opts);
+  std::unique_ptr<flatbuffers::Parser> parser(new flatbuffers::Parser(opts));
+
   for (auto file_it = filenames.begin();
             file_it != filenames.end();
           ++file_it) {
@@ -309,8 +242,8 @@ int main(int argc, const char *argv[]) {
       bool is_binary = static_cast<size_t>(file_it - filenames.begin()) >=
                        binary_files_from;
       if (is_binary) {
-        g_parser->builder_.Clear();
-        g_parser->builder_.PushFlatBuffer(
+        parser->builder_.Clear();
+        parser->builder_.PushFlatBuffer(
           reinterpret_cast<const uint8_t *>(contents.c_str()),
           contents.length());
         if (!raw_binary) {
@@ -319,17 +252,17 @@ int main(int argc, const char *argv[]) {
           // does not contain a file identifier.
           // We'd expect that typically any binary used as a file would have
           // such an identifier, so by default we require them to match.
-          if (!g_parser->file_identifier_.length()) {
+          if (!parser->file_identifier_.length()) {
             Error("current schema has no file_identifier: cannot test if \"" +
                  *file_it +
                  "\" matches the schema, use --raw-binary to read this file"
                  " anyway.");
           } else if (!flatbuffers::BufferHasIdentifier(contents.c_str(),
-                         g_parser->file_identifier_.c_str())) {
+                         parser->file_identifier_.c_str())) {
             Error("binary \"" +
                  *file_it +
                  "\" does not have expected file_identifier \"" +
-                 g_parser->file_identifier_ +
+                 parser->file_identifier_ +
                  "\", use --raw-binary to read this file anyway.");
           }
         }
@@ -343,63 +276,62 @@ int main(int argc, const char *argv[]) {
           // If we're processing multiple schemas, make sure to start each
           // one from scratch. If it depends on previous schemas it must do
           // so explicitly using an include.
-          delete g_parser;
-          g_parser = new flatbuffers::Parser(opts);
+          parser.reset(new flatbuffers::Parser(opts));
         }
-        ParseFile(*g_parser, *file_it, contents, include_directories);
+        ParseFile(*parser.get(), *file_it, contents, include_directories);
         if (is_schema && !conform_to_schema.empty()) {
-          auto err = g_parser->ConformTo(conform_parser);
+          auto err = parser->ConformTo(conform_parser);
           if (!err.empty()) Error("schemas don\'t conform: " + err);
         }
         if (schema_binary) {
-          g_parser->Serialize();
-          g_parser->file_extension_ = reflection::SchemaExtension();
+          parser->Serialize();
+          parser->file_extension_ = reflection::SchemaExtension();
         }
       }
 
       std::string filebase = flatbuffers::StripPath(
                                flatbuffers::StripExtension(*file_it));
 
-      for (size_t i = 0; i < num_generators; ++i) {
-        g_parser->opts.lang = generators[i].lang;
+      for (size_t i = 0; i < params_.num_generators; ++i) {
+        parser->opts.lang = params_.generators[i].lang;
         if (generator_enabled[i]) {
           if (!print_make_rules) {
             flatbuffers::EnsureDirExists(output_path);
-            if (!generators[i].generate(*g_parser, output_path, filebase)) {
+            if (!params_.generators[i].generate(*parser.get(), output_path, filebase)) {
               Error(std::string("Unable to generate ") +
-                    generators[i].lang_name +
+                    params_.generators[i].lang_name +
                     " for " +
                     filebase);
             }
           } else {
-            std::string make_rule = generators[i].make_rule(
-                *g_parser, output_path, *file_it);
+            std::string make_rule = params_.generators[i].make_rule(
+                *parser.get(), output_path, *file_it);
             if (!make_rule.empty())
               printf("%s\n", flatbuffers::WordWrap(
                   make_rule, 80, " ", " \\").c_str());
           }
           if (grpc_enabled) {
-            if (generators[i].generateGRPC != nullptr) {
-              if (!generators[i].generateGRPC(*g_parser, output_path,
+            if (params_.generators[i].generateGRPC != nullptr) {
+              if (!params_.generators[i].generateGRPC(*parser.get(), output_path,
                                               filebase)) {
                 Error(std::string("Unable to generate GRPC interface for") +
-                      generators[i].lang_name);
+                      params_.generators[i].lang_name);
               }
             } else {
               Warn(std::string("GRPC interface generator not implemented for ")
-                   + generators[i].lang_name);
+                   + params_.generators[i].lang_name);
             }
           }
         }
       }
 
-      if (opts.proto_mode) GenerateFBS(*g_parser, output_path, filebase);
+      if (opts.proto_mode) GenerateFBS(*parser.get(), output_path, filebase);
 
       // We do not want to generate code for the definitions in this file
       // in any files coming up next.
-      g_parser->MarkGenerated();
+      parser->MarkGenerated();
   }
-
-  delete g_parser;
   return 0;
 }
+
+}  // namespace flatbuffers
diff --git a/src/flatc_main.cpp b/src/flatc_main.cpp
new file mode 100644
index 00000000..2063731c
--- /dev/null
+++ b/src/flatc_main.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flatbuffers/flatc.h"
+
+static const char *g_program_name = nullptr;
+
+static void Warn(const flatbuffers::FlatCompiler* flatc,
+                 const std::string &warn,
+                 bool show_exe_name) {
+  if (show_exe_name) {
+    printf("%s: ", g_program_name);
+  }
+  printf("warning: %s\n", warn.c_str());
+}
+
+static void Error(const flatbuffers::FlatCompiler* flatc,
+                  const std::string &err,
+                  bool usage,
+                  bool show_exe_name) {
+  if (show_exe_name) {
+    printf("%s: ", g_program_name);
+  }
+  printf("error: %s\n", err.c_str());
+  if (usage) {
+    printf("%s", flatc->GetUsageString(g_program_name).c_str());
+  }
+  exit(1);
+}
+
+int main(int argc, const char *argv[]) {
+  g_program_name = argv[0];
+
+  const flatbuffers::FlatCompiler::Generator generators[] = {
+    { flatbuffers::GenerateBinary,   "-b", "--binary", "binary",
+      nullptr,
+      flatbuffers::IDLOptions::kBinary,
+      "Generate wire format binaries for any data definitions",
+      flatbuffers::BinaryMakeRule },
+    { flatbuffers::GenerateTextFile, "-t", "--json", "text",
+      nullptr,
+      flatbuffers::IDLOptions::kJson,
+      "Generate text output for any data definitions",
+      flatbuffers::TextMakeRule },
+    { flatbuffers::GenerateCPP,      "-c", "--cpp", "C++",
+      flatbuffers::GenerateCppGRPC,
+      flatbuffers::IDLOptions::kCpp,
+      "Generate C++ headers for tables/structs",
+      flatbuffers::CPPMakeRule },
+    { flatbuffers::GenerateGo,       "-g", "--go", "Go",
+      flatbuffers::GenerateGoGRPC,
+      flatbuffers::IDLOptions::kGo,
+      "Generate Go files for tables/structs",
+      flatbuffers::GeneralMakeRule },
+    { flatbuffers::GenerateGeneral,  "-j", "--java", "Java",
+      nullptr,
+      flatbuffers::IDLOptions::kJava,
+      "Generate Java classes for tables/structs",
+      flatbuffers::GeneralMakeRule },
+    { flatbuffers::GenerateJS,       "-s", "--js", "JavaScript",
+      nullptr,
+      flatbuffers::IDLOptions::kJs,
+      "Generate JavaScript code for tables/structs",
+      flatbuffers::JSMakeRule },
+    { flatbuffers::GenerateGeneral,  "-n", "--csharp", "C#",
+      nullptr,
+      flatbuffers::IDLOptions::kCSharp,
+      "Generate C# classes for tables/structs",
+      flatbuffers::GeneralMakeRule },
+    { flatbuffers::GeneratePython,   "-p", "--python", "Python",
+      nullptr,
+      flatbuffers::IDLOptions::kPython,
+      "Generate Python files for tables/structs",
+      flatbuffers::GeneralMakeRule },
+    { flatbuffers::GeneratePhp, nullptr, "--php", "PHP",
+      nullptr,
+      flatbuffers::IDLOptions::kPhp,
+      "Generate PHP files for tables/structs",
+      flatbuffers::GeneralMakeRule },
+    };
+
+  flatbuffers::FlatCompiler::InitParams params;
+  params.generators = generators;
+  params.num_generators = sizeof(generators) / sizeof(generators[0]);
+  params.warn_fn = Warn;
+  params.error_fn = Error;
+
+  flatbuffers::FlatCompiler flatc(params);
+  return flatc.Compile(argc - 1, argv + 1);
+}
-- 
GitLab