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