aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2017-09-26 02:17:48 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2017-09-29 23:02:03 +0300
commitef1459feaf422fbbb069f2525547ef9b2204ccdf (patch)
tree4253606a7513b9465f5d7eabff0be6555c5f0794
parent1dc020cb957943c5c5c5364613061fedb50b9d9f (diff)
Allow pattern group to start with inclusion
-rw-r--r--build2/parser.cxx160
-rw-r--r--doc/manual.cli47
-rw-r--r--tests/name/pattern.test114
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
- <stdin>:1:8: error: invalid 'file{bar}' in name pattern
+ <stdin>:1:19: error: name pattern inclusion or exclusion expected
EOE
: inclusion-exclusion-sign
:
$* <'print {*.txt -foo bar}' 2>>EOE != 0
- <stdin>:1:8: error: missing leading +/- in 'bar' name pattern
+ <stdin>:1:19: error: name pattern inclusion or exclusion expected
+ EOE
+
+ : inclusion-quoted
+ :
+ $* <'print {*.txt -foo "+bar"}' 2>>EOE != 0
+ <stdin>: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
- #:
- #$* <<EOI >'<*.txt>'
- #pat = '*.txt'
- #print $pat
- #EOI
-
- #: pattern-via-expansion-list
- #:
- #$* <<EOI >'<*.hxx+*.txt>'
- #pats = '*.hxx' '+*.txt'
- #print {$pats}
- #EOI
-
- #: pattern-via-expansion-type
- #:
- #$* <<EOI >'<*.txt>'
- #pat = '*.txt'
- #print txt{$pat}
- #EOI
-
- #: pattern-via-expansion-dir
- #:
- #$* <<EOI >'<*.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;
+ $* <<EOI >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;
+ $* <<EOI >'+foo.txt'
+ pat = '+*'
+ ext = 'txt'
+ print {$pat.$ext}
+ EOI
+
+ : not-pattern-expansion
+ :
+ $* <<EOI >'+*.txt'
+ pat = '+*.txt'
+ print {$pat}
+ EOI
+
+ : not-pattern-quoted
+ :
+ $* <<EOI >'+*.txt'
+ print {'+*.txt'}
+ EOI
+
+ : pattern-via-expansion-list
+ :
+ touch foo.txt bar.hxx;
+ $* <<EOI >'bar.hxx foo.txt'
+ pats = '*.hxx' '*.txt'
+ print {+{$pats}}
+ EOI
+
+ : pattern-via-expansion-type
+ :
+ touch foo.txt;
+ $* <<EOI >'txt{foo}'
+ pat = '*'
+ print txt{+$pat}
+ EOI
+
+ : pattern-via-expansion-dir
+ :
+ mkdir dir;
+ touch dir/foo.txt;
+ $* <<EOI >dir/foo.txt
+ pat = '*.txt'
+ print dir/{+$pat}
+ EOI
+
+ : pattern-via-expansion-dir-type
+ :
+ mkdir dir;
+ touch dir/foo.txt;
+ $* <<EOI >dir/txt{foo}
+ pat = '*'
+ print dir/txt{+$pat}
+ EOI
: pattern-via-concat
: