varmod-indirect.mk revision 1.7 1 # $NetBSD: varmod-indirect.mk,v 1.7 2021/02/14 17:22:37 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 variable expression.
15 # The following expression generates a parse error since its indirect
16 # modifier contains more than a sole variable 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+1: Unknown modifier '$'
51 #.MAKEFLAGS: -dvc
52 .if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}"
53 . warning FIXME: this expression should have resulted in a parse $\
54 error rather than returning the unparsed portion of the $\
55 expression.
56 .else
57 . error
58 .endif
59 #.MAKEFLAGS: -d0
60
61 # An indirect modifier can be followed by other modifiers, no matter if the
62 # indirect modifier evaluates to an empty string or not.
63 #
64 # This makes it possible to define conditional modifiers, like this:
65 #
66 # M.little-endian= S,1234,4321,
67 # M.big-endian= # none
68 .if ${value:L:${:D empty }:S,value,replaced,} != "replaced"
69 . error
70 .endif
71
72
73 # The nested variable expression expands to "tu", and this is interpreted as
74 # a variable modifier for the value "Upper", resulting in "UPPER".
75 .if ${Upper:L:${:Utu}} != "UPPER"
76 . error
77 .endif
78
79 # The nested variable expression expands to "tl", and this is interpreted as
80 # a variable modifier for the value "Lower", resulting in "lower".
81 .if ${Lower:L:${:Utl}} != "lower"
82 . error
83 .endif
84
85
86 # The nested variable expression is ${1 != 1:?Z:tl}, consisting of the
87 # condition "1 != 1", the then-branch "Z" and the else-branch "tl". Since
88 # the condition evaluates to false, the then-branch is ignored (it would
89 # have been an unknown modifier anyway) and the ":tl" modifier is applied.
90 .if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed"
91 . error
92 .endif
93
94
95 # The indirect modifier can also replace an ':L' modifier, which allows for
96 # brain twisters since by reading the expression alone, it is not possible
97 # to say whether the variable name will be evaluated as a variable name or
98 # as the immediate value of the expression.
99 VAR= value
100 M_ExpandVar= # an empty modifier
101 M_VarAsValue= L
102 #
103 .if ${VAR:${M_ExpandVar}} != "value"
104 . error
105 .endif
106 .if ${VAR:${M_VarAsValue}} != "VAR"
107 . error
108 .endif
109
110 # The indirect modifier M_ListToSkip, when applied to a list of patterns,
111 # expands to a sequence of ':N' modifiers, each of which filters one of the
112 # patterns. This list of patterns can then be applied to another variable
113 # to actually filter that variable.
114 #
115 M_ListToSkip= @pat@N$${pat}@:ts:
116 #
117 # The dollar signs need to be doubled in the above modifier expression,
118 # otherwise they would be expanded too early, that is, when parsing the
119 # modifier itself.
120 #
121 # In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'.
122 # The 'N' comes from the expression 'N${pat}', the separating colons come
123 # from the modifier ':ts:'.
124 #
125 #.MAKEFLAGS: -dcv # Uncomment this line to see the details
126 #
127 PRIMES= 2 3 5 7 1[1379]
128 M_NoPrimes= ${PRIMES:${M_ListToSkip}}
129 .if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20"
130 . error
131 .endif
132 .MAKEFLAGS: -d0
133
134
135 # In contrast to the .if conditions, the .for loop allows undefined variable
136 # expressions. These expressions expand to empty strings.
137
138 # An undefined expression without any modifiers expands to an empty string.
139 .for var in before ${UNDEF} after
140 . info ${var}
141 .endfor
142
143 # An undefined expression with only modifiers that keep the expression
144 # undefined expands to an empty string.
145 .for var in before ${UNDEF:${:US,a,a,}} after
146 . info ${var}
147 .endfor
148
149 # Even in an indirect modifier based on an undefined variable, the value of
150 # the expression in Var_Parse is a simple empty string.
151 .for var in before ${UNDEF:${:U}} after
152 . info ${var}
153 .endfor
154
155 # An error in an indirect modifier.
156 .for var in before ${UNDEF:${:UZ}} after
157 . info ${var}
158 .endfor
159
160
161 # Another slightly different evaluation context is the right-hand side of
162 # a variable assignment using ':='.
163 .MAKEFLAGS: -dpv
164
165 # The undefined variable expression is kept as-is.
166 _:= before ${UNDEF} after
167
168 # The undefined variable expression is kept as-is.
169 _:= before ${UNDEF:${:US,a,a,}} after
170
171 # XXX: The subexpression ${:U} is fully defined, therefore it is expanded.
172 # This results in ${UNDEF:}, which can lead to tricky parse errors later,
173 # when the variable '_' is expanded further.
174 #
175 # XXX: What should be the correct strategy here? One possibility is to
176 # expand the defined subexpression and replace it with ${:U...}, just like
177 # in .for loops. This would preserve the structure of the expression while
178 # at the same time expanding the expression as far as possible.
179 _:= before ${UNDEF:${:U}} after
180
181 # XXX: This expands to ${UNDEF:Z}, which will behave differently if the
182 # variable '_' is used in a context where the variable expression ${_} is
183 # parsed but not evaluated.
184 _:= before ${UNDEF:${:UZ}} after
185
186 .MAKEFLAGS: -d0
187 .undef _
188
189
190 # When evaluating indirect modifiers, these modifiers may expand to ':tW',
191 # which modifies the interpretation of the expression value. This modified
192 # interpretation only lasts until the end of the indirect modifier, it does
193 # not influence the outer variable expression.
194 .if ${1 2 3:L:tW:[#]} != 1 # direct :tW applies to the :[#]
195 . error
196 .endif
197 .if ${1 2 3:L:${:UtW}:[#]} != 3 # indirect :tW does not apply to :[#]
198 . error
199 .endif
200
201
202 # When evaluating indirect modifiers, these modifiers may expand to ':ts*',
203 # which modifies the interpretation of the expression value. This modified
204 # interpretation only lasts until the end of the indirect modifier, it does
205 # not influence the outer variable expression.
206 #
207 # In this first expression, the direct ':ts*' has no effect since ':U' does not
208 # treat the expression value as a list of words but as a single word. It has
209 # to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no
210 # variable of that name.
211 #.MAKEFLAGS: -dcpv
212 .if ${1 2 3:L:ts*:Ua b c} != "a b c"
213 . error
214 .endif
215 # In this expression, the direct ':ts*' affects the ':M' at the end.
216 .if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c"
217 . error
218 .endif
219 # In this expression, the ':ts*' is indirect, therefore the changed separator
220 # only lasts until the end of the indirect modifier. It does not affect the
221 # ':M' since that is outside the scope.
222 .if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c"
223 . error
224 .endif
225
226 all:
227