From 54870fb76b5f59cc2e6d69a8c7a8ef17853a0029 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 5 Dec 2016 15:09:04 +0200 Subject: Add comma, ternary, logical operators support in eval context --- build2/lexer.cxx | 51 +++-- build2/parser | 25 +- build2/parser.cxx | 489 ++++++++++++++++++++++++++++------------ build2/test/script/token | 2 - build2/test/script/token.cxx | 3 - build2/token | 46 ++-- build2/token.cxx | 17 +- build2/variable | 11 + build2/variable.txx | 14 ++ doc/manual.cli | 28 ++- doc/testscript.cli | 2 +- old-tests/eval/buildfile | 72 ------ old-tests/eval/test.out | 36 --- old-tests/eval/test.sh | 3 - old-tests/test.sh | 1 - tests/buildfile | 2 +- tests/eval/buildfile | 5 + tests/eval/comma.test | 13 ++ tests/eval/comp.test | 47 ++++ tests/eval/or-and.test | 28 +++ tests/eval/qual.test | 28 +++ tests/eval/ternary.test | 30 +++ tests/eval/value.test | 23 ++ unit-tests/function/call.test | 3 +- unit-tests/function/driver.cxx | 1 + unit-tests/function/syntax.test | 2 + unit-tests/lexer/buildfile | 2 +- unit-tests/lexer/eval.test | 76 +++++++ 28 files changed, 742 insertions(+), 318 deletions(-) delete mode 100644 old-tests/eval/buildfile delete mode 100644 old-tests/eval/test.out delete mode 100755 old-tests/eval/test.sh create mode 100644 tests/eval/buildfile create mode 100644 tests/eval/comma.test create mode 100644 tests/eval/comp.test create mode 100644 tests/eval/or-and.test create mode 100644 tests/eval/qual.test create mode 100644 tests/eval/ternary.test create mode 100644 tests/eval/value.test create mode 100644 unit-tests/lexer/eval.test 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 + 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 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 (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 (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 (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 (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 (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 ()); - 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 ().begin ()), - make_move_iterator (rhs.as ().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 (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 ().size () != 1) + fail (l) << "expected scope/target before ':'"; + + if (n.type != nullptr || !n || n.as ().size () != 1) + fail (nl) << "expected variable name after ':'"; + + names& ns (v.as ()); + ns.back ().pair = ':'; + ns.push_back (move (n.as ().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 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 ( - 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; + // 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 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 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 + T + convert (value&& v) + { + if (v.type == nullptr) + return convert (move (v).as ()); + else if (v.type == &value_traits::value_type) + return move (v).as (); + + throw invalid_argument ( + string ("invalid ") + value_traits::type_name + + " value: conversion from " + v.type->name); + } + + template 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? ( | eval | '!' eval-value) +eval-qual: ':' + +value-attributes: '[' ']' +\ + +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 +: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 + :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 +:1:2: error: attributes before qualified variable name +EOE + +: leader +: +$* <'(foo == foo:bar)' 2>>EOE != 0 +:1:12: error: unexpected ':' +EOE + +: trailer +: +$* <'(foo:bar == foo)' 2>>EOE != 0 +: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) {return a;}; f["ambig"] = [](names a, optional) {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!=xx>=)' >>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 -- cgit v1.1