From 70f5ab11c55ff4a43b32aafe21e839d050301215 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 25 Sep 2019 13:45:08 +0200 Subject: Pattern matching support (switch): multiple values implementation --- libbuild2/lexer.cxx | 15 +++++++--- libbuild2/lexer.hxx | 23 ++++++++------- libbuild2/parser.cxx | 75 ++++++++++++++++++++++++++++++++++--------------- tests/switch/testscript | 37 ++++++++++++++++++++++++ 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 + // switch [, ....] // { - // case + // case [, ...] // // - // case + // case [, ...] // { // // } @@ -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 +: +$* <>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 : $* <:3:3: error: expected case or default instead of 'x' EOE + +: multiple-more-patterns +: +$* <>EOE != 0 +switch 1 +{ + case 1, 1 +} +EOI +:3:11: error: more patterns than switch expressions +EOE -- cgit v1.1