1 1.41 rillig # $NetBSD: varmod-ifelse.mk,v 1.41 2025/06/29 11:27:21 rillig Exp $ 2 1.1 rillig # 3 1.2 rillig # Tests for the ${cond:?then:else} variable modifier, which evaluates either 4 1.2 rillig # the then-expression or the else-expression, depending on the condition. 5 1.5 rillig # 6 1.5 rillig # The modifier was added on 1998-04-01. 7 1.5 rillig # 8 1.5 rillig # Until 2015-10-11, the modifier always evaluated both the "then" and the 9 1.5 rillig # "else" expressions. 10 1.1 rillig 11 1.1 rillig # TODO: Implementation 12 1.1 rillig 13 1.5 rillig # The variable name of the expression is expanded and then taken as the 14 1.18 rillig # condition. In the below example it becomes: 15 1.5 rillig # 16 1.25 rillig # bare words == "literal" 17 1.5 rillig # 18 1.5 rillig # This confuses the parser, which expects an operator instead of the bare 19 1.5 rillig # word "expression". If the name were expanded lazily, everything would be 20 1.5 rillig # fine since the condition would be: 21 1.5 rillig # 22 1.25 rillig # ${:Ubare words} == "literal" 23 1.5 rillig # 24 1.5 rillig # Evaluating the variable name lazily would require additional code in 25 1.5 rillig # Var_Parse and ParseVarname, it would be more useful and predictable 26 1.5 rillig # though. 27 1.35 rillig # expect+1: Bad condition 28 1.25 rillig .if ${${:Ubare words} == "literal":?bad:bad} 29 1.5 rillig . error 30 1.5 rillig .else 31 1.5 rillig . error 32 1.5 rillig .endif 33 1.5 rillig 34 1.5 rillig # In a variable assignment, undefined variables are not an error. 35 1.5 rillig # Because of the early expansion, the whole condition evaluates to 36 1.5 rillig # ' == ""' though, which cannot be parsed because the left-hand side looks 37 1.5 rillig # empty. 38 1.34 rillig # expect+1: Bad condition 39 1.5 rillig COND:= ${${UNDEF} == "":?bad-assign:bad-assign} 40 1.5 rillig 41 1.37 rillig # In a conditional directive, undefined variables are reported as such. In a 42 1.37 rillig # ':?' modifier, though, the "variable name" is expanded first, and in that 43 1.37 rillig # context, an undefined expression is not an error. The "variable name" then 44 1.37 rillig # becomes the condition, in this case ' == ""', which is malformed because the 45 1.37 rillig # left-hand side looks empty. 46 1.35 rillig # expect+1: Bad condition 47 1.5 rillig .if ${${UNDEF} == "":?bad-cond:bad-cond} 48 1.5 rillig . error 49 1.5 rillig .else 50 1.5 rillig . error 51 1.5 rillig .endif 52 1.5 rillig 53 1.4 rillig # When the :? is parsed, it is greedy. The else branch spans all the 54 1.4 rillig # text, up until the closing character '}', even if the text looks like 55 1.4 rillig # another modifier. 56 1.4 rillig .if ${1:?then:else:Q} != "then" 57 1.4 rillig . error 58 1.4 rillig .endif 59 1.4 rillig .if ${0:?then:else:Q} != "else:Q" 60 1.4 rillig . error 61 1.4 rillig .endif 62 1.3 rillig 63 1.6 rillig # This line generates 2 error messages. The first comes from evaluating the 64 1.6 rillig # malformed conditional "1 == == 2", which is reported as "Bad conditional 65 1.25 rillig # expression" by ApplyModifier_IfElse. The expression containing that 66 1.6 rillig # conditional therefore returns a parse error from Var_Parse, and this parse 67 1.6 rillig # error propagates to CondEvalExpression, where the "Malformed conditional" 68 1.6 rillig # comes from. 69 1.35 rillig # expect+1: Bad condition 70 1.6 rillig .if ${1 == == 2:?yes:no} != "" 71 1.6 rillig . error 72 1.6 rillig .else 73 1.6 rillig . error 74 1.6 rillig .endif 75 1.6 rillig 76 1.41 rillig # If the "Bad condition" appears in a quoted string literal, the 77 1.6 rillig # error message "Malformed conditional" is not printed, leaving only the "Bad 78 1.41 rillig # condition". 79 1.6 rillig # 80 1.6 rillig # XXX: The left-hand side is enclosed in quotes. This results in Var_Parse 81 1.29 rillig # being called without VARE_EVAL_DEFINED. When ApplyModifier_IfElse 82 1.6 rillig # returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the 83 1.25 rillig # value of the expression is still undefined. CondParser_String is 84 1.6 rillig # then supposed to do proper error handling, but since varUndefined is local 85 1.6 rillig # to var.c, it cannot distinguish this return value from an ordinary empty 86 1.6 rillig # string. The left-hand side of the comparison is therefore just an empty 87 1.6 rillig # string, which is obviously equal to the empty string on the right-hand side. 88 1.6 rillig # 89 1.6 rillig # XXX: The debug log for -dc shows a comparison between 1.0 and 0.0. The 90 1.6 rillig # condition should be detected as being malformed before any comparison is 91 1.6 rillig # done since there is no well-formed comparison in the condition at all. 92 1.6 rillig .MAKEFLAGS: -dc 93 1.34 rillig # expect+1: Bad condition 94 1.6 rillig .if "${1 == == 2:?yes:no}" != "" 95 1.6 rillig . error 96 1.6 rillig .else 97 1.41 rillig . error 98 1.6 rillig .endif 99 1.6 rillig .MAKEFLAGS: -d0 100 1.6 rillig 101 1.23 rillig # As of 2020-12-10, the variable "VAR" is first expanded, and the result of 102 1.25 rillig # this expansion is then taken as the condition. To force the 103 1.7 rillig # expression in the condition to be evaluated at exactly the right point, 104 1.7 rillig # the '$' of the intended '${VAR}' escapes from the parser in form of the 105 1.23 rillig # expression ${:U\$}. Because of this escaping, the variable "VAR" and thus 106 1.7 rillig # the condition ends up as "${VAR} == value", just as intended. 107 1.8 rillig # 108 1.8 rillig # This hack does not work for variables from .for loops since these are 109 1.8 rillig # expanded at parse time to their corresponding ${:Uvalue} expressions. 110 1.8 rillig # Making the '$' of the '${VAR}' expression indirect hides this expression 111 1.9 rillig # from the parser of the .for loop body. See ForLoop_SubstVarLong. 112 1.7 rillig .MAKEFLAGS: -dc 113 1.7 rillig VAR= value 114 1.20 rillig .if ${ ${:U\$}{VAR} == value:?ok:bad} != "ok" 115 1.7 rillig . error 116 1.7 rillig .endif 117 1.7 rillig .MAKEFLAGS: -d0 118 1.7 rillig 119 1.14 rillig # On 2021-04-19, when building external/bsd/tmux with HAVE_LLVM=yes and 120 1.14 rillig # HAVE_GCC=no, the following conditional generated this error message: 121 1.14 rillig # 122 1.14 rillig # make: Bad conditional expression 'string == "literal" && no >= 10' 123 1.14 rillig # in 'string == "literal" && no >= 10?yes:no' 124 1.14 rillig # 125 1.14 rillig # Despite the error message (which was not clearly marked with "error:"), 126 1.14 rillig # the build continued, for historical reasons, see main_Exit. 127 1.14 rillig # 128 1.14 rillig # The tricky detail here is that the condition that looks so obvious in the 129 1.14 rillig # form written in the makefile becomes tricky when it is actually evaluated. 130 1.14 rillig # This is because the condition is written in the place of the variable name 131 1.14 rillig # of the expression, and in an expression, the variable name is always 132 1.14 rillig # expanded first, before even looking at the modifiers. This happens for the 133 1.14 rillig # modifier ':?' as well, so when CondEvalExpression gets to see the 134 1.14 rillig # expression, it already looks like this: 135 1.14 rillig # 136 1.14 rillig # string == "literal" && no >= 10 137 1.14 rillig # 138 1.14 rillig # When parsing such an expression, the parser used to be strict. It first 139 1.14 rillig # evaluated the left-hand side of the operator '&&' and then started parsing 140 1.14 rillig # the right-hand side 'no >= 10'. The word 'no' is obviously a string 141 1.19 rillig # literal, not enclosed in quotes, which is OK, even on the left-hand side of 142 1.14 rillig # the comparison operator, but only because this is a condition in the 143 1.14 rillig # modifier ':?'. In an ordinary directive '.if', this would be a parse error. 144 1.14 rillig # For strings, only the comparison operators '==' and '!=' are defined, 145 1.14 rillig # therefore parsing stopped at the '>', producing the 'Bad conditional 146 1.14 rillig # expression'. 147 1.12 rillig # 148 1.16 rillig # Ideally, the conditional expression would not be expanded before parsing 149 1.16 rillig # it. This would allow to write the conditions exactly as seen below. That 150 1.16 rillig # change has a high chance of breaking _some_ existing code and would need 151 1.16 rillig # to be thoroughly tested. 152 1.16 rillig # 153 1.16 rillig # Since cond.c 1.262 from 2021-04-20, make reports a more specific error 154 1.16 rillig # message in situations like these, pointing directly to the specific problem 155 1.16 rillig # instead of just saying that the whole condition is bad. 156 1.12 rillig STRING= string 157 1.14 rillig NUMBER= no # not really a number 158 1.22 rillig # expect+1: no. 159 1.12 rillig .info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. 160 1.40 rillig # expect+2: Comparison with ">=" requires both operands "no" and "10" to be numeric 161 1.23 rillig # expect+1: . 162 1.14 rillig .info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. 163 1.15 rillig 164 1.15 rillig # The following situation occasionally occurs with MKINET6 or similar 165 1.15 rillig # variables. 166 1.15 rillig NUMBER= # empty, not really a number either 167 1.34 rillig # expect+2: Bad condition 168 1.22 rillig # expect+1: . 169 1.15 rillig .info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. 170 1.34 rillig # expect+2: Bad condition 171 1.22 rillig # expect+1: . 172 1.15 rillig .info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. 173 1.17 rillig 174 1.17 rillig # CondParser_LeafToken handles [0-9-+] specially, treating them as a number. 175 1.17 rillig PLUS= + 176 1.17 rillig ASTERISK= * 177 1.17 rillig EMPTY= # empty 178 1.17 rillig # "true" since "+" is not the empty string. 179 1.23 rillig # expect+1: <true> 180 1.23 rillig .info <${${PLUS} :?true:false}> 181 1.17 rillig # "false" since the variable named "*" is not defined. 182 1.23 rillig # expect+1: <false> 183 1.23 rillig .info <${${ASTERISK} :?true:false}> 184 1.17 rillig # syntax error since the condition is completely blank. 185 1.34 rillig # expect+2: Bad condition 186 1.23 rillig # expect+1: <> 187 1.23 rillig .info <${${EMPTY} :?true:false}> 188 1.19 rillig 189 1.19 rillig 190 1.19 rillig # Since the condition of the '?:' modifier is expanded before being parsed and 191 1.19 rillig # evaluated, it is common practice to enclose expressions in quotes, to avoid 192 1.19 rillig # producing syntactically invalid conditions such as ' == value'. This only 193 1.19 rillig # works if the expanded values neither contain quotes nor backslashes. For 194 1.19 rillig # strings containing quotes or backslashes, the '?:' modifier should not be 195 1.19 rillig # used. 196 1.19 rillig PRIMES= 2 3 5 7 11 197 1.19 rillig .if ${1 2 3 4 5:L:@n@$n:${ ("${PRIMES:M$n}" != "") :?prime:not_prime}@} != \ 198 1.19 rillig "1:not_prime 2:prime 3:prime 4:not_prime 5:prime" 199 1.19 rillig . error 200 1.19 rillig .endif 201 1.21 rillig 202 1.21 rillig # When parsing the modifier ':?', there are 3 possible cases: 203 1.21 rillig # 204 1.21 rillig # 1. The whole expression is only parsed. 205 1.21 rillig # 2. The expression is parsed and the 'then' branch is evaluated. 206 1.21 rillig # 3. The expression is parsed and the 'else' branch is evaluated. 207 1.21 rillig # 208 1.21 rillig # In all of these cases, the expression must be parsed in the same way, 209 1.21 rillig # especially when one of the branches contains unbalanced '{}' braces. 210 1.21 rillig # 211 1.21 rillig # At 2020-01-01, the expressions from the 'then' and 'else' branches were 212 1.21 rillig # parsed differently, depending on whether the branch was taken or not. When 213 1.21 rillig # the branch was taken, the parser recognized that in the modifier ':S,}},,', 214 1.21 rillig # the '}}' were ordinary characters. When the branch was not taken, the 215 1.21 rillig # parser only counted balanced '{' and '}', ignoring any escaping or other 216 1.21 rillig # changes in the interpretation. 217 1.21 rillig # 218 1.21 rillig # In var.c 1.285 from 2020-07-20, the parsing of the expressions changed so 219 1.21 rillig # that in both cases the expression is parsed in the same way, taking the 220 1.21 rillig # unbalanced braces in the ':S' modifiers into account. This change was not 221 1.21 rillig # on purpose, the commit message mentioned 'has the same effect', which was a 222 1.21 rillig # wrong assumption. 223 1.21 rillig # 224 1.21 rillig # In var.c 1.323 from 2020-07-26, the unintended fix from var.c 1.285 was 225 1.21 rillig # reverted, still not knowing about the difference between regular parsing and 226 1.21 rillig # balanced-mode parsing. 227 1.21 rillig # 228 1.21 rillig # In var.c 1.1028 from 2022-08-08, there was another attempt at fixing this 229 1.21 rillig # inconsistency in parsing, but since that broke parsing of the modifier ':@', 230 1.21 rillig # it was reverted in var.c 1.1029 from 2022-08-23. 231 1.21 rillig # 232 1.21 rillig # In var.c 1.1047 from 2023-02-18, the inconsistency in parsing was finally 233 1.21 rillig # fixed. The modifier ':@' now parses the body in balanced mode, while 234 1.21 rillig # everywhere else the modifier parts have their subexpressions parsed in the 235 1.21 rillig # same way, no matter whether they are evaluated or not. 236 1.21 rillig # 237 1.21 rillig # The modifiers ':@' and ':?' are similar in that they conceptually contain 238 1.21 rillig # text to be evaluated later or conditionally, still they parse that text 239 1.21 rillig # differently. The crucial difference is that the body of the modifier ':@' 240 1.21 rillig # is always parsed using balanced mode. The modifier ':?', on the other hand, 241 1.21 rillig # must parse both of its branches in the same way, no matter whether they are 242 1.21 rillig # evaluated or not. Since balanced mode and standard mode are incompatible, 243 1.21 rillig # it's impossible to use balanced mode in the modifier ':?'. 244 1.21 rillig .MAKEFLAGS: -dc 245 1.21 rillig .if 0 && ${1:?${:Uthen0:S,}},,}:${:Uelse0:S,}},,}} != "not evaluated" 246 1.21 rillig # At 2020-01-07, the expression evaluated to 'then0,,}}', even though it was 247 1.21 rillig # irrelevant as the '0' had already been evaluated to 'false'. 248 1.21 rillig . error 249 1.21 rillig .endif 250 1.21 rillig .if 1 && ${0:?${:Uthen1:S,}},,}:${:Uelse1:S,}},,}} != "else1" 251 1.21 rillig . error 252 1.21 rillig .endif 253 1.21 rillig .if 2 && ${1:?${:Uthen2:S,}},,}:${:Uelse2:S,}},,}} != "then2" 254 1.21 rillig # At 2020-01-07, the whole expression evaluated to 'then2,,}}' instead of the 255 1.21 rillig # expected 'then2'. The 'then' branch of the ':?' modifier was parsed 256 1.21 rillig # normally, parsing and evaluating the ':S' modifier, thereby treating the 257 1.21 rillig # '}}' as ordinary characters and resulting in 'then2'. The 'else' branch was 258 1.21 rillig # parsed in balanced mode, ignoring that the inner '}}' were ordinary 259 1.21 rillig # characters. The '}}' were thus interpreted as the end of the 'else' branch 260 1.21 rillig # and the whole expression. This left the trailing ',,}}', which together 261 1.21 rillig # with the 'then2' formed the result 'then2,,}}'. 262 1.21 rillig . error 263 1.21 rillig .endif 264 1.23 rillig 265 1.23 rillig 266 1.23 rillig # Since the condition is taken from the variable name of the expression, not 267 1.23 rillig # from its value, it is evaluated early. It is possible though to construct 268 1.23 rillig # conditions that are evaluated lazily, at exactly the right point. There is 269 1.23 rillig # no way to escape a '$' directly in the variable name, but there are 270 1.23 rillig # alternative ways to bring a '$' into the condition. 271 1.23 rillig # 272 1.23 rillig # In an indirect condition using the ':U' modifier, each '$', ':' and 273 1.23 rillig # '}' must be escaped as '\$', '\:' and '\}', respectively, but '{' must 274 1.23 rillig # not be escaped. 275 1.23 rillig # 276 1.23 rillig # In an indirect condition using a separate variable, each '$' must be 277 1.23 rillig # escaped as '$$'. 278 1.23 rillig # 279 1.23 rillig # These two forms allow the variables to contain arbitrary characters, as the 280 1.23 rillig # condition parser does not see them. 281 1.23 rillig DELAYED= two 282 1.23 rillig # expect+1: no 283 1.23 rillig .info ${ ${:U \${DELAYED\} == "one"}:?yes:no} 284 1.23 rillig # expect+1: yes 285 1.23 rillig .info ${ ${:U \${DELAYED\} == "two"}:?yes:no} 286 1.23 rillig INDIRECT_COND1= $${DELAYED} == "one" 287 1.23 rillig # expect+1: no 288 1.23 rillig .info ${ ${INDIRECT_COND1}:?yes:no} 289 1.23 rillig INDIRECT_COND2= $${DELAYED} == "two" 290 1.23 rillig # expect+1: yes 291 1.23 rillig .info ${ ${INDIRECT_COND2}:?yes:no} 292 1.23 rillig 293 1.23 rillig 294 1.21 rillig .MAKEFLAGS: -d0 295 1.24 rillig 296 1.24 rillig 297 1.24 rillig # In the modifier parts for the 'then' and 'else' branches, subexpressions are 298 1.28 rillig # parsed by inspecting the actual modifiers. In 2008, 2015, 2020, 2022 and 299 1.24 rillig # 2023, the exact parsing algorithm switched a few times, counting balanced 300 1.24 rillig # braces instead of proper subexpressions, which meant that unbalanced braces 301 1.24 rillig # were parsed differently, depending on whether the branch was active or not. 302 1.24 rillig BRACES= }}} 303 1.24 rillig NO= ${0:?${BRACES:S,}}},yes,}:${BRACES:S,}}},no,}} 304 1.24 rillig YES= ${1:?${BRACES:S,}}},yes,}:${BRACES:S,}}},no,}} 305 1.24 rillig BOTH= <${YES}> <${NO}> 306 1.24 rillig .if ${BOTH} != "<yes> <no>" 307 1.24 rillig . error 308 1.24 rillig .endif 309 1.30 rillig 310 1.30 rillig 311 1.36 rillig # expect+2: Unknown modifier ":X-then" 312 1.36 rillig # expect+1: Unknown modifier ":X-else" 313 1.30 rillig .if ${1:?${:X-then}:${:X-else}} 314 1.30 rillig .endif 315 1.38 rillig 316 1.38 rillig 317 1.39 rillig # expect+4: Bad condition 318 1.38 rillig # expect+3: Unknown modifier ":Z1" 319 1.38 rillig # expect+2: Unknown modifier ":Z2" 320 1.39 rillig # expect+1: <> 321 1.39 rillig .info <${ < 0 :?${:Z1}:${:Z2}}> 322