// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include #include // for generate_code() #include // for generate_code() #include // for generate_code() #include // for the cpp_entity_kind definition #include // for is_definition() #include // for cpp_namespace #include // for libclang_parser, libclang_compile_config, cpp_entity,... #include // for visit() #include std::string CLASS_SUFFIX = "Ext"; std::string FUNC_SUFFIX = "Func"; std::string RES_NAME = "res"; std::string FUNC_VAR_PREFIX = "m_"; std::string FUNC_PARAM_PREFIX = "a_"; std::string PREFIX_SPACING = " "; template void pop_front(std::vector& vec) { assert(!vec.empty()); vec.erase(vec.begin()); } std::string get_scopes(const cppast::cpp_entity& e) { std::stringstream stream; std::vector stack; if (e.parent().has_value()) { stack.push_back(&e.parent().value()); } while (!stack.empty()) { auto ent = stack.front(); pop_front(stack); if (ent->parent().has_value()) { stack.push_back(&ent->parent().value()); } if (ent->scope_name().has_value()) { stream << ent->scope_name().value().name() << "::"; } } return stream.str(); } std::string get_scopes(const cppast::cpp_type& type, const cppast::cpp_entity_index& idx) { std::string ret; if (type.kind() == cppast::cpp_type_kind::user_defined_t) { auto entity = idx.lookup(*static_cast(type).entity().id().data()); if (entity.has_value()) { ret = get_scopes(entity.value()) + ret; } } return ret; } std::string get_base_type(const cppast::cpp_type& type, const cppast::cpp_entity_index& idx) { std::string res = cppast::to_string(type); if (type.kind() != cppast::cpp_type_kind::user_defined_t) return res; auto& utype = static_cast(type); auto entity = idx.lookup(*utype.entity().id().data()); if (!entity.has_value()) return res; if (entity.value().kind() != cppast::cpp_entity_kind::type_alias_t) return res; auto& talias = static_cast(entity.value()); if (talias.underlying_type().kind() != cppast::cpp_type_kind::builtin_t) return res; auto& basetype = static_cast(talias.underlying_type()); return cppast::to_string(basetype); } std::vector resplit(const std::string &s, const std::regex &sep_regex = std::regex{"\\s+"}) { std::sregex_token_iterator iter(s.begin(), s.end(), sep_regex, -1); std::sregex_token_iterator end; return {iter, end}; } class synopsis_generator final : public cppast::code_generator { public: // get the resulting string std::string result() { return std::move(str_); } private: // whether or not the entity is the main entity that is being documented bool is_main_entity(const cppast::cpp_entity& e) { if (cppast::is_templated(e) || cppast::is_friended(e)) // need to ask the real entity return is_main_entity(e.parent().value()); else return &e == &this->main_entity(); } // get some nicer formatting cppast::formatting do_get_formatting() const override { return cppast::formatting_flags::brace_nl | cppast::formatting_flags::comma_ws | cppast::formatting_flags::operator_ws; } // calculate generation options generation_options do_get_options(const cppast::cpp_entity& e, cppast::cpp_access_specifier_kind access) override { if (!is_main_entity(e)) // only generation declaration for the non-documented entity return cppast::code_generator::declaration; else // default options return {}; } // update indendation level void do_indent() override { ++indent_; } void do_unindent() override { if (indent_) --indent_; } // write specified tokens // need to change indentation for each newline void do_write_token_seq(cppast::string_view tokens) override { if (was_newline_) { str_ += std::string(indent_ * 2u, ' '); was_newline_ = false; } str_ += tokens.c_str(); } // write + remember newline void do_write_newline() override { str_ += "\n"; was_newline_ = true; } std::string str_; unsigned indent_ = 0; bool was_newline_ = false; }; namespace std { template std::basic_string regex_replace_lambda(BidirIt first, BidirIt last, const std::basic_regex& re, UnaryFunction f) { std::basic_string s; typename std::match_results::difference_type positionOfLastMatch = 0; auto endOfLastMatch = first; auto callback = [&](const std::match_results& match) { auto positionOfThisMatch = match.position(0); auto diff = positionOfThisMatch - positionOfLastMatch; auto startOfThisMatch = endOfLastMatch; std::advance(startOfThisMatch, diff); s.append(endOfLastMatch, startOfThisMatch); s.append(f(match)); auto lengthOfMatch = match.length(0); positionOfLastMatch = positionOfThisMatch + lengthOfMatch; endOfLastMatch = startOfThisMatch; std::advance(endOfLastMatch, lengthOfMatch); }; std::regex_iterator begin(first, last, re), end; std::for_each(begin, end, callback); s.append(endOfLastMatch, last); return s; } template std::string regex_replace_lambda(const std::string& s, const std::basic_regex& re, UnaryFunction f) { return regex_replace_lambda(s.cbegin(), s.cend(), re, f); } } // namespace std bool is_excluded_synopsis(const cppast::cpp_entity& e, cppast::cpp_access_specifier_kind access) { // exclude privates and those marked for exclusion return access == cppast::cpp_private || cppast::has_attribute(e, "documentation::exclude"); } // print help options void print_help(const cxxopts::Options& options) { std::cout << options.help({"", "compilation"}) << '\n'; } const std::map operator_map{ {"+", "_plus"}, {"-", "_minus"}, {"*", "_mul"}, {"/", "_div"}, {"%", "_mod"}, {"^", "_xor"}, {"&", "_and"}, {"|", "_or"}, {"~", "_complement"}, {"!", "_not"}, {"=", "_eq"}, {"<", "_lt"}, {">", "_gt"}, {"+=", "_plus_eq"}, {"-=", "_minus_eq"}, {"*=", "_mul_eq"}, {"/=", "_div_eq"}, {"%=", "_mod_eq"}, {"^=", "_xor_eq"}, {"&=", "_and_eq"}, {"|=", "_or_eq"}, {"<<", "_shl"}, {">>", "_shr"}, {">>=", "_shr_eq"}, {"<<=", "_shl_eq"}, {"==", "_dbl_eq"}, {"!=", "_neq"}, {"<=", "_lt_eq"}, {">=", "_gt_eq"}, {"<=>", "_three_way_comp"}, {"&&", "_dbl_and"}, {"||", "_dbl_or"}, {"++", "_increment"}, {"--", "_decrement"}, {",", "_comma"}, {"->*", "_ptr_accessor_star"}, {"->", "_ptr_accessor"}, {"()", "_call"}, {"[]", "_index"}, {"co_await", "_co_await"}, {"new", "_new"}, {"new []", "_new_index"}, {"delete", "_delete"}, {"delete []", "_delete_index"}, }; std::string sanitize_method_name(const std::string& str) { std::string result; auto reg = R"(operator\s*(\+|-|\*|/|%|\^|&|\||~|\!|\=|<|>|\+\=|-=|\*\=|/\=|%\=|\^\=|&\=|\|=|<<|>>|>>\=|<<\=|\=\=|\!\=|<\=|>\=|<\=>|&&|\|\||\+\+|--|,|->\*|->|\(\s*\)|\[\s*\]|new|new\s*\[\]|delete|delete\s*\[\]|\"\"\s*_\w+|co_await|\w+)\s*$)"; result = std::regex_replace_lambda( str, std::regex(reg), [](const std::smatch& m){ std::string match = m.str(1); //std::cerr << "MATCH: " << match << std::endl; if (operator_map.find(match) == operator_map.end()) { // doesn't exist if (match[0] == '"') { std::string suffix = resplit(match).back(); return "_operator_suffix_" + suffix; } return "_operator_type_" + match; } return "_operator" + operator_map.at(match); } ); return result; } // print error message void print_error(const std::string& msg) { std::cerr << msg << '\n'; } std::string generate_synopsis(const cppast::cpp_entity& e) { synopsis_generator generator; cppast::generate_code(generator, e); return generator.result(); } std::string generate_param(const cppast::cpp_function_parameter& e) { class param_generator : public cppast::code_generator { public: std::string get() { return std::move(result_); } private: void do_indent() override {} void do_unindent() override {} void do_write_token_seq(cppast::string_view tokens) override { result_ += tokens.c_str(); } std::string result_; } generator; // just a dummy type for the output static auto dummy_entity = cppast::cpp_type_alias::build("foo", cppast::cpp_builtin_type::build(cppast::cpp_int)); param_generator::output output(type_safe::ref(generator), type_safe::ref(*dummy_entity), cppast::cpp_public); cppast::detail::write_type(output, e.type(), e.name()); return generator.get(); } // trim from end (in place) static inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end()); } std::string serialize_method_params(const cppast::cpp_member_function& mf) { std::stringstream stream; for (auto& param : mf.parameters()) { stream << cppast::to_string(param.type()); } auto result = stream.str(); auto reg = R"((const\s*|&|\*|::|\[\]|\s*|\w+))"; result = std::regex_replace_lambda( result, std::regex(reg), [](const std::smatch& m){ std::string match = m.str(1); if (match == "&") { return std::string("R"); } else if (match == "*") { return std::string("P"); } else if (match == "[]") { return std::string("B"); } else if (match == "::") { return std::string("N"); } else if (match.starts_with("const")) { return std::string("C"); } else if (match.find_first_not_of(' ') == match.npos) { return std::string(""); } else { return match; } } ); if (!result.empty()) { result = "__" + result; } return result; } bool param_is_subclass(const cppast::cpp_function_parameter& param, const cppast::cpp_entity_index& idx) { if (param.type().kind() != cppast::cpp_type_kind::user_defined_t) return false; auto& type = static_cast(param.type()); auto entity = idx.lookup(*type.entity().id().data()); if (!entity.has_value()) return false; auto& entclass = static_cast(entity.value()); auto par = entclass.parent(); if (par.has_value()) return true; return false; } void output_member_function_typedef(std::stringstream& out, const cppast::cpp_entity& e, std::string& current_class, const cppast::cpp_entity_index& idx) { if (e.kind() != cppast::cpp_entity_kind::member_function_t) return; std::stringstream stream; auto& mf = static_cast(e); std::string method_stub = generate_synopsis(mf); bool method_is_zero = false; if (method_stub.find("= 0;") != std::string::npos) { method_is_zero = true; } std::string ret_type = cppast::to_string(mf.return_type()); bool is_void = ret_type == "void"; ret_type = get_scopes(mf.return_type(), idx) + ret_type; stream << "typedef "; stream << ret_type << " "; stream << "(*"<< current_class << sanitize_method_name(mf.name()) << serialize_method_params(mf) << FUNC_SUFFIX << ")(const " << current_class << "*" << " self"; for (auto& param : mf.parameters()) { stream << ", " << generate_param(param); } if (!is_void && !method_is_zero) { stream << ", " << ret_type << " " << RES_NAME; } stream << ");\n"; out << stream.str(); } void output_member_function_param(std::stringstream& out, const cppast::cpp_entity& e, std::string& current_class) { if (e.kind() != cppast::cpp_entity_kind::member_function_t) return; auto& mf = static_cast(e); std::string mpar_name = FUNC_PARAM_PREFIX + sanitize_method_name(e.name()) + serialize_method_params(mf); out << current_class << sanitize_method_name(e.name()) << serialize_method_params(mf) << FUNC_SUFFIX << " " << mpar_name; out << ", "; } void output_member_function_var_init(std::stringstream& out, std::string& prefix, const cppast::cpp_entity& e, std::string& current_class) { if (e.kind() != cppast::cpp_entity_kind::member_function_t) return; auto& mf = static_cast(e); std::string mpar_name = FUNC_PARAM_PREFIX + sanitize_method_name(e.name()) + serialize_method_params(mf); std::string mvar_name = FUNC_VAR_PREFIX + current_class + sanitize_method_name(e.name()) + serialize_method_params(mf); out << prefix << prefix << mvar_name << " = " << mpar_name << ";\n"; } void output_member_function(std::stringstream& out, const cppast::cpp_entity& e, std::string& prefix, std::string& base_class, std::string& current_class, const cppast::cpp_entity_index& idx) { if (e.kind() != cppast::cpp_entity_kind::member_function_t) return; auto& mf = static_cast(e); int j = 0; for (auto& param : mf.parameters()) { if (param.name().empty()) { param.set_name("param" + std::to_string(j)); } j++; } std::string method_stub = generate_synopsis(e); bool method_is_zero = false; if (method_stub.find("= 0;") != std::string::npos) { method_is_zero = true; } /* method_is_zero = false; */ method_stub = std::regex_replace(method_stub, std::regex(R"((\s*=\s*[a-zA-Z0-9_]+)?;)"), "", std::regex_constants::format_first_only); rtrim(method_stub); std::string mvar_name = FUNC_VAR_PREFIX + current_class + sanitize_method_name(mf.name()) + serialize_method_params(mf); std::string func_type = current_class + sanitize_method_name(mf.name()) + serialize_method_params(mf) + FUNC_SUFFIX; std::stringstream all_params_stream; int num_params = 0; for (auto& param : mf.parameters()) { all_params_stream << ( num_params ? ", " : "" ); all_params_stream << param.name(); num_params++; } std::string ret_type = cppast::to_string(mf.return_type()); bool is_void = ret_type == "void"; ret_type = get_scopes(mf.return_type(), idx) + ret_type; std::string base_type = get_base_type(mf.return_type(), idx); std::string all_params = all_params_stream.str(); // generate class var out << prefix << func_type << " " << mvar_name << " = NULL;\n"; // generate method out << prefix << method_stub; if (method_stub.find("override") == std::string::npos) { out << " override"; } out << "\n"; out << prefix << "{\n"; out << prefix << PREFIX_SPACING; if (!method_is_zero) { // call super if (!is_void) { out << ret_type << " " << RES_NAME << " = "; } out << base_class << "::" << mf.name() << "(" << all_params << ");\n"; out << prefix << PREFIX_SPACING; } out << "if (*" << mvar_name << " != NULL){\n"; out << prefix << PREFIX_SPACING << PREFIX_SPACING; out << "return "; if (num_params > 0) { if (!is_void && !method_is_zero) { out << mvar_name << "(this, " << all_params << ", " << RES_NAME << ");\n"; } else { out << mvar_name << "(this, " << all_params << ");\n"; } } else { if (!is_void && !method_is_zero) { out << mvar_name << "(this, " << RES_NAME << ");\n"; } else { out << mvar_name << "(this);\n"; } } out << prefix << PREFIX_SPACING; out << "}\n"; // endif if (!is_void && !method_is_zero) { out << prefix << PREFIX_SPACING; out << "else {\n"; out << prefix << PREFIX_SPACING << PREFIX_SPACING; out << "return " << RES_NAME << ";\n"; out << prefix << PREFIX_SPACING; out << "}\n"; } else if (!is_void && method_is_zero) { out << prefix << PREFIX_SPACING; out << "else {\n"; out << prefix << PREFIX_SPACING << PREFIX_SPACING; if (base_type == "bool") { out << "return false;\n"; } else if (base_type.ends_with("int") || base_type.ends_with("char") || base_type.ends_with("double")) { out << "return 0;\n"; } else if (base_type == "wxString" || base_type == "std::string") { out << "return \"\";\n"; } else if (base_type.ends_with("*")) { out << "return NULL;\n"; } else { out << "throw std::runtime_error(\"" << mf.name() << " is not implemented.\");\n"; } out << prefix << PREFIX_SPACING; out << "}\n"; } out << prefix << "}\n"; } void output_consdes_typedef(std::stringstream& out, const cppast::cpp_entity& e, std::string& current_class, const cppast::cpp_entity_index& idx) { bool is_destructor = e.kind() == cppast::cpp_entity_kind::destructor_t; bool is_constructor = e.kind() == cppast::cpp_entity_kind::constructor_t; if (!is_destructor && !is_constructor) return; auto& mf = static_cast(e); out << "typedef "; out << "void" << " "; if (is_constructor) { out << "(*new"; } else { out << "(*destroy"; } out << current_class << FUNC_SUFFIX << ")("; int i = 0; for (auto& param : mf.parameters()) { out << ( i ? ", " : "" ); out << get_scopes(param.type(), idx) + cppast::to_string(param.type()) << " " << param.name(); i++; } out << ");\n"; } void output_constructor(std::stringstream& out, std::string& var_init_str, std::string& all_params, cppast::detail::iteratable_intrusive_list constr_params, bool has_constructor, std::string& prefix, std::string& base_class, std::string& current_class) { if (all_params.length() == 0 and constr_params.empty()) return; std::stringstream constr_param_init_stream; std::stringstream constr_param_stream; int i = 0; for (auto& param : constr_params) { constr_param_init_stream << (i ? ", " : ""); constr_param_init_stream << cppast::to_string(param.type()) << " " << param.name(); constr_param_stream << (i ? ", " : ""); constr_param_stream << param.name(); i++; } if (all_params.length() > 0 && !constr_params.empty()) { constr_param_init_stream << ", "; } out << prefix << current_class << "(" << constr_param_init_stream.str() << all_params << ")"; if (has_constructor) { out << ": " << base_class << "(" << constr_param_stream.str() << ")"; } out << " {\n"; out << var_init_str; out << prefix << "}\n"; } void output_consdes(std::stringstream& out, const cppast::cpp_entity& e, std::string& prefix, std::string& base_class, std::string& current_class) { bool is_destructor = e.kind() == cppast::cpp_entity_kind::destructor_t; bool is_constructor = e.kind() == cppast::cpp_entity_kind::constructor_t; if (!is_destructor && !is_constructor) return; auto& mf = static_cast(e); int num_params = 0; for (auto& param : mf.parameters()) { if (param.name().empty()) { param.set_name("param" + std::to_string(num_params)); } num_params++; } std::string method_stub = generate_synopsis(e); // get rid of the ' = 0;' or ' = default;' in the constructor method_stub = std::regex_replace(method_stub, std::regex(R"((\s*=\s*[a-zA-Z0-9_]+)?;)"), "", std::regex_constants::format_first_only); rtrim(method_stub); /* std::string mvar_name = FUNC_VAR_PREFIX + (is_constructor ? "new" : "destroy") + current_class; */ std::string func_type = current_class + FUNC_SUFFIX; std::stringstream all_params_stream; int i = 0; for (auto& param : mf.parameters()) { all_params_stream << ( i ? ", " : "" ) << param.name(); i++; } std::string all_params = all_params_stream.str(); // generate method auto new_stub = std::regex_replace(method_stub, std::regex(base_class), current_class, std::regex_constants::format_first_only); out << prefix << new_stub; // call super if (is_constructor) { out << ": " << base_class << "(" << all_params << ")"; } out << prefix << "{"; out << prefix << "}\n"; } bool efilter(const cppast::cpp_entity& e, cppast::cpp_access_specifier_kind access) { // only visit non-templated class definitions return (!cppast::is_templated(e) && !cppast::is_template(e.kind()) && e.kind() != cppast::cpp_entity_kind::class_template_t && access != cppast::cpp_private && ( (e.kind() == cppast::cpp_entity_kind::class_t && cppast::is_definition(e)) || (e.kind() == cppast::cpp_entity_kind::member_function_t && static_cast(e).is_virtual()) || (e.kind() == cppast::cpp_entity_kind::constructor_t) || (e.kind() == cppast::cpp_entity_kind::destructor_t && static_cast(e).is_virtual()) || e.kind() == cppast::cpp_entity_kind::base_class_t || e.kind() == cppast::cpp_entity_kind::macro_definition_t || e.kind() == cppast::cpp_entity_kind::type_alias_t ) ); } bool method_filter(const cppast::cpp_entity& e, cppast::cpp_access_specifier_kind access) { // only visit non-templated class definitions return (!cppast::is_templated(e) && !cppast::is_template(e.kind()) && e.kind() != cppast::cpp_entity_kind::class_template_t && access != cppast::cpp_private && ( (e.kind() == cppast::cpp_entity_kind::member_function_t && static_cast(e).is_virtual()) ) ); } std::string hash_member_function(const cppast::cpp_member_function& mf, const cppast::cpp_entity_index& idx) { std::stringstream stream; stream << sanitize_method_name(mf.name()) << "<"; int i = 0; for (auto& param : mf.parameters()) { stream << (i != 0 ? ", " : "") << get_base_type(param.type(), idx); i++; } stream << ">"; return stream.str(); } // prints the AST of a file void print_ast(std::ostream& out, const cppast::cpp_file& file, const cppast::cpp_entity_index& idx) { // print file name std::cerr << "AST for '" << file.name() << "':\n"; std::string prefix; // the current prefix string std::string current_class; // the current class std::string base_class; // the current base class std::string current_class_name; // the current base class // std::string fname = file.name(); auto pos = fname.rfind("wx/"); auto len_to_end = fname.length() - pos; std::string include_path = fname.substr(pos, len_to_end); std::string guard_name = "_" + std::regex_replace(include_path, std::regex("/|\\."), "_") + "_EXT_"; std::transform(guard_name.begin(), guard_name.end(), guard_name.begin(), [](char c) { return std::toupper(c); }); //std::cerr << guard_name; out << "#ifndef " << guard_name << "\n"; out << "#define " << guard_name << "\n\n"; out << "#include \n#include <" << include_path << ">\n#include \n#include \n\n"; /* out << "namespace wxname {\n#include_next <" << include_path << ">\n};\n\n"; */ std::stringstream forward_decls; // TODO: Allow specifying all params by constructor std::stringstream init_params; std::stringstream var_init; std::stringstream class_stream; std::stringstream current_class_stream; std::stringstream current_forward_decls; bool in_class = false; bool has_constructor = false; int min_num_params = INT_MAX; std::vector constr_params; std::vector constructors; std::map members; // - Forward declare the class // - typedef all of the virtual methods with class as first arg // - declare the class with a postfix (class wxAppC: public wxApp) // - output variable and virtual method override // check function pointer for null, then call it // cppast::visit(file, efilter, [&](const cppast::cpp_entity& e, cppast::visitor_info info) { if (info.event == cppast::visitor_info::container_entity_enter) { if (e.kind() == cppast::cpp_entity_kind::class_t && !in_class) { if (e.name().empty()) { // it was a fake exit event std::cerr << "FAAAAAAAAAAAKE\n"; std::cerr << current_class << "\n\n"; exit(1); } auto& cl = static_cast(e); current_class_name = cl.name(); base_class = get_scopes(cl) + cl.name(); current_class = cl.name() + CLASS_SUFFIX; //std::cerr << "ENTER CLASS\n"; //std::cerr << current_class << "\n\n"; current_forward_decls << "class " << current_class << ";\n"; current_class_stream << "class " << current_class << ": public " << base_class; current_class_stream << "\n{\n"; current_class_stream << "public:\n"; prefix += PREFIX_SPACING; in_class = true; // Record the methods of the classes and base classes auto& mf = static_cast(e); std::vector stack; std::cerr << "\nCLASS: " << mf.name() << std::endl; for (auto& base : mf.bases()) { if (base.type().kind() != cppast::cpp_type_kind::user_defined_t) continue; auto entity = idx.lookup(*static_cast(base.type()).entity().id().data()); std::cerr << "BASENAME: " << base.name() << std::endl; if (entity.has_value()) { stack.push_back(&entity.value()); } } while (!stack.empty()) { auto entity = stack.back(); stack.pop_back(); if (entity->kind() != cppast::cpp_entity_kind::class_t) continue; auto& entclass = static_cast(*entity); if (entclass.name().empty()) continue; std::cerr << "BASECLASS: " << entclass.name() << std::endl; cppast::visit(entclass, method_filter, [&](const cppast::cpp_entity& e, cppast::visitor_info info) { if (e.kind() != cppast::cpp_entity_kind::member_function_t) return true; if (!e.parent().has_value()) return true; if (e.parent().value().name() != entclass.name()) return true; auto& mf = static_cast(e); auto mname = hash_member_function(mf, idx); std::cerr << "METHOD NAME: " << mf.name() << " (" << mname << ")" << std::endl; std::cerr << "IN MEMBERS: " << !members.count(mname) << std::endl; if (!members.count(mname)) { members[mname] = &mf; } return true; } ); for (auto& entbase : entclass.bases()) { if (entbase.type().kind() != cppast::cpp_type_kind::user_defined_t) { continue; } auto ent = idx.lookup(*static_cast(entbase.type()).entity().id().data()); if (ent.has_value()) { stack.push_back(&ent.value()); } } } } } else if (info.event == cppast::visitor_info::container_entity_exit) { if (e.kind() == cppast::cpp_entity_kind::class_t) { // only exit a class if we get a close event for it if (e.name() != current_class_name) return true; in_class = false; bool members_empty = true; for (auto const& kv : members) { members_empty = false; auto& mf = kv.second; std::cerr << "OUTPUTTING: " << current_class << " " << mf->name() << std::endl; int num_params = 0; for (auto& param : mf->parameters()) { if (param.name().empty()) { param.set_name("param" + std::to_string(num_params)); } num_params++; } output_member_function_param(init_params, *mf, current_class); output_member_function_var_init(var_init, prefix, *mf, current_class); output_member_function_typedef(current_forward_decls, *mf, current_class, idx); output_member_function(current_class_stream, *mf, prefix, base_class, current_class, idx); } auto param_str = init_params.str(); auto var_init_str = var_init.str(); if (param_str.length() >= 2) { // delete comma and space param_str.pop_back(); param_str.pop_back(); } if (constructors.empty()) { cppast::detail::intrusive_list params; cppast::detail::iteratable_intrusive_list emptyList(type_safe::ref(params)); output_constructor(current_class_stream, var_init_str, param_str, emptyList, false, prefix, base_class, current_class); } for (auto& fn : constructors) { output_constructor(current_class_stream, var_init_str, param_str, fn->parameters(), true, prefix, base_class, current_class); } init_params.str(std::string()); // clear var_init.str(std::string()); // clear current_class_stream << "};\n\n"; if (!members_empty) { class_stream << current_class_stream.str(); forward_decls << current_forward_decls.str(); } current_class_stream.str(std::string()); current_forward_decls.str(std::string()); // we have visited all children of a container, // remove prefix prefix.pop_back(); prefix.pop_back(); members.clear(); min_num_params = INT_MAX; has_constructor = false; constr_params.clear(); constructors.clear(); //std::cerr << "EXIT CLASS\n"; //std::cerr << current_class << "\n\n"; current_class = ""; base_class = ""; current_class_name = ""; } } else { if (!e.parent().has_value()) return true; if (e.parent().value().name() != current_class_name) return true; if (e.kind() == cppast::cpp_entity_kind::constructor_t) { auto& mf = static_cast(e); constructors.push_back(&mf); int num_params = 0; std::vector tempparams; has_constructor = true; for (auto& param : mf.parameters()) { if (param.name().empty()) { param.set_name("param" + std::to_string(num_params)); } num_params++; // ignore params with default values if (param.default_value()) continue; tempparams.push_back(¶m); } if (tempparams.size() < min_num_params) { constr_params = tempparams; min_num_params = tempparams.size(); } // output_consdes_typedef(current_forward_decls, e, current_class); output_consdes(current_class_stream, e, prefix, base_class, current_class); } else if (e.kind() == cppast::cpp_entity_kind::destructor_t) { output_consdes(current_class_stream, e, prefix, base_class, current_class); } else if (e.kind() == cppast::cpp_entity_kind::member_function_t) { auto& mf = static_cast(e); auto mname = hash_member_function(mf, idx); if (!members.count(mname)) { members[mname] = &mf; } } } return true; }); out << forward_decls.str() << "\n"; out << class_stream.str() << "\n\n"; out << "#endif" << "\n"; } // 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, 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 && parser.error()) return nullptr; return file; } bool hasEnding (std::string const &fullString, std::string const &ending) { if (fullString.length() >= ending.length()) { return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); } else { return false; } } template std::size_t resolve_includes2(FileParser& parser, const cppast::cpp_file& file, typename FileParser::config config) { auto count = 0u; for (auto& entity : file) { if (entity.kind() == cppast::cpp_include_directive::kind()) { auto& include = static_cast(entity); auto& path = include.full_path(); if (path.ends_with("button.h")) { parser.parse(path, config); ++count; } } } return count; } void readFile(std::string filename, std::list& lines) { lines.clear(); std::ifstream file(filename); std::string s; while (getline(file, s)) lines.push_back(s); } int main(int argc, char* argv[]) try { cxxopts::Options option_list("cppast", "cppast - The commandline interface to the cppast library.\n"); // clang-format off option_list.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()); option_list.add_options("compilation") ("database_dir", "set the directory where a 'compile_commands.json' file is located containing build information", cxxopts::value()) ("database_file", "set the file name whose configuration will be used regardless of the current file name", cxxopts::value()) ("file_list", "set the file name to get a list of files to process from, separated by newlines", cxxopts::value()) ("std", "set the C++ standard (c++98, c++03, c++11, c++14, c++1z (experimental), c++17, c++2a, c++20)", 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>()) ("f,feature", "enable a custom feature (-fXX flag)", 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)") ("fast_preprocessing", "enable fast preprocessing, be careful, this breaks if you e.g. redefine macros in the same file!") ("remove_comments_in_macro", "whether or not comments generated by macro are kept, enable if you run into errors"); // clang-format on option_list.parse_positional("file"); auto options = option_list.parse(argc, argv); if (options.count("help")) print_help(option_list); else if (options.count("version")) { std::cout << "cppast version " << CPPAST_VERSION_STRING << "\n"; std::cout << "Copyright (C) Jonathan Müller 2017-2019 \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("database_dir")) { cppast::libclang_compilation_database database( options["database_dir"].as()); if (options.count("database_file")) config = cppast::libclang_compile_config(database, options["database_file"].as()); else config = cppast::libclang_compile_config(database, options["file"].as()); } if (options.count("verbose")) config.write_preprocessed(true); if (options.count("fast_preprocessing")) config.fast_preprocessing(true); if (options.count("remove_comments_in_macro")) config.remove_comments_in_macro(true); 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); if (equal == std::string::npos) config.define_macro(std::move(name), ""); else { auto def = 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); if (options.count("feature")) for (auto& name : options["feature"].as>()) config.enable_feature(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 if (options["std"].as() == "c++1z") config.set_flags(cppast::cpp_standard::cpp_1z, flags); else if (options["std"].as() == "c++17") config.set_flags(cppast::cpp_standard::cpp_17, flags); else if (options["std"].as() == "c++2a") config.set_flags(cppast::cpp_standard::cpp_2a, flags); else if (options["std"].as() == "c++20") config.set_flags(cppast::cpp_standard::cpp_20, 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); // the entity index is used to resolve cross references in the AST cppast::cpp_entity_index idx; /* auto file = parse_file(config, logger, options["file"].as(), */ /* options.count("fatal_errors") == 1, idx); */ cppast::simple_file_parser parser(type_safe::ref(idx)); std::list fileList; if (options.count("file_list")) { auto fname = options["file_list"].as(); readFile(fname, fileList); } else { fileList.push_back(options["file"].as()); } std::unordered_set to_visit; to_visit.insert(options["file"].as()); /* for (auto f : files) { */ /* to_visit.insert(f); */ /* } */ cppast::parse_files(parser, fileList, config); for (auto& f : parser.files()) { std::cerr << "PARSING: " << f.name() << std::endl; //resolve_includes2(parser, f, config); /* if (f.name().ends_with("/button.h")) { */ /* print_ast(std::cout, f, idx); */ /* } */ if (!to_visit.count(f.name())) continue; print_ast(std::cout, f, idx); } } } catch (const cppast::libclang_error& ex) { print_error(std::string("[fatal parsing error] ") + ex.what()); return 2; }