1*9f45a3c8SSimon J. Gerraty /* $NetBSD: cond.c,v 1.327 2022/01/29 01:12:36 rillig Exp $ */ 23955d011SMarcel Moolenaar 33955d011SMarcel Moolenaar /* 43955d011SMarcel Moolenaar * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. 53955d011SMarcel Moolenaar * All rights reserved. 63955d011SMarcel Moolenaar * 73955d011SMarcel Moolenaar * This code is derived from software contributed to Berkeley by 83955d011SMarcel Moolenaar * Adam de Boor. 93955d011SMarcel Moolenaar * 103955d011SMarcel Moolenaar * Redistribution and use in source and binary forms, with or without 113955d011SMarcel Moolenaar * modification, are permitted provided that the following conditions 123955d011SMarcel Moolenaar * are met: 133955d011SMarcel Moolenaar * 1. Redistributions of source code must retain the above copyright 143955d011SMarcel Moolenaar * notice, this list of conditions and the following disclaimer. 153955d011SMarcel Moolenaar * 2. Redistributions in binary form must reproduce the above copyright 163955d011SMarcel Moolenaar * notice, this list of conditions and the following disclaimer in the 173955d011SMarcel Moolenaar * documentation and/or other materials provided with the distribution. 183955d011SMarcel Moolenaar * 3. Neither the name of the University nor the names of its contributors 193955d011SMarcel Moolenaar * may be used to endorse or promote products derived from this software 203955d011SMarcel Moolenaar * without specific prior written permission. 213955d011SMarcel Moolenaar * 223955d011SMarcel Moolenaar * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 233955d011SMarcel Moolenaar * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 243955d011SMarcel Moolenaar * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 253955d011SMarcel Moolenaar * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 263955d011SMarcel Moolenaar * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 273955d011SMarcel Moolenaar * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 283955d011SMarcel Moolenaar * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 293955d011SMarcel Moolenaar * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 303955d011SMarcel Moolenaar * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 313955d011SMarcel Moolenaar * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 323955d011SMarcel Moolenaar * SUCH DAMAGE. 333955d011SMarcel Moolenaar */ 343955d011SMarcel Moolenaar 353955d011SMarcel Moolenaar /* 363955d011SMarcel Moolenaar * Copyright (c) 1988, 1989 by Adam de Boor 373955d011SMarcel Moolenaar * Copyright (c) 1989 by Berkeley Softworks 383955d011SMarcel Moolenaar * All rights reserved. 393955d011SMarcel Moolenaar * 403955d011SMarcel Moolenaar * This code is derived from software contributed to Berkeley by 413955d011SMarcel Moolenaar * Adam de Boor. 423955d011SMarcel Moolenaar * 433955d011SMarcel Moolenaar * Redistribution and use in source and binary forms, with or without 443955d011SMarcel Moolenaar * modification, are permitted provided that the following conditions 453955d011SMarcel Moolenaar * are met: 463955d011SMarcel Moolenaar * 1. Redistributions of source code must retain the above copyright 473955d011SMarcel Moolenaar * notice, this list of conditions and the following disclaimer. 483955d011SMarcel Moolenaar * 2. Redistributions in binary form must reproduce the above copyright 493955d011SMarcel Moolenaar * notice, this list of conditions and the following disclaimer in the 503955d011SMarcel Moolenaar * documentation and/or other materials provided with the distribution. 513955d011SMarcel Moolenaar * 3. All advertising materials mentioning features or use of this software 523955d011SMarcel Moolenaar * must display the following acknowledgement: 533955d011SMarcel Moolenaar * This product includes software developed by the University of 543955d011SMarcel Moolenaar * California, Berkeley and its contributors. 553955d011SMarcel Moolenaar * 4. Neither the name of the University nor the names of its contributors 563955d011SMarcel Moolenaar * may be used to endorse or promote products derived from this software 573955d011SMarcel Moolenaar * without specific prior written permission. 583955d011SMarcel Moolenaar * 593955d011SMarcel Moolenaar * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 603955d011SMarcel Moolenaar * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 613955d011SMarcel Moolenaar * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 623955d011SMarcel Moolenaar * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 633955d011SMarcel Moolenaar * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 643955d011SMarcel Moolenaar * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 653955d011SMarcel Moolenaar * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 663955d011SMarcel Moolenaar * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 673955d011SMarcel Moolenaar * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 683955d011SMarcel Moolenaar * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 693955d011SMarcel Moolenaar * SUCH DAMAGE. 703955d011SMarcel Moolenaar */ 713955d011SMarcel Moolenaar 7206b9b3e0SSimon J. Gerraty /* 7306b9b3e0SSimon J. Gerraty * Handling of conditionals in a makefile. 743955d011SMarcel Moolenaar * 753955d011SMarcel Moolenaar * Interface: 76e2eeea75SSimon J. Gerraty * Cond_EvalLine Evaluate the conditional directive, such as 77e2eeea75SSimon J. Gerraty * '.if <cond>', '.elifnmake <cond>', '.else', '.endif'. 783955d011SMarcel Moolenaar * 79956e45f6SSimon J. Gerraty * Cond_EvalCondition 80956e45f6SSimon J. Gerraty * Evaluate the conditional, which is either the argument 81956e45f6SSimon J. Gerraty * of one of the .if directives or the condition in a 82956e45f6SSimon J. Gerraty * ':?then:else' variable modifier. 83956e45f6SSimon J. Gerraty * 84956e45f6SSimon J. Gerraty * Cond_save_depth 85956e45f6SSimon J. Gerraty * Cond_restore_depth 86956e45f6SSimon J. Gerraty * Save and restore the nesting of the conditions, at 87956e45f6SSimon J. Gerraty * the start and end of including another makefile, to 88956e45f6SSimon J. Gerraty * ensure that in each makefile the conditional 89956e45f6SSimon J. Gerraty * directives are well-balanced. 903955d011SMarcel Moolenaar */ 913955d011SMarcel Moolenaar 922c3632d1SSimon J. Gerraty #include <errno.h> 933955d011SMarcel Moolenaar 943955d011SMarcel Moolenaar #include "make.h" 953955d011SMarcel Moolenaar #include "dir.h" 963955d011SMarcel Moolenaar 97956e45f6SSimon J. Gerraty /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ 98*9f45a3c8SSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.327 2022/01/29 01:12:36 rillig Exp $"); 99956e45f6SSimon J. Gerraty 1003955d011SMarcel Moolenaar /* 101*9f45a3c8SSimon J. Gerraty * Conditional expressions conform to this grammar: 10212904384SSimon J. Gerraty * Or -> And ('||' And)* 10312904384SSimon J. Gerraty * And -> Term ('&&' Term)* 104dba7b0efSSimon J. Gerraty * Term -> Function '(' Argument ')' 105dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 106dba7b0efSSimon J. Gerraty * Term -> Leaf 107dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 108dba7b0efSSimon J. Gerraty * Term -> '!' Term 109dba7b0efSSimon J. Gerraty * Leaf -> "string" 110dba7b0efSSimon J. Gerraty * Leaf -> Number 111dba7b0efSSimon J. Gerraty * Leaf -> VariableExpression 112*9f45a3c8SSimon J. Gerraty * Leaf -> BareWord 113dba7b0efSSimon J. Gerraty * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' 1143955d011SMarcel Moolenaar * 115*9f45a3c8SSimon J. Gerraty * BareWord is an unquoted string literal, its evaluation depends on the kind 116*9f45a3c8SSimon J. Gerraty * of '.if' directive. 1173955d011SMarcel Moolenaar * 118*9f45a3c8SSimon J. Gerraty * The tokens are scanned by CondParser_Token, which returns: 119dba7b0efSSimon J. Gerraty * TOK_AND for '&&' 120dba7b0efSSimon J. Gerraty * TOK_OR for '||' 121956e45f6SSimon J. Gerraty * TOK_NOT for '!' 122956e45f6SSimon J. Gerraty * TOK_LPAREN for '(' 123956e45f6SSimon J. Gerraty * TOK_RPAREN for ')' 124dba7b0efSSimon J. Gerraty * 125956e45f6SSimon J. Gerraty * Other terminal symbols are evaluated using either the default function or 126*9f45a3c8SSimon J. Gerraty * the function given in the terminal, they return either TOK_TRUE, TOK_FALSE 127*9f45a3c8SSimon J. Gerraty * or TOK_ERROR. 1283955d011SMarcel Moolenaar */ 129956e45f6SSimon J. Gerraty typedef enum Token { 130dba7b0efSSimon J. Gerraty TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT, 1313955d011SMarcel Moolenaar TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR 1323955d011SMarcel Moolenaar } Token; 1333955d011SMarcel Moolenaar 134dba7b0efSSimon J. Gerraty typedef enum ComparisonOp { 135dba7b0efSSimon J. Gerraty LT, LE, GT, GE, EQ, NE 136dba7b0efSSimon J. Gerraty } ComparisonOp; 137dba7b0efSSimon J. Gerraty 138956e45f6SSimon J. Gerraty typedef struct CondParser { 139dba7b0efSSimon J. Gerraty 140dba7b0efSSimon J. Gerraty /* 141dba7b0efSSimon J. Gerraty * The plain '.if ${VAR}' evaluates to true if the value of the 142dba7b0efSSimon J. Gerraty * expression has length > 0. The other '.if' variants delegate 143dba7b0efSSimon J. Gerraty * to evalBare instead. 144dba7b0efSSimon J. Gerraty */ 145b0c40a00SSimon J. Gerraty bool plain; 146dba7b0efSSimon J. Gerraty 147dba7b0efSSimon J. Gerraty /* The function to apply on unquoted bare words. */ 14812904384SSimon J. Gerraty bool (*evalBare)(const char *); 149b0c40a00SSimon J. Gerraty bool negateEvalBare; 150dba7b0efSSimon J. Gerraty 15112904384SSimon J. Gerraty /* 15212904384SSimon J. Gerraty * Whether the left-hand side of a comparison may be an unquoted 15312904384SSimon J. Gerraty * string. This is allowed for expressions of the form 15412904384SSimon J. Gerraty * ${condition:?:}, see ApplyModifier_IfElse. Such a condition is 15512904384SSimon J. Gerraty * expanded before it is evaluated, due to ease of implementation. 15612904384SSimon J. Gerraty * This means that at the point where the condition is evaluated, 15712904384SSimon J. Gerraty * make cannot know anymore whether the left-hand side had originally 15812904384SSimon J. Gerraty * been a variable expression or a plain word. 15912904384SSimon J. Gerraty * 16012904384SSimon J. Gerraty * In all other contexts, the left-hand side must either be a 16112904384SSimon J. Gerraty * variable expression, a quoted string or a number. 16212904384SSimon J. Gerraty */ 16312904384SSimon J. Gerraty bool leftUnquotedOK; 16412904384SSimon J. Gerraty 165956e45f6SSimon J. Gerraty const char *p; /* The remaining condition to parse */ 166956e45f6SSimon J. Gerraty Token curr; /* Single push-back token used in parsing */ 1673955d011SMarcel Moolenaar 16806b9b3e0SSimon J. Gerraty /* 16906b9b3e0SSimon J. Gerraty * Whether an error message has already been printed for this 17006b9b3e0SSimon J. Gerraty * condition. The first available error message is usually the most 17106b9b3e0SSimon J. Gerraty * specific one, therefore it makes sense to suppress the standard 17206b9b3e0SSimon J. Gerraty * "Malformed conditional" message. 17306b9b3e0SSimon J. Gerraty */ 174b0c40a00SSimon J. Gerraty bool printedError; 175956e45f6SSimon J. Gerraty } CondParser; 176956e45f6SSimon J. Gerraty 177b0c40a00SSimon J. Gerraty static CondResult CondParser_Or(CondParser *par, bool); 1783955d011SMarcel Moolenaar 1793955d011SMarcel Moolenaar static unsigned int cond_depth = 0; /* current .if nesting level */ 1803955d011SMarcel Moolenaar static unsigned int cond_min_depth = 0; /* depth at makefile open */ 1813955d011SMarcel Moolenaar 18212904384SSimon J. Gerraty /* Names for ComparisonOp. */ 18312904384SSimon J. Gerraty static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" }; 18428a6bc81SSimon J. Gerraty 185*9f45a3c8SSimon J. Gerraty MAKE_INLINE bool 186*9f45a3c8SSimon J. Gerraty skip_string(const char **pp, const char *str) 1873955d011SMarcel Moolenaar { 188*9f45a3c8SSimon J. Gerraty size_t len = strlen(str); 189*9f45a3c8SSimon J. Gerraty bool ok = strncmp(*pp, str, len) == 0; 190*9f45a3c8SSimon J. Gerraty if (ok) 191*9f45a3c8SSimon J. Gerraty *pp += len; 192*9f45a3c8SSimon J. Gerraty return ok; 1933955d011SMarcel Moolenaar } 1943955d011SMarcel Moolenaar 195e2eeea75SSimon J. Gerraty static Token 196b0c40a00SSimon J. Gerraty ToToken(bool cond) 197e2eeea75SSimon J. Gerraty { 198e2eeea75SSimon J. Gerraty return cond ? TOK_TRUE : TOK_FALSE; 199e2eeea75SSimon J. Gerraty } 200e2eeea75SSimon J. Gerraty 201956e45f6SSimon J. Gerraty static void 202956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(CondParser *par) 2033955d011SMarcel Moolenaar { 204956e45f6SSimon J. Gerraty cpp_skip_whitespace(&par->p); 205956e45f6SSimon J. Gerraty } 206956e45f6SSimon J. Gerraty 20706b9b3e0SSimon J. Gerraty /* 20812904384SSimon J. Gerraty * Parse a single word, taking into account balanced parentheses as well as 20912904384SSimon J. Gerraty * embedded expressions. Used for the argument of a built-in function as 21012904384SSimon J. Gerraty * well as for bare words, which are then passed to the default function. 21106b9b3e0SSimon J. Gerraty */ 212*9f45a3c8SSimon J. Gerraty static char * 213*9f45a3c8SSimon J. Gerraty ParseWord(const char **pp, bool doEval) 21406b9b3e0SSimon J. Gerraty { 215956e45f6SSimon J. Gerraty const char *p = *pp; 216956e45f6SSimon J. Gerraty Buffer argBuf; 2173955d011SMarcel Moolenaar int paren_depth; 2183955d011SMarcel Moolenaar 219e2eeea75SSimon J. Gerraty Buf_InitSize(&argBuf, 16); 2203955d011SMarcel Moolenaar 2213955d011SMarcel Moolenaar paren_depth = 0; 2223955d011SMarcel Moolenaar for (;;) { 223956e45f6SSimon J. Gerraty char ch = *p; 224e2eeea75SSimon J. Gerraty if (ch == '\0' || ch == ' ' || ch == '\t') 2253955d011SMarcel Moolenaar break; 2263955d011SMarcel Moolenaar if ((ch == '&' || ch == '|') && paren_depth == 0) 2273955d011SMarcel Moolenaar break; 228*9f45a3c8SSimon J. Gerraty if (ch == '$') { 2293955d011SMarcel Moolenaar /* 23006b9b3e0SSimon J. Gerraty * Parse the variable expression and install it as 23106b9b3e0SSimon J. Gerraty * part of the argument if it's valid. We tell 23206b9b3e0SSimon J. Gerraty * Var_Parse to complain on an undefined variable, 23306b9b3e0SSimon J. Gerraty * (XXX: but Var_Parse ignores that request) 23406b9b3e0SSimon J. Gerraty * so we don't need to do it. Nor do we return an 23506b9b3e0SSimon J. Gerraty * error, though perhaps we should. 2363955d011SMarcel Moolenaar */ 237b0c40a00SSimon J. Gerraty VarEvalMode emode = doEval 238b0c40a00SSimon J. Gerraty ? VARE_UNDEFERR 239b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 24006b9b3e0SSimon J. Gerraty FStr nestedVal; 241b0c40a00SSimon J. Gerraty (void)Var_Parse(&p, SCOPE_CMDLINE, emode, &nestedVal); 242956e45f6SSimon J. Gerraty /* TODO: handle errors */ 24306b9b3e0SSimon J. Gerraty Buf_AddStr(&argBuf, nestedVal.str); 24406b9b3e0SSimon J. Gerraty FStr_Done(&nestedVal); 2453955d011SMarcel Moolenaar continue; 2463955d011SMarcel Moolenaar } 2473955d011SMarcel Moolenaar if (ch == '(') 2483955d011SMarcel Moolenaar paren_depth++; 2492c3632d1SSimon J. Gerraty else if (ch == ')' && --paren_depth < 0) 2503955d011SMarcel Moolenaar break; 251*9f45a3c8SSimon J. Gerraty Buf_AddByte(&argBuf, ch); 252956e45f6SSimon J. Gerraty p++; 2533955d011SMarcel Moolenaar } 2543955d011SMarcel Moolenaar 255*9f45a3c8SSimon J. Gerraty cpp_skip_hspace(&p); 256*9f45a3c8SSimon J. Gerraty *pp = p; 2573955d011SMarcel Moolenaar 258*9f45a3c8SSimon J. Gerraty return Buf_DoneData(&argBuf); 259*9f45a3c8SSimon J. Gerraty } 260*9f45a3c8SSimon J. Gerraty 261*9f45a3c8SSimon J. Gerraty /* Parse the function argument, including the surrounding parentheses. */ 262*9f45a3c8SSimon J. Gerraty static char * 263*9f45a3c8SSimon J. Gerraty ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) 264*9f45a3c8SSimon J. Gerraty { 265*9f45a3c8SSimon J. Gerraty const char *p = *pp; 266*9f45a3c8SSimon J. Gerraty char *res; 267*9f45a3c8SSimon J. Gerraty 268*9f45a3c8SSimon J. Gerraty p++; /* Skip opening '(' - verified by caller */ 269*9f45a3c8SSimon J. Gerraty cpp_skip_hspace(&p); 270*9f45a3c8SSimon J. Gerraty res = ParseWord(&p, doEval); 271e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2723955d011SMarcel Moolenaar 273*9f45a3c8SSimon J. Gerraty if (*p++ != ')') { 274*9f45a3c8SSimon J. Gerraty int len = 0; 275*9f45a3c8SSimon J. Gerraty while (ch_isalpha(func[len])) 276*9f45a3c8SSimon J. Gerraty len++; 277*9f45a3c8SSimon J. Gerraty 278dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 279*9f45a3c8SSimon J. Gerraty "Missing closing parenthesis for %.*s()", len, func); 280b0c40a00SSimon J. Gerraty par->printedError = true; 281*9f45a3c8SSimon J. Gerraty free(res); 282*9f45a3c8SSimon J. Gerraty return NULL; 2833955d011SMarcel Moolenaar } 2843955d011SMarcel Moolenaar 285956e45f6SSimon J. Gerraty *pp = p; 286*9f45a3c8SSimon J. Gerraty return res; 2873955d011SMarcel Moolenaar } 2882c3632d1SSimon J. Gerraty 2892c3632d1SSimon J. Gerraty /* Test whether the given variable is defined. */ 290b0c40a00SSimon J. Gerraty static bool 291*9f45a3c8SSimon J. Gerraty FuncDefined(const char *var) 2923955d011SMarcel Moolenaar { 293*9f45a3c8SSimon J. Gerraty return Var_Exists(SCOPE_CMDLINE, var); 2943955d011SMarcel Moolenaar } 2952c3632d1SSimon J. Gerraty 29612904384SSimon J. Gerraty /* See if the given target is requested to be made. */ 297b0c40a00SSimon J. Gerraty static bool 298*9f45a3c8SSimon J. Gerraty FuncMake(const char *target) 2993955d011SMarcel Moolenaar { 300956e45f6SSimon J. Gerraty StringListNode *ln; 301956e45f6SSimon J. Gerraty 30206b9b3e0SSimon J. Gerraty for (ln = opts.create.first; ln != NULL; ln = ln->next) 303*9f45a3c8SSimon J. Gerraty if (Str_Match(ln->datum, target)) 304b0c40a00SSimon J. Gerraty return true; 305b0c40a00SSimon J. Gerraty return false; 3063955d011SMarcel Moolenaar } 3072c3632d1SSimon J. Gerraty 3082c3632d1SSimon J. Gerraty /* See if the given file exists. */ 309b0c40a00SSimon J. Gerraty static bool 310*9f45a3c8SSimon J. Gerraty FuncExists(const char *file) 3113955d011SMarcel Moolenaar { 312b0c40a00SSimon J. Gerraty bool result; 3133955d011SMarcel Moolenaar char *path; 3143955d011SMarcel Moolenaar 315*9f45a3c8SSimon J. Gerraty path = Dir_FindFile(file, &dirSearchPath); 316e2eeea75SSimon J. Gerraty DEBUG2(COND, "exists(%s) result is \"%s\"\n", 317*9f45a3c8SSimon J. Gerraty file, path != NULL ? path : ""); 318e2eeea75SSimon J. Gerraty result = path != NULL; 3193955d011SMarcel Moolenaar free(path); 3203841c287SSimon J. Gerraty return result; 3213955d011SMarcel Moolenaar } 3222c3632d1SSimon J. Gerraty 3232c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target. */ 324b0c40a00SSimon J. Gerraty static bool 325*9f45a3c8SSimon J. Gerraty FuncTarget(const char *node) 3263955d011SMarcel Moolenaar { 327*9f45a3c8SSimon J. Gerraty GNode *gn = Targ_FindNode(node); 328956e45f6SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn); 3293955d011SMarcel Moolenaar } 3303955d011SMarcel Moolenaar 33106b9b3e0SSimon J. Gerraty /* 33206b9b3e0SSimon J. Gerraty * See if the given node exists and is an actual target with commands 33306b9b3e0SSimon J. Gerraty * associated with it. 33406b9b3e0SSimon J. Gerraty */ 335b0c40a00SSimon J. Gerraty static bool 336*9f45a3c8SSimon J. Gerraty FuncCommands(const char *node) 3373955d011SMarcel Moolenaar { 338*9f45a3c8SSimon J. Gerraty GNode *gn = Targ_FindNode(node); 339*9f45a3c8SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn) && 340*9f45a3c8SSimon J. Gerraty !Lst_IsEmpty(&gn->commands); 3413955d011SMarcel Moolenaar } 3422c3632d1SSimon J. Gerraty 343e2eeea75SSimon J. Gerraty /* 344*9f45a3c8SSimon J. Gerraty * Convert the string into a floating-point number. Accepted formats are 345*9f45a3c8SSimon J. Gerraty * base-10 integer, base-16 integer and finite floating point numbers. 3463955d011SMarcel Moolenaar */ 347b0c40a00SSimon J. Gerraty static bool 348e2eeea75SSimon J. Gerraty TryParseNumber(const char *str, double *out_value) 3493955d011SMarcel Moolenaar { 350e2eeea75SSimon J. Gerraty char *end; 351e2eeea75SSimon J. Gerraty unsigned long ul_val; 352e2eeea75SSimon J. Gerraty double dbl_val; 3533955d011SMarcel Moolenaar 354e2eeea75SSimon J. Gerraty if (str[0] == '\0') { /* XXX: why is an empty string a number? */ 355e2eeea75SSimon J. Gerraty *out_value = 0.0; 356b0c40a00SSimon J. Gerraty return true; 357ac3446e9SSimon J. Gerraty } 358e2eeea75SSimon J. Gerraty 35912904384SSimon J. Gerraty errno = 0; 360e2eeea75SSimon J. Gerraty ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); 361e2eeea75SSimon J. Gerraty if (*end == '\0' && errno != ERANGE) { 362e2eeea75SSimon J. Gerraty *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; 363b0c40a00SSimon J. Gerraty return true; 3643955d011SMarcel Moolenaar } 3653955d011SMarcel Moolenaar 366e2eeea75SSimon J. Gerraty if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') 367b0c40a00SSimon J. Gerraty return false; /* skip the expensive strtod call */ 368e2eeea75SSimon J. Gerraty dbl_val = strtod(str, &end); 369e2eeea75SSimon J. Gerraty if (*end != '\0') 370b0c40a00SSimon J. Gerraty return false; 371e2eeea75SSimon J. Gerraty 372e2eeea75SSimon J. Gerraty *out_value = dbl_val; 373b0c40a00SSimon J. Gerraty return true; 3743955d011SMarcel Moolenaar } 3753955d011SMarcel Moolenaar 376b0c40a00SSimon J. Gerraty static bool 377956e45f6SSimon J. Gerraty is_separator(char ch) 378956e45f6SSimon J. Gerraty { 379b0c40a00SSimon J. Gerraty return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' || 380b0c40a00SSimon J. Gerraty ch == '>' || ch == '<' || ch == ')' /* but not '(' */; 381956e45f6SSimon J. Gerraty } 382956e45f6SSimon J. Gerraty 383dba7b0efSSimon J. Gerraty /* 384dba7b0efSSimon J. Gerraty * In a quoted or unquoted string literal or a number, parse a variable 385dba7b0efSSimon J. Gerraty * expression. 386dba7b0efSSimon J. Gerraty * 387dba7b0efSSimon J. Gerraty * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} 388dba7b0efSSimon J. Gerraty */ 389b0c40a00SSimon J. Gerraty static bool 390dba7b0efSSimon J. Gerraty CondParser_StringExpr(CondParser *par, const char *start, 39112904384SSimon J. Gerraty bool doEval, bool quoted, 39212904384SSimon J. Gerraty Buffer *buf, FStr *inout_str) 393dba7b0efSSimon J. Gerraty { 394b0c40a00SSimon J. Gerraty VarEvalMode emode; 395*9f45a3c8SSimon J. Gerraty const char *p; 396b0c40a00SSimon J. Gerraty bool atStart; 397dba7b0efSSimon J. Gerraty VarParseResult parseResult; 398dba7b0efSSimon J. Gerraty 39912904384SSimon J. Gerraty emode = doEval && quoted ? VARE_WANTRES 40012904384SSimon J. Gerraty : doEval ? VARE_UNDEFERR 401b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 402dba7b0efSSimon J. Gerraty 403*9f45a3c8SSimon J. Gerraty p = par->p; 404*9f45a3c8SSimon J. Gerraty atStart = p == start; 405*9f45a3c8SSimon J. Gerraty parseResult = Var_Parse(&p, SCOPE_CMDLINE, emode, inout_str); 406dba7b0efSSimon J. Gerraty /* TODO: handle errors */ 407dba7b0efSSimon J. Gerraty if (inout_str->str == var_Error) { 408dba7b0efSSimon J. Gerraty if (parseResult == VPR_ERR) { 409dba7b0efSSimon J. Gerraty /* 410dba7b0efSSimon J. Gerraty * FIXME: Even if an error occurs, there is no 411dba7b0efSSimon J. Gerraty * guarantee that it is reported. 412dba7b0efSSimon J. Gerraty * 413dba7b0efSSimon J. Gerraty * See cond-token-plain.mk $$$$$$$$. 414dba7b0efSSimon J. Gerraty */ 415b0c40a00SSimon J. Gerraty par->printedError = true; 416dba7b0efSSimon J. Gerraty } 417dba7b0efSSimon J. Gerraty /* 418dba7b0efSSimon J. Gerraty * XXX: Can there be any situation in which a returned 419b0c40a00SSimon J. Gerraty * var_Error needs to be freed? 420dba7b0efSSimon J. Gerraty */ 421dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 422dba7b0efSSimon J. Gerraty /* 423dba7b0efSSimon J. Gerraty * Even if !doEval, we still report syntax errors, which is 424dba7b0efSSimon J. Gerraty * what getting var_Error back with !doEval means. 425dba7b0efSSimon J. Gerraty */ 426dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); 427b0c40a00SSimon J. Gerraty return false; 428dba7b0efSSimon J. Gerraty } 429*9f45a3c8SSimon J. Gerraty par->p = p; 430dba7b0efSSimon J. Gerraty 431dba7b0efSSimon J. Gerraty /* 432dba7b0efSSimon J. Gerraty * If the '$' started the string literal (which means no quotes), and 433dba7b0efSSimon J. Gerraty * the variable expression is followed by a space, looks like a 434dba7b0efSSimon J. Gerraty * comparison operator or is the end of the expression, we are done. 435dba7b0efSSimon J. Gerraty */ 436dba7b0efSSimon J. Gerraty if (atStart && is_separator(par->p[0])) 437b0c40a00SSimon J. Gerraty return false; 438dba7b0efSSimon J. Gerraty 439dba7b0efSSimon J. Gerraty Buf_AddStr(buf, inout_str->str); 440dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 441dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); /* not finished yet */ 442b0c40a00SSimon J. Gerraty return true; 443dba7b0efSSimon J. Gerraty } 444dba7b0efSSimon J. Gerraty 445dba7b0efSSimon J. Gerraty /* 446*9f45a3c8SSimon J. Gerraty * Parse a string from a variable expression or an optionally quoted string, 447*9f45a3c8SSimon J. Gerraty * on the left-hand and right-hand sides of comparisons. 4483955d011SMarcel Moolenaar * 4493955d011SMarcel Moolenaar * Results: 450*9f45a3c8SSimon J. Gerraty * Returns the string without any enclosing quotes, or NULL on error. 451b0c40a00SSimon J. Gerraty * Sets out_quoted if the leaf was a quoted string literal. 4523955d011SMarcel Moolenaar */ 45306b9b3e0SSimon J. Gerraty static void 45412904384SSimon J. Gerraty CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, 455b0c40a00SSimon J. Gerraty FStr *out_str, bool *out_quoted) 4563955d011SMarcel Moolenaar { 4573955d011SMarcel Moolenaar Buffer buf; 45806b9b3e0SSimon J. Gerraty FStr str; 459b0c40a00SSimon J. Gerraty bool quoted; 4602c3632d1SSimon J. Gerraty const char *start; 4613955d011SMarcel Moolenaar 462e2eeea75SSimon J. Gerraty Buf_Init(&buf); 46306b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 464e2eeea75SSimon J. Gerraty *out_quoted = quoted = par->p[0] == '"'; 465956e45f6SSimon J. Gerraty start = par->p; 466e2eeea75SSimon J. Gerraty if (quoted) 467956e45f6SSimon J. Gerraty par->p++; 46806b9b3e0SSimon J. Gerraty 46906b9b3e0SSimon J. Gerraty while (par->p[0] != '\0' && str.str == NULL) { 470956e45f6SSimon J. Gerraty switch (par->p[0]) { 4713955d011SMarcel Moolenaar case '\\': 472956e45f6SSimon J. Gerraty par->p++; 473956e45f6SSimon J. Gerraty if (par->p[0] != '\0') { 474956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 475956e45f6SSimon J. Gerraty par->p++; 4763955d011SMarcel Moolenaar } 477956e45f6SSimon J. Gerraty continue; 4783955d011SMarcel Moolenaar case '"': 479956e45f6SSimon J. Gerraty par->p++; 480dba7b0efSSimon J. Gerraty if (quoted) 481*9f45a3c8SSimon J. Gerraty goto return_buf; /* skip the closing quote */ 482dba7b0efSSimon J. Gerraty Buf_AddByte(&buf, '"'); 483956e45f6SSimon J. Gerraty continue; 484e2eeea75SSimon J. Gerraty case ')': /* see is_separator */ 4853955d011SMarcel Moolenaar case '!': 4863955d011SMarcel Moolenaar case '=': 4873955d011SMarcel Moolenaar case '>': 4883955d011SMarcel Moolenaar case '<': 4893955d011SMarcel Moolenaar case ' ': 4903955d011SMarcel Moolenaar case '\t': 491e2eeea75SSimon J. Gerraty if (!quoted) 492*9f45a3c8SSimon J. Gerraty goto return_buf; 493956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 494956e45f6SSimon J. Gerraty par->p++; 495956e45f6SSimon J. Gerraty continue; 4963955d011SMarcel Moolenaar case '$': 497dba7b0efSSimon J. Gerraty if (!CondParser_StringExpr(par, 498dba7b0efSSimon J. Gerraty start, doEval, quoted, &buf, &str)) 499*9f45a3c8SSimon J. Gerraty goto return_str; 500956e45f6SSimon J. Gerraty continue; 5013955d011SMarcel Moolenaar default: 50212904384SSimon J. Gerraty if (!unquotedOK && !quoted && *start != '$' && 50306b9b3e0SSimon J. Gerraty !ch_isdigit(*start)) { 50406b9b3e0SSimon J. Gerraty /* 50506b9b3e0SSimon J. Gerraty * The left-hand side must be quoted, 50612904384SSimon J. Gerraty * a variable expression or a number. 50706b9b3e0SSimon J. Gerraty */ 50806b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 509*9f45a3c8SSimon J. Gerraty goto return_str; 51028a6bc81SSimon J. Gerraty } 511956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 512956e45f6SSimon J. Gerraty par->p++; 513956e45f6SSimon J. Gerraty continue; 5143955d011SMarcel Moolenaar } 5153955d011SMarcel Moolenaar } 516*9f45a3c8SSimon J. Gerraty return_buf: 517dba7b0efSSimon J. Gerraty str = FStr_InitOwn(buf.data); 51812904384SSimon J. Gerraty buf.data = NULL; 519*9f45a3c8SSimon J. Gerraty return_str: 52012904384SSimon J. Gerraty Buf_Done(&buf); 52106b9b3e0SSimon J. Gerraty *out_str = str; 5223955d011SMarcel Moolenaar } 5232c3632d1SSimon J. Gerraty 52406b9b3e0SSimon J. Gerraty /* 52506b9b3e0SSimon J. Gerraty * Evaluate a "comparison without operator", such as in ".if ${VAR}" or 52606b9b3e0SSimon J. Gerraty * ".if 0". 52706b9b3e0SSimon J. Gerraty */ 528b0c40a00SSimon J. Gerraty static bool 529b0c40a00SSimon J. Gerraty EvalNotEmpty(CondParser *par, const char *value, bool quoted) 5303955d011SMarcel Moolenaar { 531e2eeea75SSimon J. Gerraty double num; 532956e45f6SSimon J. Gerraty 533e2eeea75SSimon J. Gerraty /* For .ifxxx "...", check for non-empty string. */ 534e2eeea75SSimon J. Gerraty if (quoted) 535e2eeea75SSimon J. Gerraty return value[0] != '\0'; 536956e45f6SSimon J. Gerraty 537e2eeea75SSimon J. Gerraty /* For .ifxxx <number>, compare against zero */ 538e2eeea75SSimon J. Gerraty if (TryParseNumber(value, &num)) 539e2eeea75SSimon J. Gerraty return num != 0.0; 540956e45f6SSimon J. Gerraty 541*9f45a3c8SSimon J. Gerraty /* 542*9f45a3c8SSimon J. Gerraty * For .if ${...}, check for non-empty string. This is different 543*9f45a3c8SSimon J. Gerraty * from the evaluation function from that .if variant, which would 544*9f45a3c8SSimon J. Gerraty * test whether a variable of the given name were defined. 545*9f45a3c8SSimon J. Gerraty */ 54612904384SSimon J. Gerraty /* 54712904384SSimon J. Gerraty * XXX: Whitespace should count as empty, just as in 54812904384SSimon J. Gerraty * CondParser_FuncCallEmpty. 54912904384SSimon J. Gerraty */ 550dba7b0efSSimon J. Gerraty if (par->plain) 551e2eeea75SSimon J. Gerraty return value[0] != '\0'; 552956e45f6SSimon J. Gerraty 553*9f45a3c8SSimon J. Gerraty return par->evalBare(value) != par->negateEvalBare; 554956e45f6SSimon J. Gerraty } 555956e45f6SSimon J. Gerraty 556956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ 557b0c40a00SSimon J. Gerraty static bool 558dba7b0efSSimon J. Gerraty EvalCompareNum(double lhs, ComparisonOp op, double rhs) 559956e45f6SSimon J. Gerraty { 560dba7b0efSSimon J. Gerraty DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, opname[op]); 561956e45f6SSimon J. Gerraty 562dba7b0efSSimon J. Gerraty switch (op) { 563dba7b0efSSimon J. Gerraty case LT: 564dba7b0efSSimon J. Gerraty return lhs < rhs; 565dba7b0efSSimon J. Gerraty case LE: 566dba7b0efSSimon J. Gerraty return lhs <= rhs; 567dba7b0efSSimon J. Gerraty case GT: 568dba7b0efSSimon J. Gerraty return lhs > rhs; 569dba7b0efSSimon J. Gerraty case GE: 570dba7b0efSSimon J. Gerraty return lhs >= rhs; 571dba7b0efSSimon J. Gerraty case NE: 572dba7b0efSSimon J. Gerraty return lhs != rhs; 573dba7b0efSSimon J. Gerraty default: 574dba7b0efSSimon J. Gerraty return lhs == rhs; 575956e45f6SSimon J. Gerraty } 576956e45f6SSimon J. Gerraty } 577956e45f6SSimon J. Gerraty 578956e45f6SSimon J. Gerraty static Token 579dba7b0efSSimon J. Gerraty EvalCompareStr(CondParser *par, const char *lhs, 580dba7b0efSSimon J. Gerraty ComparisonOp op, const char *rhs) 581956e45f6SSimon J. Gerraty { 582dba7b0efSSimon J. Gerraty if (op != EQ && op != NE) { 583dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 584dba7b0efSSimon J. Gerraty "String comparison operator must be either == or !="); 585b0c40a00SSimon J. Gerraty par->printedError = true; 586956e45f6SSimon J. Gerraty return TOK_ERROR; 587956e45f6SSimon J. Gerraty } 588956e45f6SSimon J. Gerraty 589dba7b0efSSimon J. Gerraty DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", 590dba7b0efSSimon J. Gerraty lhs, rhs, opname[op]); 591dba7b0efSSimon J. Gerraty return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); 592956e45f6SSimon J. Gerraty } 593956e45f6SSimon J. Gerraty 594956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */ 595956e45f6SSimon J. Gerraty static Token 596b0c40a00SSimon J. Gerraty EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, 597b0c40a00SSimon J. Gerraty ComparisonOp op, const char *rhs, bool rhsQuoted) 598956e45f6SSimon J. Gerraty { 5993955d011SMarcel Moolenaar double left, right; 6003955d011SMarcel Moolenaar 601956e45f6SSimon J. Gerraty if (!rhsQuoted && !lhsQuoted) 602956e45f6SSimon J. Gerraty if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) 603dba7b0efSSimon J. Gerraty return ToToken(EvalCompareNum(left, op, right)); 604956e45f6SSimon J. Gerraty 605dba7b0efSSimon J. Gerraty return EvalCompareStr(par, lhs, op, rhs); 606dba7b0efSSimon J. Gerraty } 607dba7b0efSSimon J. Gerraty 608b0c40a00SSimon J. Gerraty static bool 609dba7b0efSSimon J. Gerraty CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) 610dba7b0efSSimon J. Gerraty { 611dba7b0efSSimon J. Gerraty const char *p = par->p; 612dba7b0efSSimon J. Gerraty 613*9f45a3c8SSimon J. Gerraty if (p[0] == '<' && p[1] == '=') 614*9f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = LE, true; 615*9f45a3c8SSimon J. Gerraty if (p[0] == '<') 616*9f45a3c8SSimon J. Gerraty return par->p += 1, *out_op = LT, true; 617*9f45a3c8SSimon J. Gerraty if (p[0] == '>' && p[1] == '=') 618*9f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = GE, true; 619*9f45a3c8SSimon J. Gerraty if (p[0] == '>') 620*9f45a3c8SSimon J. Gerraty return par->p += 1, *out_op = GT, true; 621*9f45a3c8SSimon J. Gerraty if (p[0] == '=' && p[1] == '=') 622*9f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = EQ, true; 623*9f45a3c8SSimon J. Gerraty if (p[0] == '!' && p[1] == '=') 624*9f45a3c8SSimon J. Gerraty return par->p += 2, *out_op = NE, true; 625b0c40a00SSimon J. Gerraty return false; 626956e45f6SSimon J. Gerraty } 627956e45f6SSimon J. Gerraty 62806b9b3e0SSimon J. Gerraty /* 62906b9b3e0SSimon J. Gerraty * Parse a comparison condition such as: 630956e45f6SSimon J. Gerraty * 631956e45f6SSimon J. Gerraty * 0 632956e45f6SSimon J. Gerraty * ${VAR:Mpattern} 633956e45f6SSimon J. Gerraty * ${VAR} == value 634956e45f6SSimon J. Gerraty * ${VAR:U0} < 12345 635956e45f6SSimon J. Gerraty */ 636956e45f6SSimon J. Gerraty static Token 637b0c40a00SSimon J. Gerraty CondParser_Comparison(CondParser *par, bool doEval) 638956e45f6SSimon J. Gerraty { 639956e45f6SSimon J. Gerraty Token t = TOK_ERROR; 64006b9b3e0SSimon J. Gerraty FStr lhs, rhs; 641dba7b0efSSimon J. Gerraty ComparisonOp op; 642b0c40a00SSimon J. Gerraty bool lhsQuoted, rhsQuoted; 643956e45f6SSimon J. Gerraty 64412904384SSimon J. Gerraty CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhs, &lhsQuoted); 64506b9b3e0SSimon J. Gerraty if (lhs.str == NULL) 646e2eeea75SSimon J. Gerraty goto done_lhs; 6473955d011SMarcel Moolenaar 648956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6493955d011SMarcel Moolenaar 650dba7b0efSSimon J. Gerraty if (!CondParser_ComparisonOp(par, &op)) { 651e2eeea75SSimon J. Gerraty /* Unknown operator, compare against an empty string or 0. */ 65206b9b3e0SSimon J. Gerraty t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); 653e2eeea75SSimon J. Gerraty goto done_lhs; 6543955d011SMarcel Moolenaar } 6553955d011SMarcel Moolenaar 656956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6573955d011SMarcel Moolenaar 658956e45f6SSimon J. Gerraty if (par->p[0] == '\0') { 659dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 66012904384SSimon J. Gerraty "Missing right-hand side of operator '%s'", opname[op]); 661b0c40a00SSimon J. Gerraty par->printedError = true; 662e2eeea75SSimon J. Gerraty goto done_lhs; 6633955d011SMarcel Moolenaar } 6643955d011SMarcel Moolenaar 66512904384SSimon J. Gerraty CondParser_Leaf(par, doEval, true, &rhs, &rhsQuoted); 66606b9b3e0SSimon J. Gerraty if (rhs.str == NULL) 667e2eeea75SSimon J. Gerraty goto done_rhs; 6683955d011SMarcel Moolenaar 6693841c287SSimon J. Gerraty if (!doEval) { 6703841c287SSimon J. Gerraty t = TOK_FALSE; 671e2eeea75SSimon J. Gerraty goto done_rhs; 6723841c287SSimon J. Gerraty } 6733841c287SSimon J. Gerraty 674dba7b0efSSimon J. Gerraty t = EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); 6753955d011SMarcel Moolenaar 676e2eeea75SSimon J. Gerraty done_rhs: 67706b9b3e0SSimon J. Gerraty FStr_Done(&rhs); 678e2eeea75SSimon J. Gerraty done_lhs: 67906b9b3e0SSimon J. Gerraty FStr_Done(&lhs); 6803955d011SMarcel Moolenaar return t; 6813955d011SMarcel Moolenaar } 6823955d011SMarcel Moolenaar 68306b9b3e0SSimon J. Gerraty /* 68406b9b3e0SSimon J. Gerraty * The argument to empty() is a variable name, optionally followed by 68506b9b3e0SSimon J. Gerraty * variable modifiers. 68606b9b3e0SSimon J. Gerraty */ 68712904384SSimon J. Gerraty static bool 68812904384SSimon J. Gerraty CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) 6893955d011SMarcel Moolenaar { 69012904384SSimon J. Gerraty const char *cp = par->p; 69112904384SSimon J. Gerraty Token tok; 69206b9b3e0SSimon J. Gerraty FStr val; 6933955d011SMarcel Moolenaar 694*9f45a3c8SSimon J. Gerraty if (!skip_string(&cp, "empty")) 69512904384SSimon J. Gerraty return false; 6963955d011SMarcel Moolenaar 69712904384SSimon J. Gerraty cpp_skip_whitespace(&cp); 69812904384SSimon J. Gerraty if (*cp != '(') 69912904384SSimon J. Gerraty return false; 70012904384SSimon J. Gerraty 70112904384SSimon J. Gerraty cp--; /* Make cp[1] point to the '('. */ 70212904384SSimon J. Gerraty (void)Var_Parse(&cp, SCOPE_CMDLINE, 703b0c40a00SSimon J. Gerraty doEval ? VARE_WANTRES : VARE_PARSE_ONLY, &val); 704956e45f6SSimon J. Gerraty /* TODO: handle errors */ 7053955d011SMarcel Moolenaar 70612904384SSimon J. Gerraty if (val.str == var_Error) 70712904384SSimon J. Gerraty tok = TOK_ERROR; 70812904384SSimon J. Gerraty else { 70906b9b3e0SSimon J. Gerraty cpp_skip_whitespace(&val.str); 710*9f45a3c8SSimon J. Gerraty tok = ToToken(doEval && val.str[0] == '\0'); 7113955d011SMarcel Moolenaar } 7123955d011SMarcel Moolenaar 71312904384SSimon J. Gerraty FStr_Done(&val); 71412904384SSimon J. Gerraty *out_token = tok; 71512904384SSimon J. Gerraty par->p = cp; 71612904384SSimon J. Gerraty return true; 7173955d011SMarcel Moolenaar } 7183955d011SMarcel Moolenaar 719b0c40a00SSimon J. Gerraty /* Parse a function call expression, such as 'defined(${file})'. */ 720b0c40a00SSimon J. Gerraty static bool 721b0c40a00SSimon J. Gerraty CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) 7223955d011SMarcel Moolenaar { 723*9f45a3c8SSimon J. Gerraty char *arg; 724*9f45a3c8SSimon J. Gerraty const char *p = par->p; 725*9f45a3c8SSimon J. Gerraty bool (*fn)(const char *); 726*9f45a3c8SSimon J. Gerraty const char *fn_name = p; 727e2eeea75SSimon J. Gerraty 728*9f45a3c8SSimon J. Gerraty if (skip_string(&p, "defined")) 729*9f45a3c8SSimon J. Gerraty fn = FuncDefined; 730*9f45a3c8SSimon J. Gerraty else if (skip_string(&p, "make")) 731*9f45a3c8SSimon J. Gerraty fn = FuncMake; 732*9f45a3c8SSimon J. Gerraty else if (skip_string(&p, "exists")) 733*9f45a3c8SSimon J. Gerraty fn = FuncExists; 734*9f45a3c8SSimon J. Gerraty else if (skip_string(&p, "target")) 735*9f45a3c8SSimon J. Gerraty fn = FuncTarget; 736*9f45a3c8SSimon J. Gerraty else if (skip_string(&p, "commands")) 737*9f45a3c8SSimon J. Gerraty fn = FuncCommands; 738*9f45a3c8SSimon J. Gerraty else 73912904384SSimon J. Gerraty return false; 740e2eeea75SSimon J. Gerraty 741*9f45a3c8SSimon J. Gerraty cpp_skip_whitespace(&p); 742*9f45a3c8SSimon J. Gerraty if (*p != '(') 74312904384SSimon J. Gerraty return false; 744e2eeea75SSimon J. Gerraty 745*9f45a3c8SSimon J. Gerraty arg = ParseFuncArg(par, &p, doEval, fn_name); 746*9f45a3c8SSimon J. Gerraty *out_token = ToToken(doEval && 747*9f45a3c8SSimon J. Gerraty arg != NULL && arg[0] != '\0' && fn(arg)); 748e2eeea75SSimon J. Gerraty free(arg); 749*9f45a3c8SSimon J. Gerraty 750*9f45a3c8SSimon J. Gerraty par->p = p; 751b0c40a00SSimon J. Gerraty return true; 752e2eeea75SSimon J. Gerraty } 753e2eeea75SSimon J. Gerraty 75406b9b3e0SSimon J. Gerraty /* 75512904384SSimon J. Gerraty * Parse a comparison that neither starts with '"' nor '$', such as the 75612904384SSimon J. Gerraty * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without 757b0c40a00SSimon J. Gerraty * operator, which is a number, a variable expression or a string literal. 75812904384SSimon J. Gerraty * 75912904384SSimon J. Gerraty * TODO: Can this be merged into CondParser_Comparison? 76006b9b3e0SSimon J. Gerraty */ 761e2eeea75SSimon J. Gerraty static Token 762b0c40a00SSimon J. Gerraty CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) 763e2eeea75SSimon J. Gerraty { 7643955d011SMarcel Moolenaar Token t; 765*9f45a3c8SSimon J. Gerraty char *arg; 76606b9b3e0SSimon J. Gerraty const char *cp; 7673955d011SMarcel Moolenaar 7683955d011SMarcel Moolenaar /* Push anything numeric through the compare expression */ 769956e45f6SSimon J. Gerraty cp = par->p; 770e2eeea75SSimon J. Gerraty if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') 771956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 7723955d011SMarcel Moolenaar 7733955d011SMarcel Moolenaar /* 7743955d011SMarcel Moolenaar * Most likely we have a naked token to apply the default function to. 7753955d011SMarcel Moolenaar * However ".if a == b" gets here when the "a" is unquoted and doesn't 7763955d011SMarcel Moolenaar * start with a '$'. This surprises people. 77706b9b3e0SSimon J. Gerraty * If what follows the function argument is a '=' or '!' then the 77806b9b3e0SSimon J. Gerraty * syntax would be invalid if we did "defined(a)" - so instead treat 77906b9b3e0SSimon J. Gerraty * as an expression. 7803955d011SMarcel Moolenaar */ 781b0c40a00SSimon J. Gerraty /* 782*9f45a3c8SSimon J. Gerraty * XXX: In edge cases, a variable expression may be evaluated twice, 783*9f45a3c8SSimon J. Gerraty * see cond-token-plain.mk, keyword 'twice'. 784b0c40a00SSimon J. Gerraty */ 785*9f45a3c8SSimon J. Gerraty arg = ParseWord(&cp, doEval); 786*9f45a3c8SSimon J. Gerraty assert(arg[0] != '\0'); 787*9f45a3c8SSimon J. Gerraty 788*9f45a3c8SSimon J. Gerraty if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>') 789956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 790956e45f6SSimon J. Gerraty par->p = cp; 7913955d011SMarcel Moolenaar 7923955d011SMarcel Moolenaar /* 7933955d011SMarcel Moolenaar * Evaluate the argument using the default function. 794956e45f6SSimon J. Gerraty * This path always treats .if as .ifdef. To get here, the character 7953955d011SMarcel Moolenaar * after .if must have been taken literally, so the argument cannot 7963955d011SMarcel Moolenaar * be empty - even if it contained a variable expansion. 7973955d011SMarcel Moolenaar */ 798*9f45a3c8SSimon J. Gerraty t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); 7993955d011SMarcel Moolenaar free(arg); 8003955d011SMarcel Moolenaar return t; 8013955d011SMarcel Moolenaar } 8023955d011SMarcel Moolenaar 803956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */ 8043955d011SMarcel Moolenaar static Token 805b0c40a00SSimon J. Gerraty CondParser_Token(CondParser *par, bool doEval) 8063955d011SMarcel Moolenaar { 8073955d011SMarcel Moolenaar Token t; 8083955d011SMarcel Moolenaar 809956e45f6SSimon J. Gerraty t = par->curr; 8103955d011SMarcel Moolenaar if (t != TOK_NONE) { 811956e45f6SSimon J. Gerraty par->curr = TOK_NONE; 8123955d011SMarcel Moolenaar return t; 8133955d011SMarcel Moolenaar } 8143955d011SMarcel Moolenaar 815e2eeea75SSimon J. Gerraty cpp_skip_hspace(&par->p); 8163955d011SMarcel Moolenaar 817956e45f6SSimon J. Gerraty switch (par->p[0]) { 8183955d011SMarcel Moolenaar 8193955d011SMarcel Moolenaar case '(': 820956e45f6SSimon J. Gerraty par->p++; 8213955d011SMarcel Moolenaar return TOK_LPAREN; 8223955d011SMarcel Moolenaar 8233955d011SMarcel Moolenaar case ')': 824956e45f6SSimon J. Gerraty par->p++; 8253955d011SMarcel Moolenaar return TOK_RPAREN; 8263955d011SMarcel Moolenaar 8273955d011SMarcel Moolenaar case '|': 828956e45f6SSimon J. Gerraty par->p++; 829e2eeea75SSimon J. Gerraty if (par->p[0] == '|') 830956e45f6SSimon J. Gerraty par->p++; 83106b9b3e0SSimon J. Gerraty else if (opts.strict) { 832e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '|'"); 833b0c40a00SSimon J. Gerraty par->printedError = true; 834e2eeea75SSimon J. Gerraty return TOK_ERROR; 8353955d011SMarcel Moolenaar } 8363955d011SMarcel Moolenaar return TOK_OR; 8373955d011SMarcel Moolenaar 8383955d011SMarcel Moolenaar case '&': 839956e45f6SSimon J. Gerraty par->p++; 840e2eeea75SSimon J. Gerraty if (par->p[0] == '&') 841956e45f6SSimon J. Gerraty par->p++; 84206b9b3e0SSimon J. Gerraty else if (opts.strict) { 843e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '&'"); 844b0c40a00SSimon J. Gerraty par->printedError = true; 845e2eeea75SSimon J. Gerraty return TOK_ERROR; 8463955d011SMarcel Moolenaar } 8473955d011SMarcel Moolenaar return TOK_AND; 8483955d011SMarcel Moolenaar 8493955d011SMarcel Moolenaar case '!': 850956e45f6SSimon J. Gerraty par->p++; 8513955d011SMarcel Moolenaar return TOK_NOT; 8523955d011SMarcel Moolenaar 853e2eeea75SSimon J. Gerraty case '#': /* XXX: see unit-tests/cond-token-plain.mk */ 854e2eeea75SSimon J. Gerraty case '\n': /* XXX: why should this end the condition? */ 855e2eeea75SSimon J. Gerraty /* Probably obsolete now, from 1993-03-21. */ 8563955d011SMarcel Moolenaar case '\0': 8573955d011SMarcel Moolenaar return TOK_EOF; 8583955d011SMarcel Moolenaar 8593955d011SMarcel Moolenaar case '"': 8603955d011SMarcel Moolenaar case '$': 861956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 8623955d011SMarcel Moolenaar 8633955d011SMarcel Moolenaar default: 86412904384SSimon J. Gerraty if (CondParser_FuncCallEmpty(par, doEval, &t)) 86512904384SSimon J. Gerraty return t; 866b0c40a00SSimon J. Gerraty if (CondParser_FuncCall(par, doEval, &t)) 867b0c40a00SSimon J. Gerraty return t; 868b0c40a00SSimon J. Gerraty return CondParser_ComparisonOrLeaf(par, doEval); 8693955d011SMarcel Moolenaar } 8703955d011SMarcel Moolenaar } 8713955d011SMarcel Moolenaar 87212904384SSimon J. Gerraty /* Skip the next token if it equals t. */ 87312904384SSimon J. Gerraty static bool 87412904384SSimon J. Gerraty CondParser_Skip(CondParser *par, Token t) 87512904384SSimon J. Gerraty { 87612904384SSimon J. Gerraty Token actual; 87712904384SSimon J. Gerraty 87812904384SSimon J. Gerraty actual = CondParser_Token(par, false); 87912904384SSimon J. Gerraty if (actual == t) 88012904384SSimon J. Gerraty return true; 88112904384SSimon J. Gerraty 88212904384SSimon J. Gerraty assert(par->curr == TOK_NONE); 88312904384SSimon J. Gerraty assert(actual != TOK_NONE); 88412904384SSimon J. Gerraty par->curr = actual; 88512904384SSimon J. Gerraty return false; 88612904384SSimon J. Gerraty } 88712904384SSimon J. Gerraty 88806b9b3e0SSimon J. Gerraty /* 889dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 890dba7b0efSSimon J. Gerraty * Term -> '!' Term 891dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 892dba7b0efSSimon J. Gerraty * Term -> Leaf 8933955d011SMarcel Moolenaar */ 894dba7b0efSSimon J. Gerraty static CondResult 895b0c40a00SSimon J. Gerraty CondParser_Term(CondParser *par, bool doEval) 8963955d011SMarcel Moolenaar { 897dba7b0efSSimon J. Gerraty CondResult res; 8983955d011SMarcel Moolenaar Token t; 8993955d011SMarcel Moolenaar 900956e45f6SSimon J. Gerraty t = CondParser_Token(par, doEval); 901dba7b0efSSimon J. Gerraty if (t == TOK_TRUE) 902dba7b0efSSimon J. Gerraty return CR_TRUE; 903dba7b0efSSimon J. Gerraty if (t == TOK_FALSE) 904dba7b0efSSimon J. Gerraty return CR_FALSE; 9053955d011SMarcel Moolenaar 906dba7b0efSSimon J. Gerraty if (t == TOK_LPAREN) { 907dba7b0efSSimon J. Gerraty res = CondParser_Or(par, doEval); 908dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 909dba7b0efSSimon J. Gerraty return CR_ERROR; 910dba7b0efSSimon J. Gerraty if (CondParser_Token(par, doEval) != TOK_RPAREN) 911dba7b0efSSimon J. Gerraty return CR_ERROR; 912dba7b0efSSimon J. Gerraty return res; 9133955d011SMarcel Moolenaar } 914dba7b0efSSimon J. Gerraty 915dba7b0efSSimon J. Gerraty if (t == TOK_NOT) { 916dba7b0efSSimon J. Gerraty res = CondParser_Term(par, doEval); 917dba7b0efSSimon J. Gerraty if (res == CR_TRUE) 918dba7b0efSSimon J. Gerraty res = CR_FALSE; 919dba7b0efSSimon J. Gerraty else if (res == CR_FALSE) 920dba7b0efSSimon J. Gerraty res = CR_TRUE; 921dba7b0efSSimon J. Gerraty return res; 9223955d011SMarcel Moolenaar } 923dba7b0efSSimon J. Gerraty 924dba7b0efSSimon J. Gerraty return CR_ERROR; 9253955d011SMarcel Moolenaar } 9262c3632d1SSimon J. Gerraty 92706b9b3e0SSimon J. Gerraty /* 92812904384SSimon J. Gerraty * And -> Term ('&&' Term)* 9293955d011SMarcel Moolenaar */ 930dba7b0efSSimon J. Gerraty static CondResult 931b0c40a00SSimon J. Gerraty CondParser_And(CondParser *par, bool doEval) 9323955d011SMarcel Moolenaar { 93312904384SSimon J. Gerraty CondResult res, rhs; 9343955d011SMarcel Moolenaar 93512904384SSimon J. Gerraty res = CR_TRUE; 93612904384SSimon J. Gerraty do { 93712904384SSimon J. Gerraty if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR) 938dba7b0efSSimon J. Gerraty return CR_ERROR; 93912904384SSimon J. Gerraty if (rhs == CR_FALSE) { 94012904384SSimon J. Gerraty res = CR_FALSE; 94112904384SSimon J. Gerraty doEval = false; 9423955d011SMarcel Moolenaar } 94312904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_AND)); 944dba7b0efSSimon J. Gerraty 945dba7b0efSSimon J. Gerraty return res; 9463955d011SMarcel Moolenaar } 9472c3632d1SSimon J. Gerraty 94806b9b3e0SSimon J. Gerraty /* 94912904384SSimon J. Gerraty * Or -> And ('||' And)* 9503955d011SMarcel Moolenaar */ 951dba7b0efSSimon J. Gerraty static CondResult 952b0c40a00SSimon J. Gerraty CondParser_Or(CondParser *par, bool doEval) 9533955d011SMarcel Moolenaar { 95412904384SSimon J. Gerraty CondResult res, rhs; 9553955d011SMarcel Moolenaar 95612904384SSimon J. Gerraty res = CR_FALSE; 95712904384SSimon J. Gerraty do { 95812904384SSimon J. Gerraty if ((rhs = CondParser_And(par, doEval)) == CR_ERROR) 959dba7b0efSSimon J. Gerraty return CR_ERROR; 96012904384SSimon J. Gerraty if (rhs == CR_TRUE) { 96112904384SSimon J. Gerraty res = CR_TRUE; 96212904384SSimon J. Gerraty doEval = false; 9633955d011SMarcel Moolenaar } 96412904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_OR)); 965dba7b0efSSimon J. Gerraty 966dba7b0efSSimon J. Gerraty return res; 9673955d011SMarcel Moolenaar } 9683955d011SMarcel Moolenaar 969*9f45a3c8SSimon J. Gerraty static CondResult 970*9f45a3c8SSimon J. Gerraty CondParser_Eval(CondParser *par) 9712c3632d1SSimon J. Gerraty { 972dba7b0efSSimon J. Gerraty CondResult res; 9732c3632d1SSimon J. Gerraty 974956e45f6SSimon J. Gerraty DEBUG1(COND, "CondParser_Eval: %s\n", par->p); 9752c3632d1SSimon J. Gerraty 976b0c40a00SSimon J. Gerraty res = CondParser_Or(par, true); 977*9f45a3c8SSimon J. Gerraty if (res != CR_ERROR && CondParser_Token(par, false) != TOK_EOF) 978*9f45a3c8SSimon J. Gerraty return CR_ERROR; 979956e45f6SSimon J. Gerraty 980*9f45a3c8SSimon J. Gerraty return res; 9812c3632d1SSimon J. Gerraty } 9822c3632d1SSimon J. Gerraty 98306b9b3e0SSimon J. Gerraty /* 98406b9b3e0SSimon J. Gerraty * Evaluate the condition, including any side effects from the variable 985956e45f6SSimon J. Gerraty * expressions in the condition. The condition consists of &&, ||, !, 986956e45f6SSimon J. Gerraty * function(arg), comparisons and parenthetical groupings thereof. 9873955d011SMarcel Moolenaar */ 988*9f45a3c8SSimon J. Gerraty static CondResult 989*9f45a3c8SSimon J. Gerraty CondEvalExpression(const char *cond, bool plain, 99012904384SSimon J. Gerraty bool (*evalBare)(const char *), bool negate, 99112904384SSimon J. Gerraty bool eprint, bool leftUnquotedOK) 9923955d011SMarcel Moolenaar { 993956e45f6SSimon J. Gerraty CondParser par; 994*9f45a3c8SSimon J. Gerraty CondResult rval; 9953955d011SMarcel Moolenaar 996e2eeea75SSimon J. Gerraty cpp_skip_hspace(&cond); 9973955d011SMarcel Moolenaar 998dba7b0efSSimon J. Gerraty par.plain = plain; 999dba7b0efSSimon J. Gerraty par.evalBare = evalBare; 1000dba7b0efSSimon J. Gerraty par.negateEvalBare = negate; 100112904384SSimon J. Gerraty par.leftUnquotedOK = leftUnquotedOK; 1002956e45f6SSimon J. Gerraty par.p = cond; 1003956e45f6SSimon J. Gerraty par.curr = TOK_NONE; 1004b0c40a00SSimon J. Gerraty par.printedError = false; 10053955d011SMarcel Moolenaar 1006*9f45a3c8SSimon J. Gerraty rval = CondParser_Eval(&par); 10073955d011SMarcel Moolenaar 1008*9f45a3c8SSimon J. Gerraty if (rval == CR_ERROR && eprint && !par.printedError) 1009956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); 10103955d011SMarcel Moolenaar 10113955d011SMarcel Moolenaar return rval; 10123955d011SMarcel Moolenaar } 10133955d011SMarcel Moolenaar 101406b9b3e0SSimon J. Gerraty /* 101506b9b3e0SSimon J. Gerraty * Evaluate a condition in a :? modifier, such as 101606b9b3e0SSimon J. Gerraty * ${"${VAR}" == value:?yes:no}. 101706b9b3e0SSimon J. Gerraty */ 1018*9f45a3c8SSimon J. Gerraty CondResult 1019*9f45a3c8SSimon J. Gerraty Cond_EvalCondition(const char *cond) 1020956e45f6SSimon J. Gerraty { 1021*9f45a3c8SSimon J. Gerraty return CondEvalExpression(cond, true, 102212904384SSimon J. Gerraty FuncDefined, false, false, true); 1023956e45f6SSimon J. Gerraty } 10243955d011SMarcel Moolenaar 1025b0c40a00SSimon J. Gerraty static bool 102606b9b3e0SSimon J. Gerraty IsEndif(const char *p) 102706b9b3e0SSimon J. Gerraty { 102806b9b3e0SSimon J. Gerraty return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && 102906b9b3e0SSimon J. Gerraty p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); 103006b9b3e0SSimon J. Gerraty } 103106b9b3e0SSimon J. Gerraty 1032b0c40a00SSimon J. Gerraty static bool 1033b0c40a00SSimon J. Gerraty DetermineKindOfConditional(const char **pp, bool *out_plain, 103412904384SSimon J. Gerraty bool (**out_evalBare)(const char *), 1035b0c40a00SSimon J. Gerraty bool *out_negate) 1036dba7b0efSSimon J. Gerraty { 1037*9f45a3c8SSimon J. Gerraty const char *p = *pp + 2; 1038dba7b0efSSimon J. Gerraty 1039b0c40a00SSimon J. Gerraty *out_plain = false; 1040dba7b0efSSimon J. Gerraty *out_evalBare = FuncDefined; 1041*9f45a3c8SSimon J. Gerraty *out_negate = skip_string(&p, "n"); 1042*9f45a3c8SSimon J. Gerraty 1043*9f45a3c8SSimon J. Gerraty if (skip_string(&p, "def")) { /* .ifdef and .ifndef */ 1044*9f45a3c8SSimon J. Gerraty } else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */ 1045dba7b0efSSimon J. Gerraty *out_evalBare = FuncMake; 1046*9f45a3c8SSimon J. Gerraty else if (!*out_negate) /* plain .if */ 1047b0c40a00SSimon J. Gerraty *out_plain = true; 1048*9f45a3c8SSimon J. Gerraty else 1049*9f45a3c8SSimon J. Gerraty goto unknown_directive; 1050*9f45a3c8SSimon J. Gerraty if (ch_isalpha(*p)) 1051*9f45a3c8SSimon J. Gerraty goto unknown_directive; 1052*9f45a3c8SSimon J. Gerraty 1053*9f45a3c8SSimon J. Gerraty *pp = p; 1054*9f45a3c8SSimon J. Gerraty return true; 1055*9f45a3c8SSimon J. Gerraty 1056*9f45a3c8SSimon J. Gerraty unknown_directive: 1057dba7b0efSSimon J. Gerraty /* 1058*9f45a3c8SSimon J. Gerraty * TODO: Add error message about unknown directive, since there is no 1059*9f45a3c8SSimon J. Gerraty * other known directive that starts with 'el' or 'if'. 1060dba7b0efSSimon J. Gerraty * 1061dba7b0efSSimon J. Gerraty * Example: .elifx 123 1062dba7b0efSSimon J. Gerraty */ 1063b0c40a00SSimon J. Gerraty return false; 1064dba7b0efSSimon J. Gerraty } 1065dba7b0efSSimon J. Gerraty 106606b9b3e0SSimon J. Gerraty /* 106706b9b3e0SSimon J. Gerraty * Evaluate the conditional directive in the line, which is one of: 10683955d011SMarcel Moolenaar * 1069e2eeea75SSimon J. Gerraty * .if <cond> 1070e2eeea75SSimon J. Gerraty * .ifmake <cond> 1071e2eeea75SSimon J. Gerraty * .ifnmake <cond> 1072e2eeea75SSimon J. Gerraty * .ifdef <cond> 1073e2eeea75SSimon J. Gerraty * .ifndef <cond> 1074e2eeea75SSimon J. Gerraty * .elif <cond> 1075e2eeea75SSimon J. Gerraty * .elifmake <cond> 1076e2eeea75SSimon J. Gerraty * .elifnmake <cond> 1077e2eeea75SSimon J. Gerraty * .elifdef <cond> 1078e2eeea75SSimon J. Gerraty * .elifndef <cond> 1079e2eeea75SSimon J. Gerraty * .else 1080e2eeea75SSimon J. Gerraty * .endif 1081e2eeea75SSimon J. Gerraty * 1082e2eeea75SSimon J. Gerraty * In these directives, <cond> consists of &&, ||, !, function(arg), 1083e2eeea75SSimon J. Gerraty * comparisons, expressions, bare words, numbers and strings, and 1084e2eeea75SSimon J. Gerraty * parenthetical groupings thereof. 1085956e45f6SSimon J. Gerraty * 1086956e45f6SSimon J. Gerraty * Results: 1087*9f45a3c8SSimon J. Gerraty * CR_TRUE to continue parsing the lines that follow the 1088b0c40a00SSimon J. Gerraty * conditional (when <cond> evaluates to true) 1089*9f45a3c8SSimon J. Gerraty * CR_FALSE to skip the lines after the conditional 1090b0c40a00SSimon J. Gerraty * (when <cond> evaluates to false, or when a previous 1091956e45f6SSimon J. Gerraty * branch has already been taken) 1092*9f45a3c8SSimon J. Gerraty * CR_ERROR if the conditional was not valid, either because of 1093956e45f6SSimon J. Gerraty * a syntax error or because some variable was undefined 1094956e45f6SSimon J. Gerraty * or because the condition could not be evaluated 10953955d011SMarcel Moolenaar */ 1096*9f45a3c8SSimon J. Gerraty CondResult 109706b9b3e0SSimon J. Gerraty Cond_EvalLine(const char *line) 10983955d011SMarcel Moolenaar { 1099e2eeea75SSimon J. Gerraty typedef enum IfState { 1100e2eeea75SSimon J. Gerraty 1101b0c40a00SSimon J. Gerraty /* None of the previous <cond> evaluated to true. */ 1102e2eeea75SSimon J. Gerraty IFS_INITIAL = 0, 1103e2eeea75SSimon J. Gerraty 1104*9f45a3c8SSimon J. Gerraty /* 1105*9f45a3c8SSimon J. Gerraty * The previous <cond> evaluated to true. The lines following 1106*9f45a3c8SSimon J. Gerraty * this condition are interpreted. 1107*9f45a3c8SSimon J. Gerraty */ 1108e2eeea75SSimon J. Gerraty IFS_ACTIVE = 1 << 0, 1109e2eeea75SSimon J. Gerraty 1110e2eeea75SSimon J. Gerraty /* The previous directive was an '.else'. */ 1111e2eeea75SSimon J. Gerraty IFS_SEEN_ELSE = 1 << 1, 1112e2eeea75SSimon J. Gerraty 1113b0c40a00SSimon J. Gerraty /* One of the previous <cond> evaluated to true. */ 1114e2eeea75SSimon J. Gerraty IFS_WAS_ACTIVE = 1 << 2 1115e2eeea75SSimon J. Gerraty 1116e2eeea75SSimon J. Gerraty } IfState; 1117e2eeea75SSimon J. Gerraty 1118e2eeea75SSimon J. Gerraty static enum IfState *cond_states = NULL; 1119e2eeea75SSimon J. Gerraty static unsigned int cond_states_cap = 128; 11203955d011SMarcel Moolenaar 1121b0c40a00SSimon J. Gerraty bool plain; 112212904384SSimon J. Gerraty bool (*evalBare)(const char *); 1123b0c40a00SSimon J. Gerraty bool negate; 1124b0c40a00SSimon J. Gerraty bool isElif; 1125*9f45a3c8SSimon J. Gerraty CondResult res; 1126e2eeea75SSimon J. Gerraty IfState state; 1127e2eeea75SSimon J. Gerraty const char *p = line; 11283955d011SMarcel Moolenaar 1129e2eeea75SSimon J. Gerraty if (cond_states == NULL) { 113006b9b3e0SSimon J. Gerraty cond_states = bmake_malloc( 113106b9b3e0SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 1132e2eeea75SSimon J. Gerraty cond_states[0] = IFS_ACTIVE; 113359a02420SSimon J. Gerraty } 11343955d011SMarcel Moolenaar 1135e2eeea75SSimon J. Gerraty p++; /* skip the leading '.' */ 1136e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 1137e2eeea75SSimon J. Gerraty 113806b9b3e0SSimon J. Gerraty if (IsEndif(p)) { /* It is an '.endif'. */ 113906b9b3e0SSimon J. Gerraty if (p[5] != '\0') { 114006b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, 114112904384SSimon J. Gerraty "The .endif directive does not take arguments"); 1142e2eeea75SSimon J. Gerraty } 1143e2eeea75SSimon J. Gerraty 11443955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1145956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less endif"); 1146*9f45a3c8SSimon J. Gerraty return CR_TRUE; 11473955d011SMarcel Moolenaar } 1148e2eeea75SSimon J. Gerraty 11493955d011SMarcel Moolenaar /* Return state for previous conditional */ 11503955d011SMarcel Moolenaar cond_depth--; 1151e2eeea75SSimon J. Gerraty return cond_states[cond_depth] & IFS_ACTIVE 1152*9f45a3c8SSimon J. Gerraty ? CR_TRUE : CR_FALSE; 11533955d011SMarcel Moolenaar } 11543955d011SMarcel Moolenaar 115506b9b3e0SSimon J. Gerraty /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ 115606b9b3e0SSimon J. Gerraty if (p[0] == 'e') { 115706b9b3e0SSimon J. Gerraty if (p[1] != 'l') { 115806b9b3e0SSimon J. Gerraty /* 115906b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a 116012904384SSimon J. Gerraty * transformation rule like '.err.txt', 116106b9b3e0SSimon J. Gerraty * therefore no error message here. 116206b9b3e0SSimon J. Gerraty */ 1163*9f45a3c8SSimon J. Gerraty return CR_ERROR; 116406b9b3e0SSimon J. Gerraty } 116506b9b3e0SSimon J. Gerraty 11663955d011SMarcel Moolenaar /* Quite likely this is 'else' or 'elif' */ 1167e2eeea75SSimon J. Gerraty p += 2; 1168*9f45a3c8SSimon J. Gerraty if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) { 116906b9b3e0SSimon J. Gerraty if (p[2] != '\0') 1170e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, 117106b9b3e0SSimon J. Gerraty "The .else directive " 117212904384SSimon J. Gerraty "does not take arguments"); 1173e2eeea75SSimon J. Gerraty 11743955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1175956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less else"); 1176*9f45a3c8SSimon J. Gerraty return CR_TRUE; 11773955d011SMarcel Moolenaar } 11783955d011SMarcel Moolenaar 1179e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1180e2eeea75SSimon J. Gerraty if (state == IFS_INITIAL) { 1181e2eeea75SSimon J. Gerraty state = IFS_ACTIVE | IFS_SEEN_ELSE; 1182e2eeea75SSimon J. Gerraty } else { 1183e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) 118406b9b3e0SSimon J. Gerraty Parse_Error(PARSE_WARNING, 118506b9b3e0SSimon J. Gerraty "extra else"); 1186e2eeea75SSimon J. Gerraty state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 11873955d011SMarcel Moolenaar } 1188e2eeea75SSimon J. Gerraty cond_states[cond_depth] = state; 1189e2eeea75SSimon J. Gerraty 1190*9f45a3c8SSimon J. Gerraty return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE; 11913955d011SMarcel Moolenaar } 11923955d011SMarcel Moolenaar /* Assume for now it is an elif */ 1193b0c40a00SSimon J. Gerraty isElif = true; 11943955d011SMarcel Moolenaar } else 1195b0c40a00SSimon J. Gerraty isElif = false; 11963955d011SMarcel Moolenaar 1197e2eeea75SSimon J. Gerraty if (p[0] != 'i' || p[1] != 'f') { 119806b9b3e0SSimon J. Gerraty /* 119906b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a transformation rule 120006b9b3e0SSimon J. Gerraty * like '.elisp.scm', therefore no error message here. 120106b9b3e0SSimon J. Gerraty */ 1202*9f45a3c8SSimon J. Gerraty return CR_ERROR; /* Not an ifxxx or elifxxx line */ 1203e2eeea75SSimon J. Gerraty } 12043955d011SMarcel Moolenaar 1205dba7b0efSSimon J. Gerraty if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) 1206*9f45a3c8SSimon J. Gerraty return CR_ERROR; 12073955d011SMarcel Moolenaar 12083955d011SMarcel Moolenaar if (isElif) { 12093955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1210956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less elif"); 1211*9f45a3c8SSimon J. Gerraty return CR_TRUE; 12123955d011SMarcel Moolenaar } 1213e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1214e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) { 12153955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "extra elif"); 121606b9b3e0SSimon J. Gerraty cond_states[cond_depth] = 121706b9b3e0SSimon J. Gerraty IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 1218*9f45a3c8SSimon J. Gerraty return CR_FALSE; 12193955d011SMarcel Moolenaar } 1220e2eeea75SSimon J. Gerraty if (state != IFS_INITIAL) { 1221e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 1222*9f45a3c8SSimon J. Gerraty return CR_FALSE; 12233955d011SMarcel Moolenaar } 12243955d011SMarcel Moolenaar } else { 12253955d011SMarcel Moolenaar /* Normal .if */ 1226e2eeea75SSimon J. Gerraty if (cond_depth + 1 >= cond_states_cap) { 122759a02420SSimon J. Gerraty /* 122859a02420SSimon J. Gerraty * This is rare, but not impossible. 122959a02420SSimon J. Gerraty * In meta mode, dirdeps.mk (only runs at level 0) 123059a02420SSimon J. Gerraty * can need more than the default. 123159a02420SSimon J. Gerraty */ 1232e2eeea75SSimon J. Gerraty cond_states_cap += 32; 1233e2eeea75SSimon J. Gerraty cond_states = bmake_realloc(cond_states, 1234*9f45a3c8SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 12353955d011SMarcel Moolenaar } 1236e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 12373955d011SMarcel Moolenaar cond_depth++; 1238e2eeea75SSimon J. Gerraty if (!(state & IFS_ACTIVE)) { 123906b9b3e0SSimon J. Gerraty /* 124006b9b3e0SSimon J. Gerraty * If we aren't parsing the data, 124106b9b3e0SSimon J. Gerraty * treat as always false. 124206b9b3e0SSimon J. Gerraty */ 1243e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 1244*9f45a3c8SSimon J. Gerraty return CR_FALSE; 12453955d011SMarcel Moolenaar } 12463955d011SMarcel Moolenaar } 12473955d011SMarcel Moolenaar 1248956e45f6SSimon J. Gerraty /* And evaluate the conditional expression */ 1249*9f45a3c8SSimon J. Gerraty res = CondEvalExpression(p, plain, evalBare, negate, true, false); 1250*9f45a3c8SSimon J. Gerraty if (res == CR_ERROR) { 1251*9f45a3c8SSimon J. Gerraty /* Syntax error, error message already output. */ 1252*9f45a3c8SSimon J. Gerraty /* Skip everything to the matching '.endif'. */ 1253*9f45a3c8SSimon J. Gerraty /* An extra '.else' is not detected in this case. */ 1254e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 1255*9f45a3c8SSimon J. Gerraty return CR_FALSE; 12563955d011SMarcel Moolenaar } 12573955d011SMarcel Moolenaar 1258*9f45a3c8SSimon J. Gerraty cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL; 1259*9f45a3c8SSimon J. Gerraty return res; 12603955d011SMarcel Moolenaar } 12613955d011SMarcel Moolenaar 12623955d011SMarcel Moolenaar void 12633955d011SMarcel Moolenaar Cond_restore_depth(unsigned int saved_depth) 12643955d011SMarcel Moolenaar { 1265956e45f6SSimon J. Gerraty unsigned int open_conds = cond_depth - cond_min_depth; 12663955d011SMarcel Moolenaar 12673955d011SMarcel Moolenaar if (open_conds != 0 || saved_depth > cond_depth) { 126806b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, "%u open conditional%s", 126906b9b3e0SSimon J. Gerraty open_conds, open_conds == 1 ? "" : "s"); 12703955d011SMarcel Moolenaar cond_depth = cond_min_depth; 12713955d011SMarcel Moolenaar } 12723955d011SMarcel Moolenaar 12733955d011SMarcel Moolenaar cond_min_depth = saved_depth; 12743955d011SMarcel Moolenaar } 12753955d011SMarcel Moolenaar 12763955d011SMarcel Moolenaar unsigned int 12773955d011SMarcel Moolenaar Cond_save_depth(void) 12783955d011SMarcel Moolenaar { 1279956e45f6SSimon J. Gerraty unsigned int depth = cond_min_depth; 12803955d011SMarcel Moolenaar 12813955d011SMarcel Moolenaar cond_min_depth = cond_depth; 12823955d011SMarcel Moolenaar return depth; 12833955d011SMarcel Moolenaar } 1284