diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2015-01-20 17:18:09 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2015-01-20 17:18:09 +0200 |
commit | b0524a0b18eec9d5e5c3f6ce30b6cecdd02a6306 (patch) | |
tree | 4b1efc586782507e0647e884d6a13c6605298508 /build | |
parent | 47751abc43dab40e0ac4a1523994fd533e6a3b22 (diff) |
Diagnostic infrastructure revamp
Diffstat (limited to 'build')
-rw-r--r-- | build/algorithm.cxx | 41 | ||||
-rw-r--r-- | build/b.cxx | 244 | ||||
-rw-r--r-- | build/buildfile | 3 | ||||
-rw-r--r-- | build/cxx/rule.cxx | 76 | ||||
-rw-r--r-- | build/diagnostics | 310 | ||||
-rw-r--r-- | build/diagnostics.cxx | 66 | ||||
-rw-r--r-- | build/lexer | 25 | ||||
-rw-r--r-- | build/lexer.cxx | 13 | ||||
-rw-r--r-- | build/parser | 27 | ||||
-rw-r--r-- | build/parser.cxx | 87 | ||||
-rw-r--r-- | build/prerequisite | 2 | ||||
-rw-r--r-- | build/prerequisite.cxx | 6 | ||||
-rw-r--r-- | build/rule.cxx | 15 | ||||
-rw-r--r-- | build/target.cxx | 6 | ||||
-rw-r--r-- | build/timestamp | 4 | ||||
-rw-r--r-- | build/timestamp.cxx | 4 | ||||
-rw-r--r-- | build/trace | 80 | ||||
-rw-r--r-- | build/trace.cxx | 12 | ||||
-rw-r--r-- | build/utility | 53 | ||||
-rw-r--r-- | build/utility.cxx | 2 |
20 files changed, 650 insertions, 426 deletions
diff --git a/build/algorithm.cxx b/build/algorithm.cxx index af0e3f3..616d21e 100644 --- a/build/algorithm.cxx +++ b/build/algorithm.cxx @@ -7,13 +7,13 @@ #include <memory> // unique_ptr #include <utility> // move #include <cassert> -#include <iostream> #include <build/path> #include <build/scope> #include <build/target> #include <build/prerequisite> #include <build/rule> +#include <build/utility> #include <build/diagnostics> using namespace std; @@ -23,7 +23,7 @@ namespace build target& search (prerequisite& p) { - tracer tr ("search"); + tracer trace ("search"); assert (p.target == nullptr); @@ -40,11 +40,10 @@ namespace build // Find or insert. // - auto r (targets.insert (p.type, move (d), p.name, p.ext, tr)); + auto r (targets.insert (p.type, move (d), p.name, p.ext, trace)); - trace (4, [&]{ - tr << (r.second ? "new" : "existing") << " target " << r.first - << " for prerequsite " << p;}); + level4 ([&]{trace << (r.second ? "new" : "existing") << " target " + << r.first << " for prerequsite " << p;}); p.target = &r.first; return r.first; @@ -81,10 +80,9 @@ namespace build { auto g ( make_exception_guard ( - [] (target& t, const string& n) + [](target& t, const string& n) { - cerr << "info: while matching rule " << n - << " for target " << t << endl; + info << "while matching rule " << n << " for target " << t; }, t, n)); @@ -97,6 +95,8 @@ namespace build // bool ambig (false); + diag_record dr; + for (++i; i != rs.second; ++i) { const string& n1 (i->first); @@ -106,10 +106,10 @@ namespace build { auto g ( make_exception_guard ( - [] (target& t, const string& n1) + [](target& t, const string& n1) { - cerr << "info: while matching rule " << n1 - << " for target " << t << endl; + info << "while matching rule " << n1 << " for target " + << t; }, t, n1)); @@ -120,23 +120,22 @@ namespace build { if (!ambig) { - cerr << "error: multiple rules matching target " << t << endl; - cerr << "info: rule " << n << " matches" << endl; + dr << fail << "multiple rules matching target " << t + << info << "rule " << n << " matches"; ambig = true; } - cerr << "info: rule " << n1 << " also matches" << endl; + dr << info << "rule " << n1 << " also matches"; } } - if (ambig) + if (!ambig) { - cerr << "info: use rule hint to disambiguate this match" << endl; - throw error (); + t.recipe (ru.select (t, m)); + break; } - - t.recipe (ru.select (t, m)); - break; + else + dr << info << "use rule hint to disambiguate this match"; } } } diff --git a/build/b.cxx b/build/b.cxx index 14b23fc..3d19131 100644 --- a/build/b.cxx +++ b/build/b.cxx @@ -13,7 +13,7 @@ #include <vector> #include <cassert> #include <fstream> -#include <iostream> +#include <iostream> //@@ TMP, for dump() #include <typeinfo> #include <system_error> @@ -25,6 +25,7 @@ #include <build/process> #include <build/diagnostics> #include <build/context> +#include <build/utility> #include <build/lexer> #include <build/parser> @@ -44,7 +45,7 @@ namespace build { if (!match (t)) { - cerr << "error: no rule to update target " << t << endl; + error << "no rule to update target " << t; return false; } } @@ -60,7 +61,7 @@ namespace build if (!match_recursive (*p.target)) { - cerr << "info: required by " << t << endl; + info << "required by " << t; return false; } } @@ -73,7 +74,10 @@ namespace build { assert (t.state () == target_state::unknown); - target_state ts; + auto g ( + make_exception_guard ( + [](target& t){info << "while building target " << t;}, + t)); for (prerequisite& p: t.prerequisites) { @@ -81,26 +85,19 @@ namespace build if (pt.state () == target_state::unknown) { - pt.state ((ts = update (pt))); + target_state ts (update (pt)); if (ts == target_state::failed) return ts; } } + // @@ Why do we indicate failure via code rather than throw? Now + // there is no diagnostics via exception_guard above. + const recipe& r (t.recipe ()); - { - auto g ( - make_exception_guard ( - [] (target& t) - { - cerr << "info: while building target " << t << endl; - }, - t)); - - ts = r (t); - } + target_state ts (r (t)); assert (ts != target_state::unknown); t.state (ts); @@ -142,142 +139,128 @@ using namespace build; int main (int argc, char* argv[]) { - tracer tr ("main"); - - // Initialize time conversion data that is used by localtime_r(). - // - tzset (); + try + { + tracer trace ("main"); - // Trace verbosity. - // - verb = 5; + // Initialize time conversion data that is used by localtime_r(). + // + tzset (); - // Register target types. - // - target_types.insert (file::static_type); + // Trace verbosity. + // + verb = 5; - target_types.insert (exe::static_type); - target_types.insert (obj::static_type); + // Register target types. + // + target_types.insert (file::static_type); - target_types.insert (cxx::h::static_type); - target_types.insert (cxx::c::static_type); + target_types.insert (exe::static_type); + target_types.insert (obj::static_type); - target_types.insert (cxx::cxx::static_type); - target_types.insert (cxx::hxx::static_type); - target_types.insert (cxx::ixx::static_type); - target_types.insert (cxx::txx::static_type); + target_types.insert (cxx::h::static_type); + target_types.insert (cxx::c::static_type); - // Figure out directories: work, home, and {src,out}_{root,base}. - // - work = path::current (); + target_types.insert (cxx::cxx::static_type); + target_types.insert (cxx::hxx::static_type); + target_types.insert (cxx::ixx::static_type); + target_types.insert (cxx::txx::static_type); - if (const char* h = getenv ("HOME")) - home = path (h); - else - { - struct passwd* pw (getpwuid (getuid ())); + // Figure out directories: work, home, and {src,out}_{root,base}. + // + work = path::current (); - if (pw == nullptr) + if (const char* h = getenv ("HOME")) + home = path (h); + else { - const char* msg (strerror (errno)); - cerr << "error: unable to determine home directory: " << msg << endl; - return 1; - } + struct passwd* pw (getpwuid (getuid ())); - home = path (pw->pw_dir); - } + if (pw == nullptr) + { + const char* msg (strerror (errno)); + fail << "unable to determine home directory: " << msg; + } + + home = path (pw->pw_dir); + } - //@@ Must be normalized. - // - out_base = work; - src_base = out_base; + //@@ Must be normalized. + // + out_base = work; + src_base = out_base; - // The project's root directory is the one that contains the build/ - // sub-directory which contains the pre.build file. - // - for (path d (src_base); !d.root () && d != home; d = d.directory ()) - { - path f (d / path ("build/pre.build")); - if (path_mtime (f) != timestamp_nonexistent) + // The project's root directory is the one that contains the build/ + // sub-directory which contains the pre.build file. + // + for (path d (src_base); !d.root () && d != home; d = d.directory ()) { - src_root = d; - break; + path f (d / path ("build/pre.build")); + if (path_mtime (f) != timestamp_nonexistent) + { + src_root = d; + break; + } } - } - if (src_root.empty ()) - { - src_root = src_base; - out_root = out_base; - } - else - out_root = out_base.directory (src_base.leaf (src_root)); + if (src_root.empty ()) + { + src_root = src_base; + out_root = out_base; + } + else + out_root = out_base.directory (src_base.leaf (src_root)); - if (verb >= 4) - { - tr << "work dir: " << work.string (); - tr << "home dir: " << home.string (); - tr << "out_base: " << out_base.string (); - tr << "src_base: " << src_base.string (); - tr << "out_root: " << out_root.string (); - tr << "src_root: " << src_root.string (); - } + if (verb >= 4) + { + trace << "work dir: " << work.string (); + trace << "home dir: " << home.string (); + trace << "out_base: " << out_base.string (); + trace << "src_base: " << src_base.string (); + trace << "out_root: " << out_root.string (); + trace << "src_root: " << src_root.string (); + } - // Parse buildfile. - // - path bf ("buildfile"); + // Parse buildfile. + // + path bf ("buildfile"); - ifstream ifs (bf.string ().c_str ()); - if (!ifs.is_open ()) - { - cerr << "error: unable to open " << bf << " in read mode" << endl; - return 1; - } + ifstream ifs (bf.string ().c_str ()); + if (!ifs.is_open ()) + fail << "unable to open " << bf; - ifs.exceptions (ifstream::failbit | ifstream::badbit); - parser p (cerr); + ifs.exceptions (ifstream::failbit | ifstream::badbit); + parser p; - try - { - p.parse (ifs, bf, scopes[path::current ()]); - } - catch (const lexer_error&) - { - return 1; // Diagnostics has already been issued. - } - catch (const parser_error&) - { - return 1; // Diagnostics has already been issued. - } - catch (const std::ios_base::failure&) - { - cerr << "error: failed to read from " << bf << endl; - return 1; - } + try + { + p.parse (ifs, bf, scopes[path::current ()]); + } + catch (const std::ios_base::failure&) + { + fail << "failed to read from " << bf; + } - dump (); + dump (); - // Register rules. - // - cxx::link cxx_link; - rules[typeid (exe)].emplace ("cxx.gnu.link", cxx_link); + // Register rules. + // + cxx::link cxx_link; + rules[typeid (exe)].emplace ("cxx.gnu.link", cxx_link); - cxx::compile cxx_compile; - rules[typeid (obj)].emplace ("cxx.gnu.compile", cxx_compile); + cxx::compile cxx_compile; + rules[typeid (obj)].emplace ("cxx.gnu.compile", cxx_compile); - default_path_rule path_exists; - rules[typeid (path_target)].emplace ("", path_exists); + default_path_rule path_exists; + rules[typeid (path_target)].emplace ("", path_exists); - // Build. - // - if (default_target == nullptr) - { - cerr << "error: no default target" << endl; - return 1; - } + // Build. + // + if (default_target == nullptr) + { + fail << "no default target"; + } - try - { target& d (*default_target); if (!match_recursive (d)) @@ -289,27 +272,26 @@ main (int argc, char* argv[]) { case target_state::uptodate: { - cerr << "info: target " << d << " is up to date" << endl; + info << "target " << d << " is up to date"; break; } case target_state::updated: break; case target_state::failed: { - cerr << "error: failed to update target " << d << endl; - return 1; + fail << "failed to update target " << d; } case target_state::unknown: assert (false); } } - catch (const error&) + catch (const failed&) { return 1; // Diagnostics has already been issued. } catch (const std::exception& e) { - cerr << "error: " << e.what () << endl; + error << e.what (); return 1; } } diff --git a/build/buildfile b/build/buildfile index 3575baa..819f2e4 100644 --- a/build/buildfile +++ b/build/buildfile @@ -1,4 +1,3 @@ -exe{b1}: cxx{b algorithm scope parser lexer trace target prerequisite rule \ +exe{b1}: cxx{b algorithm scope parser lexer target prerequisite rule \ native context diagnostics cxx/target cxx/rule process timestamp path \ utility} - diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx index c9dd7d6..8d2c0b2 100644 --- a/build/cxx/rule.cxx +++ b/build/cxx/rule.cxx @@ -9,7 +9,6 @@ #include <cstddef> // size_t #include <cstdlib> // exit #include <utility> // move() -#include <iostream> #include <ext/stdio_filebuf.h> @@ -31,7 +30,7 @@ namespace build void* compile:: match (target& t, const string&) const { - tracer tr ("cxx::compile::match"); + tracer trace ("cxx::compile::match"); // @@ TODO: // @@ -57,7 +56,7 @@ namespace build return &p; } - trace (3, [&]{tr << "no c++ source file for target " << t;}); + level3 ([&]{trace << "no c++ source file for target " << t;}); return nullptr; } @@ -139,7 +138,7 @@ namespace build void compile:: inject_prerequisites (obj& o, const cxx& s, scope& ds) const { - tracer tr ("cxx::compile::inject_prerequisites"); + tracer trace ("cxx::compile::inject_prerequisites"); // We are using absolute source file path in order to get // absolute paths in the result. @@ -157,8 +156,7 @@ namespace build if (verb >= 2) print_process (args); - if (verb >= 5) - tr << "target: " << o; + level5 ([&]{trace << "target: " << o;}); try { @@ -173,10 +171,7 @@ namespace build getline (is, l); if (is.fail () && !is.eof ()) - { - cerr << "error: io error while parsing g++ -M output" << endl; - throw error (); - } + fail << "io error while parsing g++ -M output"; size_t pos (0); @@ -199,8 +194,7 @@ namespace build path file (next (l, pos)); file.normalize (); - if (verb >= 5) - tr << "prerequisite path: " << file.string (); + level5 ([&]{trace << "prerequisite path: " << file.string ();}); // If there is no extension (e.g., standard C++ headers), // then assume it is a header. Otherwise, let the standard @@ -223,7 +217,7 @@ namespace build // prerequisite& p ( ds.prerequisites.insert ( - hxx::static_type, move (d), move (n), e, ds, tr).first); + hxx::static_type, move (d), move (n), e, ds, trace).first); // Resolve to target so that we can assign its path. // @@ -241,12 +235,11 @@ namespace build // We assume the child process issued some diagnostics. // if (!pr.wait ()) - throw error (); + throw failed (); } catch (const process_error& e) { - cerr << "error: unable to execute '" << args[0] << "': " << - e.what () << endl; + error << "unable to execute " << args[0] << ": " << e.what (); // In a multi-threaded program that fork()'ed but did not exec(), // it is unwise to try to do any kind of cleanup (like unwinding @@ -255,7 +248,7 @@ namespace build if (e.child ()) exit (1); - throw error (); + throw failed (); } } @@ -319,7 +312,7 @@ namespace build if (verb >= 1) print_process (args); else - cerr << "c++ " << *s << endl; + text << "c++ " << *s; try { @@ -338,8 +331,7 @@ namespace build } catch (const process_error& e) { - cerr << "error: unable to execute '" << args[0] << "': " << - e.what () << endl; + error << "unable to execute " << args[0] << ": " << e.what (); // In a multi-threaded program that fork()'ed but did not exec(), // it is unwise to try to do any kind of cleanup (like unwinding @@ -357,7 +349,7 @@ namespace build void* link:: match (target& t, const string& hint) const { - tracer tr ("cxx::link::match"); + tracer trace ("cxx::link::match"); // @@ TODO: // @@ -395,7 +387,7 @@ namespace build } else { - trace (3, [&]{tr << "unexpected prerequisite type " << p.type;}); + level3 ([&]{trace << "unexpected prerequisite type " << p.type;}); return nullptr; } } @@ -405,7 +397,7 @@ namespace build // if (seen_c && !seen_cxx && hint < "cxx") { - trace (3, [&]{tr << "c prerequisite(s) without c++ or hint";}); + level3 ([&]{trace << "c prerequisite(s) without c++ or hint";}); return nullptr; } @@ -415,7 +407,7 @@ namespace build recipe link:: select (target& t, void*) const { - tracer tr ("cxx::link::select"); + tracer trace ("cxx::link::select"); // Derive executable file name from target name. // @@ -449,10 +441,8 @@ namespace build { if (!cp.directory.sub (src_root)) { - cerr << "error: out of project prerequisite " << cp << endl; - cerr << "info: specify corresponding obj{} target explicitly" - << endl; - throw error (); + fail << "out of project prerequisite " << cp << + info << "specify corresponding obj{} target explicitly"; } d = out_root / cp.directory.leaf (src_root); @@ -460,7 +450,12 @@ namespace build prerequisite& op ( cp.scope.prerequisites.insert ( - obj::static_type, move (d), cp.name, nullptr, cp.scope, tr).first); + obj::static_type, + move (d), + cp.name, + nullptr, + cp.scope, + trace).first); // Resolve this prerequisite to target. // @@ -495,20 +490,18 @@ namespace build } } - cerr << "error: synthesized target for prerequisite " << cp - << " would be incompatible with existing target " << ot - << endl; + diag_record r; + + r << fail << "synthesized target for prerequisite " << cp + << " would be incompatible with existing target " << ot; if (p.type.id == typeid (cxx)) - cerr << "info: existing prerequsite " << p << " does not " - << "match " << cp << endl; + r << info << "existing prerequsite " << p << " does not " + << "match " << cp; else - cerr << "info: unknown existing prerequsite " << p << endl; - - cerr << "info: specify corresponding obj{} target explicitly" - << endl; + r << info << "unknown existing prerequsite " << p; - throw error (); + r << info << "specify corresponding obj{} target explicitly"; } if (add) @@ -583,7 +576,7 @@ namespace build if (verb >= 1) print_process (args); else - cerr << "ld " << e << endl; + text << "ld " << e; try { @@ -602,8 +595,7 @@ namespace build } catch (const process_error& e) { - cerr << "error: unable to execute '" << args[0] << "': " << - e.what () << endl; + error << "unable to execute " << args[0] << ": " << e.what (); // In a multi-threaded program that fork()'ed but did not exec(), // it is unwise to try to do any kind of cleanup (like unwinding diff --git a/build/diagnostics b/build/diagnostics index 98f481f..f85d118 100644 --- a/build/diagnostics +++ b/build/diagnostics @@ -5,20 +5,17 @@ #ifndef BUILD_DIAGNOSTICS #define BUILD_DIAGNOSTICS -#include <tuple> #include <vector> +#include <cstdint> #include <utility> +#include <cassert> +#include <sstream> +#include <ostream> #include <exception> - -#include <build/trace> +#include <type_traits> namespace build { - // Throw this exception to terminate the build. The handler should - // assume that the diagnostics has already been issued. - // - class error: public std::exception {}; - // Print process commmand line. // void @@ -30,39 +27,296 @@ namespace build print_process (args.data ()); } - // Call a function if there is an exception. + // Throw this exception to terminate the build. The handler should + // assume that the diagnostics has already been issued. + // + class failed: public std::exception {}; + + // Trace verbosity level. // - template <typename F, typename T> - struct exception_guard; + // 1 - command lines to update explicit targets (e.g., .o) + // 2 - command lines to update implicit targets (e.g., .d) + // 3 - things didn't work out (e.g., rule did not match) + // 4 - additional information + // 5 - more additional information + // + extern std::uint8_t verb; + + template <typename F> inline void level1 (const F& f) {if (verb >= 1) f ();} + template <typename F> inline void level2 (const F& f) {if (verb >= 2) f ();} + template <typename F> inline void level3 (const F& f) {if (verb >= 3) f ();} + template <typename F> inline void level4 (const F& f) {if (verb >= 4) f ();} + template <typename F> inline void level5 (const F& f) {if (verb >= 5) f ();} + + // Diagnostic facility, base infrastructure (potentially reusable). + // + extern std::ostream* diag_stream; + + template <typename> struct diag_prologue; + template <typename> struct diag_mark; - template <typename F, typename... A> - inline exception_guard<F, std::tuple<A&&...>> - make_exception_guard (F f, A&&... a) + struct diag_record; + + typedef void (*diag_epilogue) (const diag_record&); + + struct diag_record { - return exception_guard<F, std::tuple<A&&...>> ( - std::move (f), std::forward_as_tuple (a...)); - } + template <typename T> + friend const diag_record& + operator<< (const diag_record& r, const T& x) + { + r.os_ << x; + return r; + } + + diag_record (): empty_ (true), epilogue_ (nullptr) {} + + template <typename B> + explicit + diag_record (const diag_prologue<B>& p) + : empty_ (true), epilogue_ (nullptr) { *this << p;} + + template <typename B> + explicit + diag_record (const diag_mark<B>& m) + : empty_ (true), epilogue_ (nullptr) { *this << m;} + + ~diag_record () noexcept(false); + + void + append (diag_epilogue e) const + { + if (e != nullptr) + { + assert (epilogue_ == nullptr); // No multiple epilogues support. + epilogue_ = e; + } + + if (empty_) + empty_ = false; + else + os_ << "\n "; + } + + // Move constructible-only type. + // + /* + @@ libstdc++ doesn't yet have the ostringstream move support. + + diag_record (diag_record&& r) + : os_ (std::move (r.os_)) + { + empty_ = r.empty_; + r.empty_ = true; + + epilogue_ = r.epilogue_; + r.epilogue_ = nullptr; + } + */ + + diag_record (diag_record&& r) + { + empty_ = r.empty_; + epilogue_ = r.epilogue_; + + if (!empty_) + { + os_ << r.os_.str (); + + r.empty_ = true; + r.epilogue_ = nullptr; + } + } + + diag_record& operator= (diag_record&&) = delete; + + diag_record (const diag_record&) = delete; + diag_record& operator= (const diag_record&) = delete; + + private: + mutable bool empty_; + mutable std::ostringstream os_; + mutable diag_epilogue epilogue_; + }; + + template <typename B> + struct diag_prologue: B + { + diag_prologue (diag_epilogue e = nullptr): B (), epilogue_ (e) {} + + template <typename... A> + diag_prologue (A&&... a) + : B (std::forward<A> (a)...), epilogue_ (nullptr) {} + + template <typename... A> + diag_prologue (diag_epilogue e, A&&... a) + : B (std::forward<A> (a)...), epilogue_ (e) {} + + template <typename T> + diag_record + operator<< (const T& x) const + { + diag_record r; + r.append (epilogue_); + B::operator() (r); + r << x; + return r; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_prologue& p) + { + r.append (p.epilogue_); + p (r); + return r; + } + + private: + diag_epilogue epilogue_; + }; - template <typename F, typename... A> - struct exception_guard<F, std::tuple<A...>> + template <typename B> + struct diag_mark: B { - typedef std::tuple<A...> T; + diag_mark (): B () {} - exception_guard (F f, T a): f_ (std::move (f)), a_ (std::move (a)) {} - ~exception_guard () + template <typename... A> + diag_mark (A&&... a): B (std::forward<A> (a)...) {} + + template <typename T> + diag_record + operator<< (const T& x) const { - if (std::uncaught_exception ()) - call (std::index_sequence_for<A...> ()); + return B::operator() () << x; } + friend const diag_record& + operator<< (const diag_record& r, const diag_mark& m) + { + return r << m (); + } + }; + + // Diagnostic facility, project specifics. + // + struct simple_prologue_base + { + explicit + simple_prologue_base (const char* type, const char* name) + : type_ (type), name_ (name) {} + + void + operator() (const diag_record& r) const; + private: - template <std::size_t... I> + const char* type_; + const char* name_; + }; + typedef diag_prologue<simple_prologue_base> simple_prologue; + + struct location + { + location () {} + location (const char* f, std::uint64_t l, std::uint64_t c) + : file (f), line (l), column (c) {} + + const char* file; + std::uint64_t line; + std::uint64_t column; + }; + + struct location_prologue_base + { + location_prologue_base (const char* type, + const char* name, + const location& l) + : type_ (type), name_ (name), loc_ (l) {} + void - call (std::index_sequence<I...>) {f_ (std::get<I> (a_)...);} + operator() (const diag_record& r) const; + + private: + const char* type_; + const char* name_; + const location& loc_; + }; + typedef diag_prologue<location_prologue_base> location_prologue; + + struct basic_mark_base + { + explicit + basic_mark_base (const char* type, const char* name = nullptr) + : type_ (type), name_ (name) {} + + simple_prologue + operator() () const + { + return simple_prologue (type_, name_); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (type_, name_, l); + } + + template <typename L> + location_prologue + operator() (const L& l) const + { + return location_prologue (type_, name_, get_location (l)); + } - F f_; - T a_; + private: + const char* type_; + const char* name_; }; + typedef diag_mark<basic_mark_base> basic_mark; + + extern const basic_mark error; + extern const basic_mark warn; + extern const basic_mark info; + extern const basic_mark text; + + struct trace_mark_base: basic_mark_base + { + explicit + trace_mark_base (const char* name): basic_mark_base ("trace", name) {} + }; + typedef diag_mark<trace_mark_base> trace_mark; + + typedef trace_mark tracer; + + template <typename E> + struct fail_mark_base + { + simple_prologue + operator() () const + { + return simple_prologue (&epilogue, "error", nullptr); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (&epilogue, "error", nullptr, l); + } + + template <typename L> + location_prologue + operator() (const L& l) const + { + return location_prologue (&epilogue, "error", nullptr, get_location (l)); + } + + static void + epilogue (const diag_record&) {throw E ();} + }; + + template <typename E> + using fail_mark = diag_mark<fail_mark_base<E>>; + + extern const fail_mark<failed> fail; } #endif // BUILD_DIAGNOSTICS diff --git a/build/diagnostics.cxx b/build/diagnostics.cxx index f213707..6b524a5 100644 --- a/build/diagnostics.cxx +++ b/build/diagnostics.cxx @@ -6,6 +6,8 @@ #include <iostream> +#include <build/utility> + using namespace std; namespace build @@ -13,8 +15,68 @@ namespace build void print_process (const char* const* args) { + diag_record r (text); + for (const char* const* p (args); *p != nullptr; p++) - cerr << (p != args ? " " : "") << *p; - cerr << endl; + r << (p != args ? " " : "") << *p; + } + + // Trace verbosity level. + // + uint8_t verb; + + // Diagnostic facility, base infrastructure. + // + ostream* diag_stream = &cerr; + + diag_record:: + ~diag_record () noexcept(false) + { + // Don't flush the record if this destructor was called as part of + // the stack unwinding. Right now this means we cannot use this + // mechanism in destructors, which is not a big deal, except for + // one place: exception_guard. So for now we are going to have + // this ugly special check which we will be able to get rid of + // once C++17 uncaught_exceptions() becomes available. + // + if (!empty_ && (!std::uncaught_exception () || exception_unwinding_dtor)) + { + *diag_stream << os_.str () << std::endl; + + if (epilogue_ != nullptr) + epilogue_ (*this); // Can throw. + } + } + + // Diagnostic facility, project specifics. + // + + void simple_prologue_base:: + operator() (const diag_record& r) const + { + if (type_ != nullptr) + r << type_ << ": "; + + if (name_ != nullptr) + r << name_ << ": "; + } + + void location_prologue_base:: + operator() (const diag_record& r) const + { + r << loc_.file << ':' << loc_.line << ':' << loc_.column << ": "; + + if (type_ != nullptr) + r << type_ << ": "; + + if (name_ != nullptr) + r << name_ << ": "; } + + const basic_mark error ("error"); + const basic_mark warn ("warning"); + const basic_mark info ("info"); + const basic_mark text (nullptr); + + const fail_mark<failed> fail; } diff --git a/build/lexer b/build/lexer index cf67eec..1944727 100644 --- a/build/lexer +++ b/build/lexer @@ -11,18 +11,14 @@ #include <exception> #include <build/token> +#include <build/diagnostics> namespace build { - // The handler must assume the diagnostics has already been issued. - // - struct lexer_error: std::exception {}; - class lexer { public: - lexer (std::istream& is, const std::string& name, std::ostream& diag) - : is_ (is), name_ (name), diag_ (diag) {} + lexer (std::istream& is, const std::string& name): is_ (is), fail (name) {} token next (); @@ -83,16 +79,23 @@ namespace build token name (xchar); - // Utilities. + // Diagnostics. // private: - std::ostream& - error (const xchar&); + struct fail_mark_base: build::fail_mark_base<failed> + { + fail_mark_base (const std::string& n): name_ (n) {} + + location_prologue + operator() (const xchar&) const; + + std::string name_; + }; + typedef diag_mark<fail_mark_base> fail_mark; private: std::istream& is_; - std::string name_; - std::ostream& diag_; + fail_mark fail; std::uint64_t l_ {1}; std::uint64_t c_ {1}; diff --git a/build/lexer.cxx b/build/lexer.cxx index a1aa375..ea11680 100644 --- a/build/lexer.cxx +++ b/build/lexer.cxx @@ -4,8 +4,6 @@ #include <build/lexer> -#include <iostream> - using namespace std; namespace build @@ -56,8 +54,7 @@ namespace build if (!is_eos (c)) return c; - error (c) << "unterminated escape sequence" << endl; - throw lexer_error (); + fail (c) << "unterminated escape sequence"; } void lexer:: @@ -217,10 +214,10 @@ namespace build unget_ = true; } - ostream& lexer:: - error (const xchar& c) + location_prologue lexer::fail_mark_base:: + operator() (const xchar& c) const { - return diag_ << name_ << ':' << c.line () << ':' << - c.column () << ": error: "; + return build::fail_mark_base<failed>::operator() ( + location (name_.c_str (), c.line (), c.column ())); } } diff --git a/build/parser b/build/parser index 66c0357..7515908 100644 --- a/build/parser +++ b/build/parser @@ -9,10 +9,9 @@ #include <vector> #include <iosfwd> #include <utility> // std::move -#include <exception> - #include <build/path> +#include <build/diagnostics> namespace build { @@ -22,15 +21,11 @@ namespace build enum class token_type; class lexer; - // The handler must assume the diagnostics has already been issued. - // - struct parser_error: std::exception {}; - class parser { public: - parser (std::ostream& diag): diag_ (diag) {} - + // Issues diagnostics and throws failed in case of an error. + // void parse (std::istream&, const path&, scope&); @@ -70,14 +65,22 @@ namespace build token_type next (token&, token_type&); - std::ostream& - error (const token&); + // Diagnostics. + // + private: + struct fail_mark_base: build::fail_mark_base<failed> + { + location_prologue + operator() (const token&) const; + + const path* path_; + }; + typedef diag_mark<fail_mark_base> fail_mark; private: - std::ostream& diag_; + fail_mark fail; lexer* lexer_; - const path* path_; scope* scope_; }; } diff --git a/build/parser.cxx b/build/parser.cxx index ccb41b4..696e2a3 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -5,7 +5,6 @@ #include <build/parser> #include <memory> // unique_ptr -#include <iostream> #include <build/token> #include <build/lexer> @@ -29,9 +28,9 @@ namespace build void parser:: parse (istream& is, const path& p, scope& s) { - lexer l (is, p.string (), diag_); + lexer l (is, p.string ()); lexer_ = &l; - path_ = &p; + fail.path_ = &p; scope_ = &s; token t (type::eos, 0, 0); @@ -41,16 +40,13 @@ namespace build parse_clause (t, tt); if (tt != type::eos) - { - error (t) << "unexpected " << t << endl; - throw parser_error (); - } + fail (t) << "unexpected " << t; } void parser:: parse_clause (token& t, token_type& tt) { - tracer tr ("parser::parse_clause"); + tracer trace ("parser::parse_clause"); while (tt != type::eos) { @@ -90,8 +86,7 @@ namespace build { //@@ TODO name (or better yet, type) location - error (t) << "unknown prerequisite type '" << tt << "'" << endl; - throw parser_error (); + fail (t) << "unknown prerequisite type " << tt; } const target_type& ti (i->second); @@ -133,7 +128,7 @@ namespace build // prerequisite& p ( scope_->prerequisites.insert ( - ti, move (d), move (n), e, *scope_, tr).first); + ti, move (d), move (n), e, *scope_, trace).first); ps.push_back (p); } @@ -190,15 +185,16 @@ namespace build { //@@ TODO name (or better yet, type) location - error (t) << "unknown target type '" << tt << "'" << endl; - throw parser_error (); + fail (t) << "unknown target type " << tt; } const target_type& ti (i->second); // Find or insert. // - target& t (targets.insert (ti, move (d), move (n), e, tr).first); + target& t ( + targets.insert ( + ti, move (d), move (n), e, trace).first); t.prerequisites = ps; //@@ OPT: move if last target. @@ -209,10 +205,7 @@ namespace build if (tt == type::newline) next (t, tt); else if (tt != type::eos) - { - error (t) << "expected newline instead of " << t << endl; - throw parser_error (); - } + fail (t) << "expected newline instead of " << t; continue; } @@ -226,10 +219,7 @@ namespace build // Should be on its own line. // if (next (t, tt) != type::newline) - { - error (t) << "expected newline after '{'" << endl; - throw parser_error (); - } + fail (t) << "expected newline after {"; // See if this is a directory or target scope. Different // things can appear inside depending on which one it is. @@ -243,8 +233,7 @@ namespace build { // @@ TODO: point to name. // - error (t) << "multiple names in directory scope" << endl; - throw parser_error (); + fail (t) << "multiple names in directory scope"; } dir = true; @@ -276,20 +265,14 @@ namespace build } if (tt != type::rcbrace) - { - error (t) << "expected '}' instead of " << t << endl; - throw parser_error (); - } + fail (t) << "expected '}' instead of " << t; // Should be on its own line. // if (next (t, tt) == type::newline) next (t, tt); else if (tt != type::eos) - { - error (t) << "expected newline after '}'" << endl; - throw parser_error (); - } + fail (t) << "expected newline after }"; } continue; @@ -298,12 +281,10 @@ namespace build if (tt == type::eos) continue; - error (t) << "expected newline insetad of " << t << endl; - throw parser_error (); + fail (t) << "expected newline insetad of " << t; } - error (t) << "unexpected " << t << endl; - throw parser_error (); + fail (t) << "unexpected " << t; } } @@ -320,10 +301,7 @@ namespace build parse_names (t, tt, ns, dp, tp); if (tt != type::rcbrace) - { - error (t) << "expected '}' instead of " << t << endl; - throw parser_error (); - } + fail (t) << "expected '}' instead of " << t; next (t, tt); continue; @@ -343,10 +321,7 @@ namespace build string::size_type p (name.rfind ('/')), n (name.size () - 1); if (p != n && tp != nullptr) - { - error (t) << "nested type name '" << name << "'" << endl; - throw parser_error (); - } + fail (t) << "nested type name " << name; path d1; const path* dp1 (dp); @@ -382,10 +357,7 @@ namespace build parse_names (t, tt, ns, dp1, tp1); if (tt != type::rcbrace) - { - error (t) << "expected '}' instead of " << t << endl; - throw parser_error (); - } + fail (t) << "expected '}' instead of " << t; next (t, tt); continue; @@ -400,8 +372,7 @@ namespace build if (!first) break; - error (t) << "expected name instead of " << t << endl; - throw parser_error (); + fail (t) << "expected name instead of " << t; } } @@ -413,11 +384,11 @@ namespace build return tt; } - ostream& parser:: - error (const token& t) + location_prologue parser::fail_mark_base:: + operator() (const token& t) const { - return diag_ << path_->string () << ':' << t.line () << ':' << - t.column () << ": error: "; + return build::fail_mark_base<failed>::operator() ( + location (path_->string ().c_str (), t.line (), t.column ())); } // Output the token type and value in a format suitable for diagnostics. @@ -429,10 +400,10 @@ namespace build { case token_type::eos: os << "<end-of-stream>"; break; case token_type::newline: os << "<newline>"; break; - case token_type::colon: os << "':'"; break; - case token_type::lcbrace: os << "'{'"; break; - case token_type::rcbrace: os << "'}'"; break; - case token_type::name: os << '\'' << t.name () << '\''; break; + case token_type::colon: os << ":"; break; + case token_type::lcbrace: os << "{"; break; + case token_type::rcbrace: os << "}"; break; + case token_type::name: os << t.name (); break; } return os; diff --git a/build/prerequisite b/build/prerequisite index a14621f..da90df4 100644 --- a/build/prerequisite +++ b/build/prerequisite @@ -13,13 +13,13 @@ #include <build/path> #include <build/utility> // extension_pool +#include <build/diagnostics> namespace build { class scope; class target; class target_type; - class tracer; class prerequisite { diff --git a/build/prerequisite.cxx b/build/prerequisite.cxx index f5461ef..8d86392 100644 --- a/build/prerequisite.cxx +++ b/build/prerequisite.cxx @@ -82,7 +82,7 @@ namespace build std::string name, const std::string* ext, scope& s, - tracer& tr) -> pair<prerequisite&, bool> + tracer& trace) -> pair<prerequisite&, bool> { //@@ OPT: would be nice to somehow first check if this prerequisite is // already in the set before allocating a new instance. @@ -96,8 +96,8 @@ namespace build // if (p.ext != ext) { - trace (4, [&]{ - tracer::record r (tr); + level4 ([&]{ + diag_record r (trace); r << "assuming prerequisite " << p << " is the same as the " << "one with "; if (ext == nullptr) diff --git a/build/rule.cxx b/build/rule.cxx index b73b053..d40eebf 100644 --- a/build/rule.cxx +++ b/build/rule.cxx @@ -5,7 +5,8 @@ #include <build/rule> #include <utility> // move() -#include <iostream> + +#include <build/diagnostics> using namespace std; @@ -74,9 +75,9 @@ namespace build { if (mt < mtp->mtime ()) { - cerr << "error: no rule to update target " << t << endl - << "info: prerequisite " << pt << " is ahead of " << t << - " by " << (mtp->mtime () - mt) << endl; + error << "no rule to update target " << t << + info << "prerequisite " << pt << " is ahead of " << t + << " by " << (mtp->mtime () - mt); return target_state::failed; } @@ -87,9 +88,9 @@ namespace build // if (pt.state () == target_state::updated) { - cerr << "error: no rule to update target " << t << endl - << "info: prerequisite " << pt << " is ahead of " << t << - " because it was updated" << endl; + error << "no rule to update target " << t << + info << "prerequisite " << pt << " is ahead of " << t + << " because it was updated"; return target_state::failed; } diff --git a/build/target.cxx b/build/target.cxx index b20f53b..3852bc4 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -43,7 +43,7 @@ namespace build path dir, std::string name, const std::string* ext, - tracer& tr) -> pair<target&, bool> + tracer& trace) -> pair<target&, bool> { //@@ OPT: would be nice to somehow first check if this target is // already in the set before allocating a new instance. @@ -60,8 +60,8 @@ namespace build // if (t.ext != ext) { - trace (4, [&]{ - tracer::record r (tr); + level4 ([&]{ + diag_record r (trace); r << "assuming target " << t << " is the same as the one with "; if (ext == nullptr) r << "unspecified extension"; diff --git a/build/timestamp b/build/timestamp index 2386125..04a0c29 100644 --- a/build/timestamp +++ b/build/timestamp @@ -38,10 +38,10 @@ namespace build const timestamp timestamp_nonexistent {duration {0}}; std::ostream& - operator<< (std::ostream&, timestamp); + operator<< (std::ostream&, const timestamp&); std::ostream& - operator<< (std::ostream&, duration); + operator<< (std::ostream&, const duration&); // Returns timestamp_nonexistent if the entry at the specified path // does not exist. All other errors are reported by throwing diff --git a/build/timestamp.cxx b/build/timestamp.cxx index 0ad3f3f..0d450ea 100644 --- a/build/timestamp.cxx +++ b/build/timestamp.cxx @@ -58,7 +58,7 @@ namespace build } ostream& - operator<< (ostream& os, timestamp ts) + operator<< (ostream& os, const timestamp& ts) { // @@ replace with put_time() // @@ -97,7 +97,7 @@ namespace build } ostream& - operator<< (ostream& os, duration d) + operator<< (ostream& os, const duration& d) { // @@ replace with put_time() // diff --git a/build/trace b/build/trace deleted file mode 100644 index 1764a69..0000000 --- a/build/trace +++ /dev/null @@ -1,80 +0,0 @@ -// file : build/trace -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD_TRACE -#define BUILD_TRACE - -#include <cstdint> -#include <iostream> - -namespace build -{ - // 1 - command lines to update explicit targets (e.g., .o) - // 2 - command lines to update implicit targets (e.g., .d) - // 3 - things didn't work out (e.g., rule did not match) - // 4 - additional information - // 5 - more additional information - // - extern std::uint8_t verb; - - struct tracer - { - explicit - tracer (const char* name): name_ (name) {} - - struct record - { - ~record () {if (!empty_) std::cerr << std::endl;} - - template <typename T> - std::ostream& - operator<< (const T& x) const - { - return std::cerr << x; - } - - explicit record (tracer& t): empty_ (false) {t.begin ();} - explicit record (bool e = true): empty_ (e) {} - - // Movable-only type. - // - record (record&& r) {empty_ = r.empty_; r.empty_ = true;} - record& operator= (record&& r) {empty_ = r.empty_; r.empty_ = true;} - - record (const record&) = delete; - record& operator= (const record&) = delete; - - private: - mutable bool empty_; - }; - - template <typename T> - record - operator<< (const T& x) const - { - begin (); - std::cerr << x; - return record (false); - } - - void - begin () const - { - std::cerr << "trace: " << name_ << ": "; - } - - private: - const char* name_; - }; - - template <typename F> - inline void - trace (std::uint8_t level, const F& f) - { - if (verb >= level) - f (); - } -} - -#endif // BUILD_TRACE diff --git a/build/trace.cxx b/build/trace.cxx deleted file mode 100644 index a7e6566..0000000 --- a/build/trace.cxx +++ /dev/null @@ -1,12 +0,0 @@ -// file : build/trace.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC -// license : MIT; see accompanying LICENSE file - -#include <build/trace> - -using namespace std; - -namespace build -{ - uint8_t verb; -} diff --git a/build/utility b/build/utility index bcbf834..5523e8b 100644 --- a/build/utility +++ b/build/utility @@ -5,9 +5,12 @@ #ifndef BUILD_UTILITY #define BUILD_UTILITY +#include <tuple> #include <string> -#include <unordered_set> +#include <utility> #include <cstring> // strcmp +#include <exception> +#include <unordered_set> namespace build @@ -26,6 +29,54 @@ namespace build bool operator() (const P& x, const P& y) const {return *x < *y;} }; + // Call a function if there is an exception. + // + + // Means we are in the body of a destructor that is being called + // as part of the exception stack unwindining. Used to compensate + // for the deficiencies of uncaught_exception() until C++17 + // uncaught_exceptions() becomes available. + // + // @@ MT: will have to be TLS. + // + extern bool exception_unwinding_dtor; + + template <typename F, typename T> + struct exception_guard; + + template <typename F, typename... A> + inline exception_guard<F, std::tuple<A&&...>> + make_exception_guard (F f, A&&... a) + { + return exception_guard<F, std::tuple<A&&...>> ( + std::move (f), std::forward_as_tuple (a...)); + } + + template <typename F, typename... A> + struct exception_guard<F, std::tuple<A...>> + { + typedef std::tuple<A...> T; + + exception_guard (F f, T a): f_ (std::move (f)), a_ (std::move (a)) {} + ~exception_guard () + { + if (std::uncaught_exception ()) + { + exception_unwinding_dtor = true; + call (std::index_sequence_for<A...> ()); + exception_unwinding_dtor = false; + } + } + + private: + template <std::size_t... I> + void + call (std::index_sequence<I...>) {f_ (std::get<I> (a_)...);} + + F f_; + T a_; + }; + // Pools (@@ perhaps move into a separate header). // struct string_pool: std::unordered_set<std::string> diff --git a/build/utility.cxx b/build/utility.cxx index f3fd51b..cd92740 100644 --- a/build/utility.cxx +++ b/build/utility.cxx @@ -8,5 +8,7 @@ using namespace std; namespace build { + bool exception_unwinding_dtor = false; + string_pool extension_pool; } |