1*8d5c8e21SSimon J. Gerraty# $NetBSD: varmod-ifelse.mk,v 1.29 2024/06/02 15:31:26 rillig Exp $ 22c3632d1SSimon J. Gerraty# 32c3632d1SSimon J. Gerraty# Tests for the ${cond:?then:else} variable modifier, which evaluates either 42c3632d1SSimon J. Gerraty# the then-expression or the else-expression, depending on the condition. 5956e45f6SSimon J. Gerraty# 6956e45f6SSimon J. Gerraty# The modifier was added on 1998-04-01. 7956e45f6SSimon J. Gerraty# 8956e45f6SSimon J. Gerraty# Until 2015-10-11, the modifier always evaluated both the "then" and the 9956e45f6SSimon J. Gerraty# "else" expressions. 102c3632d1SSimon J. Gerraty 112c3632d1SSimon J. Gerraty# TODO: Implementation 122c3632d1SSimon J. Gerraty 13956e45f6SSimon J. Gerraty# The variable name of the expression is expanded and then taken as the 149f45a3c8SSimon J. Gerraty# condition. In the below example it becomes: 15956e45f6SSimon J. Gerraty# 16d5e0a182SSimon J. Gerraty# bare words == "literal" 17956e45f6SSimon J. Gerraty# 18956e45f6SSimon J. Gerraty# This confuses the parser, which expects an operator instead of the bare 19956e45f6SSimon J. Gerraty# word "expression". If the name were expanded lazily, everything would be 20956e45f6SSimon J. Gerraty# fine since the condition would be: 21956e45f6SSimon J. Gerraty# 22d5e0a182SSimon J. Gerraty# ${:Ubare words} == "literal" 23956e45f6SSimon J. Gerraty# 24956e45f6SSimon J. Gerraty# Evaluating the variable name lazily would require additional code in 25956e45f6SSimon J. Gerraty# Var_Parse and ParseVarname, it would be more useful and predictable 26956e45f6SSimon J. Gerraty# though. 27d5e0a182SSimon J. Gerraty# expect+1: Malformed conditional (${${:Ubare words} == "literal":?bad:bad}) 28d5e0a182SSimon J. Gerraty.if ${${:Ubare words} == "literal":?bad:bad} 29956e45f6SSimon J. Gerraty. error 30956e45f6SSimon J. Gerraty.else 31956e45f6SSimon J. Gerraty. error 32956e45f6SSimon J. Gerraty.endif 33956e45f6SSimon J. Gerraty 34956e45f6SSimon J. Gerraty# In a variable assignment, undefined variables are not an error. 35956e45f6SSimon J. Gerraty# Because of the early expansion, the whole condition evaluates to 36956e45f6SSimon J. Gerraty# ' == ""' though, which cannot be parsed because the left-hand side looks 37956e45f6SSimon J. Gerraty# empty. 38956e45f6SSimon J. GerratyCOND:= ${${UNDEF} == "":?bad-assign:bad-assign} 39956e45f6SSimon J. Gerraty 40956e45f6SSimon J. Gerraty# In a condition, undefined variables generate a "Malformed conditional" 41956e45f6SSimon J. Gerraty# error. That error message is wrong though. In lint mode, the correct 42956e45f6SSimon J. Gerraty# "Undefined variable" error message is generated. 43956e45f6SSimon J. Gerraty# The difference to the ':=' variable assignment is the additional 44956e45f6SSimon J. Gerraty# "Malformed conditional" error message. 45148ee845SSimon J. Gerraty# expect+1: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond}) 46956e45f6SSimon J. Gerraty.if ${${UNDEF} == "":?bad-cond:bad-cond} 47956e45f6SSimon J. Gerraty. error 48956e45f6SSimon J. Gerraty.else 49956e45f6SSimon J. Gerraty. error 50956e45f6SSimon J. Gerraty.endif 51956e45f6SSimon J. Gerraty 52956e45f6SSimon J. Gerraty# When the :? is parsed, it is greedy. The else branch spans all the 53956e45f6SSimon J. Gerraty# text, up until the closing character '}', even if the text looks like 54956e45f6SSimon J. Gerraty# another modifier. 55956e45f6SSimon J. Gerraty.if ${1:?then:else:Q} != "then" 56956e45f6SSimon J. Gerraty. error 57956e45f6SSimon J. Gerraty.endif 58956e45f6SSimon J. Gerraty.if ${0:?then:else:Q} != "else:Q" 59956e45f6SSimon J. Gerraty. error 60956e45f6SSimon J. Gerraty.endif 61956e45f6SSimon J. Gerraty 62e2eeea75SSimon J. Gerraty# This line generates 2 error messages. The first comes from evaluating the 63e2eeea75SSimon J. Gerraty# malformed conditional "1 == == 2", which is reported as "Bad conditional 64d5e0a182SSimon J. Gerraty# expression" by ApplyModifier_IfElse. The expression containing that 65e2eeea75SSimon J. Gerraty# conditional therefore returns a parse error from Var_Parse, and this parse 66e2eeea75SSimon J. Gerraty# error propagates to CondEvalExpression, where the "Malformed conditional" 67e2eeea75SSimon J. Gerraty# comes from. 68148ee845SSimon J. Gerraty# expect+1: Malformed conditional (${1 == == 2:?yes:no} != "") 69e2eeea75SSimon J. Gerraty.if ${1 == == 2:?yes:no} != "" 70e2eeea75SSimon J. Gerraty. error 71e2eeea75SSimon J. Gerraty.else 72e2eeea75SSimon J. Gerraty. error 73e2eeea75SSimon J. Gerraty.endif 74e2eeea75SSimon J. Gerraty 75e2eeea75SSimon J. Gerraty# If the "Bad conditional expression" appears in a quoted string literal, the 76e2eeea75SSimon J. Gerraty# error message "Malformed conditional" is not printed, leaving only the "Bad 77e2eeea75SSimon J. Gerraty# conditional expression". 78e2eeea75SSimon J. Gerraty# 79e2eeea75SSimon J. Gerraty# XXX: The left-hand side is enclosed in quotes. This results in Var_Parse 80*8d5c8e21SSimon J. Gerraty# being called without VARE_EVAL_DEFINED. When ApplyModifier_IfElse 81e2eeea75SSimon J. Gerraty# returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the 82d5e0a182SSimon J. Gerraty# value of the expression is still undefined. CondParser_String is 83e2eeea75SSimon J. Gerraty# then supposed to do proper error handling, but since varUndefined is local 84e2eeea75SSimon J. Gerraty# to var.c, it cannot distinguish this return value from an ordinary empty 85e2eeea75SSimon J. Gerraty# string. The left-hand side of the comparison is therefore just an empty 86e2eeea75SSimon J. Gerraty# string, which is obviously equal to the empty string on the right-hand side. 87e2eeea75SSimon J. Gerraty# 88e2eeea75SSimon J. Gerraty# XXX: The debug log for -dc shows a comparison between 1.0 and 0.0. The 89e2eeea75SSimon J. Gerraty# condition should be detected as being malformed before any comparison is 90e2eeea75SSimon J. Gerraty# done since there is no well-formed comparison in the condition at all. 91e2eeea75SSimon J. Gerraty.MAKEFLAGS: -dc 92e2eeea75SSimon J. Gerraty.if "${1 == == 2:?yes:no}" != "" 93e2eeea75SSimon J. Gerraty. error 94e2eeea75SSimon J. Gerraty.else 95148ee845SSimon J. Gerraty# expect+1: warning: Oops, the parse error should have been propagated. 96e2eeea75SSimon J. Gerraty. warning Oops, the parse error should have been propagated. 97e2eeea75SSimon J. Gerraty.endif 98e2eeea75SSimon J. Gerraty.MAKEFLAGS: -d0 99e2eeea75SSimon J. Gerraty 10098875883SSimon J. Gerraty# As of 2020-12-10, the variable "VAR" is first expanded, and the result of 101d5e0a182SSimon J. Gerraty# this expansion is then taken as the condition. To force the 10206b9b3e0SSimon J. Gerraty# expression in the condition to be evaluated at exactly the right point, 10306b9b3e0SSimon J. Gerraty# the '$' of the intended '${VAR}' escapes from the parser in form of the 10498875883SSimon J. Gerraty# expression ${:U\$}. Because of this escaping, the variable "VAR" and thus 10506b9b3e0SSimon J. Gerraty# the condition ends up as "${VAR} == value", just as intended. 10606b9b3e0SSimon J. Gerraty# 10706b9b3e0SSimon J. Gerraty# This hack does not work for variables from .for loops since these are 10806b9b3e0SSimon J. Gerraty# expanded at parse time to their corresponding ${:Uvalue} expressions. 10906b9b3e0SSimon J. Gerraty# Making the '$' of the '${VAR}' expression indirect hides this expression 110dba7b0efSSimon J. Gerraty# from the parser of the .for loop body. See ForLoop_SubstVarLong. 11106b9b3e0SSimon J. Gerraty.MAKEFLAGS: -dc 11206b9b3e0SSimon J. GerratyVAR= value 11306b9b3e0SSimon J. Gerraty.if ${ ${:U\$}{VAR} == value:?ok:bad} != "ok" 11406b9b3e0SSimon J. Gerraty. error 11506b9b3e0SSimon J. Gerraty.endif 11606b9b3e0SSimon J. Gerraty.MAKEFLAGS: -d0 11706b9b3e0SSimon J. Gerraty 118b0c40a00SSimon J. Gerraty# On 2021-04-19, when building external/bsd/tmux with HAVE_LLVM=yes and 119b0c40a00SSimon J. Gerraty# HAVE_GCC=no, the following conditional generated this error message: 120b0c40a00SSimon J. Gerraty# 121b0c40a00SSimon J. Gerraty# make: Bad conditional expression 'string == "literal" && no >= 10' 122b0c40a00SSimon J. Gerraty# in 'string == "literal" && no >= 10?yes:no' 123b0c40a00SSimon J. Gerraty# 124b0c40a00SSimon J. Gerraty# Despite the error message (which was not clearly marked with "error:"), 125b0c40a00SSimon J. Gerraty# the build continued, for historical reasons, see main_Exit. 126b0c40a00SSimon J. Gerraty# 127b0c40a00SSimon J. Gerraty# The tricky detail here is that the condition that looks so obvious in the 128b0c40a00SSimon J. Gerraty# form written in the makefile becomes tricky when it is actually evaluated. 129b0c40a00SSimon J. Gerraty# This is because the condition is written in the place of the variable name 130b0c40a00SSimon J. Gerraty# of the expression, and in an expression, the variable name is always 131b0c40a00SSimon J. Gerraty# expanded first, before even looking at the modifiers. This happens for the 132b0c40a00SSimon J. Gerraty# modifier ':?' as well, so when CondEvalExpression gets to see the 133b0c40a00SSimon J. Gerraty# expression, it already looks like this: 134b0c40a00SSimon J. Gerraty# 135b0c40a00SSimon J. Gerraty# string == "literal" && no >= 10 136b0c40a00SSimon J. Gerraty# 137b0c40a00SSimon J. Gerraty# When parsing such an expression, the parser used to be strict. It first 138b0c40a00SSimon J. Gerraty# evaluated the left-hand side of the operator '&&' and then started parsing 139b0c40a00SSimon J. Gerraty# the right-hand side 'no >= 10'. The word 'no' is obviously a string 140954401e6SSimon J. Gerraty# literal, not enclosed in quotes, which is OK, even on the left-hand side of 141b0c40a00SSimon J. Gerraty# the comparison operator, but only because this is a condition in the 142b0c40a00SSimon J. Gerraty# modifier ':?'. In an ordinary directive '.if', this would be a parse error. 143b0c40a00SSimon J. Gerraty# For strings, only the comparison operators '==' and '!=' are defined, 144b0c40a00SSimon J. Gerraty# therefore parsing stopped at the '>', producing the 'Bad conditional 145b0c40a00SSimon J. Gerraty# expression'. 146b0c40a00SSimon J. Gerraty# 147b0c40a00SSimon J. Gerraty# Ideally, the conditional expression would not be expanded before parsing 148b0c40a00SSimon J. Gerraty# it. This would allow to write the conditions exactly as seen below. That 149b0c40a00SSimon J. Gerraty# change has a high chance of breaking _some_ existing code and would need 150b0c40a00SSimon J. Gerraty# to be thoroughly tested. 151b0c40a00SSimon J. Gerraty# 152b0c40a00SSimon J. Gerraty# Since cond.c 1.262 from 2021-04-20, make reports a more specific error 153b0c40a00SSimon J. Gerraty# message in situations like these, pointing directly to the specific problem 154b0c40a00SSimon J. Gerraty# instead of just saying that the whole condition is bad. 155b0c40a00SSimon J. GerratySTRING= string 156b0c40a00SSimon J. GerratyNUMBER= no # not really a number 157148ee845SSimon J. Gerraty# expect+1: no. 158b0c40a00SSimon J. Gerraty.info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. 159548bfc56SSimon J. Gerraty# expect+3: while evaluating variable "string == "literal" || no >= 10": Comparison with '>=' requires both operands 'no' and '10' to be numeric 160d5e0a182SSimon J. Gerraty# expect: make: Bad conditional expression 'string == "literal" || no >= 10' before '?yes:no' 16198875883SSimon J. Gerraty# expect+1: . 162b0c40a00SSimon J. Gerraty.info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. 163b0c40a00SSimon J. Gerraty 164b0c40a00SSimon J. Gerraty# The following situation occasionally occurs with MKINET6 or similar 165b0c40a00SSimon J. Gerraty# variables. 166b0c40a00SSimon J. GerratyNUMBER= # empty, not really a number either 167d5e0a182SSimon J. Gerraty# expect: make: Bad conditional expression 'string == "literal" && >= 10' before '?yes:no' 168148ee845SSimon J. Gerraty# expect+1: . 169b0c40a00SSimon J. Gerraty.info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. 170d5e0a182SSimon J. Gerraty# expect: make: Bad conditional expression 'string == "literal" || >= 10' before '?yes:no' 171148ee845SSimon J. Gerraty# expect+1: . 172b0c40a00SSimon J. Gerraty.info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. 173b0c40a00SSimon J. Gerraty 174b0c40a00SSimon J. Gerraty# CondParser_LeafToken handles [0-9-+] specially, treating them as a number. 175b0c40a00SSimon J. GerratyPLUS= + 176b0c40a00SSimon J. GerratyASTERISK= * 177b0c40a00SSimon J. GerratyEMPTY= # empty 178b0c40a00SSimon J. Gerraty# "true" since "+" is not the empty string. 17998875883SSimon J. Gerraty# expect+1: <true> 18098875883SSimon J. Gerraty.info <${${PLUS} :?true:false}> 181b0c40a00SSimon J. Gerraty# "false" since the variable named "*" is not defined. 18298875883SSimon J. Gerraty# expect+1: <false> 18398875883SSimon J. Gerraty.info <${${ASTERISK} :?true:false}> 184b0c40a00SSimon J. Gerraty# syntax error since the condition is completely blank. 18598875883SSimon J. Gerraty# expect+1: <> 18698875883SSimon J. Gerraty.info <${${EMPTY} :?true:false}> 187954401e6SSimon J. Gerraty 188954401e6SSimon J. Gerraty 189954401e6SSimon J. Gerraty# Since the condition of the '?:' modifier is expanded before being parsed and 190954401e6SSimon J. Gerraty# evaluated, it is common practice to enclose expressions in quotes, to avoid 191954401e6SSimon J. Gerraty# producing syntactically invalid conditions such as ' == value'. This only 192954401e6SSimon J. Gerraty# works if the expanded values neither contain quotes nor backslashes. For 193954401e6SSimon J. Gerraty# strings containing quotes or backslashes, the '?:' modifier should not be 194954401e6SSimon J. Gerraty# used. 195954401e6SSimon J. GerratyPRIMES= 2 3 5 7 11 196954401e6SSimon J. Gerraty.if ${1 2 3 4 5:L:@n@$n:${ ("${PRIMES:M$n}" != "") :?prime:not_prime}@} != \ 197954401e6SSimon J. Gerraty "1:not_prime 2:prime 3:prime 4:not_prime 5:prime" 198954401e6SSimon J. Gerraty. error 199954401e6SSimon J. Gerraty.endif 2008c973ee2SSimon J. Gerraty 2018c973ee2SSimon J. Gerraty# When parsing the modifier ':?', there are 3 possible cases: 2028c973ee2SSimon J. Gerraty# 2038c973ee2SSimon J. Gerraty# 1. The whole expression is only parsed. 2048c973ee2SSimon J. Gerraty# 2. The expression is parsed and the 'then' branch is evaluated. 2058c973ee2SSimon J. Gerraty# 3. The expression is parsed and the 'else' branch is evaluated. 2068c973ee2SSimon J. Gerraty# 2078c973ee2SSimon J. Gerraty# In all of these cases, the expression must be parsed in the same way, 2088c973ee2SSimon J. Gerraty# especially when one of the branches contains unbalanced '{}' braces. 2098c973ee2SSimon J. Gerraty# 2108c973ee2SSimon J. Gerraty# At 2020-01-01, the expressions from the 'then' and 'else' branches were 2118c973ee2SSimon J. Gerraty# parsed differently, depending on whether the branch was taken or not. When 2128c973ee2SSimon J. Gerraty# the branch was taken, the parser recognized that in the modifier ':S,}},,', 2138c973ee2SSimon J. Gerraty# the '}}' were ordinary characters. When the branch was not taken, the 2148c973ee2SSimon J. Gerraty# parser only counted balanced '{' and '}', ignoring any escaping or other 2158c973ee2SSimon J. Gerraty# changes in the interpretation. 2168c973ee2SSimon J. Gerraty# 2178c973ee2SSimon J. Gerraty# In var.c 1.285 from 2020-07-20, the parsing of the expressions changed so 2188c973ee2SSimon J. Gerraty# that in both cases the expression is parsed in the same way, taking the 2198c973ee2SSimon J. Gerraty# unbalanced braces in the ':S' modifiers into account. This change was not 2208c973ee2SSimon J. Gerraty# on purpose, the commit message mentioned 'has the same effect', which was a 2218c973ee2SSimon J. Gerraty# wrong assumption. 2228c973ee2SSimon J. Gerraty# 2238c973ee2SSimon J. Gerraty# In var.c 1.323 from 2020-07-26, the unintended fix from var.c 1.285 was 2248c973ee2SSimon J. Gerraty# reverted, still not knowing about the difference between regular parsing and 2258c973ee2SSimon J. Gerraty# balanced-mode parsing. 2268c973ee2SSimon J. Gerraty# 2278c973ee2SSimon J. Gerraty# In var.c 1.1028 from 2022-08-08, there was another attempt at fixing this 2288c973ee2SSimon J. Gerraty# inconsistency in parsing, but since that broke parsing of the modifier ':@', 2298c973ee2SSimon J. Gerraty# it was reverted in var.c 1.1029 from 2022-08-23. 2308c973ee2SSimon J. Gerraty# 2318c973ee2SSimon J. Gerraty# In var.c 1.1047 from 2023-02-18, the inconsistency in parsing was finally 2328c973ee2SSimon J. Gerraty# fixed. The modifier ':@' now parses the body in balanced mode, while 2338c973ee2SSimon J. Gerraty# everywhere else the modifier parts have their subexpressions parsed in the 2348c973ee2SSimon J. Gerraty# same way, no matter whether they are evaluated or not. 2358c973ee2SSimon J. Gerraty# 2368c973ee2SSimon J. Gerraty# The modifiers ':@' and ':?' are similar in that they conceptually contain 2378c973ee2SSimon J. Gerraty# text to be evaluated later or conditionally, still they parse that text 2388c973ee2SSimon J. Gerraty# differently. The crucial difference is that the body of the modifier ':@' 2398c973ee2SSimon J. Gerraty# is always parsed using balanced mode. The modifier ':?', on the other hand, 2408c973ee2SSimon J. Gerraty# must parse both of its branches in the same way, no matter whether they are 2418c973ee2SSimon J. Gerraty# evaluated or not. Since balanced mode and standard mode are incompatible, 2428c973ee2SSimon J. Gerraty# it's impossible to use balanced mode in the modifier ':?'. 2438c973ee2SSimon J. Gerraty.MAKEFLAGS: -dc 2448c973ee2SSimon J. Gerraty.if 0 && ${1:?${:Uthen0:S,}},,}:${:Uelse0:S,}},,}} != "not evaluated" 2458c973ee2SSimon J. Gerraty# At 2020-01-07, the expression evaluated to 'then0,,}}', even though it was 2468c973ee2SSimon J. Gerraty# irrelevant as the '0' had already been evaluated to 'false'. 2478c973ee2SSimon J. Gerraty. error 2488c973ee2SSimon J. Gerraty.endif 2498c973ee2SSimon J. Gerraty.if 1 && ${0:?${:Uthen1:S,}},,}:${:Uelse1:S,}},,}} != "else1" 2508c973ee2SSimon J. Gerraty. error 2518c973ee2SSimon J. Gerraty.endif 2528c973ee2SSimon J. Gerraty.if 2 && ${1:?${:Uthen2:S,}},,}:${:Uelse2:S,}},,}} != "then2" 2538c973ee2SSimon J. Gerraty# At 2020-01-07, the whole expression evaluated to 'then2,,}}' instead of the 2548c973ee2SSimon J. Gerraty# expected 'then2'. The 'then' branch of the ':?' modifier was parsed 2558c973ee2SSimon J. Gerraty# normally, parsing and evaluating the ':S' modifier, thereby treating the 2568c973ee2SSimon J. Gerraty# '}}' as ordinary characters and resulting in 'then2'. The 'else' branch was 2578c973ee2SSimon J. Gerraty# parsed in balanced mode, ignoring that the inner '}}' were ordinary 2588c973ee2SSimon J. Gerraty# characters. The '}}' were thus interpreted as the end of the 'else' branch 2598c973ee2SSimon J. Gerraty# and the whole expression. This left the trailing ',,}}', which together 2608c973ee2SSimon J. Gerraty# with the 'then2' formed the result 'then2,,}}'. 2618c973ee2SSimon J. Gerraty. error 2628c973ee2SSimon J. Gerraty.endif 26398875883SSimon J. Gerraty 26498875883SSimon J. Gerraty 26598875883SSimon J. Gerraty# Since the condition is taken from the variable name of the expression, not 26698875883SSimon J. Gerraty# from its value, it is evaluated early. It is possible though to construct 26798875883SSimon J. Gerraty# conditions that are evaluated lazily, at exactly the right point. There is 26898875883SSimon J. Gerraty# no way to escape a '$' directly in the variable name, but there are 26998875883SSimon J. Gerraty# alternative ways to bring a '$' into the condition. 27098875883SSimon J. Gerraty# 27198875883SSimon J. Gerraty# In an indirect condition using the ':U' modifier, each '$', ':' and 27298875883SSimon J. Gerraty# '}' must be escaped as '\$', '\:' and '\}', respectively, but '{' must 27398875883SSimon J. Gerraty# not be escaped. 27498875883SSimon J. Gerraty# 27598875883SSimon J. Gerraty# In an indirect condition using a separate variable, each '$' must be 27698875883SSimon J. Gerraty# escaped as '$$'. 27798875883SSimon J. Gerraty# 27898875883SSimon J. Gerraty# These two forms allow the variables to contain arbitrary characters, as the 27998875883SSimon J. Gerraty# condition parser does not see them. 28098875883SSimon J. GerratyDELAYED= two 28198875883SSimon J. Gerraty# expect+1: no 28298875883SSimon J. Gerraty.info ${ ${:U \${DELAYED\} == "one"}:?yes:no} 28398875883SSimon J. Gerraty# expect+1: yes 28498875883SSimon J. Gerraty.info ${ ${:U \${DELAYED\} == "two"}:?yes:no} 28598875883SSimon J. GerratyINDIRECT_COND1= $${DELAYED} == "one" 28698875883SSimon J. Gerraty# expect+1: no 28798875883SSimon J. Gerraty.info ${ ${INDIRECT_COND1}:?yes:no} 28898875883SSimon J. GerratyINDIRECT_COND2= $${DELAYED} == "two" 28998875883SSimon J. Gerraty# expect+1: yes 29098875883SSimon J. Gerraty.info ${ ${INDIRECT_COND2}:?yes:no} 29198875883SSimon J. Gerraty 29298875883SSimon J. Gerraty 2938c973ee2SSimon J. Gerraty.MAKEFLAGS: -d0 294d5e0a182SSimon J. Gerraty 295d5e0a182SSimon J. Gerraty 296d5e0a182SSimon J. Gerraty# In the modifier parts for the 'then' and 'else' branches, subexpressions are 297548bfc56SSimon J. Gerraty# parsed by inspecting the actual modifiers. In 2008, 2015, 2020, 2022 and 298d5e0a182SSimon J. Gerraty# 2023, the exact parsing algorithm switched a few times, counting balanced 299d5e0a182SSimon J. Gerraty# braces instead of proper subexpressions, which meant that unbalanced braces 300d5e0a182SSimon J. Gerraty# were parsed differently, depending on whether the branch was active or not. 301d5e0a182SSimon J. GerratyBRACES= }}} 302d5e0a182SSimon J. GerratyNO= ${0:?${BRACES:S,}}},yes,}:${BRACES:S,}}},no,}} 303d5e0a182SSimon J. GerratyYES= ${1:?${BRACES:S,}}},yes,}:${BRACES:S,}}},no,}} 304d5e0a182SSimon J. GerratyBOTH= <${YES}> <${NO}> 305d5e0a182SSimon J. Gerraty.if ${BOTH} != "<yes> <no>" 306d5e0a182SSimon J. Gerraty. error 307d5e0a182SSimon J. Gerraty.endif 308