2
0
mirror of https://github.com/boostorg/wave.git synced 2026-02-02 09:22:19 +00:00
Files
wave/samples/waveidl/idl.cpp
Jeff Trull ed4fe59464 Update waveidl for Scanner class template conversion
Similar changes to those made to re2clex in 2017 (re2c output in its
own file, many more templates)

Also fixed some issues with Boost.Filesystem usage
2019-05-02 21:46:43 -07:00

538 lines
19 KiB
C++

/*=============================================================================
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 cmd_line_util {
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 cmd_line_util
///////////////////////////////////////////////////////////////////////////////
// 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 &macros = 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", po::value<string>()->composing(),
"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 = boost::wave::util::create_path(argv[0]);
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;
}
}