aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/unit-tests/varmod-indirect.mk
blob: fa58997cc849952bc54a9e9017e0cc9f299a56af (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# $NetBSD: varmod-indirect.mk,v 1.9 2021/03/15 20:00:50 rillig Exp $
#
# Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}.
# These can be used for very basic purposes like converting a string to either
# uppercase or lowercase, as well as for fairly advanced modifiers that first
# look like line noise and are hard to decipher.
#
# Initial support for indirect modifiers was added in var.c 1.101 from
# 2006-02-18.  Since var.c 1.108 from 2006-05-11 it is possible to use
# indirect modifiers for all but the very first modifier as well.


# To apply a modifier indirectly via another variable, the whole
# modifier must be put into a single variable expression.
# The following expression generates a parse error since its indirect
# modifier contains more than a sole variable expression.
#
# expect+1: Unknown modifier '$'
.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
.  warning unexpected
.endif


# Adding another level of indirection (the 2 nested :U expressions) helps.
.if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement"
.  warning unexpected
.endif


# Multiple indirect modifiers can be applied one after another as long as
# they are separated with colons.
.if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE"
.  warning unexpected
.endif


# An indirect variable that evaluates to the empty string is allowed.
# It is even allowed to write another modifier directly afterwards.
# There is no practical use case for this feature though, as demonstrated
# in the test case directly below.
.if ${value:L:${:Dempty}S,value,replaced,} != "replaced"
.  warning unexpected
.endif

# If an expression for an indirect modifier evaluates to anything else than an
# empty string and is neither followed by a ':' nor '}', this produces a parse
# error.  Because of this parse error, this feature cannot be used reasonably
# in practice.
#
# expect+1: Unknown modifier '$'
#.MAKEFLAGS: -dvc
.if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}"
.  warning	FIXME: this expression should have resulted in a parse $\
 		error rather than returning the unparsed portion of the $\
 		expression.
.else
.  error
.endif
#.MAKEFLAGS: -d0

# An indirect modifier can be followed by other modifiers, no matter if the
# indirect modifier evaluates to an empty string or not.
#
# This makes it possible to define conditional modifiers, like this:
#
# M.little-endian=	S,1234,4321,
# M.big-endian=		# none
.if ${value:L:${:D empty }:S,value,replaced,} != "replaced"
.  error
.endif


# The nested variable expression expands to "tu", and this is interpreted as
# a variable modifier for the value "Upper", resulting in "UPPER".
.if ${Upper:L:${:Utu}} != "UPPER"
.  error
.endif

# The nested variable expression expands to "tl", and this is interpreted as
# a variable modifier for the value "Lower", resulting in "lower".
.if ${Lower:L:${:Utl}} != "lower"
.  error
.endif


# The nested variable expression is ${1 != 1:?Z:tl}, consisting of the
# condition "1 != 1", the then-branch "Z" and the else-branch "tl".  Since
# the condition evaluates to false, the then-branch is ignored (it would
# have been an unknown modifier anyway) and the ":tl" modifier is applied.
.if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed"
.  error
.endif


# The indirect modifier can also replace an ':L' modifier, which allows for
# brain twisters since by reading the expression alone, it is not possible
# to say whether the variable name will be evaluated as a variable name or
# as the immediate value of the expression.
VAR=	value
M_ExpandVar=	# an empty modifier
M_VarAsValue=	L
#
.if ${VAR:${M_ExpandVar}} != "value"
.  error
.endif
.if ${VAR:${M_VarAsValue}} != "VAR"
.  error
.endif

# The indirect modifier M_ListToSkip, when applied to a list of patterns,
# expands to a sequence of ':N' modifiers, each of which filters one of the
# patterns.  This list of patterns can then be applied to another variable
# to actually filter that variable.
#
M_ListToSkip=	@pat@N$${pat}@:ts:
#
# The dollar signs need to be doubled in the above modifier expression,
# otherwise they would be expanded too early, that is, when parsing the
# modifier itself.
#
# In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'.
# The 'N' comes from the expression 'N${pat}', the separating colons come
# from the modifier ':ts:'.
#
#.MAKEFLAGS: -dcv		# Uncomment this line to see the details
#
PRIMES=		2 3 5 7 1[1379]
M_NoPrimes=	${PRIMES:${M_ListToSkip}}
.if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20"
.  error
.endif
.MAKEFLAGS: -d0


# In contrast to the .if conditions, the .for loop allows undefined variable
# expressions.  These expressions expand to empty strings.

# An undefined expression without any modifiers expands to an empty string.
.for var in before ${UNDEF} after
.  info ${var}
.endfor

# An undefined expression with only modifiers that keep the expression
# undefined expands to an empty string.
.for var in before ${UNDEF:${:US,a,a,}} after
.  info ${var}
.endfor

# Even in an indirect modifier based on an undefined variable, the value of
# the expression in Var_Parse is a simple empty string.
.for var in before ${UNDEF:${:U}} after
.  info ${var}
.endfor

# An error in an indirect modifier.
.for var in before ${UNDEF:${:UZ}} after
.  info ${var}
.endfor


# Another slightly different evaluation context is the right-hand side of
# a variable assignment using ':='.
.MAKEFLAGS: -dpv

# The undefined variable expression is kept as-is.
_:=	before ${UNDEF} after

# The undefined variable expression is kept as-is.
_:=	before ${UNDEF:${:US,a,a,}} after

# XXX: The subexpression ${:U} is fully defined, therefore it is expanded.
# This results in ${UNDEF:}, which can lead to tricky parse errors later,
# when the variable '_' is expanded further.
#
# XXX: What should be the correct strategy here?  One possibility is to
# expand the defined subexpression and replace it with ${:U...}, just like
# in .for loops.  This would preserve the structure of the expression while
# at the same time expanding the expression as far as possible.
_:=	before ${UNDEF:${:U}} after

# XXX: This expands to ${UNDEF:Z}, which will behave differently if the
# variable '_' is used in a context where the variable expression ${_} is
# parsed but not evaluated.
_:=	before ${UNDEF:${:UZ}} after

.MAKEFLAGS: -d0
.undef _


# When evaluating indirect modifiers, these modifiers may expand to ':tW',
# which modifies the interpretation of the expression value. This modified
# interpretation only lasts until the end of the indirect modifier, it does
# not influence the outer variable expression.
.if ${1 2 3:L:tW:[#]} != 1		# direct :tW applies to the :[#]
.  error
.endif
.if ${1 2 3:L:${:UtW}:[#]} != 3		# indirect :tW does not apply to :[#]
.  error
.endif


# When evaluating indirect modifiers, these modifiers may expand to ':ts*',
# which modifies the interpretation of the expression value. This modified
# interpretation only lasts until the end of the indirect modifier, it does
# not influence the outer variable expression.
#
# In this first expression, the direct ':ts*' has no effect since ':U' does not
# treat the expression value as a list of words but as a single word.  It has
# to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no
# variable of that name.
#.MAKEFLAGS: -dcpv
.if ${1 2 3:L:ts*:Ua b c} != "a b c"
.  error
.endif
# In this expression, the direct ':ts*' affects the ':M' at the end.
.if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c"
.  error
.endif
# In this expression, the ':ts*' is indirect, therefore the changed separator
# only applies to the modifiers from the indirect text.  It does not affect
# the ':M' since that is not part of the text from the indirect modifier.
#
# Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers
# (which creates a new ModChain containing a fresh separator),
# the outer separator character is not passed by reference to the inner
# evaluation, therefore the scope of the inner separator ends after applying
# the modifier ':ts*'.
.if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c"
.  error
.endif

# A direct modifier ':U' turns the expression from undefined to defined.
# An indirect modifier ':U' has the same effect, unlike the separator from
# ':ts*' or the single-word marker from ':tW'.
#
# This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes
# the definedness of the outer expression by reference.  If that weren't the
# case, the first condition below would result in a parse error because its
# left-hand side would be undefined.
.if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback"
.  error
.endif
.if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback"
.  error
.endif

all: