2 Copyright (C) 2006-2008 by Intra2net AG
5 The software in this package is distributed under the GNU General
6 Public License version 2 (with a special exception described below).
8 A copy of GNU General Public License (GPL) is included in this distribution,
9 in the file COPYING.GPL.
11 As a special exception, if other files instantiate templates or use macros
12 or inline functions from this file, or you compile this file and link it
13 with other works to produce a work based on this file, this file
14 does not by itself cause the resulting work to be covered
15 by the GNU General Public License.
17 However the source code for this file must still be made available
18 in accordance with section (3) of the GNU General Public License.
20 This exception does not invalidate any other reasons why a work based
21 on this file might be covered by the GNU General Public License.
24 #include <libxml++/libxml++.h>
32 #include <boost/lexical_cast.hpp>
38 //! map group to class name
39 std::string groupClass(const std::string &group)
41 return std::string("cmd_group_")+group;
44 //! convert string to upper case
45 std::string toupper(std::string s)
47 for (unsigned i=0; i<s.length(); ++i) s[i]=toupper(s[i]);
51 //! replace all characters f by r in string s
52 std::string replace(std::string s, char f, char r)
54 for (unsigned i=0; i<s.length(); ++i) if (s[i]==f) s[i]=r;
58 //! strip prefix from string s
60 \return string s without prefix or an empty string on error
62 std::string strip(std::string s, std::string prefix)
65 if ( (prefix.length()>s.length() ) || ( std::string(s,0,prefix.length())!=prefix ) ) return error;
66 return std::string(s, prefix.length(), s.length()-prefix.length());
69 //! get child element by id
71 \return pointer to element having id or null on error
72 \todo find libxmlpp pendant
74 const xmlpp::Element* get_element_by_id(const xmlpp::Element* element, const std::string &id)
76 const xmlpp::Attribute* cid = element->get_attribute("id");
77 if ( cid && ( cid->get_value() == id)) return element;
79 //Recurse through child nodes:
80 xmlpp::Node::NodeList list = element->get_children();
81 for (xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
83 const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(*iter);
86 const xmlpp::Element* match = get_element_by_id(element, id);
87 if (match) return match;
93 std::string get_file(const xmlpp::Element* root, const std::string &file_id)
96 const xmlpp::Element* e=get_element_by_id(root, file_id);
97 if ((!e)||(!e->get_attribute("name"))) return error;
98 return e->get_attribute("name")->get_value();
101 //! get namespace by id
103 \return namespace name or empty string on error
105 std::string get_namespace(const xmlpp::Element* root, const std::string &id)
108 const xmlpp::Element* element(get_element_by_id(root, id));
109 // [RP:20071024]: use "demangled" attribute instead of "name" to cover nested namespaces:
110 if ((!element)||(!element->get_attribute("demangled"))) return error;
111 return element->get_attribute("demangled")->get_value();
114 //! procedure marked for export?
115 bool is_marked(const std::string &attrs)
117 // todo: improve this
118 std::string to_match("gccxml(libt2n-");
119 std::string::size_type p(attrs.find(to_match));
120 return (p!=std::string::npos);
126 std::string noref_name;
127 bool operator==(const type_info& o) {return (name==o.name) && (noref_name == o.noref_name);}
128 std::string noref() const {return noref_name.empty() ? name : noref_name;}
129 bool isVoid() const {return name=="void";}
132 std::ostream &operator<<(std::ostream &o, const type_info &t)
138 struct parse_error : public std::runtime_error
140 parse_error(const std::string &file, unsigned line, const std::string &msg)
141 : std::runtime_error(file+":"+boost::lexical_cast<std::string>(line)+": error: "+msg)
147 \return type name or empty string on error
149 type_info get_type(const xmlpp::Element* root, const std::string &id)
152 const xmlpp::Element* element(get_element_by_id(root, id));
153 if (!element) return error;
155 // TODO: not yet complete
156 // if we recurse - when do we stop?
157 // if it is a typedef? yes? (hmm if the typedef is in the file parsed this will not work)
159 // TODO: const and reference types handling is a ugly hack
161 std::string tag(element->get_name());
162 if (tag=="ReferenceType")
164 assert(element->get_attribute("type"));
165 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
166 if (ret==error) return error;
167 // at the moment we only support const &
168 // todo: nice error message!
169 if ((ret.noref_name=strip(ret.name,"const ")).empty()) return error;
170 ret.name=ret.name+"&";
173 else if (tag=="CvQualifiedType")
175 assert(element->get_attribute("type"));
176 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
177 if (ret==error) return error;
178 ret.name=std::string("const ")+ret.name;
181 else if (tag=="PointerType")
183 // todo: nearly the same as reference type handling
184 assert(element->get_attribute("type"));
185 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
186 if (ret==error) return error;
187 // at the moment we only support const &
188 // todo: nice error message!
189 if ((ret.noref_name=strip(ret.name,"const ")).empty()) return error;
190 ret.name=ret.name+"*";
194 assert(element->get_attribute("name"));
196 if (element->get_attribute("context"))
198 ret.name=get_namespace(root, element->get_attribute("context")->get_value());
202 // do not explicitely add ::
205 ret.name+=element->get_attribute("name")->get_value();
213 std::string defaultArg;
218 typedef std::list<Arg> Args;
225 std::string ret_classname() const
227 return name+mangled+"_res";
229 std::string cmd_classname() const
231 return name+mangled+"_cmd";
233 bool hasReturn() const {return !ret_type.isVoid();}
236 std::ostream &operator<<(std::ostream &o, const t2n_procedure::Args &args)
238 for (t2n_procedure::Args::const_iterator it=args.begin();it!=args.end();++it)
240 if (it!=args.begin()) o << ", ";
241 o << it->type << " " << it->name;
243 if (!it->defaultArg.empty())
244 o << "=" << it->defaultArg;
249 std::ostream &operator<<(std::ostream &o, const t2n_procedure &f)
251 o << f.ret_type << " " << f.name << "(" << f.args << ")";
255 std::pair<std::string, unsigned>
256 get_file_and_line(const xmlpp::Element* root, const xmlpp::Element* element)
258 return std::pair<std::string, unsigned>(get_file(root, element->get_attribute("file")->get_value()),
259 boost::lexical_cast<unsigned>(element->get_attribute("line")->get_value())-1);
262 std::string get_file_and_line_as_string(const xmlpp::Element* root, const xmlpp::Element* element)
264 std::pair<std::string, unsigned> fl(get_file_and_line(root,element));
265 return std::string(fl.first)+":"+boost::lexical_cast<std::string>(fl.second);
271 Parser(const std::string &fname) : m_fname(fname) {}
273 std::list<t2n_procedure> get_procedures()
275 xmlpp::DomParser parser;
276 // parser.set_validate();
277 parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
278 parser.parse_file(m_fname);
282 const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
283 const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*>(pNode);
291 std::list<t2n_procedure> m_procedures;
293 void parse_function(const xmlpp::Element* root, const xmlpp::Node* node)
295 const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
296 if (!element) return;
298 const xmlpp::Attribute* attributes = element->get_attribute("attributes");
299 const xmlpp::Attribute* name = element->get_attribute("name");
300 const xmlpp::Attribute* mangled = element->get_attribute("mangled");
301 const xmlpp::Attribute* returns = element->get_attribute("returns");
302 if ((!attributes)||(!name)||(!mangled)||(!returns)) return;
304 // check wether the procedure is marked (TODO: improve)
305 // attributes are speparated by spaces?
308 if (!is_marked(attributes->get_value())) return;
310 // we need the return type
311 f.ret_type=get_type(root, returns->get_value());
312 f.name=name->get_value();
313 f.mangled=mangled->get_value();
315 xmlpp::Node::NodeList list = node->get_children("Argument");
316 for (xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
318 const xmlpp::Element* arg = dynamic_cast<const xmlpp::Element*>(*iter);
323 assert(arg->get_name() == "Argument");
325 assert(arg->get_attribute("name"));
326 a.name=arg->get_attribute("name")->get_value();
328 assert(arg->get_attribute("type"));
329 a.type=get_type(root, arg->get_attribute("type")->get_value());
331 // this would be the nice way of solving the problem of default arguments
332 // but currently the output of gccxml is unreliable (e.g. namespace only
333 // sometimes available)
334 // if(arg->get_attribute("default"))
335 // a.defaultArg=arg->get_attribute("default")->get_value();
337 // so we need to get the def. arg. via attribute
338 // which is previously set by the macro LIBT2N_DEFAULT_ARG(type,value)
339 if(arg->get_attribute("attributes"))
341 std::string attr=arg->get_attribute("attributes")->get_value();
342 const std::string look_for = "gccxml(libt2n-default-arg,";
344 if (attr.compare(0,look_for.size(),look_for) == 0)
345 a.defaultArg=attr.substr(look_for.size(),attr.size()-look_for.size()-1);
348 // todo: ugly - could be any other error
349 if (a.type.name.empty())
351 assert(element->get_attribute("file"));
352 assert(element->get_attribute("line"));
353 std::pair<std::string, unsigned> file_and_line(get_file_and_line(root, element));
354 throw parse_error(file_and_line.first,
355 file_and_line.second,
356 std::string("type of parameter '")+a.name+"' not (yet?) supported");
362 std::cerr << get_file_and_line_as_string(root, element) << ":\texport procedure: " << f << std::endl;
363 m_procedures.push_back(f);
366 void visit_node(const xmlpp::Element* root, const xmlpp::Node* node = NULL, unsigned int indentation = 0)
368 if (!node) node=root;
370 const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
371 const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
372 const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
374 if (nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
377 std::string nodename = node->get_name();
379 if (!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
381 if (node->get_name() == "Function") parse_function(root, node);
385 //Recurse through child nodes:
386 xmlpp::Node::NodeList list = node->get_children();
387 for (xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
389 visit_node(root, *iter, indentation + 2); //recursive
395 void output_common_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs)
397 o << "class " << groupClass(group) << " : public libt2n::command\n"
400 << " friend class boost::serialization::access;\n"
401 << " template<class Archive>\n"
402 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
403 << " {ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::command);}\n"
406 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
408 o << "class " << it->ret_classname() << " : public libt2n::result\n"
412 o << " " << it->ret_type << " res;\n";
413 o << " friend class boost::serialization::access;\n"
414 << " template<class Archive>\n"
415 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
417 << " ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::result);\n";
419 o << " ar & BOOST_SERIALIZATION_NVP(res);\n";
422 << " " << it->ret_classname() << "() {}\n";
425 o << " " << it->ret_classname() << "(const " << it->ret_type << " &_res) : res(_res) {}\n"
426 << " " << it->ret_type << " get_data() { return res; }\n";
430 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
432 o << "class " << it->cmd_classname() << " : public " << groupClass(group) << "\n"
435 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
437 o << " " << ait->type.noref() << " " << ait->name << ";\n";
439 o << " friend class boost::serialization::access;\n"
440 << " template<class Archive>\n"
441 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
443 << " ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(" << groupClass(group) << ");\n";
444 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
446 o << " ar & BOOST_SERIALIZATION_NVP(" << ait->name << ");\n";
449 // default constructor
453 << " " << it->cmd_classname() << "() {}\n";
455 // constructor taking all arguments
456 if (!it->args.empty())
458 o << " " << it->cmd_classname() << "(";
459 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
461 if (ait!=it->args.begin()) o << ", ";
462 o << ait->type << " _" << ait->name;
465 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
467 if (ait!=it->args.begin()) o << ", ";
468 // pointers are const pointers and must be dereferenced
469 o << ait->name << "(" << ((ait->type.name.find_first_of('*')!=std::string::npos) ? "*" : "" ) << "_" << ait->name << ")";
473 o << " libt2n::result* operator()();\n"
478 void output_common_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp)
480 o << "#include \"" << common_hpp << "\"\n"
481 << "#include <boost/serialization/export.hpp>\n"
483 << "/* register types with boost serialization */\n";
484 o << "BOOST_CLASS_EXPORT(" << groupClass(group) << ")\n";
485 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
487 o << "BOOST_CLASS_EXPORT("<<it->ret_classname()<<")\n"
488 << "BOOST_CLASS_EXPORT("<<it->cmd_classname()<<")\n";
492 void output_client_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs)
494 o << "#include <command_client.hxx>\n";
496 o << "class " << groupClass(group) << "_client : public libt2n::command_client\n"
499 << groupClass(group) << "_client(libt2n::client_connection *_c,\n"
500 << " long long _command_timeout_usec=command_timeout_usec_default,\n"
501 << " long long _hello_timeout_usec=hello_timeout_usec_default)\n"
502 << " : libt2n::command_client(_c,_command_timeout_usec,_hello_timeout_usec)\n"
504 for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit)
506 o << " " << *pit << ";\n";
511 void output_client_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp, const std::string &common_cpp, const std::string &client_hpp)
513 o << "#include \"" << client_hpp << "\"\n"
514 << "#include \"" << common_hpp << "\"\n"
516 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
518 o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return NULL; }\n";
521 for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit)
523 o << pit->ret_type << " " << groupClass(group) << "_client::" << pit->name << "(";
525 // we need to do this by hand here because we don't want default arguments within the cpp
526 for (t2n_procedure::Args::const_iterator xit=pit->args.begin();xit!=pit->args.end();++xit)
528 if (xit!=pit->args.begin())
530 o << xit->type << " " << xit->name;
535 << " libt2n::result_container rc;\n"
536 << " send_command(new " << pit->cmd_classname() << "(";
537 for (t2n_procedure::Args::const_iterator ait=pit->args.begin();ait!=pit->args.end();++ait)
539 if (ait!=pit->args.begin()) o << ", ";
543 << " " << pit->ret_classname() << "* res=dynamic_cast<" << pit->ret_classname() << "*>(rc.get_result());\n"
544 << " if (!res) throw libt2n::t2n_communication_error(\"result object of wrong type\");\n";
545 if (pit->hasReturn())
546 o << " return res->get_data();\n";
550 // include in this compilation unit to ensure the compilation unit is used
552 // http://www.google.de/search?q=g%2B%2B+static+initializer+in+static+library
553 o << "#include \"" << common_cpp << "\"\n";
556 void output_server_hpp(std::ostream &o, const std::string & /* group */, const std::list<t2n_procedure> &procs, const std::string &common_hpp)
558 o << "#include \"" << common_hpp << "\"\n";
560 // output function declarations
561 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
565 void output_server_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp, const std::string &common_cpp)
567 o << "#include \"" << common_hpp << "\"\n";
569 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
572 o << "libt2n::result* " << it->cmd_classname() << "::operator()() { ";
575 o << "return new " << it->ret_classname() << "(";
577 // output function name and args
578 o << it->name << "(";
579 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
581 if (ait!=it->args.begin()) o << ", ";
583 if (ait->type.name.find_first_of('*')!=std::string::npos)
591 o << "); return new " << it->ret_classname() << "();";
595 o << "#include \"" << common_cpp << "\"\n";
598 struct header_file : public std::ofstream
600 header_file(const char* fname) : std::ofstream(fname)
602 std::cerr << "create header: '" << fname << "'" << std::endl;
603 std::string macro(replace(toupper(fname),'.','_'));
604 *this << "// automatically generated code (generated by libt2n-codegen " << VERSION << ") - do not edit\n" << std::endl;
605 *this << "#ifndef " << macro << "\n"
606 << "#define " << macro << "\n";
610 *this << "#endif" << std::endl;
614 struct cpp_file : public std::ofstream
616 cpp_file(const char* fname) : std::ofstream(fname)
618 std::cerr << "create cpp: '" << fname << "'" << std::endl;
619 *this << "// automatically generated code - do not edit\n" << std::endl;
624 main(int argc, char* argv[])
626 // todo: maybe use getopt
627 if ((argc>1)&&(std::string(argv[1])=="--version"))
629 std::cerr << VERSION << std::endl;
634 std::cerr << "Usage: " << argv[0] << "default-group gccxml-file1 gccxml-file2 ... " << std::endl;
640 std::string group(argv[1]);
641 std::list<std::string> xmlfiles;
642 for (int i=2;i<argc;++i)
643 xmlfiles.push_back(argv[i]);
645 std::string prefix=group+"_";
646 std::list<t2n_procedure> procedures;
647 for (std::list<std::string>::iterator it=xmlfiles.begin();it!=xmlfiles.end();++it)
649 std::cerr << "Parse " << *it << std::endl;
651 const std::list<t2n_procedure> &p(parser.get_procedures());
652 std::copy(p.begin(), p.end(), std::back_inserter(procedures));
655 std::cerr << "All procedures:" << std::endl;
656 for (std::list<t2n_procedure>::const_iterator it=procedures.begin();it!=procedures.end();++it)
657 std::cerr << *it << ";" << std::endl;
659 std::string common_hpp_fname(prefix+"common.hxx");
660 std::string common_cpp_fname(prefix+"common.cpp");
661 std::string client_hpp_fname(prefix+"client.hxx");
662 std::string client_cpp_fname(prefix+"client.cpp");
663 std::string server_hpp_fname(prefix+"server.hxx");
664 std::string server_cpp_fname(prefix+"server.cpp");
666 header_file common_hpp(common_hpp_fname.c_str());
667 common_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
668 << "#include \"codegen-stubhead.hxx\"\n"
669 << "#include \"" << group << ".hxx\"\n";
671 output_common_hpp(common_hpp, group, procedures);
673 cpp_file common_cpp(common_cpp_fname.c_str());
674 output_common_cpp(common_cpp, group, procedures, common_hpp_fname);
676 header_file client_hpp(client_hpp_fname.c_str());
677 client_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
678 << "#include \"codegen-stubhead.hxx\"\n"
679 << "#include \"" << group << ".hxx\"\n";
680 output_client_hpp(client_hpp, group, procedures);
682 cpp_file client_cpp(client_cpp_fname.c_str());
683 output_client_cpp(client_cpp, group, procedures, common_hpp_fname, common_cpp_fname, client_hpp_fname);
685 header_file server_hpp(server_hpp_fname.c_str());
686 output_server_hpp(server_hpp, group, procedures, common_hpp_fname);
688 cpp_file server_cpp(server_cpp_fname.c_str());
689 output_server_cpp(server_cpp, group, procedures, common_hpp_fname, common_cpp_fname);
691 catch (const parse_error &e)
693 std::cerr << e.what() << std::endl;