xref: /freebsd/contrib/bmake/unit-tests/varmod.mk (revision df21a004be237a1dccd03c7b47254625eea62fa9)
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
72DOLLAR1=	$$
73DOLLAR2=	${: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.
123VAR=	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
190VAR_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