started docu
[libt2n] / codegen / main.cpp
... / ...
CommitLineData
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
87std::string
88groupClass(const std::string &group) {
89 return std::string("cmd_group_")+group;
90}
91
92//! convert string to upper case
93std::string
94toupper(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
100std::string
101replace(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 */
110std::string
111strip(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*/
123const 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
141std::string
142get_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*/
154std::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
163std::string
164extract_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
177struct 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
185std::ostream &operator<<(std::ostream &o, const type_info &t) {
186 o << t.name;
187 return o;
188}
189
190struct 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*/
201type_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
248struct 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
265std::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
273std::ostream &operator<<(std::ostream &o, const t2n_procedure &f) {
274 o << f.ret_type << " " << f.name << "(" << f.args << ")";
275 return o;
276}
277
278class Parser
279{
280public:
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 }
298protected:
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
375void 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
442void 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
454void 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
471void 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
501void 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
509void 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
524struct 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
538struct 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
546int
547main(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}