xref: /freebsd/contrib/bmake/cond.c (revision 226192822cddc30cacecd55bccb48f39c653058c)
1*22619282SSimon J. Gerraty /*	$NetBSD: cond.c,v 1.366 2024/07/06 21:21:09 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  *
84d5e0a182SSimon J. Gerraty  *	Cond_EndFile	At the end of reading a makefile, ensure that the
854fde40d9SSimon J. Gerraty  *			conditional directives are well-balanced.
863955d011SMarcel Moolenaar  */
873955d011SMarcel Moolenaar 
882c3632d1SSimon J. Gerraty #include <errno.h>
893955d011SMarcel Moolenaar 
903955d011SMarcel Moolenaar #include "make.h"
913955d011SMarcel Moolenaar #include "dir.h"
923955d011SMarcel Moolenaar 
93956e45f6SSimon J. Gerraty /*	"@(#)cond.c	8.2 (Berkeley) 1/2/94"	*/
94*22619282SSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.366 2024/07/06 21:21:09 rillig Exp $");
95956e45f6SSimon J. Gerraty 
963955d011SMarcel Moolenaar /*
979f45a3c8SSimon J. Gerraty  * Conditional expressions conform to this grammar:
9812904384SSimon J. Gerraty  *	Or -> And ('||' And)*
9912904384SSimon J. Gerraty  *	And -> Term ('&&' Term)*
100dba7b0efSSimon J. Gerraty  *	Term -> Function '(' Argument ')'
101dba7b0efSSimon J. Gerraty  *	Term -> Leaf Operator Leaf
102dba7b0efSSimon J. Gerraty  *	Term -> Leaf
103dba7b0efSSimon J. Gerraty  *	Term -> '(' Or ')'
104dba7b0efSSimon J. Gerraty  *	Term -> '!' Term
105dba7b0efSSimon J. Gerraty  *	Leaf -> "string"
106dba7b0efSSimon J. Gerraty  *	Leaf -> Number
107dba7b0efSSimon J. Gerraty  *	Leaf -> VariableExpression
1089f45a3c8SSimon J. Gerraty  *	Leaf -> BareWord
109dba7b0efSSimon J. Gerraty  *	Operator -> '==' | '!=' | '>' | '<' | '>=' | '<='
1103955d011SMarcel Moolenaar  *
1119f45a3c8SSimon J. Gerraty  * BareWord is an unquoted string literal, its evaluation depends on the kind
1129f45a3c8SSimon J. Gerraty  * of '.if' directive.
1133955d011SMarcel Moolenaar  *
1149f45a3c8SSimon J. Gerraty  * The tokens are scanned by CondParser_Token, which returns:
115dba7b0efSSimon J. Gerraty  *	TOK_AND		for '&&'
116dba7b0efSSimon J. Gerraty  *	TOK_OR		for '||'
117956e45f6SSimon J. Gerraty  *	TOK_NOT		for '!'
118956e45f6SSimon J. Gerraty  *	TOK_LPAREN	for '('
119956e45f6SSimon J. Gerraty  *	TOK_RPAREN	for ')'
120dba7b0efSSimon J. Gerraty  *
121956e45f6SSimon J. Gerraty  * Other terminal symbols are evaluated using either the default function or
1229f45a3c8SSimon J. Gerraty  * the function given in the terminal, they return either TOK_TRUE, TOK_FALSE
1239f45a3c8SSimon J. Gerraty  * or TOK_ERROR.
1243955d011SMarcel Moolenaar  */
125956e45f6SSimon J. Gerraty typedef enum Token {
126dba7b0efSSimon J. Gerraty 	TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT,
1273955d011SMarcel Moolenaar 	TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
1283955d011SMarcel Moolenaar } Token;
1293955d011SMarcel Moolenaar 
130dba7b0efSSimon J. Gerraty typedef enum ComparisonOp {
131dba7b0efSSimon J. Gerraty 	LT, LE, GT, GE, EQ, NE
132dba7b0efSSimon J. Gerraty } ComparisonOp;
133dba7b0efSSimon J. Gerraty 
134956e45f6SSimon J. Gerraty typedef struct CondParser {
135dba7b0efSSimon J. Gerraty 
136dba7b0efSSimon J. Gerraty 	/*
137dba7b0efSSimon J. Gerraty 	 * The plain '.if ${VAR}' evaluates to true if the value of the
138148ee845SSimon J. Gerraty 	 * expression has length > 0 and is not numerically zero.  The other
139148ee845SSimon J. Gerraty 	 * '.if' variants delegate to evalBare instead, for example '.ifdef
140148ee845SSimon J. Gerraty 	 * ${VAR}' is equivalent to '.if defined(${VAR})', checking whether
141148ee845SSimon J. Gerraty 	 * the variable named by the expression '${VAR}' is defined.
142dba7b0efSSimon J. Gerraty 	 */
143b0c40a00SSimon J. Gerraty 	bool plain;
144dba7b0efSSimon J. Gerraty 
145dba7b0efSSimon J. Gerraty 	/* The function to apply on unquoted bare words. */
14612904384SSimon J. Gerraty 	bool (*evalBare)(const char *);
147b0c40a00SSimon J. Gerraty 	bool negateEvalBare;
148dba7b0efSSimon J. Gerraty 
14912904384SSimon J. Gerraty 	/*
15012904384SSimon J. Gerraty 	 * Whether the left-hand side of a comparison may be an unquoted
15112904384SSimon J. Gerraty 	 * string.  This is allowed for expressions of the form
15212904384SSimon J. Gerraty 	 * ${condition:?:}, see ApplyModifier_IfElse.  Such a condition is
15312904384SSimon J. Gerraty 	 * expanded before it is evaluated, due to ease of implementation.
15412904384SSimon J. Gerraty 	 * This means that at the point where the condition is evaluated,
15512904384SSimon J. Gerraty 	 * make cannot know anymore whether the left-hand side had originally
156d5e0a182SSimon J. Gerraty 	 * been an expression or a plain word.
15712904384SSimon J. Gerraty 	 *
1581d3f2ddcSSimon J. Gerraty 	 * In conditional directives like '.if', the left-hand side must
159d5e0a182SSimon J. Gerraty 	 * either be an expression, a quoted string or a number.
16012904384SSimon J. Gerraty 	 */
16112904384SSimon J. Gerraty 	bool leftUnquotedOK;
16212904384SSimon J. Gerraty 
163956e45f6SSimon J. Gerraty 	const char *p;		/* The remaining condition to parse */
164956e45f6SSimon J. Gerraty 	Token curr;		/* Single push-back token used in parsing */
1653955d011SMarcel Moolenaar 
16606b9b3e0SSimon J. Gerraty 	/*
16706b9b3e0SSimon J. Gerraty 	 * Whether an error message has already been printed for this
168d5e0a182SSimon J. Gerraty 	 * condition.
16906b9b3e0SSimon J. Gerraty 	 */
170b0c40a00SSimon J. Gerraty 	bool printedError;
171956e45f6SSimon J. Gerraty } CondParser;
172956e45f6SSimon J. Gerraty 
173148ee845SSimon J. Gerraty static CondResult CondParser_Or(CondParser *, bool);
1743955d011SMarcel Moolenaar 
1754fde40d9SSimon J. Gerraty unsigned int cond_depth = 0;	/* current .if nesting level */
1763955d011SMarcel Moolenaar 
17712904384SSimon J. Gerraty /* Names for ComparisonOp. */
17812904384SSimon J. Gerraty static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" };
17928a6bc81SSimon J. Gerraty 
1809f45a3c8SSimon J. Gerraty MAKE_INLINE bool
1819f45a3c8SSimon J. Gerraty skip_string(const char **pp, const char *str)
1823955d011SMarcel Moolenaar {
1839f45a3c8SSimon J. Gerraty 	size_t len = strlen(str);
1849f45a3c8SSimon J. Gerraty 	bool ok = strncmp(*pp, str, len) == 0;
1859f45a3c8SSimon J. Gerraty 	if (ok)
1869f45a3c8SSimon J. Gerraty 		*pp += len;
1879f45a3c8SSimon J. Gerraty 	return ok;
1883955d011SMarcel Moolenaar }
1893955d011SMarcel Moolenaar 
190e2eeea75SSimon J. Gerraty static Token
191b0c40a00SSimon J. Gerraty ToToken(bool cond)
192e2eeea75SSimon J. Gerraty {
193e2eeea75SSimon J. Gerraty 	return cond ? TOK_TRUE : TOK_FALSE;
194e2eeea75SSimon J. Gerraty }
195e2eeea75SSimon J. Gerraty 
196956e45f6SSimon J. Gerraty static void
197956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(CondParser *par)
1983955d011SMarcel Moolenaar {
199956e45f6SSimon J. Gerraty 	cpp_skip_whitespace(&par->p);
200956e45f6SSimon J. Gerraty }
201956e45f6SSimon J. Gerraty 
20206b9b3e0SSimon J. Gerraty /*
20312904384SSimon J. Gerraty  * Parse a single word, taking into account balanced parentheses as well as
20412904384SSimon J. Gerraty  * embedded expressions.  Used for the argument of a built-in function as
20512904384SSimon J. Gerraty  * well as for bare words, which are then passed to the default function.
20606b9b3e0SSimon J. Gerraty  */
2079f45a3c8SSimon J. Gerraty static char *
2089f45a3c8SSimon J. Gerraty ParseWord(const char **pp, bool doEval)
20906b9b3e0SSimon J. Gerraty {
210956e45f6SSimon J. Gerraty 	const char *p = *pp;
2111d3f2ddcSSimon J. Gerraty 	Buffer word;
212d5e0a182SSimon J. Gerraty 	int depth;
2133955d011SMarcel Moolenaar 
214d5e0a182SSimon J. Gerraty 	Buf_Init(&word);
2153955d011SMarcel Moolenaar 
216d5e0a182SSimon J. Gerraty 	depth = 0;
2173955d011SMarcel Moolenaar 	for (;;) {
218956e45f6SSimon J. Gerraty 		char ch = *p;
219e2eeea75SSimon J. Gerraty 		if (ch == '\0' || ch == ' ' || ch == '\t')
2203955d011SMarcel Moolenaar 			break;
221d5e0a182SSimon J. Gerraty 		if ((ch == '&' || ch == '|') && depth == 0)
2223955d011SMarcel Moolenaar 			break;
2239f45a3c8SSimon J. Gerraty 		if (ch == '$') {
224b0c40a00SSimon J. Gerraty 			VarEvalMode emode = doEval
2258d5c8e21SSimon J. Gerraty 			    ? VARE_EVAL_DEFINED
2268d5c8e21SSimon J. Gerraty 			    : VARE_PARSE;
22798875883SSimon J. Gerraty 			/*
22898875883SSimon J. Gerraty 			 * TODO: make Var_Parse complain about undefined
22998875883SSimon J. Gerraty 			 * variables.
23098875883SSimon J. Gerraty 			 */
2318c973ee2SSimon J. Gerraty 			FStr nestedVal = Var_Parse(&p, SCOPE_CMDLINE, emode);
232956e45f6SSimon J. Gerraty 			/* TODO: handle errors */
2331d3f2ddcSSimon J. Gerraty 			Buf_AddStr(&word, nestedVal.str);
23406b9b3e0SSimon J. Gerraty 			FStr_Done(&nestedVal);
2353955d011SMarcel Moolenaar 			continue;
2363955d011SMarcel Moolenaar 		}
2373955d011SMarcel Moolenaar 		if (ch == '(')
238d5e0a182SSimon J. Gerraty 			depth++;
239d5e0a182SSimon J. Gerraty 		else if (ch == ')' && --depth < 0)
2403955d011SMarcel Moolenaar 			break;
2411d3f2ddcSSimon J. Gerraty 		Buf_AddByte(&word, ch);
242956e45f6SSimon J. Gerraty 		p++;
2433955d011SMarcel Moolenaar 	}
2443955d011SMarcel Moolenaar 
2459f45a3c8SSimon J. Gerraty 	cpp_skip_hspace(&p);
2469f45a3c8SSimon J. Gerraty 	*pp = p;
2473955d011SMarcel Moolenaar 
2481d3f2ddcSSimon J. Gerraty 	return Buf_DoneData(&word);
2499f45a3c8SSimon J. Gerraty }
2509f45a3c8SSimon J. Gerraty 
2519f45a3c8SSimon J. Gerraty /* Parse the function argument, including the surrounding parentheses. */
2529f45a3c8SSimon J. Gerraty static char *
2539f45a3c8SSimon J. Gerraty ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func)
2549f45a3c8SSimon J. Gerraty {
2559f45a3c8SSimon J. Gerraty 	const char *p = *pp;
2569f45a3c8SSimon J. Gerraty 	char *res;
2579f45a3c8SSimon J. Gerraty 
258d5e0a182SSimon J. Gerraty 	p++;			/* skip the '(' */
2599f45a3c8SSimon J. Gerraty 	cpp_skip_hspace(&p);
2609f45a3c8SSimon J. Gerraty 	res = ParseWord(&p, doEval);
261e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&p);
2623955d011SMarcel Moolenaar 
2639f45a3c8SSimon J. Gerraty 	if (*p++ != ')') {
2649f45a3c8SSimon J. Gerraty 		int len = 0;
2659f45a3c8SSimon J. Gerraty 		while (ch_isalpha(func[len]))
2669f45a3c8SSimon J. Gerraty 			len++;
2679f45a3c8SSimon J. Gerraty 
268dba7b0efSSimon J. Gerraty 		Parse_Error(PARSE_FATAL,
2699f45a3c8SSimon J. Gerraty 		    "Missing closing parenthesis for %.*s()", len, func);
270b0c40a00SSimon J. Gerraty 		par->printedError = true;
2719f45a3c8SSimon J. Gerraty 		free(res);
2729f45a3c8SSimon J. Gerraty 		return NULL;
2733955d011SMarcel Moolenaar 	}
2743955d011SMarcel Moolenaar 
275956e45f6SSimon J. Gerraty 	*pp = p;
2769f45a3c8SSimon J. Gerraty 	return res;
2773955d011SMarcel Moolenaar }
2782c3632d1SSimon J. Gerraty 
2791d3f2ddcSSimon J. Gerraty /* See if the given variable is defined. */
280b0c40a00SSimon J. Gerraty static bool
2819f45a3c8SSimon J. Gerraty FuncDefined(const char *var)
2823955d011SMarcel Moolenaar {
2839f45a3c8SSimon J. Gerraty 	return Var_Exists(SCOPE_CMDLINE, var);
2843955d011SMarcel Moolenaar }
2852c3632d1SSimon J. Gerraty 
2861d3f2ddcSSimon J. Gerraty /* See if a target matching targetPattern is requested to be made. */
287b0c40a00SSimon J. Gerraty static bool
2881d3f2ddcSSimon J. Gerraty FuncMake(const char *targetPattern)
2893955d011SMarcel Moolenaar {
290956e45f6SSimon J. Gerraty 	StringListNode *ln;
291148ee845SSimon J. Gerraty 	bool warned = false;
292956e45f6SSimon J. Gerraty 
293148ee845SSimon J. Gerraty 	for (ln = opts.create.first; ln != NULL; ln = ln->next) {
294148ee845SSimon J. Gerraty 		StrMatchResult res = Str_Match(ln->datum, targetPattern);
295148ee845SSimon J. Gerraty 		if (res.error != NULL && !warned) {
296148ee845SSimon J. Gerraty 			warned = true;
297148ee845SSimon J. Gerraty 			Parse_Error(PARSE_WARNING,
298148ee845SSimon J. Gerraty 			    "%s in pattern argument '%s' to function 'make'",
299148ee845SSimon J. Gerraty 			    res.error, targetPattern);
300148ee845SSimon J. Gerraty 		}
301148ee845SSimon J. Gerraty 		if (res.matched)
302b0c40a00SSimon J. Gerraty 			return true;
303148ee845SSimon J. Gerraty 	}
304b0c40a00SSimon J. Gerraty 	return false;
3053955d011SMarcel Moolenaar }
3062c3632d1SSimon J. Gerraty 
3072c3632d1SSimon J. Gerraty /* See if the given file exists. */
308b0c40a00SSimon J. Gerraty static bool
3099f45a3c8SSimon J. Gerraty FuncExists(const char *file)
3103955d011SMarcel Moolenaar {
311b0c40a00SSimon J. Gerraty 	bool result;
3123955d011SMarcel Moolenaar 	char *path;
3133955d011SMarcel Moolenaar 
3149f45a3c8SSimon J. Gerraty 	path = Dir_FindFile(file, &dirSearchPath);
315e2eeea75SSimon J. Gerraty 	DEBUG2(COND, "exists(%s) result is \"%s\"\n",
3169f45a3c8SSimon J. Gerraty 	    file, path != NULL ? path : "");
317e2eeea75SSimon J. Gerraty 	result = path != NULL;
3183955d011SMarcel Moolenaar 	free(path);
3193841c287SSimon J. Gerraty 	return result;
3203955d011SMarcel Moolenaar }
3212c3632d1SSimon J. Gerraty 
3222c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target. */
323b0c40a00SSimon J. Gerraty static bool
3249f45a3c8SSimon J. Gerraty FuncTarget(const char *node)
3253955d011SMarcel Moolenaar {
3269f45a3c8SSimon J. Gerraty 	GNode *gn = Targ_FindNode(node);
327956e45f6SSimon J. Gerraty 	return gn != NULL && GNode_IsTarget(gn);
3283955d011SMarcel Moolenaar }
3293955d011SMarcel Moolenaar 
33006b9b3e0SSimon J. Gerraty /*
33106b9b3e0SSimon J. Gerraty  * See if the given node exists and is an actual target with commands
33206b9b3e0SSimon J. Gerraty  * associated with it.
33306b9b3e0SSimon J. Gerraty  */
334b0c40a00SSimon J. Gerraty static bool
3359f45a3c8SSimon J. Gerraty FuncCommands(const char *node)
3363955d011SMarcel Moolenaar {
3379f45a3c8SSimon J. Gerraty 	GNode *gn = Targ_FindNode(node);
3389f45a3c8SSimon J. Gerraty 	return gn != NULL && GNode_IsTarget(gn) &&
3399f45a3c8SSimon J. Gerraty 	       !Lst_IsEmpty(&gn->commands);
3403955d011SMarcel Moolenaar }
3412c3632d1SSimon J. Gerraty 
342e2eeea75SSimon J. Gerraty /*
343148ee845SSimon J. Gerraty  * Convert the string to a floating point number.  Accepted formats are
3449f45a3c8SSimon J. Gerraty  * base-10 integer, base-16 integer and finite floating point numbers.
3453955d011SMarcel Moolenaar  */
346b0c40a00SSimon J. Gerraty static bool
347e2eeea75SSimon J. Gerraty TryParseNumber(const char *str, double *out_value)
3483955d011SMarcel Moolenaar {
349e2eeea75SSimon J. Gerraty 	char *end;
350e2eeea75SSimon J. Gerraty 	unsigned long ul_val;
351e2eeea75SSimon J. Gerraty 	double dbl_val;
3523955d011SMarcel Moolenaar 
353e2eeea75SSimon J. Gerraty 	if (str[0] == '\0') {	/* XXX: why is an empty string a number? */
354e2eeea75SSimon J. Gerraty 		*out_value = 0.0;
355b0c40a00SSimon J. Gerraty 		return true;
356ac3446e9SSimon J. Gerraty 	}
357e2eeea75SSimon J. Gerraty 
35812904384SSimon J. Gerraty 	errno = 0;
359e2eeea75SSimon J. Gerraty 	ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10);
360e2eeea75SSimon J. Gerraty 	if (*end == '\0' && errno != ERANGE) {
361e2eeea75SSimon J. Gerraty 		*out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val;
362b0c40a00SSimon J. Gerraty 		return true;
3633955d011SMarcel Moolenaar 	}
3643955d011SMarcel Moolenaar 
365e2eeea75SSimon J. Gerraty 	if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E')
366b0c40a00SSimon J. Gerraty 		return false;	/* skip the expensive strtod call */
367e2eeea75SSimon J. Gerraty 	dbl_val = strtod(str, &end);
368e2eeea75SSimon J. Gerraty 	if (*end != '\0')
369b0c40a00SSimon J. Gerraty 		return false;
370e2eeea75SSimon J. Gerraty 
371e2eeea75SSimon J. Gerraty 	*out_value = dbl_val;
372b0c40a00SSimon J. Gerraty 	return true;
3733955d011SMarcel Moolenaar }
3743955d011SMarcel Moolenaar 
375b0c40a00SSimon J. Gerraty static bool
376956e45f6SSimon J. Gerraty is_separator(char ch)
377956e45f6SSimon J. Gerraty {
378b0c40a00SSimon J. Gerraty 	return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' ||
379b0c40a00SSimon J. Gerraty 	       ch == '>' || ch == '<' || ch == ')' /* but not '(' */;
380956e45f6SSimon J. Gerraty }
381956e45f6SSimon J. Gerraty 
382dba7b0efSSimon J. Gerraty /*
383d5e0a182SSimon J. Gerraty  * In a quoted or unquoted string literal or a number, parse an
3848c973ee2SSimon J. Gerraty  * expression and add its value to the buffer.
3858c973ee2SSimon J. Gerraty  *
3868c973ee2SSimon J. Gerraty  * Return whether to continue parsing the leaf.
387dba7b0efSSimon J. Gerraty  *
388dba7b0efSSimon J. Gerraty  * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX}
389dba7b0efSSimon J. Gerraty  */
390b0c40a00SSimon J. Gerraty static bool
391dba7b0efSSimon J. Gerraty CondParser_StringExpr(CondParser *par, const char *start,
39212904384SSimon J. Gerraty 		      bool doEval, bool quoted,
39312904384SSimon J. Gerraty 		      Buffer *buf, FStr *inout_str)
394dba7b0efSSimon J. Gerraty {
395b0c40a00SSimon J. Gerraty 	VarEvalMode emode;
3969f45a3c8SSimon J. Gerraty 	const char *p;
397c59c3bf3SSimon J. Gerraty 	bool atStart;		/* true means an expression outside quotes */
398dba7b0efSSimon J. Gerraty 
3998d5c8e21SSimon J. Gerraty 	emode = doEval && quoted ? VARE_EVAL
4008d5c8e21SSimon J. Gerraty 	    : doEval ? VARE_EVAL_DEFINED
4018d5c8e21SSimon J. Gerraty 	    : VARE_PARSE;
402dba7b0efSSimon J. Gerraty 
4039f45a3c8SSimon J. Gerraty 	p = par->p;
4049f45a3c8SSimon J. Gerraty 	atStart = p == start;
4058c973ee2SSimon J. Gerraty 	*inout_str = Var_Parse(&p, SCOPE_CMDLINE, emode);
406dba7b0efSSimon J. Gerraty 	/* TODO: handle errors */
407dba7b0efSSimon J. Gerraty 	if (inout_str->str == var_Error) {
408dba7b0efSSimon J. Gerraty 		FStr_Done(inout_str);
409dba7b0efSSimon J. Gerraty 		*inout_str = FStr_InitRefer(NULL);
410b0c40a00SSimon J. Gerraty 		return false;
411dba7b0efSSimon J. Gerraty 	}
4129f45a3c8SSimon J. Gerraty 	par->p = p;
413dba7b0efSSimon J. Gerraty 
414dba7b0efSSimon J. Gerraty 	if (atStart && is_separator(par->p[0]))
415b0c40a00SSimon J. Gerraty 		return false;
416dba7b0efSSimon J. Gerraty 
417dba7b0efSSimon J. Gerraty 	Buf_AddStr(buf, inout_str->str);
418dba7b0efSSimon J. Gerraty 	FStr_Done(inout_str);
419dba7b0efSSimon J. Gerraty 	*inout_str = FStr_InitRefer(NULL);	/* not finished yet */
420b0c40a00SSimon J. Gerraty 	return true;
421dba7b0efSSimon J. Gerraty }
422dba7b0efSSimon J. Gerraty 
423dba7b0efSSimon J. Gerraty /*
424d5e0a182SSimon J. Gerraty  * Parse a string from an expression or an optionally quoted string,
4259f45a3c8SSimon J. Gerraty  * on the left-hand and right-hand sides of comparisons.
4263955d011SMarcel Moolenaar  *
427548bfc56SSimon J. Gerraty  * Return the string without any enclosing quotes, or NULL on error.
428b0c40a00SSimon J. Gerraty  * Sets out_quoted if the leaf was a quoted string literal.
4293955d011SMarcel Moolenaar  */
430548bfc56SSimon J. Gerraty static FStr
43112904384SSimon J. Gerraty CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK,
432548bfc56SSimon J. Gerraty 		bool *out_quoted)
4333955d011SMarcel Moolenaar {
4343955d011SMarcel Moolenaar 	Buffer buf;
43506b9b3e0SSimon J. Gerraty 	FStr str;
436b0c40a00SSimon J. Gerraty 	bool quoted;
4372c3632d1SSimon J. Gerraty 	const char *start;
4383955d011SMarcel Moolenaar 
439e2eeea75SSimon J. Gerraty 	Buf_Init(&buf);
44006b9b3e0SSimon J. Gerraty 	str = FStr_InitRefer(NULL);
441e2eeea75SSimon J. Gerraty 	*out_quoted = quoted = par->p[0] == '"';
442956e45f6SSimon J. Gerraty 	start = par->p;
443e2eeea75SSimon J. Gerraty 	if (quoted)
444956e45f6SSimon J. Gerraty 		par->p++;
44506b9b3e0SSimon J. Gerraty 
44606b9b3e0SSimon J. Gerraty 	while (par->p[0] != '\0' && str.str == NULL) {
447956e45f6SSimon J. Gerraty 		switch (par->p[0]) {
4483955d011SMarcel Moolenaar 		case '\\':
449956e45f6SSimon J. Gerraty 			par->p++;
450956e45f6SSimon J. Gerraty 			if (par->p[0] != '\0') {
451956e45f6SSimon J. Gerraty 				Buf_AddByte(&buf, par->p[0]);
452956e45f6SSimon J. Gerraty 				par->p++;
4533955d011SMarcel Moolenaar 			}
454956e45f6SSimon J. Gerraty 			continue;
4553955d011SMarcel Moolenaar 		case '"':
456956e45f6SSimon J. Gerraty 			par->p++;
457dba7b0efSSimon J. Gerraty 			if (quoted)
4589f45a3c8SSimon J. Gerraty 				goto return_buf;	/* skip the closing quote */
459dba7b0efSSimon J. Gerraty 			Buf_AddByte(&buf, '"');
460956e45f6SSimon J. Gerraty 			continue;
461e2eeea75SSimon J. Gerraty 		case ')':	/* see is_separator */
4623955d011SMarcel Moolenaar 		case '!':
4633955d011SMarcel Moolenaar 		case '=':
4643955d011SMarcel Moolenaar 		case '>':
4653955d011SMarcel Moolenaar 		case '<':
4663955d011SMarcel Moolenaar 		case ' ':
4673955d011SMarcel Moolenaar 		case '\t':
468e2eeea75SSimon J. Gerraty 			if (!quoted)
4699f45a3c8SSimon J. Gerraty 				goto return_buf;
470956e45f6SSimon J. Gerraty 			Buf_AddByte(&buf, par->p[0]);
471956e45f6SSimon J. Gerraty 			par->p++;
472956e45f6SSimon J. Gerraty 			continue;
4733955d011SMarcel Moolenaar 		case '$':
474dba7b0efSSimon J. Gerraty 			if (!CondParser_StringExpr(par,
475dba7b0efSSimon J. Gerraty 			    start, doEval, quoted, &buf, &str))
4769f45a3c8SSimon J. Gerraty 				goto return_str;
477956e45f6SSimon J. Gerraty 			continue;
4783955d011SMarcel Moolenaar 		default:
47912904384SSimon J. Gerraty 			if (!unquotedOK && !quoted && *start != '$' &&
48006b9b3e0SSimon J. Gerraty 			    !ch_isdigit(*start)) {
48106b9b3e0SSimon J. Gerraty 				str = FStr_InitRefer(NULL);
4829f45a3c8SSimon J. Gerraty 				goto return_str;
48328a6bc81SSimon J. Gerraty 			}
484956e45f6SSimon J. Gerraty 			Buf_AddByte(&buf, par->p[0]);
485956e45f6SSimon J. Gerraty 			par->p++;
486956e45f6SSimon J. Gerraty 			continue;
4873955d011SMarcel Moolenaar 		}
4883955d011SMarcel Moolenaar 	}
4899f45a3c8SSimon J. Gerraty return_buf:
490dba7b0efSSimon J. Gerraty 	str = FStr_InitOwn(buf.data);
49112904384SSimon J. Gerraty 	buf.data = NULL;
4929f45a3c8SSimon J. Gerraty return_str:
49312904384SSimon J. Gerraty 	Buf_Done(&buf);
494548bfc56SSimon J. Gerraty 	return str;
4953955d011SMarcel Moolenaar }
4962c3632d1SSimon J. Gerraty 
49706b9b3e0SSimon J. Gerraty /*
49806b9b3e0SSimon J. Gerraty  * Evaluate a "comparison without operator", such as in ".if ${VAR}" or
49906b9b3e0SSimon J. Gerraty  * ".if 0".
50006b9b3e0SSimon J. Gerraty  */
501b0c40a00SSimon J. Gerraty static bool
502148ee845SSimon J. Gerraty EvalTruthy(CondParser *par, const char *value, bool quoted)
5033955d011SMarcel Moolenaar {
504e2eeea75SSimon J. Gerraty 	double num;
505956e45f6SSimon J. Gerraty 
506e2eeea75SSimon J. Gerraty 	if (quoted)
507e2eeea75SSimon J. Gerraty 		return value[0] != '\0';
508e2eeea75SSimon J. Gerraty 	if (TryParseNumber(value, &num))
509e2eeea75SSimon J. Gerraty 		return num != 0.0;
510dba7b0efSSimon J. Gerraty 	if (par->plain)
511e2eeea75SSimon J. Gerraty 		return value[0] != '\0';
5129f45a3c8SSimon J. Gerraty 	return par->evalBare(value) != par->negateEvalBare;
513956e45f6SSimon J. Gerraty }
514956e45f6SSimon J. Gerraty 
515956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */
516b0c40a00SSimon J. Gerraty static bool
517dba7b0efSSimon J. Gerraty EvalCompareNum(double lhs, ComparisonOp op, double rhs)
518956e45f6SSimon J. Gerraty {
5191d3f2ddcSSimon J. Gerraty 	DEBUG3(COND, "Comparing %f %s %f\n", lhs, opname[op], rhs);
520956e45f6SSimon J. Gerraty 
521dba7b0efSSimon J. Gerraty 	switch (op) {
522dba7b0efSSimon J. Gerraty 	case LT:
523dba7b0efSSimon J. Gerraty 		return lhs < rhs;
524dba7b0efSSimon J. Gerraty 	case LE:
525dba7b0efSSimon J. Gerraty 		return lhs <= rhs;
526dba7b0efSSimon J. Gerraty 	case GT:
527dba7b0efSSimon J. Gerraty 		return lhs > rhs;
528dba7b0efSSimon J. Gerraty 	case GE:
529dba7b0efSSimon J. Gerraty 		return lhs >= rhs;
5304fde40d9SSimon J. Gerraty 	case EQ:
531dba7b0efSSimon J. Gerraty 		return lhs == rhs;
5324fde40d9SSimon J. Gerraty 	default:
5334fde40d9SSimon J. Gerraty 		return lhs != rhs;
534956e45f6SSimon J. Gerraty 	}
535956e45f6SSimon J. Gerraty }
536956e45f6SSimon J. Gerraty 
537956e45f6SSimon J. Gerraty static Token
538dba7b0efSSimon J. Gerraty EvalCompareStr(CondParser *par, const char *lhs,
539dba7b0efSSimon J. Gerraty 	       ComparisonOp op, const char *rhs)
540956e45f6SSimon J. Gerraty {
541dba7b0efSSimon J. Gerraty 	if (op != EQ && op != NE) {
542dba7b0efSSimon J. Gerraty 		Parse_Error(PARSE_FATAL,
5434fde40d9SSimon J. Gerraty 		    "Comparison with '%s' requires both operands "
5444fde40d9SSimon J. Gerraty 		    "'%s' and '%s' to be numeric",
5454fde40d9SSimon J. Gerraty 		    opname[op], lhs, rhs);
546b0c40a00SSimon J. Gerraty 		par->printedError = true;
547956e45f6SSimon J. Gerraty 		return TOK_ERROR;
548956e45f6SSimon J. Gerraty 	}
549956e45f6SSimon J. Gerraty 
5501d3f2ddcSSimon J. Gerraty 	DEBUG3(COND, "Comparing \"%s\" %s \"%s\"\n", lhs, opname[op], rhs);
551dba7b0efSSimon J. Gerraty 	return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0));
552956e45f6SSimon J. Gerraty }
553956e45f6SSimon J. Gerraty 
554956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */
555956e45f6SSimon J. Gerraty static Token
556b0c40a00SSimon J. Gerraty EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted,
557b0c40a00SSimon J. Gerraty 	    ComparisonOp op, const char *rhs, bool rhsQuoted)
558956e45f6SSimon J. Gerraty {
5593955d011SMarcel Moolenaar 	double left, right;
5603955d011SMarcel Moolenaar 
561956e45f6SSimon J. Gerraty 	if (!rhsQuoted && !lhsQuoted)
562956e45f6SSimon J. Gerraty 		if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right))
563dba7b0efSSimon J. Gerraty 			return ToToken(EvalCompareNum(left, op, right));
564956e45f6SSimon J. Gerraty 
565dba7b0efSSimon J. Gerraty 	return EvalCompareStr(par, lhs, op, rhs);
566dba7b0efSSimon J. Gerraty }
567dba7b0efSSimon J. Gerraty 
568b0c40a00SSimon J. Gerraty static bool
569dba7b0efSSimon J. Gerraty CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op)
570dba7b0efSSimon J. Gerraty {
571dba7b0efSSimon J. Gerraty 	const char *p = par->p;
572dba7b0efSSimon J. Gerraty 
5739f45a3c8SSimon J. Gerraty 	if (p[0] == '<' && p[1] == '=')
5749f45a3c8SSimon J. Gerraty 		return par->p += 2, *out_op = LE, true;
5759f45a3c8SSimon J. Gerraty 	if (p[0] == '<')
5769f45a3c8SSimon J. Gerraty 		return par->p += 1, *out_op = LT, true;
5779f45a3c8SSimon J. Gerraty 	if (p[0] == '>' && p[1] == '=')
5789f45a3c8SSimon J. Gerraty 		return par->p += 2, *out_op = GE, true;
5799f45a3c8SSimon J. Gerraty 	if (p[0] == '>')
5809f45a3c8SSimon J. Gerraty 		return par->p += 1, *out_op = GT, true;
5819f45a3c8SSimon J. Gerraty 	if (p[0] == '=' && p[1] == '=')
5829f45a3c8SSimon J. Gerraty 		return par->p += 2, *out_op = EQ, true;
5839f45a3c8SSimon J. Gerraty 	if (p[0] == '!' && p[1] == '=')
5849f45a3c8SSimon J. Gerraty 		return par->p += 2, *out_op = NE, true;
585b0c40a00SSimon J. Gerraty 	return false;
586956e45f6SSimon J. Gerraty }
587956e45f6SSimon J. Gerraty 
58806b9b3e0SSimon J. Gerraty /*
58906b9b3e0SSimon J. Gerraty  * Parse a comparison condition such as:
590956e45f6SSimon J. Gerraty  *
591956e45f6SSimon J. Gerraty  *	0
592956e45f6SSimon J. Gerraty  *	${VAR:Mpattern}
593956e45f6SSimon J. Gerraty  *	${VAR} == value
594956e45f6SSimon J. Gerraty  *	${VAR:U0} < 12345
595956e45f6SSimon J. Gerraty  */
596956e45f6SSimon J. Gerraty static Token
597b0c40a00SSimon J. Gerraty CondParser_Comparison(CondParser *par, bool doEval)
598956e45f6SSimon J. Gerraty {
599956e45f6SSimon J. Gerraty 	Token t = TOK_ERROR;
60006b9b3e0SSimon J. Gerraty 	FStr lhs, rhs;
601dba7b0efSSimon J. Gerraty 	ComparisonOp op;
602b0c40a00SSimon J. Gerraty 	bool lhsQuoted, rhsQuoted;
603956e45f6SSimon J. Gerraty 
604548bfc56SSimon J. Gerraty 	lhs = CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhsQuoted);
60506b9b3e0SSimon J. Gerraty 	if (lhs.str == NULL)
606e2eeea75SSimon J. Gerraty 		goto done_lhs;
6073955d011SMarcel Moolenaar 
608956e45f6SSimon J. Gerraty 	CondParser_SkipWhitespace(par);
6093955d011SMarcel Moolenaar 
610dba7b0efSSimon J. Gerraty 	if (!CondParser_ComparisonOp(par, &op)) {
611148ee845SSimon J. Gerraty 		t = ToToken(doEval && EvalTruthy(par, lhs.str, lhsQuoted));
612e2eeea75SSimon J. Gerraty 		goto done_lhs;
6133955d011SMarcel Moolenaar 	}
6143955d011SMarcel Moolenaar 
615956e45f6SSimon J. Gerraty 	CondParser_SkipWhitespace(par);
6163955d011SMarcel Moolenaar 
617956e45f6SSimon J. Gerraty 	if (par->p[0] == '\0') {
618dba7b0efSSimon J. Gerraty 		Parse_Error(PARSE_FATAL,
61912904384SSimon J. Gerraty 		    "Missing right-hand side of operator '%s'", opname[op]);
620b0c40a00SSimon J. Gerraty 		par->printedError = true;
621e2eeea75SSimon J. Gerraty 		goto done_lhs;
6223955d011SMarcel Moolenaar 	}
6233955d011SMarcel Moolenaar 
624548bfc56SSimon J. Gerraty 	rhs = CondParser_Leaf(par, doEval, true, &rhsQuoted);
6251d3f2ddcSSimon J. Gerraty 	t = rhs.str == NULL ? TOK_ERROR
6261d3f2ddcSSimon J. Gerraty 	    : !doEval ? TOK_FALSE
6271d3f2ddcSSimon J. Gerraty 	    : EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted);
62806b9b3e0SSimon J. Gerraty 	FStr_Done(&rhs);
6291d3f2ddcSSimon J. Gerraty 
630e2eeea75SSimon J. Gerraty done_lhs:
63106b9b3e0SSimon J. Gerraty 	FStr_Done(&lhs);
6323955d011SMarcel Moolenaar 	return t;
6333955d011SMarcel Moolenaar }
6343955d011SMarcel Moolenaar 
63506b9b3e0SSimon J. Gerraty /*
63606b9b3e0SSimon J. Gerraty  * The argument to empty() is a variable name, optionally followed by
63706b9b3e0SSimon J. Gerraty  * variable modifiers.
63806b9b3e0SSimon J. Gerraty  */
63912904384SSimon J. Gerraty static bool
64012904384SSimon J. Gerraty CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
6413955d011SMarcel Moolenaar {
642d5e0a182SSimon J. Gerraty 	const char *p = par->p;
64312904384SSimon J. Gerraty 	Token tok;
64406b9b3e0SSimon J. Gerraty 	FStr val;
6453955d011SMarcel Moolenaar 
646d5e0a182SSimon J. Gerraty 	if (!skip_string(&p, "empty"))
64712904384SSimon J. Gerraty 		return false;
6483955d011SMarcel Moolenaar 
649d5e0a182SSimon J. Gerraty 	cpp_skip_whitespace(&p);
650d5e0a182SSimon J. Gerraty 	if (*p != '(')
65112904384SSimon J. Gerraty 		return false;
65212904384SSimon J. Gerraty 
653d5e0a182SSimon J. Gerraty 	p--;			/* Make p[1] point to the '('. */
6548d5c8e21SSimon J. Gerraty 	val = Var_Parse(&p, SCOPE_CMDLINE, doEval ? VARE_EVAL : VARE_PARSE);
655956e45f6SSimon J. Gerraty 	/* TODO: handle errors */
6563955d011SMarcel Moolenaar 
65712904384SSimon J. Gerraty 	if (val.str == var_Error)
65812904384SSimon J. Gerraty 		tok = TOK_ERROR;
65912904384SSimon J. Gerraty 	else {
66006b9b3e0SSimon J. Gerraty 		cpp_skip_whitespace(&val.str);
6619f45a3c8SSimon J. Gerraty 		tok = ToToken(doEval && val.str[0] == '\0');
6623955d011SMarcel Moolenaar 	}
6633955d011SMarcel Moolenaar 
66412904384SSimon J. Gerraty 	FStr_Done(&val);
66512904384SSimon J. Gerraty 	*out_token = tok;
666d5e0a182SSimon J. Gerraty 	par->p = p;
66712904384SSimon J. Gerraty 	return true;
6683955d011SMarcel Moolenaar }
6693955d011SMarcel Moolenaar 
6702f2a5ecdSSimon J. Gerraty /* Parse a function call expression, such as 'exists(${file})'. */
671b0c40a00SSimon J. Gerraty static bool
672b0c40a00SSimon J. Gerraty CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token)
6733955d011SMarcel Moolenaar {
6749f45a3c8SSimon J. Gerraty 	char *arg;
6759f45a3c8SSimon J. Gerraty 	const char *p = par->p;
6769f45a3c8SSimon J. Gerraty 	bool (*fn)(const char *);
6779f45a3c8SSimon J. Gerraty 	const char *fn_name = p;
678e2eeea75SSimon J. Gerraty 
6799f45a3c8SSimon J. Gerraty 	if (skip_string(&p, "defined"))
6809f45a3c8SSimon J. Gerraty 		fn = FuncDefined;
6819f45a3c8SSimon J. Gerraty 	else if (skip_string(&p, "make"))
6829f45a3c8SSimon J. Gerraty 		fn = FuncMake;
6839f45a3c8SSimon J. Gerraty 	else if (skip_string(&p, "exists"))
6849f45a3c8SSimon J. Gerraty 		fn = FuncExists;
6859f45a3c8SSimon J. Gerraty 	else if (skip_string(&p, "target"))
6869f45a3c8SSimon J. Gerraty 		fn = FuncTarget;
6879f45a3c8SSimon J. Gerraty 	else if (skip_string(&p, "commands"))
6889f45a3c8SSimon J. Gerraty 		fn = FuncCommands;
6899f45a3c8SSimon J. Gerraty 	else
69012904384SSimon J. Gerraty 		return false;
691e2eeea75SSimon J. Gerraty 
6929f45a3c8SSimon J. Gerraty 	cpp_skip_whitespace(&p);
6939f45a3c8SSimon J. Gerraty 	if (*p != '(')
69412904384SSimon J. Gerraty 		return false;
695e2eeea75SSimon J. Gerraty 
6969f45a3c8SSimon J. Gerraty 	arg = ParseFuncArg(par, &p, doEval, fn_name);
6979f45a3c8SSimon J. Gerraty 	*out_token = ToToken(doEval &&
6989f45a3c8SSimon J. Gerraty 	    arg != NULL && arg[0] != '\0' && fn(arg));
699e2eeea75SSimon J. Gerraty 	free(arg);
7009f45a3c8SSimon J. Gerraty 
7019f45a3c8SSimon J. Gerraty 	par->p = p;
702b0c40a00SSimon J. Gerraty 	return true;
703e2eeea75SSimon J. Gerraty }
704e2eeea75SSimon J. Gerraty 
70506b9b3e0SSimon J. Gerraty /*
70612904384SSimon J. Gerraty  * Parse a comparison that neither starts with '"' nor '$', such as the
70712904384SSimon J. Gerraty  * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without
708d5e0a182SSimon J. Gerraty  * operator, which is a number, an expression or a string literal.
70912904384SSimon J. Gerraty  *
71012904384SSimon J. Gerraty  * TODO: Can this be merged into CondParser_Comparison?
71106b9b3e0SSimon J. Gerraty  */
712e2eeea75SSimon J. Gerraty static Token
713b0c40a00SSimon J. Gerraty CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
714e2eeea75SSimon J. Gerraty {
7153955d011SMarcel Moolenaar 	Token t;
7169f45a3c8SSimon J. Gerraty 	char *arg;
717d5e0a182SSimon J. Gerraty 	const char *p;
7183955d011SMarcel Moolenaar 
719d5e0a182SSimon J. Gerraty 	p = par->p;
720d5e0a182SSimon J. Gerraty 	if (ch_isdigit(p[0]) || p[0] == '-' || p[0] == '+')
721956e45f6SSimon J. Gerraty 		return CondParser_Comparison(par, doEval);
7223955d011SMarcel Moolenaar 
7233955d011SMarcel Moolenaar 	/*
724d5e0a182SSimon J. Gerraty 	 * Most likely we have a bare word to apply the default function to.
725d5e0a182SSimon J. Gerraty 	 * However, ".if a == b" gets here when the "a" is unquoted and
726d5e0a182SSimon J. Gerraty 	 * doesn't start with a '$'. This surprises people.
72706b9b3e0SSimon J. Gerraty 	 * If what follows the function argument is a '=' or '!' then the
72806b9b3e0SSimon J. Gerraty 	 * syntax would be invalid if we did "defined(a)" - so instead treat
72906b9b3e0SSimon J. Gerraty 	 * as an expression.
7303955d011SMarcel Moolenaar 	 */
731b0c40a00SSimon J. Gerraty 	/*
732d5e0a182SSimon J. Gerraty 	 * XXX: In edge cases, an expression may be evaluated twice,
7339f45a3c8SSimon J. Gerraty 	 *  see cond-token-plain.mk, keyword 'twice'.
734b0c40a00SSimon J. Gerraty 	 */
735d5e0a182SSimon J. Gerraty 	arg = ParseWord(&p, doEval);
7369f45a3c8SSimon J. Gerraty 	assert(arg[0] != '\0');
7379f45a3c8SSimon J. Gerraty 
7388d5c8e21SSimon J. Gerraty 	if (*p == '=' || *p == '!' || *p == '<' || *p == '>') {
7398d5c8e21SSimon J. Gerraty 		free(arg);
740956e45f6SSimon J. Gerraty 		return CondParser_Comparison(par, doEval);
7418d5c8e21SSimon J. Gerraty 	}
742d5e0a182SSimon J. Gerraty 	par->p = p;
7433955d011SMarcel Moolenaar 
7443955d011SMarcel Moolenaar 	/*
7453955d011SMarcel Moolenaar 	 * Evaluate the argument using the default function.
746956e45f6SSimon J. Gerraty 	 * This path always treats .if as .ifdef. To get here, the character
7473955d011SMarcel Moolenaar 	 * after .if must have been taken literally, so the argument cannot
748d5e0a182SSimon J. Gerraty 	 * be empty - even if it contained an expression.
7493955d011SMarcel Moolenaar 	 */
7509f45a3c8SSimon J. Gerraty 	t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare);
7513955d011SMarcel Moolenaar 	free(arg);
7523955d011SMarcel Moolenaar 	return t;
7533955d011SMarcel Moolenaar }
7543955d011SMarcel Moolenaar 
755956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */
7563955d011SMarcel Moolenaar static Token
757b0c40a00SSimon J. Gerraty CondParser_Token(CondParser *par, bool doEval)
7583955d011SMarcel Moolenaar {
7593955d011SMarcel Moolenaar 	Token t;
7603955d011SMarcel Moolenaar 
761956e45f6SSimon J. Gerraty 	t = par->curr;
7623955d011SMarcel Moolenaar 	if (t != TOK_NONE) {
763956e45f6SSimon J. Gerraty 		par->curr = TOK_NONE;
7643955d011SMarcel Moolenaar 		return t;
7653955d011SMarcel Moolenaar 	}
7663955d011SMarcel Moolenaar 
767e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&par->p);
7683955d011SMarcel Moolenaar 
769956e45f6SSimon J. Gerraty 	switch (par->p[0]) {
7703955d011SMarcel Moolenaar 
7713955d011SMarcel Moolenaar 	case '(':
772956e45f6SSimon J. Gerraty 		par->p++;
7733955d011SMarcel Moolenaar 		return TOK_LPAREN;
7743955d011SMarcel Moolenaar 
7753955d011SMarcel Moolenaar 	case ')':
776956e45f6SSimon J. Gerraty 		par->p++;
7773955d011SMarcel Moolenaar 		return TOK_RPAREN;
7783955d011SMarcel Moolenaar 
7793955d011SMarcel Moolenaar 	case '|':
780956e45f6SSimon J. Gerraty 		par->p++;
781e2eeea75SSimon J. Gerraty 		if (par->p[0] == '|')
782956e45f6SSimon J. Gerraty 			par->p++;
783*22619282SSimon J. Gerraty 		else {
784e2eeea75SSimon J. Gerraty 			Parse_Error(PARSE_FATAL, "Unknown operator '|'");
785b0c40a00SSimon J. Gerraty 			par->printedError = true;
786e2eeea75SSimon J. Gerraty 			return TOK_ERROR;
7873955d011SMarcel Moolenaar 		}
7883955d011SMarcel Moolenaar 		return TOK_OR;
7893955d011SMarcel Moolenaar 
7903955d011SMarcel Moolenaar 	case '&':
791956e45f6SSimon J. Gerraty 		par->p++;
792e2eeea75SSimon J. Gerraty 		if (par->p[0] == '&')
793956e45f6SSimon J. Gerraty 			par->p++;
794*22619282SSimon J. Gerraty 		else {
795e2eeea75SSimon J. Gerraty 			Parse_Error(PARSE_FATAL, "Unknown operator '&'");
796b0c40a00SSimon J. Gerraty 			par->printedError = true;
797e2eeea75SSimon J. Gerraty 			return TOK_ERROR;
7983955d011SMarcel Moolenaar 		}
7993955d011SMarcel Moolenaar 		return TOK_AND;
8003955d011SMarcel Moolenaar 
8013955d011SMarcel Moolenaar 	case '!':
802956e45f6SSimon J. Gerraty 		par->p++;
8033955d011SMarcel Moolenaar 		return TOK_NOT;
8043955d011SMarcel Moolenaar 
805e2eeea75SSimon J. Gerraty 	case '#':		/* XXX: see unit-tests/cond-token-plain.mk */
806e2eeea75SSimon J. Gerraty 	case '\n':		/* XXX: why should this end the condition? */
807e2eeea75SSimon J. Gerraty 		/* Probably obsolete now, from 1993-03-21. */
8083955d011SMarcel Moolenaar 	case '\0':
8093955d011SMarcel Moolenaar 		return TOK_EOF;
8103955d011SMarcel Moolenaar 
8113955d011SMarcel Moolenaar 	case '"':
8123955d011SMarcel Moolenaar 	case '$':
813956e45f6SSimon J. Gerraty 		return CondParser_Comparison(par, doEval);
8143955d011SMarcel Moolenaar 
8153955d011SMarcel Moolenaar 	default:
81612904384SSimon J. Gerraty 		if (CondParser_FuncCallEmpty(par, doEval, &t))
81712904384SSimon J. Gerraty 			return t;
818b0c40a00SSimon J. Gerraty 		if (CondParser_FuncCall(par, doEval, &t))
819b0c40a00SSimon J. Gerraty 			return t;
820b0c40a00SSimon J. Gerraty 		return CondParser_ComparisonOrLeaf(par, doEval);
8213955d011SMarcel Moolenaar 	}
8223955d011SMarcel Moolenaar }
8233955d011SMarcel Moolenaar 
82412904384SSimon J. Gerraty /* Skip the next token if it equals t. */
82512904384SSimon J. Gerraty static bool
82612904384SSimon J. Gerraty CondParser_Skip(CondParser *par, Token t)
82712904384SSimon J. Gerraty {
82812904384SSimon J. Gerraty 	Token actual;
82912904384SSimon J. Gerraty 
83012904384SSimon J. Gerraty 	actual = CondParser_Token(par, false);
83112904384SSimon J. Gerraty 	if (actual == t)
83212904384SSimon J. Gerraty 		return true;
83312904384SSimon J. Gerraty 
83412904384SSimon J. Gerraty 	assert(par->curr == TOK_NONE);
83512904384SSimon J. Gerraty 	assert(actual != TOK_NONE);
83612904384SSimon J. Gerraty 	par->curr = actual;
83712904384SSimon J. Gerraty 	return false;
83812904384SSimon J. Gerraty }
83912904384SSimon J. Gerraty 
84006b9b3e0SSimon J. Gerraty /*
841dba7b0efSSimon J. Gerraty  * Term -> '(' Or ')'
842dba7b0efSSimon J. Gerraty  * Term -> '!' Term
843dba7b0efSSimon J. Gerraty  * Term -> Leaf Operator Leaf
844dba7b0efSSimon J. Gerraty  * Term -> Leaf
8453955d011SMarcel Moolenaar  */
846dba7b0efSSimon J. Gerraty static CondResult
847b0c40a00SSimon J. Gerraty CondParser_Term(CondParser *par, bool doEval)
8483955d011SMarcel Moolenaar {
849dba7b0efSSimon J. Gerraty 	CondResult res;
8503955d011SMarcel Moolenaar 	Token t;
851c59c3bf3SSimon J. Gerraty 	bool neg = false;
8523955d011SMarcel Moolenaar 
853c59c3bf3SSimon J. Gerraty 	while ((t = CondParser_Token(par, doEval)) == TOK_NOT)
854c59c3bf3SSimon J. Gerraty 		neg = !neg;
855c59c3bf3SSimon J. Gerraty 
856c59c3bf3SSimon J. Gerraty 	if (t == TOK_TRUE || t == TOK_FALSE)
857c59c3bf3SSimon J. Gerraty 		return neg == (t == TOK_FALSE) ? CR_TRUE : CR_FALSE;
8583955d011SMarcel Moolenaar 
859dba7b0efSSimon J. Gerraty 	if (t == TOK_LPAREN) {
860dba7b0efSSimon J. Gerraty 		res = CondParser_Or(par, doEval);
861dba7b0efSSimon J. Gerraty 		if (res == CR_ERROR)
862dba7b0efSSimon J. Gerraty 			return CR_ERROR;
863dba7b0efSSimon J. Gerraty 		if (CondParser_Token(par, doEval) != TOK_RPAREN)
864dba7b0efSSimon J. Gerraty 			return CR_ERROR;
865c59c3bf3SSimon J. Gerraty 		return neg == (res == CR_FALSE) ? CR_TRUE : CR_FALSE;
8663955d011SMarcel Moolenaar 	}
867dba7b0efSSimon J. Gerraty 
868dba7b0efSSimon J. Gerraty 	return CR_ERROR;
8693955d011SMarcel Moolenaar }
8702c3632d1SSimon J. Gerraty 
87106b9b3e0SSimon J. Gerraty /*
87212904384SSimon J. Gerraty  * And -> Term ('&&' Term)*
8733955d011SMarcel Moolenaar  */
874dba7b0efSSimon J. Gerraty static CondResult
875b0c40a00SSimon J. Gerraty CondParser_And(CondParser *par, bool doEval)
8763955d011SMarcel Moolenaar {
87712904384SSimon J. Gerraty 	CondResult res, rhs;
8783955d011SMarcel Moolenaar 
87912904384SSimon J. Gerraty 	res = CR_TRUE;
88012904384SSimon J. Gerraty 	do {
88112904384SSimon J. Gerraty 		if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR)
882dba7b0efSSimon J. Gerraty 			return CR_ERROR;
88312904384SSimon J. Gerraty 		if (rhs == CR_FALSE) {
88412904384SSimon J. Gerraty 			res = CR_FALSE;
88512904384SSimon J. Gerraty 			doEval = false;
8863955d011SMarcel Moolenaar 		}
88712904384SSimon J. Gerraty 	} while (CondParser_Skip(par, TOK_AND));
888dba7b0efSSimon J. Gerraty 
889dba7b0efSSimon J. Gerraty 	return res;
8903955d011SMarcel Moolenaar }
8912c3632d1SSimon J. Gerraty 
89206b9b3e0SSimon J. Gerraty /*
89312904384SSimon J. Gerraty  * Or -> And ('||' And)*
8943955d011SMarcel Moolenaar  */
895dba7b0efSSimon J. Gerraty static CondResult
896b0c40a00SSimon J. Gerraty CondParser_Or(CondParser *par, bool doEval)
8973955d011SMarcel Moolenaar {
89812904384SSimon J. Gerraty 	CondResult res, rhs;
8993955d011SMarcel Moolenaar 
90012904384SSimon J. Gerraty 	res = CR_FALSE;
90112904384SSimon J. Gerraty 	do {
90212904384SSimon J. Gerraty 		if ((rhs = CondParser_And(par, doEval)) == CR_ERROR)
903dba7b0efSSimon J. Gerraty 			return CR_ERROR;
90412904384SSimon J. Gerraty 		if (rhs == CR_TRUE) {
90512904384SSimon J. Gerraty 			res = CR_TRUE;
90612904384SSimon J. Gerraty 			doEval = false;
9073955d011SMarcel Moolenaar 		}
90812904384SSimon J. Gerraty 	} while (CondParser_Skip(par, TOK_OR));
909dba7b0efSSimon J. Gerraty 
910dba7b0efSSimon J. Gerraty 	return res;
9113955d011SMarcel Moolenaar }
9123955d011SMarcel Moolenaar 
91306b9b3e0SSimon J. Gerraty /*
914d5e0a182SSimon J. Gerraty  * Evaluate the condition, including any side effects from the
915956e45f6SSimon J. Gerraty  * expressions in the condition. The condition consists of &&, ||, !,
916956e45f6SSimon J. Gerraty  * function(arg), comparisons and parenthetical groupings thereof.
9173955d011SMarcel Moolenaar  */
9189f45a3c8SSimon J. Gerraty static CondResult
9199f45a3c8SSimon J. Gerraty CondEvalExpression(const char *cond, bool plain,
92012904384SSimon J. Gerraty 		   bool (*evalBare)(const char *), bool negate,
92112904384SSimon J. Gerraty 		   bool eprint, bool leftUnquotedOK)
9223955d011SMarcel Moolenaar {
923956e45f6SSimon J. Gerraty 	CondParser par;
9249f45a3c8SSimon J. Gerraty 	CondResult rval;
9253955d011SMarcel Moolenaar 
926e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&cond);
9273955d011SMarcel Moolenaar 
928dba7b0efSSimon J. Gerraty 	par.plain = plain;
929dba7b0efSSimon J. Gerraty 	par.evalBare = evalBare;
930dba7b0efSSimon J. Gerraty 	par.negateEvalBare = negate;
93112904384SSimon J. Gerraty 	par.leftUnquotedOK = leftUnquotedOK;
932956e45f6SSimon J. Gerraty 	par.p = cond;
933956e45f6SSimon J. Gerraty 	par.curr = TOK_NONE;
934b0c40a00SSimon J. Gerraty 	par.printedError = false;
9353955d011SMarcel Moolenaar 
936c59c3bf3SSimon J. Gerraty 	DEBUG1(COND, "CondParser_Eval: %s\n", par.p);
937c59c3bf3SSimon J. Gerraty 	rval = CondParser_Or(&par, true);
938c59c3bf3SSimon J. Gerraty 	if (par.curr != TOK_EOF)
939c59c3bf3SSimon J. Gerraty 		rval = CR_ERROR;
9403955d011SMarcel Moolenaar 
9419f45a3c8SSimon J. Gerraty 	if (rval == CR_ERROR && eprint && !par.printedError)
942956e45f6SSimon J. Gerraty 		Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond);
9433955d011SMarcel Moolenaar 
9443955d011SMarcel Moolenaar 	return rval;
9453955d011SMarcel Moolenaar }
9463955d011SMarcel Moolenaar 
94706b9b3e0SSimon J. Gerraty /*
94806b9b3e0SSimon J. Gerraty  * Evaluate a condition in a :? modifier, such as
94906b9b3e0SSimon J. Gerraty  * ${"${VAR}" == value:?yes:no}.
95006b9b3e0SSimon J. Gerraty  */
9519f45a3c8SSimon J. Gerraty CondResult
9529f45a3c8SSimon J. Gerraty Cond_EvalCondition(const char *cond)
953956e45f6SSimon J. Gerraty {
9549f45a3c8SSimon J. Gerraty 	return CondEvalExpression(cond, true,
95512904384SSimon J. Gerraty 	    FuncDefined, false, false, true);
956956e45f6SSimon J. Gerraty }
9573955d011SMarcel Moolenaar 
958b0c40a00SSimon J. Gerraty static bool
95906b9b3e0SSimon J. Gerraty IsEndif(const char *p)
96006b9b3e0SSimon J. Gerraty {
96106b9b3e0SSimon J. Gerraty 	return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' &&
96206b9b3e0SSimon J. Gerraty 	       p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]);
96306b9b3e0SSimon J. Gerraty }
96406b9b3e0SSimon J. Gerraty 
965b0c40a00SSimon J. Gerraty static bool
966b0c40a00SSimon J. Gerraty DetermineKindOfConditional(const char **pp, bool *out_plain,
96712904384SSimon J. Gerraty 			   bool (**out_evalBare)(const char *),
968b0c40a00SSimon J. Gerraty 			   bool *out_negate)
969dba7b0efSSimon J. Gerraty {
9709f45a3c8SSimon J. Gerraty 	const char *p = *pp + 2;
971dba7b0efSSimon J. Gerraty 
972b0c40a00SSimon J. Gerraty 	*out_plain = false;
973dba7b0efSSimon J. Gerraty 	*out_evalBare = FuncDefined;
9749f45a3c8SSimon J. Gerraty 	*out_negate = skip_string(&p, "n");
9759f45a3c8SSimon J. Gerraty 
9769f45a3c8SSimon J. Gerraty 	if (skip_string(&p, "def")) {		/* .ifdef and .ifndef */
9779f45a3c8SSimon J. Gerraty 	} else if (skip_string(&p, "make"))	/* .ifmake and .ifnmake */
978dba7b0efSSimon J. Gerraty 		*out_evalBare = FuncMake;
9799f45a3c8SSimon J. Gerraty 	else if (!*out_negate)			/* plain .if */
980b0c40a00SSimon J. Gerraty 		*out_plain = true;
9819f45a3c8SSimon J. Gerraty 	else
9829f45a3c8SSimon J. Gerraty 		goto unknown_directive;
9839f45a3c8SSimon J. Gerraty 	if (ch_isalpha(*p))
9849f45a3c8SSimon J. Gerraty 		goto unknown_directive;
9859f45a3c8SSimon J. Gerraty 
9869f45a3c8SSimon J. Gerraty 	*pp = p;
9879f45a3c8SSimon J. Gerraty 	return true;
9889f45a3c8SSimon J. Gerraty 
9899f45a3c8SSimon J. Gerraty unknown_directive:
990b0c40a00SSimon J. Gerraty 	return false;
991dba7b0efSSimon J. Gerraty }
992dba7b0efSSimon J. Gerraty 
99306b9b3e0SSimon J. Gerraty /*
99406b9b3e0SSimon J. Gerraty  * Evaluate the conditional directive in the line, which is one of:
9953955d011SMarcel Moolenaar  *
996e2eeea75SSimon J. Gerraty  *	.if <cond>
997e2eeea75SSimon J. Gerraty  *	.ifmake <cond>
998e2eeea75SSimon J. Gerraty  *	.ifnmake <cond>
999e2eeea75SSimon J. Gerraty  *	.ifdef <cond>
1000e2eeea75SSimon J. Gerraty  *	.ifndef <cond>
1001e2eeea75SSimon J. Gerraty  *	.elif <cond>
1002e2eeea75SSimon J. Gerraty  *	.elifmake <cond>
1003e2eeea75SSimon J. Gerraty  *	.elifnmake <cond>
1004e2eeea75SSimon J. Gerraty  *	.elifdef <cond>
1005e2eeea75SSimon J. Gerraty  *	.elifndef <cond>
1006e2eeea75SSimon J. Gerraty  *	.else
1007e2eeea75SSimon J. Gerraty  *	.endif
1008e2eeea75SSimon J. Gerraty  *
1009e2eeea75SSimon J. Gerraty  * In these directives, <cond> consists of &&, ||, !, function(arg),
1010e2eeea75SSimon J. Gerraty  * comparisons, expressions, bare words, numbers and strings, and
1011e2eeea75SSimon J. Gerraty  * parenthetical groupings thereof.
1012956e45f6SSimon J. Gerraty  *
1013956e45f6SSimon J. Gerraty  * Results:
10149f45a3c8SSimon J. Gerraty  *	CR_TRUE		to continue parsing the lines that follow the
1015b0c40a00SSimon J. Gerraty  *			conditional (when <cond> evaluates to true)
10169f45a3c8SSimon J. Gerraty  *	CR_FALSE	to skip the lines after the conditional
1017b0c40a00SSimon J. Gerraty  *			(when <cond> evaluates to false, or when a previous
1018d5e0a182SSimon J. Gerraty  *			branch was already taken)
10199f45a3c8SSimon J. Gerraty  *	CR_ERROR	if the conditional was not valid, either because of
1020956e45f6SSimon J. Gerraty  *			a syntax error or because some variable was undefined
1021956e45f6SSimon J. Gerraty  *			or because the condition could not be evaluated
10223955d011SMarcel Moolenaar  */
10239f45a3c8SSimon J. Gerraty CondResult
102406b9b3e0SSimon J. Gerraty Cond_EvalLine(const char *line)
10253955d011SMarcel Moolenaar {
1026e2eeea75SSimon J. Gerraty 	typedef enum IfState {
1027e2eeea75SSimon J. Gerraty 
1028b0c40a00SSimon J. Gerraty 		/* None of the previous <cond> evaluated to true. */
1029e2eeea75SSimon J. Gerraty 		IFS_INITIAL	= 0,
1030e2eeea75SSimon J. Gerraty 
10319f45a3c8SSimon J. Gerraty 		/*
10329f45a3c8SSimon J. Gerraty 		 * The previous <cond> evaluated to true. The lines following
10339f45a3c8SSimon J. Gerraty 		 * this condition are interpreted.
10349f45a3c8SSimon J. Gerraty 		 */
1035e2eeea75SSimon J. Gerraty 		IFS_ACTIVE	= 1 << 0,
1036e2eeea75SSimon J. Gerraty 
1037e2eeea75SSimon J. Gerraty 		/* The previous directive was an '.else'. */
1038e2eeea75SSimon J. Gerraty 		IFS_SEEN_ELSE	= 1 << 1,
1039e2eeea75SSimon J. Gerraty 
1040b0c40a00SSimon J. Gerraty 		/* One of the previous <cond> evaluated to true. */
1041e2eeea75SSimon J. Gerraty 		IFS_WAS_ACTIVE	= 1 << 2
1042e2eeea75SSimon J. Gerraty 
1043e2eeea75SSimon J. Gerraty 	} IfState;
1044e2eeea75SSimon J. Gerraty 
1045e2eeea75SSimon J. Gerraty 	static enum IfState *cond_states = NULL;
1046e2eeea75SSimon J. Gerraty 	static unsigned int cond_states_cap = 128;
10473955d011SMarcel Moolenaar 
1048b0c40a00SSimon J. Gerraty 	bool plain;
104912904384SSimon J. Gerraty 	bool (*evalBare)(const char *);
1050b0c40a00SSimon J. Gerraty 	bool negate;
1051b0c40a00SSimon J. Gerraty 	bool isElif;
10529f45a3c8SSimon J. Gerraty 	CondResult res;
1053e2eeea75SSimon J. Gerraty 	IfState state;
1054e2eeea75SSimon J. Gerraty 	const char *p = line;
10553955d011SMarcel Moolenaar 
1056e2eeea75SSimon J. Gerraty 	if (cond_states == NULL) {
105706b9b3e0SSimon J. Gerraty 		cond_states = bmake_malloc(
105806b9b3e0SSimon J. Gerraty 		    cond_states_cap * sizeof *cond_states);
1059e2eeea75SSimon J. Gerraty 		cond_states[0] = IFS_ACTIVE;
106059a02420SSimon J. Gerraty 	}
10613955d011SMarcel Moolenaar 
1062e2eeea75SSimon J. Gerraty 	p++;			/* skip the leading '.' */
1063e2eeea75SSimon J. Gerraty 	cpp_skip_hspace(&p);
1064e2eeea75SSimon J. Gerraty 
1065d5e0a182SSimon J. Gerraty 	if (IsEndif(p)) {
106606b9b3e0SSimon J. Gerraty 		if (p[5] != '\0') {
106706b9b3e0SSimon J. Gerraty 			Parse_Error(PARSE_FATAL,
106812904384SSimon J. Gerraty 			    "The .endif directive does not take arguments");
1069e2eeea75SSimon J. Gerraty 		}
1070e2eeea75SSimon J. Gerraty 
10714fde40d9SSimon J. Gerraty 		if (cond_depth == CurFile_CondMinDepth()) {
1072956e45f6SSimon J. Gerraty 			Parse_Error(PARSE_FATAL, "if-less endif");
10739f45a3c8SSimon J. Gerraty 			return CR_TRUE;
10743955d011SMarcel Moolenaar 		}
1075e2eeea75SSimon J. Gerraty 
10763955d011SMarcel Moolenaar 		/* Return state for previous conditional */
10773955d011SMarcel Moolenaar 		cond_depth--;
1078148ee845SSimon J. Gerraty 		Parse_GuardEndif();
1079e2eeea75SSimon J. Gerraty 		return cond_states[cond_depth] & IFS_ACTIVE
10809f45a3c8SSimon J. Gerraty 		    ? CR_TRUE : CR_FALSE;
10813955d011SMarcel Moolenaar 	}
10823955d011SMarcel Moolenaar 
108306b9b3e0SSimon J. Gerraty 	/* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
108406b9b3e0SSimon J. Gerraty 	if (p[0] == 'e') {
1085d5e0a182SSimon J. Gerraty 		if (p[1] != 'l')
10869f45a3c8SSimon J. Gerraty 			return CR_ERROR;
108706b9b3e0SSimon J. Gerraty 
10883955d011SMarcel Moolenaar 		/* Quite likely this is 'else' or 'elif' */
1089e2eeea75SSimon J. Gerraty 		p += 2;
10909f45a3c8SSimon J. Gerraty 		if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) {
109106b9b3e0SSimon J. Gerraty 			if (p[2] != '\0')
1092e2eeea75SSimon J. Gerraty 				Parse_Error(PARSE_FATAL,
109306b9b3e0SSimon J. Gerraty 				    "The .else directive "
109412904384SSimon J. Gerraty 				    "does not take arguments");
1095e2eeea75SSimon J. Gerraty 
10964fde40d9SSimon J. Gerraty 			if (cond_depth == CurFile_CondMinDepth()) {
1097956e45f6SSimon J. Gerraty 				Parse_Error(PARSE_FATAL, "if-less else");
10989f45a3c8SSimon J. Gerraty 				return CR_TRUE;
10993955d011SMarcel Moolenaar 			}
1100148ee845SSimon J. Gerraty 			Parse_GuardElse();
11013955d011SMarcel Moolenaar 
1102e2eeea75SSimon J. Gerraty 			state = cond_states[cond_depth];
1103e2eeea75SSimon J. Gerraty 			if (state == IFS_INITIAL) {
1104e2eeea75SSimon J. Gerraty 				state = IFS_ACTIVE | IFS_SEEN_ELSE;
1105e2eeea75SSimon J. Gerraty 			} else {
1106e2eeea75SSimon J. Gerraty 				if (state & IFS_SEEN_ELSE)
110706b9b3e0SSimon J. Gerraty 					Parse_Error(PARSE_WARNING,
110806b9b3e0SSimon J. Gerraty 					    "extra else");
1109e2eeea75SSimon J. Gerraty 				state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
11103955d011SMarcel Moolenaar 			}
1111e2eeea75SSimon J. Gerraty 			cond_states[cond_depth] = state;
1112e2eeea75SSimon J. Gerraty 
11139f45a3c8SSimon J. Gerraty 			return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE;
11143955d011SMarcel Moolenaar 		}
11153955d011SMarcel Moolenaar 		/* Assume for now it is an elif */
1116b0c40a00SSimon J. Gerraty 		isElif = true;
11173955d011SMarcel Moolenaar 	} else
1118b0c40a00SSimon J. Gerraty 		isElif = false;
11193955d011SMarcel Moolenaar 
1120d5e0a182SSimon J. Gerraty 	if (p[0] != 'i' || p[1] != 'f')
1121d5e0a182SSimon J. Gerraty 		return CR_ERROR;
11223955d011SMarcel Moolenaar 
1123dba7b0efSSimon J. Gerraty 	if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate))
11249f45a3c8SSimon J. Gerraty 		return CR_ERROR;
11253955d011SMarcel Moolenaar 
11263955d011SMarcel Moolenaar 	if (isElif) {
11274fde40d9SSimon J. Gerraty 		if (cond_depth == CurFile_CondMinDepth()) {
1128956e45f6SSimon J. Gerraty 			Parse_Error(PARSE_FATAL, "if-less elif");
11299f45a3c8SSimon J. Gerraty 			return CR_TRUE;
11303955d011SMarcel Moolenaar 		}
1131148ee845SSimon J. Gerraty 		Parse_GuardElse();
1132e2eeea75SSimon J. Gerraty 		state = cond_states[cond_depth];
1133e2eeea75SSimon J. Gerraty 		if (state & IFS_SEEN_ELSE) {
11343955d011SMarcel Moolenaar 			Parse_Error(PARSE_WARNING, "extra elif");
113506b9b3e0SSimon J. Gerraty 			cond_states[cond_depth] =
113606b9b3e0SSimon J. Gerraty 			    IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
11379f45a3c8SSimon J. Gerraty 			return CR_FALSE;
11383955d011SMarcel Moolenaar 		}
1139e2eeea75SSimon J. Gerraty 		if (state != IFS_INITIAL) {
1140e2eeea75SSimon J. Gerraty 			cond_states[cond_depth] = IFS_WAS_ACTIVE;
11419f45a3c8SSimon J. Gerraty 			return CR_FALSE;
11423955d011SMarcel Moolenaar 		}
11433955d011SMarcel Moolenaar 	} else {
11443955d011SMarcel Moolenaar 		/* Normal .if */
1145e2eeea75SSimon J. Gerraty 		if (cond_depth + 1 >= cond_states_cap) {
114659a02420SSimon J. Gerraty 			/*
114759a02420SSimon J. Gerraty 			 * This is rare, but not impossible.
114859a02420SSimon J. Gerraty 			 * In meta mode, dirdeps.mk (only runs at level 0)
114959a02420SSimon J. Gerraty 			 * can need more than the default.
115059a02420SSimon J. Gerraty 			 */
1151e2eeea75SSimon J. Gerraty 			cond_states_cap += 32;
1152e2eeea75SSimon J. Gerraty 			cond_states = bmake_realloc(cond_states,
11539f45a3c8SSimon J. Gerraty 			    cond_states_cap * sizeof *cond_states);
11543955d011SMarcel Moolenaar 		}
1155e2eeea75SSimon J. Gerraty 		state = cond_states[cond_depth];
11563955d011SMarcel Moolenaar 		cond_depth++;
1157e2eeea75SSimon J. Gerraty 		if (!(state & IFS_ACTIVE)) {
1158e2eeea75SSimon J. Gerraty 			cond_states[cond_depth] = IFS_WAS_ACTIVE;
11599f45a3c8SSimon J. Gerraty 			return CR_FALSE;
11603955d011SMarcel Moolenaar 		}
11613955d011SMarcel Moolenaar 	}
11623955d011SMarcel Moolenaar 
11639f45a3c8SSimon J. Gerraty 	res = CondEvalExpression(p, plain, evalBare, negate, true, false);
11649f45a3c8SSimon J. Gerraty 	if (res == CR_ERROR) {
11659f45a3c8SSimon J. Gerraty 		/* Syntax error, error message already output. */
11669f45a3c8SSimon J. Gerraty 		/* Skip everything to the matching '.endif'. */
11679f45a3c8SSimon J. Gerraty 		/* An extra '.else' is not detected in this case. */
1168e2eeea75SSimon J. Gerraty 		cond_states[cond_depth] = IFS_WAS_ACTIVE;
11699f45a3c8SSimon J. Gerraty 		return CR_FALSE;
11703955d011SMarcel Moolenaar 	}
11713955d011SMarcel Moolenaar 
11729f45a3c8SSimon J. Gerraty 	cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL;
11739f45a3c8SSimon J. Gerraty 	return res;
11743955d011SMarcel Moolenaar }
11753955d011SMarcel Moolenaar 
1176148ee845SSimon J. Gerraty static bool
1177148ee845SSimon J. Gerraty ParseVarnameGuard(const char **pp, const char **varname)
1178148ee845SSimon J. Gerraty {
1179148ee845SSimon J. Gerraty 	const char *p = *pp;
1180148ee845SSimon J. Gerraty 
1181148ee845SSimon J. Gerraty 	if (ch_isalpha(*p) || *p == '_') {
1182148ee845SSimon J. Gerraty 		while (ch_isalnum(*p) || *p == '_')
1183148ee845SSimon J. Gerraty 			p++;
1184148ee845SSimon J. Gerraty 		*varname = *pp;
1185148ee845SSimon J. Gerraty 		*pp = p;
1186148ee845SSimon J. Gerraty 		return true;
1187148ee845SSimon J. Gerraty 	}
1188148ee845SSimon J. Gerraty 	return false;
1189148ee845SSimon J. Gerraty }
1190148ee845SSimon J. Gerraty 
1191148ee845SSimon J. Gerraty /* Extracts the multiple-inclusion guard from a conditional, if any. */
1192148ee845SSimon J. Gerraty Guard *
1193148ee845SSimon J. Gerraty Cond_ExtractGuard(const char *line)
1194148ee845SSimon J. Gerraty {
1195148ee845SSimon J. Gerraty 	const char *p, *varname;
1196148ee845SSimon J. Gerraty 	Substring dir;
1197148ee845SSimon J. Gerraty 	Guard *guard;
1198148ee845SSimon J. Gerraty 
1199148ee845SSimon J. Gerraty 	p = line + 1;		/* skip the '.' */
1200148ee845SSimon J. Gerraty 	cpp_skip_hspace(&p);
1201148ee845SSimon J. Gerraty 
1202148ee845SSimon J. Gerraty 	dir.start = p;
1203148ee845SSimon J. Gerraty 	while (ch_isalpha(*p))
1204148ee845SSimon J. Gerraty 		p++;
1205148ee845SSimon J. Gerraty 	dir.end = p;
1206148ee845SSimon J. Gerraty 	cpp_skip_hspace(&p);
1207148ee845SSimon J. Gerraty 
1208148ee845SSimon J. Gerraty 	if (Substring_Equals(dir, "if")) {
1209148ee845SSimon J. Gerraty 		if (skip_string(&p, "!defined(")) {
1210148ee845SSimon J. Gerraty 			if (ParseVarnameGuard(&p, &varname)
1211148ee845SSimon J. Gerraty 			    && strcmp(p, ")") == 0)
1212148ee845SSimon J. Gerraty 				goto found_variable;
1213148ee845SSimon J. Gerraty 		} else if (skip_string(&p, "!target(")) {
1214148ee845SSimon J. Gerraty 			const char *arg_p = p;
1215148ee845SSimon J. Gerraty 			free(ParseWord(&p, false));
1216148ee845SSimon J. Gerraty 			if (strcmp(p, ")") == 0) {
1217148ee845SSimon J. Gerraty 				guard = bmake_malloc(sizeof(*guard));
1218148ee845SSimon J. Gerraty 				guard->kind = GK_TARGET;
121998875883SSimon J. Gerraty 				guard->name = ParseWord(&arg_p, true);
1220148ee845SSimon J. Gerraty 				return guard;
1221148ee845SSimon J. Gerraty 			}
1222148ee845SSimon J. Gerraty 		}
1223148ee845SSimon J. Gerraty 	} else if (Substring_Equals(dir, "ifndef")) {
1224148ee845SSimon J. Gerraty 		if (ParseVarnameGuard(&p, &varname) && *p == '\0')
1225148ee845SSimon J. Gerraty 			goto found_variable;
1226148ee845SSimon J. Gerraty 	}
1227148ee845SSimon J. Gerraty 	return NULL;
1228148ee845SSimon J. Gerraty 
1229148ee845SSimon J. Gerraty found_variable:
1230148ee845SSimon J. Gerraty 	guard = bmake_malloc(sizeof(*guard));
123198875883SSimon J. Gerraty 	guard->kind = GK_VARIABLE;
1232148ee845SSimon J. Gerraty 	guard->name = bmake_strsedup(varname, p);
1233148ee845SSimon J. Gerraty 	return guard;
1234148ee845SSimon J. Gerraty }
1235148ee845SSimon J. Gerraty 
12363955d011SMarcel Moolenaar void
12374fde40d9SSimon J. Gerraty Cond_EndFile(void)
12383955d011SMarcel Moolenaar {
12394fde40d9SSimon J. Gerraty 	unsigned int open_conds = cond_depth - CurFile_CondMinDepth();
12403955d011SMarcel Moolenaar 
12414fde40d9SSimon J. Gerraty 	if (open_conds != 0) {
124206b9b3e0SSimon J. Gerraty 		Parse_Error(PARSE_FATAL, "%u open conditional%s",
124306b9b3e0SSimon J. Gerraty 		    open_conds, open_conds == 1 ? "" : "s");
12444fde40d9SSimon J. Gerraty 		cond_depth = CurFile_CondMinDepth();
12453955d011SMarcel Moolenaar 	}
12463955d011SMarcel Moolenaar }
1247