xref: /freebsd/contrib/bmake/unit-tests/varmod-indirect.mk (revision 5ca8e32633c4ffbbcd6762e5888b6a4ba0708c6c)
1# $NetBSD: varmod-indirect.mk,v 1.18 2024/02/04 10:03:10 rillig Exp $
2#
3# Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}.
4# These can be used for very basic purposes like converting a string to either
5# uppercase or lowercase, as well as for fairly advanced modifiers that first
6# look like line noise and are hard to decipher.
7#
8# Initial support for indirect modifiers was added in var.c 1.101 from
9# 2006-02-18.  Since var.c 1.108 from 2006-05-11 it is possible to use
10# indirect modifiers for all but the very first modifier as well.
11
12
13# To apply a modifier indirectly via another variable, the whole
14# modifier must be put into a single expression.
15# The following expression generates a parse error since its indirect
16# modifier contains more than a sole expression.
17#
18# expect+1: Unknown modifier "${"
19.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
20.  warning unexpected
21.endif
22
23
24# Adding another level of indirection (the 2 nested :U expressions) helps.
25.if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement"
26.  warning unexpected
27.endif
28
29
30# Multiple indirect modifiers can be applied one after another as long as
31# they are separated with colons.
32.if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE"
33.  warning unexpected
34.endif
35
36
37# An indirect variable that evaluates to the empty string is allowed.
38# It is even allowed to write another modifier directly afterwards.
39# There is no practical use case for this feature though, as demonstrated
40# in the test case directly below.
41.if ${value:L:${:Dempty}S,value,replaced,} != "replaced"
42.  warning unexpected
43.endif
44
45# If an expression for an indirect modifier evaluates to anything else than an
46# empty string and is neither followed by a ':' nor '}', this produces a parse
47# error.  Because of this parse error, this feature cannot be used reasonably
48# in practice.
49#
50# expect+2: Unknown modifier "${"
51#.MAKEFLAGS: -dvc
52.if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}"
53# expect+1: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression.
54.  warning	FIXME: this expression should have resulted in a parse $\
55 		error rather than returning the unparsed portion of the $\
56 		expression.
57.else
58.  error
59.endif
60#.MAKEFLAGS: -d0
61
62# An indirect modifier can be followed by other modifiers, no matter if the
63# indirect modifier evaluates to an empty string or not.
64#
65# This makes it possible to define conditional modifiers, like this:
66#
67# M.little-endian=	S,1234,4321,
68# M.big-endian=		# none
69.if ${value:L:${:D empty }:S,value,replaced,} != "replaced"
70.  error
71.endif
72
73
74# The nested expression expands to "tu", and this is interpreted as
75# a variable modifier for the value "Upper", resulting in "UPPER".
76.if ${Upper:L:${:Utu}} != "UPPER"
77.  error
78.endif
79
80# The nested expression expands to "tl", and this is interpreted as
81# a variable modifier for the value "Lower", resulting in "lower".
82.if ${Lower:L:${:Utl}} != "lower"
83.  error
84.endif
85
86
87# The nested expression is ${1 != 1:?Z:tl}, consisting of the
88# condition "1 != 1", the then-branch "Z" and the else-branch "tl".  Since
89# the condition evaluates to false, the then-branch is ignored (it would
90# have been an unknown modifier anyway) and the ":tl" modifier is applied.
91.if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed"
92.  error
93.endif
94
95
96# The indirect modifier can also replace an ':L' modifier, which allows for
97# brain twisters since by reading the expression alone, it is not possible
98# to say whether the variable name will be evaluated as a variable name or
99# as the immediate value of the expression.
100VAR=	value
101M_ExpandVar=	# an empty modifier
102M_VarAsValue=	L
103#
104.if ${VAR:${M_ExpandVar}} != "value"
105.  error
106.endif
107.if ${VAR:${M_VarAsValue}} != "VAR"
108.  error
109.endif
110
111# The indirect modifier M_ListToSkip, when applied to a list of patterns,
112# expands to a sequence of ':N' modifiers, each of which filters one of the
113# patterns.  This list of patterns can then be applied to another variable
114# to actually filter that variable.
115#
116M_ListToSkip=	@pat@N$${pat}@:ts:
117#
118# The dollar signs need to be doubled in the above modifier expression,
119# otherwise they would be expanded too early, that is, when parsing the
120# modifier itself.
121#
122# In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'.
123# The 'N' comes from the expression 'N${pat}', the separating colons come
124# from the modifier ':ts:'.
125#
126#.MAKEFLAGS: -dcv		# Uncomment this line to see the details
127#
128PRIMES=		2 3 5 7 1[1379]
129M_NoPrimes=	${PRIMES:${M_ListToSkip}}
130.if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20"
131.  error
132.endif
133.MAKEFLAGS: -d0
134
135
136# In contrast to the .if conditions, the .for loop allows undefined
137# expressions.  These expressions expand to empty strings.
138
139# An undefined expression without any modifiers expands to an empty string.
140.for var in before ${UNDEF} after
141# expect+2: before
142# expect+1: after
143.  info ${var}
144.endfor
145
146# An undefined expression with only modifiers that keep the expression
147# undefined expands to an empty string.
148.for var in before ${UNDEF:${:US,a,a,}} after
149# expect+2: before
150# expect+1: after
151.  info ${var}
152.endfor
153
154# Even in an indirect modifier based on an undefined variable, the value of
155# the expression in Var_Parse is a simple empty string.
156.for var in before ${UNDEF:${:U}} after
157# expect+2: before
158# expect+1: after
159.  info ${var}
160.endfor
161
162# An error in an indirect modifier.
163# expect+1: Unknown modifier "Z"
164.for var in before ${UNDEF:${:UZ}} after
165# expect+2: before
166# expect+1: after
167.  info ${var}
168.endfor
169
170
171# Another slightly different evaluation context is the right-hand side of
172# a variable assignment using ':='.
173.MAKEFLAGS: -dpv
174
175# The undefined expression is kept as-is.
176_:=	before ${UNDEF} after
177
178# The undefined expression is kept as-is.
179_:=	before ${UNDEF:${:US,a,a,}} after
180
181# XXX: The subexpression ${:U} is fully defined, therefore it is expanded.
182# This results in ${UNDEF:}, which can lead to tricky parse errors later,
183# when the variable '_' is expanded further.
184#
185# XXX: What should be the correct strategy here?  One possibility is to
186# expand the defined subexpression and replace it with ${:U...}, just like
187# in .for loops.  This would preserve the structure of the expression while
188# at the same time expanding the expression as far as possible.
189_:=	before ${UNDEF:${:U}} after
190
191# XXX: This expands to ${UNDEF:Z}, which will behave differently if the
192# variable '_' is used in a context where the expression ${_} is
193# parsed but not evaluated.
194# expect+1: Unknown modifier "Z"
195_:=	before ${UNDEF:${:UZ}} after
196
197.MAKEFLAGS: -d0
198.undef _
199
200
201# When evaluating indirect modifiers, these modifiers may expand to ':tW',
202# which modifies the interpretation of the expression value. This modified
203# interpretation only lasts until the end of the indirect modifier, it does
204# not influence the outer expression.
205.if ${1 2 3:L:tW:[#]} != 1		# direct :tW applies to the :[#]
206.  error
207.endif
208.if ${1 2 3:L:${:UtW}:[#]} != 3		# indirect :tW does not apply to :[#]
209.  error
210.endif
211
212
213# When evaluating indirect modifiers, these modifiers may expand to ':ts*',
214# which modifies the interpretation of the expression value. This modified
215# interpretation only lasts until the end of the indirect modifier, it does
216# not influence the outer expression.
217#
218# In this first expression, the direct ':ts*' has no effect since ':U' does not
219# treat the expression value as a list of words but as a single word.  It has
220# to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no
221# variable of that name.
222#.MAKEFLAGS: -dcpv
223.if ${1 2 3:L:ts*:Ua b c} != "a b c"
224.  error
225.endif
226# In this expression, the direct ':ts*' affects the ':M' at the end.
227.if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c"
228.  error
229.endif
230# In this expression, the ':ts*' is indirect, therefore the changed separator
231# only applies to the modifiers from the indirect text.  It does not affect
232# the ':M' since that is not part of the text from the indirect modifier.
233#
234# Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers
235# (which creates a new ModChain containing a fresh separator),
236# the outer separator character is not passed by reference to the inner
237# evaluation, therefore the scope of the inner separator ends after applying
238# the modifier ':ts*'.
239.if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c"
240.  error
241.endif
242
243# A direct modifier ':U' turns the expression from undefined to defined.
244# An indirect modifier ':U' has the same effect, unlike the separator from
245# ':ts*' or the single-word marker from ':tW'.
246#
247# This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes
248# the definedness of the outer expression by reference.  If that weren't the
249# case, the first condition below would result in a parse error because its
250# left-hand side would be undefined.
251.if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback"
252.  error
253.endif
254.if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback"
255.  error
256.endif
257
258
259# In parse-only mode, the indirect modifiers must not be evaluated.
260#
261# Before var.c 1.1098 from 2024-02-04, the expression for an indirect modifier
262# was partially evaluated (only the variable value, without applying any
263# modifiers) and then interpreted as modifiers to the main expression.
264#
265# The expression ${:UZ} starts with the value "", and in parse-only mode, the
266# modifier ':UZ' does not modify the expression value.  This results in an
267# empty string for the indirect modifiers, generating no warning.
268.if 0 && ${VAR:${:UZ}}
269.endif
270# The expression ${M_invalid} starts with the value "Z", which is an unknown
271# modifier.  Trying to apply this unknown modifier generated a warning.
272M_invalid=	Z
273.if 0 && ${VAR:${M_invalid}}
274.endif
275# The ':S' modifier does not change the expression value in parse-only mode,
276# keeping the "Z", which is then skipped in parse-only mode.
277.if 0 && ${VAR:${M_invalid:S,^,N*,:ts:}}
278.endif
279# The ':@' modifier does not change the expression value in parse-only mode,
280# keeping the "Z", which is then skipped in parse-only mode.
281.if 0 && ${VAR:${M_invalid:@m@N*$m@:ts:}}
282.endif
283