45aa9d70a6dee52debba714542870e50eccaf26b
[libt2n] / codegen / main.cpp
1 /*
2     Copyright (C) 2006                                                    
3     intra2net.com                                                         
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 /*! \mainpage libt2n - (talk2neighbor)
21  \section intro_sec Introduction
22  libt2n (talk2neighbor) is a C++ library for inter-process communication (IPC, s.a. http://en.wikipedia.org/wiki/Inter-process_communication)
23  with an additional code generator to make remote procedure calls simple. XXX: improve this paragraph: The input for the code generator is standard C++ code (in fact we use gccxml to parse the C++ code and the code generator takes the XML as input) and you mark the procedures you want to expose to other processes.
24  It then generates the stubs needed.
25  The exported procedures can be grouped. For each group the code generator is called which generates 6 outputfiles: group_common.hxx, group_common.cpp, group_client.hxx, group_client.cpp, group_server.hxx, group_server.cpp. The _common files are used by client and server whereas the _client files are used by the client and the _server files by the server. 
26
27  \section install_sec Installation
28
29  \subsection requirements Requirements
30  - boost <http://www.boost.org/> (serialization <http://www.boost.org/libs/serialization/doc/>)
31  - gccxml <http://www.gccxml.org>
32  - libxmlpp <http://libxmlplusplus.sourceforge.net/>
33
34  \section usage Usage example
35
36  In this example we create two packages:
37  - server program and library to connect to the server. The server exports a simple procedure using one group: "t2nexample"
38  - client program using the library
39
40  \subsection server Example server program and client library
41
42  \par The procedure to export (input for the code generator - libt2n-codegen): t2nexample.cpp:
43  \verbinclude libt2n-example1/t2nexample.cpp
44
45  \par Required includes must be put into a seperate group header file: t2nexample.hxx:
46  \verbinclude libt2n-example1/t2nexample.hxx
47
48  \par The server program:
49  \verbinclude libt2n-example1/server.cpp
50
51  \par Using autoconf and automake to build a example server program and a client library.
52  In the configure.in(.ac) we put a check for libt2n:
53  \verbinclude libt2n-example1/configure.in
54  Writing the Makefile.am isn't difficult either:
55  \verbinclude libt2n-example1/Makefile.am
56
57  \subsection client Client using the library
58  Using the library is as simple as using any other library using pkg-config (the pkg-config .pc file is created automatically by the included Makefile snippet)
59  \par We only have to check that the library is installed
60  \verbinclude libt2n-example1-client/configure.in
61  \par Nothing special
62  \verbinclude libt2n-example1-client/Makefile.am
63  \par The client program
64  \verbinclude libt2n-example1-client/client.cpp
65  
66 */
67
68 /*!
69  \example t2nexample.cpp
70  example input for libt2n-codegen
71 */
72
73 #include <libxml++/libxml++.h>
74 #include <cassert>
75 #include <iostream>
76 #include <set>
77 #include <fstream>
78 #include <list>
79 #include <stdexcept>
80 #include <boost/lexical_cast.hpp>
81 #ifdef HAVE_CONFIG_H
82 #include "config.h"
83 #endif
84
85
86 //! map group to class name
87 std::string
88 groupClass(const std::string &group) {
89     return std::string("cmd_group_")+group;
90 }
91
92 //! convert string to upper case
93 std::string
94 toupper(std::string s) {
95      for (unsigned i=0; i<s.length(); ++i) s[i]=toupper(s[i]);
96      return s;
97 }
98
99 //! replace all characters f by r in string s
100 std::string
101 replace(std::string s, char f, char r) {
102      for (unsigned i=0; i<s.length(); ++i) if (s[i]==f) s[i]=r;
103      return s;
104 }
105
106 //! strip prefix from string s
107 /*!
108   \return string s without prefix or an empty string on error
109  */
110 std::string
111 strip(std::string s, std::string prefix)
112 {
113      std::string error;
114      if ( (prefix.length()>s.length() ) || ( std::string(s,0,prefix.length())!=prefix ) ) return error;
115      return std::string(s, prefix.length(), s.length()-prefix.length());
116 }
117
118 //! get child element by id
119 /*!
120   \return pointer to element having id or null on error
121   \todo find libxmlpp pendant
122 */
123 const xmlpp::Element* get_element_by_id(const xmlpp::Element* element, const std::string &id)
124 {
125      const xmlpp::Attribute* cid = element->get_attribute("id");
126      if ( cid && ( cid->get_value() == id)) return element;
127
128      //Recurse through child nodes:
129      xmlpp::Node::NodeList list = element->get_children();
130      for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
131      {
132           const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(*iter);
133           if (element) {
134                const xmlpp::Element* match = get_element_by_id(element, id);
135                if (match) return match;
136           }
137      }
138      return NULL;
139 }
140
141 std::string
142 get_file(const xmlpp::Element* root, const std::string &file_id)
143 {
144      std::string error;
145      const xmlpp::Element* e=get_element_by_id(root, file_id);
146      if ((!e)||(!e->get_attribute("name"))) return error;
147      return e->get_attribute("name")->get_value();
148 }
149
150 //! get namespace by id
151 /*!
152   \return namespace name or empty string on error
153 */
154 std::string get_namespace(const xmlpp::Element* root, const std::string &id)
155 {
156      std::string error;
157      const xmlpp::Element* element(get_element_by_id(root, id));
158      if ((!element)||(!element->get_attribute("name"))) return error;
159      return element->get_attribute("name")->get_value();
160 }
161
162 //! extract group from attributes
163 std::string
164 extract_group(const std::string &attrs)
165 {
166      // todo: improve this
167      std::string error;
168      std::string to_match("gccxml(libt2n-");
169      std::string::size_type p(attrs.find(to_match));
170      if (p==std::string::npos) return error;
171      std::string group(attrs, p+to_match.length(), attrs.length());
172      p=group.find_first_of(')');
173      assert(p!=std::string::npos);
174      return std::string(group,0,p);
175 }
176
177 struct type_info
178 {
179      std::string name;
180      std::string noref_name;
181      bool operator==(const type_info& o) {return (name==o.name) && (noref_name == o.noref_name);}
182      std::string noref() const {return noref_name.empty() ? name : noref_name;}
183 };
184
185 std::ostream &operator<<(std::ostream &o, const type_info &t) {
186      o << t.name;
187      return o;
188 }
189
190 struct parse_error : public std::runtime_error
191 {
192      parse_error(const std::string &file, unsigned line, const std::string &msg)
193           : std::runtime_error(file+":"+boost::lexical_cast<std::string>(line)+": error: "+msg)
194           {}
195 };
196
197 //! get type by id
198 /*!
199   \return type name or empty string on error
200 */
201 type_info get_type(const xmlpp::Element* root, const std::string &id)
202 {
203      type_info error;
204      const xmlpp::Element* element(get_element_by_id(root, id));
205      if (!element) return error;
206
207      // TODO: not yet complete
208      // if we recurse - when do we stop?
209      // if it is a typedef? yes? (hmm if the typedef is in the file parsed this will not work)
210
211      // TODO: const and reference types handling is a ugly hack
212
213      std::string tag(element->get_name());
214      if (tag=="ReferenceType") {
215           assert(element->get_attribute("type"));
216           type_info ret(get_type(root, element->get_attribute("type")->get_value()));
217           if (ret==error) return error;
218           // at the moment we only support const &
219           // todo: nice error message!
220           if ((ret.noref_name=strip(ret.name,"const ")).empty()) return error;
221           ret.name=ret.name+"&";
222           return ret;
223      }else if (tag=="CvQualifiedType") {
224           assert(element->get_attribute("type"));
225           type_info ret(get_type(root, element->get_attribute("type")->get_value()));
226           if (ret==error) return error;
227           ret.name=std::string("const ")+ret.name;
228           return ret;
229      }else if (tag=="PointerType") {
230           // not yet supported
231           return error;
232      }
233
234      assert(element->get_attribute("name"));
235      type_info ret;
236      if (element->get_attribute("context")) {
237           ret.name=get_namespace(root, element->get_attribute("context")->get_value());
238           if (ret.name!="::")
239                ret.name+="::";
240           else
241                // do not explicitely add ::
242                ret.name="";
243      }
244      ret.name+=element->get_attribute("name")->get_value();
245      return ret;
246 }
247
248 struct t2n_procedure
249 {
250      typedef std::list<std::pair<std::string, type_info> > Args;
251
252      type_info ret_type;
253      std::string name;
254      std::string mangled;
255      Args  args;
256
257      std::string ret_classname() const {
258           return name+mangled+"_res";
259      }
260      std::string cmd_classname() const {
261           return name+mangled+"_cmd";
262      }
263 };
264
265 std::ostream &operator<<(std::ostream &o, const t2n_procedure::Args &args) {
266      for (t2n_procedure::Args::const_iterator it=args.begin();it!=args.end();++it) {
267           if (it!=args.begin()) o << ", ";
268           o << it->second << " " << it->first;
269      }
270      return o;
271 }
272
273 std::ostream &operator<<(std::ostream &o, const t2n_procedure &f) {
274      o << f.ret_type << " " << f.name << "(" << f.args << ")";
275      return o;
276 }
277
278 class Parser
279 {
280 public:
281      Parser(const std::string &fname) : m_fname(fname) {}
282
283      std::list<t2n_procedure> get_procedures() {
284           xmlpp::DomParser parser;
285           //    parser.set_validate();
286           parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
287           parser.parse_file(m_fname);
288           if(parser)
289           {
290                //Walk the tree:
291                const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
292                const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*>(pNode);
293                assert(root);
294                visit_node(root);
295           }
296           return m_procedures;
297      }
298 protected:
299      std::string m_fname;
300      std::list<t2n_procedure> m_procedures;
301
302      void parse_function(const xmlpp::Element* root, const xmlpp::Node* node) {
303           const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
304           if (!element) return;
305
306           const xmlpp::Attribute* attributes = element->get_attribute("attributes");
307           const xmlpp::Attribute* name = element->get_attribute("name");
308           const xmlpp::Attribute* mangled = element->get_attribute("mangled");
309           const xmlpp::Attribute* returns = element->get_attribute("returns");
310           if ((!attributes)||(!name)||(!mangled)||(!returns)) return;
311
312           // check wether the procedure is marked (TODO: improve)
313           // attributes are speparated by spaces?
314
315           t2n_procedure f;
316           if (extract_group(attributes->get_value()).empty()) return;
317
318           // we need the return type
319           f.ret_type=get_type(root, returns->get_value());
320           f.name=name->get_value();
321           f.mangled=mangled->get_value();
322
323           xmlpp::Node::NodeList list = node->get_children("Argument");
324           for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
325           {
326                const xmlpp::Element* arg = dynamic_cast<const xmlpp::Element*>(*iter);
327                if ( arg ) {
328                     assert(arg->get_name() == "Argument");
329                     assert(arg->get_attribute("name"));
330                     assert(arg->get_attribute("type"));
331                     f.args.push_back(std::pair<std::string, type_info>(arg->get_attribute("name")->get_value(), get_type(root, arg->get_attribute("type")->get_value())));
332                     // todo: ugly - could be any other error
333                     if (f.args.back().second.name.empty()) {
334                          assert(element->get_attribute("file"));
335                          assert(element->get_attribute("line"));
336                          throw parse_error(get_file(root, element->get_attribute("file")->get_value()),
337                                            boost::lexical_cast<unsigned>(element->get_attribute("line")->get_value())-1,
338                                            std::string("type of parameter `")+f.args.back().first+"' not (yet?) supported");
339                     }
340                }
341           }
342           std::cerr << "Found function: " << f << std::endl;
343           m_procedures.push_back(f);
344      }
345
346      void visit_node(const xmlpp::Element* root, const xmlpp::Node* node = NULL, unsigned int indentation = 0)
347           {
348                if (!node) node=root;
349           
350                const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
351                const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
352                const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
353
354                if(nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
355                     return;
356     
357                std::string nodename = node->get_name();
358
359                if(!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
360                {
361                     if (node->get_name() == "Function") parse_function(root, node);
362                }
363                if(!nodeContent)
364                {
365                     //Recurse through child nodes:
366                     xmlpp::Node::NodeList list = node->get_children();
367                     for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
368                     {
369                          visit_node(root, *iter, indentation + 2); //recursive
370                     }
371                }
372           }
373 };
374
375 void output_common_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs) {
376     o << "class " << groupClass(group) << " : public libt2n::command\n"
377       << "{\n"
378       << "private:\n"
379       << "      friend class boost::serialization::access;\n"
380       << "      template<class Archive>\n"
381       << "      void serialize(Archive & ar, const unsigned int /* version */)\n"
382       << "      {ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::command);}\n"
383       << "};\n";
384      
385     for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
386         o << "class " << it->ret_classname() << " : public libt2n::result\n"
387           << "{\n"
388           << "private:\n"
389           << "  " << it->ret_type << " res;\n"
390           << "  friend class boost::serialization::access;\n"
391           << "  template<class Archive>\n"
392           << "  void serialize(Archive & ar, const unsigned int /* version */)\n"
393           << "  {\n"
394           << "          ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::result);\n"
395           << "          ar & BOOST_SERIALIZATION_NVP(res);\n"
396           << "  }\n"
397           << "public:\n"
398           << "  " << it->ret_classname() << "() {}\n"
399           << "  " << it->ret_classname() << "(const " << it->ret_type << " &_res) : res(_res) {}\n"
400           << "  " << it->ret_type << " get_data() { return res; }\n"
401           << "};\n";
402     }
403     for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
404         o << "class " << it->cmd_classname() << " : public " << groupClass(group) << "\n"
405           << "{\n"
406           << "private:\n";
407         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
408             o << "      " << ait->second.noref() << " " << ait->first << ";\n";
409         }
410         o << "  friend class boost::serialization::access;\n"
411           << "  template<class Archive>\n"
412           << "  void serialize(Archive & ar, const unsigned int /* version */)\n"
413           << "  {\n"
414           << "          ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(" << groupClass(group) << ");\n";
415         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
416             o << "              ar & BOOST_SERIALIZATION_NVP(" << ait->first << ");\n";
417         }
418         
419         // default constructor
420         o << "  }\n"
421           << "\n"
422           << "public:\n"
423           << "  " << it->cmd_classname() << "() {}\n";
424         
425         // constructor taking all arguments
426         o << "  " << it->cmd_classname() << "(";
427         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
428             if (ait!=it->args.begin()) o << ", ";
429             o << ait->second << " _" << ait->first;
430         }
431         o << ") : ";
432         for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
433             if (ait!=it->args.begin()) o << ", ";
434             o << ait->first << "(_" << ait->first << ")";
435         }
436         o << " {}\n"
437           << "  libt2n::result* operator()();\n"
438           << "};\n";
439     }
440 }
441
442 void output_common_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp) {
443      o << "#include \"" << common_hpp << "\"\n"
444        << "#include <boost/serialization/export.hpp>\n"
445        << "\n"
446        << "/* register types with boost serialization */\n";
447      o << "BOOST_CLASS_EXPORT(" << groupClass(group) << ")\n";
448      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
449           o << "BOOST_CLASS_EXPORT("<<it->ret_classname()<<")\n"
450             << "BOOST_CLASS_EXPORT("<<it->cmd_classname()<<")\n";
451      }
452 }
453
454 void output_client_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs) {
455      o << "#include <command_client.hxx>\n";
456
457      o << "class " << groupClass(group) << "_client : public libt2n::command_client\n"
458        << "{\n"
459        << "public:\n"
460        << groupClass(group) << "_client(libt2n::client_connection &_c,\n"
461        << "     long long _command_timeout_usec=command_timeout_usec_default,\n"
462        << "     long long _hello_timeout_usec=hello_timeout_usec_default)\n"
463        << "     : libt2n::command_client(_c,_command_timeout_usec,_hello_timeout_usec)\n"
464        << "     {}\n";
465      for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
466          o << " " << *pit << ";\n";
467      }
468      o << "};\n";
469 }
470
471 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) {
472      o << "#include \"" << client_hpp << "\"\n"
473        << "#include \"" << common_hpp << "\"\n"
474        << "// fake\n";
475      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
476           o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return NULL; }\n";
477      }
478
479      for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
480          o << pit->ret_type << " " << groupClass(group) << "_client::" << pit->name << "(" << pit->args << ")\n"
481            << "{\n"
482            << " libt2n::result_container rc;\n"
483            << " send_command(new " << pit->cmd_classname() << "(";
484          for (t2n_procedure::Args::const_iterator ait=pit->args.begin();ait!=pit->args.end();++ait) {
485              if (ait!=pit->args.begin()) o << ", ";
486              o << ait->first;
487          }
488          o << "), rc);\n"
489            << " " << pit->ret_classname() << "* res=dynamic_cast<" << pit->ret_classname() << "*>(rc.get_result());\n"
490            << " if (!res) throw libt2n::t2n_communication_error(\"result object of wrong type\");\n"
491            << " return res->get_data();\n"
492            << "}\n";
493      }
494
495      // include in this compilation unit to ensure the compilation unit is used
496      // see also:
497      // http://www.google.de/search?q=g%2B%2B+static+initializer+in+static+library
498      o << "#include \"" << common_cpp << "\"\n";
499 }
500
501 void output_server_hpp(std::ostream &o, const std::string & /* group */, const std::list<t2n_procedure> &procs, const std::string &common_hpp) {
502      o << "#include \"" << common_hpp << "\"\n";
503
504      // output function declarations
505      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
506        o << *it << ";\n";
507 }
508
509 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) {
510      o << "#include \"" << common_hpp << "\"\n";
511
512      for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
513           o << *it << ";\n";
514           o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return new " << it->ret_classname() << "(" << it->name << "(";
515           for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
516                if (ait!=it->args.begin()) o << ", ";
517                o << ait->first;
518           }
519           o << ")); }\n";
520      }
521      o << "#include \"" << common_cpp << "\"\n";
522 }
523
524 struct header_file : public std::ofstream
525 {
526      header_file(const char* fname) : std::ofstream(fname) {
527           std::cerr << "create header: '" << fname << "'" << std::endl;
528           std::string macro(replace(toupper(fname),'.','_'));
529           *this << "// automatically generated code (generated by libt2n-codegen " << VERSION << ") - do not edit\n" << std::endl;
530           *this << "#ifndef " << macro << "\n"
531                 << "#define " << macro << "\n";
532      }
533      ~header_file() {
534           *this << "#endif" << std::endl;
535      }
536 };
537
538 struct cpp_file : public std::ofstream
539 {
540      cpp_file(const char* fname) : std::ofstream(fname) {
541           std::cerr << "create cpp: '" << fname << "'" << std::endl;
542           *this << "// automatically generated code - do not edit\n" << std::endl;
543      }
544 };
545
546 int
547 main(int argc, char* argv[])
548 {
549     // todo: maybe use getopt
550     if ((argc>1)&&(std::string(argv[1])=="--version")) {
551         std::cerr << VERSION << std::endl;
552         return 0;
553     }
554     if (argc < 3)
555     {
556         std::cerr << "Usage: " << argv[0] << "default-group gccxml-file1 gccxml-file2 ... " << std::endl;
557         return 1;
558     }
559
560     try{
561           std::string group(argv[1]);
562           std::list<std::string> xmlfiles;
563           for (int i=2;i<argc;++i)
564             xmlfiles.push_back(argv[i]);
565
566           std::string prefix=group+"_";
567           std::list<t2n_procedure> procedures;
568           for (std::list<std::string>::iterator it=xmlfiles.begin();it!=xmlfiles.end();++it) {
569               std::cerr << "Parse " << *it << std::endl;
570               Parser parser(*it);
571               const std::list<t2n_procedure> &p(parser.get_procedures());
572               std::copy(p.begin(), p.end(), std::back_inserter(procedures));
573           }
574
575           std::cerr << "Procedures:" << std::endl;
576           for (std::list<t2n_procedure>::const_iterator it=procedures.begin();it!=procedures.end();++it)
577                std::cerr << *it << ";" << std::endl;
578
579           std::string common_hpp_fname(prefix+"common.hxx");
580           std::string common_cpp_fname(prefix+"common.cpp");
581           std::string client_hpp_fname(prefix+"client.hxx");
582           std::string client_cpp_fname(prefix+"client.cpp");
583           std::string server_hpp_fname(prefix+"server.hxx");
584           std::string server_cpp_fname(prefix+"server.cpp");
585
586           header_file common_hpp(common_hpp_fname.c_str());
587           common_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
588                      << "#include \"codegen-stubhead.hxx\"\n"
589                      << "#include \"" << group << ".hxx\"\n";
590
591           output_common_hpp(common_hpp, group, procedures);
592
593           cpp_file common_cpp(common_cpp_fname.c_str());
594           output_common_cpp(common_cpp, group, procedures, common_hpp_fname);
595
596           header_file client_hpp(client_hpp_fname.c_str());
597           client_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
598                      << "#include \"codegen-stubhead.hxx\"\n"
599                      << "#include \"" << group << ".hxx\"\n";
600           output_client_hpp(client_hpp, group, procedures);
601
602           cpp_file client_cpp(client_cpp_fname.c_str());
603           output_client_cpp(client_cpp, group, procedures, common_hpp_fname, common_cpp_fname, client_hpp_fname);
604
605           header_file server_hpp(server_hpp_fname.c_str());
606           output_server_hpp(server_hpp, group, procedures, common_hpp_fname);
607
608           cpp_file server_cpp(server_cpp_fname.c_str());
609           output_server_cpp(server_cpp, group, procedures, common_hpp_fname, common_cpp_fname);
610      }catch(const parse_error &e){
611        std::cerr << e.what() << std::endl;
612        return EXIT_FAILURE;
613      }
614      return EXIT_SUCCESS;
615 }