aboutsummaryrefslogtreecommitdiff
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
parentef7cb7ea3e6fcb21a4fcf38602b3f43f03232ace (diff)
Add comma, ternary, logical operators support in eval context
-rw-r--r--build2/lexer.cxx51
-rw-r--r--build2/parser25
-rw-r--r--build2/parser.cxx489
-rw-r--r--build2/test/script/token2
-rw-r--r--build2/test/script/token.cxx3
-rw-r--r--build2/token46
-rw-r--r--build2/token.cxx17
-rw-r--r--build2/variable11
-rw-r--r--build2/variable.txx14
-rw-r--r--doc/manual.cli28
-rw-r--r--doc/testscript.cli2
-rw-r--r--old-tests/eval/buildfile72
-rw-r--r--old-tests/eval/test.out36
-rwxr-xr-xold-tests/eval/test.sh3
-rwxr-xr-xold-tests/test.sh1
-rw-r--r--tests/buildfile2
-rw-r--r--tests/eval/buildfile5
-rw-r--r--tests/eval/comma.test13
-rw-r--r--tests/eval/comp.test47
-rw-r--r--tests/eval/or-and.test28
-rw-r--r--tests/eval/qual.test28
-rw-r--r--tests/eval/ternary.test30
-rw-r--r--tests/eval/value.test23
-rw-r--r--unit-tests/function/call.test3
-rw-r--r--unit-tests/function/driver.cxx1
-rw-r--r--unit-tests/function/syntax.test2
-rw-r--r--unit-tests/lexer/buildfile2
-rw-r--r--unit-tests/lexer/eval.test76
28 files changed, 742 insertions, 318 deletions
diff --git a/build2/lexer.cxx b/build2/lexer.cxx
index 8918740..81cfea5 100644
--- a/build2/lexer.cxx
+++ b/build2/lexer.cxx
@@ -65,8 +65,8 @@ namespace build2
}
case lexer_mode::eval:
{
- s1 = ":<>=! $(){}[]#\t\n";
- s2 = " == ";
+ s1 = ":<>=!&|?, $(){}[]#\t\n";
+ s2 = " = &| ";
break;
}
case lexer_mode::single_quoted:
@@ -233,32 +233,51 @@ namespace build2
case '[': return make_token (type::lsbrace);
case ']': return make_token (type::rsbrace);
case '$': return make_token (type::dollar);
+ case '?': return make_token (type::question);
+ case ',': return make_token (type::comma);
case '(': return make_token (type::lparen);
case ')':
{
state_.pop (); // Expire eval mode.
return make_token (type::rparen);
}
+ // Potentially two-character tokens.
+ //
case '=':
case '!':
- {
- if (peek () == '=')
- {
- get ();
- return make_token (c == '=' ? type::equal : type::not_equal);
- }
- break;
- }
case '<':
case '>':
+ case '|':
+ case '&':
{
- bool e (peek () == '=');
- if (e)
- get ();
+ xchar p (peek ());
+
+ type r (type::eos);
+ switch (c)
+ {
+ case '|': if (p == '|') r = type::log_or; break;
+ case '&': if (p == '&') r = type::log_and; break;
+
+ case '<': r = (p == '=' ? type::less_equal : type::less); break;
+ case '>': r = (p == '=' ? type::greater_equal : type::greater); break;
+
+ case '=': if (p == '=') r = type::equal; break;
+
+ case '!': r = (p == '=' ? type::not_equal : type::log_not); break;
+ }
+
+ if (r == type::eos)
+ break;
+
+ switch (r)
+ {
+ case type::less:
+ case type::greater:
+ case type::log_not: break;
+ default: get ();
+ }
- return make_token (c == '<'
- ? e ? type::less_equal : type::less
- : e ? type::greater_equal : type::greater);
+ return make_token (r);
}
}
diff --git a/build2/parser b/build2/parser
index e1f6e77..45f5b16 100644
--- a/build2/parser
+++ b/build2/parser
@@ -110,15 +110,30 @@ namespace build2
value&& rhs,
token_type kind);
- // Return the value (can be NULL/typed) as well as the indication of
- // whether this is a non-empty eval context (i.e., '()' potentially with
- // whitespace in between).
+ // Return the value pack (values can be NULL/typed). Note that for an
+ // empty eval context ('()' potentially with whitespaces in between) the
+ // result is an empty pack, not a pack of one empty.
//
- pair<value, bool>
+ values
parse_eval (token&, token_type&);
+ values
+ parse_eval_comma (token&, token_type&, bool = false);
+
+ value
+ parse_eval_ternary (token&, token_type&, bool = false);
+
+ value
+ parse_eval_or (token&, token_type&, bool = false);
+
+ value
+ parse_eval_and (token&, token_type&, bool = false);
+
+ value
+ parse_eval_comp (token&, token_type&, bool = false);
+
value
- parse_eval_trailer (token&, token_type&);
+ parse_eval_value (token&, token_type&, bool = false);
// Attributes stack. We can have nested attributes, for example:
//
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";
}
diff --git a/build2/test/script/token b/build2/test/script/token
index 7f79746..117ccaa 100644
--- a/build2/test/script/token
+++ b/build2/test/script/token
@@ -31,8 +31,6 @@ namespace build2
pipe, // |
clean, // &{?!} (modifiers in value)
- log_and, // &&
- log_or, // ||
in_pass, // <+
in_null, // <-
diff --git a/build2/test/script/token.cxx b/build2/test/script/token.cxx
index a8ef5b4..5552994 100644
--- a/build2/test/script/token.cxx
+++ b/build2/test/script/token.cxx
@@ -29,10 +29,7 @@ namespace build2
case token_type::minus: os << q << '-' << q; break;
case token_type::clean: os << q << '&' << v << q; break;
-
case token_type::pipe: os << q << '|' << q; break;
- case token_type::log_and: os << q << "&&" << q; break;
- case token_type::log_or: os << q << "||" << q; break;
case token_type::in_pass: os << q << "<+" << q; break;
case token_type::in_null: os << q << "<-" << q; break;
diff --git a/build2/token b/build2/token
index 3d580b9..2cc1fca 100644
--- a/build2/token
+++ b/build2/token
@@ -28,23 +28,35 @@ namespace build2
newline,
word,
pair_separator,
- colon,
- lcbrace, // {
- rcbrace, // }
- lsbrace, // [
- rsbrace, // ]
- assign, // =
- prepend, // =+
- append, // +=
- equal, // ==
- not_equal, // !=
- less, // <
- greater, // >
- less_equal, // <=
- greater_equal, // >=
- dollar, // $
- lparen, // (
- rparen, // )
+
+ colon, // :
+ dollar, // $
+ question, // ?
+ comma, // ,
+
+ lparen, // (
+ rparen, // )
+
+ lcbrace, // {
+ rcbrace, // }
+
+ lsbrace, // [
+ rsbrace, // ]
+
+ assign, // =
+ prepend, // =+
+ append, // +=
+
+ equal, // ==
+ not_equal, // !=
+ less, // <
+ greater, // >
+ less_equal, // <=
+ greater_equal, // >=
+
+ log_or, // ||
+ log_and, // &&
+ log_not, // !
value_next
};
diff --git a/build2/token.cxx b/build2/token.cxx
index df0d8ce..caa775b 100644
--- a/build2/token.cxx
+++ b/build2/token.cxx
@@ -23,22 +23,33 @@ namespace build2
case token_type::word: os << '\'' << t.value << '\''; break;
case token_type::colon: os << q << ':' << q; break;
+ case token_type::dollar: os << q << '$' << q; break;
+ case token_type::question: os << q << '?' << q; break;
+ case token_type::comma: os << q << ',' << q; break;
+
+ case token_type::lparen: os << q << '(' << q; break;
+ case token_type::rparen: os << q << ')' << q; break;
+
case token_type::lcbrace: os << q << '{' << q; break;
case token_type::rcbrace: os << q << '}' << q; break;
+
case token_type::lsbrace: os << q << '[' << q; break;
case token_type::rsbrace: 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;
+
case token_type::equal: os << q << "==" << q; break;
case token_type::not_equal: os << q << "!=" << q; break;
case token_type::less: os << q << '<' << q; break;
case token_type::greater: os << q << '>' << q; break;
case token_type::less_equal: os << q << "<=" << q; break;
case token_type::greater_equal: os << q << ">=" << q; break;
- case token_type::dollar: os << q << '$' << q; break;
- case token_type::lparen: os << q << '(' << q; break;
- case token_type::rparen: os << q << ')' << q; break;
+
+ case token_type::log_or: os << q << "||" << q; break;
+ case token_type::log_and: os << q << "&&" << q; break;
+ case token_type::log_not: os << q << '!' << q; break;
default: assert (false); // Unhandled extended token.
}
diff --git a/build2/variable b/build2/variable
index b773047..abff1d5 100644
--- a/build2/variable
+++ b/build2/variable
@@ -248,6 +248,12 @@ namespace build2
reset ();
};
+ // This is what we call a "value pack"; it can be created by the eval
+ // context and passed as arguments to functions. Usually we will have just
+ // one value.
+ //
+ using values = small_vector<value, 1>;
+
// The values should be of the same type (or both be untyped) except NULL
// values can also be untyped. NULL values compare equal and a NULL value
// is always less than a non-NULL.
@@ -439,6 +445,11 @@ namespace build2
//
//template <typename T> T convert (names&&); (declaration causes ambiguity)
+ // Convert value to T. If value is already of type T, then simply cast it.
+ // Otherwise call convert(names) above.
+ //
+ template <typename T> T convert (value&&);
+
// Default implementations of the dtor/copy_ctor/copy_assing callbacks for
// types that are stored directly in value::data_ and the provide all the
// necessary functions (copy/move ctor and assignment operator).
diff --git a/build2/variable.txx b/build2/variable.txx
index ab31a45..d566a04 100644
--- a/build2/variable.txx
+++ b/build2/variable.txx
@@ -35,6 +35,20 @@ namespace build2
}
template <typename T>
+ T
+ convert (value&& v)
+ {
+ if (v.type == nullptr)
+ return convert<T> (move (v).as<names> ());
+ else if (v.type == &value_traits<T>::value_type)
+ return move (v).as<T> ();
+
+ throw invalid_argument (
+ string ("invalid ") + value_traits<T>::type_name +
+ " value: conversion from " + v.type->name);
+ }
+
+ template <typename T>
void
default_dtor (value& v)
{
diff --git a/doc/manual.cli b/doc/manual.cli
index de16aae..2d23688 100644
--- a/doc/manual.cli
+++ b/doc/manual.cli
@@ -15,11 +15,27 @@
\h0#preface|Preface|
This is the preface.
-"
-/*
-"
-\h Installation
+\h1#grammar|Grammar|
+
+\
+eval: '(' (eval-comma | eval-qual)? ')'
+eval-comma: eval-ternary (',' eval-ternary)*
+eval-ternary: eval-or ('?' eval-ternary ':' eval-ternary)?
+eval-or: eval-and ('||' eval-and)*
+eval-and: eval-comp ('&&' eval-comp)*
+eval-comp: eval-value (('=='|'!='|'<'|'>'|'<='|'>=') eval-value)*
+eval-value: value-attributes? (<value> | eval | '!' eval-value)
+eval-qual: <name> ':' <name>
+
+value-attributes: '[' <key-value-pairs> ']'
+\
+
+Note that \c{?:} (ternary operator) and \c{!} (logical not) are
+right-associative. Unlike C++, all the comparison operators have the same
+precedence. A qualified name cannot be combined with any other operator
+(including ternary) unless enclosed in parentheses. The \c{eval} option
+in the \c{eval-value} production shall contain single value only (no
+commas).
+
"
-source "../INSTALL.cli";
-*/
diff --git a/doc/testscript.cli b/doc/testscript.cli
index eaccd0a..1a02c12 100644
--- a/doc/testscript.cli
+++ b/doc/testscript.cli
@@ -653,7 +653,7 @@ foo? - zero or one multiplier
foo bar - concatenation (foo then bar)
foo | bar - alternation (foo or bar)
(foo bar) - grouping
-{foo bar} - concatenation in any order (foo then bar or bar then foo)
+{foo bar} - grouping in any order (foo then bar or bar then foo)
foo \
bar - line continuation
# foo - comment
diff --git a/old-tests/eval/buildfile b/old-tests/eval/buildfile
deleted file mode 100644
index f26a9a0..0000000
--- a/old-tests/eval/buildfile
+++ /dev/null
@@ -1,72 +0,0 @@
-(./):
-()
-
-# Invalid.
-#
-#(foo
-#(foo #comment
-
-print ()
-print ((foo)(bar))
-print ((foo) (bar))
-
-print (foo\
-bar)
-
-# !=, == vs !, = recognition
-#
-print (=)
-print (!)
-print (= foo)
-print (foo!)
-
-# !=, == evaluation
-#
-
-# print ( == bar)
-# print (foo == )
-
-print (foo == bar)
-print (foo == foo)
-print (foo != bar)
-print (foo != foo)
-
-print (foo == (foo))
-print ((foo bar) == foo bar)
-print (foo != foo bar)
-print ("" == '')
-
-print ((foo != bar) baz)
-print "foo equals bar is (foo == bar)"
-
-foo = foo
-print ($foo == foo)
-print (bar != $foo)
-
-print ([null])
-print (([null]))
-print ([uint64] 01)
-
-n = [null]
-print ($n == [null])
-print ($N == [null])
-print ([null] == [null])
-
-print ($n == $N == true)
-
-n =
-print ($n == )
-n = {}
-print ($n == "")
-
-#print ([uint64] 01 == [string] 01)
-
-# <, <=, >, >= evaluation
-#
-print (a < b)
-print (a b > a a)
-print (123 <= 123)
-print ([uint64] 02 > [uint64] 01)
-print (a > [null])
-print ([uint64] 02 > [null])
-print ($build.version > 30000)
diff --git a/old-tests/eval/test.out b/old-tests/eval/test.out
deleted file mode 100644
index 555853f..0000000
--- a/old-tests/eval/test.out
+++ /dev/null
@@ -1,36 +0,0 @@
-
-foobar
-foo bar
-foobar
-=
-!
-= foo
-foo!
-false
-true
-true
-false
-true
-true
-true
-true
-true baz
-foo equals bar is false
-true
-true
-[null]
-[null]
-1
-true
-true
-true
-true
-true
-true
-true
-true
-true
-true
-true
-true
-true
diff --git a/old-tests/eval/test.sh b/old-tests/eval/test.sh
deleted file mode 100755
index c745b76..0000000
--- a/old-tests/eval/test.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-b -q | diff --strip-trailing-cr -u test.out -
diff --git a/old-tests/test.sh b/old-tests/test.sh
index ebd7b47..6a8e39c 100755
--- a/old-tests/test.sh
+++ b/old-tests/test.sh
@@ -14,7 +14,6 @@ function test ()
test "amalgam/unnamed"
test "escaping"
-test "eval"
test "if-else"
test "keyword"
test "names"
diff --git a/tests/buildfile b/tests/buildfile
index b05e2e7..a2fff9f 100644
--- a/tests/buildfile
+++ b/tests/buildfile
@@ -2,6 +2,6 @@
# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
-d = directive/ expansion/ function/ test/ value/
+d = directive/ eval/ expansion/ function/ test/ value/
./: $d file{common.test}
include $d
diff --git a/tests/eval/buildfile b/tests/eval/buildfile
new file mode 100644
index 0000000..184a72a
--- /dev/null
+++ b/tests/eval/buildfile
@@ -0,0 +1,5 @@
+# file : tests/eval/buildfile
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+./: test{comma comp or-and qual ternary value}
diff --git a/tests/eval/comma.test b/tests/eval/comma.test
new file mode 100644
index 0000000..0e9384a
--- /dev/null
+++ b/tests/eval/comma.test
@@ -0,0 +1,13 @@
+# file : tests/eval/comma.test
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test value packs (eval-comma).
+
+.include ../common.test
+
+: comma
+:
+$* <'print (foo, bar)' 2>>EOE != 0
+<stdin>:1:7: error: expected single value
+EOE
diff --git a/tests/eval/comp.test b/tests/eval/comp.test
new file mode 100644
index 0000000..4ef6c2d
--- /dev/null
+++ b/tests/eval/comp.test
@@ -0,0 +1,47 @@
+# file : tests/eval/comp.test
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test comparison operators (eval-comp).
+
+.include ../common.test
+
+$* <'print (foo == bar)' >'false' : eq-false
+$* <'print (foo == foo)' >'true' : eq-true
+
+$* <'print (foo != foo)' >'false' : ne-false
+$* <'print (foo != bar)' >'true' : ne-true
+
+$* <'print (foo < bar)' >'false' : lt-false
+$* <'print (bar < foo)' >'true' : lt-true
+
+$* <'print (bar > foo)' >'false' : gt-false
+$* <'print (foo > bar)' >'true' : gt-true
+
+$* <'print (foo <= bar)' >'false' : le-false
+$* <'print (bar <= foo)' >'true' : le-true
+$* <'print (bar <= bar)' >'true' : le-true-eq
+
+$* <'print (bar >= foo)' >'false' : gt-false
+$* <'print (foo >= bar)' >'true' : gt-true
+$* <'print (foo >= foo)' >'true' : gt-true-eq
+
+: associativity
+:
+$* <'print (foo == bar == false)' >'true'
+
+: type
+:
+{
+ $* <'print ((foo bar) == foo bar)' >'true' : untyped-list
+ $* <'print ("" == "")' >'true' : untyped-empty
+
+ $* <'print (0 < 00)' >'true' : untyped-untyped
+ $* <'print (0 < [null])' >'false' : untyped-null
+ $* <'print ([uint64] 00 < 0)' >'false' : uint64-untyped
+ $* <'print ([uint64] 00 < [null])' >'false' : uint64-null
+ $* <'print ([uint64] 00 < [uint64] 0)' >'false' : uint64-uint64
+ $* <'print ([uint64] 00 < [string] 0)' 2>>EOE != 0 : uint64-string
+ <stdin>:1:20: error: comparison between uint64 and string
+ EOE
+}
diff --git a/tests/eval/or-and.test b/tests/eval/or-and.test
new file mode 100644
index 0000000..b9fb0b7
--- /dev/null
+++ b/tests/eval/or-and.test
@@ -0,0 +1,28 @@
+# file : tests/eval/or-and.test
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test logical operators (eval-or, eval-and).
+
+.include ../common.test
+
+$* <'print (false || false)' >'false' : or-false
+$* <'print (false || true)' >'true' : or-true-1
+$* <'print (true || false)' >'true' : or-true-2
+
+$* <'print (false && true)' >'false' : and-false-1
+$* <'print (true && false)' >'false' : and-false-2
+$* <'print (true && true)' >'true' : and-true
+
+: associativity
+:
+: Also tests short-circuit.
+:
+{
+ $* <'print (true || $bogus($foo) || false)' >'true' : or
+ $* <'print (false && $bogus($foo) && false)' >'false' : and
+}
+
+: priority
+:
+$* <'print (false && true || true)' >'true'
diff --git a/tests/eval/qual.test b/tests/eval/qual.test
new file mode 100644
index 0000000..719ee9f
--- /dev/null
+++ b/tests/eval/qual.test
@@ -0,0 +1,28 @@
+# file : tests/eval/qual.test
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test qualified name (eval-qual).
+
+.include ../common.test
+
+$* <'print (file{foo}: bar)' >'file{foo}:bar' : target
+$* <'print (foo/dir{}: bar)' >'dir{foo/}:bar' : scope
+
+: attribute
+:
+$* <'([string] foo:bar)' 2>>EOE != 0
+<stdin>:1:2: error: attributes before qualified variable name
+EOE
+
+: leader
+:
+$* <'(foo == foo:bar)' 2>>EOE != 0
+<stdin>:1:12: error: unexpected ':'
+EOE
+
+: trailer
+:
+$* <'(foo:bar == foo)' 2>>EOE != 0
+<stdin>:1:10: error: expected ')' after variable name
+EOE
diff --git a/tests/eval/ternary.test b/tests/eval/ternary.test
new file mode 100644
index 0000000..30a7e05
--- /dev/null
+++ b/tests/eval/ternary.test
@@ -0,0 +1,30 @@
+# file : tests/eval/ternary.test
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test the ternary operator (eval-ternary).
+
+.include ../common.test
+
+$* <'print (true ? foo : bar)' >'foo' : true
+$* <'print (false ? foo : bar)' >'bar' : false
+
+: associativity
+:
+{
+ $* <'print (true ? false ? foo : bar : baz)' >'bar' : middle
+ $* <'print (true ? foo : true ? bar : baz)' >'foo' : right
+ $* <'print (true ? false ? foo : bar : true ? baz : fox)' >'bar' : both
+ $* <'print (false ? foo ? false ? bar : false : baz : fox)' >'fox' : chain
+}
+
+: short-circuit
+:
+{
+ $* <'print (false ? $bogus() : foo)' >'foo' : middle
+ $* <'print (true ? foo : $bogus())' >'foo' : right
+
+ # @@ TODO: complate pre-parse support in parse_name().
+ #
+ #$* <'print (true ? foo : $out_base/{foo bar})' >'foo' : pre-parse
+}
diff --git a/tests/eval/value.test b/tests/eval/value.test
new file mode 100644
index 0000000..cf91ec1
--- /dev/null
+++ b/tests/eval/value.test
@@ -0,0 +1,23 @@
+# file : tests/eval/value.test
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test value part of the grammar (eval-value).
+
+.include ../common.test
+
+$* <'print (foo)' >'foo' : value
+$* <'print ()' >'' : value-empty
+$* <'print ((foo bar))' >'foo bar' : eval
+$* <'print (!false)' >'true' : not
+$* <'print (!!true)' >'true' : not-double
+
+: attribute
+:
+{
+ $* <'print $type([string] foo)' >'string' : type
+ $* <'print ([uint64] 001)' >'1' : value
+ $* <'print $null([null])' >'true' : null
+ $* <'print $type([string] (foo == bar))' >'string' : eval
+ $* <'print $type([string] !true)' >'string' : not
+}
diff --git a/unit-tests/function/call.test b/unit-tests/function/call.test
index d16b91c..9129785 100644
--- a/unit-tests/function/call.test
+++ b/unit-tests/function/call.test
@@ -25,8 +25,7 @@ $* <'print $dummy.abs([abs_dir_path] .)' >'true'
: variadic
:
-# @@ TMP: add some args
-$* <'print $variadic([bool] true)' >'1'
+$* <'print $variadic([bool] true, foo, bar)' >'3'
: fail
:
diff --git a/unit-tests/function/driver.cxx b/unit-tests/function/driver.cxx
index 3dea374..235af0c 100644
--- a/unit-tests/function/driver.cxx
+++ b/unit-tests/function/driver.cxx
@@ -38,6 +38,7 @@ namespace build2
f["dummy0"] = []() {return "abc";};
f["dummy1"] = [](string s) {return s;};
+ f["dummy2"] = [](uint64_t x, uint64_t y) {return x + y;};
f["ambig"] = [](names a, optional<string>) {return a;};
f["ambig"] = [](names a, optional<uint64_t>) {return a;};
diff --git a/unit-tests/function/syntax.test b/unit-tests/function/syntax.test
index d644fd1..11ff3c1 100644
--- a/unit-tests/function/syntax.test
+++ b/unit-tests/function/syntax.test
@@ -12,6 +12,8 @@ $* <'$dump(d/t{x y z})' >'d/t{x} d/t{y} d/t{z}' : one-names
$* <'print a$dummy1 ([string] b)c' >'abc' : concat
+$* <'print $dummy2([uint64] 123, [uint64] 321)' >'444' : multi-arg
+
: quoting
: Verify we can inhibit function call with quoting
:
diff --git a/unit-tests/lexer/buildfile b/unit-tests/lexer/buildfile
index d9bd2df..bdd8dd1 100644
--- a/unit-tests/lexer/buildfile
+++ b/unit-tests/lexer/buildfile
@@ -8,6 +8,6 @@ import libs = libbutl%lib{butl}
src = token lexer diagnostics utility variable name b-options types-parsers
exe{driver}: cxx{driver} ../../build2/cxx{$src} $libs \
-test{comment quoting}
+test{comment eval quoting}
include ../../build2/
diff --git a/unit-tests/lexer/eval.test b/unit-tests/lexer/eval.test
new file mode 100644
index 0000000..0f4f79b
--- /dev/null
+++ b/unit-tests/lexer/eval.test
@@ -0,0 +1,76 @@
+# file : unit-tests/lexer/eval.test
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = eval
+
+: punctuation
+:
+$* <:'x:x{x}x[x]x$x?x,x(x)' >>EOO
+'x'
+:
+'x'
+{
+'x'
+}
+'x'
+[
+'x'
+]
+'x'
+$
+'x'
+?
+'x'
+,
+'x'
+(
+'x'
+)
+EOO
+
+: logical
+:
+$* <:'x|x||x&x&&x!x!!x)' >>EOO
+'x|x'
+||
+'x&x'
+&&
+'x'
+!
+'x'
+!
+!
+'x'
+)
+EOO
+
+: comparison
+:
+$* <:'x=x==x!=x<x<=x>x>=)' >>EOO
+'x=x'
+==
+'x'
+!=
+'x'
+<
+'x'
+<=
+'x'
+>
+'x'
+>=
+)
+EOO
+
+: newline
+:
+$* <'x' >- 2>>EOE != 0
+stdin:1:2: error: newline in evaluation context
+EOE
+
+: eof
+:
+$* <:'' 2>>EOE != 0
+stdin:1:1: error: unterminated evaluation context
+EOE