xref: /freebsd/contrib/bmake/unit-tests/varmod-indirect.mk (revision 6a7405f5a6b639682cacf01e35d561411ff556aa)
1*6a7405f5SSimon J. Gerraty# $NetBSD: varmod-indirect.mk,v 1.21 2024/08/29 20:20:36 rillig Exp $
206b9b3e0SSimon J. Gerraty#
306b9b3e0SSimon J. Gerraty# Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}.
406b9b3e0SSimon J. Gerraty# These can be used for very basic purposes like converting a string to either
506b9b3e0SSimon J. Gerraty# uppercase or lowercase, as well as for fairly advanced modifiers that first
606b9b3e0SSimon J. Gerraty# look like line noise and are hard to decipher.
706b9b3e0SSimon J. Gerraty#
8b0c40a00SSimon J. Gerraty# Initial support for indirect modifiers was added in var.c 1.101 from
9b0c40a00SSimon J. Gerraty# 2006-02-18.  Since var.c 1.108 from 2006-05-11 it is possible to use
10b0c40a00SSimon J. Gerraty# indirect modifiers for all but the very first modifier as well.
1106b9b3e0SSimon J. Gerraty
1206b9b3e0SSimon J. Gerraty
1306b9b3e0SSimon J. Gerraty# To apply a modifier indirectly via another variable, the whole
14d5e0a182SSimon J. Gerraty# modifier must be put into a single expression.
15b0c40a00SSimon J. Gerraty# The following expression generates a parse error since its indirect
16d5e0a182SSimon J. Gerraty# modifier contains more than a sole expression.
17b0c40a00SSimon J. Gerraty#
18*6a7405f5SSimon J. Gerraty# expect+1: Unknown modifier "${"
1906b9b3e0SSimon J. Gerraty.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
2006b9b3e0SSimon J. Gerraty.  warning unexpected
2106b9b3e0SSimon J. Gerraty.endif
2206b9b3e0SSimon J. Gerraty
2306b9b3e0SSimon J. Gerraty
2406b9b3e0SSimon J. Gerraty# Adding another level of indirection (the 2 nested :U expressions) helps.
2506b9b3e0SSimon J. Gerraty.if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement"
2606b9b3e0SSimon J. Gerraty.  warning unexpected
2706b9b3e0SSimon J. Gerraty.endif
2806b9b3e0SSimon J. Gerraty
2906b9b3e0SSimon J. Gerraty
3006b9b3e0SSimon J. Gerraty# Multiple indirect modifiers can be applied one after another as long as
3106b9b3e0SSimon J. Gerraty# they are separated with colons.
3206b9b3e0SSimon J. Gerraty.if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE"
3306b9b3e0SSimon J. Gerraty.  warning unexpected
3406b9b3e0SSimon J. Gerraty.endif
3506b9b3e0SSimon J. Gerraty
3606b9b3e0SSimon J. Gerraty
37b0c40a00SSimon J. Gerraty# An indirect variable that evaluates to the empty string is allowed.
38b0c40a00SSimon J. Gerraty# It is even allowed to write another modifier directly afterwards.
39b0c40a00SSimon J. Gerraty# There is no practical use case for this feature though, as demonstrated
40b0c40a00SSimon J. Gerraty# in the test case directly below.
41b0c40a00SSimon J. Gerraty.if ${value:L:${:Dempty}S,value,replaced,} != "replaced"
42b0c40a00SSimon J. Gerraty.  warning unexpected
43b0c40a00SSimon J. Gerraty.endif
44b0c40a00SSimon J. Gerraty
45b0c40a00SSimon J. Gerraty# If an expression for an indirect modifier evaluates to anything else than an
46b0c40a00SSimon J. Gerraty# empty string and is neither followed by a ':' nor '}', this produces a parse
47b0c40a00SSimon J. Gerraty# error.  Because of this parse error, this feature cannot be used reasonably
48b0c40a00SSimon J. Gerraty# in practice.
49b0c40a00SSimon J. Gerraty#
50*6a7405f5SSimon J. Gerraty# expect+2: Unknown modifier "${"
51b0c40a00SSimon J. Gerraty#.MAKEFLAGS: -dvc
52b0c40a00SSimon J. Gerraty.if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}"
53148ee845SSimon J. Gerraty# expect+1: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression.
54b0c40a00SSimon J. Gerraty.  warning	FIXME: this expression should have resulted in a parse $\
55b0c40a00SSimon J. Gerraty 		error rather than returning the unparsed portion of the $\
56b0c40a00SSimon J. Gerraty 		expression.
57b0c40a00SSimon J. Gerraty.else
58b0c40a00SSimon J. Gerraty.  error
59b0c40a00SSimon J. Gerraty.endif
60b0c40a00SSimon J. Gerraty#.MAKEFLAGS: -d0
61b0c40a00SSimon J. Gerraty
62b0c40a00SSimon J. Gerraty# An indirect modifier can be followed by other modifiers, no matter if the
63b0c40a00SSimon J. Gerraty# indirect modifier evaluates to an empty string or not.
64b0c40a00SSimon J. Gerraty#
6506b9b3e0SSimon J. Gerraty# This makes it possible to define conditional modifiers, like this:
6606b9b3e0SSimon J. Gerraty#
6706b9b3e0SSimon J. Gerraty# M.little-endian=	S,1234,4321,
6806b9b3e0SSimon J. Gerraty# M.big-endian=		# none
69b0c40a00SSimon J. Gerraty.if ${value:L:${:D empty }:S,value,replaced,} != "replaced"
70b0c40a00SSimon J. Gerraty.  error
7106b9b3e0SSimon J. Gerraty.endif
7206b9b3e0SSimon J. Gerraty
7306b9b3e0SSimon J. Gerraty
74d5e0a182SSimon J. Gerraty# The nested expression expands to "tu", and this is interpreted as
7506b9b3e0SSimon J. Gerraty# a variable modifier for the value "Upper", resulting in "UPPER".
7606b9b3e0SSimon J. Gerraty.if ${Upper:L:${:Utu}} != "UPPER"
7706b9b3e0SSimon J. Gerraty.  error
7806b9b3e0SSimon J. Gerraty.endif
7906b9b3e0SSimon J. Gerraty
80d5e0a182SSimon J. Gerraty# The nested expression expands to "tl", and this is interpreted as
8106b9b3e0SSimon J. Gerraty# a variable modifier for the value "Lower", resulting in "lower".
8206b9b3e0SSimon J. Gerraty.if ${Lower:L:${:Utl}} != "lower"
8306b9b3e0SSimon J. Gerraty.  error
8406b9b3e0SSimon J. Gerraty.endif
8506b9b3e0SSimon J. Gerraty
8606b9b3e0SSimon J. Gerraty
87d5e0a182SSimon J. Gerraty# The nested expression is ${1 != 1:?Z:tl}, consisting of the
8806b9b3e0SSimon J. Gerraty# condition "1 != 1", the then-branch "Z" and the else-branch "tl".  Since
8906b9b3e0SSimon J. Gerraty# the condition evaluates to false, the then-branch is ignored (it would
9006b9b3e0SSimon J. Gerraty# have been an unknown modifier anyway) and the ":tl" modifier is applied.
9106b9b3e0SSimon J. Gerraty.if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed"
9206b9b3e0SSimon J. Gerraty.  error
9306b9b3e0SSimon J. Gerraty.endif
9406b9b3e0SSimon J. Gerraty
9506b9b3e0SSimon J. Gerraty
9606b9b3e0SSimon J. Gerraty# The indirect modifier can also replace an ':L' modifier, which allows for
9706b9b3e0SSimon J. Gerraty# brain twisters since by reading the expression alone, it is not possible
9806b9b3e0SSimon J. Gerraty# to say whether the variable name will be evaluated as a variable name or
9906b9b3e0SSimon J. Gerraty# as the immediate value of the expression.
10006b9b3e0SSimon J. GerratyVAR=	value
10106b9b3e0SSimon J. GerratyM_ExpandVar=	# an empty modifier
10206b9b3e0SSimon J. GerratyM_VarAsValue=	L
10306b9b3e0SSimon J. Gerraty#
10406b9b3e0SSimon J. Gerraty.if ${VAR:${M_ExpandVar}} != "value"
10506b9b3e0SSimon J. Gerraty.  error
10606b9b3e0SSimon J. Gerraty.endif
10706b9b3e0SSimon J. Gerraty.if ${VAR:${M_VarAsValue}} != "VAR"
10806b9b3e0SSimon J. Gerraty.  error
10906b9b3e0SSimon J. Gerraty.endif
11006b9b3e0SSimon J. Gerraty
11106b9b3e0SSimon J. Gerraty# The indirect modifier M_ListToSkip, when applied to a list of patterns,
11206b9b3e0SSimon J. Gerraty# expands to a sequence of ':N' modifiers, each of which filters one of the
11306b9b3e0SSimon J. Gerraty# patterns.  This list of patterns can then be applied to another variable
11406b9b3e0SSimon J. Gerraty# to actually filter that variable.
11506b9b3e0SSimon J. Gerraty#
11606b9b3e0SSimon J. GerratyM_ListToSkip=	@pat@N$${pat}@:ts:
11706b9b3e0SSimon J. Gerraty#
11806b9b3e0SSimon J. Gerraty# The dollar signs need to be doubled in the above modifier expression,
11906b9b3e0SSimon J. Gerraty# otherwise they would be expanded too early, that is, when parsing the
12006b9b3e0SSimon J. Gerraty# modifier itself.
12106b9b3e0SSimon J. Gerraty#
12206b9b3e0SSimon J. Gerraty# In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'.
12306b9b3e0SSimon J. Gerraty# The 'N' comes from the expression 'N${pat}', the separating colons come
12406b9b3e0SSimon J. Gerraty# from the modifier ':ts:'.
12506b9b3e0SSimon J. Gerraty#
12606b9b3e0SSimon J. Gerraty#.MAKEFLAGS: -dcv		# Uncomment this line to see the details
12706b9b3e0SSimon J. Gerraty#
12806b9b3e0SSimon J. GerratyPRIMES=		2 3 5 7 1[1379]
12906b9b3e0SSimon J. GerratyM_NoPrimes=	${PRIMES:${M_ListToSkip}}
13006b9b3e0SSimon J. Gerraty.if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20"
13106b9b3e0SSimon J. Gerraty.  error
13206b9b3e0SSimon J. Gerraty.endif
13306b9b3e0SSimon J. Gerraty.MAKEFLAGS: -d0
13406b9b3e0SSimon J. Gerraty
13506b9b3e0SSimon J. Gerraty
136d5e0a182SSimon J. Gerraty# In contrast to the .if conditions, the .for loop allows undefined
13706b9b3e0SSimon J. Gerraty# expressions.  These expressions expand to empty strings.
13806b9b3e0SSimon J. Gerraty
13906b9b3e0SSimon J. Gerraty# An undefined expression without any modifiers expands to an empty string.
14006b9b3e0SSimon J. Gerraty.for var in before ${UNDEF} after
141148ee845SSimon J. Gerraty# expect+2: before
142148ee845SSimon J. Gerraty# expect+1: after
14306b9b3e0SSimon J. Gerraty.  info ${var}
14406b9b3e0SSimon J. Gerraty.endfor
14506b9b3e0SSimon J. Gerraty
14606b9b3e0SSimon J. Gerraty# An undefined expression with only modifiers that keep the expression
14706b9b3e0SSimon J. Gerraty# undefined expands to an empty string.
14806b9b3e0SSimon J. Gerraty.for var in before ${UNDEF:${:US,a,a,}} after
149148ee845SSimon J. Gerraty# expect+2: before
150148ee845SSimon J. Gerraty# expect+1: after
15106b9b3e0SSimon J. Gerraty.  info ${var}
15206b9b3e0SSimon J. Gerraty.endfor
15306b9b3e0SSimon J. Gerraty
15406b9b3e0SSimon J. Gerraty# Even in an indirect modifier based on an undefined variable, the value of
15506b9b3e0SSimon J. Gerraty# the expression in Var_Parse is a simple empty string.
15606b9b3e0SSimon J. Gerraty.for var in before ${UNDEF:${:U}} after
157148ee845SSimon J. Gerraty# expect+2: before
158148ee845SSimon J. Gerraty# expect+1: after
15906b9b3e0SSimon J. Gerraty.  info ${var}
16006b9b3e0SSimon J. Gerraty.endfor
16106b9b3e0SSimon J. Gerraty
16206b9b3e0SSimon J. Gerraty# An error in an indirect modifier.
163*6a7405f5SSimon J. Gerraty# expect+1: Unknown modifier "Z"
16406b9b3e0SSimon J. Gerraty.for var in before ${UNDEF:${:UZ}} after
165148ee845SSimon J. Gerraty# expect+2: before
166148ee845SSimon J. Gerraty# expect+1: after
16706b9b3e0SSimon J. Gerraty.  info ${var}
16806b9b3e0SSimon J. Gerraty.endfor
16906b9b3e0SSimon J. Gerraty
17006b9b3e0SSimon J. Gerraty
17106b9b3e0SSimon J. Gerraty# Another slightly different evaluation context is the right-hand side of
17206b9b3e0SSimon J. Gerraty# a variable assignment using ':='.
17306b9b3e0SSimon J. Gerraty.MAKEFLAGS: -dpv
17406b9b3e0SSimon J. Gerraty
175d5e0a182SSimon J. Gerraty# The undefined expression is kept as-is.
17606b9b3e0SSimon J. Gerraty_:=	before ${UNDEF} after
17706b9b3e0SSimon J. Gerraty
178d5e0a182SSimon J. Gerraty# The undefined expression is kept as-is.
17906b9b3e0SSimon J. Gerraty_:=	before ${UNDEF:${:US,a,a,}} after
18006b9b3e0SSimon J. Gerraty
18106b9b3e0SSimon J. Gerraty# XXX: The subexpression ${:U} is fully defined, therefore it is expanded.
18206b9b3e0SSimon J. Gerraty# This results in ${UNDEF:}, which can lead to tricky parse errors later,
18306b9b3e0SSimon J. Gerraty# when the variable '_' is expanded further.
18406b9b3e0SSimon J. Gerraty#
18506b9b3e0SSimon J. Gerraty# XXX: What should be the correct strategy here?  One possibility is to
18606b9b3e0SSimon J. Gerraty# expand the defined subexpression and replace it with ${:U...}, just like
18706b9b3e0SSimon J. Gerraty# in .for loops.  This would preserve the structure of the expression while
18806b9b3e0SSimon J. Gerraty# at the same time expanding the expression as far as possible.
18906b9b3e0SSimon J. Gerraty_:=	before ${UNDEF:${:U}} after
19006b9b3e0SSimon J. Gerraty
19106b9b3e0SSimon J. Gerraty# XXX: This expands to ${UNDEF:Z}, which will behave differently if the
192d5e0a182SSimon J. Gerraty# variable '_' is used in a context where the expression ${_} is
19306b9b3e0SSimon J. Gerraty# parsed but not evaluated.
194*6a7405f5SSimon J. Gerraty# expect+1: Unknown modifier "Z"
19506b9b3e0SSimon J. Gerraty_:=	before ${UNDEF:${:UZ}} after
19606b9b3e0SSimon J. Gerraty
19706b9b3e0SSimon J. Gerraty.MAKEFLAGS: -d0
19806b9b3e0SSimon J. Gerraty.undef _
19906b9b3e0SSimon J. Gerraty
200b0c40a00SSimon J. Gerraty
201b0c40a00SSimon J. Gerraty# When evaluating indirect modifiers, these modifiers may expand to ':tW',
202b0c40a00SSimon J. Gerraty# which modifies the interpretation of the expression value. This modified
203b0c40a00SSimon J. Gerraty# interpretation only lasts until the end of the indirect modifier, it does
204d5e0a182SSimon J. Gerraty# not influence the outer expression.
205b0c40a00SSimon J. Gerraty.if ${1 2 3:L:tW:[#]} != 1		# direct :tW applies to the :[#]
206b0c40a00SSimon J. Gerraty.  error
207b0c40a00SSimon J. Gerraty.endif
208b0c40a00SSimon J. Gerraty.if ${1 2 3:L:${:UtW}:[#]} != 3		# indirect :tW does not apply to :[#]
209b0c40a00SSimon J. Gerraty.  error
210b0c40a00SSimon J. Gerraty.endif
211b0c40a00SSimon J. Gerraty
212b0c40a00SSimon J. Gerraty
213b0c40a00SSimon J. Gerraty# When evaluating indirect modifiers, these modifiers may expand to ':ts*',
214b0c40a00SSimon J. Gerraty# which modifies the interpretation of the expression value. This modified
215b0c40a00SSimon J. Gerraty# interpretation only lasts until the end of the indirect modifier, it does
216d5e0a182SSimon J. Gerraty# not influence the outer expression.
217b0c40a00SSimon J. Gerraty#
218b0c40a00SSimon J. Gerraty# In this first expression, the direct ':ts*' has no effect since ':U' does not
219b0c40a00SSimon J. Gerraty# treat the expression value as a list of words but as a single word.  It has
220b0c40a00SSimon J. Gerraty# to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no
221b0c40a00SSimon J. Gerraty# variable of that name.
222b0c40a00SSimon J. Gerraty#.MAKEFLAGS: -dcpv
223b0c40a00SSimon J. Gerraty.if ${1 2 3:L:ts*:Ua b c} != "a b c"
224b0c40a00SSimon J. Gerraty.  error
225b0c40a00SSimon J. Gerraty.endif
226b0c40a00SSimon J. Gerraty# In this expression, the direct ':ts*' affects the ':M' at the end.
227b0c40a00SSimon J. Gerraty.if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c"
228b0c40a00SSimon J. Gerraty.  error
229b0c40a00SSimon J. Gerraty.endif
230b0c40a00SSimon J. Gerraty# In this expression, the ':ts*' is indirect, therefore the changed separator
231b0c40a00SSimon J. Gerraty# only applies to the modifiers from the indirect text.  It does not affect
232b0c40a00SSimon J. Gerraty# the ':M' since that is not part of the text from the indirect modifier.
233b0c40a00SSimon J. Gerraty#
234b0c40a00SSimon J. Gerraty# Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers
235b0c40a00SSimon J. Gerraty# (which creates a new ModChain containing a fresh separator),
236b0c40a00SSimon J. Gerraty# the outer separator character is not passed by reference to the inner
237b0c40a00SSimon J. Gerraty# evaluation, therefore the scope of the inner separator ends after applying
238b0c40a00SSimon J. Gerraty# the modifier ':ts*'.
239b0c40a00SSimon J. Gerraty.if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c"
240b0c40a00SSimon J. Gerraty.  error
241b0c40a00SSimon J. Gerraty.endif
242b0c40a00SSimon J. Gerraty
243b0c40a00SSimon J. Gerraty# A direct modifier ':U' turns the expression from undefined to defined.
244b0c40a00SSimon J. Gerraty# An indirect modifier ':U' has the same effect, unlike the separator from
245b0c40a00SSimon J. Gerraty# ':ts*' or the single-word marker from ':tW'.
246b0c40a00SSimon J. Gerraty#
247b0c40a00SSimon J. Gerraty# This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes
248b0c40a00SSimon J. Gerraty# the definedness of the outer expression by reference.  If that weren't the
249b0c40a00SSimon J. Gerraty# case, the first condition below would result in a parse error because its
250b0c40a00SSimon J. Gerraty# left-hand side would be undefined.
251b0c40a00SSimon J. Gerraty.if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback"
252b0c40a00SSimon J. Gerraty.  error
253b0c40a00SSimon J. Gerraty.endif
254b0c40a00SSimon J. Gerraty.if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback"
255b0c40a00SSimon J. Gerraty.  error
256b0c40a00SSimon J. Gerraty.endif
257b0c40a00SSimon J. Gerraty
258c59c3bf3SSimon J. Gerraty
259c59c3bf3SSimon J. Gerraty# In parse-only mode, the indirect modifiers must not be evaluated.
260c59c3bf3SSimon J. Gerraty#
261c59c3bf3SSimon J. Gerraty# Before var.c 1.1098 from 2024-02-04, the expression for an indirect modifier
262c59c3bf3SSimon J. Gerraty# was partially evaluated (only the variable value, without applying any
263c59c3bf3SSimon J. Gerraty# modifiers) and then interpreted as modifiers to the main expression.
264c59c3bf3SSimon J. Gerraty#
265c59c3bf3SSimon J. Gerraty# The expression ${:UZ} starts with the value "", and in parse-only mode, the
266c59c3bf3SSimon J. Gerraty# modifier ':UZ' does not modify the expression value.  This results in an
267c59c3bf3SSimon J. Gerraty# empty string for the indirect modifiers, generating no warning.
268c59c3bf3SSimon J. Gerraty.if 0 && ${VAR:${:UZ}}
269c59c3bf3SSimon J. Gerraty.endif
270c59c3bf3SSimon J. Gerraty# The expression ${M_invalid} starts with the value "Z", which is an unknown
271c59c3bf3SSimon J. Gerraty# modifier.  Trying to apply this unknown modifier generated a warning.
272c59c3bf3SSimon J. GerratyM_invalid=	Z
273c59c3bf3SSimon J. Gerraty.if 0 && ${VAR:${M_invalid}}
274c59c3bf3SSimon J. Gerraty.endif
275c59c3bf3SSimon J. Gerraty# The ':S' modifier does not change the expression value in parse-only mode,
276c59c3bf3SSimon J. Gerraty# keeping the "Z", which is then skipped in parse-only mode.
277c59c3bf3SSimon J. Gerraty.if 0 && ${VAR:${M_invalid:S,^,N*,:ts:}}
278c59c3bf3SSimon J. Gerraty.endif
279c59c3bf3SSimon J. Gerraty# The ':@' modifier does not change the expression value in parse-only mode,
280c59c3bf3SSimon J. Gerraty# keeping the "Z", which is then skipped in parse-only mode.
281c59c3bf3SSimon J. Gerraty.if 0 && ${VAR:${M_invalid:@m@N*$m@:ts:}}
282c59c3bf3SSimon J. Gerraty.endif
283