xref: /freebsd/contrib/bmake/cond.c (revision 129043849f62f9cfa72f6fae68417d9995860f3f)
1*12904384SSimon J. Gerraty /*	$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $	*/
23955d011SMarcel Moolenaar 
33955d011SMarcel Moolenaar /*
43955d011SMarcel Moolenaar  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
53955d011SMarcel Moolenaar  * All rights reserved.
63955d011SMarcel Moolenaar  *
73955d011SMarcel Moolenaar  * This code is derived from software contributed to Berkeley by
83955d011SMarcel Moolenaar  * Adam de Boor.
93955d011SMarcel Moolenaar  *
103955d011SMarcel Moolenaar  * Redistribution and use in source and binary forms, with or without
113955d011SMarcel Moolenaar  * modification, are permitted provided that the following conditions
123955d011SMarcel Moolenaar  * are met:
133955d011SMarcel Moolenaar  * 1. Redistributions of source code must retain the above copyright
143955d011SMarcel Moolenaar  *    notice, this list of conditions and the following disclaimer.
153955d011SMarcel Moolenaar  * 2. Redistributions in binary form must reproduce the above copyright
163955d011SMarcel Moolenaar  *    notice, this list of conditions and the following disclaimer in the
173955d011SMarcel Moolenaar  *    documentation and/or other materials provided with the distribution.
183955d011SMarcel Moolenaar  * 3. Neither the name of the University nor the names of its contributors
193955d011SMarcel Moolenaar  *    may be used to endorse or promote products derived from this software
203955d011SMarcel Moolenaar  *    without specific prior written permission.
213955d011SMarcel Moolenaar  *
223955d011SMarcel Moolenaar  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
233955d011SMarcel Moolenaar  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
243955d011SMarcel Moolenaar  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
253955d011SMarcel Moolenaar  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
263955d011SMarcel Moolenaar  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
273955d011SMarcel Moolenaar  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
283955d011SMarcel Moolenaar  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
293955d011SMarcel Moolenaar  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
303955d011SMarcel Moolenaar  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
313955d011SMarcel Moolenaar  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
323955d011SMarcel Moolenaar  * SUCH DAMAGE.
333955d011SMarcel Moolenaar  */
343955d011SMarcel Moolenaar 
353955d011SMarcel Moolenaar /*
363955d011SMarcel Moolenaar  * Copyright (c) 1988, 1989 by Adam de Boor
373955d011SMarcel Moolenaar  * Copyright (c) 1989 by Berkeley Softworks
383955d011SMarcel Moolenaar  * All rights reserved.
393955d011SMarcel Moolenaar  *
403955d011SMarcel Moolenaar  * This code is derived from software contributed to Berkeley by
413955d011SMarcel Moolenaar  * Adam de Boor.
423955d011SMarcel Moolenaar  *
433955d011SMarcel Moolenaar  * Redistribution and use in source and binary forms, with or without
443955d011SMarcel Moolenaar  * modification, are permitted provided that the following conditions
453955d011SMarcel Moolenaar  * are met:
463955d011SMarcel Moolenaar  * 1. Redistributions of source code must retain the above copyright
473955d011SMarcel Moolenaar  *    notice, this list of conditions and the following disclaimer.
483955d011SMarcel Moolenaar  * 2. Redistributions in binary form must reproduce the above copyright
493955d011SMarcel Moolenaar  *    notice, this list of conditions and the following disclaimer in the
503955d011SMarcel Moolenaar  *    documentation and/or other materials provided with the distribution.
513955d011SMarcel Moolenaar  * 3. All advertising materials mentioning features or use of this software
523955d011SMarcel Moolenaar  *    must display the following acknowledgement:
533955d011SMarcel Moolenaar  *	This product includes software developed by the University of
543955d011SMarcel Moolenaar  *	California, Berkeley and its contributors.
553955d011SMarcel Moolenaar  * 4. Neither the name of the University nor the names of its contributors
563955d011SMarcel Moolenaar  *    may be used to endorse or promote products derived from this software
573955d011SMarcel Moolenaar  *    without specific prior written permission.
583955d011SMarcel Moolenaar  *
593955d011SMarcel Moolenaar  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
603955d011SMarcel Moolenaar  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
613955d011SMarcel Moolenaar  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
623955d011SMarcel Moolenaar  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
633955d011SMarcel Moolenaar  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
643955d011SMarcel Moolenaar  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
653955d011SMarcel Moolenaar  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
663955d011SMarcel Moolenaar  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
673955d011SMarcel Moolenaar  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
683955d011SMarcel Moolenaar  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
693955d011SMarcel Moolenaar  * SUCH DAMAGE.
703955d011SMarcel Moolenaar  */
713955d011SMarcel Moolenaar 
7206b9b3e0SSimon J. Gerraty /*
7306b9b3e0SSimon J. Gerraty  * Handling of conditionals in a makefile.
743955d011SMarcel Moolenaar  *
753955d011SMarcel Moolenaar  * Interface:
76e2eeea75SSimon J. Gerraty  *	Cond_EvalLine   Evaluate the conditional directive, such as
77e2eeea75SSimon J. Gerraty  *			'.if <cond>', '.elifnmake <cond>', '.else', '.endif'.
783955d011SMarcel Moolenaar  *
79956e45f6SSimon J. Gerraty  *	Cond_EvalCondition
80956e45f6SSimon J. Gerraty  *			Evaluate the conditional, which is either the argument
81956e45f6SSimon J. Gerraty  *			of one of the .if directives or the condition in a
82956e45f6SSimon J. Gerraty  *			':?then:else' variable modifier.
83956e45f6SSimon J. Gerraty  *
84956e45f6SSimon J. Gerraty  *	Cond_save_depth
85956e45f6SSimon J. Gerraty  *	Cond_restore_depth
86956e45f6SSimon J. Gerraty  *			Save and restore the nesting of the conditions, at
87956e45f6SSimon J. Gerraty  *			the start and end of including another makefile, to
88956e45f6SSimon J. Gerraty  *			ensure that in each makefile the conditional
89956e45f6SSimon J. Gerraty  *			directives are well-balanced.
903955d011SMarcel Moolenaar  */
913955d011SMarcel Moolenaar 
922c3632d1SSimon J. Gerraty #include <errno.h>
933955d011SMarcel Moolenaar 
943955d011SMarcel Moolenaar #include "make.h"
953955d011SMarcel Moolenaar #include "dir.h"
963955d011SMarcel Moolenaar 
97956e45f6SSimon J. Gerraty /*	"@(#)cond.c	8.2 (Berkeley) 1/2/94"	*/
98*12904384SSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $");
99956e45f6SSimon J. Gerraty 
1003955d011SMarcel Moolenaar /*
1013955d011SMarcel Moolenaar  * The parsing of conditional expressions is based on this grammar:
102*12904384SSimon J. Gerraty  *	Or -> And ('||' And)*
103*12904384SSimon J. Gerraty  *	And -> Term ('&&' Term)*
104dba7b0efSSimon J. Gerraty  *	Term -> Function '(' Argument ')'
105dba7b0efSSimon J. Gerraty  *	Term -> Leaf Operator Leaf
106dba7b0efSSimon J. Gerraty  *	Term -> Leaf
107dba7b0efSSimon J. Gerraty  *	Term -> '(' Or ')'
108dba7b0efSSimon J. Gerraty  *	Term -> '!' Term
109dba7b0efSSimon J. Gerraty  *	Leaf -> "string"
110dba7b0efSSimon J. Gerraty  *	Leaf -> Number
111dba7b0efSSimon J. Gerraty  *	Leaf -> VariableExpression
112dba7b0efSSimon J. Gerraty  *	Leaf -> Symbol
113dba7b0efSSimon J. Gerraty  *	Operator -> '==' | '!=' | '>' | '<' | '>=' | '<='
1143955d011SMarcel Moolenaar  *
115dba7b0efSSimon J. Gerraty  * 'Symbol' is an unquoted string literal to which the default function is
116dba7b0efSSimon J. Gerraty  * applied.
1173955d011SMarcel Moolenaar  *
118956e45f6SSimon J. Gerraty  * The tokens are scanned by CondToken, which returns:
119dba7b0efSSimon J. Gerraty  *	TOK_AND		for '&&'
120dba7b0efSSimon J. Gerraty  *	TOK_OR		for '||'
121956e45f6SSimon J. Gerraty  *	TOK_NOT		for '!'
122956e45f6SSimon J. Gerraty  *	TOK_LPAREN	for '('
123956e45f6SSimon J. Gerraty  *	TOK_RPAREN	for ')'
124dba7b0efSSimon J. Gerraty  *
125956e45f6SSimon J. Gerraty  * Other terminal symbols are evaluated using either the default function or
126956e45f6SSimon J. Gerraty  * the function given in the terminal, they return either TOK_TRUE or
127956e45f6SSimon J. Gerraty  * TOK_FALSE.
1283955d011SMarcel Moolenaar  */
129956e45f6SSimon J. Gerraty typedef enum Token {
130dba7b0efSSimon J. Gerraty 	TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT,
1313955d011SMarcel Moolenaar 	TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
1323955d011SMarcel Moolenaar } Token;
1333955d011SMarcel Moolenaar 
134dba7b0efSSimon J. Gerraty typedef enum CondResult {
135dba7b0efSSimon J. Gerraty 	CR_FALSE, CR_TRUE, CR_ERROR
136dba7b0efSSimon J. Gerraty } CondResult;
137dba7b0efSSimon J. Gerraty 
138dba7b0efSSimon J. Gerraty typedef enum ComparisonOp {
139dba7b0efSSimon J. Gerraty 	LT, LE, GT, GE, EQ, NE
140dba7b0efSSimon J. Gerraty } ComparisonOp;
141dba7b0efSSimon J. Gerraty 
142956e45f6SSimon J. Gerraty typedef struct CondParser {
143dba7b0efSSimon J. Gerraty 
144dba7b0efSSimon J. Gerraty 	/*
145dba7b0efSSimon J. Gerraty 	 * The plain '.if ${VAR}' evaluates to true if the value of the
146dba7b0efSSimon J. Gerraty 	 * expression has length > 0.  The other '.if' variants delegate
147dba7b0efSSimon J. Gerraty 	 * to evalBare instead.
148dba7b0efSSimon J. Gerraty 	 */
149b0c40a00SSimon J. Gerraty 	bool plain;
150dba7b0efSSimon J. Gerraty 
151dba7b0efSSimon J. Gerraty 	/* The function to apply on unquoted bare words. */
152*12904384SSimon J. Gerraty 	bool (*evalBare)(const char *);
153b0c40a00SSimon J. Gerraty 	bool negateEvalBare;
154dba7b0efSSimon J. Gerraty 
155*12904384SSimon J. Gerraty 	/*
156*12904384SSimon J. Gerraty 	 * Whether the left-hand side of a comparison may be an unquoted
157*12904384SSimon J. Gerraty 	 * string.  This is allowed for expressions of the form
158*12904384SSimon J. Gerraty 	 * ${condition:?:}, see ApplyModifier_IfElse.  Such a condition is
159*12904384SSimon J. Gerraty 	 * expanded before it is evaluated, due to ease of implementation.
160*12904384SSimon J. Gerraty 	 * This means that at the point where the condition is evaluated,
161*12904384SSimon J. Gerraty 	 * make cannot know anymore whether the left-hand side had originally
162*12904384SSimon J. Gerraty 	 * been a variable expression or a plain word.
163*12904384SSimon J. Gerraty 	 *
164*12904384SSimon J. Gerraty 	 * In all other contexts, the left-hand side must either be a
165*12904384SSimon J. Gerraty 	 * variable expression, a quoted string or a number.
166*12904384SSimon J. Gerraty 	 */
167*12904384SSimon J. Gerraty 	bool leftUnquotedOK;
168*12904384SSimon J. Gerraty 
169956e45f6SSimon J. Gerraty 	const char *p;		/* The remaining condition to parse */
170956e45f6SSimon J. Gerraty 	Token curr;		/* Single push-back token used in parsing */
1713955d011SMarcel Moolenaar 
17206b9b3e0SSimon J. Gerraty 	/*
17306b9b3e0SSimon J. Gerraty 	 * Whether an error message has already been printed for this
17406b9b3e0SSimon J. Gerraty 	 * condition. The first available error message is usually the most
17506b9b3e0SSimon J. Gerraty 	 * specific one, therefore it makes sense to suppress the standard
17606b9b3e0SSimon J. Gerraty 	 * "Malformed conditional" message.
17706b9b3e0SSimon J. Gerraty 	 */
178b0c40a00SSimon J. Gerraty 	bool printedError;
179956e45f6SSimon J. Gerraty } CondParser;
180956e45f6SSimon J. Gerraty 
181b0c40a00SSimon J. Gerraty static CondResult CondParser_Or(CondParser *par, bool);
1823955d011SMarcel Moolenaar 
1833955d011SMarcel Moolenaar static unsigned int cond_depth = 0;	/* current .if nesting level */
1843955d011SMarcel Moolenaar static unsigned int cond_min_depth = 0;	/* depth at makefile open */
1853955d011SMarcel Moolenaar 
186*12904384SSimon J. Gerraty /* Names for ComparisonOp. */
187*12904384SSimon J. Gerraty static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" };
18828a6bc81SSimon J. Gerraty 
189b0c40a00SSimon J. Gerraty static bool
190*12904384SSimon J. Gerraty is_token(const char *str, const char *tok, unsigned char len)
1913955d011SMarcel Moolenaar {
192*12904384SSimon J. Gerraty 	return strncmp(str, tok, (size_t)len) == 0 && !ch_isalpha(str[len]);
1933955d011SMarcel Moolenaar }
1943955d011SMarcel Moolenaar 
195e2eeea75SSimon J. Gerraty static Token
196b0c40a00SSimon J. Gerraty ToToken(bool cond)
197e2eeea75SSimon J. Gerraty {
198e2eeea75SSimon J. Gerraty 	return cond ? TOK_TRUE : TOK_FALSE;
199e2eeea75SSimon J. Gerraty }
200e2eeea75SSimon J. Gerraty 
201956e45f6SSimon J. Gerraty static void
202956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(CondParser *par)
2033955d011SMarcel Moolenaar {
204956e45f6SSimon J. Gerraty 	cpp_skip_whitespace(&par->p);
205956e45f6SSimon J. Gerraty }
206956e45f6SSimon J. Gerraty 
20706b9b3e0SSimon J. Gerraty /*
208*12904384SSimon J. Gerraty  * Parse a single word, taking into account balanced parentheses as well as
209*12904384SSimon J. Gerraty  * embedded expressions.  Used for the argument of a built-in function as
210*12904384SSimon J. Gerraty  * well as for bare words, which are then passed to the default function.
211956e45f6SSimon J. Gerraty  *
212956e45f6SSimon J. Gerraty  * Arguments:
213956e45f6SSimon J. Gerraty  *	*pp initially points at the '(',
214956e45f6SSimon J. Gerraty  *	upon successful return it points right after the ')'.
215956e45f6SSimon J. Gerraty  *
216956e45f6SSimon J. Gerraty  *	*out_arg receives the argument as string.
217956e45f6SSimon J. Gerraty  *
218956e45f6SSimon J. Gerraty  *	func says whether the argument belongs to an actual function, or
219*12904384SSimon J. Gerraty  *	NULL when parsing a bare word.
220956e45f6SSimon J. Gerraty  *
221*12904384SSimon J. Gerraty  * Return the length of the argument, or an ambiguous 0 on error.
22206b9b3e0SSimon J. Gerraty  */
223956e45f6SSimon J. Gerraty static size_t
224*12904384SSimon J. Gerraty ParseWord(CondParser *par, const char **pp, bool doEval, const char *func,
22506b9b3e0SSimon J. Gerraty 	     char **out_arg)
22606b9b3e0SSimon J. Gerraty {
227956e45f6SSimon J. Gerraty 	const char *p = *pp;
228956e45f6SSimon J. Gerraty 	Buffer argBuf;
2293955d011SMarcel Moolenaar 	int paren_depth;
2302c3632d1SSimon J. Gerraty 	size_t argLen;
2313955d011SMarcel Moolenaar 
2323955d011SMarcel Moolenaar 	if (func != NULL)
233956e45f6SSimon J. Gerraty 		p++;		/* Skip opening '(' - verified by caller */
2343955d011SMarcel Moolenaar 
235e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&p);
2363955d011SMarcel Moolenaar 
237e2eeea75SSimon J. Gerraty 	Buf_InitSize(&argBuf, 16);
2383955d011SMarcel Moolenaar 
2393955d011SMarcel Moolenaar 	paren_depth = 0;
2403955d011SMarcel Moolenaar 	for (;;) {
241956e45f6SSimon J. Gerraty 		char ch = *p;
242e2eeea75SSimon J. Gerraty 		if (ch == '\0' || ch == ' ' || ch == '\t')
2433955d011SMarcel Moolenaar 			break;
2443955d011SMarcel Moolenaar 		if ((ch == '&' || ch == '|') && paren_depth == 0)
2453955d011SMarcel Moolenaar 			break;
246956e45f6SSimon J. Gerraty 		if (*p == '$') {
2473955d011SMarcel Moolenaar 			/*
24806b9b3e0SSimon J. Gerraty 			 * Parse the variable expression and install it as
24906b9b3e0SSimon J. Gerraty 			 * part of the argument if it's valid. We tell
25006b9b3e0SSimon J. Gerraty 			 * Var_Parse to complain on an undefined variable,
25106b9b3e0SSimon J. Gerraty 			 * (XXX: but Var_Parse ignores that request)
25206b9b3e0SSimon J. Gerraty 			 * so we don't need to do it. Nor do we return an
25306b9b3e0SSimon J. Gerraty 			 * error, though perhaps we should.
2543955d011SMarcel Moolenaar 			 */
255b0c40a00SSimon J. Gerraty 			VarEvalMode emode = doEval
256b0c40a00SSimon J. Gerraty 			    ? VARE_UNDEFERR
257b0c40a00SSimon J. Gerraty 			    : VARE_PARSE_ONLY;
25806b9b3e0SSimon J. Gerraty 			FStr nestedVal;
259b0c40a00SSimon J. Gerraty 			(void)Var_Parse(&p, SCOPE_CMDLINE, emode, &nestedVal);
260956e45f6SSimon J. Gerraty 			/* TODO: handle errors */
26106b9b3e0SSimon J. Gerraty 			Buf_AddStr(&argBuf, nestedVal.str);
26206b9b3e0SSimon J. Gerraty 			FStr_Done(&nestedVal);
2633955d011SMarcel Moolenaar 			continue;
2643955d011SMarcel Moolenaar 		}
2653955d011SMarcel Moolenaar 		if (ch == '(')
2663955d011SMarcel Moolenaar 			paren_depth++;
2672c3632d1SSimon J. Gerraty 		else if (ch == ')' && --paren_depth < 0)
2683955d011SMarcel Moolenaar 			break;
269956e45f6SSimon J. Gerraty 		Buf_AddByte(&argBuf, *p);
270956e45f6SSimon J. Gerraty 		p++;
2713955d011SMarcel Moolenaar 	}
2723955d011SMarcel Moolenaar 
273dba7b0efSSimon J. Gerraty 	argLen = argBuf.len;
274dba7b0efSSimon J. Gerraty 	*out_arg = Buf_DoneData(&argBuf);
2753955d011SMarcel Moolenaar 
276e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&p);
2773955d011SMarcel Moolenaar 
278956e45f6SSimon J. Gerraty 	if (func != NULL && *p++ != ')') {
279dba7b0efSSimon J. Gerraty 		Parse_Error(PARSE_FATAL,
280dba7b0efSSimon J. Gerraty 		    "Missing closing parenthesis for %s()", func);
281b0c40a00SSimon J. Gerraty 		par->printedError = true;
2823841c287SSimon J. Gerraty 		return 0;
2833955d011SMarcel Moolenaar 	}
2843955d011SMarcel Moolenaar 
285956e45f6SSimon J. Gerraty 	*pp = p;
2863841c287SSimon J. Gerraty 	return argLen;
2873955d011SMarcel Moolenaar }
2882c3632d1SSimon J. Gerraty 
2892c3632d1SSimon J. Gerraty /* Test whether the given variable is defined. */
290b0c40a00SSimon J. Gerraty static bool
291*12904384SSimon J. Gerraty FuncDefined(const char *arg)
2923955d011SMarcel Moolenaar {
293dba7b0efSSimon J. Gerraty 	FStr value = Var_Value(SCOPE_CMDLINE, arg);
294b0c40a00SSimon J. Gerraty 	bool result = value.str != NULL;
29506b9b3e0SSimon J. Gerraty 	FStr_Done(&value);
2963841c287SSimon J. Gerraty 	return result;
2973955d011SMarcel Moolenaar }
2982c3632d1SSimon J. Gerraty 
299*12904384SSimon J. Gerraty /* See if the given target is requested to be made. */
300b0c40a00SSimon J. Gerraty static bool
301*12904384SSimon J. Gerraty FuncMake(const char *arg)
3023955d011SMarcel Moolenaar {
303956e45f6SSimon J. Gerraty 	StringListNode *ln;
304956e45f6SSimon J. Gerraty 
30506b9b3e0SSimon J. Gerraty 	for (ln = opts.create.first; ln != NULL; ln = ln->next)
306956e45f6SSimon J. Gerraty 		if (Str_Match(ln->datum, arg))
307b0c40a00SSimon J. Gerraty 			return true;
308b0c40a00SSimon J. Gerraty 	return false;
3093955d011SMarcel Moolenaar }
3102c3632d1SSimon J. Gerraty 
3112c3632d1SSimon J. Gerraty /* See if the given file exists. */
312b0c40a00SSimon J. Gerraty static bool
313*12904384SSimon J. Gerraty FuncExists(const char *arg)
3143955d011SMarcel Moolenaar {
315b0c40a00SSimon J. Gerraty 	bool result;
3163955d011SMarcel Moolenaar 	char *path;
3173955d011SMarcel Moolenaar 
31806b9b3e0SSimon J. Gerraty 	path = Dir_FindFile(arg, &dirSearchPath);
319e2eeea75SSimon J. Gerraty 	DEBUG2(COND, "exists(%s) result is \"%s\"\n",
320e2eeea75SSimon J. Gerraty 	       arg, path != NULL ? path : "");
321e2eeea75SSimon J. Gerraty 	result = path != NULL;
3223955d011SMarcel Moolenaar 	free(path);
3233841c287SSimon J. Gerraty 	return result;
3243955d011SMarcel Moolenaar }
3252c3632d1SSimon J. Gerraty 
3262c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target. */
327b0c40a00SSimon J. Gerraty static bool
328*12904384SSimon J. Gerraty FuncTarget(const char *arg)
3293955d011SMarcel Moolenaar {
330956e45f6SSimon J. Gerraty 	GNode *gn = Targ_FindNode(arg);
331956e45f6SSimon J. Gerraty 	return gn != NULL && GNode_IsTarget(gn);
3323955d011SMarcel Moolenaar }
3333955d011SMarcel Moolenaar 
33406b9b3e0SSimon J. Gerraty /*
33506b9b3e0SSimon J. Gerraty  * See if the given node exists and is an actual target with commands
33606b9b3e0SSimon J. Gerraty  * associated with it.
33706b9b3e0SSimon J. Gerraty  */
338b0c40a00SSimon J. Gerraty static bool
339*12904384SSimon J. Gerraty FuncCommands(const char *arg)
3403955d011SMarcel Moolenaar {
341956e45f6SSimon J. Gerraty 	GNode *gn = Targ_FindNode(arg);
34206b9b3e0SSimon J. Gerraty 	return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands);
3433955d011SMarcel Moolenaar }
3442c3632d1SSimon J. Gerraty 
345e2eeea75SSimon J. Gerraty /*
3463955d011SMarcel Moolenaar  * Convert the given number into a double.
3473955d011SMarcel Moolenaar  * We try a base 10 or 16 integer conversion first, if that fails
3483955d011SMarcel Moolenaar  * then we try a floating point conversion instead.
3493955d011SMarcel Moolenaar  *
3503955d011SMarcel Moolenaar  * Results:
351b0c40a00SSimon J. Gerraty  *	Returns true if the conversion succeeded.
352e2eeea75SSimon J. Gerraty  *	Sets 'out_value' to the converted number.
3533955d011SMarcel Moolenaar  */
354b0c40a00SSimon J. Gerraty static bool
355e2eeea75SSimon J. Gerraty TryParseNumber(const char *str, double *out_value)
3563955d011SMarcel Moolenaar {
357e2eeea75SSimon J. Gerraty 	char *end;
358e2eeea75SSimon J. Gerraty 	unsigned long ul_val;
359e2eeea75SSimon J. Gerraty 	double dbl_val;
3603955d011SMarcel Moolenaar 
361e2eeea75SSimon J. Gerraty 	if (str[0] == '\0') {	/* XXX: why is an empty string a number? */
362e2eeea75SSimon J. Gerraty 		*out_value = 0.0;
363b0c40a00SSimon J. Gerraty 		return true;
364ac3446e9SSimon J. Gerraty 	}
365e2eeea75SSimon J. Gerraty 
366*12904384SSimon J. Gerraty 	errno = 0;
367e2eeea75SSimon J. Gerraty 	ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10);
368e2eeea75SSimon J. Gerraty 	if (*end == '\0' && errno != ERANGE) {
369e2eeea75SSimon J. Gerraty 		*out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val;
370b0c40a00SSimon J. Gerraty 		return true;
3713955d011SMarcel Moolenaar 	}
3723955d011SMarcel Moolenaar 
373e2eeea75SSimon J. Gerraty 	if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E')
374b0c40a00SSimon J. Gerraty 		return false;	/* skip the expensive strtod call */
375e2eeea75SSimon J. Gerraty 	dbl_val = strtod(str, &end);
376e2eeea75SSimon J. Gerraty 	if (*end != '\0')
377b0c40a00SSimon J. Gerraty 		return false;
378e2eeea75SSimon J. Gerraty 
379e2eeea75SSimon J. Gerraty 	*out_value = dbl_val;
380b0c40a00SSimon J. Gerraty 	return true;
3813955d011SMarcel Moolenaar }
3823955d011SMarcel Moolenaar 
383b0c40a00SSimon J. Gerraty static bool
384956e45f6SSimon J. Gerraty is_separator(char ch)
385956e45f6SSimon J. Gerraty {
386b0c40a00SSimon J. Gerraty 	return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' ||
387b0c40a00SSimon J. Gerraty 	       ch == '>' || ch == '<' || ch == ')' /* but not '(' */;
388956e45f6SSimon J. Gerraty }
389956e45f6SSimon J. Gerraty 
390dba7b0efSSimon J. Gerraty /*
391dba7b0efSSimon J. Gerraty  * In a quoted or unquoted string literal or a number, parse a variable
392dba7b0efSSimon J. Gerraty  * expression.
393dba7b0efSSimon J. Gerraty  *
394dba7b0efSSimon J. Gerraty  * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX}
395dba7b0efSSimon J. Gerraty  */
396b0c40a00SSimon J. Gerraty static bool
397dba7b0efSSimon J. Gerraty CondParser_StringExpr(CondParser *par, const char *start,
398*12904384SSimon J. Gerraty 		      bool doEval, bool quoted,
399*12904384SSimon J. Gerraty 		      Buffer *buf, FStr *inout_str)
400dba7b0efSSimon J. Gerraty {
401b0c40a00SSimon J. Gerraty 	VarEvalMode emode;
402dba7b0efSSimon J. Gerraty 	const char *nested_p;
403b0c40a00SSimon J. Gerraty 	bool atStart;
404dba7b0efSSimon J. Gerraty 	VarParseResult parseResult;
405dba7b0efSSimon J. Gerraty 
406*12904384SSimon J. Gerraty 	emode = doEval && quoted ? VARE_WANTRES
407*12904384SSimon J. Gerraty 	    : doEval ? VARE_UNDEFERR
408b0c40a00SSimon J. Gerraty 	    : VARE_PARSE_ONLY;
409dba7b0efSSimon J. Gerraty 
410dba7b0efSSimon J. Gerraty 	nested_p = par->p;
411dba7b0efSSimon J. Gerraty 	atStart = nested_p == start;
412b0c40a00SSimon J. Gerraty 	parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, emode, inout_str);
413dba7b0efSSimon J. Gerraty 	/* TODO: handle errors */
414dba7b0efSSimon J. Gerraty 	if (inout_str->str == var_Error) {
415dba7b0efSSimon J. Gerraty 		if (parseResult == VPR_ERR) {
416dba7b0efSSimon J. Gerraty 			/*
417dba7b0efSSimon J. Gerraty 			 * FIXME: Even if an error occurs, there is no
418dba7b0efSSimon J. Gerraty 			 *  guarantee that it is reported.
419dba7b0efSSimon J. Gerraty 			 *
420dba7b0efSSimon J. Gerraty 			 * See cond-token-plain.mk $$$$$$$$.
421dba7b0efSSimon J. Gerraty 			 */
422b0c40a00SSimon J. Gerraty 			par->printedError = true;
423dba7b0efSSimon J. Gerraty 		}
424dba7b0efSSimon J. Gerraty 		/*
425dba7b0efSSimon J. Gerraty 		 * XXX: Can there be any situation in which a returned
426b0c40a00SSimon J. Gerraty 		 * var_Error needs to be freed?
427dba7b0efSSimon J. Gerraty 		 */
428dba7b0efSSimon J. Gerraty 		FStr_Done(inout_str);
429dba7b0efSSimon J. Gerraty 		/*
430dba7b0efSSimon J. Gerraty 		 * Even if !doEval, we still report syntax errors, which is
431dba7b0efSSimon J. Gerraty 		 * what getting var_Error back with !doEval means.
432dba7b0efSSimon J. Gerraty 		 */
433dba7b0efSSimon J. Gerraty 		*inout_str = FStr_InitRefer(NULL);
434b0c40a00SSimon J. Gerraty 		return false;
435dba7b0efSSimon J. Gerraty 	}
436dba7b0efSSimon J. Gerraty 	par->p = nested_p;
437dba7b0efSSimon J. Gerraty 
438dba7b0efSSimon J. Gerraty 	/*
439dba7b0efSSimon J. Gerraty 	 * If the '$' started the string literal (which means no quotes), and
440dba7b0efSSimon J. Gerraty 	 * the variable expression is followed by a space, looks like a
441dba7b0efSSimon J. Gerraty 	 * comparison operator or is the end of the expression, we are done.
442dba7b0efSSimon J. Gerraty 	 */
443dba7b0efSSimon J. Gerraty 	if (atStart && is_separator(par->p[0]))
444b0c40a00SSimon J. Gerraty 		return false;
445dba7b0efSSimon J. Gerraty 
446dba7b0efSSimon J. Gerraty 	Buf_AddStr(buf, inout_str->str);
447dba7b0efSSimon J. Gerraty 	FStr_Done(inout_str);
448dba7b0efSSimon J. Gerraty 	*inout_str = FStr_InitRefer(NULL); /* not finished yet */
449b0c40a00SSimon J. Gerraty 	return true;
450dba7b0efSSimon J. Gerraty }
451dba7b0efSSimon J. Gerraty 
452dba7b0efSSimon J. Gerraty /*
453b0c40a00SSimon J. Gerraty  * Parse a string from a variable expression or an optionally quoted
454b0c40a00SSimon J. Gerraty  * string.  This is called for the left-hand and right-hand sides of
455b0c40a00SSimon J. Gerraty  * comparisons.
4563955d011SMarcel Moolenaar  *
4573955d011SMarcel Moolenaar  * Results:
4582c3632d1SSimon J. Gerraty  *	Returns the string, absent any quotes, or NULL on error.
459b0c40a00SSimon J. Gerraty  *	Sets out_quoted if the leaf was a quoted string literal.
4603955d011SMarcel Moolenaar  */
46106b9b3e0SSimon J. Gerraty static void
462*12904384SSimon J. Gerraty CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK,
463b0c40a00SSimon J. Gerraty 		  FStr *out_str, bool *out_quoted)
4643955d011SMarcel Moolenaar {
4653955d011SMarcel Moolenaar 	Buffer buf;
46606b9b3e0SSimon J. Gerraty 	FStr str;
467b0c40a00SSimon J. Gerraty 	bool quoted;
4682c3632d1SSimon J. Gerraty 	const char *start;
4693955d011SMarcel Moolenaar 
470e2eeea75SSimon J. Gerraty 	Buf_Init(&buf);
47106b9b3e0SSimon J. Gerraty 	str = FStr_InitRefer(NULL);
472e2eeea75SSimon J. Gerraty 	*out_quoted = quoted = par->p[0] == '"';
473956e45f6SSimon J. Gerraty 	start = par->p;
474e2eeea75SSimon J. Gerraty 	if (quoted)
475956e45f6SSimon J. Gerraty 		par->p++;
47606b9b3e0SSimon J. Gerraty 
47706b9b3e0SSimon J. Gerraty 	while (par->p[0] != '\0' && str.str == NULL) {
478956e45f6SSimon J. Gerraty 		switch (par->p[0]) {
4793955d011SMarcel Moolenaar 		case '\\':
480956e45f6SSimon J. Gerraty 			par->p++;
481956e45f6SSimon J. Gerraty 			if (par->p[0] != '\0') {
482956e45f6SSimon J. Gerraty 				Buf_AddByte(&buf, par->p[0]);
483956e45f6SSimon J. Gerraty 				par->p++;
4843955d011SMarcel Moolenaar 			}
485956e45f6SSimon J. Gerraty 			continue;
4863955d011SMarcel Moolenaar 		case '"':
487956e45f6SSimon J. Gerraty 			par->p++;
488dba7b0efSSimon J. Gerraty 			if (quoted)
489dba7b0efSSimon J. Gerraty 				goto got_str;	/* skip the closing quote */
490dba7b0efSSimon J. Gerraty 			Buf_AddByte(&buf, '"');
491956e45f6SSimon J. Gerraty 			continue;
492e2eeea75SSimon J. Gerraty 		case ')':	/* see is_separator */
4933955d011SMarcel Moolenaar 		case '!':
4943955d011SMarcel Moolenaar 		case '=':
4953955d011SMarcel Moolenaar 		case '>':
4963955d011SMarcel Moolenaar 		case '<':
4973955d011SMarcel Moolenaar 		case ' ':
4983955d011SMarcel Moolenaar 		case '\t':
499e2eeea75SSimon J. Gerraty 			if (!quoted)
5003955d011SMarcel Moolenaar 				goto got_str;
501956e45f6SSimon J. Gerraty 			Buf_AddByte(&buf, par->p[0]);
502956e45f6SSimon J. Gerraty 			par->p++;
503956e45f6SSimon J. Gerraty 			continue;
5043955d011SMarcel Moolenaar 		case '$':
505dba7b0efSSimon J. Gerraty 			if (!CondParser_StringExpr(par,
506dba7b0efSSimon J. Gerraty 			    start, doEval, quoted, &buf, &str))
5073955d011SMarcel Moolenaar 				goto cleanup;
508956e45f6SSimon J. Gerraty 			continue;
5093955d011SMarcel Moolenaar 		default:
510*12904384SSimon J. Gerraty 			if (!unquotedOK && !quoted && *start != '$' &&
51106b9b3e0SSimon J. Gerraty 			    !ch_isdigit(*start)) {
51206b9b3e0SSimon J. Gerraty 				/*
51306b9b3e0SSimon J. Gerraty 				 * The left-hand side must be quoted,
514*12904384SSimon J. Gerraty 				 * a variable expression or a number.
51506b9b3e0SSimon J. Gerraty 				 */
51606b9b3e0SSimon J. Gerraty 				str = FStr_InitRefer(NULL);
51728a6bc81SSimon J. Gerraty 				goto cleanup;
51828a6bc81SSimon J. Gerraty 			}
519956e45f6SSimon J. Gerraty 			Buf_AddByte(&buf, par->p[0]);
520956e45f6SSimon J. Gerraty 			par->p++;
521956e45f6SSimon J. Gerraty 			continue;
5223955d011SMarcel Moolenaar 		}
5233955d011SMarcel Moolenaar 	}
5243955d011SMarcel Moolenaar got_str:
525dba7b0efSSimon J. Gerraty 	str = FStr_InitOwn(buf.data);
526*12904384SSimon J. Gerraty 	buf.data = NULL;
5273955d011SMarcel Moolenaar cleanup:
528*12904384SSimon J. Gerraty 	Buf_Done(&buf);
52906b9b3e0SSimon J. Gerraty 	*out_str = str;
5303955d011SMarcel Moolenaar }
5312c3632d1SSimon J. Gerraty 
532b0c40a00SSimon J. Gerraty static bool
533*12904384SSimon J. Gerraty EvalBare(const CondParser *par, const char *arg)
534e2eeea75SSimon J. Gerraty {
535*12904384SSimon J. Gerraty 	bool res = par->evalBare(arg);
536dba7b0efSSimon J. Gerraty 	return par->negateEvalBare ? !res : res;
537e2eeea75SSimon J. Gerraty }
5382c3632d1SSimon J. Gerraty 
53906b9b3e0SSimon J. Gerraty /*
54006b9b3e0SSimon J. Gerraty  * Evaluate a "comparison without operator", such as in ".if ${VAR}" or
54106b9b3e0SSimon J. Gerraty  * ".if 0".
54206b9b3e0SSimon J. Gerraty  */
543b0c40a00SSimon J. Gerraty static bool
544b0c40a00SSimon J. Gerraty EvalNotEmpty(CondParser *par, const char *value, bool quoted)
5453955d011SMarcel Moolenaar {
546e2eeea75SSimon J. Gerraty 	double num;
547956e45f6SSimon J. Gerraty 
548e2eeea75SSimon J. Gerraty 	/* For .ifxxx "...", check for non-empty string. */
549e2eeea75SSimon J. Gerraty 	if (quoted)
550e2eeea75SSimon J. Gerraty 		return value[0] != '\0';
551956e45f6SSimon J. Gerraty 
552e2eeea75SSimon J. Gerraty 	/* For .ifxxx <number>, compare against zero */
553e2eeea75SSimon J. Gerraty 	if (TryParseNumber(value, &num))
554e2eeea75SSimon J. Gerraty 		return num != 0.0;
555956e45f6SSimon J. Gerraty 
556e2eeea75SSimon J. Gerraty 	/* For .if ${...}, check for non-empty string.  This is different from
557e2eeea75SSimon J. Gerraty 	 * the evaluation function from that .if variant, which would test
558e2eeea75SSimon J. Gerraty 	 * whether a variable of the given name were defined. */
559*12904384SSimon J. Gerraty 	/*
560*12904384SSimon J. Gerraty 	 * XXX: Whitespace should count as empty, just as in
561*12904384SSimon J. Gerraty 	 * CondParser_FuncCallEmpty.
562*12904384SSimon J. Gerraty 	 */
563dba7b0efSSimon J. Gerraty 	if (par->plain)
564e2eeea75SSimon J. Gerraty 		return value[0] != '\0';
565956e45f6SSimon J. Gerraty 
566*12904384SSimon J. Gerraty 	return EvalBare(par, value);
567956e45f6SSimon J. Gerraty }
568956e45f6SSimon J. Gerraty 
569956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */
570b0c40a00SSimon J. Gerraty static bool
571dba7b0efSSimon J. Gerraty EvalCompareNum(double lhs, ComparisonOp op, double rhs)
572956e45f6SSimon J. Gerraty {
573dba7b0efSSimon J. Gerraty 	DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, opname[op]);
574956e45f6SSimon J. Gerraty 
575dba7b0efSSimon J. Gerraty 	switch (op) {
576dba7b0efSSimon J. Gerraty 	case LT:
577dba7b0efSSimon J. Gerraty 		return lhs < rhs;
578dba7b0efSSimon J. Gerraty 	case LE:
579dba7b0efSSimon J. Gerraty 		return lhs <= rhs;
580dba7b0efSSimon J. Gerraty 	case GT:
581dba7b0efSSimon J. Gerraty 		return lhs > rhs;
582dba7b0efSSimon J. Gerraty 	case GE:
583dba7b0efSSimon J. Gerraty 		return lhs >= rhs;
584dba7b0efSSimon J. Gerraty 	case NE:
585dba7b0efSSimon J. Gerraty 		return lhs != rhs;
586dba7b0efSSimon J. Gerraty 	default:
587dba7b0efSSimon J. Gerraty 		return lhs == rhs;
588956e45f6SSimon J. Gerraty 	}
589956e45f6SSimon J. Gerraty }
590956e45f6SSimon J. Gerraty 
591956e45f6SSimon J. Gerraty static Token
592dba7b0efSSimon J. Gerraty EvalCompareStr(CondParser *par, const char *lhs,
593dba7b0efSSimon J. Gerraty 	       ComparisonOp op, const char *rhs)
594956e45f6SSimon J. Gerraty {
595dba7b0efSSimon J. Gerraty 	if (op != EQ && op != NE) {
596dba7b0efSSimon J. Gerraty 		Parse_Error(PARSE_FATAL,
597dba7b0efSSimon J. Gerraty 		    "String comparison operator must be either == or !=");
598b0c40a00SSimon J. Gerraty 		par->printedError = true;
599956e45f6SSimon J. Gerraty 		return TOK_ERROR;
600956e45f6SSimon J. Gerraty 	}
601956e45f6SSimon J. Gerraty 
602dba7b0efSSimon J. Gerraty 	DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
603dba7b0efSSimon J. Gerraty 	    lhs, rhs, opname[op]);
604dba7b0efSSimon J. Gerraty 	return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0));
605956e45f6SSimon J. Gerraty }
606956e45f6SSimon J. Gerraty 
607956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */
608956e45f6SSimon J. Gerraty static Token
609b0c40a00SSimon J. Gerraty EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted,
610b0c40a00SSimon J. Gerraty 	    ComparisonOp op, const char *rhs, bool rhsQuoted)
611956e45f6SSimon J. Gerraty {
6123955d011SMarcel Moolenaar 	double left, right;
6133955d011SMarcel Moolenaar 
614956e45f6SSimon J. Gerraty 	if (!rhsQuoted && !lhsQuoted)
615956e45f6SSimon J. Gerraty 		if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right))
616dba7b0efSSimon J. Gerraty 			return ToToken(EvalCompareNum(left, op, right));
617956e45f6SSimon J. Gerraty 
618dba7b0efSSimon J. Gerraty 	return EvalCompareStr(par, lhs, op, rhs);
619dba7b0efSSimon J. Gerraty }
620dba7b0efSSimon J. Gerraty 
621b0c40a00SSimon J. Gerraty static bool
622dba7b0efSSimon J. Gerraty CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op)
623dba7b0efSSimon J. Gerraty {
624dba7b0efSSimon J. Gerraty 	const char *p = par->p;
625dba7b0efSSimon J. Gerraty 
626dba7b0efSSimon J. Gerraty 	if (p[0] == '<' && p[1] == '=') {
627dba7b0efSSimon J. Gerraty 		*out_op = LE;
628dba7b0efSSimon J. Gerraty 		goto length_2;
629dba7b0efSSimon J. Gerraty 	} else if (p[0] == '<') {
630dba7b0efSSimon J. Gerraty 		*out_op = LT;
631dba7b0efSSimon J. Gerraty 		goto length_1;
632dba7b0efSSimon J. Gerraty 	} else if (p[0] == '>' && p[1] == '=') {
633dba7b0efSSimon J. Gerraty 		*out_op = GE;
634dba7b0efSSimon J. Gerraty 		goto length_2;
635dba7b0efSSimon J. Gerraty 	} else if (p[0] == '>') {
636dba7b0efSSimon J. Gerraty 		*out_op = GT;
637dba7b0efSSimon J. Gerraty 		goto length_1;
638dba7b0efSSimon J. Gerraty 	} else if (p[0] == '=' && p[1] == '=') {
639dba7b0efSSimon J. Gerraty 		*out_op = EQ;
640dba7b0efSSimon J. Gerraty 		goto length_2;
641dba7b0efSSimon J. Gerraty 	} else if (p[0] == '!' && p[1] == '=') {
642dba7b0efSSimon J. Gerraty 		*out_op = NE;
643dba7b0efSSimon J. Gerraty 		goto length_2;
644dba7b0efSSimon J. Gerraty 	}
645b0c40a00SSimon J. Gerraty 	return false;
646dba7b0efSSimon J. Gerraty 
647dba7b0efSSimon J. Gerraty length_2:
648dba7b0efSSimon J. Gerraty 	par->p = p + 2;
649b0c40a00SSimon J. Gerraty 	return true;
650dba7b0efSSimon J. Gerraty length_1:
651dba7b0efSSimon J. Gerraty 	par->p = p + 1;
652b0c40a00SSimon J. Gerraty 	return true;
653956e45f6SSimon J. Gerraty }
654956e45f6SSimon J. Gerraty 
65506b9b3e0SSimon J. Gerraty /*
65606b9b3e0SSimon J. Gerraty  * Parse a comparison condition such as:
657956e45f6SSimon J. Gerraty  *
658956e45f6SSimon J. Gerraty  *	0
659956e45f6SSimon J. Gerraty  *	${VAR:Mpattern}
660956e45f6SSimon J. Gerraty  *	${VAR} == value
661956e45f6SSimon J. Gerraty  *	${VAR:U0} < 12345
662956e45f6SSimon J. Gerraty  */
663956e45f6SSimon J. Gerraty static Token
664b0c40a00SSimon J. Gerraty CondParser_Comparison(CondParser *par, bool doEval)
665956e45f6SSimon J. Gerraty {
666956e45f6SSimon J. Gerraty 	Token t = TOK_ERROR;
66706b9b3e0SSimon J. Gerraty 	FStr lhs, rhs;
668dba7b0efSSimon J. Gerraty 	ComparisonOp op;
669b0c40a00SSimon J. Gerraty 	bool lhsQuoted, rhsQuoted;
670956e45f6SSimon J. Gerraty 
671*12904384SSimon J. Gerraty 	CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhs, &lhsQuoted);
67206b9b3e0SSimon J. Gerraty 	if (lhs.str == NULL)
673e2eeea75SSimon J. Gerraty 		goto done_lhs;
6743955d011SMarcel Moolenaar 
675956e45f6SSimon J. Gerraty 	CondParser_SkipWhitespace(par);
6763955d011SMarcel Moolenaar 
677dba7b0efSSimon J. Gerraty 	if (!CondParser_ComparisonOp(par, &op)) {
678e2eeea75SSimon J. Gerraty 		/* Unknown operator, compare against an empty string or 0. */
67906b9b3e0SSimon J. Gerraty 		t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted));
680e2eeea75SSimon J. Gerraty 		goto done_lhs;
6813955d011SMarcel Moolenaar 	}
6823955d011SMarcel Moolenaar 
683956e45f6SSimon J. Gerraty 	CondParser_SkipWhitespace(par);
6843955d011SMarcel Moolenaar 
685956e45f6SSimon J. Gerraty 	if (par->p[0] == '\0') {
686dba7b0efSSimon J. Gerraty 		Parse_Error(PARSE_FATAL,
687*12904384SSimon J. Gerraty 		    "Missing right-hand side of operator '%s'", opname[op]);
688b0c40a00SSimon J. Gerraty 		par->printedError = true;
689e2eeea75SSimon J. Gerraty 		goto done_lhs;
6903955d011SMarcel Moolenaar 	}
6913955d011SMarcel Moolenaar 
692*12904384SSimon J. Gerraty 	CondParser_Leaf(par, doEval, true, &rhs, &rhsQuoted);
69306b9b3e0SSimon J. Gerraty 	if (rhs.str == NULL)
694e2eeea75SSimon J. Gerraty 		goto done_rhs;
6953955d011SMarcel Moolenaar 
6963841c287SSimon J. Gerraty 	if (!doEval) {
6973841c287SSimon J. Gerraty 		t = TOK_FALSE;
698e2eeea75SSimon J. Gerraty 		goto done_rhs;
6993841c287SSimon J. Gerraty 	}
7003841c287SSimon J. Gerraty 
701dba7b0efSSimon J. Gerraty 	t = EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted);
7023955d011SMarcel Moolenaar 
703e2eeea75SSimon J. Gerraty done_rhs:
70406b9b3e0SSimon J. Gerraty 	FStr_Done(&rhs);
705e2eeea75SSimon J. Gerraty done_lhs:
70606b9b3e0SSimon J. Gerraty 	FStr_Done(&lhs);
7073955d011SMarcel Moolenaar 	return t;
7083955d011SMarcel Moolenaar }
7093955d011SMarcel Moolenaar 
71006b9b3e0SSimon J. Gerraty /*
71106b9b3e0SSimon J. Gerraty  * The argument to empty() is a variable name, optionally followed by
71206b9b3e0SSimon J. Gerraty  * variable modifiers.
71306b9b3e0SSimon J. Gerraty  */
714*12904384SSimon J. Gerraty static bool
715*12904384SSimon J. Gerraty CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
7163955d011SMarcel Moolenaar {
717*12904384SSimon J. Gerraty 	const char *cp = par->p;
718*12904384SSimon J. Gerraty 	Token tok;
71906b9b3e0SSimon J. Gerraty 	FStr val;
7203955d011SMarcel Moolenaar 
721*12904384SSimon J. Gerraty 	if (!is_token(cp, "empty", 5))
722*12904384SSimon J. Gerraty 		return false;
723*12904384SSimon J. Gerraty 	cp += 5;
7243955d011SMarcel Moolenaar 
725*12904384SSimon J. Gerraty 	cpp_skip_whitespace(&cp);
726*12904384SSimon J. Gerraty 	if (*cp != '(')
727*12904384SSimon J. Gerraty 		return false;
728*12904384SSimon J. Gerraty 
729*12904384SSimon J. Gerraty 	cp--;			/* Make cp[1] point to the '('. */
730*12904384SSimon J. Gerraty 	(void)Var_Parse(&cp, SCOPE_CMDLINE,
731b0c40a00SSimon J. Gerraty 	    doEval ? VARE_WANTRES : VARE_PARSE_ONLY, &val);
732956e45f6SSimon J. Gerraty 	/* TODO: handle errors */
7333955d011SMarcel Moolenaar 
734*12904384SSimon J. Gerraty 	if (val.str == var_Error)
735*12904384SSimon J. Gerraty 		tok = TOK_ERROR;
736*12904384SSimon J. Gerraty 	else {
73706b9b3e0SSimon J. Gerraty 		cpp_skip_whitespace(&val.str);
738*12904384SSimon J. Gerraty 		tok = val.str[0] != '\0' && doEval ? TOK_FALSE : TOK_TRUE;
7393955d011SMarcel Moolenaar 	}
7403955d011SMarcel Moolenaar 
741*12904384SSimon J. Gerraty 	FStr_Done(&val);
742*12904384SSimon J. Gerraty 	*out_token = tok;
743*12904384SSimon J. Gerraty 	par->p = cp;
744*12904384SSimon J. Gerraty 	return true;
7453955d011SMarcel Moolenaar }
7463955d011SMarcel Moolenaar 
747b0c40a00SSimon J. Gerraty /* Parse a function call expression, such as 'defined(${file})'. */
748b0c40a00SSimon J. Gerraty static bool
749b0c40a00SSimon J. Gerraty CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token)
7503955d011SMarcel Moolenaar {
7513955d011SMarcel Moolenaar 	static const struct fn_def {
752*12904384SSimon J. Gerraty 		const char fn_name[9];
753*12904384SSimon J. Gerraty 		unsigned char fn_name_len;
754*12904384SSimon J. Gerraty 		bool (*fn_eval)(const char *);
755e2eeea75SSimon J. Gerraty 	} fns[] = {
756*12904384SSimon J. Gerraty 		{ "defined",  7, FuncDefined },
757*12904384SSimon J. Gerraty 		{ "make",     4, FuncMake },
758*12904384SSimon J. Gerraty 		{ "exists",   6, FuncExists },
759*12904384SSimon J. Gerraty 		{ "target",   6, FuncTarget },
760*12904384SSimon J. Gerraty 		{ "commands", 8, FuncCommands }
7613955d011SMarcel Moolenaar 	};
762e2eeea75SSimon J. Gerraty 	const struct fn_def *fn;
763e2eeea75SSimon J. Gerraty 	char *arg = NULL;
764e2eeea75SSimon J. Gerraty 	size_t arglen;
765e2eeea75SSimon J. Gerraty 	const char *cp = par->p;
766*12904384SSimon J. Gerraty 	const struct fn_def *last_fn = fns + sizeof fns / sizeof fns[0] - 1;
767e2eeea75SSimon J. Gerraty 
768*12904384SSimon J. Gerraty 	for (fn = fns; !is_token(cp, fn->fn_name, fn->fn_name_len); fn++)
769*12904384SSimon J. Gerraty 		if (fn == last_fn)
770*12904384SSimon J. Gerraty 			return false;
771e2eeea75SSimon J. Gerraty 
772e2eeea75SSimon J. Gerraty 	cp += fn->fn_name_len;
773e2eeea75SSimon J. Gerraty 	cpp_skip_whitespace(&cp);
774e2eeea75SSimon J. Gerraty 	if (*cp != '(')
775*12904384SSimon J. Gerraty 		return false;
776e2eeea75SSimon J. Gerraty 
777*12904384SSimon J. Gerraty 	arglen = ParseWord(par, &cp, doEval, fn->fn_name, &arg);
778*12904384SSimon J. Gerraty 	*out_token = ToToken(arglen != 0 && (!doEval || fn->fn_eval(arg)));
779e2eeea75SSimon J. Gerraty 
780e2eeea75SSimon J. Gerraty 	free(arg);
781e2eeea75SSimon J. Gerraty 	par->p = cp;
782b0c40a00SSimon J. Gerraty 	return true;
783e2eeea75SSimon J. Gerraty }
784e2eeea75SSimon J. Gerraty 
78506b9b3e0SSimon J. Gerraty /*
786*12904384SSimon J. Gerraty  * Parse a comparison that neither starts with '"' nor '$', such as the
787*12904384SSimon J. Gerraty  * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without
788b0c40a00SSimon J. Gerraty  * operator, which is a number, a variable expression or a string literal.
789*12904384SSimon J. Gerraty  *
790*12904384SSimon J. Gerraty  * TODO: Can this be merged into CondParser_Comparison?
79106b9b3e0SSimon J. Gerraty  */
792e2eeea75SSimon J. Gerraty static Token
793b0c40a00SSimon J. Gerraty CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
794e2eeea75SSimon J. Gerraty {
7953955d011SMarcel Moolenaar 	Token t;
7963955d011SMarcel Moolenaar 	char *arg = NULL;
79706b9b3e0SSimon J. Gerraty 	const char *cp;
7982c3632d1SSimon J. Gerraty 	const char *cp1;
7993955d011SMarcel Moolenaar 
8003955d011SMarcel Moolenaar 	/* Push anything numeric through the compare expression */
801956e45f6SSimon J. Gerraty 	cp = par->p;
802e2eeea75SSimon J. Gerraty 	if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+')
803956e45f6SSimon J. Gerraty 		return CondParser_Comparison(par, doEval);
8043955d011SMarcel Moolenaar 
8053955d011SMarcel Moolenaar 	/*
8063955d011SMarcel Moolenaar 	 * Most likely we have a naked token to apply the default function to.
8073955d011SMarcel Moolenaar 	 * However ".if a == b" gets here when the "a" is unquoted and doesn't
8083955d011SMarcel Moolenaar 	 * start with a '$'. This surprises people.
80906b9b3e0SSimon J. Gerraty 	 * If what follows the function argument is a '=' or '!' then the
81006b9b3e0SSimon J. Gerraty 	 * syntax would be invalid if we did "defined(a)" - so instead treat
81106b9b3e0SSimon J. Gerraty 	 * as an expression.
8123955d011SMarcel Moolenaar 	 */
813b0c40a00SSimon J. Gerraty 	/*
814b0c40a00SSimon J. Gerraty 	 * XXX: Is it possible to have a variable expression evaluated twice
815b0c40a00SSimon J. Gerraty 	 *  at this point?
816b0c40a00SSimon J. Gerraty 	 */
817*12904384SSimon J. Gerraty 	(void)ParseWord(par, &cp, doEval, NULL, &arg);
818956e45f6SSimon J. Gerraty 	cp1 = cp;
819956e45f6SSimon J. Gerraty 	cpp_skip_whitespace(&cp1);
820b0c40a00SSimon J. Gerraty 	if (*cp1 == '=' || *cp1 == '!' || *cp1 == '<' || *cp1 == '>')
821956e45f6SSimon J. Gerraty 		return CondParser_Comparison(par, doEval);
822956e45f6SSimon J. Gerraty 	par->p = cp;
8233955d011SMarcel Moolenaar 
8243955d011SMarcel Moolenaar 	/*
8253955d011SMarcel Moolenaar 	 * Evaluate the argument using the default function.
826956e45f6SSimon J. Gerraty 	 * This path always treats .if as .ifdef. To get here, the character
8273955d011SMarcel Moolenaar 	 * after .if must have been taken literally, so the argument cannot
8283955d011SMarcel Moolenaar 	 * be empty - even if it contained a variable expansion.
8293955d011SMarcel Moolenaar 	 */
830*12904384SSimon J. Gerraty 	t = ToToken(!doEval || EvalBare(par, arg));
8313955d011SMarcel Moolenaar 	free(arg);
8323955d011SMarcel Moolenaar 	return t;
8333955d011SMarcel Moolenaar }
8343955d011SMarcel Moolenaar 
835956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */
8363955d011SMarcel Moolenaar static Token
837b0c40a00SSimon J. Gerraty CondParser_Token(CondParser *par, bool doEval)
8383955d011SMarcel Moolenaar {
8393955d011SMarcel Moolenaar 	Token t;
8403955d011SMarcel Moolenaar 
841956e45f6SSimon J. Gerraty 	t = par->curr;
8423955d011SMarcel Moolenaar 	if (t != TOK_NONE) {
843956e45f6SSimon J. Gerraty 		par->curr = TOK_NONE;
8443955d011SMarcel Moolenaar 		return t;
8453955d011SMarcel Moolenaar 	}
8463955d011SMarcel Moolenaar 
847e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&par->p);
8483955d011SMarcel Moolenaar 
849956e45f6SSimon J. Gerraty 	switch (par->p[0]) {
8503955d011SMarcel Moolenaar 
8513955d011SMarcel Moolenaar 	case '(':
852956e45f6SSimon J. Gerraty 		par->p++;
8533955d011SMarcel Moolenaar 		return TOK_LPAREN;
8543955d011SMarcel Moolenaar 
8553955d011SMarcel Moolenaar 	case ')':
856956e45f6SSimon J. Gerraty 		par->p++;
8573955d011SMarcel Moolenaar 		return TOK_RPAREN;
8583955d011SMarcel Moolenaar 
8593955d011SMarcel Moolenaar 	case '|':
860956e45f6SSimon J. Gerraty 		par->p++;
861e2eeea75SSimon J. Gerraty 		if (par->p[0] == '|')
862956e45f6SSimon J. Gerraty 			par->p++;
86306b9b3e0SSimon J. Gerraty 		else if (opts.strict) {
864e2eeea75SSimon J. Gerraty 			Parse_Error(PARSE_FATAL, "Unknown operator '|'");
865b0c40a00SSimon J. Gerraty 			par->printedError = true;
866e2eeea75SSimon J. Gerraty 			return TOK_ERROR;
8673955d011SMarcel Moolenaar 		}
8683955d011SMarcel Moolenaar 		return TOK_OR;
8693955d011SMarcel Moolenaar 
8703955d011SMarcel Moolenaar 	case '&':
871956e45f6SSimon J. Gerraty 		par->p++;
872e2eeea75SSimon J. Gerraty 		if (par->p[0] == '&')
873956e45f6SSimon J. Gerraty 			par->p++;
87406b9b3e0SSimon J. Gerraty 		else if (opts.strict) {
875e2eeea75SSimon J. Gerraty 			Parse_Error(PARSE_FATAL, "Unknown operator '&'");
876b0c40a00SSimon J. Gerraty 			par->printedError = true;
877e2eeea75SSimon J. Gerraty 			return TOK_ERROR;
8783955d011SMarcel Moolenaar 		}
8793955d011SMarcel Moolenaar 		return TOK_AND;
8803955d011SMarcel Moolenaar 
8813955d011SMarcel Moolenaar 	case '!':
882956e45f6SSimon J. Gerraty 		par->p++;
8833955d011SMarcel Moolenaar 		return TOK_NOT;
8843955d011SMarcel Moolenaar 
885e2eeea75SSimon J. Gerraty 	case '#':		/* XXX: see unit-tests/cond-token-plain.mk */
886e2eeea75SSimon J. Gerraty 	case '\n':		/* XXX: why should this end the condition? */
887e2eeea75SSimon J. Gerraty 		/* Probably obsolete now, from 1993-03-21. */
8883955d011SMarcel Moolenaar 	case '\0':
8893955d011SMarcel Moolenaar 		return TOK_EOF;
8903955d011SMarcel Moolenaar 
8913955d011SMarcel Moolenaar 	case '"':
8923955d011SMarcel Moolenaar 	case '$':
893956e45f6SSimon J. Gerraty 		return CondParser_Comparison(par, doEval);
8943955d011SMarcel Moolenaar 
8953955d011SMarcel Moolenaar 	default:
896*12904384SSimon J. Gerraty 		if (CondParser_FuncCallEmpty(par, doEval, &t))
897*12904384SSimon J. Gerraty 			return t;
898b0c40a00SSimon J. Gerraty 		if (CondParser_FuncCall(par, doEval, &t))
899b0c40a00SSimon J. Gerraty 			return t;
900b0c40a00SSimon J. Gerraty 		return CondParser_ComparisonOrLeaf(par, doEval);
9013955d011SMarcel Moolenaar 	}
9023955d011SMarcel Moolenaar }
9033955d011SMarcel Moolenaar 
904*12904384SSimon J. Gerraty /* Skip the next token if it equals t. */
905*12904384SSimon J. Gerraty static bool
906*12904384SSimon J. Gerraty CondParser_Skip(CondParser *par, Token t)
907*12904384SSimon J. Gerraty {
908*12904384SSimon J. Gerraty 	Token actual;
909*12904384SSimon J. Gerraty 
910*12904384SSimon J. Gerraty 	actual = CondParser_Token(par, false);
911*12904384SSimon J. Gerraty 	if (actual == t)
912*12904384SSimon J. Gerraty 		return true;
913*12904384SSimon J. Gerraty 
914*12904384SSimon J. Gerraty 	assert(par->curr == TOK_NONE);
915*12904384SSimon J. Gerraty 	assert(actual != TOK_NONE);
916*12904384SSimon J. Gerraty 	par->curr = actual;
917*12904384SSimon J. Gerraty 	return false;
918*12904384SSimon J. Gerraty }
919*12904384SSimon J. Gerraty 
92006b9b3e0SSimon J. Gerraty /*
921dba7b0efSSimon J. Gerraty  * Term -> '(' Or ')'
922dba7b0efSSimon J. Gerraty  * Term -> '!' Term
923dba7b0efSSimon J. Gerraty  * Term -> Leaf Operator Leaf
924dba7b0efSSimon J. Gerraty  * Term -> Leaf
9253955d011SMarcel Moolenaar  */
926dba7b0efSSimon J. Gerraty static CondResult
927b0c40a00SSimon J. Gerraty CondParser_Term(CondParser *par, bool doEval)
9283955d011SMarcel Moolenaar {
929dba7b0efSSimon J. Gerraty 	CondResult res;
9303955d011SMarcel Moolenaar 	Token t;
9313955d011SMarcel Moolenaar 
932956e45f6SSimon J. Gerraty 	t = CondParser_Token(par, doEval);
933dba7b0efSSimon J. Gerraty 	if (t == TOK_TRUE)
934dba7b0efSSimon J. Gerraty 		return CR_TRUE;
935dba7b0efSSimon J. Gerraty 	if (t == TOK_FALSE)
936dba7b0efSSimon J. Gerraty 		return CR_FALSE;
9373955d011SMarcel Moolenaar 
938dba7b0efSSimon J. Gerraty 	if (t == TOK_LPAREN) {
939dba7b0efSSimon J. Gerraty 		res = CondParser_Or(par, doEval);
940dba7b0efSSimon J. Gerraty 		if (res == CR_ERROR)
941dba7b0efSSimon J. Gerraty 			return CR_ERROR;
942dba7b0efSSimon J. Gerraty 		if (CondParser_Token(par, doEval) != TOK_RPAREN)
943dba7b0efSSimon J. Gerraty 			return CR_ERROR;
944dba7b0efSSimon J. Gerraty 		return res;
9453955d011SMarcel Moolenaar 	}
946dba7b0efSSimon J. Gerraty 
947dba7b0efSSimon J. Gerraty 	if (t == TOK_NOT) {
948dba7b0efSSimon J. Gerraty 		res = CondParser_Term(par, doEval);
949dba7b0efSSimon J. Gerraty 		if (res == CR_TRUE)
950dba7b0efSSimon J. Gerraty 			res = CR_FALSE;
951dba7b0efSSimon J. Gerraty 		else if (res == CR_FALSE)
952dba7b0efSSimon J. Gerraty 			res = CR_TRUE;
953dba7b0efSSimon J. Gerraty 		return res;
9543955d011SMarcel Moolenaar 	}
955dba7b0efSSimon J. Gerraty 
956dba7b0efSSimon J. Gerraty 	return CR_ERROR;
9573955d011SMarcel Moolenaar }
9582c3632d1SSimon J. Gerraty 
95906b9b3e0SSimon J. Gerraty /*
960*12904384SSimon J. Gerraty  * And -> Term ('&&' Term)*
9613955d011SMarcel Moolenaar  */
962dba7b0efSSimon J. Gerraty static CondResult
963b0c40a00SSimon J. Gerraty CondParser_And(CondParser *par, bool doEval)
9643955d011SMarcel Moolenaar {
965*12904384SSimon J. Gerraty 	CondResult res, rhs;
9663955d011SMarcel Moolenaar 
967*12904384SSimon J. Gerraty 	res = CR_TRUE;
968*12904384SSimon J. Gerraty 	do {
969*12904384SSimon J. Gerraty 		if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR)
970dba7b0efSSimon J. Gerraty 			return CR_ERROR;
971*12904384SSimon J. Gerraty 		if (rhs == CR_FALSE) {
972*12904384SSimon J. Gerraty 			res = CR_FALSE;
973*12904384SSimon J. Gerraty 			doEval = false;
9743955d011SMarcel Moolenaar 		}
975*12904384SSimon J. Gerraty 	} while (CondParser_Skip(par, TOK_AND));
976dba7b0efSSimon J. Gerraty 
977dba7b0efSSimon J. Gerraty 	return res;
9783955d011SMarcel Moolenaar }
9792c3632d1SSimon J. Gerraty 
98006b9b3e0SSimon J. Gerraty /*
981*12904384SSimon J. Gerraty  * Or -> And ('||' And)*
9823955d011SMarcel Moolenaar  */
983dba7b0efSSimon J. Gerraty static CondResult
984b0c40a00SSimon J. Gerraty CondParser_Or(CondParser *par, bool doEval)
9853955d011SMarcel Moolenaar {
986*12904384SSimon J. Gerraty 	CondResult res, rhs;
9873955d011SMarcel Moolenaar 
988*12904384SSimon J. Gerraty 	res = CR_FALSE;
989*12904384SSimon J. Gerraty 	do {
990*12904384SSimon J. Gerraty 		if ((rhs = CondParser_And(par, doEval)) == CR_ERROR)
991dba7b0efSSimon J. Gerraty 			return CR_ERROR;
992*12904384SSimon J. Gerraty 		if (rhs == CR_TRUE) {
993*12904384SSimon J. Gerraty 			res = CR_TRUE;
994*12904384SSimon J. Gerraty 			doEval = false;
9953955d011SMarcel Moolenaar 		}
996*12904384SSimon J. Gerraty 	} while (CondParser_Skip(par, TOK_OR));
997dba7b0efSSimon J. Gerraty 
998dba7b0efSSimon J. Gerraty 	return res;
9993955d011SMarcel Moolenaar }
10003955d011SMarcel Moolenaar 
10012c3632d1SSimon J. Gerraty static CondEvalResult
1002b0c40a00SSimon J. Gerraty CondParser_Eval(CondParser *par, bool *out_value)
10032c3632d1SSimon J. Gerraty {
1004dba7b0efSSimon J. Gerraty 	CondResult res;
10052c3632d1SSimon J. Gerraty 
1006956e45f6SSimon J. Gerraty 	DEBUG1(COND, "CondParser_Eval: %s\n", par->p);
10072c3632d1SSimon J. Gerraty 
1008b0c40a00SSimon J. Gerraty 	res = CondParser_Or(par, true);
1009dba7b0efSSimon J. Gerraty 	if (res == CR_ERROR)
10102c3632d1SSimon J. Gerraty 		return COND_INVALID;
1011956e45f6SSimon J. Gerraty 
1012b0c40a00SSimon J. Gerraty 	if (CondParser_Token(par, false) != TOK_EOF)
1013956e45f6SSimon J. Gerraty 		return COND_INVALID;
1014956e45f6SSimon J. Gerraty 
1015dba7b0efSSimon J. Gerraty 	*out_value = res == CR_TRUE;
1016956e45f6SSimon J. Gerraty 	return COND_PARSE;
10172c3632d1SSimon J. Gerraty }
10182c3632d1SSimon J. Gerraty 
101906b9b3e0SSimon J. Gerraty /*
102006b9b3e0SSimon J. Gerraty  * Evaluate the condition, including any side effects from the variable
1021956e45f6SSimon J. Gerraty  * expressions in the condition. The condition consists of &&, ||, !,
1022956e45f6SSimon J. Gerraty  * function(arg), comparisons and parenthetical groupings thereof.
10233955d011SMarcel Moolenaar  *
10243955d011SMarcel Moolenaar  * Results:
10253955d011SMarcel Moolenaar  *	COND_PARSE	if the condition was valid grammatically
10263955d011SMarcel Moolenaar  *	COND_INVALID	if not a valid conditional.
10273955d011SMarcel Moolenaar  *
1028*12904384SSimon J. Gerraty  *	*out_value	is set to the boolean value of the condition
10293955d011SMarcel Moolenaar  */
1030956e45f6SSimon J. Gerraty static CondEvalResult
1031b0c40a00SSimon J. Gerraty CondEvalExpression(const char *cond, bool *out_value, bool plain,
1032*12904384SSimon J. Gerraty 		   bool (*evalBare)(const char *), bool negate,
1033*12904384SSimon J. Gerraty 		   bool eprint, bool leftUnquotedOK)
10343955d011SMarcel Moolenaar {
1035956e45f6SSimon J. Gerraty 	CondParser par;
1036e2eeea75SSimon J. Gerraty 	CondEvalResult rval;
10373955d011SMarcel Moolenaar 
1038e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&cond);
10393955d011SMarcel Moolenaar 
1040dba7b0efSSimon J. Gerraty 	par.plain = plain;
1041dba7b0efSSimon J. Gerraty 	par.evalBare = evalBare;
1042dba7b0efSSimon J. Gerraty 	par.negateEvalBare = negate;
1043*12904384SSimon J. Gerraty 	par.leftUnquotedOK = leftUnquotedOK;
1044956e45f6SSimon J. Gerraty 	par.p = cond;
1045956e45f6SSimon J. Gerraty 	par.curr = TOK_NONE;
1046b0c40a00SSimon J. Gerraty 	par.printedError = false;
10473955d011SMarcel Moolenaar 
1048dba7b0efSSimon J. Gerraty 	rval = CondParser_Eval(&par, out_value);
10493955d011SMarcel Moolenaar 
1050956e45f6SSimon J. Gerraty 	if (rval == COND_INVALID && eprint && !par.printedError)
1051956e45f6SSimon J. Gerraty 		Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond);
10523955d011SMarcel Moolenaar 
10533955d011SMarcel Moolenaar 	return rval;
10543955d011SMarcel Moolenaar }
10553955d011SMarcel Moolenaar 
105606b9b3e0SSimon J. Gerraty /*
105706b9b3e0SSimon J. Gerraty  * Evaluate a condition in a :? modifier, such as
105806b9b3e0SSimon J. Gerraty  * ${"${VAR}" == value:?yes:no}.
105906b9b3e0SSimon J. Gerraty  */
1060956e45f6SSimon J. Gerraty CondEvalResult
1061b0c40a00SSimon J. Gerraty Cond_EvalCondition(const char *cond, bool *out_value)
1062956e45f6SSimon J. Gerraty {
1063b0c40a00SSimon J. Gerraty 	return CondEvalExpression(cond, out_value, true,
1064*12904384SSimon J. Gerraty 	    FuncDefined, false, false, true);
1065956e45f6SSimon J. Gerraty }
10663955d011SMarcel Moolenaar 
1067b0c40a00SSimon J. Gerraty static bool
106806b9b3e0SSimon J. Gerraty IsEndif(const char *p)
106906b9b3e0SSimon J. Gerraty {
107006b9b3e0SSimon J. Gerraty 	return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' &&
107106b9b3e0SSimon J. Gerraty 	       p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]);
107206b9b3e0SSimon J. Gerraty }
107306b9b3e0SSimon J. Gerraty 
1074b0c40a00SSimon J. Gerraty static bool
1075b0c40a00SSimon J. Gerraty DetermineKindOfConditional(const char **pp, bool *out_plain,
1076*12904384SSimon J. Gerraty 			   bool (**out_evalBare)(const char *),
1077b0c40a00SSimon J. Gerraty 			   bool *out_negate)
1078dba7b0efSSimon J. Gerraty {
1079dba7b0efSSimon J. Gerraty 	const char *p = *pp;
1080dba7b0efSSimon J. Gerraty 
1081dba7b0efSSimon J. Gerraty 	p += 2;
1082b0c40a00SSimon J. Gerraty 	*out_plain = false;
1083dba7b0efSSimon J. Gerraty 	*out_evalBare = FuncDefined;
1084b0c40a00SSimon J. Gerraty 	*out_negate = false;
1085dba7b0efSSimon J. Gerraty 	if (*p == 'n') {
1086dba7b0efSSimon J. Gerraty 		p++;
1087b0c40a00SSimon J. Gerraty 		*out_negate = true;
1088dba7b0efSSimon J. Gerraty 	}
1089dba7b0efSSimon J. Gerraty 	if (is_token(p, "def", 3)) {		/* .ifdef and .ifndef */
1090dba7b0efSSimon J. Gerraty 		p += 3;
1091dba7b0efSSimon J. Gerraty 	} else if (is_token(p, "make", 4)) {	/* .ifmake and .ifnmake */
1092dba7b0efSSimon J. Gerraty 		p += 4;
1093dba7b0efSSimon J. Gerraty 		*out_evalBare = FuncMake;
1094dba7b0efSSimon J. Gerraty 	} else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */
1095b0c40a00SSimon J. Gerraty 		*out_plain = true;
1096dba7b0efSSimon J. Gerraty 	} else {
1097dba7b0efSSimon J. Gerraty 		/*
1098dba7b0efSSimon J. Gerraty 		 * TODO: Add error message about unknown directive,
1099dba7b0efSSimon J. Gerraty 		 * since there is no other known directive that starts
1100dba7b0efSSimon J. Gerraty 		 * with 'el' or 'if'.
1101dba7b0efSSimon J. Gerraty 		 *
1102dba7b0efSSimon J. Gerraty 		 * Example: .elifx 123
1103dba7b0efSSimon J. Gerraty 		 */
1104b0c40a00SSimon J. Gerraty 		return false;
1105dba7b0efSSimon J. Gerraty 	}
1106dba7b0efSSimon J. Gerraty 
1107dba7b0efSSimon J. Gerraty 	*pp = p;
1108b0c40a00SSimon J. Gerraty 	return true;
1109dba7b0efSSimon J. Gerraty }
1110dba7b0efSSimon J. Gerraty 
111106b9b3e0SSimon J. Gerraty /*
111206b9b3e0SSimon J. Gerraty  * Evaluate the conditional directive in the line, which is one of:
11133955d011SMarcel Moolenaar  *
1114e2eeea75SSimon J. Gerraty  *	.if <cond>
1115e2eeea75SSimon J. Gerraty  *	.ifmake <cond>
1116e2eeea75SSimon J. Gerraty  *	.ifnmake <cond>
1117e2eeea75SSimon J. Gerraty  *	.ifdef <cond>
1118e2eeea75SSimon J. Gerraty  *	.ifndef <cond>
1119e2eeea75SSimon J. Gerraty  *	.elif <cond>
1120e2eeea75SSimon J. Gerraty  *	.elifmake <cond>
1121e2eeea75SSimon J. Gerraty  *	.elifnmake <cond>
1122e2eeea75SSimon J. Gerraty  *	.elifdef <cond>
1123e2eeea75SSimon J. Gerraty  *	.elifndef <cond>
1124e2eeea75SSimon J. Gerraty  *	.else
1125e2eeea75SSimon J. Gerraty  *	.endif
1126e2eeea75SSimon J. Gerraty  *
1127e2eeea75SSimon J. Gerraty  * In these directives, <cond> consists of &&, ||, !, function(arg),
1128e2eeea75SSimon J. Gerraty  * comparisons, expressions, bare words, numbers and strings, and
1129e2eeea75SSimon J. Gerraty  * parenthetical groupings thereof.
1130956e45f6SSimon J. Gerraty  *
1131956e45f6SSimon J. Gerraty  * Results:
1132e2eeea75SSimon J. Gerraty  *	COND_PARSE	to continue parsing the lines that follow the
1133b0c40a00SSimon J. Gerraty  *			conditional (when <cond> evaluates to true)
1134956e45f6SSimon J. Gerraty  *	COND_SKIP	to skip the lines after the conditional
1135b0c40a00SSimon J. Gerraty  *			(when <cond> evaluates to false, or when a previous
1136956e45f6SSimon J. Gerraty  *			branch has already been taken)
1137956e45f6SSimon J. Gerraty  *	COND_INVALID	if the conditional was not valid, either because of
1138956e45f6SSimon J. Gerraty  *			a syntax error or because some variable was undefined
1139956e45f6SSimon J. Gerraty  *			or because the condition could not be evaluated
11403955d011SMarcel Moolenaar  */
11412c3632d1SSimon J. Gerraty CondEvalResult
114206b9b3e0SSimon J. Gerraty Cond_EvalLine(const char *line)
11433955d011SMarcel Moolenaar {
1144e2eeea75SSimon J. Gerraty 	typedef enum IfState {
1145e2eeea75SSimon J. Gerraty 
1146b0c40a00SSimon J. Gerraty 		/* None of the previous <cond> evaluated to true. */
1147e2eeea75SSimon J. Gerraty 		IFS_INITIAL	= 0,
1148e2eeea75SSimon J. Gerraty 
1149b0c40a00SSimon J. Gerraty 		/* The previous <cond> evaluated to true.
1150e2eeea75SSimon J. Gerraty 		 * The lines following this condition are interpreted. */
1151e2eeea75SSimon J. Gerraty 		IFS_ACTIVE	= 1 << 0,
1152e2eeea75SSimon J. Gerraty 
1153e2eeea75SSimon J. Gerraty 		/* The previous directive was an '.else'. */
1154e2eeea75SSimon J. Gerraty 		IFS_SEEN_ELSE	= 1 << 1,
1155e2eeea75SSimon J. Gerraty 
1156b0c40a00SSimon J. Gerraty 		/* One of the previous <cond> evaluated to true. */
1157e2eeea75SSimon J. Gerraty 		IFS_WAS_ACTIVE	= 1 << 2
1158e2eeea75SSimon J. Gerraty 
1159e2eeea75SSimon J. Gerraty 	} IfState;
1160e2eeea75SSimon J. Gerraty 
1161e2eeea75SSimon J. Gerraty 	static enum IfState *cond_states = NULL;
1162e2eeea75SSimon J. Gerraty 	static unsigned int cond_states_cap = 128;
11633955d011SMarcel Moolenaar 
1164b0c40a00SSimon J. Gerraty 	bool plain;
1165*12904384SSimon J. Gerraty 	bool (*evalBare)(const char *);
1166b0c40a00SSimon J. Gerraty 	bool negate;
1167b0c40a00SSimon J. Gerraty 	bool isElif;
1168b0c40a00SSimon J. Gerraty 	bool value;
1169e2eeea75SSimon J. Gerraty 	IfState state;
1170e2eeea75SSimon J. Gerraty 	const char *p = line;
11713955d011SMarcel Moolenaar 
1172e2eeea75SSimon J. Gerraty 	if (cond_states == NULL) {
117306b9b3e0SSimon J. Gerraty 		cond_states = bmake_malloc(
117406b9b3e0SSimon J. Gerraty 		    cond_states_cap * sizeof *cond_states);
1175e2eeea75SSimon J. Gerraty 		cond_states[0] = IFS_ACTIVE;
117659a02420SSimon J. Gerraty 	}
11773955d011SMarcel Moolenaar 
1178e2eeea75SSimon J. Gerraty 	p++;			/* skip the leading '.' */
1179e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&p);
1180e2eeea75SSimon J. Gerraty 
118106b9b3e0SSimon J. Gerraty 	if (IsEndif(p)) {	/* It is an '.endif'. */
118206b9b3e0SSimon J. Gerraty 		if (p[5] != '\0') {
118306b9b3e0SSimon J. Gerraty 			Parse_Error(PARSE_FATAL,
1184*12904384SSimon J. Gerraty 			    "The .endif directive does not take arguments");
1185e2eeea75SSimon J. Gerraty 		}
1186e2eeea75SSimon J. Gerraty 
11873955d011SMarcel Moolenaar 		if (cond_depth == cond_min_depth) {
1188956e45f6SSimon J. Gerraty 			Parse_Error(PARSE_FATAL, "if-less endif");
11893955d011SMarcel Moolenaar 			return COND_PARSE;
11903955d011SMarcel Moolenaar 		}
1191e2eeea75SSimon J. Gerraty 
11923955d011SMarcel Moolenaar 		/* Return state for previous conditional */
11933955d011SMarcel Moolenaar 		cond_depth--;
1194e2eeea75SSimon J. Gerraty 		return cond_states[cond_depth] & IFS_ACTIVE
11952c3632d1SSimon J. Gerraty 		    ? COND_PARSE : COND_SKIP;
11963955d011SMarcel Moolenaar 	}
11973955d011SMarcel Moolenaar 
119806b9b3e0SSimon J. Gerraty 	/* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
119906b9b3e0SSimon J. Gerraty 	if (p[0] == 'e') {
120006b9b3e0SSimon J. Gerraty 		if (p[1] != 'l') {
120106b9b3e0SSimon J. Gerraty 			/*
120206b9b3e0SSimon J. Gerraty 			 * Unknown directive.  It might still be a
1203*12904384SSimon J. Gerraty 			 * transformation rule like '.err.txt',
120406b9b3e0SSimon J. Gerraty 			 * therefore no error message here.
120506b9b3e0SSimon J. Gerraty 			 */
120606b9b3e0SSimon J. Gerraty 			return COND_INVALID;
120706b9b3e0SSimon J. Gerraty 		}
120806b9b3e0SSimon J. Gerraty 
12093955d011SMarcel Moolenaar 		/* Quite likely this is 'else' or 'elif' */
1210e2eeea75SSimon J. Gerraty 		p += 2;
1211e2eeea75SSimon J. Gerraty 		if (is_token(p, "se", 2)) {	/* It is an 'else'. */
1212e2eeea75SSimon J. Gerraty 
121306b9b3e0SSimon J. Gerraty 			if (p[2] != '\0')
1214e2eeea75SSimon J. Gerraty 				Parse_Error(PARSE_FATAL,
121506b9b3e0SSimon J. Gerraty 				    "The .else directive "
1216*12904384SSimon J. Gerraty 				    "does not take arguments");
1217e2eeea75SSimon J. Gerraty 
12183955d011SMarcel Moolenaar 			if (cond_depth == cond_min_depth) {
1219956e45f6SSimon J. Gerraty 				Parse_Error(PARSE_FATAL, "if-less else");
12203955d011SMarcel Moolenaar 				return COND_PARSE;
12213955d011SMarcel Moolenaar 			}
12223955d011SMarcel Moolenaar 
1223e2eeea75SSimon J. Gerraty 			state = cond_states[cond_depth];
1224e2eeea75SSimon J. Gerraty 			if (state == IFS_INITIAL) {
1225e2eeea75SSimon J. Gerraty 				state = IFS_ACTIVE | IFS_SEEN_ELSE;
1226e2eeea75SSimon J. Gerraty 			} else {
1227e2eeea75SSimon J. Gerraty 				if (state & IFS_SEEN_ELSE)
122806b9b3e0SSimon J. Gerraty 					Parse_Error(PARSE_WARNING,
122906b9b3e0SSimon J. Gerraty 						    "extra else");
1230e2eeea75SSimon J. Gerraty 				state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
12313955d011SMarcel Moolenaar 			}
1232e2eeea75SSimon J. Gerraty 			cond_states[cond_depth] = state;
1233e2eeea75SSimon J. Gerraty 
1234e2eeea75SSimon J. Gerraty 			return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP;
12353955d011SMarcel Moolenaar 		}
12363955d011SMarcel Moolenaar 		/* Assume for now it is an elif */
1237b0c40a00SSimon J. Gerraty 		isElif = true;
12383955d011SMarcel Moolenaar 	} else
1239b0c40a00SSimon J. Gerraty 		isElif = false;
12403955d011SMarcel Moolenaar 
1241e2eeea75SSimon J. Gerraty 	if (p[0] != 'i' || p[1] != 'f') {
124206b9b3e0SSimon J. Gerraty 		/*
124306b9b3e0SSimon J. Gerraty 		 * Unknown directive.  It might still be a transformation rule
124406b9b3e0SSimon J. Gerraty 		 * like '.elisp.scm', therefore no error message here.
124506b9b3e0SSimon J. Gerraty 		 */
1246e2eeea75SSimon J. Gerraty 		return COND_INVALID;	/* Not an ifxxx or elifxxx line */
1247e2eeea75SSimon J. Gerraty 	}
12483955d011SMarcel Moolenaar 
1249dba7b0efSSimon J. Gerraty 	if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate))
12503955d011SMarcel Moolenaar 		return COND_INVALID;
12513955d011SMarcel Moolenaar 
12523955d011SMarcel Moolenaar 	if (isElif) {
12533955d011SMarcel Moolenaar 		if (cond_depth == cond_min_depth) {
1254956e45f6SSimon J. Gerraty 			Parse_Error(PARSE_FATAL, "if-less elif");
12553955d011SMarcel Moolenaar 			return COND_PARSE;
12563955d011SMarcel Moolenaar 		}
1257e2eeea75SSimon J. Gerraty 		state = cond_states[cond_depth];
1258e2eeea75SSimon J. Gerraty 		if (state & IFS_SEEN_ELSE) {
12593955d011SMarcel Moolenaar 			Parse_Error(PARSE_WARNING, "extra elif");
126006b9b3e0SSimon J. Gerraty 			cond_states[cond_depth] =
126106b9b3e0SSimon J. Gerraty 			    IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
12623955d011SMarcel Moolenaar 			return COND_SKIP;
12633955d011SMarcel Moolenaar 		}
1264e2eeea75SSimon J. Gerraty 		if (state != IFS_INITIAL) {
1265e2eeea75SSimon J. Gerraty 			cond_states[cond_depth] = IFS_WAS_ACTIVE;
12663955d011SMarcel Moolenaar 			return COND_SKIP;
12673955d011SMarcel Moolenaar 		}
12683955d011SMarcel Moolenaar 	} else {
12693955d011SMarcel Moolenaar 		/* Normal .if */
1270e2eeea75SSimon J. Gerraty 		if (cond_depth + 1 >= cond_states_cap) {
127159a02420SSimon J. Gerraty 			/*
127259a02420SSimon J. Gerraty 			 * This is rare, but not impossible.
127359a02420SSimon J. Gerraty 			 * In meta mode, dirdeps.mk (only runs at level 0)
127459a02420SSimon J. Gerraty 			 * can need more than the default.
127559a02420SSimon J. Gerraty 			 */
1276e2eeea75SSimon J. Gerraty 			cond_states_cap += 32;
1277e2eeea75SSimon J. Gerraty 			cond_states = bmake_realloc(cond_states,
127806b9b3e0SSimon J. Gerraty 						    cond_states_cap *
127906b9b3e0SSimon J. Gerraty 						    sizeof *cond_states);
12803955d011SMarcel Moolenaar 		}
1281e2eeea75SSimon J. Gerraty 		state = cond_states[cond_depth];
12823955d011SMarcel Moolenaar 		cond_depth++;
1283e2eeea75SSimon J. Gerraty 		if (!(state & IFS_ACTIVE)) {
128406b9b3e0SSimon J. Gerraty 			/*
128506b9b3e0SSimon J. Gerraty 			 * If we aren't parsing the data,
128606b9b3e0SSimon J. Gerraty 			 * treat as always false.
128706b9b3e0SSimon J. Gerraty 			 */
1288e2eeea75SSimon J. Gerraty 			cond_states[cond_depth] = IFS_WAS_ACTIVE;
12893955d011SMarcel Moolenaar 			return COND_SKIP;
12903955d011SMarcel Moolenaar 		}
12913955d011SMarcel Moolenaar 	}
12923955d011SMarcel Moolenaar 
1293956e45f6SSimon J. Gerraty 	/* And evaluate the conditional expression */
1294dba7b0efSSimon J. Gerraty 	if (CondEvalExpression(p, &value, plain, evalBare, negate,
1295*12904384SSimon J. Gerraty 	    true, false) == COND_INVALID) {
12963955d011SMarcel Moolenaar 		/* Syntax error in conditional, error message already output. */
12973955d011SMarcel Moolenaar 		/* Skip everything to matching .endif */
1298e2eeea75SSimon J. Gerraty 		/* XXX: An extra '.else' is not detected in this case. */
1299e2eeea75SSimon J. Gerraty 		cond_states[cond_depth] = IFS_WAS_ACTIVE;
13003955d011SMarcel Moolenaar 		return COND_SKIP;
13013955d011SMarcel Moolenaar 	}
13023955d011SMarcel Moolenaar 
13033955d011SMarcel Moolenaar 	if (!value) {
1304e2eeea75SSimon J. Gerraty 		cond_states[cond_depth] = IFS_INITIAL;
13053955d011SMarcel Moolenaar 		return COND_SKIP;
13063955d011SMarcel Moolenaar 	}
1307e2eeea75SSimon J. Gerraty 	cond_states[cond_depth] = IFS_ACTIVE;
13083955d011SMarcel Moolenaar 	return COND_PARSE;
13093955d011SMarcel Moolenaar }
13103955d011SMarcel Moolenaar 
13113955d011SMarcel Moolenaar void
13123955d011SMarcel Moolenaar Cond_restore_depth(unsigned int saved_depth)
13133955d011SMarcel Moolenaar {
1314956e45f6SSimon J. Gerraty 	unsigned int open_conds = cond_depth - cond_min_depth;
13153955d011SMarcel Moolenaar 
13163955d011SMarcel Moolenaar 	if (open_conds != 0 || saved_depth > cond_depth) {
131706b9b3e0SSimon J. Gerraty 		Parse_Error(PARSE_FATAL, "%u open conditional%s",
131806b9b3e0SSimon J. Gerraty 			    open_conds, open_conds == 1 ? "" : "s");
13193955d011SMarcel Moolenaar 		cond_depth = cond_min_depth;
13203955d011SMarcel Moolenaar 	}
13213955d011SMarcel Moolenaar 
13223955d011SMarcel Moolenaar 	cond_min_depth = saved_depth;
13233955d011SMarcel Moolenaar }
13243955d011SMarcel Moolenaar 
13253955d011SMarcel Moolenaar unsigned int
13263955d011SMarcel Moolenaar Cond_save_depth(void)
13273955d011SMarcel Moolenaar {
1328956e45f6SSimon J. Gerraty 	unsigned int depth = cond_min_depth;
13293955d011SMarcel Moolenaar 
13303955d011SMarcel Moolenaar 	cond_min_depth = cond_depth;
13313955d011SMarcel Moolenaar 	return depth;
13323955d011SMarcel Moolenaar }
1333