From ef1459feaf422fbbb069f2525547ef9b2204ccdf Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 26 Sep 2017 02:17:48 +0300 Subject: Allow pattern group to start with inclusion --- build2/parser.cxx | 160 +++++++++++++++++++++++++++++++++++++++--------- doc/manual.cli | 47 +++++++++----- tests/name/pattern.test | 114 +++++++++++++++++++++++++--------- 3 files changed, 248 insertions(+), 73 deletions(-) diff --git a/build2/parser.cxx b/build2/parser.cxx index 3782230..5188b39 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -2549,12 +2549,20 @@ namespace build2 // Reduce inclusions/exclusions group (-/+{foo bar}) to simple name/dir. // - if (!first && n.typed () && n.type.size () == 1) + if (n.typed () && n.type.size () == 1) { - s = n.type[0]; + if (!first) + { + s = n.type[0]; - if (s == '-' || s == '+') + if (s == '-' || s == '+') + n.type.clear (); + } + else + { + assert (n.type[0] == '+'); // Can only belong to inclusion group. n.type.clear (); + } } if (n.empty () || !(n.simple () || n.directory ())) @@ -2570,10 +2578,7 @@ namespace build2 { s = v[0]; - if (s != '-' && s != '+') - fail (l) << "missing leading +/- in '" << v << "' " << what - << " pattern"; - + assert (s == '-' || s == '+'); // Validated at the token level. v.erase (0, 1); if (v.empty ()) @@ -2920,6 +2925,65 @@ namespace build2 } }; + // Set the result pattern target type and switch to the ignore mode. + // + // The goal of the detect mode is to assemble the "raw" list (the pattern + // itself plus inclusions/exclusions) that will then be passed to + // parse_names_pattern(). So clear pair, directory, and type (they will be + // added during pattern expansion) and change the mode to ignore (to + // prevent any expansions in inclusions/exclusions). + // + auto pattern_detected = + [&pairn, &dp, &tp, &rpat, &pmode] (const target_type* ttp) + { + assert (pmode == pattern_mode::detect); + + pairn = 0; + dp = nullptr; + tp = nullptr; + pmode = pattern_mode::ignore; + rpat = ttp; + }; + + // Return '+' or '-' if a token can start an inclusion or exclusion + // (pattern or group), '\0' otherwise. The result can be used as bool. + // + // @@ Note that we only need to make sure that the leading '+' or '-' + // characters are unquoted. We could consider some partially quoted + // tokens as starting inclusion or exclusion as well, for example + // +'foo*'. However, currently we can not determine which part of a + // token is quoted, and so can't distinguish the above token from + // '+'foo*. This is why we end up with a criteria that is stricter than + // is really required. + // + auto pattern_prefix = [] (const token& t) -> char + { + char c; + return t.type == type::word && ((c = t.value[0]) == '+' || c == '-') && + t.qtype == quote_type::unquoted + ? c + : '\0'; + }; + + // A name sequence potentially starts with a pattern if it starts with a + // literal unquoted plus character. + // + bool ppat (pmode == pattern_mode::detect && pattern_prefix (t) == '+'); + + // Potential pattern inclusion group. To be recognized as such it should + // start with the literal unquoted '+{' string and expand into a non-empty + // name sequence. + // + // The first name in such a group is a pattern, regardless of whether it + // contains wildcard characters or not. The trailing names are inclusions. + // For example the following pattern groups are equivalent: + // + // cxx{+{f* *oo}} + // cxx{f* +*oo} + // + bool pinc (ppat && t.value == "+" && + peek () == type::lcbrace && !peeked ().separated); + // Number of names in the last group. This is used to detect when // we need to add an empty first pair element (e.g., @y) or when // we have a (for now unsupported) multi-name LHS (e.g., {x y}@z). @@ -3057,6 +3121,12 @@ namespace build2 if (chunk && t.separated) break; + + // If we are parsing the pattern group, then space-separated tokens + // must start inclusions or exclusions (see above). + // + if (rpat && t.separated && tt != type::rcbrace && !pattern_prefix (t)) + fail (t) << "name pattern inclusion or exclusion expected"; } // Name. @@ -3152,7 +3222,25 @@ namespace build2 { next (t, tt); - if (p != n && tp != nullptr) + // Resolve the target, if there is one, for the potential pattern + // inclusion group. If we fail, then this is not an inclusion group. + // + const target_type* ttp (nullptr); + + if (pinc) + { + assert (val == "+"); + + if (tp != nullptr && scope_ != nullptr) + { + ttp = scope_->find_target_type (*tp); + + if (ttp == nullptr) + ppat = pinc = false; + } + } + + if (p != n && tp != nullptr && !pinc) fail (t) << "nested type name " << val; dir_path d1; @@ -3187,6 +3275,18 @@ namespace build2 count = parse_names_trailer ( t, tt, ns, pmode, what, separators, pairn, *pp1, dp1, tp1, cross); + + // If empty group or empty name, then this is not a pattern inclusion + // group (see above). + // + if (pinc) + { + if (count != 0 && (count > 1 || !ns.back ().empty ())) + pattern_detected (ttp); + + ppat = pinc = false; + } + tt = peek (); continue; } @@ -3208,16 +3308,27 @@ namespace build2 if (tp == nullptr || ttp != nullptr) { - // Reset the detect pattern mode to expand if the pattern is not - // followed by the inclusion/exclusion pattern/match. Note that - // if it is '}' (i.e., the end of the group), then it is a single - // pattern and the expansion is what we want. - // - char c; - if (pmode == pattern_mode::detect && - (tt != type::word || - ((c = peeked ().value[0]) != '+' && c != '-'))) - pmode = pattern_mode::expand; + if (pmode == pattern_mode::detect) + { + // Strip the literal unquoted plus character for the first + // pattern in the group. + // + if (ppat) + { + assert (val[0] == '+'); + + val.erase (0, 1); + ppat = pinc = false; + } + + // Reset the detect pattern mode to expand if the pattern is not + // followed by the inclusion/exclusion pattern/match. Note that + // if it is '}' (i.e., the end of the group), then it is a single + // pattern and the expansion is what we want. + // + if (!pattern_prefix (peeked ())) + pmode = pattern_mode::expand; + } if (pmode == pattern_mode::expand) { @@ -3230,18 +3341,7 @@ namespace build2 continue; } - // The goal of the detect mode is to assemble the "raw" list (the - // pattern itself plus inclusions/exclusions) that will be passed - // to parse_names_pattern(). So clear pair, directory, and type - // (they will be added during pattern expansion) and change the - // mode to ignore (to prevent any expansions in - // inclusions/exclusions). - // - pairn = 0; - dp = nullptr; - tp = nullptr; - pmode = pattern_mode::ignore; - rpat = ttp; + pattern_detected (ttp); // Fall through. } diff --git a/doc/manual.cli b/doc/manual.cli index 73025c2..04db55c 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -75,14 +75,16 @@ inclusion or exclusion if it starts with a literal, unquoted plus (\c{+}) or minus (\c{-}) sign, respectively. In this case the remaining group values, if any, must all be inclusions or exclusions. If the second value doesn't start with a plus or minus, then all the group values are considered independent -with leading pluses and minuses not having any special meaning. For example: +with leading pluses and minuses not having any special meaning. For +regularity, the first pattern can also start with the plus sign. For example: \ -exe{hello}: cxx{f* -foo} # Exclude foo if present. -exe{hello}: cxx{f* +foo} # Include foo if not present. -exe{hello}: cxx{f* -fo?} # Exclude foo and fox if present. -exe{hello}: cxx{f* +b* -foo -bar} # Exclude foo and bar if present. -exe{hello}: cxx{f* b* -z*} # Names matching three patterns. +exe{hello}: cxx{f* -foo} # Exclude foo if present. +exe{hello}: cxx{f* +foo} # Include foo if not present. +exe{hello}: cxx{f* -fo?} # Exclude foo and fox if present. +exe{hello}: cxx{f* +b* -foo -bar} # Exclude foo and bar if present. +exe{hello}: cxx{+f* +b* -foo -bar} # Same as above. +exe{hello}: cxx{f* b* -z*} # Names matching three patterns. \ Inclusions and exclusions are applied in the order specified and only to the @@ -99,8 +101,8 @@ exe{hello}: cxx{f* +*oo} # Ok, no duplicates. \ As a more realistic example, let's say we want to exclude source files that -reside in the \c{test/} directories (and their subdirectories) anywhere in -the tree. This can be achieved with the following pattern: +reside in the \c{test/} directories (and their subdirectories) anywhere in the +tree. This can be achieved with the following pattern: \ exe{hello}: cxx{** -***/test/**} @@ -124,17 +126,34 @@ If many inclusions or exclusions need to be specified, then an inclusion/exclusion group can be used. For example: \ -exe{hello}: cxx{f* -{foo bar}} # Exclude foo and bar if present. +exe{hello}: cxx{f* -{foo bar}} +exe{hello}: cxx{+{f* b*} -{foo bar}} \ -This is particularly useful if you would like to list the names to exclude -in a variable. For example, this is how we can exclude certain files from -compilation but still include them as ordinary file prerequisites (so that -they are still included into the distribution): +This is particularly useful if you would like to list the names to include or +exclude in a variable. For example, this is how we can exclude certain files +from compilation but still include them as ordinary file prerequisites (so +that they are still included into the distribution): \ exc = foo.cxx bar.cxx -exe{hello}: cxx{f* -{$exc}} file{$exc} +exe{hello}: cxx{+{f* b*} -{$exc}} file{$exc} +\ + +If we want to specify our pattern in a variable, then we have to use the +explicit inclusion syntax, for example: + +\ +pat = 'f*' +exe{hello}: cxx{+$pat} # Pattern match. +exe{hello}: cxx{$pat} # Literal 'f*'. + +pat = '+f*' +exe{hello}: cxx{$pat} # Literal '+f*'. + +inc = 'f*' 'b*' +exc = 'f*o' 'b*r' +exe{hello}: cxx{+{$inc} -{$exc}} \ One common situation that calls for exclusions is auto-generated source diff --git a/tests/name/pattern.test b/tests/name/pattern.test index b5b0bea..00838d0 100644 --- a/tests/name/pattern.test +++ b/tests/name/pattern.test @@ -45,13 +45,19 @@ EOI : simple : $* <'print {*.txt +foo file{bar}}' 2>>EOE != 0 - :1:8: error: invalid 'file{bar}' in name pattern + :1:19: error: name pattern inclusion or exclusion expected EOE : inclusion-exclusion-sign : $* <'print {*.txt -foo bar}' 2>>EOE != 0 - :1:8: error: missing leading +/- in 'bar' name pattern + :1:19: error: name pattern inclusion or exclusion expected + EOE + + : inclusion-quoted + : + $* <'print {*.txt -foo "+bar"}' 2>>EOE != 0 + :1:19: error: name pattern inclusion or exclusion expected EOE : empty-inclusion-exclusion @@ -79,6 +85,9 @@ EOI touch foo.txt; $* <'print {*.txt}' >'foo.txt' : group + touch foo.txt; + $* <'print {+*.txt}' >'foo.txt' : plus-prefixed + mkdir dir && touch dir/foo.txt; $* <'print dir/{*.txt}' >'dir/foo.txt' : dir @@ -190,38 +199,85 @@ EOI # print dir1/{$pat} # print dir2/{$pat} # + # Instead, he should write it as follows: + # + # pat = '*.txt' + # print dir1/{+$pat} + # print dir2/{+$pat} + # # Note that if we make it work, escaping this case will be pretty hairy: # # filters = --include '*.txt' --exclude '*.obj' # options += $filters - #: pattern-via-expansion - #: - #$* <'<*.txt>' - #pat = '*.txt' - #print $pat - #EOI - - #: pattern-via-expansion-list - #: - #$* <'<*.hxx+*.txt>' - #pats = '*.hxx' '+*.txt' - #print {$pats} - #EOI - - #: pattern-via-expansion-type - #: - #$* <'<*.txt>' - #pat = '*.txt' - #print txt{$pat} - #EOI - - #: pattern-via-expansion-dir - #: - #$* <'<*.txt>' - #pat = '*.txt' - #print dir/{$pat} - #EOI + : prefixed-pattern-via-expansion + : + : Pattern is prefixed with the literal unquoted plus character, that is + : stripped. + : + touch foo.txt; + $* <foo.txt + pat = '*.txt' + print {+$pat} + EOI + + : non-prefixed-pattern-via-concat-expansion + : + : Plus character is a part of the pattern and so is not stripped. + : + touch +foo.txt; + $* <'+foo.txt' + pat = '+*' + ext = 'txt' + print {$pat.$ext} + EOI + + : not-pattern-expansion + : + $* <'+*.txt' + pat = '+*.txt' + print {$pat} + EOI + + : not-pattern-quoted + : + $* <'+*.txt' + print {'+*.txt'} + EOI + + : pattern-via-expansion-list + : + touch foo.txt bar.hxx; + $* <'bar.hxx foo.txt' + pats = '*.hxx' '*.txt' + print {+{$pats}} + EOI + + : pattern-via-expansion-type + : + touch foo.txt; + $* <'txt{foo}' + pat = '*' + print txt{+$pat} + EOI + + : pattern-via-expansion-dir + : + mkdir dir; + touch dir/foo.txt; + $* <dir/foo.txt + pat = '*.txt' + print dir/{+$pat} + EOI + + : pattern-via-expansion-dir-type + : + mkdir dir; + touch dir/foo.txt; + $* <dir/txt{foo} + pat = '*' + print dir/txt{+$pat} + EOI : pattern-via-concat : -- cgit v1.1