Add simple command line tool to view the AST

This commit is contained in:
Jonathan Müller 2017-04-10 19:57:06 +02:00
commit 01b206d453
7 changed files with 358 additions and 60 deletions

View file

@ -4,6 +4,282 @@
#include <iostream>
int main()
#include <cxxopts.hpp>
#include <cppast/libclang_parser.hpp> // for libclang_parser, libclang_compile_config, cpp_entity,...
#include <cppast/visitor.hpp> // for visit()
#include <cppast/code_generator.hpp> // for generate_code()
#include <cppast/cpp_entity_kind.hpp> // for the cpp_entity_kind definition
#include <cppast/cpp_namespace.hpp> // for cpp_namespace
// print help options
void print_help(const cxxopts::Options& options)
{
std::cout << options.help({"", "compilation"}) << '\n';
}
// print error message
void print_error(const std::string& msg)
{
std::cerr << msg << '\n';
}
// prints the AST entry of a cpp_entity (base class for all entities),
// will only print a single line
void print_entity(std::ostream& out, const cppast::cpp_entity& e)
{
// print name and the kind of the entity
out << e.name() << " (" << cppast::to_string(e.kind()) << ")";
if (e.kind() == cppast::cpp_entity_kind::language_linkage_t)
// no need to print additional information for language linkages
out << '\n';
else if (e.kind() == cppast::cpp_entity_kind::namespace_t)
{
// cast to cpp_namespace
auto& ns = static_cast<const cppast::cpp_namespace&>(e);
// print whether or not it is inline
if (ns.is_inline())
out << " [inline]";
out << '\n';
}
else
{
// print the declaration of the entity
// it will only use a single line
// derive from code_generator and implement various callbacks for printing
// it will print into a std::string
class code_generator : public cppast::code_generator
{
std::string str_; // the result
bool was_newline_ = false; // whether or not the last token was a newline
// needed for lazily printing them
public:
code_generator(const cppast::cpp_entity& e)
{
// kickoff code generation here
cppast::generate_code(*this, e);
}
// return the result
const std::string& str() const noexcept
{
return str_;
}
private:
// called at the beginning of the code generation of a container entity (i.e. one with child)
synopsis_options on_container_begin(const cppast::cpp_entity&) override
{
// generate declaration only
return synopsis_options::declaration;
}
// called before code generation of a leaf entity
synopsis_options on_leaf(const cppast::cpp_entity&) override
{
// generate declaration only
return synopsis_options::declaration;
}
// no need to handle indentation, as only a single line is used
void do_indent() override
{
}
void do_unindent() override
{
}
// called when a generic token sequence should be generated
// there are specialized callbacks for various token kinds,
// to e.g. implement syntax highlighting
void do_write_token_seq(cppast::string_view tokens) override
{
if (was_newline_)
{
// lazily append newline as space
str_ += ' ';
was_newline_ = false;
}
// append tokens
str_ += tokens.c_str();
}
// called when a newline should be generated
// we're lazy as it will always generate a trailing newline,
// we don't want
void do_write_newline() override
{
was_newline_ = true;
}
} generator(e);
// print generated code
out << ": `" << generator.str() << '`' << '\n';
}
}
// prints the AST of a file
void print_ast(std::ostream& out, const cppast::cpp_file& file)
{
// print file name
out << "AST for '" << file.name() << "':\n";
std::string prefix; // the current prefix string
// recursively visit file and all children
cppast::visit(file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) {
if (e.kind() == cppast::cpp_entity_kind::file_t)
// no need to do anything for a file
// return value of true continues visit
return true;
else if (info.event == cppast::visitor_info::container_entity_exit)
{
// we have visited all children of a container,
// remove prefix
prefix.pop_back();
prefix.pop_back();
}
else
{
out << prefix; // print prefix for previous entities
// calculate next prefix
if (info.last_child)
{
if (info.event == cppast::visitor_info::container_entity_enter)
prefix += " ";
out << "+-";
}
else
{
if (info.event == cppast::visitor_info::container_entity_enter)
prefix += "| ";
out << "|-";
}
print_entity(out, e);
}
return true;
});
}
// parse a file
std::unique_ptr<cppast::cpp_file> parse_file(const cppast::libclang_compile_config& config,
const cppast::diagnostic_logger& logger,
const std::string& filename, bool fatal_error) try
{
// the entity index is used to resolve cross references in the AST
// we don't need that, so it will not be needed afterwards
cppast::cpp_entity_index idx;
// the parser is used to parse the entity
// there can be multiple parser implementations
cppast::libclang_parser parser(type_safe::ref(logger));
// parse the file
auto file = parser.parse(idx, filename, config);
if (fatal_error && logger.error_logged())
return nullptr;
return file;
}
catch (const cppast::libclang_error& ex)
{
print_error(std::string("[fatal parsing error] ") + ex.what());
return nullptr;
}
int main(int argc, char* argv[])
{
cxxopts::Options options("cppast",
"cppast - The commandline interface to the cppast library.\n");
// clang-format off
options.add_options()
("h,help", "display this help and exit")
("version", "display version information and exit")
("v,verbose", "be verbose when parsing")
("fatal_errors", "abort program when a parser error occurs, instead of doing error correction")
("file", "the file that is being parsed (last positional argument)",
cxxopts::value<std::string>());
options.add_options("compilation")
("std", "set the C++ standard (c++98, c++03, c++11, c++14)",
cxxopts::value<std::string>()->default_value(cppast::to_string(cppast::cpp_standard::cpp_latest)))
("I,include_directory", "add directory to include search path",
cxxopts::value<std::vector<std::string>>())
("D,macro_definition", "define a macro on the command line",
cxxopts::value<std::vector<std::string>>())
("U,macro_undefinition", "undefine a macro on the command line",
cxxopts::value<std::vector<std::string>>())
("gnu_extensions", "enable GNU extensions (equivalent to -std=gnu++XX)")
("msvc_extensions", "enable MSVC extensions (equivalent to -fms-extensions)")
("msvc_compatibility", "enable MSVC compatibility (equivalent to -fms-compatibility)");
// clang-format on
options.parse_positional("file");
options.parse(argc, argv);
if (options.count("help"))
print_help(options);
else if (options.count("version"))
{
std::cout << "cppast version 0.0\n";
std::cout << "Copyright (C) Jonathan Müller 2017 <jonathanmueller.dev@gmail.com>\n";
std::cout << '\n';
std::cout << "Using libclang version " << CPPAST_CLANG_VERSION_STRING << '\n';
}
else if (!options.count("file") || options["file"].as<std::string>().empty())
{
print_error("missing file argument");
return 1;
}
else
{
// the compile config stores compilation flags
cppast::libclang_compile_config config;
if (options.count("include_directory"))
for (auto& include : options["include_directory"].as<std::vector<std::string>>())
config.add_include_dir(include);
if (options.count("macro_definition"))
for (auto& macro : options["macro_definition"].as<std::vector<std::string>>())
{
auto equal = macro.find('=');
auto name = macro.substr(0, equal);
auto def = equal == std::string::npos ? macro.substr(equal + 1u) : "";
config.define_macro(std::move(name), std::move(def));
}
if (options.count("macro_undefinition"))
for (auto& name : options["macro_undefinition"].as<std::vector<std::string>>())
config.undefine_macro(name);
// the compile_flags are generic flags
cppast::compile_flags flags;
if (options.count("gnu_extensions"))
flags |= cppast::compile_flag::gnu_extensions;
if (options.count("msvc_extensions"))
flags |= cppast::compile_flag::ms_extensions;
if (options.count("msvc_compatibility"))
flags |= cppast::compile_flag::ms_compatibility;
if (options["std"].as<std::string>() == "c++98")
config.set_flags(cppast::cpp_standard::cpp_98, flags);
else if (options["std"].as<std::string>() == "c++03")
config.set_flags(cppast::cpp_standard::cpp_03, flags);
else if (options["std"].as<std::string>() == "c++11")
config.set_flags(cppast::cpp_standard::cpp_11, flags);
else if (options["std"].as<std::string>() == "c++14")
config.set_flags(cppast::cpp_standard::cpp_14, flags);
else
{
print_error("invalid value '" + options["std"].as<std::string>() + "' for std flag");
return 1;
}
// the logger is used to print diagnostics
cppast::stderr_diagnostic_logger logger;
if (options.count("verbose"))
logger.set_verbose(true);
auto file = parse_file(config, logger, options["file"].as<std::string>(),
options.count("fatal_errors") == 1u);
if (!file)
return 2;
print_ast(std::cout, *file);
}
}