From 31e16a7413813293e3cccb6799eaa08b7af5af4e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sat, 22 Oct 2016 11:59:20 +0200 Subject: Implement support for compound tests --- build2/test/script/lexer.cxx | 26 ++++++-- build2/test/script/parser | 8 +-- build2/test/script/parser.cxx | 146 +++++++++++++++++++++++++++++++----------- build2/test/script/script | 10 ++- build2/test/script/token | 4 +- build2/test/script/token.cxx | 2 + 6 files changed, 144 insertions(+), 52 deletions(-) (limited to 'build2') diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx index 5061a1d..2192f35 100644 --- a/build2/test/script/lexer.cxx +++ b/build2/test/script/lexer.cxx @@ -26,24 +26,24 @@ namespace build2 { case lexer_mode::script_line: { - s1 = "=!|&<> $(#\t\n"; - s2 = "== "; + s1 = ";=!|&<> $(#\t\n"; + s2 = " == "; break; } case lexer_mode::assign_line: { // As script_line but with variable assignments. // - s1 = "=+!|&<> $(#\t\n"; - s2 = " == "; + s1 = ";=+!|&<> $(#\t\n"; + s2 = " == "; break; } case lexer_mode::variable_line: { - // Like value except we don't recognize {. + // Like value except we recognize ';' and don't recognize '{'. // - s1 = " $([]#\t\n"; - s2 = " "; + s1 = "; $([]#\t\n"; + s2 = " "; break; } @@ -159,6 +159,18 @@ namespace build2 } } + // Line separators. + // + if (m == lexer_mode::script_line || + m == lexer_mode::assign_line || + m == lexer_mode::variable_line) + { + switch (c) + { + case ';': return make_token (type::semi); + } + } + // Command line operator/separators. // if (m == lexer_mode::script_line || m == lexer_mode::assign_line) diff --git a/build2/test/script/parser b/build2/test/script/parser index df79cf6..39ec4f8 100644 --- a/build2/test/script/parser +++ b/build2/test/script/parser @@ -54,16 +54,16 @@ namespace build2 void parse_script (); - line_type + pair pre_parse_script_line (token&, token_type&); void parse_script_line (token&, token_type&, line_type, size_t); - void + bool parse_variable_line (token&, token_type&); - void + bool parse_test_line (token&, token_type&, size_t); command_exit @@ -100,7 +100,7 @@ namespace build2 // Pre-parse state. // group* group_; - test* test_; + lines* lines_; // Parse state. // diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx index 1448e7a..510b8cc 100644 --- a/build2/test/script/parser.cxx +++ b/build2/test/script/parser.cxx @@ -22,6 +22,8 @@ namespace build2 { path_ = &p; + pre_parse_ = true; + lexer l (is, *path_, lexer_mode::script_line); lexer_ = &l; base_parser::lexer_ = &l; @@ -30,10 +32,9 @@ namespace build2 runner_ = nullptr; group_ = script_; - test_ = nullptr; - scope_ = nullptr; + lines_ = nullptr; - pre_parse_ = true; + scope_ = nullptr; pre_parse_script (); } @@ -43,6 +44,8 @@ namespace build2 { path_ = &p; + pre_parse_ = false; + lexer_ = nullptr; base_parser::lexer_ = nullptr; @@ -50,10 +53,9 @@ namespace build2 runner_ = &r; group_ = nullptr; - test_ = nullptr; - scope_ = ≻ + lines_ = nullptr; - pre_parse_ = false; + scope_ = ≻ parse_script (); } @@ -81,47 +83,98 @@ namespace build2 if (tt == type::eos) { + // Check that we don't expect more lines. + // + // @@ Also for explicit scope close. + // + if (lines_ != nullptr) + fail (t) << "expected another line after semicolon"; + group_->end_loc_ = get_location (t); replay_stop (); // Discard replay of eos. break; } const location ll (get_location (t)); - line_type lt (pre_parse_script_line (t, tt)); + pair lt (pre_parse_script_line (t, tt)); assert (tt == type::newline); // Stop saving and get the tokens. // - line l {lt, replay_data ()}; + line l {lt.first, replay_data ()}; // Decide where it goes. // lines* ls (nullptr); - switch (lt) + + // If lines_ is not NULL then the previous command ended with a + // semicolon and we should add this one to the same place. + // + if (lines_ != nullptr) + ls = lines_; + else { - case line_type::variable: + switch (lt.first) { - ls = &group_->setup_; - break; - } - case line_type::test: - { - // Create implicit test scope. Use line number as the scope id. - // - group_->scopes.push_back ( - unique_ptr ( - (test_ = new test (to_string (ll.line), *group_)))); + case line_type::variable: + { + // If there is a semicolon after the variable then we assume + // it is part of a test (there is no reason to use semicolons + // after variables in the group scope). + // + if (!lt.second) + { + // If we don't have any nested scopes or teardown commands, + // then we assume this is a setup, otherwise -- teardown. + // + ls = group_->scopes.empty () && group_->tdown_.empty () + ? &group_->setup_ + : &group_->tdown_; + + break; + } - test_->start_loc_ = ll; - test_->end_loc_ = get_location (t); + // Fall through. + } + case line_type::test: + { + // First check that we don't have any teardown commands yet. + // This will detect things like variable assignments between + // tests. + // + // @@ Can the teardown line be from a different file? + // + if (!group_->tdown_.empty ()) + { + location tl ( + get_location ( + group_->tdown_.back ().tokens.front ().token)); + + fail (ll) << "test after teardown" << + info (tl) << "last teardown line appears here"; + } - ls = &test_->tests_; + // Create implicit test scope. Use line number as the scope id. + // + unique_ptr p (new test (to_string (ll.line), *group_)); + + p->start_loc_ = ll; + p->end_loc_ = get_location (t); - test_ = nullptr; + ls = &p->tests_; + + group_->scopes.push_back (move (p)); + break; + } } } ls->push_back (move (l)); + + // If this command ended with a semicolon, then the next one should + // go to the same place. + // + lines_ = lt.second ? ls : nullptr; } } @@ -144,6 +197,9 @@ namespace build2 // next (t, tt); + // @@ The index counts variable assignment lines. This is probably + // not what we want. + // parse_script_line (t, tt, l.type, n == 1 ? 0 : i + 1); assert (tt == type::newline); @@ -153,14 +209,14 @@ namespace build2 runner_->enter (*scope_, scope_->start_loc_); - play (scope_->setup_); - if (test* t = dynamic_cast (scope_)) { play (t->tests_); } else if (group* g = dynamic_cast (scope_)) { + play (g->setup_); + for (const unique_ptr& s: g->scopes) { // Hand it off to a sub-parser potentially in another thread. But @@ -174,16 +230,16 @@ namespace build2 parser p; p.parse (*s, *path_, *script_, *runner_); } + + play (g->tdown_); } else assert (false); - play (scope_->tdown_); - runner_->leave (*scope_, scope_->end_loc_); } - line_type parser:: + pair parser:: pre_parse_script_line (token& t, type& tt) { // Decide whether this is a variable assignment or a command. It is a @@ -202,13 +258,12 @@ namespace build2 if (p == type::assign || p == type::prepend || p == type::append) { - parse_variable_line (t, tt); - return line_type::variable; + return make_pair (line_type::variable, + parse_variable_line (t, tt)); } } - parse_test_line (t, tt, 0); - return line_type::test; + return make_pair (line_type::test, parse_test_line (t, tt, 0)); } void parser:: @@ -234,7 +289,7 @@ namespace build2 return !s.empty (); } - void parser:: + bool parser:: parse_variable_line (token& t, type& tt) { string name (move (t.value)); @@ -262,12 +317,17 @@ namespace build2 // attributes_push (t, tt, true); - value rhs (tt != type::newline && tt != type::eos + value rhs (tt != type::newline && tt != type::semi ? parse_names_value (t, tt, "variable value", nullptr) : value (names ())); + bool semi (tt == type::semi); + + if (semi) + next (t, tt); // Get newline. + if (tt != type::newline) - fail (t) << "unexpected " << t; + fail (t) << "expected newline instead of " << t; if (!pre_parse_) { @@ -298,9 +358,11 @@ namespace build2 scope_->assign (script_->cmd_var) = nullptr; } } + + return semi; } - void parser:: + bool parser:: parse_test_line (token& t, type& tt, size_t li) { command c; @@ -506,6 +568,7 @@ namespace build2 { case type::equal: case type::not_equal: + case type::semi: case type::newline: { done = true; @@ -724,8 +787,13 @@ namespace build2 if (tt == type::equal || tt == type::not_equal) c.exit = parse_command_exit (t, tt); + bool semi (tt == type::semi); + + if (semi) + next (t, tt); // Get newline. + if (tt != type::newline) - fail (t) << "unexpected " << t; + fail (t) << "expected newline instead of " << t; // Parse here-document fragments in the order they were mentioned on // the command line. @@ -754,6 +822,8 @@ namespace build2 // if (!pre_parse_) runner_->run (*scope_, c, li, ll); + + return semi; } command_exit parser:: diff --git a/build2/test/script/script b/build2/test/script/script index ff9fa46..a7bdd7b 100644 --- a/build2/test/script/script +++ b/build2/test/script/script @@ -161,8 +161,6 @@ namespace build2 friend class parser; location start_loc_; - lines setup_; - lines tdown_; location end_loc_; }; @@ -176,6 +174,14 @@ namespace build2 protected: group (const string& id): scope (id, nullptr) {} // For root. + + // Pre-parse data. + // + private: + friend class parser; + + lines setup_; + lines tdown_; }; class test: public scope diff --git a/build2/test/script/token b/build2/test/script/token index c39811a..da468af 100644 --- a/build2/test/script/token +++ b/build2/test/script/token @@ -24,7 +24,9 @@ namespace build2 { // NOTE: remember to update token_printer()! - pipe = base_type::value_next, // | + semi = base_type::value_next, // ; + + pipe, // | clean, // & log_and, // && log_or, // || diff --git a/build2/test/script/token.cxx b/build2/test/script/token.cxx index 27e86d6..6ed0443 100644 --- a/build2/test/script/token.cxx +++ b/build2/test/script/token.cxx @@ -21,6 +21,8 @@ namespace build2 switch (t.type) { + case token_type::semi: os << q << ';' << q; break; + case token_type::pipe: os << q << '|' << q; break; case token_type::clean: os << q << '&' << q; break; case token_type::log_and: os << q << "&&" << q; break; -- cgit v1.1