aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-11-08 16:37:44 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-11-08 16:38:53 +0200
commitf4b2107284329b12f595eba856072b79b824a63f (patch)
tree0e34fdcd61747adc0cad2c5af5f32d27dab42ae2
parent278140ebf2bc97eb72a1e8adb04a40a0a5807d8f (diff)
Implement testscript command-if
-rw-r--r--build2/test/script/parser13
-rw-r--r--build2/test/script/parser.cxx524
-rw-r--r--build2/test/script/runner6
-rw-r--r--build2/test/script/runner.cxx7
-rw-r--r--build2/test/script/script15
-rw-r--r--build2/test/script/script.cxx22
-rw-r--r--unit-tests/test/script/parser/buildfile3
-rw-r--r--unit-tests/test/script/parser/description.test2
-rw-r--r--unit-tests/test/script/parser/driver.cxx39
-rw-r--r--unit-tests/test/script/parser/if-else.test518
-rw-r--r--unit-tests/test/script/parser/scope.test4
11 files changed, 1001 insertions, 152 deletions
diff --git a/build2/test/script/parser b/build2/test/script/parser
index b99c487..2dc14c8 100644
--- a/build2/test/script/parser
+++ b/build2/test/script/parser
@@ -63,10 +63,19 @@ namespace build2
description
parse_trailing_description (token&, token_type&);
- void
+ bool
pre_parse_line (token&, token_type&,
optional<description>&,
- lines* = nullptr);
+ lines* = nullptr,
+ bool one = false);
+
+ void
+ parse_lines (lines::iterator, lines::iterator, size_t&, bool);
+
+ bool
+ pre_parse_if_else (token&, token_type&,
+ optional<description>&,
+ lines&);
void
parse_directive_line (token&, token_type&);
diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx
index 3cae314..b12ca8f 100644
--- a/build2/test/script/parser.cxx
+++ b/build2/test/script/parser.cxx
@@ -203,7 +203,7 @@ namespace build2
su.begin (), su.end (),
[] (const line& l)
{
- return l.type != line_type::variable;
+ return l.type != line_type::var;
}) == su.end () &&
td.empty () &&
(!g->desc || !t->desc))
@@ -287,129 +287,15 @@ namespace build2
{
size_t li (0);
- // Note: destructive to lines.
- //
- auto play = [&li, this] (lines& ls, bool test)
- {
- token t;
- type tt;
-
- for (size_t i (0), n (ls.size ()); i != n; ++i)
- {
- line& l (ls[i]);
-
- assert (path_ == nullptr);
- replay_data (move (l.tokens)); // Set the tokens and start playing.
-
- // We don't really need to change the mode since we already know
- // the line type.
- //
- next (t, tt);
-
- switch (l.type)
- {
- case line_type::variable:
- {
- // Parse.
- //
- string name (move (t.value));
-
- next (t, tt);
- type kind (tt); // Assignment kind.
-
- value rhs (parse_variable_line (t, tt));
-
- if (tt == type::semi)
- next (t, tt);
-
- assert (tt == type::newline);
-
- // Assign.
- //
- const variable& var (script_->var_pool.insert (move (name)));
-
- value& lhs (kind == type::assign
- ? scope_->assign (var)
- : scope_->append (var));
-
- // @@ Need to adjust to make strings the default type.
- //
- apply_value_attributes (&var, lhs, move (rhs), kind);
-
- // Handle the $*, $NN special aliases.
- //
- // The plan is as follows: here we detect modification of the
- // source variables (test*), and (re)set $* to NULL on this
- // scope (this is important to both invalidate any old values
- // but also to "stake" the lookup position). This signals to
- // the variable lookup function below that the $* and $NN
- // values need to be recalculated from their sources. Note
- // that we don't need to invalidate $NN since their lookup
- // always checks $* first.
- //
- if (var.name == script_->test_var.name ||
- var.name == script_->opts_var.name ||
- var.name == script_->args_var.name)
- {
- scope_->assign (script_->cmd_var) = nullptr;
- }
-
- break;
- }
- case line_type::command:
- {
- // We use the 0 index to signal that this is the only command.
- // Note that we only do this for test commands.
- //
- if (test && li == 0)
- {
- size_t j (i + 1);
- for (; j != n && ls[j].type == line_type::variable; ++j) ;
-
- if (j != n) // We have another command.
- ++li;
- }
- else
- ++li;
-
- // Parse.
- //
- const location& ll (get_location (t));
- pair<command_expr, here_docs> p (parse_command_line (t, tt));
-
- switch (tt)
- {
- case type::colon: parse_trailing_description (t, tt); break;
- case type::semi: next (t, tt); break; // Get newline.
- }
-
- assert (tt == type::newline);
-
- parse_here_documents (t, tt, p);
-
- // Now that we have all the pieces, run the command.
- //
- runner_->run (*scope_, p.first, li, ll);
- break;
- }
- }
-
- assert (tt == type::newline);
-
- replay_stop (); // Stop playing.
- assert (path_ == nullptr);
- }
- };
-
runner_->enter (*scope_, scope_->start_loc_);
if (test* t = dynamic_cast<test*> (scope_))
{
- play (t->tests_, true);
+ parse_lines (t->tests_.begin (), t->tests_.end (), li, true);
}
else if (group* g = dynamic_cast<group*> (scope_))
{
- play (g->setup_, false);
+ parse_lines (g->setup_.begin (), g->setup_.end (), li, false);
for (const unique_ptr<scope>& s: g->scopes)
{
@@ -425,7 +311,7 @@ namespace build2
p.parse (*s, *script_, *runner_);
}
- play (g->tdown_, false);
+ parse_lines (g->tdown_.begin (), g->tdown_.end (), li, false);
}
else
assert (false);
@@ -611,14 +497,20 @@ namespace build2
return r;
}
- void parser::
- pre_parse_line (token& t, type& tt, optional<description>& d, lines* ls)
+ // If one is true then only parse one line returning an indication of
+ // whether the line ended with a semicolon.
+ //
+ bool parser::
+ pre_parse_line (token& t, type& tt,
+ optional<description>& d,
+ lines* ls,
+ bool one)
{
// Note: token is only peeked at.
//
const location ll (get_location (peeked ()));
- // Determine the line type/subtype.
+ // Determine the line type/start token.
//
line_type lt;
type st (type::eos);
@@ -630,7 +522,7 @@ namespace build2
{
// Setup/teardown command.
//
- lt = line_type::command;
+ lt = line_type::cmd;
st = tt;
next (t, tt); // Start saving tokens from the next one.
@@ -676,30 +568,52 @@ namespace build2
parse_directive_line (t, tt);
assert (tt == type::newline);
- return;
+ return false;
}
else if (p == type::assign ||
p == type::prepend ||
p == type::append)
{
- lt = line_type::variable;
+ lt = line_type::var;
st = p;
break;
}
}
- lt = line_type::command;
+ lt = line_type::cmd;
break;
}
}
+ // Detect if-else commands.
+ //
+ if (lt == line_type::cmd && tt == type::word && !t.quoted)
+ {
+ // The next token should be separated. We make an exception for
+ // colon and semicolon (think end;).
+ //
+ type p (peek ());
+ if (peeked ().separated || p == type::semi || p == type::colon)
+ {
+ if (t.value == "if") lt = line_type::cmd_if;
+ else if (t.value == "if!") lt = line_type::cmd_ifn;
+ else if (t.value == "elif") lt = line_type::cmd_elif;
+ else if (t.value == "elif!") lt = line_type::cmd_elifn;
+ else if (t.value == "else") lt = line_type::cmd_else;
+ else if (t.value == "end") lt = line_type::cmd_end;
+
+ if (lt != line_type::cmd)
+ next (t, tt); // Skip to start of command.
+ }
+ }
+
// Pre-parse the line keeping track of whether it ends with a semi.
//
bool semi (false);
switch (lt)
{
- case line_type::variable:
+ case line_type::var:
{
// Check if we are trying to modify any of the special aliases
// ($*, $~, $N).
@@ -722,16 +636,28 @@ namespace build2
break;
}
- case line_type::command:
+ case line_type::cmd:
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_else:
+ case line_type::cmd_end:
{
- pair<command_expr, here_docs> p (parse_command_line (t, tt));
+ pair<command_expr, here_docs> p;
+
+ if (lt != line_type::cmd_else && lt != line_type::cmd_end)
+ p = parse_command_line (t, tt);
- // Colon and semicolon are only valid in test command lines. Note
- // that we still recognize them lexically, they are just not valid
- // tokens per the grammar.
+ // Colon and semicolon are only valid in test command lines and
+ // after 'end' in if-else. Note that we still recognize them
+ // lexically, they are just not valid tokens per the grammar.
//
if (tt != type::newline)
{
+ if (lt != line_type::cmd && lt != line_type::cmd_end)
+ fail (t) << "expected newline instead of " << t;
+
switch (st)
{
case type::plus: fail (t) << t << " after setup command";
@@ -744,7 +670,7 @@ namespace build2
case type::colon:
{
if (d)
- fail (ll) << "both leading and trailing description";
+ fail (ll) << "both leading and trailing descriptions";
d = parse_trailing_description (t, tt);
break;
@@ -778,7 +704,7 @@ namespace build2
{
switch (lt)
{
- case line_type::variable:
+ case line_type::var:
{
// If there is a semicolon after the variable then we assume
// it is part of a test (there is no reason to use semicolons
@@ -802,7 +728,9 @@ namespace build2
// Fall through.
}
- case line_type::command:
+ case line_type::cmd:
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
{
switch (st)
{
@@ -855,15 +783,25 @@ namespace build2
}
break;
}
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_else:
+ case line_type::cmd_end:
+ {
+ fail (ll) << lt << " without preceding 'if'";
+ }
}
}
ls->push_back (move (l));
+ if (lt == line_type::cmd_if || lt == line_type::cmd_ifn)
+ semi = pre_parse_if_else (t, tt, d, *ls);
+
// If this command ended with a semicolon, then the next one should
// go to the same place.
//
- if (semi)
+ if (semi && !one)
{
mode (lexer_mode::first_token);
tt = peek ();
@@ -876,13 +814,13 @@ namespace build2
case type::eos:
case type::rcbrace:
case type::lcbrace:
- fail (ll) << "expected another line after semicolon";
+ fail (ll) << "expected another line after ';'";
case type::plus:
fail (ll) << "setup command in test";
case type::minus:
fail (ll) << "teardown command in test";
default:
- pre_parse_line (t, tt, d, ls);
+ semi = pre_parse_line (t, tt, d, ls);
assert (tt == type::newline); // End of last test line.
}
}
@@ -908,7 +846,317 @@ namespace build2
p->end_loc_ = get_location (t);
group_->scopes.push_back (move (p));
+ }
+
+ return semi;
+ }
+
+ void parser::
+ parse_lines (lines::iterator i, lines::iterator e, size_t& li, bool test)
+ {
+ token t;
+ type tt;
+
+ auto parse_cmd = [&t, &tt, this] () -> command_expr
+ {
+ pair<command_expr, here_docs> p (parse_command_line (t, tt));
+
+ switch (tt)
+ {
+ case type::colon: parse_trailing_description (t, tt); break;
+ case type::semi: next (t, tt); break; // Get newline.
+ }
+
+ assert (tt == type::newline);
+
+ parse_here_documents (t, tt, p);
+ assert (tt == type::newline);
+
+ return move (p.first);
+ };
+
+ for (; i != e; ++i)
+ {
+ line& l (*i);
+ line_type lt (l.type);
+
+ assert (path_ == nullptr);
+ replay_data (move (l.tokens)); // Set the tokens and start playing.
+
+ // We don't really need to change the mode since we already know
+ // the line type.
+ //
+ next (t, tt);
+ const location ll (get_location (t));
+
+ switch (lt)
+ {
+ case line_type::var:
+ {
+ // Parse.
+ //
+ string name (move (t.value));
+
+ next (t, tt);
+ type kind (tt); // Assignment kind.
+
+ value rhs (parse_variable_line (t, tt));
+
+ if (tt == type::semi)
+ next (t, tt);
+ assert (tt == type::newline);
+
+ // Assign.
+ //
+ const variable& var (script_->var_pool.insert (move (name)));
+
+ value& lhs (kind == type::assign
+ ? scope_->assign (var)
+ : scope_->append (var));
+
+ // @@ Need to adjust to make strings the default type.
+ //
+ apply_value_attributes (&var, lhs, move (rhs), kind);
+
+ // Handle the $*, $NN special aliases.
+ //
+ // The plan is as follows: here we detect modification of the
+ // source variables (test*), and (re)set $* to NULL on this
+ // scope (this is important to both invalidate any old values
+ // but also to "stake" the lookup position). This signals to
+ // the variable lookup function below that the $* and $NN
+ // values need to be recalculated from their sources. Note
+ // that we don't need to invalidate $NN since their lookup
+ // always checks $* first.
+ //
+ if (var.name == script_->test_var.name ||
+ var.name == script_->opts_var.name ||
+ var.name == script_->args_var.name)
+ {
+ scope_->assign (script_->cmd_var) = nullptr;
+ }
+
+ replay_stop ();
+ break;
+ }
+ case line_type::cmd:
+ {
+ // We use the 0 index to signal that this is the only command.
+ // Note that we only do this for test commands.
+ //
+ if (test && li == 0)
+ {
+ lines::iterator j (i);
+ for (++j; j != e && j->type == line_type::var; ++j) ;
+
+ if (j != e) // We have another command.
+ ++li;
+ }
+ else
+ ++li;
+
+ command_expr ce (parse_cmd ());
+ runner_->run (*scope_, ce, li, ll);
+
+ replay_stop ();
+ break;
+ }
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_else:
+ {
+ next (t, tt); // Skip to start of command.
+
+ bool take;
+ if (lt != line_type::cmd_else)
+ {
+ // Assume if-else always involves multiple commands.
+ //
+ command_expr ce (parse_cmd ());
+ take = runner_->run_if (*scope_, ce, ++li, ll);
+
+ if (lt == line_type::cmd_ifn || lt == line_type::cmd_elifn)
+ take = !take;
+ }
+ else
+ {
+ assert (tt == type::newline);
+ take = true;
+ }
+
+ replay_stop ();
+
+ // If end is true, then find the 'end' line. Otherwise, find the
+ // next if-else line. If skip is true then increment the command
+ // line index.
+ //
+ auto next = [e, &li]
+ (lines::iterator j, bool end, bool skip) -> lines::iterator
+ {
+ // We need to be aware of nested if-else chains.
+ //
+ size_t n (0);
+
+ for (++j; j != e; ++j)
+ {
+ line_type lt (j->type);
+
+ if (lt == line_type::cmd_if ||
+ lt == line_type::cmd_ifn)
+ ++n;
+
+ // If we are nested then we just wait until we get back to
+ // the surface.
+ //
+ if (n == 0)
+ {
+ switch (lt)
+ {
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_else: if (end) break; // Fall through.
+ case line_type::cmd_end: return j;
+ default: break;
+ }
+ }
+
+ if (lt == line_type::cmd_end)
+ --n;
+
+ if (skip)
+ {
+ // Note that we don't count else and end as commands.
+ //
+ switch (lt)
+ {
+ case line_type::cmd:
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn: ++li; break;
+ default: break;
+ }
+ }
+ }
+
+ assert (false); // Missing end.
+ return e;
+ };
+
+ // If we are taking this branch then we need to parse all the
+ // lines until the next if-else line and then skip all the lines
+ // until the end (unless next is already end).
+ //
+ // Otherwise, we need to skip all the lines until the next
+ // if-else line and then continue parsing.
+ //
+ if (take)
+ {
+ lines::iterator j (next (i, false, false)); // Next if-else.
+ parse_lines (i + 1, j, li, test);
+ i = j->type == line_type::cmd_end ? j : next (j, true, true);
+ }
+ else
+ {
+ i = next (i, false, true);
+ if (i->type != line_type::cmd_end)
+ --i; // Continue with this line (e.g., elif or else).
+ }
+
+ break;
+ }
+ case line_type::cmd_end:
+ {
+ assert (false);
+ }
+ }
+ }
+ }
+
+ bool parser::
+ pre_parse_if_else (token& t, type& tt,
+ optional<description>& d,
+ lines& ls)
+ {
+ // Parse lines until we see closing 'end'. Nested if-else blocks are
+ // handled recursively.
+ //
+ for (line_type bt (line_type::cmd_if);;) // Current block.
+ {
+ mode (lexer_mode::first_token);
+ tt = peek ();
+ const location ll (get_location (peeked ()));
+
+ switch (tt)
+ {
+ case type::colon:
+ fail (ll) << "description inside " << bt;
+ case type::eos:
+ case type::rcbrace:
+ case type::lcbrace:
+ fail (ll) << "expected closing 'end'";
+ case type::plus:
+ fail (ll) << "setup command inside " << bt;
+ case type::minus:
+ fail (ll) << "teardown command inside " << bt;
+ }
+
+ // Parse one line. Note that this one line can still be multiple
+ // lines in case of if-else. In this case we want to view it as
+ // cmd_if, not cmd_end. Thus remember the start position of the
+ // next logical line.
+ //
+ size_t i (ls.size ());
+
+ optional<description> td;
+ bool semi (pre_parse_line (t, tt, td, &ls, true));
+ assert (tt == type::newline);
+
+ line_type lt (ls[i].type);
+
+ // First take care of 'end'.
+ //
+ if (lt == line_type::cmd_end)
+ {
+ if (d && td)
+ fail (ll) << "both leading and trailing descriptions";
+
+ d = move (td);
+ return semi;
+ }
+
+ // For any other line trailing semi or description is illegal.
+ //
+ // @@ Not the exact location of semi/colon.
+ //
+ if (semi)
+ fail (ll) << "';' inside " << bt;
+
+ if (td)
+ fail (ll) << "description inside " << bt;
+
+ // Check if-else block sequencing.
+ //
+ if (bt == line_type::cmd_else)
+ {
+ if (lt == line_type::cmd_else ||
+ lt == line_type::cmd_elif ||
+ lt == line_type::cmd_elifn)
+ fail (ll) << lt << " after " << bt;
+ }
+
+ // Update current if-else block.
+ //
+ switch (lt)
+ {
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn: bt = line_type::cmd_elif; break;
+ case line_type::cmd_else: bt = line_type::cmd_else; break;
+ default: break;
+ }
}
}
@@ -2051,7 +2299,7 @@ namespace build2
// Handle the $*, $NN special aliases.
//
- // See the parse_scope_body() for the overall plan.
+ // See the parse_lines() for the overall plan.
//
// @@ MT: we are potentially changing outer scopes. Could force
// lookup before executing tests in each group scope. Poblem is
diff --git a/build2/test/script/runner b/build2/test/script/runner
index b78628c..266835d 100644
--- a/build2/test/script/runner
+++ b/build2/test/script/runner
@@ -37,6 +37,9 @@ namespace build2
virtual void
run (scope&, const command_expr&, size_t index, const location&) = 0;
+ virtual bool
+ run_if (scope&, const command_expr&, size_t, const location&) = 0;
+
// Location is the scope end location (for diagnostics, etc).
//
virtual void
@@ -52,6 +55,9 @@ namespace build2
virtual void
run (scope&, const command_expr&, size_t, const location&) override;
+ virtual bool
+ run_if (scope&, const command_expr&, size_t, const location&) override;
+
virtual void
leave (scope&, const location&) override;
};
diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx
index 8db31d9..1d6920a 100644
--- a/build2/test/script/runner.cxx
+++ b/build2/test/script/runner.cxx
@@ -702,6 +702,13 @@ namespace build2
check_output (p, osp, isp, c.out, ll, sp, "stdout");
check_output (p, esp, isp, c.err, ll, sp, "stderr");
}
+
+ bool concurrent_runner::
+ run_if (scope&, const command_expr& expr, size_t, const location&)
+ {
+ const command& c (expr.back ().pipe.back ()); // @@ TMP
+ return c.program.string () == "true"; // @@ TMP
+ }
}
}
}
diff --git a/build2/test/script/script b/build2/test/script/script
index a2e4c41..93ae661 100644
--- a/build2/test/script/script
+++ b/build2/test/script/script
@@ -29,7 +29,20 @@ namespace build2
// Pre-parse representation.
//
- enum class line_type {variable, command};
+ enum class line_type
+ {
+ var,
+ cmd,
+ cmd_if,
+ cmd_ifn,
+ cmd_elif,
+ cmd_elifn,
+ cmd_else,
+ cmd_end
+ };
+
+ ostream&
+ operator<< (ostream&, line_type);
struct line
{
diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx
index 7d1d8cf..b128077 100644
--- a/build2/test/script/script.cxx
+++ b/build2/test/script/script.cxx
@@ -17,8 +17,26 @@ namespace build2
{
namespace script
{
- // Utility functions
- //
+ ostream&
+ operator<< (ostream& o, line_type lt)
+ {
+ const char* s (nullptr);
+
+ switch (lt)
+ {
+ case line_type::var: s = "variable"; break;
+ case line_type::cmd: s = "command"; break;
+ case line_type::cmd_if: s = "'if'"; break;
+ case line_type::cmd_ifn: s = "'if!'"; break;
+ case line_type::cmd_elif: s = "'elif'"; break;
+ case line_type::cmd_elifn: s = "'elif!'"; break;
+ case line_type::cmd_else: s = "'else'"; break;
+ case line_type::cmd_end: s = "'end'"; break;
+ }
+
+ return o << s;
+ }
+
// Quote if empty or contains spaces or any of the special characters.
//
// @@ What if it contains quotes, escapes?
diff --git a/unit-tests/test/script/parser/buildfile b/unit-tests/test/script/parser/buildfile
index f87f7e8..eab5b36 100644
--- a/unit-tests/test/script/parser/buildfile
+++ b/unit-tests/test/script/parser/buildfile
@@ -12,6 +12,7 @@ test/{target script/{token lexer parser script}}
exe{driver}: cxx{driver} ../../../../build2/cxx{$src} $libs \
test{cleanup command-re-parse description exit expansion here-document \
- here-string include pipe-expr pre-parse redirect scope setup-teardown}
+ here-string if-else include pipe-expr pre-parse redirect scope \
+ setup-teardown}
include ../../../../build2/
diff --git a/unit-tests/test/script/parser/description.test b/unit-tests/test/script/parser/description.test
index c2c441b..48a2faf 100644
--- a/unit-tests/test/script/parser/description.test
+++ b/unit-tests/test/script/parser/description.test
@@ -172,7 +172,7 @@ $* <<EOI 2>>EOE != 0 # both
: foo
cmd : bar
EOI
-testscript:2:1: error: both leading and trailing description
+testscript:2:1: error: both leading and trailing descriptions
EOE
# Legal places for a description.
diff --git a/unit-tests/test/script/parser/driver.cxx b/unit-tests/test/script/parser/driver.cxx
index badb658..de34da7 100644
--- a/unit-tests/test/script/parser/driver.cxx
+++ b/unit-tests/test/script/parser/driver.cxx
@@ -30,7 +30,8 @@ namespace build2
class print_runner: public runner
{
public:
- print_runner (bool scope, bool id): scope_ (scope), id_ (id) {}
+ print_runner (bool scope, bool id, bool line)
+ : scope_ (scope), id_ (id), line_ (line) {}
virtual void
enter (scope& s, const location&) override
@@ -81,9 +82,33 @@ namespace build2
}
virtual void
- run (scope&, const command_expr& e, size_t, const location&) override
+ run (scope&,
+ const command_expr& e,
+ size_t i,
+ const location&) override
{
- cout << ind_ << e << endl;
+ cout << ind_ << e;
+
+ if (line_)
+ cout << " # " << i;
+
+ cout << endl;
+ }
+
+ virtual bool
+ run_if (scope&,
+ const command_expr& e,
+ size_t i,
+ const location&) override
+ {
+ cout << ind_ << "? " << e;
+
+ if (line_)
+ cout << " # " << i;
+
+ cout << endl;
+
+ return e.back ().pipe.back ().program.string () == "true";
}
virtual void
@@ -99,10 +124,11 @@ namespace build2
private:
bool scope_;
bool id_;
+ bool line_;
string ind_;
};
- // Usage: argv[0] [-s] [-i] [<testscript-name>]
+ // Usage: argv[0] [-s] [-i] [-l] [<testscript-name>]
//
int
main (int argc, char* argv[])
@@ -114,6 +140,7 @@ namespace build2
bool scope (false);
bool id (false);
+ bool line (false);
path name;
for (int i (1); i != argc; ++i)
@@ -124,6 +151,8 @@ namespace build2
scope = true;
else if (a == "-i")
id = true;
+ else if (a == "-l")
+ line = true;
else
{
name = path (move (a));
@@ -168,7 +197,7 @@ namespace build2
script s (tt, st, dir_path (work) /= "test-driver");
p.pre_parse (cin, s);
- print_runner r (scope, id);
+ print_runner r (scope, id, line);
p.parse (s, r);
}
catch (const failed&)
diff --git a/unit-tests/test/script/parser/if-else.test b/unit-tests/test/script/parser/if-else.test
new file mode 100644
index 0000000..8663920
--- /dev/null
+++ b/unit-tests/test/script/parser/if-else.test
@@ -0,0 +1,518 @@
+: if-true
+:
+$* <<EOI >>EOO
+if true foo
+ cmd1
+ cmd2
+end
+EOI
+? true foo
+cmd1
+cmd2
+EOO
+
+: if-false
+:
+$* <<EOI >>EOO
+if false foo
+ cmd1
+ cmd2
+end
+EOI
+? false foo
+EOO
+
+: ifn-true
+:
+$* <<EOI >>EOO
+if! true foo
+ cmd1
+ cmd2
+end
+EOI
+? true foo
+EOO
+
+: ifn-false
+:
+$* <<EOI >>EOO
+if! false foo
+ cmd1
+ cmd2
+end
+EOI
+? false foo
+cmd1
+cmd2
+EOO
+
+: elif-true
+:
+$* <<EOI >>EOO
+if false
+ cmd1
+ cmd2
+elif true
+ cmd3
+ cmd4
+end
+EOI
+? false
+? true
+cmd3
+cmd4
+EOO
+
+: elif-false
+:
+$* <<EOI >>EOO
+if false
+ cmd1
+ cmd2
+elif false
+ cmd3
+ cmd4
+end
+EOI
+? false
+? false
+EOO
+
+: elifn-true
+:
+$* <<EOI >>EOO
+if false
+ cmd1
+ cmd2
+elif! true
+ cmd3
+ cmd4
+end
+EOI
+? false
+? true
+EOO
+
+: elifn-false
+:
+$* <<EOI >>EOO
+if false
+ cmd1
+ cmd2
+elif! false
+ cmd3
+ cmd4
+end
+EOI
+? false
+? false
+cmd3
+cmd4
+EOO
+
+: else-true
+:
+$* <<EOI >>EOO
+if false
+ cmd1
+ cmd2
+else
+ cmd3
+ cmd4
+end
+EOI
+? false
+cmd3
+cmd4
+EOO
+
+: else-false
+:
+$* <<EOI >>EOO
+if true
+ cmd1
+ cmd2
+else
+ cmd3
+ cmd4
+end
+EOI
+? true
+cmd1
+cmd2
+EOO
+
+: if-chain
+:
+$* <<EOI >>EOO
+if false
+ cmd
+ cmd
+elif false
+ cmd
+ cmd
+elif false
+ cmd
+ cmd
+elif true
+ cmd1
+ cmd2
+elif false
+ cmd
+ cmd
+else
+ cmd
+ cmd
+end
+EOI
+? false
+? false
+? false
+? true
+cmd1
+cmd2
+EOO
+
+: nested-take
+:
+$* <<EOI >>EOO
+if true
+ cmd1
+ if false
+ cmd
+ elif false
+ if true
+ cmd
+ end
+ else
+ cmd2
+ end
+ cmd3
+end
+EOI
+? true
+cmd1
+? false
+? false
+cmd2
+cmd3
+EOO
+
+: nested-skip
+:
+$* <<EOI >>EOO
+if false
+ cmd1
+ if false
+ cmd
+ elif false
+ if true
+ cmd
+ end
+ else
+ cmd2
+ end
+ cmd3
+else
+ cmd
+end
+EOI
+? false
+cmd
+EOO
+
+: line-index
+:
+$* -l <<EOI >>EOO
+if false
+ cmd
+ if true
+ cmd
+ end
+ cmd
+elif false
+ cmd
+else
+ cmd
+end
+EOI
+? false # 1
+? false # 6
+cmd # 8
+EOO
+
+: var
+:
+$* <<EOI >>EOO
+if true
+ x = foo
+else
+ x = bar
+end;
+cmd \$x
+EOI
+? true
+cmd foo
+EOO
+
+: semi-inside
+:
+$* <<EOI 2>>EOE != 0
+if
+ cmd;
+ cmd
+end
+EOI
+testscript:2:3: error: ';' inside 'if'
+EOE
+
+: colon-inside-leading
+:
+$* <<EOI 2>>EOE != 0
+if
+ : foo
+ cmd
+end
+EOI
+testscript:2:3: error: description inside 'if'
+EOE
+
+: colon-inside-trailing
+:
+$* <<EOI 2>>EOE != 0
+if
+ cmd : foo
+end
+EOI
+testscript:2:3: error: description inside 'if'
+EOE
+
+: eos-inside
+:
+$* <<EOI 2>>EOE != 0
+if
+EOI
+testscript:2:1: error: expected closing 'end'
+EOE
+
+: scope-inside
+:
+$* <<EOI 2>>EOE != 0
+if
+ {
+ }
+end
+EOI
+testscript:2:3: error: expected closing 'end'
+EOE
+
+: setup-inside
+:
+$* <<EOI 2>>EOE != 0
+if
+ +cmd
+end
+EOI
+testscript:2:3: error: setup command inside 'if'
+EOE
+
+: tdown-inside
+:
+$* <<EOI 2>>EOE != 0
+if
+ -cmd
+end
+EOI
+testscript:2:3: error: teardown command inside 'if'
+EOE
+
+: if-without-command
+:
+$* <<EOI 2>>EOE != 0
+if
+ cmd
+end
+EOI
+testscript:1:3: error: missing program
+EOE
+
+: command-after-else
+:
+$* <<EOI 2>>EOE != 0
+if true
+ cmd
+else cmd
+ cmd
+end
+EOI
+testscript:3:6: error: expected newline instead of 'cmd'
+EOE
+
+: command-after-end
+:
+$* <<EOI 2>>EOE != 0
+if true
+ cmd
+end cmd
+EOI
+testscript:3:5: error: expected newline instead of 'cmd'
+EOE
+
+: elif-without-if
+:
+$* <<EOI 2>>EOE != 0
+cmd
+elif true
+ cmd
+end
+EOI
+testscript:2:1: error: 'elif' without preceding 'if'
+EOE
+
+: elifn-without-if
+:
+$* <<EOI 2>>EOE != 0
+cmd
+elif! true
+ cmd
+end
+EOI
+testscript:2:1: error: 'elif!' without preceding 'if'
+EOE
+
+: else-without-if
+:
+$* <<EOI 2>>EOE != 0
+cmd
+else
+ cmd
+end
+EOI
+testscript:2:1: error: 'else' without preceding 'if'
+EOE
+
+: end-without-if
+:
+$* <<EOI 2>>EOE != 0
+cmd
+end
+EOI
+testscript:2:1: error: 'end' without preceding 'if'
+EOE
+
+: else-after-else
+:
+$* <<EOI 2>>EOE != 0
+if false
+ cmd
+else
+ cmd
+else
+ cmd
+end
+EOI
+testscript:5:1: error: 'else' after 'else'
+EOE
+
+: elif-after-else
+:
+$* <<EOI 2>>EOE != 0
+if false
+ cmd
+else
+ cmd
+elif true
+ cmd
+end
+EOI
+testscript:5:1: error: 'elif' after 'else'
+EOE
+
+: if-after-semi
+:
+$* -s <<EOI >>EOO
+cmd1;
+if true
+ cmd2
+end
+EOI
+{
+ {
+ cmd1
+ ? true
+ cmd2
+ }
+}
+EOO
+
+: setup-if
+:
+$* -s <<EOI >>EOO
++if true
+ cmd
+end
+EOI
+{
+ ? true
+ cmd
+}
+EOO
+
+: tdown-if
+:
+$* -s <<EOI >>EOO
+-if true
+ cmd
+end
+EOI
+{
+ ? true
+ cmd
+}
+EOO
+
+: semi-after-end
+:
+$* -s <<EOI >>EOO
+if true
+ cmd1
+end;
+cmd2
+EOI
+{
+ {
+ ? true
+ cmd1
+ cmd2
+ }
+}
+EOO
+
+: colon-after-end
+:
+$* -s <<EOI >>EOO
+if true
+ cmd1
+ cmd2
+end : test
+EOI
+{
+ : id:test
+ {
+ ? true
+ cmd1
+ cmd2
+ }
+}
+EOO
+
+: leading-and-trailing-description
+:
+$* <<EOI 2>>EOE != 0
+: foo
+if true
+ cmd
+end : bar
+EOI
+testscript:4:1: error: both leading and trailing descriptions
+EOE
diff --git a/unit-tests/test/script/parser/scope.test b/unit-tests/test/script/parser/scope.test
index 38b6a76..8708b19 100644
--- a/unit-tests/test/script/parser/scope.test
+++ b/unit-tests/test/script/parser/scope.test
@@ -123,7 +123,7 @@ $* <<EOI 2>>EOE != 0 # expected-line-rcbrace
cmd;
}
EOI
-testscript:3:1: error: expected another line after semicolon
+testscript:3:1: error: expected another line after ';'
EOE
$* <<:EOI 2>>EOE != 0 # expected-newline-rcbrace
@@ -217,7 +217,7 @@ EOE
$* <<EOI 2>>EOE != 0 # expected-line-eof
cmd;
EOI
-testscript:2:1: error: expected another line after semicolon
+testscript:2:1: error: expected another line after ';'
EOE
$* <<:EOI 2>>EOE != 0 # expected-newline-cmd