1*dba7b0efSSimon J. Gerraty /* $NetBSD: cond.c,v 1.256 2021/02/05 05:15:12 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*dba7b0efSSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.256 2021/02/05 05:15:12 rillig Exp $"); 99956e45f6SSimon J. Gerraty 1003955d011SMarcel Moolenaar /* 1013955d011SMarcel Moolenaar * The parsing of conditional expressions is based on this grammar: 102*dba7b0efSSimon J. Gerraty * Or -> And '||' Or 103*dba7b0efSSimon J. Gerraty * Or -> And 104*dba7b0efSSimon J. Gerraty * And -> Term '&&' And 105*dba7b0efSSimon J. Gerraty * And -> Term 106*dba7b0efSSimon J. Gerraty * Term -> Function '(' Argument ')' 107*dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 108*dba7b0efSSimon J. Gerraty * Term -> Leaf 109*dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 110*dba7b0efSSimon J. Gerraty * Term -> '!' Term 111*dba7b0efSSimon J. Gerraty * Leaf -> "string" 112*dba7b0efSSimon J. Gerraty * Leaf -> Number 113*dba7b0efSSimon J. Gerraty * Leaf -> VariableExpression 114*dba7b0efSSimon J. Gerraty * Leaf -> Symbol 115*dba7b0efSSimon J. Gerraty * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' 1163955d011SMarcel Moolenaar * 117*dba7b0efSSimon J. Gerraty * 'Symbol' is an unquoted string literal to which the default function is 118*dba7b0efSSimon J. Gerraty * applied. 1193955d011SMarcel Moolenaar * 120956e45f6SSimon J. Gerraty * The tokens are scanned by CondToken, which returns: 121*dba7b0efSSimon J. Gerraty * TOK_AND for '&&' 122*dba7b0efSSimon J. Gerraty * TOK_OR for '||' 123956e45f6SSimon J. Gerraty * TOK_NOT for '!' 124956e45f6SSimon J. Gerraty * TOK_LPAREN for '(' 125956e45f6SSimon J. Gerraty * TOK_RPAREN for ')' 126*dba7b0efSSimon J. Gerraty * 127956e45f6SSimon J. Gerraty * Other terminal symbols are evaluated using either the default function or 128956e45f6SSimon J. Gerraty * the function given in the terminal, they return either TOK_TRUE or 129956e45f6SSimon J. Gerraty * TOK_FALSE. 1303955d011SMarcel Moolenaar */ 131956e45f6SSimon J. Gerraty typedef enum Token { 132*dba7b0efSSimon J. Gerraty TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT, 1333955d011SMarcel Moolenaar TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR 1343955d011SMarcel Moolenaar } Token; 1353955d011SMarcel Moolenaar 136*dba7b0efSSimon J. Gerraty typedef enum CondResult { 137*dba7b0efSSimon J. Gerraty CR_FALSE, CR_TRUE, CR_ERROR 138*dba7b0efSSimon J. Gerraty } CondResult; 139*dba7b0efSSimon J. Gerraty 140*dba7b0efSSimon J. Gerraty typedef enum ComparisonOp { 141*dba7b0efSSimon J. Gerraty LT, LE, GT, GE, EQ, NE 142*dba7b0efSSimon J. Gerraty } ComparisonOp; 143*dba7b0efSSimon J. Gerraty 144956e45f6SSimon J. Gerraty typedef struct CondParser { 145*dba7b0efSSimon J. Gerraty 146*dba7b0efSSimon J. Gerraty /* 147*dba7b0efSSimon J. Gerraty * The plain '.if ${VAR}' evaluates to true if the value of the 148*dba7b0efSSimon J. Gerraty * expression has length > 0. The other '.if' variants delegate 149*dba7b0efSSimon J. Gerraty * to evalBare instead. 150*dba7b0efSSimon J. Gerraty */ 151*dba7b0efSSimon J. Gerraty Boolean plain; 152*dba7b0efSSimon J. Gerraty 153*dba7b0efSSimon J. Gerraty /* The function to apply on unquoted bare words. */ 154*dba7b0efSSimon J. Gerraty Boolean (*evalBare)(size_t, const char *); 155*dba7b0efSSimon J. Gerraty Boolean negateEvalBare; 156*dba7b0efSSimon J. Gerraty 157956e45f6SSimon J. Gerraty const char *p; /* The remaining condition to parse */ 158956e45f6SSimon J. Gerraty Token curr; /* Single push-back token used in parsing */ 1593955d011SMarcel Moolenaar 16006b9b3e0SSimon J. Gerraty /* 16106b9b3e0SSimon J. Gerraty * Whether an error message has already been printed for this 16206b9b3e0SSimon J. Gerraty * condition. The first available error message is usually the most 16306b9b3e0SSimon J. Gerraty * specific one, therefore it makes sense to suppress the standard 16406b9b3e0SSimon J. Gerraty * "Malformed conditional" message. 16506b9b3e0SSimon J. Gerraty */ 166956e45f6SSimon J. Gerraty Boolean printedError; 167956e45f6SSimon J. Gerraty } CondParser; 168956e45f6SSimon J. Gerraty 169*dba7b0efSSimon J. Gerraty static CondResult CondParser_Or(CondParser *par, Boolean); 1703955d011SMarcel Moolenaar 1713955d011SMarcel Moolenaar static unsigned int cond_depth = 0; /* current .if nesting level */ 1723955d011SMarcel Moolenaar static unsigned int cond_min_depth = 0; /* depth at makefile open */ 1733955d011SMarcel Moolenaar 174*dba7b0efSSimon J. Gerraty static const char *opname[] = { "<", "<=", ">", ">=", "==", "!=" }; 175*dba7b0efSSimon J. Gerraty 17628a6bc81SSimon J. Gerraty /* 17728a6bc81SSimon J. Gerraty * Indicate when we should be strict about lhs of comparisons. 178956e45f6SSimon J. Gerraty * In strict mode, the lhs must be a variable expression or a string literal 179956e45f6SSimon J. Gerraty * in quotes. In non-strict mode it may also be an unquoted string literal. 180956e45f6SSimon J. Gerraty * 181956e45f6SSimon J. Gerraty * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) 182956e45f6SSimon J. Gerraty * FALSE when CondEvalExpression is called from ApplyModifier_IfElse 183e2eeea75SSimon J. Gerraty * since lhs is already expanded, and at that point we cannot tell if 18428a6bc81SSimon J. Gerraty * it was a variable reference or not. 18528a6bc81SSimon J. Gerraty */ 18628a6bc81SSimon J. Gerraty static Boolean lhsStrict; 18728a6bc81SSimon J. Gerraty 18806b9b3e0SSimon J. Gerraty static Boolean 189956e45f6SSimon J. Gerraty is_token(const char *str, const char *tok, size_t len) 1903955d011SMarcel Moolenaar { 191956e45f6SSimon J. Gerraty return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); 1923955d011SMarcel Moolenaar } 1933955d011SMarcel Moolenaar 194e2eeea75SSimon J. Gerraty static Token 195e2eeea75SSimon J. Gerraty ToToken(Boolean cond) 196e2eeea75SSimon J. Gerraty { 197e2eeea75SSimon J. Gerraty return cond ? TOK_TRUE : TOK_FALSE; 198e2eeea75SSimon J. Gerraty } 199e2eeea75SSimon J. Gerraty 200956e45f6SSimon J. Gerraty /* Push back the most recent token read. We only need one level of this. */ 2013955d011SMarcel Moolenaar static void 202956e45f6SSimon J. Gerraty CondParser_PushBack(CondParser *par, Token t) 2033955d011SMarcel Moolenaar { 204956e45f6SSimon J. Gerraty assert(par->curr == TOK_NONE); 205956e45f6SSimon J. Gerraty assert(t != TOK_NONE); 206956e45f6SSimon J. Gerraty 207956e45f6SSimon J. Gerraty par->curr = t; 2083955d011SMarcel Moolenaar } 2092c3632d1SSimon J. Gerraty 210956e45f6SSimon J. Gerraty static void 211956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(CondParser *par) 2123955d011SMarcel Moolenaar { 213956e45f6SSimon J. Gerraty cpp_skip_whitespace(&par->p); 214956e45f6SSimon J. Gerraty } 215956e45f6SSimon J. Gerraty 21606b9b3e0SSimon J. Gerraty /* 21706b9b3e0SSimon J. Gerraty * Parse the argument of a built-in function. 218956e45f6SSimon J. Gerraty * 219956e45f6SSimon J. Gerraty * Arguments: 220956e45f6SSimon J. Gerraty * *pp initially points at the '(', 221956e45f6SSimon J. Gerraty * upon successful return it points right after the ')'. 222956e45f6SSimon J. Gerraty * 223956e45f6SSimon J. Gerraty * *out_arg receives the argument as string. 224956e45f6SSimon J. Gerraty * 225956e45f6SSimon J. Gerraty * func says whether the argument belongs to an actual function, or 226956e45f6SSimon J. Gerraty * whether the parsed argument is passed to the default function. 227956e45f6SSimon J. Gerraty * 22806b9b3e0SSimon J. Gerraty * Return the length of the argument, or 0 on error. 22906b9b3e0SSimon J. Gerraty */ 230956e45f6SSimon J. Gerraty static size_t 231*dba7b0efSSimon J. Gerraty ParseFuncArg(CondParser *par, const char **pp, Boolean doEval, const char *func, 23206b9b3e0SSimon J. Gerraty char **out_arg) 23306b9b3e0SSimon J. Gerraty { 234956e45f6SSimon J. Gerraty const char *p = *pp; 235956e45f6SSimon J. Gerraty Buffer argBuf; 2363955d011SMarcel Moolenaar int paren_depth; 2372c3632d1SSimon J. Gerraty size_t argLen; 2383955d011SMarcel Moolenaar 2393955d011SMarcel Moolenaar if (func != NULL) 240956e45f6SSimon J. Gerraty p++; /* Skip opening '(' - verified by caller */ 2413955d011SMarcel Moolenaar 242956e45f6SSimon J. Gerraty if (*p == '\0') { 243e2eeea75SSimon J. Gerraty *out_arg = NULL; /* Missing closing parenthesis: */ 244e2eeea75SSimon J. Gerraty return 0; /* .if defined( */ 2453955d011SMarcel Moolenaar } 2463955d011SMarcel Moolenaar 247e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2483955d011SMarcel Moolenaar 249e2eeea75SSimon J. Gerraty Buf_InitSize(&argBuf, 16); 2503955d011SMarcel Moolenaar 2513955d011SMarcel Moolenaar paren_depth = 0; 2523955d011SMarcel Moolenaar for (;;) { 253956e45f6SSimon J. Gerraty char ch = *p; 254e2eeea75SSimon J. Gerraty if (ch == '\0' || ch == ' ' || ch == '\t') 2553955d011SMarcel Moolenaar break; 2563955d011SMarcel Moolenaar if ((ch == '&' || ch == '|') && paren_depth == 0) 2573955d011SMarcel Moolenaar break; 258956e45f6SSimon J. Gerraty if (*p == '$') { 2593955d011SMarcel Moolenaar /* 26006b9b3e0SSimon J. Gerraty * Parse the variable expression and install it as 26106b9b3e0SSimon J. Gerraty * part of the argument if it's valid. We tell 26206b9b3e0SSimon J. Gerraty * Var_Parse to complain on an undefined variable, 26306b9b3e0SSimon J. Gerraty * (XXX: but Var_Parse ignores that request) 26406b9b3e0SSimon J. Gerraty * so we don't need to do it. Nor do we return an 26506b9b3e0SSimon J. Gerraty * error, though perhaps we should. 2663955d011SMarcel Moolenaar */ 26706b9b3e0SSimon J. Gerraty VarEvalFlags eflags = doEval 26806b9b3e0SSimon J. Gerraty ? VARE_WANTRES | VARE_UNDEFERR 269e2eeea75SSimon J. Gerraty : VARE_NONE; 27006b9b3e0SSimon J. Gerraty FStr nestedVal; 271*dba7b0efSSimon J. Gerraty (void)Var_Parse(&p, SCOPE_CMDLINE, eflags, &nestedVal); 272956e45f6SSimon J. Gerraty /* TODO: handle errors */ 27306b9b3e0SSimon J. Gerraty Buf_AddStr(&argBuf, nestedVal.str); 27406b9b3e0SSimon J. Gerraty FStr_Done(&nestedVal); 2753955d011SMarcel Moolenaar continue; 2763955d011SMarcel Moolenaar } 2773955d011SMarcel Moolenaar if (ch == '(') 2783955d011SMarcel Moolenaar paren_depth++; 2792c3632d1SSimon J. Gerraty else if (ch == ')' && --paren_depth < 0) 2803955d011SMarcel Moolenaar break; 281956e45f6SSimon J. Gerraty Buf_AddByte(&argBuf, *p); 282956e45f6SSimon J. Gerraty p++; 2833955d011SMarcel Moolenaar } 2843955d011SMarcel Moolenaar 285*dba7b0efSSimon J. Gerraty argLen = argBuf.len; 286*dba7b0efSSimon J. Gerraty *out_arg = Buf_DoneData(&argBuf); 2873955d011SMarcel Moolenaar 288e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2893955d011SMarcel Moolenaar 290956e45f6SSimon J. Gerraty if (func != NULL && *p++ != ')') { 291*dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 292*dba7b0efSSimon J. Gerraty "Missing closing parenthesis for %s()", func); 293*dba7b0efSSimon J. Gerraty par->printedError = TRUE; 2943841c287SSimon J. Gerraty return 0; 2953955d011SMarcel Moolenaar } 2963955d011SMarcel Moolenaar 297956e45f6SSimon J. Gerraty *pp = p; 2983841c287SSimon J. Gerraty return argLen; 2993955d011SMarcel Moolenaar } 3002c3632d1SSimon J. Gerraty 3012c3632d1SSimon J. Gerraty /* Test whether the given variable is defined. */ 30206b9b3e0SSimon J. Gerraty /*ARGSUSED*/ 3033955d011SMarcel Moolenaar static Boolean 304956e45f6SSimon J. Gerraty FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 3053955d011SMarcel Moolenaar { 306*dba7b0efSSimon J. Gerraty FStr value = Var_Value(SCOPE_CMDLINE, arg); 30706b9b3e0SSimon J. Gerraty Boolean result = value.str != NULL; 30806b9b3e0SSimon J. Gerraty FStr_Done(&value); 3093841c287SSimon J. Gerraty return result; 3103955d011SMarcel Moolenaar } 3112c3632d1SSimon J. Gerraty 3122c3632d1SSimon J. Gerraty /* See if the given target is being made. */ 31306b9b3e0SSimon J. Gerraty /*ARGSUSED*/ 3143955d011SMarcel Moolenaar static Boolean 315956e45f6SSimon J. Gerraty FuncMake(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 3163955d011SMarcel Moolenaar { 317956e45f6SSimon J. Gerraty StringListNode *ln; 318956e45f6SSimon J. Gerraty 31906b9b3e0SSimon J. Gerraty for (ln = opts.create.first; ln != NULL; ln = ln->next) 320956e45f6SSimon J. Gerraty if (Str_Match(ln->datum, arg)) 321956e45f6SSimon J. Gerraty return TRUE; 322956e45f6SSimon J. Gerraty return FALSE; 3233955d011SMarcel Moolenaar } 3242c3632d1SSimon J. Gerraty 3252c3632d1SSimon J. Gerraty /* See if the given file exists. */ 32606b9b3e0SSimon J. Gerraty /*ARGSUSED*/ 3273955d011SMarcel Moolenaar static Boolean 328956e45f6SSimon J. Gerraty FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 3293955d011SMarcel Moolenaar { 3303955d011SMarcel Moolenaar Boolean result; 3313955d011SMarcel Moolenaar char *path; 3323955d011SMarcel Moolenaar 33306b9b3e0SSimon J. Gerraty path = Dir_FindFile(arg, &dirSearchPath); 334e2eeea75SSimon J. Gerraty DEBUG2(COND, "exists(%s) result is \"%s\"\n", 335e2eeea75SSimon J. Gerraty arg, path != NULL ? path : ""); 336e2eeea75SSimon J. Gerraty result = path != NULL; 3373955d011SMarcel Moolenaar free(path); 3383841c287SSimon J. Gerraty return result; 3393955d011SMarcel Moolenaar } 3402c3632d1SSimon J. Gerraty 3412c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target. */ 34206b9b3e0SSimon J. Gerraty /*ARGSUSED*/ 3433955d011SMarcel Moolenaar static Boolean 344956e45f6SSimon J. Gerraty FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 3453955d011SMarcel Moolenaar { 346956e45f6SSimon J. Gerraty GNode *gn = Targ_FindNode(arg); 347956e45f6SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn); 3483955d011SMarcel Moolenaar } 3493955d011SMarcel Moolenaar 35006b9b3e0SSimon J. Gerraty /* 35106b9b3e0SSimon J. Gerraty * See if the given node exists and is an actual target with commands 35206b9b3e0SSimon J. Gerraty * associated with it. 35306b9b3e0SSimon J. Gerraty */ 35406b9b3e0SSimon J. Gerraty /*ARGSUSED*/ 3553955d011SMarcel Moolenaar static Boolean 356956e45f6SSimon J. Gerraty FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 3573955d011SMarcel Moolenaar { 358956e45f6SSimon J. Gerraty GNode *gn = Targ_FindNode(arg); 35906b9b3e0SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands); 3603955d011SMarcel Moolenaar } 3612c3632d1SSimon J. Gerraty 362e2eeea75SSimon J. Gerraty /* 3633955d011SMarcel Moolenaar * Convert the given number into a double. 3643955d011SMarcel Moolenaar * We try a base 10 or 16 integer conversion first, if that fails 3653955d011SMarcel Moolenaar * then we try a floating point conversion instead. 3663955d011SMarcel Moolenaar * 3673955d011SMarcel Moolenaar * Results: 3682c3632d1SSimon J. Gerraty * Returns TRUE if the conversion succeeded. 369e2eeea75SSimon J. Gerraty * Sets 'out_value' to the converted number. 3703955d011SMarcel Moolenaar */ 3713955d011SMarcel Moolenaar static Boolean 372e2eeea75SSimon J. Gerraty TryParseNumber(const char *str, double *out_value) 3733955d011SMarcel Moolenaar { 374e2eeea75SSimon J. Gerraty char *end; 375e2eeea75SSimon J. Gerraty unsigned long ul_val; 376e2eeea75SSimon J. Gerraty double dbl_val; 3773955d011SMarcel Moolenaar 3783955d011SMarcel Moolenaar errno = 0; 379e2eeea75SSimon J. Gerraty if (str[0] == '\0') { /* XXX: why is an empty string a number? */ 380e2eeea75SSimon J. Gerraty *out_value = 0.0; 381ac3446e9SSimon J. Gerraty return TRUE; 382ac3446e9SSimon J. Gerraty } 383e2eeea75SSimon J. Gerraty 384e2eeea75SSimon J. Gerraty ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); 385e2eeea75SSimon J. Gerraty if (*end == '\0' && errno != ERANGE) { 386e2eeea75SSimon J. Gerraty *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; 387e2eeea75SSimon J. Gerraty return TRUE; 3883955d011SMarcel Moolenaar } 3893955d011SMarcel Moolenaar 390e2eeea75SSimon J. Gerraty if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') 391e2eeea75SSimon J. Gerraty return FALSE; /* skip the expensive strtod call */ 392e2eeea75SSimon J. Gerraty dbl_val = strtod(str, &end); 393e2eeea75SSimon J. Gerraty if (*end != '\0') 394e2eeea75SSimon J. Gerraty return FALSE; 395e2eeea75SSimon J. Gerraty 396e2eeea75SSimon J. Gerraty *out_value = dbl_val; 3973955d011SMarcel Moolenaar return TRUE; 3983955d011SMarcel Moolenaar } 3993955d011SMarcel Moolenaar 400956e45f6SSimon J. Gerraty static Boolean 401956e45f6SSimon J. Gerraty is_separator(char ch) 402956e45f6SSimon J. Gerraty { 403956e45f6SSimon J. Gerraty return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; 404956e45f6SSimon J. Gerraty } 405956e45f6SSimon J. Gerraty 406*dba7b0efSSimon J. Gerraty /* 407*dba7b0efSSimon J. Gerraty * In a quoted or unquoted string literal or a number, parse a variable 408*dba7b0efSSimon J. Gerraty * expression. 409*dba7b0efSSimon J. Gerraty * 410*dba7b0efSSimon J. Gerraty * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} 411*dba7b0efSSimon J. Gerraty */ 412*dba7b0efSSimon J. Gerraty static Boolean 413*dba7b0efSSimon J. Gerraty CondParser_StringExpr(CondParser *par, const char *start, 414*dba7b0efSSimon J. Gerraty Boolean const doEval, Boolean const quoted, 415*dba7b0efSSimon J. Gerraty Buffer *buf, FStr *const inout_str) 416*dba7b0efSSimon J. Gerraty { 417*dba7b0efSSimon J. Gerraty VarEvalFlags eflags; 418*dba7b0efSSimon J. Gerraty const char *nested_p; 419*dba7b0efSSimon J. Gerraty Boolean atStart; 420*dba7b0efSSimon J. Gerraty VarParseResult parseResult; 421*dba7b0efSSimon J. Gerraty 422*dba7b0efSSimon J. Gerraty /* if we are in quotes, an undefined variable is ok */ 423*dba7b0efSSimon J. Gerraty eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR 424*dba7b0efSSimon J. Gerraty : doEval ? VARE_WANTRES 425*dba7b0efSSimon J. Gerraty : VARE_NONE; 426*dba7b0efSSimon J. Gerraty 427*dba7b0efSSimon J. Gerraty nested_p = par->p; 428*dba7b0efSSimon J. Gerraty atStart = nested_p == start; 429*dba7b0efSSimon J. Gerraty parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, eflags, inout_str); 430*dba7b0efSSimon J. Gerraty /* TODO: handle errors */ 431*dba7b0efSSimon J. Gerraty if (inout_str->str == var_Error) { 432*dba7b0efSSimon J. Gerraty if (parseResult == VPR_ERR) { 433*dba7b0efSSimon J. Gerraty /* 434*dba7b0efSSimon J. Gerraty * FIXME: Even if an error occurs, there is no 435*dba7b0efSSimon J. Gerraty * guarantee that it is reported. 436*dba7b0efSSimon J. Gerraty * 437*dba7b0efSSimon J. Gerraty * See cond-token-plain.mk $$$$$$$$. 438*dba7b0efSSimon J. Gerraty */ 439*dba7b0efSSimon J. Gerraty par->printedError = TRUE; 440*dba7b0efSSimon J. Gerraty } 441*dba7b0efSSimon J. Gerraty /* 442*dba7b0efSSimon J. Gerraty * XXX: Can there be any situation in which a returned 443*dba7b0efSSimon J. Gerraty * var_Error requires freeIt? 444*dba7b0efSSimon J. Gerraty */ 445*dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 446*dba7b0efSSimon J. Gerraty /* 447*dba7b0efSSimon J. Gerraty * Even if !doEval, we still report syntax errors, which is 448*dba7b0efSSimon J. Gerraty * what getting var_Error back with !doEval means. 449*dba7b0efSSimon J. Gerraty */ 450*dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); 451*dba7b0efSSimon J. Gerraty return FALSE; 452*dba7b0efSSimon J. Gerraty } 453*dba7b0efSSimon J. Gerraty par->p = nested_p; 454*dba7b0efSSimon J. Gerraty 455*dba7b0efSSimon J. Gerraty /* 456*dba7b0efSSimon J. Gerraty * If the '$' started the string literal (which means no quotes), and 457*dba7b0efSSimon J. Gerraty * the variable expression is followed by a space, looks like a 458*dba7b0efSSimon J. Gerraty * comparison operator or is the end of the expression, we are done. 459*dba7b0efSSimon J. Gerraty */ 460*dba7b0efSSimon J. Gerraty if (atStart && is_separator(par->p[0])) 461*dba7b0efSSimon J. Gerraty return FALSE; 462*dba7b0efSSimon J. Gerraty 463*dba7b0efSSimon J. Gerraty Buf_AddStr(buf, inout_str->str); 464*dba7b0efSSimon J. Gerraty FStr_Done(inout_str); 465*dba7b0efSSimon J. Gerraty *inout_str = FStr_InitRefer(NULL); /* not finished yet */ 466*dba7b0efSSimon J. Gerraty return TRUE; 467*dba7b0efSSimon J. Gerraty } 468*dba7b0efSSimon J. Gerraty 469*dba7b0efSSimon J. Gerraty /* 470956e45f6SSimon J. Gerraty * Parse a string from a variable reference or an optionally quoted 471956e45f6SSimon J. Gerraty * string. This is called for the lhs and rhs of string comparisons. 4723955d011SMarcel Moolenaar * 4733955d011SMarcel Moolenaar * Results: 4742c3632d1SSimon J. Gerraty * Returns the string, absent any quotes, or NULL on error. 475e2eeea75SSimon J. Gerraty * Sets out_quoted if the string was quoted. 476e2eeea75SSimon J. Gerraty * Sets out_freeIt. 4773955d011SMarcel Moolenaar */ 47806b9b3e0SSimon J. Gerraty static void 479956e45f6SSimon J. Gerraty CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, 48006b9b3e0SSimon J. Gerraty FStr *out_str, Boolean *out_quoted) 4813955d011SMarcel Moolenaar { 4823955d011SMarcel Moolenaar Buffer buf; 48306b9b3e0SSimon J. Gerraty FStr str; 484e2eeea75SSimon J. Gerraty Boolean quoted; 4852c3632d1SSimon J. Gerraty const char *start; 4863955d011SMarcel Moolenaar 487e2eeea75SSimon J. Gerraty Buf_Init(&buf); 48806b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 489e2eeea75SSimon J. Gerraty *out_quoted = quoted = par->p[0] == '"'; 490956e45f6SSimon J. Gerraty start = par->p; 491e2eeea75SSimon J. Gerraty if (quoted) 492956e45f6SSimon J. Gerraty par->p++; 49306b9b3e0SSimon J. Gerraty 49406b9b3e0SSimon J. Gerraty while (par->p[0] != '\0' && str.str == NULL) { 495956e45f6SSimon J. Gerraty switch (par->p[0]) { 4963955d011SMarcel Moolenaar case '\\': 497956e45f6SSimon J. Gerraty par->p++; 498956e45f6SSimon J. Gerraty if (par->p[0] != '\0') { 499956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 500956e45f6SSimon J. Gerraty par->p++; 5013955d011SMarcel Moolenaar } 502956e45f6SSimon J. Gerraty continue; 5033955d011SMarcel Moolenaar case '"': 504956e45f6SSimon J. Gerraty par->p++; 505*dba7b0efSSimon J. Gerraty if (quoted) 506*dba7b0efSSimon J. Gerraty goto got_str; /* skip the closing quote */ 507*dba7b0efSSimon J. Gerraty Buf_AddByte(&buf, '"'); 508956e45f6SSimon J. Gerraty continue; 509e2eeea75SSimon J. Gerraty case ')': /* see is_separator */ 5103955d011SMarcel Moolenaar case '!': 5113955d011SMarcel Moolenaar case '=': 5123955d011SMarcel Moolenaar case '>': 5133955d011SMarcel Moolenaar case '<': 5143955d011SMarcel Moolenaar case ' ': 5153955d011SMarcel Moolenaar case '\t': 516e2eeea75SSimon J. Gerraty if (!quoted) 5173955d011SMarcel Moolenaar goto got_str; 518956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 519956e45f6SSimon J. Gerraty par->p++; 520956e45f6SSimon J. Gerraty continue; 5213955d011SMarcel Moolenaar case '$': 522*dba7b0efSSimon J. Gerraty if (!CondParser_StringExpr(par, 523*dba7b0efSSimon J. Gerraty start, doEval, quoted, &buf, &str)) 5243955d011SMarcel Moolenaar goto cleanup; 525956e45f6SSimon J. Gerraty continue; 5263955d011SMarcel Moolenaar default: 52706b9b3e0SSimon J. Gerraty if (strictLHS && !quoted && *start != '$' && 52806b9b3e0SSimon J. Gerraty !ch_isdigit(*start)) { 52906b9b3e0SSimon J. Gerraty /* 53006b9b3e0SSimon J. Gerraty * The left-hand side must be quoted, 53106b9b3e0SSimon J. Gerraty * a variable reference or a number. 53206b9b3e0SSimon J. Gerraty */ 53306b9b3e0SSimon J. Gerraty str = FStr_InitRefer(NULL); 53428a6bc81SSimon J. Gerraty goto cleanup; 53528a6bc81SSimon J. Gerraty } 536956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 537956e45f6SSimon J. Gerraty par->p++; 538956e45f6SSimon J. Gerraty continue; 5393955d011SMarcel Moolenaar } 5403955d011SMarcel Moolenaar } 5413955d011SMarcel Moolenaar got_str: 542*dba7b0efSSimon J. Gerraty str = FStr_InitOwn(buf.data); 5433955d011SMarcel Moolenaar cleanup: 544*dba7b0efSSimon J. Gerraty Buf_DoneData(&buf); 54506b9b3e0SSimon J. Gerraty *out_str = str; 5463955d011SMarcel Moolenaar } 5472c3632d1SSimon J. Gerraty 548e2eeea75SSimon J. Gerraty static Boolean 549*dba7b0efSSimon J. Gerraty If_Eval(const CondParser *par, const char *arg, size_t arglen) 550e2eeea75SSimon J. Gerraty { 551*dba7b0efSSimon J. Gerraty Boolean res = par->evalBare(arglen, arg); 552*dba7b0efSSimon J. Gerraty return par->negateEvalBare ? !res : res; 553e2eeea75SSimon J. Gerraty } 5542c3632d1SSimon J. Gerraty 55506b9b3e0SSimon J. Gerraty /* 55606b9b3e0SSimon J. Gerraty * Evaluate a "comparison without operator", such as in ".if ${VAR}" or 55706b9b3e0SSimon J. Gerraty * ".if 0". 55806b9b3e0SSimon J. Gerraty */ 559e2eeea75SSimon J. Gerraty static Boolean 560e2eeea75SSimon J. Gerraty EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) 5613955d011SMarcel Moolenaar { 562e2eeea75SSimon J. Gerraty double num; 563956e45f6SSimon J. Gerraty 564e2eeea75SSimon J. Gerraty /* For .ifxxx "...", check for non-empty string. */ 565e2eeea75SSimon J. Gerraty if (quoted) 566e2eeea75SSimon J. Gerraty return value[0] != '\0'; 567956e45f6SSimon J. Gerraty 568e2eeea75SSimon J. Gerraty /* For .ifxxx <number>, compare against zero */ 569e2eeea75SSimon J. Gerraty if (TryParseNumber(value, &num)) 570e2eeea75SSimon J. Gerraty return num != 0.0; 571956e45f6SSimon J. Gerraty 572e2eeea75SSimon J. Gerraty /* For .if ${...}, check for non-empty string. This is different from 573e2eeea75SSimon J. Gerraty * the evaluation function from that .if variant, which would test 574e2eeea75SSimon J. Gerraty * whether a variable of the given name were defined. */ 575e2eeea75SSimon J. Gerraty /* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */ 576*dba7b0efSSimon J. Gerraty if (par->plain) 577e2eeea75SSimon J. Gerraty return value[0] != '\0'; 578956e45f6SSimon J. Gerraty 579*dba7b0efSSimon J. Gerraty return If_Eval(par, value, strlen(value)); 580956e45f6SSimon J. Gerraty } 581956e45f6SSimon J. Gerraty 582956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ 583*dba7b0efSSimon J. Gerraty static Boolean 584*dba7b0efSSimon J. Gerraty EvalCompareNum(double lhs, ComparisonOp op, double rhs) 585956e45f6SSimon J. Gerraty { 586*dba7b0efSSimon J. Gerraty DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, opname[op]); 587956e45f6SSimon J. Gerraty 588*dba7b0efSSimon J. Gerraty switch (op) { 589*dba7b0efSSimon J. Gerraty case LT: 590*dba7b0efSSimon J. Gerraty return lhs < rhs; 591*dba7b0efSSimon J. Gerraty case LE: 592*dba7b0efSSimon J. Gerraty return lhs <= rhs; 593*dba7b0efSSimon J. Gerraty case GT: 594*dba7b0efSSimon J. Gerraty return lhs > rhs; 595*dba7b0efSSimon J. Gerraty case GE: 596*dba7b0efSSimon J. Gerraty return lhs >= rhs; 597*dba7b0efSSimon J. Gerraty case NE: 598*dba7b0efSSimon J. Gerraty return lhs != rhs; 599*dba7b0efSSimon J. Gerraty default: 600*dba7b0efSSimon J. Gerraty return lhs == rhs; 601956e45f6SSimon J. Gerraty } 602956e45f6SSimon J. Gerraty } 603956e45f6SSimon J. Gerraty 604956e45f6SSimon J. Gerraty static Token 605*dba7b0efSSimon J. Gerraty EvalCompareStr(CondParser *par, const char *lhs, 606*dba7b0efSSimon J. Gerraty ComparisonOp op, const char *rhs) 607956e45f6SSimon J. Gerraty { 608*dba7b0efSSimon J. Gerraty if (op != EQ && op != NE) { 609*dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 610*dba7b0efSSimon J. Gerraty "String comparison operator must be either == or !="); 611*dba7b0efSSimon J. Gerraty par->printedError = TRUE; 612956e45f6SSimon J. Gerraty return TOK_ERROR; 613956e45f6SSimon J. Gerraty } 614956e45f6SSimon J. Gerraty 615*dba7b0efSSimon J. Gerraty DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", 616*dba7b0efSSimon J. Gerraty lhs, rhs, opname[op]); 617*dba7b0efSSimon J. Gerraty return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); 618956e45f6SSimon J. Gerraty } 619956e45f6SSimon J. Gerraty 620956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */ 621956e45f6SSimon J. Gerraty static Token 622*dba7b0efSSimon J. Gerraty EvalCompare(CondParser *par, const char *lhs, Boolean lhsQuoted, 623*dba7b0efSSimon J. Gerraty ComparisonOp op, const char *rhs, Boolean rhsQuoted) 624956e45f6SSimon J. Gerraty { 6253955d011SMarcel Moolenaar double left, right; 6263955d011SMarcel Moolenaar 627956e45f6SSimon J. Gerraty if (!rhsQuoted && !lhsQuoted) 628956e45f6SSimon J. Gerraty if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) 629*dba7b0efSSimon J. Gerraty return ToToken(EvalCompareNum(left, op, right)); 630956e45f6SSimon J. Gerraty 631*dba7b0efSSimon J. Gerraty return EvalCompareStr(par, lhs, op, rhs); 632*dba7b0efSSimon J. Gerraty } 633*dba7b0efSSimon J. Gerraty 634*dba7b0efSSimon J. Gerraty static Boolean 635*dba7b0efSSimon J. Gerraty CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) 636*dba7b0efSSimon J. Gerraty { 637*dba7b0efSSimon J. Gerraty const char *p = par->p; 638*dba7b0efSSimon J. Gerraty 639*dba7b0efSSimon J. Gerraty if (p[0] == '<' && p[1] == '=') { 640*dba7b0efSSimon J. Gerraty *out_op = LE; 641*dba7b0efSSimon J. Gerraty goto length_2; 642*dba7b0efSSimon J. Gerraty } else if (p[0] == '<') { 643*dba7b0efSSimon J. Gerraty *out_op = LT; 644*dba7b0efSSimon J. Gerraty goto length_1; 645*dba7b0efSSimon J. Gerraty } else if (p[0] == '>' && p[1] == '=') { 646*dba7b0efSSimon J. Gerraty *out_op = GE; 647*dba7b0efSSimon J. Gerraty goto length_2; 648*dba7b0efSSimon J. Gerraty } else if (p[0] == '>') { 649*dba7b0efSSimon J. Gerraty *out_op = GT; 650*dba7b0efSSimon J. Gerraty goto length_1; 651*dba7b0efSSimon J. Gerraty } else if (p[0] == '=' && p[1] == '=') { 652*dba7b0efSSimon J. Gerraty *out_op = EQ; 653*dba7b0efSSimon J. Gerraty goto length_2; 654*dba7b0efSSimon J. Gerraty } else if (p[0] == '!' && p[1] == '=') { 655*dba7b0efSSimon J. Gerraty *out_op = NE; 656*dba7b0efSSimon J. Gerraty goto length_2; 657*dba7b0efSSimon J. Gerraty } 658*dba7b0efSSimon J. Gerraty return FALSE; 659*dba7b0efSSimon J. Gerraty 660*dba7b0efSSimon J. Gerraty length_2: 661*dba7b0efSSimon J. Gerraty par->p = p + 2; 662*dba7b0efSSimon J. Gerraty return TRUE; 663*dba7b0efSSimon J. Gerraty length_1: 664*dba7b0efSSimon J. Gerraty par->p = p + 1; 665*dba7b0efSSimon J. Gerraty return TRUE; 666956e45f6SSimon J. Gerraty } 667956e45f6SSimon J. Gerraty 66806b9b3e0SSimon J. Gerraty /* 66906b9b3e0SSimon J. Gerraty * Parse a comparison condition such as: 670956e45f6SSimon J. Gerraty * 671956e45f6SSimon J. Gerraty * 0 672956e45f6SSimon J. Gerraty * ${VAR:Mpattern} 673956e45f6SSimon J. Gerraty * ${VAR} == value 674956e45f6SSimon J. Gerraty * ${VAR:U0} < 12345 675956e45f6SSimon J. Gerraty */ 676956e45f6SSimon J. Gerraty static Token 677956e45f6SSimon J. Gerraty CondParser_Comparison(CondParser *par, Boolean doEval) 678956e45f6SSimon J. Gerraty { 679956e45f6SSimon J. Gerraty Token t = TOK_ERROR; 68006b9b3e0SSimon J. Gerraty FStr lhs, rhs; 681*dba7b0efSSimon J. Gerraty ComparisonOp op; 682956e45f6SSimon J. Gerraty Boolean lhsQuoted, rhsQuoted; 683956e45f6SSimon J. Gerraty 6843955d011SMarcel Moolenaar /* 6853955d011SMarcel Moolenaar * Parse the variable spec and skip over it, saving its 6863955d011SMarcel Moolenaar * value in lhs. 6873955d011SMarcel Moolenaar */ 68806b9b3e0SSimon J. Gerraty CondParser_String(par, doEval, lhsStrict, &lhs, &lhsQuoted); 68906b9b3e0SSimon J. Gerraty if (lhs.str == NULL) 690e2eeea75SSimon J. Gerraty goto done_lhs; 6913955d011SMarcel Moolenaar 692956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6933955d011SMarcel Moolenaar 694*dba7b0efSSimon J. Gerraty if (!CondParser_ComparisonOp(par, &op)) { 695e2eeea75SSimon J. Gerraty /* Unknown operator, compare against an empty string or 0. */ 69606b9b3e0SSimon J. Gerraty t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); 697e2eeea75SSimon J. Gerraty goto done_lhs; 6983955d011SMarcel Moolenaar } 6993955d011SMarcel Moolenaar 700956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 7013955d011SMarcel Moolenaar 702956e45f6SSimon J. Gerraty if (par->p[0] == '\0') { 703*dba7b0efSSimon J. Gerraty Parse_Error(PARSE_FATAL, 704*dba7b0efSSimon J. Gerraty "Missing right-hand-side of operator '%s'", opname[op]); 705*dba7b0efSSimon J. Gerraty par->printedError = TRUE; 706e2eeea75SSimon J. Gerraty goto done_lhs; 7073955d011SMarcel Moolenaar } 7083955d011SMarcel Moolenaar 70906b9b3e0SSimon J. Gerraty CondParser_String(par, doEval, FALSE, &rhs, &rhsQuoted); 71006b9b3e0SSimon J. Gerraty if (rhs.str == NULL) 711e2eeea75SSimon J. Gerraty goto done_rhs; 7123955d011SMarcel Moolenaar 7133841c287SSimon J. Gerraty if (!doEval) { 7143841c287SSimon J. Gerraty t = TOK_FALSE; 715e2eeea75SSimon J. Gerraty goto done_rhs; 7163841c287SSimon J. Gerraty } 7173841c287SSimon J. Gerraty 718*dba7b0efSSimon J. Gerraty t = EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); 7193955d011SMarcel Moolenaar 720e2eeea75SSimon J. Gerraty done_rhs: 72106b9b3e0SSimon J. Gerraty FStr_Done(&rhs); 722e2eeea75SSimon J. Gerraty done_lhs: 72306b9b3e0SSimon J. Gerraty FStr_Done(&lhs); 7243955d011SMarcel Moolenaar return t; 7253955d011SMarcel Moolenaar } 7263955d011SMarcel Moolenaar 72706b9b3e0SSimon J. Gerraty /* 72806b9b3e0SSimon J. Gerraty * The argument to empty() is a variable name, optionally followed by 72906b9b3e0SSimon J. Gerraty * variable modifiers. 73006b9b3e0SSimon J. Gerraty */ 73106b9b3e0SSimon J. Gerraty /*ARGSUSED*/ 732956e45f6SSimon J. Gerraty static size_t 733*dba7b0efSSimon J. Gerraty ParseEmptyArg(CondParser *par MAKE_ATTR_UNUSED, const char **pp, 734*dba7b0efSSimon J. Gerraty Boolean doEval, const char *func MAKE_ATTR_UNUSED, 735*dba7b0efSSimon J. Gerraty char **out_arg) 7363955d011SMarcel Moolenaar { 73706b9b3e0SSimon J. Gerraty FStr val; 738956e45f6SSimon J. Gerraty size_t magic_res; 7393955d011SMarcel Moolenaar 7403955d011SMarcel Moolenaar /* We do all the work here and return the result as the length */ 741e2eeea75SSimon J. Gerraty *out_arg = NULL; 7423955d011SMarcel Moolenaar 743e2eeea75SSimon J. Gerraty (*pp)--; /* Make (*pp)[1] point to the '('. */ 744*dba7b0efSSimon J. Gerraty (void)Var_Parse(pp, SCOPE_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE, 74506b9b3e0SSimon J. Gerraty &val); 746956e45f6SSimon J. Gerraty /* TODO: handle errors */ 747e2eeea75SSimon J. Gerraty /* If successful, *pp points beyond the closing ')' now. */ 7483955d011SMarcel Moolenaar 74906b9b3e0SSimon J. Gerraty if (val.str == var_Error) { 75006b9b3e0SSimon J. Gerraty FStr_Done(&val); 751956e45f6SSimon J. Gerraty return (size_t)-1; 7523955d011SMarcel Moolenaar } 7533955d011SMarcel Moolenaar 75406b9b3e0SSimon J. Gerraty /* 75506b9b3e0SSimon J. Gerraty * A variable is empty when it just contains spaces... 75606b9b3e0SSimon J. Gerraty * 4/15/92, christos 75706b9b3e0SSimon J. Gerraty */ 75806b9b3e0SSimon J. Gerraty cpp_skip_whitespace(&val.str); 7593955d011SMarcel Moolenaar 7603955d011SMarcel Moolenaar /* 7613955d011SMarcel Moolenaar * For consistency with the other functions we can't generate the 7623955d011SMarcel Moolenaar * true/false here. 7633955d011SMarcel Moolenaar */ 76406b9b3e0SSimon J. Gerraty magic_res = val.str[0] != '\0' ? 2 : 1; 76506b9b3e0SSimon J. Gerraty FStr_Done(&val); 766956e45f6SSimon J. Gerraty return magic_res; 7673955d011SMarcel Moolenaar } 7683955d011SMarcel Moolenaar 76906b9b3e0SSimon J. Gerraty /*ARGSUSED*/ 7703955d011SMarcel Moolenaar static Boolean 771956e45f6SSimon J. Gerraty FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) 7723955d011SMarcel Moolenaar { 773956e45f6SSimon J. Gerraty /* Magic values ahead, see ParseEmptyArg. */ 7743955d011SMarcel Moolenaar return arglen == 1; 7753955d011SMarcel Moolenaar } 7763955d011SMarcel Moolenaar 777e2eeea75SSimon J. Gerraty static Boolean 778e2eeea75SSimon J. Gerraty CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) 7793955d011SMarcel Moolenaar { 7803955d011SMarcel Moolenaar static const struct fn_def { 7813955d011SMarcel Moolenaar const char *fn_name; 7822c3632d1SSimon J. Gerraty size_t fn_name_len; 783*dba7b0efSSimon J. Gerraty size_t (*fn_parse)(CondParser *, const char **, Boolean, 784*dba7b0efSSimon J. Gerraty const char *, char **); 785956e45f6SSimon J. Gerraty Boolean (*fn_eval)(size_t, const char *); 786e2eeea75SSimon J. Gerraty } fns[] = { 787956e45f6SSimon J. Gerraty { "defined", 7, ParseFuncArg, FuncDefined }, 788956e45f6SSimon J. Gerraty { "make", 4, ParseFuncArg, FuncMake }, 789956e45f6SSimon J. Gerraty { "exists", 6, ParseFuncArg, FuncExists }, 790956e45f6SSimon J. Gerraty { "empty", 5, ParseEmptyArg, FuncEmpty }, 791956e45f6SSimon J. Gerraty { "target", 6, ParseFuncArg, FuncTarget }, 792e2eeea75SSimon J. Gerraty { "commands", 8, ParseFuncArg, FuncCommands } 7933955d011SMarcel Moolenaar }; 794e2eeea75SSimon J. Gerraty const struct fn_def *fn; 795e2eeea75SSimon J. Gerraty char *arg = NULL; 796e2eeea75SSimon J. Gerraty size_t arglen; 797e2eeea75SSimon J. Gerraty const char *cp = par->p; 798e2eeea75SSimon J. Gerraty const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0]; 799e2eeea75SSimon J. Gerraty 800e2eeea75SSimon J. Gerraty for (fn = fns; fn != fns_end; fn++) { 801e2eeea75SSimon J. Gerraty if (!is_token(cp, fn->fn_name, fn->fn_name_len)) 802e2eeea75SSimon J. Gerraty continue; 803e2eeea75SSimon J. Gerraty 804e2eeea75SSimon J. Gerraty cp += fn->fn_name_len; 805e2eeea75SSimon J. Gerraty cpp_skip_whitespace(&cp); 806e2eeea75SSimon J. Gerraty if (*cp != '(') 807e2eeea75SSimon J. Gerraty break; 808e2eeea75SSimon J. Gerraty 809*dba7b0efSSimon J. Gerraty arglen = fn->fn_parse(par, &cp, doEval, fn->fn_name, &arg); 810e2eeea75SSimon J. Gerraty if (arglen == 0 || arglen == (size_t)-1) { 811e2eeea75SSimon J. Gerraty par->p = cp; 812e2eeea75SSimon J. Gerraty *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR; 813e2eeea75SSimon J. Gerraty return TRUE; 814e2eeea75SSimon J. Gerraty } 815e2eeea75SSimon J. Gerraty 816e2eeea75SSimon J. Gerraty /* Evaluate the argument using the required function. */ 817e2eeea75SSimon J. Gerraty *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg)); 818e2eeea75SSimon J. Gerraty free(arg); 819e2eeea75SSimon J. Gerraty par->p = cp; 820e2eeea75SSimon J. Gerraty return TRUE; 821e2eeea75SSimon J. Gerraty } 822e2eeea75SSimon J. Gerraty 823e2eeea75SSimon J. Gerraty return FALSE; 824e2eeea75SSimon J. Gerraty } 825e2eeea75SSimon J. Gerraty 82606b9b3e0SSimon J. Gerraty /* 82706b9b3e0SSimon J. Gerraty * Parse a function call, a number, a variable expression or a string 82806b9b3e0SSimon J. Gerraty * literal. 82906b9b3e0SSimon J. Gerraty */ 830e2eeea75SSimon J. Gerraty static Token 831e2eeea75SSimon J. Gerraty CondParser_LeafToken(CondParser *par, Boolean doEval) 832e2eeea75SSimon J. Gerraty { 8333955d011SMarcel Moolenaar Token t; 8343955d011SMarcel Moolenaar char *arg = NULL; 835956e45f6SSimon J. Gerraty size_t arglen; 83606b9b3e0SSimon J. Gerraty const char *cp; 8372c3632d1SSimon J. Gerraty const char *cp1; 8383955d011SMarcel Moolenaar 839e2eeea75SSimon J. Gerraty if (CondParser_Func(par, doEval, &t)) 8403955d011SMarcel Moolenaar return t; 8413955d011SMarcel Moolenaar 8423955d011SMarcel Moolenaar /* Push anything numeric through the compare expression */ 843956e45f6SSimon J. Gerraty cp = par->p; 844e2eeea75SSimon J. Gerraty if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') 845956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 8463955d011SMarcel Moolenaar 8473955d011SMarcel Moolenaar /* 8483955d011SMarcel Moolenaar * Most likely we have a naked token to apply the default function to. 8493955d011SMarcel Moolenaar * However ".if a == b" gets here when the "a" is unquoted and doesn't 8503955d011SMarcel Moolenaar * start with a '$'. This surprises people. 85106b9b3e0SSimon J. Gerraty * If what follows the function argument is a '=' or '!' then the 85206b9b3e0SSimon J. Gerraty * syntax would be invalid if we did "defined(a)" - so instead treat 85306b9b3e0SSimon J. Gerraty * as an expression. 8543955d011SMarcel Moolenaar */ 855*dba7b0efSSimon J. Gerraty arglen = ParseFuncArg(par, &cp, doEval, NULL, &arg); 856956e45f6SSimon J. Gerraty cp1 = cp; 857956e45f6SSimon J. Gerraty cpp_skip_whitespace(&cp1); 8583955d011SMarcel Moolenaar if (*cp1 == '=' || *cp1 == '!') 859956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 860956e45f6SSimon J. Gerraty par->p = cp; 8613955d011SMarcel Moolenaar 8623955d011SMarcel Moolenaar /* 8633955d011SMarcel Moolenaar * Evaluate the argument using the default function. 864956e45f6SSimon J. Gerraty * This path always treats .if as .ifdef. To get here, the character 8653955d011SMarcel Moolenaar * after .if must have been taken literally, so the argument cannot 8663955d011SMarcel Moolenaar * be empty - even if it contained a variable expansion. 8673955d011SMarcel Moolenaar */ 868*dba7b0efSSimon J. Gerraty t = ToToken(!doEval || If_Eval(par, arg, arglen)); 8693955d011SMarcel Moolenaar free(arg); 8703955d011SMarcel Moolenaar return t; 8713955d011SMarcel Moolenaar } 8723955d011SMarcel Moolenaar 873956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */ 8743955d011SMarcel Moolenaar static Token 875956e45f6SSimon J. Gerraty CondParser_Token(CondParser *par, Boolean doEval) 8763955d011SMarcel Moolenaar { 8773955d011SMarcel Moolenaar Token t; 8783955d011SMarcel Moolenaar 879956e45f6SSimon J. Gerraty t = par->curr; 8803955d011SMarcel Moolenaar if (t != TOK_NONE) { 881956e45f6SSimon J. Gerraty par->curr = TOK_NONE; 8823955d011SMarcel Moolenaar return t; 8833955d011SMarcel Moolenaar } 8843955d011SMarcel Moolenaar 885e2eeea75SSimon J. Gerraty cpp_skip_hspace(&par->p); 8863955d011SMarcel Moolenaar 887956e45f6SSimon J. Gerraty switch (par->p[0]) { 8883955d011SMarcel Moolenaar 8893955d011SMarcel Moolenaar case '(': 890956e45f6SSimon J. Gerraty par->p++; 8913955d011SMarcel Moolenaar return TOK_LPAREN; 8923955d011SMarcel Moolenaar 8933955d011SMarcel Moolenaar case ')': 894956e45f6SSimon J. Gerraty par->p++; 8953955d011SMarcel Moolenaar return TOK_RPAREN; 8963955d011SMarcel Moolenaar 8973955d011SMarcel Moolenaar case '|': 898956e45f6SSimon J. Gerraty par->p++; 899e2eeea75SSimon J. Gerraty if (par->p[0] == '|') 900956e45f6SSimon J. Gerraty par->p++; 90106b9b3e0SSimon J. Gerraty else if (opts.strict) { 902e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '|'"); 903e2eeea75SSimon J. Gerraty par->printedError = TRUE; 904e2eeea75SSimon J. Gerraty return TOK_ERROR; 9053955d011SMarcel Moolenaar } 9063955d011SMarcel Moolenaar return TOK_OR; 9073955d011SMarcel Moolenaar 9083955d011SMarcel Moolenaar case '&': 909956e45f6SSimon J. Gerraty par->p++; 910e2eeea75SSimon J. Gerraty if (par->p[0] == '&') 911956e45f6SSimon J. Gerraty par->p++; 91206b9b3e0SSimon J. Gerraty else if (opts.strict) { 913e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '&'"); 914e2eeea75SSimon J. Gerraty par->printedError = TRUE; 915e2eeea75SSimon J. Gerraty return TOK_ERROR; 9163955d011SMarcel Moolenaar } 9173955d011SMarcel Moolenaar return TOK_AND; 9183955d011SMarcel Moolenaar 9193955d011SMarcel Moolenaar case '!': 920956e45f6SSimon J. Gerraty par->p++; 9213955d011SMarcel Moolenaar return TOK_NOT; 9223955d011SMarcel Moolenaar 923e2eeea75SSimon J. Gerraty case '#': /* XXX: see unit-tests/cond-token-plain.mk */ 924e2eeea75SSimon J. Gerraty case '\n': /* XXX: why should this end the condition? */ 925e2eeea75SSimon J. Gerraty /* Probably obsolete now, from 1993-03-21. */ 9263955d011SMarcel Moolenaar case '\0': 9273955d011SMarcel Moolenaar return TOK_EOF; 9283955d011SMarcel Moolenaar 9293955d011SMarcel Moolenaar case '"': 9303955d011SMarcel Moolenaar case '$': 931956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 9323955d011SMarcel Moolenaar 9333955d011SMarcel Moolenaar default: 934e2eeea75SSimon J. Gerraty return CondParser_LeafToken(par, doEval); 9353955d011SMarcel Moolenaar } 9363955d011SMarcel Moolenaar } 9373955d011SMarcel Moolenaar 93806b9b3e0SSimon J. Gerraty /* 939*dba7b0efSSimon J. Gerraty * Term -> '(' Or ')' 940*dba7b0efSSimon J. Gerraty * Term -> '!' Term 941*dba7b0efSSimon J. Gerraty * Term -> Leaf Operator Leaf 942*dba7b0efSSimon J. Gerraty * Term -> Leaf 9433955d011SMarcel Moolenaar */ 944*dba7b0efSSimon J. Gerraty static CondResult 945956e45f6SSimon J. Gerraty CondParser_Term(CondParser *par, Boolean doEval) 9463955d011SMarcel Moolenaar { 947*dba7b0efSSimon J. Gerraty CondResult res; 9483955d011SMarcel Moolenaar Token t; 9493955d011SMarcel Moolenaar 950956e45f6SSimon J. Gerraty t = CondParser_Token(par, doEval); 951*dba7b0efSSimon J. Gerraty if (t == TOK_TRUE) 952*dba7b0efSSimon J. Gerraty return CR_TRUE; 953*dba7b0efSSimon J. Gerraty if (t == TOK_FALSE) 954*dba7b0efSSimon J. Gerraty return CR_FALSE; 9553955d011SMarcel Moolenaar 956*dba7b0efSSimon J. Gerraty if (t == TOK_LPAREN) { 957*dba7b0efSSimon J. Gerraty res = CondParser_Or(par, doEval); 958*dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 959*dba7b0efSSimon J. Gerraty return CR_ERROR; 960*dba7b0efSSimon J. Gerraty if (CondParser_Token(par, doEval) != TOK_RPAREN) 961*dba7b0efSSimon J. Gerraty return CR_ERROR; 962*dba7b0efSSimon J. Gerraty return res; 9633955d011SMarcel Moolenaar } 964*dba7b0efSSimon J. Gerraty 965*dba7b0efSSimon J. Gerraty if (t == TOK_NOT) { 966*dba7b0efSSimon J. Gerraty res = CondParser_Term(par, doEval); 967*dba7b0efSSimon J. Gerraty if (res == CR_TRUE) 968*dba7b0efSSimon J. Gerraty res = CR_FALSE; 969*dba7b0efSSimon J. Gerraty else if (res == CR_FALSE) 970*dba7b0efSSimon J. Gerraty res = CR_TRUE; 971*dba7b0efSSimon J. Gerraty return res; 9723955d011SMarcel Moolenaar } 973*dba7b0efSSimon J. Gerraty 974*dba7b0efSSimon J. Gerraty return CR_ERROR; 9753955d011SMarcel Moolenaar } 9762c3632d1SSimon J. Gerraty 97706b9b3e0SSimon J. Gerraty /* 978*dba7b0efSSimon J. Gerraty * And -> Term '&&' And 979*dba7b0efSSimon J. Gerraty * And -> Term 9803955d011SMarcel Moolenaar */ 981*dba7b0efSSimon J. Gerraty static CondResult 982*dba7b0efSSimon J. Gerraty CondParser_And(CondParser *par, Boolean doEval) 9833955d011SMarcel Moolenaar { 984*dba7b0efSSimon J. Gerraty CondResult res; 985*dba7b0efSSimon J. Gerraty Token op; 9863955d011SMarcel Moolenaar 987*dba7b0efSSimon J. Gerraty res = CondParser_Term(par, doEval); 988*dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 989*dba7b0efSSimon J. Gerraty return CR_ERROR; 9903955d011SMarcel Moolenaar 991*dba7b0efSSimon J. Gerraty op = CondParser_Token(par, doEval); 992*dba7b0efSSimon J. Gerraty if (op == TOK_AND) { 993*dba7b0efSSimon J. Gerraty if (res == CR_TRUE) 994*dba7b0efSSimon J. Gerraty return CondParser_And(par, doEval); 995*dba7b0efSSimon J. Gerraty if (CondParser_And(par, FALSE) == CR_ERROR) 996*dba7b0efSSimon J. Gerraty return CR_ERROR; 997*dba7b0efSSimon J. Gerraty return res; 9983955d011SMarcel Moolenaar } 999*dba7b0efSSimon J. Gerraty 1000*dba7b0efSSimon J. Gerraty CondParser_PushBack(par, op); 1001*dba7b0efSSimon J. Gerraty return res; 10023955d011SMarcel Moolenaar } 10032c3632d1SSimon J. Gerraty 100406b9b3e0SSimon J. Gerraty /* 1005*dba7b0efSSimon J. Gerraty * Or -> And '||' Or 1006*dba7b0efSSimon J. Gerraty * Or -> And 10073955d011SMarcel Moolenaar */ 1008*dba7b0efSSimon J. Gerraty static CondResult 1009*dba7b0efSSimon J. Gerraty CondParser_Or(CondParser *par, Boolean doEval) 10103955d011SMarcel Moolenaar { 1011*dba7b0efSSimon J. Gerraty CondResult res; 1012*dba7b0efSSimon J. Gerraty Token op; 10133955d011SMarcel Moolenaar 1014*dba7b0efSSimon J. Gerraty res = CondParser_And(par, doEval); 1015*dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 1016*dba7b0efSSimon J. Gerraty return CR_ERROR; 10173955d011SMarcel Moolenaar 1018*dba7b0efSSimon J. Gerraty op = CondParser_Token(par, doEval); 1019*dba7b0efSSimon J. Gerraty if (op == TOK_OR) { 1020*dba7b0efSSimon J. Gerraty if (res == CR_FALSE) 1021*dba7b0efSSimon J. Gerraty return CondParser_Or(par, doEval); 1022*dba7b0efSSimon J. Gerraty if (CondParser_Or(par, FALSE) == CR_ERROR) 1023*dba7b0efSSimon J. Gerraty return CR_ERROR; 1024*dba7b0efSSimon J. Gerraty return res; 10253955d011SMarcel Moolenaar } 1026*dba7b0efSSimon J. Gerraty 1027*dba7b0efSSimon J. Gerraty CondParser_PushBack(par, op); 1028*dba7b0efSSimon J. Gerraty return res; 10293955d011SMarcel Moolenaar } 10303955d011SMarcel Moolenaar 10312c3632d1SSimon J. Gerraty static CondEvalResult 1032*dba7b0efSSimon J. Gerraty CondParser_Eval(CondParser *par, Boolean *out_value) 10332c3632d1SSimon J. Gerraty { 1034*dba7b0efSSimon J. Gerraty CondResult res; 10352c3632d1SSimon J. Gerraty 1036956e45f6SSimon J. Gerraty DEBUG1(COND, "CondParser_Eval: %s\n", par->p); 10372c3632d1SSimon J. Gerraty 1038*dba7b0efSSimon J. Gerraty res = CondParser_Or(par, TRUE); 1039*dba7b0efSSimon J. Gerraty if (res == CR_ERROR) 10402c3632d1SSimon J. Gerraty return COND_INVALID; 1041956e45f6SSimon J. Gerraty 104206b9b3e0SSimon J. Gerraty if (CondParser_Token(par, FALSE) != TOK_EOF) 1043956e45f6SSimon J. Gerraty return COND_INVALID; 1044956e45f6SSimon J. Gerraty 1045*dba7b0efSSimon J. Gerraty *out_value = res == CR_TRUE; 1046956e45f6SSimon J. Gerraty return COND_PARSE; 10472c3632d1SSimon J. Gerraty } 10482c3632d1SSimon J. Gerraty 104906b9b3e0SSimon J. Gerraty /* 105006b9b3e0SSimon J. Gerraty * Evaluate the condition, including any side effects from the variable 1051956e45f6SSimon J. Gerraty * expressions in the condition. The condition consists of &&, ||, !, 1052956e45f6SSimon J. Gerraty * function(arg), comparisons and parenthetical groupings thereof. 10533955d011SMarcel Moolenaar * 10543955d011SMarcel Moolenaar * Results: 10553955d011SMarcel Moolenaar * COND_PARSE if the condition was valid grammatically 10563955d011SMarcel Moolenaar * COND_INVALID if not a valid conditional. 10573955d011SMarcel Moolenaar * 10583955d011SMarcel Moolenaar * (*value) is set to the boolean value of the condition 10593955d011SMarcel Moolenaar */ 1060956e45f6SSimon J. Gerraty static CondEvalResult 1061*dba7b0efSSimon J. Gerraty CondEvalExpression(const char *cond, Boolean *out_value, Boolean plain, 1062*dba7b0efSSimon J. Gerraty Boolean (*evalBare)(size_t, const char *), Boolean negate, 1063956e45f6SSimon J. Gerraty Boolean eprint, Boolean strictLHS) 10643955d011SMarcel Moolenaar { 1065956e45f6SSimon J. Gerraty CondParser par; 1066e2eeea75SSimon J. Gerraty CondEvalResult rval; 10673955d011SMarcel Moolenaar 106828a6bc81SSimon J. Gerraty lhsStrict = strictLHS; 106928a6bc81SSimon J. Gerraty 1070e2eeea75SSimon J. Gerraty cpp_skip_hspace(&cond); 10713955d011SMarcel Moolenaar 1072*dba7b0efSSimon J. Gerraty par.plain = plain; 1073*dba7b0efSSimon J. Gerraty par.evalBare = evalBare; 1074*dba7b0efSSimon J. Gerraty par.negateEvalBare = negate; 1075956e45f6SSimon J. Gerraty par.p = cond; 1076956e45f6SSimon J. Gerraty par.curr = TOK_NONE; 1077956e45f6SSimon J. Gerraty par.printedError = FALSE; 10783955d011SMarcel Moolenaar 1079*dba7b0efSSimon J. Gerraty rval = CondParser_Eval(&par, out_value); 10803955d011SMarcel Moolenaar 1081956e45f6SSimon J. Gerraty if (rval == COND_INVALID && eprint && !par.printedError) 1082956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); 10833955d011SMarcel Moolenaar 10843955d011SMarcel Moolenaar return rval; 10853955d011SMarcel Moolenaar } 10863955d011SMarcel Moolenaar 108706b9b3e0SSimon J. Gerraty /* 108806b9b3e0SSimon J. Gerraty * Evaluate a condition in a :? modifier, such as 108906b9b3e0SSimon J. Gerraty * ${"${VAR}" == value:?yes:no}. 109006b9b3e0SSimon J. Gerraty */ 1091956e45f6SSimon J. Gerraty CondEvalResult 1092956e45f6SSimon J. Gerraty Cond_EvalCondition(const char *cond, Boolean *out_value) 1093956e45f6SSimon J. Gerraty { 1094*dba7b0efSSimon J. Gerraty return CondEvalExpression(cond, out_value, TRUE, 1095*dba7b0efSSimon J. Gerraty FuncDefined, FALSE, FALSE, FALSE); 1096956e45f6SSimon J. Gerraty } 10973955d011SMarcel Moolenaar 109806b9b3e0SSimon J. Gerraty static Boolean 109906b9b3e0SSimon J. Gerraty IsEndif(const char *p) 110006b9b3e0SSimon J. Gerraty { 110106b9b3e0SSimon J. Gerraty return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && 110206b9b3e0SSimon J. Gerraty p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); 110306b9b3e0SSimon J. Gerraty } 110406b9b3e0SSimon J. Gerraty 1105*dba7b0efSSimon J. Gerraty static Boolean 1106*dba7b0efSSimon J. Gerraty DetermineKindOfConditional(const char **pp, Boolean *out_plain, 1107*dba7b0efSSimon J. Gerraty Boolean (**out_evalBare)(size_t, const char *), 1108*dba7b0efSSimon J. Gerraty Boolean *out_negate) 1109*dba7b0efSSimon J. Gerraty { 1110*dba7b0efSSimon J. Gerraty const char *p = *pp; 1111*dba7b0efSSimon J. Gerraty 1112*dba7b0efSSimon J. Gerraty p += 2; 1113*dba7b0efSSimon J. Gerraty *out_plain = FALSE; 1114*dba7b0efSSimon J. Gerraty *out_evalBare = FuncDefined; 1115*dba7b0efSSimon J. Gerraty *out_negate = FALSE; 1116*dba7b0efSSimon J. Gerraty if (*p == 'n') { 1117*dba7b0efSSimon J. Gerraty p++; 1118*dba7b0efSSimon J. Gerraty *out_negate = TRUE; 1119*dba7b0efSSimon J. Gerraty } 1120*dba7b0efSSimon J. Gerraty if (is_token(p, "def", 3)) { /* .ifdef and .ifndef */ 1121*dba7b0efSSimon J. Gerraty p += 3; 1122*dba7b0efSSimon J. Gerraty } else if (is_token(p, "make", 4)) { /* .ifmake and .ifnmake */ 1123*dba7b0efSSimon J. Gerraty p += 4; 1124*dba7b0efSSimon J. Gerraty *out_evalBare = FuncMake; 1125*dba7b0efSSimon J. Gerraty } else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */ 1126*dba7b0efSSimon J. Gerraty *out_plain = TRUE; 1127*dba7b0efSSimon J. Gerraty } else { 1128*dba7b0efSSimon J. Gerraty /* 1129*dba7b0efSSimon J. Gerraty * TODO: Add error message about unknown directive, 1130*dba7b0efSSimon J. Gerraty * since there is no other known directive that starts 1131*dba7b0efSSimon J. Gerraty * with 'el' or 'if'. 1132*dba7b0efSSimon J. Gerraty * 1133*dba7b0efSSimon J. Gerraty * Example: .elifx 123 1134*dba7b0efSSimon J. Gerraty */ 1135*dba7b0efSSimon J. Gerraty return FALSE; 1136*dba7b0efSSimon J. Gerraty } 1137*dba7b0efSSimon J. Gerraty 1138*dba7b0efSSimon J. Gerraty *pp = p; 1139*dba7b0efSSimon J. Gerraty return TRUE; 1140*dba7b0efSSimon J. Gerraty } 1141*dba7b0efSSimon J. Gerraty 114206b9b3e0SSimon J. Gerraty /* 114306b9b3e0SSimon J. Gerraty * Evaluate the conditional directive in the line, which is one of: 11443955d011SMarcel Moolenaar * 1145e2eeea75SSimon J. Gerraty * .if <cond> 1146e2eeea75SSimon J. Gerraty * .ifmake <cond> 1147e2eeea75SSimon J. Gerraty * .ifnmake <cond> 1148e2eeea75SSimon J. Gerraty * .ifdef <cond> 1149e2eeea75SSimon J. Gerraty * .ifndef <cond> 1150e2eeea75SSimon J. Gerraty * .elif <cond> 1151e2eeea75SSimon J. Gerraty * .elifmake <cond> 1152e2eeea75SSimon J. Gerraty * .elifnmake <cond> 1153e2eeea75SSimon J. Gerraty * .elifdef <cond> 1154e2eeea75SSimon J. Gerraty * .elifndef <cond> 1155e2eeea75SSimon J. Gerraty * .else 1156e2eeea75SSimon J. Gerraty * .endif 1157e2eeea75SSimon J. Gerraty * 1158e2eeea75SSimon J. Gerraty * In these directives, <cond> consists of &&, ||, !, function(arg), 1159e2eeea75SSimon J. Gerraty * comparisons, expressions, bare words, numbers and strings, and 1160e2eeea75SSimon J. Gerraty * parenthetical groupings thereof. 1161956e45f6SSimon J. Gerraty * 1162956e45f6SSimon J. Gerraty * Results: 1163e2eeea75SSimon J. Gerraty * COND_PARSE to continue parsing the lines that follow the 1164e2eeea75SSimon J. Gerraty * conditional (when <cond> evaluates to TRUE) 1165956e45f6SSimon J. Gerraty * COND_SKIP to skip the lines after the conditional 1166e2eeea75SSimon J. Gerraty * (when <cond> evaluates to FALSE, or when a previous 1167956e45f6SSimon J. Gerraty * branch has already been taken) 1168956e45f6SSimon J. Gerraty * COND_INVALID if the conditional was not valid, either because of 1169956e45f6SSimon J. Gerraty * a syntax error or because some variable was undefined 1170956e45f6SSimon J. Gerraty * or because the condition could not be evaluated 11713955d011SMarcel Moolenaar */ 11722c3632d1SSimon J. Gerraty CondEvalResult 117306b9b3e0SSimon J. Gerraty Cond_EvalLine(const char *line) 11743955d011SMarcel Moolenaar { 1175e2eeea75SSimon J. Gerraty typedef enum IfState { 1176e2eeea75SSimon J. Gerraty 1177e2eeea75SSimon J. Gerraty /* None of the previous <cond> evaluated to TRUE. */ 1178e2eeea75SSimon J. Gerraty IFS_INITIAL = 0, 1179e2eeea75SSimon J. Gerraty 1180e2eeea75SSimon J. Gerraty /* The previous <cond> evaluated to TRUE. 1181e2eeea75SSimon J. Gerraty * The lines following this condition are interpreted. */ 1182e2eeea75SSimon J. Gerraty IFS_ACTIVE = 1 << 0, 1183e2eeea75SSimon J. Gerraty 1184e2eeea75SSimon J. Gerraty /* The previous directive was an '.else'. */ 1185e2eeea75SSimon J. Gerraty IFS_SEEN_ELSE = 1 << 1, 1186e2eeea75SSimon J. Gerraty 1187e2eeea75SSimon J. Gerraty /* One of the previous <cond> evaluated to TRUE. */ 1188e2eeea75SSimon J. Gerraty IFS_WAS_ACTIVE = 1 << 2 1189e2eeea75SSimon J. Gerraty 1190e2eeea75SSimon J. Gerraty } IfState; 1191e2eeea75SSimon J. Gerraty 1192e2eeea75SSimon J. Gerraty static enum IfState *cond_states = NULL; 1193e2eeea75SSimon J. Gerraty static unsigned int cond_states_cap = 128; 11943955d011SMarcel Moolenaar 1195*dba7b0efSSimon J. Gerraty Boolean plain; 1196*dba7b0efSSimon J. Gerraty Boolean (*evalBare)(size_t, const char *); 1197*dba7b0efSSimon J. Gerraty Boolean negate; 11983955d011SMarcel Moolenaar Boolean isElif; 11993955d011SMarcel Moolenaar Boolean value; 1200e2eeea75SSimon J. Gerraty IfState state; 1201e2eeea75SSimon J. Gerraty const char *p = line; 12023955d011SMarcel Moolenaar 1203e2eeea75SSimon J. Gerraty if (cond_states == NULL) { 120406b9b3e0SSimon J. Gerraty cond_states = bmake_malloc( 120506b9b3e0SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 1206e2eeea75SSimon J. Gerraty cond_states[0] = IFS_ACTIVE; 120759a02420SSimon J. Gerraty } 12083955d011SMarcel Moolenaar 1209e2eeea75SSimon J. Gerraty p++; /* skip the leading '.' */ 1210e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 1211e2eeea75SSimon J. Gerraty 121206b9b3e0SSimon J. Gerraty if (IsEndif(p)) { /* It is an '.endif'. */ 121306b9b3e0SSimon J. Gerraty if (p[5] != '\0') { 121406b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, 121506b9b3e0SSimon J. Gerraty "The .endif directive does not take arguments."); 1216e2eeea75SSimon J. Gerraty } 1217e2eeea75SSimon J. Gerraty 12183955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1219956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less endif"); 12203955d011SMarcel Moolenaar return COND_PARSE; 12213955d011SMarcel Moolenaar } 1222e2eeea75SSimon J. Gerraty 12233955d011SMarcel Moolenaar /* Return state for previous conditional */ 12243955d011SMarcel Moolenaar cond_depth--; 1225e2eeea75SSimon J. Gerraty return cond_states[cond_depth] & IFS_ACTIVE 12262c3632d1SSimon J. Gerraty ? COND_PARSE : COND_SKIP; 12273955d011SMarcel Moolenaar } 12283955d011SMarcel Moolenaar 122906b9b3e0SSimon J. Gerraty /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ 123006b9b3e0SSimon J. Gerraty if (p[0] == 'e') { 123106b9b3e0SSimon J. Gerraty if (p[1] != 'l') { 123206b9b3e0SSimon J. Gerraty /* 123306b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a 123406b9b3e0SSimon J. Gerraty * transformation rule like '.elisp.scm', 123506b9b3e0SSimon J. Gerraty * therefore no error message here. 123606b9b3e0SSimon J. Gerraty */ 123706b9b3e0SSimon J. Gerraty return COND_INVALID; 123806b9b3e0SSimon J. Gerraty } 123906b9b3e0SSimon J. Gerraty 12403955d011SMarcel Moolenaar /* Quite likely this is 'else' or 'elif' */ 1241e2eeea75SSimon J. Gerraty p += 2; 1242e2eeea75SSimon J. Gerraty if (is_token(p, "se", 2)) { /* It is an 'else'. */ 1243e2eeea75SSimon J. Gerraty 124406b9b3e0SSimon J. Gerraty if (p[2] != '\0') 1245e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, 124606b9b3e0SSimon J. Gerraty "The .else directive " 124706b9b3e0SSimon J. Gerraty "does not take arguments."); 1248e2eeea75SSimon J. Gerraty 12493955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1250956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less else"); 12513955d011SMarcel Moolenaar return COND_PARSE; 12523955d011SMarcel Moolenaar } 12533955d011SMarcel Moolenaar 1254e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1255e2eeea75SSimon J. Gerraty if (state == IFS_INITIAL) { 1256e2eeea75SSimon J. Gerraty state = IFS_ACTIVE | IFS_SEEN_ELSE; 1257e2eeea75SSimon J. Gerraty } else { 1258e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) 125906b9b3e0SSimon J. Gerraty Parse_Error(PARSE_WARNING, 126006b9b3e0SSimon J. Gerraty "extra else"); 1261e2eeea75SSimon J. Gerraty state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 12623955d011SMarcel Moolenaar } 1263e2eeea75SSimon J. Gerraty cond_states[cond_depth] = state; 1264e2eeea75SSimon J. Gerraty 1265e2eeea75SSimon J. Gerraty return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; 12663955d011SMarcel Moolenaar } 12673955d011SMarcel Moolenaar /* Assume for now it is an elif */ 12683955d011SMarcel Moolenaar isElif = TRUE; 12693955d011SMarcel Moolenaar } else 12703955d011SMarcel Moolenaar isElif = FALSE; 12713955d011SMarcel Moolenaar 1272e2eeea75SSimon J. Gerraty if (p[0] != 'i' || p[1] != 'f') { 127306b9b3e0SSimon J. Gerraty /* 127406b9b3e0SSimon J. Gerraty * Unknown directive. It might still be a transformation rule 127506b9b3e0SSimon J. Gerraty * like '.elisp.scm', therefore no error message here. 127606b9b3e0SSimon J. Gerraty */ 1277e2eeea75SSimon J. Gerraty return COND_INVALID; /* Not an ifxxx or elifxxx line */ 1278e2eeea75SSimon J. Gerraty } 12793955d011SMarcel Moolenaar 1280*dba7b0efSSimon J. Gerraty if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) 12813955d011SMarcel Moolenaar return COND_INVALID; 12823955d011SMarcel Moolenaar 12833955d011SMarcel Moolenaar if (isElif) { 12843955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1285956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less elif"); 12863955d011SMarcel Moolenaar return COND_PARSE; 12873955d011SMarcel Moolenaar } 1288e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1289e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) { 12903955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "extra elif"); 129106b9b3e0SSimon J. Gerraty cond_states[cond_depth] = 129206b9b3e0SSimon J. Gerraty IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 12933955d011SMarcel Moolenaar return COND_SKIP; 12943955d011SMarcel Moolenaar } 1295e2eeea75SSimon J. Gerraty if (state != IFS_INITIAL) { 1296e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12973955d011SMarcel Moolenaar return COND_SKIP; 12983955d011SMarcel Moolenaar } 12993955d011SMarcel Moolenaar } else { 13003955d011SMarcel Moolenaar /* Normal .if */ 1301e2eeea75SSimon J. Gerraty if (cond_depth + 1 >= cond_states_cap) { 130259a02420SSimon J. Gerraty /* 130359a02420SSimon J. Gerraty * This is rare, but not impossible. 130459a02420SSimon J. Gerraty * In meta mode, dirdeps.mk (only runs at level 0) 130559a02420SSimon J. Gerraty * can need more than the default. 130659a02420SSimon J. Gerraty */ 1307e2eeea75SSimon J. Gerraty cond_states_cap += 32; 1308e2eeea75SSimon J. Gerraty cond_states = bmake_realloc(cond_states, 130906b9b3e0SSimon J. Gerraty cond_states_cap * 131006b9b3e0SSimon J. Gerraty sizeof *cond_states); 13113955d011SMarcel Moolenaar } 1312e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 13133955d011SMarcel Moolenaar cond_depth++; 1314e2eeea75SSimon J. Gerraty if (!(state & IFS_ACTIVE)) { 131506b9b3e0SSimon J. Gerraty /* 131606b9b3e0SSimon J. Gerraty * If we aren't parsing the data, 131706b9b3e0SSimon J. Gerraty * treat as always false. 131806b9b3e0SSimon J. Gerraty */ 1319e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 13203955d011SMarcel Moolenaar return COND_SKIP; 13213955d011SMarcel Moolenaar } 13223955d011SMarcel Moolenaar } 13233955d011SMarcel Moolenaar 1324956e45f6SSimon J. Gerraty /* And evaluate the conditional expression */ 1325*dba7b0efSSimon J. Gerraty if (CondEvalExpression(p, &value, plain, evalBare, negate, 1326*dba7b0efSSimon J. Gerraty TRUE, TRUE) == COND_INVALID) { 13273955d011SMarcel Moolenaar /* Syntax error in conditional, error message already output. */ 13283955d011SMarcel Moolenaar /* Skip everything to matching .endif */ 1329e2eeea75SSimon J. Gerraty /* XXX: An extra '.else' is not detected in this case. */ 1330e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 13313955d011SMarcel Moolenaar return COND_SKIP; 13323955d011SMarcel Moolenaar } 13333955d011SMarcel Moolenaar 13343955d011SMarcel Moolenaar if (!value) { 1335e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_INITIAL; 13363955d011SMarcel Moolenaar return COND_SKIP; 13373955d011SMarcel Moolenaar } 1338e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_ACTIVE; 13393955d011SMarcel Moolenaar return COND_PARSE; 13403955d011SMarcel Moolenaar } 13413955d011SMarcel Moolenaar 13423955d011SMarcel Moolenaar void 13433955d011SMarcel Moolenaar Cond_restore_depth(unsigned int saved_depth) 13443955d011SMarcel Moolenaar { 1345956e45f6SSimon J. Gerraty unsigned int open_conds = cond_depth - cond_min_depth; 13463955d011SMarcel Moolenaar 13473955d011SMarcel Moolenaar if (open_conds != 0 || saved_depth > cond_depth) { 134806b9b3e0SSimon J. Gerraty Parse_Error(PARSE_FATAL, "%u open conditional%s", 134906b9b3e0SSimon J. Gerraty open_conds, open_conds == 1 ? "" : "s"); 13503955d011SMarcel Moolenaar cond_depth = cond_min_depth; 13513955d011SMarcel Moolenaar } 13523955d011SMarcel Moolenaar 13533955d011SMarcel Moolenaar cond_min_depth = saved_depth; 13543955d011SMarcel Moolenaar } 13553955d011SMarcel Moolenaar 13563955d011SMarcel Moolenaar unsigned int 13573955d011SMarcel Moolenaar Cond_save_depth(void) 13583955d011SMarcel Moolenaar { 1359956e45f6SSimon J. Gerraty unsigned int depth = cond_min_depth; 13603955d011SMarcel Moolenaar 13613955d011SMarcel Moolenaar cond_min_depth = cond_depth; 13623955d011SMarcel Moolenaar return depth; 13633955d011SMarcel Moolenaar } 1364