#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include nlohmann::ordered_json buildClass(const std::string& scope, const cppast::cpp_class& classNode); nlohmann::ordered_json buildEnum(const std::string& scope, const cppast::cpp_enum& enumNode); class simpleCodeGenerator : 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: simpleCodeGenerator(const cppast::cpp_entity& e) { // kickoff code generation here cppast::generate_code(*this, e); } simpleCodeGenerator(const cppast::cpp_expression& e) { struct dummy_entity : public cppast::cpp_entity { using cpp_entity::cpp_entity; cppast::cpp_entity_kind do_get_entity_kind() const noexcept override { return cppast::cpp_entity_kind::unexposed_t; } }; dummy_entity dummy("dummy"); output output(type_safe::ref(*this), type_safe::ref(dummy), cppast::cpp_access_specifier_kind::cpp_public); cppast::detail::write_expression(output, e); } // return the result const std::string& str() const noexcept { return str_; } private: // called to retrieve the generation options of an entity generation_options do_get_options(const cppast::cpp_entity&, cppast::cpp_access_specifier_kind) override { // generate declaration only return code_generator::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; } }; int main() { nlohmann::ordered_json outputDoc; nlohmann::ordered_json& moduleDoc = outputDoc["modules"]; cppast::libclang_compilation_database database("compile_commands"); cppast::libclang_compile_config config; Nz::MillisecondClock time; for (auto&& entry : std::filesystem::directory_iterator("../include/Nazara")) { if (!entry.is_directory()) continue; std::string moduleName = Nz::PathToString(entry.path().filename()); std::cout << "Parsing " << moduleName << " headers..." << std::endl; nlohmann::ordered_json& moduleEntryDoc = moduleDoc[moduleName]; // Use module source file as flags reference to parse this module headers cppast::libclang_compile_config config(database, "src/Nazara/" + moduleName + "/" + moduleName + ".cpp"); config.define_macro("NAZARA_DOCGEN", ""); cppast::stderr_diagnostic_logger logger; //logger.set_verbose(true); std::vector threads; std::mutex jsonMutex; for (auto&& headerFile : std::filesystem::recursive_directory_iterator(entry.path())) { if (!headerFile.is_regular_file() || headerFile.path().extension().generic_u8string() != ".hpp") continue; std::string filepath = Nz::PathToString(headerFile.path()); threads.push_back(std::thread([&, filepath] { std::cout << " - Parsing " + filepath + "...\n"; // 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 try { auto file = parser.parse(idx, filepath, config); if (parser.error()) { std::cerr << "failed to parse " << filepath << "\n"; return; } std::vector prefixes; auto prefix = [&] { std::string p; for (const std::string& prefix : prefixes) { p += prefix; p += "::"; } return p; }; // visit each entity in the file bool insideNazaraNamespace = false; cppast::visit(*file, [&](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::file_t) return true; if (!insideNazaraNamespace) { if (e.kind() != cppast::cpp_entity_kind::namespace_t) return false; if (!prefixes.empty() || e.name() != "Nz") return false; insideNazaraNamespace = true; } bool shouldEnter = true; switch (e.kind()) { case cppast::cpp_entity_kind::class_t: { auto& classNode = static_cast(e); if (!classNode.is_definition()) { shouldEnter = false; break; } std::cout << "found " << cppast::to_string(classNode.class_kind()) << " " << prefix() << e.name() << std::endl; nlohmann::ordered_json classDoc = buildClass(prefix(), classNode); std::unique_lock lock(jsonMutex); moduleEntryDoc["classes"].push_back(std::move(classDoc)); break; } case cppast::cpp_entity_kind::enum_t: { auto& enumNode = static_cast(e); if (!enumNode.is_definition()) { shouldEnter = false; break; } std::cout << "found " << (enumNode.is_scoped() ? "enum class" : "enum") << " " << prefix() << e.name() << std::endl; nlohmann::ordered_json enumDoc = buildEnum(prefix(), enumNode); std::unique_lock lock(jsonMutex); moduleEntryDoc["enums"].push_back(std::move(enumDoc)); break; } default: break; } prefixes.push_back(e.name()); return shouldEnter; } else if (info.event == cppast::visitor_info::container_entity_exit) // exiting an old container { if (!prefixes.empty()) { prefixes.pop_back(); if (prefixes.empty()) insideNazaraNamespace = false; } } return true; }); } catch (const cppast::libclang_error& err) { std::cerr << "failed to parse " << filepath << ": " << err.what() << "\n"; } })); } for (std::thread& t : threads) t.join(); auto& classArray = moduleEntryDoc["classes"]; std::sort(classArray.begin(), classArray.end(), [](const nlohmann::ordered_json& classA, const nlohmann::ordered_json& classB) { const std::string& nameA = classA["name"]; const std::string& nameB = classB["name"]; return nameA < nameB; }); } std::fstream outputFile("docgen.json", outputFile.trunc | outputFile.out); outputFile << outputDoc.dump(1, '\t'); std::cout << "Generated documentation in " << time.GetElapsedTime() << std::endl; return EXIT_SUCCESS; } nlohmann::ordered_json buildClass(const std::string& scope, const cppast::cpp_class& classNode) { nlohmann::ordered_json classDoc; classDoc["name"] = scope + classNode.name(); bool isPublic = classNode.class_kind() != cppast::cpp_class_kind::class_t; for (const auto& e : classNode) { switch (e.kind()) { case cppast::cpp_entity_kind::access_specifier_t: { isPublic = static_cast(e).access_specifier() == cppast::cpp_access_specifier_kind::cpp_public; break; } case cppast::cpp_entity_kind::constructor_t: { if (!isPublic) break; const auto& memberFunc = static_cast(e); auto& constructorDoc = classDoc["constructors"].emplace_back(); constructorDoc["name"] = memberFunc.name(); constructorDoc["signature"] = memberFunc.signature(); constructorDoc["codeGen"] = simpleCodeGenerator(memberFunc).str(); auto& parameterArray = constructorDoc["parameters"]; parameterArray = nlohmann::ordered_json::array(); for (const auto& parameter : memberFunc.parameters()) { auto& parameterDoc = parameterArray.emplace_back(); parameterDoc["name"] = parameter.name(); parameterDoc["type"] = cppast::to_string(parameter.type()); if (const auto& defaultOpt = parameter.default_value()) parameterDoc["default"] = simpleCodeGenerator(defaultOpt.value()).str(); } break; } case cppast::cpp_entity_kind::destructor_t: break; case cppast::cpp_entity_kind::member_function_t: { if (!isPublic) break; const auto& memberFunc = static_cast(e); auto& methodDoc = classDoc["methods"].emplace_back(); methodDoc["name"] = memberFunc.name(); methodDoc["signature"] = memberFunc.signature(); methodDoc["codeGen"] = simpleCodeGenerator(memberFunc).str(); methodDoc["returnType"] = cppast::to_string(memberFunc.return_type()); auto& parameterArray = methodDoc["parameters"]; parameterArray = nlohmann::ordered_json::array(); for (const auto& parameter : memberFunc.parameters()) { auto& parameterDoc = parameterArray.emplace_back(); parameterDoc["name"] = parameter.name(); parameterDoc["type"] = cppast::to_string(parameter.type()); if (const auto& defaultOpt = parameter.default_value()) parameterDoc["default"] = simpleCodeGenerator(defaultOpt.value()).str(); } break; } case cppast::cpp_entity_kind::member_variable_t: { if (!isPublic) break; const auto& memberVariable = static_cast(e); auto& variableDoc = classDoc["variables"].emplace_back(); variableDoc["name"] = memberVariable.name(); variableDoc["type"] = cppast::to_string(memberVariable.type()); break; } case cppast::cpp_entity_kind::function_t: { if (!isPublic) break; const auto& memberFunc = static_cast(e); auto& methodDoc = classDoc["staticMethods"].emplace_back(); methodDoc["name"] = memberFunc.name(); methodDoc["signature"] = memberFunc.signature(); methodDoc["codeGen"] = simpleCodeGenerator(memberFunc).str(); methodDoc["returnType"] = cppast::to_string(memberFunc.return_type()); auto& parameterArray = methodDoc["parameters"]; parameterArray = nlohmann::ordered_json::array(); for (const auto& parameter : memberFunc.parameters()) { auto& parameterDoc = parameterArray.emplace_back(); parameterDoc["name"] = parameter.name(); parameterDoc["type"] = cppast::to_string(parameter.type()); if (const auto& defaultOpt = parameter.default_value()) parameterDoc["default"] = simpleCodeGenerator(defaultOpt.value()).str(); } break; } default: { if (isPublic) std::cerr << "ignored public " << cppast::to_string(e.kind()) << " " << e.name() << std::endl; break; } } } return classDoc; } nlohmann::ordered_json buildEnum(const std::string& scope, const cppast::cpp_enum& enumNode) { nlohmann::ordered_json enumDoc; enumDoc["name"] = scope + enumNode.name(); enumDoc["scoped"] = enumNode.is_scoped(); if (enumNode.has_explicit_type()) enumDoc["underlying_type"] = cppast::to_string(enumNode.underlying_type()); for (const auto& entry : enumNode) { auto& valueDoc = enumDoc["values"].emplace_back(); valueDoc["name"] = entry.name(); if (const auto& exprOpt = entry.value()) valueDoc["value"] = simpleCodeGenerator(exprOpt.value()).str(); } return enumDoc; }