aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-10-27 11:07:01 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-11-04 09:26:36 +0200
commitedd5e517be41effbe760f4e5b5743a431c8344e6 (patch)
tree3ec379eabc1abd83826fe7e6c59598740b49a7e7
parent722b5857aa431cad20735dcbf850e82f1eaf70e8 (diff)
Use recursion to parse compound tests
-rw-r--r--build2/test/script/parser4
-rw-r--r--build2/test/script/parser.cxx384
2 files changed, 200 insertions, 188 deletions
diff --git a/build2/test/script/parser b/build2/test/script/parser
index 0f215d1..1d8ecf3 100644
--- a/build2/test/script/parser
+++ b/build2/test/script/parser
@@ -54,6 +54,9 @@ namespace build2
void
parse_scope_body ();
+ void
+ pre_parse_line (token&, token_type&, lines* = nullptr);
+
bool
parse_variable_line (token&, token_type&);
@@ -94,7 +97,6 @@ namespace build2
// Pre-parse state.
//
group* group_;
- lines* lines_;
// Parse state.
//
diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx
index 2d9a065..ac042c5 100644
--- a/build2/test/script/parser.cxx
+++ b/build2/test/script/parser.cxx
@@ -30,10 +30,7 @@ namespace build2
script_ = &s;
runner_ = nullptr;
-
group_ = script_;
- lines_ = nullptr;
-
scope_ = nullptr;
// Start location of the implied script group is the beginning of the
@@ -46,11 +43,6 @@ namespace build2
if (t.type != type::eos)
fail (t) << "stray " << t;
- // Check that we don't expect more lines.
- //
- if (lines_ != nullptr)
- fail (t) << "expected another line after semicolon";
-
group_->end_loc_ = get_location (t);
}
@@ -66,10 +58,7 @@ namespace build2
script_ = &s;
runner_ = &r;
-
group_ = nullptr;
- lines_ = nullptr;
-
scope_ = &sc;
parse_scope_body ();
@@ -91,11 +80,7 @@ namespace build2
// Determine the line type by peeking at the first token.
//
- tt = peek ();
- const location ll (get_location (peeked ()));
-
- line_type lt;
- switch (tt)
+ switch (tt = peek ())
{
case type::eos:
case type::rcbrace:
@@ -108,18 +93,19 @@ namespace build2
// Nested scope.
//
next (t, tt); // Get '{'.
+ const location sl (get_location (t));
if (next (t, tt) != type::newline)
fail (t) << "expected newline after '{'";
// Push group. Use line number as the scope id.
//
- unique_ptr<group> g (new group (to_string (ll.line), *group_));
+ unique_ptr<group> g (new group (to_string (sl.line), *group_));
group* og (group_);
group_ = g.get ();
- group_->start_loc_ = ll;
+ group_->start_loc_ = sl;
token e (pre_parse_scope_body ());
group_->end_loc_ = get_location (e);
@@ -190,185 +176,18 @@ namespace build2
if (e.type != type::rcbrace)
fail (e) << "expected '}' at the end of the scope";
- // Check that we don't expect more lines.
- //
- if (lines_ != nullptr)
- fail (e) << "expected another line after semicolon";
-
if (next (t, tt) != type::newline)
fail (t) << "expected newline after '}'";
continue;
}
- case type::plus:
- case type::minus:
- {
- // Setup/teardown command.
- //
- lt = (tt == type::plus ? line_type::setup : line_type::tdown);
-
- next (t, tt); // Start saving tokens from the next one.
- replay_save ();
- next (t, tt);
- break;
- }
default:
{
- // Either a test command or a variable assignment.
- //
- replay_save (); // Start saving tokens from the current one.
- next (t, tt);
-
- // Decide whether this is a variable assignment or a command. It
- // is an assignment if the first token is an unquoted word and
- // the next is an assign/append/prepend operator. Assignment to
- // a computed variable name must use the set builtin.
- //
- if (tt == type::word && !t.quoted)
- {
- // Switch the recognition of leading variable assignments for
- // the next token. This is safe to do because we know we
- // cannot be in the quoted mode (since the current token is
- // not quoted).
- //
- mode (lexer_mode::second_token);
- type p (peek ());
-
- if (p == type::assign ||
- p == type::prepend ||
- p == type::append)
- {
- lt = line_type::variable;
- break;
- }
- }
-
- lt = line_type::test;
+ pre_parse_line (t, tt);
+ assert (tt == type::newline);
break;
}
}
-
- // Being here means we know the line type and token saving is on.
- // First we pre-parse the line keeping track of whether it ends with
- // a semicolon.
- //
- bool semi;
-
- switch (lt)
- {
- case line_type::variable:
- semi = parse_variable_line (t, tt);
- break;
- case line_type::setup:
- case line_type::tdown:
- case line_type::test:
- semi = parse_command_line (t, tt, lt, 0);
- break;
- }
-
- assert (tt == type::newline);
-
- // Stop saving and get the tokens.
- //
- line l {lt, replay_data ()};
-
- // Decide where it goes.
- //
- lines* ls (nullptr);
-
- // 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)
- {
- switch (lt)
- {
- case line_type::setup: fail (ll) << "setup command in test";
- case line_type::tdown: fail (ll) << "teardown command in test";
- default: break;
- }
-
- ls = lines_;
- }
- else
- {
- switch (lt)
- {
- case line_type::setup:
- {
- if (!group_->scopes.empty ())
- fail (ll) << "setup command after tests";
-
- if (!group_->tdown_.empty ())
- fail (ll) << "setup command after teardown";
-
- ls = &group_->setup_;
- break;
- }
- case line_type::tdown:
- {
- ls = &group_->tdown_;
- break;
- }
- 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 (!semi)
- {
- // 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;
- }
-
- // 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.
- //
- if (!group_->tdown_.empty ())
- {
- // @@ Can the teardown line be from a different file?
- //
- location tl (
- get_location (
- group_->tdown_.back ().tokens.front ().token));
-
- fail (ll) << "test after teardown" <<
- info (tl) << "last teardown line appears here";
- }
-
- // Create implicit test scope. Use line number as the scope id.
- //
- unique_ptr<test> p (new test (to_string (ll.line), *group_));
-
- p->start_loc_ = ll;
- p->end_loc_ = get_location (t);
-
- 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_ = semi ? ls : nullptr;
}
}
@@ -464,6 +283,197 @@ namespace build2
runner_->leave (*scope_, scope_->end_loc_);
}
+ void parser::
+ pre_parse_line (token& t, type& tt, lines* ls)
+ {
+ // Note: token is only peeked at.
+ //
+ const location ll (get_location (peeked ()));
+
+ // Determine the line type.
+ //
+ line_type lt;
+ switch (tt)
+ {
+ case type::plus:
+ case type::minus:
+ {
+ // Setup/teardown command.
+ //
+ lt = (tt == type::plus ? line_type::setup : line_type::tdown);
+
+ next (t, tt); // Start saving tokens from the next one.
+ replay_save ();
+ next (t, tt);
+ break;
+ }
+ default:
+ {
+ // Either test command or variable assignment.
+ //
+ replay_save (); // Start saving tokens from the current one.
+ next (t, tt);
+
+ // Decide whether this is a variable assignment or a command. It
+ // is an assignment if the first token is an unquoted word and
+ // the next is an assign/append/prepend operator. Assignment to
+ // a computed variable name must use the set builtin.
+ //
+ if (tt == type::word && !t.quoted)
+ {
+ // Switch the recognition of leading variable assignments for
+ // the next token. This is safe to do because we know we
+ // cannot be in the quoted mode (since the current token is
+ // not quoted).
+ //
+ mode (lexer_mode::second_token);
+ type p (peek ());
+
+ if (p == type::assign ||
+ p == type::prepend ||
+ p == type::append)
+ {
+ lt = line_type::variable;
+ break;
+ }
+ }
+
+ lt = line_type::test;
+ break;
+ }
+ }
+
+ // Pre-parse the line keeping track of whether it ends with a
+ // semicolon.
+ //
+ bool semi;
+
+ switch (lt)
+ {
+ case line_type::variable:
+ semi = parse_variable_line (t, tt);
+ break;
+ case line_type::setup:
+ case line_type::tdown:
+ case line_type::test:
+ semi = parse_command_line (t, tt, lt, 0);
+ break;
+ }
+
+ assert (tt == type::newline);
+
+ // Stop saving and get the tokens.
+ //
+ line l {lt, replay_data ()};
+
+ // Decide where it goes.
+ //
+ lines tests;
+ if (ls == nullptr)
+ {
+ switch (lt)
+ {
+ case line_type::setup:
+ {
+ if (!group_->scopes.empty ())
+ fail (ll) << "setup command after tests";
+
+ if (!group_->tdown_.empty ())
+ fail (ll) << "setup command after teardown";
+
+ ls = &group_->setup_;
+ break;
+ }
+ case line_type::tdown:
+ {
+ ls = &group_->tdown_;
+ break;
+ }
+ 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 (!semi)
+ {
+ // 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;
+ }
+
+ // 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.
+ //
+ if (!group_->tdown_.empty ())
+ {
+ // @@ Can the teardown line be from a different file?
+ //
+ location tl (
+ get_location (
+ group_->tdown_.back ().tokens.front ().token));
+
+ fail (ll) << "test after teardown" <<
+ info (tl) << "last teardown line appears here";
+ }
+
+ ls = &tests;
+ }
+ }
+ }
+
+ ls->push_back (move (l));
+
+ // If this command ended with a semicolon, then the next one should
+ // go to the same place.
+ //
+ if (semi)
+ {
+ mode (lexer_mode::first_token);
+ tt = peek ();
+ const location ll (get_location (peeked ()));
+
+ switch (tt)
+ {
+ case type::eos:
+ case type::rcbrace:
+ case type::lcbrace:
+ fail (ll) << "expected another line after semicolon";
+ case type::plus:
+ fail (ll) << "setup command in test";
+ case type::minus:
+ fail (ll) << "teardown command in test";
+ default:
+ pre_parse_line (t, tt, ls);
+ assert (tt == type::newline); // End of last test line.
+ }
+ }
+
+ // Create implicit test scope. Use line number as the scope id.
+ //
+ if (ls == &tests)
+ {
+ unique_ptr<test> p (new test (to_string (ll.line), *group_));
+
+ p->start_loc_ = ll;
+ p->end_loc_ = get_location (t);
+
+ p->tests_ = move (tests);
+
+ group_->scopes.push_back (move (p));
+ }
+ }
+
// Return true if the string contains only digit characters (used to
// detect the special $NN variables).
//