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