From e59c2bc979293d8cdea3f9733ecd59c080fce63c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 26 Sep 2019 13:36:44 +0200 Subject: Add support for `case` pattern alternatives case [ | ...] --- libbuild2/lexer.cxx | 27 +++++++++++++++++++++++++-- libbuild2/lexer.hxx | 32 ++++++++++++++++++-------------- libbuild2/parser.cxx | 46 ++++++++++++++++++++++++++++++++++++++-------- libbuild2/token.hxx | 2 ++ tests/switch/testscript | 23 ++++++++++++++--------- 5 files changed, 97 insertions(+), 33 deletions(-) diff --git a/libbuild2/lexer.cxx b/libbuild2/lexer.cxx index 6da8a80..ac62996 100644 --- a/libbuild2/lexer.cxx +++ b/libbuild2/lexer.cxx @@ -55,6 +55,12 @@ namespace build2 s2 = " "; break; } + case lexer_mode::case_patterns: + { + s1 = " $(){}[],|#\t\n"; + s2 = " "; + break; + } case lexer_mode::attribute: { s1 = " $(]#\t\n"; @@ -113,6 +119,7 @@ namespace build2 case lexer_mode::normal: case lexer_mode::value: case lexer_mode::values: + case lexer_mode::case_patterns: case lexer_mode::attribute: case lexer_mode::variable: case lexer_mode::buildspec: break; @@ -150,7 +157,9 @@ namespace build2 { // Expire value/values modes at the end of the line. // - if (m == lexer_mode::value || m == lexer_mode::values) + if (m == lexer_mode::value || + m == lexer_mode::values || + m == lexer_mode::case_patterns) state_.pop (); sep = true; // Treat newline as always separated. @@ -227,7 +236,9 @@ namespace build2 // The following characters are special in the values and buildspec mode. // - if (m == lexer_mode::values || m == lexer_mode::buildspec) + if (m == lexer_mode::buildspec || + m == lexer_mode::values || + m == lexer_mode::case_patterns) { // NOTE: remember to update mode() if adding new special characters. // @@ -237,6 +248,18 @@ namespace build2 } } + // The following characters are special in the case_patterns mode. + // + if (m == lexer_mode::case_patterns) + { + // NOTE: remember to update mode() if adding new special characters. + // + switch (c) + { + case '|': return make_token (type::bit_or); + } + } + // Otherwise it is a word. // unget (c); diff --git a/libbuild2/lexer.hxx b/libbuild2/lexer.hxx index e92980b..a629ba7 100644 --- a/libbuild2/lexer.hxx +++ b/libbuild2/lexer.hxx @@ -19,22 +19,25 @@ namespace build2 { - // Context-dependent lexing mode. In the value mode we don't treat certain - // 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 - // 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. + // Context-dependent lexing mode. Quoted modes are internal and should not + // be set explicitly. In the value mode we don't treat certain 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 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. // - // 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). + // A number of modes are "derived" from the value/values mode by recognizing + // a few extra characters: case_patterns (values plus '|'). // - // The alternative modes must be set manually. The value/values mode - // automatically expires after the end of the line. The attribute mode + // Note that the normal, value/values and derived, as well as eval modes + // split words separated by the pair character (to disable pairs one can + // pass '\0' as a pair character). + // + // The alternative modes must be set manually. The value/values and derived + // modes 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 ')'. // @@ -57,6 +60,7 @@ namespace build2 variable, value, values, + case_patterns, attribute, eval, single_quoted, diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index 2a5c31d..33b6d11 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -2092,10 +2092,14 @@ namespace build2 // case [, ...] // ... // + // case [|] + // // default // ... // } + assert (!pre_parse_); // Used to skip pattern alternatives. + // Parse and evaluate the values we are matching. Similar to if-else, we // expand patterns. // @@ -2186,7 +2190,13 @@ namespace build2 // Parse the patterns and match them against the values. Note that // here we don't expand patterns in names. // - mode (lexer_mode::values); // Recognize `,`. + mode (lexer_mode::case_patterns); // Recognize `|` and `,`. + + auto parse_pattern = [this] (token& t, type& tt) + { + return parse_value ( + t, tt, pattern_mode::ignore, "pattern", nullptr); + }; for (size_t i (0);; ++i) { @@ -2194,16 +2204,36 @@ namespace build2 if (tt == type::newline || tt == type::eos) fail (t) << "expected case pattern instead of " << t; - const location l (get_location (t)); + if (i == vs.size ()) + fail (t) << "more patterns than switch expressions"; - value p ( - parse_value ( - t, tt, pattern_mode::ignore, "pattern", nullptr)); + // Handle pattern alternatives (|). + // + for (;; next (t, tt)) + { + const location l (get_location (t)); + value p (parse_pattern (t, tt)); + take = compare_values (type::equal, vs[i], p, l); - if (i == vs.size ()) - fail (l) << "more patterns than switch expressions"; + if (tt != type::bit_or) + break; + + if (take) + { + // Use the pre-parse mechanism to skip remaining alternatives. + // + pre_parse_ = true; + do + { + next (t, tt); // Skip `|`. + parse_pattern (t, tt); + } + while (tt == type::bit_or); + pre_parse_ = false; - take = compare_values (type::equal, vs[i], p, l); + break; + } + } if (!take) { diff --git a/libbuild2/token.hxx b/libbuild2/token.hxx index a9b9a11..0ee5248 100644 --- a/libbuild2/token.hxx +++ b/libbuild2/token.hxx @@ -59,6 +59,8 @@ namespace build2 less_equal, // <= greater_equal, // >= + bit_or, // | + log_or, // || log_and, // && log_not, // ! diff --git a/tests/switch/testscript b/tests/switch/testscript index 877f640..1399df0 100644 --- a/tests/switch/testscript +++ b/tests/switch/testscript @@ -9,7 +9,7 @@ : basics : $* <>EOO -for i: 1 2 3 4 +for i: 1 2 3 4 5 { switch $i { @@ -19,9 +19,11 @@ for i: 1 2 3 4 { print 2 } - case 5 + case 0 case 3 - print 3,5 + print 0,3 + case 0|4 + print 0,4 default print d } @@ -29,14 +31,15 @@ for i: 1 2 3 4 EOI 1 2 -3,5 +0,3 +0,4 d EOO : basics-multiple : $* <>EOO -for i: 1 2 3 4 +for i: 1 2 3 4 5 { switch $i, $i { @@ -49,8 +52,10 @@ for i: 1 2 3 4 print 2 } case 3, 3 - case 5, 5 - print 3,5 + case 0, 0 + print 3,0 + case 4|0, 0|4|0 + print 4,0 default print d } @@ -58,11 +63,11 @@ for i: 1 2 3 4 EOI 1 2 -3,5 +3,0 +4,0 d EOO - : empty : $* <