1*6a7405f5SSimon J. Gerraty# $NetBSD: varmod-indirect.mk,v 1.21 2024/08/29 20:20:36 rillig Exp $ 206b9b3e0SSimon J. Gerraty# 306b9b3e0SSimon J. Gerraty# Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}. 406b9b3e0SSimon J. Gerraty# These can be used for very basic purposes like converting a string to either 506b9b3e0SSimon J. Gerraty# uppercase or lowercase, as well as for fairly advanced modifiers that first 606b9b3e0SSimon J. Gerraty# look like line noise and are hard to decipher. 706b9b3e0SSimon J. Gerraty# 8b0c40a00SSimon J. Gerraty# Initial support for indirect modifiers was added in var.c 1.101 from 9b0c40a00SSimon J. Gerraty# 2006-02-18. Since var.c 1.108 from 2006-05-11 it is possible to use 10b0c40a00SSimon J. Gerraty# indirect modifiers for all but the very first modifier as well. 1106b9b3e0SSimon J. Gerraty 1206b9b3e0SSimon J. Gerraty 1306b9b3e0SSimon J. Gerraty# To apply a modifier indirectly via another variable, the whole 14d5e0a182SSimon J. Gerraty# modifier must be put into a single expression. 15b0c40a00SSimon J. Gerraty# The following expression generates a parse error since its indirect 16d5e0a182SSimon J. Gerraty# modifier contains more than a sole expression. 17b0c40a00SSimon J. Gerraty# 18*6a7405f5SSimon J. Gerraty# expect+1: Unknown modifier "${" 1906b9b3e0SSimon J. Gerraty.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}" 2006b9b3e0SSimon J. Gerraty. warning unexpected 2106b9b3e0SSimon J. Gerraty.endif 2206b9b3e0SSimon J. Gerraty 2306b9b3e0SSimon J. Gerraty 2406b9b3e0SSimon J. Gerraty# Adding another level of indirection (the 2 nested :U expressions) helps. 2506b9b3e0SSimon J. Gerraty.if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement" 2606b9b3e0SSimon J. Gerraty. warning unexpected 2706b9b3e0SSimon J. Gerraty.endif 2806b9b3e0SSimon J. Gerraty 2906b9b3e0SSimon J. Gerraty 3006b9b3e0SSimon J. Gerraty# Multiple indirect modifiers can be applied one after another as long as 3106b9b3e0SSimon J. Gerraty# they are separated with colons. 3206b9b3e0SSimon J. Gerraty.if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE" 3306b9b3e0SSimon J. Gerraty. warning unexpected 3406b9b3e0SSimon J. Gerraty.endif 3506b9b3e0SSimon J. Gerraty 3606b9b3e0SSimon J. Gerraty 37b0c40a00SSimon J. Gerraty# An indirect variable that evaluates to the empty string is allowed. 38b0c40a00SSimon J. Gerraty# It is even allowed to write another modifier directly afterwards. 39b0c40a00SSimon J. Gerraty# There is no practical use case for this feature though, as demonstrated 40b0c40a00SSimon J. Gerraty# in the test case directly below. 41b0c40a00SSimon J. Gerraty.if ${value:L:${:Dempty}S,value,replaced,} != "replaced" 42b0c40a00SSimon J. Gerraty. warning unexpected 43b0c40a00SSimon J. Gerraty.endif 44b0c40a00SSimon J. Gerraty 45b0c40a00SSimon J. Gerraty# If an expression for an indirect modifier evaluates to anything else than an 46b0c40a00SSimon J. Gerraty# empty string and is neither followed by a ':' nor '}', this produces a parse 47b0c40a00SSimon J. Gerraty# error. Because of this parse error, this feature cannot be used reasonably 48b0c40a00SSimon J. Gerraty# in practice. 49b0c40a00SSimon J. Gerraty# 50*6a7405f5SSimon J. Gerraty# expect+2: Unknown modifier "${" 51b0c40a00SSimon J. Gerraty#.MAKEFLAGS: -dvc 52b0c40a00SSimon J. Gerraty.if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}" 53148ee845SSimon J. Gerraty# expect+1: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression. 54b0c40a00SSimon J. Gerraty. warning FIXME: this expression should have resulted in a parse $\ 55b0c40a00SSimon J. Gerraty error rather than returning the unparsed portion of the $\ 56b0c40a00SSimon J. Gerraty expression. 57b0c40a00SSimon J. Gerraty.else 58b0c40a00SSimon J. Gerraty. error 59b0c40a00SSimon J. Gerraty.endif 60b0c40a00SSimon J. Gerraty#.MAKEFLAGS: -d0 61b0c40a00SSimon J. Gerraty 62b0c40a00SSimon J. Gerraty# An indirect modifier can be followed by other modifiers, no matter if the 63b0c40a00SSimon J. Gerraty# indirect modifier evaluates to an empty string or not. 64b0c40a00SSimon J. Gerraty# 6506b9b3e0SSimon J. Gerraty# This makes it possible to define conditional modifiers, like this: 6606b9b3e0SSimon J. Gerraty# 6706b9b3e0SSimon J. Gerraty# M.little-endian= S,1234,4321, 6806b9b3e0SSimon J. Gerraty# M.big-endian= # none 69b0c40a00SSimon J. Gerraty.if ${value:L:${:D empty }:S,value,replaced,} != "replaced" 70b0c40a00SSimon J. Gerraty. error 7106b9b3e0SSimon J. Gerraty.endif 7206b9b3e0SSimon J. Gerraty 7306b9b3e0SSimon J. Gerraty 74d5e0a182SSimon J. Gerraty# The nested expression expands to "tu", and this is interpreted as 7506b9b3e0SSimon J. Gerraty# a variable modifier for the value "Upper", resulting in "UPPER". 7606b9b3e0SSimon J. Gerraty.if ${Upper:L:${:Utu}} != "UPPER" 7706b9b3e0SSimon J. Gerraty. error 7806b9b3e0SSimon J. Gerraty.endif 7906b9b3e0SSimon J. Gerraty 80d5e0a182SSimon J. Gerraty# The nested expression expands to "tl", and this is interpreted as 8106b9b3e0SSimon J. Gerraty# a variable modifier for the value "Lower", resulting in "lower". 8206b9b3e0SSimon J. Gerraty.if ${Lower:L:${:Utl}} != "lower" 8306b9b3e0SSimon J. Gerraty. error 8406b9b3e0SSimon J. Gerraty.endif 8506b9b3e0SSimon J. Gerraty 8606b9b3e0SSimon J. Gerraty 87d5e0a182SSimon J. Gerraty# The nested expression is ${1 != 1:?Z:tl}, consisting of the 8806b9b3e0SSimon J. Gerraty# condition "1 != 1", the then-branch "Z" and the else-branch "tl". Since 8906b9b3e0SSimon J. Gerraty# the condition evaluates to false, the then-branch is ignored (it would 9006b9b3e0SSimon J. Gerraty# have been an unknown modifier anyway) and the ":tl" modifier is applied. 9106b9b3e0SSimon J. Gerraty.if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed" 9206b9b3e0SSimon J. Gerraty. error 9306b9b3e0SSimon J. Gerraty.endif 9406b9b3e0SSimon J. Gerraty 9506b9b3e0SSimon J. Gerraty 9606b9b3e0SSimon J. Gerraty# The indirect modifier can also replace an ':L' modifier, which allows for 9706b9b3e0SSimon J. Gerraty# brain twisters since by reading the expression alone, it is not possible 9806b9b3e0SSimon J. Gerraty# to say whether the variable name will be evaluated as a variable name or 9906b9b3e0SSimon J. Gerraty# as the immediate value of the expression. 10006b9b3e0SSimon J. GerratyVAR= value 10106b9b3e0SSimon J. GerratyM_ExpandVar= # an empty modifier 10206b9b3e0SSimon J. GerratyM_VarAsValue= L 10306b9b3e0SSimon J. Gerraty# 10406b9b3e0SSimon J. Gerraty.if ${VAR:${M_ExpandVar}} != "value" 10506b9b3e0SSimon J. Gerraty. error 10606b9b3e0SSimon J. Gerraty.endif 10706b9b3e0SSimon J. Gerraty.if ${VAR:${M_VarAsValue}} != "VAR" 10806b9b3e0SSimon J. Gerraty. error 10906b9b3e0SSimon J. Gerraty.endif 11006b9b3e0SSimon J. Gerraty 11106b9b3e0SSimon J. Gerraty# The indirect modifier M_ListToSkip, when applied to a list of patterns, 11206b9b3e0SSimon J. Gerraty# expands to a sequence of ':N' modifiers, each of which filters one of the 11306b9b3e0SSimon J. Gerraty# patterns. This list of patterns can then be applied to another variable 11406b9b3e0SSimon J. Gerraty# to actually filter that variable. 11506b9b3e0SSimon J. Gerraty# 11606b9b3e0SSimon J. GerratyM_ListToSkip= @pat@N$${pat}@:ts: 11706b9b3e0SSimon J. Gerraty# 11806b9b3e0SSimon J. Gerraty# The dollar signs need to be doubled in the above modifier expression, 11906b9b3e0SSimon J. Gerraty# otherwise they would be expanded too early, that is, when parsing the 12006b9b3e0SSimon J. Gerraty# modifier itself. 12106b9b3e0SSimon J. Gerraty# 12206b9b3e0SSimon J. Gerraty# In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'. 12306b9b3e0SSimon J. Gerraty# The 'N' comes from the expression 'N${pat}', the separating colons come 12406b9b3e0SSimon J. Gerraty# from the modifier ':ts:'. 12506b9b3e0SSimon J. Gerraty# 12606b9b3e0SSimon J. Gerraty#.MAKEFLAGS: -dcv # Uncomment this line to see the details 12706b9b3e0SSimon J. Gerraty# 12806b9b3e0SSimon J. GerratyPRIMES= 2 3 5 7 1[1379] 12906b9b3e0SSimon J. GerratyM_NoPrimes= ${PRIMES:${M_ListToSkip}} 13006b9b3e0SSimon J. Gerraty.if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20" 13106b9b3e0SSimon J. Gerraty. error 13206b9b3e0SSimon J. Gerraty.endif 13306b9b3e0SSimon J. Gerraty.MAKEFLAGS: -d0 13406b9b3e0SSimon J. Gerraty 13506b9b3e0SSimon J. Gerraty 136d5e0a182SSimon J. Gerraty# In contrast to the .if conditions, the .for loop allows undefined 13706b9b3e0SSimon J. Gerraty# expressions. These expressions expand to empty strings. 13806b9b3e0SSimon J. Gerraty 13906b9b3e0SSimon J. Gerraty# An undefined expression without any modifiers expands to an empty string. 14006b9b3e0SSimon J. Gerraty.for var in before ${UNDEF} after 141148ee845SSimon J. Gerraty# expect+2: before 142148ee845SSimon J. Gerraty# expect+1: after 14306b9b3e0SSimon J. Gerraty. info ${var} 14406b9b3e0SSimon J. Gerraty.endfor 14506b9b3e0SSimon J. Gerraty 14606b9b3e0SSimon J. Gerraty# An undefined expression with only modifiers that keep the expression 14706b9b3e0SSimon J. Gerraty# undefined expands to an empty string. 14806b9b3e0SSimon J. Gerraty.for var in before ${UNDEF:${:US,a,a,}} after 149148ee845SSimon J. Gerraty# expect+2: before 150148ee845SSimon J. Gerraty# expect+1: after 15106b9b3e0SSimon J. Gerraty. info ${var} 15206b9b3e0SSimon J. Gerraty.endfor 15306b9b3e0SSimon J. Gerraty 15406b9b3e0SSimon J. Gerraty# Even in an indirect modifier based on an undefined variable, the value of 15506b9b3e0SSimon J. Gerraty# the expression in Var_Parse is a simple empty string. 15606b9b3e0SSimon J. Gerraty.for var in before ${UNDEF:${:U}} after 157148ee845SSimon J. Gerraty# expect+2: before 158148ee845SSimon J. Gerraty# expect+1: after 15906b9b3e0SSimon J. Gerraty. info ${var} 16006b9b3e0SSimon J. Gerraty.endfor 16106b9b3e0SSimon J. Gerraty 16206b9b3e0SSimon J. Gerraty# An error in an indirect modifier. 163*6a7405f5SSimon J. Gerraty# expect+1: Unknown modifier "Z" 16406b9b3e0SSimon J. Gerraty.for var in before ${UNDEF:${:UZ}} after 165148ee845SSimon J. Gerraty# expect+2: before 166148ee845SSimon J. Gerraty# expect+1: after 16706b9b3e0SSimon J. Gerraty. info ${var} 16806b9b3e0SSimon J. Gerraty.endfor 16906b9b3e0SSimon J. Gerraty 17006b9b3e0SSimon J. Gerraty 17106b9b3e0SSimon J. Gerraty# Another slightly different evaluation context is the right-hand side of 17206b9b3e0SSimon J. Gerraty# a variable assignment using ':='. 17306b9b3e0SSimon J. Gerraty.MAKEFLAGS: -dpv 17406b9b3e0SSimon J. Gerraty 175d5e0a182SSimon J. Gerraty# The undefined expression is kept as-is. 17606b9b3e0SSimon J. Gerraty_:= before ${UNDEF} after 17706b9b3e0SSimon J. Gerraty 178d5e0a182SSimon J. Gerraty# The undefined expression is kept as-is. 17906b9b3e0SSimon J. Gerraty_:= before ${UNDEF:${:US,a,a,}} after 18006b9b3e0SSimon J. Gerraty 18106b9b3e0SSimon J. Gerraty# XXX: The subexpression ${:U} is fully defined, therefore it is expanded. 18206b9b3e0SSimon J. Gerraty# This results in ${UNDEF:}, which can lead to tricky parse errors later, 18306b9b3e0SSimon J. Gerraty# when the variable '_' is expanded further. 18406b9b3e0SSimon J. Gerraty# 18506b9b3e0SSimon J. Gerraty# XXX: What should be the correct strategy here? One possibility is to 18606b9b3e0SSimon J. Gerraty# expand the defined subexpression and replace it with ${:U...}, just like 18706b9b3e0SSimon J. Gerraty# in .for loops. This would preserve the structure of the expression while 18806b9b3e0SSimon J. Gerraty# at the same time expanding the expression as far as possible. 18906b9b3e0SSimon J. Gerraty_:= before ${UNDEF:${:U}} after 19006b9b3e0SSimon J. Gerraty 19106b9b3e0SSimon J. Gerraty# XXX: This expands to ${UNDEF:Z}, which will behave differently if the 192d5e0a182SSimon J. Gerraty# variable '_' is used in a context where the expression ${_} is 19306b9b3e0SSimon J. Gerraty# parsed but not evaluated. 194*6a7405f5SSimon J. Gerraty# expect+1: Unknown modifier "Z" 19506b9b3e0SSimon J. Gerraty_:= before ${UNDEF:${:UZ}} after 19606b9b3e0SSimon J. Gerraty 19706b9b3e0SSimon J. Gerraty.MAKEFLAGS: -d0 19806b9b3e0SSimon J. Gerraty.undef _ 19906b9b3e0SSimon J. Gerraty 200b0c40a00SSimon J. Gerraty 201b0c40a00SSimon J. Gerraty# When evaluating indirect modifiers, these modifiers may expand to ':tW', 202b0c40a00SSimon J. Gerraty# which modifies the interpretation of the expression value. This modified 203b0c40a00SSimon J. Gerraty# interpretation only lasts until the end of the indirect modifier, it does 204d5e0a182SSimon J. Gerraty# not influence the outer expression. 205b0c40a00SSimon J. Gerraty.if ${1 2 3:L:tW:[#]} != 1 # direct :tW applies to the :[#] 206b0c40a00SSimon J. Gerraty. error 207b0c40a00SSimon J. Gerraty.endif 208b0c40a00SSimon J. Gerraty.if ${1 2 3:L:${:UtW}:[#]} != 3 # indirect :tW does not apply to :[#] 209b0c40a00SSimon J. Gerraty. error 210b0c40a00SSimon J. Gerraty.endif 211b0c40a00SSimon J. Gerraty 212b0c40a00SSimon J. Gerraty 213b0c40a00SSimon J. Gerraty# When evaluating indirect modifiers, these modifiers may expand to ':ts*', 214b0c40a00SSimon J. Gerraty# which modifies the interpretation of the expression value. This modified 215b0c40a00SSimon J. Gerraty# interpretation only lasts until the end of the indirect modifier, it does 216d5e0a182SSimon J. Gerraty# not influence the outer expression. 217b0c40a00SSimon J. Gerraty# 218b0c40a00SSimon J. Gerraty# In this first expression, the direct ':ts*' has no effect since ':U' does not 219b0c40a00SSimon J. Gerraty# treat the expression value as a list of words but as a single word. It has 220b0c40a00SSimon J. Gerraty# to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no 221b0c40a00SSimon J. Gerraty# variable of that name. 222b0c40a00SSimon J. Gerraty#.MAKEFLAGS: -dcpv 223b0c40a00SSimon J. Gerraty.if ${1 2 3:L:ts*:Ua b c} != "a b c" 224b0c40a00SSimon J. Gerraty. error 225b0c40a00SSimon J. Gerraty.endif 226b0c40a00SSimon J. Gerraty# In this expression, the direct ':ts*' affects the ':M' at the end. 227b0c40a00SSimon J. Gerraty.if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c" 228b0c40a00SSimon J. Gerraty. error 229b0c40a00SSimon J. Gerraty.endif 230b0c40a00SSimon J. Gerraty# In this expression, the ':ts*' is indirect, therefore the changed separator 231b0c40a00SSimon J. Gerraty# only applies to the modifiers from the indirect text. It does not affect 232b0c40a00SSimon J. Gerraty# the ':M' since that is not part of the text from the indirect modifier. 233b0c40a00SSimon J. Gerraty# 234b0c40a00SSimon J. Gerraty# Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers 235b0c40a00SSimon J. Gerraty# (which creates a new ModChain containing a fresh separator), 236b0c40a00SSimon J. Gerraty# the outer separator character is not passed by reference to the inner 237b0c40a00SSimon J. Gerraty# evaluation, therefore the scope of the inner separator ends after applying 238b0c40a00SSimon J. Gerraty# the modifier ':ts*'. 239b0c40a00SSimon J. Gerraty.if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c" 240b0c40a00SSimon J. Gerraty. error 241b0c40a00SSimon J. Gerraty.endif 242b0c40a00SSimon J. Gerraty 243b0c40a00SSimon J. Gerraty# A direct modifier ':U' turns the expression from undefined to defined. 244b0c40a00SSimon J. Gerraty# An indirect modifier ':U' has the same effect, unlike the separator from 245b0c40a00SSimon J. Gerraty# ':ts*' or the single-word marker from ':tW'. 246b0c40a00SSimon J. Gerraty# 247b0c40a00SSimon J. Gerraty# This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes 248b0c40a00SSimon J. Gerraty# the definedness of the outer expression by reference. If that weren't the 249b0c40a00SSimon J. Gerraty# case, the first condition below would result in a parse error because its 250b0c40a00SSimon J. Gerraty# left-hand side would be undefined. 251b0c40a00SSimon J. Gerraty.if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback" 252b0c40a00SSimon J. Gerraty. error 253b0c40a00SSimon J. Gerraty.endif 254b0c40a00SSimon J. Gerraty.if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback" 255b0c40a00SSimon J. Gerraty. error 256b0c40a00SSimon J. Gerraty.endif 257b0c40a00SSimon J. Gerraty 258c59c3bf3SSimon J. Gerraty 259c59c3bf3SSimon J. Gerraty# In parse-only mode, the indirect modifiers must not be evaluated. 260c59c3bf3SSimon J. Gerraty# 261c59c3bf3SSimon J. Gerraty# Before var.c 1.1098 from 2024-02-04, the expression for an indirect modifier 262c59c3bf3SSimon J. Gerraty# was partially evaluated (only the variable value, without applying any 263c59c3bf3SSimon J. Gerraty# modifiers) and then interpreted as modifiers to the main expression. 264c59c3bf3SSimon J. Gerraty# 265c59c3bf3SSimon J. Gerraty# The expression ${:UZ} starts with the value "", and in parse-only mode, the 266c59c3bf3SSimon J. Gerraty# modifier ':UZ' does not modify the expression value. This results in an 267c59c3bf3SSimon J. Gerraty# empty string for the indirect modifiers, generating no warning. 268c59c3bf3SSimon J. Gerraty.if 0 && ${VAR:${:UZ}} 269c59c3bf3SSimon J. Gerraty.endif 270c59c3bf3SSimon J. Gerraty# The expression ${M_invalid} starts with the value "Z", which is an unknown 271c59c3bf3SSimon J. Gerraty# modifier. Trying to apply this unknown modifier generated a warning. 272c59c3bf3SSimon J. GerratyM_invalid= Z 273c59c3bf3SSimon J. Gerraty.if 0 && ${VAR:${M_invalid}} 274c59c3bf3SSimon J. Gerraty.endif 275c59c3bf3SSimon J. Gerraty# The ':S' modifier does not change the expression value in parse-only mode, 276c59c3bf3SSimon J. Gerraty# keeping the "Z", which is then skipped in parse-only mode. 277c59c3bf3SSimon J. Gerraty.if 0 && ${VAR:${M_invalid:S,^,N*,:ts:}} 278c59c3bf3SSimon J. Gerraty.endif 279c59c3bf3SSimon J. Gerraty# The ':@' modifier does not change the expression value in parse-only mode, 280c59c3bf3SSimon J. Gerraty# keeping the "Z", which is then skipped in parse-only mode. 281c59c3bf3SSimon J. Gerraty.if 0 && ${VAR:${M_invalid:@m@N*$m@:ts:}} 282c59c3bf3SSimon J. Gerraty.endif 283