| /*============================================================================= |
| Boost.Wave: A Standard compliant C++ preprocessor library |
| |
| Sample: IDL oriented preprocessor |
| |
| http://www.boost.org/ |
| |
| Copyright (c) 2001-2010 Hartmut Kaiser. Distributed under the Boost |
| Software License, Version 1.0. (See accompanying file |
| LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
| =============================================================================*/ |
| |
| #include "idl.hpp" // global configuration |
| |
| #include <boost/assert.hpp> |
| #include <boost/program_options.hpp> |
| #include <boost/filesystem/path.hpp> |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Include Wave itself |
| #include <boost/wave.hpp> |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Include the lexer related stuff |
| #include <boost/wave/cpplexer/cpp_lex_token.hpp> // token type |
| #include "idllexer/idl_lex_iterator.hpp" // lexer type |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // include lexer specifics, import lexer names |
| // |
| #if BOOST_WAVE_SEPARATE_LEXER_INSTANTIATION == 0 |
| #include "idllexer/idl_re2c_lexer.hpp" |
| #endif |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // include the grammar definitions, if these shouldn't be compiled separately |
| // (ATTENTION: _very_ large compilation times!) |
| // |
| #if BOOST_WAVE_SEPARATE_GRAMMAR_INSTANTIATION == 0 |
| #include <boost/wave/grammars/cpp_intlit_grammar.hpp> |
| #include <boost/wave/grammars/cpp_chlit_grammar.hpp> |
| #include <boost/wave/grammars/cpp_grammar.hpp> |
| #include <boost/wave/grammars/cpp_expression_grammar.hpp> |
| #include <boost/wave/grammars/cpp_predef_macros_grammar.hpp> |
| #include <boost/wave/grammars/cpp_defined_grammar.hpp> |
| #endif |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // import required names |
| using namespace boost::spirit::classic; |
| |
| using std::string; |
| using std::pair; |
| using std::vector; |
| using std::getline; |
| using std::ifstream; |
| using std::cout; |
| using std::cerr; |
| using std::endl; |
| using std::ostream; |
| using std::istreambuf_iterator; |
| |
| namespace po = boost::program_options; |
| namespace fs = boost::filesystem; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // print the current version |
| int print_version() |
| { |
| typedef boost::wave::idllexer::lex_iterator< |
| boost::wave::cpplexer::lex_token<> > |
| lex_iterator_type; |
| typedef boost::wave::context<std::string::iterator, lex_iterator_type> |
| context_type; |
| |
| string version (context_type::get_version_string()); |
| cout |
| << version.substr(1, version.size()-2) // strip quotes |
| << " (" << IDL_VERSION_DATE << ")" // add date |
| << endl; |
| return 0; // exit app |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // print the copyright statement |
| int print_copyright() |
| { |
| char const *copyright[] = { |
| "", |
| "Sample: IDL oriented preprocessor", |
| "Based on: Wave, A Standard conformant C++ preprocessor library", |
| "It is hosted by http://www.boost.org/.", |
| "", |
| "Copyright (c) 2001-2010 Hartmut Kaiser, Distributed under the Boost", |
| "Software License, Version 1.0. (See accompanying file", |
| "LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)", |
| 0 |
| }; |
| |
| for (int i = 0; 0 != copyright[i]; ++i) |
| cout << copyright[i] << endl; |
| |
| return 0; // exit app |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| namespace cmd_line_util { |
| |
| // Additional command line parser which interprets '@something' as an |
| // option "config-file" with the value "something". |
| pair<string, string> at_option_parser(string const&s) |
| { |
| if ('@' == s[0]) |
| return std::make_pair(string("config-file"), s.substr(1)); |
| else |
| return pair<string, string>(); |
| } |
| |
| // class, which keeps include file information read from the command line |
| class include_paths { |
| public: |
| include_paths() : seen_separator(false) {} |
| |
| vector<string> paths; // stores user paths |
| vector<string> syspaths; // stores system paths |
| bool seen_separator; // command line contains a '-I-' option |
| |
| // Function which validates additional tokens from command line. |
| static void |
| validate(boost::any &v, vector<string> const &tokens) |
| { |
| if (v.empty()) |
| v = boost::any(include_paths()); |
| |
| include_paths *p = boost::any_cast<include_paths>(&v); |
| |
| BOOST_ASSERT(p); |
| // Assume only one path per '-I' occurrence. |
| string t = tokens[0]; |
| if (t == "-") { |
| // found -I- option, so switch behaviour |
| p->seen_separator = true; |
| } |
| else if (p->seen_separator) { |
| // store this path as a system path |
| p->syspaths.push_back(t); |
| } |
| else { |
| // store this path as an user path |
| p->paths.push_back(t); |
| } |
| } |
| }; |
| |
| // Read all options from a given config file, parse and add them to the |
| // given variables_map |
| void read_config_file_options(string const &filename, |
| po::options_description const &desc, po::variables_map &vm, |
| bool may_fail = false) |
| { |
| ifstream ifs(filename.c_str()); |
| |
| if (!ifs.is_open()) { |
| if (!may_fail) { |
| cerr << filename |
| << ": command line warning: config file not found" |
| << endl; |
| } |
| return; |
| } |
| |
| vector<string> options; |
| string line; |
| |
| while (std::getline(ifs, line)) { |
| // skip empty lines |
| string::size_type pos = line.find_first_not_of(" \t"); |
| if (pos == string::npos) |
| continue; |
| |
| // skip comment lines |
| if ('#' != line[pos]) |
| options.push_back(line); |
| } |
| |
| if (options.size() > 0) { |
| using namespace boost::program_options::command_line_style; |
| po::store(po::command_line_parser(options) |
| .options(desc).style(unix_style).run(), vm); |
| po::notify(vm); |
| } |
| } |
| |
| // predicate to extract all positional arguments from the command line |
| struct is_argument { |
| bool operator()(po::option const &opt) |
| { |
| return (opt.position_key == -1) ? true : false; |
| } |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // |
| // Special validator overload, which allows to handle the -I- syntax for |
| // switching the semantics of an -I option. |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| namespace boost { namespace program_options { |
| |
| void validate(boost::any &v, std::vector<std::string> const &s, |
| cmd_line_util::include_paths *, int) |
| { |
| cmd_line_util::include_paths::validate(v, s); |
| } |
| |
| }} // namespace boost::program_options |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // do the actual preprocessing |
| int |
| do_actual_work (std::string file_name, po::variables_map const &vm) |
| { |
| // current file position is saved for exception handling |
| boost::wave::util::file_position_type current_position; |
| |
| try { |
| // process the given file |
| ifstream instream(file_name.c_str()); |
| string instring; |
| |
| if (!instream.is_open()) { |
| cerr << "waveidl: could not open input file: " << file_name << endl; |
| return -1; |
| } |
| instream.unsetf(std::ios::skipws); |
| |
| #if defined(BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS) |
| // this is known to be very slow for large files on some systems |
| copy (istream_iterator<char>(instream), |
| istream_iterator<char>(), |
| inserter(instring, instring.end())); |
| #else |
| instring = string(istreambuf_iterator<char>(instream.rdbuf()), |
| istreambuf_iterator<char>()); |
| #endif |
| |
| // This sample uses the lex_token type predefined in the Wave library, but |
| // but uses a custom lexer type. |
| typedef boost::wave::idllexer::lex_iterator< |
| boost::wave::cpplexer::lex_token<> > |
| lex_iterator_type; |
| typedef boost::wave::context<std::string::iterator, lex_iterator_type> |
| context_type; |
| |
| // The C++ preprocessor iterators shouldn't be constructed directly. They |
| // are to be generated through a boost::wave::context<> object. This |
| // boost::wave::context object is additionally to be used to initialize and |
| // define different parameters of the actual preprocessing. |
| // The preprocessing of the input stream is done on the fly behind the |
| // scenes during iteration over the context_type::iterator_type stream. |
| context_type ctx (instring.begin(), instring.end(), file_name.c_str()); |
| |
| // add include directories to the system include search paths |
| if (vm.count("sysinclude")) { |
| vector<string> const &syspaths = |
| vm["sysinclude"].as<vector<string> >(); |
| vector<string>::const_iterator end = syspaths.end(); |
| for (vector<string>::const_iterator cit = syspaths.begin(); |
| cit != end; ++cit) |
| { |
| ctx.add_sysinclude_path((*cit).c_str()); |
| } |
| } |
| |
| // add include directories to the include search paths |
| if (vm.count("include")) { |
| cmd_line_util::include_paths const &ip = |
| vm["include"].as<cmd_line_util::include_paths>(); |
| vector<string>::const_iterator end = ip.paths.end(); |
| |
| for (vector<string>::const_iterator cit = ip.paths.begin(); |
| cit != end; ++cit) |
| { |
| ctx.add_include_path((*cit).c_str()); |
| } |
| |
| // if on the command line was given -I- , this has to be propagated |
| if (ip.seen_separator) |
| ctx.set_sysinclude_delimiter(); |
| |
| // add system include directories to the include path |
| vector<string>::const_iterator sysend = ip.syspaths.end(); |
| for (vector<string>::const_iterator syscit = ip.syspaths.begin(); |
| syscit != sysend; ++syscit) |
| { |
| ctx.add_sysinclude_path((*syscit).c_str()); |
| } |
| } |
| |
| // add additional defined macros |
| if (vm.count("define")) { |
| vector<string> const ¯os = vm["define"].as<vector<string> >(); |
| vector<string>::const_iterator end = macros.end(); |
| for (vector<string>::const_iterator cit = macros.begin(); |
| cit != end; ++cit) |
| { |
| ctx.add_macro_definition(*cit); |
| } |
| } |
| |
| // add additional predefined macros |
| if (vm.count("predefine")) { |
| vector<string> const &predefmacros = |
| vm["predefine"].as<vector<string> >(); |
| vector<string>::const_iterator end = predefmacros.end(); |
| for (vector<string>::const_iterator cit = predefmacros.begin(); |
| cit != end; ++cit) |
| { |
| ctx.add_macro_definition(*cit, true); |
| } |
| } |
| |
| // undefine specified macros |
| if (vm.count("undefine")) { |
| vector<string> const &undefmacros = |
| vm["undefine"].as<vector<string> >(); |
| vector<string>::const_iterator end = undefmacros.end(); |
| for (vector<string>::const_iterator cit = undefmacros.begin(); |
| cit != end; ++cit) |
| { |
| ctx.remove_macro_definition((*cit).c_str(), true); |
| } |
| } |
| |
| // open the output file |
| std::ofstream output; |
| |
| if (vm.count("output")) { |
| // try to open the file, where to put the preprocessed output |
| string out_file (vm["output"].as<string>()); |
| |
| output.open(out_file.c_str()); |
| if (!output.is_open()) { |
| cerr << "waveidl: could not open output file: " << out_file |
| << endl; |
| return -1; |
| } |
| } |
| else { |
| // output the preprocessed result to std::cout |
| output.copyfmt(cout); |
| output.clear(cout.rdstate()); |
| static_cast<std::basic_ios<char> &>(output).rdbuf(cout.rdbuf()); |
| } |
| |
| // analyze the input file |
| context_type::iterator_type first = ctx.begin(); |
| context_type::iterator_type last = ctx.end(); |
| |
| // loop over all generated tokens outputing the generated text |
| while (first != last) { |
| // print out the string representation of this token (skip comments) |
| using namespace boost::wave; |
| |
| // store the last known good token position |
| current_position = (*first).get_position(); |
| |
| token_id id = token_id(*first); |
| |
| if (T_CPPCOMMENT == id || T_NEWLINE == id) { |
| // C++ comment tokens contain the trailing newline |
| output << endl; |
| } |
| else if (id != T_CCOMMENT) { |
| // print out the current token value |
| output << (*first).get_value(); |
| } |
| ++first; // advance to the next token |
| } |
| } |
| catch (boost::wave::cpp_exception const& e) { |
| // some preprocessing error |
| cerr |
| << e.file_name() << "(" << e.line_no() << "): " |
| << e.description() << endl; |
| return 1; |
| } |
| catch (boost::wave::cpplexer::lexing_exception const& e) { |
| // some lexing error |
| cerr |
| << e.file_name() << "(" << e.line_no() << "): " |
| << e.description() << endl; |
| return 2; |
| } |
| catch (std::exception const& e) { |
| // use last recognized token to retrieve the error position |
| cerr |
| << current_position.get_file() |
| << "(" << current_position.get_line() << "): " |
| << "exception caught: " << e.what() |
| << endl; |
| return 3; |
| } |
| catch (...) { |
| // use last recognized token to retrieve the error position |
| cerr |
| << current_position.get_file() |
| << "(" << current_position.get_line() << "): " |
| << "unexpected exception caught." << endl; |
| return 4; |
| } |
| return 0; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // main entry point |
| int |
| main (int argc, char *argv[]) |
| { |
| try { |
| // analyze the command line options and arguments |
| |
| // declare the options allowed from the command line only |
| po::options_description desc_cmdline ("Options allowed on the command line only"); |
| |
| desc_cmdline.add_options() |
| ("help,h", "print out program usage (this message)") |
| ("version,v", "print the version number") |
| ("copyright,c", "print out the copyright statement") |
| ("config-file", po::value<vector<string> >(), |
| "specify a config file (alternatively: @filepath)") |
| ; |
| |
| // declare the options allowed on command line and in config files |
| po::options_description desc_generic ("Options allowed additionally in a config file"); |
| |
| desc_generic.add_options() |
| ("output,o", "specify a file to use for output instead of stdout") |
| ("include,I", po::value<cmd_line_util::include_paths>()->composing(), |
| "specify an additional include directory") |
| ("sysinclude,S", po::value<vector<string> >()->composing(), |
| "specify an additional system include directory") |
| ("define,D", po::value<vector<string> >()->composing(), |
| "specify a macro to define (as macro[=[value]])") |
| ("predefine,P", po::value<vector<string> >()->composing(), |
| "specify a macro to predefine (as macro[=[value]])") |
| ("undefine,U", po::value<vector<string> >()->composing(), |
| "specify a macro to undefine") |
| ; |
| |
| // combine the options for the different usage schemes |
| po::options_description desc_overall_cmdline; |
| po::options_description desc_overall_cfgfile; |
| |
| desc_overall_cmdline.add(desc_cmdline).add(desc_generic); |
| desc_overall_cfgfile.add(desc_generic); |
| |
| // parse command line and store results |
| using namespace boost::program_options::command_line_style; |
| |
| po::parsed_options opts = po::parse_command_line(argc, argv, |
| desc_overall_cmdline, unix_style, cmd_line_util::at_option_parser); |
| po::variables_map vm; |
| |
| po::store(opts, vm); |
| po::notify(vm); |
| |
| // Try to find a waveidl.cfg in the same directory as the executable was |
| // started from. If this exists, treat it as a wave config file |
| fs::path filename(argv[0], fs::native); |
| |
| filename = filename.branch_path() / "waveidl.cfg"; |
| cmd_line_util::read_config_file_options(filename.string(), |
| desc_overall_cfgfile, vm, true); |
| |
| // if there is specified at least one config file, parse it and add the |
| // options to the main variables_map |
| if (vm.count("config-file")) { |
| vector<string> const &cfg_files = |
| vm["config-file"].as<vector<string> >(); |
| vector<string>::const_iterator end = cfg_files.end(); |
| for (vector<string>::const_iterator cit = cfg_files.begin(); |
| cit != end; ++cit) |
| { |
| // parse a single config file and store the results |
| cmd_line_util::read_config_file_options(*cit, |
| desc_overall_cfgfile, vm); |
| } |
| } |
| |
| // ... act as required |
| if (vm.count("help")) { |
| po::options_description desc_help ( |
| "Usage: waveidl [options] [@config-file(s)] file"); |
| |
| desc_help.add(desc_cmdline).add(desc_generic); |
| cout << desc_help << endl; |
| return 1; |
| } |
| |
| if (vm.count("version")) { |
| return print_version(); |
| } |
| |
| if (vm.count("copyright")) { |
| return print_copyright(); |
| } |
| |
| // extract the arguments from the parsed command line |
| vector<po::option> arguments; |
| |
| std::remove_copy_if(opts.options.begin(), opts.options.end(), |
| inserter(arguments, arguments.end()), cmd_line_util::is_argument()); |
| |
| // if there is no input file given, then exit |
| if (0 == arguments.size() || 0 == arguments[0].value.size()) { |
| cerr << "waveidl: no input file given, " |
| << "use --help to get a hint." << endl; |
| return 5; |
| } |
| |
| // preprocess the given input file |
| return do_actual_work(arguments[0].value[0], vm); |
| } |
| catch (std::exception const& e) { |
| cout << "waveidl: exception caught: " << e.what() << endl; |
| return 6; |
| } |
| catch (...) { |
| cerr << "waveidl: unexpected exception caught." << endl; |
| return 7; |
| } |
| } |
| |