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