1*12904384SSimon J. Gerraty /* $NetBSD: cond.c,v 1.302 2021/12/12 09:36: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 * 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*12904384SSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $"); 99956e45f6SSimon J. Gerraty 1003955d011SMarcel Moolenaar /* 1013955d011SMarcel Moolenaar * The parsing of conditional expressions is based on this grammar: 102*12904384SSimon J. Gerraty * Or -> And ('||' And)* 103*12904384SSimon 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 112dba7b0efSSimon J. Gerraty * Leaf -> Symbol 113dba7b0efSSimon J. Gerraty * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' 1143955d011SMarcel Moolenaar * 115dba7b0efSSimon J. Gerraty * 'Symbol' is an unquoted string literal to which the default function is 116dba7b0efSSimon J. Gerraty * applied. 1173955d011SMarcel Moolenaar * 118956e45f6SSimon J. Gerraty * The tokens are scanned by CondToken, 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 126956e45f6SSimon J. Gerraty * the function given in the terminal, they return either TOK_TRUE or 127956e45f6SSimon J. Gerraty * TOK_FALSE. 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 CondResult { 135dba7b0efSSimon J. Gerraty CR_FALSE, CR_TRUE, CR_ERROR 136dba7b0efSSimon J. Gerraty } CondResult; 137dba7b0efSSimon J. Gerraty 138dba7b0efSSimon J. Gerraty typedef enum ComparisonOp { 139dba7b0efSSimon J. Gerraty LT, LE, GT, GE, EQ, NE 140dba7b0efSSimon J. Gerraty } ComparisonOp; 141dba7b0efSSimon J. Gerraty 142956e45f6SSimon J. Gerraty typedef struct CondParser { 143dba7b0efSSimon J. Gerraty 144dba7b0efSSimon J. Gerraty /* 145dba7b0efSSimon J. Gerraty * The plain '.if ${VAR}' evaluates to true if the value of the 146dba7b0efSSimon J. Gerraty * expression has length > 0. The other '.if' variants delegate 147dba7b0efSSimon J. Gerraty * to evalBare instead. 148dba7b0efSSimon J. Gerraty */ 149b0c40a00SSimon J. Gerraty bool plain; 150dba7b0efSSimon J. Gerraty 151dba7b0efSSimon J. Gerraty /* The function to apply on unquoted bare words. */ 152*12904384SSimon J. Gerraty bool (*evalBare)(const char *); 153b0c40a00SSimon J. Gerraty bool negateEvalBare; 154dba7b0efSSimon J. Gerraty 155*12904384SSimon J. Gerraty /* 156*12904384SSimon J. Gerraty * Whether the left-hand side of a comparison may be an unquoted 157*12904384SSimon J. Gerraty * string. This is allowed for expressions of the form 158*12904384SSimon J. Gerraty * ${condition:?:}, see ApplyModifier_IfElse. Such a condition is 159*12904384SSimon J. Gerraty * expanded before it is evaluated, due to ease of implementation. 160*12904384SSimon J. Gerraty * This means that at the point where the condition is evaluated, 161*12904384SSimon J. Gerraty * make cannot know anymore whether the left-hand side had originally 162*12904384SSimon J. Gerraty * been a variable expression or a plain word. 163*12904384SSimon J. Gerraty * 164*12904384SSimon J. Gerraty * In all other contexts, the left-hand side must either be a 165*12904384SSimon J. Gerraty * variable expression, a quoted string or a number. 166*12904384SSimon J. Gerraty */ 167*12904384SSimon J. Gerraty bool leftUnquotedOK; 168*12904384SSimon J. Gerraty 169956e45f6SSimon J. Gerraty const char *p; /* The remaining condition to parse */ 170956e45f6SSimon J. Gerraty Token curr; /* Single push-back token used in parsing */ 1713955d011SMarcel Moolenaar 17206b9b3e0SSimon J. Gerraty /* 17306b9b3e0SSimon J. Gerraty * Whether an error message has already been printed for this 17406b9b3e0SSimon J. Gerraty * condition. The first available error message is usually the most 17506b9b3e0SSimon J. Gerraty * specific one, therefore it makes sense to suppress the standard 17606b9b3e0SSimon J. Gerraty * "Malformed conditional" message. 17706b9b3e0SSimon J. Gerraty */ 178b0c40a00SSimon J. Gerraty bool printedError; 179956e45f6SSimon J. Gerraty } CondParser; 180956e45f6SSimon J. Gerraty 181b0c40a00SSimon J. Gerraty static CondResult CondParser_Or(CondParser *par, bool); 1823955d011SMarcel Moolenaar 1833955d011SMarcel Moolenaar static unsigned int cond_depth = 0; /* current .if nesting level */ 1843955d011SMarcel Moolenaar static unsigned int cond_min_depth = 0; /* depth at makefile open */ 1853955d011SMarcel Moolenaar 186*12904384SSimon J. Gerraty /* Names for ComparisonOp. */ 187*12904384SSimon J. Gerraty static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" }; 18828a6bc81SSimon J. Gerraty 189b0c40a00SSimon J. Gerraty static bool 190*12904384SSimon J. Gerraty is_token(const char *str, const char *tok, unsigned char len) 1913955d011SMarcel Moolenaar { 192*12904384SSimon J. Gerraty return strncmp(str, tok, (size_t)len) == 0 && !ch_isalpha(str[len]); 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 /* 208*12904384SSimon J. Gerraty * Parse a single word, taking into account balanced parentheses as well as 209*12904384SSimon J. Gerraty * embedded expressions. Used for the argument of a built-in function as 210*12904384SSimon J. Gerraty * well as for bare words, which are then passed to the default function. 211956e45f6SSimon J. Gerraty * 212956e45f6SSimon J. Gerraty * Arguments: 213956e45f6SSimon J. Gerraty * *pp initially points at the '(', 214956e45f6SSimon J. Gerraty * upon successful return it points right after the ')'. 215956e45f6SSimon J. Gerraty * 216956e45f6SSimon J. Gerraty * *out_arg receives the argument as string. 217956e45f6SSimon J. Gerraty * 218956e45f6SSimon J. Gerraty * func says whether the argument belongs to an actual function, or 219*12904384SSimon J. Gerraty * NULL when parsing a bare word. 220956e45f6SSimon J. Gerraty * 221*12904384SSimon J. Gerraty * Return the length of the argument, or an ambiguous 0 on error. 22206b9b3e0SSimon J. Gerraty */ 223956e45f6SSimon J. Gerraty static size_t 224*12904384SSimon J. Gerraty ParseWord(CondParser *par, const char **pp, bool doEval, const char *func, 22506b9b3e0SSimon J. Gerraty char **out_arg) 22606b9b3e0SSimon J. Gerraty { 227956e45f6SSimon J. Gerraty const char *p = *pp; 228956e45f6SSimon J. Gerraty Buffer argBuf; 2293955d011SMarcel Moolenaar int paren_depth; 2302c3632d1SSimon J. Gerraty size_t argLen; 2313955d011SMarcel Moolenaar 2323955d011SMarcel Moolenaar if (func != NULL) 233956e45f6SSimon J. Gerraty p++; /* Skip opening '(' - verified by caller */ 2343955d011SMarcel Moolenaar 235e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2363955d011SMarcel Moolenaar 237e2eeea75SSimon J. Gerraty Buf_InitSize(&argBuf, 16); 2383955d011SMarcel Moolenaar 2393955d011SMarcel Moolenaar paren_depth = 0; 2403955d011SMarcel Moolenaar for (;;) { 241956e45f6SSimon J. Gerraty char ch = *p; 242e2eeea75SSimon J. Gerraty if (ch == '\0' || ch == ' ' || ch == '\t') 2433955d011SMarcel Moolenaar break; 2443955d011SMarcel Moolenaar if ((ch == '&' || ch == '|') && paren_depth == 0) 2453955d011SMarcel Moolenaar break; 246956e45f6SSimon J. Gerraty if (*p == '$') { 2473955d011SMarcel Moolenaar /* 24806b9b3e0SSimon J. Gerraty * Parse the variable expression and install it as 24906b9b3e0SSimon J. Gerraty * part of the argument if it's valid. We tell 25006b9b3e0SSimon J. Gerraty * Var_Parse to complain on an undefined variable, 25106b9b3e0SSimon J. Gerraty * (XXX: but Var_Parse ignores that request) 25206b9b3e0SSimon J. Gerraty * so we don't need to do it. Nor do we return an 25306b9b3e0SSimon J. Gerraty * error, though perhaps we should. 2543955d011SMarcel Moolenaar */ 255b0c40a00SSimon J. Gerraty VarEvalMode emode = doEval 256b0c40a00SSimon J. Gerraty ? VARE_UNDEFERR 257b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 25806b9b3e0SSimon J. Gerraty FStr nestedVal; 259b0c40a00SSimon J. Gerraty (void)Var_Parse(&p, SCOPE_CMDLINE, emode, &nestedVal); 260956e45f6SSimon J. Gerraty /* TODO: handle errors */ 26106b9b3e0SSimon J. Gerraty Buf_AddStr(&argBuf, nestedVal.str); 26206b9b3e0SSimon J. Gerraty FStr_Done(&nestedVal); 2633955d011SMarcel Moolenaar continue; 2643955d011SMarcel Moolenaar } 2653955d011SMarcel Moolenaar if (ch == '(') 2663955d011SMarcel Moolenaar paren_depth++; 2672c3632d1SSimon J. Gerraty else if (ch == ')' && --paren_depth < 0) 2683955d011SMarcel Moolenaar break; 269956e45f6SSimon J. Gerraty Buf_AddByte(&argBuf, *p); 270956e45f6SSimon J. Gerraty p++; 2713955d011SMarcel Moolenaar } 2723955d011SMarcel Moolenaar 273dba7b0efSSimon J. Gerraty argLen = argBuf.len; 274dba7b0efSSimon J. Gerraty *out_arg = Buf_DoneData(&argBuf); 2753955d011SMarcel Moolenaar 276e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2773955d011SMarcel Moolenaar 278956e45f6SSimon J. Gerraty if (func != NULL && *p++ != ')') { 279dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 280dba7b0efSSimon J. Gerraty "Missing closing parenthesis for %s()", func); 281b0c40a00SSimon J. Gerraty par->printedError = true; 2823841c287SSimon J. Gerraty return 0; 2833955d011SMarcel Moolenaar } 2843955d011SMarcel Moolenaar 285956e45f6SSimon J. Gerraty *pp = p; 2863841c287SSimon J. Gerraty return argLen; 2873955d011SMarcel Moolenaar } 2882c3632d1SSimon J. Gerraty 2892c3632d1SSimon J. Gerraty /* Test whether the given variable is defined. */ 290b0c40a00SSimon J. Gerraty static bool 291*12904384SSimon J. Gerraty FuncDefined(const char *arg) 2923955d011SMarcel Moolenaar { 293dba7b0efSSimon J. Gerraty FStr value = Var_Value(SCOPE_CMDLINE, arg); 294b0c40a00SSimon J. Gerraty bool result = value.str != NULL; 29506b9b3e0SSimon J. Gerraty FStr_Done(&value); 2963841c287SSimon J. Gerraty return result; 2973955d011SMarcel Moolenaar } 2982c3632d1SSimon J. Gerraty 299*12904384SSimon J. Gerraty /* See if the given target is requested to be made. */ 300b0c40a00SSimon J. Gerraty static bool 301*12904384SSimon J. Gerraty FuncMake(const char *arg) 3023955d011SMarcel Moolenaar { 303956e45f6SSimon J. Gerraty StringListNode *ln; 304956e45f6SSimon J. Gerraty 30506b9b3e0SSimon J. Gerraty for (ln = opts.create.first; ln != NULL; ln = ln->next) 306956e45f6SSimon J. Gerraty if (Str_Match(ln->datum, arg)) 307b0c40a00SSimon J. Gerraty return true; 308b0c40a00SSimon J. Gerraty return false; 3093955d011SMarcel Moolenaar } 3102c3632d1SSimon J. Gerraty 3112c3632d1SSimon J. Gerraty /* See if the given file exists. */ 312b0c40a00SSimon J. Gerraty static bool 313*12904384SSimon J. Gerraty FuncExists(const char *arg) 3143955d011SMarcel Moolenaar { 315b0c40a00SSimon J. Gerraty bool result; 3163955d011SMarcel Moolenaar char *path; 3173955d011SMarcel Moolenaar 31806b9b3e0SSimon J. Gerraty path = Dir_FindFile(arg, &dirSearchPath); 319e2eeea75SSimon J. Gerraty DEBUG2(COND, "exists(%s) result is \"%s\"\n", 320e2eeea75SSimon J. Gerraty arg, path != NULL ? path : ""); 321e2eeea75SSimon J. Gerraty result = path != NULL; 3223955d011SMarcel Moolenaar free(path); 3233841c287SSimon J. Gerraty return result; 3243955d011SMarcel Moolenaar } 3252c3632d1SSimon J. Gerraty 3262c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target. */ 327b0c40a00SSimon J. Gerraty static bool 328*12904384SSimon J. Gerraty FuncTarget(const char *arg) 3293955d011SMarcel Moolenaar { 330956e45f6SSimon J. Gerraty GNode *gn = Targ_FindNode(arg); 331956e45f6SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn); 3323955d011SMarcel Moolenaar } 3333955d011SMarcel Moolenaar 33406b9b3e0SSimon J. Gerraty /* 33506b9b3e0SSimon J. Gerraty * See if the given node exists and is an actual target with commands 33606b9b3e0SSimon J. Gerraty * associated with it. 33706b9b3e0SSimon J. Gerraty */ 338b0c40a00SSimon J. Gerraty static bool 339*12904384SSimon J. Gerraty FuncCommands(const char *arg) 3403955d011SMarcel Moolenaar { 341956e45f6SSimon J. Gerraty GNode *gn = Targ_FindNode(arg); 34206b9b3e0SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands); 3433955d011SMarcel Moolenaar } 3442c3632d1SSimon J. Gerraty 345e2eeea75SSimon J. Gerraty /* 3463955d011SMarcel Moolenaar * Convert the given number into a double. 3473955d011SMarcel Moolenaar * We try a base 10 or 16 integer conversion first, if that fails 3483955d011SMarcel Moolenaar * then we try a floating point conversion instead. 3493955d011SMarcel Moolenaar * 3503955d011SMarcel Moolenaar * Results: 351b0c40a00SSimon J. Gerraty * Returns true if the conversion succeeded. 352e2eeea75SSimon J. Gerraty * Sets 'out_value' to the converted number. 3533955d011SMarcel Moolenaar */ 354b0c40a00SSimon J. Gerraty static bool 355e2eeea75SSimon J. Gerraty TryParseNumber(const char *str, double *out_value) 3563955d011SMarcel Moolenaar { 357e2eeea75SSimon J. Gerraty char *end; 358e2eeea75SSimon J. Gerraty unsigned long ul_val; 359e2eeea75SSimon J. Gerraty double dbl_val; 3603955d011SMarcel Moolenaar 361e2eeea75SSimon J. Gerraty if (str[0] == '\0') { /* XXX: why is an empty string a number? */ 362e2eeea75SSimon J. Gerraty *out_value = 0.0; 363b0c40a00SSimon J. Gerraty return true; 364ac3446e9SSimon J. Gerraty } 365e2eeea75SSimon J. Gerraty 366*12904384SSimon J. Gerraty errno = 0; 367e2eeea75SSimon J. Gerraty ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); 368e2eeea75SSimon J. Gerraty if (*end == '\0' && errno != ERANGE) { 369e2eeea75SSimon J. Gerraty *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; 370b0c40a00SSimon J. Gerraty return true; 3713955d011SMarcel Moolenaar } 3723955d011SMarcel Moolenaar 373e2eeea75SSimon J. Gerraty if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') 374b0c40a00SSimon J. Gerraty return false; /* skip the expensive strtod call */ 375e2eeea75SSimon J. Gerraty dbl_val = strtod(str, &end); 376e2eeea75SSimon J. Gerraty if (*end != '\0') 377b0c40a00SSimon J. Gerraty return false; 378e2eeea75SSimon J. Gerraty 379e2eeea75SSimon J. Gerraty *out_value = dbl_val; 380b0c40a00SSimon J. Gerraty return true; 3813955d011SMarcel Moolenaar } 3823955d011SMarcel Moolenaar 383b0c40a00SSimon J. Gerraty static bool 384956e45f6SSimon J. Gerraty is_separator(char ch) 385956e45f6SSimon J. Gerraty { 386b0c40a00SSimon J. Gerraty return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' || 387b0c40a00SSimon J. Gerraty ch == '>' || ch == '<' || ch == ')' /* but not '(' */; 388956e45f6SSimon J. Gerraty } 389956e45f6SSimon J. Gerraty 390dba7b0efSSimon J. Gerraty /* 391dba7b0efSSimon J. Gerraty * In a quoted or unquoted string literal or a number, parse a variable 392dba7b0efSSimon J. Gerraty * expression. 393dba7b0efSSimon J. Gerraty * 394dba7b0efSSimon J. Gerraty * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} 395dba7b0efSSimon J. Gerraty */ 396b0c40a00SSimon J. Gerraty static bool 397dba7b0efSSimon J. Gerraty CondParser_StringExpr(CondParser *par, const char *start, 398*12904384SSimon J. Gerraty bool doEval, bool quoted, 399*12904384SSimon J. Gerraty Buffer *buf, FStr *inout_str) 400dba7b0efSSimon J. Gerraty { 401b0c40a00SSimon J. Gerraty VarEvalMode emode; 402dba7b0efSSimon J. Gerraty const char *nested_p; 403b0c40a00SSimon J. Gerraty bool atStart; 404dba7b0efSSimon J. Gerraty VarParseResult parseResult; 405dba7b0efSSimon J. Gerraty 406*12904384SSimon J. Gerraty emode = doEval && quoted ? VARE_WANTRES 407*12904384SSimon J. Gerraty : doEval ? VARE_UNDEFERR 408b0c40a00SSimon J. Gerraty : VARE_PARSE_ONLY; 409dba7b0efSSimon J. Gerraty 410dba7b0efSSimon J. Gerraty nested_p = par->p; 411dba7b0efSSimon J. Gerraty atStart = nested_p == start; 412b0c40a00SSimon J. Gerraty parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, emode, inout_str); 413dba7b0efSSimon J. Gerraty /* TODO: handle errors */ 414dba7b0efSSimon J. Gerraty if (inout_str->str == var_Error) { 415dba7b0efSSimon J. Gerraty if (parseResult == VPR_ERR) { 416dba7b0efSSimon J. Gerraty /* 417dba7b0efSSimon J. Gerraty * FIXME: Even if an error occurs, there is no 418dba7b0efSSimon J. Gerraty * guarantee that it is reported. 419dba7b0efSSimon J. Gerraty * 420dba7b0efSSimon J. Gerraty * See cond-token-plain.mk $$$$$$$$. 421dba7b0efSSimon J. Gerraty */ 422b0c40a00SSimon J. Gerraty par->printedError = true; 423dba7b0efSSimon J. Gerraty } 424dba7b0efSSimon J. Gerraty /* 425dba7b0efSSimon J. Gerraty * XXX: Can there be any situation in which a returned 426b0c40a00SSimon J. Gerraty * var_Error needs to be freed? 427dba7b0efSSimon J. Gerraty */ 428dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 429dba7b0efSSimon J. Gerraty /* 430dba7b0efSSimon J. Gerraty * Even if !doEval, we still report syntax errors, which is 431dba7b0efSSimon J. Gerraty * what getting var_Error back with !doEval means. 432dba7b0efSSimon J. Gerraty */ 433dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); 434b0c40a00SSimon J. Gerraty return false; 435dba7b0efSSimon J. Gerraty } 436dba7b0efSSimon J. Gerraty par->p = nested_p; 437dba7b0efSSimon J. Gerraty 438dba7b0efSSimon J. Gerraty /* 439dba7b0efSSimon J. Gerraty * If the '$' started the string literal (which means no quotes), and 440dba7b0efSSimon J. Gerraty * the variable expression is followed by a space, looks like a 441dba7b0efSSimon J. Gerraty * comparison operator or is the end of the expression, we are done. 442dba7b0efSSimon J. Gerraty */ 443dba7b0efSSimon J. Gerraty if (atStart && is_separator(par->p[0])) 444b0c40a00SSimon J. Gerraty return false; 445dba7b0efSSimon J. Gerraty 446dba7b0efSSimon J. Gerraty Buf_AddStr(buf, inout_str->str); 447dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 448dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); /* not finished yet */ 449b0c40a00SSimon J. Gerraty return true; 450dba7b0efSSimon J. Gerraty } 451dba7b0efSSimon J. Gerraty 452dba7b0efSSimon J. Gerraty /* 453b0c40a00SSimon J. Gerraty * Parse a string from a variable expression or an optionally quoted 454b0c40a00SSimon J. Gerraty * string. This is called for the left-hand and right-hand sides of 455b0c40a00SSimon J. Gerraty * comparisons. 4563955d011SMarcel Moolenaar * 4573955d011SMarcel Moolenaar * Results: 4582c3632d1SSimon J. Gerraty * Returns the string, absent any quotes, or NULL on error. 459b0c40a00SSimon J. Gerraty * Sets out_quoted if the leaf was a quoted string literal. 4603955d011SMarcel Moolenaar */ 46106b9b3e0SSimon J. Gerraty static void 462*12904384SSimon J. Gerraty CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, 463b0c40a00SSimon J. Gerraty FStr *out_str, bool *out_quoted) 4643955d011SMarcel Moolenaar { 4653955d011SMarcel Moolenaar Buffer buf; 46606b9b3e0SSimon J. Gerraty FStr str; 467b0c40a00SSimon J. Gerraty bool quoted; 4682c3632d1SSimon J. Gerraty const char *start; 4693955d011SMarcel Moolenaar 470e2eeea75SSimon J. Gerraty Buf_Init(&buf); 47106b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 472e2eeea75SSimon J. Gerraty *out_quoted = quoted = par->p[0] == '"'; 473956e45f6SSimon J. Gerraty start = par->p; 474e2eeea75SSimon J. Gerraty if (quoted) 475956e45f6SSimon J. Gerraty par->p++; 47606b9b3e0SSimon J. Gerraty 47706b9b3e0SSimon J. Gerraty while (par->p[0] != '\0' && str.str == NULL) { 478956e45f6SSimon J. Gerraty switch (par->p[0]) { 4793955d011SMarcel Moolenaar case '\\': 480956e45f6SSimon J. Gerraty par->p++; 481956e45f6SSimon J. Gerraty if (par->p[0] != '\0') { 482956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 483956e45f6SSimon J. Gerraty par->p++; 4843955d011SMarcel Moolenaar } 485956e45f6SSimon J. Gerraty continue; 4863955d011SMarcel Moolenaar case '"': 487956e45f6SSimon J. Gerraty par->p++; 488dba7b0efSSimon J. Gerraty if (quoted) 489dba7b0efSSimon J. Gerraty goto got_str; /* skip the closing quote */ 490dba7b0efSSimon J. Gerraty Buf_AddByte(&buf, '"'); 491956e45f6SSimon J. Gerraty continue; 492e2eeea75SSimon J. Gerraty case ')': /* see is_separator */ 4933955d011SMarcel Moolenaar case '!': 4943955d011SMarcel Moolenaar case '=': 4953955d011SMarcel Moolenaar case '>': 4963955d011SMarcel Moolenaar case '<': 4973955d011SMarcel Moolenaar case ' ': 4983955d011SMarcel Moolenaar case '\t': 499e2eeea75SSimon J. Gerraty if (!quoted) 5003955d011SMarcel Moolenaar goto got_str; 501956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 502956e45f6SSimon J. Gerraty par->p++; 503956e45f6SSimon J. Gerraty continue; 5043955d011SMarcel Moolenaar case '$': 505dba7b0efSSimon J. Gerraty if (!CondParser_StringExpr(par, 506dba7b0efSSimon J. Gerraty start, doEval, quoted, &buf, &str)) 5073955d011SMarcel Moolenaar goto cleanup; 508956e45f6SSimon J. Gerraty continue; 5093955d011SMarcel Moolenaar default: 510*12904384SSimon J. Gerraty if (!unquotedOK && !quoted && *start != '$' && 51106b9b3e0SSimon J. Gerraty !ch_isdigit(*start)) { 51206b9b3e0SSimon J. Gerraty /* 51306b9b3e0SSimon J. Gerraty * The left-hand side must be quoted, 514*12904384SSimon J. Gerraty * a variable expression or a number. 51506b9b3e0SSimon J. Gerraty */ 51606b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 51728a6bc81SSimon J. Gerraty goto cleanup; 51828a6bc81SSimon J. Gerraty } 519956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 520956e45f6SSimon J. Gerraty par->p++; 521956e45f6SSimon J. Gerraty continue; 5223955d011SMarcel Moolenaar } 5233955d011SMarcel Moolenaar } 5243955d011SMarcel Moolenaar got_str: 525dba7b0efSSimon J. Gerraty str = FStr_InitOwn(buf.data); 526*12904384SSimon J. Gerraty buf.data = NULL; 5273955d011SMarcel Moolenaar cleanup: 528*12904384SSimon J. Gerraty Buf_Done(&buf); 52906b9b3e0SSimon J. Gerraty *out_str = str; 5303955d011SMarcel Moolenaar } 5312c3632d1SSimon J. Gerraty 532b0c40a00SSimon J. Gerraty static bool 533*12904384SSimon J. Gerraty EvalBare(const CondParser *par, const char *arg) 534e2eeea75SSimon J. Gerraty { 535*12904384SSimon J. Gerraty bool res = par->evalBare(arg); 536dba7b0efSSimon J. Gerraty return par->negateEvalBare ? !res : res; 537e2eeea75SSimon J. Gerraty } 5382c3632d1SSimon J. Gerraty 53906b9b3e0SSimon J. Gerraty /* 54006b9b3e0SSimon J. Gerraty * Evaluate a "comparison without operator", such as in ".if ${VAR}" or 54106b9b3e0SSimon J. Gerraty * ".if 0". 54206b9b3e0SSimon J. Gerraty */ 543b0c40a00SSimon J. Gerraty static bool 544b0c40a00SSimon J. Gerraty EvalNotEmpty(CondParser *par, const char *value, bool quoted) 5453955d011SMarcel Moolenaar { 546e2eeea75SSimon J. Gerraty double num; 547956e45f6SSimon J. Gerraty 548e2eeea75SSimon J. Gerraty /* For .ifxxx "...", check for non-empty string. */ 549e2eeea75SSimon J. Gerraty if (quoted) 550e2eeea75SSimon J. Gerraty return value[0] != '\0'; 551956e45f6SSimon J. Gerraty 552e2eeea75SSimon J. Gerraty /* For .ifxxx <number>, compare against zero */ 553e2eeea75SSimon J. Gerraty if (TryParseNumber(value, &num)) 554e2eeea75SSimon J. Gerraty return num != 0.0; 555956e45f6SSimon J. Gerraty 556e2eeea75SSimon J. Gerraty /* For .if ${...}, check for non-empty string. This is different from 557e2eeea75SSimon J. Gerraty * the evaluation function from that .if variant, which would test 558e2eeea75SSimon J. Gerraty * whether a variable of the given name were defined. */ 559*12904384SSimon J. Gerraty /* 560*12904384SSimon J. Gerraty * XXX: Whitespace should count as empty, just as in 561*12904384SSimon J. Gerraty * CondParser_FuncCallEmpty. 562*12904384SSimon J. Gerraty */ 563dba7b0efSSimon J. Gerraty if (par->plain) 564e2eeea75SSimon J. Gerraty return value[0] != '\0'; 565956e45f6SSimon J. Gerraty 566*12904384SSimon J. Gerraty return EvalBare(par, value); 567956e45f6SSimon J. Gerraty } 568956e45f6SSimon J. Gerraty 569956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ 570b0c40a00SSimon J. Gerraty static bool 571dba7b0efSSimon J. Gerraty EvalCompareNum(double lhs, ComparisonOp op, double rhs) 572956e45f6SSimon J. Gerraty { 573dba7b0efSSimon J. Gerraty DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, opname[op]); 574956e45f6SSimon J. Gerraty 575dba7b0efSSimon J. Gerraty switch (op) { 576dba7b0efSSimon J. Gerraty case LT: 577dba7b0efSSimon J. Gerraty return lhs < rhs; 578dba7b0efSSimon J. Gerraty case LE: 579dba7b0efSSimon J. Gerraty return lhs <= rhs; 580dba7b0efSSimon J. Gerraty case GT: 581dba7b0efSSimon J. Gerraty return lhs > rhs; 582dba7b0efSSimon J. Gerraty case GE: 583dba7b0efSSimon J. Gerraty return lhs >= rhs; 584dba7b0efSSimon J. Gerraty case NE: 585dba7b0efSSimon J. Gerraty return lhs != rhs; 586dba7b0efSSimon J. Gerraty default: 587dba7b0efSSimon J. Gerraty return lhs == rhs; 588956e45f6SSimon J. Gerraty } 589956e45f6SSimon J. Gerraty } 590956e45f6SSimon J. Gerraty 591956e45f6SSimon J. Gerraty static Token 592dba7b0efSSimon J. Gerraty EvalCompareStr(CondParser *par, const char *lhs, 593dba7b0efSSimon J. Gerraty ComparisonOp op, const char *rhs) 594956e45f6SSimon J. Gerraty { 595dba7b0efSSimon J. Gerraty if (op != EQ && op != NE) { 596dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 597dba7b0efSSimon J. Gerraty "String comparison operator must be either == or !="); 598b0c40a00SSimon J. Gerraty par->printedError = true; 599956e45f6SSimon J. Gerraty return TOK_ERROR; 600956e45f6SSimon J. Gerraty } 601956e45f6SSimon J. Gerraty 602dba7b0efSSimon J. Gerraty DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", 603dba7b0efSSimon J. Gerraty lhs, rhs, opname[op]); 604dba7b0efSSimon J. Gerraty return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); 605956e45f6SSimon J. Gerraty } 606956e45f6SSimon J. Gerraty 607956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */ 608956e45f6SSimon J. Gerraty static Token 609b0c40a00SSimon J. Gerraty EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, 610b0c40a00SSimon J. Gerraty ComparisonOp op, const char *rhs, bool rhsQuoted) 611956e45f6SSimon J. Gerraty { 6123955d011SMarcel Moolenaar double left, right; 6133955d011SMarcel Moolenaar 614956e45f6SSimon J. Gerraty if (!rhsQuoted && !lhsQuoted) 615956e45f6SSimon J. Gerraty if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) 616dba7b0efSSimon J. Gerraty return ToToken(EvalCompareNum(left, op, right)); 617956e45f6SSimon J. Gerraty 618dba7b0efSSimon J. Gerraty return EvalCompareStr(par, lhs, op, rhs); 619dba7b0efSSimon J. Gerraty } 620dba7b0efSSimon J. Gerraty 621b0c40a00SSimon J. Gerraty static bool 622dba7b0efSSimon J. Gerraty CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) 623dba7b0efSSimon J. Gerraty { 624dba7b0efSSimon J. Gerraty const char *p = par->p; 625dba7b0efSSimon J. Gerraty 626dba7b0efSSimon J. Gerraty if (p[0] == '<' && p[1] == '=') { 627dba7b0efSSimon J. Gerraty *out_op = LE; 628dba7b0efSSimon J. Gerraty goto length_2; 629dba7b0efSSimon J. Gerraty } else if (p[0] == '<') { 630dba7b0efSSimon J. Gerraty *out_op = LT; 631dba7b0efSSimon J. Gerraty goto length_1; 632dba7b0efSSimon J. Gerraty } else if (p[0] == '>' && p[1] == '=') { 633dba7b0efSSimon J. Gerraty *out_op = GE; 634dba7b0efSSimon J. Gerraty goto length_2; 635dba7b0efSSimon J. Gerraty } else if (p[0] == '>') { 636dba7b0efSSimon J. Gerraty *out_op = GT; 637dba7b0efSSimon J. Gerraty goto length_1; 638dba7b0efSSimon J. Gerraty } else if (p[0] == '=' && p[1] == '=') { 639dba7b0efSSimon J. Gerraty *out_op = EQ; 640dba7b0efSSimon J. Gerraty goto length_2; 641dba7b0efSSimon J. Gerraty } else if (p[0] == '!' && p[1] == '=') { 642dba7b0efSSimon J. Gerraty *out_op = NE; 643dba7b0efSSimon J. Gerraty goto length_2; 644dba7b0efSSimon J. Gerraty } 645b0c40a00SSimon J. Gerraty return false; 646dba7b0efSSimon J. Gerraty 647dba7b0efSSimon J. Gerraty length_2: 648dba7b0efSSimon J. Gerraty par->p = p + 2; 649b0c40a00SSimon J. Gerraty return true; 650dba7b0efSSimon J. Gerraty length_1: 651dba7b0efSSimon J. Gerraty par->p = p + 1; 652b0c40a00SSimon J. Gerraty return true; 653956e45f6SSimon J. Gerraty } 654956e45f6SSimon J. Gerraty 65506b9b3e0SSimon J. Gerraty /* 65606b9b3e0SSimon J. Gerraty * Parse a comparison condition such as: 657956e45f6SSimon J. Gerraty * 658956e45f6SSimon J. Gerraty * 0 659956e45f6SSimon J. Gerraty * ${VAR:Mpattern} 660956e45f6SSimon J. Gerraty * ${VAR} == value 661956e45f6SSimon J. Gerraty * ${VAR:U0} < 12345 662956e45f6SSimon J. Gerraty */ 663956e45f6SSimon J. Gerraty static Token 664b0c40a00SSimon J. Gerraty CondParser_Comparison(CondParser *par, bool doEval) 665956e45f6SSimon J. Gerraty { 666956e45f6SSimon J. Gerraty Token t = TOK_ERROR; 66706b9b3e0SSimon J. Gerraty FStr lhs, rhs; 668dba7b0efSSimon J. Gerraty ComparisonOp op; 669b0c40a00SSimon J. Gerraty bool lhsQuoted, rhsQuoted; 670956e45f6SSimon J. Gerraty 671*12904384SSimon J. Gerraty CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhs, &lhsQuoted); 67206b9b3e0SSimon J. Gerraty if (lhs.str == NULL) 673e2eeea75SSimon J. Gerraty goto done_lhs; 6743955d011SMarcel Moolenaar 675956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6763955d011SMarcel Moolenaar 677dba7b0efSSimon J. Gerraty if (!CondParser_ComparisonOp(par, &op)) { 678e2eeea75SSimon J. Gerraty /* Unknown operator, compare against an empty string or 0. */ 67906b9b3e0SSimon J. Gerraty t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); 680e2eeea75SSimon J. Gerraty goto done_lhs; 6813955d011SMarcel Moolenaar } 6823955d011SMarcel Moolenaar 683956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6843955d011SMarcel Moolenaar 685956e45f6SSimon J. Gerraty if (par->p[0] == '\0') { 686dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 687*12904384SSimon J. Gerraty "Missing right-hand side of operator '%s'", opname[op]); 688b0c40a00SSimon J. Gerraty par->printedError = true; 689e2eeea75SSimon J. Gerraty goto done_lhs; 6903955d011SMarcel Moolenaar } 6913955d011SMarcel Moolenaar 692*12904384SSimon J. Gerraty CondParser_Leaf(par, doEval, true, &rhs, &rhsQuoted); 69306b9b3e0SSimon J. Gerraty if (rhs.str == NULL) 694e2eeea75SSimon J. Gerraty goto done_rhs; 6953955d011SMarcel Moolenaar 6963841c287SSimon J. Gerraty if (!doEval) { 6973841c287SSimon J. Gerraty t = TOK_FALSE; 698e2eeea75SSimon J. Gerraty goto done_rhs; 6993841c287SSimon J. Gerraty } 7003841c287SSimon J. Gerraty 701dba7b0efSSimon J. Gerraty t = EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); 7023955d011SMarcel Moolenaar 703e2eeea75SSimon J. Gerraty done_rhs: 70406b9b3e0SSimon J. Gerraty FStr_Done(&rhs); 705e2eeea75SSimon J. Gerraty done_lhs: 70606b9b3e0SSimon J. Gerraty FStr_Done(&lhs); 7073955d011SMarcel Moolenaar return t; 7083955d011SMarcel Moolenaar } 7093955d011SMarcel Moolenaar 71006b9b3e0SSimon J. Gerraty /* 71106b9b3e0SSimon J. Gerraty * The argument to empty() is a variable name, optionally followed by 71206b9b3e0SSimon J. Gerraty * variable modifiers. 71306b9b3e0SSimon J. Gerraty */ 714*12904384SSimon J. Gerraty static bool 715*12904384SSimon J. Gerraty CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) 7163955d011SMarcel Moolenaar { 717*12904384SSimon J. Gerraty const char *cp = par->p; 718*12904384SSimon J. Gerraty Token tok; 71906b9b3e0SSimon J. Gerraty FStr val; 7203955d011SMarcel Moolenaar 721*12904384SSimon J. Gerraty if (!is_token(cp, "empty", 5)) 722*12904384SSimon J. Gerraty return false; 723*12904384SSimon J. Gerraty cp += 5; 7243955d011SMarcel Moolenaar 725*12904384SSimon J. Gerraty cpp_skip_whitespace(&cp); 726*12904384SSimon J. Gerraty if (*cp != '(') 727*12904384SSimon J. Gerraty return false; 728*12904384SSimon J. Gerraty 729*12904384SSimon J. Gerraty cp--; /* Make cp[1] point to the '('. */ 730*12904384SSimon J. Gerraty (void)Var_Parse(&cp, SCOPE_CMDLINE, 731b0c40a00SSimon J. Gerraty doEval ? VARE_WANTRES : VARE_PARSE_ONLY, &val); 732956e45f6SSimon J. Gerraty /* TODO: handle errors */ 7333955d011SMarcel Moolenaar 734*12904384SSimon J. Gerraty if (val.str == var_Error) 735*12904384SSimon J. Gerraty tok = TOK_ERROR; 736*12904384SSimon J. Gerraty else { 73706b9b3e0SSimon J. Gerraty cpp_skip_whitespace(&val.str); 738*12904384SSimon J. Gerraty tok = val.str[0] != '\0' && doEval ? TOK_FALSE : TOK_TRUE; 7393955d011SMarcel Moolenaar } 7403955d011SMarcel Moolenaar 741*12904384SSimon J. Gerraty FStr_Done(&val); 742*12904384SSimon J. Gerraty *out_token = tok; 743*12904384SSimon J. Gerraty par->p = cp; 744*12904384SSimon J. Gerraty return true; 7453955d011SMarcel Moolenaar } 7463955d011SMarcel Moolenaar 747b0c40a00SSimon J. Gerraty /* Parse a function call expression, such as 'defined(${file})'. */ 748b0c40a00SSimon J. Gerraty static bool 749b0c40a00SSimon J. Gerraty CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) 7503955d011SMarcel Moolenaar { 7513955d011SMarcel Moolenaar static const struct fn_def { 752*12904384SSimon J. Gerraty const char fn_name[9]; 753*12904384SSimon J. Gerraty unsigned char fn_name_len; 754*12904384SSimon J. Gerraty bool (*fn_eval)(const char *); 755e2eeea75SSimon J. Gerraty } fns[] = { 756*12904384SSimon J. Gerraty { "defined", 7, FuncDefined }, 757*12904384SSimon J. Gerraty { "make", 4, FuncMake }, 758*12904384SSimon J. Gerraty { "exists", 6, FuncExists }, 759*12904384SSimon J. Gerraty { "target", 6, FuncTarget }, 760*12904384SSimon J. Gerraty { "commands", 8, FuncCommands } 7613955d011SMarcel Moolenaar }; 762e2eeea75SSimon J. Gerraty const struct fn_def *fn; 763e2eeea75SSimon J. Gerraty char *arg = NULL; 764e2eeea75SSimon J. Gerraty size_t arglen; 765e2eeea75SSimon J. Gerraty const char *cp = par->p; 766*12904384SSimon J. Gerraty const struct fn_def *last_fn = fns + sizeof fns / sizeof fns[0] - 1; 767e2eeea75SSimon J. Gerraty 768*12904384SSimon J. Gerraty for (fn = fns; !is_token(cp, fn->fn_name, fn->fn_name_len); fn++) 769*12904384SSimon J. Gerraty if (fn == last_fn) 770*12904384SSimon J. Gerraty return false; 771e2eeea75SSimon J. Gerraty 772e2eeea75SSimon J. Gerraty cp += fn->fn_name_len; 773e2eeea75SSimon J. Gerraty cpp_skip_whitespace(&cp); 774e2eeea75SSimon J. Gerraty if (*cp != '(') 775*12904384SSimon J. Gerraty return false; 776e2eeea75SSimon J. Gerraty 777*12904384SSimon J. Gerraty arglen = ParseWord(par, &cp, doEval, fn->fn_name, &arg); 778*12904384SSimon J. Gerraty *out_token = ToToken(arglen != 0 && (!doEval || fn->fn_eval(arg))); 779e2eeea75SSimon J. Gerraty 780e2eeea75SSimon J. Gerraty free(arg); 781e2eeea75SSimon J. Gerraty par->p = cp; 782b0c40a00SSimon J. Gerraty return true; 783e2eeea75SSimon J. Gerraty } 784e2eeea75SSimon J. Gerraty 78506b9b3e0SSimon J. Gerraty /* 786*12904384SSimon J. Gerraty * Parse a comparison that neither starts with '"' nor '$', such as the 787*12904384SSimon J. Gerraty * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without 788b0c40a00SSimon J. Gerraty * operator, which is a number, a variable expression or a string literal. 789*12904384SSimon J. Gerraty * 790*12904384SSimon J. Gerraty * TODO: Can this be merged into CondParser_Comparison? 79106b9b3e0SSimon J. Gerraty */ 792e2eeea75SSimon J. Gerraty static Token 793b0c40a00SSimon J. Gerraty CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) 794e2eeea75SSimon J. Gerraty { 7953955d011SMarcel Moolenaar Token t; 7963955d011SMarcel Moolenaar char *arg = NULL; 79706b9b3e0SSimon J. Gerraty const char *cp; 7982c3632d1SSimon J. Gerraty const char *cp1; 7993955d011SMarcel Moolenaar 8003955d011SMarcel Moolenaar /* Push anything numeric through the compare expression */ 801956e45f6SSimon J. Gerraty cp = par->p; 802e2eeea75SSimon J. Gerraty if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') 803956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 8043955d011SMarcel Moolenaar 8053955d011SMarcel Moolenaar /* 8063955d011SMarcel Moolenaar * Most likely we have a naked token to apply the default function to. 8073955d011SMarcel Moolenaar * However ".if a == b" gets here when the "a" is unquoted and doesn't 8083955d011SMarcel Moolenaar * start with a '$'. This surprises people. 80906b9b3e0SSimon J. Gerraty * If what follows the function argument is a '=' or '!' then the 81006b9b3e0SSimon J. Gerraty * syntax would be invalid if we did "defined(a)" - so instead treat 81106b9b3e0SSimon J. Gerraty * as an expression. 8123955d011SMarcel Moolenaar */ 813b0c40a00SSimon J. Gerraty /* 814b0c40a00SSimon J. Gerraty * XXX: Is it possible to have a variable expression evaluated twice 815b0c40a00SSimon J. Gerraty * at this point? 816b0c40a00SSimon J. Gerraty */ 817*12904384SSimon J. Gerraty (void)ParseWord(par, &cp, doEval, NULL, &arg); 818956e45f6SSimon J. Gerraty cp1 = cp; 819956e45f6SSimon J. Gerraty cpp_skip_whitespace(&cp1); 820b0c40a00SSimon J. Gerraty if (*cp1 == '=' || *cp1 == '!' || *cp1 == '<' || *cp1 == '>') 821956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 822956e45f6SSimon J. Gerraty par->p = cp; 8233955d011SMarcel Moolenaar 8243955d011SMarcel Moolenaar /* 8253955d011SMarcel Moolenaar * Evaluate the argument using the default function. 826956e45f6SSimon J. Gerraty * This path always treats .if as .ifdef. To get here, the character 8273955d011SMarcel Moolenaar * after .if must have been taken literally, so the argument cannot 8283955d011SMarcel Moolenaar * be empty - even if it contained a variable expansion. 8293955d011SMarcel Moolenaar */ 830*12904384SSimon J. Gerraty t = ToToken(!doEval || EvalBare(par, arg)); 8313955d011SMarcel Moolenaar free(arg); 8323955d011SMarcel Moolenaar return t; 8333955d011SMarcel Moolenaar } 8343955d011SMarcel Moolenaar 835956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */ 8363955d011SMarcel Moolenaar static Token 837b0c40a00SSimon J. Gerraty CondParser_Token(CondParser *par, bool doEval) 8383955d011SMarcel Moolenaar { 8393955d011SMarcel Moolenaar Token t; 8403955d011SMarcel Moolenaar 841956e45f6SSimon J. Gerraty t = par->curr; 8423955d011SMarcel Moolenaar if (t != TOK_NONE) { 843956e45f6SSimon J. Gerraty par->curr = TOK_NONE; 8443955d011SMarcel Moolenaar return t; 8453955d011SMarcel Moolenaar } 8463955d011SMarcel Moolenaar 847e2eeea75SSimon J. Gerraty cpp_skip_hspace(&par->p); 8483955d011SMarcel Moolenaar 849956e45f6SSimon J. Gerraty switch (par->p[0]) { 8503955d011SMarcel Moolenaar 8513955d011SMarcel Moolenaar case '(': 852956e45f6SSimon J. Gerraty par->p++; 8533955d011SMarcel Moolenaar return TOK_LPAREN; 8543955d011SMarcel Moolenaar 8553955d011SMarcel Moolenaar case ')': 856956e45f6SSimon J. Gerraty par->p++; 8573955d011SMarcel Moolenaar return TOK_RPAREN; 8583955d011SMarcel Moolenaar 8593955d011SMarcel Moolenaar case '|': 860956e45f6SSimon J. Gerraty par->p++; 861e2eeea75SSimon J. Gerraty if (par->p[0] == '|') 862956e45f6SSimon J. Gerraty par->p++; 86306b9b3e0SSimon J. Gerraty else if (opts.strict) { 864e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '|'"); 865b0c40a00SSimon J. Gerraty par->printedError = true; 866e2eeea75SSimon J. Gerraty return TOK_ERROR; 8673955d011SMarcel Moolenaar } 8683955d011SMarcel Moolenaar return TOK_OR; 8693955d011SMarcel Moolenaar 8703955d011SMarcel Moolenaar case '&': 871956e45f6SSimon J. Gerraty par->p++; 872e2eeea75SSimon J. Gerraty if (par->p[0] == '&') 873956e45f6SSimon J. Gerraty par->p++; 87406b9b3e0SSimon J. Gerraty else if (opts.strict) { 875e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '&'"); 876b0c40a00SSimon J. Gerraty par->printedError = true; 877e2eeea75SSimon J. Gerraty return TOK_ERROR; 8783955d011SMarcel Moolenaar } 8793955d011SMarcel Moolenaar return TOK_AND; 8803955d011SMarcel Moolenaar 8813955d011SMarcel Moolenaar case '!': 882956e45f6SSimon J. Gerraty par->p++; 8833955d011SMarcel Moolenaar return TOK_NOT; 8843955d011SMarcel Moolenaar 885e2eeea75SSimon J. Gerraty case '#': /* XXX: see unit-tests/cond-token-plain.mk */ 886e2eeea75SSimon J. Gerraty case '\n': /* XXX: why should this end the condition? */ 887e2eeea75SSimon J. Gerraty /* Probably obsolete now, from 1993-03-21. */ 8883955d011SMarcel Moolenaar case '\0': 8893955d011SMarcel Moolenaar return TOK_EOF; 8903955d011SMarcel Moolenaar 8913955d011SMarcel Moolenaar case '"': 8923955d011SMarcel Moolenaar case '$': 893956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 8943955d011SMarcel Moolenaar 8953955d011SMarcel Moolenaar default: 896*12904384SSimon J. Gerraty if (CondParser_FuncCallEmpty(par, doEval, &t)) 897*12904384SSimon J. Gerraty return t; 898b0c40a00SSimon J. Gerraty if (CondParser_FuncCall(par, doEval, &t)) 899b0c40a00SSimon J. Gerraty return t; 900b0c40a00SSimon J. Gerraty return CondParser_ComparisonOrLeaf(par, doEval); 9013955d011SMarcel Moolenaar } 9023955d011SMarcel Moolenaar } 9033955d011SMarcel Moolenaar 904*12904384SSimon J. Gerraty /* Skip the next token if it equals t. */ 905*12904384SSimon J. Gerraty static bool 906*12904384SSimon J. Gerraty CondParser_Skip(CondParser *par, Token t) 907*12904384SSimon J. Gerraty { 908*12904384SSimon J. Gerraty Token actual; 909*12904384SSimon J. Gerraty 910*12904384SSimon J. Gerraty actual = CondParser_Token(par, false); 911*12904384SSimon J. Gerraty if (actual == t) 912*12904384SSimon J. Gerraty return true; 913*12904384SSimon J. Gerraty 914*12904384SSimon J. Gerraty assert(par->curr == TOK_NONE); 915*12904384SSimon J. Gerraty assert(actual != TOK_NONE); 916*12904384SSimon J. Gerraty par->curr = actual; 917*12904384SSimon J. Gerraty return false; 918*12904384SSimon J. Gerraty } 919*12904384SSimon J. Gerraty 92006b9b3e0SSimon J. Gerraty /* 921dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 922dba7b0efSSimon J. Gerraty * Term -> '!' Term 923dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 924dba7b0efSSimon J. Gerraty * Term -> Leaf 9253955d011SMarcel Moolenaar */ 926dba7b0efSSimon J. Gerraty static CondResult 927b0c40a00SSimon J. Gerraty CondParser_Term(CondParser *par, bool doEval) 9283955d011SMarcel Moolenaar { 929dba7b0efSSimon J. Gerraty CondResult res; 9303955d011SMarcel Moolenaar Token t; 9313955d011SMarcel Moolenaar 932956e45f6SSimon J. Gerraty t = CondParser_Token(par, doEval); 933dba7b0efSSimon J. Gerraty if (t == TOK_TRUE) 934dba7b0efSSimon J. Gerraty return CR_TRUE; 935dba7b0efSSimon J. Gerraty if (t == TOK_FALSE) 936dba7b0efSSimon J. Gerraty return CR_FALSE; 9373955d011SMarcel Moolenaar 938dba7b0efSSimon J. Gerraty if (t == TOK_LPAREN) { 939dba7b0efSSimon J. Gerraty res = CondParser_Or(par, doEval); 940dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 941dba7b0efSSimon J. Gerraty return CR_ERROR; 942dba7b0efSSimon J. Gerraty if (CondParser_Token(par, doEval) != TOK_RPAREN) 943dba7b0efSSimon J. Gerraty return CR_ERROR; 944dba7b0efSSimon J. Gerraty return res; 9453955d011SMarcel Moolenaar } 946dba7b0efSSimon J. Gerraty 947dba7b0efSSimon J. Gerraty if (t == TOK_NOT) { 948dba7b0efSSimon J. Gerraty res = CondParser_Term(par, doEval); 949dba7b0efSSimon J. Gerraty if (res == CR_TRUE) 950dba7b0efSSimon J. Gerraty res = CR_FALSE; 951dba7b0efSSimon J. Gerraty else if (res == CR_FALSE) 952dba7b0efSSimon J. Gerraty res = CR_TRUE; 953dba7b0efSSimon J. Gerraty return res; 9543955d011SMarcel Moolenaar } 955dba7b0efSSimon J. Gerraty 956dba7b0efSSimon J. Gerraty return CR_ERROR; 9573955d011SMarcel Moolenaar } 9582c3632d1SSimon J. Gerraty 95906b9b3e0SSimon J. Gerraty /* 960*12904384SSimon J. Gerraty * And -> Term ('&&' Term)* 9613955d011SMarcel Moolenaar */ 962dba7b0efSSimon J. Gerraty static CondResult 963b0c40a00SSimon J. Gerraty CondParser_And(CondParser *par, bool doEval) 9643955d011SMarcel Moolenaar { 965*12904384SSimon J. Gerraty CondResult res, rhs; 9663955d011SMarcel Moolenaar 967*12904384SSimon J. Gerraty res = CR_TRUE; 968*12904384SSimon J. Gerraty do { 969*12904384SSimon J. Gerraty if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR) 970dba7b0efSSimon J. Gerraty return CR_ERROR; 971*12904384SSimon J. Gerraty if (rhs == CR_FALSE) { 972*12904384SSimon J. Gerraty res = CR_FALSE; 973*12904384SSimon J. Gerraty doEval = false; 9743955d011SMarcel Moolenaar } 975*12904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_AND)); 976dba7b0efSSimon J. Gerraty 977dba7b0efSSimon J. Gerraty return res; 9783955d011SMarcel Moolenaar } 9792c3632d1SSimon J. Gerraty 98006b9b3e0SSimon J. Gerraty /* 981*12904384SSimon J. Gerraty * Or -> And ('||' And)* 9823955d011SMarcel Moolenaar */ 983dba7b0efSSimon J. Gerraty static CondResult 984b0c40a00SSimon J. Gerraty CondParser_Or(CondParser *par, bool doEval) 9853955d011SMarcel Moolenaar { 986*12904384SSimon J. Gerraty CondResult res, rhs; 9873955d011SMarcel Moolenaar 988*12904384SSimon J. Gerraty res = CR_FALSE; 989*12904384SSimon J. Gerraty do { 990*12904384SSimon J. Gerraty if ((rhs = CondParser_And(par, doEval)) == CR_ERROR) 991dba7b0efSSimon J. Gerraty return CR_ERROR; 992*12904384SSimon J. Gerraty if (rhs == CR_TRUE) { 993*12904384SSimon J. Gerraty res = CR_TRUE; 994*12904384SSimon J. Gerraty doEval = false; 9953955d011SMarcel Moolenaar } 996*12904384SSimon J. Gerraty } while (CondParser_Skip(par, TOK_OR)); 997dba7b0efSSimon J. Gerraty 998dba7b0efSSimon J. Gerraty return res; 9993955d011SMarcel Moolenaar } 10003955d011SMarcel Moolenaar 10012c3632d1SSimon J. Gerraty static CondEvalResult 1002b0c40a00SSimon J. Gerraty CondParser_Eval(CondParser *par, bool *out_value) 10032c3632d1SSimon J. Gerraty { 1004dba7b0efSSimon J. Gerraty CondResult res; 10052c3632d1SSimon J. Gerraty 1006956e45f6SSimon J. Gerraty DEBUG1(COND, "CondParser_Eval: %s\n", par->p); 10072c3632d1SSimon J. Gerraty 1008b0c40a00SSimon J. Gerraty res = CondParser_Or(par, true); 1009dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 10102c3632d1SSimon J. Gerraty return COND_INVALID; 1011956e45f6SSimon J. Gerraty 1012b0c40a00SSimon J. Gerraty if (CondParser_Token(par, false) != TOK_EOF) 1013956e45f6SSimon J. Gerraty return COND_INVALID; 1014956e45f6SSimon J. Gerraty 1015dba7b0efSSimon J. Gerraty *out_value = res == CR_TRUE; 1016956e45f6SSimon J. Gerraty return COND_PARSE; 10172c3632d1SSimon J. Gerraty } 10182c3632d1SSimon J. Gerraty 101906b9b3e0SSimon J. Gerraty /* 102006b9b3e0SSimon J. Gerraty * Evaluate the condition, including any side effects from the variable 1021956e45f6SSimon J. Gerraty * expressions in the condition. The condition consists of &&, ||, !, 1022956e45f6SSimon J. Gerraty * function(arg), comparisons and parenthetical groupings thereof. 10233955d011SMarcel Moolenaar * 10243955d011SMarcel Moolenaar * Results: 10253955d011SMarcel Moolenaar * COND_PARSE if the condition was valid grammatically 10263955d011SMarcel Moolenaar * COND_INVALID if not a valid conditional. 10273955d011SMarcel Moolenaar * 1028*12904384SSimon J. Gerraty * *out_value is set to the boolean value of the condition 10293955d011SMarcel Moolenaar */ 1030956e45f6SSimon J. Gerraty static CondEvalResult 1031b0c40a00SSimon J. Gerraty CondEvalExpression(const char *cond, bool *out_value, bool plain, 1032*12904384SSimon J. Gerraty bool (*evalBare)(const char *), bool negate, 1033*12904384SSimon J. Gerraty bool eprint, bool leftUnquotedOK) 10343955d011SMarcel Moolenaar { 1035956e45f6SSimon J. Gerraty CondParser par; 1036e2eeea75SSimon J. Gerraty CondEvalResult rval; 10373955d011SMarcel Moolenaar 1038e2eeea75SSimon J. Gerraty cpp_skip_hspace(&cond); 10393955d011SMarcel Moolenaar 1040dba7b0efSSimon J. Gerraty par.plain = plain; 1041dba7b0efSSimon J. Gerraty par.evalBare = evalBare; 1042dba7b0efSSimon J. Gerraty par.negateEvalBare = negate; 1043*12904384SSimon J. Gerraty par.leftUnquotedOK = leftUnquotedOK; 1044956e45f6SSimon J. Gerraty par.p = cond; 1045956e45f6SSimon J. Gerraty par.curr = TOK_NONE; 1046b0c40a00SSimon J. Gerraty par.printedError = false; 10473955d011SMarcel Moolenaar 1048dba7b0efSSimon J. Gerraty rval = CondParser_Eval(&par, out_value); 10493955d011SMarcel Moolenaar 1050956e45f6SSimon J. Gerraty if (rval == COND_INVALID && eprint && !par.printedError) 1051956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); 10523955d011SMarcel Moolenaar 10533955d011SMarcel Moolenaar return rval; 10543955d011SMarcel Moolenaar } 10553955d011SMarcel Moolenaar 105606b9b3e0SSimon J. Gerraty /* 105706b9b3e0SSimon J. Gerraty * Evaluate a condition in a :? modifier, such as 105806b9b3e0SSimon J. Gerraty * ${"${VAR}" == value:?yes:no}. 105906b9b3e0SSimon J. Gerraty */ 1060956e45f6SSimon J. Gerraty CondEvalResult 1061b0c40a00SSimon J. Gerraty Cond_EvalCondition(const char *cond, bool *out_value) 1062956e45f6SSimon J. Gerraty { 1063b0c40a00SSimon J. Gerraty return CondEvalExpression(cond, out_value, true, 1064*12904384SSimon J. Gerraty FuncDefined, false, false, true); 1065956e45f6SSimon J. Gerraty } 10663955d011SMarcel Moolenaar 1067b0c40a00SSimon J. Gerraty static bool 106806b9b3e0SSimon J. Gerraty IsEndif(const char *p) 106906b9b3e0SSimon J. Gerraty { 107006b9b3e0SSimon J. Gerraty return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && 107106b9b3e0SSimon J. Gerraty p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); 107206b9b3e0SSimon J. Gerraty } 107306b9b3e0SSimon J. Gerraty 1074b0c40a00SSimon J. Gerraty static bool 1075b0c40a00SSimon J. Gerraty DetermineKindOfConditional(const char **pp, bool *out_plain, 1076*12904384SSimon J. Gerraty bool (**out_evalBare)(const char *), 1077b0c40a00SSimon J. Gerraty bool *out_negate) 1078dba7b0efSSimon J. Gerraty { 1079dba7b0efSSimon J. Gerraty const char *p = *pp; 1080dba7b0efSSimon J. Gerraty 1081dba7b0efSSimon J. Gerraty p += 2; 1082b0c40a00SSimon J. Gerraty *out_plain = false; 1083dba7b0efSSimon J. Gerraty *out_evalBare = FuncDefined; 1084b0c40a00SSimon J. Gerraty *out_negate = false; 1085dba7b0efSSimon J. Gerraty if (*p == 'n') { 1086dba7b0efSSimon J. Gerraty p++; 1087b0c40a00SSimon J. Gerraty *out_negate = true; 1088dba7b0efSSimon J. Gerraty } 1089dba7b0efSSimon J. Gerraty if (is_token(p, "def", 3)) { /* .ifdef and .ifndef */ 1090dba7b0efSSimon J. Gerraty p += 3; 1091dba7b0efSSimon J. Gerraty } else if (is_token(p, "make", 4)) { /* .ifmake and .ifnmake */ 1092dba7b0efSSimon J. Gerraty p += 4; 1093dba7b0efSSimon J. Gerraty *out_evalBare = FuncMake; 1094dba7b0efSSimon J. Gerraty } else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */ 1095b0c40a00SSimon J. Gerraty *out_plain = true; 1096dba7b0efSSimon J. Gerraty } else { 1097dba7b0efSSimon J. Gerraty /* 1098dba7b0efSSimon J. Gerraty * TODO: Add error message about unknown directive, 1099dba7b0efSSimon J. Gerraty * since there is no other known directive that starts 1100dba7b0efSSimon J. Gerraty * with 'el' or 'if'. 1101dba7b0efSSimon J. Gerraty * 1102dba7b0efSSimon J. Gerraty * Example: .elifx 123 1103dba7b0efSSimon J. Gerraty */ 1104b0c40a00SSimon J. Gerraty return false; 1105dba7b0efSSimon J. Gerraty } 1106dba7b0efSSimon J. Gerraty 1107dba7b0efSSimon J. Gerraty *pp = p; 1108b0c40a00SSimon J. Gerraty return true; 1109dba7b0efSSimon J. Gerraty } 1110dba7b0efSSimon J. Gerraty 111106b9b3e0SSimon J. Gerraty /* 111206b9b3e0SSimon J. Gerraty * Evaluate the conditional directive in the line, which is one of: 11133955d011SMarcel Moolenaar * 1114e2eeea75SSimon J. Gerraty * .if <cond> 1115e2eeea75SSimon J. Gerraty * .ifmake <cond> 1116e2eeea75SSimon J. Gerraty * .ifnmake <cond> 1117e2eeea75SSimon J. Gerraty * .ifdef <cond> 1118e2eeea75SSimon J. Gerraty * .ifndef <cond> 1119e2eeea75SSimon J. Gerraty * .elif <cond> 1120e2eeea75SSimon J. Gerraty * .elifmake <cond> 1121e2eeea75SSimon J. Gerraty * .elifnmake <cond> 1122e2eeea75SSimon J. Gerraty * .elifdef <cond> 1123e2eeea75SSimon J. Gerraty * .elifndef <cond> 1124e2eeea75SSimon J. Gerraty * .else 1125e2eeea75SSimon J. Gerraty * .endif 1126e2eeea75SSimon J. Gerraty * 1127e2eeea75SSimon J. Gerraty * In these directives, <cond> consists of &&, ||, !, function(arg), 1128e2eeea75SSimon J. Gerraty * comparisons, expressions, bare words, numbers and strings, and 1129e2eeea75SSimon J. Gerraty * parenthetical groupings thereof. 1130956e45f6SSimon J. Gerraty * 1131956e45f6SSimon J. Gerraty * Results: 1132e2eeea75SSimon J. Gerraty * COND_PARSE to continue parsing the lines that follow the 1133b0c40a00SSimon J. Gerraty * conditional (when <cond> evaluates to true) 1134956e45f6SSimon J. Gerraty * COND_SKIP to skip the lines after the conditional 1135b0c40a00SSimon J. Gerraty * (when <cond> evaluates to false, or when a previous 1136956e45f6SSimon J. Gerraty * branch has already been taken) 1137956e45f6SSimon J. Gerraty * COND_INVALID if the conditional was not valid, either because of 1138956e45f6SSimon J. Gerraty * a syntax error or because some variable was undefined 1139956e45f6SSimon J. Gerraty * or because the condition could not be evaluated 11403955d011SMarcel Moolenaar */ 11412c3632d1SSimon J. Gerraty CondEvalResult 114206b9b3e0SSimon J. Gerraty Cond_EvalLine(const char *line) 11433955d011SMarcel Moolenaar { 1144e2eeea75SSimon J. Gerraty typedef enum IfState { 1145e2eeea75SSimon J. Gerraty 1146b0c40a00SSimon J. Gerraty /* None of the previous <cond> evaluated to true. */ 1147e2eeea75SSimon J. Gerraty IFS_INITIAL = 0, 1148e2eeea75SSimon J. Gerraty 1149b0c40a00SSimon J. Gerraty /* The previous <cond> evaluated to true. 1150e2eeea75SSimon J. Gerraty * The lines following this condition are interpreted. */ 1151e2eeea75SSimon J. Gerraty IFS_ACTIVE = 1 << 0, 1152e2eeea75SSimon J. Gerraty 1153e2eeea75SSimon J. Gerraty /* The previous directive was an '.else'. */ 1154e2eeea75SSimon J. Gerraty IFS_SEEN_ELSE = 1 << 1, 1155e2eeea75SSimon J. Gerraty 1156b0c40a00SSimon J. Gerraty /* One of the previous <cond> evaluated to true. */ 1157e2eeea75SSimon J. Gerraty IFS_WAS_ACTIVE = 1 << 2 1158e2eeea75SSimon J. Gerraty 1159e2eeea75SSimon J. Gerraty } IfState; 1160e2eeea75SSimon J. Gerraty 1161e2eeea75SSimon J. Gerraty static enum IfState *cond_states = NULL; 1162e2eeea75SSimon J. Gerraty static unsigned int cond_states_cap = 128; 11633955d011SMarcel Moolenaar 1164b0c40a00SSimon J. Gerraty bool plain; 1165*12904384SSimon J. Gerraty bool (*evalBare)(const char *); 1166b0c40a00SSimon J. Gerraty bool negate; 1167b0c40a00SSimon J. Gerraty bool isElif; 1168b0c40a00SSimon J. Gerraty bool value; 1169e2eeea75SSimon J. Gerraty IfState state; 1170e2eeea75SSimon J. Gerraty const char *p = line; 11713955d011SMarcel Moolenaar 1172e2eeea75SSimon J. Gerraty if (cond_states == NULL) { 117306b9b3e0SSimon J. Gerraty cond_states = bmake_malloc( 117406b9b3e0SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 1175e2eeea75SSimon J. Gerraty cond_states[0] = IFS_ACTIVE; 117659a02420SSimon J. Gerraty } 11773955d011SMarcel Moolenaar 1178e2eeea75SSimon J. Gerraty p++; /* skip the leading '.' */ 1179e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 1180e2eeea75SSimon J. Gerraty 118106b9b3e0SSimon J. Gerraty if (IsEndif(p)) { /* It is an '.endif'. */ 118206b9b3e0SSimon J. Gerraty if (p[5] != '\0') { 118306b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, 1184*12904384SSimon J. Gerraty "The .endif directive does not take arguments"); 1185e2eeea75SSimon J. Gerraty } 1186e2eeea75SSimon J. Gerraty 11873955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1188956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less endif"); 11893955d011SMarcel Moolenaar return COND_PARSE; 11903955d011SMarcel Moolenaar } 1191e2eeea75SSimon J. Gerraty 11923955d011SMarcel Moolenaar /* Return state for previous conditional */ 11933955d011SMarcel Moolenaar cond_depth--; 1194e2eeea75SSimon J. Gerraty return cond_states[cond_depth] & IFS_ACTIVE 11952c3632d1SSimon J. Gerraty ? COND_PARSE : COND_SKIP; 11963955d011SMarcel Moolenaar } 11973955d011SMarcel Moolenaar 119806b9b3e0SSimon J. Gerraty /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ 119906b9b3e0SSimon J. Gerraty if (p[0] == 'e') { 120006b9b3e0SSimon J. Gerraty if (p[1] != 'l') { 120106b9b3e0SSimon J. Gerraty /* 120206b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a 1203*12904384SSimon J. Gerraty * transformation rule like '.err.txt', 120406b9b3e0SSimon J. Gerraty * therefore no error message here. 120506b9b3e0SSimon J. Gerraty */ 120606b9b3e0SSimon J. Gerraty return COND_INVALID; 120706b9b3e0SSimon J. Gerraty } 120806b9b3e0SSimon J. Gerraty 12093955d011SMarcel Moolenaar /* Quite likely this is 'else' or 'elif' */ 1210e2eeea75SSimon J. Gerraty p += 2; 1211e2eeea75SSimon J. Gerraty if (is_token(p, "se", 2)) { /* It is an 'else'. */ 1212e2eeea75SSimon J. Gerraty 121306b9b3e0SSimon J. Gerraty if (p[2] != '\0') 1214e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, 121506b9b3e0SSimon J. Gerraty "The .else directive " 1216*12904384SSimon J. Gerraty "does not take arguments"); 1217e2eeea75SSimon J. Gerraty 12183955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1219956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less else"); 12203955d011SMarcel Moolenaar return COND_PARSE; 12213955d011SMarcel Moolenaar } 12223955d011SMarcel Moolenaar 1223e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1224e2eeea75SSimon J. Gerraty if (state == IFS_INITIAL) { 1225e2eeea75SSimon J. Gerraty state = IFS_ACTIVE | IFS_SEEN_ELSE; 1226e2eeea75SSimon J. Gerraty } else { 1227e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) 122806b9b3e0SSimon J. Gerraty Parse_Error(PARSE_WARNING, 122906b9b3e0SSimon J. Gerraty "extra else"); 1230e2eeea75SSimon J. Gerraty state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 12313955d011SMarcel Moolenaar } 1232e2eeea75SSimon J. Gerraty cond_states[cond_depth] = state; 1233e2eeea75SSimon J. Gerraty 1234e2eeea75SSimon J. Gerraty return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; 12353955d011SMarcel Moolenaar } 12363955d011SMarcel Moolenaar /* Assume for now it is an elif */ 1237b0c40a00SSimon J. Gerraty isElif = true; 12383955d011SMarcel Moolenaar } else 1239b0c40a00SSimon J. Gerraty isElif = false; 12403955d011SMarcel Moolenaar 1241e2eeea75SSimon J. Gerraty if (p[0] != 'i' || p[1] != 'f') { 124206b9b3e0SSimon J. Gerraty /* 124306b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a transformation rule 124406b9b3e0SSimon J. Gerraty * like '.elisp.scm', therefore no error message here. 124506b9b3e0SSimon J. Gerraty */ 1246e2eeea75SSimon J. Gerraty return COND_INVALID; /* Not an ifxxx or elifxxx line */ 1247e2eeea75SSimon J. Gerraty } 12483955d011SMarcel Moolenaar 1249dba7b0efSSimon J. Gerraty if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) 12503955d011SMarcel Moolenaar return COND_INVALID; 12513955d011SMarcel Moolenaar 12523955d011SMarcel Moolenaar if (isElif) { 12533955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1254956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less elif"); 12553955d011SMarcel Moolenaar return COND_PARSE; 12563955d011SMarcel Moolenaar } 1257e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1258e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) { 12593955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "extra elif"); 126006b9b3e0SSimon J. Gerraty cond_states[cond_depth] = 126106b9b3e0SSimon J. Gerraty IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 12623955d011SMarcel Moolenaar return COND_SKIP; 12633955d011SMarcel Moolenaar } 1264e2eeea75SSimon J. Gerraty if (state != IFS_INITIAL) { 1265e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12663955d011SMarcel Moolenaar return COND_SKIP; 12673955d011SMarcel Moolenaar } 12683955d011SMarcel Moolenaar } else { 12693955d011SMarcel Moolenaar /* Normal .if */ 1270e2eeea75SSimon J. Gerraty if (cond_depth + 1 >= cond_states_cap) { 127159a02420SSimon J. Gerraty /* 127259a02420SSimon J. Gerraty * This is rare, but not impossible. 127359a02420SSimon J. Gerraty * In meta mode, dirdeps.mk (only runs at level 0) 127459a02420SSimon J. Gerraty * can need more than the default. 127559a02420SSimon J. Gerraty */ 1276e2eeea75SSimon J. Gerraty cond_states_cap += 32; 1277e2eeea75SSimon J. Gerraty cond_states = bmake_realloc(cond_states, 127806b9b3e0SSimon J. Gerraty cond_states_cap * 127906b9b3e0SSimon J. Gerraty sizeof *cond_states); 12803955d011SMarcel Moolenaar } 1281e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 12823955d011SMarcel Moolenaar cond_depth++; 1283e2eeea75SSimon J. Gerraty if (!(state & IFS_ACTIVE)) { 128406b9b3e0SSimon J. Gerraty /* 128506b9b3e0SSimon J. Gerraty * If we aren't parsing the data, 128606b9b3e0SSimon J. Gerraty * treat as always false. 128706b9b3e0SSimon J. Gerraty */ 1288e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12893955d011SMarcel Moolenaar return COND_SKIP; 12903955d011SMarcel Moolenaar } 12913955d011SMarcel Moolenaar } 12923955d011SMarcel Moolenaar 1293956e45f6SSimon J. Gerraty /* And evaluate the conditional expression */ 1294dba7b0efSSimon J. Gerraty if (CondEvalExpression(p, &value, plain, evalBare, negate, 1295*12904384SSimon J. Gerraty true, false) == COND_INVALID) { 12963955d011SMarcel Moolenaar /* Syntax error in conditional, error message already output. */ 12973955d011SMarcel Moolenaar /* Skip everything to matching .endif */ 1298e2eeea75SSimon J. Gerraty /* XXX: An extra '.else' is not detected in this case. */ 1299e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 13003955d011SMarcel Moolenaar return COND_SKIP; 13013955d011SMarcel Moolenaar } 13023955d011SMarcel Moolenaar 13033955d011SMarcel Moolenaar if (!value) { 1304e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_INITIAL; 13053955d011SMarcel Moolenaar return COND_SKIP; 13063955d011SMarcel Moolenaar } 1307e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_ACTIVE; 13083955d011SMarcel Moolenaar return COND_PARSE; 13093955d011SMarcel Moolenaar } 13103955d011SMarcel Moolenaar 13113955d011SMarcel Moolenaar void 13123955d011SMarcel Moolenaar Cond_restore_depth(unsigned int saved_depth) 13133955d011SMarcel Moolenaar { 1314956e45f6SSimon J. Gerraty unsigned int open_conds = cond_depth - cond_min_depth; 13153955d011SMarcel Moolenaar 13163955d011SMarcel Moolenaar if (open_conds != 0 || saved_depth > cond_depth) { 131706b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, "%u open conditional%s", 131806b9b3e0SSimon J. Gerraty open_conds, open_conds == 1 ? "" : "s"); 13193955d011SMarcel Moolenaar cond_depth = cond_min_depth; 13203955d011SMarcel Moolenaar } 13213955d011SMarcel Moolenaar 13223955d011SMarcel Moolenaar cond_min_depth = saved_depth; 13233955d011SMarcel Moolenaar } 13243955d011SMarcel Moolenaar 13253955d011SMarcel Moolenaar unsigned int 13263955d011SMarcel Moolenaar Cond_save_depth(void) 13273955d011SMarcel Moolenaar { 1328956e45f6SSimon J. Gerraty unsigned int depth = cond_min_depth; 13293955d011SMarcel Moolenaar 13303955d011SMarcel Moolenaar cond_min_depth = cond_depth; 13313955d011SMarcel Moolenaar return depth; 13323955d011SMarcel Moolenaar } 1333