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 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