varmod-match-escape.mk revision 1.19 1 # $NetBSD: varmod-match-escape.mk,v 1.19 2025/06/28 21:09:43 rillig Exp $
2 #
3 # As of 2020-08-01, the :M and :N modifiers interpret backslashes differently,
4 # depending on whether there was an expression somewhere before the
5 # first backslash or not. See ParseModifier_Match, "copy = true".
6 #
7 # Apart from the different and possibly confusing debug output, there is no
8 # difference in behavior. When parsing the modifier text, only \{, \} and \:
9 # are unescaped, and in the pattern matching these have the same meaning as
10 # their plain variants '{', '}' and ':'. In the pattern matching from
11 # Str_Match, only \*, \? or \[ would make a noticeable difference.
12
13 .MAKEFLAGS: -dcv
14
15 SPECIALS= \: : \\ * \*
16 .if ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}}
17 . warning unexpected
18 .endif
19
20 # And now both cases combined: A single modifier with both an escaped ':'
21 # as well as an expression that expands to a ':'.
22 #
23 # XXX: As of 2020-11-01, when an escaped ':' occurs before the
24 # expression, the whole modifier text is subject to unescaping '\:' to ':',
25 # before the expression is expanded. This means that the '\:' in
26 # the expression is expanded as well, turning ${:U\:} into a simple
27 # ${:U:}, which silently expands to an empty string, instead of generating
28 # an error message.
29 #
30 # XXX: As of 2020-11-01, the modifier on the right-hand side of the
31 # comparison is parsed differently though. First, the expression
32 # is parsed, resulting in ':' and needSubst=true. After that, the escaped
33 # ':' is seen, and this time, copy=true is not executed but stays copy=false.
34 # Therefore the escaped ':' is kept as-is, and the final pattern becomes
35 # ':\:'.
36 #
37 # If ParseModifier_Match had used the same parsing algorithm as Var_Subst,
38 # both patterns would end up as '::'.
39 #
40 VALUES= : :: :\:
41 .if ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:}
42 # expect+1: warning: XXX: Oops
43 . warning XXX: Oops
44 .endif
45
46 .MAKEFLAGS: -d0
47
48 # XXX: As of 2020-11-01, unlike all other variable modifiers, a '$' in the
49 # :M and :N modifiers is written as '$$', not as '\$'. This is confusing,
50 # undocumented and hopefully not used in practice.
51 .if ${:U\$:M$$} != "\$"
52 . error
53 .endif
54
55 # XXX: As of 2020-11-01, unlike all other variable modifiers, '\$' is not
56 # parsed as an escaped '$'. Instead, ParseModifier_Match first scans for
57 # the ':' at the end of the modifier, which results in the pattern '\$'.
58 # No unescaping takes place since the pattern neither contained '\:' nor
59 # '\{' nor '\}'. But the text is expanded, and a lonely '$' at the end
60 # is silently discarded. The resulting expanded pattern is thus '\', that
61 # is a single backslash.
62 # expect+1: Unfinished backslash at the end in pattern '\' of modifier ':M'
63 .if ${:U\$:M\$} != ""
64 . error
65 .endif
66
67 # In lint mode, the case of a lonely '$' is covered with an error message.
68 .MAKEFLAGS: -dL
69 # expect+2: Dollar followed by nothing
70 # expect+1: Unfinished backslash at the end in pattern '\' of modifier ':M'
71 .if ${:U\$:M\$} != ""
72 . error
73 .endif
74
75 # The control flow of the pattern parser depends on the actual string that
76 # is being matched. There needs to be either a test that shows a difference
77 # in behavior, or a proof that the behavior does not depend on the actual
78 # string.
79 #
80 # TODO: Str_Match("a-z]", "[a-z]")
81 # TODO: Str_Match("012", "[0-]]")
82 # TODO: Str_Match("[", "[[]")
83 # TODO: Str_Match("]", "[]")
84 # TODO: Str_Match("]", "[[-]]")
85
86 # Demonstrate an inconsistency between positive and negative character lists
87 # when the range ends with the character ']'.
88 #
89 # 'A' begins the range, 'B' is in the middle of the range, ']' ends the range,
90 # 'a' is outside the range.
91 WORDS= A A] A]] B B] B]] ] ]] ]]] a a] a]]
92 # The ']' is part of the character range and at the same time ends the
93 # character list.
94 EXP.[A-]= A B ]
95 # The first ']' is part of the character range and at the same time ends the
96 # character list.
97 EXP.[A-]]= A] B] ]]
98 # The first ']' is part of the character range and at the same time ends the
99 # character list.
100 EXP.[A-]]]= A]] B]] ]]]
101 # For negative character lists, the ']' ends the character range but does not
102 # end the character list.
103 # XXX: This is unnecessarily inconsistent but irrelevant in practice as there
104 # is no practical need for a character range that ends at ']'.
105 EXP.[^A-]= a
106 EXP.[^A-]]= a
107 EXP.[^A-]]]= a]
108
109 .for pattern in [A-] [A-]] [A-]]] [^A-] [^A-]] [^A-]]]
110 # expect+2: Unfinished character list in pattern '[A-]' of modifier ':M'
111 # expect+1: Unfinished character list in pattern '[^A-]' of modifier ':M'
112 . if ${WORDS:M${pattern}} != ${EXP.${pattern}}
113 . warning ${pattern}: ${WORDS:M${pattern}} != ${EXP.${pattern}}
114 . endif
115 .endfor
116
117 # In brackets, the backslash is just an ordinary character.
118 # Outside brackets, it is an escape character for a few special characters.
119 # TODO: Str_Match("\\", "[\\-]]")
120 # TODO: Str_Match("-]", "[\\-]]")
121
122 all:
123 @:;
124