• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*=============================================================================
2     Boost.Wave: A Standard compliant C++ preprocessor library
3 
4     Sample: IDL oriented preprocessor
5 
6     http://www.boost.org/
7 
8     Copyright (c) 2001-2010 Hartmut Kaiser. Distributed under the Boost
9     Software License, Version 1.0. (See accompanying file
10     LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
11 =============================================================================*/
12 
13 #include "idl.hpp"                  // global configuration
14 
15 #include <boost/assert.hpp>
16 #include <boost/program_options.hpp>
17 #include <boost/filesystem/path.hpp>
18 
19 ///////////////////////////////////////////////////////////////////////////////
20 //  Include Wave itself
21 #include <boost/wave.hpp>
22 
23 ///////////////////////////////////////////////////////////////////////////////
24 //  Include the lexer related stuff
25 #include <boost/wave/cpplexer/cpp_lex_token.hpp>  // token type
26 #include "idllexer/idl_lex_iterator.hpp"          // lexer type
27 
28 ///////////////////////////////////////////////////////////////////////////////
29 //  include lexer specifics, import lexer names
30 //
31 #if BOOST_WAVE_SEPARATE_LEXER_INSTANTIATION == 0
32 #include "idllexer/idl_re2c_lexer.hpp"
33 #endif
34 
35 #include <iostream>
36 
37 ///////////////////////////////////////////////////////////////////////////////
38 //  include the grammar definitions, if these shouldn't be compiled separately
39 //  (ATTENTION: _very_ large compilation times!)
40 //
41 #if BOOST_WAVE_SEPARATE_GRAMMAR_INSTANTIATION == 0
42 #include <boost/wave/grammars/cpp_intlit_grammar.hpp>
43 #include <boost/wave/grammars/cpp_chlit_grammar.hpp>
44 #include <boost/wave/grammars/cpp_grammar.hpp>
45 #include <boost/wave/grammars/cpp_expression_grammar.hpp>
46 #include <boost/wave/grammars/cpp_predef_macros_grammar.hpp>
47 #include <boost/wave/grammars/cpp_defined_grammar.hpp>
48 #include <boost/wave/grammars/cpp_has_include_grammar.hpp>
49 #endif
50 
51 ///////////////////////////////////////////////////////////////////////////////
52 //  import required names
53 using namespace boost::spirit::classic;
54 
55 using std::string;
56 using std::pair;
57 using std::vector;
58 using std::getline;
59 using std::ifstream;
60 using std::cout;
61 using std::cerr;
62 using std::endl;
63 using std::ostream;
64 using std::istreambuf_iterator;
65 
66 namespace po = boost::program_options;
67 namespace fs = boost::filesystem;
68 
69 ///////////////////////////////////////////////////////////////////////////////
70 // print the current version
print_version()71 int print_version()
72 {
73     typedef boost::wave::idllexer::lex_iterator<
74             boost::wave::cpplexer::lex_token<> >
75         lex_iterator_type;
76     typedef boost::wave::context<std::string::iterator, lex_iterator_type>
77         context_type;
78 
79     string version (context_type::get_version_string());
80     cout
81         << version.substr(1, version.size()-2)  // strip quotes
82         << " (" << IDL_VERSION_DATE << ")"      // add date
83         << endl;
84     return 0;                       // exit app
85 }
86 
87 ///////////////////////////////////////////////////////////////////////////////
88 // print the copyright statement
print_copyright()89 int print_copyright()
90 {
91     char const *copyright[] = {
92         "",
93         "Sample: IDL oriented preprocessor",
94         "Based on: Wave, A Standard conformant C++ preprocessor library",
95         "It is hosted by http://www.boost.org/.",
96         "",
97         "Copyright (c) 2001-2010 Hartmut Kaiser, Distributed under the Boost",
98         "Software License, Version 1.0. (See accompanying file",
99         "LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)",
100         0
101     };
102 
103     for (int i = 0; 0 != copyright[i]; ++i)
104         cout << copyright[i] << endl;
105 
106     return 0;                       // exit app
107 }
108 
109 ///////////////////////////////////////////////////////////////////////////////
110 namespace cmd_line_util {
111 
112     // Additional command line parser which interprets '@something' as an
113     // option "config-file" with the value "something".
at_option_parser(string const & s)114     pair<string, string> at_option_parser(string const&s)
115     {
116         if ('@' == s[0])
117             return std::make_pair(string("config-file"), s.substr(1));
118         else
119             return pair<string, string>();
120     }
121 
122     // class, which keeps include file information read from the command line
123     class include_paths {
124     public:
include_paths()125         include_paths() : seen_separator(false) {}
126 
127         vector<string> paths;       // stores user paths
128         vector<string> syspaths;    // stores system paths
129         bool seen_separator;        // command line contains a '-I-' option
130 
131         // Function which validates additional tokens from command line.
132         static void
validate(boost::any & v,vector<string> const & tokens)133         validate(boost::any &v, vector<string> const &tokens)
134         {
135             if (v.empty())
136                 v = boost::any(include_paths());
137 
138             include_paths *p = boost::any_cast<include_paths>(&v);
139 
140             BOOST_ASSERT(p);
141             // Assume only one path per '-I' occurrence.
142             string t = tokens[0];
143             if (t == "-") {
144             // found -I- option, so switch behaviour
145                 p->seen_separator = true;
146             }
147             else if (p->seen_separator) {
148             // store this path as a system path
149                 p->syspaths.push_back(t);
150             }
151             else {
152             // store this path as an user path
153                 p->paths.push_back(t);
154             }
155         }
156     };
157 
158     // Read all options from a given config file, parse and add them to the
159     // given variables_map
read_config_file_options(string const & filename,po::options_description const & desc,po::variables_map & vm,bool may_fail=false)160     void read_config_file_options(string const &filename,
161         po::options_description const &desc, po::variables_map &vm,
162         bool may_fail = false)
163     {
164     ifstream ifs(filename.c_str());
165 
166         if (!ifs.is_open()) {
167             if (!may_fail) {
168                 cerr << filename
169                     << ": command line warning: config file not found"
170                     << endl;
171             }
172             return;
173         }
174 
175     vector<string> options;
176     string line;
177 
178         while (std::getline(ifs, line)) {
179         // skip empty lines
180             string::size_type pos = line.find_first_not_of(" \t");
181             if (pos == string::npos)
182                 continue;
183 
184         // skip comment lines
185             if ('#' != line[pos])
186                 options.push_back(line);
187         }
188 
189         if (options.size() > 0) {
190             using namespace boost::program_options::command_line_style;
191             po::store(po::command_line_parser(options)
192                 .options(desc).style(unix_style).run(), vm);
193             po::notify(vm);
194         }
195     }
196 
197     // predicate to extract all positional arguments from the command line
198     struct is_argument {
operator ()cmd_line_util::is_argument199         bool operator()(po::option const &opt)
200         {
201           return (opt.position_key == -1) ? true : false;
202         }
203     };
204 
205 ///////////////////////////////////////////////////////////////////////////////
206 }
207 
208 ///////////////////////////////////////////////////////////////////////////////
209 //
210 //  Special validator overload, which allows to handle the -I- syntax for
211 //  switching the semantics of an -I option.
212 //
213 ///////////////////////////////////////////////////////////////////////////////
214 namespace cmd_line_util {
215 
validate(boost::any & v,std::vector<std::string> const & s,cmd_line_util::include_paths *,int)216 void validate(boost::any &v, std::vector<std::string> const &s,
217               cmd_line_util::include_paths *, int) {
218   cmd_line_util::include_paths::validate(v, s);
219 }
220 
221 } // namespace cmd_line_util
222 
223 ///////////////////////////////////////////////////////////////////////////////
224 //  do the actual preprocessing
225 int
do_actual_work(std::string file_name,po::variables_map const & vm)226 do_actual_work (std::string file_name, po::variables_map const &vm)
227 {
228 // current file position is saved for exception handling
229 boost::wave::util::file_position_type current_position;
230 
231     try {
232     // process the given file
233     ifstream instream(file_name.c_str());
234     string instring;
235 
236         if (!instream.is_open()) {
237             cerr << "waveidl: could not open input file: " << file_name << endl;
238             return -1;
239         }
240         instream.unsetf(std::ios::skipws);
241 
242 #if defined(BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS)
243         // this is known to be very slow for large files on some systems
244         copy (istream_iterator<char>(instream),
245               istream_iterator<char>(),
246               inserter(instring, instring.end()));
247 #else
248         instring = string(istreambuf_iterator<char>(instream.rdbuf()),
249                           istreambuf_iterator<char>());
250 #endif
251 
252     //  This sample uses the lex_token type predefined in the Wave library, but
253     //  but uses a custom lexer type.
254         typedef boost::wave::idllexer::lex_iterator<
255                 boost::wave::cpplexer::lex_token<> >
256             lex_iterator_type;
257         typedef boost::wave::context<std::string::iterator, lex_iterator_type>
258             context_type;
259 
260     // The C++ preprocessor iterators shouldn't be constructed directly. They
261     // are to be generated through a boost::wave::context<> object. This
262     // boost::wave::context object is additionally to be used to initialize and
263     // define different parameters of the actual preprocessing.
264     // The preprocessing of the input stream is done on the fly behind the
265     // scenes during iteration over the context_type::iterator_type stream.
266     context_type ctx (instring.begin(), instring.end(), file_name.c_str());
267 
268     // add include directories to the system include search paths
269         if (vm.count("sysinclude")) {
270             vector<string> const &syspaths =
271                 vm["sysinclude"].as<vector<string> >();
272             vector<string>::const_iterator end = syspaths.end();
273             for (vector<string>::const_iterator cit = syspaths.begin();
274                  cit != end; ++cit)
275             {
276                 ctx.add_sysinclude_path((*cit).c_str());
277             }
278         }
279 
280     // add include directories to the include search paths
281         if (vm.count("include")) {
282             cmd_line_util::include_paths const &ip =
283                 vm["include"].as<cmd_line_util::include_paths>();
284             vector<string>::const_iterator end = ip.paths.end();
285 
286             for (vector<string>::const_iterator cit = ip.paths.begin();
287                  cit != end; ++cit)
288             {
289                 ctx.add_include_path((*cit).c_str());
290             }
291 
292         // if on the command line was given -I- , this has to be propagated
293             if (ip.seen_separator)
294                 ctx.set_sysinclude_delimiter();
295 
296         // add system include directories to the include path
297             vector<string>::const_iterator sysend = ip.syspaths.end();
298             for (vector<string>::const_iterator syscit = ip.syspaths.begin();
299                  syscit != sysend; ++syscit)
300             {
301                 ctx.add_sysinclude_path((*syscit).c_str());
302             }
303         }
304 
305     // add additional defined macros
306         if (vm.count("define")) {
307             vector<string> const &macros = vm["define"].as<vector<string> >();
308             vector<string>::const_iterator end = macros.end();
309             for (vector<string>::const_iterator cit = macros.begin();
310                  cit != end; ++cit)
311             {
312                 ctx.add_macro_definition(*cit);
313             }
314         }
315 
316     // add additional predefined macros
317         if (vm.count("predefine")) {
318             vector<string> const &predefmacros =
319                 vm["predefine"].as<vector<string> >();
320             vector<string>::const_iterator end = predefmacros.end();
321             for (vector<string>::const_iterator cit = predefmacros.begin();
322                  cit != end; ++cit)
323             {
324                 ctx.add_macro_definition(*cit, true);
325             }
326         }
327 
328     // undefine specified macros
329         if (vm.count("undefine")) {
330             vector<string> const &undefmacros =
331                 vm["undefine"].as<vector<string> >();
332             vector<string>::const_iterator end = undefmacros.end();
333             for (vector<string>::const_iterator cit = undefmacros.begin();
334                  cit != end; ++cit)
335             {
336                 ctx.remove_macro_definition((*cit).c_str(), true);
337             }
338         }
339 
340     // open the output file
341     std::ofstream output;
342 
343         if (vm.count("output")) {
344         // try to open the file, where to put the preprocessed output
345         string out_file (vm["output"].as<string>());
346 
347             output.open(out_file.c_str());
348             if (!output.is_open()) {
349                 cerr << "waveidl: could not open output file: " << out_file
350                      << endl;
351                 return -1;
352             }
353         }
354         else {
355         // output the preprocessed result to std::cout
356             output.copyfmt(cout);
357             output.clear(cout.rdstate());
358             static_cast<std::basic_ios<char> &>(output).rdbuf(cout.rdbuf());
359         }
360 
361     // analyze the input file
362     context_type::iterator_type first = ctx.begin();
363     context_type::iterator_type last = ctx.end();
364 
365     // loop over all generated tokens outputing the generated text
366         while (first != last) {
367         // print out the string representation of this token (skip comments)
368             using namespace boost::wave;
369 
370         // store the last known good token position
371             current_position = (*first).get_position();
372 
373         token_id id = token_id(*first);
374 
375             if (T_CPPCOMMENT == id || T_NEWLINE == id) {
376             // C++ comment tokens contain the trailing newline
377                 output << endl;
378             }
379             else if (id != T_CCOMMENT) {
380             // print out the current token value
381                 output << (*first).get_value();
382             }
383             ++first;        // advance to the next token
384         }
385     }
386     catch (boost::wave::cpp_exception const& e) {
387     // some preprocessing error
388         cerr
389             << e.file_name() << "(" << e.line_no() << "): "
390             << e.description() << endl;
391         return 1;
392     }
393     catch (boost::wave::cpplexer::lexing_exception const& e) {
394     // some lexing error
395         cerr
396             << e.file_name() << "(" << e.line_no() << "): "
397             << e.description() << endl;
398         return 2;
399     }
400     catch (std::exception const& e) {
401     // use last recognized token to retrieve the error position
402         cerr
403             << current_position.get_file()
404             << "(" << current_position.get_line() << "): "
405             << "exception caught: " << e.what()
406             << endl;
407         return 3;
408     }
409     catch (...) {
410     // use last recognized token to retrieve the error position
411         cerr
412             << current_position.get_file()
413             << "(" << current_position.get_line() << "): "
414             << "unexpected exception caught." << endl;
415         return 4;
416     }
417     return 0;
418 }
419 
420 ///////////////////////////////////////////////////////////////////////////////
421 //  main entry point
422 int
main(int argc,char * argv[])423 main (int argc, char *argv[])
424 {
425     try {
426     // analyze the command line options and arguments
427 
428     // declare the options allowed from the command line only
429     po::options_description desc_cmdline ("Options allowed on the command line only");
430 
431         desc_cmdline.add_options()
432             ("help,h", "print out program usage (this message)")
433             ("version,v", "print the version number")
434             ("copyright,c", "print out the copyright statement")
435             ("config-file", po::value<vector<string> >(),
436                 "specify a config file (alternatively: @filepath)")
437         ;
438 
439     // declare the options allowed on command line and in config files
440     po::options_description desc_generic ("Options allowed additionally in a config file");
441 
442         desc_generic.add_options()
443             ("output,o", po::value<string>()->composing(),
444                 "specify a file to use for output instead of stdout")
445             ("include,I", po::value<cmd_line_util::include_paths>()->composing(),
446                 "specify an additional include directory")
447             ("sysinclude,S", po::value<vector<string> >()->composing(),
448                 "specify an additional system include directory")
449             ("define,D", po::value<vector<string> >()->composing(),
450                 "specify a macro to define (as macro[=[value]])")
451             ("predefine,P", po::value<vector<string> >()->composing(),
452                 "specify a macro to predefine (as macro[=[value]])")
453             ("undefine,U", po::value<vector<string> >()->composing(),
454                 "specify a macro to undefine")
455         ;
456 
457     // combine the options for the different usage schemes
458     po::options_description desc_overall_cmdline;
459     po::options_description desc_overall_cfgfile;
460 
461         desc_overall_cmdline.add(desc_cmdline).add(desc_generic);
462         desc_overall_cfgfile.add(desc_generic);
463 
464     // parse command line and store results
465         using namespace boost::program_options::command_line_style;
466 
467     po::parsed_options opts = po::parse_command_line(argc, argv,
468         desc_overall_cmdline, unix_style, cmd_line_util::at_option_parser);
469     po::variables_map vm;
470 
471         po::store(opts, vm);
472         po::notify(vm);
473 
474     // Try to find a waveidl.cfg in the same directory as the executable was
475     // started from. If this exists, treat it as a wave config file
476     fs::path filename = boost::wave::util::create_path(argv[0]);
477 
478         filename = filename.branch_path() / "waveidl.cfg";
479         cmd_line_util::read_config_file_options(filename.string(),
480             desc_overall_cfgfile, vm, true);
481 
482     // if there is specified at least one config file, parse it and add the
483     // options to the main variables_map
484         if (vm.count("config-file")) {
485             vector<string> const &cfg_files =
486                 vm["config-file"].as<vector<string> >();
487             vector<string>::const_iterator end = cfg_files.end();
488             for (vector<string>::const_iterator cit = cfg_files.begin();
489                  cit != end; ++cit)
490             {
491             // parse a single config file and store the results
492                 cmd_line_util::read_config_file_options(*cit,
493                     desc_overall_cfgfile, vm);
494             }
495         }
496 
497     // ... act as required
498         if (vm.count("help")) {
499         po::options_description desc_help (
500             "Usage: waveidl [options] [@config-file(s)] file");
501 
502             desc_help.add(desc_cmdline).add(desc_generic);
503             cout << desc_help << endl;
504             return 1;
505         }
506 
507         if (vm.count("version")) {
508             return print_version();
509         }
510 
511         if (vm.count("copyright")) {
512             return print_copyright();
513         }
514 
515     // extract the arguments from the parsed command line
516     vector<po::option> arguments;
517 
518         std::remove_copy_if(opts.options.begin(), opts.options.end(),
519             inserter(arguments, arguments.end()), cmd_line_util::is_argument());
520 
521     // if there is no input file given, then exit
522         if (0 == arguments.size() || 0 == arguments[0].value.size()) {
523             cerr << "waveidl: no input file given, "
524                  << "use --help to get a hint." << endl;
525             return 5;
526         }
527 
528     // preprocess the given input file
529         return do_actual_work(arguments[0].value[0], vm);
530     }
531     catch (std::exception const& e) {
532         cout << "waveidl: exception caught: " << e.what() << endl;
533         return 6;
534     }
535     catch (...) {
536         cerr << "waveidl: unexpected exception caught." << endl;
537         return 7;
538     }
539 }
540 
541