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