aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-03-07 14:36:51 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-03-07 14:36:51 +0200
commit7de6f6f275d840e8d9523c72d8f4309c51b4dcd3 (patch)
tree4b0387fe8f80db3ea6214aea330c03f3d1145c08
parent897a0e4fdf9ca90ee8d236a38e138a8ae6bc3627 (diff)
Add support for buildspec
-rw-r--r--build/b.cxx43
-rw-r--r--build/buildfile6
-rw-r--r--build/name.cxx19
-rw-r--r--build/parser13
-rw-r--r--build/parser.cxx192
-rw-r--r--build/spec58
-rw-r--r--build/spec.cxx89
7 files changed, 402 insertions, 18 deletions
diff --git a/build/b.cxx b/build/b.cxx
index 5509cba..de76071 100644
--- a/build/b.cxx
+++ b/build/b.cxx
@@ -13,10 +13,12 @@
#include <vector>
#include <cassert>
#include <fstream>
+#include <sstream>
#include <iostream> //@@ TMP, for dump()
#include <typeinfo>
#include <system_error>
+#include <build/spec>
#include <build/scope>
#include <build/target>
#include <build/prerequisite>
@@ -133,6 +135,45 @@ main (int argc, char* argv[])
root_scope = &scopes[path ("/")];
#endif
+ root_scope->variables["work"] = work;
+ root_scope->variables["home"] = home;
+
+ // Parse the buildspec.
+ //
+ buildspec bspec;
+ {
+ // Merge all the individual buildspec arguments into a single
+ // string. Instead, we could also parse them individually (
+ // and merge the result). The benefit of doing it this way
+ // is potentially better diagnostics (i.e., we could have
+ // used <buildspec-1>, <buildspec-2> to give the idea about
+ // which argument is invalid).
+ //
+ string s;
+ for (int i (1); i != argc;)
+ {
+ s += argv[i];
+ if (++i != argc)
+ s += ' ';
+ }
+
+ istringstream is (s);
+ is.exceptions (ifstream::failbit | ifstream::badbit);
+ parser p;
+
+ try
+ {
+ bspec = p.parse_buildspec (is, "<buildspec>");
+ }
+ catch (const std::ios_base::failure&)
+ {
+ fail << "failed to parse buildspec string";
+ }
+ }
+
+ if (verb >= 4)
+ trace << "buildspec: " << bspec;
+
// Figure out {src,out}_{root,base}. Note that all the paths must be
// normalized.
//
@@ -200,7 +241,7 @@ main (int argc, char* argv[])
try
{
- p.parse (ifs, bf, pbase_scope);
+ p.parse_buildfile (ifs, bf, pbase_scope);
}
catch (const std::ios_base::failure&)
{
diff --git a/build/buildfile b/build/buildfile
index d8f683b..5c1225d 100644
--- a/build/buildfile
+++ b/build/buildfile
@@ -1,3 +1,3 @@
-exe{b1}: cxx{b algorithm parser lexer name scope variable target prerequisite \
- rule native context search diagnostics cxx/target cxx/rule process \
- timestamp path utility mkdir dump}
+exe{b1}: cxx{b algorithm parser lexer name spec scope variable target \
+ prerequisite rule native context search diagnostics cxx/target cxx/rule \
+ process timestamp path utility mkdir dump}
diff --git a/build/name.cxx b/build/name.cxx
index 46e2440..7f300f4 100644
--- a/build/name.cxx
+++ b/build/name.cxx
@@ -19,9 +19,8 @@ namespace build
bool hv (!n.value.empty ());
bool hd (false);
- if (ht)
- os << n.type << '{';
-
+ // Print the directory before type.
+ //
if (!n.dir.empty ())
{
string s (diag_relative_work (n.dir));
@@ -32,20 +31,24 @@ namespace build
{
os << s;
- // Add the directory separator unless it is already there
- // or we have type but no value. The idea is to print foo/
- // or dir{foo}.
+ // Add the directory separator unless it is already there.
//
- if (s.back () != path::traits::directory_separator && (hv || !ht))
+ if (s.back () != path::traits::directory_separator)
os << path::traits::directory_separator;
hd = true;
}
}
+ if (ht)
+ os << n.type;
+
+ if (ht || (hd && hv))
+ os << '{';
+
os << n.value;
- if (ht)
+ if (ht || (hd && hv))
os << '}';
if (!ht && !hv && !hd)
diff --git a/build/parser b/build/parser
index 4375b27..25866bc 100644
--- a/build/parser
+++ b/build/parser
@@ -12,6 +12,7 @@
#include <build/path>
#include <build/token>
#include <build/name>
+#include <build/spec>
#include <build/diagnostics>
namespace build
@@ -25,10 +26,13 @@ namespace build
public:
parser (): fail (&path_) {}
- // Issues diagnostics and throws failed in case of an error.
+ // Issue diagnostics and throw failed in case of an error.
//
void
- parse (std::istream&, const path&, scope&);
+ parse_buildfile (std::istream&, const path&, scope&);
+
+ buildspec
+ parse_buildspec (std::istream&, const std::string& name);
// Recursive descent parser.
//
@@ -60,6 +64,11 @@ namespace build
names_type&, std::size_t pair,
const path* dir, const std::string* type);
+ // Buildspec.
+ //
+ buildspec
+ buildspec_clause (token&, token_type&, token_type end);
+
// Utilities.
//
private:
diff --git a/build/parser.cxx b/build/parser.cxx
index 56a61c5..5cdd627 100644
--- a/build/parser.cxx
+++ b/build/parser.cxx
@@ -4,6 +4,8 @@
#include <build/parser>
+#include <cctype> // is{alpha alnum}()
+
#include <memory> // unique_ptr
#include <fstream>
#include <utility> // move()
@@ -113,12 +115,12 @@ namespace build
}
void parser::
- parse (istream& is, const path& p, scope& s)
+ parse_buildfile (istream& is, const path& p, scope& s)
{
string rw (diag_relative_work (p));
path_ = &rw;
- lexer l (is, p.string ());
+ lexer l (is, rw);
lexer_ = &l;
scope_ = &s;
default_target_ = nullptr;
@@ -472,7 +474,7 @@ namespace build
const string* op (path_);
path_ = &rw;
- lexer l (ifs, p.string ());
+ lexer l (ifs, rw);
lexer* ol (lexer_);
lexer_ = &l;
@@ -581,7 +583,7 @@ namespace build
const string* op (path_);
path_ = &rw;
- lexer l (ifs, p.string ());
+ lexer l (ifs, rw);
lexer* ol (lexer_);
lexer_ = &l;
@@ -1013,6 +1015,188 @@ namespace build
}
}
+ // Buildspec parsing.
+ //
+
+ buildspec parser::
+ parse_buildspec (istream& is, const std::string& name)
+ {
+ path_ = &name;
+
+ lexer l (is, name);
+ lexer_ = &l;
+ scope_ = root_scope;
+
+ // Turn on pairs recognition (e.g., src_root/=out_root/exe{foo bar}).
+ //
+ lexer_->mode (lexer_mode::pairs);
+
+ token t (type::eos, false, 0, 0);
+ type tt;
+ next (t, tt);
+
+ return buildspec_clause (t, tt, type::eos);
+ }
+
+ static bool
+ opname (const name& n)
+ {
+ // First it has to be a non-empty simple name.
+ //
+ if (n.pair || !n.type.empty () || !n.dir.empty () || n.value.empty ())
+ return false;
+
+ // C identifier.
+ //
+ for (size_t i (0); i != n.value.size (); ++i)
+ {
+ char c (n.value[i]);
+ if (c != '_' && !(i != 0 ? isalnum (c) : isalpha (c)))
+ return false;
+ }
+
+ return true;
+ }
+
+ buildspec parser::
+ buildspec_clause (token& t, token_type& tt, token_type tt_end)
+ {
+ buildspec bs;
+
+ while (tt != tt_end)
+ {
+ // We always start with one or more names.
+ //
+ if (tt != type::name &&
+ tt != type::lcbrace && // Untyped name group: '{foo ...'
+ tt != type::dollar && // Variable expansion: '$foo ...'
+ tt != type::equal) // Empty pair LHS: '=foo ...'
+ fail (t) << "operation or target expected instead of " << t;
+
+ location l (get_location (t, &path_)); // Start of names.
+
+ // This call will produce zero or more names and should stop
+ // at either tt_end or '('.
+ //
+ names_type ns (names (t, tt));
+ size_t targets (ns.size ());
+
+ if (tt == type::lparen)
+ {
+ if (targets == 0 || !opname (ns.back ()))
+ fail (t) << "operation name expected before (";
+
+ targets--; // Last one is an operation name.
+ }
+
+ // Group all the targets into a single operation. In other
+ // words, 'foo bar' is equivalent to 'build(foo bar)'.
+ //
+ if (targets != 0)
+ {
+ if (bs.empty () || !bs.back ().meta_operation.empty ())
+ bs.push_back (metaopspec ()); // Empty (default) meta operation.
+
+ metaopspec& mo (bs.back ());
+
+ for (auto i (ns.begin ()), e (i + targets); i != e; ++i)
+ {
+ if (opname (*i))
+ mo.push_back (opspec (move (i->value)));
+ else
+ {
+ // Do we have the src_root?
+ //
+ path src_root;
+ if (i->pair)
+ {
+ if (!i->type.empty ())
+ fail (l) << "expected target src_root instead of " << *i;
+
+ src_root = move (i->dir);
+
+ if (!i->value.empty ())
+ src_root /= path (move (i->value));
+
+ ++i;
+ assert (i != e);
+ }
+
+ if (mo.empty () || !mo.back ().operation.empty ())
+ mo.push_back (opspec ()); // Empty (default) operation.
+
+ opspec& os (mo.back ());
+ os.emplace_back (move (src_root), move (*i));
+ }
+ }
+ }
+
+ // Handle the operation.
+ //
+ if (tt == type::lparen)
+ {
+ // Inside '(' and ')' we have another buildspec.
+ //
+ next (t, tt);
+ location l (get_location (t, &path_)); // Start of nested names.
+ buildspec nbs (buildspec_clause (t, tt, type::rparen));
+
+ // Merge the nested buildspec into ours. But first determine
+ // if we are an operation or meta-operation and do some sanity
+ // checks.
+ //
+ bool meta (false);
+ for (const metaopspec& mo: nbs)
+ {
+ if (!mo.meta_operation.empty ())
+ fail (l) << "nested meta-operation " << mo.meta_operation;
+
+ if (!meta)
+ {
+ for (const opspec& o: mo)
+ {
+ if (!o.operation.empty ())
+ {
+ meta = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // No nested meta-operations means we should have a single
+ // metaopspec object with empty meta-operation name.
+ //
+ assert (nbs.size () == 1);
+ metaopspec& nmo (nbs.back ());
+
+ if (meta)
+ {
+ nmo.meta_operation = move (ns.back ().value);
+ bs.push_back (move (nmo));
+ }
+ else
+ {
+ // Since we are not a meta-operation, the nested buildspec
+ // should be just a bunch of targets.
+ //
+ assert (nmo.size () == 1);
+ opspec& no (nmo.back ());
+
+ if (bs.empty () || !bs.back ().meta_operation.empty ())
+ bs.push_back (metaopspec ()); // Empty (default) meta operation.
+
+ no.operation = move (ns.back ().value);
+ bs.back ().push_back (move (no));
+ }
+
+ next (t, tt); // Done with ')'.
+ }
+ }
+
+ return bs;
+ }
+
void parser::
process_default_target (token& t)
{
diff --git a/build/spec b/build/spec
new file mode 100644
index 0000000..9ca8c4d
--- /dev/null
+++ b/build/spec
@@ -0,0 +1,58 @@
+// file : build/spec -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_SPEC
+#define BUILD_SPEC
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+#include <utility> // move()
+
+#include <build/path>
+#include <build/name>
+
+namespace build
+{
+ struct targetspec
+ {
+ targetspec (path sr, name t)
+ : src_root (std::move (sr)), target (std::move (t)) {}
+
+ path src_root;
+ name target; // target.dir is out_root.
+ };
+
+ struct opspec: std::vector<targetspec>
+ {
+ opspec () = default;
+ opspec (std::string o): operation (std::move (o)) {}
+
+ std::string operation;
+ };
+
+ struct metaopspec: std::vector<opspec>
+ {
+ metaopspec () = default;
+ metaopspec (std::string mo): meta_operation (std::move (mo)) {}
+
+ std::string meta_operation;
+ };
+
+ typedef std::vector<metaopspec> buildspec;
+
+ std::ostream&
+ operator<< (std::ostream&, const targetspec&);
+
+ std::ostream&
+ operator<< (std::ostream&, const opspec&);
+
+ std::ostream&
+ operator<< (std::ostream&, const metaopspec&);
+
+ std::ostream&
+ operator<< (std::ostream&, const buildspec&);
+}
+
+#endif // BUILD_SPEC
diff --git a/build/spec.cxx b/build/spec.cxx
new file mode 100644
index 0000000..fb83b31
--- /dev/null
+++ b/build/spec.cxx
@@ -0,0 +1,89 @@
+// file : build/spec.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <build/spec>
+
+#include <ostream>
+
+#include <build/diagnostics>
+
+using namespace std;
+
+namespace build
+{
+ ostream&
+ operator<< (ostream& os, const targetspec& s)
+ {
+ if (!s.src_root.empty ())
+ {
+ string d (diag_relative_work (s.src_root));
+
+ if (d != ".")
+ {
+ os << d;
+
+ // Add the directory separator unless it is already there.
+ //
+ if (d.back () != path::traits::directory_separator)
+ os << path::traits::directory_separator;
+
+ os << '=';
+ }
+ }
+
+ os << s.target;
+ return os;
+ }
+
+ ostream&
+ operator<< (ostream& os, const opspec& s)
+ {
+ bool ho (!s.operation.empty ());
+ bool ht (!s.empty ());
+
+ //os << s.operation;
+ os << (ho ? "\"" : "") << s.operation << (ho ? "\"" : "");
+
+ if (ho && ht)
+ os << '(';
+
+ for (auto b (s.begin ()), i (b); i != s.end (); ++i)
+ os << (i != b ? " " : "") << *i;
+
+ if (ho && ht)
+ os << ')';
+
+ return os;
+ }
+
+ ostream&
+ operator<< (ostream& os, const metaopspec& s)
+ {
+ bool hm (!s.meta_operation.empty ());
+ bool ho (!s.empty ());
+
+ //os << s.meta_operation;
+ os << (hm ? "\'" : "") << s.meta_operation << (hm ? "\'" : "");
+
+ if (hm && ho)
+ os << '(';
+
+ for (auto b (s.begin ()), i (b); i != s.end (); ++i)
+ os << (i != b ? " " : "") << *i;
+
+ if (hm && ho)
+ os << ')';
+
+ return os;
+ }
+
+ ostream&
+ operator<< (ostream& os, const buildspec& s)
+ {
+ for (auto b (s.begin ()), i (b); i != s.end (); ++i)
+ os << (i != b ? " " : "") << *i;
+
+ return os;
+ }
+}