1*2f2a5ecdSSimon J. Gerraty /* $NetBSD: cond.c,v 1.334 2022/04/15 09:33:20 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*2f2a5ecdSSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.334 2022/04/15 09:33:20 rillig Exp $"); 99956e45f6SSimon J. Gerraty 1003955d011SMarcel Moolenaar /* 1019f45a3c8SSimon J. Gerraty * Conditional expressions conform to this grammar: 10212904384SSimon J. Gerraty * Or -> And ('||' And)* 10312904384SSimon 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 1129f45a3c8SSimon J. Gerraty * Leaf -> BareWord 113dba7b0efSSimon J. Gerraty * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' 1143955d011SMarcel Moolenaar * 1159f45a3c8SSimon J. Gerraty * BareWord is an unquoted string literal, its evaluation depends on the kind 1169f45a3c8SSimon J. Gerraty * of '.if' directive. 1173955d011SMarcel Moolenaar * 1189f45a3c8SSimon J. Gerraty * The tokens are scanned by CondParser_Token, 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 1269f45a3c8SSimon J. Gerraty * the function given in the terminal, they return either TOK_TRUE, TOK_FALSE 1279f45a3c8SSimon J. Gerraty * or TOK_ERROR. 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 ComparisonOp { 135dba7b0efSSimon J. Gerraty LT, LE, GT, GE, EQ, NE 136dba7b0efSSimon J. Gerraty } ComparisonOp; 137dba7b0efSSimon J. Gerraty 138956e45f6SSimon J. Gerraty typedef struct CondParser { 139dba7b0efSSimon J. Gerraty 140dba7b0efSSimon J. Gerraty /* 141dba7b0efSSimon J. Gerraty * The plain '.if ${VAR}' evaluates to true if the value of the 142dba7b0efSSimon J. Gerraty * expression has length > 0. The other '.if' variants delegate 1431d3f2ddcSSimon J. Gerraty * to evalBare instead, for example '.ifdef ${VAR}' is equivalent to 1441d3f2ddcSSimon J. Gerraty * '.if defined(${VAR})', checking whether the variable named by the 1451d3f2ddcSSimon J. Gerraty * expression '${VAR}' is defined. 146dba7b0efSSimon J. Gerraty */ 147b0c40a00SSimon J. Gerraty bool plain; 148dba7b0efSSimon J. Gerraty 149dba7b0efSSimon J. Gerraty /* The function to apply on unquoted bare words. */ 15012904384SSimon J. Gerraty bool (*evalBare)(const char *); 151b0c40a00SSimon J. Gerraty bool negateEvalBare; 152dba7b0efSSimon J. Gerraty 15312904384SSimon J. Gerraty /* 15412904384SSimon J. Gerraty * Whether the left-hand side of a comparison may be an unquoted 15512904384SSimon J. Gerraty * string. This is allowed for expressions of the form 15612904384SSimon J. Gerraty * ${condition:?:}, see ApplyModifier_IfElse. Such a condition is 15712904384SSimon J. Gerraty * expanded before it is evaluated, due to ease of implementation. 15812904384SSimon J. Gerraty * This means that at the point where the condition is evaluated, 15912904384SSimon J. Gerraty * make cannot know anymore whether the left-hand side had originally 16012904384SSimon J. Gerraty * been a variable expression or a plain word. 16112904384SSimon J. Gerraty * 1621d3f2ddcSSimon J. Gerraty * In conditional directives like '.if', the left-hand side must 1631d3f2ddcSSimon J. Gerraty * either be a variable expression, a quoted string or a number. 16412904384SSimon J. Gerraty */ 16512904384SSimon J. Gerraty bool leftUnquotedOK; 16612904384SSimon J. Gerraty 167956e45f6SSimon J. Gerraty const char *p; /* The remaining condition to parse */ 168956e45f6SSimon J. Gerraty Token curr; /* Single push-back token used in parsing */ 1693955d011SMarcel Moolenaar 17006b9b3e0SSimon J. Gerraty /* 17106b9b3e0SSimon J. Gerraty * Whether an error message has already been printed for this 17206b9b3e0SSimon J. Gerraty * condition. The first available error message is usually the most 17306b9b3e0SSimon J. Gerraty * specific one, therefore it makes sense to suppress the standard 17406b9b3e0SSimon J. Gerraty * "Malformed conditional" message. 17506b9b3e0SSimon J. Gerraty */ 176b0c40a00SSimon J. Gerraty bool printedError; 177956e45f6SSimon J. Gerraty } CondParser; 178956e45f6SSimon J. Gerraty 179b0c40a00SSimon J. Gerraty static CondResult CondParser_Or(CondParser *par, bool); 1803955d011SMarcel Moolenaar 1813955d011SMarcel Moolenaar static unsigned int cond_depth = 0; /* current .if nesting level */ 1823955d011SMarcel Moolenaar static unsigned int cond_min_depth = 0; /* depth at makefile open */ 1833955d011SMarcel Moolenaar 18412904384SSimon J. Gerraty /* Names for ComparisonOp. */ 18512904384SSimon J. Gerraty static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" }; 18628a6bc81SSimon J. Gerraty 1879f45a3c8SSimon J. Gerraty MAKE_INLINE bool 1889f45a3c8SSimon J. Gerraty skip_string(const char **pp, const char *str) 1893955d011SMarcel Moolenaar { 1909f45a3c8SSimon J. Gerraty size_t len = strlen(str); 1919f45a3c8SSimon J. Gerraty bool ok = strncmp(*pp, str, len) == 0; 1929f45a3c8SSimon J. Gerraty if (ok) 1939f45a3c8SSimon J. Gerraty *pp += len; 1949f45a3c8SSimon J. Gerraty return ok; 1953955d011SMarcel Moolenaar } 1963955d011SMarcel Moolenaar 197e2eeea75SSimon J. Gerraty static Token 198b0c40a00SSimon J. Gerraty ToToken(bool cond) 199e2eeea75SSimon J. Gerraty { 200e2eeea75SSimon J. Gerraty return cond ? TOK_TRUE : TOK_FALSE; 201e2eeea75SSimon J. Gerraty } 202e2eeea75SSimon J. Gerraty 203956e45f6SSimon J. Gerraty static void 204956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(CondParser *par) 2053955d011SMarcel Moolenaar { 206956e45f6SSimon J. Gerraty cpp_skip_whitespace(&par->p); 207956e45f6SSimon J. Gerraty } 208956e45f6SSimon J. Gerraty 20906b9b3e0SSimon J. Gerraty /* 21012904384SSimon J. Gerraty * Parse a single word, taking into account balanced parentheses as well as 21112904384SSimon J. Gerraty * embedded expressions. Used for the argument of a built-in function as 21212904384SSimon J. Gerraty * well as for bare words, which are then passed to the default function. 21306b9b3e0SSimon J. Gerraty */ 2149f45a3c8SSimon J. Gerraty static char * 2159f45a3c8SSimon J. Gerraty ParseWord(const char **pp, bool doEval) 21606b9b3e0SSimon J. Gerraty { 217956e45f6SSimon J. Gerraty const char *p = *pp; 2181d3f2ddcSSimon J. Gerraty Buffer word; 2193955d011SMarcel Moolenaar int paren_depth; 2203955d011SMarcel Moolenaar 2211d3f2ddcSSimon J. Gerraty Buf_InitSize(&word, 16); 2223955d011SMarcel Moolenaar 2233955d011SMarcel Moolenaar paren_depth = 0; 2243955d011SMarcel Moolenaar for (;;) { 225956e45f6SSimon J. Gerraty char ch = *p; 226e2eeea75SSimon J. Gerraty if (ch == '\0' || ch == ' ' || ch == '\t') 2273955d011SMarcel Moolenaar break; 2283955d011SMarcel Moolenaar if ((ch == '&' || ch == '|') && paren_depth == 0) 2293955d011SMarcel Moolenaar break; 2309f45a3c8SSimon J. Gerraty if (ch == '$') { 2313955d011SMarcel Moolenaar /* 23206b9b3e0SSimon J. Gerraty * Parse the variable expression and install it as 23306b9b3e0SSimon J. Gerraty * part of the argument if it's valid. We tell 23406b9b3e0SSimon J. Gerraty * Var_Parse to complain on an undefined variable, 23506b9b3e0SSimon J. Gerraty * (XXX: but Var_Parse ignores that request) 23606b9b3e0SSimon J. Gerraty * so we don't need to do it. Nor do we return an 23706b9b3e0SSimon J. Gerraty * error, though perhaps we should. 2383955d011SMarcel Moolenaar */ 239b0c40a00SSimon J. Gerraty VarEvalMode emode = doEval 240b0c40a00SSimon J. Gerraty ? VARE_UNDEFERR 241b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 24206b9b3e0SSimon J. Gerraty FStr nestedVal; 243b0c40a00SSimon J. Gerraty (void)Var_Parse(&p, SCOPE_CMDLINE, emode, &nestedVal); 244956e45f6SSimon J. Gerraty /* TODO: handle errors */ 2451d3f2ddcSSimon J. Gerraty Buf_AddStr(&word, nestedVal.str); 24606b9b3e0SSimon J. Gerraty FStr_Done(&nestedVal); 2473955d011SMarcel Moolenaar continue; 2483955d011SMarcel Moolenaar } 2493955d011SMarcel Moolenaar if (ch == '(') 2503955d011SMarcel Moolenaar paren_depth++; 2512c3632d1SSimon J. Gerraty else if (ch == ')' && --paren_depth < 0) 2523955d011SMarcel Moolenaar break; 2531d3f2ddcSSimon J. Gerraty Buf_AddByte(&word, ch); 254956e45f6SSimon J. Gerraty p++; 2553955d011SMarcel Moolenaar } 2563955d011SMarcel Moolenaar 2579f45a3c8SSimon J. Gerraty cpp_skip_hspace(&p); 2589f45a3c8SSimon J. Gerraty *pp = p; 2593955d011SMarcel Moolenaar 2601d3f2ddcSSimon J. Gerraty return Buf_DoneData(&word); 2619f45a3c8SSimon J. Gerraty } 2629f45a3c8SSimon J. Gerraty 2639f45a3c8SSimon J. Gerraty /* Parse the function argument, including the surrounding parentheses. */ 2649f45a3c8SSimon J. Gerraty static char * 2659f45a3c8SSimon J. Gerraty ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) 2669f45a3c8SSimon J. Gerraty { 2679f45a3c8SSimon J. Gerraty const char *p = *pp; 2689f45a3c8SSimon J. Gerraty char *res; 2699f45a3c8SSimon J. Gerraty 2709f45a3c8SSimon J. Gerraty p++; /* Skip opening '(' - verified by caller */ 2719f45a3c8SSimon J. Gerraty cpp_skip_hspace(&p); 2729f45a3c8SSimon J. Gerraty res = ParseWord(&p, doEval); 273e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2743955d011SMarcel Moolenaar 2759f45a3c8SSimon J. Gerraty if (*p++ != ')') { 2769f45a3c8SSimon J. Gerraty int len = 0; 2779f45a3c8SSimon J. Gerraty while (ch_isalpha(func[len])) 2789f45a3c8SSimon J. Gerraty len++; 2799f45a3c8SSimon J. Gerraty 280dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 2819f45a3c8SSimon J. Gerraty "Missing closing parenthesis for %.*s()", len, func); 282b0c40a00SSimon J. Gerraty par->printedError = true; 2839f45a3c8SSimon J. Gerraty free(res); 2849f45a3c8SSimon J. Gerraty return NULL; 2853955d011SMarcel Moolenaar } 2863955d011SMarcel Moolenaar 287956e45f6SSimon J. Gerraty *pp = p; 2889f45a3c8SSimon J. Gerraty return res; 2893955d011SMarcel Moolenaar } 2902c3632d1SSimon J. Gerraty 2911d3f2ddcSSimon J. Gerraty /* See if the given variable is defined. */ 292b0c40a00SSimon J. Gerraty static bool 2939f45a3c8SSimon J. Gerraty FuncDefined(const char *var) 2943955d011SMarcel Moolenaar { 2959f45a3c8SSimon J. Gerraty return Var_Exists(SCOPE_CMDLINE, var); 2963955d011SMarcel Moolenaar } 2972c3632d1SSimon J. Gerraty 2981d3f2ddcSSimon J. Gerraty /* See if a target matching targetPattern is requested to be made. */ 299b0c40a00SSimon J. Gerraty static bool 3001d3f2ddcSSimon J. Gerraty FuncMake(const char *targetPattern) 3013955d011SMarcel Moolenaar { 302956e45f6SSimon J. Gerraty StringListNode *ln; 303956e45f6SSimon J. Gerraty 30406b9b3e0SSimon J. Gerraty for (ln = opts.create.first; ln != NULL; ln = ln->next) 3051d3f2ddcSSimon J. Gerraty if (Str_Match(ln->datum, targetPattern)) 306b0c40a00SSimon J. Gerraty return true; 307b0c40a00SSimon J. Gerraty return false; 3083955d011SMarcel Moolenaar } 3092c3632d1SSimon J. Gerraty 3102c3632d1SSimon J. Gerraty /* See if the given file exists. */ 311b0c40a00SSimon J. Gerraty static bool 3129f45a3c8SSimon J. Gerraty FuncExists(const char *file) 3133955d011SMarcel Moolenaar { 314b0c40a00SSimon J. Gerraty bool result; 3153955d011SMarcel Moolenaar char *path; 3163955d011SMarcel Moolenaar 3179f45a3c8SSimon J. Gerraty path = Dir_FindFile(file, &dirSearchPath); 318e2eeea75SSimon J. Gerraty DEBUG2(COND, "exists(%s) result is \"%s\"\n", 3199f45a3c8SSimon J. Gerraty file, path != NULL ? path : ""); 320e2eeea75SSimon J. Gerraty result = path != NULL; 3213955d011SMarcel Moolenaar free(path); 3223841c287SSimon J. Gerraty return result; 3233955d011SMarcel Moolenaar } 3242c3632d1SSimon J. Gerraty 3252c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target. */ 326b0c40a00SSimon J. Gerraty static bool 3279f45a3c8SSimon J. Gerraty FuncTarget(const char *node) 3283955d011SMarcel Moolenaar { 3299f45a3c8SSimon J. Gerraty GNode *gn = Targ_FindNode(node); 330956e45f6SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn); 3313955d011SMarcel Moolenaar } 3323955d011SMarcel Moolenaar 33306b9b3e0SSimon J. Gerraty /* 33406b9b3e0SSimon J. Gerraty * See if the given node exists and is an actual target with commands 33506b9b3e0SSimon J. Gerraty * associated with it. 33606b9b3e0SSimon J. Gerraty */ 337b0c40a00SSimon J. Gerraty static bool 3389f45a3c8SSimon J. Gerraty FuncCommands(const char *node) 3393955d011SMarcel Moolenaar { 3409f45a3c8SSimon J. Gerraty GNode *gn = Targ_FindNode(node); 3419f45a3c8SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn) && 3429f45a3c8SSimon J. Gerraty !Lst_IsEmpty(&gn->commands); 3433955d011SMarcel Moolenaar } 3442c3632d1SSimon J. Gerraty 345e2eeea75SSimon J. Gerraty /* 3469f45a3c8SSimon J. Gerraty * Convert the string into a floating-point number. Accepted formats are 3479f45a3c8SSimon J. Gerraty * base-10 integer, base-16 integer and finite floating point numbers. 3483955d011SMarcel Moolenaar */ 349b0c40a00SSimon J. Gerraty static bool 350e2eeea75SSimon J. Gerraty TryParseNumber(const char *str, double *out_value) 3513955d011SMarcel Moolenaar { 352e2eeea75SSimon J. Gerraty char *end; 353e2eeea75SSimon J. Gerraty unsigned long ul_val; 354e2eeea75SSimon J. Gerraty double dbl_val; 3553955d011SMarcel Moolenaar 356e2eeea75SSimon J. Gerraty if (str[0] == '\0') { /* XXX: why is an empty string a number? */ 357e2eeea75SSimon J. Gerraty *out_value = 0.0; 358b0c40a00SSimon J. Gerraty return true; 359ac3446e9SSimon J. Gerraty } 360e2eeea75SSimon J. Gerraty 36112904384SSimon J. Gerraty errno = 0; 362e2eeea75SSimon J. Gerraty ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); 363e2eeea75SSimon J. Gerraty if (*end == '\0' && errno != ERANGE) { 364e2eeea75SSimon J. Gerraty *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; 365b0c40a00SSimon J. Gerraty return true; 3663955d011SMarcel Moolenaar } 3673955d011SMarcel Moolenaar 368e2eeea75SSimon J. Gerraty if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') 369b0c40a00SSimon J. Gerraty return false; /* skip the expensive strtod call */ 370e2eeea75SSimon J. Gerraty dbl_val = strtod(str, &end); 371e2eeea75SSimon J. Gerraty if (*end != '\0') 372b0c40a00SSimon J. Gerraty return false; 373e2eeea75SSimon J. Gerraty 374e2eeea75SSimon J. Gerraty *out_value = dbl_val; 375b0c40a00SSimon J. Gerraty return true; 3763955d011SMarcel Moolenaar } 3773955d011SMarcel Moolenaar 378b0c40a00SSimon J. Gerraty static bool 379956e45f6SSimon J. Gerraty is_separator(char ch) 380956e45f6SSimon J. Gerraty { 381b0c40a00SSimon J. Gerraty return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' || 382b0c40a00SSimon J. Gerraty ch == '>' || ch == '<' || ch == ')' /* but not '(' */; 383956e45f6SSimon J. Gerraty } 384956e45f6SSimon J. Gerraty 385dba7b0efSSimon J. Gerraty /* 386dba7b0efSSimon J. Gerraty * In a quoted or unquoted string literal or a number, parse a variable 387dba7b0efSSimon J. Gerraty * expression. 388dba7b0efSSimon J. Gerraty * 389dba7b0efSSimon J. Gerraty * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} 390dba7b0efSSimon J. Gerraty */ 391b0c40a00SSimon J. Gerraty static bool 392dba7b0efSSimon J. Gerraty CondParser_StringExpr(CondParser *par, const char *start, 39312904384SSimon J. Gerraty bool doEval, bool quoted, 39412904384SSimon J. Gerraty Buffer *buf, FStr *inout_str) 395dba7b0efSSimon J. Gerraty { 396b0c40a00SSimon J. Gerraty VarEvalMode emode; 3979f45a3c8SSimon J. Gerraty const char *p; 398b0c40a00SSimon J. Gerraty bool atStart; 399dba7b0efSSimon J. Gerraty VarParseResult parseResult; 400dba7b0efSSimon J. Gerraty 40112904384SSimon J. Gerraty emode = doEval && quoted ? VARE_WANTRES 40212904384SSimon J. Gerraty : doEval ? VARE_UNDEFERR 403b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 404dba7b0efSSimon J. Gerraty 4059f45a3c8SSimon J. Gerraty p = par->p; 4069f45a3c8SSimon J. Gerraty atStart = p == start; 4079f45a3c8SSimon J. Gerraty parseResult = Var_Parse(&p, SCOPE_CMDLINE, emode, inout_str); 408dba7b0efSSimon J. Gerraty /* TODO: handle errors */ 409dba7b0efSSimon J. Gerraty if (inout_str->str == var_Error) { 410dba7b0efSSimon J. Gerraty if (parseResult == VPR_ERR) { 411dba7b0efSSimon J. Gerraty /* 412dba7b0efSSimon J. Gerraty * FIXME: Even if an error occurs, there is no 413dba7b0efSSimon J. Gerraty * guarantee that it is reported. 414dba7b0efSSimon J. Gerraty * 415dba7b0efSSimon J. Gerraty * See cond-token-plain.mk $$$$$$$$. 416dba7b0efSSimon J. Gerraty */ 417b0c40a00SSimon J. Gerraty par->printedError = true; 418dba7b0efSSimon J. Gerraty } 419dba7b0efSSimon J. Gerraty /* 420dba7b0efSSimon J. Gerraty * XXX: Can there be any situation in which a returned 421b0c40a00SSimon J. Gerraty * var_Error needs to be freed? 422dba7b0efSSimon J. Gerraty */ 423dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 424dba7b0efSSimon J. Gerraty /* 425dba7b0efSSimon J. Gerraty * Even if !doEval, we still report syntax errors, which is 426dba7b0efSSimon J. Gerraty * what getting var_Error back with !doEval means. 427dba7b0efSSimon J. Gerraty */ 428dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); 429b0c40a00SSimon J. Gerraty return false; 430dba7b0efSSimon J. Gerraty } 4319f45a3c8SSimon J. Gerraty par->p = p; 432dba7b0efSSimon J. Gerraty 433dba7b0efSSimon J. Gerraty /* 434dba7b0efSSimon J. Gerraty * If the '$' started the string literal (which means no quotes), and 435dba7b0efSSimon J. Gerraty * the variable expression is followed by a space, looks like a 436dba7b0efSSimon J. Gerraty * comparison operator or is the end of the expression, we are done. 437dba7b0efSSimon J. Gerraty */ 438dba7b0efSSimon J. Gerraty if (atStart && is_separator(par->p[0])) 439b0c40a00SSimon J. Gerraty return false; 440dba7b0efSSimon J. Gerraty 441dba7b0efSSimon J. Gerraty Buf_AddStr(buf, inout_str->str); 442dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 443dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); /* not finished yet */ 444b0c40a00SSimon J. Gerraty return true; 445dba7b0efSSimon J. Gerraty } 446dba7b0efSSimon J. Gerraty 447dba7b0efSSimon J. Gerraty /* 4489f45a3c8SSimon J. Gerraty * Parse a string from a variable expression or an optionally quoted string, 4499f45a3c8SSimon J. Gerraty * on the left-hand and right-hand sides of comparisons. 4503955d011SMarcel Moolenaar * 4513955d011SMarcel Moolenaar * Results: 4529f45a3c8SSimon J. Gerraty * Returns the string without any enclosing quotes, or NULL on error. 453b0c40a00SSimon J. Gerraty * Sets out_quoted if the leaf was a quoted string literal. 4543955d011SMarcel Moolenaar */ 45506b9b3e0SSimon J. Gerraty static void 45612904384SSimon J. Gerraty CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, 457b0c40a00SSimon J. Gerraty FStr *out_str, bool *out_quoted) 4583955d011SMarcel Moolenaar { 4593955d011SMarcel Moolenaar Buffer buf; 46006b9b3e0SSimon J. Gerraty FStr str; 461b0c40a00SSimon J. Gerraty bool quoted; 4622c3632d1SSimon J. Gerraty const char *start; 4633955d011SMarcel Moolenaar 464e2eeea75SSimon J. Gerraty Buf_Init(&buf); 46506b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 466e2eeea75SSimon J. Gerraty *out_quoted = quoted = par->p[0] == '"'; 467956e45f6SSimon J. Gerraty start = par->p; 468e2eeea75SSimon J. Gerraty if (quoted) 469956e45f6SSimon J. Gerraty par->p++; 47006b9b3e0SSimon J. Gerraty 47106b9b3e0SSimon J. Gerraty while (par->p[0] != '\0' && str.str == NULL) { 472956e45f6SSimon J. Gerraty switch (par->p[0]) { 4733955d011SMarcel Moolenaar case '\\': 474956e45f6SSimon J. Gerraty par->p++; 475956e45f6SSimon J. Gerraty if (par->p[0] != '\0') { 476956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 477956e45f6SSimon J. Gerraty par->p++; 4783955d011SMarcel Moolenaar } 479956e45f6SSimon J. Gerraty continue; 4803955d011SMarcel Moolenaar case '"': 481956e45f6SSimon J. Gerraty par->p++; 482dba7b0efSSimon J. Gerraty if (quoted) 4839f45a3c8SSimon J. Gerraty goto return_buf; /* skip the closing quote */ 484dba7b0efSSimon J. Gerraty Buf_AddByte(&buf, '"'); 485956e45f6SSimon J. Gerraty continue; 486e2eeea75SSimon J. Gerraty case ')': /* see is_separator */ 4873955d011SMarcel Moolenaar case '!': 4883955d011SMarcel Moolenaar case '=': 4893955d011SMarcel Moolenaar case '>': 4903955d011SMarcel Moolenaar case '<': 4913955d011SMarcel Moolenaar case ' ': 4923955d011SMarcel Moolenaar case '\t': 493e2eeea75SSimon J. Gerraty if (!quoted) 4949f45a3c8SSimon J. Gerraty goto return_buf; 495956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 496956e45f6SSimon J. Gerraty par->p++; 497956e45f6SSimon J. Gerraty continue; 4983955d011SMarcel Moolenaar case '$': 499dba7b0efSSimon J. Gerraty if (!CondParser_StringExpr(par, 500dba7b0efSSimon J. Gerraty start, doEval, quoted, &buf, &str)) 5019f45a3c8SSimon J. Gerraty goto return_str; 502956e45f6SSimon J. Gerraty continue; 5033955d011SMarcel Moolenaar default: 50412904384SSimon J. Gerraty if (!unquotedOK && !quoted && *start != '$' && 50506b9b3e0SSimon J. Gerraty !ch_isdigit(*start)) { 50606b9b3e0SSimon J. Gerraty /* 50706b9b3e0SSimon J. Gerraty * The left-hand side must be quoted, 50812904384SSimon J. Gerraty * a variable expression or a number. 50906b9b3e0SSimon J. Gerraty */ 51006b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 5119f45a3c8SSimon J. Gerraty goto return_str; 51228a6bc81SSimon J. Gerraty } 513956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 514956e45f6SSimon J. Gerraty par->p++; 515956e45f6SSimon J. Gerraty continue; 5163955d011SMarcel Moolenaar } 5173955d011SMarcel Moolenaar } 5189f45a3c8SSimon J. Gerraty return_buf: 519dba7b0efSSimon J. Gerraty str = FStr_InitOwn(buf.data); 52012904384SSimon J. Gerraty buf.data = NULL; 5219f45a3c8SSimon J. Gerraty return_str: 52212904384SSimon J. Gerraty Buf_Done(&buf); 52306b9b3e0SSimon J. Gerraty *out_str = str; 5243955d011SMarcel Moolenaar } 5252c3632d1SSimon J. Gerraty 52606b9b3e0SSimon J. Gerraty /* 52706b9b3e0SSimon J. Gerraty * Evaluate a "comparison without operator", such as in ".if ${VAR}" or 52806b9b3e0SSimon J. Gerraty * ".if 0". 52906b9b3e0SSimon J. Gerraty */ 530b0c40a00SSimon J. Gerraty static bool 531b0c40a00SSimon J. Gerraty EvalNotEmpty(CondParser *par, const char *value, bool quoted) 5323955d011SMarcel Moolenaar { 533e2eeea75SSimon J. Gerraty double num; 534956e45f6SSimon J. Gerraty 535e2eeea75SSimon J. Gerraty /* For .ifxxx "...", check for non-empty string. */ 536e2eeea75SSimon J. Gerraty if (quoted) 537e2eeea75SSimon J. Gerraty return value[0] != '\0'; 538956e45f6SSimon J. Gerraty 539e2eeea75SSimon J. Gerraty /* For .ifxxx <number>, compare against zero */ 540e2eeea75SSimon J. Gerraty if (TryParseNumber(value, &num)) 541e2eeea75SSimon J. Gerraty return num != 0.0; 542956e45f6SSimon J. Gerraty 5439f45a3c8SSimon J. Gerraty /* 5449f45a3c8SSimon J. Gerraty * For .if ${...}, check for non-empty string. This is different 5459f45a3c8SSimon J. Gerraty * from the evaluation function from that .if variant, which would 5469f45a3c8SSimon J. Gerraty * test whether a variable of the given name were defined. 5479f45a3c8SSimon J. Gerraty */ 54812904384SSimon J. Gerraty /* 54912904384SSimon J. Gerraty * XXX: Whitespace should count as empty, just as in 55012904384SSimon J. Gerraty * CondParser_FuncCallEmpty. 55112904384SSimon J. Gerraty */ 552dba7b0efSSimon J. Gerraty if (par->plain) 553e2eeea75SSimon J. Gerraty return value[0] != '\0'; 554956e45f6SSimon J. Gerraty 5559f45a3c8SSimon J. Gerraty return par->evalBare(value) != par->negateEvalBare; 556956e45f6SSimon J. Gerraty } 557956e45f6SSimon J. Gerraty 558956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ 559b0c40a00SSimon J. Gerraty static bool 560dba7b0efSSimon J. Gerraty EvalCompareNum(double lhs, ComparisonOp op, double rhs) 561956e45f6SSimon J. Gerraty { 5621d3f2ddcSSimon J. Gerraty DEBUG3(COND, "Comparing %f %s %f\n", lhs, opname[op], rhs); 563956e45f6SSimon J. Gerraty 564dba7b0efSSimon J. Gerraty switch (op) { 565dba7b0efSSimon J. Gerraty case LT: 566dba7b0efSSimon J. Gerraty return lhs < rhs; 567dba7b0efSSimon J. Gerraty case LE: 568dba7b0efSSimon J. Gerraty return lhs <= rhs; 569dba7b0efSSimon J. Gerraty case GT: 570dba7b0efSSimon J. Gerraty return lhs > rhs; 571dba7b0efSSimon J. Gerraty case GE: 572dba7b0efSSimon J. Gerraty return lhs >= rhs; 573dba7b0efSSimon J. Gerraty case NE: 574dba7b0efSSimon J. Gerraty return lhs != rhs; 575dba7b0efSSimon J. Gerraty default: 576dba7b0efSSimon J. Gerraty return lhs == rhs; 577956e45f6SSimon J. Gerraty } 578956e45f6SSimon J. Gerraty } 579956e45f6SSimon J. Gerraty 580956e45f6SSimon J. Gerraty static Token 581dba7b0efSSimon J. Gerraty EvalCompareStr(CondParser *par, const char *lhs, 582dba7b0efSSimon J. Gerraty ComparisonOp op, const char *rhs) 583956e45f6SSimon J. Gerraty { 584dba7b0efSSimon J. Gerraty if (op != EQ && op != NE) { 585dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 586dba7b0efSSimon J. Gerraty "String comparison operator must be either == or !="); 587b0c40a00SSimon J. Gerraty par->printedError = true; 588956e45f6SSimon J. Gerraty return TOK_ERROR; 589956e45f6SSimon J. Gerraty } 590956e45f6SSimon J. Gerraty 5911d3f2ddcSSimon J. Gerraty DEBUG3(COND, "Comparing \"%s\" %s \"%s\"\n", lhs, opname[op], rhs); 592dba7b0efSSimon J. Gerraty return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); 593956e45f6SSimon J. Gerraty } 594956e45f6SSimon J. Gerraty 595956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */ 596956e45f6SSimon J. Gerraty static Token 597b0c40a00SSimon J. Gerraty EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, 598b0c40a00SSimon J. Gerraty ComparisonOp op, const char *rhs, bool rhsQuoted) 599956e45f6SSimon J. Gerraty { 6003955d011SMarcel Moolenaar double left, right; 6013955d011SMarcel Moolenaar 602956e45f6SSimon J. Gerraty if (!rhsQuoted && !lhsQuoted) 603956e45f6SSimon J. Gerraty if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) 604dba7b0efSSimon J. Gerraty return ToToken(EvalCompareNum(left, op, right)); 605956e45f6SSimon J. Gerraty 606dba7b0efSSimon J. Gerraty return EvalCompareStr(par, lhs, op, rhs); 607dba7b0efSSimon J. Gerraty } 608dba7b0efSSimon J. Gerraty 609b0c40a00SSimon J. Gerraty static bool 610dba7b0efSSimon J. Gerraty CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) 611dba7b0efSSimon J. Gerraty { 612dba7b0efSSimon J. Gerraty const char *p = par->p; 613dba7b0efSSimon J. Gerraty 6149f45a3c8SSimon J. Gerraty if (p[0] == '<' && p[1] == '=') 6159f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = LE, true; 6169f45a3c8SSimon J. Gerraty if (p[0] == '<') 6179f45a3c8SSimon J. Gerraty return par->p += 1, *out_op = LT, true; 6189f45a3c8SSimon J. Gerraty if (p[0] == '>' && p[1] == '=') 6199f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = GE, true; 6209f45a3c8SSimon J. Gerraty if (p[0] == '>') 6219f45a3c8SSimon J. Gerraty return par->p += 1, *out_op = GT, true; 6229f45a3c8SSimon J. Gerraty if (p[0] == '=' && p[1] == '=') 6239f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = EQ, true; 6249f45a3c8SSimon J. Gerraty if (p[0] == '!' && p[1] == '=') 6259f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = NE, true; 626b0c40a00SSimon J. Gerraty return false; 627956e45f6SSimon J. Gerraty } 628956e45f6SSimon J. Gerraty 62906b9b3e0SSimon J. Gerraty /* 63006b9b3e0SSimon J. Gerraty * Parse a comparison condition such as: 631956e45f6SSimon J. Gerraty * 632956e45f6SSimon J. Gerraty * 0 633956e45f6SSimon J. Gerraty * ${VAR:Mpattern} 634956e45f6SSimon J. Gerraty * ${VAR} == value 635956e45f6SSimon J. Gerraty * ${VAR:U0} < 12345 636956e45f6SSimon J. Gerraty */ 637956e45f6SSimon J. Gerraty static Token 638b0c40a00SSimon J. Gerraty CondParser_Comparison(CondParser *par, bool doEval) 639956e45f6SSimon J. Gerraty { 640956e45f6SSimon J. Gerraty Token t = TOK_ERROR; 64106b9b3e0SSimon J. Gerraty FStr lhs, rhs; 642dba7b0efSSimon J. Gerraty ComparisonOp op; 643b0c40a00SSimon J. Gerraty bool lhsQuoted, rhsQuoted; 644956e45f6SSimon J. Gerraty 64512904384SSimon J. Gerraty CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhs, &lhsQuoted); 64606b9b3e0SSimon J. Gerraty if (lhs.str == NULL) 647e2eeea75SSimon J. Gerraty goto done_lhs; 6483955d011SMarcel Moolenaar 649956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6503955d011SMarcel Moolenaar 651dba7b0efSSimon J. Gerraty if (!CondParser_ComparisonOp(par, &op)) { 652e2eeea75SSimon J. Gerraty /* Unknown operator, compare against an empty string or 0. */ 65306b9b3e0SSimon J. Gerraty t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); 654e2eeea75SSimon J. Gerraty goto done_lhs; 6553955d011SMarcel Moolenaar } 6563955d011SMarcel Moolenaar 657956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6583955d011SMarcel Moolenaar 659956e45f6SSimon J. Gerraty if (par->p[0] == '\0') { 660dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 66112904384SSimon J. Gerraty "Missing right-hand side of operator '%s'", opname[op]); 662b0c40a00SSimon J. Gerraty par->printedError = true; 663e2eeea75SSimon J. Gerraty goto done_lhs; 6643955d011SMarcel Moolenaar } 6653955d011SMarcel Moolenaar 66612904384SSimon J. Gerraty CondParser_Leaf(par, doEval, true, &rhs, &rhsQuoted); 6671d3f2ddcSSimon J. Gerraty t = rhs.str == NULL ? TOK_ERROR 6681d3f2ddcSSimon J. Gerraty : !doEval ? TOK_FALSE 6691d3f2ddcSSimon J. Gerraty : EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); 67006b9b3e0SSimon J. Gerraty FStr_Done(&rhs); 6711d3f2ddcSSimon J. Gerraty 672e2eeea75SSimon J. Gerraty done_lhs: 67306b9b3e0SSimon J. Gerraty FStr_Done(&lhs); 6743955d011SMarcel Moolenaar return t; 6753955d011SMarcel Moolenaar } 6763955d011SMarcel Moolenaar 67706b9b3e0SSimon J. Gerraty /* 67806b9b3e0SSimon J. Gerraty * The argument to empty() is a variable name, optionally followed by 67906b9b3e0SSimon J. Gerraty * variable modifiers. 68006b9b3e0SSimon J. Gerraty */ 68112904384SSimon J. Gerraty static bool 68212904384SSimon J. Gerraty CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) 6833955d011SMarcel Moolenaar { 68412904384SSimon J. Gerraty const char *cp = par->p; 68512904384SSimon J. Gerraty Token tok; 68606b9b3e0SSimon J. Gerraty FStr val; 6873955d011SMarcel Moolenaar 6889f45a3c8SSimon J. Gerraty if (!skip_string(&cp, "empty")) 68912904384SSimon J. Gerraty return false; 6903955d011SMarcel Moolenaar 69112904384SSimon J. Gerraty cpp_skip_whitespace(&cp); 69212904384SSimon J. Gerraty if (*cp != '(') 69312904384SSimon J. Gerraty return false; 69412904384SSimon J. Gerraty 69512904384SSimon J. Gerraty cp--; /* Make cp[1] point to the '('. */ 69612904384SSimon J. Gerraty (void)Var_Parse(&cp, SCOPE_CMDLINE, 697b0c40a00SSimon J. Gerraty doEval ? VARE_WANTRES : VARE_PARSE_ONLY, &val); 698956e45f6SSimon J. Gerraty /* TODO: handle errors */ 6993955d011SMarcel Moolenaar 70012904384SSimon J. Gerraty if (val.str == var_Error) 70112904384SSimon J. Gerraty tok = TOK_ERROR; 70212904384SSimon J. Gerraty else { 70306b9b3e0SSimon J. Gerraty cpp_skip_whitespace(&val.str); 7049f45a3c8SSimon J. Gerraty tok = ToToken(doEval && val.str[0] == '\0'); 7053955d011SMarcel Moolenaar } 7063955d011SMarcel Moolenaar 70712904384SSimon J. Gerraty FStr_Done(&val); 70812904384SSimon J. Gerraty *out_token = tok; 70912904384SSimon J. Gerraty par->p = cp; 71012904384SSimon J. Gerraty return true; 7113955d011SMarcel Moolenaar } 7123955d011SMarcel Moolenaar 713*2f2a5ecdSSimon J. Gerraty /* Parse a function call expression, such as 'exists(${file})'. */ 714b0c40a00SSimon J. Gerraty static bool 715b0c40a00SSimon J. Gerraty CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) 7163955d011SMarcel Moolenaar { 7179f45a3c8SSimon J. Gerraty char *arg; 7189f45a3c8SSimon J. Gerraty const char *p = par->p; 7199f45a3c8SSimon J. Gerraty bool (*fn)(const char *); 7209f45a3c8SSimon J. Gerraty const char *fn_name = p; 721e2eeea75SSimon J. Gerraty 7229f45a3c8SSimon J. Gerraty if (skip_string(&p, "defined")) 7239f45a3c8SSimon J. Gerraty fn = FuncDefined; 7249f45a3c8SSimon J. Gerraty else if (skip_string(&p, "make")) 7259f45a3c8SSimon J. Gerraty fn = FuncMake; 7269f45a3c8SSimon J. Gerraty else if (skip_string(&p, "exists")) 7279f45a3c8SSimon J. Gerraty fn = FuncExists; 7289f45a3c8SSimon J. Gerraty else if (skip_string(&p, "target")) 7299f45a3c8SSimon J. Gerraty fn = FuncTarget; 7309f45a3c8SSimon J. Gerraty else if (skip_string(&p, "commands")) 7319f45a3c8SSimon J. Gerraty fn = FuncCommands; 7329f45a3c8SSimon J. Gerraty else 73312904384SSimon J. Gerraty return false; 734e2eeea75SSimon J. Gerraty 7359f45a3c8SSimon J. Gerraty cpp_skip_whitespace(&p); 7369f45a3c8SSimon J. Gerraty if (*p != '(') 73712904384SSimon J. Gerraty return false; 738e2eeea75SSimon J. Gerraty 7399f45a3c8SSimon J. Gerraty arg = ParseFuncArg(par, &p, doEval, fn_name); 7409f45a3c8SSimon J. Gerraty *out_token = ToToken(doEval && 7419f45a3c8SSimon J. Gerraty arg != NULL && arg[0] != '\0' && fn(arg)); 742e2eeea75SSimon J. Gerraty free(arg); 7439f45a3c8SSimon J. Gerraty 7449f45a3c8SSimon J. Gerraty par->p = p; 745b0c40a00SSimon J. Gerraty return true; 746e2eeea75SSimon J. Gerraty } 747e2eeea75SSimon J. Gerraty 74806b9b3e0SSimon J. Gerraty /* 74912904384SSimon J. Gerraty * Parse a comparison that neither starts with '"' nor '$', such as the 75012904384SSimon J. Gerraty * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without 751b0c40a00SSimon J. Gerraty * operator, which is a number, a variable expression or a string literal. 75212904384SSimon J. Gerraty * 75312904384SSimon J. Gerraty * TODO: Can this be merged into CondParser_Comparison? 75406b9b3e0SSimon J. Gerraty */ 755e2eeea75SSimon J. Gerraty static Token 756b0c40a00SSimon J. Gerraty CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) 757e2eeea75SSimon J. Gerraty { 7583955d011SMarcel Moolenaar Token t; 7599f45a3c8SSimon J. Gerraty char *arg; 76006b9b3e0SSimon J. Gerraty const char *cp; 7613955d011SMarcel Moolenaar 7623955d011SMarcel Moolenaar /* Push anything numeric through the compare expression */ 763956e45f6SSimon J. Gerraty cp = par->p; 764e2eeea75SSimon J. Gerraty if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') 765956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 7663955d011SMarcel Moolenaar 7673955d011SMarcel Moolenaar /* 7683955d011SMarcel Moolenaar * Most likely we have a naked token to apply the default function to. 7693955d011SMarcel Moolenaar * However ".if a == b" gets here when the "a" is unquoted and doesn't 7703955d011SMarcel Moolenaar * start with a '$'. This surprises people. 77106b9b3e0SSimon J. Gerraty * If what follows the function argument is a '=' or '!' then the 77206b9b3e0SSimon J. Gerraty * syntax would be invalid if we did "defined(a)" - so instead treat 77306b9b3e0SSimon J. Gerraty * as an expression. 7743955d011SMarcel Moolenaar */ 775b0c40a00SSimon J. Gerraty /* 7769f45a3c8SSimon J. Gerraty * XXX: In edge cases, a variable expression may be evaluated twice, 7779f45a3c8SSimon J. Gerraty * see cond-token-plain.mk, keyword 'twice'. 778b0c40a00SSimon J. Gerraty */ 7799f45a3c8SSimon J. Gerraty arg = ParseWord(&cp, doEval); 7809f45a3c8SSimon J. Gerraty assert(arg[0] != '\0'); 7819f45a3c8SSimon J. Gerraty 7829f45a3c8SSimon J. Gerraty if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>') 783956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 784956e45f6SSimon J. Gerraty par->p = cp; 7853955d011SMarcel Moolenaar 7863955d011SMarcel Moolenaar /* 7873955d011SMarcel Moolenaar * Evaluate the argument using the default function. 788956e45f6SSimon J. Gerraty * This path always treats .if as .ifdef. To get here, the character 7893955d011SMarcel Moolenaar * after .if must have been taken literally, so the argument cannot 7903955d011SMarcel Moolenaar * be empty - even if it contained a variable expansion. 7913955d011SMarcel Moolenaar */ 7929f45a3c8SSimon J. Gerraty t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); 7933955d011SMarcel Moolenaar free(arg); 7943955d011SMarcel Moolenaar return t; 7953955d011SMarcel Moolenaar } 7963955d011SMarcel Moolenaar 797956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */ 7983955d011SMarcel Moolenaar static Token 799b0c40a00SSimon J. Gerraty CondParser_Token(CondParser *par, bool doEval) 8003955d011SMarcel Moolenaar { 8013955d011SMarcel Moolenaar Token t; 8023955d011SMarcel Moolenaar 803956e45f6SSimon J. Gerraty t = par->curr; 8043955d011SMarcel Moolenaar if (t != TOK_NONE) { 805956e45f6SSimon J. Gerraty par->curr = TOK_NONE; 8063955d011SMarcel Moolenaar return t; 8073955d011SMarcel Moolenaar } 8083955d011SMarcel Moolenaar 809e2eeea75SSimon J. Gerraty cpp_skip_hspace(&par->p); 8103955d011SMarcel Moolenaar 811956e45f6SSimon J. Gerraty switch (par->p[0]) { 8123955d011SMarcel Moolenaar 8133955d011SMarcel Moolenaar case '(': 814956e45f6SSimon J. Gerraty par->p++; 8153955d011SMarcel Moolenaar return TOK_LPAREN; 8163955d011SMarcel Moolenaar 8173955d011SMarcel Moolenaar case ')': 818956e45f6SSimon J. Gerraty par->p++; 8193955d011SMarcel Moolenaar return TOK_RPAREN; 8203955d011SMarcel Moolenaar 8213955d011SMarcel Moolenaar case '|': 822956e45f6SSimon J. Gerraty par->p++; 823e2eeea75SSimon J. Gerraty if (par->p[0] == '|') 824956e45f6SSimon J. Gerraty par->p++; 82506b9b3e0SSimon J. Gerraty else if (opts.strict) { 826e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '|'"); 827b0c40a00SSimon J. Gerraty par->printedError = true; 828e2eeea75SSimon J. Gerraty return TOK_ERROR; 8293955d011SMarcel Moolenaar } 8303955d011SMarcel Moolenaar return TOK_OR; 8313955d011SMarcel Moolenaar 8323955d011SMarcel Moolenaar case '&': 833956e45f6SSimon J. Gerraty par->p++; 834e2eeea75SSimon J. Gerraty if (par->p[0] == '&') 835956e45f6SSimon J. Gerraty par->p++; 83606b9b3e0SSimon J. Gerraty else if (opts.strict) { 837e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '&'"); 838b0c40a00SSimon J. Gerraty par->printedError = true; 839e2eeea75SSimon J. Gerraty return TOK_ERROR; 8403955d011SMarcel Moolenaar } 8413955d011SMarcel Moolenaar return TOK_AND; 8423955d011SMarcel Moolenaar 8433955d011SMarcel Moolenaar case '!': 844956e45f6SSimon J. Gerraty par->p++; 8453955d011SMarcel Moolenaar return TOK_NOT; 8463955d011SMarcel Moolenaar 847e2eeea75SSimon J. Gerraty case '#': /* XXX: see unit-tests/cond-token-plain.mk */ 848e2eeea75SSimon J. Gerraty case '\n': /* XXX: why should this end the condition? */ 849e2eeea75SSimon J. Gerraty /* Probably obsolete now, from 1993-03-21. */ 8503955d011SMarcel Moolenaar case '\0': 8513955d011SMarcel Moolenaar return TOK_EOF; 8523955d011SMarcel Moolenaar 8533955d011SMarcel Moolenaar case '"': 8543955d011SMarcel Moolenaar case '$': 855956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 8563955d011SMarcel Moolenaar 8573955d011SMarcel Moolenaar default: 85812904384SSimon J. Gerraty if (CondParser_FuncCallEmpty(par, doEval, &t)) 85912904384SSimon J. Gerraty return t; 860b0c40a00SSimon J. Gerraty if (CondParser_FuncCall(par, doEval, &t)) 861b0c40a00SSimon J. Gerraty return t; 862b0c40a00SSimon J. Gerraty return CondParser_ComparisonOrLeaf(par, doEval); 8633955d011SMarcel Moolenaar } 8643955d011SMarcel Moolenaar } 8653955d011SMarcel Moolenaar 86612904384SSimon J. Gerraty /* Skip the next token if it equals t. */ 86712904384SSimon J. Gerraty static bool 86812904384SSimon J. Gerraty CondParser_Skip(CondParser *par, Token t) 86912904384SSimon J. Gerraty { 87012904384SSimon J. Gerraty Token actual; 87112904384SSimon J. Gerraty 87212904384SSimon J. Gerraty actual = CondParser_Token(par, false); 87312904384SSimon J. Gerraty if (actual == t) 87412904384SSimon J. Gerraty return true; 87512904384SSimon J. Gerraty 87612904384SSimon J. Gerraty assert(par->curr == TOK_NONE); 87712904384SSimon J. Gerraty assert(actual != TOK_NONE); 87812904384SSimon J. Gerraty par->curr = actual; 87912904384SSimon J. Gerraty return false; 88012904384SSimon J. Gerraty } 88112904384SSimon J. Gerraty 88206b9b3e0SSimon J. Gerraty /* 883dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 884dba7b0efSSimon J. Gerraty * Term -> '!' Term 885dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 886dba7b0efSSimon J. Gerraty * Term -> Leaf 8873955d011SMarcel Moolenaar */ 888dba7b0efSSimon J. Gerraty static CondResult 889b0c40a00SSimon J. Gerraty CondParser_Term(CondParser *par, bool doEval) 8903955d011SMarcel Moolenaar { 891dba7b0efSSimon J. Gerraty CondResult res; 8923955d011SMarcel Moolenaar Token t; 8933955d011SMarcel Moolenaar 894956e45f6SSimon J. Gerraty t = CondParser_Token(par, doEval); 895dba7b0efSSimon J. Gerraty if (t == TOK_TRUE) 896dba7b0efSSimon J. Gerraty return CR_TRUE; 897dba7b0efSSimon J. Gerraty if (t == TOK_FALSE) 898dba7b0efSSimon J. Gerraty return CR_FALSE; 8993955d011SMarcel Moolenaar 900dba7b0efSSimon J. Gerraty if (t == TOK_LPAREN) { 901dba7b0efSSimon J. Gerraty res = CondParser_Or(par, doEval); 902dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 903dba7b0efSSimon J. Gerraty return CR_ERROR; 904dba7b0efSSimon J. Gerraty if (CondParser_Token(par, doEval) != TOK_RPAREN) 905dba7b0efSSimon J. Gerraty return CR_ERROR; 906dba7b0efSSimon J. Gerraty return res; 9073955d011SMarcel Moolenaar } 908dba7b0efSSimon J. Gerraty 909dba7b0efSSimon J. Gerraty if (t == TOK_NOT) { 910dba7b0efSSimon J. Gerraty res = CondParser_Term(par, doEval); 911dba7b0efSSimon J. Gerraty if (res == CR_TRUE) 912dba7b0efSSimon J. Gerraty res = CR_FALSE; 913dba7b0efSSimon J. Gerraty else if (res == CR_FALSE) 914dba7b0efSSimon J. Gerraty res = CR_TRUE; 915dba7b0efSSimon J. Gerraty return res; 9163955d011SMarcel Moolenaar } 917dba7b0efSSimon J. Gerraty 918dba7b0efSSimon J. Gerraty return CR_ERROR; 9193955d011SMarcel Moolenaar } 9202c3632d1SSimon J. Gerraty 92106b9b3e0SSimon J. Gerraty /* 92212904384SSimon J. Gerraty * And -> Term ('&&' Term)* 9233955d011SMarcel Moolenaar */ 924dba7b0efSSimon J. Gerraty static CondResult 925b0c40a00SSimon J. Gerraty CondParser_And(CondParser *par, bool doEval) 9263955d011SMarcel Moolenaar { 92712904384SSimon J. Gerraty CondResult res, rhs; 9283955d011SMarcel Moolenaar 92912904384SSimon J. Gerraty res = CR_TRUE; 93012904384SSimon J. Gerraty do { 93112904384SSimon J. Gerraty if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR) 932dba7b0efSSimon J. Gerraty return CR_ERROR; 93312904384SSimon J. Gerraty if (rhs == CR_FALSE) { 93412904384SSimon J. Gerraty res = CR_FALSE; 93512904384SSimon J. Gerraty doEval = false; 9363955d011SMarcel Moolenaar } 93712904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_AND)); 938dba7b0efSSimon J. Gerraty 939dba7b0efSSimon J. Gerraty return res; 9403955d011SMarcel Moolenaar } 9412c3632d1SSimon J. Gerraty 94206b9b3e0SSimon J. Gerraty /* 94312904384SSimon J. Gerraty * Or -> And ('||' And)* 9443955d011SMarcel Moolenaar */ 945dba7b0efSSimon J. Gerraty static CondResult 946b0c40a00SSimon J. Gerraty CondParser_Or(CondParser *par, bool doEval) 9473955d011SMarcel Moolenaar { 94812904384SSimon J. Gerraty CondResult res, rhs; 9493955d011SMarcel Moolenaar 95012904384SSimon J. Gerraty res = CR_FALSE; 95112904384SSimon J. Gerraty do { 95212904384SSimon J. Gerraty if ((rhs = CondParser_And(par, doEval)) == CR_ERROR) 953dba7b0efSSimon J. Gerraty return CR_ERROR; 95412904384SSimon J. Gerraty if (rhs == CR_TRUE) { 95512904384SSimon J. Gerraty res = CR_TRUE; 95612904384SSimon J. Gerraty doEval = false; 9573955d011SMarcel Moolenaar } 95812904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_OR)); 959dba7b0efSSimon J. Gerraty 960dba7b0efSSimon J. Gerraty return res; 9613955d011SMarcel Moolenaar } 9623955d011SMarcel Moolenaar 9639f45a3c8SSimon J. Gerraty static CondResult 9649f45a3c8SSimon J. Gerraty CondParser_Eval(CondParser *par) 9652c3632d1SSimon J. Gerraty { 966dba7b0efSSimon J. Gerraty CondResult res; 9672c3632d1SSimon J. Gerraty 968956e45f6SSimon J. Gerraty DEBUG1(COND, "CondParser_Eval: %s\n", par->p); 9692c3632d1SSimon J. Gerraty 970b0c40a00SSimon J. Gerraty res = CondParser_Or(par, true); 9719f45a3c8SSimon J. Gerraty if (res != CR_ERROR && CondParser_Token(par, false) != TOK_EOF) 9729f45a3c8SSimon J. Gerraty return CR_ERROR; 973956e45f6SSimon J. Gerraty 9749f45a3c8SSimon J. Gerraty return res; 9752c3632d1SSimon J. Gerraty } 9762c3632d1SSimon J. Gerraty 97706b9b3e0SSimon J. Gerraty /* 97806b9b3e0SSimon J. Gerraty * Evaluate the condition, including any side effects from the variable 979956e45f6SSimon J. Gerraty * expressions in the condition. The condition consists of &&, ||, !, 980956e45f6SSimon J. Gerraty * function(arg), comparisons and parenthetical groupings thereof. 9813955d011SMarcel Moolenaar */ 9829f45a3c8SSimon J. Gerraty static CondResult 9839f45a3c8SSimon J. Gerraty CondEvalExpression(const char *cond, bool plain, 98412904384SSimon J. Gerraty bool (*evalBare)(const char *), bool negate, 98512904384SSimon J. Gerraty bool eprint, bool leftUnquotedOK) 9863955d011SMarcel Moolenaar { 987956e45f6SSimon J. Gerraty CondParser par; 9889f45a3c8SSimon J. Gerraty CondResult rval; 9893955d011SMarcel Moolenaar 990e2eeea75SSimon J. Gerraty cpp_skip_hspace(&cond); 9913955d011SMarcel Moolenaar 992dba7b0efSSimon J. Gerraty par.plain = plain; 993dba7b0efSSimon J. Gerraty par.evalBare = evalBare; 994dba7b0efSSimon J. Gerraty par.negateEvalBare = negate; 99512904384SSimon J. Gerraty par.leftUnquotedOK = leftUnquotedOK; 996956e45f6SSimon J. Gerraty par.p = cond; 997956e45f6SSimon J. Gerraty par.curr = TOK_NONE; 998b0c40a00SSimon J. Gerraty par.printedError = false; 9993955d011SMarcel Moolenaar 10009f45a3c8SSimon J. Gerraty rval = CondParser_Eval(&par); 10013955d011SMarcel Moolenaar 10029f45a3c8SSimon J. Gerraty if (rval == CR_ERROR && eprint && !par.printedError) 1003956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); 10043955d011SMarcel Moolenaar 10053955d011SMarcel Moolenaar return rval; 10063955d011SMarcel Moolenaar } 10073955d011SMarcel Moolenaar 100806b9b3e0SSimon J. Gerraty /* 100906b9b3e0SSimon J. Gerraty * Evaluate a condition in a :? modifier, such as 101006b9b3e0SSimon J. Gerraty * ${"${VAR}" == value:?yes:no}. 101106b9b3e0SSimon J. Gerraty */ 10129f45a3c8SSimon J. Gerraty CondResult 10139f45a3c8SSimon J. Gerraty Cond_EvalCondition(const char *cond) 1014956e45f6SSimon J. Gerraty { 10159f45a3c8SSimon J. Gerraty return CondEvalExpression(cond, true, 101612904384SSimon J. Gerraty FuncDefined, false, false, true); 1017956e45f6SSimon J. Gerraty } 10183955d011SMarcel Moolenaar 1019b0c40a00SSimon J. Gerraty static bool 102006b9b3e0SSimon J. Gerraty IsEndif(const char *p) 102106b9b3e0SSimon J. Gerraty { 102206b9b3e0SSimon J. Gerraty return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && 102306b9b3e0SSimon J. Gerraty p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); 102406b9b3e0SSimon J. Gerraty } 102506b9b3e0SSimon J. Gerraty 1026b0c40a00SSimon J. Gerraty static bool 1027b0c40a00SSimon J. Gerraty DetermineKindOfConditional(const char **pp, bool *out_plain, 102812904384SSimon J. Gerraty bool (**out_evalBare)(const char *), 1029b0c40a00SSimon J. Gerraty bool *out_negate) 1030dba7b0efSSimon J. Gerraty { 10319f45a3c8SSimon J. Gerraty const char *p = *pp + 2; 1032dba7b0efSSimon J. Gerraty 1033b0c40a00SSimon J. Gerraty *out_plain = false; 1034dba7b0efSSimon J. Gerraty *out_evalBare = FuncDefined; 10359f45a3c8SSimon J. Gerraty *out_negate = skip_string(&p, "n"); 10369f45a3c8SSimon J. Gerraty 10379f45a3c8SSimon J. Gerraty if (skip_string(&p, "def")) { /* .ifdef and .ifndef */ 10389f45a3c8SSimon J. Gerraty } else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */ 1039dba7b0efSSimon J. Gerraty *out_evalBare = FuncMake; 10409f45a3c8SSimon J. Gerraty else if (!*out_negate) /* plain .if */ 1041b0c40a00SSimon J. Gerraty *out_plain = true; 10429f45a3c8SSimon J. Gerraty else 10439f45a3c8SSimon J. Gerraty goto unknown_directive; 10449f45a3c8SSimon J. Gerraty if (ch_isalpha(*p)) 10459f45a3c8SSimon J. Gerraty goto unknown_directive; 10469f45a3c8SSimon J. Gerraty 10479f45a3c8SSimon J. Gerraty *pp = p; 10489f45a3c8SSimon J. Gerraty return true; 10499f45a3c8SSimon J. Gerraty 10509f45a3c8SSimon J. Gerraty unknown_directive: 1051dba7b0efSSimon J. Gerraty /* 10529f45a3c8SSimon J. Gerraty * TODO: Add error message about unknown directive, since there is no 10539f45a3c8SSimon J. Gerraty * other known directive that starts with 'el' or 'if'. 1054dba7b0efSSimon J. Gerraty * 1055dba7b0efSSimon J. Gerraty * Example: .elifx 123 1056dba7b0efSSimon J. Gerraty */ 1057b0c40a00SSimon J. Gerraty return false; 1058dba7b0efSSimon J. Gerraty } 1059dba7b0efSSimon J. Gerraty 106006b9b3e0SSimon J. Gerraty /* 106106b9b3e0SSimon J. Gerraty * Evaluate the conditional directive in the line, which is one of: 10623955d011SMarcel Moolenaar * 1063e2eeea75SSimon J. Gerraty * .if <cond> 1064e2eeea75SSimon J. Gerraty * .ifmake <cond> 1065e2eeea75SSimon J. Gerraty * .ifnmake <cond> 1066e2eeea75SSimon J. Gerraty * .ifdef <cond> 1067e2eeea75SSimon J. Gerraty * .ifndef <cond> 1068e2eeea75SSimon J. Gerraty * .elif <cond> 1069e2eeea75SSimon J. Gerraty * .elifmake <cond> 1070e2eeea75SSimon J. Gerraty * .elifnmake <cond> 1071e2eeea75SSimon J. Gerraty * .elifdef <cond> 1072e2eeea75SSimon J. Gerraty * .elifndef <cond> 1073e2eeea75SSimon J. Gerraty * .else 1074e2eeea75SSimon J. Gerraty * .endif 1075e2eeea75SSimon J. Gerraty * 1076e2eeea75SSimon J. Gerraty * In these directives, <cond> consists of &&, ||, !, function(arg), 1077e2eeea75SSimon J. Gerraty * comparisons, expressions, bare words, numbers and strings, and 1078e2eeea75SSimon J. Gerraty * parenthetical groupings thereof. 1079956e45f6SSimon J. Gerraty * 1080956e45f6SSimon J. Gerraty * Results: 10819f45a3c8SSimon J. Gerraty * CR_TRUE to continue parsing the lines that follow the 1082b0c40a00SSimon J. Gerraty * conditional (when <cond> evaluates to true) 10839f45a3c8SSimon J. Gerraty * CR_FALSE to skip the lines after the conditional 1084b0c40a00SSimon J. Gerraty * (when <cond> evaluates to false, or when a previous 1085956e45f6SSimon J. Gerraty * branch has already been taken) 10869f45a3c8SSimon J. Gerraty * CR_ERROR if the conditional was not valid, either because of 1087956e45f6SSimon J. Gerraty * a syntax error or because some variable was undefined 1088956e45f6SSimon J. Gerraty * or because the condition could not be evaluated 10893955d011SMarcel Moolenaar */ 10909f45a3c8SSimon J. Gerraty CondResult 109106b9b3e0SSimon J. Gerraty Cond_EvalLine(const char *line) 10923955d011SMarcel Moolenaar { 1093e2eeea75SSimon J. Gerraty typedef enum IfState { 1094e2eeea75SSimon J. Gerraty 1095b0c40a00SSimon J. Gerraty /* None of the previous <cond> evaluated to true. */ 1096e2eeea75SSimon J. Gerraty IFS_INITIAL = 0, 1097e2eeea75SSimon J. Gerraty 10989f45a3c8SSimon J. Gerraty /* 10999f45a3c8SSimon J. Gerraty * The previous <cond> evaluated to true. The lines following 11009f45a3c8SSimon J. Gerraty * this condition are interpreted. 11019f45a3c8SSimon J. Gerraty */ 1102e2eeea75SSimon J. Gerraty IFS_ACTIVE = 1 << 0, 1103e2eeea75SSimon J. Gerraty 1104e2eeea75SSimon J. Gerraty /* The previous directive was an '.else'. */ 1105e2eeea75SSimon J. Gerraty IFS_SEEN_ELSE = 1 << 1, 1106e2eeea75SSimon J. Gerraty 1107b0c40a00SSimon J. Gerraty /* One of the previous <cond> evaluated to true. */ 1108e2eeea75SSimon J. Gerraty IFS_WAS_ACTIVE = 1 << 2 1109e2eeea75SSimon J. Gerraty 1110e2eeea75SSimon J. Gerraty } IfState; 1111e2eeea75SSimon J. Gerraty 1112e2eeea75SSimon J. Gerraty static enum IfState *cond_states = NULL; 1113e2eeea75SSimon J. Gerraty static unsigned int cond_states_cap = 128; 11143955d011SMarcel Moolenaar 1115b0c40a00SSimon J. Gerraty bool plain; 111612904384SSimon J. Gerraty bool (*evalBare)(const char *); 1117b0c40a00SSimon J. Gerraty bool negate; 1118b0c40a00SSimon J. Gerraty bool isElif; 11199f45a3c8SSimon J. Gerraty CondResult res; 1120e2eeea75SSimon J. Gerraty IfState state; 1121e2eeea75SSimon J. Gerraty const char *p = line; 11223955d011SMarcel Moolenaar 1123e2eeea75SSimon J. Gerraty if (cond_states == NULL) { 112406b9b3e0SSimon J. Gerraty cond_states = bmake_malloc( 112506b9b3e0SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 1126e2eeea75SSimon J. Gerraty cond_states[0] = IFS_ACTIVE; 112759a02420SSimon J. Gerraty } 11283955d011SMarcel Moolenaar 1129e2eeea75SSimon J. Gerraty p++; /* skip the leading '.' */ 1130e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 1131e2eeea75SSimon J. Gerraty 113206b9b3e0SSimon J. Gerraty if (IsEndif(p)) { /* It is an '.endif'. */ 113306b9b3e0SSimon J. Gerraty if (p[5] != '\0') { 113406b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, 113512904384SSimon J. Gerraty "The .endif directive does not take arguments"); 1136e2eeea75SSimon J. Gerraty } 1137e2eeea75SSimon J. Gerraty 11383955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1139956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less endif"); 11409f45a3c8SSimon J. Gerraty return CR_TRUE; 11413955d011SMarcel Moolenaar } 1142e2eeea75SSimon J. Gerraty 11433955d011SMarcel Moolenaar /* Return state for previous conditional */ 11443955d011SMarcel Moolenaar cond_depth--; 1145e2eeea75SSimon J. Gerraty return cond_states[cond_depth] & IFS_ACTIVE 11469f45a3c8SSimon J. Gerraty ? CR_TRUE : CR_FALSE; 11473955d011SMarcel Moolenaar } 11483955d011SMarcel Moolenaar 114906b9b3e0SSimon J. Gerraty /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ 115006b9b3e0SSimon J. Gerraty if (p[0] == 'e') { 115106b9b3e0SSimon J. Gerraty if (p[1] != 'l') { 115206b9b3e0SSimon J. Gerraty /* 115306b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a 115412904384SSimon J. Gerraty * transformation rule like '.err.txt', 115506b9b3e0SSimon J. Gerraty * therefore no error message here. 115606b9b3e0SSimon J. Gerraty */ 11579f45a3c8SSimon J. Gerraty return CR_ERROR; 115806b9b3e0SSimon J. Gerraty } 115906b9b3e0SSimon J. Gerraty 11603955d011SMarcel Moolenaar /* Quite likely this is 'else' or 'elif' */ 1161e2eeea75SSimon J. Gerraty p += 2; 11629f45a3c8SSimon J. Gerraty if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) { 116306b9b3e0SSimon J. Gerraty if (p[2] != '\0') 1164e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, 116506b9b3e0SSimon J. Gerraty "The .else directive " 116612904384SSimon J. Gerraty "does not take arguments"); 1167e2eeea75SSimon J. Gerraty 11683955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1169956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less else"); 11709f45a3c8SSimon J. Gerraty return CR_TRUE; 11713955d011SMarcel Moolenaar } 11723955d011SMarcel Moolenaar 1173e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1174e2eeea75SSimon J. Gerraty if (state == IFS_INITIAL) { 1175e2eeea75SSimon J. Gerraty state = IFS_ACTIVE | IFS_SEEN_ELSE; 1176e2eeea75SSimon J. Gerraty } else { 1177e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) 117806b9b3e0SSimon J. Gerraty Parse_Error(PARSE_WARNING, 117906b9b3e0SSimon J. Gerraty "extra else"); 1180e2eeea75SSimon J. Gerraty state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 11813955d011SMarcel Moolenaar } 1182e2eeea75SSimon J. Gerraty cond_states[cond_depth] = state; 1183e2eeea75SSimon J. Gerraty 11849f45a3c8SSimon J. Gerraty return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE; 11853955d011SMarcel Moolenaar } 11863955d011SMarcel Moolenaar /* Assume for now it is an elif */ 1187b0c40a00SSimon J. Gerraty isElif = true; 11883955d011SMarcel Moolenaar } else 1189b0c40a00SSimon J. Gerraty isElif = false; 11903955d011SMarcel Moolenaar 1191e2eeea75SSimon J. Gerraty if (p[0] != 'i' || p[1] != 'f') { 119206b9b3e0SSimon J. Gerraty /* 119306b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a transformation rule 119406b9b3e0SSimon J. Gerraty * like '.elisp.scm', therefore no error message here. 119506b9b3e0SSimon J. Gerraty */ 11969f45a3c8SSimon J. Gerraty return CR_ERROR; /* Not an ifxxx or elifxxx line */ 1197e2eeea75SSimon J. Gerraty } 11983955d011SMarcel Moolenaar 1199dba7b0efSSimon J. Gerraty if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) 12009f45a3c8SSimon J. Gerraty return CR_ERROR; 12013955d011SMarcel Moolenaar 12023955d011SMarcel Moolenaar if (isElif) { 12033955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1204956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less elif"); 12059f45a3c8SSimon J. Gerraty return CR_TRUE; 12063955d011SMarcel Moolenaar } 1207e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1208e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) { 12093955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "extra elif"); 121006b9b3e0SSimon J. Gerraty cond_states[cond_depth] = 121106b9b3e0SSimon J. Gerraty IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 12129f45a3c8SSimon J. Gerraty return CR_FALSE; 12133955d011SMarcel Moolenaar } 1214e2eeea75SSimon J. Gerraty if (state != IFS_INITIAL) { 1215e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12169f45a3c8SSimon J. Gerraty return CR_FALSE; 12173955d011SMarcel Moolenaar } 12183955d011SMarcel Moolenaar } else { 12193955d011SMarcel Moolenaar /* Normal .if */ 1220e2eeea75SSimon J. Gerraty if (cond_depth + 1 >= cond_states_cap) { 122159a02420SSimon J. Gerraty /* 122259a02420SSimon J. Gerraty * This is rare, but not impossible. 122359a02420SSimon J. Gerraty * In meta mode, dirdeps.mk (only runs at level 0) 122459a02420SSimon J. Gerraty * can need more than the default. 122559a02420SSimon J. Gerraty */ 1226e2eeea75SSimon J. Gerraty cond_states_cap += 32; 1227e2eeea75SSimon J. Gerraty cond_states = bmake_realloc(cond_states, 12289f45a3c8SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 12293955d011SMarcel Moolenaar } 1230e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 12313955d011SMarcel Moolenaar cond_depth++; 1232e2eeea75SSimon J. Gerraty if (!(state & IFS_ACTIVE)) { 123306b9b3e0SSimon J. Gerraty /* 123406b9b3e0SSimon J. Gerraty * If we aren't parsing the data, 123506b9b3e0SSimon J. Gerraty * treat as always false. 123606b9b3e0SSimon J. Gerraty */ 1237e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12389f45a3c8SSimon J. Gerraty return CR_FALSE; 12393955d011SMarcel Moolenaar } 12403955d011SMarcel Moolenaar } 12413955d011SMarcel Moolenaar 1242956e45f6SSimon J. Gerraty /* And evaluate the conditional expression */ 12439f45a3c8SSimon J. Gerraty res = CondEvalExpression(p, plain, evalBare, negate, true, false); 12449f45a3c8SSimon J. Gerraty if (res == CR_ERROR) { 12459f45a3c8SSimon J. Gerraty /* Syntax error, error message already output. */ 12469f45a3c8SSimon J. Gerraty /* Skip everything to the matching '.endif'. */ 12479f45a3c8SSimon J. Gerraty /* An extra '.else' is not detected in this case. */ 1248e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12499f45a3c8SSimon J. Gerraty return CR_FALSE; 12503955d011SMarcel Moolenaar } 12513955d011SMarcel Moolenaar 12529f45a3c8SSimon J. Gerraty cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL; 12539f45a3c8SSimon J. Gerraty return res; 12543955d011SMarcel Moolenaar } 12553955d011SMarcel Moolenaar 12563955d011SMarcel Moolenaar void 12573955d011SMarcel Moolenaar Cond_restore_depth(unsigned int saved_depth) 12583955d011SMarcel Moolenaar { 1259956e45f6SSimon J. Gerraty unsigned int open_conds = cond_depth - cond_min_depth; 12603955d011SMarcel Moolenaar 12613955d011SMarcel Moolenaar if (open_conds != 0 || saved_depth > cond_depth) { 126206b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, "%u open conditional%s", 126306b9b3e0SSimon J. Gerraty open_conds, open_conds == 1 ? "" : "s"); 12643955d011SMarcel Moolenaar cond_depth = cond_min_depth; 12653955d011SMarcel Moolenaar } 12663955d011SMarcel Moolenaar 12673955d011SMarcel Moolenaar cond_min_depth = saved_depth; 12683955d011SMarcel Moolenaar } 12693955d011SMarcel Moolenaar 12703955d011SMarcel Moolenaar unsigned int 12713955d011SMarcel Moolenaar Cond_save_depth(void) 12723955d011SMarcel Moolenaar { 1273956e45f6SSimon J. Gerraty unsigned int depth = cond_min_depth; 12743955d011SMarcel Moolenaar 12753955d011SMarcel Moolenaar cond_min_depth = cond_depth; 12763955d011SMarcel Moolenaar return depth; 12773955d011SMarcel Moolenaar } 1278