aboutsummaryrefslogtreecommitdiff
path: root/build/parser.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'build/parser.cxx')
-rw-r--r--build/parser.cxx2206
1 files changed, 0 insertions, 2206 deletions
diff --git a/build/parser.cxx b/build/parser.cxx
deleted file mode 100644
index ad17ae9..0000000
--- a/build/parser.cxx
+++ /dev/null
@@ -1,2206 +0,0 @@
-// file : build/parser.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
-// license : MIT; see accompanying LICENSE file
-
-#include <build/parser>
-
-#include <cctype> // is{alpha alnum}()
-
-#include <memory> // unique_ptr
-#include <fstream>
-#include <utility> // move()
-#include <iterator> // make_move_iterator()
-#include <iostream>
-
-#include <build/types>
-#include <build/utility>
-#include <build/version>
-
-#include <build/scope>
-#include <build/target>
-#include <build/prerequisite>
-#include <build/variable>
-#include <build/module>
-#include <build/file>
-#include <build/diagnostics>
-#include <build/context>
-
-using namespace std;
-
-namespace build
-{
- static location
- get_location (const token&, const void*);
-
- typedef token_type type;
-
- void parser::
- parse_buildfile (istream& is, const path& p, scope& root, scope& base)
- {
- enter_buildfile (p);
-
- path_ = &path_pool.find (diag_relative (p)); // Relative to work.
-
- lexer l (is, *path_);
- lexer_ = &l;
- target_ = nullptr;
- scope_ = &base;
- root_ = &root;
- default_target_ = nullptr;
-
- token t (type::eos, false, 0, 0);
- type tt;
- next (t, tt);
-
- clause (t, tt);
-
- if (tt != type::eos)
- fail (t) << "unexpected " << t;
-
- process_default_target (t);
- }
-
- token parser::
- parse_variable (lexer& l, scope& s, string name, type kind)
- {
- path_ = &l.name (); // Note: not pooled.
- lexer_ = &l;
- target_ = nullptr;
- scope_ = &s;
-
- type tt;
- token t (type::eos, false, 0, 0);
- variable (t, tt, name, kind);
- return t;
- }
-
- void parser::
- clause (token& t, type& tt)
- {
- tracer trace ("parser::clause", &path_);
-
- // clause() should always stop at a token that is at the beginning of
- // the line (except for eof). That is, if something is called to parse
- // a line, it should parse it until newline (or fail). This is important
- // for if-else blocks, directory scopes, etc., that assume the } token
- // they see is on the new line.
- //
- while (tt != type::eos)
- {
- // 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::lparen && // Eval context: '(foo) ...'
- tt != type::colon) // Empty name: ': ...'
- break; // Something else. Let our caller handle that.
-
- // See if this is one of the directives.
- //
- if (tt == type::name && keyword (t))
- {
- const string& n (t.value);
-
- if (n == "print")
- {
- // @@ Is this the only place where it is valid? Probably also
- // in var namespace.
- //
- print (t, tt);
- continue;
- }
- else if (n == "source")
- {
- source (t, tt);
- continue;
- }
- else if (n == "include")
- {
- include (t, tt);
- continue;
- }
- else if (n == "import")
- {
- import (t, tt);
- continue;
- }
- else if (n == "export")
- {
- export_ (t, tt);
- continue;
- }
- else if (n == "using" ||
- n == "using?")
- {
- using_ (t, tt);
- continue;
- }
- else if (n == "define")
- {
- define (t, tt);
- continue;
- }
- else if (n == "if" ||
- n == "if!")
- {
- if_else (t, tt);
- continue;
- }
- else if (n == "else" ||
- n == "elif" ||
- n == "elif!")
- {
- // Valid ones are handled in if_else().
- //
- fail (t) << n << " without if";
- }
- }
-
- // ': foo' is equvalent to '{}: foo' and to 'dir{}: foo'.
- //
- const location nloc (get_location (t, &path_));
- names_type ns (tt != type::colon
- ? names (t, tt)
- : names_type ({name ("dir", string ())}));
-
- if (tt == type::colon)
- {
- // While '{}:' means empty name, '{$x}:' where x is empty list
- // means empty list.
- //
- if (ns.empty ())
- fail (t) << "target expected before :";
-
- next (t, tt);
-
- if (tt == type::newline)
- {
- // See if this is a directory/target scope.
- //
- if (peek () == type::lcbrace)
- {
- next (t, tt);
-
- // Should be on its own line.
- //
- if (next (t, tt) != type::newline)
- 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.
- //
- bool dir (false);
- for (const auto& n: ns)
- {
- // A name represents directory as an empty value.
- //
- if (n.directory ())
- {
- if (ns.size () != 1)
- {
- // @@ TODO: point to name (and above).
- //
- fail (nloc) << "multiple names in directory scope";
- }
-
- dir = true;
- }
- }
-
- next (t, tt);
-
- if (dir)
- {
- // Directory scope.
- //
- dir_path p (move (ns[0].dir)); // Steal.
-
- // Relative scopes are opened relative to out, not src.
- //
- if (p.relative ())
- p = scope_->out_path () / p;
-
- p.normalize ();
-
- scope* ors (root_);
- scope* ocs (scope_);
- switch_scope (p);
-
- // A directory scope can contain anything that a top level can.
- //
- clause (t, tt);
-
- scope_ = ocs;
- root_ = ors;
- }
- else
- {
- // @@ TODO: target scope.
- }
-
- if (tt != type::rcbrace)
- 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)
- fail (t) << "expected newline after }";
-
- continue;
- }
-
- // If this is not a scope, then it is a target without any
- // prerequisites.
- //
- }
-
- // Dependency declaration or scope/target-specific variable
- // assignment.
- //
- if (tt == type::name ||
- tt == type::lcbrace ||
- tt == type::dollar ||
- tt == type::lparen ||
- tt == type::newline ||
- tt == type::eos)
- {
- const location ploc (get_location (t, &path_));
- names_type pns (tt != type::newline && tt != type::eos
- ? names (t, tt)
- : names_type ());
-
- // Common target entering code used in both cases.
- //
- auto enter_target = [this, &nloc, &trace] (name&& tn) -> target&
- {
- const string* e;
- const target_type* ti (scope_->find_target_type (tn, e));
-
- if (ti == nullptr)
- fail (nloc) << "unknown target type " << tn.type;
-
- path& d (tn.dir);
-
- if (d.empty ())
- d = scope_->out_path (); // Already normalized.
- else
- {
- if (d.relative ())
- d = scope_->out_path () / d;
-
- d.normalize ();
- }
-
- // Find or insert.
- //
- return targets.insert (
- *ti, move (tn.dir), move (tn.value), e, trace).first;
- };
-
- // Scope/target-specific variable assignment.
- //
- if (tt == type::equal ||
- tt == type::equal_plus ||
- tt == type::plus_equal)
- {
- token at (t);
- type att (tt);
-
- string v (variable_name (move (pns), ploc));
-
- // If we have multiple targets/scopes, then we save the value
- // tokens when parsing the first one and then replay them for
- // the subsequent. We have to do it this way because the value
- // may contain variable expansions that would be sensitive to
- // the target/scope context in which they are evaluated.
- //
- replay_guard rg (*this, ns.size () > 1);
-
- for (name& n: ns)
- {
- if (n.qualified ())
- fail (nloc) << "project name in scope/target " << n;
-
- if (n.directory ())
- {
- // The same code as in directory scope handling code above.
- //
- dir_path p (move (n.dir));
-
- if (p.relative ())
- p = scope_->out_path () / p;
-
- p.normalize ();
-
- scope* ors (root_);
- scope* ocs (scope_);
- switch_scope (p);
-
- variable (t, tt, v, att);
-
- scope_ = ocs;
- root_ = ors;
- }
- else
- {
- // Figure out if this is a target or type/pattern specific
- // variable.
- //
- size_t p (n.value.find ('*'));
-
- if (p == string::npos)
- {
- target* ot (target_);
- target_ = &enter_target (move (n));
- variable (t, tt, v, att);
- target_ = ot;
- }
- else
- {
- // See tests/variable/type-pattern.
- //
- if (!n.dir.empty ())
- fail (nloc) << "directory in target type/pattern " << n;
-
- if (n.value.find ('*', p + 1) != string::npos)
- fail (nloc) << "multiple wildcards in target type/pattern "
- << n;
-
- // Resolve target type. If none is specified, use the root
- // of the hierarchy.
- //
- const target_type* ti (
- n.untyped ()
- ? &target::static_type
- : scope_->find_target_type (n.type));
-
- if (ti == nullptr)
- fail (nloc) << "unknown target type " << n.type;
-
- if (att == type::equal_plus)
- fail (at) << "prepend to target type/pattern-specific "
- << "variable " << v;
-
- if (att == type::plus_equal)
- fail (at) << "append to target type/pattern-specific "
- << "variable " << v;
-
- const auto& var (var_pool.find (v));
-
- // Note: expand variables in the value in the context of
- // the scope.
- //
- names_type vns (variable_value (t, tt, var));
- value& val (scope_->target_vars[*ti][move (n.value)].assign (
- var).first);
- val.assign (move (vns), var);
- }
- }
-
- rg.play (); // Replay.
- }
- }
- // Dependency declaration.
- //
- else
- {
- // Prepare the prerequisite list.
- //
- target::prerequisites_type ps;
- ps.reserve (pns.size ());
-
- for (auto& pn: pns)
- {
- const string* e;
- const target_type* ti (scope_->find_target_type (pn, e));
-
- if (ti == nullptr)
- fail (ploc) << "unknown target type " << pn.type;
-
- pn.dir.normalize ();
-
- // Find or insert.
- //
- prerequisite& p (
- scope_->prerequisites.insert (
- pn.proj,
- *ti,
- move (pn.dir),
- move (pn.value),
- e,
- *scope_,
- trace).first);
-
- ps.emplace_back (p);
- }
-
- for (auto& tn: ns)
- {
- if (tn.qualified ())
- fail (nloc) << "project name in target " << tn;
-
- target& t (enter_target (move (tn)));
-
- //@@ OPT: move if last/single target (common cases).
- //
- t.prerequisites.insert (t.prerequisites.end (),
- ps.begin (),
- ps.end ());
-
- if (default_target_ == nullptr)
- default_target_ = &t;
- }
- }
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
-
- continue;
- }
-
- if (tt == type::eos)
- continue;
-
- fail (t) << "expected newline instead of " << t;
- }
-
- // Variable assignment.
- //
- if (tt == type::equal ||
- tt == type::equal_plus ||
- tt == type::plus_equal)
- {
- variable (t, tt, variable_name (move (ns), nloc), tt);
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
-
- continue;
- }
-
- // Allow things like function calls that don't result in anything.
- //
- if (tt == type::newline && ns.empty ())
- {
- next (t, tt);
- continue;
- }
-
- fail (t) << "unexpected " << t;
- }
- }
-
- void parser::
- source (token& t, type& tt)
- {
- tracer trace ("parser::source", &path_);
-
- // The rest should be a list of buildfiles. Parse them as names
- // to get variable expansion and directory prefixes.
- //
- mode (lexer_mode::value);
- next (t, tt);
- const location l (get_location (t, &path_));
- names_type ns (tt != type::newline && tt != type::eos
- ? names (t, tt)
- : names_type ());
-
- for (name& n: ns)
- {
- if (n.qualified () || n.empty () || n.value.empty ())
- fail (l) << "expected buildfile instead of " << n;
-
- // Construct the buildfile path.
- //
- path p (move (n.dir));
- p /= path (move (n.value));
-
- // If the path is relative then use the src directory corresponding
- // to the current directory scope.
- //
- if (root_->src_path_ != nullptr && p.relative ())
- p = src_out (scope_->out_path (), *root_) / p;
-
- p.normalize ();
-
- try
- {
- ifstream ifs (p.string ());
-
- if (!ifs.is_open ())
- fail (l) << "unable to open " << p;
-
- ifs.exceptions (ifstream::failbit | ifstream::badbit);
-
- level5 ([&]{trace (t) << "entering " << p;});
-
- enter_buildfile (p);
-
- const string* op (path_);
- path_ = &path_pool.find (diag_relative (p)); // Relative to work.
-
- lexer l (ifs, *path_);
- lexer* ol (lexer_);
- lexer_ = &l;
-
- token t (type::eos, false, 0, 0);
- type tt;
- next (t, tt);
- clause (t, tt);
-
- if (tt != type::eos)
- fail (t) << "unexpected " << t;
-
- level5 ([&]{trace (t) << "leaving " << p;});
-
- lexer_ = ol;
- path_ = op;
- }
- catch (const ifstream::failure&)
- {
- fail (l) << "unable to read buildfile " << p;
- }
- }
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
- }
-
- void parser::
- include (token& t, type& tt)
- {
- tracer trace ("parser::include", &path_);
-
- if (root_->src_path_ == nullptr)
- fail (t) << "inclusion during bootstrap";
-
- // The rest should be a list of buildfiles. Parse them as names
- // to get variable expansion and directory prefixes.
- //
- mode (lexer_mode::value);
- next (t, tt);
- const location l (get_location (t, &path_));
- names_type ns (tt != type::newline && tt != type::eos
- ? names (t, tt)
- : names_type ());
-
- for (name& n: ns)
- {
- if (n.qualified () || n.empty ())
- fail (l) << "expected buildfile instead of " << n;
-
- // Construct the buildfile path. If it is a directory, then append
- // 'buildfile'.
- //
- path p (move (n.dir));
- if (n.value.empty ())
- p /= path ("buildfile");
- else
- {
- bool d (path::traits::is_separator (n.value.back ())
- || n.type == "dir");
-
- p /= path (move (n.value));
- if (d)
- p /= path ("buildfile");
- }
-
- level6 ([&]{trace (l) << "relative path " << p;});
-
- // Determine new out_base.
- //
- dir_path out_base;
-
- if (p.relative ())
- {
- out_base = scope_->out_path () / p.directory ();
- out_base.normalize ();
- }
- else
- {
- p.normalize ();
-
- // Make sure the path is in this project. Include is only meant
- // to be used for intra-project inclusion (plus amalgamation).
- //
- bool in_out (false);
- if (!p.sub (root_->src_path ()) &&
- !(in_out = p.sub (root_->out_path ())))
- fail (l) << "out of project include " << p;
-
- out_base = in_out
- ? p.directory ()
- : out_src (p.directory (), *root_);
- }
-
- // Switch the scope. Note that we need to do this before figuring
- // out the absolute buildfile path since we may switch the project
- // root and src_root with it (i.e., include into a sub-project).
- //
- scope* ors (root_);
- scope* ocs (scope_);
- switch_scope (out_base);
-
- // Use the new scope's src_base to get absolute buildfile path
- // if it is relative.
- //
- if (p.relative ())
- p = scope_->src_path () / p.leaf ();
-
- level6 ([&]{trace (l) << "absolute path " << p;});
-
- if (!root_->buildfiles.insert (p).second) // Note: may be "new" root.
- {
- level5 ([&]{trace (l) << "skipping already included " << p;});
- scope_ = ocs;
- root_ = ors;
- continue;
- }
-
- try
- {
- ifstream ifs (p.string ());
-
- if (!ifs.is_open ())
- fail (l) << "unable to open " << p;
-
- ifs.exceptions (ifstream::failbit | ifstream::badbit);
-
- level5 ([&]{trace (t) << "entering " << p;});
-
- enter_buildfile (p);
-
- const string* op (path_);
- path_ = &path_pool.find (diag_relative (p)); // Relative to work.
-
- lexer l (ifs, *path_);
- lexer* ol (lexer_);
- lexer_ = &l;
-
- target* odt (default_target_);
- default_target_ = nullptr;
-
- token t (type::eos, false, 0, 0);
- type tt;
- next (t, tt);
- clause (t, tt);
-
- if (tt != type::eos)
- fail (t) << "unexpected " << t;
-
- process_default_target (t);
-
- level5 ([&]{trace (t) << "leaving " << p;});
-
- default_target_ = odt;
- lexer_ = ol;
- path_ = op;
- }
- catch (const ifstream::failure&)
- {
- fail (l) << "unable to read buildfile " << p;
- }
-
- scope_ = ocs;
- root_ = ors;
- }
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
- }
-
- void parser::
- import (token& t, type& tt)
- {
- tracer trace ("parser::import", &path_);
-
- if (root_->src_path_ == nullptr)
- fail (t) << "import during bootstrap";
-
- next (t, tt);
-
- // General import format:
- //
- // import [<var>=](<project>|<project>/<target>])+
- //
- value* val (nullptr);
- const build::variable* var (nullptr);
-
- type at; // Assignment type.
- if (tt == type::name)
- {
- at = peek ();
-
- if (at == type::equal ||
- at == type::equal_plus ||
- at == type::plus_equal)
- {
- var = &var_pool.find (t.value);
- val = at == type::equal
- ? &scope_->assign (*var)
- : &scope_->append (*var);
- next (t, tt); // Consume =/=+/+=.
- mode (lexer_mode::value);
- next (t, tt);
- }
- }
-
- // The rest should be a list of projects and/or targets. Parse
- // them as names to get variable expansion and directory prefixes.
- //
- const location l (get_location (t, &path_));
- names_type ns (tt != type::newline && tt != type::eos
- ? names (t, tt)
- : names_type ());
-
- for (name& n: ns)
- {
- // build::import() will check the name, if required.
- //
- names_type r (build::import (*scope_, move (n), l));
-
- if (val != nullptr)
- {
- if (at == type::equal)
- val->assign (move (r), *var);
- else if (at == type::equal_plus)
- val->prepend (move (r), *var);
- else
- val->append (move (r), *var);
- }
- }
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
- }
-
- void parser::
- export_ (token& t, type& tt)
- {
- tracer trace ("parser::export", &path_);
-
- scope* ps (scope_->parent_scope ());
-
- // This should be temp_scope.
- //
- if (ps == nullptr || ps->out_path () != scope_->out_path ())
- fail (t) << "export outside export stub";
-
- // The rest is a value. Parse it as names to get variable expansion.
- // build::import() will check the names, if required.
- //
- mode (lexer_mode::value);
- next (t, tt);
-
- if (tt != type::newline && tt != type::eos)
- export_value_ = names (t, tt);
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
- }
-
- void parser::
- using_ (token& t, type& tt)
- {
- tracer trace ("parser::using", &path_);
-
- bool optional (t.value.back () == '?');
-
- if (optional && boot_)
- fail (t) << "optional module in bootstrap";
-
- // The rest should be a list of module names. Parse them as names
- // to get variable expansion, etc.
- //
- mode (lexer_mode::pairs, '@');
- next (t, tt);
- const location l (get_location (t, &path_));
- names_type ns (tt != type::newline && tt != type::eos
- ? names (t, tt)
- : names_type ());
-
- for (auto i (ns.begin ()); i != ns.end (); ++i)
- {
- string n, v;
-
- if (!i->simple ())
- fail (l) << "module name expected instead of " << *i;
-
- n = move (i->value);
-
- if (i->pair)
- {
- ++i;
- if (!i->simple ())
- fail (l) << "module version expected instead of " << *i;
-
- v = move (i->value);
- }
-
- // Handle the special 'build' module.
- //
- if (n == "build")
- {
- if (!v.empty ())
- {
- unsigned int iv;
- try {iv = to_version (v);}
- catch (const invalid_argument& e)
- {
- fail (l) << "invalid version '" << v << "': " << e.what ();
- }
-
- if (iv > BUILD_VERSION)
- fail (l) << "build2 " << v << " required" <<
- info << "running build2 " << BUILD_VERSION_STR;
- }
- }
- else
- {
- assert (v.empty ()); // Module versioning not yet implemented.
-
- if (boot_)
- boot_module (n, *root_, l);
- else
- load_module (optional, n, *root_, *scope_, l);
- }
- }
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
- }
-
- static target*
- derived_factory (const target_type& t, dir_path d, string n, const string* e)
- {
- // Pass our type to the base factory so that it can detect that it is
- // being called to construct a derived target. This can be used, for
- // example, to decide whether to "link up" to the group.
- //
- target* r (t.base->factory (t, move (d), move (n), e));
- r->derived_type = &t;
- return r;
- }
-
- constexpr const char derived_ext_var[] = "extension";
-
- void parser::
- define (token& t, type& tt)
- {
- // define <derived>: <base>
- //
- // See tests/define.
- //
- if (next (t, tt) != type::name)
- fail (t) << "expected name instead of " << t << " in target type "
- << "definition";
-
- string dn (move (t.value));
- const location dnl (get_location (t, &path_));
-
- if (next (t, tt) != type::colon)
- fail (t) << "expected ':' instead of " << t << " in target type "
- << "definition";
-
- next (t, tt);
-
- if (tt == type::name)
- {
- // Target.
- //
- const string& bn (t.value);
- const target_type* bt (scope_->find_target_type (bn));
-
- if (bt == nullptr)
- fail (t) << "unknown target type " << bn;
-
- unique_ptr<target_type> dt (new target_type (*bt));
- dt->base = bt;
- dt->factory = &derived_factory;
-
- // Override extension derivation function: we most likely don't want
- // to use the same default as our base (think cli: file).
- //
- dt->extension = &target_extension_var<derived_ext_var, nullptr>;
-
- target_type& rdt (*dt); // Save a non-const reference to the object.
-
- auto pr (scope_->target_types.emplace (dn, target_type_ref (move (dt))));
-
- if (!pr.second)
- fail (dnl) << "target type " << dn << " already define in this scope";
-
- // Patch the alias name to use the map's key storage.
- //
- rdt.name = pr.first->first.c_str ();
-
- next (t, tt); // Get newline.
- }
- else
- fail (t) << "expected name instead of " << t << " in target type "
- << "definition";
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
- }
-
- void parser::
- if_else (token& t, type& tt)
- {
- // Handle the whole if-else chain. See tests/if-else.
- //
- bool taken (false); // One of the branches has been taken.
-
- for (;;)
- {
- string k (move (t.value));
- next (t, tt);
-
- bool take (false); // Take this branch?
-
- if (k != "else")
- {
- // Should we evaluate the expression if one of the branches has
- // already been taken? On the one hand, evaluating it is a waste
- // of time. On the other, it can be invalid and the only way for
- // the user to know their buildfile is valid is to test every
- // branch. There could also be side effects. We also have the same
- // problem with ignored branch blocks except there evaluating it
- // is not an option. So let's skip it.
- //
- if (taken)
- skip_line (t, tt);
- else
- {
- if (tt == type::newline || tt == type::eos)
- fail (t) << "expected " << k << "-expression instead of " << t;
-
- // Parse as names to get variable expansion, evaluation, etc.
- //
- const location nsl (get_location (t, &path_));
- names_type ns (names (t, tt));
-
- // Should evaluate to true or false.
- //
- if (ns.size () != 1 || !value_traits<bool>::assign (ns[0]))
- fail (nsl) << "expected " << k << "-expression to evaluate to "
- << "'true' or 'false' instead of '" << ns << "'";
-
- bool e (ns[0].value == "true");
- take = (k.back () == '!' ? !e : e);
- }
- }
- else
- take = !taken;
-
- if (tt != type::newline)
- fail (t) << "expected newline instead of " << t << " after " << k
- << (k != "else" ? "-expression" : "");
-
- if (next (t, tt) != type::lcbrace)
- fail (t) << "expected { instead of " << t << " at the beginning of "
- << k << "-block";
-
- if (next (t, tt) != type::newline)
- fail (t) << "expected newline after {";
-
- next (t, tt);
-
- if (take)
- {
- clause (t, tt);
- taken = true;
- }
- else
- skip_block (t, tt);
-
- if (tt != type::rcbrace)
- fail (t) << "expected } instead of " << t << " at the end of " << k
- << "-block";
-
- next (t, tt);
-
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline after }";
-
- // See if we have another el* keyword.
- //
- if (k != "else" && tt == type::name && keyword (t))
- {
- const string& n (t.value);
-
- if (n == "else" || n == "elif" || n == "elif!")
- continue;
- }
-
- break;
- }
- }
-
- void parser::
- print (token& t, type& tt)
- {
- // Parse the rest as names to get variable expansion, etc. Switch
- // to the variable value lexing mode so that we don't treat special
- // characters (e.g., ':') as the end of the names.
- //
- mode (lexer_mode::value);
- next (t, tt);
- names_type ns (tt != type::newline && tt != type::eos
- ? names (t, tt)
- : names_type ());
-
- cout << ns << endl;
-
- if (tt != type::eos)
- next (t, tt); // Swallow newline.
- }
-
- string parser::
- variable_name (names_type&& ns, const location& l)
- {
- // The list should contain a single, simple name.
- //
- if (ns.size () != 1 || !ns[0].simple () || ns[0].empty ())
- fail (l) << "variable name expected instead of " << ns;
-
- string& n (ns[0].value);
-
- if (n.front () == '.') // Fully qualified name.
- return string (n, 1, string::npos);
- else
- //@@ TODO: append namespace if any.
- return move (n);
- }
-
- void parser::
- variable (token& t, type& tt, string name, type kind)
- {
- const auto& var (var_pool.find (move (name)));
- names_type vns (variable_value (t, tt, var));
-
- if (kind == type::equal)
- {
- value& v (target_ != nullptr
- ? target_->assign (var)
- : scope_->assign (var));
- v.assign (move (vns), var);
- }
- else
- {
- value& v (target_ != nullptr
- ? target_->append (var)
- : scope_->append (var));
-
- if (kind == type::equal_plus)
- v.prepend (move (vns), var);
- else
- v.append (move (vns), var);
- }
- }
-
- names parser::
- variable_value (token& t, type& tt, const variable_type& var)
- {
- if (var.pairs != '\0')
- mode (lexer_mode::pairs, var.pairs);
- else
- mode (lexer_mode::value);
-
- next (t, tt);
- return (tt != type::newline && tt != type::eos
- ? names (t, tt)
- : names_type ());
- }
-
- parser::names_type parser::
- eval (token& t, type& tt)
- {
- mode (lexer_mode::eval);
- next (t, tt);
-
- names_type ns (tt != type::rparen ? names (t, tt) : names_type ());
-
- if (tt != type::rparen)
- fail (t) << "expected ')' instead of " << t;
-
- return ns;
- }
-
- // Parse names inside {} and handle the following "crosses" (i.e.,
- // {a b}{x y}) if any. Return the number of names added to the list.
- //
- size_t parser::
- names_trailer (token& t, type& tt,
- names_type& ns,
- size_t pair,
- const string* pp,
- const dir_path* dp,
- const string* tp)
- {
- next (t, tt); // Get what's after '{'.
-
- size_t count (ns.size ());
- names (t, tt,
- ns,
- false,
- (pair != 0
- ? pair
- : (ns.empty () || ns.back ().pair == '\0' ? 0 : ns.size ())),
- pp, dp, tp);
- count = ns.size () - count;
-
- if (tt != type::rcbrace)
- fail (t) << "expected } instead of " << t;
-
- // See if we have a cross. See tests/names.
- //
- if (peek () == type::lcbrace && !peeked ().separated)
- {
- next (t, tt); // Get '{'.
- const location loc (get_location (t, &path_));
-
- names_type x; // Parse into a separate list of names.
- names_trailer (t, tt, x, 0, nullptr, nullptr, nullptr);
-
- if (size_t n = x.size ())
- {
- // Now cross the last 'count' names in 'ns' with 'x'. First we will
- // allocate n - 1 additional sets of last 'count' names in 'ns'.
- //
- size_t b (ns.size () - count); // Start of 'count' names.
- ns.reserve (ns.size () + count * (n - 1));
- for (size_t i (0); i != n - 1; ++i)
- for (size_t j (0); j != count; ++j)
- ns.push_back (ns[b + j]);
-
- // Now cross each name, this time including the first set.
- //
- for (size_t i (0); i != n; ++i)
- {
- for (size_t j (0); j != count; ++j)
- {
- name& l (ns[b + i * count + j]);
- const name& r (x[i]);
-
- // Move the project names.
- //
- if (r.proj != nullptr)
- {
- if (l.proj != nullptr)
- fail (loc) << "nested project name " << *r.proj;
-
- l.proj = r.proj;
- }
-
- // Merge directories.
- //
- if (!r.dir.empty ())
- {
- if (l.dir.empty ())
- l.dir = move (r.dir);
- else
- l.dir /= r.dir;
- }
-
- // Figure out the type. As a first step, "promote" the lhs value
- // to type.
- //
- if (!l.value.empty ())
- {
- if (!l.type.empty ())
- fail (loc) << "nested type name " << l.value;
-
- l.type.swap (l.value);
- }
-
- if (!r.type.empty ())
- {
- if (!l.type.empty ())
- fail (loc) << "nested type name " << r.type;
-
- l.type = move (r.type);
- }
-
- l.value = move (r.value);
-
- // @@ TODO: need to handle pairs on lhs. I think all that needs
- // to be done is skip pair's first elements. Maybe also check
- // that there are no pairs on the rhs. There is just no easy
- // way to enable the pairs mode to test it, yet.
- }
- }
-
- count *= n;
- }
- }
-
- return count;
- }
-
- void parser::
- names (token& t, type& tt,
- names_type& ns,
- bool chunk,
- size_t pair,
- const string* pp,
- const dir_path* dp,
- const string* tp)
- {
- // If pair is not 0, then it is an index + 1 of the first half of
- // the pair for which we are parsing the second halves, e.g.,
- // a={b c d{e f} {}}.
- //
-
- // Buffer that is used to collect the complete name in case of
- // an unseparated variable expansion or eval context, e.g.,
- // 'foo$bar($baz)fox'. The idea is to concatenate all the
- // individual parts in this buffer and then re-inject it into
- // the loop as a single token.
- //
- string concat;
-
- // Number of names in the last group. This is used to detect when
- // we need to add an empty first pair element (e.g., {=y}) or when
- // we have a for now unsupported multi-name LHS (e.g., {x y}=z).
- //
- size_t count (0);
-
- for (bool first (true);; first = false)
- {
- // If the accumulating buffer is not empty, then we have two options:
- // continue accumulating or inject. We inject if the next token is
- // not a name, var expansion, or eval context or if it is separated.
- //
- if (!concat.empty () &&
- ((tt != type::name &&
- tt != type::dollar &&
- tt != type::lparen) || peeked ().separated))
- {
- tt = type::name;
- t = token (move (concat), true, false, t.line, t.column);
- concat.clear ();
- }
- else if (!first)
- {
- // If we are chunking, stop at the next separated token. Unless
- // current or next token is a pair separator, since we want the
- // "x = y" pair to be parsed as a single chunk.
- //
- bool p (t.type == type::pair_separator); // Current token.
-
- next (t, tt);
-
- if (chunk && t.separated && (tt != type::pair_separator && !p))
- break;
- }
-
- // Name.
- //
- if (tt == type::name)
- {
- string name (t.value); //@@ move?
- tt = peek ();
-
- // Should we accumulate? If the buffer is not empty, then
- // we continue accumulating (the case where we are separated
- // should have been handled by the injection code above). If
- // the next token is a var expansion or eval context and it
- // is not separated, then we need to start accumulating.
- //
- if (!concat.empty () || // Continue.
- ((tt == type::dollar ||
- tt == type::lparen) && !peeked ().separated)) // Start.
- {
- concat += name;
- continue;
- }
-
- string::size_type p (name.find_last_of ("/%"));
-
- // First take care of project. A project-qualified name is
- // not very common, so we can afford some copying for the
- // sake of simplicity.
- //
- const string* pp1 (pp);
-
- if (p != string::npos)
- {
- bool last (name[p] == '%');
- string::size_type p1 (last ? p : name.rfind ('%', p - 1));
-
- if (p1 != string::npos)
- {
- string proj;
- proj.swap (name);
-
- // First fix the rest of the name.
- //
- name.assign (proj, p1 + 1, string::npos);
- p = last ? string::npos : p - (p1 + 1);
-
- // Now process the project name.
- // @@ Validate it.
- //
- proj.resize (p1);
-
- if (pp != nullptr)
- fail (t) << "nested project name " << proj;
-
- pp1 = &project_name_pool.find (proj);
- }
- }
-
- string::size_type n (p != string::npos ? name.size () - 1 : 0);
-
- // See if this is a type name, directory prefix, or both. That
- // is, it is followed by an un-separated '{'.
- //
- if (tt == type::lcbrace && !peeked ().separated)
- {
- next (t, tt);
-
- if (p != n && tp != nullptr)
- fail (t) << "nested type name " << name;
-
- dir_path d1;
- const dir_path* dp1 (dp);
-
- string t1;
- const string* tp1 (tp);
-
- if (p == string::npos) // type
- tp1 = &name;
- else if (p == n) // directory
- {
- if (dp == nullptr)
- d1 = dir_path (name);
- else
- d1 = *dp / dir_path (name);
-
- dp1 = &d1;
- }
- else // both
- {
- t1.assign (name, p + 1, n - p);
-
- if (dp == nullptr)
- d1 = dir_path (name, 0, p + 1);
- else
- d1 = *dp / dir_path (name, 0, p + 1);
-
- dp1 = &d1;
- tp1 = &t1;
- }
-
- count = names_trailer (t, tt, ns, pair, pp1, dp1, tp1);
- tt = peek ();
- continue;
- }
-
- // If we are a second half of a pair, add another first half
- // unless this is the first instance.
- //
- if (pair != 0 && pair != ns.size ())
- ns.push_back (ns[pair - 1]);
-
- count = 1;
-
- // If it ends with a directory separator, then it is a directory.
- // Note that at this stage we don't treat '.' and '..' as special
- // (unless they are specified with a directory separator) because
- // then we would have ended up treating '.: ...' as a directory
- // scope. Instead, this is handled higher up the processing chain,
- // in target_types::find(). This would also mess up reversibility
- // to simple name.
- //
- // @@ TODO: and not quoted
- //
- if (p == n)
- {
- // For reversibility to simple name, only treat it as a directory
- // if the string is an exact representation.
- //
- if (p != 0 && name[p - 1] != '/') // Take care of the "//" case.
- name.resize (p); // Strip trailing '/'.
-
- dir_path dir (move (name), dir_path::exact);
-
- if (!dir.empty ())
- {
- if (dp != nullptr)
- dir = *dp / dir;
-
- ns.emplace_back (pp1,
- move (dir),
- (tp != nullptr ? *tp : string ()),
- string ());
- continue;
- }
-
- // Add the trailing slash back and treat it as a simple name.
- //
- if (p != 0 && name[p - 1] != '/')
- name.push_back ('/');
- }
-
- ns.emplace_back (pp1,
- (dp != nullptr ? *dp : dir_path ()),
- (tp != nullptr ? *tp : string ()),
- move (name));
- continue;
- }
-
- // Variable expansion/function call or eval context.
- //
- if (tt == type::dollar || tt == type::lparen)
- {
- // These two cases are pretty similar in that in both we
- // pretty quickly end up with a list of names that we need
- // to splice into the result.
- //
- names_type lv_data;
- const names_type* plv;
-
- location loc;
- const char* what; // Variable or evaluation context.
-
- if (tt == type::dollar)
- {
- // Switch to the variable name mode. We want to use this
- // mode for $foo but not for $(foo). Since we don't know
- // whether the next token is a paren or a name, we turn
- // it on and switch to the eval mode if what we get next
- // is a paren.
- //
- mode (lexer_mode::variable);
- next (t, tt);
- loc = get_location (t, &path_);
-
- string n;
- if (tt == type::name)
- n = t.value;
- else if (tt == type::lparen)
- {
- expire_mode ();
- names_type ns (eval (t, tt));
-
- // Make sure the result of evaluation is a single, simple name.
- //
- if (ns.size () != 1 || !ns.front ().simple ())
- fail (loc) << "variable/function name expected instead of '"
- << ns << "'";
-
- n = move (ns.front ().value);
- }
- else
- fail (t) << "variable/function name expected instead of " << t;
-
- if (n.empty ())
- fail (loc) << "empty variable/function name";
-
- // Figure out whether this is a variable expansion or a function
- // call.
- //
- tt = peek ();
-
- if (tt == type::lparen)
- {
- next (t, tt); // Get '('.
- names_type ns (eval (t, tt));
-
- // Just a stub for now.
- //
- cout << n << "(" << ns << ")" << endl;
-
- tt = peek ();
-
- if (lv_data.empty ())
- continue;
-
- plv = &lv_data;
- what = "function call";
- }
- else
- {
- // Process variable name.
- //
- if (n.front () == '.') // Fully qualified name.
- n.erase (0, 1);
- else
- {
- //@@ TODO: append namespace if any.
- }
-
- // Lookup.
- //
- const auto& var (var_pool.find (move (n)));
- auto l (target_ != nullptr ? (*target_)[var] : (*scope_)[var]);
-
- // Undefined/NULL namespace variables are not allowed.
- //
- if (!l && var.name.find ('.') != string::npos)
- fail (loc) << "undefined/null namespace variable " << var.name;
-
- if (!l || l->empty ())
- continue;
-
- plv = &l->data_;
- what = "variable expansion";
- }
- }
- else
- {
- loc = get_location (t, &path_);
- lv_data = eval (t, tt);
-
- tt = peek ();
-
- if (lv_data.empty ())
- continue;
-
- plv = &lv_data;
- what = "context evaluation";
- }
-
- // @@ Could move if (lv == &lv_data).
- //
- const names_type& lv (*plv);
-
- // Should we accumulate? If the buffer is not empty, then
- // we continue accumulating (the case where we are separated
- // should have been handled by the injection code above). If
- // the next token is a name or var expansion and it is not
- // separated, then we need to start accumulating.
- //
- if (!concat.empty () || // Continue.
- ((tt == type::name || // Start.
- tt == type::dollar ||
- tt == type::lparen) && !peeked ().separated))
- {
- // This should be a simple value or a simple directory. The
- // token still points to the name (or closing paren).
- //
- if (lv.size () > 1)
- fail (loc) << "concatenating " << what << " contains multiple "
- << "values";
-
- const name& n (lv[0]);
-
- if (n.qualified ())
- fail (loc) << "concatenating " << what << " contains project name";
-
- if (n.typed ())
- fail (loc) << "concatenating " << what << " contains type";
-
- if (!n.dir.empty ())
- {
- if (!n.value.empty ())
- fail (loc) << "concatenating " << what << " contains directory";
-
- concat += n.dir.string ();
- }
- else
- concat += n.value;
- }
- else
- {
- // Copy the names from the variable into the resulting name list
- // while doing sensible things with the types and directories.
- //
- for (const name& n: lv)
- {
- const string* pp1 (pp);
- const dir_path* dp1 (dp);
- const string* tp1 (tp);
-
- if (n.proj != 0)
- {
- if (pp == nullptr)
- pp1 = n.proj;
- else
- fail (loc) << "nested project name " << *n.proj << " in "
- << what;
- }
-
- dir_path d1;
- if (!n.dir.empty ())
- {
- if (dp != nullptr)
- {
- if (n.dir.absolute ())
- fail (loc) << "nested absolute directory " << n.dir
- << " in " << what;
-
- d1 = *dp / n.dir;
- dp1 = &d1;
- }
- else
- dp1 = &n.dir;
- }
-
- if (!n.type.empty ())
- {
- if (tp == nullptr)
- tp1 = &n.type;
- else
- fail (loc) << "nested type name " << n.type << " in " << what;
- }
-
- // If we are a second half of a pair.
- //
- if (pair != 0)
- {
- // Check that there are no nested pairs.
- //
- if (n.pair != '\0')
- fail (loc) << "nested pair in " << what;
-
- // And add another first half unless this is the first instance.
- //
- if (pair != ns.size ())
- ns.push_back (ns[pair - 1]);
- }
-
- ns.emplace_back (pp1,
- (dp1 != nullptr ? *dp1 : dir_path ()),
- (tp1 != nullptr ? *tp1 : string ()),
- n.value);
-
- ns.back ().pair = n.pair;
- }
-
- count = lv.size ();
- }
-
- continue;
- }
-
- // Untyped name group without a directory prefix, e.g., '{foo bar}'.
- //
- if (tt == type::lcbrace)
- {
- count = names_trailer (t, tt, ns, pair, pp, dp, tp);
- tt = peek ();
- continue;
- }
-
- // A pair separator (only in the pairs mode).
- //
- if (tt == type::pair_separator)
- {
- if (pair != 0)
- fail (t) << "nested pair on the right hand side of a pair";
-
- if (count > 1)
- fail (t) << "multiple names on the left hand side of a pair";
-
- if (count == 0)
- {
- // Empty LHS, (e.g., {=y}), create an empty name.
- //
- ns.emplace_back (pp,
- (dp != nullptr ? *dp : dir_path ()),
- (tp != nullptr ? *tp : string ()),
- string ());
- count = 1;
- }
-
- ns.back ().pair = t.pair;
- tt = peek ();
- continue;
- }
-
- if (!first)
- break;
-
- if (tt == type::rcbrace) // Empty name, e.g., dir{}.
- {
- // If we are a second half of a pair, add another first half
- // unless this is the first instance.
- //
- if (pair != 0 && pair != ns.size ())
- ns.push_back (ns[pair - 1]);
-
- ns.emplace_back (pp,
- (dp != nullptr ? *dp : dir_path ()),
- (tp != nullptr ? *tp : string ()),
- string ());
- break;
- }
- else
- // Our caller expected this to be a name.
- //
- fail (t) << "expected name instead of " << t;
- }
-
- // Handle the empty RHS in a pair, (e.g., {y=}).
- //
- if (!ns.empty () && ns.back ().pair != '\0')
- {
- ns.emplace_back (pp,
- (dp != nullptr ? *dp : dir_path ()),
- (tp != nullptr ? *tp : string ()),
- string ());
- }
- }
-
- void parser::
- skip_line (token& t, type& tt)
- {
- for (; tt != type::newline && tt != type::eos; next (t, tt)) ;
- }
-
- void parser::
- skip_block (token& t, type& tt)
- {
- // Skip until } or eos, keeping track of the {}-balance.
- //
- for (size_t b (0); tt != type::eos; )
- {
- if (tt == type::lcbrace || tt == type::rcbrace)
- {
- type ptt (peek ());
- if (ptt == type::newline || ptt == type::eos) // Block { or }.
- {
- if (tt == type::lcbrace)
- ++b;
- else
- {
- if (b == 0)
- break;
-
- --b;
- }
- }
- }
-
- skip_line (t, tt);
-
- if (tt != type::eos)
- next (t, tt);
- }
- }
-
- bool parser::
- keyword (token& t)
- {
- assert (replay_ == replay::stop); // Can't be used in a replay.
- assert (t.type == type::name);
-
- // The goal here is to allow using keywords as variable names and
- // target types without imposing ugly restrictions/decorators on
- // keywords (e.g., '.using' or 'USING'). A name is considered a
- // potential keyword if:
- //
- // - it is not quoted [so a keyword can always be escaped] and
- // - next token is '\n' (or eos) or '(' [so if(...) will work] or
- // - next token is separated and is not '=', '=+', or '+=' [which
- // means a "directive trailer" can never start with one of them].
- //
- // See tests/keyword.
- //
- if (!t.quoted)
- {
- // We cannot peek at the whole token here since it might have to be
- // lexed in a different mode. So peek at its first character.
- //
- pair<char, bool> p (lexer_->peek_char ());
- char c (p.first);
-
- return c == '\n' || c == '\0' || c == '(' ||
- (p.second && c != '=' && c != '+');
- }
-
- return false;
- }
-
- // Buildspec parsing.
- //
-
- // Here is the problem: we "overload" '(' and ')' to mean operation
- // application rather than the eval context. At the same time we want
- // to use names() to parse names, get variable expansion/function calls,
- // quoting, etc. We just need to disable the eval context. The way this
- // is done has two parts: Firstly, we parse names in chunks and detect
- // and handle the opening paren. In other words, a buildspec like
- // 'clean (./)' is "chunked" as 'clean', '(', etc. While this is fairly
- // straightforward, there is one snag: concatenating eval contexts, as
- // in 'clean(./)'. Normally, this will be treated as a single chunk and
- // we don't want that. So here comes the trick (or hack, if you like):
- // we will make every opening paren token "separated" (i.e., as if it
- // was proceeded by a space). This will disable concatenating eval. In
- // fact, we will even go a step further and only do this if we are in
- // the original pairs mode. This will allow us to still use eval
- // contexts in buildspec, provided that we quote it: '"cle(an)"'. Note
- // also that function calls still work as usual: '$filter (clean test)'.
- // To disable a function call and make it instead a var that is expanded
- // into operation name(s), we can use quoting: '"$ops"(./)'.
- //
- static void
- paren_processor (token& t, const lexer& l)
- {
- if (t.type == type::lparen && l.mode () == lexer_mode::pairs)
- t.separated = true;
- }
-
- buildspec parser::
- parse_buildspec (istream& is, const std::string& name)
- {
- path_ = &name; // Note: caller pools.
-
- lexer l (is, name, &paren_processor);
- lexer_ = &l;
- target_ = nullptr;
- scope_ = root_ = global_scope;
-
- // Turn on pairs recognition with '@' as the pair separator (e.g.,
- // src_root/@out_root/exe{foo bar}).
- //
- 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 != '\0' || !n.simple () || n.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, type& tt, type tt_end)
- {
- buildspec bs;
-
- while (tt != tt_end)
- {
- // We always start with one or more names. Eval context
- // (lparen) only allowed if quoted.
- //
- if (tt != type::name &&
- tt != type::lcbrace && // Untyped name group: '{foo ...'
- tt != type::dollar && // Variable expansion: '$foo ...'
- !(tt == type::lparen && mode () == lexer_mode::quoted) &&
- tt != type::pair_separator) // Empty pair LHS: '@foo ...'
- fail (t) << "operation or target expected instead of " << t;
-
- const location l (get_location (t, &path_)); // Start of names.
-
- // This call will parse the next chunk of output and produce
- // zero or more names.
- //
- names_type ns (names (t, tt, true));
-
- // What these names mean depends on what's next. If it is an
- // opening paren, then they are operation/meta-operation names.
- // Otherwise they are targets.
- //
- if (tt == type::lparen) // Peeked into by names().
- {
- if (ns.empty ())
- fail (t) << "operation name expected before '('";
-
- for (const name& n: ns)
- if (!opname (n))
- fail (l) << "operation name expected instead of '" << n << "'";
-
- // Inside '(' and ')' we have another, nested, buildspec.
- //
- next (t, tt);
- const 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& nms: nbs)
- {
- // We definitely shouldn't have any meta-operations.
- //
- if (!nms.name.empty ())
- fail (l) << "nested meta-operation " << nms.name;
-
- if (!meta)
- {
- // If we have any operations in the nested spec, then this
- // mean that our names are meta-operation names.
- //
- for (const opspec& nos: nms)
- {
- if (!nos.name.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);
- const metaopspec& nmo (nbs.back ());
-
- if (meta)
- {
- for (name& n: ns)
- {
- bs.push_back (nmo);
- bs.back ().name = move (n.value);
- }
- }
- else
- {
- // Since we are not a meta-operation, the nested buildspec
- // should be just a bunch of targets.
- //
- assert (nmo.size () == 1);
- const opspec& nos (nmo.back ());
-
- if (bs.empty () || !bs.back ().name.empty ())
- bs.push_back (metaopspec ()); // Empty (default) meta operation.
-
- for (name& n: ns)
- {
- bs.back ().push_back (nos);
- bs.back ().back ().name = move (n.value);
- }
- }
-
- next (t, tt); // Done with '('.
- }
- else if (!ns.empty ())
- {
- // Group all the targets into a single operation. In other
- // words, 'foo bar' is equivalent to 'update(foo bar)'.
- //
- if (bs.empty () || !bs.back ().name.empty ())
- bs.push_back (metaopspec ()); // Empty (default) meta operation.
-
- metaopspec& ms (bs.back ());
-
- for (auto i (ns.begin ()), e (ns.end ()); i != e; ++i)
- {
- // @@ We may actually want to support this at some point.
- //
- if (i->qualified ())
- fail (l) << "target name expected instead of " << *i;
-
- if (opname (*i))
- ms.push_back (opspec (move (i->value)));
- else
- {
- // Do we have the src_base?
- //
- dir_path src_base;
- if (i->pair != '\0')
- {
- if (i->typed ())
- fail (l) << "expected target src_base instead of " << *i;
-
- src_base = move (i->dir);
-
- if (!i->value.empty ())
- src_base /= dir_path (move (i->value));
-
- ++i;
- assert (i != e); // Got to have the second half of the pair.
- }
-
- if (ms.empty () || !ms.back ().name.empty ())
- ms.push_back (opspec ()); // Empty (default) operation.
-
- opspec& os (ms.back ());
- os.emplace_back (move (src_base), move (*i));
- }
- }
- }
- }
-
- return bs;
- }
-
- void parser::
- switch_scope (const dir_path& p)
- {
- tracer trace ("parser::switch_scope", &path_);
-
- // First, enter the scope into the map and see if it is in any
- // project. If it is not, then there is nothing else to do.
- //
- auto i (scopes.insert (p, nullptr, true, false));
- scope_ = i->second;
- scope* rs (scope_->root_scope ());
-
- if (rs == nullptr)
- return;
-
- // Path p can be src_base or out_base. Figure out which one it is.
- //
- dir_path out_base (p.sub (rs->out_path ()) ? p : src_out (p, *rs));
-
- // Create and bootstrap root scope(s) of subproject(s) that this
- // scope may belong to. If any were created, load them. Note that
- // we need to do this before figuring out src_base since we may
- // switch the root project (and src_root with it).
- //
- {
- scope* nrs (&create_bootstrap_inner (*rs, out_base));
-
- if (rs != nrs)
- {
- load_root_pre (*nrs); // Load outer roots recursively.
- rs = nrs;
- }
- }
-
- // Switch to the new root scope.
- //
- if (rs != root_)
- {
- level5 ([&]{trace << "switching to root scope " << rs->out_path ();});
- root_ = rs;
- }
-
- // Now we can figure out src_base and finish setting the scope.
- //
- dir_path src_base (src_out (out_base, *rs));
- setup_base (i, move (out_base), move (src_base));
- }
-
- void parser::
- process_default_target (token& t)
- {
- tracer trace ("parser::process_default_target", &path_);
-
- // The logic is as follows: if we have an explicit current directory
- // target, then that's the default target. Otherwise, we take the
- // first target and use it as a prerequisite to create an implicit
- // current directory target, effectively making it the default
- // target via an alias. If there are no targets in this buildfile,
- // then we don't do anything.
- //
- if (default_target_ == nullptr || // No targets in this buildfile.
- targets.find (dir::static_type, // Explicit current dir target.
- scope_->out_path (),
- "",
- nullptr,
- trace) != targets.end ())
- return;
-
- target& dt (*default_target_);
-
- level5 ([&]{trace (t) << "creating current directory alias for " << dt;});
-
- target& ct (
- targets.insert (
- dir::static_type, scope_->out_path (), "", nullptr, trace).first);
-
- prerequisite& p (
- scope_->prerequisites.insert (
- nullptr,
- dt.type (),
- dt.dir,
- dt.name,
- dt.ext,
- *scope_, // Doesn't matter which scope since dir is absolute.
- trace).first);
-
- p.target = &dt;
- ct.prerequisites.emplace_back (p);
- }
-
- void parser::
- enter_buildfile (const path& p)
- {
- tracer trace ("parser::enter_buildfile", &path_);
-
- const char* e (p.extension ());
- targets.insert<buildfile> (
- p.directory (),
- p.leaf ().base ().string (),
- &extension_pool.find (e == nullptr ? "" : e), // Always specified.
- trace);
- }
-
- type parser::
- next (token& t, type& tt)
- {
- if (peeked_)
- {
- t = move (peek_);
- peeked_ = false;
- }
- else
- t = (replay_ == replay::play ? replay_next () : lexer_->next ());
-
- if (replay_ == replay::save)
- replay_data_.push_back (t);
-
- tt = t.type;
- return tt;
- }
-
- type parser::
- peek ()
- {
- if (!peeked_)
- {
- peek_ = (replay_ == replay::play ? replay_next () : lexer_->next ());
- peeked_ = true;
- }
-
- return peek_.type;
- }
-
- static location
- get_location (const token& t, const void* data)
- {
- assert (data != nullptr);
- const string& p (**static_cast<const string* const*> (data));
- return location (p.c_str (), t.line, t.column);
- }
-}