// Read a single-file C++ program having a very specific structure, and split // it up into multiple separate compilation units to reduce the work needed to // rebuild after a small change. Write each compilation unit only if it has // changed from what was on disk before. // // This tool is tightly coupled with the build system for this project. The // makefile already auto-generates various things; we only do here what // standard unix tools can't easily do. // // Usage: // cleave [input C++ file] [existing output directory] // // The input C++ file has the following structure: // [#includes] // [type definitions] // // Globals // [global variable definitions] // // End Globals // [function definitions] // // Afterwards, the output directory contains: // header -- everything before the '// Globals' delimiter // global_definitions_list -- everything between '// Globals' and '// End Globals' delimiters // [.cc files partitioning function definitions] // // Each output function definition file contains: // #include "header" // #include "global_declarations_list" // [function definitions] // // To preserve the original layer-based line numbers in error messages and the // debugger, we'll chunk the files only at boundaries where we encounter a // '#line ' directive (generated by the previous tangle/ stage) between // functions. // // One exception: the first file emitted #includes "global_definitions_list" instead // of "global_declarations_list" // Tune this parameter to balance time for initial vs incremental build. // // decrease value -> faster initial build // increase value -> faster incremental build int Num_compilation_units = 3; #include #include #include #include using std::vector; #include using std::list; #include using std::pair; #include using std::string; #include using std::istream; using std::ostream; using std::cin; using std::cout; using std::cerr; #include using std::istringstream; using std::ostringstream; #include using std::ifstream; using std::ofstream; #include using std::isspace; // unicode-aware string trim(const string& s) { string::const_iterator first = s.begin(); while (first != s.end() && isspace(*first)) ++first; if (first == s.end()) return ""; string::const_iterator last = --s.end(); while (last != s.begin() && isspace(*last)) --last; ++last; return string(first, last); } bool starts_with(const string& s, const string& pat) { string::const_iterator a=s.begin(), b=pat.begin(); for (/*nada*/; a!=s.end() && b!=pat.end(); ++a, ++b) if (*a != *b) return false; return b == pat.end(); } bool has_data(istream& in) { return in && !in.eof(); } void slurp(const string& filename, vector& lines) { lines.clear(); ifstream in(filename.c_str()); while (has_data(in)) { string curr_line; getline(in, curr_line); lines.push_back(curr_line); } } size_t slurp_some_functions(const vector& in, size_t start, vector& out, bool first) { out.clear(); if (start >= in.size()) return start; out.push_back("#include \"header\""); if (first) out.push_back("#include \"global_definitions_list\""); else out.push_back("#include \"global_declarations_list\""); out.push_back(""); size_t curr = start; while (true) { if (curr >= in.size()) break; if (out.size() >= in.size()/Num_compilation_units) break; while (curr < in.size()) { // read functions -- lines until unindented '}' while (curr < in.size()) { const string& line = in.at(curr); //? cerr << curr << ": adding to function: " << line << '\n'; out.push_back(line); ++curr; if (!line.empty() && line.at(0) == '}') break; } // now look for a '#line' directive before the next non-comment non-empty // line while (curr < in.size()) { const string& line = in.at(curr); if (starts_with(line, "#line ")) goto try_return; out.push_back(line); ++curr; if (trim(line).empty()) continue; if (starts_with(trim(line), "//")) cont