idl.cpp 19 KB

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