1 /*=============================================================================
2 Boost.Wave: A Standard compliant C++ preprocessor library
4 Sample: IDL oriented preprocessor
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 =============================================================================*/
13 #include "idl.hpp" // global configuration
15 #include <boost/assert.hpp>
16 #include <boost/program_options.hpp>
17 #include <boost/filesystem/path.hpp>
19 ///////////////////////////////////////////////////////////////////////////////
20 // Include Wave itself
21 #include <boost/wave.hpp>
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
28 ///////////////////////////////////////////////////////////////////////////////
29 // include lexer specifics, import lexer names
31 #if BOOST_WAVE_SEPARATE_LEXER_INSTANTIATION == 0
32 #include "idllexer/idl_re2c_lexer.hpp"
37 ///////////////////////////////////////////////////////////////////////////////
38 // include the grammar definitions, if these shouldn't be compiled separately
39 // (ATTENTION: _very_ large compilation times!)
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>
51 ///////////////////////////////////////////////////////////////////////////////
52 // import required names
53 using namespace boost::spirit::classic
;
64 using std::istreambuf_iterator
;
66 namespace po
= boost::program_options
;
67 namespace fs
= boost::filesystem
;
69 ///////////////////////////////////////////////////////////////////////////////
70 // print the current version
73 typedef boost::wave::idllexer::lex_iterator
<
74 boost::wave::cpplexer::lex_token
<> >
76 typedef boost::wave::context
<std::string::iterator
, lex_iterator_type
>
79 string
version (context_type::get_version_string());
81 << version
.substr(1, version
.size()-2) // strip quotes
82 << " (" << IDL_VERSION_DATE
<< ")" // add date
87 ///////////////////////////////////////////////////////////////////////////////
88 // print the copyright statement
91 char const *copyright
[] = {
93 "Sample: IDL oriented preprocessor",
94 "Based on: Wave, A Standard conformant C++ preprocessor library",
95 "It is hosted by http://www.boost.org/.",
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)",
103 for (int i
= 0; 0 != copyright
[i
]; ++i
)
104 cout
<< copyright
[i
] << endl
;
106 return 0; // exit app
109 ///////////////////////////////////////////////////////////////////////////////
110 namespace cmd_line_util
{
112 // Additional command line parser which interprets '@something' as an
113 // option "config-file" with the value "something".
114 pair
<string
, string
> at_option_parser(string
const&s
)
117 return std::make_pair(string("config-file"), s
.substr(1));
119 return pair
<string
, string
>();
122 // class, which keeps include file information read from the command line
123 class include_paths
{
125 include_paths() : seen_separator(false) {}
127 vector
<string
> paths
; // stores user paths
128 vector
<string
> syspaths
; // stores system paths
129 bool seen_separator
; // command line contains a '-I-' option
131 // Function which validates additional tokens from command line.
133 validate(boost::any
&v
, vector
<string
> const &tokens
)
136 v
= boost::any(include_paths());
138 include_paths
*p
= boost::any_cast
<include_paths
>(&v
);
141 // Assume only one path per '-I' occurrence.
142 string t
= tokens
[0];
144 // found -I- option, so switch behaviour
145 p
->seen_separator
= true;
147 else if (p
->seen_separator
) {
148 // store this path as a system path
149 p
->syspaths
.push_back(t
);
152 // store this path as an user path
153 p
->paths
.push_back(t
);
158 // Read all options from a given config file, parse and add them to the
159 // given variables_map
160 void read_config_file_options(string
const &filename
,
161 po::options_description
const &desc
, po::variables_map
&vm
,
162 bool may_fail
= false)
164 ifstream
ifs(filename
.c_str());
166 if (!ifs
.is_open()) {
169 << ": command line warning: config file not found"
175 vector
<string
> options
;
178 while (std::getline(ifs
, line
)) {
180 string::size_type pos
= line
.find_first_not_of(" \t");
181 if (pos
== string::npos
)
184 // skip comment lines
185 if ('#' != line
[pos
])
186 options
.push_back(line
);
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
);
197 // predicate to extract all positional arguments from the command line
199 bool operator()(po::option
const &opt
)
201 return (opt
.position_key
== -1) ? true : false;
205 ///////////////////////////////////////////////////////////////////////////////
208 ///////////////////////////////////////////////////////////////////////////////
210 // Special validator overload, which allows to handle the -I- syntax for
211 // switching the semantics of an -I option.
213 ///////////////////////////////////////////////////////////////////////////////
214 namespace cmd_line_util
{
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
);
221 } // namespace cmd_line_util
223 ///////////////////////////////////////////////////////////////////////////////
224 // do the actual preprocessing
226 do_actual_work (std::string file_name
, po::variables_map
const &vm
)
228 // current file position is saved for exception handling
229 boost::wave::util::file_position_type current_position
;
232 // process the given file
233 ifstream
instream(file_name
.c_str());
236 if (!instream
.is_open()) {
237 cerr
<< "waveidl: could not open input file: " << file_name
<< endl
;
240 instream
.unsetf(std::ios::skipws
);
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()));
248 instring
= string(istreambuf_iterator
<char>(instream
.rdbuf()),
249 istreambuf_iterator
<char>());
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
<> >
257 typedef boost::wave::context
<std::string::iterator
, lex_iterator_type
>
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());
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();
276 ctx
.add_sysinclude_path((*cit
).c_str());
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();
286 for (vector
<string
>::const_iterator cit
= ip
.paths
.begin();
289 ctx
.add_include_path((*cit
).c_str());
292 // if on the command line was given -I- , this has to be propagated
293 if (ip
.seen_separator
)
294 ctx
.set_sysinclude_delimiter();
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
)
301 ctx
.add_sysinclude_path((*syscit
).c_str());
305 // add additional defined macros
306 if (vm
.count("define")) {
307 vector
<string
> const ¯os
= vm
["define"].as
<vector
<string
> >();
308 vector
<string
>::const_iterator end
= macros
.end();
309 for (vector
<string
>::const_iterator cit
= macros
.begin();
312 ctx
.add_macro_definition(*cit
);
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();
324 ctx
.add_macro_definition(*cit
, true);
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();
336 ctx
.remove_macro_definition((*cit
).c_str(), true);
340 // open the output file
341 std::ofstream output
;
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
>());
347 output
.open(out_file
.c_str());
348 if (!output
.is_open()) {
349 cerr
<< "waveidl: could not open output file: " << out_file
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());
361 // analyze the input file
362 context_type::iterator_type first
= ctx
.begin();
363 context_type::iterator_type last
= ctx
.end();
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
;
370 // store the last known good token position
371 current_position
= (*first
).get_position();
373 token_id id
= token_id(*first
);
375 if (T_CPPCOMMENT
== id
|| T_NEWLINE
== id
) {
376 // C++ comment tokens contain the trailing newline
379 else if (id
!= T_CCOMMENT
) {
380 // print out the current token value
381 output
<< (*first
).get_value();
383 ++first
; // advance to the next token
386 catch (boost::wave::cpp_exception
const& e
) {
387 // some preprocessing error
389 << e
.file_name() << "(" << e
.line_no() << "): "
390 << e
.description() << endl
;
393 catch (boost::wave::cpplexer::lexing_exception
const& e
) {
396 << e
.file_name() << "(" << e
.line_no() << "): "
397 << e
.description() << endl
;
400 catch (std::exception
const& e
) {
401 // use last recognized token to retrieve the error position
403 << current_position
.get_file()
404 << "(" << current_position
.get_line() << "): "
405 << "exception caught: " << e
.what()
410 // use last recognized token to retrieve the error position
412 << current_position
.get_file()
413 << "(" << current_position
.get_line() << "): "
414 << "unexpected exception caught." << endl
;
420 ///////////////////////////////////////////////////////////////////////////////
423 main (int argc
, char *argv
[])
426 // analyze the command line options and arguments
428 // declare the options allowed from the command line only
429 po::options_description
desc_cmdline ("Options allowed on the command line only");
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)")
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");
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")
457 // combine the options for the different usage schemes
458 po::options_description desc_overall_cmdline
;
459 po::options_description desc_overall_cfgfile
;
461 desc_overall_cmdline
.add(desc_cmdline
).add(desc_generic
);
462 desc_overall_cfgfile
.add(desc_generic
);
464 // parse command line and store results
465 using namespace boost::program_options::command_line_style
;
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
;
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]);
478 filename
= filename
.branch_path() / "waveidl.cfg";
479 cmd_line_util::read_config_file_options(filename
.string(),
480 desc_overall_cfgfile
, vm
, true);
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();
491 // parse a single config file and store the results
492 cmd_line_util::read_config_file_options(*cit
,
493 desc_overall_cfgfile
, vm
);
497 // ... act as required
498 if (vm
.count("help")) {
499 po::options_description
desc_help (
500 "Usage: waveidl [options] [@config-file(s)] file");
502 desc_help
.add(desc_cmdline
).add(desc_generic
);
503 cout
<< desc_help
<< endl
;
507 if (vm
.count("version")) {
508 return print_version();
511 if (vm
.count("copyright")) {
512 return print_copyright();
515 // extract the arguments from the parsed command line
516 vector
<po::option
> arguments
;
518 std::remove_copy_if(opts
.options
.begin(), opts
.options
.end(),
519 inserter(arguments
, arguments
.end()), cmd_line_util::is_argument());
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
;
528 // preprocess the given input file
529 return do_actual_work(arguments
[0].value
[0], vm
);
531 catch (std::exception
const& e
) {
532 cout
<< "waveidl: exception caught: " << e
.what() << endl
;
536 cerr
<< "waveidl: unexpected exception caught." << endl
;