xref: /freebsd/contrib/bmake/unit-tests/varmod-ifelse.mk (revision 759b177aecbfc49ebc900739954ac56b1aa5fc53)
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