varmod.mk revision 1.30 1 # $NetBSD: varmod.mk,v 1.30 2025/06/29 11:27:21 rillig Exp $
2 #
3 # Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback.
4 #
5 # See also:
6 # varparse-errors.mk
7
8 # As of 2024-06-05, the possible behaviors during parsing are:
9 #
10 # * `strict`: the parsing style used by most modifiers:
11 # * either uses `ParseModifierPart` or parses the modifier literal
12 # * other modifiers may follow, separated by a ':'
13 #
14 # * `greedy`: calls `ParseModifierPart` with `ch->endc`; this means
15 # that no further modifiers are parsed in that expression.
16 #
17 # * `no-colon`: after parsing this modifier, the following modifier
18 # does not need to be separated by a colon.
19 # Omitting this colon is bad style.
20 #
21 # * `individual`: parsing this modifier does not follow the common
22 # pattern of calling `ParseModifierPart`.
23 #
24 # The SysV column says whether a modifier falls back trying the `:from=to`
25 # System V modifier. Remarks:
26 #
27 # In the assignment modifiers `::=` and its variants, the `=` is part of
28 # the modifier name, so they never fall back to the `:from=to` modifier.
29 #
30 # All no-colon modifiers get a "no", as the modifier name would be
31 # trimmed off before the `:from=to` modifier could see them, for
32 # example, ${VAR:LAR=ALUE} and ${VAR:L:AR=ALUE} behave the same.
33 #
34 # | **Modifier** | **Behavior** | **Remarks** | **SysV** |
35 # |--------------|--------------|--------------------|----------|
36 # | ! | no-colon | | no |
37 # | := | greedy | | no |
38 # | :?= | greedy | | no |
39 # | :+= | greedy | | no |
40 # | :!= | greedy | | no |
41 # | ?: | greedy | | no |
42 # | @ | no-colon | | no |
43 # | C | no-colon | | no |
44 # | D | individual | custom parser | no |
45 # | E | strict | | yes |
46 # | H | strict | | yes |
47 # | L | no-colon | | no |
48 # | M | individual | custom parser | no |
49 # | N | individual | custom parser | no |
50 # | O | strict | only literal value | yes |
51 # | P | no-colon | | no |
52 # | Q | strict | | yes |
53 # | R | strict | | yes |
54 # | S | no-colon | | no |
55 # | T | strict | | yes |
56 # | U | individual | custom parser | no |
57 # | [ | strict | | no |
58 # | _ | individual | strcspn | no |
59 # | gmtime | strict | | no |
60 # | hash | strict | | yes |
61 # | localtime | strict | | no |
62 # | q | strict | | yes |
63 # | range | strict | | no |
64 # | sh | strict | | yes |
65 # | t | strict | | yes |
66 # | u | strict | | yes |
67 # | from=to | greedy | SysV, fallback | --- |
68
69 # These tests assume
70 .MAKE.SAVE_DOLLARS = yes
71
72 DOLLAR1= $$
73 DOLLAR2= ${:U\$}
74
75 # To get a single '$' sign in the value of an expression, it has to
76 # be written as '$$' in a literal variable value.
77 #
78 # See Var_Parse, where it calls Var_Subst.
79 .if ${DOLLAR1} != "\$"
80 . error
81 .endif
82
83 # Another way to get a single '$' sign is to use the :U modifier. In the
84 # argument of that modifier, a '$' is escaped using the backslash instead.
85 #
86 # See Var_Parse, where it calls Var_Subst.
87 .if ${DOLLAR2} != "\$"
88 . error
89 .endif
90
91 # It is also possible to use the :U modifier directly in the expression.
92 #
93 # See Var_Parse, where it calls Var_Subst.
94 .if ${:U\$} != "\$"
95 . error
96 .endif
97
98 # XXX: As of 2020-09-13, it is not possible to use '$$' in a variable name
99 # to mean a single '$'. This contradicts the manual page, which says that
100 # '$' can be escaped as '$$'.
101 .if ${$$:L} != ""
102 . error
103 .endif
104
105 # In lint mode, make prints helpful error messages.
106 # For compatibility, make does not print these error messages in normal mode.
107 # Should it?
108 .MAKEFLAGS: -dL
109 # expect+2: To escape a dollar, use \$, not $$, at "$$:L} != """
110 # expect+1: Invalid variable name ":", at "$:L} != """
111 .if ${$$:L} != ""
112 . error
113 .endif
114
115 # A '$' followed by nothing is an error as well.
116 # expect+1: Dollar followed by nothing
117 .if ${:Uword:@word@${word}$@} != "word"
118 . error
119 .endif
120
121 # The modifier :P does not fall back to the SysV modifier.
122 # Therefore the modifier :P=RE generates a parse error.
123 VAR= STOP
124 # expect+1: Missing delimiter ":" after modifier "P"
125 .if ${VAR:P=RE} != "STORE"
126 . error
127 .else
128 . error
129 .endif
130
131 # Test the word selection modifier ':[n]' with a very large number that is
132 # larger than ULONG_MAX for any supported platform.
133 # expect+1: Invalid modifier ":[99333000222000111000]"
134 .if ${word:L:[99333000222000111000]}
135 .endif
136 # expect+1: Invalid modifier ":[2147483648]"
137 .if ${word:L:[2147483648]}
138 .endif
139
140 # Test the range generation modifier ':range=n' with a very large number that
141 # is larger than SIZE_MAX for any supported platform.
142 # expect+1: Invalid number "99333000222000111000}" for modifier ":range"
143 .if ${word:L:range=99333000222000111000}
144 .endif
145
146 # In an indirect modifier, the delimiter is '\0', which at the same time marks
147 # the end of the string. The sequence '\\' '\0' is not an escaped delimiter,
148 # as it would be wrong to skip past the end of the string.
149 # expect+1: Invalid time value "\"
150 .if ${:${:Ugmtime=\\}}
151 . error
152 .endif
153
154 # Test a '$' at the end of a modifier part, for all modifiers in the order
155 # listed in ApplyModifier.
156 #
157 # The only modifier parts where an unescaped '$' makes sense at the end are
158 # the 'from' parts of the ':S' and ':C' modifiers. In all other modifier
159 # parts, an unescaped '$' is an undocumented and discouraged edge case, as it
160 # means the same as an escaped '$'.
161 .if ${:U:!printf '%s\n' $!} != "\$"
162 . error
163 .endif
164 # expect+1: Dollar followed by nothing
165 .if ${VAR::=value$} != "" || ${VAR} != "value"
166 . error
167 .endif
168 ${:U }= <space>
169 # expect+2: Dollar followed by nothing
170 # expect+1: Dollar followed by nothing
171 .if ${VAR::+=appended$} != "" || ${VAR} != "value<space>appended"
172 . error
173 .endif
174 .if ${1:?then$:else$} != "then\$"
175 . error
176 .endif
177 .if ${0:?then$:else$} != "else\$"
178 . error
179 .endif
180 # expect+1: Dollar followed by nothing
181 .if ${word:L:@w@$w$@} != "word"
182 . error
183 .endif
184 # expect+1: Invalid modifier ":[$]"
185 .if ${word:[$]}
186 . error
187 .else
188 . error
189 .endif
190 VAR_DOLLAR= VAR$$
191 .if ${word:L:_=VAR$} != "word" || ${${VAR_DOLLAR}} != "word"
192 . error
193 .endif
194 .if ${word:L:C,d$,m,} != "worm"
195 . error
196 .endif
197 .if ${word:L:C,d,$,} != "wor\$"
198 . error
199 .endif
200 # expect+2: Dollar followed by nothing
201 # expect+1: Invalid variable name "}", at "$} != "set""
202 .if ${VAR:Dset$} != "set"
203 . error
204 .endif
205 # expect+1: Invalid variable name "}", at "$} != "fallback""
206 .if ${:Ufallback$} != "fallback"
207 . error
208 .endif
209 # expect+1: Invalid time value "1000$"
210 .if ${%y:L:gmtime=1000$}
211 . error
212 .else
213 . error
214 .endif
215 # expect+1: Invalid time value "1000$"
216 .if ${%y:L:localtime=1000$}
217 . error
218 .else
219 . error
220 .endif
221 # expect+1: Dollar followed by nothing
222 .if ${word:L:Mw*$} != "word"
223 . error
224 .endif
225 # expect+1: Dollar followed by nothing
226 .if ${word:L:NX*$} != "word"
227 . error
228 .endif
229 # expect+1: Invalid argument "fallback$" for modifier ":mtime"
230 .if ${.:L:mtime=fallback$}
231 . error
232 .else
233 . error
234 .endif
235 .if ${word:L:S,d$,m,} != "worm"
236 . error
237 .endif
238 .if ${word:L:S,d,m$,} != "worm\$"
239 . error
240 .endif
241
242 .undef VAR
243 # expect+1: Missing delimiter ":" after modifier "L"
244 .if ${VAR:LAR=ALUE} != "VALUE"
245 . error
246 .endif
247 .if ${VAR:L:AR=ALUE} != "VALUE"
248 . error
249 .endif
250
251
252 # When an expression has the usual form ${...} with braces,
253 # in the part of a modifier, ":}\$" can be escaped using a backslash.
254 # All other characters are passed through unmodified.
255 # expect+1: Invalid time value " : } \ $ ) \) ( "
256 .if ${%Y:L:localtime= \: \} \\ \$ ) \) ( :M*} != ": } \\ \$ ) \\) ("
257 . error
258 .endif
259 # When an expression has the unusual form $(...) with parentheses,
260 # in the part of a modifier, ":)\$" can be escaped using a backslash.
261 # All other characters are passed through unmodified.
262 # expect+1: Invalid time value " : \) \ $ "
263 .if ${%Y:L:localtime= \: \) \\ \$ } \} { :M*} != ": ) \\ \$ } \\} {"
264 . error
265 .endif
266 # Same when the modifier is the last modifier in an expression.
267 # expect+1: Invalid time value " : } \ $ ) \) ( "
268 .if ${%Y:L:localtime= \: \} \\ \$ ) \) ( } != " : } \\ \$ ) \\) ( "
269 . error
270 .endif
271 # Same when the modifier is the last modifier in an expression.
272 # expect+1: Invalid time value " : \) \ $ "
273 .if ${%Y:L:localtime= \: \) \\ \$ } \} { } != " : ) \\ \$ } \\} { "
274 . error
275 .endif
276