1*8c973ee2SSimon J. Gerraty /* $NetBSD: cond.c,v 1.344 2023/02/14 21:08:00 rillig Exp $ */ 23955d011SMarcel Moolenaar 33955d011SMarcel Moolenaar /* 43955d011SMarcel Moolenaar * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. 53955d011SMarcel Moolenaar * All rights reserved. 63955d011SMarcel Moolenaar * 73955d011SMarcel Moolenaar * This code is derived from software contributed to Berkeley by 83955d011SMarcel Moolenaar * Adam de Boor. 93955d011SMarcel Moolenaar * 103955d011SMarcel Moolenaar * Redistribution and use in source and binary forms, with or without 113955d011SMarcel Moolenaar * modification, are permitted provided that the following conditions 123955d011SMarcel Moolenaar * are met: 133955d011SMarcel Moolenaar * 1. Redistributions of source code must retain the above copyright 143955d011SMarcel Moolenaar * notice, this list of conditions and the following disclaimer. 153955d011SMarcel Moolenaar * 2. Redistributions in binary form must reproduce the above copyright 163955d011SMarcel Moolenaar * notice, this list of conditions and the following disclaimer in the 173955d011SMarcel Moolenaar * documentation and/or other materials provided with the distribution. 183955d011SMarcel Moolenaar * 3. Neither the name of the University nor the names of its contributors 193955d011SMarcel Moolenaar * may be used to endorse or promote products derived from this software 203955d011SMarcel Moolenaar * without specific prior written permission. 213955d011SMarcel Moolenaar * 223955d011SMarcel Moolenaar * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 233955d011SMarcel Moolenaar * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 243955d011SMarcel Moolenaar * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 253955d011SMarcel Moolenaar * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 263955d011SMarcel Moolenaar * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 273955d011SMarcel Moolenaar * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 283955d011SMarcel Moolenaar * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 293955d011SMarcel Moolenaar * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 303955d011SMarcel Moolenaar * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 313955d011SMarcel Moolenaar * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 323955d011SMarcel Moolenaar * SUCH DAMAGE. 333955d011SMarcel Moolenaar */ 343955d011SMarcel Moolenaar 353955d011SMarcel Moolenaar /* 363955d011SMarcel Moolenaar * Copyright (c) 1988, 1989 by Adam de Boor 373955d011SMarcel Moolenaar * Copyright (c) 1989 by Berkeley Softworks 383955d011SMarcel Moolenaar * All rights reserved. 393955d011SMarcel Moolenaar * 403955d011SMarcel Moolenaar * This code is derived from software contributed to Berkeley by 413955d011SMarcel Moolenaar * Adam de Boor. 423955d011SMarcel Moolenaar * 433955d011SMarcel Moolenaar * Redistribution and use in source and binary forms, with or without 443955d011SMarcel Moolenaar * modification, are permitted provided that the following conditions 453955d011SMarcel Moolenaar * are met: 463955d011SMarcel Moolenaar * 1. Redistributions of source code must retain the above copyright 473955d011SMarcel Moolenaar * notice, this list of conditions and the following disclaimer. 483955d011SMarcel Moolenaar * 2. Redistributions in binary form must reproduce the above copyright 493955d011SMarcel Moolenaar * notice, this list of conditions and the following disclaimer in the 503955d011SMarcel Moolenaar * documentation and/or other materials provided with the distribution. 513955d011SMarcel Moolenaar * 3. All advertising materials mentioning features or use of this software 523955d011SMarcel Moolenaar * must display the following acknowledgement: 533955d011SMarcel Moolenaar * This product includes software developed by the University of 543955d011SMarcel Moolenaar * California, Berkeley and its contributors. 553955d011SMarcel Moolenaar * 4. Neither the name of the University nor the names of its contributors 563955d011SMarcel Moolenaar * may be used to endorse or promote products derived from this software 573955d011SMarcel Moolenaar * without specific prior written permission. 583955d011SMarcel Moolenaar * 593955d011SMarcel Moolenaar * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 603955d011SMarcel Moolenaar * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 613955d011SMarcel Moolenaar * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 623955d011SMarcel Moolenaar * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 633955d011SMarcel Moolenaar * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 643955d011SMarcel Moolenaar * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 653955d011SMarcel Moolenaar * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 663955d011SMarcel Moolenaar * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 673955d011SMarcel Moolenaar * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 683955d011SMarcel Moolenaar * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 693955d011SMarcel Moolenaar * SUCH DAMAGE. 703955d011SMarcel Moolenaar */ 713955d011SMarcel Moolenaar 7206b9b3e0SSimon J. Gerraty /* 7306b9b3e0SSimon J. Gerraty * Handling of conditionals in a makefile. 743955d011SMarcel Moolenaar * 753955d011SMarcel Moolenaar * Interface: 76e2eeea75SSimon J. Gerraty * Cond_EvalLine Evaluate the conditional directive, such as 77e2eeea75SSimon J. Gerraty * '.if <cond>', '.elifnmake <cond>', '.else', '.endif'. 783955d011SMarcel Moolenaar * 79956e45f6SSimon J. Gerraty * Cond_EvalCondition 80956e45f6SSimon J. Gerraty * Evaluate the conditional, which is either the argument 81956e45f6SSimon J. Gerraty * of one of the .if directives or the condition in a 82956e45f6SSimon J. Gerraty * ':?then:else' variable modifier. 83956e45f6SSimon J. Gerraty * 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*8c973ee2SSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.344 2023/02/14 21:08:00 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 139dba7b0efSSimon J. Gerraty * expression has length > 0. The other '.if' variants delegate 1401d3f2ddcSSimon J. Gerraty * to evalBare instead, for example '.ifdef ${VAR}' is equivalent to 1411d3f2ddcSSimon J. Gerraty * '.if defined(${VAR})', checking whether the variable named by the 1421d3f2ddcSSimon J. Gerraty * 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 176b0c40a00SSimon J. Gerraty static CondResult CondParser_Or(CondParser *par, 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 == '$') { 2273955d011SMarcel Moolenaar /* 22806b9b3e0SSimon J. Gerraty * Parse the variable expression and install it as 22906b9b3e0SSimon J. Gerraty * part of the argument if it's valid. We tell 23006b9b3e0SSimon J. Gerraty * Var_Parse to complain on an undefined variable, 23106b9b3e0SSimon J. Gerraty * (XXX: but Var_Parse ignores that request) 23206b9b3e0SSimon J. Gerraty * so we don't need to do it. Nor do we return an 23306b9b3e0SSimon J. Gerraty * error, though perhaps we should. 2343955d011SMarcel Moolenaar */ 235b0c40a00SSimon J. Gerraty VarEvalMode emode = doEval 236b0c40a00SSimon J. Gerraty ? VARE_UNDEFERR 237b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 238*8c973ee2SSimon J. Gerraty FStr nestedVal = Var_Parse(&p, SCOPE_CMDLINE, emode); 239956e45f6SSimon J. Gerraty /* TODO: handle errors */ 2401d3f2ddcSSimon J. Gerraty Buf_AddStr(&word, nestedVal.str); 24106b9b3e0SSimon J. Gerraty FStr_Done(&nestedVal); 2423955d011SMarcel Moolenaar continue; 2433955d011SMarcel Moolenaar } 2443955d011SMarcel Moolenaar if (ch == '(') 2453955d011SMarcel Moolenaar paren_depth++; 2462c3632d1SSimon J. Gerraty else if (ch == ')' && --paren_depth < 0) 2473955d011SMarcel Moolenaar break; 2481d3f2ddcSSimon J. Gerraty Buf_AddByte(&word, ch); 249956e45f6SSimon J. Gerraty p++; 2503955d011SMarcel Moolenaar } 2513955d011SMarcel Moolenaar 2529f45a3c8SSimon J. Gerraty cpp_skip_hspace(&p); 2539f45a3c8SSimon J. Gerraty *pp = p; 2543955d011SMarcel Moolenaar 2551d3f2ddcSSimon J. Gerraty return Buf_DoneData(&word); 2569f45a3c8SSimon J. Gerraty } 2579f45a3c8SSimon J. Gerraty 2589f45a3c8SSimon J. Gerraty /* Parse the function argument, including the surrounding parentheses. */ 2599f45a3c8SSimon J. Gerraty static char * 2609f45a3c8SSimon J. Gerraty ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) 2619f45a3c8SSimon J. Gerraty { 2629f45a3c8SSimon J. Gerraty const char *p = *pp; 2639f45a3c8SSimon J. Gerraty char *res; 2649f45a3c8SSimon J. Gerraty 2659f45a3c8SSimon J. Gerraty p++; /* Skip opening '(' - verified by caller */ 2669f45a3c8SSimon J. Gerraty cpp_skip_hspace(&p); 2679f45a3c8SSimon J. Gerraty res = ParseWord(&p, doEval); 268e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2693955d011SMarcel Moolenaar 2709f45a3c8SSimon J. Gerraty if (*p++ != ')') { 2719f45a3c8SSimon J. Gerraty int len = 0; 2729f45a3c8SSimon J. Gerraty while (ch_isalpha(func[len])) 2739f45a3c8SSimon J. Gerraty len++; 2749f45a3c8SSimon J. Gerraty 275dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 2769f45a3c8SSimon J. Gerraty "Missing closing parenthesis for %.*s()", len, func); 277b0c40a00SSimon J. Gerraty par->printedError = true; 2789f45a3c8SSimon J. Gerraty free(res); 2799f45a3c8SSimon J. Gerraty return NULL; 2803955d011SMarcel Moolenaar } 2813955d011SMarcel Moolenaar 282956e45f6SSimon J. Gerraty *pp = p; 2839f45a3c8SSimon J. Gerraty return res; 2843955d011SMarcel Moolenaar } 2852c3632d1SSimon J. Gerraty 2861d3f2ddcSSimon J. Gerraty /* See if the given variable is defined. */ 287b0c40a00SSimon J. Gerraty static bool 2889f45a3c8SSimon J. Gerraty FuncDefined(const char *var) 2893955d011SMarcel Moolenaar { 2909f45a3c8SSimon J. Gerraty return Var_Exists(SCOPE_CMDLINE, var); 2913955d011SMarcel Moolenaar } 2922c3632d1SSimon J. Gerraty 2931d3f2ddcSSimon J. Gerraty /* See if a target matching targetPattern is requested to be made. */ 294b0c40a00SSimon J. Gerraty static bool 2951d3f2ddcSSimon J. Gerraty FuncMake(const char *targetPattern) 2963955d011SMarcel Moolenaar { 297956e45f6SSimon J. Gerraty StringListNode *ln; 298956e45f6SSimon J. Gerraty 29906b9b3e0SSimon J. Gerraty for (ln = opts.create.first; ln != NULL; ln = ln->next) 3001d3f2ddcSSimon J. Gerraty if (Str_Match(ln->datum, targetPattern)) 301b0c40a00SSimon J. Gerraty return true; 302b0c40a00SSimon J. Gerraty return false; 3033955d011SMarcel Moolenaar } 3042c3632d1SSimon J. Gerraty 3052c3632d1SSimon J. Gerraty /* See if the given file exists. */ 306b0c40a00SSimon J. Gerraty static bool 3079f45a3c8SSimon J. Gerraty FuncExists(const char *file) 3083955d011SMarcel Moolenaar { 309b0c40a00SSimon J. Gerraty bool result; 3103955d011SMarcel Moolenaar char *path; 3113955d011SMarcel Moolenaar 3129f45a3c8SSimon J. Gerraty path = Dir_FindFile(file, &dirSearchPath); 313e2eeea75SSimon J. Gerraty DEBUG2(COND, "exists(%s) result is \"%s\"\n", 3149f45a3c8SSimon J. Gerraty file, path != NULL ? path : ""); 315e2eeea75SSimon J. Gerraty result = path != NULL; 3163955d011SMarcel Moolenaar free(path); 3173841c287SSimon J. Gerraty return result; 3183955d011SMarcel Moolenaar } 3192c3632d1SSimon J. Gerraty 3202c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target. */ 321b0c40a00SSimon J. Gerraty static bool 3229f45a3c8SSimon J. Gerraty FuncTarget(const char *node) 3233955d011SMarcel Moolenaar { 3249f45a3c8SSimon J. Gerraty GNode *gn = Targ_FindNode(node); 325956e45f6SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn); 3263955d011SMarcel Moolenaar } 3273955d011SMarcel Moolenaar 32806b9b3e0SSimon J. Gerraty /* 32906b9b3e0SSimon J. Gerraty * See if the given node exists and is an actual target with commands 33006b9b3e0SSimon J. Gerraty * associated with it. 33106b9b3e0SSimon J. Gerraty */ 332b0c40a00SSimon J. Gerraty static bool 3339f45a3c8SSimon J. Gerraty FuncCommands(const char *node) 3343955d011SMarcel Moolenaar { 3359f45a3c8SSimon J. Gerraty GNode *gn = Targ_FindNode(node); 3369f45a3c8SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn) && 3379f45a3c8SSimon J. Gerraty !Lst_IsEmpty(&gn->commands); 3383955d011SMarcel Moolenaar } 3392c3632d1SSimon J. Gerraty 340e2eeea75SSimon J. Gerraty /* 3419f45a3c8SSimon J. Gerraty * Convert the string into a floating-point number. Accepted formats are 3429f45a3c8SSimon J. Gerraty * base-10 integer, base-16 integer and finite floating point numbers. 3433955d011SMarcel Moolenaar */ 344b0c40a00SSimon J. Gerraty static bool 345e2eeea75SSimon J. Gerraty TryParseNumber(const char *str, double *out_value) 3463955d011SMarcel Moolenaar { 347e2eeea75SSimon J. Gerraty char *end; 348e2eeea75SSimon J. Gerraty unsigned long ul_val; 349e2eeea75SSimon J. Gerraty double dbl_val; 3503955d011SMarcel Moolenaar 351e2eeea75SSimon J. Gerraty if (str[0] == '\0') { /* XXX: why is an empty string a number? */ 352e2eeea75SSimon J. Gerraty *out_value = 0.0; 353b0c40a00SSimon J. Gerraty return true; 354ac3446e9SSimon J. Gerraty } 355e2eeea75SSimon J. Gerraty 35612904384SSimon J. Gerraty errno = 0; 357e2eeea75SSimon J. Gerraty ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); 358e2eeea75SSimon J. Gerraty if (*end == '\0' && errno != ERANGE) { 359e2eeea75SSimon J. Gerraty *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; 360b0c40a00SSimon J. Gerraty return true; 3613955d011SMarcel Moolenaar } 3623955d011SMarcel Moolenaar 363e2eeea75SSimon J. Gerraty if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') 364b0c40a00SSimon J. Gerraty return false; /* skip the expensive strtod call */ 365e2eeea75SSimon J. Gerraty dbl_val = strtod(str, &end); 366e2eeea75SSimon J. Gerraty if (*end != '\0') 367b0c40a00SSimon J. Gerraty return false; 368e2eeea75SSimon J. Gerraty 369e2eeea75SSimon J. Gerraty *out_value = dbl_val; 370b0c40a00SSimon J. Gerraty return true; 3713955d011SMarcel Moolenaar } 3723955d011SMarcel Moolenaar 373b0c40a00SSimon J. Gerraty static bool 374956e45f6SSimon J. Gerraty is_separator(char ch) 375956e45f6SSimon J. Gerraty { 376b0c40a00SSimon J. Gerraty return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' || 377b0c40a00SSimon J. Gerraty ch == '>' || ch == '<' || ch == ')' /* but not '(' */; 378956e45f6SSimon J. Gerraty } 379956e45f6SSimon J. Gerraty 380dba7b0efSSimon J. Gerraty /* 381dba7b0efSSimon J. Gerraty * In a quoted or unquoted string literal or a number, parse a variable 382*8c973ee2SSimon J. Gerraty * expression and add its value to the buffer. 383*8c973ee2SSimon J. Gerraty * 384*8c973ee2SSimon J. Gerraty * Return whether to continue parsing the leaf. 385dba7b0efSSimon J. Gerraty * 386dba7b0efSSimon J. Gerraty * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} 387dba7b0efSSimon J. Gerraty */ 388b0c40a00SSimon J. Gerraty static bool 389dba7b0efSSimon J. Gerraty CondParser_StringExpr(CondParser *par, const char *start, 39012904384SSimon J. Gerraty bool doEval, bool quoted, 39112904384SSimon J. Gerraty Buffer *buf, FStr *inout_str) 392dba7b0efSSimon J. Gerraty { 393b0c40a00SSimon J. Gerraty VarEvalMode emode; 3949f45a3c8SSimon J. Gerraty const char *p; 395b0c40a00SSimon J. Gerraty bool atStart; 396dba7b0efSSimon J. Gerraty 39712904384SSimon J. Gerraty emode = doEval && quoted ? VARE_WANTRES 39812904384SSimon J. Gerraty : doEval ? VARE_UNDEFERR 399b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 400dba7b0efSSimon J. Gerraty 4019f45a3c8SSimon J. Gerraty p = par->p; 4029f45a3c8SSimon J. Gerraty atStart = p == start; 403*8c973ee2SSimon J. Gerraty *inout_str = Var_Parse(&p, SCOPE_CMDLINE, emode); 404dba7b0efSSimon J. Gerraty /* TODO: handle errors */ 405dba7b0efSSimon J. Gerraty if (inout_str->str == var_Error) { 406dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 407dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); 408b0c40a00SSimon J. Gerraty return false; 409dba7b0efSSimon J. Gerraty } 4109f45a3c8SSimon J. Gerraty par->p = p; 411dba7b0efSSimon J. Gerraty 412dba7b0efSSimon J. Gerraty /* 413dba7b0efSSimon J. Gerraty * If the '$' started the string literal (which means no quotes), and 414*8c973ee2SSimon J. Gerraty * the expression is followed by a space, a comparison operator or 415*8c973ee2SSimon J. Gerraty * the end of the expression, we are done. 416dba7b0efSSimon J. Gerraty */ 417dba7b0efSSimon J. Gerraty if (atStart && is_separator(par->p[0])) 418b0c40a00SSimon J. Gerraty return false; 419dba7b0efSSimon J. Gerraty 420dba7b0efSSimon J. Gerraty Buf_AddStr(buf, inout_str->str); 421dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 422dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); /* not finished yet */ 423b0c40a00SSimon J. Gerraty return true; 424dba7b0efSSimon J. Gerraty } 425dba7b0efSSimon J. Gerraty 426dba7b0efSSimon J. Gerraty /* 4279f45a3c8SSimon J. Gerraty * Parse a string from a variable expression or an optionally quoted string, 4289f45a3c8SSimon J. Gerraty * on the left-hand and right-hand sides of comparisons. 4293955d011SMarcel Moolenaar * 4303955d011SMarcel Moolenaar * Results: 4319f45a3c8SSimon J. Gerraty * Returns the string without any enclosing quotes, or NULL on error. 432b0c40a00SSimon J. Gerraty * Sets out_quoted if the leaf was a quoted string literal. 4333955d011SMarcel Moolenaar */ 43406b9b3e0SSimon J. Gerraty static void 43512904384SSimon J. Gerraty CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, 436b0c40a00SSimon J. Gerraty FStr *out_str, bool *out_quoted) 4373955d011SMarcel Moolenaar { 4383955d011SMarcel Moolenaar Buffer buf; 43906b9b3e0SSimon J. Gerraty FStr str; 440b0c40a00SSimon J. Gerraty bool quoted; 4412c3632d1SSimon J. Gerraty const char *start; 4423955d011SMarcel Moolenaar 443e2eeea75SSimon J. Gerraty Buf_Init(&buf); 44406b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 445e2eeea75SSimon J. Gerraty *out_quoted = quoted = par->p[0] == '"'; 446956e45f6SSimon J. Gerraty start = par->p; 447e2eeea75SSimon J. Gerraty if (quoted) 448956e45f6SSimon J. Gerraty par->p++; 44906b9b3e0SSimon J. Gerraty 45006b9b3e0SSimon J. Gerraty while (par->p[0] != '\0' && str.str == NULL) { 451956e45f6SSimon J. Gerraty switch (par->p[0]) { 4523955d011SMarcel Moolenaar case '\\': 453956e45f6SSimon J. Gerraty par->p++; 454956e45f6SSimon J. Gerraty if (par->p[0] != '\0') { 455956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 456956e45f6SSimon J. Gerraty par->p++; 4573955d011SMarcel Moolenaar } 458956e45f6SSimon J. Gerraty continue; 4593955d011SMarcel Moolenaar case '"': 460956e45f6SSimon J. Gerraty par->p++; 461dba7b0efSSimon J. Gerraty if (quoted) 4629f45a3c8SSimon J. Gerraty goto return_buf; /* skip the closing quote */ 463dba7b0efSSimon J. Gerraty Buf_AddByte(&buf, '"'); 464956e45f6SSimon J. Gerraty continue; 465e2eeea75SSimon J. Gerraty case ')': /* see is_separator */ 4663955d011SMarcel Moolenaar case '!': 4673955d011SMarcel Moolenaar case '=': 4683955d011SMarcel Moolenaar case '>': 4693955d011SMarcel Moolenaar case '<': 4703955d011SMarcel Moolenaar case ' ': 4713955d011SMarcel Moolenaar case '\t': 472e2eeea75SSimon J. Gerraty if (!quoted) 4739f45a3c8SSimon J. Gerraty goto return_buf; 474956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 475956e45f6SSimon J. Gerraty par->p++; 476956e45f6SSimon J. Gerraty continue; 4773955d011SMarcel Moolenaar case '$': 478dba7b0efSSimon J. Gerraty if (!CondParser_StringExpr(par, 479dba7b0efSSimon J. Gerraty start, doEval, quoted, &buf, &str)) 4809f45a3c8SSimon J. Gerraty goto return_str; 481956e45f6SSimon J. Gerraty continue; 4823955d011SMarcel Moolenaar default: 48312904384SSimon J. Gerraty if (!unquotedOK && !quoted && *start != '$' && 48406b9b3e0SSimon J. Gerraty !ch_isdigit(*start)) { 48506b9b3e0SSimon J. Gerraty /* 48606b9b3e0SSimon J. Gerraty * The left-hand side must be quoted, 48712904384SSimon J. Gerraty * a variable expression or a number. 48806b9b3e0SSimon J. Gerraty */ 48906b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 4909f45a3c8SSimon J. Gerraty goto return_str; 49128a6bc81SSimon J. Gerraty } 492956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 493956e45f6SSimon J. Gerraty par->p++; 494956e45f6SSimon J. Gerraty continue; 4953955d011SMarcel Moolenaar } 4963955d011SMarcel Moolenaar } 4979f45a3c8SSimon J. Gerraty return_buf: 498dba7b0efSSimon J. Gerraty str = FStr_InitOwn(buf.data); 49912904384SSimon J. Gerraty buf.data = NULL; 5009f45a3c8SSimon J. Gerraty return_str: 50112904384SSimon J. Gerraty Buf_Done(&buf); 50206b9b3e0SSimon J. Gerraty *out_str = str; 5033955d011SMarcel Moolenaar } 5042c3632d1SSimon J. Gerraty 50506b9b3e0SSimon J. Gerraty /* 50606b9b3e0SSimon J. Gerraty * Evaluate a "comparison without operator", such as in ".if ${VAR}" or 50706b9b3e0SSimon J. Gerraty * ".if 0". 50806b9b3e0SSimon J. Gerraty */ 509b0c40a00SSimon J. Gerraty static bool 510b0c40a00SSimon J. Gerraty EvalNotEmpty(CondParser *par, const char *value, bool quoted) 5113955d011SMarcel Moolenaar { 512e2eeea75SSimon J. Gerraty double num; 513956e45f6SSimon J. Gerraty 514e2eeea75SSimon J. Gerraty /* For .ifxxx "...", check for non-empty string. */ 515e2eeea75SSimon J. Gerraty if (quoted) 516e2eeea75SSimon J. Gerraty return value[0] != '\0'; 517956e45f6SSimon J. Gerraty 518e2eeea75SSimon J. Gerraty /* For .ifxxx <number>, compare against zero */ 519e2eeea75SSimon J. Gerraty if (TryParseNumber(value, &num)) 520e2eeea75SSimon J. Gerraty return num != 0.0; 521956e45f6SSimon J. Gerraty 5229f45a3c8SSimon J. Gerraty /* 5239f45a3c8SSimon J. Gerraty * For .if ${...}, check for non-empty string. This is different 5249f45a3c8SSimon J. Gerraty * from the evaluation function from that .if variant, which would 5259f45a3c8SSimon J. Gerraty * test whether a variable of the given name were defined. 5269f45a3c8SSimon J. Gerraty */ 52712904384SSimon J. Gerraty /* 52812904384SSimon J. Gerraty * XXX: Whitespace should count as empty, just as in 52912904384SSimon J. Gerraty * CondParser_FuncCallEmpty. 53012904384SSimon J. Gerraty */ 531dba7b0efSSimon J. Gerraty if (par->plain) 532e2eeea75SSimon J. Gerraty return value[0] != '\0'; 533956e45f6SSimon J. Gerraty 5349f45a3c8SSimon J. Gerraty return par->evalBare(value) != par->negateEvalBare; 535956e45f6SSimon J. Gerraty } 536956e45f6SSimon J. Gerraty 537956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ 538b0c40a00SSimon J. Gerraty static bool 539dba7b0efSSimon J. Gerraty EvalCompareNum(double lhs, ComparisonOp op, double rhs) 540956e45f6SSimon J. Gerraty { 5411d3f2ddcSSimon J. Gerraty DEBUG3(COND, "Comparing %f %s %f\n", lhs, opname[op], rhs); 542956e45f6SSimon J. Gerraty 543dba7b0efSSimon J. Gerraty switch (op) { 544dba7b0efSSimon J. Gerraty case LT: 545dba7b0efSSimon J. Gerraty return lhs < rhs; 546dba7b0efSSimon J. Gerraty case LE: 547dba7b0efSSimon J. Gerraty return lhs <= rhs; 548dba7b0efSSimon J. Gerraty case GT: 549dba7b0efSSimon J. Gerraty return lhs > rhs; 550dba7b0efSSimon J. Gerraty case GE: 551dba7b0efSSimon J. Gerraty return lhs >= rhs; 5524fde40d9SSimon J. Gerraty case EQ: 553dba7b0efSSimon J. Gerraty return lhs == rhs; 5544fde40d9SSimon J. Gerraty default: 5554fde40d9SSimon J. Gerraty return lhs != rhs; 556956e45f6SSimon J. Gerraty } 557956e45f6SSimon J. Gerraty } 558956e45f6SSimon J. Gerraty 559956e45f6SSimon J. Gerraty static Token 560dba7b0efSSimon J. Gerraty EvalCompareStr(CondParser *par, const char *lhs, 561dba7b0efSSimon J. Gerraty ComparisonOp op, const char *rhs) 562956e45f6SSimon J. Gerraty { 563dba7b0efSSimon J. Gerraty if (op != EQ && op != NE) { 564dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 5654fde40d9SSimon J. Gerraty "Comparison with '%s' requires both operands " 5664fde40d9SSimon J. Gerraty "'%s' and '%s' to be numeric", 5674fde40d9SSimon J. Gerraty opname[op], lhs, rhs); 568b0c40a00SSimon J. Gerraty par->printedError = true; 569956e45f6SSimon J. Gerraty return TOK_ERROR; 570956e45f6SSimon J. Gerraty } 571956e45f6SSimon J. Gerraty 5721d3f2ddcSSimon J. Gerraty DEBUG3(COND, "Comparing \"%s\" %s \"%s\"\n", lhs, opname[op], rhs); 573dba7b0efSSimon J. Gerraty return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); 574956e45f6SSimon J. Gerraty } 575956e45f6SSimon J. Gerraty 576956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */ 577956e45f6SSimon J. Gerraty static Token 578b0c40a00SSimon J. Gerraty EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, 579b0c40a00SSimon J. Gerraty ComparisonOp op, const char *rhs, bool rhsQuoted) 580956e45f6SSimon J. Gerraty { 5813955d011SMarcel Moolenaar double left, right; 5823955d011SMarcel Moolenaar 583956e45f6SSimon J. Gerraty if (!rhsQuoted && !lhsQuoted) 584956e45f6SSimon J. Gerraty if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) 585dba7b0efSSimon J. Gerraty return ToToken(EvalCompareNum(left, op, right)); 586956e45f6SSimon J. Gerraty 587dba7b0efSSimon J. Gerraty return EvalCompareStr(par, lhs, op, rhs); 588dba7b0efSSimon J. Gerraty } 589dba7b0efSSimon J. Gerraty 590b0c40a00SSimon J. Gerraty static bool 591dba7b0efSSimon J. Gerraty CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) 592dba7b0efSSimon J. Gerraty { 593dba7b0efSSimon J. Gerraty const char *p = par->p; 594dba7b0efSSimon J. Gerraty 5959f45a3c8SSimon J. Gerraty if (p[0] == '<' && p[1] == '=') 5969f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = LE, true; 5979f45a3c8SSimon J. Gerraty if (p[0] == '<') 5989f45a3c8SSimon J. Gerraty return par->p += 1, *out_op = LT, true; 5999f45a3c8SSimon J. Gerraty if (p[0] == '>' && p[1] == '=') 6009f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = GE, true; 6019f45a3c8SSimon J. Gerraty if (p[0] == '>') 6029f45a3c8SSimon J. Gerraty return par->p += 1, *out_op = GT, true; 6039f45a3c8SSimon J. Gerraty if (p[0] == '=' && p[1] == '=') 6049f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = EQ, true; 6059f45a3c8SSimon J. Gerraty if (p[0] == '!' && p[1] == '=') 6069f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = NE, true; 607b0c40a00SSimon J. Gerraty return false; 608956e45f6SSimon J. Gerraty } 609956e45f6SSimon J. Gerraty 61006b9b3e0SSimon J. Gerraty /* 61106b9b3e0SSimon J. Gerraty * Parse a comparison condition such as: 612956e45f6SSimon J. Gerraty * 613956e45f6SSimon J. Gerraty * 0 614956e45f6SSimon J. Gerraty * ${VAR:Mpattern} 615956e45f6SSimon J. Gerraty * ${VAR} == value 616956e45f6SSimon J. Gerraty * ${VAR:U0} < 12345 617956e45f6SSimon J. Gerraty */ 618956e45f6SSimon J. Gerraty static Token 619b0c40a00SSimon J. Gerraty CondParser_Comparison(CondParser *par, bool doEval) 620956e45f6SSimon J. Gerraty { 621956e45f6SSimon J. Gerraty Token t = TOK_ERROR; 62206b9b3e0SSimon J. Gerraty FStr lhs, rhs; 623dba7b0efSSimon J. Gerraty ComparisonOp op; 624b0c40a00SSimon J. Gerraty bool lhsQuoted, rhsQuoted; 625956e45f6SSimon J. Gerraty 62612904384SSimon J. Gerraty CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhs, &lhsQuoted); 62706b9b3e0SSimon J. Gerraty if (lhs.str == NULL) 628e2eeea75SSimon J. Gerraty goto done_lhs; 6293955d011SMarcel Moolenaar 630956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6313955d011SMarcel Moolenaar 632dba7b0efSSimon J. Gerraty if (!CondParser_ComparisonOp(par, &op)) { 633e2eeea75SSimon J. Gerraty /* Unknown operator, compare against an empty string or 0. */ 63406b9b3e0SSimon J. Gerraty t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); 635e2eeea75SSimon J. Gerraty goto done_lhs; 6363955d011SMarcel Moolenaar } 6373955d011SMarcel Moolenaar 638956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6393955d011SMarcel Moolenaar 640956e45f6SSimon J. Gerraty if (par->p[0] == '\0') { 641dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 64212904384SSimon J. Gerraty "Missing right-hand side of operator '%s'", opname[op]); 643b0c40a00SSimon J. Gerraty par->printedError = true; 644e2eeea75SSimon J. Gerraty goto done_lhs; 6453955d011SMarcel Moolenaar } 6463955d011SMarcel Moolenaar 64712904384SSimon J. Gerraty CondParser_Leaf(par, doEval, true, &rhs, &rhsQuoted); 6481d3f2ddcSSimon J. Gerraty t = rhs.str == NULL ? TOK_ERROR 6491d3f2ddcSSimon J. Gerraty : !doEval ? TOK_FALSE 6501d3f2ddcSSimon J. Gerraty : EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); 65106b9b3e0SSimon J. Gerraty FStr_Done(&rhs); 6521d3f2ddcSSimon J. Gerraty 653e2eeea75SSimon J. Gerraty done_lhs: 65406b9b3e0SSimon J. Gerraty FStr_Done(&lhs); 6553955d011SMarcel Moolenaar return t; 6563955d011SMarcel Moolenaar } 6573955d011SMarcel Moolenaar 65806b9b3e0SSimon J. Gerraty /* 65906b9b3e0SSimon J. Gerraty * The argument to empty() is a variable name, optionally followed by 66006b9b3e0SSimon J. Gerraty * variable modifiers. 66106b9b3e0SSimon J. Gerraty */ 66212904384SSimon J. Gerraty static bool 66312904384SSimon J. Gerraty CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) 6643955d011SMarcel Moolenaar { 66512904384SSimon J. Gerraty const char *cp = par->p; 66612904384SSimon J. Gerraty Token tok; 66706b9b3e0SSimon J. Gerraty FStr val; 6683955d011SMarcel Moolenaar 6699f45a3c8SSimon J. Gerraty if (!skip_string(&cp, "empty")) 67012904384SSimon J. Gerraty return false; 6713955d011SMarcel Moolenaar 67212904384SSimon J. Gerraty cpp_skip_whitespace(&cp); 67312904384SSimon J. Gerraty if (*cp != '(') 67412904384SSimon J. Gerraty return false; 67512904384SSimon J. Gerraty 67612904384SSimon J. Gerraty cp--; /* Make cp[1] point to the '('. */ 677*8c973ee2SSimon J. Gerraty val = Var_Parse(&cp, SCOPE_CMDLINE, 678*8c973ee2SSimon J. Gerraty doEval ? VARE_WANTRES : VARE_PARSE_ONLY); 679956e45f6SSimon J. Gerraty /* TODO: handle errors */ 6803955d011SMarcel Moolenaar 68112904384SSimon J. Gerraty if (val.str == var_Error) 68212904384SSimon J. Gerraty tok = TOK_ERROR; 68312904384SSimon J. Gerraty else { 68406b9b3e0SSimon J. Gerraty cpp_skip_whitespace(&val.str); 6859f45a3c8SSimon J. Gerraty tok = ToToken(doEval && val.str[0] == '\0'); 6863955d011SMarcel Moolenaar } 6873955d011SMarcel Moolenaar 68812904384SSimon J. Gerraty FStr_Done(&val); 68912904384SSimon J. Gerraty *out_token = tok; 69012904384SSimon J. Gerraty par->p = cp; 69112904384SSimon J. Gerraty return true; 6923955d011SMarcel Moolenaar } 6933955d011SMarcel Moolenaar 6942f2a5ecdSSimon J. Gerraty /* Parse a function call expression, such as 'exists(${file})'. */ 695b0c40a00SSimon J. Gerraty static bool 696b0c40a00SSimon J. Gerraty CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) 6973955d011SMarcel Moolenaar { 6989f45a3c8SSimon J. Gerraty char *arg; 6999f45a3c8SSimon J. Gerraty const char *p = par->p; 7009f45a3c8SSimon J. Gerraty bool (*fn)(const char *); 7019f45a3c8SSimon J. Gerraty const char *fn_name = p; 702e2eeea75SSimon J. Gerraty 7039f45a3c8SSimon J. Gerraty if (skip_string(&p, "defined")) 7049f45a3c8SSimon J. Gerraty fn = FuncDefined; 7059f45a3c8SSimon J. Gerraty else if (skip_string(&p, "make")) 7069f45a3c8SSimon J. Gerraty fn = FuncMake; 7079f45a3c8SSimon J. Gerraty else if (skip_string(&p, "exists")) 7089f45a3c8SSimon J. Gerraty fn = FuncExists; 7099f45a3c8SSimon J. Gerraty else if (skip_string(&p, "target")) 7109f45a3c8SSimon J. Gerraty fn = FuncTarget; 7119f45a3c8SSimon J. Gerraty else if (skip_string(&p, "commands")) 7129f45a3c8SSimon J. Gerraty fn = FuncCommands; 7139f45a3c8SSimon J. Gerraty else 71412904384SSimon J. Gerraty return false; 715e2eeea75SSimon J. Gerraty 7169f45a3c8SSimon J. Gerraty cpp_skip_whitespace(&p); 7179f45a3c8SSimon J. Gerraty if (*p != '(') 71812904384SSimon J. Gerraty return false; 719e2eeea75SSimon J. Gerraty 7209f45a3c8SSimon J. Gerraty arg = ParseFuncArg(par, &p, doEval, fn_name); 7219f45a3c8SSimon J. Gerraty *out_token = ToToken(doEval && 7229f45a3c8SSimon J. Gerraty arg != NULL && arg[0] != '\0' && fn(arg)); 723e2eeea75SSimon J. Gerraty free(arg); 7249f45a3c8SSimon J. Gerraty 7259f45a3c8SSimon J. Gerraty par->p = p; 726b0c40a00SSimon J. Gerraty return true; 727e2eeea75SSimon J. Gerraty } 728e2eeea75SSimon J. Gerraty 72906b9b3e0SSimon J. Gerraty /* 73012904384SSimon J. Gerraty * Parse a comparison that neither starts with '"' nor '$', such as the 73112904384SSimon J. Gerraty * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without 732b0c40a00SSimon J. Gerraty * operator, which is a number, a variable expression or a string literal. 73312904384SSimon J. Gerraty * 73412904384SSimon J. Gerraty * TODO: Can this be merged into CondParser_Comparison? 73506b9b3e0SSimon J. Gerraty */ 736e2eeea75SSimon J. Gerraty static Token 737b0c40a00SSimon J. Gerraty CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) 738e2eeea75SSimon J. Gerraty { 7393955d011SMarcel Moolenaar Token t; 7409f45a3c8SSimon J. Gerraty char *arg; 74106b9b3e0SSimon J. Gerraty const char *cp; 7423955d011SMarcel Moolenaar 7433955d011SMarcel Moolenaar /* Push anything numeric through the compare expression */ 744956e45f6SSimon J. Gerraty cp = par->p; 745e2eeea75SSimon J. Gerraty if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') 746956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 7473955d011SMarcel Moolenaar 7483955d011SMarcel Moolenaar /* 7493955d011SMarcel Moolenaar * Most likely we have a naked token to apply the default function to. 7503955d011SMarcel Moolenaar * However ".if a == b" gets here when the "a" is unquoted and doesn't 7513955d011SMarcel Moolenaar * start with a '$'. This surprises people. 75206b9b3e0SSimon J. Gerraty * If what follows the function argument is a '=' or '!' then the 75306b9b3e0SSimon J. Gerraty * syntax would be invalid if we did "defined(a)" - so instead treat 75406b9b3e0SSimon J. Gerraty * as an expression. 7553955d011SMarcel Moolenaar */ 756b0c40a00SSimon J. Gerraty /* 7579f45a3c8SSimon J. Gerraty * XXX: In edge cases, a variable expression may be evaluated twice, 7589f45a3c8SSimon J. Gerraty * see cond-token-plain.mk, keyword 'twice'. 759b0c40a00SSimon J. Gerraty */ 7609f45a3c8SSimon J. Gerraty arg = ParseWord(&cp, doEval); 7619f45a3c8SSimon J. Gerraty assert(arg[0] != '\0'); 7629f45a3c8SSimon J. Gerraty 7639f45a3c8SSimon J. Gerraty if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>') 764956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 765956e45f6SSimon J. Gerraty par->p = cp; 7663955d011SMarcel Moolenaar 7673955d011SMarcel Moolenaar /* 7683955d011SMarcel Moolenaar * Evaluate the argument using the default function. 769956e45f6SSimon J. Gerraty * This path always treats .if as .ifdef. To get here, the character 7703955d011SMarcel Moolenaar * after .if must have been taken literally, so the argument cannot 7713955d011SMarcel Moolenaar * be empty - even if it contained a variable expansion. 7723955d011SMarcel Moolenaar */ 7739f45a3c8SSimon J. Gerraty t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); 7743955d011SMarcel Moolenaar free(arg); 7753955d011SMarcel Moolenaar return t; 7763955d011SMarcel Moolenaar } 7773955d011SMarcel Moolenaar 778956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */ 7793955d011SMarcel Moolenaar static Token 780b0c40a00SSimon J. Gerraty CondParser_Token(CondParser *par, bool doEval) 7813955d011SMarcel Moolenaar { 7823955d011SMarcel Moolenaar Token t; 7833955d011SMarcel Moolenaar 784956e45f6SSimon J. Gerraty t = par->curr; 7853955d011SMarcel Moolenaar if (t != TOK_NONE) { 786956e45f6SSimon J. Gerraty par->curr = TOK_NONE; 7873955d011SMarcel Moolenaar return t; 7883955d011SMarcel Moolenaar } 7893955d011SMarcel Moolenaar 790e2eeea75SSimon J. Gerraty cpp_skip_hspace(&par->p); 7913955d011SMarcel Moolenaar 792956e45f6SSimon J. Gerraty switch (par->p[0]) { 7933955d011SMarcel Moolenaar 7943955d011SMarcel Moolenaar case '(': 795956e45f6SSimon J. Gerraty par->p++; 7963955d011SMarcel Moolenaar return TOK_LPAREN; 7973955d011SMarcel Moolenaar 7983955d011SMarcel Moolenaar case ')': 799956e45f6SSimon J. Gerraty par->p++; 8003955d011SMarcel Moolenaar return TOK_RPAREN; 8013955d011SMarcel Moolenaar 8023955d011SMarcel Moolenaar case '|': 803956e45f6SSimon J. Gerraty par->p++; 804e2eeea75SSimon J. Gerraty if (par->p[0] == '|') 805956e45f6SSimon J. Gerraty par->p++; 80606b9b3e0SSimon J. Gerraty else if (opts.strict) { 807e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '|'"); 808b0c40a00SSimon J. Gerraty par->printedError = true; 809e2eeea75SSimon J. Gerraty return TOK_ERROR; 8103955d011SMarcel Moolenaar } 8113955d011SMarcel Moolenaar return TOK_OR; 8123955d011SMarcel Moolenaar 8133955d011SMarcel Moolenaar case '&': 814956e45f6SSimon J. Gerraty par->p++; 815e2eeea75SSimon J. Gerraty if (par->p[0] == '&') 816956e45f6SSimon J. Gerraty par->p++; 81706b9b3e0SSimon J. Gerraty else if (opts.strict) { 818e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '&'"); 819b0c40a00SSimon J. Gerraty par->printedError = true; 820e2eeea75SSimon J. Gerraty return TOK_ERROR; 8213955d011SMarcel Moolenaar } 8223955d011SMarcel Moolenaar return TOK_AND; 8233955d011SMarcel Moolenaar 8243955d011SMarcel Moolenaar case '!': 825956e45f6SSimon J. Gerraty par->p++; 8263955d011SMarcel Moolenaar return TOK_NOT; 8273955d011SMarcel Moolenaar 828e2eeea75SSimon J. Gerraty case '#': /* XXX: see unit-tests/cond-token-plain.mk */ 829e2eeea75SSimon J. Gerraty case '\n': /* XXX: why should this end the condition? */ 830e2eeea75SSimon J. Gerraty /* Probably obsolete now, from 1993-03-21. */ 8313955d011SMarcel Moolenaar case '\0': 8323955d011SMarcel Moolenaar return TOK_EOF; 8333955d011SMarcel Moolenaar 8343955d011SMarcel Moolenaar case '"': 8353955d011SMarcel Moolenaar case '$': 836956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 8373955d011SMarcel Moolenaar 8383955d011SMarcel Moolenaar default: 83912904384SSimon J. Gerraty if (CondParser_FuncCallEmpty(par, doEval, &t)) 84012904384SSimon J. Gerraty return t; 841b0c40a00SSimon J. Gerraty if (CondParser_FuncCall(par, doEval, &t)) 842b0c40a00SSimon J. Gerraty return t; 843b0c40a00SSimon J. Gerraty return CondParser_ComparisonOrLeaf(par, doEval); 8443955d011SMarcel Moolenaar } 8453955d011SMarcel Moolenaar } 8463955d011SMarcel Moolenaar 84712904384SSimon J. Gerraty /* Skip the next token if it equals t. */ 84812904384SSimon J. Gerraty static bool 84912904384SSimon J. Gerraty CondParser_Skip(CondParser *par, Token t) 85012904384SSimon J. Gerraty { 85112904384SSimon J. Gerraty Token actual; 85212904384SSimon J. Gerraty 85312904384SSimon J. Gerraty actual = CondParser_Token(par, false); 85412904384SSimon J. Gerraty if (actual == t) 85512904384SSimon J. Gerraty return true; 85612904384SSimon J. Gerraty 85712904384SSimon J. Gerraty assert(par->curr == TOK_NONE); 85812904384SSimon J. Gerraty assert(actual != TOK_NONE); 85912904384SSimon J. Gerraty par->curr = actual; 86012904384SSimon J. Gerraty return false; 86112904384SSimon J. Gerraty } 86212904384SSimon J. Gerraty 86306b9b3e0SSimon J. Gerraty /* 864dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 865dba7b0efSSimon J. Gerraty * Term -> '!' Term 866dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 867dba7b0efSSimon J. Gerraty * Term -> Leaf 8683955d011SMarcel Moolenaar */ 869dba7b0efSSimon J. Gerraty static CondResult 870b0c40a00SSimon J. Gerraty CondParser_Term(CondParser *par, bool doEval) 8713955d011SMarcel Moolenaar { 872dba7b0efSSimon J. Gerraty CondResult res; 8733955d011SMarcel Moolenaar Token t; 8743955d011SMarcel Moolenaar 875956e45f6SSimon J. Gerraty t = CondParser_Token(par, doEval); 876dba7b0efSSimon J. Gerraty if (t == TOK_TRUE) 877dba7b0efSSimon J. Gerraty return CR_TRUE; 878dba7b0efSSimon J. Gerraty if (t == TOK_FALSE) 879dba7b0efSSimon J. Gerraty return CR_FALSE; 8803955d011SMarcel Moolenaar 881dba7b0efSSimon J. Gerraty if (t == TOK_LPAREN) { 882dba7b0efSSimon J. Gerraty res = CondParser_Or(par, doEval); 883dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 884dba7b0efSSimon J. Gerraty return CR_ERROR; 885dba7b0efSSimon J. Gerraty if (CondParser_Token(par, doEval) != TOK_RPAREN) 886dba7b0efSSimon J. Gerraty return CR_ERROR; 887dba7b0efSSimon J. Gerraty return res; 8883955d011SMarcel Moolenaar } 889dba7b0efSSimon J. Gerraty 890dba7b0efSSimon J. Gerraty if (t == TOK_NOT) { 891dba7b0efSSimon J. Gerraty res = CondParser_Term(par, doEval); 892dba7b0efSSimon J. Gerraty if (res == CR_TRUE) 893dba7b0efSSimon J. Gerraty res = CR_FALSE; 894dba7b0efSSimon J. Gerraty else if (res == CR_FALSE) 895dba7b0efSSimon J. Gerraty res = CR_TRUE; 896dba7b0efSSimon J. Gerraty return res; 8973955d011SMarcel Moolenaar } 898dba7b0efSSimon J. Gerraty 899dba7b0efSSimon J. Gerraty return CR_ERROR; 9003955d011SMarcel Moolenaar } 9012c3632d1SSimon J. Gerraty 90206b9b3e0SSimon J. Gerraty /* 90312904384SSimon J. Gerraty * And -> Term ('&&' Term)* 9043955d011SMarcel Moolenaar */ 905dba7b0efSSimon J. Gerraty static CondResult 906b0c40a00SSimon J. Gerraty CondParser_And(CondParser *par, bool doEval) 9073955d011SMarcel Moolenaar { 90812904384SSimon J. Gerraty CondResult res, rhs; 9093955d011SMarcel Moolenaar 91012904384SSimon J. Gerraty res = CR_TRUE; 91112904384SSimon J. Gerraty do { 91212904384SSimon J. Gerraty if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR) 913dba7b0efSSimon J. Gerraty return CR_ERROR; 91412904384SSimon J. Gerraty if (rhs == CR_FALSE) { 91512904384SSimon J. Gerraty res = CR_FALSE; 91612904384SSimon J. Gerraty doEval = false; 9173955d011SMarcel Moolenaar } 91812904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_AND)); 919dba7b0efSSimon J. Gerraty 920dba7b0efSSimon J. Gerraty return res; 9213955d011SMarcel Moolenaar } 9222c3632d1SSimon J. Gerraty 92306b9b3e0SSimon J. Gerraty /* 92412904384SSimon J. Gerraty * Or -> And ('||' And)* 9253955d011SMarcel Moolenaar */ 926dba7b0efSSimon J. Gerraty static CondResult 927b0c40a00SSimon J. Gerraty CondParser_Or(CondParser *par, bool doEval) 9283955d011SMarcel Moolenaar { 92912904384SSimon J. Gerraty CondResult res, rhs; 9303955d011SMarcel Moolenaar 93112904384SSimon J. Gerraty res = CR_FALSE; 93212904384SSimon J. Gerraty do { 93312904384SSimon J. Gerraty if ((rhs = CondParser_And(par, doEval)) == CR_ERROR) 934dba7b0efSSimon J. Gerraty return CR_ERROR; 93512904384SSimon J. Gerraty if (rhs == CR_TRUE) { 93612904384SSimon J. Gerraty res = CR_TRUE; 93712904384SSimon J. Gerraty doEval = false; 9383955d011SMarcel Moolenaar } 93912904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_OR)); 940dba7b0efSSimon J. Gerraty 941dba7b0efSSimon J. Gerraty return res; 9423955d011SMarcel Moolenaar } 9433955d011SMarcel Moolenaar 9449f45a3c8SSimon J. Gerraty static CondResult 9459f45a3c8SSimon J. Gerraty CondParser_Eval(CondParser *par) 9462c3632d1SSimon J. Gerraty { 947dba7b0efSSimon J. Gerraty CondResult res; 9482c3632d1SSimon J. Gerraty 949956e45f6SSimon J. Gerraty DEBUG1(COND, "CondParser_Eval: %s\n", par->p); 9502c3632d1SSimon J. Gerraty 951b0c40a00SSimon J. Gerraty res = CondParser_Or(par, true); 9529f45a3c8SSimon J. Gerraty if (res != CR_ERROR && CondParser_Token(par, false) != TOK_EOF) 9539f45a3c8SSimon J. Gerraty return CR_ERROR; 954956e45f6SSimon J. Gerraty 9559f45a3c8SSimon J. Gerraty return res; 9562c3632d1SSimon J. Gerraty } 9572c3632d1SSimon J. Gerraty 95806b9b3e0SSimon J. Gerraty /* 95906b9b3e0SSimon J. Gerraty * Evaluate the condition, including any side effects from the variable 960956e45f6SSimon J. Gerraty * expressions in the condition. The condition consists of &&, ||, !, 961956e45f6SSimon J. Gerraty * function(arg), comparisons and parenthetical groupings thereof. 9623955d011SMarcel Moolenaar */ 9639f45a3c8SSimon J. Gerraty static CondResult 9649f45a3c8SSimon J. Gerraty CondEvalExpression(const char *cond, bool plain, 96512904384SSimon J. Gerraty bool (*evalBare)(const char *), bool negate, 96612904384SSimon J. Gerraty bool eprint, bool leftUnquotedOK) 9673955d011SMarcel Moolenaar { 968956e45f6SSimon J. Gerraty CondParser par; 9699f45a3c8SSimon J. Gerraty CondResult rval; 9703955d011SMarcel Moolenaar 971e2eeea75SSimon J. Gerraty cpp_skip_hspace(&cond); 9723955d011SMarcel Moolenaar 973dba7b0efSSimon J. Gerraty par.plain = plain; 974dba7b0efSSimon J. Gerraty par.evalBare = evalBare; 975dba7b0efSSimon J. Gerraty par.negateEvalBare = negate; 97612904384SSimon J. Gerraty par.leftUnquotedOK = leftUnquotedOK; 977956e45f6SSimon J. Gerraty par.p = cond; 978956e45f6SSimon J. Gerraty par.curr = TOK_NONE; 979b0c40a00SSimon J. Gerraty par.printedError = false; 9803955d011SMarcel Moolenaar 9819f45a3c8SSimon J. Gerraty rval = CondParser_Eval(&par); 9823955d011SMarcel Moolenaar 9839f45a3c8SSimon J. Gerraty if (rval == CR_ERROR && eprint && !par.printedError) 984956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); 9853955d011SMarcel Moolenaar 9863955d011SMarcel Moolenaar return rval; 9873955d011SMarcel Moolenaar } 9883955d011SMarcel Moolenaar 98906b9b3e0SSimon J. Gerraty /* 99006b9b3e0SSimon J. Gerraty * Evaluate a condition in a :? modifier, such as 99106b9b3e0SSimon J. Gerraty * ${"${VAR}" == value:?yes:no}. 99206b9b3e0SSimon J. Gerraty */ 9939f45a3c8SSimon J. Gerraty CondResult 9949f45a3c8SSimon J. Gerraty Cond_EvalCondition(const char *cond) 995956e45f6SSimon J. Gerraty { 9969f45a3c8SSimon J. Gerraty return CondEvalExpression(cond, true, 99712904384SSimon J. Gerraty FuncDefined, false, false, true); 998956e45f6SSimon J. Gerraty } 9993955d011SMarcel Moolenaar 1000b0c40a00SSimon J. Gerraty static bool 100106b9b3e0SSimon J. Gerraty IsEndif(const char *p) 100206b9b3e0SSimon J. Gerraty { 100306b9b3e0SSimon J. Gerraty return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && 100406b9b3e0SSimon J. Gerraty p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); 100506b9b3e0SSimon J. Gerraty } 100606b9b3e0SSimon J. Gerraty 1007b0c40a00SSimon J. Gerraty static bool 1008b0c40a00SSimon J. Gerraty DetermineKindOfConditional(const char **pp, bool *out_plain, 100912904384SSimon J. Gerraty bool (**out_evalBare)(const char *), 1010b0c40a00SSimon J. Gerraty bool *out_negate) 1011dba7b0efSSimon J. Gerraty { 10129f45a3c8SSimon J. Gerraty const char *p = *pp + 2; 1013dba7b0efSSimon J. Gerraty 1014b0c40a00SSimon J. Gerraty *out_plain = false; 1015dba7b0efSSimon J. Gerraty *out_evalBare = FuncDefined; 10169f45a3c8SSimon J. Gerraty *out_negate = skip_string(&p, "n"); 10179f45a3c8SSimon J. Gerraty 10189f45a3c8SSimon J. Gerraty if (skip_string(&p, "def")) { /* .ifdef and .ifndef */ 10199f45a3c8SSimon J. Gerraty } else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */ 1020dba7b0efSSimon J. Gerraty *out_evalBare = FuncMake; 10219f45a3c8SSimon J. Gerraty else if (!*out_negate) /* plain .if */ 1022b0c40a00SSimon J. Gerraty *out_plain = true; 10239f45a3c8SSimon J. Gerraty else 10249f45a3c8SSimon J. Gerraty goto unknown_directive; 10259f45a3c8SSimon J. Gerraty if (ch_isalpha(*p)) 10269f45a3c8SSimon J. Gerraty goto unknown_directive; 10279f45a3c8SSimon J. Gerraty 10289f45a3c8SSimon J. Gerraty *pp = p; 10299f45a3c8SSimon J. Gerraty return true; 10309f45a3c8SSimon J. Gerraty 10319f45a3c8SSimon J. Gerraty unknown_directive: 1032dba7b0efSSimon J. Gerraty /* 10339f45a3c8SSimon J. Gerraty * TODO: Add error message about unknown directive, since there is no 10349f45a3c8SSimon J. Gerraty * other known directive that starts with 'el' or 'if'. 1035dba7b0efSSimon J. Gerraty * 1036dba7b0efSSimon J. Gerraty * Example: .elifx 123 1037dba7b0efSSimon J. Gerraty */ 1038b0c40a00SSimon J. Gerraty return false; 1039dba7b0efSSimon J. Gerraty } 1040dba7b0efSSimon J. Gerraty 104106b9b3e0SSimon J. Gerraty /* 104206b9b3e0SSimon J. Gerraty * Evaluate the conditional directive in the line, which is one of: 10433955d011SMarcel Moolenaar * 1044e2eeea75SSimon J. Gerraty * .if <cond> 1045e2eeea75SSimon J. Gerraty * .ifmake <cond> 1046e2eeea75SSimon J. Gerraty * .ifnmake <cond> 1047e2eeea75SSimon J. Gerraty * .ifdef <cond> 1048e2eeea75SSimon J. Gerraty * .ifndef <cond> 1049e2eeea75SSimon J. Gerraty * .elif <cond> 1050e2eeea75SSimon J. Gerraty * .elifmake <cond> 1051e2eeea75SSimon J. Gerraty * .elifnmake <cond> 1052e2eeea75SSimon J. Gerraty * .elifdef <cond> 1053e2eeea75SSimon J. Gerraty * .elifndef <cond> 1054e2eeea75SSimon J. Gerraty * .else 1055e2eeea75SSimon J. Gerraty * .endif 1056e2eeea75SSimon J. Gerraty * 1057e2eeea75SSimon J. Gerraty * In these directives, <cond> consists of &&, ||, !, function(arg), 1058e2eeea75SSimon J. Gerraty * comparisons, expressions, bare words, numbers and strings, and 1059e2eeea75SSimon J. Gerraty * parenthetical groupings thereof. 1060956e45f6SSimon J. Gerraty * 1061956e45f6SSimon J. Gerraty * Results: 10629f45a3c8SSimon J. Gerraty * CR_TRUE to continue parsing the lines that follow the 1063b0c40a00SSimon J. Gerraty * conditional (when <cond> evaluates to true) 10649f45a3c8SSimon J. Gerraty * CR_FALSE to skip the lines after the conditional 1065b0c40a00SSimon J. Gerraty * (when <cond> evaluates to false, or when a previous 1066956e45f6SSimon J. Gerraty * branch has already been taken) 10679f45a3c8SSimon J. Gerraty * CR_ERROR if the conditional was not valid, either because of 1068956e45f6SSimon J. Gerraty * a syntax error or because some variable was undefined 1069956e45f6SSimon J. Gerraty * or because the condition could not be evaluated 10703955d011SMarcel Moolenaar */ 10719f45a3c8SSimon J. Gerraty CondResult 107206b9b3e0SSimon J. Gerraty Cond_EvalLine(const char *line) 10733955d011SMarcel Moolenaar { 1074e2eeea75SSimon J. Gerraty typedef enum IfState { 1075e2eeea75SSimon J. Gerraty 1076b0c40a00SSimon J. Gerraty /* None of the previous <cond> evaluated to true. */ 1077e2eeea75SSimon J. Gerraty IFS_INITIAL = 0, 1078e2eeea75SSimon J. Gerraty 10799f45a3c8SSimon J. Gerraty /* 10809f45a3c8SSimon J. Gerraty * The previous <cond> evaluated to true. The lines following 10819f45a3c8SSimon J. Gerraty * this condition are interpreted. 10829f45a3c8SSimon J. Gerraty */ 1083e2eeea75SSimon J. Gerraty IFS_ACTIVE = 1 << 0, 1084e2eeea75SSimon J. Gerraty 1085e2eeea75SSimon J. Gerraty /* The previous directive was an '.else'. */ 1086e2eeea75SSimon J. Gerraty IFS_SEEN_ELSE = 1 << 1, 1087e2eeea75SSimon J. Gerraty 1088b0c40a00SSimon J. Gerraty /* One of the previous <cond> evaluated to true. */ 1089e2eeea75SSimon J. Gerraty IFS_WAS_ACTIVE = 1 << 2 1090e2eeea75SSimon J. Gerraty 1091e2eeea75SSimon J. Gerraty } IfState; 1092e2eeea75SSimon J. Gerraty 1093e2eeea75SSimon J. Gerraty static enum IfState *cond_states = NULL; 1094e2eeea75SSimon J. Gerraty static unsigned int cond_states_cap = 128; 10953955d011SMarcel Moolenaar 1096b0c40a00SSimon J. Gerraty bool plain; 109712904384SSimon J. Gerraty bool (*evalBare)(const char *); 1098b0c40a00SSimon J. Gerraty bool negate; 1099b0c40a00SSimon J. Gerraty bool isElif; 11009f45a3c8SSimon J. Gerraty CondResult res; 1101e2eeea75SSimon J. Gerraty IfState state; 1102e2eeea75SSimon J. Gerraty const char *p = line; 11033955d011SMarcel Moolenaar 1104e2eeea75SSimon J. Gerraty if (cond_states == NULL) { 110506b9b3e0SSimon J. Gerraty cond_states = bmake_malloc( 110606b9b3e0SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 1107e2eeea75SSimon J. Gerraty cond_states[0] = IFS_ACTIVE; 110859a02420SSimon J. Gerraty } 11093955d011SMarcel Moolenaar 1110e2eeea75SSimon J. Gerraty p++; /* skip the leading '.' */ 1111e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 1112e2eeea75SSimon J. Gerraty 111306b9b3e0SSimon J. Gerraty if (IsEndif(p)) { /* It is an '.endif'. */ 111406b9b3e0SSimon J. Gerraty if (p[5] != '\0') { 111506b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, 111612904384SSimon J. Gerraty "The .endif directive does not take arguments"); 1117e2eeea75SSimon J. Gerraty } 1118e2eeea75SSimon J. Gerraty 11194fde40d9SSimon J. Gerraty if (cond_depth == CurFile_CondMinDepth()) { 1120956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less endif"); 11219f45a3c8SSimon J. Gerraty return CR_TRUE; 11223955d011SMarcel Moolenaar } 1123e2eeea75SSimon J. Gerraty 11243955d011SMarcel Moolenaar /* Return state for previous conditional */ 11253955d011SMarcel Moolenaar cond_depth--; 1126e2eeea75SSimon J. Gerraty return cond_states[cond_depth] & IFS_ACTIVE 11279f45a3c8SSimon J. Gerraty ? CR_TRUE : CR_FALSE; 11283955d011SMarcel Moolenaar } 11293955d011SMarcel Moolenaar 113006b9b3e0SSimon J. Gerraty /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ 113106b9b3e0SSimon J. Gerraty if (p[0] == 'e') { 113206b9b3e0SSimon J. Gerraty if (p[1] != 'l') { 113306b9b3e0SSimon J. Gerraty /* 113406b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a 113512904384SSimon J. Gerraty * transformation rule like '.err.txt', 113606b9b3e0SSimon J. Gerraty * therefore no error message here. 113706b9b3e0SSimon J. Gerraty */ 11389f45a3c8SSimon J. Gerraty return CR_ERROR; 113906b9b3e0SSimon J. Gerraty } 114006b9b3e0SSimon J. Gerraty 11413955d011SMarcel Moolenaar /* Quite likely this is 'else' or 'elif' */ 1142e2eeea75SSimon J. Gerraty p += 2; 11439f45a3c8SSimon J. Gerraty if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) { 114406b9b3e0SSimon J. Gerraty if (p[2] != '\0') 1145e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, 114606b9b3e0SSimon J. Gerraty "The .else directive " 114712904384SSimon J. Gerraty "does not take arguments"); 1148e2eeea75SSimon J. Gerraty 11494fde40d9SSimon J. Gerraty if (cond_depth == CurFile_CondMinDepth()) { 1150956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less else"); 11519f45a3c8SSimon J. Gerraty return CR_TRUE; 11523955d011SMarcel Moolenaar } 11533955d011SMarcel Moolenaar 1154e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1155e2eeea75SSimon J. Gerraty if (state == IFS_INITIAL) { 1156e2eeea75SSimon J. Gerraty state = IFS_ACTIVE | IFS_SEEN_ELSE; 1157e2eeea75SSimon J. Gerraty } else { 1158e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) 115906b9b3e0SSimon J. Gerraty Parse_Error(PARSE_WARNING, 116006b9b3e0SSimon J. Gerraty "extra else"); 1161e2eeea75SSimon J. Gerraty state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 11623955d011SMarcel Moolenaar } 1163e2eeea75SSimon J. Gerraty cond_states[cond_depth] = state; 1164e2eeea75SSimon J. Gerraty 11659f45a3c8SSimon J. Gerraty return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE; 11663955d011SMarcel Moolenaar } 11673955d011SMarcel Moolenaar /* Assume for now it is an elif */ 1168b0c40a00SSimon J. Gerraty isElif = true; 11693955d011SMarcel Moolenaar } else 1170b0c40a00SSimon J. Gerraty isElif = false; 11713955d011SMarcel Moolenaar 1172e2eeea75SSimon J. Gerraty if (p[0] != 'i' || p[1] != 'f') { 117306b9b3e0SSimon J. Gerraty /* 117406b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a transformation rule 117506b9b3e0SSimon J. Gerraty * like '.elisp.scm', therefore no error message here. 117606b9b3e0SSimon J. Gerraty */ 11779f45a3c8SSimon J. Gerraty return CR_ERROR; /* Not an ifxxx or elifxxx line */ 1178e2eeea75SSimon J. Gerraty } 11793955d011SMarcel Moolenaar 1180dba7b0efSSimon J. Gerraty if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) 11819f45a3c8SSimon J. Gerraty return CR_ERROR; 11823955d011SMarcel Moolenaar 11833955d011SMarcel Moolenaar if (isElif) { 11844fde40d9SSimon J. Gerraty if (cond_depth == CurFile_CondMinDepth()) { 1185956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less elif"); 11869f45a3c8SSimon J. Gerraty return CR_TRUE; 11873955d011SMarcel Moolenaar } 1188e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1189e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) { 11903955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "extra elif"); 119106b9b3e0SSimon J. Gerraty cond_states[cond_depth] = 119206b9b3e0SSimon J. Gerraty IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 11939f45a3c8SSimon J. Gerraty return CR_FALSE; 11943955d011SMarcel Moolenaar } 1195e2eeea75SSimon J. Gerraty if (state != IFS_INITIAL) { 1196e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 11979f45a3c8SSimon J. Gerraty return CR_FALSE; 11983955d011SMarcel Moolenaar } 11993955d011SMarcel Moolenaar } else { 12003955d011SMarcel Moolenaar /* Normal .if */ 1201e2eeea75SSimon J. Gerraty if (cond_depth + 1 >= cond_states_cap) { 120259a02420SSimon J. Gerraty /* 120359a02420SSimon J. Gerraty * This is rare, but not impossible. 120459a02420SSimon J. Gerraty * In meta mode, dirdeps.mk (only runs at level 0) 120559a02420SSimon J. Gerraty * can need more than the default. 120659a02420SSimon J. Gerraty */ 1207e2eeea75SSimon J. Gerraty cond_states_cap += 32; 1208e2eeea75SSimon J. Gerraty cond_states = bmake_realloc(cond_states, 12099f45a3c8SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 12103955d011SMarcel Moolenaar } 1211e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 12123955d011SMarcel Moolenaar cond_depth++; 1213e2eeea75SSimon J. Gerraty if (!(state & IFS_ACTIVE)) { 121406b9b3e0SSimon J. Gerraty /* 121506b9b3e0SSimon J. Gerraty * If we aren't parsing the data, 121606b9b3e0SSimon J. Gerraty * treat as always false. 121706b9b3e0SSimon J. Gerraty */ 1218e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12199f45a3c8SSimon J. Gerraty return CR_FALSE; 12203955d011SMarcel Moolenaar } 12213955d011SMarcel Moolenaar } 12223955d011SMarcel Moolenaar 1223956e45f6SSimon J. Gerraty /* And evaluate the conditional expression */ 12249f45a3c8SSimon J. Gerraty res = CondEvalExpression(p, plain, evalBare, negate, true, false); 12259f45a3c8SSimon J. Gerraty if (res == CR_ERROR) { 12269f45a3c8SSimon J. Gerraty /* Syntax error, error message already output. */ 12279f45a3c8SSimon J. Gerraty /* Skip everything to the matching '.endif'. */ 12289f45a3c8SSimon J. Gerraty /* An extra '.else' is not detected in this case. */ 1229e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12309f45a3c8SSimon J. Gerraty return CR_FALSE; 12313955d011SMarcel Moolenaar } 12323955d011SMarcel Moolenaar 12339f45a3c8SSimon J. Gerraty cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL; 12349f45a3c8SSimon J. Gerraty return res; 12353955d011SMarcel Moolenaar } 12363955d011SMarcel Moolenaar 12373955d011SMarcel Moolenaar void 12384fde40d9SSimon J. Gerraty Cond_EndFile(void) 12393955d011SMarcel Moolenaar { 12404fde40d9SSimon J. Gerraty unsigned int open_conds = cond_depth - CurFile_CondMinDepth(); 12413955d011SMarcel Moolenaar 12424fde40d9SSimon J. Gerraty if (open_conds != 0) { 124306b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, "%u open conditional%s", 124406b9b3e0SSimon J. Gerraty open_conds, open_conds == 1 ? "" : "s"); 12454fde40d9SSimon J. Gerraty cond_depth = CurFile_CondMinDepth(); 12463955d011SMarcel Moolenaar } 12473955d011SMarcel Moolenaar } 1248