1# $NetBSD: varmod-indirect.mk,v 1.20 2024/07/04 17:47:54 rillig Exp $ 2# 3# Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}. 4# These can be used for very basic purposes like converting a string to either 5# uppercase or lowercase, as well as for fairly advanced modifiers that first 6# look like line noise and are hard to decipher. 7# 8# Initial support for indirect modifiers was added in var.c 1.101 from 9# 2006-02-18. Since var.c 1.108 from 2006-05-11 it is possible to use 10# indirect modifiers for all but the very first modifier as well. 11 12 13# To apply a modifier indirectly via another variable, the whole 14# modifier must be put into a single expression. 15# The following expression generates a parse error since its indirect 16# modifier contains more than a sole expression. 17# 18# expect+1: while evaluating variable "value" with value "value": Unknown modifier "${" 19.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}" 20. warning unexpected 21.endif 22 23 24# Adding another level of indirection (the 2 nested :U expressions) helps. 25.if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement" 26. warning unexpected 27.endif 28 29 30# Multiple indirect modifiers can be applied one after another as long as 31# they are separated with colons. 32.if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE" 33. warning unexpected 34.endif 35 36 37# An indirect variable that evaluates to the empty string is allowed. 38# It is even allowed to write another modifier directly afterwards. 39# There is no practical use case for this feature though, as demonstrated 40# in the test case directly below. 41.if ${value:L:${:Dempty}S,value,replaced,} != "replaced" 42. warning unexpected 43.endif 44 45# If an expression for an indirect modifier evaluates to anything else than an 46# empty string and is neither followed by a ':' nor '}', this produces a parse 47# error. Because of this parse error, this feature cannot be used reasonably 48# in practice. 49# 50# expect+2: while evaluating variable "value" with value "value": Unknown modifier "${" 51#.MAKEFLAGS: -dvc 52.if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}" 53# expect+1: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression. 54. warning FIXME: this expression should have resulted in a parse $\ 55 error rather than returning the unparsed portion of the $\ 56 expression. 57.else 58. error 59.endif 60#.MAKEFLAGS: -d0 61 62# An indirect modifier can be followed by other modifiers, no matter if the 63# indirect modifier evaluates to an empty string or not. 64# 65# This makes it possible to define conditional modifiers, like this: 66# 67# M.little-endian= S,1234,4321, 68# M.big-endian= # none 69.if ${value:L:${:D empty }:S,value,replaced,} != "replaced" 70. error 71.endif 72 73 74# The nested expression expands to "tu", and this is interpreted as 75# a variable modifier for the value "Upper", resulting in "UPPER". 76.if ${Upper:L:${:Utu}} != "UPPER" 77. error 78.endif 79 80# The nested expression expands to "tl", and this is interpreted as 81# a variable modifier for the value "Lower", resulting in "lower". 82.if ${Lower:L:${:Utl}} != "lower" 83. error 84.endif 85 86 87# The nested expression is ${1 != 1:?Z:tl}, consisting of the 88# condition "1 != 1", the then-branch "Z" and the else-branch "tl". Since 89# the condition evaluates to false, the then-branch is ignored (it would 90# have been an unknown modifier anyway) and the ":tl" modifier is applied. 91.if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed" 92. error 93.endif 94 95 96# The indirect modifier can also replace an ':L' modifier, which allows for 97# brain twisters since by reading the expression alone, it is not possible 98# to say whether the variable name will be evaluated as a variable name or 99# as the immediate value of the expression. 100VAR= value 101M_ExpandVar= # an empty modifier 102M_VarAsValue= L 103# 104.if ${VAR:${M_ExpandVar}} != "value" 105. error 106.endif 107.if ${VAR:${M_VarAsValue}} != "VAR" 108. error 109.endif 110 111# The indirect modifier M_ListToSkip, when applied to a list of patterns, 112# expands to a sequence of ':N' modifiers, each of which filters one of the 113# patterns. This list of patterns can then be applied to another variable 114# to actually filter that variable. 115# 116M_ListToSkip= @pat@N$${pat}@:ts: 117# 118# The dollar signs need to be doubled in the above modifier expression, 119# otherwise they would be expanded too early, that is, when parsing the 120# modifier itself. 121# 122# In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'. 123# The 'N' comes from the expression 'N${pat}', the separating colons come 124# from the modifier ':ts:'. 125# 126#.MAKEFLAGS: -dcv # Uncomment this line to see the details 127# 128PRIMES= 2 3 5 7 1[1379] 129M_NoPrimes= ${PRIMES:${M_ListToSkip}} 130.if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20" 131. error 132.endif 133.MAKEFLAGS: -d0 134 135 136# In contrast to the .if conditions, the .for loop allows undefined 137# expressions. These expressions expand to empty strings. 138 139# An undefined expression without any modifiers expands to an empty string. 140.for var in before ${UNDEF} after 141# expect+2: before 142# expect+1: after 143. info ${var} 144.endfor 145 146# An undefined expression with only modifiers that keep the expression 147# undefined expands to an empty string. 148.for var in before ${UNDEF:${:US,a,a,}} after 149# expect+2: before 150# expect+1: after 151. info ${var} 152.endfor 153 154# Even in an indirect modifier based on an undefined variable, the value of 155# the expression in Var_Parse is a simple empty string. 156.for var in before ${UNDEF:${:U}} after 157# expect+2: before 158# expect+1: after 159. info ${var} 160.endfor 161 162# An error in an indirect modifier. 163# expect+1: while evaluating variable "UNDEF" with value "": Unknown modifier "Z" 164.for var in before ${UNDEF:${:UZ}} after 165# expect+2: before 166# expect+1: after 167. info ${var} 168.endfor 169 170 171# Another slightly different evaluation context is the right-hand side of 172# a variable assignment using ':='. 173.MAKEFLAGS: -dpv 174 175# The undefined expression is kept as-is. 176_:= before ${UNDEF} after 177 178# The undefined expression is kept as-is. 179_:= before ${UNDEF:${:US,a,a,}} after 180 181# XXX: The subexpression ${:U} is fully defined, therefore it is expanded. 182# This results in ${UNDEF:}, which can lead to tricky parse errors later, 183# when the variable '_' is expanded further. 184# 185# XXX: What should be the correct strategy here? One possibility is to 186# expand the defined subexpression and replace it with ${:U...}, just like 187# in .for loops. This would preserve the structure of the expression while 188# at the same time expanding the expression as far as possible. 189_:= before ${UNDEF:${:U}} after 190 191# XXX: This expands to ${UNDEF:Z}, which will behave differently if the 192# variable '_' is used in a context where the expression ${_} is 193# parsed but not evaluated. 194# expect+1: while evaluating variable "UNDEF" with value "": Unknown modifier "Z" 195_:= before ${UNDEF:${:UZ}} after 196 197.MAKEFLAGS: -d0 198.undef _ 199 200 201# When evaluating indirect modifiers, these modifiers may expand to ':tW', 202# which modifies the interpretation of the expression value. This modified 203# interpretation only lasts until the end of the indirect modifier, it does 204# not influence the outer expression. 205.if ${1 2 3:L:tW:[#]} != 1 # direct :tW applies to the :[#] 206. error 207.endif 208.if ${1 2 3:L:${:UtW}:[#]} != 3 # indirect :tW does not apply to :[#] 209. error 210.endif 211 212 213# When evaluating indirect modifiers, these modifiers may expand to ':ts*', 214# which modifies the interpretation of the expression value. This modified 215# interpretation only lasts until the end of the indirect modifier, it does 216# not influence the outer expression. 217# 218# In this first expression, the direct ':ts*' has no effect since ':U' does not 219# treat the expression value as a list of words but as a single word. It has 220# to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no 221# variable of that name. 222#.MAKEFLAGS: -dcpv 223.if ${1 2 3:L:ts*:Ua b c} != "a b c" 224. error 225.endif 226# In this expression, the direct ':ts*' affects the ':M' at the end. 227.if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c" 228. error 229.endif 230# In this expression, the ':ts*' is indirect, therefore the changed separator 231# only applies to the modifiers from the indirect text. It does not affect 232# the ':M' since that is not part of the text from the indirect modifier. 233# 234# Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers 235# (which creates a new ModChain containing a fresh separator), 236# the outer separator character is not passed by reference to the inner 237# evaluation, therefore the scope of the inner separator ends after applying 238# the modifier ':ts*'. 239.if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c" 240. error 241.endif 242 243# A direct modifier ':U' turns the expression from undefined to defined. 244# An indirect modifier ':U' has the same effect, unlike the separator from 245# ':ts*' or the single-word marker from ':tW'. 246# 247# This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes 248# the definedness of the outer expression by reference. If that weren't the 249# case, the first condition below would result in a parse error because its 250# left-hand side would be undefined. 251.if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback" 252. error 253.endif 254.if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback" 255. error 256.endif 257 258 259# In parse-only mode, the indirect modifiers must not be evaluated. 260# 261# Before var.c 1.1098 from 2024-02-04, the expression for an indirect modifier 262# was partially evaluated (only the variable value, without applying any 263# modifiers) and then interpreted as modifiers to the main expression. 264# 265# The expression ${:UZ} starts with the value "", and in parse-only mode, the 266# modifier ':UZ' does not modify the expression value. This results in an 267# empty string for the indirect modifiers, generating no warning. 268.if 0 && ${VAR:${:UZ}} 269.endif 270# The expression ${M_invalid} starts with the value "Z", which is an unknown 271# modifier. Trying to apply this unknown modifier generated a warning. 272M_invalid= Z 273.if 0 && ${VAR:${M_invalid}} 274.endif 275# The ':S' modifier does not change the expression value in parse-only mode, 276# keeping the "Z", which is then skipped in parse-only mode. 277.if 0 && ${VAR:${M_invalid:S,^,N*,:ts:}} 278.endif 279# The ':@' modifier does not change the expression value in parse-only mode, 280# keeping the "Z", which is then skipped in parse-only mode. 281.if 0 && ${VAR:${M_invalid:@m@N*$m@:ts:}} 282.endif 283