aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-09-25 13:45:08 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2019-09-30 15:30:08 +0200
commit70f5ab11c55ff4a43b32aafe21e839d050301215 (patch)
tree45db520a78c5b6b0b84027908a6ea4f416ea7cdf
parentea997c89f7ea59db0164c79ac0fda5b607754753 (diff)
Pattern matching support (switch): multiple values implementation
-rw-r--r--libbuild2/lexer.cxx15
-rw-r--r--libbuild2/lexer.hxx23
-rw-r--r--libbuild2/parser.cxx75
-rw-r--r--tests/switch/testscript37
4 files changed, 113 insertions, 37 deletions
diff --git a/libbuild2/lexer.cxx b/libbuild2/lexer.cxx
index fd13c31..6da8a80 100644
--- a/libbuild2/lexer.cxx
+++ b/libbuild2/lexer.cxx
@@ -49,6 +49,12 @@ namespace build2
s2 = " ";
break;
}
+ case lexer_mode::values:
+ {
+ s1 = " $(){}[],#\t\n";
+ s2 = " ";
+ break;
+ }
case lexer_mode::attribute:
{
s1 = " $(]#\t\n";
@@ -106,6 +112,7 @@ namespace build2
{
case lexer_mode::normal:
case lexer_mode::value:
+ case lexer_mode::values:
case lexer_mode::attribute:
case lexer_mode::variable:
case lexer_mode::buildspec: break;
@@ -141,9 +148,9 @@ namespace build2
//
case '\n':
{
- // Expire value mode at the end of the line.
+ // Expire value/values modes at the end of the line.
//
- if (m == lexer_mode::value)
+ if (m == lexer_mode::value || m == lexer_mode::values)
state_.pop ();
sep = true; // Treat newline as always separated.
@@ -218,9 +225,9 @@ namespace build2
}
}
- // The following characters are special in the buildspec mode.
+ // The following characters are special in the values and buildspec mode.
//
- if (m == lexer_mode::buildspec)
+ if (m == lexer_mode::values || m == lexer_mode::buildspec)
{
// NOTE: remember to update mode() if adding new special characters.
//
diff --git a/libbuild2/lexer.hxx b/libbuild2/lexer.hxx
index f987071..e92980b 100644
--- a/libbuild2/lexer.hxx
+++ b/libbuild2/lexer.hxx
@@ -23,18 +23,20 @@ namespace build2
// characters (e.g., '+', '=') as special so that we can use them in the
// variable values, e.g., 'foo = g++'. In contrast, in the variable mode, we
// restrict certain character (e.g., '/') from appearing in the name. The
- // attribute mode is like value except it doesn't treat '{' and '}' as
- // special (so we cannot have name groups in attributes). The eval mode is
- // used in the evaluation context. Quoted modes are internal and should not
- // be set explicitly.
+ // values mode is like value but recogizes ',' as special (used in contexts
+ // where we need to list multiple values). The attribute mode is also like
+ // value except it doesn't treat '{' and '}' as special (so we cannot have
+ // name groups in attributes). The eval mode is used in the evaluation
+ // context. Quoted modes are internal and should not be set explicitly.
//
- // Note that the normal, value, and eval modes split words separated by the
- // pair character (to disable pairs one can pass '\0' as a pair character).
+ // Note that the normal, value/values, and eval modes split words separated
+ // by the pair character (to disable pairs one can pass '\0' as a pair
+ // character).
//
- // The alternnative modes must be set manually. The value mode automatically
- // expires after the end of the line. The attribute mode expires after the
- // closing ']'. The variable mode expires after the word token. And the eval
- // mode expires after the closing ')'.
+ // The alternative modes must be set manually. The value/values mode
+ // automatically expires after the end of the line. The attribute mode
+ // expires after the closing ']'. The variable mode expires after the word
+ // token. And the eval mode expires after the closing ')'.
//
// Note that normally it is only safe to switch mode when the current token
// is not quoted (or, more generally, when you are not in the double-quoted
@@ -54,6 +56,7 @@ namespace build2
normal = base_type::value_next,
variable,
value,
+ values,
attribute,
eval,
single_quoted,
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index a854df7..181ee05 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -2077,12 +2077,12 @@ namespace build2
void parser::
parse_switch (token& t, type& tt)
{
- // switch <value>
+ // switch <value>[, <value>....]
// {
- // case <value>
+ // case <pattern>[, <pattern>...]
// <line>
//
- // case <value>
+ // case <pattern>[, <pattern>...]
// {
// <block>
// }
@@ -2091,18 +2091,28 @@ namespace build2
// ...
// }
- // Parse and evaluate the value we are switching on. Similar to if-else,
- // we expand patterns.
+ // Parse and evaluate the values we are matching. Similar to if-else, we
+ // expand patterns.
//
- next (t, tt);
- if (tt == type::newline || tt == type::eos)
- fail (t) << "expected switch expression instead of " << t;
+ values vs;
+ {
+ mode (lexer_mode::values); // Recognize `,`.
- value v (parse_value (t, tt, pattern_mode::expand, "expression", nullptr));
+ do
+ {
+ next (t, tt);
+ if (tt == type::newline || tt == type::eos)
+ fail (t) << "expected switch expression instead of " << t;
- if (tt != type::newline)
- fail (t) << "expected newline instead of " << t << " after "
- << "the switch expression";
+ vs.push_back (
+ parse_value (t, tt, pattern_mode::expand, "expression", nullptr));
+ }
+ while (tt == type::comma);
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t << " after "
+ << "the switch expression";
+ }
// Next we should always have a block.
//
@@ -2150,12 +2160,13 @@ namespace build2
good:
- next (t, tt);
-
bool take (false); // Take this case/default?
if (seen_default)
+ {
take = !taken;
+ next (t, tt);
+ }
else
{
// Similar to if-else we are not going to evaluate the case conditions
@@ -2165,19 +2176,37 @@ namespace build2
skip_line (t, tt);
else
{
- // Parse the pattern and match it against the value. Note that here
- // we don't expand patterns.
+ // Parse the patterns and match them against the values. Note that
+ // here we don't expand patterns in names.
//
- if (tt == type::newline || tt == type::eos)
- fail (t) << "expected case pattern instead of " << t;
+ mode (lexer_mode::values); // Recognize `,`.
- const location l (get_location (t));
+ for (size_t i (0);; ++i)
+ {
+ next (t, tt);
+ if (tt == type::newline || tt == type::eos)
+ fail (t) << "expected case pattern instead of " << t;
+
+ const location l (get_location (t));
+
+ value p (
+ parse_value (
+ t, tt, pattern_mode::ignore, "pattern", nullptr));
- value p (
- parse_value (
- t, tt, pattern_mode::ignore, "pattern", nullptr));
+ if (i == vs.size ())
+ fail (l) << "more patterns than switch expressions";
- take = compare_values (type::equal, v, p, l);
+ take = compare_values (type::equal, vs[i], p, l);
+
+ if (!take)
+ {
+ skip_line (t, tt); // Skip the rest.
+ break;
+ }
+
+ if (tt != type::comma)
+ break;
+ }
}
}
diff --git a/tests/switch/testscript b/tests/switch/testscript
index d59b33d..86f1d7e 100644
--- a/tests/switch/testscript
+++ b/tests/switch/testscript
@@ -29,6 +29,32 @@ EOI
default
EOO
+: basics-multiple
+:
+$* <<EOI >>EOO
+for i: 1 2 3
+{
+ switch $i, $i
+ {
+ case 1, 1
+ print 1
+ case 1, 2
+ assert
+ case 2
+ {
+ print 2
+ }
+ default
+ print default
+ }
+}
+EOI
+1
+2
+default
+EOO
+
+
: empty
:
$* <<EOI
@@ -132,3 +158,14 @@ switch 1
EOI
<stdin>:3:3: error: expected case or default instead of 'x'
EOE
+
+: multiple-more-patterns
+:
+$* <<EOI 2>>EOE != 0
+switch 1
+{
+ case 1, 1
+}
+EOI
+<stdin>:3:11: error: more patterns than switch expressions
+EOE