From 7de6f6f275d840e8d9523c72d8f4309c51b4dcd3 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sat, 7 Mar 2015 14:36:51 +0200 Subject: Add support for buildspec --- build/b.cxx | 43 ++++++++++++- build/buildfile | 6 +- build/name.cxx | 19 +++--- build/parser | 13 +++- build/parser.cxx | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- build/spec | 58 +++++++++++++++++ build/spec.cxx | 89 ++++++++++++++++++++++++++ 7 files changed, 402 insertions(+), 18 deletions(-) create mode 100644 build/spec create mode 100644 build/spec.cxx 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 #include #include +#include #include //@@ TMP, for dump() #include #include +#include #include #include #include @@ -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 , 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, ""); + } + 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 #include #include +#include #include 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 +#include // is{alpha alnum}() + #include // unique_ptr #include #include // 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 +#include +#include +#include // move() + +#include +#include + +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 + { + opspec () = default; + opspec (std::string o): operation (std::move (o)) {} + + std::string operation; + }; + + struct metaopspec: std::vector + { + metaopspec () = default; + metaopspec (std::string mo): meta_operation (std::move (mo)) {} + + std::string meta_operation; + }; + + typedef std::vector 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 + +#include + +#include + +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; + } +} -- cgit v1.1