From 333f468d264f0fa36a772b10b885fff6160ae4c7 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 26 Sep 2019 12:29:56 +0200 Subject: Allow multiple `case` for single line/block --- libbuild2/parser.cxx | 116 ++++++++++++++++++++++++++++++++++-------------- libbuild2/parser.hxx | 8 +++- tests/switch/testscript | 41 +++++++++++++---- 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 // // } // + // case [, ...] + // ... + // case [, ...] + // ... + // // 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 : $* <>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 : $* <>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 @@ $* <>EOO switch 1 { default - print default + print d } EOI -default +d EOO : nested @@ -97,6 +105,23 @@ EOI 2 EOO +: case-default +: +$* <>EOO +for i: 1 2 +{ + switch $i + { + case 1 + default + print 1,d + } +} +EOI +1,d +1,d +EOO + : default-before-case : $* <>EOE != 0 -- cgit v1.1