xref: /freebsd/contrib/bmake/unit-tests/varmod-match-escape.mk (revision edbbf26e2650e02cd3925dd1deaacf9b8fb2e2a0)
1# $NetBSD: varmod-match-escape.mk,v 1.20 2025/06/28 22:39:29 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
15SPECIALS=	\: : \\ * \*
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#
40VALUES=		: :: :\:
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.
91WORDS=		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.
94EXP.[A-]=	A B ]
95# The first ']' is part of the character range and at the same time ends the
96# character list.
97EXP.[A-]]=	A] B] ]]
98# The first ']' is part of the character range and at the same time ends the
99# character list.
100EXP.[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 ']'.
105EXP.[^A-]=	a
106EXP.[^A-]]=	a
107EXP.[^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
122all:
123	@:;
124