aboutsummaryrefslogtreecommitdiff
path: root/build2/parser.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-12-05 15:09:04 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-12-05 15:09:04 +0200
commit54870fb76b5f59cc2e6d69a8c7a8ef17853a0029 (patch)
tree770b01aa56348ec75f17fb834a2a7123ba9c3f73 /build2/parser.cxx
parentef7cb7ea3e6fcb21a4fcf38602b3f43f03232ace (diff)
Add comma, ternary, logical operators support in eval context
Diffstat (limited to 'build2/parser.cxx')
-rw-r--r--build2/parser.cxx489
1 files changed, 340 insertions, 149 deletions
diff --git a/build2/parser.cxx b/build2/parser.cxx
index 5d4ff0a..4126aff 100644
--- a/build2/parser.cxx
+++ b/build2/parser.cxx
@@ -1738,181 +1738,367 @@ namespace build2
}
}
- pair<value, bool> parser::
+ values parser::
parse_eval (token& t, type& tt)
{
- mode (lexer_mode::eval, '@');
+ // enter: lparen
+ // leave: rparen
+
+ mode (lexer_mode::eval, '@'); // Auto-expires at rparen.
next (t, tt);
- return tt != type::rparen
- ? make_pair (parse_eval_trailer (t, tt), true)
- : make_pair (value (names ()), false);
+
+ if (tt == type::rparen)
+ return values ();
+
+ values r (parse_eval_comma (t, tt, true));
+
+ if (tt != type::rparen)
+ fail (t) << "unexpected " << t; // E.g., stray ':'.
+
+ return r;
+ }
+
+ values parser::
+ parse_eval_comma (token& t, type& tt, bool first)
+ {
+ // enter: first token of LHS
+ // leave: next token after last RHS
+
+ // Left-associative: parse in a loop for as long as we can.
+ //
+ values r;
+ value lhs (parse_eval_ternary (t, tt, first));
+
+ if (!pre_parse_)
+ r.push_back (move (lhs));
+
+ while (tt == type::comma)
+ {
+ next (t, tt);
+ value rhs (parse_eval_ternary (t, tt));
+
+ if (!pre_parse_)
+ r.push_back (move (rhs));
+ }
+
+ return r;
}
value parser::
- parse_eval_trailer (token& t, type& tt)
+ parse_eval_ternary (token& t, type& tt, bool first)
{
- // Parse the next value (if any) handling its attributes.
+ // enter: first token of LHS
+ // leave: next token after last RHS
+
+ // Right-associative (kind of): we parse what's between ?: without
+ // regard for priority and we recurse on what's after :. Here is an
+ // example:
+ //
+ // a ? x ? y : z : b ? c : d
+ //
+ // This should be parsed/evaluated as:
+ //
+ // a ? (x ? y : z) : (b ? c : d)
+ //
+ location l (get_location (t));
+ value lhs (parse_eval_or (t, tt, first));
+
+ if (tt != type::question)
+ return lhs;
+
+ // Use the pre-parse mechanism to implement short-circuit.
//
- auto parse = [&t, &tt, this] () -> value
+ bool pp (pre_parse_);
+
+ bool q;
+ try
{
- // Parse value attributes if any. Note that it's ok not to have anything
- // after the attributes, as in, ($foo == [null]), or even ([null])
- //
- auto at (attributes_push (t, tt, true));
+ q = pp ? true : convert<bool> (move (lhs));
+ }
+ catch (const invalid_argument& e) { fail (l) << e.what (); }
- // Note that if parse_value() gets called, it expects to see a value.
- //
- value r (tt != type::rparen &&
- tt != type::colon &&
- tt != type::equal &&
- tt != type::not_equal &&
- tt != type::less &&
- tt != type::less_equal &&
- tt != type::greater &&
- tt != type::greater_equal
- ? parse_value (t, tt)
- : value (names ()));
+ if (!pp)
+ pre_parse_ = !q; // Short-circuit middle?
- if (pre_parse_)
- return r; // Empty.
+ next (t, tt);
+ value mhs (parse_eval_ternary (t, tt));
- // Process attributes if any.
- //
- if (!at.first)
+ if (tt != type::colon)
+ fail (t) << "expected ':' instead of " << t;
+
+ if (!pp)
+ pre_parse_ = q; // Short-circuit right?
+
+ next (t, tt);
+ value rhs (parse_eval_ternary (t, tt));
+
+ pre_parse_ = pp;
+ return q ? move (mhs) : move (rhs);
+ }
+
+ value parser::
+ parse_eval_or (token& t, type& tt, bool first)
+ {
+ // enter: first token of LHS
+ // leave: next token after last RHS
+
+ // Left-associative: parse in a loop for as long as we can.
+ //
+ location l (get_location (t));
+ value lhs (parse_eval_and (t, tt, first));
+
+ // Use the pre-parse mechanism to implement short-circuit.
+ //
+ bool pp (pre_parse_);
+
+ while (tt == type::log_or)
+ {
+ try
{
- attributes_pop ();
- return r;
+ if (!pre_parse_ && convert<bool> (move (lhs)))
+ pre_parse_ = true;
+
+ next (t, tt);
+ l = get_location (t);
+ value rhs (parse_eval_and (t, tt));
+
+ if (pre_parse_)
+ continue;
+
+ // Store the result as bool value.
+ //
+ lhs = convert<bool> (move (rhs));
}
+ catch (const invalid_argument& e) { fail (l) << e.what (); }
+ }
- value v;
- apply_value_attributes (nullptr, v, move (r), type::assign);
- return v;
- };
+ pre_parse_ = pp;
+ return lhs;
+ }
- value lhs (parse ());
+ value parser::
+ parse_eval_and (token& t, type& tt, bool first)
+ {
+ // enter: first token of LHS
+ // leave: next token after last RHS
- // We continue evaluating from left to right until we reach ')', storing
- // the result in lhs (think 'a == b == false').
+ // Left-associative: parse in a loop for as long as we can.
//
- while (tt != type::rparen)
- {
- const location l (get_location (t));
+ location l (get_location (t));
+ value lhs (parse_eval_comp (t, tt, first));
- // Remember to update parse_value above if adding any new name
- // separators here.
- //
- switch (tt)
+ // Use the pre-parse mechanism to implement short-circuit.
+ //
+ bool pp (pre_parse_);
+
+ while (tt == type::log_and)
+ {
+ try
{
- case type::colon:
- {
- // Later, when we support '?:', we will need to decide which case
- // this is. But for now ':' is always a scope/target qualified name
- // which we represent as a special ':'-style pair.
- //
- next (t, tt);
- value rhs (parse_eval_trailer (t, tt));
+ if (!pre_parse_ && !convert<bool> (move (lhs)))
+ pre_parse_ = true;
- if (tt != type::rparen)
- fail (l) << "variable name expected after ':'";
+ next (t, tt);
+ l = get_location (t);
+ value rhs (parse_eval_comp (t, tt));
- if (pre_parse_)
- break;
+ if (pre_parse_)
+ continue;
+
+ // Store the result as bool value.
+ //
+ lhs = convert<bool> (move (rhs));
+ }
+ catch (const invalid_argument& e) { fail (l) << e.what (); }
+ }
- if (lhs.type != nullptr || !lhs || lhs.empty ())
- fail (l) << "scope/target expected before ':'";
+ pre_parse_ = pp;
+ return lhs;
+ }
- if (rhs.type != nullptr || !rhs || rhs.empty ())
- fail (l) << "variable name expected after ':'";
+ value parser::
+ parse_eval_comp (token& t, type& tt, bool first)
+ {
+ // enter: first token of LHS
+ // leave: next token after last RHS
- names& ns (lhs.as<names> ());
- ns.back ().pair = ':';
+ // Left-associative: parse in a loop for as long as we can.
+ //
+ value lhs (parse_eval_value (t, tt, first));
+
+ while (tt == type::equal ||
+ tt == type::not_equal ||
+ tt == type::less ||
+ tt == type::less_equal ||
+ tt == type::greater ||
+ tt == type::greater_equal)
+ {
+ type op (tt);
+ location l (get_location (t));
- ns.insert (ns.end (),
- make_move_iterator (rhs.as<names> ().begin ()),
- make_move_iterator (rhs.as<names> ().end ()));
+ next (t, tt);
+ value rhs (parse_eval_value (t, tt));
- break;
+ if (pre_parse_)
+ continue;
+
+ // Use (potentially typed) comparison via value. If one of the values is
+ // typed while the other is not, then try to convert the untyped one to
+ // the other's type instead of complaining. This seems like a reasonable
+ // thing to do and will allow us to write:
+ //
+ // if ($build.version > 30000)
+ //
+ // Rather than having to write:
+ //
+ // if ($build.version > [uint64] 30000)
+ //
+ if (lhs.type != rhs.type)
+ {
+ // @@ Would be nice to pass location for diagnostics.
+ //
+ if (lhs.type == nullptr)
+ {
+ if (lhs)
+ typify (lhs, *rhs.type, nullptr);
}
- case type::equal:
- case type::not_equal:
- case type::less:
- case type::less_equal:
- case type::greater:
- case type::greater_equal:
+ else if (rhs.type == nullptr)
{
- type op (tt);
+ if (rhs)
+ typify (rhs, *lhs.type, nullptr);
+ }
+ else
+ fail (l) << "comparison between " << lhs.type->name << " and "
+ << rhs.type->name;
+ }
- // These are left-associative, so get the rhs value and evaluate.
- //
- // @@ In C++ == and != have lower precedence than <, etc.
- //
- next (t, tt);
- value rhs (parse ());
+ bool r;
+ switch (op)
+ {
+ case type::equal: r = lhs == rhs; break;
+ case type::not_equal: r = lhs != rhs; break;
+ case type::less: r = lhs < rhs; break;
+ case type::less_equal: r = lhs <= rhs; break;
+ case type::greater: r = lhs > rhs; break;
+ case type::greater_equal: r = lhs >= rhs; break;
+ default: r = false; assert (false);
+ }
- if (pre_parse_)
- break;
+ // Store the result as a bool value.
+ //
+ lhs = value (r);
+ }
- // Use (potentially typed) comparison via value. If one of the
- // values is typed while the other is not, then try to convert the
- // untyped one to the other's type instead of complaining. This
- // seems like a reasonable thing to do and will allow us to write:
- //
- // if ($build.version > 30000)
- //
- // Rather than having to write:
- //
- // if ($build.version > [uint64] 30000)
- //
- if (lhs.type != rhs.type)
- {
- // @@ Would be nice to pass location for diagnostics.
- //
- if (lhs.type == nullptr)
- {
- if (lhs)
- typify (lhs, *rhs.type, nullptr);
- }
- else if (rhs.type == nullptr)
- {
- if (rhs)
- typify (rhs, *lhs.type, nullptr);
- }
- else
- fail (l) << "comparison between " << lhs.type->name << " and "
- << rhs.type->name;
- }
+ return lhs;
+ }
- bool r;
- switch (op)
- {
- case type::equal: r = lhs == rhs; break;
- case type::not_equal: r = lhs != rhs; break;
- case type::less: r = lhs < rhs; break;
- case type::less_equal: r = lhs <= rhs; break;
- case type::greater: r = lhs > rhs; break;
- case type::greater_equal: r = lhs >= rhs; break;
- default: r = false; assert (false);
- }
+ value parser::
+ parse_eval_value (token& t, type& tt, bool first)
+ {
+ // enter: first token of value
+ // leave: next token after value
- // Store the result as a bool value.
- //
- if (lhs.type != nullptr)
- {
- // Reset to NULL and untype.
- //
- lhs = nullptr;
- lhs.type = nullptr;
- }
+ // Parse value attributes if any. Note that it's ok not to have anything
+ // after the attributes, as in, ($foo == [null]), or even ([null])
+ //
+ auto at (attributes_push (t, tt, true));
+
+ const location l (get_location (t));
+
+ value v;
+ switch (tt)
+ {
+ case type::log_not:
+ {
+ next (t, tt);
+ v = parse_eval_value (t, tt);
- lhs = r;
+ if (pre_parse_)
break;
+
+ try
+ {
+ // Store the result as bool value.
+ //
+ v = !convert<bool> (move (v));
}
- default:
- fail (l) << "expected ')' instead of " << t;
+ catch (const invalid_argument& e) { fail (l) << e.what (); }
+ break;
+ }
+ default:
+ {
+ // If parse_value() gets called, it expects to see a value. Note that
+ // it will also handle nested eval contexts.
+ //
+ v = (tt != type::colon &&
+ tt != type::question &&
+ tt != type::comma &&
+
+ tt != type::rparen &&
+
+ tt != type::equal &&
+ tt != type::not_equal &&
+ tt != type::less &&
+ tt != type::less_equal &&
+ tt != type::greater &&
+ tt != type::greater_equal &&
+
+ tt != type::log_or &&
+ tt != type::log_and
+
+ ? parse_value (t, tt)
+ : value (names ()));
}
}
- return lhs;
+ // If this is the first expression then handle the eval-qual special case
+ // (scope/target qualified name represented as a special ':'-style pair).
+ //
+ if (first && tt == type::colon)
+ {
+ if (at.first)
+ fail (at.second) << "attributes before qualified variable name";
+
+ attributes_pop ();
+
+ const location nl (get_location (t));
+ next (t, tt);
+ value n (parse_value (t, tt));
+
+ if (tt != type::rparen)
+ fail (t) << "expected ')' after variable name";
+
+ if (pre_parse_)
+ return v; // Empty.
+
+ if (v.type != nullptr || !v || v.as<names> ().size () != 1)
+ fail (l) << "expected scope/target before ':'";
+
+ if (n.type != nullptr || !n || n.as<names> ().size () != 1)
+ fail (nl) << "expected variable name after ':'";
+
+ names& ns (v.as<names> ());
+ ns.back ().pair = ':';
+ ns.push_back (move (n.as<names> ().back ()));
+ return v;
+ }
+ else
+ {
+ if (pre_parse_)
+ return v; // Empty.
+
+ // Process attributes if any.
+ //
+ if (!at.first)
+ {
+ attributes_pop ();
+ return v;
+ }
+
+ value r;
+ apply_value_attributes (nullptr, r, move (v), type::assign);
+ return r;
+ }
}
pair<bool, location> parser::
@@ -2536,10 +2722,15 @@ namespace build2
else if (tt == type::lparen)
{
expire_mode ();
- value v (parse_eval (t, tt).first); //@@ OUT will parse @-pair and do well?
+ values vs (parse_eval (t, tt)); //@@ OUT will parse @-pair and do well?
if (!pre_parse_)
{
+ if (vs.size () != 1)
+ fail (loc) << "expected single variable/function name";
+
+ value& v (vs[0]);
+
if (!v)
fail (loc) << "null variable/function name";
@@ -2553,7 +2744,7 @@ namespace build2
if (n > 2 ||
(n == 2 && ns[0].pair != ':') ||
!ns[n - 1].simple ())
- fail (loc) << "variable/function name expected instead of '"
+ fail (loc) << "expected variable/function name instead of '"
<< ns << "'";
if (n == 2)
@@ -2570,7 +2761,7 @@ namespace build2
}
}
else
- fail (t) << "variable/function name expected instead of " << t;
+ fail (t) << "expected variable/function name instead of " << t;
if (!pre_parse_ && name.empty ())
fail (loc) << "empty variable/function name";
@@ -2590,21 +2781,15 @@ namespace build2
// @@ Should we use (target/scope) qualification (of name) as the
// context in which to call the function?
//
- auto args (parse_eval (t, tt));
+ values args (parse_eval (t, tt));
tt = peek ();
if (pre_parse_)
continue; // As if empty result.
- // Note that we move args to call().
+ // Note that we "move" args to call().
//
- result_data = functions.call (
- name,
- vector_view<value> (
- args.second ? &args.first : nullptr,
- args.second ? 1 : 0),
- loc);
-
+ result_data = functions.call (name, args, loc);
what = "function call";
}
else
@@ -2629,13 +2814,19 @@ namespace build2
//
loc = get_location (t);
- result_data = parse_eval (t, tt).first;
-
+ values vs (parse_eval (t, tt));
tt = peek ();
if (pre_parse_)
continue; // As if empty result.
+ switch (vs.size ())
+ {
+ case 0: result_data = value (names ()); break;
+ case 1: result_data = move (vs[0]); break;
+ default: fail (loc) << "expected single value";
+ }
+
what = "context evaluation";
}