create files
[libt2n] / codegen / main.cpp
index e2f7bc0..84718fe 100644 (file)
@@ -1,6 +1,8 @@
 #include <libxml++/libxml++.h>
 #include <cassert>
 #include <iostream>
+#include <set>
+#include <fstream>
 
 void print_indentation(unsigned int indentation)
 {
@@ -58,6 +60,10 @@ std::string get_type(const xmlpp::Element* root, const std::string &id)
      // TODO: not yet complete
      // if we recurse - when do we stop?
      // if it is a typedef? yes? (hmm if the typedef is in the file parsed this will not work)
+
+     // TODO: const and reference types (perhaps it is good enough to remove the const and &?)
+     //       non-const reference types/pointers (output error message? not yet supported)
+
      std::string tag(element->get_name());
      if (tag=="ReferenceType") {
          assert(element->get_attribute("type"));
@@ -76,131 +82,388 @@ std::string get_type(const xmlpp::Element* root, const std::string &id)
      return get_namespace(root, element->get_attribute("context")->get_value())+"::"+element->get_attribute("name")->get_value();
 }
 
-void parse_function(const xmlpp::Element* root, const xmlpp::Node* node, unsigned int indentation = 0) {
-     const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
-     if (!element) return;
+struct t2n_procedure
+{
+     typedef std::map<std::string, std::string> Args;
 
-     const xmlpp::Attribute* attributes = element->get_attribute("attributes");
-     const xmlpp::Attribute* name = element->get_attribute("name");
-     const xmlpp::Attribute* returns = element->get_attribute("returns");
-     if ((!attributes)||(!name)||(!returns)) return;
+     std::string group;
+     std::string ret_type;
+     std::string name;
+     Args  args;
 
-     // check wether the function is marked (TODO: improve)
-     if (attributes->get_value().find("gccxml(libt2n")==std::string::npos) return;
+     std::string ret_classname() const {
+         return name+"_res";
+     }
+     std::string cmd_classname() const {
+         return name+"_cmd";
+     }
+};
 
-     // TODO: extract command group
-     // something like: sed 's,gccxml(libt2n-\([a-z]*}),\1,'
+std::ostream &operator<<(std::ostream &o, const t2n_procedure::Args &args) {
+     for (t2n_procedure::Args::const_iterator it=args.begin();it!=args.end();++it) {
+         if (it!=args.begin()) o << ", ";
+         o << it->second << " " << it->first;
+     }
+     return o;
+}
 
-     // we need the return type
-     std::string ret_type(get_type(root, returns->get_value()));
+std::ostream &operator<<(std::ostream &o, const t2n_procedure &f) {
+     o << f.ret_type << " " << f.name << "(" << f.args << ")";
+     return o;
+}
 
-     // and the argument types
-     typedef std::map<std::string, std::string> Args;
-     Args  args;
+class Parser
+{
+public:
+     Parser(const std::string &fname) : m_fname(fname) {}
 
-     xmlpp::Node::NodeList list = node->get_children("Argument");
-     for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
-     {
-         const xmlpp::Element* arg = dynamic_cast<const xmlpp::Element*>(*iter);
-         if ( arg ) {
-              assert(arg->get_name() == "Argument");
-              assert(arg->get_attribute("name"));
-              assert(arg->get_attribute("type"));
-              assert(args.find(arg->get_attribute("name")->get_value())==args.end());
-              args[arg->get_attribute("name")->get_value()]=get_type(root, arg->get_attribute("type")->get_value());
+     std::list<t2n_procedure> get_procedures() {
+         xmlpp::DomParser parser;
+         //    parser.set_validate();
+         parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
+         parser.parse_file(m_fname);
+         if(parser)
+         {
+              //Walk the tree:
+              const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
+              const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*>(pNode);
+              assert(root);
+              visit_node(root);
          }
+         return m_procedures;
      }
+protected:
+     std::string m_fname;
+     std::list<t2n_procedure> m_procedures;
+
+     void parse_function(const xmlpp::Element* root, const xmlpp::Node* node) {
+         const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
+         if (!element) return;
+
+         const xmlpp::Attribute* attributes = element->get_attribute("attributes");
+         const xmlpp::Attribute* name = element->get_attribute("name");
+         const xmlpp::Attribute* returns = element->get_attribute("returns");
+         if ((!attributes)||(!name)||(!returns)) return;
+
+         // check wether the procedure is marked (TODO: improve)
+         // attributes are speparated by spaces?
+         if (attributes->get_value().find("gccxml(libt2n")==std::string::npos) return;
+
+         // TODO: extract command group
+         // something like: sed 's,gccxml(libt2n-\([a-z]*}),\1,'
+         t2n_procedure f;
+         f.group=("default");
 
-     std::cerr << "Found marked Function: " << ret_type << " " << name->get_value() << "(";
-     for (Args::const_iterator it=args.begin();it!=args.end();++it) {
-         if (it!=args.begin()) std::cerr << ", ";
-         std::cerr << it->second << " " << it->first;
+         // we need the return type
+         f.ret_type=get_type(root, returns->get_value());
+         f.name=name->get_value();
+
+         xmlpp::Node::NodeList list = node->get_children("Argument");
+         for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
+         {
+              const xmlpp::Element* arg = dynamic_cast<const xmlpp::Element*>(*iter);
+              if ( arg ) {
+                   assert(arg->get_name() == "Argument");
+                   assert(arg->get_attribute("name"));
+                   assert(arg->get_attribute("type"));
+                   assert(f.args.find(arg->get_attribute("name")->get_value())==f.args.end());
+                   f.args[arg->get_attribute("name")->get_value()]=get_type(root, arg->get_attribute("type")->get_value());
+              }
+         }
+         m_procedures.push_back(f);
      }
-     std::cerr << ");" << std::endl;
-}
 
-void print_node(const xmlpp::Element* root, const xmlpp::Node* node = NULL, unsigned int indentation = 0)
-{
-     if (!node) node=root;
-     std::cout << std::endl; //Separate nodes by an empty line.
-  
-     const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
-     const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
-     const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
+     void visit_node(const xmlpp::Element* root, const xmlpp::Node* node = NULL, unsigned int indentation = 0)
+         {
+              if (!node) node=root;
+              std::cout << std::endl; //Separate nodes by an empty line.
+         
+              const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
+              const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
+              const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
 
-     if(nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
-         return;
+              if(nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
+                   return;
     
-     std::string nodename = node->get_name();
+              std::string nodename = node->get_name();
 
-     if(!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
-     {
-         print_indentation(indentation);
-         //    std::cout << "Node name = " << node->get_name() << std::endl;
-         //    std::cout << "Node name = " << nodename << std::endl;
-         if (node->get_name() == "Function") {
-              parse_function(root, node, indentation);
+              if(!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
+              {
+                   print_indentation(indentation);
+                   //    std::cout << "Node name = " << node->get_name() << std::endl;
+                   //    std::cout << "Node name = " << nodename << std::endl;
+                   if (node->get_name() == "Function") {
+                        parse_function(root, node);
+                   }
+              }
+              else if(nodeText) //Let's say when it's text. - e.g. let's say what that white space is.
+              {
+                   print_indentation(indentation);
+                   std::cout << "Text Node" << std::endl;
+              }
+
+              //Treat the various node types differently: 
+              if(nodeText)
+              {
+                   print_indentation(indentation);
+                   std::cout << "text = \"" << nodeText->get_content() << "\"" << std::endl;
+              }
+              else if(nodeComment)
+              {
+                   print_indentation(indentation);
+                   std::cout << "comment = " << nodeComment->get_content() << std::endl;
+              }
+              else if(nodeContent)
+              {
+                   print_indentation(indentation);
+                   std::cout << "content = " << nodeContent->get_content() << std::endl;
+              }
+              else if(const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node))
+              {
+                   //A normal Element node:
+
+                   //line() works only for ElementNodes.
+                   print_indentation(indentation);
+                   std::cout << "     line = " << node->get_line() << std::endl;
+
+                   //Print attributes:
+                   const xmlpp::Element::AttributeList& attributes = nodeElement->get_attributes();
+                   for(xmlpp::Element::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
+                   {
+                        const xmlpp::Attribute* attribute = *iter;
+                        print_indentation(indentation);
+                        std::cout << "  Attribute " << attribute->get_name() << " = " << attribute->get_value() << std::endl;
+                   }
+
+                   const xmlpp::Attribute* attribute = nodeElement->get_attribute("title");
+                   if(attribute)
+                   {
+                        std::cout << "title found: =" << attribute->get_value() << std::endl;
+      
+                   }
+              }
+  
+              if(!nodeContent)
+              {
+                   //Recurse through child nodes:
+                   xmlpp::Node::NodeList list = node->get_children();
+                   for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
+                   {
+                        visit_node(root, *iter, indentation + 2); //recursive
+                   }
+              }
          }
+};
+
+//! find used groups
+std::set<std::string>
+used_groups(const std::list<t2n_procedure> &funcs) {
+     typedef std::set<std::string> Groups;
+     Groups groups;
+     for (std::list<t2n_procedure>::const_iterator it=funcs.begin();it!=funcs.end();++it)
+         // since we use std::set each group is inserted only once
+         groups.insert(it->group);
+     return groups;
+}
+
+void output_common_hpp(std::ostream &o, const std::list<t2n_procedure> &procs) {
+     std::set<std::string> groups(used_groups(procs));
+
+     o << "#include \"codegen-stubhead.hxx\"\n";
+     for (std::set<std::string>::iterator it=groups.begin();it!=groups.end();++it) {
+         o << "class cmd_group_" << *it << " : public libt2n::command\n"
+           << "{\n"
+           << "private:\n"
+           << "        friend class boost::serialization::access;\n"
+           << "        template<class Archive>\n"
+           << "        void serialize(Archive & ar, const unsigned int version)\n"
+           << "        {ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::command);}\n"
+           << "};\n";
      }
-     else if(nodeText) //Let's say when it's text. - e.g. let's say what that white space is.
-     {
-         print_indentation(indentation);
-         std::cout << "Text Node" << std::endl;
+     
+     for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
+         o << "class " << it->ret_classname() << " : public libt2n::result\n"
+           << "{\n"
+           << "private:\n"
+           << "        " << it->ret_type << " res;\n"
+           << "        friend class boost::serialization::access;\n"
+           << "        template<class Archive>\n"
+           << "        void serialize(Archive & ar, const unsigned int version)\n"
+           << "        {\n"
+           << "                ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::result);\n"
+           << "                ar & BOOST_SERIALIZATION_NVP(res);\n"
+           << "        }\n"
+           << "public:\n"
+           << "        " << it->ret_classname() << "(const " << it->ret_type << " &_res) : res(_res) {}\n"
+           << "        " << it->ret_type << " get_data() { return res; }\n"
+           << "};\n";
      }
+     for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
+         o << "class " << it->cmd_classname() << " : public " << "cmd_group_" << it->group << "\n"
+           << "{\n"
+           << "private:\n";
+         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
+              o << "   " << ait->second << " " << ait->first << ";\n";
+         }
+         o << "        friend class boost::serialization::access;\n"
+           << "        template<class Archive>\n"
+           << "        void serialize(Archive & ar, const unsigned int version)\n"
+           << "        {\n"
+           << "                ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(cmd_group_" << it->group << ");\n";
+         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
+              o << "           ar & BOOST_SERIALIZATION_NVP(" << ait->first << ");\n";
+         }
 
-     //Treat the various node types differently: 
-     if(nodeText)
-     {
-         print_indentation(indentation);
-         std::cout << "text = \"" << nodeText->get_content() << "\"" << std::endl;
+         // default constructor
+         o << "        }\n"
+           << "\n"
+           << "public:\n"
+           << "        " << it->cmd_classname() << "() {}\n";
+
+         // constructor taking all arguments
+         o << "        " << it->cmd_classname() << "(";
+         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
+              if (ait!=it->args.begin()) o << ", ";
+              o << ait->second << " _" << ait->first;
+         }
+         o << ") : ";
+         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
+              if (ait!=it->args.begin()) o << ", ";
+              o << ait->first << "(_" << ait->first << ")";
+         }
+         o << " {}\n"
+           << "        libt2n::result* operator()();\n"
+           << "};\n";
      }
-     else if(nodeComment)
-     {
-         print_indentation(indentation);
-         std::cout << "comment = " << nodeComment->get_content() << std::endl;
+}
+
+void output_common_cpp(std::ostream &o, const std::list<t2n_procedure> &procs, const std::string &common_hpp) {
+     std::set<std::string> groups(used_groups(procs));
+
+     o << "#include \"" << common_hpp << "\"\n"
+       << "#include <boost/serialization/export.hxx>\n"
+       << "\n"
+       << "/* register types with boost serialization */\n";
+     for (std::set<std::string>::iterator it=groups.begin();it!=groups.end();++it) {
+         o << "BOOST_CLASS_EXPORT(cmd_group_" << *it << ")\n";
      }
-     else if(nodeContent)
-     {
-         print_indentation(indentation);
-         std::cout << "content = " << nodeContent->get_content() << std::endl;
+     for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
+         o << "BOOST_CLASS_EXPORT("<<it->ret_classname()<<")\n"
+           << "BOOST_CLASS_EXPORT("<<it->cmd_classname()<<")\n";
      }
-     else if(const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node))
-     {
-         //A normal Element node:
+}
 
-         //line() works only for ElementNodes.
-         print_indentation(indentation);
-         std::cout << "     line = " << node->get_line() << std::endl;
+void output_client_hpp(std::ostream &o, const std::list<t2n_procedure> &procs) {
+     std::set<std::string> groups(used_groups(procs));
 
-         //Print attributes:
-         const xmlpp::Element::AttributeList& attributes = nodeElement->get_attributes();
-         for(xmlpp::Element::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
-         {
-              const xmlpp::Attribute* attribute = *iter;
-              print_indentation(indentation);
-              std::cout << "  Attribute " << attribute->get_name() << " = " << attribute->get_value() << std::endl;
+     o << "#include <command_client.hxx>\n";
+
+     for (std::set<std::string>::iterator it=groups.begin();it!=groups.end();++it) {
+         o << "class cmd_group_" << *it << "_client : public libt2n::command_client\n"
+           << "{\n"
+           << "public:\n"
+           << "cmd_group_" << *it << "_client(libt2n::client_connection &_c,\n"
+           << "        long long _command_timeout_usec=command_timeout_usec_default,\n"
+           << "        long long _hello_timeout_usec=hello_timeout_usec_default)\n"
+           << "        : libt2n::command_client(_c,_command_timeout_usec,_hello_timeout_usec)\n"
+            << "       {}\n";
+         for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
+              if (pit->group==*it) {
+                   o << "      " << *pit << ";\n";
+              }
          }
+         o << "};\n";
+     }
+}
 
-         const xmlpp::Attribute* attribute = nodeElement->get_attribute("title");
-         if(attribute)
-         {
-              std::cout << "title found: =" << attribute->get_value() << std::endl;
-      
+void output_client_cpp(std::ostream &o, const std::list<t2n_procedure> &procs, const std::string &common_hpp, const std::string &common_cpp, const std::string &client_hpp) {
+     std::set<std::string> groups(used_groups(procs));
+
+     o << "#include \"" << client_hpp << "\"\n"
+       << "#include \"" << common_hpp << "\"\n"
+       << "// fake\n";
+     for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
+         o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return NULL; }\n";
+     }
+
+     for (std::set<std::string>::iterator it=groups.begin();it!=groups.end();++it) {
+         for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
+              if (pit->group==*it) {
+                   o << pit->ret_type << " cmd_group_" << *it << "_client::" << pit->name << "(" << pit->args << ")\n"
+                     << "{\n"
+                     << "      libt2n::result_container rc;\n"
+                     << "      send_command(new " << pit->cmd_classname() << "(";
+                   for (t2n_procedure::Args::const_iterator ait=pit->args.begin();ait!=pit->args.end();++ait) {
+                        if (ait!=pit->args.begin()) o << ", ";
+                        o << ait->first;
+                   }
+                   o << "), rc);\n"
+                     << "      " << pit->ret_classname() << "* res=dynamic_cast<" << pit->ret_classname() << "*>(rc.get_result());\n"
+                     << "      if (!res) throw libt2n::t2n_communication_error(\"result object of wrong type\");\n"
+                     << "      return res->get_data();\n"
+                     << "}\n";
+              }
          }
      }
-  
-     if(!nodeContent)
-     {
-         //Recurse through child nodes:
-         xmlpp::Node::NodeList list = node->get_children();
-         for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
-         {
-              print_node(root, *iter, indentation + 2); //recursive
+
+     // include in this compilation unit to ensure the compilation unit is used
+     // see also:
+     // http://www.google.de/search?q=g%2B%2B+static+initializer+in+static+library
+     o << "#include \"" << common_cpp << "\"\n";
+}
+
+void output_server_cpp(std::ostream &o, const std::list<t2n_procedure> &procs, const std::string &common_hpp, const std::string &common_cpp) {
+     o << "#include \"" << common_hpp << "\"\n";
+
+     for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
+         o << *it << ";\n";
+         o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return new " << it->ret_classname() << "(" << it->name << "(";
+         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
+              if (ait!=it->args.begin()) o << ", ";
+              o << ait->first;
          }
+         o << ")); }\n";
      }
+     o << "#include \"" << common_cpp << "\"\n";
+}
+
+
+std::string
+toupper(std::string s) {
+     for (unsigned i=0; i<s.length(); ++i) s[i]=toupper(s[i]);
+     return s;
 }
 
-int main(int argc, char* argv[])
+std::string
+replace(std::string s, char f, char r) {
+     for (unsigned i=0; i<s.length(); ++i) if (s[i]==f) s[i]=r;
+     return s;
+}
+
+struct header_file : public std::ofstream
+{
+     header_file(const char* fname) : std::ofstream(fname) {
+         std::cerr << "create header: '" << fname << "'" << std::endl;
+         std::string macro(replace(toupper(fname),'.','_'));
+         *this << "// automatically generated code - do not edit\n" << std::endl;
+         *this << "#ifndef " << macro << "\n"
+               << "#define " << macro << "\n";
+     }
+     ~header_file() {
+         *this << "#endif" << std::endl;
+     }
+};
+
+struct cpp_file : public std::ofstream
+{
+     cpp_file(const char* fname) : std::ofstream(fname) {
+         std::cerr << "create cpp: '" << fname << "'" << std::endl;
+         *this << "// automatically generated code - do not edit\n" << std::endl;
+     }
+};
+
+int
+main(int argc, char* argv[])
 {
      std::string filepath;
      if(argc > 1 )
@@ -208,25 +471,38 @@ int main(int argc, char* argv[])
      else
          filepath = "example.xml";
   
-     try
-     {
-         xmlpp::DomParser parser;
-         //    parser.set_validate();
-         parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
-         parser.parse_file(filepath);
-         if(parser)
-         {
-              //Walk the tree:
-              const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
-              const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*>(pNode);
-              assert(root);
-              print_node(root);
-         }
-     }
-     catch(const std::exception& ex)
-     {
-         std::cout << "Exception caught: " << ex.what() << std::endl;
-     }
+     Parser parser(filepath);
+     std::list<t2n_procedure> procedures(parser.get_procedures());
+
+     std::cerr << "Procedures:" << std::endl;
+     for (std::list<t2n_procedure>::const_iterator it=procedures.begin();it!=procedures.end();++it)
+         std::cerr << *it << ";" << std::endl;
+
+     std::set<std::string> groups(used_groups(procedures));
+     std::cerr << "Used groups:" << std::endl;
+     for (std::set<std::string>::const_iterator it=groups.begin();it!=groups.end();++it)
+         std::cerr << *it << std::endl;
+
+     std::string prefix("test_");
+     std::string common_hpp_fname(prefix+"common.hxx");
+     std::string common_cpp_fname(prefix+"common.cpp");
+     std::string client_hpp_fname(prefix+"client.hxx");
+     std::string client_cpp_fname(prefix+"client.cpp");
+     std::string server_cpp_fname(prefix+"client.cpp");
+
+     header_file common_hpp(common_hpp_fname.c_str());
+     output_common_hpp(common_hpp, procedures);
+
+     cpp_file common_cpp(common_cpp_fname.c_str());
+     output_common_cpp(common_cpp, procedures, "common-stub.hxx");
+
+     header_file client_hpp(client_hpp_fname.c_str());
+     output_client_hpp(client_hpp, procedures);
+
+     cpp_file client_cpp(client_cpp_fname.c_str());
+     output_client_cpp(client_cpp, procedures, common_hpp_fname, common_cpp_fname, client_hpp_fname);
 
+     cpp_file server_cpp(server_cpp_fname.c_str());
+     output_server_cpp(server_cpp, procedures, common_hpp_fname, common_cpp_fname);
      return 0;
 }