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>
33 #include <boost/serialization/extended_type_info.hpp>
39 //! map group to class name
40 std::string groupClass(const std::string &group)
42 return std::string("cmd_group_")+group;
45 //! convert string to upper case
46 std::string toupper(std::string s)
48 for (unsigned i=0; i<s.length(); ++i) s[i]=toupper(s[i]);
52 //! replace all characters f by r in string s
53 std::string replace(std::string s, char f, char r)
55 for (unsigned i=0; i<s.length(); ++i) if (s[i]==f) s[i]=r;
59 //! strip prefix from string s
61 \return string s without prefix or an empty string on error
63 std::string strip(std::string s, std::string prefix)
66 if ( (prefix.length()>s.length() ) || ( std::string(s,0,prefix.length())!=prefix ) ) return error;
67 return std::string(s, prefix.length(), s.length()-prefix.length());
70 //! get child element by id
72 \return pointer to element having id or null on error
73 \todo find libxmlpp pendant
75 const xmlpp::Element* get_element_by_id(const xmlpp::Element* element, const std::string &id)
77 const xmlpp::Attribute* cid = element->get_attribute("id");
78 if ( cid && ( cid->get_value() == id)) return element;
80 //Recurse through child nodes:
81 xmlpp::Node::NodeList list = element->get_children();
82 for (xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
84 const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(*iter);
87 const xmlpp::Element* match = get_element_by_id(element, id);
88 if (match) return match;
94 std::string get_file(const xmlpp::Element* root, const std::string &file_id)
97 const xmlpp::Element* e=get_element_by_id(root, file_id);
98 if ((!e)||(!e->get_attribute("name"))) return error;
99 return e->get_attribute("name")->get_value();
102 //! get namespace by id
104 \return namespace name or empty string on error
106 std::string get_namespace(const xmlpp::Element* root, const std::string &id)
109 const xmlpp::Element* element(get_element_by_id(root, id));
110 // [RP:20071024]: use "demangled" attribute instead of "name" to cover nested namespaces:
111 if ((!element)||(!element->get_attribute("demangled"))) return error;
112 return element->get_attribute("demangled")->get_value();
115 //! procedure marked for export?
116 bool is_marked(const std::string &attrs)
118 // todo: improve this
119 std::string to_match("gccxml(libt2n-");
120 std::string::size_type p(attrs.find(to_match));
121 return (p!=std::string::npos);
127 std::string noref_name;
128 bool operator==(const type_info& o) {return (name==o.name) && (noref_name == o.noref_name);}
129 std::string noref() const {return noref_name.empty() ? name : noref_name;}
130 bool isVoid() const {return name=="void";}
133 std::ostream &operator<<(std::ostream &o, const type_info &t)
139 struct parse_error : public std::runtime_error
141 parse_error(const std::string &file, unsigned line, const std::string &msg)
142 : std::runtime_error(file+":"+boost::lexical_cast<std::string>(line)+": error: "+msg)
146 struct error_name_too_long : public std::runtime_error
148 error_name_too_long(const std::string &name)
149 : std::runtime_error("symbol name '" + name + "' too long for serialization ("
150 + boost::lexical_cast<std::string>(name.length()) + ">"
151 + boost::lexical_cast<std::string>(BOOST_SERIALIZATION_MAX_KEY_SIZE-1) + ")")
157 \return type name or empty string on error
159 type_info get_type(const xmlpp::Element* root, const std::string &id)
162 const xmlpp::Element* element(get_element_by_id(root, id));
163 if (!element) return error;
165 // TODO: not yet complete
166 // if we recurse - when do we stop?
167 // if it is a typedef? yes? (hmm if the typedef is in the file parsed this will not work)
169 // TODO: const and reference types handling is a ugly hack
171 std::string tag(element->get_name());
172 if (tag=="ReferenceType")
174 assert(element->get_attribute("type"));
175 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
176 if (ret==error) return error;
177 // at the moment we only support const &
178 // todo: nice error message!
179 if ((ret.noref_name=strip(ret.name,"const ")).empty()) return error;
180 ret.name=ret.name+"&";
183 else if (tag=="CvQualifiedType")
185 assert(element->get_attribute("type"));
186 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
187 if (ret==error) return error;
188 ret.name=std::string("const ")+ret.name;
191 else if (tag=="PointerType")
193 // todo: nearly the same as reference type handling
194 assert(element->get_attribute("type"));
195 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
196 if (ret==error) return error;
197 // at the moment we only support const &
198 // todo: nice error message!
199 if ((ret.noref_name=strip(ret.name,"const ")).empty()) return error;
200 ret.name=ret.name+"*";
204 assert(element->get_attribute("name"));
206 if (element->get_attribute("context"))
208 ret.name=get_namespace(root, element->get_attribute("context")->get_value());
212 // do not explicitely add ::
215 ret.name+=element->get_attribute("name")->get_value();
223 std::string defaultArg;
228 typedef std::list<Arg> Args;
235 std::string ret_classname() const
237 std::string result = name+mangled+"_res";
238 if (result.length() >= BOOST_SERIALIZATION_MAX_KEY_SIZE)
239 throw error_name_too_long(result);
242 std::string cmd_classname() const
244 std::string result = name+mangled+"_cmd";
245 if (result.length() >= BOOST_SERIALIZATION_MAX_KEY_SIZE)
246 throw error_name_too_long(result);
249 bool hasReturn() const {return !ret_type.isVoid();}
252 std::ostream &operator<<(std::ostream &o, const t2n_procedure::Args &args)
254 for (t2n_procedure::Args::const_iterator it=args.begin();it!=args.end();++it)
256 if (it!=args.begin()) o << ", ";
257 o << it->type << " " << it->name;
259 if (!it->defaultArg.empty())
260 o << "=" << it->defaultArg;
265 std::ostream &operator<<(std::ostream &o, const t2n_procedure &f)
267 o << f.ret_type << " " << f.name << "(" << f.args << ")";
271 std::pair<std::string, unsigned>
272 get_file_and_line(const xmlpp::Element* root, const xmlpp::Element* element)
274 return std::pair<std::string, unsigned>(get_file(root, element->get_attribute("file")->get_value()),
275 boost::lexical_cast<unsigned>(element->get_attribute("line")->get_value())-1);
278 std::string get_file_and_line_as_string(const xmlpp::Element* root, const xmlpp::Element* element)
280 std::pair<std::string, unsigned> fl(get_file_and_line(root,element));
281 return std::string(fl.first)+":"+boost::lexical_cast<std::string>(fl.second);
287 Parser(const std::string &fname) : m_fname(fname) {}
289 std::list<t2n_procedure> get_procedures()
291 xmlpp::DomParser parser;
292 // parser.set_validate();
293 parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
294 parser.parse_file(m_fname);
298 const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
299 const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*>(pNode);
307 std::list<t2n_procedure> m_procedures;
309 void parse_function(const xmlpp::Element* root, const xmlpp::Node* node)
311 const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
312 if (!element) return;
314 const xmlpp::Attribute* attributes = element->get_attribute("attributes");
315 const xmlpp::Attribute* name = element->get_attribute("name");
316 const xmlpp::Attribute* mangled = element->get_attribute("mangled");
317 const xmlpp::Attribute* returns = element->get_attribute("returns");
318 if ((!attributes)||(!name)||(!mangled)||(!returns)) return;
320 // check wether the procedure is marked (TODO: improve)
321 // attributes are speparated by spaces?
324 if (!is_marked(attributes->get_value())) return;
326 // we need the return type
327 f.ret_type=get_type(root, returns->get_value());
328 f.name=name->get_value();
329 f.mangled=mangled->get_value();
331 xmlpp::Node::NodeList list = node->get_children("Argument");
332 for (xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
334 const xmlpp::Element* arg = dynamic_cast<const xmlpp::Element*>(*iter);
339 assert(arg->get_name() == "Argument");
341 assert(arg->get_attribute("name"));
342 a.name=arg->get_attribute("name")->get_value();
344 assert(arg->get_attribute("type"));
345 a.type=get_type(root, arg->get_attribute("type")->get_value());
347 // this would be the nice way of solving the problem of default arguments
348 // but currently the output of gccxml is unreliable (e.g. namespace only
349 // sometimes available)
350 // if(arg->get_attribute("default"))
351 // a.defaultArg=arg->get_attribute("default")->get_value();
353 // so we need to get the def. arg. via attribute
354 // which is previously set by the macro LIBT2N_DEFAULT_ARG(type,value)
355 if(arg->get_attribute("attributes"))
357 std::string attr=arg->get_attribute("attributes")->get_value();
358 const std::string look_for = "gccxml(libt2n-default-arg,";
360 if (attr.compare(0,look_for.size(),look_for) == 0)
361 a.defaultArg=attr.substr(look_for.size(),attr.size()-look_for.size()-1);
364 // todo: ugly - could be any other error
365 if (a.type.name.empty())
367 assert(element->get_attribute("file"));
368 assert(element->get_attribute("line"));
369 std::pair<std::string, unsigned> file_and_line(get_file_and_line(root, element));
370 throw parse_error(file_and_line.first,
371 file_and_line.second,
372 std::string("type of parameter '")+a.name+"' not (yet?) supported");
378 std::cerr << get_file_and_line_as_string(root, element) << ":\texport procedure: " << f << std::endl;
379 m_procedures.push_back(f);
382 void visit_node(const xmlpp::Element* root, const xmlpp::Node* node = NULL, unsigned int indentation = 0)
384 if (!node) node=root;
386 const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
387 const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
388 const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
390 if (nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
393 std::string nodename = node->get_name();
395 if (!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
397 if (node->get_name() == "Function") parse_function(root, node);
401 //Recurse through child nodes:
402 xmlpp::Node::NodeList list = node->get_children();
403 for (xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
405 visit_node(root, *iter, indentation + 2); //recursive
411 void output_common_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs)
413 o << "class " << groupClass(group) << " : public libt2n::command\n"
416 << " friend class boost::serialization::access;\n"
417 << " template<class Archive>\n"
418 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
419 << " {ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::command);}\n"
422 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
424 o << "class " << it->ret_classname() << " : public libt2n::result\n"
428 o << " " << it->ret_type << " res;\n";
429 o << " friend class boost::serialization::access;\n"
430 << " template<class Archive>\n"
431 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
433 << " ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::result);\n";
435 o << " ar & BOOST_SERIALIZATION_NVP(res);\n";
438 << " " << it->ret_classname() << "() {}\n";
441 o << " " << it->ret_classname() << "(const " << it->ret_type << " &_res) : res(_res) {}\n"
442 << " " << it->ret_type << " get_data() { return res; }\n";
446 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
448 o << "class " << it->cmd_classname() << " : public " << groupClass(group) << "\n"
451 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
453 o << " " << ait->type.noref() << " " << ait->name << ";\n";
455 o << " friend class boost::serialization::access;\n"
456 << " template<class Archive>\n"
457 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
459 << " ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(" << groupClass(group) << ");\n";
460 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
462 o << " ar & BOOST_SERIALIZATION_NVP(" << ait->name << ");\n";
465 // default constructor
469 << " " << it->cmd_classname() << "() {}\n";
471 // constructor taking all arguments
472 if (!it->args.empty())
474 o << " " << it->cmd_classname() << "(";
475 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
477 if (ait!=it->args.begin()) o << ", ";
478 o << ait->type << " _" << ait->name;
481 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
483 if (ait!=it->args.begin()) o << ", ";
484 // pointers are const pointers and must be dereferenced
485 o << ait->name << "(" << ((ait->type.name.find_first_of('*')!=std::string::npos) ? "*" : "" ) << "_" << ait->name << ")";
489 o << " libt2n::result* operator()();\n"
494 void output_common_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp)
496 o << "#include \"" << common_hpp << "\"\n"
497 << "#include <boost/serialization/export.hpp>\n"
499 << "/* register types with boost serialization */\n";
500 o << "BOOST_CLASS_EXPORT(" << groupClass(group) << ")\n";
501 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
503 o << "BOOST_CLASS_EXPORT("<<it->ret_classname()<<")\n"
504 << "BOOST_CLASS_EXPORT("<<it->cmd_classname()<<")\n";
508 void output_client_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs)
510 o << "#include <command_client.hxx>\n";
512 o << "class " << groupClass(group) << "_client : public libt2n::command_client\n"
515 << groupClass(group) << "_client(libt2n::client_connection *_c,\n"
516 << " long long _command_timeout_usec=command_timeout_usec_default,\n"
517 << " long long _hello_timeout_usec=hello_timeout_usec_default)\n"
518 << " : libt2n::command_client(_c,_command_timeout_usec,_hello_timeout_usec)\n"
520 for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit)
522 o << " " << *pit << ";\n";
527 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)
529 o << "#include \"" << client_hpp << "\"\n"
530 << "#include \"" << common_hpp << "\"\n"
532 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
534 o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return NULL; }\n";
537 for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit)
539 o << pit->ret_type << " " << groupClass(group) << "_client::" << pit->name << "(";
541 // we need to do this by hand here because we don't want default arguments within the cpp
542 for (t2n_procedure::Args::const_iterator xit=pit->args.begin();xit!=pit->args.end();++xit)
544 if (xit!=pit->args.begin())
546 o << xit->type << " " << xit->name;
551 << " libt2n::result_container rc;\n"
552 << " send_command(new " << pit->cmd_classname() << "(";
553 for (t2n_procedure::Args::const_iterator ait=pit->args.begin();ait!=pit->args.end();++ait)
555 if (ait!=pit->args.begin()) o << ", ";
559 << " " << pit->ret_classname() << "* res=dynamic_cast<" << pit->ret_classname() << "*>(rc.get_result());\n"
560 << " if (!res) throw libt2n::t2n_communication_error(\"result object of wrong type\");\n";
561 if (pit->hasReturn())
562 o << " return res->get_data();\n";
566 // include in this compilation unit to ensure the compilation unit is used
568 // http://www.google.de/search?q=g%2B%2B+static+initializer+in+static+library
569 o << "#include \"" << common_cpp << "\"\n";
572 void output_server_hpp(std::ostream &o, const std::string & /* group */, const std::list<t2n_procedure> &procs, const std::string &common_hpp)
574 o << "#include \"" << common_hpp << "\"\n";
576 // output function declarations
577 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
581 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)
583 o << "#include \"" << common_hpp << "\"\n";
585 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
588 o << "libt2n::result* " << it->cmd_classname() << "::operator()() { ";
591 o << "return new " << it->ret_classname() << "(";
593 // output function name and args
594 o << it->name << "(";
595 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait)
597 if (ait!=it->args.begin()) o << ", ";
599 if (ait->type.name.find_first_of('*')!=std::string::npos)
607 o << "); return new " << it->ret_classname() << "();";
611 o << "#include \"" << common_cpp << "\"\n";
614 struct header_file : public std::ofstream
616 header_file(const char* fname) : std::ofstream(fname)
618 std::cerr << "create header: '" << fname << "'" << std::endl;
619 std::string macro(replace(toupper(fname),'.','_'));
620 *this << "// automatically generated code (generated by libt2n-codegen " << VERSION << ") - do not edit\n" << std::endl;
621 *this << "#ifndef " << macro << "\n"
622 << "#define " << macro << "\n";
626 *this << "#endif" << std::endl;
630 struct cpp_file : public std::ofstream
632 cpp_file(const char* fname) : std::ofstream(fname)
634 std::cerr << "create cpp: '" << fname << "'" << std::endl;
635 *this << "// automatically generated code - do not edit\n" << std::endl;
640 main(int argc, char* argv[])
642 // todo: maybe use getopt
643 if ((argc>1)&&(std::string(argv[1])=="--version"))
645 std::cerr << VERSION << std::endl;
650 std::cerr << "Usage: " << argv[0] << "default-group gccxml-file1 gccxml-file2 ... " << std::endl;
656 std::string group(argv[1]);
657 std::list<std::string> xmlfiles;
658 for (int i=2;i<argc;++i)
659 xmlfiles.push_back(argv[i]);
661 std::string prefix=group+"_";
662 std::list<t2n_procedure> procedures;
663 for (std::list<std::string>::iterator it=xmlfiles.begin();it!=xmlfiles.end();++it)
665 std::cerr << "Parse " << *it << std::endl;
667 const std::list<t2n_procedure> &p(parser.get_procedures());
668 std::copy(p.begin(), p.end(), std::back_inserter(procedures));
671 std::cerr << "All procedures:" << std::endl;
672 for (std::list<t2n_procedure>::const_iterator it=procedures.begin();it!=procedures.end();++it)
673 std::cerr << *it << ";" << std::endl;
675 std::string common_hpp_fname(prefix+"common.hxx");
676 std::string common_cpp_fname(prefix+"common.cpp");
677 std::string client_hpp_fname(prefix+"client.hxx");
678 std::string client_cpp_fname(prefix+"client.cpp");
679 std::string server_hpp_fname(prefix+"server.hxx");
680 std::string server_cpp_fname(prefix+"server.cpp");
682 header_file common_hpp(common_hpp_fname.c_str());
683 common_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
684 << "#include \"codegen-stubhead.hxx\"\n"
685 << "#include \"" << group << ".hxx\"\n";
687 output_common_hpp(common_hpp, group, procedures);
689 cpp_file common_cpp(common_cpp_fname.c_str());
690 output_common_cpp(common_cpp, group, procedures, common_hpp_fname);
692 header_file client_hpp(client_hpp_fname.c_str());
693 client_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
694 << "#include \"codegen-stubhead.hxx\"\n"
695 << "#include \"" << group << ".hxx\"\n";
696 output_client_hpp(client_hpp, group, procedures);
698 cpp_file client_cpp(client_cpp_fname.c_str());
699 output_client_cpp(client_cpp, group, procedures, common_hpp_fname, common_cpp_fname, client_hpp_fname);
701 header_file server_hpp(server_hpp_fname.c_str());
702 output_server_hpp(server_hpp, group, procedures, common_hpp_fname);
704 cpp_file server_cpp(server_cpp_fname.c_str());
705 output_server_cpp(server_cpp, group, procedures, common_hpp_fname, common_cpp_fname);
707 catch (const parse_error &e)
709 std::cerr << e.what() << std::endl;