1*98875883SSimon J. Gerraty /* $NetBSD: cond.c,v 1.354 2023/08/11 04:56:31 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 * 844fde40d9SSimon J. Gerraty * Cond_EndFile 854fde40d9SSimon J. Gerraty * At the end of reading a makefile, ensure that the 864fde40d9SSimon J. Gerraty * conditional directives are well-balanced. 873955d011SMarcel Moolenaar */ 883955d011SMarcel Moolenaar 892c3632d1SSimon J. Gerraty #include <errno.h> 903955d011SMarcel Moolenaar 913955d011SMarcel Moolenaar #include "make.h" 923955d011SMarcel Moolenaar #include "dir.h" 933955d011SMarcel Moolenaar 94956e45f6SSimon J. Gerraty /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ 95*98875883SSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.354 2023/08/11 04:56:31 rillig Exp $"); 96956e45f6SSimon J. Gerraty 973955d011SMarcel Moolenaar /* 989f45a3c8SSimon J. Gerraty * Conditional expressions conform to this grammar: 9912904384SSimon J. Gerraty * Or -> And ('||' And)* 10012904384SSimon J. Gerraty * And -> Term ('&&' Term)* 101dba7b0efSSimon J. Gerraty * Term -> Function '(' Argument ')' 102dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 103dba7b0efSSimon J. Gerraty * Term -> Leaf 104dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 105dba7b0efSSimon J. Gerraty * Term -> '!' Term 106dba7b0efSSimon J. Gerraty * Leaf -> "string" 107dba7b0efSSimon J. Gerraty * Leaf -> Number 108dba7b0efSSimon J. Gerraty * Leaf -> VariableExpression 1099f45a3c8SSimon J. Gerraty * Leaf -> BareWord 110dba7b0efSSimon J. Gerraty * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' 1113955d011SMarcel Moolenaar * 1129f45a3c8SSimon J. Gerraty * BareWord is an unquoted string literal, its evaluation depends on the kind 1139f45a3c8SSimon J. Gerraty * of '.if' directive. 1143955d011SMarcel Moolenaar * 1159f45a3c8SSimon J. Gerraty * The tokens are scanned by CondParser_Token, which returns: 116dba7b0efSSimon J. Gerraty * TOK_AND for '&&' 117dba7b0efSSimon J. Gerraty * TOK_OR for '||' 118956e45f6SSimon J. Gerraty * TOK_NOT for '!' 119956e45f6SSimon J. Gerraty * TOK_LPAREN for '(' 120956e45f6SSimon J. Gerraty * TOK_RPAREN for ')' 121dba7b0efSSimon J. Gerraty * 122956e45f6SSimon J. Gerraty * Other terminal symbols are evaluated using either the default function or 1239f45a3c8SSimon J. Gerraty * the function given in the terminal, they return either TOK_TRUE, TOK_FALSE 1249f45a3c8SSimon J. Gerraty * or TOK_ERROR. 1253955d011SMarcel Moolenaar */ 126956e45f6SSimon J. Gerraty typedef enum Token { 127dba7b0efSSimon J. Gerraty TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT, 1283955d011SMarcel Moolenaar TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR 1293955d011SMarcel Moolenaar } Token; 1303955d011SMarcel Moolenaar 131dba7b0efSSimon J. Gerraty typedef enum ComparisonOp { 132dba7b0efSSimon J. Gerraty LT, LE, GT, GE, EQ, NE 133dba7b0efSSimon J. Gerraty } ComparisonOp; 134dba7b0efSSimon J. Gerraty 135956e45f6SSimon J. Gerraty typedef struct CondParser { 136dba7b0efSSimon J. Gerraty 137dba7b0efSSimon J. Gerraty /* 138dba7b0efSSimon J. Gerraty * The plain '.if ${VAR}' evaluates to true if the value of the 139148ee845SSimon J. Gerraty * expression has length > 0 and is not numerically zero. The other 140148ee845SSimon J. Gerraty * '.if' variants delegate to evalBare instead, for example '.ifdef 141148ee845SSimon J. Gerraty * ${VAR}' is equivalent to '.if defined(${VAR})', checking whether 142148ee845SSimon J. Gerraty * the variable named by the expression '${VAR}' is defined. 143dba7b0efSSimon J. Gerraty */ 144b0c40a00SSimon J. Gerraty bool plain; 145dba7b0efSSimon J. Gerraty 146dba7b0efSSimon J. Gerraty /* The function to apply on unquoted bare words. */ 14712904384SSimon J. Gerraty bool (*evalBare)(const char *); 148b0c40a00SSimon J. Gerraty bool negateEvalBare; 149dba7b0efSSimon J. Gerraty 15012904384SSimon J. Gerraty /* 15112904384SSimon J. Gerraty * Whether the left-hand side of a comparison may be an unquoted 15212904384SSimon J. Gerraty * string. This is allowed for expressions of the form 15312904384SSimon J. Gerraty * ${condition:?:}, see ApplyModifier_IfElse. Such a condition is 15412904384SSimon J. Gerraty * expanded before it is evaluated, due to ease of implementation. 15512904384SSimon J. Gerraty * This means that at the point where the condition is evaluated, 15612904384SSimon J. Gerraty * make cannot know anymore whether the left-hand side had originally 15712904384SSimon J. Gerraty * been a variable expression or a plain word. 15812904384SSimon J. Gerraty * 1591d3f2ddcSSimon J. Gerraty * In conditional directives like '.if', the left-hand side must 1601d3f2ddcSSimon J. Gerraty * either be a variable expression, a quoted string or a number. 16112904384SSimon J. Gerraty */ 16212904384SSimon J. Gerraty bool leftUnquotedOK; 16312904384SSimon J. Gerraty 164956e45f6SSimon J. Gerraty const char *p; /* The remaining condition to parse */ 165956e45f6SSimon J. Gerraty Token curr; /* Single push-back token used in parsing */ 1663955d011SMarcel Moolenaar 16706b9b3e0SSimon J. Gerraty /* 16806b9b3e0SSimon J. Gerraty * Whether an error message has already been printed for this 16906b9b3e0SSimon J. Gerraty * condition. The first available error message is usually the most 17006b9b3e0SSimon J. Gerraty * specific one, therefore it makes sense to suppress the standard 17106b9b3e0SSimon J. Gerraty * "Malformed conditional" message. 17206b9b3e0SSimon J. Gerraty */ 173b0c40a00SSimon J. Gerraty bool printedError; 174956e45f6SSimon J. Gerraty } CondParser; 175956e45f6SSimon J. Gerraty 176148ee845SSimon J. Gerraty static CondResult CondParser_Or(CondParser *, bool); 1773955d011SMarcel Moolenaar 1784fde40d9SSimon J. Gerraty unsigned int cond_depth = 0; /* current .if nesting level */ 1793955d011SMarcel Moolenaar 18012904384SSimon J. Gerraty /* Names for ComparisonOp. */ 18112904384SSimon J. Gerraty static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" }; 18228a6bc81SSimon J. Gerraty 1839f45a3c8SSimon J. Gerraty MAKE_INLINE bool 1849f45a3c8SSimon J. Gerraty skip_string(const char **pp, const char *str) 1853955d011SMarcel Moolenaar { 1869f45a3c8SSimon J. Gerraty size_t len = strlen(str); 1879f45a3c8SSimon J. Gerraty bool ok = strncmp(*pp, str, len) == 0; 1889f45a3c8SSimon J. Gerraty if (ok) 1899f45a3c8SSimon J. Gerraty *pp += len; 1909f45a3c8SSimon J. Gerraty return ok; 1913955d011SMarcel Moolenaar } 1923955d011SMarcel Moolenaar 193e2eeea75SSimon J. Gerraty static Token 194b0c40a00SSimon J. Gerraty ToToken(bool cond) 195e2eeea75SSimon J. Gerraty { 196e2eeea75SSimon J. Gerraty return cond ? TOK_TRUE : TOK_FALSE; 197e2eeea75SSimon J. Gerraty } 198e2eeea75SSimon J. Gerraty 199956e45f6SSimon J. Gerraty static void 200956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(CondParser *par) 2013955d011SMarcel Moolenaar { 202956e45f6SSimon J. Gerraty cpp_skip_whitespace(&par->p); 203956e45f6SSimon J. Gerraty } 204956e45f6SSimon J. Gerraty 20506b9b3e0SSimon J. Gerraty /* 20612904384SSimon J. Gerraty * Parse a single word, taking into account balanced parentheses as well as 20712904384SSimon J. Gerraty * embedded expressions. Used for the argument of a built-in function as 20812904384SSimon J. Gerraty * well as for bare words, which are then passed to the default function. 20906b9b3e0SSimon J. Gerraty */ 2109f45a3c8SSimon J. Gerraty static char * 2119f45a3c8SSimon J. Gerraty ParseWord(const char **pp, bool doEval) 21206b9b3e0SSimon J. Gerraty { 213956e45f6SSimon J. Gerraty const char *p = *pp; 2141d3f2ddcSSimon J. Gerraty Buffer word; 2153955d011SMarcel Moolenaar int paren_depth; 2163955d011SMarcel Moolenaar 2171d3f2ddcSSimon J. Gerraty Buf_InitSize(&word, 16); 2183955d011SMarcel Moolenaar 2193955d011SMarcel Moolenaar paren_depth = 0; 2203955d011SMarcel Moolenaar for (;;) { 221956e45f6SSimon J. Gerraty char ch = *p; 222e2eeea75SSimon J. Gerraty if (ch == '\0' || ch == ' ' || ch == '\t') 2233955d011SMarcel Moolenaar break; 2243955d011SMarcel Moolenaar if ((ch == '&' || ch == '|') && paren_depth == 0) 2253955d011SMarcel Moolenaar break; 2269f45a3c8SSimon J. Gerraty if (ch == '$') { 227b0c40a00SSimon J. Gerraty VarEvalMode emode = doEval 228b0c40a00SSimon J. Gerraty ? VARE_UNDEFERR 229b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 230*98875883SSimon J. Gerraty /* 231*98875883SSimon J. Gerraty * TODO: make Var_Parse complain about undefined 232*98875883SSimon J. Gerraty * variables. 233*98875883SSimon J. Gerraty */ 2348c973ee2SSimon J. Gerraty FStr nestedVal = Var_Parse(&p, SCOPE_CMDLINE, emode); 235956e45f6SSimon J. Gerraty /* TODO: handle errors */ 2361d3f2ddcSSimon J. Gerraty Buf_AddStr(&word, nestedVal.str); 23706b9b3e0SSimon J. Gerraty FStr_Done(&nestedVal); 2383955d011SMarcel Moolenaar continue; 2393955d011SMarcel Moolenaar } 2403955d011SMarcel Moolenaar if (ch == '(') 2413955d011SMarcel Moolenaar paren_depth++; 2422c3632d1SSimon J. Gerraty else if (ch == ')' && --paren_depth < 0) 2433955d011SMarcel Moolenaar break; 2441d3f2ddcSSimon J. Gerraty Buf_AddByte(&word, ch); 245956e45f6SSimon J. Gerraty p++; 2463955d011SMarcel Moolenaar } 2473955d011SMarcel Moolenaar 2489f45a3c8SSimon J. Gerraty cpp_skip_hspace(&p); 2499f45a3c8SSimon J. Gerraty *pp = p; 2503955d011SMarcel Moolenaar 2511d3f2ddcSSimon J. Gerraty return Buf_DoneData(&word); 2529f45a3c8SSimon J. Gerraty } 2539f45a3c8SSimon J. Gerraty 2549f45a3c8SSimon J. Gerraty /* Parse the function argument, including the surrounding parentheses. */ 2559f45a3c8SSimon J. Gerraty static char * 2569f45a3c8SSimon J. Gerraty ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) 2579f45a3c8SSimon J. Gerraty { 2589f45a3c8SSimon J. Gerraty const char *p = *pp; 2599f45a3c8SSimon J. Gerraty char *res; 2609f45a3c8SSimon J. Gerraty 2619f45a3c8SSimon J. Gerraty p++; /* Skip opening '(' - verified by caller */ 2629f45a3c8SSimon J. Gerraty cpp_skip_hspace(&p); 2639f45a3c8SSimon J. Gerraty res = ParseWord(&p, doEval); 264e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2653955d011SMarcel Moolenaar 2669f45a3c8SSimon J. Gerraty if (*p++ != ')') { 2679f45a3c8SSimon J. Gerraty int len = 0; 2689f45a3c8SSimon J. Gerraty while (ch_isalpha(func[len])) 2699f45a3c8SSimon J. Gerraty len++; 2709f45a3c8SSimon J. Gerraty 271dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 2729f45a3c8SSimon J. Gerraty "Missing closing parenthesis for %.*s()", len, func); 273b0c40a00SSimon J. Gerraty par->printedError = true; 2749f45a3c8SSimon J. Gerraty free(res); 2759f45a3c8SSimon J. Gerraty return NULL; 2763955d011SMarcel Moolenaar } 2773955d011SMarcel Moolenaar 278956e45f6SSimon J. Gerraty *pp = p; 2799f45a3c8SSimon J. Gerraty return res; 2803955d011SMarcel Moolenaar } 2812c3632d1SSimon J. Gerraty 2821d3f2ddcSSimon J. Gerraty /* See if the given variable is defined. */ 283b0c40a00SSimon J. Gerraty static bool 2849f45a3c8SSimon J. Gerraty FuncDefined(const char *var) 2853955d011SMarcel Moolenaar { 2869f45a3c8SSimon J. Gerraty return Var_Exists(SCOPE_CMDLINE, var); 2873955d011SMarcel Moolenaar } 2882c3632d1SSimon J. Gerraty 2891d3f2ddcSSimon J. Gerraty /* See if a target matching targetPattern is requested to be made. */ 290b0c40a00SSimon J. Gerraty static bool 2911d3f2ddcSSimon J. Gerraty FuncMake(const char *targetPattern) 2923955d011SMarcel Moolenaar { 293956e45f6SSimon J. Gerraty StringListNode *ln; 294148ee845SSimon J. Gerraty bool warned = false; 295956e45f6SSimon J. Gerraty 296148ee845SSimon J. Gerraty for (ln = opts.create.first; ln != NULL; ln = ln->next) { 297148ee845SSimon J. Gerraty StrMatchResult res = Str_Match(ln->datum, targetPattern); 298148ee845SSimon J. Gerraty if (res.error != NULL && !warned) { 299148ee845SSimon J. Gerraty warned = true; 300148ee845SSimon J. Gerraty Parse_Error(PARSE_WARNING, 301148ee845SSimon J. Gerraty "%s in pattern argument '%s' to function 'make'", 302148ee845SSimon J. Gerraty res.error, targetPattern); 303148ee845SSimon J. Gerraty } 304148ee845SSimon J. Gerraty if (res.matched) 305b0c40a00SSimon J. Gerraty return true; 306148ee845SSimon J. Gerraty } 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 /* 346148ee845SSimon J. Gerraty * Convert the string to 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 3878c973ee2SSimon J. Gerraty * expression and add its value to the buffer. 3888c973ee2SSimon J. Gerraty * 3898c973ee2SSimon J. Gerraty * Return whether to continue parsing the leaf. 390dba7b0efSSimon J. Gerraty * 391dba7b0efSSimon J. Gerraty * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} 392dba7b0efSSimon J. Gerraty */ 393b0c40a00SSimon J. Gerraty static bool 394dba7b0efSSimon J. Gerraty CondParser_StringExpr(CondParser *par, const char *start, 39512904384SSimon J. Gerraty bool doEval, bool quoted, 39612904384SSimon J. Gerraty Buffer *buf, FStr *inout_str) 397dba7b0efSSimon J. Gerraty { 398b0c40a00SSimon J. Gerraty VarEvalMode emode; 3999f45a3c8SSimon J. Gerraty const char *p; 400b0c40a00SSimon J. Gerraty bool atStart; 401dba7b0efSSimon J. Gerraty 40212904384SSimon J. Gerraty emode = doEval && quoted ? VARE_WANTRES 40312904384SSimon J. Gerraty : doEval ? VARE_UNDEFERR 404b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 405dba7b0efSSimon J. Gerraty 4069f45a3c8SSimon J. Gerraty p = par->p; 4079f45a3c8SSimon J. Gerraty atStart = p == start; 4088c973ee2SSimon J. Gerraty *inout_str = Var_Parse(&p, SCOPE_CMDLINE, emode); 409dba7b0efSSimon J. Gerraty /* TODO: handle errors */ 410dba7b0efSSimon J. Gerraty if (inout_str->str == var_Error) { 411dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 412dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); 413b0c40a00SSimon J. Gerraty return false; 414dba7b0efSSimon J. Gerraty } 4159f45a3c8SSimon J. Gerraty par->p = p; 416dba7b0efSSimon J. Gerraty 417dba7b0efSSimon J. Gerraty /* 418dba7b0efSSimon J. Gerraty * If the '$' started the string literal (which means no quotes), and 4198c973ee2SSimon J. Gerraty * the expression is followed by a space, a comparison operator or 4208c973ee2SSimon J. Gerraty * the end of the expression, we are done. 421dba7b0efSSimon J. Gerraty */ 422dba7b0efSSimon J. Gerraty if (atStart && is_separator(par->p[0])) 423b0c40a00SSimon J. Gerraty return false; 424dba7b0efSSimon J. Gerraty 425dba7b0efSSimon J. Gerraty Buf_AddStr(buf, inout_str->str); 426dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 427dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); /* not finished yet */ 428b0c40a00SSimon J. Gerraty return true; 429dba7b0efSSimon J. Gerraty } 430dba7b0efSSimon J. Gerraty 431dba7b0efSSimon J. Gerraty /* 4329f45a3c8SSimon J. Gerraty * Parse a string from a variable expression or an optionally quoted string, 4339f45a3c8SSimon J. Gerraty * on the left-hand and right-hand sides of comparisons. 4343955d011SMarcel Moolenaar * 4353955d011SMarcel Moolenaar * Results: 4369f45a3c8SSimon J. Gerraty * Returns the string without any enclosing quotes, or NULL on error. 437b0c40a00SSimon J. Gerraty * Sets out_quoted if the leaf was a quoted string literal. 4383955d011SMarcel Moolenaar */ 43906b9b3e0SSimon J. Gerraty static void 44012904384SSimon J. Gerraty CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, 441b0c40a00SSimon J. Gerraty FStr *out_str, bool *out_quoted) 4423955d011SMarcel Moolenaar { 4433955d011SMarcel Moolenaar Buffer buf; 44406b9b3e0SSimon J. Gerraty FStr str; 445b0c40a00SSimon J. Gerraty bool quoted; 4462c3632d1SSimon J. Gerraty const char *start; 4473955d011SMarcel Moolenaar 448e2eeea75SSimon J. Gerraty Buf_Init(&buf); 44906b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 450e2eeea75SSimon J. Gerraty *out_quoted = quoted = par->p[0] == '"'; 451956e45f6SSimon J. Gerraty start = par->p; 452e2eeea75SSimon J. Gerraty if (quoted) 453956e45f6SSimon J. Gerraty par->p++; 45406b9b3e0SSimon J. Gerraty 45506b9b3e0SSimon J. Gerraty while (par->p[0] != '\0' && str.str == NULL) { 456956e45f6SSimon J. Gerraty switch (par->p[0]) { 4573955d011SMarcel Moolenaar case '\\': 458956e45f6SSimon J. Gerraty par->p++; 459956e45f6SSimon J. Gerraty if (par->p[0] != '\0') { 460956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 461956e45f6SSimon J. Gerraty par->p++; 4623955d011SMarcel Moolenaar } 463956e45f6SSimon J. Gerraty continue; 4643955d011SMarcel Moolenaar case '"': 465956e45f6SSimon J. Gerraty par->p++; 466dba7b0efSSimon J. Gerraty if (quoted) 4679f45a3c8SSimon J. Gerraty goto return_buf; /* skip the closing quote */ 468dba7b0efSSimon J. Gerraty Buf_AddByte(&buf, '"'); 469956e45f6SSimon J. Gerraty continue; 470e2eeea75SSimon J. Gerraty case ')': /* see is_separator */ 4713955d011SMarcel Moolenaar case '!': 4723955d011SMarcel Moolenaar case '=': 4733955d011SMarcel Moolenaar case '>': 4743955d011SMarcel Moolenaar case '<': 4753955d011SMarcel Moolenaar case ' ': 4763955d011SMarcel Moolenaar case '\t': 477e2eeea75SSimon J. Gerraty if (!quoted) 4789f45a3c8SSimon J. Gerraty goto return_buf; 479956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 480956e45f6SSimon J. Gerraty par->p++; 481956e45f6SSimon J. Gerraty continue; 4823955d011SMarcel Moolenaar case '$': 483dba7b0efSSimon J. Gerraty if (!CondParser_StringExpr(par, 484dba7b0efSSimon J. Gerraty start, doEval, quoted, &buf, &str)) 4859f45a3c8SSimon J. Gerraty goto return_str; 486956e45f6SSimon J. Gerraty continue; 4873955d011SMarcel Moolenaar default: 48812904384SSimon J. Gerraty if (!unquotedOK && !quoted && *start != '$' && 48906b9b3e0SSimon J. Gerraty !ch_isdigit(*start)) { 49006b9b3e0SSimon J. Gerraty /* 49106b9b3e0SSimon J. Gerraty * The left-hand side must be quoted, 49212904384SSimon J. Gerraty * a variable expression or a number. 49306b9b3e0SSimon J. Gerraty */ 49406b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 4959f45a3c8SSimon J. Gerraty goto return_str; 49628a6bc81SSimon J. Gerraty } 497956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 498956e45f6SSimon J. Gerraty par->p++; 499956e45f6SSimon J. Gerraty continue; 5003955d011SMarcel Moolenaar } 5013955d011SMarcel Moolenaar } 5029f45a3c8SSimon J. Gerraty return_buf: 503dba7b0efSSimon J. Gerraty str = FStr_InitOwn(buf.data); 50412904384SSimon J. Gerraty buf.data = NULL; 5059f45a3c8SSimon J. Gerraty return_str: 50612904384SSimon J. Gerraty Buf_Done(&buf); 50706b9b3e0SSimon J. Gerraty *out_str = str; 5083955d011SMarcel Moolenaar } 5092c3632d1SSimon J. Gerraty 51006b9b3e0SSimon J. Gerraty /* 51106b9b3e0SSimon J. Gerraty * Evaluate a "comparison without operator", such as in ".if ${VAR}" or 51206b9b3e0SSimon J. Gerraty * ".if 0". 51306b9b3e0SSimon J. Gerraty */ 514b0c40a00SSimon J. Gerraty static bool 515148ee845SSimon J. Gerraty EvalTruthy(CondParser *par, const char *value, bool quoted) 5163955d011SMarcel Moolenaar { 517e2eeea75SSimon J. Gerraty double num; 518956e45f6SSimon J. Gerraty 519e2eeea75SSimon J. Gerraty /* For .ifxxx "...", check for non-empty string. */ 520e2eeea75SSimon J. Gerraty if (quoted) 521e2eeea75SSimon J. Gerraty return value[0] != '\0'; 522956e45f6SSimon J. Gerraty 523e2eeea75SSimon J. Gerraty /* For .ifxxx <number>, compare against zero */ 524e2eeea75SSimon J. Gerraty if (TryParseNumber(value, &num)) 525e2eeea75SSimon J. Gerraty return num != 0.0; 526956e45f6SSimon J. Gerraty 5279f45a3c8SSimon J. Gerraty /* 5289f45a3c8SSimon J. Gerraty * For .if ${...}, check for non-empty string. This is different 5299f45a3c8SSimon J. Gerraty * from the evaluation function from that .if variant, which would 5309f45a3c8SSimon J. Gerraty * test whether a variable of the given name were defined. 5319f45a3c8SSimon J. Gerraty */ 53212904384SSimon J. Gerraty /* 53312904384SSimon J. Gerraty * XXX: Whitespace should count as empty, just as in 53412904384SSimon J. Gerraty * CondParser_FuncCallEmpty. 53512904384SSimon J. Gerraty */ 536dba7b0efSSimon J. Gerraty if (par->plain) 537e2eeea75SSimon J. Gerraty return value[0] != '\0'; 538956e45f6SSimon J. Gerraty 5399f45a3c8SSimon J. Gerraty return par->evalBare(value) != par->negateEvalBare; 540956e45f6SSimon J. Gerraty } 541956e45f6SSimon J. Gerraty 542956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ 543b0c40a00SSimon J. Gerraty static bool 544dba7b0efSSimon J. Gerraty EvalCompareNum(double lhs, ComparisonOp op, double rhs) 545956e45f6SSimon J. Gerraty { 5461d3f2ddcSSimon J. Gerraty DEBUG3(COND, "Comparing %f %s %f\n", lhs, opname[op], rhs); 547956e45f6SSimon J. Gerraty 548dba7b0efSSimon J. Gerraty switch (op) { 549dba7b0efSSimon J. Gerraty case LT: 550dba7b0efSSimon J. Gerraty return lhs < rhs; 551dba7b0efSSimon J. Gerraty case LE: 552dba7b0efSSimon J. Gerraty return lhs <= rhs; 553dba7b0efSSimon J. Gerraty case GT: 554dba7b0efSSimon J. Gerraty return lhs > rhs; 555dba7b0efSSimon J. Gerraty case GE: 556dba7b0efSSimon J. Gerraty return lhs >= rhs; 5574fde40d9SSimon J. Gerraty case EQ: 558dba7b0efSSimon J. Gerraty return lhs == rhs; 5594fde40d9SSimon J. Gerraty default: 5604fde40d9SSimon J. Gerraty return lhs != rhs; 561956e45f6SSimon J. Gerraty } 562956e45f6SSimon J. Gerraty } 563956e45f6SSimon J. Gerraty 564956e45f6SSimon J. Gerraty static Token 565dba7b0efSSimon J. Gerraty EvalCompareStr(CondParser *par, const char *lhs, 566dba7b0efSSimon J. Gerraty ComparisonOp op, const char *rhs) 567956e45f6SSimon J. Gerraty { 568dba7b0efSSimon J. Gerraty if (op != EQ && op != NE) { 569dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 5704fde40d9SSimon J. Gerraty "Comparison with '%s' requires both operands " 5714fde40d9SSimon J. Gerraty "'%s' and '%s' to be numeric", 5724fde40d9SSimon J. Gerraty opname[op], lhs, rhs); 573b0c40a00SSimon J. Gerraty par->printedError = true; 574956e45f6SSimon J. Gerraty return TOK_ERROR; 575956e45f6SSimon J. Gerraty } 576956e45f6SSimon J. Gerraty 5771d3f2ddcSSimon J. Gerraty DEBUG3(COND, "Comparing \"%s\" %s \"%s\"\n", lhs, opname[op], rhs); 578dba7b0efSSimon J. Gerraty return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); 579956e45f6SSimon J. Gerraty } 580956e45f6SSimon J. Gerraty 581956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */ 582956e45f6SSimon J. Gerraty static Token 583b0c40a00SSimon J. Gerraty EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, 584b0c40a00SSimon J. Gerraty ComparisonOp op, const char *rhs, bool rhsQuoted) 585956e45f6SSimon J. Gerraty { 5863955d011SMarcel Moolenaar double left, right; 5873955d011SMarcel Moolenaar 588956e45f6SSimon J. Gerraty if (!rhsQuoted && !lhsQuoted) 589956e45f6SSimon J. Gerraty if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) 590dba7b0efSSimon J. Gerraty return ToToken(EvalCompareNum(left, op, right)); 591956e45f6SSimon J. Gerraty 592dba7b0efSSimon J. Gerraty return EvalCompareStr(par, lhs, op, rhs); 593dba7b0efSSimon J. Gerraty } 594dba7b0efSSimon J. Gerraty 595b0c40a00SSimon J. Gerraty static bool 596dba7b0efSSimon J. Gerraty CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) 597dba7b0efSSimon J. Gerraty { 598dba7b0efSSimon J. Gerraty const char *p = par->p; 599dba7b0efSSimon J. Gerraty 6009f45a3c8SSimon J. Gerraty if (p[0] == '<' && p[1] == '=') 6019f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = LE, true; 6029f45a3c8SSimon J. Gerraty if (p[0] == '<') 6039f45a3c8SSimon J. Gerraty return par->p += 1, *out_op = LT, true; 6049f45a3c8SSimon J. Gerraty if (p[0] == '>' && p[1] == '=') 6059f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = GE, true; 6069f45a3c8SSimon J. Gerraty if (p[0] == '>') 6079f45a3c8SSimon J. Gerraty return par->p += 1, *out_op = GT, true; 6089f45a3c8SSimon J. Gerraty if (p[0] == '=' && p[1] == '=') 6099f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = EQ, true; 6109f45a3c8SSimon J. Gerraty if (p[0] == '!' && p[1] == '=') 6119f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = NE, true; 612b0c40a00SSimon J. Gerraty return false; 613956e45f6SSimon J. Gerraty } 614956e45f6SSimon J. Gerraty 61506b9b3e0SSimon J. Gerraty /* 61606b9b3e0SSimon J. Gerraty * Parse a comparison condition such as: 617956e45f6SSimon J. Gerraty * 618956e45f6SSimon J. Gerraty * 0 619956e45f6SSimon J. Gerraty * ${VAR:Mpattern} 620956e45f6SSimon J. Gerraty * ${VAR} == value 621956e45f6SSimon J. Gerraty * ${VAR:U0} < 12345 622956e45f6SSimon J. Gerraty */ 623956e45f6SSimon J. Gerraty static Token 624b0c40a00SSimon J. Gerraty CondParser_Comparison(CondParser *par, bool doEval) 625956e45f6SSimon J. Gerraty { 626956e45f6SSimon J. Gerraty Token t = TOK_ERROR; 62706b9b3e0SSimon J. Gerraty FStr lhs, rhs; 628dba7b0efSSimon J. Gerraty ComparisonOp op; 629b0c40a00SSimon J. Gerraty bool lhsQuoted, rhsQuoted; 630956e45f6SSimon J. Gerraty 63112904384SSimon J. Gerraty CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhs, &lhsQuoted); 63206b9b3e0SSimon J. Gerraty if (lhs.str == NULL) 633e2eeea75SSimon J. Gerraty goto done_lhs; 6343955d011SMarcel Moolenaar 635956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6363955d011SMarcel Moolenaar 637dba7b0efSSimon J. Gerraty if (!CondParser_ComparisonOp(par, &op)) { 638e2eeea75SSimon J. Gerraty /* Unknown operator, compare against an empty string or 0. */ 639148ee845SSimon J. Gerraty t = ToToken(doEval && EvalTruthy(par, lhs.str, lhsQuoted)); 640e2eeea75SSimon J. Gerraty goto done_lhs; 6413955d011SMarcel Moolenaar } 6423955d011SMarcel Moolenaar 643956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6443955d011SMarcel Moolenaar 645956e45f6SSimon J. Gerraty if (par->p[0] == '\0') { 646dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 64712904384SSimon J. Gerraty "Missing right-hand side of operator '%s'", opname[op]); 648b0c40a00SSimon J. Gerraty par->printedError = true; 649e2eeea75SSimon J. Gerraty goto done_lhs; 6503955d011SMarcel Moolenaar } 6513955d011SMarcel Moolenaar 65212904384SSimon J. Gerraty CondParser_Leaf(par, doEval, true, &rhs, &rhsQuoted); 6531d3f2ddcSSimon J. Gerraty t = rhs.str == NULL ? TOK_ERROR 6541d3f2ddcSSimon J. Gerraty : !doEval ? TOK_FALSE 6551d3f2ddcSSimon J. Gerraty : EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); 65606b9b3e0SSimon J. Gerraty FStr_Done(&rhs); 6571d3f2ddcSSimon J. Gerraty 658e2eeea75SSimon J. Gerraty done_lhs: 65906b9b3e0SSimon J. Gerraty FStr_Done(&lhs); 6603955d011SMarcel Moolenaar return t; 6613955d011SMarcel Moolenaar } 6623955d011SMarcel Moolenaar 66306b9b3e0SSimon J. Gerraty /* 66406b9b3e0SSimon J. Gerraty * The argument to empty() is a variable name, optionally followed by 66506b9b3e0SSimon J. Gerraty * variable modifiers. 66606b9b3e0SSimon J. Gerraty */ 66712904384SSimon J. Gerraty static bool 66812904384SSimon J. Gerraty CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) 6693955d011SMarcel Moolenaar { 67012904384SSimon J. Gerraty const char *cp = par->p; 67112904384SSimon J. Gerraty Token tok; 67206b9b3e0SSimon J. Gerraty FStr val; 6733955d011SMarcel Moolenaar 6749f45a3c8SSimon J. Gerraty if (!skip_string(&cp, "empty")) 67512904384SSimon J. Gerraty return false; 6763955d011SMarcel Moolenaar 67712904384SSimon J. Gerraty cpp_skip_whitespace(&cp); 67812904384SSimon J. Gerraty if (*cp != '(') 67912904384SSimon J. Gerraty return false; 68012904384SSimon J. Gerraty 68112904384SSimon J. Gerraty cp--; /* Make cp[1] point to the '('. */ 6828c973ee2SSimon J. Gerraty val = Var_Parse(&cp, SCOPE_CMDLINE, 6838c973ee2SSimon J. Gerraty doEval ? VARE_WANTRES : VARE_PARSE_ONLY); 684956e45f6SSimon J. Gerraty /* TODO: handle errors */ 6853955d011SMarcel Moolenaar 68612904384SSimon J. Gerraty if (val.str == var_Error) 68712904384SSimon J. Gerraty tok = TOK_ERROR; 68812904384SSimon J. Gerraty else { 68906b9b3e0SSimon J. Gerraty cpp_skip_whitespace(&val.str); 6909f45a3c8SSimon J. Gerraty tok = ToToken(doEval && val.str[0] == '\0'); 6913955d011SMarcel Moolenaar } 6923955d011SMarcel Moolenaar 69312904384SSimon J. Gerraty FStr_Done(&val); 69412904384SSimon J. Gerraty *out_token = tok; 69512904384SSimon J. Gerraty par->p = cp; 69612904384SSimon J. Gerraty return true; 6973955d011SMarcel Moolenaar } 6983955d011SMarcel Moolenaar 6992f2a5ecdSSimon J. Gerraty /* Parse a function call expression, such as 'exists(${file})'. */ 700b0c40a00SSimon J. Gerraty static bool 701b0c40a00SSimon J. Gerraty CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) 7023955d011SMarcel Moolenaar { 7039f45a3c8SSimon J. Gerraty char *arg; 7049f45a3c8SSimon J. Gerraty const char *p = par->p; 7059f45a3c8SSimon J. Gerraty bool (*fn)(const char *); 7069f45a3c8SSimon J. Gerraty const char *fn_name = p; 707e2eeea75SSimon J. Gerraty 7089f45a3c8SSimon J. Gerraty if (skip_string(&p, "defined")) 7099f45a3c8SSimon J. Gerraty fn = FuncDefined; 7109f45a3c8SSimon J. Gerraty else if (skip_string(&p, "make")) 7119f45a3c8SSimon J. Gerraty fn = FuncMake; 7129f45a3c8SSimon J. Gerraty else if (skip_string(&p, "exists")) 7139f45a3c8SSimon J. Gerraty fn = FuncExists; 7149f45a3c8SSimon J. Gerraty else if (skip_string(&p, "target")) 7159f45a3c8SSimon J. Gerraty fn = FuncTarget; 7169f45a3c8SSimon J. Gerraty else if (skip_string(&p, "commands")) 7179f45a3c8SSimon J. Gerraty fn = FuncCommands; 7189f45a3c8SSimon J. Gerraty else 71912904384SSimon J. Gerraty return false; 720e2eeea75SSimon J. Gerraty 7219f45a3c8SSimon J. Gerraty cpp_skip_whitespace(&p); 7229f45a3c8SSimon J. Gerraty if (*p != '(') 72312904384SSimon J. Gerraty return false; 724e2eeea75SSimon J. Gerraty 7259f45a3c8SSimon J. Gerraty arg = ParseFuncArg(par, &p, doEval, fn_name); 7269f45a3c8SSimon J. Gerraty *out_token = ToToken(doEval && 7279f45a3c8SSimon J. Gerraty arg != NULL && arg[0] != '\0' && fn(arg)); 728e2eeea75SSimon J. Gerraty free(arg); 7299f45a3c8SSimon J. Gerraty 7309f45a3c8SSimon J. Gerraty par->p = p; 731b0c40a00SSimon J. Gerraty return true; 732e2eeea75SSimon J. Gerraty } 733e2eeea75SSimon J. Gerraty 73406b9b3e0SSimon J. Gerraty /* 73512904384SSimon J. Gerraty * Parse a comparison that neither starts with '"' nor '$', such as the 73612904384SSimon J. Gerraty * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without 737b0c40a00SSimon J. Gerraty * operator, which is a number, a variable expression or a string literal. 73812904384SSimon J. Gerraty * 73912904384SSimon J. Gerraty * TODO: Can this be merged into CondParser_Comparison? 74006b9b3e0SSimon J. Gerraty */ 741e2eeea75SSimon J. Gerraty static Token 742b0c40a00SSimon J. Gerraty CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) 743e2eeea75SSimon J. Gerraty { 7443955d011SMarcel Moolenaar Token t; 7459f45a3c8SSimon J. Gerraty char *arg; 74606b9b3e0SSimon J. Gerraty const char *cp; 7473955d011SMarcel Moolenaar 7483955d011SMarcel Moolenaar /* Push anything numeric through the compare expression */ 749956e45f6SSimon J. Gerraty cp = par->p; 750e2eeea75SSimon J. Gerraty if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') 751956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 7523955d011SMarcel Moolenaar 7533955d011SMarcel Moolenaar /* 7543955d011SMarcel Moolenaar * Most likely we have a naked token to apply the default function to. 7553955d011SMarcel Moolenaar * However ".if a == b" gets here when the "a" is unquoted and doesn't 7563955d011SMarcel Moolenaar * start with a '$'. This surprises people. 75706b9b3e0SSimon J. Gerraty * If what follows the function argument is a '=' or '!' then the 75806b9b3e0SSimon J. Gerraty * syntax would be invalid if we did "defined(a)" - so instead treat 75906b9b3e0SSimon J. Gerraty * as an expression. 7603955d011SMarcel Moolenaar */ 761b0c40a00SSimon J. Gerraty /* 7629f45a3c8SSimon J. Gerraty * XXX: In edge cases, a variable expression may be evaluated twice, 7639f45a3c8SSimon J. Gerraty * see cond-token-plain.mk, keyword 'twice'. 764b0c40a00SSimon J. Gerraty */ 7659f45a3c8SSimon J. Gerraty arg = ParseWord(&cp, doEval); 7669f45a3c8SSimon J. Gerraty assert(arg[0] != '\0'); 7679f45a3c8SSimon J. Gerraty 7689f45a3c8SSimon J. Gerraty if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>') 769956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 770956e45f6SSimon J. Gerraty par->p = cp; 7713955d011SMarcel Moolenaar 7723955d011SMarcel Moolenaar /* 7733955d011SMarcel Moolenaar * Evaluate the argument using the default function. 774956e45f6SSimon J. Gerraty * This path always treats .if as .ifdef. To get here, the character 7753955d011SMarcel Moolenaar * after .if must have been taken literally, so the argument cannot 7763955d011SMarcel Moolenaar * be empty - even if it contained a variable expansion. 7773955d011SMarcel Moolenaar */ 7789f45a3c8SSimon J. Gerraty t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); 7793955d011SMarcel Moolenaar free(arg); 7803955d011SMarcel Moolenaar return t; 7813955d011SMarcel Moolenaar } 7823955d011SMarcel Moolenaar 783956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */ 7843955d011SMarcel Moolenaar static Token 785b0c40a00SSimon J. Gerraty CondParser_Token(CondParser *par, bool doEval) 7863955d011SMarcel Moolenaar { 7873955d011SMarcel Moolenaar Token t; 7883955d011SMarcel Moolenaar 789956e45f6SSimon J. Gerraty t = par->curr; 7903955d011SMarcel Moolenaar if (t != TOK_NONE) { 791956e45f6SSimon J. Gerraty par->curr = TOK_NONE; 7923955d011SMarcel Moolenaar return t; 7933955d011SMarcel Moolenaar } 7943955d011SMarcel Moolenaar 795e2eeea75SSimon J. Gerraty cpp_skip_hspace(&par->p); 7963955d011SMarcel Moolenaar 797956e45f6SSimon J. Gerraty switch (par->p[0]) { 7983955d011SMarcel Moolenaar 7993955d011SMarcel Moolenaar case '(': 800956e45f6SSimon J. Gerraty par->p++; 8013955d011SMarcel Moolenaar return TOK_LPAREN; 8023955d011SMarcel Moolenaar 8033955d011SMarcel Moolenaar case ')': 804956e45f6SSimon J. Gerraty par->p++; 8053955d011SMarcel Moolenaar return TOK_RPAREN; 8063955d011SMarcel Moolenaar 8073955d011SMarcel Moolenaar case '|': 808956e45f6SSimon J. Gerraty par->p++; 809e2eeea75SSimon J. Gerraty if (par->p[0] == '|') 810956e45f6SSimon J. Gerraty par->p++; 81106b9b3e0SSimon J. Gerraty else if (opts.strict) { 812e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '|'"); 813b0c40a00SSimon J. Gerraty par->printedError = true; 814e2eeea75SSimon J. Gerraty return TOK_ERROR; 8153955d011SMarcel Moolenaar } 8163955d011SMarcel Moolenaar return TOK_OR; 8173955d011SMarcel Moolenaar 8183955d011SMarcel Moolenaar case '&': 819956e45f6SSimon J. Gerraty par->p++; 820e2eeea75SSimon J. Gerraty if (par->p[0] == '&') 821956e45f6SSimon J. Gerraty par->p++; 82206b9b3e0SSimon J. Gerraty else if (opts.strict) { 823e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '&'"); 824b0c40a00SSimon J. Gerraty par->printedError = true; 825e2eeea75SSimon J. Gerraty return TOK_ERROR; 8263955d011SMarcel Moolenaar } 8273955d011SMarcel Moolenaar return TOK_AND; 8283955d011SMarcel Moolenaar 8293955d011SMarcel Moolenaar case '!': 830956e45f6SSimon J. Gerraty par->p++; 8313955d011SMarcel Moolenaar return TOK_NOT; 8323955d011SMarcel Moolenaar 833e2eeea75SSimon J. Gerraty case '#': /* XXX: see unit-tests/cond-token-plain.mk */ 834e2eeea75SSimon J. Gerraty case '\n': /* XXX: why should this end the condition? */ 835e2eeea75SSimon J. Gerraty /* Probably obsolete now, from 1993-03-21. */ 8363955d011SMarcel Moolenaar case '\0': 8373955d011SMarcel Moolenaar return TOK_EOF; 8383955d011SMarcel Moolenaar 8393955d011SMarcel Moolenaar case '"': 8403955d011SMarcel Moolenaar case '$': 841956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 8423955d011SMarcel Moolenaar 8433955d011SMarcel Moolenaar default: 84412904384SSimon J. Gerraty if (CondParser_FuncCallEmpty(par, doEval, &t)) 84512904384SSimon J. Gerraty return t; 846b0c40a00SSimon J. Gerraty if (CondParser_FuncCall(par, doEval, &t)) 847b0c40a00SSimon J. Gerraty return t; 848b0c40a00SSimon J. Gerraty return CondParser_ComparisonOrLeaf(par, doEval); 8493955d011SMarcel Moolenaar } 8503955d011SMarcel Moolenaar } 8513955d011SMarcel Moolenaar 85212904384SSimon J. Gerraty /* Skip the next token if it equals t. */ 85312904384SSimon J. Gerraty static bool 85412904384SSimon J. Gerraty CondParser_Skip(CondParser *par, Token t) 85512904384SSimon J. Gerraty { 85612904384SSimon J. Gerraty Token actual; 85712904384SSimon J. Gerraty 85812904384SSimon J. Gerraty actual = CondParser_Token(par, false); 85912904384SSimon J. Gerraty if (actual == t) 86012904384SSimon J. Gerraty return true; 86112904384SSimon J. Gerraty 86212904384SSimon J. Gerraty assert(par->curr == TOK_NONE); 86312904384SSimon J. Gerraty assert(actual != TOK_NONE); 86412904384SSimon J. Gerraty par->curr = actual; 86512904384SSimon J. Gerraty return false; 86612904384SSimon J. Gerraty } 86712904384SSimon J. Gerraty 86806b9b3e0SSimon J. Gerraty /* 869dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 870dba7b0efSSimon J. Gerraty * Term -> '!' Term 871dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 872dba7b0efSSimon J. Gerraty * Term -> Leaf 8733955d011SMarcel Moolenaar */ 874dba7b0efSSimon J. Gerraty static CondResult 875b0c40a00SSimon J. Gerraty CondParser_Term(CondParser *par, bool doEval) 8763955d011SMarcel Moolenaar { 877dba7b0efSSimon J. Gerraty CondResult res; 8783955d011SMarcel Moolenaar Token t; 8793955d011SMarcel Moolenaar 880956e45f6SSimon J. Gerraty t = CondParser_Token(par, doEval); 881dba7b0efSSimon J. Gerraty if (t == TOK_TRUE) 882dba7b0efSSimon J. Gerraty return CR_TRUE; 883dba7b0efSSimon J. Gerraty if (t == TOK_FALSE) 884dba7b0efSSimon J. Gerraty return CR_FALSE; 8853955d011SMarcel Moolenaar 886dba7b0efSSimon J. Gerraty if (t == TOK_LPAREN) { 887dba7b0efSSimon J. Gerraty res = CondParser_Or(par, doEval); 888dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 889dba7b0efSSimon J. Gerraty return CR_ERROR; 890dba7b0efSSimon J. Gerraty if (CondParser_Token(par, doEval) != TOK_RPAREN) 891dba7b0efSSimon J. Gerraty return CR_ERROR; 892dba7b0efSSimon J. Gerraty return res; 8933955d011SMarcel Moolenaar } 894dba7b0efSSimon J. Gerraty 895dba7b0efSSimon J. Gerraty if (t == TOK_NOT) { 896dba7b0efSSimon J. Gerraty res = CondParser_Term(par, doEval); 897dba7b0efSSimon J. Gerraty if (res == CR_TRUE) 898dba7b0efSSimon J. Gerraty res = CR_FALSE; 899dba7b0efSSimon J. Gerraty else if (res == CR_FALSE) 900dba7b0efSSimon J. Gerraty res = CR_TRUE; 901dba7b0efSSimon J. Gerraty return res; 9023955d011SMarcel Moolenaar } 903dba7b0efSSimon J. Gerraty 904dba7b0efSSimon J. Gerraty return CR_ERROR; 9053955d011SMarcel Moolenaar } 9062c3632d1SSimon J. Gerraty 90706b9b3e0SSimon J. Gerraty /* 90812904384SSimon J. Gerraty * And -> Term ('&&' Term)* 9093955d011SMarcel Moolenaar */ 910dba7b0efSSimon J. Gerraty static CondResult 911b0c40a00SSimon J. Gerraty CondParser_And(CondParser *par, bool doEval) 9123955d011SMarcel Moolenaar { 91312904384SSimon J. Gerraty CondResult res, rhs; 9143955d011SMarcel Moolenaar 91512904384SSimon J. Gerraty res = CR_TRUE; 91612904384SSimon J. Gerraty do { 91712904384SSimon J. Gerraty if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR) 918dba7b0efSSimon J. Gerraty return CR_ERROR; 91912904384SSimon J. Gerraty if (rhs == CR_FALSE) { 92012904384SSimon J. Gerraty res = CR_FALSE; 92112904384SSimon J. Gerraty doEval = false; 9223955d011SMarcel Moolenaar } 92312904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_AND)); 924dba7b0efSSimon J. Gerraty 925dba7b0efSSimon J. Gerraty return res; 9263955d011SMarcel Moolenaar } 9272c3632d1SSimon J. Gerraty 92806b9b3e0SSimon J. Gerraty /* 92912904384SSimon J. Gerraty * Or -> And ('||' And)* 9303955d011SMarcel Moolenaar */ 931dba7b0efSSimon J. Gerraty static CondResult 932b0c40a00SSimon J. Gerraty CondParser_Or(CondParser *par, bool doEval) 9333955d011SMarcel Moolenaar { 93412904384SSimon J. Gerraty CondResult res, rhs; 9353955d011SMarcel Moolenaar 93612904384SSimon J. Gerraty res = CR_FALSE; 93712904384SSimon J. Gerraty do { 93812904384SSimon J. Gerraty if ((rhs = CondParser_And(par, doEval)) == CR_ERROR) 939dba7b0efSSimon J. Gerraty return CR_ERROR; 94012904384SSimon J. Gerraty if (rhs == CR_TRUE) { 94112904384SSimon J. Gerraty res = CR_TRUE; 94212904384SSimon J. Gerraty doEval = false; 9433955d011SMarcel Moolenaar } 94412904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_OR)); 945dba7b0efSSimon J. Gerraty 946dba7b0efSSimon J. Gerraty return res; 9473955d011SMarcel Moolenaar } 9483955d011SMarcel Moolenaar 9499f45a3c8SSimon J. Gerraty static CondResult 9509f45a3c8SSimon J. Gerraty CondParser_Eval(CondParser *par) 9512c3632d1SSimon J. Gerraty { 952dba7b0efSSimon J. Gerraty CondResult res; 9532c3632d1SSimon J. Gerraty 954956e45f6SSimon J. Gerraty DEBUG1(COND, "CondParser_Eval: %s\n", par->p); 9552c3632d1SSimon J. Gerraty 956b0c40a00SSimon J. Gerraty res = CondParser_Or(par, true); 9579f45a3c8SSimon J. Gerraty if (res != CR_ERROR && CondParser_Token(par, false) != TOK_EOF) 9589f45a3c8SSimon J. Gerraty return CR_ERROR; 959956e45f6SSimon J. Gerraty 9609f45a3c8SSimon J. Gerraty return res; 9612c3632d1SSimon J. Gerraty } 9622c3632d1SSimon J. Gerraty 96306b9b3e0SSimon J. Gerraty /* 96406b9b3e0SSimon J. Gerraty * Evaluate the condition, including any side effects from the variable 965956e45f6SSimon J. Gerraty * expressions in the condition. The condition consists of &&, ||, !, 966956e45f6SSimon J. Gerraty * function(arg), comparisons and parenthetical groupings thereof. 9673955d011SMarcel Moolenaar */ 9689f45a3c8SSimon J. Gerraty static CondResult 9699f45a3c8SSimon J. Gerraty CondEvalExpression(const char *cond, bool plain, 97012904384SSimon J. Gerraty bool (*evalBare)(const char *), bool negate, 97112904384SSimon J. Gerraty bool eprint, bool leftUnquotedOK) 9723955d011SMarcel Moolenaar { 973956e45f6SSimon J. Gerraty CondParser par; 9749f45a3c8SSimon J. Gerraty CondResult rval; 9753955d011SMarcel Moolenaar 976e2eeea75SSimon J. Gerraty cpp_skip_hspace(&cond); 9773955d011SMarcel Moolenaar 978dba7b0efSSimon J. Gerraty par.plain = plain; 979dba7b0efSSimon J. Gerraty par.evalBare = evalBare; 980dba7b0efSSimon J. Gerraty par.negateEvalBare = negate; 98112904384SSimon J. Gerraty par.leftUnquotedOK = leftUnquotedOK; 982956e45f6SSimon J. Gerraty par.p = cond; 983956e45f6SSimon J. Gerraty par.curr = TOK_NONE; 984b0c40a00SSimon J. Gerraty par.printedError = false; 9853955d011SMarcel Moolenaar 9869f45a3c8SSimon J. Gerraty rval = CondParser_Eval(&par); 9873955d011SMarcel Moolenaar 9889f45a3c8SSimon J. Gerraty if (rval == CR_ERROR && eprint && !par.printedError) 989956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); 9903955d011SMarcel Moolenaar 9913955d011SMarcel Moolenaar return rval; 9923955d011SMarcel Moolenaar } 9933955d011SMarcel Moolenaar 99406b9b3e0SSimon J. Gerraty /* 99506b9b3e0SSimon J. Gerraty * Evaluate a condition in a :? modifier, such as 99606b9b3e0SSimon J. Gerraty * ${"${VAR}" == value:?yes:no}. 99706b9b3e0SSimon J. Gerraty */ 9989f45a3c8SSimon J. Gerraty CondResult 9999f45a3c8SSimon J. Gerraty Cond_EvalCondition(const char *cond) 1000956e45f6SSimon J. Gerraty { 10019f45a3c8SSimon J. Gerraty return CondEvalExpression(cond, true, 100212904384SSimon J. Gerraty FuncDefined, false, false, true); 1003956e45f6SSimon J. Gerraty } 10043955d011SMarcel Moolenaar 1005b0c40a00SSimon J. Gerraty static bool 100606b9b3e0SSimon J. Gerraty IsEndif(const char *p) 100706b9b3e0SSimon J. Gerraty { 100806b9b3e0SSimon J. Gerraty return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && 100906b9b3e0SSimon J. Gerraty p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); 101006b9b3e0SSimon J. Gerraty } 101106b9b3e0SSimon J. Gerraty 1012b0c40a00SSimon J. Gerraty static bool 1013b0c40a00SSimon J. Gerraty DetermineKindOfConditional(const char **pp, bool *out_plain, 101412904384SSimon J. Gerraty bool (**out_evalBare)(const char *), 1015b0c40a00SSimon J. Gerraty bool *out_negate) 1016dba7b0efSSimon J. Gerraty { 10179f45a3c8SSimon J. Gerraty const char *p = *pp + 2; 1018dba7b0efSSimon J. Gerraty 1019b0c40a00SSimon J. Gerraty *out_plain = false; 1020dba7b0efSSimon J. Gerraty *out_evalBare = FuncDefined; 10219f45a3c8SSimon J. Gerraty *out_negate = skip_string(&p, "n"); 10229f45a3c8SSimon J. Gerraty 10239f45a3c8SSimon J. Gerraty if (skip_string(&p, "def")) { /* .ifdef and .ifndef */ 10249f45a3c8SSimon J. Gerraty } else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */ 1025dba7b0efSSimon J. Gerraty *out_evalBare = FuncMake; 10269f45a3c8SSimon J. Gerraty else if (!*out_negate) /* plain .if */ 1027b0c40a00SSimon J. Gerraty *out_plain = true; 10289f45a3c8SSimon J. Gerraty else 10299f45a3c8SSimon J. Gerraty goto unknown_directive; 10309f45a3c8SSimon J. Gerraty if (ch_isalpha(*p)) 10319f45a3c8SSimon J. Gerraty goto unknown_directive; 10329f45a3c8SSimon J. Gerraty 10339f45a3c8SSimon J. Gerraty *pp = p; 10349f45a3c8SSimon J. Gerraty return true; 10359f45a3c8SSimon J. Gerraty 10369f45a3c8SSimon J. Gerraty unknown_directive: 1037dba7b0efSSimon J. Gerraty /* 10389f45a3c8SSimon J. Gerraty * TODO: Add error message about unknown directive, since there is no 10399f45a3c8SSimon J. Gerraty * other known directive that starts with 'el' or 'if'. 1040dba7b0efSSimon J. Gerraty * 1041dba7b0efSSimon J. Gerraty * Example: .elifx 123 1042dba7b0efSSimon J. Gerraty */ 1043b0c40a00SSimon J. Gerraty return false; 1044dba7b0efSSimon J. Gerraty } 1045dba7b0efSSimon J. Gerraty 104606b9b3e0SSimon J. Gerraty /* 104706b9b3e0SSimon J. Gerraty * Evaluate the conditional directive in the line, which is one of: 10483955d011SMarcel Moolenaar * 1049e2eeea75SSimon J. Gerraty * .if <cond> 1050e2eeea75SSimon J. Gerraty * .ifmake <cond> 1051e2eeea75SSimon J. Gerraty * .ifnmake <cond> 1052e2eeea75SSimon J. Gerraty * .ifdef <cond> 1053e2eeea75SSimon J. Gerraty * .ifndef <cond> 1054e2eeea75SSimon J. Gerraty * .elif <cond> 1055e2eeea75SSimon J. Gerraty * .elifmake <cond> 1056e2eeea75SSimon J. Gerraty * .elifnmake <cond> 1057e2eeea75SSimon J. Gerraty * .elifdef <cond> 1058e2eeea75SSimon J. Gerraty * .elifndef <cond> 1059e2eeea75SSimon J. Gerraty * .else 1060e2eeea75SSimon J. Gerraty * .endif 1061e2eeea75SSimon J. Gerraty * 1062e2eeea75SSimon J. Gerraty * In these directives, <cond> consists of &&, ||, !, function(arg), 1063e2eeea75SSimon J. Gerraty * comparisons, expressions, bare words, numbers and strings, and 1064e2eeea75SSimon J. Gerraty * parenthetical groupings thereof. 1065956e45f6SSimon J. Gerraty * 1066956e45f6SSimon J. Gerraty * Results: 10679f45a3c8SSimon J. Gerraty * CR_TRUE to continue parsing the lines that follow the 1068b0c40a00SSimon J. Gerraty * conditional (when <cond> evaluates to true) 10699f45a3c8SSimon J. Gerraty * CR_FALSE to skip the lines after the conditional 1070b0c40a00SSimon J. Gerraty * (when <cond> evaluates to false, or when a previous 1071956e45f6SSimon J. Gerraty * branch has already been taken) 10729f45a3c8SSimon J. Gerraty * CR_ERROR if the conditional was not valid, either because of 1073956e45f6SSimon J. Gerraty * a syntax error or because some variable was undefined 1074956e45f6SSimon J. Gerraty * or because the condition could not be evaluated 10753955d011SMarcel Moolenaar */ 10769f45a3c8SSimon J. Gerraty CondResult 107706b9b3e0SSimon J. Gerraty Cond_EvalLine(const char *line) 10783955d011SMarcel Moolenaar { 1079e2eeea75SSimon J. Gerraty typedef enum IfState { 1080e2eeea75SSimon J. Gerraty 1081b0c40a00SSimon J. Gerraty /* None of the previous <cond> evaluated to true. */ 1082e2eeea75SSimon J. Gerraty IFS_INITIAL = 0, 1083e2eeea75SSimon J. Gerraty 10849f45a3c8SSimon J. Gerraty /* 10859f45a3c8SSimon J. Gerraty * The previous <cond> evaluated to true. The lines following 10869f45a3c8SSimon J. Gerraty * this condition are interpreted. 10879f45a3c8SSimon J. Gerraty */ 1088e2eeea75SSimon J. Gerraty IFS_ACTIVE = 1 << 0, 1089e2eeea75SSimon J. Gerraty 1090e2eeea75SSimon J. Gerraty /* The previous directive was an '.else'. */ 1091e2eeea75SSimon J. Gerraty IFS_SEEN_ELSE = 1 << 1, 1092e2eeea75SSimon J. Gerraty 1093b0c40a00SSimon J. Gerraty /* One of the previous <cond> evaluated to true. */ 1094e2eeea75SSimon J. Gerraty IFS_WAS_ACTIVE = 1 << 2 1095e2eeea75SSimon J. Gerraty 1096e2eeea75SSimon J. Gerraty } IfState; 1097e2eeea75SSimon J. Gerraty 1098e2eeea75SSimon J. Gerraty static enum IfState *cond_states = NULL; 1099e2eeea75SSimon J. Gerraty static unsigned int cond_states_cap = 128; 11003955d011SMarcel Moolenaar 1101b0c40a00SSimon J. Gerraty bool plain; 110212904384SSimon J. Gerraty bool (*evalBare)(const char *); 1103b0c40a00SSimon J. Gerraty bool negate; 1104b0c40a00SSimon J. Gerraty bool isElif; 11059f45a3c8SSimon J. Gerraty CondResult res; 1106e2eeea75SSimon J. Gerraty IfState state; 1107e2eeea75SSimon J. Gerraty const char *p = line; 11083955d011SMarcel Moolenaar 1109e2eeea75SSimon J. Gerraty if (cond_states == NULL) { 111006b9b3e0SSimon J. Gerraty cond_states = bmake_malloc( 111106b9b3e0SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 1112e2eeea75SSimon J. Gerraty cond_states[0] = IFS_ACTIVE; 111359a02420SSimon J. Gerraty } 11143955d011SMarcel Moolenaar 1115e2eeea75SSimon J. Gerraty p++; /* skip the leading '.' */ 1116e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 1117e2eeea75SSimon J. Gerraty 111806b9b3e0SSimon J. Gerraty if (IsEndif(p)) { /* It is an '.endif'. */ 111906b9b3e0SSimon J. Gerraty if (p[5] != '\0') { 112006b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, 112112904384SSimon J. Gerraty "The .endif directive does not take arguments"); 1122e2eeea75SSimon J. Gerraty } 1123e2eeea75SSimon J. Gerraty 11244fde40d9SSimon J. Gerraty if (cond_depth == CurFile_CondMinDepth()) { 1125956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less endif"); 11269f45a3c8SSimon J. Gerraty return CR_TRUE; 11273955d011SMarcel Moolenaar } 1128e2eeea75SSimon J. Gerraty 11293955d011SMarcel Moolenaar /* Return state for previous conditional */ 11303955d011SMarcel Moolenaar cond_depth--; 1131148ee845SSimon J. Gerraty Parse_GuardEndif(); 1132e2eeea75SSimon J. Gerraty return cond_states[cond_depth] & IFS_ACTIVE 11339f45a3c8SSimon J. Gerraty ? CR_TRUE : CR_FALSE; 11343955d011SMarcel Moolenaar } 11353955d011SMarcel Moolenaar 113606b9b3e0SSimon J. Gerraty /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ 113706b9b3e0SSimon J. Gerraty if (p[0] == 'e') { 113806b9b3e0SSimon J. Gerraty if (p[1] != 'l') { 113906b9b3e0SSimon J. Gerraty /* 114006b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a 114112904384SSimon J. Gerraty * transformation rule like '.err.txt', 114206b9b3e0SSimon J. Gerraty * therefore no error message here. 114306b9b3e0SSimon J. Gerraty */ 11449f45a3c8SSimon J. Gerraty return CR_ERROR; 114506b9b3e0SSimon J. Gerraty } 114606b9b3e0SSimon J. Gerraty 11473955d011SMarcel Moolenaar /* Quite likely this is 'else' or 'elif' */ 1148e2eeea75SSimon J. Gerraty p += 2; 11499f45a3c8SSimon J. Gerraty if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) { 115006b9b3e0SSimon J. Gerraty if (p[2] != '\0') 1151e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, 115206b9b3e0SSimon J. Gerraty "The .else directive " 115312904384SSimon J. Gerraty "does not take arguments"); 1154e2eeea75SSimon J. Gerraty 11554fde40d9SSimon J. Gerraty if (cond_depth == CurFile_CondMinDepth()) { 1156956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less else"); 11579f45a3c8SSimon J. Gerraty return CR_TRUE; 11583955d011SMarcel Moolenaar } 1159148ee845SSimon J. Gerraty Parse_GuardElse(); 11603955d011SMarcel Moolenaar 1161e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1162e2eeea75SSimon J. Gerraty if (state == IFS_INITIAL) { 1163e2eeea75SSimon J. Gerraty state = IFS_ACTIVE | IFS_SEEN_ELSE; 1164e2eeea75SSimon J. Gerraty } else { 1165e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) 116606b9b3e0SSimon J. Gerraty Parse_Error(PARSE_WARNING, 116706b9b3e0SSimon J. Gerraty "extra else"); 1168e2eeea75SSimon J. Gerraty state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 11693955d011SMarcel Moolenaar } 1170e2eeea75SSimon J. Gerraty cond_states[cond_depth] = state; 1171e2eeea75SSimon J. Gerraty 11729f45a3c8SSimon J. Gerraty return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE; 11733955d011SMarcel Moolenaar } 11743955d011SMarcel Moolenaar /* Assume for now it is an elif */ 1175b0c40a00SSimon J. Gerraty isElif = true; 11763955d011SMarcel Moolenaar } else 1177b0c40a00SSimon J. Gerraty isElif = false; 11783955d011SMarcel Moolenaar 1179e2eeea75SSimon J. Gerraty if (p[0] != 'i' || p[1] != 'f') { 118006b9b3e0SSimon J. Gerraty /* 118106b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a transformation rule 118206b9b3e0SSimon J. Gerraty * like '.elisp.scm', therefore no error message here. 118306b9b3e0SSimon J. Gerraty */ 11849f45a3c8SSimon J. Gerraty return CR_ERROR; /* Not an ifxxx or elifxxx line */ 1185e2eeea75SSimon J. Gerraty } 11863955d011SMarcel Moolenaar 1187dba7b0efSSimon J. Gerraty if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) 11889f45a3c8SSimon J. Gerraty return CR_ERROR; 11893955d011SMarcel Moolenaar 11903955d011SMarcel Moolenaar if (isElif) { 11914fde40d9SSimon J. Gerraty if (cond_depth == CurFile_CondMinDepth()) { 1192956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less elif"); 11939f45a3c8SSimon J. Gerraty return CR_TRUE; 11943955d011SMarcel Moolenaar } 1195148ee845SSimon J. Gerraty Parse_GuardElse(); 1196e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1197e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) { 11983955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "extra elif"); 119906b9b3e0SSimon J. Gerraty cond_states[cond_depth] = 120006b9b3e0SSimon J. Gerraty IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 12019f45a3c8SSimon J. Gerraty return CR_FALSE; 12023955d011SMarcel Moolenaar } 1203e2eeea75SSimon J. Gerraty if (state != IFS_INITIAL) { 1204e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12059f45a3c8SSimon J. Gerraty return CR_FALSE; 12063955d011SMarcel Moolenaar } 12073955d011SMarcel Moolenaar } else { 12083955d011SMarcel Moolenaar /* Normal .if */ 1209e2eeea75SSimon J. Gerraty if (cond_depth + 1 >= cond_states_cap) { 121059a02420SSimon J. Gerraty /* 121159a02420SSimon J. Gerraty * This is rare, but not impossible. 121259a02420SSimon J. Gerraty * In meta mode, dirdeps.mk (only runs at level 0) 121359a02420SSimon J. Gerraty * can need more than the default. 121459a02420SSimon J. Gerraty */ 1215e2eeea75SSimon J. Gerraty cond_states_cap += 32; 1216e2eeea75SSimon J. Gerraty cond_states = bmake_realloc(cond_states, 12179f45a3c8SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 12183955d011SMarcel Moolenaar } 1219e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 12203955d011SMarcel Moolenaar cond_depth++; 1221e2eeea75SSimon J. Gerraty if (!(state & IFS_ACTIVE)) { 122206b9b3e0SSimon J. Gerraty /* 122306b9b3e0SSimon J. Gerraty * If we aren't parsing the data, 122406b9b3e0SSimon J. Gerraty * treat as always false. 122506b9b3e0SSimon J. Gerraty */ 1226e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12279f45a3c8SSimon J. Gerraty return CR_FALSE; 12283955d011SMarcel Moolenaar } 12293955d011SMarcel Moolenaar } 12303955d011SMarcel Moolenaar 1231956e45f6SSimon J. Gerraty /* And evaluate the conditional expression */ 12329f45a3c8SSimon J. Gerraty res = CondEvalExpression(p, plain, evalBare, negate, true, false); 12339f45a3c8SSimon J. Gerraty if (res == CR_ERROR) { 12349f45a3c8SSimon J. Gerraty /* Syntax error, error message already output. */ 12359f45a3c8SSimon J. Gerraty /* Skip everything to the matching '.endif'. */ 12369f45a3c8SSimon J. Gerraty /* An extra '.else' is not detected in this case. */ 1237e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12389f45a3c8SSimon J. Gerraty return CR_FALSE; 12393955d011SMarcel Moolenaar } 12403955d011SMarcel Moolenaar 12419f45a3c8SSimon J. Gerraty cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL; 12429f45a3c8SSimon J. Gerraty return res; 12433955d011SMarcel Moolenaar } 12443955d011SMarcel Moolenaar 1245148ee845SSimon J. Gerraty static bool 1246148ee845SSimon J. Gerraty ParseVarnameGuard(const char **pp, const char **varname) 1247148ee845SSimon J. Gerraty { 1248148ee845SSimon J. Gerraty const char *p = *pp; 1249148ee845SSimon J. Gerraty 1250148ee845SSimon J. Gerraty if (ch_isalpha(*p) || *p == '_') { 1251148ee845SSimon J. Gerraty while (ch_isalnum(*p) || *p == '_') 1252148ee845SSimon J. Gerraty p++; 1253148ee845SSimon J. Gerraty *varname = *pp; 1254148ee845SSimon J. Gerraty *pp = p; 1255148ee845SSimon J. Gerraty return true; 1256148ee845SSimon J. Gerraty } 1257148ee845SSimon J. Gerraty return false; 1258148ee845SSimon J. Gerraty } 1259148ee845SSimon J. Gerraty 1260148ee845SSimon J. Gerraty /* Extracts the multiple-inclusion guard from a conditional, if any. */ 1261148ee845SSimon J. Gerraty Guard * 1262148ee845SSimon J. Gerraty Cond_ExtractGuard(const char *line) 1263148ee845SSimon J. Gerraty { 1264148ee845SSimon J. Gerraty const char *p, *varname; 1265148ee845SSimon J. Gerraty Substring dir; 1266148ee845SSimon J. Gerraty Guard *guard; 1267148ee845SSimon J. Gerraty 1268148ee845SSimon J. Gerraty p = line + 1; /* skip the '.' */ 1269148ee845SSimon J. Gerraty cpp_skip_hspace(&p); 1270148ee845SSimon J. Gerraty 1271148ee845SSimon J. Gerraty dir.start = p; 1272148ee845SSimon J. Gerraty while (ch_isalpha(*p)) 1273148ee845SSimon J. Gerraty p++; 1274148ee845SSimon J. Gerraty dir.end = p; 1275148ee845SSimon J. Gerraty cpp_skip_hspace(&p); 1276148ee845SSimon J. Gerraty 1277148ee845SSimon J. Gerraty if (Substring_Equals(dir, "if")) { 1278148ee845SSimon J. Gerraty if (skip_string(&p, "!defined(")) { 1279148ee845SSimon J. Gerraty if (ParseVarnameGuard(&p, &varname) 1280148ee845SSimon J. Gerraty && strcmp(p, ")") == 0) 1281148ee845SSimon J. Gerraty goto found_variable; 1282148ee845SSimon J. Gerraty } else if (skip_string(&p, "!target(")) { 1283148ee845SSimon J. Gerraty const char *arg_p = p; 1284148ee845SSimon J. Gerraty free(ParseWord(&p, false)); 1285148ee845SSimon J. Gerraty if (strcmp(p, ")") == 0) { 1286148ee845SSimon J. Gerraty guard = bmake_malloc(sizeof(*guard)); 1287148ee845SSimon J. Gerraty guard->kind = GK_TARGET; 1288*98875883SSimon J. Gerraty guard->name = ParseWord(&arg_p, true); 1289148ee845SSimon J. Gerraty return guard; 1290148ee845SSimon J. Gerraty } 1291148ee845SSimon J. Gerraty } 1292148ee845SSimon J. Gerraty } else if (Substring_Equals(dir, "ifndef")) { 1293148ee845SSimon J. Gerraty if (ParseVarnameGuard(&p, &varname) && *p == '\0') 1294148ee845SSimon J. Gerraty goto found_variable; 1295148ee845SSimon J. Gerraty } 1296148ee845SSimon J. Gerraty return NULL; 1297148ee845SSimon J. Gerraty 1298148ee845SSimon J. Gerraty found_variable: 1299148ee845SSimon J. Gerraty guard = bmake_malloc(sizeof(*guard)); 1300*98875883SSimon J. Gerraty guard->kind = GK_VARIABLE; 1301148ee845SSimon J. Gerraty guard->name = bmake_strsedup(varname, p); 1302148ee845SSimon J. Gerraty return guard; 1303148ee845SSimon J. Gerraty } 1304148ee845SSimon J. Gerraty 13053955d011SMarcel Moolenaar void 13064fde40d9SSimon J. Gerraty Cond_EndFile(void) 13073955d011SMarcel Moolenaar { 13084fde40d9SSimon J. Gerraty unsigned int open_conds = cond_depth - CurFile_CondMinDepth(); 13093955d011SMarcel Moolenaar 13104fde40d9SSimon J. Gerraty if (open_conds != 0) { 131106b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, "%u open conditional%s", 131206b9b3e0SSimon J. Gerraty open_conds, open_conds == 1 ? "" : "s"); 13134fde40d9SSimon J. Gerraty cond_depth = CurFile_CondMinDepth(); 13143955d011SMarcel Moolenaar } 13153955d011SMarcel Moolenaar } 1316