diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-01-05 11:55:15 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-01-05 11:55:15 +0200 |
commit | 9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b (patch) | |
tree | d60322d4382ca5f97b676c5abe2e39524f35eab4 /build2/parser.cxx | |
parent | f159b1dac68c8714f7ba71ca168e3b695891aad9 (diff) |
Rename build directory/namespace to build2
Diffstat (limited to 'build2/parser.cxx')
-rw-r--r-- | build2/parser.cxx | 2206 |
1 files changed, 2206 insertions, 0 deletions
diff --git a/build2/parser.cxx b/build2/parser.cxx new file mode 100644 index 0000000..139d2a2 --- /dev/null +++ b/build2/parser.cxx @@ -0,0 +1,2206 @@ +// file : build2/parser.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <build2/parser> + +#include <cctype> // is{alpha alnum}() + +#include <memory> // unique_ptr +#include <fstream> +#include <utility> // move() +#include <iterator> // make_move_iterator() +#include <iostream> + +#include <build2/types> +#include <build2/utility> +#include <build2/version> + +#include <build2/scope> +#include <build2/target> +#include <build2/prerequisite> +#include <build2/variable> +#include <build2/module> +#include <build2/file> +#include <build2/diagnostics> +#include <build2/context> + +using namespace std; + +namespace build2 +{ + 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 build2::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) + { + // build2::import() will check the name, if required. + // + names_type r (build2::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. + // build2::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 > BUILD2_VERSION) + fail (l) << "build2 " << v << " required" << + info << "running build2 " << BUILD2_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); + } +} |