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/parser.cxx | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 188 insertions(+), 4 deletions(-) (limited to 'build/parser.cxx') 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) { -- cgit v1.1