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