aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/unit-tests/cond-func-empty.mk
blob: 057b175a7693b17d438c97e9ef1dcedae389ad13 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# $NetBSD: cond-func-empty.mk,v 1.24 2023/12/19 19:33:40 rillig Exp $
#
# Tests for the empty() function in .if conditions, which tests an
# expression for emptiness.
#
# Note that the argument in the parentheses is a variable name, not an
# expression.  That name may be followed by ':...' modifiers.
#

.undef UNDEF
EMPTY=	# empty
SPACE=	${:U }
ZERO=	0
WORD=	word

# An undefined variable counts as empty.
.if !empty(UNDEF)
.  error
.endif

# An undefined variable has the empty string as the value, and the :M
# variable modifier does not change that.
#
.if !empty(UNDEF:M*)
.  error
.endif

# The :S modifier replaces the empty value with an actual word.  After
# applying the :S modifier to the expression, its value is 'empty', so it is
# no longer empty, but it is still based on an undefined variable.  There are
# a few modifiers that turn an undefined expression into a defined expression,
# among them :U and :D, but not :S.  Therefore, at the end of evaluating the
# expression, the expression is still undefined, so its final value becomes an
# empty string.
#
# XXX: This is hard to explain to someone who doesn't know these
# implementation details.
#
.if !empty(UNDEF:S,^$,value,W)
.  error
.endif

# The :U modifier changes the state of a previously undefined expression from
# DEF_UNDEF to DEF_DEFINED.  This marks the expression as "being interesting
# enough to be further processed".
#
.if empty(UNDEF:S,^$,value,W:Ufallback)
.  error
.endif

# When an expression is based on an undefined variable, its modifiers interact
# in sometimes surprising ways.  Applying the :S modifier to the undefined
# expression makes its value non-empty, but doesn't change that the expression
# is based on an undefined variable.  The :U modifier that follows only looks
# at the definedness state to decide whether the variable is defined or not.
# This kind of makes sense since the :U modifier tests the _variable_, not the
# _expression_.
#
# Since the variable was undefined to begin with, the fallback value from the
# :U modifier is used in this expression, instead of keeping the 'value' from
# the :S modifier.
#
.if ${UNDEF:S,^$,value,W:Ufallback} != "fallback"
.  error
.endif

# The variable EMPTY is completely empty (0 characters).
.if !empty(EMPTY)
.  error
.endif

# The variable SPACE has a single space, which counts as being empty.
.if !empty(SPACE)
.  error
.endif

# The variable .newline has a single newline, which counts as being empty.
.if !empty(.newline)
.  error
.endif

# The variable ZERO has the numeric value 0, but is not empty.  This is a
# subtle difference between using either 'empty(ZERO)' or the expression
# '${ZERO}' in a condition.
.if empty(ZERO)
.  error
.elif ${ZERO}
.  error
.elif ${ZERO} == ""
.  error
.endif

# The following example constructs an expression with the variable name ""
# and the value " ".  This expression counts as empty since the value contains
# only whitespace.
#
# Contrary to the other functions in conditionals, the trailing space is not
# stripped off, as can be seen in the -dv debug log.  If the space had been
# stripped, it wouldn't make a difference in this case, but in other cases.
#
.if !empty(:U )
.  error
.endif

# Now the variable named " " gets a non-empty value, which demonstrates that
# neither leading nor trailing spaces are trimmed in the argument of the
# function.  If the spaces were trimmed, the variable name would be "" and
# that variable is indeed undefined.  Since CondParser_FuncCallEmpty calls
# Var_Parse without VARE_UNDEFERR, the value of the undefined variable ""
# would be returned as an empty string.
${:U }=	space
.if empty( )
.  error
.endif

# The value of the following expression is " word", which is not empty.  To be
# empty, _all_ characters in the expression value have to be whitespace, not
# only the first.
.if empty(:U word)
.  error
.endif

# The :L modifier creates an expression that has the same value as
# its name, which both are "VAR" in this case.  The value is therefore not
# empty.
.if empty(VAR:L)
.  error
.endif

# The variable WORD has the value "word", which does not count as empty.
.if empty(WORD)
.  error
.endif

# The expression ${} for a variable with the empty name always evaluates
# to an empty string (see Var_Parse, varUndefined).
.if !empty()
.  error
.endif

# Ensure that expressions that appear as part of the function call
# argument are properly parsed.  Typical use cases for this are .for loops,
# which are expanded to exactly these ${:U} expressions.
#
# The argument expands to "WORD", and that variable is defined at the
# beginning of this file.  The surrounding 'W' and 'D' ensure that
# CondParser_FuncCallEmpty keeps track of the parsing position, both before
# and after the call to Var_Parse.
.if empty(W${:UOR}D)
.  error
.endif

# There may be spaces outside the parentheses.
# Spaces inside the parentheses are interpreted as part of the variable name.
.if ! empty ( WORD )
.  error
.endif

${:U WORD }=	variable name with spaces

# Now there is a variable named " WORD ", and it is not empty.
.if empty ( WORD )
.  error
.endif

# expect+2: Unclosed variable "WORD"
# expect+1: Malformed conditional (empty(WORD)
.if empty(WORD
.  error
.else
.  error
.endif

# Since cond.c 1.76 from 2020-06-28 and before var.c 1.226 from 2020-07-02,
# the following example generated a wrong error message "Variable VARNAME is
# recursive".
#
# Since at least 1993, the manual page claimed that irrelevant parts of
# conditions were not evaluated, but that was wrong for a long time.  The
# expressions in irrelevant parts of the condition were actually evaluated,
# they just allowed undefined variables to be used in the conditions.  These
# unnecessary evaluations were fixed in several commits, starting with var.c
# 1.226 from 2020-07-02.
#
# In this example, the variable "VARNAME2" is not defined, so evaluation of
# the condition should have stopped at this point, and the rest of the
# condition should have been processed in parse-only mode.  The right-hand
# side containing the '!empty' was evaluated though, as it had always been.
#
# When evaluating the !empty condition, the variable name was parsed as
# "VARNAME${:U2}", but without expanding any nested expression, in
# this case the ${:U2}.  The expression '${:U2}' was replaced with an empty
# string, the resulting variable name was thus "VARNAME".  This conceptually
# wrong variable name should have been discarded quickly after parsing it, to
# prevent it from doing any harm.
#
# The expression was expanded, and this was wrong.  The
# expansion was done without VARE_WANTRES (called VARF_WANTRES back then)
# though.  This had the effect that the ${:U1} from the value of VARNAME
# expanded to an empty string.  This in turn created the seemingly recursive
# definition VARNAME=${VARNAME}, and that definition was never meant to be
# expanded.
#
# This was fixed by expanding nested expressions in the variable name
# only if the flag VARE_WANTRES is given.
VARNAME=	${VARNAME${:U1}}
.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
.endif


# If the word 'empty' is not followed by '(', it is not a function call but an
# ordinary bare word.  This bare word is interpreted as 'defined(empty)', and
# since there is no variable named 'empty', the condition evaluates to false.
.if empty
.  error
.endif

empty=		# defined but empty
.if empty
.else
.  error
.endif