#include #include #include #include #include #include #include #include #include #include std::optional is_include(const std::string& line, const std::filesystem::path& filepath) { // [ws] # [ws] include [ws] \".+\" auto iter = line.begin(); while(iter < line.end()) { if(*iter != ' ' && *iter != '\t') { break; } ++iter; } if(iter == line.end() || *iter != '#') {return std::nullopt;} assert(*iter == '#'); ++iter; while(iter < line.end()) { if(*iter != ' ' && *iter != '\t') { break; } ++iter; } if(iter == line.end() || *iter != 'i') {return std::nullopt;} else {++iter;} if(iter == line.end() || *iter != 'n') {return std::nullopt;} else {++iter;} if(iter == line.end() || *iter != 'c') {return std::nullopt;} else {++iter;} if(iter == line.end() || *iter != 'l') {return std::nullopt;} else {++iter;} if(iter == line.end() || *iter != 'u') {return std::nullopt;} else {++iter;} if(iter == line.end() || *iter != 'd') {return std::nullopt;} else {++iter;} if(iter == line.end() || *iter != 'e') {return std::nullopt;} else {++iter;} while(iter < line.end()) { if(*iter != ' ' && *iter != '\t') { break; } ++iter; } if(iter == line.end() || *iter != '"') {return std::nullopt;} else {++iter;} std::string filename; while(iter < line.end()) { if(*iter == '"') {break;} filename += *iter; ++iter; } if(iter == line.end() || *iter != '"') {return std::nullopt;} else {++iter;} return std::filesystem::canonical(filepath.parent_path() / std::filesystem::path(filename)); } struct File { File() = default; explicit File(std::filesystem::path f) : filename(std::move(f)) { std::ifstream ifs(filename); if( ! ifs.good()) { throw std::runtime_error("file open error: " + filename.string()); } std::string line; while(std::getline(ifs, line)) { if(const auto incl = is_include(line, filename)) { includes.push_back(incl.value()); } else { content.push_back(line); } } } File(std::filesystem::path f, std::vector c, std::vector i) : filename(std::move(f)), content(std::move(c)), includes(std::move(i)) {} std::filesystem::path filename; std::vector content; // w/o include std::vector includes; }; struct Graph { struct Node { std::vector included; std::vector includes; }; std::map nodes; }; int main(int argc, char** argv) { using namespace std::literals::string_literals; if(argc != 2) { std::cerr << "Usage: ./a.out path/to/toml.hpp > single_include/toml.hpp" << std::endl; return 1; } const auto input_file = std::filesystem::path(std::string(argv[1])); assert(input_file.filename() == "toml.hpp"); const auto include_path = input_file.parent_path(); // ------------------------------------------------------------------------- // load files and detect `include "xxx.hpp"`. // If the file has `_fwd` and `_impl`, expand those files first. std::set fwd_impl_files; for(const auto& entities : std::filesystem::directory_iterator(include_path/"toml11"/"fwd")) { if( ! entities.is_regular_file()) {continue;} std::string fname = entities.path().filename().string(); if(fname.ends_with("_fwd.hpp")) { for(const auto c : "_fwd.hpp"s) {fname.pop_back(); (void)c;} fwd_impl_files.insert(std::move(fname)); } } for(const auto& entities : std::filesystem::directory_iterator(include_path/"toml11"/"impl")) { if( ! entities.is_regular_file()) {continue;} std::string fname = entities.path().filename().string(); if(fname.ends_with("_impl.hpp")) { for(const auto c : "_impl.hpp"s) {fname.pop_back(); (void)c;} // all impl files has fwd file assert(fwd_impl_files.contains(fname)); } } const auto input = File(input_file); std::map files; files[input_file] = input; for(const auto& fname : input.includes) { if(fwd_impl_files.contains(fname.stem().string())) { std::cerr << "expanding fwd/impl file of " << fname.string() << std::endl; // expand the first include std::ifstream ifs(fname); std::vector content; std::vector includes; std::string line; while(std::getline(ifs, line)) { // expand _fwd and _impl files first. const auto incl = is_include(line, fname); if(incl.has_value()) { // if a file has _fwd/_impl files, it only includes fwd/impl files. assert(incl.value().string().ends_with("_impl.hpp") || incl.value().string().ends_with("_fwd.hpp") ); const File included(incl.value()); for(const auto& l : included.content) { content.push_back(l); } for(const auto& i : included.includes) { includes.push_back(i); } } else { content.push_back(line); } } files[fname] = File(fname, std::move(content), std::move(includes)); } else { files[fname] = File(fname); } std::cerr << "file " << fname << " has " << files.at(fname).content.size() << " lines." << std::endl; } std::cerr << "-------------------------------------------------------------\n"; std::cerr << "files have been read. next: constructing dependency graph...\n"; std::cerr << "-------------------------------------------------------------\n"; // ------------------------------------------------------------------------- // construct dependency graph Graph g; for(const auto& [k, v] : files) { g.nodes[k] = Graph::Node{}; } for(const auto& [fname, file] : files) { for(auto incl : file.includes) { auto incl_stem = incl.stem().string(); if(incl_stem.ends_with("_fwd")) { for(const char c : "_fwd"s) {incl_stem.pop_back(); (void)c;} // include original file instaed incl = incl.parent_path() / ".." / std::filesystem::path(incl_stem + ".hpp"); } else if(incl_stem.ends_with("_impl")) { for(const char c : "_impl"s) {incl_stem.pop_back(); (void)c;} // include original file instaed incl = incl.parent_path() / ".." / std::filesystem::path(incl_stem + ".hpp"); } incl = std::filesystem::canonical(incl); // avoid self include loop if(fname != incl) { std::cerr << fname << " includes " << incl << std::endl; g.nodes.at(fname).includes.push_back(incl); g.nodes.at(incl) .included.push_back(fname); } } } std::cerr << "-------------------------------------------------------------\n"; std::cerr << "graph has been constructed. flattening...\n"; std::cerr << "-------------------------------------------------------------\n"; // ------------------------------------------------------------------------- // flatten graph by topological sort // collect files that does not include std::vector sources; for(const auto& [fname, node] : g.nodes) { if(node.includes.empty()) { sources.push_back(fname); } } assert( ! sources.empty()); std::vector sorted; while( ! sources.empty()) { const auto file = sources.back(); sorted.push_back(sources.back()); sources.pop_back(); for(const auto& included : g.nodes.at(file).included) { auto found = std::find(g.nodes.at(included).includes.begin(), g.nodes.at(included).includes.end(), file); g.nodes.at(included).includes.erase(found); if(g.nodes.at(included).includes.empty()) { sources.push_back(included); } } } std::cerr << "-------------------------------------------------------------\n"; std::cerr << "flattened. outputting...\n"; std::cerr << "-------------------------------------------------------------\n"; // ------------------------------------------------------------------------- // output all the file in the sorted order for(const auto& fname : sorted) { std::cerr << "expanding: " << fname << std::endl; for(const auto& line : files.at(fname).content) { std::cout << line << '\n'; } } return 0; }