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"
35 ///////////////////////////////////////////////////////////////////////////////
36 // include the grammar definitions, if these shouldn't be compiled separately
37 // (ATTENTION: _very_ large compilation times!)
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>
48 ///////////////////////////////////////////////////////////////////////////////
49 // import required names
50 using namespace boost::spirit::classic
;
61 using std::istreambuf_iterator
;
63 namespace po
= boost::program_options
;
64 namespace fs
= boost::filesystem
;
66 ///////////////////////////////////////////////////////////////////////////////
67 // print the current version
70 typedef boost::wave::idllexer::lex_iterator
<
71 boost::wave::cpplexer::lex_token
<> >
73 typedef boost::wave::context
<std::string::iterator
, lex_iterator_type
>
76 string
version (context_type::get_version_string());
78 << version
.substr(1, version
.size()-2) // strip quotes
79 << " (" << IDL_VERSION_DATE
<< ")" // add date
84 ///////////////////////////////////////////////////////////////////////////////
85 // print the copyright statement
88 char const *copyright
[] = {
90 "Sample: IDL oriented preprocessor",
91 "Based on: Wave, A Standard conformant C++ preprocessor library",
92 "It is hosted by http://www.boost.org/.",
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)",
100 for (int i
= 0; 0 != copyright
[i
]; ++i
)
101 cout
<< copyright
[i
] << endl
;
103 return 0; // exit app
106 ///////////////////////////////////////////////////////////////////////////////
107 namespace cmd_line_util
{
109 // Additional command line parser which interprets '@something' as an
110 // option "config-file" with the value "something".
111 pair
<string
, string
> at_option_parser(string
const&s
)
114 return std::make_pair(string("config-file"), s
.substr(1));
116 return pair
<string
, string
>();
119 // class, which keeps include file information read from the command line
120 class include_paths
{
122 include_paths() : seen_separator(false) {}
124 vector
<string
> paths
; // stores user paths
125 vector
<string
> syspaths
; // stores system paths
126 bool seen_separator
; // command line contains a '-I-' option
128 // Function which validates additional tokens from command line.
130 validate(boost::any
&v
, vector
<string
> const &tokens
)
133 v
= boost::any(include_paths());
135 include_paths
*p
= boost::any_cast
<include_paths
>(&v
);
138 // Assume only one path per '-I' occurrence.
139 string t
= tokens
[0];
141 // found -I- option, so switch behaviour
142 p
->seen_separator
= true;
144 else if (p
->seen_separator
) {
145 // store this path as a system path
146 p
->syspaths
.push_back(t
);
149 // store this path as an user path
150 p
->paths
.push_back(t
);
155 // Read all options from a given config file, parse and add them to the
156 // given variables_map
157 void read_config_file_options(string
const &filename
,
158 po::options_description
const &desc
, po::variables_map
&vm
,
159 bool may_fail
= false)
161 ifstream
ifs(filename
.c_str());
163 if (!ifs
.is_open()) {
166 << ": command line warning: config file not found"
172 vector
<string
> options
;
175 while (std::getline(ifs
, line
)) {
177 string::size_type pos
= line
.find_first_not_of(" \t");
178 if (pos
== string::npos
)
181 // skip comment lines
182 if ('#' != line
[pos
])
183 options
.push_back(line
);
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
);
194 // predicate to extract all positional arguments from the command line
196 bool operator()(po::option
const &opt
)
198 return (opt
.position_key
== -1) ? true : false;
202 ///////////////////////////////////////////////////////////////////////////////
205 ///////////////////////////////////////////////////////////////////////////////
207 // Special validator overload, which allows to handle the -I- syntax for
208 // switching the semantics of an -I option.
210 ///////////////////////////////////////////////////////////////////////////////
211 namespace boost
{ namespace program_options
{
213 void validate(boost::any
&v
, std::vector
<std::string
> const &s
,
214 cmd_line_util::include_paths
*, int)
216 cmd_line_util::include_paths::validate(v
, s
);
219 }} // namespace boost::program_options
221 ///////////////////////////////////////////////////////////////////////////////
222 // do the actual preprocessing
224 do_actual_work (std::string file_name
, po::variables_map
const &vm
)
226 // current file position is saved for exception handling
227 boost::wave::util::file_position_type current_position
;
230 // process the given file
231 ifstream
instream(file_name
.c_str());
234 if (!instream
.is_open()) {
235 cerr
<< "waveidl: could not open input file: " << file_name
<< endl
;
238 instream
.unsetf(std::ios::skipws
);
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()));
246 instring
= string(istreambuf_iterator
<char>(instream
.rdbuf()),
247 istreambuf_iterator
<char>());
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
<> >
255 typedef boost::wave::context
<std::string::iterator
, lex_iterator_type
>
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());
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();
274 ctx
.add_sysinclude_path((*cit
).c_str());
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();
284 for (vector
<string
>::const_iterator cit
= ip
.paths
.begin();
287 ctx
.add_include_path((*cit
).c_str());
290 // if on the command line was given -I- , this has to be propagated
291 if (ip
.seen_separator
)
292 ctx
.set_sysinclude_delimiter();
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
)
299 ctx
.add_sysinclude_path((*syscit
).c_str());
303 // add additional defined macros
304 if (vm
.count("define")) {
305 vector
<string
> const ¯os
= vm
["define"].as
<vector
<string
> >();
306 vector
<string
>::const_iterator end
= macros
.end();
307 for (vector
<string
>::const_iterator cit
= macros
.begin();
310 ctx
.add_macro_definition(*cit
);
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();
322 ctx
.add_macro_definition(*cit
, true);
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();
334 ctx
.remove_macro_definition((*cit
).c_str(), true);
338 // open the output file
339 std::ofstream output
;
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
>());
345 output
.open(out_file
.c_str());
346 if (!output
.is_open()) {
347 cerr
<< "waveidl: could not open output file: " << out_file
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());
359 // analyze the input file
360 context_type::iterator_type first
= ctx
.begin();
361 context_type::iterator_type last
= ctx
.end();
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
;
368 // store the last known good token position
369 current_position
= (*first
).get_position();
371 token_id id
= token_id(*first
);
373 if (T_CPPCOMMENT
== id
|| T_NEWLINE
== id
) {
374 // C++ comment tokens contain the trailing newline
377 else if (id
!= T_CCOMMENT
) {
378 // print out the current token value
379 output
<< (*first
).get_value();
381 ++first
; // advance to the next token
384 catch (boost::wave::cpp_exception
const& e
) {
385 // some preprocessing error
387 << e
.file_name() << "(" << e
.line_no() << "): "
388 << e
.description() << endl
;
391 catch (boost::wave::cpplexer::lexing_exception
const& e
) {
394 << e
.file_name() << "(" << e
.line_no() << "): "
395 << e
.description() << endl
;
398 catch (std::exception
const& e
) {
399 // use last recognized token to retrieve the error position
401 << current_position
.get_file()
402 << "(" << current_position
.get_line() << "): "
403 << "exception caught: " << e
.what()
408 // use last recognized token to retrieve the error position
410 << current_position
.get_file()
411 << "(" << current_position
.get_line() << "): "
412 << "unexpected exception caught." << endl
;
418 ///////////////////////////////////////////////////////////////////////////////
421 main (int argc
, char *argv
[])
424 // analyze the command line options and arguments
426 // declare the options allowed from the command line only
427 po::options_description
desc_cmdline ("Options allowed on the command line only");
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)")
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");
440 desc_generic
.add_options()
441 ("output,o", po::value
<string
>()->composing(),
442 "specify a file to use for output instead of stdout")
443 ("include,I", po::value
<cmd_line_util::include_paths
>()->composing(),
444 "specify an additional include directory")
445 ("sysinclude,S", po::value
<vector
<string
> >()->composing(),
446 "specify an additional system include directory")
447 ("define,D", po::value
<vector
<string
> >()->composing(),
448 "specify a macro to define (as macro[=[value]])")
449 ("predefine,P", po::value
<vector
<string
> >()->composing(),
450 "specify a macro to predefine (as macro[=[value]])")
451 ("undefine,U", po::value
<vector
<string
> >()->composing(),
452 "specify a macro to undefine")
455 // combine the options for the different usage schemes
456 po::options_description desc_overall_cmdline
;
457 po::options_description desc_overall_cfgfile
;
459 desc_overall_cmdline
.add(desc_cmdline
).add(desc_generic
);
460 desc_overall_cfgfile
.add(desc_generic
);
462 // parse command line and store results
463 using namespace boost::program_options::command_line_style
;
465 po::parsed_options opts
= po::parse_command_line(argc
, argv
,
466 desc_overall_cmdline
, unix_style
, cmd_line_util::at_option_parser
);
467 po::variables_map vm
;
472 // Try to find a waveidl.cfg in the same directory as the executable was
473 // started from. If this exists, treat it as a wave config file
474 fs::path
filename(argv
[0], fs::native
);
476 filename
= filename
.branch_path() / "waveidl.cfg";
477 cmd_line_util::read_config_file_options(filename
.string(),
478 desc_overall_cfgfile
, vm
, true);
480 // if there is specified at least one config file, parse it and add the
481 // options to the main variables_map
482 if (vm
.count("config-file")) {
483 vector
<string
> const &cfg_files
=
484 vm
["config-file"].as
<vector
<string
> >();
485 vector
<string
>::const_iterator end
= cfg_files
.end();
486 for (vector
<string
>::const_iterator cit
= cfg_files
.begin();
489 // parse a single config file and store the results
490 cmd_line_util::read_config_file_options(*cit
,
491 desc_overall_cfgfile
, vm
);
495 // ... act as required
496 if (vm
.count("help")) {
497 po::options_description
desc_help (
498 "Usage: waveidl [options] [@config-file(s)] file");
500 desc_help
.add(desc_cmdline
).add(desc_generic
);
501 cout
<< desc_help
<< endl
;
505 if (vm
.count("version")) {
506 return print_version();
509 if (vm
.count("copyright")) {
510 return print_copyright();
513 // extract the arguments from the parsed command line
514 vector
<po::option
> arguments
;
516 std::remove_copy_if(opts
.options
.begin(), opts
.options
.end(),
517 inserter(arguments
, arguments
.end()), cmd_line_util::is_argument());
519 // if there is no input file given, then exit
520 if (0 == arguments
.size() || 0 == arguments
[0].value
.size()) {
521 cerr
<< "waveidl: no input file given, "
522 << "use --help to get a hint." << endl
;
526 // preprocess the given input file
527 return do_actual_work(arguments
[0].value
[0], vm
);
529 catch (std::exception
const& e
) {
530 cout
<< "waveidl: exception caught: " << e
.what() << endl
;
534 cerr
<< "waveidl: unexpected exception caught." << endl
;