6cf197e748e146de9d85629a0c20998db76d0090
[libt2n] / codegen / main.cpp
1 #include <libxml++/libxml++.h>
2 #include <cassert>
3 #include <iostream>
4 #include <set>
5 #include <fstream>
6
7 void print_indentation(unsigned int indentation)
8 {
9      for(unsigned int i = 0; i < indentation; ++i)
10           std::cout << " ";
11 }
12
13 std::string
14 toupper(std::string s) {
15      for (unsigned i=0; i<s.length(); ++i) s[i]=toupper(s[i]);
16      return s;
17 }
18
19 std::string
20 replace(std::string s, char f, char r) {
21      for (unsigned i=0; i<s.length(); ++i) if (s[i]==f) s[i]=r;
22      return s;
23 }
24
25 std::string
26 strip(std::string s, std::string prefix)
27 {
28      std::string error;
29      if ( (prefix.length()>s.length() ) || ( std::string(s,0,prefix.length())!=prefix ) ) return error;
30      return std::string(s, prefix.length(), s.length()-prefix.length());
31 }
32
33 //! get child element by id
34 /*!
35   \return pointer to element having id or null on error
36   \todo find libxmlpp pendant
37 */
38 const xmlpp::Element* get_element_by_id(const xmlpp::Element* element, const std::string &id)
39 {
40      const xmlpp::Attribute* cid = element->get_attribute("id");
41      if ( cid && ( cid->get_value() == id)) return element;
42
43      //Recurse through child nodes:
44      xmlpp::Node::NodeList list = element->get_children();
45      for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
46      {
47           const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(*iter);
48           if (element) {
49                const xmlpp::Element* match = get_element_by_id(element, id);
50                if (match) return match;
51           }
52      }
53      return NULL;
54 }
55
56 //! get namespace by id
57 /*!
58   \return namespace name or empty string on error
59 */
60 std::string get_namespace(const xmlpp::Element* root, const std::string &id)
61 {
62      std::string error;
63      const xmlpp::Element* element(get_element_by_id(root, id));
64      if ((!element)||(!element->get_attribute("name"))) return error;
65      return element->get_attribute("name")->get_value();
66 }
67
68
69 struct type_info
70 {
71      std::string name;
72      std::string noref_name;
73      bool operator==(const type_info& o) {return (name==o.name) && (noref_name == o.noref_name);}
74      std::string noref() const {return noref_name;}
75 };
76
77 std::ostream &operator<<(std::ostream &o, const type_info &t) {
78      o << t.name;
79      return o;
80 }
81
82 //! get type by id
83 /*!
84   \return type name or empty string on error
85 */
86 type_info get_type(const xmlpp::Element* root, const std::string &id)
87 {
88      type_info error;
89      const xmlpp::Element* element(get_element_by_id(root, id));
90      if (!element) return error;
91
92      // TODO: not yet complete
93      // if we recurse - when do we stop?
94      // if it is a typedef? yes? (hmm if the typedef is in the file parsed this will not work)
95
96      // TODO: const and reference types (perhaps it is good enough to remove the const and &?)
97      //       non-const reference types/pointers (output error message? not yet supported)
98
99      std::string tag(element->get_name());
100      if (tag=="ReferenceType") {
101           assert(element->get_attribute("type"));
102           type_info ret(get_type(root, element->get_attribute("type")->get_value()));
103           if (ret==error) return error;
104           // at the moment we only support const &
105           // todo: nice error message!
106           if ((ret.noref_name=strip(ret.name,"const ")).empty()) return error;
107           ret.name=ret.name+"&";
108           return ret;
109      }else if (tag=="CvQualifiedType") {
110           assert(element->get_attribute("type"));
111           type_info ret(get_type(root, element->get_attribute("type")->get_value()));
112           if (ret==error) return error;
113           ret.name=std::string("const ")+ret.name;
114           return ret;
115      }
116
117      assert(element->get_attribute("name"));
118      assert(element->get_attribute("context"));
119      type_info ret;
120      ret.name=get_namespace(root, element->get_attribute("context")->get_value())+"::"+element->get_attribute("name")->get_value();
121      return ret;
122 }
123
124 struct t2n_procedure
125 {
126      typedef std::map<std::string, type_info> Args;
127
128      std::string group;
129      type_info ret_type;
130      std::string name;
131      Args  args;
132
133      std::string ret_classname() const {
134           return name+"_res";
135      }
136      std::string cmd_classname() const {
137           return name+"_cmd";
138      }
139 };
140
141 std::ostream &operator<<(std::ostream &o, const t2n_procedure::Args &args) {
142      for (t2n_procedure::Args::const_iterator it=args.begin();it!=args.end();++it) {
143           if (it!=args.begin()) o << ", ";
144           o << it->second << " " << it->first;
145      }
146      return o;
147 }
148
149 std::ostream &operator<<(std::ostream &o, const t2n_procedure &f) {
150      o << f.ret_type << " " << f.name << "(" << f.args << ")";
151      return o;
152 }
153
154 class Parser
155 {
156 public:
157      Parser(const std::string &fname) : m_fname(fname) {}
158
159      std::list<t2n_procedure> get_procedures() {
160           xmlpp::DomParser parser;
161           //    parser.set_validate();
162           parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
163           parser.parse_file(m_fname);
164           if(parser)
165           {
166                //Walk the tree:
167                const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
168                const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*>(pNode);
169                assert(root);
170                visit_node(root);
171           }
172           return m_procedures;
173      }
174 protected:
175      std::string m_fname;
176      std::list<t2n_procedure> m_procedures;
177
178      void parse_function(const xmlpp::Element* root, const xmlpp::Node* node) {
179           const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
180           if (!element) return;
181
182           const xmlpp::Attribute* attributes = element->get_attribute("attributes");
183           const xmlpp::Attribute* name = element->get_attribute("name");
184           const xmlpp::Attribute* returns = element->get_attribute("returns");
185           if ((!attributes)||(!name)||(!returns)) return;
186
187           // check wether the procedure is marked (TODO: improve)
188           // attributes are speparated by spaces?
189           if (attributes->get_value().find("gccxml(libt2n")==std::string::npos) return;
190
191           // TODO: extract command group
192           // something like: sed 's,gccxml(libt2n-\([a-z]*}),\1,'
193           t2n_procedure f;
194           f.group=("default");
195
196           // we need the return type
197           f.ret_type=get_type(root, returns->get_value());
198           f.name=name->get_value();
199
200           xmlpp::Node::NodeList list = node->get_children("Argument");
201           for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
202           {
203                const xmlpp::Element* arg = dynamic_cast<const xmlpp::Element*>(*iter);
204                if ( arg ) {
205                     assert(arg->get_name() == "Argument");
206                     assert(arg->get_attribute("name"));
207                     assert(arg->get_attribute("type"));
208                     assert(f.args.find(arg->get_attribute("name")->get_value())==f.args.end());
209                     f.args[arg->get_attribute("name")->get_value()]=get_type(root, arg->get_attribute("type")->get_value());
210                }
211           }
212           m_procedures.push_back(f);
213      }
214
215      void visit_node(const xmlpp::Element* root, const xmlpp::Node* node = NULL, unsigned int indentation = 0)
216           {
217                if (!node) node=root;
218           
219                const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
220                const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
221                const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
222
223                if(nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
224                     return;
225     
226                std::string nodename = node->get_name();
227
228                if(!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
229                {
230                     //    print_indentation(indentation);
231                     //    std::cout << "Node name = " << node->get_name() << std::endl;
232                     //    std::cout << "Node name = " << nodename << std::endl;
233                     if (node->get_name() == "Function") {
234                          parse_function(root, node);
235                     }
236                }
237 #if 0
238                else if(nodeText) //Let's say when it's text. - e.g. let's say what that white space is.
239                {
240                     print_indentation(indentation);
241                     std::cout << "Text Node" << std::endl;
242                }
243
244                //Treat the various node types differently: 
245                if(nodeText)
246                {
247                     print_indentation(indentation);
248                     std::cout << "text = \"" << nodeText->get_content() << "\"" << std::endl;
249                }
250                else if(nodeComment)
251                {
252                     print_indentation(indentation);
253                     std::cout << "comment = " << nodeComment->get_content() << std::endl;
254                }
255                else if(nodeContent)
256                {
257                     print_indentation(indentation);
258                     std::cout << "content = " << nodeContent->get_content() << std::endl;
259                }
260                else if(const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node))
261                {
262                     //A normal Element node:
263
264                     //line() works only for ElementNodes.
265                     print_indentation(indentation);
266                     std::cout << "     line = " << node->get_line() << std::endl;
267
268                     //Print attributes:
269                     const xmlpp::Element::AttributeList& attributes = nodeElement->get_attributes();
270                     for(xmlpp::Element::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
271                     {
272                          const xmlpp::Attribute* attribute = *iter;
273                          print_indentation(indentation);
274                          std::cout << "  Attribute " << attribute->get_name() << " = " << attribute->get_value() << std::endl;
275                     }
276
277                     const xmlpp::Attribute* attribute = nodeElement->get_attribute("title");
278                     if(attribute)
279                     {
280                          std::cout << "title found: =" << attribute->get_value() << std::endl;
281       
282                     }
283                }
284 #endif
285                if(!nodeContent)
286                {
287                     //Recurse through child nodes:
288                     xmlpp::Node::NodeList list = node->get_children();
289                     for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
290                     {
291                          visit_node(root, *iter, indentation + 2); //recursive
292                     }
293                }
294           }
295 };
296
297 //! find used groups
298 std::set<std::string>
299 used_groups(const std::list<t2n_procedure> &funcs) {
300      typedef std::set<std::string> Groups;
301      Groups groups;
302      for (std::list<t2n_procedure>::const_iterator it=funcs.begin();it!=funcs.end();++it)
303           // since we use std::set each group is inserted only once
304           groups.insert(it->group);
305      return groups;
306 }
307
308 void output_common_hpp(std::ostream &o, const std::list<t2n_procedure> &procs) {
309      std::set<std::string> groups(used_groups(procs));
310
311      o << "#include \"codegen-stubhead.hxx\"\n";
312      for (std::set<std::string>::iterator it=groups.begin();it!=groups.end();++it) {
313           o << "class cmd_group_" << *it << " : public libt2n::command\n"
314             << "{\n"
315             << "private:\n"
316             << "        friend class boost::serialization::access;\n"
317             << "        template<class Archive>\n"
318             << "        void serialize(Archive & ar, const unsigned int version)\n"
319             << "        {ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::command);}\n"
320             << "};\n";
321      }
322      
323      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
324           o << "class " << it->ret_classname() << " : public libt2n::result\n"
325             << "{\n"
326             << "private:\n"
327             << "        " << it->ret_type << " res;\n"
328             << "        friend class boost::serialization::access;\n"
329             << "        template<class Archive>\n"
330             << "        void serialize(Archive & ar, const unsigned int version)\n"
331             << "        {\n"
332             << "                ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::result);\n"
333             << "                ar & BOOST_SERIALIZATION_NVP(res);\n"
334             << "        }\n"
335             << "public:\n"
336             << "        " << it->ret_classname() << "() {}\n"
337             << "        " << it->ret_classname() << "(const " << it->ret_type << " &_res) : res(_res) {}\n"
338             << "        " << it->ret_type << " get_data() { return res; }\n"
339             << "};\n";
340      }
341      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
342           o << "class " << it->cmd_classname() << " : public " << "cmd_group_" << it->group << "\n"
343             << "{\n"
344             << "private:\n";
345           for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
346                o << "   " << ait->second.noref() << " " << ait->first << ";\n";
347           }
348           o << "        friend class boost::serialization::access;\n"
349             << "        template<class Archive>\n"
350             << "        void serialize(Archive & ar, const unsigned int version)\n"
351             << "        {\n"
352             << "                ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(cmd_group_" << it->group << ");\n";
353           for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
354                o << "           ar & BOOST_SERIALIZATION_NVP(" << ait->first << ");\n";
355           }
356
357           // default constructor
358           o << "        }\n"
359             << "\n"
360             << "public:\n"
361             << "        " << it->cmd_classname() << "() {}\n";
362
363           // constructor taking all arguments
364           o << "        " << it->cmd_classname() << "(";
365           for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
366                if (ait!=it->args.begin()) o << ", ";
367                o << ait->second << " _" << ait->first;
368           }
369           o << ") : ";
370           for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
371                if (ait!=it->args.begin()) o << ", ";
372                o << ait->first << "(_" << ait->first << ")";
373           }
374           o << " {}\n"
375             << "        libt2n::result* operator()();\n"
376             << "};\n";
377      }
378 }
379
380 void output_common_cpp(std::ostream &o, const std::list<t2n_procedure> &procs, const std::string &common_hpp) {
381      std::set<std::string> groups(used_groups(procs));
382
383      o << "#include \"" << common_hpp << "\"\n"
384        << "#include <boost/serialization/export.hpp>\n"
385        << "\n"
386        << "/* register types with boost serialization */\n";
387      for (std::set<std::string>::iterator it=groups.begin();it!=groups.end();++it) {
388           o << "BOOST_CLASS_EXPORT(cmd_group_" << *it << ")\n";
389      }
390      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
391           o << "BOOST_CLASS_EXPORT("<<it->ret_classname()<<")\n"
392             << "BOOST_CLASS_EXPORT("<<it->cmd_classname()<<")\n";
393      }
394 }
395
396 void output_client_hpp(std::ostream &o, const std::list<t2n_procedure> &procs) {
397      std::set<std::string> groups(used_groups(procs));
398
399      o << "#include <command_client.hxx>\n";
400
401      for (std::set<std::string>::iterator it=groups.begin();it!=groups.end();++it) {
402           o << "class cmd_group_" << *it << "_client : public libt2n::command_client\n"
403             << "{\n"
404             << "public:\n"
405             << "cmd_group_" << *it << "_client(libt2n::client_connection &_c,\n"
406             << "        long long _command_timeout_usec=command_timeout_usec_default,\n"
407             << "        long long _hello_timeout_usec=hello_timeout_usec_default)\n"
408             << "        : libt2n::command_client(_c,_command_timeout_usec,_hello_timeout_usec)\n"
409             << "        {}\n";
410           for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
411                if (pit->group==*it) {
412                     o << "      " << *pit << ";\n";
413                }
414           }
415           o << "};\n";
416      }
417 }
418
419 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) {
420      std::set<std::string> groups(used_groups(procs));
421
422      o << "#include \"" << client_hpp << "\"\n"
423        << "#include \"" << common_hpp << "\"\n"
424        << "// fake\n";
425      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
426           o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return NULL; }\n";
427      }
428
429      for (std::set<std::string>::iterator it=groups.begin();it!=groups.end();++it) {
430           for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
431                if (pit->group==*it) {
432                     o << pit->ret_type << " cmd_group_" << *it << "_client::" << pit->name << "(" << pit->args << ")\n"
433                       << "{\n"
434                       << "      libt2n::result_container rc;\n"
435                       << "      send_command(new " << pit->cmd_classname() << "(";
436                     for (t2n_procedure::Args::const_iterator ait=pit->args.begin();ait!=pit->args.end();++ait) {
437                          if (ait!=pit->args.begin()) o << ", ";
438                          o << ait->first;
439                     }
440                     o << "), rc);\n"
441                       << "      " << pit->ret_classname() << "* res=dynamic_cast<" << pit->ret_classname() << "*>(rc.get_result());\n"
442                       << "      if (!res) throw libt2n::t2n_communication_error(\"result object of wrong type\");\n"
443                       << "      return res->get_data();\n"
444                       << "}\n";
445                }
446           }
447      }
448
449      // include in this compilation unit to ensure the compilation unit is used
450      // see also:
451      // http://www.google.de/search?q=g%2B%2B+static+initializer+in+static+library
452      o << "#include \"" << common_cpp << "\"\n";
453 }
454
455 void output_server_cpp(std::ostream &o, const std::list<t2n_procedure> &procs, const std::string &common_hpp, const std::string &common_cpp) {
456      o << "#include \"" << common_hpp << "\"\n";
457
458      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
459           o << *it << ";\n";
460           o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return new " << it->ret_classname() << "(" << it->name << "(";
461           for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
462                if (ait!=it->args.begin()) o << ", ";
463                o << ait->first;
464           }
465           o << ")); }\n";
466      }
467      o << "#include \"" << common_cpp << "\"\n";
468 }
469
470 struct header_file : public std::ofstream
471 {
472      header_file(const char* fname) : std::ofstream(fname) {
473           std::cerr << "create header: '" << fname << "'" << std::endl;
474           std::string macro(replace(toupper(fname),'.','_'));
475           *this << "// automatically generated code - do not edit\n" << std::endl;
476           *this << "#ifndef " << macro << "\n"
477                 << "#define " << macro << "\n";
478      }
479      ~header_file() {
480           *this << "#endif" << std::endl;
481      }
482 };
483
484 struct cpp_file : public std::ofstream
485 {
486      cpp_file(const char* fname) : std::ofstream(fname) {
487           std::cerr << "create cpp: '" << fname << "'" << std::endl;
488           *this << "// automatically generated code - do not edit\n" << std::endl;
489      }
490 };
491
492 int
493 main(int argc, char* argv[])
494 {
495      if (argc != 3) {
496           std::cerr << "Usage: " << argv[0] << " gccxml-file outputprefix" << std::endl;
497           return 1;
498      }
499      std::string filepath(argv[1]);
500      std::string prefix(argv[2]);
501   
502      Parser parser(filepath);
503      std::list<t2n_procedure> procedures(parser.get_procedures());
504
505      std::cerr << "Procedures:" << std::endl;
506      for (std::list<t2n_procedure>::const_iterator it=procedures.begin();it!=procedures.end();++it)
507           std::cerr << *it << ";" << std::endl;
508
509      std::set<std::string> groups(used_groups(procedures));
510      std::cerr << "Used groups:" << std::endl;
511      for (std::set<std::string>::const_iterator it=groups.begin();it!=groups.end();++it)
512           std::cerr << *it << std::endl;
513
514      std::string common_hpp_fname(prefix+"common.hxx");
515      std::string common_cpp_fname(prefix+"common.cpp");
516      std::string client_hpp_fname(prefix+"client.hxx");
517      std::string client_cpp_fname(prefix+"client.cpp");
518      std::string server_cpp_fname(prefix+"server.cpp");
519
520      header_file common_hpp(common_hpp_fname.c_str());
521      output_common_hpp(common_hpp, procedures);
522
523      cpp_file common_cpp(common_cpp_fname.c_str());
524      output_common_cpp(common_cpp, procedures, common_hpp_fname);
525
526      header_file client_hpp(client_hpp_fname.c_str());
527      output_client_hpp(client_hpp, procedures);
528
529      cpp_file client_cpp(client_cpp_fname.c_str());
530      output_client_cpp(client_cpp, procedures, common_hpp_fname, common_cpp_fname, client_hpp_fname);
531
532      cpp_file server_cpp(server_cpp_fname.c_str());
533      output_server_cpp(server_cpp, procedures, common_hpp_fname, common_cpp_fname);
534      return 0;
535 }