xref: /freebsd/contrib/bmake/unit-tests/varmod.mk (revision 2e47f35be5dc61945afdbd1a70e8fd505c032c94)
1# $NetBSD: varmod.mk,v 1.26 2025/03/30 01:27:13 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 variable modifier :P does not fall back to the SysV modifier.
122# Therefore the modifier :P=RE generates a parse error.
123# XXX: The .error should not be reached since the expression is
124# malformed, and this error should be propagated up to Cond_EvalLine.
125VAR=	STOP
126# expect+1: Missing delimiter ':' after modifier "P"
127.if ${VAR:P=RE} != "STORE"
128# expect+1: Missing argument for ".error"
129.  error
130.endif
131
132# Test the word selection modifier ':[n]' with a very large number that is
133# larger than ULONG_MAX for any supported platform.
134# expect+1: Invalid modifier ":[99333000222000111000]"
135.if ${word:L:[99333000222000111000]}
136.endif
137# expect+1: Invalid modifier ":[2147483648]"
138.if ${word:L:[2147483648]}
139.endif
140
141# Test the range generation modifier ':range=n' with a very large number that
142# is larger than SIZE_MAX for any supported platform.
143# expect+1: Invalid number "99333000222000111000}" for ':range' modifier
144.if ${word:L:range=99333000222000111000}
145.endif
146
147# In an indirect modifier, the delimiter is '\0', which at the same time marks
148# the end of the string.  The sequence '\\' '\0' is not an escaped delimiter,
149# as it would be wrong to skip past the end of the string.
150# expect+1: Invalid time value "\"
151.if ${:${:Ugmtime=\\}}
152.  error
153.endif
154
155# Test a '$' at the end of a modifier part, for all modifiers in the order
156# listed in ApplyModifier.
157#
158# The only modifier parts where an unescaped '$' makes sense at the end are
159# the 'from' parts of the ':S' and ':C' modifiers.  In all other modifier
160# parts, an unescaped '$' is an undocumented and discouraged edge case, as it
161# means the same as an escaped '$'.
162.if ${:U:!printf '%s\n' $!} != "\$"
163.  error
164.endif
165# expect+1: Dollar followed by nothing
166.if ${VAR::=value$} != "" || ${VAR} != "value"
167.  error
168.endif
169${:U }=		<space>
170# expect+2: Dollar followed by nothing
171# expect+1: Dollar followed by nothing
172.if ${VAR::+=appended$} != "" || ${VAR} != "value<space>appended"
173.  error
174.endif
175.if ${1:?then$:else$} != "then\$"
176.  error
177.endif
178.if ${0:?then$:else$} != "else\$"
179.  error
180.endif
181# expect+1: Dollar followed by nothing
182.if ${word:L:@w@$w$@} != "word"
183.  error
184.endif
185# expect+1: Invalid modifier ":[$]"
186.if ${word:[$]}
187.  error
188.else
189.  error
190.endif
191VAR_DOLLAR=	VAR$$
192.if ${word:L:_=VAR$} != "word" || ${${VAR_DOLLAR}} != "word"
193.  error
194.endif
195.if ${word:L:C,d$,m,} != "worm"
196.  error
197.endif
198.if ${word:L:C,d,$,} != "wor\$"
199.  error
200.endif
201# expect+2: Dollar followed by nothing
202# expect+1: Invalid variable name '}', at "$} != "set""
203.if ${VAR:Dset$} != "set"
204.  error
205.endif
206# expect+1: Invalid variable name '}', at "$} != "fallback""
207.if ${:Ufallback$} != "fallback"
208.  error
209.endif
210# expect+1: Invalid time value "1000$"
211.if ${%y:L:gmtime=1000$}
212.  error
213.else
214.  error
215.endif
216# expect+1: Invalid time value "1000$"
217.if ${%y:L:localtime=1000$}
218.  error
219.else
220.  error
221.endif
222# expect+1: Dollar followed by nothing
223.if ${word:L:Mw*$} != "word"
224.  error
225.endif
226# expect+1: Dollar followed by nothing
227.if ${word:L:NX*$} != "word"
228.  error
229.endif
230# expect+1: Invalid argument 'fallback$' for modifier ':mtime'
231.if ${.:L:mtime=fallback$}
232.  error
233.else
234.  error
235.endif
236.if ${word:L:S,d$,m,} != "worm"
237.  error
238.endif
239.if ${word:L:S,d,m$,} != "worm\$"
240.  error
241.endif
242
243.undef VAR
244# expect+1: Missing delimiter ':' after modifier "L"
245.if ${VAR:LAR=ALUE} != "VALUE"
246.  error
247.endif
248.if ${VAR:L:AR=ALUE} != "VALUE"
249.  error
250.endif
251