From 01b206d453d45dda8eb8be859e41e14515659c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Mon, 10 Apr 2017 19:57:06 +0200 Subject: [PATCH] Add simple command line tool to view the AST --- include/cppast/compile_config.hpp | 46 +++-- include/cppast/libclang_parser.hpp | 2 +- include/cppast/visitor.hpp | 34 ++-- src/libclang/libclang_parser.cpp | 3 +- src/visitor.cpp | 53 +++--- test/test_parser.hpp | 2 +- tool/main.cpp | 278 ++++++++++++++++++++++++++++- 7 files changed, 358 insertions(+), 60 deletions(-) diff --git a/include/cppast/compile_config.hpp b/include/cppast/compile_config.hpp index 5db32fd..9473350 100644 --- a/include/cppast/compile_config.hpp +++ b/include/cppast/compile_config.hpp @@ -11,6 +11,8 @@ #include #include +#include + namespace cppast { /// The C++ standard that should be used. @@ -24,6 +26,26 @@ namespace cppast cpp_latest = cpp_standard::cpp_14, }; + /// \returns A human readable string representing the option, + /// it is e.g. `c++14` for `cpp_14`. + inline const char* to_string(cpp_standard standard) noexcept + { + switch (standard) + { + case cpp_standard::cpp_98: + return "c++98"; + case cpp_standard::cpp_03: + return "c++03"; + case cpp_standard::cpp_11: + return "c++11"; + case cpp_standard::cpp_14: + return "c++14"; + } + + DEBUG_UNREACHABLE(detail::assert_handler{}); + return "ups"; + } + /// Other special compilation flags. enum class compile_flag { @@ -32,31 +54,18 @@ namespace cppast ms_extensions, //< Enable MSVC extensions. ms_compatibility, //< Enable MSVC compatibility. - _count, //< \exclude + _flag_set_size, //< \exclude }; -} // namespace cppast -namespace type_safe -{ - /// Specialization of [ts::flag_set_traits]() to use [cppast::compile_flag]() with [ts::flag_set](). - template <> - struct flag_set_traits : std::true_type - { - static constexpr std::size_t size() noexcept - { - return static_cast(cppast::compile_flag::_count); - } - }; -} // namespace type_safe + /// A [ts::flag_set]() of [cppast::compile_flag](). + using compile_flags = type_safe::flag_set; -namespace cppast -{ /// Base class for the configuration of a [cppast::parser](). class compile_config { public: /// \effects Sets the given C++ standard and compilation flags. - void set_flags(cpp_standard standard, type_safe::flag_set flags = {}) + void set_flags(cpp_standard standard, compile_flags flags = {}) { do_set_flags(standard, flags); } @@ -105,8 +114,7 @@ namespace cppast private: /// \effects Sets the given C++ standard and compilation flags. - virtual void do_set_flags(cpp_standard standard, - type_safe::flag_set flags) = 0; + virtual void do_set_flags(cpp_standard standard, compile_flags flags) = 0; /// \effects Adds the given path to the set of include directories. virtual void do_add_include_dir(std::string path) = 0; diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index 441344e..e808a54 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -40,7 +40,7 @@ namespace cppast } private: - void do_set_flags(cpp_standard standard, type_safe::flag_set flags) override; + void do_set_flags(cpp_standard standard, compile_flags flags) override; void do_add_include_dir(std::string path) override; diff --git a/include/cppast/visitor.hpp b/include/cppast/visitor.hpp index 243a0f5..b4ce2ec 100644 --- a/include/cppast/visitor.hpp +++ b/include/cppast/visitor.hpp @@ -10,12 +10,22 @@ namespace cppast class cpp_entity; /// Information about the state of a visit operation. - enum class visitor_info + struct visitor_info { - leaf_entity, //< Callback called for a leaf entity without children. + enum event_type + { + leaf_entity, //< Callback called for a leaf entity without children. + /// If callback returns `false`, visit operation will be aborted. - container_entity_enter, //< Callback called for a container entity before the children. - container_entity_exit, //< Callback called for a container entity after the children. + container_entity_enter, //< Callback called for a container entity before the children. + /// If callback returns `false`, none of the children will be visited, + /// going immediately to the exit event. + container_entity_exit, //< Callback called for a container entity after the children. + /// If callback returns `false`, visit operation will be aborted. + } event; + bool + last_child; //< True when the current entity is the last child of the visited parent entity. + /// \notes It will always be `false` for the initial entity. }; /// \exclude @@ -30,21 +40,19 @@ namespace cppast return func(e, info); } - bool visit(const cpp_entity& e, visitor_callback_t cb, void* functor); + bool visit(const cpp_entity& e, visitor_callback_t cb, void* functor, bool last_child); } // namespace detail /// Visits a [cppast::cpp_entity](). - /// \effects If the given entity is a container, i.e. if it has child entities, - /// calls `f(e, visitor_info::container_entity_enter)`. - /// If that returns `true`, recursively calls `visit()` for all child entities, - /// followed by a call to `f(e, visitor_info::container_entity_exit)`. - /// If the given entity is not a container, calls `f(e, visitor_info::leave_entity)`. - /// If the functor returns `false` for [cppast::visitor_info]() other than `container_entity_enter`, - /// the visit operation is aborted. + /// \effects Invokes the callback for the current entity, + /// and any child entities. + /// It will pass a reference to the current entity and the [cppast::visitor_info](). + /// The return value of the callback controls the visit operation, + /// the semantic depend on the [cppast::visitor_info::event_type](). template void visit(const cpp_entity& e, Func f) { - detail::visit(e, &detail::visitor_callback, &f); + detail::visit(e, &detail::visitor_callback, &f, false); } } // namespace cppast diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 200bbaa..1b62c48 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -58,8 +58,7 @@ libclang_compile_config::libclang_compile_config() : compile_config({}) add_include_dir(CPPAST_LIBCLANG_SYSTEM_INCLUDE_DIR); } -void libclang_compile_config::do_set_flags(cpp_standard standard, - type_safe::flag_set flags) +void libclang_compile_config::do_set_flags(cpp_standard standard, compile_flags flags) { switch (standard) { diff --git a/src/visitor.cpp b/src/visitor.cpp index cd688ff..1a98e6f 100644 --- a/src/visitor.cpp +++ b/src/visitor.cpp @@ -24,58 +24,65 @@ using namespace cppast; namespace { template - bool handle_container(const cpp_entity& e, detail::visitor_callback_t cb, void* functor) + bool handle_container(const cpp_entity& e, detail::visitor_callback_t cb, void* functor, + bool last_child) { auto& container = static_cast(e); - auto handle_children = cb(functor, container, visitor_info::container_entity_enter); + auto handle_children = + cb(functor, container, {visitor_info::container_entity_enter, last_child}); if (handle_children) { - for (auto& child : container) - if (!detail::visit(child, cb, functor)) + for (auto iter = container.begin(); iter != container.end();) + { + auto& cur = *iter; + ++iter; + if (!detail::visit(cur, cb, functor, iter == container.end())) return false; + } } - return cb(functor, container, visitor_info::container_entity_exit); + return cb(functor, container, {visitor_info::container_entity_exit, last_child}); } } -bool detail::visit(const cpp_entity& e, detail::visitor_callback_t cb, void* functor) +bool detail::visit(const cpp_entity& e, detail::visitor_callback_t cb, void* functor, + bool last_child) { switch (e.kind()) { case cpp_entity_kind::file_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::language_linkage_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::namespace_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::enum_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::class_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::function_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::member_function_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::conversion_op_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::constructor_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::template_template_parameter_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::alias_template_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::variable_template_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::function_template_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::function_template_specialization_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::class_template_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::class_template_specialization_t: - return handle_container(e, cb, functor); + return handle_container(e, cb, functor, last_child); case cpp_entity_kind::macro_definition_t: case cpp_entity_kind::include_directive_t: @@ -95,7 +102,7 @@ bool detail::visit(const cpp_entity& e, detail::visitor_callback_t cb, void* fun case cpp_entity_kind::template_type_parameter_t: case cpp_entity_kind::non_type_template_parameter_t: case cpp_entity_kind::unexposed_t: - return cb(functor, e, visitor_info::leaf_entity); + return cb(functor, e, {visitor_info::leaf_entity, last_child}); case cpp_entity_kind::count: break; diff --git a/test/test_parser.hpp b/test/test_parser.hpp index 5f7951f..eeb09ef 100644 --- a/test/test_parser.hpp +++ b/test/test_parser.hpp @@ -110,7 +110,7 @@ unsigned test_visit(const cppast::cpp_file& file, Func f, bool check_code = true { auto count = 0u; cppast::visit(file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) { - if (info == cppast::visitor_info::container_entity_exit) + if (info.event == cppast::visitor_info::container_entity_exit) return true; // already handled if (e.kind() == T::kind()) diff --git a/tool/main.cpp b/tool/main.cpp index 385a9c5..9365123 100644 --- a/tool/main.cpp +++ b/tool/main.cpp @@ -4,6 +4,282 @@ #include -int main() +#include + +#include // for libclang_parser, libclang_compile_config, cpp_entity,... +#include // for visit() +#include // for generate_code() +#include // for the cpp_entity_kind definition +#include // 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(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 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()); + options.add_options("compilation") + ("std", "set the C++ standard (c++98, c++03, c++11, c++14)", + cxxopts::value()->default_value(cppast::to_string(cppast::cpp_standard::cpp_latest))) + ("I,include_directory", "add directory to include search path", + cxxopts::value>()) + ("D,macro_definition", "define a macro on the command line", + cxxopts::value>()) + ("U,macro_undefinition", "undefine a macro on the command line", + cxxopts::value>()) + ("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 \n"; + std::cout << '\n'; + std::cout << "Using libclang version " << CPPAST_CLANG_VERSION_STRING << '\n'; + } + else if (!options.count("file") || options["file"].as().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>()) + config.add_include_dir(include); + if (options.count("macro_definition")) + for (auto& macro : options["macro_definition"].as>()) + { + 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>()) + 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() == "c++98") + config.set_flags(cppast::cpp_standard::cpp_98, flags); + else if (options["std"].as() == "c++03") + config.set_flags(cppast::cpp_standard::cpp_03, flags); + else if (options["std"].as() == "c++11") + config.set_flags(cppast::cpp_standard::cpp_11, flags); + else if (options["std"].as() == "c++14") + config.set_flags(cppast::cpp_standard::cpp_14, flags); + else + { + print_error("invalid value '" + options["std"].as() + "' 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(), + options.count("fatal_errors") == 1u); + if (!file) + return 2; + print_ast(std::cout, *file); + } }