aboutsummaryrefslogtreecommitdiff
path: root/build
diff options
context:
space:
mode:
Diffstat (limited to 'build')
-rw-r--r--build/algorithm.cxx41
-rw-r--r--build/b.cxx244
-rw-r--r--build/buildfile3
-rw-r--r--build/cxx/rule.cxx76
-rw-r--r--build/diagnostics310
-rw-r--r--build/diagnostics.cxx66
-rw-r--r--build/lexer25
-rw-r--r--build/lexer.cxx13
-rw-r--r--build/parser27
-rw-r--r--build/parser.cxx87
-rw-r--r--build/prerequisite2
-rw-r--r--build/prerequisite.cxx6
-rw-r--r--build/rule.cxx15
-rw-r--r--build/target.cxx6
-rw-r--r--build/timestamp4
-rw-r--r--build/timestamp.cxx4
-rw-r--r--build/trace80
-rw-r--r--build/trace.cxx12
-rw-r--r--build/utility53
-rw-r--r--build/utility.cxx2
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;
}