aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-09-26 12:29:56 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2019-09-30 15:30:22 +0200
commit333f468d264f0fa36a772b10b885fff6160ae4c7 (patch)
tree212479a6b793a0911a7de7d647173f66c04a411d
parent70f5ab11c55ff4a43b32aafe21e839d050301215 (diff)
Allow multiple `case` for single line/block
-rw-r--r--libbuild2/parser.cxx116
-rw-r--r--libbuild2/parser.hxx8
-rw-r--r--tests/switch/testscript41
3 files changed, 122 insertions, 43 deletions
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index 181ee05..2a5c31d 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -2087,6 +2087,11 @@ namespace build2
// <block>
// }
//
+ // case <pattern>[, <pattern>...]
+ // ...
+ // case <pattern>[, <pattern>...]
+ // ...
+ //
// default
// ...
// }
@@ -2109,59 +2114,61 @@ namespace build2
}
while (tt == type::comma);
- if (tt != type::newline)
- fail (t) << "expected newline instead of " << t << " after "
- << "the switch expression";
+ next_after_newline (t, tt, "switch expression");
}
// Next we should always have a block.
//
- if (next (t, tt) != type::lcbrace)
+ if (tt != type::lcbrace)
fail (t) << "expected '{' instead of " << t << " after switch";
- if (next (t, tt) != type::newline)
- fail (t) << "expected newline instead of " << t << " after '{'";
+ next (t, tt);
+ next_after_newline (t, tt, '{');
- // Next we have zero or more `case` lines/blocks optionally followed by the
- // `default` lines/blocks followed by the closing `}`.
+ // Next we have zero or more `case` lines/blocks (potentially with
+ // multiple `case`s per line/block) optionally followed by the `default`
+ // lines/blocks followed by the closing `}`.
//
bool taken (false); // One of the cases/default has been taken.
+ bool seen_default (false);
- next (t, tt);
- for (bool seen_default (false); tt != type::eos; )
+ auto special = [&seen_default, this] (const token& t, const type& tt)
{
- if (tt == type::rcbrace)
- break;
-
- string k;
if (tt == type::word && keyword (t))
{
- k = move (t.value);
-
- if (k == "case")
+ if (t.value == "case")
{
if (seen_default)
fail (t) << "case after default" <<
info << "default must be last in the switch block";
- goto good;
+ return true;
}
- else if (k == "default")
+ else if (t.value == "default")
{
if (seen_default)
fail (t) << "multiple defaults";
seen_default = true;
- goto good;
+ return true;
}
+ // Fall through.
}
- fail (t) << "expected case or default instead of " << t << endf;
+ return false;
+ };
+
+ while (tt != type::eos)
+ {
+ if (tt == type::rcbrace)
+ break;
- good:
+ if (!special (t, tt))
+ fail (t) << "expected case or default instead of " << t;
- bool take (false); // Take this case/default?
+ string k (move (t.value));
+ bool take (false); // Take this case/default?
if (seen_default)
{
take = !taken;
@@ -2210,13 +2217,36 @@ namespace build2
}
}
- if (tt != type::newline)
- fail (t) << "expected newline instead of " << t << " after "
- << (seen_default ? "default" : "the case pattern");
+ next_after_newline (t, tt, seen_default ? "default" : "case pattern");
- // This can be a block or a single line (the same logic as in if-else).
+ // This can be another `case` or `default`.
//
- if (next (t, tt) == type::lcbrace && peek () == type::newline)
+ if (special (t, tt))
+ {
+ // If we are still looking for a match, simply restart from the
+ // beginning as if this were the first `case` or `default`.
+ //
+ if (!take && !taken)
+ {
+ seen_default = false;
+ continue;
+ }
+
+ // Otherwise, we need to skip this and all the subsequent special
+ // lines.
+ //
+ do
+ {
+ skip_line (t, tt);
+ next_after_newline (t, tt, seen_default ? "default" : "case pattern");
+ }
+ while (special (t, tt));
+ }
+
+ // Otherwise this must be a block or a single line (the same logic as in
+ // if-else).
+ //
+ if (tt == type::lcbrace && peek () == type::newline)
{
next (t, tt); // Get newline.
next (t, tt);
@@ -5147,7 +5177,7 @@ namespace build2
}
bool parser::
- keyword (token& t)
+ keyword (const token& t)
{
assert (replay_ == replay::stop); // Can't be used in a replay.
assert (t.type == type::word);
@@ -5686,16 +5716,34 @@ namespace build2
}
inline type parser::
- next_after_newline (token& t, type& tt, char e)
+ next_after_newline (token& t, type& tt, char a)
{
if (tt == type::newline)
next (t, tt);
else if (tt != type::eos)
{
- if (e == '\0')
- fail (t) << "expected newline instead of " << t;
- else
- fail (t) << "expected newline after '" << e << "'";
+ diag_record dr (fail (t));
+ dr << "expected newline instead of " << t;
+
+ if (a != '\0')
+ dr << " after '" << a << "'";
+ }
+
+ return tt;
+ }
+
+ inline type parser::
+ next_after_newline (token& t, type& tt, const char* a)
+ {
+ if (tt == type::newline)
+ next (t, tt);
+ else if (tt != type::eos)
+ {
+ diag_record dr (fail (t));
+ dr << "expected newline instead of " << t;
+
+ if (a != nullptr)
+ dr << " after " << a;
}
return tt;
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index 3014681..e199a9a 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -414,7 +414,7 @@ namespace build2
// Return true if the name token can be considered a directive keyword.
//
bool
- keyword (token&);
+ keyword (const token&);
// Buildspec.
//
@@ -472,6 +472,12 @@ namespace build2
token_type
next_after_newline (token&, token_type&, char after = '\0');
+ // As above but the after argument is assumed to be a name rather than
+ // a token (printed as is rather than quoted).
+ //
+ token_type
+ next_after_newline (token&, token_type&, const char* after);
+
// Be careful with peeking and switching the lexer mode. See keyword()
// for more information.
//
diff --git a/tests/switch/testscript b/tests/switch/testscript
index 86f1d7e..877f640 100644
--- a/tests/switch/testscript
+++ b/tests/switch/testscript
@@ -9,7 +9,7 @@
: basics
:
$* <<EOI >>EOO
-for i: 1 2 3
+for i: 1 2 3 4
{
switch $i
{
@@ -19,20 +19,24 @@ for i: 1 2 3
{
print 2
}
+ case 5
+ case 3
+ print 3,5
default
- print default
+ print d
}
}
EOI
1
2
-default
+3,5
+d
EOO
: basics-multiple
:
$* <<EOI >>EOO
-for i: 1 2 3
+for i: 1 2 3 4
{
switch $i, $i
{
@@ -44,14 +48,18 @@ for i: 1 2 3
{
print 2
}
+ case 3, 3
+ case 5, 5
+ print 3,5
default
- print default
+ print d
}
}
EOI
1
2
-default
+3,5
+d
EOO
@@ -69,10 +77,10 @@ $* <<EOI >>EOO
switch 1
{
default
- print default
+ print d
}
EOI
-default
+d
EOO
: nested
@@ -97,6 +105,23 @@ EOI
2
EOO
+: case-default
+:
+$* <<EOI >>EOO
+for i: 1 2
+{
+ switch $i
+ {
+ case 1
+ default
+ print 1,d
+ }
+}
+EOI
+1,d
+1,d
+EOO
+
: default-before-case
:
$* <<EOI 2>>EOE != 0