diff options
Diffstat (limited to 'build/parser.cxx')
-rw-r--r-- | build/parser.cxx | 2206 |
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); - } -} |