varmod-indirect.mk revision 1.20 1 # $NetBSD: varmod-indirect.mk,v 1.20 2024/07/04 17:47:54 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: while evaluating variable "value" with value "value": 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: while evaluating variable "value" with value "value": 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.
100 VAR= value
101 M_ExpandVar= # an empty modifier
102 M_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 #
116 M_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 #
128 PRIMES= 2 3 5 7 1[1379]
129 M_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: while evaluating variable "UNDEF" with value "": 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: while evaluating variable "UNDEF" with value "": 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.
272 M_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