From f4b2107284329b12f595eba856072b79b824a63f Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 8 Nov 2016 16:37:44 +0200 Subject: Implement testscript command-if --- build2/test/script/parser | 13 +- build2/test/script/parser.cxx | 524 ++++++++++++++++++------- build2/test/script/runner | 6 + build2/test/script/runner.cxx | 7 + build2/test/script/script | 15 +- build2/test/script/script.cxx | 22 +- unit-tests/test/script/parser/buildfile | 3 +- unit-tests/test/script/parser/description.test | 2 +- unit-tests/test/script/parser/driver.cxx | 39 +- unit-tests/test/script/parser/if-else.test | 518 ++++++++++++++++++++++++ unit-tests/test/script/parser/scope.test | 4 +- 11 files changed, 1001 insertions(+), 152 deletions(-) create mode 100644 unit-tests/test/script/parser/if-else.test 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&, - 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&, + 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 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 (scope_)) { - play (t->tests_, true); + parse_lines (t->tests_.begin (), t->tests_.end (), li, true); } else if (group* g = dynamic_cast (scope_)) { - play (g->setup_, false); + parse_lines (g->setup_.begin (), g->setup_.end (), li, false); for (const unique_ptr& 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& 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& 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 p (parse_command_line (t, tt)); + pair 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 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& 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 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 @@ $* <>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] [] + // Usage: argv[0] [-s] [-i] [-l] [] // 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 +: +$* <>EOO +if true foo + cmd1 + cmd2 +end +EOI +? true foo +cmd1 +cmd2 +EOO + +: if-false +: +$* <>EOO +if false foo + cmd1 + cmd2 +end +EOI +? false foo +EOO + +: ifn-true +: +$* <>EOO +if! true foo + cmd1 + cmd2 +end +EOI +? true foo +EOO + +: ifn-false +: +$* <>EOO +if! false foo + cmd1 + cmd2 +end +EOI +? false foo +cmd1 +cmd2 +EOO + +: elif-true +: +$* <>EOO +if false + cmd1 + cmd2 +elif true + cmd3 + cmd4 +end +EOI +? false +? true +cmd3 +cmd4 +EOO + +: elif-false +: +$* <>EOO +if false + cmd1 + cmd2 +elif false + cmd3 + cmd4 +end +EOI +? false +? false +EOO + +: elifn-true +: +$* <>EOO +if false + cmd1 + cmd2 +elif! true + cmd3 + cmd4 +end +EOI +? false +? true +EOO + +: elifn-false +: +$* <>EOO +if false + cmd1 + cmd2 +elif! false + cmd3 + cmd4 +end +EOI +? false +? false +cmd3 +cmd4 +EOO + +: else-true +: +$* <>EOO +if false + cmd1 + cmd2 +else + cmd3 + cmd4 +end +EOI +? false +cmd3 +cmd4 +EOO + +: else-false +: +$* <>EOO +if true + cmd1 + cmd2 +else + cmd3 + cmd4 +end +EOI +? true +cmd1 +cmd2 +EOO + +: if-chain +: +$* <>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 +: +$* <>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 +: +$* <>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 <>EOO +if false + cmd + if true + cmd + end + cmd +elif false + cmd +else + cmd +end +EOI +? false # 1 +? false # 6 +cmd # 8 +EOO + +: var +: +$* <>EOO +if true + x = foo +else + x = bar +end; +cmd \$x +EOI +? true +cmd foo +EOO + +: semi-inside +: +$* <>EOE != 0 +if + cmd; + cmd +end +EOI +testscript:2:3: error: ';' inside 'if' +EOE + +: colon-inside-leading +: +$* <>EOE != 0 +if + : foo + cmd +end +EOI +testscript:2:3: error: description inside 'if' +EOE + +: colon-inside-trailing +: +$* <>EOE != 0 +if + cmd : foo +end +EOI +testscript:2:3: error: description inside 'if' +EOE + +: eos-inside +: +$* <>EOE != 0 +if +EOI +testscript:2:1: error: expected closing 'end' +EOE + +: scope-inside +: +$* <>EOE != 0 +if + { + } +end +EOI +testscript:2:3: error: expected closing 'end' +EOE + +: setup-inside +: +$* <>EOE != 0 +if + +cmd +end +EOI +testscript:2:3: error: setup command inside 'if' +EOE + +: tdown-inside +: +$* <>EOE != 0 +if + -cmd +end +EOI +testscript:2:3: error: teardown command inside 'if' +EOE + +: if-without-command +: +$* <>EOE != 0 +if + cmd +end +EOI +testscript:1:3: error: missing program +EOE + +: command-after-else +: +$* <>EOE != 0 +if true + cmd +else cmd + cmd +end +EOI +testscript:3:6: error: expected newline instead of 'cmd' +EOE + +: command-after-end +: +$* <>EOE != 0 +if true + cmd +end cmd +EOI +testscript:3:5: error: expected newline instead of 'cmd' +EOE + +: elif-without-if +: +$* <>EOE != 0 +cmd +elif true + cmd +end +EOI +testscript:2:1: error: 'elif' without preceding 'if' +EOE + +: elifn-without-if +: +$* <>EOE != 0 +cmd +elif! true + cmd +end +EOI +testscript:2:1: error: 'elif!' without preceding 'if' +EOE + +: else-without-if +: +$* <>EOE != 0 +cmd +else + cmd +end +EOI +testscript:2:1: error: 'else' without preceding 'if' +EOE + +: end-without-if +: +$* <>EOE != 0 +cmd +end +EOI +testscript:2:1: error: 'end' without preceding 'if' +EOE + +: else-after-else +: +$* <>EOE != 0 +if false + cmd +else + cmd +else + cmd +end +EOI +testscript:5:1: error: 'else' after 'else' +EOE + +: elif-after-else +: +$* <>EOE != 0 +if false + cmd +else + cmd +elif true + cmd +end +EOI +testscript:5:1: error: 'elif' after 'else' +EOE + +: if-after-semi +: +$* -s <>EOO +cmd1; +if true + cmd2 +end +EOI +{ + { + cmd1 + ? true + cmd2 + } +} +EOO + +: setup-if +: +$* -s <>EOO ++if true + cmd +end +EOI +{ + ? true + cmd +} +EOO + +: tdown-if +: +$* -s <>EOO +-if true + cmd +end +EOI +{ + ? true + cmd +} +EOO + +: semi-after-end +: +$* -s <>EOO +if true + cmd1 +end; +cmd2 +EOI +{ + { + ? true + cmd1 + cmd2 + } +} +EOO + +: colon-after-end +: +$* -s <>EOO +if true + cmd1 + cmd2 +end : test +EOI +{ + : id:test + { + ? true + cmd1 + cmd2 + } +} +EOO + +: leading-and-trailing-description +: +$* <>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 @@ $* <>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 $* <>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 -- cgit v1.1