aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-05-09 14:12:45 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2019-05-10 08:01:33 +0200
commit2b1272f1c94ab1dbaf806af9c02ef866267ffed7 (patch)
treee7ad99fbb36367a0883eadaade94c41c9894fced
parent1b0efce7791d4d61aa57038edb30fb823ed48e21 (diff)
Generalize target/prerequisite var block, initial ad hoc target work
Target/prerequisite-specific variable blocks can now be present even if there are prerequisites. For example, now instead of: exe{foo}: cxx{foo} exe{foo}: cc.loptions += ... Or: exe{foo}: cxx{foo} exe{foo}: { cc.loptions += ... cc.libs += ... } We can write: exe{foo}: cxx{foo} { cc.loptions += ... cc.libs += ... } This also works with dependency chains in which case the block applies to the set of prerequisites (note: not targets) before the last ':'. For example: ./: exe{foo}: libue{foo}: cxx{foo} { bin.whole = false # Applies to the libue{foo} prerequisite. }
-rw-r--r--build2/algorithm.hxx2
-rw-r--r--build2/algorithm.ixx2
-rw-r--r--build2/lexer.cxx19
-rw-r--r--build2/lexer.hxx8
-rw-r--r--build2/parser.cxx789
-rw-r--r--build2/parser.hxx46
-rw-r--r--build2/token.cxx3
-rw-r--r--build2/token.hxx3
8 files changed, 565 insertions, 307 deletions
diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx
index c7839d1..f1ac135 100644
--- a/build2/algorithm.hxx
+++ b/build2/algorithm.hxx
@@ -184,6 +184,8 @@ namespace build2
// existing member of this target type is the same. Return the locked member
// target.
//
+ // @@ Maybe the same type and name?
+ //
target_lock
add_adhoc_member (action,
target&,
diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx
index b6d3ccb..0b3a23e 100644
--- a/build2/algorithm.ixx
+++ b/build2/algorithm.ixx
@@ -380,7 +380,7 @@ namespace build2
// (which are not executed). Plus, group action means real recipe is in
// the group so this also feels right conceptually.
//
- // We also avoid increment this count twice for the same target if we
+ // We also avoid incrementing this count twice for the same target if we
// have both the inner and outer operations. In our model the outer
// operation is either noop or it must delegate to the inner. While it's
// possible the inner is noop while the outer is not, it is not very
diff --git a/build2/lexer.cxx b/build2/lexer.cxx
index 66f50b0..8287640 100644
--- a/build2/lexer.cxx
+++ b/build2/lexer.cxx
@@ -39,8 +39,8 @@ namespace build2
{
case lexer_mode::normal:
{
- s1 = ":=+ $(){}[]#\t\n";
- s2 = " = ";
+ s1 = ":<>=+ $(){}[]#\t\n";
+ s2 = " = ";
break;
}
case lexer_mode::value:
@@ -205,10 +205,25 @@ namespace build2
}
}
+ // The following characters are special in the normal mode.
+ //
+ if (m == lexer_mode::normal)
+ {
+ // NOTE: remember to update mode() if adding new special characters.
+ //
+ switch (c)
+ {
+ case '<': return make_token (type::labrace);
+ case '>': return make_token (type::rabrace);
+ }
+ }
+
// The following characters are special in the buildspec mode.
//
if (m == lexer_mode::buildspec)
{
+ // NOTE: remember to update mode() if adding new special characters.
+ //
switch (c)
{
case ',': return make_token (type::comma);
diff --git a/build2/lexer.hxx b/build2/lexer.hxx
index 45d69d9..b71167a 100644
--- a/build2/lexer.hxx
+++ b/build2/lexer.hxx
@@ -23,7 +23,7 @@ namespace build2
// restrict certain character (e.g., '/') from appearing in the name. The
// attribute mode is like value except it doesn't treat '{' and '}' as
// special (so we cannot have name groups in attributes). The eval mode is
- // used in the evaluation context. Quoted are internal modes and should not
+ // used in the evaluation context. Quoted modes are internal and should not
// be set explicitly.
//
// Note that the normal, value, and eval modes split words separated by the
@@ -124,9 +124,9 @@ namespace build2
lexer_mode mode;
char sep_pair;
- bool sep_space; // Are whitespaces separators (see skip_spaces())?
- bool sep_newline; // Is newline special (see skip_spaces())?
- bool quotes; // Recognize quoted fragments.
+ bool sep_space; // Are whitespaces separators (see skip_spaces())?
+ bool sep_newline; // Is newline special (see skip_spaces())?
+ bool quotes; // Recognize quoted fragments.
const char* escapes; // Effective escape sequences to recognize.
diff --git a/build2/parser.cxx b/build2/parser.cxx
index 997a1ac..06999d7 100644
--- a/build2/parser.cxx
+++ b/build2/parser.cxx
@@ -116,18 +116,31 @@ namespace build2
tracer& tr)
: p_ (&p), t_ (p.target_)
{
- // Find or insert.
- //
+ p.target_ = &insert_target (p, move (n), move (o), implied, loc, tr);
+ }
+
+ // Find or insert.
+ //
+ static target&
+ insert_target (parser& p,
+ name&& n, // If n.pair, then o is out dir.
+ name&& o,
+ bool implied,
+ const location& loc,
+ tracer& tr)
+ {
auto r (process_target (p, n, o, loc));
- p.target_ = &targets.insert (*r.first, // target type
- move (n.dir),
- move (o.dir),
- move (n.value),
- move (r.second), // extension
- implied,
- tr).first;
+ return targets.insert (*r.first, // target type
+ move (n.dir),
+ move (o.dir),
+ move (n.value),
+ move (r.second), // extension
+ implied,
+ tr).first;
}
+ // Only find.
+ //
static const target*
find_target (parser& p,
name& n, // If n.pair, then o is out dir.
@@ -161,7 +174,7 @@ namespace build2
assert (n.pair == '@');
if (!o.directory ())
- p.fail (loc) << "expected directory after @";
+ p.fail (loc) << "expected directory after '@'";
}
dir_path& d (n.dir);
@@ -309,6 +322,14 @@ namespace build2
return make_pair (move (lhs), move (t));
}
+ // Test if a string is a wildcard pattern.
+ //
+ static inline bool
+ pattern (const string& s)
+ {
+ return s.find_first_of ("*?") != string::npos;
+ };
+
bool parser::
parse_clause (token& t, type& tt, bool one)
{
@@ -317,7 +338,7 @@ namespace build2
// 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
+ // for if-else blocks, directory scopes, etc., that assume the '}' token
// they see is on the new line.
//
bool parsed (false);
@@ -329,13 +350,9 @@ namespace build2
assert (attributes_.empty ());
auto at (attributes_push (t, tt));
- // We always start with one or more names.
+ // We should always start with one or more names.
//
- if (tt != type::word &&
- tt != type::lcbrace && // Untyped name group: '{foo ...'
- tt != type::dollar && // Variable expansion: '$foo ...'
- tt != type::lparen && // Eval context: '(foo) ...'
- tt != type::colon) // Empty name: ': ...'
+ if (!start_names (tt))
{
// Something else. Let our caller handle that.
//
@@ -457,12 +474,49 @@ namespace build2
continue;
}
- // Check if a string is a wildcard pattern.
+ // Handle ad hoc target group specification.
+ //
+ // We keep an "optional" (empty) vector of names parallel to ns.
//
- auto pattern = [] (const string& s)
+ adhoc_names ans;
+ if (tt == type::labrace)
{
- return s.find_first_of ("*?") != string::npos;
- };
+ if (ns.empty ())
+ fail (t) << "expected target before '<'";
+
+ while (tt == type::labrace)
+ {
+ ans.resize (ns.size ()); // Catch up with the target names vector.
+
+ // Parse ad hoc target names inside < >.
+ //
+ next (t, tt);
+
+ auto at (attributes_push (t, tt));
+
+ if (at.first)
+ fail (at.second) << "attributes before ad hoc target";
+ else
+ attributes_pop ();
+
+ ans.back ().loc = get_location (t);
+ parse_names (t, tt, ans.back ().ns, pattern_mode::ignore);
+
+ if (tt != type::rabrace)
+ fail (t) << "expected '>' instead of " << t;
+
+ // Parse the next chunk of target names after >, if any.
+ //
+ next (t, tt);
+ if (start_names (tt))
+ parse_names (t, tt, ns, pattern_mode::ignore);
+ }
+
+ if (tt != type::colon)
+ fail (t) << "expected ':' instead of " << t;
+
+ ans.resize (ns.size ()); // Final chunk.
+ }
// If we have a colon, then this is target-related.
//
@@ -472,28 +526,33 @@ namespace build2
// means empty list.
//
if (ns.empty ())
- fail (t) << "expected target before :";
+ fail (t) << "expected target before ':'";
+
+ if (at.first)
+ fail (at.second) << "attributes before target";
+ else
+ attributes_pop ();
// Call the specified parsing function (either variable or block) for
- // each target. We handle multiple targets by replaying the tokens.
+ // each target. We handle multiple targets by replaying the tokens
// since the value/block may contain variable expansions that would be
// sensitive to the target context in which they are evaluated. The
// function signature is:
//
// void (token& t, type& tt, const target_type* type, string pat)
//
- auto for_each = [this, &trace, &pattern,
+ auto for_each = [this, &trace,
&t, &tt,
- &ns, &nloc] (auto&& f)
+ &ns, &nloc, &ans] (auto&& f)
{
// Note: watch out for an out-qualified single target (two names).
//
replay_guard rg (*this,
ns.size () > 2 || (ns.size () == 2 && !ns[0].pair));
- for (auto i (ns.begin ()), e (ns.end ()); i != e; )
+ for (size_t i (0), e (ns.size ()); i != e; )
{
- name& n (*i);
+ name& n (ns[i]);
if (n.qualified ())
fail (nloc) << "project name in target " << n;
@@ -504,8 +563,10 @@ namespace build2
if (pattern (n.value))
{
if (n.pair)
- fail (nloc) << "out-qualified target type/pattern-specific "
- << "variable";
+ fail (nloc) << "out-qualified target type/pattern";
+
+ if (!ans.empty () && !ans[i].ns.empty ())
+ fail (ans[i].loc) << "ad hoc member in target type/pattern";
// If we have the directory, then it is the scope.
//
@@ -532,7 +593,7 @@ namespace build2
}
else
{
- name o (n.pair ? move (*++i) : name ());
+ name o (n.pair ? move (ns[++i]) : name ());
enter_target tg (*this,
move (n),
move (o),
@@ -540,6 +601,15 @@ namespace build2
nloc,
trace);
+ // Enter ad hoc members.
+ //
+ if (!ans.empty ())
+ {
+ // Note: index after the pair increment.
+ //
+ enter_adhoc_members (move (ans[i]), true /* implied */);
+ }
+
f (t, tt, nullptr, string ());
}
@@ -548,24 +618,16 @@ namespace build2
}
};
- next (t, tt);
- if (tt == type::newline)
+ if (next (t, tt) == type::newline)
{
// See if this is a target block.
//
- if (peek () == type::lcbrace)
+ // Note that we cannot just let parse_dependency() handle this case
+ // because we can have (a mixture of) target type/patterns.
+ //
+ if (next (t, tt) == type::lcbrace && peek () == type::newline)
{
- next (t, tt);
-
- // Should be on its own line.
- //
- if (next (t, tt) != type::newline)
- fail (t) << "expected newline after {";
-
- if (at.first)
- fail (at.second) << "attributes before target block";
- else
- attributes_pop ();
+ next (t, tt); // Newline.
// Parse the block for each target.
//
@@ -577,114 +639,92 @@ namespace build2
parse_variable_block (t, tt, type, move (pat));
if (tt != type::rcbrace)
- fail (t) << "expected } instead of " << t;
+ fail (t) << "expected '}' instead of " << t;
});
- // Should be on its own line.
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ }
+ else
+ {
+ // If not followed by a block, then it's a target without any
+ // prerequisites. We, however, cannot just fall through to the
+ // parse_dependency() call because we have already seen the next
+ // token.
//
- if (next (t, tt) == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline after }";
-
- continue;
+ // Note also that we treat this as an explicit dependency
+ // declaration (i.e., not implied).
+ //
+ enter_targets (move (ns), nloc, move (ans), 0);
}
- // If this is not a block, then it is a target without any
- // prerequisites. Fall through.
+ continue;
}
// Target-specific variable assignment or dependency declaration,
// including a dependency chain and/or prerequisite-specific variable
// assignment.
//
- if (at.first)
- fail (at.second) << "attributes before target";
- else
- attributes_pop ();
-
auto at (attributes_push (t, tt));
- if (tt == type::word ||
- tt == type::lcbrace ||
- tt == type::dollar ||
- tt == type::lparen ||
- tt == type::newline ||
- tt == type::eos)
- {
- // @@ PAT: currently we pattern-expand target-specific vars.
- //
- const location ploc (get_location (t));
- names pns (tt != type::newline && tt != type::eos
- ? parse_names (t, tt, pattern_mode::expand)
- : names ());
+ if (!start_names (tt))
+ fail (t) << "unexpected " << t;
- // Target-specific variable assignment.
- //
- if (tt == type::assign || tt == type::prepend || tt == type::append)
- {
- type akind (tt);
- const location aloc (get_location (t));
+ // @@ PAT: currently we pattern-expand target-specific vars.
+ //
+ const location ploc (get_location (t));
+ names pns (parse_names (t, tt, pattern_mode::expand));
- const variable& var (parse_variable_name (move (pns), ploc));
- apply_variable_attributes (var);
+ // Target-specific variable assignment.
+ //
+ if (tt == type::assign || tt == type::prepend || tt == type::append)
+ {
+ type akind (tt);
+ const location aloc (get_location (t));
- if (var.visibility > variable_visibility::target)
- {
- fail (nloc) << "variable " << var << " has " << var.visibility
- << " visibility but is assigned on a target";
- }
+ const variable& var (parse_variable_name (move (pns), ploc));
+ apply_variable_attributes (var);
- // Parse the assignment for each target.
- //
- for_each ([this, &var, akind, &aloc] (token& t, type& tt,
- const target_type* type,
- string pat)
- {
- if (type == nullptr)
- parse_variable (t, tt, var, akind);
- else
- parse_type_pattern_variable (t, tt,
- *type, move (pat),
- var, akind, aloc);
- });
- }
- // Dependency declaration potentially followed by a chain and/or
- // a prerequisite-specific variable assignment.
- //
- else
+ if (var.visibility > variable_visibility::target)
{
- if (at.first)
- fail (at.second) << "attributes before prerequisites";
- else
- attributes_pop ();
-
- // Make sure none of our targets are patterns (maybe we will allow
- // quoting later).
- //
- for (const name& n: ns)
- {
- if (pattern (n.value))
- fail (nloc) << "pattern in target " << n;
- }
-
- parse_dependency (t, tt, move (ns), nloc, move (pns), ploc);
+ fail (nloc) << "variable " << var << " has " << var.visibility
+ << " visibility but is assigned on a target";
}
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
-
- continue;
+ // Parse the assignment for each target.
+ //
+ for_each ([this, &var, akind, &aloc] (token& t, type& tt,
+ const target_type* type,
+ string pat)
+ {
+ if (type == nullptr)
+ parse_variable (t, tt, var, akind);
+ else
+ parse_type_pattern_variable (t, tt,
+ *type, move (pat),
+ var, akind, aloc);
+ });
+
+ next_after_newline (t, tt);
}
+ // Dependency declaration potentially followed by a chain and/or a
+ // prerequisite-specific variable assignment/block.
+ //
else
- fail (t) << "unexpected " << t;
+ {
+ if (at.first)
+ fail (at.second) << "attributes before prerequisites";
+ else
+ attributes_pop ();
- if (tt == type::eos)
- continue;
+ bool r (parse_dependency (t, tt,
+ move (ns), nloc,
+ move (ans),
+ move (pns), ploc));
+ assert (r); // Block must have been claimed.
+ }
- fail (t) << "expected newline instead of " << t;
+ continue;
}
// Variable assignment.
@@ -765,11 +805,7 @@ namespace build2
parse_variable (t, tt, var, tt);
}
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
-
+ next_after_newline (t, tt);
continue;
}
@@ -778,59 +814,50 @@ namespace build2
// See if this is a directory scope.
//
- if (tt == type::newline && peek () == type::lcbrace)
+ // Note: must be last since we are going to get the next token.
+ //
+ if (ns.size () == 1 && ns[0].directory () && tt == type::newline)
{
- if (ns.size () != 1)
- fail (nloc) << "multiple scope directories";
-
- name& n (ns[0]);
-
- if (!n.directory ())
- fail (nloc) << "expected scope directory";
+ token ot (t);
- dir_path& d (n.dir);
+ if (next (t, tt) == type::lcbrace && peek () == type::newline)
+ {
+ dir_path&& d (move (ns[0].dir));
- // Make sure not a pattern (see also the target and directory cases
- // above).
- //
- if (pattern (d.string ()))
- fail (nloc) << "pattern in directory " << d.representation ();
+ // Make sure not a pattern (see also the target and directory cases
+ // above).
+ //
+ if (pattern (d.string ()))
+ fail (nloc) << "pattern in directory " << d.representation ();
- next (t, tt);
+ next (t, tt); // Newline.
+ next (t, tt); // First token inside the block.
- // Should be on its own line.
- //
- if (next (t, tt) != type::newline)
- fail (t) << "expected newline after {";
+ if (at.first)
+ fail (at.second) << "attributes before scope directory";
+ else
+ attributes_pop ();
- next (t, tt);
+ // Can contain anything that a top level can.
+ //
+ {
+ enter_scope sg (*this, move (d));
+ parse_clause (t, tt);
+ }
- if (at.first)
- fail (at.second) << "attributes before scope directory";
- else
- attributes_pop ();
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t;
- // Can contain anything that a top level can.
- //
- {
- enter_scope sg (*this, move (d));
- parse_clause (t, tt);
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ continue;
}
- 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;
+ t = ot;
+ // Fall through to fail.
}
- fail (t) << "unexpected " << t;
+ fail (t) << "unexpected " << t << " after " << ns;
}
return parsed;
@@ -891,40 +918,158 @@ namespace build2
}
void parser::
- parse_dependency (token& t, token_type& tt,
- names&& tns, const location& tloc, // Target names.
- names&& pns, const location& ploc) // Prereq names.
+ enter_adhoc_members (adhoc_names_loc&& ans, bool implied)
{
- // Parse a dependency chain and/or a prerequisite-specific variable
- // assignment.
- //
- // enter: colon (anything else is not handled)
- // leave: newline
- //
- tracer trace ("parser::parse_dependency", &path_);
+ tracer trace ("parser::enter_adhoc_members", &path_);
+
+ names& ns (ans.ns);
+ const location& loc (ans.loc);
+
+ for (size_t i (0); i != ns.size (); ++i)
+ {
+ name&& n (move (ns[i]));
+ name&& o (n.pair ? move (ns[++i]) : name ());
+
+ if (n.qualified ())
+ fail (loc) << "project name in target " << n;
+
+ target& at (
+ enter_target::insert_target (*this,
+ move (n), move (o),
+ implied,
+ loc, trace));
+
+ if (target_ == &at)
+ fail (loc) << "ad hoc group member " << at << " is primary target";
+
+ // Add as an ad hoc member at the end of the chain skipping duplicates.
+ //
+ {
+ const_ptr<target>* mp (&target_->member);
+ for (; *mp != nullptr; mp = &(*mp)->member)
+ {
+ if (*mp == &at)
+ {
+ mp = nullptr;
+ break;
+ }
+ }
- // First enter all the targets (normally we will have just one).
+ if (mp != nullptr)
+ {
+ *mp = &at;
+ at.group = target_;
+ }
+ else
+ continue; // Duplicate.
+ }
+
+ if (file* ft = at.is_a<file> ())
+ ft->derive_path ();
+
+ // Pre-match this target. Feels fuzzy/hacky.
+ //
+ // See match_recipe() and set_recipe() that it calls for the
+ // approximate semantics we want to achieve.
+ //
+ // @@ Can such a target be used as a prerequisite? Feels like
+ // will require a "permanenly applied" task_count value? Maybe
+ // special "adhoc" value?
+ //
+ {
+ auto& i (at.state.data[0]); // inner opstate
+ auto& o (at.state.data[1]); // outer opstate
+
+ i.rule = o.rule = nullptr;
+ i.recipe = o.recipe = group_recipe;
+ i.state = o.state = target_state::group;
+ }
+ }
+ }
+
+ small_vector<reference_wrapper<target>, 1> parser::
+ enter_targets (names&& tns, const location& tloc, // Target names.
+ adhoc_names&& ans, // Ad hoc target names.
+ size_t prereq_size)
+ {
+ // Enter all the targets (normally we will have just one) and their ad hoc
+ // groups.
//
+ tracer trace ("parser::enter_targets", &path_);
+
small_vector<reference_wrapper<target>, 1> tgs;
- for (auto i (tns.begin ()), e (tns.end ()); i != e; ++i)
+ for (size_t i (0); i != tns.size (); ++i)
{
- name& n (*i);
+ name&& n (move (tns[i]));
+ name&& o (n.pair ? move (tns[++i]) : name ());
if (n.qualified ())
fail (tloc) << "project name in target " << n;
- name o (n.pair ? move (*++i) : name ());
- enter_target tg (*this, move (n), move (o), false, tloc, trace);
+ // Make sure none of our targets are patterns (maybe we will allow
+ // quoting later).
+ //
+ if (pattern (n.value))
+ fail (tloc) << "pattern in target " << n;
+
+ enter_target tg (*this,
+ move (n), move (o),
+ false /* implied */,
+ tloc, trace);
+
+ // Enter ad hoc members.
+ //
+ if (!ans.empty ())
+ {
+ // Note: index after the pair increment.
+ //
+ enter_adhoc_members (move (ans[i]), false /* implied */);
+ }
if (default_target_ == nullptr)
default_target_ = target_;
target_->prerequisites_state_.store (2, memory_order_relaxed);
- target_->prerequisites_.reserve (pns.size ());
+ target_->prerequisites_.reserve (prereq_size);
tgs.push_back (*target_);
}
+ return tgs;
+ }
+
+ bool parser::
+ parse_dependency (token& t, token_type& tt,
+ names&& tns, const location& tloc, // Target names.
+ adhoc_names&& ans, // Ad hoc target names.
+ names&& pns, const location& ploc, // Prereq names.
+ bool chain)
+ {
+ // Parse a dependency chain and/or a target/prerequisite-specific variable
+ // assignment/block. Return true if the following block (if any) has been
+ // "claimed" (the block "belongs" to targets/prerequisites before the last
+ // colon).
+ //
+ // enter: colon (anything else is not handled)
+ // leave: - first token on the next line if returning true
+ // - newline (presumably, must be verified) if returning false
+ //
+ // Note that top-level call (with chain == false) is expected to always
+ // return true.
+ //
+ // This dual-return "complication" is necessary to handle non-block cases
+ // like this:
+ //
+ // foo: bar
+ // {hxx ixx}: install = true
+ //
+ tracer trace ("parser::parse_dependency", &path_);
+
+ // First enter all the targets.
+ //
+ small_vector<reference_wrapper<target>, 1> tgs (
+ enter_targets (move (tns), tloc, move (ans), pns.size ()));
+
// Now enter each prerequisite into each target.
//
for (name& pn: pns)
@@ -974,31 +1119,32 @@ namespace build2
}
}
- // Do we have a dependency chain and/or prerequisite-specific variable
- // assignment?
- //
- if (tt != type::colon)
- return;
-
- // What should we do if there are no prerequisites (for example, because
- // of an empty wildcard result)? We can fail or we can ignore. In most
- // cases, however, this is probably an error (for example, forgetting to
- // checkout a git submodule) so let's not confuse the user and fail (one
- // can always handle the optional prerequisites case with a variable and
- // an if).
+ // Call the specified parsing function (either variable or block) for each
+ // target in tgs (for_each_t) or for the last pns.size() prerequisites of
+ // each target (for_each_p).
//
- if (pns.empty ())
- fail (ploc) << "no prerequisites in dependency chain or prerequisite-"
- << "specific variable assignment";
-
- // Call the specified parsing function (either variable or block) for the
- // last pns.size() prerequisites of each target. We handle multiple
- // targets and/or prerequisites by replaying the tokens (see the target-
- // specific case above for details). The function signature is:
+ // We handle multiple targets and/or prerequisites by replaying the tokens
+ // (see the target-specific case for details). The function signature is:
//
// void (token& t, type& tt)
//
- auto for_each = [this, &t, &tt, &tgs, &pns] (auto&& f)
+ auto for_each_t = [this, &t, &tt, &tgs] (auto&& f)
+ {
+ replay_guard rg (*this, tgs.size () > 1);
+
+ for (auto ti (tgs.begin ()), te (tgs.end ()); ti != te; )
+ {
+ target& tg (*ti);
+ enter_target tgg (*this, tg);
+
+ f (t, tt);
+
+ if (++ti != te)
+ rg.play (); // Replay.
+ }
+ };
+
+ auto for_each_p = [this, &t, &tt, &tgs, &pns] (auto&& f)
{
replay_guard rg (*this, tgs.size () > 1 || pns.size () > 1);
@@ -1023,41 +1169,55 @@ namespace build2
}
};
- next (t, tt);
- auto at (attributes_push (t, tt));
-
- // See if this is a prerequisite block.
+ // Do we have a dependency chain and/or prerequisite-specific variable
+ // assignment? If not, check for the target-specific variable block unless
+ // this is a chained call (in which case the block, if any, "belongs" to
+ // prerequisites).
//
- if (tt == type::newline && peek () == type::lcbrace)
+ if (tt != type::colon)
{
- next (t, tt);
+ if (chain)
+ return false;
- // Should be on its own line.
- //
- if (next (t, tt) != type::newline)
- fail (t) << "expected newline after {";
+ next_after_newline (t, tt); // Must be a newline then.
- if (at.first)
- fail (at.second) << "attributes before prerequisite block";
- else
- attributes_pop ();
+ if (tt == type::lcbrace && peek () == type::newline)
+ {
+ next (t, tt); // Newline.
- // Parse the block for each prerequisites of each target.
- //
- for_each ([this] (token& t, token_type& tt)
- {
- next (t, tt); // First token of first line in the block.
+ // Parse the block for each target.
+ //
+ for_each_t ([this] (token& t, token_type& tt)
+ {
+ next (t, tt); // First token inside the block.
- parse_variable_block (t, tt, nullptr, string ());
+ parse_variable_block (t, tt, nullptr, string ());
- if (tt != type::rcbrace)
- fail (t) << "expected } instead of " << t;
- });
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t;
+ });
- next (t, tt); // Presumably newline after '}'.
- return;
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ }
+
+ return true; // Claimed or isn't any.
}
+ // What should we do if there are no prerequisites (for example, because
+ // of an empty wildcard result)? We can fail or we can ignore. In most
+ // cases, however, this is probably an error (for example, forgetting to
+ // checkout a git submodule) so let's not confuse the user and fail (one
+ // can always handle the optional prerequisites case with a variable and
+ // an if).
+ //
+ if (pns.empty ())
+ fail (ploc) << "no prerequisites in dependency chain or prerequisite-"
+ << "specific variable assignment";
+
+ next (t, tt);
+ auto at (attributes_push (t, tt));
+
// @@ PAT: currently we pattern-expand prerequisite-specific vars.
//
const location loc (get_location (t));
@@ -1076,10 +1236,22 @@ namespace build2
// Parse the assignment for each prerequisites of each target.
//
- for_each ([this, &var, at] (token& t, token_type& tt)
+ for_each_p ([this, &var, at] (token& t, token_type& tt)
{
parse_variable (t, tt, var, at);
});
+
+ // Pretend that we have claimed the block to cause an error if there is
+ // one. Failed that, the following would result in a valid (target-
+ // specific) block:
+ //
+ // foo: bar: x = y
+ // {
+ // ...
+ // }
+ //
+ next_after_newline (t, tt);
+ return true;
}
//
// Dependency chain.
@@ -1097,7 +1269,46 @@ namespace build2
// that the dependency chain is equivalent to specifying each dependency
// separately.
//
- parse_dependency (t, tt, move (pns), ploc, move (ns), loc);
+ // Also note that supporting ad hoc target group specification in chains
+ // will be complicated. For example, what if prerequisites that have ad
+ // hoc targets don't end up being chained? Do we just silently drop
+ // them? Also, these are prerequsites first that happened to be reused
+ // as target names so perhaps it is the right thing not to support,
+ // conceptually.
+ //
+ if (parse_dependency (t, tt,
+ names (pns), ploc, // Note: can't move.
+ {} /* ad hoc target name */,
+ move (ns), loc,
+ true /* chain */))
+ return true;
+
+ // Claim the block (if any) for these prerequisites if it hasn't been
+ // claimed by the inner ones.
+ //
+ next_after_newline (t, tt); // Must be a newline.
+
+ if (tt == type::lcbrace && peek () == type::newline)
+ {
+ next (t, tt); // Newline.
+
+ // Parse the block for each prerequisites of each target.
+ //
+ for_each_p ([this] (token& t, token_type& tt)
+ {
+ next (t, tt); // First token inside the block.
+
+ parse_variable_block (t, tt, nullptr, string ());
+
+ if (tt != type::rcbrace)
+ fail (t) << "expected '}' instead of " << t;
+ });
+
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
+ }
+
+ return true; // Claimed or isn't any.
}
}
@@ -1199,10 +1410,7 @@ namespace build2
}
}
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
+ next_after_newline (t, tt);
}
void parser::
@@ -1251,7 +1459,7 @@ namespace build2
// This shouldn't happen but let's make sure.
//
if (root_->root_extra == nullptr)
- fail (l) << "build file naming scheme is not yet known";
+ fail (l) << "buildfile naming scheme is not yet known";
p /= root_->root_extra->buildfile_file;
}
@@ -1332,10 +1540,7 @@ namespace build2
root_ = ors;
}
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
+ next_after_newline (t, tt);
}
void parser::
@@ -1428,10 +1633,7 @@ namespace build2
if (bad)
fail (l) << "error reading " << args[0] << " output";
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
+ next_after_newline (t, tt);
}
void parser::
@@ -1591,10 +1793,7 @@ namespace build2
}
}
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
+ next_after_newline (t, tt);
}
void parser::
@@ -1640,10 +1839,7 @@ namespace build2
if (export_value_.empty ())
fail (l) << "empty value in export";
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
+ next_after_newline (t, tt);
}
void parser::
@@ -1717,10 +1913,7 @@ namespace build2
}
}
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline instead of " << t;
+ next_after_newline (t, tt);
}
void parser::
@@ -1762,10 +1955,7 @@ namespace build2
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;
+ next_after_newline (t, tt);
}
void parser::
@@ -1850,15 +2040,11 @@ namespace build2
skip_block (t, tt);
if (tt != type::rcbrace)
- fail (t) << "expected } instead of " << t << " at the end of " << k
+ 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 }";
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
}
else
{
@@ -1973,14 +2159,11 @@ namespace build2
sg.stop ();
if (tt != type::rcbrace)
- fail (t) << "expected } instead of " << t << " at the end of for-block";
-
- next (t, tt);
+ fail (t) << "expected '}' instead of " << t << " at the end of "
+ << "for-block";
- if (tt == type::newline)
- next (t, tt);
- else if (tt != type::eos)
- fail (t) << "expected newline after }";
+ next (t, tt); // Presumably newline after '}'.
+ next_after_newline (t, tt, '}'); // Should be on its own line.
}
else
{
@@ -2956,7 +3139,8 @@ namespace build2
mode (lexer_mode::attribute, '=');
next (t, tt);
- if (tt != type::rsbrace)
+ has = (tt != type::rsbrace);
+ if (has)
{
names ns (
parse_names (
@@ -3007,7 +3191,7 @@ namespace build2
if (!standalone && (tt == type::newline || tt == type::eos))
fail (t) << "standalone attributes";
- return make_pair (true, l);
+ return make_pair (has, l);
}
// Splice names from the name view into the destination name list while
@@ -3631,6 +3815,16 @@ namespace build2
return ns.size () - start;
}
+ bool parser::
+ start_names (type& tt, bool lp)
+ {
+ return (tt == type::word ||
+ tt == type::lcbrace || // Untyped name group: '{foo ...'.
+ tt == type::dollar || // Variable expansion: '$foo ...'.
+ (tt == type::lparen && lp) || // Eval context: '(foo) ...'.
+ tt == type::pair_separator); // Empty pair LHS: '@foo ...'.
+ }
+
// Slashe(s) plus '%'. Note that here we assume '/' is there since that's
// in our buildfile "syntax".
//
@@ -3842,12 +4036,7 @@ namespace build2
const token& t (peeked ());
type tt (t.type);
- return ((chunk && t.separated) ||
- (tt != type::word &&
- tt != type::dollar &&
- tt != type::lparen &&
- tt != type::lcbrace &&
- tt != type::pair_separator));
+ return ((chunk && t.separated) || !start_names (tt));
};
// Return true if the next token (which should be peeked at) won't be
@@ -4857,11 +5046,7 @@ namespace build2
// We always start with one or more names. Eval context (lparen) only
// allowed if quoted.
//
- if (tt != type::word &&
- tt != type::lcbrace && // Untyped name group: '{foo ...'
- tt != type::dollar && // Variable expansion: '$foo ...'
- !(tt == type::lparen && mode () == lexer_mode::double_quoted) &&
- tt != type::pair_separator) // Empty pair LHS: '@foo ...'
+ if (!start_names (tt, mode () == lexer_mode::double_quoted))
{
if (first)
fail (t) << "expected operation or target instead of " << t;
@@ -5270,6 +5455,22 @@ namespace build2
return tt;
}
+ inline type parser::
+ next_after_newline (token& t, type& tt, char e)
+ {
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ {
+ if (e == '\0')
+ fail (t) << "expected newline instead of " << t;
+ else
+ fail (t) << "expected newline after '" << e << "'";
+ }
+
+ return tt;
+ }
+
type parser::
peek ()
{
diff --git a/build2/parser.hxx b/build2/parser.hxx
index fb7fb4e..b4b7093 100644
--- a/build2/parser.hxx
+++ b/build2/parser.hxx
@@ -78,10 +78,28 @@ namespace build2
void
parse_variable_block (token&, token_type&, const target_type*, string);
+ // Ad hoc target names inside < ... >.
+ //
+ struct adhoc_names_loc
+ {
+ names ns;
+ location loc;
+ };
+
+ using adhoc_names = small_vector<adhoc_names_loc, 1>;
+
void
+ enter_adhoc_members (adhoc_names_loc&&, bool);
+
+ small_vector<reference_wrapper<target>, 1>
+ enter_targets (names&&, const location&, adhoc_names&&, size_t);
+
+ bool
parse_dependency (token&, token_type&,
names&&, const location&,
- names&&, const location&);
+ adhoc_names&&,
+ names&&, const location&,
+ bool = false);
void
parse_assert (token&, token_type&);
@@ -189,11 +207,11 @@ namespace build2
};
// Push a new entry into the attributes_ stack. If the next token is '['
- // parse the attribute sequence until ']' setting the 'has' flag and
- // storing the result on the stack. Then get the next token and, if
- // standalone is false, verify it is not newline/eos (i.e., there is
- // something after it). Return the indication of whether there are
- // any attributes and their location.
+ // parse the attribute sequence until ']' storing the result in the new
+ // stack entry and setting the 'has' flag (unless the attribute list is
+ // empty). Then get the next token and, if standalone is false, verify
+ // it is not newline/eos (i.e., there is something after it). Return the
+ // indication of whether there are any attributes and their location.
//
// Note that during pre-parsing nothing is pushed into the stack and
// the returned attributes object indicates there are no attributes.
@@ -258,6 +276,14 @@ namespace build2
return ns;
}
+ // Return true if this token starts a name. Or, to put it another way,
+ // calling parse_names() on this token won't fail with the "expected name
+ // instead of <this-token>" error. Only consider '(' if the second
+ // argument is true.
+ //
+ bool
+ start_names (token_type&, bool lparen = true);
+
// As above but return the result as a value, which can be typed and NULL.
//
value
@@ -422,6 +448,14 @@ namespace build2
token_type
next (token&, token_type&);
+ // If the current token is newline, then get the next token. Otherwise,
+ // fail unless the current token is eos (i.e., optional newline at the end
+ // of stream). If the after argument is not \0, use it in diagnostics as
+ // the token after which the newline was expectd.
+ //
+ token_type
+ next_after_newline (token&, token_type&, char after = '\0');
+
// Be careful with peeking and switching the lexer mode. See keyword()
// for more information.
//
diff --git a/build2/token.cxx b/build2/token.cxx
index 81b7c55..8b62b46 100644
--- a/build2/token.cxx
+++ b/build2/token.cxx
@@ -36,6 +36,9 @@ namespace build2
case token_type::lsbrace: os << q << '[' << q; break;
case token_type::rsbrace: os << q << ']' << q; break;
+ case token_type::labrace: os << q << '<' << q; break;
+ case token_type::rabrace: os << q << '>' << q; break;
+
case token_type::assign: os << q << '=' << q; break;
case token_type::prepend: os << q << "=+" << q; break;
case token_type::append: os << q << "+=" << q; break;
diff --git a/build2/token.hxx b/build2/token.hxx
index b679bf9..50d1396 100644
--- a/build2/token.hxx
+++ b/build2/token.hxx
@@ -43,6 +43,9 @@ namespace build2
lsbrace, // [
rsbrace, // ]
+ labrace, // <
+ rabrace, // >
+
assign, // =
prepend, // =+
append, // +=