1*e2eeea75SSimon J. Gerraty /* $NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 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 72956e45f6SSimon J. Gerraty /* Handling of conditionals in a makefile. 733955d011SMarcel Moolenaar * 743955d011SMarcel Moolenaar * Interface: 75*e2eeea75SSimon J. Gerraty * Cond_EvalLine Evaluate the conditional directive, such as 76*e2eeea75SSimon J. Gerraty * '.if <cond>', '.elifnmake <cond>', '.else', '.endif'. 773955d011SMarcel Moolenaar * 78956e45f6SSimon J. Gerraty * Cond_EvalCondition 79956e45f6SSimon J. Gerraty * Evaluate the conditional, which is either the argument 80956e45f6SSimon J. Gerraty * of one of the .if directives or the condition in a 81956e45f6SSimon J. Gerraty * ':?then:else' variable modifier. 82956e45f6SSimon J. Gerraty * 83956e45f6SSimon J. Gerraty * Cond_save_depth 84956e45f6SSimon J. Gerraty * Cond_restore_depth 85956e45f6SSimon J. Gerraty * Save and restore the nesting of the conditions, at 86956e45f6SSimon J. Gerraty * the start and end of including another makefile, to 87956e45f6SSimon J. Gerraty * ensure that in each makefile the conditional 88956e45f6SSimon J. Gerraty * directives are well-balanced. 893955d011SMarcel Moolenaar */ 903955d011SMarcel Moolenaar 912c3632d1SSimon J. Gerraty #include <errno.h> 923955d011SMarcel Moolenaar 933955d011SMarcel Moolenaar #include "make.h" 943955d011SMarcel Moolenaar #include "dir.h" 953955d011SMarcel Moolenaar 96956e45f6SSimon J. Gerraty /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ 97*e2eeea75SSimon J. Gerraty MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $"); 98956e45f6SSimon J. Gerraty 993955d011SMarcel Moolenaar /* 1003955d011SMarcel Moolenaar * The parsing of conditional expressions is based on this grammar: 1013955d011SMarcel Moolenaar * E -> F || E 1023955d011SMarcel Moolenaar * E -> F 1033955d011SMarcel Moolenaar * F -> T && F 1043955d011SMarcel Moolenaar * F -> T 1053955d011SMarcel Moolenaar * T -> defined(variable) 1063955d011SMarcel Moolenaar * T -> make(target) 1073955d011SMarcel Moolenaar * T -> exists(file) 1083955d011SMarcel Moolenaar * T -> empty(varspec) 1093955d011SMarcel Moolenaar * T -> target(name) 1103955d011SMarcel Moolenaar * T -> commands(name) 1113955d011SMarcel Moolenaar * T -> symbol 1123955d011SMarcel Moolenaar * T -> $(varspec) op value 1133955d011SMarcel Moolenaar * T -> $(varspec) == "string" 1143955d011SMarcel Moolenaar * T -> $(varspec) != "string" 1153955d011SMarcel Moolenaar * T -> "string" 1163955d011SMarcel Moolenaar * T -> ( E ) 1173955d011SMarcel Moolenaar * T -> ! T 1183955d011SMarcel Moolenaar * op -> == | != | > | < | >= | <= 1193955d011SMarcel Moolenaar * 1202c3632d1SSimon J. Gerraty * 'symbol' is some other symbol to which the default function is applied. 1213955d011SMarcel Moolenaar * 122956e45f6SSimon J. Gerraty * The tokens are scanned by CondToken, which returns: 123956e45f6SSimon J. Gerraty * TOK_AND for '&' or '&&' 124956e45f6SSimon J. Gerraty * TOK_OR for '|' or '||' 125956e45f6SSimon J. Gerraty * TOK_NOT for '!' 126956e45f6SSimon J. Gerraty * TOK_LPAREN for '(' 127956e45f6SSimon J. Gerraty * TOK_RPAREN for ')' 128956e45f6SSimon J. Gerraty * Other terminal symbols are evaluated using either the default function or 129956e45f6SSimon J. Gerraty * the function given in the terminal, they return either TOK_TRUE or 130956e45f6SSimon J. Gerraty * TOK_FALSE. 1313955d011SMarcel Moolenaar * 1323955d011SMarcel Moolenaar * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons. 1333955d011SMarcel Moolenaar * 134956e45f6SSimon J. Gerraty * All non-terminal functions (CondParser_Expr, CondParser_Factor and 135956e45f6SSimon J. Gerraty * CondParser_Term) return either TOK_FALSE, TOK_TRUE, or TOK_ERROR on error. 1363955d011SMarcel Moolenaar */ 137956e45f6SSimon J. Gerraty typedef enum Token { 1383955d011SMarcel Moolenaar TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT, 1393955d011SMarcel Moolenaar TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR 1403955d011SMarcel Moolenaar } Token; 1413955d011SMarcel Moolenaar 142956e45f6SSimon J. Gerraty typedef struct CondParser { 143956e45f6SSimon J. Gerraty const struct If *if_info; /* Info for current statement */ 144956e45f6SSimon J. Gerraty const char *p; /* The remaining condition to parse */ 145956e45f6SSimon J. Gerraty Token curr; /* Single push-back token used in parsing */ 1463955d011SMarcel Moolenaar 147956e45f6SSimon J. Gerraty /* Whether an error message has already been printed for this condition. 148956e45f6SSimon J. Gerraty * The first available error message is usually the most specific one, 149956e45f6SSimon J. Gerraty * therefore it makes sense to suppress the standard "Malformed 150956e45f6SSimon J. Gerraty * conditional" message. */ 151956e45f6SSimon J. Gerraty Boolean printedError; 152956e45f6SSimon J. Gerraty } CondParser; 153956e45f6SSimon J. Gerraty 154956e45f6SSimon J. Gerraty static Token CondParser_Expr(CondParser *par, Boolean); 1553955d011SMarcel Moolenaar 1563955d011SMarcel Moolenaar static unsigned int cond_depth = 0; /* current .if nesting level */ 1573955d011SMarcel Moolenaar static unsigned int cond_min_depth = 0; /* depth at makefile open */ 1583955d011SMarcel Moolenaar 15928a6bc81SSimon J. Gerraty /* 16028a6bc81SSimon J. Gerraty * Indicate when we should be strict about lhs of comparisons. 161956e45f6SSimon J. Gerraty * In strict mode, the lhs must be a variable expression or a string literal 162956e45f6SSimon J. Gerraty * in quotes. In non-strict mode it may also be an unquoted string literal. 163956e45f6SSimon J. Gerraty * 164956e45f6SSimon J. Gerraty * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) 165956e45f6SSimon J. Gerraty * FALSE when CondEvalExpression is called from ApplyModifier_IfElse 166*e2eeea75SSimon J. Gerraty * since lhs is already expanded, and at that point we cannot tell if 16728a6bc81SSimon J. Gerraty * it was a variable reference or not. 16828a6bc81SSimon J. Gerraty */ 16928a6bc81SSimon J. Gerraty static Boolean lhsStrict; 17028a6bc81SSimon J. Gerraty 1713955d011SMarcel Moolenaar static int 172956e45f6SSimon J. Gerraty is_token(const char *str, const char *tok, size_t len) 1733955d011SMarcel Moolenaar { 174956e45f6SSimon J. Gerraty return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); 1753955d011SMarcel Moolenaar } 1763955d011SMarcel Moolenaar 177*e2eeea75SSimon J. Gerraty static Token 178*e2eeea75SSimon J. Gerraty ToToken(Boolean cond) 179*e2eeea75SSimon J. Gerraty { 180*e2eeea75SSimon J. Gerraty return cond ? TOK_TRUE : TOK_FALSE; 181*e2eeea75SSimon J. Gerraty } 182*e2eeea75SSimon J. Gerraty 183956e45f6SSimon J. Gerraty /* Push back the most recent token read. We only need one level of this. */ 1843955d011SMarcel Moolenaar static void 185956e45f6SSimon J. Gerraty CondParser_PushBack(CondParser *par, Token t) 1863955d011SMarcel Moolenaar { 187956e45f6SSimon J. Gerraty assert(par->curr == TOK_NONE); 188956e45f6SSimon J. Gerraty assert(t != TOK_NONE); 189956e45f6SSimon J. Gerraty 190956e45f6SSimon J. Gerraty par->curr = t; 1913955d011SMarcel Moolenaar } 1922c3632d1SSimon J. Gerraty 193956e45f6SSimon J. Gerraty static void 194956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(CondParser *par) 1953955d011SMarcel Moolenaar { 196956e45f6SSimon J. Gerraty cpp_skip_whitespace(&par->p); 197956e45f6SSimon J. Gerraty } 198956e45f6SSimon J. Gerraty 199956e45f6SSimon J. Gerraty /* Parse the argument of a built-in function. 200956e45f6SSimon J. Gerraty * 201956e45f6SSimon J. Gerraty * Arguments: 202956e45f6SSimon J. Gerraty * *pp initially points at the '(', 203956e45f6SSimon J. Gerraty * upon successful return it points right after the ')'. 204956e45f6SSimon J. Gerraty * 205956e45f6SSimon J. Gerraty * *out_arg receives the argument as string. 206956e45f6SSimon J. Gerraty * 207956e45f6SSimon J. Gerraty * func says whether the argument belongs to an actual function, or 208956e45f6SSimon J. Gerraty * whether the parsed argument is passed to the default function. 209956e45f6SSimon J. Gerraty * 210*e2eeea75SSimon J. Gerraty * Return the length of the argument, or 0 on error. */ 211956e45f6SSimon J. Gerraty static size_t 212956e45f6SSimon J. Gerraty ParseFuncArg(const char **pp, Boolean doEval, const char *func, 213956e45f6SSimon J. Gerraty char **out_arg) { 214956e45f6SSimon J. Gerraty const char *p = *pp; 215956e45f6SSimon J. Gerraty Buffer argBuf; 2163955d011SMarcel Moolenaar int paren_depth; 2172c3632d1SSimon J. Gerraty size_t argLen; 2183955d011SMarcel Moolenaar 2193955d011SMarcel Moolenaar if (func != NULL) 220956e45f6SSimon J. Gerraty p++; /* Skip opening '(' - verified by caller */ 2213955d011SMarcel Moolenaar 222956e45f6SSimon J. Gerraty if (*p == '\0') { 223*e2eeea75SSimon J. Gerraty *out_arg = NULL; /* Missing closing parenthesis: */ 224*e2eeea75SSimon J. Gerraty return 0; /* .if defined( */ 2253955d011SMarcel Moolenaar } 2263955d011SMarcel Moolenaar 227*e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2283955d011SMarcel Moolenaar 229*e2eeea75SSimon J. Gerraty Buf_InitSize(&argBuf, 16); 2303955d011SMarcel Moolenaar 2313955d011SMarcel Moolenaar paren_depth = 0; 2323955d011SMarcel Moolenaar for (;;) { 233956e45f6SSimon J. Gerraty char ch = *p; 234*e2eeea75SSimon J. Gerraty if (ch == '\0' || ch == ' ' || ch == '\t') 2353955d011SMarcel Moolenaar break; 2363955d011SMarcel Moolenaar if ((ch == '&' || ch == '|') && paren_depth == 0) 2373955d011SMarcel Moolenaar break; 238956e45f6SSimon J. Gerraty if (*p == '$') { 2393955d011SMarcel Moolenaar /* 2403955d011SMarcel Moolenaar * Parse the variable spec and install it as part of the argument 2413955d011SMarcel Moolenaar * if it's valid. We tell Var_Parse to complain on an undefined 242956e45f6SSimon J. Gerraty * variable, so we don't need to do it. Nor do we return an error, 2433955d011SMarcel Moolenaar * though perhaps we should... 2443955d011SMarcel Moolenaar */ 245956e45f6SSimon J. Gerraty void *nestedVal_freeIt; 246*e2eeea75SSimon J. Gerraty VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR 247*e2eeea75SSimon J. Gerraty : VARE_NONE; 248956e45f6SSimon J. Gerraty const char *nestedVal; 249956e45f6SSimon J. Gerraty (void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal, 250956e45f6SSimon J. Gerraty &nestedVal_freeIt); 251956e45f6SSimon J. Gerraty /* TODO: handle errors */ 252956e45f6SSimon J. Gerraty Buf_AddStr(&argBuf, nestedVal); 253956e45f6SSimon J. Gerraty free(nestedVal_freeIt); 2543955d011SMarcel Moolenaar continue; 2553955d011SMarcel Moolenaar } 2563955d011SMarcel Moolenaar if (ch == '(') 2573955d011SMarcel Moolenaar paren_depth++; 2582c3632d1SSimon J. Gerraty else if (ch == ')' && --paren_depth < 0) 2593955d011SMarcel Moolenaar break; 260956e45f6SSimon J. Gerraty Buf_AddByte(&argBuf, *p); 261956e45f6SSimon J. Gerraty p++; 2623955d011SMarcel Moolenaar } 2633955d011SMarcel Moolenaar 264956e45f6SSimon J. Gerraty *out_arg = Buf_GetAll(&argBuf, &argLen); 265956e45f6SSimon J. Gerraty Buf_Destroy(&argBuf, FALSE); 2663955d011SMarcel Moolenaar 267*e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 2683955d011SMarcel Moolenaar 269956e45f6SSimon J. Gerraty if (func != NULL && *p++ != ')') { 2703955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", 2713955d011SMarcel Moolenaar func); 272956e45f6SSimon J. Gerraty /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ 2733841c287SSimon J. Gerraty return 0; 2743955d011SMarcel Moolenaar } 2753955d011SMarcel Moolenaar 276956e45f6SSimon J. Gerraty *pp = p; 2773841c287SSimon J. Gerraty return argLen; 2783955d011SMarcel Moolenaar } 2792c3632d1SSimon J. Gerraty 2802c3632d1SSimon J. Gerraty /* Test whether the given variable is defined. */ 2813955d011SMarcel Moolenaar static Boolean 282956e45f6SSimon J. Gerraty FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 2833955d011SMarcel Moolenaar { 284956e45f6SSimon J. Gerraty void *freeIt; 285956e45f6SSimon J. Gerraty Boolean result = Var_Value(arg, VAR_CMDLINE, &freeIt) != NULL; 2862c3632d1SSimon J. Gerraty bmake_free(freeIt); 2873841c287SSimon J. Gerraty return result; 2883955d011SMarcel Moolenaar } 2892c3632d1SSimon J. Gerraty 2902c3632d1SSimon J. Gerraty /* See if the given target is being made. */ 2913955d011SMarcel Moolenaar static Boolean 292956e45f6SSimon J. Gerraty FuncMake(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 2933955d011SMarcel Moolenaar { 294956e45f6SSimon J. Gerraty StringListNode *ln; 295956e45f6SSimon J. Gerraty 296956e45f6SSimon J. Gerraty for (ln = opts.create->first; ln != NULL; ln = ln->next) 297956e45f6SSimon J. Gerraty if (Str_Match(ln->datum, arg)) 298956e45f6SSimon J. Gerraty return TRUE; 299956e45f6SSimon J. Gerraty return FALSE; 3003955d011SMarcel Moolenaar } 3012c3632d1SSimon J. Gerraty 3022c3632d1SSimon J. Gerraty /* See if the given file exists. */ 3033955d011SMarcel Moolenaar static Boolean 304956e45f6SSimon J. Gerraty FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 3053955d011SMarcel Moolenaar { 3063955d011SMarcel Moolenaar Boolean result; 3073955d011SMarcel Moolenaar char *path; 3083955d011SMarcel Moolenaar 3093955d011SMarcel Moolenaar path = Dir_FindFile(arg, dirSearchPath); 310*e2eeea75SSimon J. Gerraty DEBUG2(COND, "exists(%s) result is \"%s\"\n", 311*e2eeea75SSimon J. Gerraty arg, path != NULL ? path : ""); 312*e2eeea75SSimon J. Gerraty result = path != NULL; 3133955d011SMarcel Moolenaar free(path); 3143841c287SSimon J. Gerraty return result; 3153955d011SMarcel Moolenaar } 3162c3632d1SSimon J. Gerraty 3172c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target. */ 3183955d011SMarcel Moolenaar static Boolean 319956e45f6SSimon J. Gerraty FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 3203955d011SMarcel Moolenaar { 321956e45f6SSimon J. Gerraty GNode *gn = Targ_FindNode(arg); 322956e45f6SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn); 3233955d011SMarcel Moolenaar } 3243955d011SMarcel Moolenaar 3252c3632d1SSimon J. Gerraty /* See if the given node exists and is an actual target with commands 3262c3632d1SSimon J. Gerraty * associated with it. */ 3273955d011SMarcel Moolenaar static Boolean 328956e45f6SSimon J. Gerraty FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) 3293955d011SMarcel Moolenaar { 330956e45f6SSimon J. Gerraty GNode *gn = Targ_FindNode(arg); 331956e45f6SSimon J. Gerraty return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands); 3323955d011SMarcel Moolenaar } 3332c3632d1SSimon J. Gerraty 334*e2eeea75SSimon J. Gerraty /* 3353955d011SMarcel Moolenaar * Convert the given number into a double. 3363955d011SMarcel Moolenaar * We try a base 10 or 16 integer conversion first, if that fails 3373955d011SMarcel Moolenaar * then we try a floating point conversion instead. 3383955d011SMarcel Moolenaar * 3393955d011SMarcel Moolenaar * Results: 3402c3632d1SSimon J. Gerraty * Returns TRUE if the conversion succeeded. 341*e2eeea75SSimon J. Gerraty * Sets 'out_value' to the converted number. 3423955d011SMarcel Moolenaar */ 3433955d011SMarcel Moolenaar static Boolean 344*e2eeea75SSimon J. Gerraty TryParseNumber(const char *str, double *out_value) 3453955d011SMarcel Moolenaar { 346*e2eeea75SSimon J. Gerraty char *end; 347*e2eeea75SSimon J. Gerraty unsigned long ul_val; 348*e2eeea75SSimon J. Gerraty double dbl_val; 3493955d011SMarcel Moolenaar 3503955d011SMarcel Moolenaar errno = 0; 351*e2eeea75SSimon J. Gerraty if (str[0] == '\0') { /* XXX: why is an empty string a number? */ 352*e2eeea75SSimon J. Gerraty *out_value = 0.0; 353ac3446e9SSimon J. Gerraty return TRUE; 354ac3446e9SSimon J. Gerraty } 355*e2eeea75SSimon J. Gerraty 356*e2eeea75SSimon J. Gerraty ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); 357*e2eeea75SSimon J. Gerraty if (*end == '\0' && errno != ERANGE) { 358*e2eeea75SSimon J. Gerraty *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; 359*e2eeea75SSimon J. Gerraty return TRUE; 3603955d011SMarcel Moolenaar } 3613955d011SMarcel Moolenaar 362*e2eeea75SSimon J. Gerraty if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') 363*e2eeea75SSimon J. Gerraty return FALSE; /* skip the expensive strtod call */ 364*e2eeea75SSimon J. Gerraty dbl_val = strtod(str, &end); 365*e2eeea75SSimon J. Gerraty if (*end != '\0') 366*e2eeea75SSimon J. Gerraty return FALSE; 367*e2eeea75SSimon J. Gerraty 368*e2eeea75SSimon J. Gerraty *out_value = dbl_val; 3693955d011SMarcel Moolenaar return TRUE; 3703955d011SMarcel Moolenaar } 3713955d011SMarcel Moolenaar 372956e45f6SSimon J. Gerraty static Boolean 373956e45f6SSimon J. Gerraty is_separator(char ch) 374956e45f6SSimon J. Gerraty { 375956e45f6SSimon J. Gerraty return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; 376956e45f6SSimon J. Gerraty } 377956e45f6SSimon J. Gerraty 3783955d011SMarcel Moolenaar /*- 379956e45f6SSimon J. Gerraty * Parse a string from a variable reference or an optionally quoted 380956e45f6SSimon J. Gerraty * string. This is called for the lhs and rhs of string comparisons. 3813955d011SMarcel Moolenaar * 3823955d011SMarcel Moolenaar * Results: 3832c3632d1SSimon J. Gerraty * Returns the string, absent any quotes, or NULL on error. 384*e2eeea75SSimon J. Gerraty * Sets out_quoted if the string was quoted. 385*e2eeea75SSimon J. Gerraty * Sets out_freeIt. 3863955d011SMarcel Moolenaar */ 387956e45f6SSimon J. Gerraty /* coverity:[+alloc : arg-*4] */ 3882c3632d1SSimon J. Gerraty static const char * 389956e45f6SSimon J. Gerraty CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, 390*e2eeea75SSimon J. Gerraty Boolean *out_quoted, void **out_freeIt) 3913955d011SMarcel Moolenaar { 3923955d011SMarcel Moolenaar Buffer buf; 3932c3632d1SSimon J. Gerraty const char *str; 394956e45f6SSimon J. Gerraty Boolean atStart; 395956e45f6SSimon J. Gerraty const char *nested_p; 396*e2eeea75SSimon J. Gerraty Boolean quoted; 3972c3632d1SSimon J. Gerraty const char *start; 3982c3632d1SSimon J. Gerraty VarEvalFlags eflags; 399956e45f6SSimon J. Gerraty VarParseResult parseResult; 4003955d011SMarcel Moolenaar 401*e2eeea75SSimon J. Gerraty Buf_Init(&buf); 4023955d011SMarcel Moolenaar str = NULL; 403*e2eeea75SSimon J. Gerraty *out_freeIt = NULL; 404*e2eeea75SSimon J. Gerraty *out_quoted = quoted = par->p[0] == '"'; 405956e45f6SSimon J. Gerraty start = par->p; 406*e2eeea75SSimon J. Gerraty if (quoted) 407956e45f6SSimon J. Gerraty par->p++; 408*e2eeea75SSimon J. Gerraty while (par->p[0] != '\0' && str == NULL) { 409956e45f6SSimon J. Gerraty switch (par->p[0]) { 4103955d011SMarcel Moolenaar case '\\': 411956e45f6SSimon J. Gerraty par->p++; 412956e45f6SSimon J. Gerraty if (par->p[0] != '\0') { 413956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 414956e45f6SSimon J. Gerraty par->p++; 4153955d011SMarcel Moolenaar } 416956e45f6SSimon J. Gerraty continue; 4173955d011SMarcel Moolenaar case '"': 418*e2eeea75SSimon J. Gerraty if (quoted) { 419*e2eeea75SSimon J. Gerraty par->p++; /* skip the closing quote */ 4203955d011SMarcel Moolenaar goto got_str; 421956e45f6SSimon J. Gerraty } 422956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); /* likely? */ 423956e45f6SSimon J. Gerraty par->p++; 424956e45f6SSimon J. Gerraty continue; 425*e2eeea75SSimon J. Gerraty case ')': /* see is_separator */ 4263955d011SMarcel Moolenaar case '!': 4273955d011SMarcel Moolenaar case '=': 4283955d011SMarcel Moolenaar case '>': 4293955d011SMarcel Moolenaar case '<': 4303955d011SMarcel Moolenaar case ' ': 4313955d011SMarcel Moolenaar case '\t': 432*e2eeea75SSimon J. Gerraty if (!quoted) 4333955d011SMarcel Moolenaar goto got_str; 434956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 435956e45f6SSimon J. Gerraty par->p++; 436956e45f6SSimon J. Gerraty continue; 4373955d011SMarcel Moolenaar case '$': 438956e45f6SSimon J. Gerraty /* if we are in quotes, an undefined variable is ok */ 439*e2eeea75SSimon J. Gerraty eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR : 440*e2eeea75SSimon J. Gerraty doEval ? VARE_WANTRES : 441*e2eeea75SSimon J. Gerraty VARE_NONE; 442*e2eeea75SSimon J. Gerraty 443956e45f6SSimon J. Gerraty nested_p = par->p; 444956e45f6SSimon J. Gerraty atStart = nested_p == start; 445956e45f6SSimon J. Gerraty parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str, 446*e2eeea75SSimon J. Gerraty out_freeIt); 447956e45f6SSimon J. Gerraty /* TODO: handle errors */ 4483955d011SMarcel Moolenaar if (str == var_Error) { 449956e45f6SSimon J. Gerraty if (parseResult & VPR_ANY_MSG) 450956e45f6SSimon J. Gerraty par->printedError = TRUE; 451*e2eeea75SSimon J. Gerraty if (*out_freeIt != NULL) { 452*e2eeea75SSimon J. Gerraty /* XXX: Can there be any situation in which a returned 453*e2eeea75SSimon J. Gerraty * var_Error requires freeIt? */ 454*e2eeea75SSimon J. Gerraty free(*out_freeIt); 455*e2eeea75SSimon J. Gerraty *out_freeIt = NULL; 4563955d011SMarcel Moolenaar } 4573955d011SMarcel Moolenaar /* 4583955d011SMarcel Moolenaar * Even if !doEval, we still report syntax errors, which 4593955d011SMarcel Moolenaar * is what getting var_Error back with !doEval means. 4603955d011SMarcel Moolenaar */ 4613955d011SMarcel Moolenaar str = NULL; 4623955d011SMarcel Moolenaar goto cleanup; 4633955d011SMarcel Moolenaar } 464956e45f6SSimon J. Gerraty par->p = nested_p; 465956e45f6SSimon J. Gerraty 4663955d011SMarcel Moolenaar /* 467956e45f6SSimon J. Gerraty * If the '$' started the string literal (which means no quotes), 468956e45f6SSimon J. Gerraty * and the variable expression is followed by a space, looks like 469956e45f6SSimon J. Gerraty * a comparison operator or is the end of the expression, we are 470956e45f6SSimon J. Gerraty * done. 4713955d011SMarcel Moolenaar */ 472956e45f6SSimon J. Gerraty if (atStart && is_separator(par->p[0])) 4733955d011SMarcel Moolenaar goto cleanup; 474956e45f6SSimon J. Gerraty 475956e45f6SSimon J. Gerraty Buf_AddStr(&buf, str); 476*e2eeea75SSimon J. Gerraty if (*out_freeIt) { 477*e2eeea75SSimon J. Gerraty free(*out_freeIt); 478*e2eeea75SSimon J. Gerraty *out_freeIt = NULL; 4793955d011SMarcel Moolenaar } 4803955d011SMarcel Moolenaar str = NULL; /* not finished yet */ 481956e45f6SSimon J. Gerraty continue; 4823955d011SMarcel Moolenaar default: 483*e2eeea75SSimon J. Gerraty if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) { 48428a6bc81SSimon J. Gerraty /* lhs must be quoted, a variable reference or number */ 48528a6bc81SSimon J. Gerraty str = NULL; 48628a6bc81SSimon J. Gerraty goto cleanup; 48728a6bc81SSimon J. Gerraty } 488956e45f6SSimon J. Gerraty Buf_AddByte(&buf, par->p[0]); 489956e45f6SSimon J. Gerraty par->p++; 490956e45f6SSimon J. Gerraty continue; 4913955d011SMarcel Moolenaar } 4923955d011SMarcel Moolenaar } 4933955d011SMarcel Moolenaar got_str: 494*e2eeea75SSimon J. Gerraty *out_freeIt = Buf_GetAll(&buf, NULL); 495*e2eeea75SSimon J. Gerraty str = *out_freeIt; 4963955d011SMarcel Moolenaar cleanup: 4973955d011SMarcel Moolenaar Buf_Destroy(&buf, FALSE); 4983955d011SMarcel Moolenaar return str; 4993955d011SMarcel Moolenaar } 5002c3632d1SSimon J. Gerraty 501*e2eeea75SSimon J. Gerraty struct If { 5022c3632d1SSimon J. Gerraty const char *form; /* Form of if */ 5032c3632d1SSimon J. Gerraty size_t formlen; /* Length of form */ 5042c3632d1SSimon J. Gerraty Boolean doNot; /* TRUE if default function should be negated */ 505956e45f6SSimon J. Gerraty Boolean (*defProc)(size_t, const char *); /* Default function to apply */ 506*e2eeea75SSimon J. Gerraty }; 507*e2eeea75SSimon J. Gerraty 508*e2eeea75SSimon J. Gerraty /* The different forms of .if directives. */ 509*e2eeea75SSimon J. Gerraty static const struct If ifs[] = { 510956e45f6SSimon J. Gerraty { "def", 3, FALSE, FuncDefined }, 511956e45f6SSimon J. Gerraty { "ndef", 4, TRUE, FuncDefined }, 512956e45f6SSimon J. Gerraty { "make", 4, FALSE, FuncMake }, 513956e45f6SSimon J. Gerraty { "nmake", 5, TRUE, FuncMake }, 514956e45f6SSimon J. Gerraty { "", 0, FALSE, FuncDefined }, 5152c3632d1SSimon J. Gerraty { NULL, 0, FALSE, NULL } 5162c3632d1SSimon J. Gerraty }; 517*e2eeea75SSimon J. Gerraty enum { PLAIN_IF_INDEX = 4 }; 518*e2eeea75SSimon J. Gerraty 519*e2eeea75SSimon J. Gerraty static Boolean 520*e2eeea75SSimon J. Gerraty If_Eval(const struct If *if_info, const char *arg, size_t arglen) 521*e2eeea75SSimon J. Gerraty { 522*e2eeea75SSimon J. Gerraty Boolean res = if_info->defProc(arglen, arg); 523*e2eeea75SSimon J. Gerraty return if_info->doNot ? !res : res; 524*e2eeea75SSimon J. Gerraty } 5252c3632d1SSimon J. Gerraty 526956e45f6SSimon J. Gerraty /* Evaluate a "comparison without operator", such as in ".if ${VAR}" or 527956e45f6SSimon J. Gerraty * ".if 0". */ 528*e2eeea75SSimon J. Gerraty static Boolean 529*e2eeea75SSimon J. Gerraty EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) 5303955d011SMarcel Moolenaar { 531*e2eeea75SSimon J. Gerraty double num; 532956e45f6SSimon J. Gerraty 533*e2eeea75SSimon J. Gerraty /* For .ifxxx "...", check for non-empty string. */ 534*e2eeea75SSimon J. Gerraty if (quoted) 535*e2eeea75SSimon J. Gerraty return value[0] != '\0'; 536956e45f6SSimon J. Gerraty 537*e2eeea75SSimon J. Gerraty /* For .ifxxx <number>, compare against zero */ 538*e2eeea75SSimon J. Gerraty if (TryParseNumber(value, &num)) 539*e2eeea75SSimon J. Gerraty return num != 0.0; 540956e45f6SSimon J. Gerraty 541*e2eeea75SSimon J. Gerraty /* For .if ${...}, check for non-empty string. This is different from 542*e2eeea75SSimon J. Gerraty * the evaluation function from that .if variant, which would test 543*e2eeea75SSimon J. Gerraty * whether a variable of the given name were defined. */ 544*e2eeea75SSimon J. Gerraty /* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */ 545956e45f6SSimon J. Gerraty if (par->if_info->form[0] == '\0') 546*e2eeea75SSimon J. Gerraty return value[0] != '\0'; 547956e45f6SSimon J. Gerraty 548*e2eeea75SSimon J. Gerraty /* For the other variants of .ifxxx ${...}, use its default function. */ 549*e2eeea75SSimon J. Gerraty return If_Eval(par->if_info, value, strlen(value)); 550956e45f6SSimon J. Gerraty } 551956e45f6SSimon J. Gerraty 552956e45f6SSimon J. Gerraty /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ 553956e45f6SSimon J. Gerraty static Token 554956e45f6SSimon J. Gerraty EvalCompareNum(double lhs, const char *op, double rhs) 555956e45f6SSimon J. Gerraty { 556956e45f6SSimon J. Gerraty DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, op); 557956e45f6SSimon J. Gerraty 558956e45f6SSimon J. Gerraty switch (op[0]) { 559956e45f6SSimon J. Gerraty case '!': 560956e45f6SSimon J. Gerraty if (op[1] != '=') { 561956e45f6SSimon J. Gerraty Parse_Error(PARSE_WARNING, "Unknown operator"); 562956e45f6SSimon J. Gerraty /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ 563956e45f6SSimon J. Gerraty return TOK_ERROR; 564956e45f6SSimon J. Gerraty } 565*e2eeea75SSimon J. Gerraty return ToToken(lhs != rhs); 566956e45f6SSimon J. Gerraty case '=': 567956e45f6SSimon J. Gerraty if (op[1] != '=') { 568956e45f6SSimon J. Gerraty Parse_Error(PARSE_WARNING, "Unknown operator"); 569956e45f6SSimon J. Gerraty /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ 570956e45f6SSimon J. Gerraty return TOK_ERROR; 571956e45f6SSimon J. Gerraty } 572*e2eeea75SSimon J. Gerraty return ToToken(lhs == rhs); 573956e45f6SSimon J. Gerraty case '<': 574*e2eeea75SSimon J. Gerraty return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs); 575956e45f6SSimon J. Gerraty case '>': 576*e2eeea75SSimon J. Gerraty return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs); 577956e45f6SSimon J. Gerraty } 578956e45f6SSimon J. Gerraty return TOK_ERROR; 579956e45f6SSimon J. Gerraty } 580956e45f6SSimon J. Gerraty 581956e45f6SSimon J. Gerraty static Token 582956e45f6SSimon J. Gerraty EvalCompareStr(const char *lhs, const char *op, const char *rhs) 583956e45f6SSimon J. Gerraty { 584956e45f6SSimon J. Gerraty if (!((op[0] == '!' || op[0] == '=') && op[1] == '=')) { 585956e45f6SSimon J. Gerraty Parse_Error(PARSE_WARNING, 586956e45f6SSimon J. Gerraty "String comparison operator must be either == or !="); 587956e45f6SSimon J. Gerraty /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ 588956e45f6SSimon J. Gerraty return TOK_ERROR; 589956e45f6SSimon J. Gerraty } 590956e45f6SSimon J. Gerraty 591956e45f6SSimon J. Gerraty DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op); 592*e2eeea75SSimon J. Gerraty return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0)); 593956e45f6SSimon J. Gerraty } 594956e45f6SSimon J. Gerraty 595956e45f6SSimon J. Gerraty /* Evaluate a comparison, such as "${VAR} == 12345". */ 596956e45f6SSimon J. Gerraty static Token 597956e45f6SSimon J. Gerraty EvalCompare(const char *lhs, Boolean lhsQuoted, const char *op, 598956e45f6SSimon J. Gerraty const char *rhs, Boolean rhsQuoted) 599956e45f6SSimon J. Gerraty { 6003955d011SMarcel Moolenaar double left, right; 6013955d011SMarcel Moolenaar 602956e45f6SSimon J. Gerraty if (!rhsQuoted && !lhsQuoted) 603956e45f6SSimon J. Gerraty if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) 604956e45f6SSimon J. Gerraty return EvalCompareNum(left, op, right); 605956e45f6SSimon J. Gerraty 606956e45f6SSimon J. Gerraty return EvalCompareStr(lhs, op, rhs); 607956e45f6SSimon J. Gerraty } 608956e45f6SSimon J. Gerraty 609956e45f6SSimon J. Gerraty /* Parse a comparison condition such as: 610956e45f6SSimon J. Gerraty * 611956e45f6SSimon J. Gerraty * 0 612956e45f6SSimon J. Gerraty * ${VAR:Mpattern} 613956e45f6SSimon J. Gerraty * ${VAR} == value 614956e45f6SSimon J. Gerraty * ${VAR:U0} < 12345 615956e45f6SSimon J. Gerraty */ 616956e45f6SSimon J. Gerraty static Token 617956e45f6SSimon J. Gerraty CondParser_Comparison(CondParser *par, Boolean doEval) 618956e45f6SSimon J. Gerraty { 619956e45f6SSimon J. Gerraty Token t = TOK_ERROR; 620956e45f6SSimon J. Gerraty const char *lhs, *op, *rhs; 621*e2eeea75SSimon J. Gerraty void *lhs_freeIt, *rhs_freeIt; 622956e45f6SSimon J. Gerraty Boolean lhsQuoted, rhsQuoted; 623956e45f6SSimon J. Gerraty 6243955d011SMarcel Moolenaar /* 6253955d011SMarcel Moolenaar * Parse the variable spec and skip over it, saving its 6263955d011SMarcel Moolenaar * value in lhs. 6273955d011SMarcel Moolenaar */ 628*e2eeea75SSimon J. Gerraty lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt); 629*e2eeea75SSimon J. Gerraty if (lhs == NULL) 630*e2eeea75SSimon J. Gerraty goto done_lhs; 6313955d011SMarcel Moolenaar 632956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6333955d011SMarcel Moolenaar 634956e45f6SSimon J. Gerraty op = par->p; 635956e45f6SSimon J. Gerraty switch (par->p[0]) { 6363955d011SMarcel Moolenaar case '!': 6373955d011SMarcel Moolenaar case '=': 6383955d011SMarcel Moolenaar case '<': 6393955d011SMarcel Moolenaar case '>': 640*e2eeea75SSimon J. Gerraty if (par->p[1] == '=') 641956e45f6SSimon J. Gerraty par->p += 2; 642*e2eeea75SSimon J. Gerraty else 643956e45f6SSimon J. Gerraty par->p++; 6443955d011SMarcel Moolenaar break; 6453955d011SMarcel Moolenaar default: 646*e2eeea75SSimon J. Gerraty /* Unknown operator, compare against an empty string or 0. */ 647*e2eeea75SSimon J. Gerraty t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted)); 648*e2eeea75SSimon J. Gerraty goto done_lhs; 6493955d011SMarcel Moolenaar } 6503955d011SMarcel Moolenaar 651956e45f6SSimon J. Gerraty CondParser_SkipWhitespace(par); 6523955d011SMarcel Moolenaar 653956e45f6SSimon J. Gerraty if (par->p[0] == '\0') { 654956e45f6SSimon J. Gerraty Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator"); 655956e45f6SSimon J. Gerraty /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ 656*e2eeea75SSimon J. Gerraty goto done_lhs; 6573955d011SMarcel Moolenaar } 6583955d011SMarcel Moolenaar 659*e2eeea75SSimon J. Gerraty rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhs_freeIt); 660956e45f6SSimon J. Gerraty if (rhs == NULL) 661*e2eeea75SSimon J. Gerraty goto done_rhs; 6623955d011SMarcel Moolenaar 6633841c287SSimon J. Gerraty if (!doEval) { 6643841c287SSimon J. Gerraty t = TOK_FALSE; 665*e2eeea75SSimon J. Gerraty goto done_rhs; 6663841c287SSimon J. Gerraty } 6673841c287SSimon J. Gerraty 668956e45f6SSimon J. Gerraty t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted); 6693955d011SMarcel Moolenaar 670*e2eeea75SSimon J. Gerraty done_rhs: 671*e2eeea75SSimon J. Gerraty free(rhs_freeIt); 672*e2eeea75SSimon J. Gerraty done_lhs: 673*e2eeea75SSimon J. Gerraty free(lhs_freeIt); 6743955d011SMarcel Moolenaar return t; 6753955d011SMarcel Moolenaar } 6763955d011SMarcel Moolenaar 677*e2eeea75SSimon J. Gerraty /* The argument to empty() is a variable name, optionally followed by 678*e2eeea75SSimon J. Gerraty * variable modifiers. */ 679956e45f6SSimon J. Gerraty static size_t 680*e2eeea75SSimon J. Gerraty ParseEmptyArg(const char **pp, Boolean doEval, 681*e2eeea75SSimon J. Gerraty const char *func MAKE_ATTR_UNUSED, char **out_arg) 6823955d011SMarcel Moolenaar { 6832c3632d1SSimon J. Gerraty void *val_freeIt; 6842c3632d1SSimon J. Gerraty const char *val; 685956e45f6SSimon J. Gerraty size_t magic_res; 6863955d011SMarcel Moolenaar 6873955d011SMarcel Moolenaar /* We do all the work here and return the result as the length */ 688*e2eeea75SSimon J. Gerraty *out_arg = NULL; 6893955d011SMarcel Moolenaar 690*e2eeea75SSimon J. Gerraty (*pp)--; /* Make (*pp)[1] point to the '('. */ 691*e2eeea75SSimon J. Gerraty (void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE, 692956e45f6SSimon J. Gerraty &val, &val_freeIt); 693956e45f6SSimon J. Gerraty /* TODO: handle errors */ 694*e2eeea75SSimon J. Gerraty /* If successful, *pp points beyond the closing ')' now. */ 6953955d011SMarcel Moolenaar 6963955d011SMarcel Moolenaar if (val == var_Error) { 6972c3632d1SSimon J. Gerraty free(val_freeIt); 698956e45f6SSimon J. Gerraty return (size_t)-1; 6993955d011SMarcel Moolenaar } 7003955d011SMarcel Moolenaar 7013955d011SMarcel Moolenaar /* A variable is empty when it just contains spaces... 4/15/92, christos */ 702956e45f6SSimon J. Gerraty cpp_skip_whitespace(&val); 7033955d011SMarcel Moolenaar 7043955d011SMarcel Moolenaar /* 7053955d011SMarcel Moolenaar * For consistency with the other functions we can't generate the 7063955d011SMarcel Moolenaar * true/false here. 7073955d011SMarcel Moolenaar */ 708956e45f6SSimon J. Gerraty magic_res = *val != '\0' ? 2 : 1; 7092c3632d1SSimon J. Gerraty free(val_freeIt); 710956e45f6SSimon J. Gerraty return magic_res; 7113955d011SMarcel Moolenaar } 7123955d011SMarcel Moolenaar 7133955d011SMarcel Moolenaar static Boolean 714956e45f6SSimon J. Gerraty FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) 7153955d011SMarcel Moolenaar { 716956e45f6SSimon J. Gerraty /* Magic values ahead, see ParseEmptyArg. */ 7173955d011SMarcel Moolenaar return arglen == 1; 7183955d011SMarcel Moolenaar } 7193955d011SMarcel Moolenaar 720*e2eeea75SSimon J. Gerraty static Boolean 721*e2eeea75SSimon J. Gerraty CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) 7223955d011SMarcel Moolenaar { 7233955d011SMarcel Moolenaar static const struct fn_def { 7243955d011SMarcel Moolenaar const char *fn_name; 7252c3632d1SSimon J. Gerraty size_t fn_name_len; 726956e45f6SSimon J. Gerraty size_t (*fn_parse)(const char **, Boolean, const char *, char **); 727956e45f6SSimon J. Gerraty Boolean (*fn_eval)(size_t, const char *); 728*e2eeea75SSimon J. Gerraty } fns[] = { 729956e45f6SSimon J. Gerraty { "defined", 7, ParseFuncArg, FuncDefined }, 730956e45f6SSimon J. Gerraty { "make", 4, ParseFuncArg, FuncMake }, 731956e45f6SSimon J. Gerraty { "exists", 6, ParseFuncArg, FuncExists }, 732956e45f6SSimon J. Gerraty { "empty", 5, ParseEmptyArg, FuncEmpty }, 733956e45f6SSimon J. Gerraty { "target", 6, ParseFuncArg, FuncTarget }, 734*e2eeea75SSimon J. Gerraty { "commands", 8, ParseFuncArg, FuncCommands } 7353955d011SMarcel Moolenaar }; 736*e2eeea75SSimon J. Gerraty const struct fn_def *fn; 737*e2eeea75SSimon J. Gerraty char *arg = NULL; 738*e2eeea75SSimon J. Gerraty size_t arglen; 739*e2eeea75SSimon J. Gerraty const char *cp = par->p; 740*e2eeea75SSimon J. Gerraty const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0]; 741*e2eeea75SSimon J. Gerraty 742*e2eeea75SSimon J. Gerraty for (fn = fns; fn != fns_end; fn++) { 743*e2eeea75SSimon J. Gerraty if (!is_token(cp, fn->fn_name, fn->fn_name_len)) 744*e2eeea75SSimon J. Gerraty continue; 745*e2eeea75SSimon J. Gerraty 746*e2eeea75SSimon J. Gerraty cp += fn->fn_name_len; 747*e2eeea75SSimon J. Gerraty cpp_skip_whitespace(&cp); 748*e2eeea75SSimon J. Gerraty if (*cp != '(') 749*e2eeea75SSimon J. Gerraty break; 750*e2eeea75SSimon J. Gerraty 751*e2eeea75SSimon J. Gerraty arglen = fn->fn_parse(&cp, doEval, fn->fn_name, &arg); 752*e2eeea75SSimon J. Gerraty if (arglen == 0 || arglen == (size_t)-1) { 753*e2eeea75SSimon J. Gerraty par->p = cp; 754*e2eeea75SSimon J. Gerraty *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR; 755*e2eeea75SSimon J. Gerraty return TRUE; 756*e2eeea75SSimon J. Gerraty } 757*e2eeea75SSimon J. Gerraty 758*e2eeea75SSimon J. Gerraty /* Evaluate the argument using the required function. */ 759*e2eeea75SSimon J. Gerraty *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg)); 760*e2eeea75SSimon J. Gerraty free(arg); 761*e2eeea75SSimon J. Gerraty par->p = cp; 762*e2eeea75SSimon J. Gerraty return TRUE; 763*e2eeea75SSimon J. Gerraty } 764*e2eeea75SSimon J. Gerraty 765*e2eeea75SSimon J. Gerraty return FALSE; 766*e2eeea75SSimon J. Gerraty } 767*e2eeea75SSimon J. Gerraty 768*e2eeea75SSimon J. Gerraty /* Parse a function call, a number, a variable expression or a string 769*e2eeea75SSimon J. Gerraty * literal. */ 770*e2eeea75SSimon J. Gerraty static Token 771*e2eeea75SSimon J. Gerraty CondParser_LeafToken(CondParser *par, Boolean doEval) 772*e2eeea75SSimon J. Gerraty { 7733955d011SMarcel Moolenaar Token t; 7743955d011SMarcel Moolenaar char *arg = NULL; 775956e45f6SSimon J. Gerraty size_t arglen; 776956e45f6SSimon J. Gerraty const char *cp = par->p; 7772c3632d1SSimon J. Gerraty const char *cp1; 7783955d011SMarcel Moolenaar 779*e2eeea75SSimon J. Gerraty if (CondParser_Func(par, doEval, &t)) 7803955d011SMarcel Moolenaar return t; 7813955d011SMarcel Moolenaar 7823955d011SMarcel Moolenaar /* Push anything numeric through the compare expression */ 783956e45f6SSimon J. Gerraty cp = par->p; 784*e2eeea75SSimon J. Gerraty if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') 785956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 7863955d011SMarcel Moolenaar 7873955d011SMarcel Moolenaar /* 7883955d011SMarcel Moolenaar * Most likely we have a naked token to apply the default function to. 7893955d011SMarcel Moolenaar * However ".if a == b" gets here when the "a" is unquoted and doesn't 7903955d011SMarcel Moolenaar * start with a '$'. This surprises people. 7913955d011SMarcel Moolenaar * If what follows the function argument is a '=' or '!' then the syntax 7923955d011SMarcel Moolenaar * would be invalid if we did "defined(a)" - so instead treat as an 7933955d011SMarcel Moolenaar * expression. 7943955d011SMarcel Moolenaar */ 795956e45f6SSimon J. Gerraty arglen = ParseFuncArg(&cp, doEval, NULL, &arg); 796956e45f6SSimon J. Gerraty cp1 = cp; 797956e45f6SSimon J. Gerraty cpp_skip_whitespace(&cp1); 7983955d011SMarcel Moolenaar if (*cp1 == '=' || *cp1 == '!') 799956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 800956e45f6SSimon J. Gerraty par->p = cp; 8013955d011SMarcel Moolenaar 8023955d011SMarcel Moolenaar /* 8033955d011SMarcel Moolenaar * Evaluate the argument using the default function. 804956e45f6SSimon J. Gerraty * This path always treats .if as .ifdef. To get here, the character 8053955d011SMarcel Moolenaar * after .if must have been taken literally, so the argument cannot 8063955d011SMarcel Moolenaar * be empty - even if it contained a variable expansion. 8073955d011SMarcel Moolenaar */ 808*e2eeea75SSimon J. Gerraty t = ToToken(!doEval || If_Eval(par->if_info, arg, arglen)); 8093955d011SMarcel Moolenaar free(arg); 8103955d011SMarcel Moolenaar return t; 8113955d011SMarcel Moolenaar } 8123955d011SMarcel Moolenaar 813956e45f6SSimon J. Gerraty /* Return the next token or comparison result from the parser. */ 8143955d011SMarcel Moolenaar static Token 815956e45f6SSimon J. Gerraty CondParser_Token(CondParser *par, Boolean doEval) 8163955d011SMarcel Moolenaar { 8173955d011SMarcel Moolenaar Token t; 8183955d011SMarcel Moolenaar 819956e45f6SSimon J. Gerraty t = par->curr; 8203955d011SMarcel Moolenaar if (t != TOK_NONE) { 821956e45f6SSimon J. Gerraty par->curr = TOK_NONE; 8223955d011SMarcel Moolenaar return t; 8233955d011SMarcel Moolenaar } 8243955d011SMarcel Moolenaar 825*e2eeea75SSimon J. Gerraty cpp_skip_hspace(&par->p); 8263955d011SMarcel Moolenaar 827956e45f6SSimon J. Gerraty switch (par->p[0]) { 8283955d011SMarcel Moolenaar 8293955d011SMarcel Moolenaar case '(': 830956e45f6SSimon J. Gerraty par->p++; 8313955d011SMarcel Moolenaar return TOK_LPAREN; 8323955d011SMarcel Moolenaar 8333955d011SMarcel Moolenaar case ')': 834956e45f6SSimon J. Gerraty par->p++; 8353955d011SMarcel Moolenaar return TOK_RPAREN; 8363955d011SMarcel Moolenaar 8373955d011SMarcel Moolenaar case '|': 838956e45f6SSimon J. Gerraty par->p++; 839*e2eeea75SSimon J. Gerraty if (par->p[0] == '|') 840956e45f6SSimon J. Gerraty par->p++; 841*e2eeea75SSimon J. Gerraty else if (opts.lint) { 842*e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '|'"); 843*e2eeea75SSimon J. Gerraty par->printedError = TRUE; 844*e2eeea75SSimon J. Gerraty return TOK_ERROR; 8453955d011SMarcel Moolenaar } 8463955d011SMarcel Moolenaar return TOK_OR; 8473955d011SMarcel Moolenaar 8483955d011SMarcel Moolenaar case '&': 849956e45f6SSimon J. Gerraty par->p++; 850*e2eeea75SSimon J. Gerraty if (par->p[0] == '&') 851956e45f6SSimon J. Gerraty par->p++; 852*e2eeea75SSimon J. Gerraty else if (opts.lint) { 853*e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Unknown operator '&'"); 854*e2eeea75SSimon J. Gerraty par->printedError = TRUE; 855*e2eeea75SSimon J. Gerraty return TOK_ERROR; 8563955d011SMarcel Moolenaar } 8573955d011SMarcel Moolenaar return TOK_AND; 8583955d011SMarcel Moolenaar 8593955d011SMarcel Moolenaar case '!': 860956e45f6SSimon J. Gerraty par->p++; 8613955d011SMarcel Moolenaar return TOK_NOT; 8623955d011SMarcel Moolenaar 863*e2eeea75SSimon J. Gerraty case '#': /* XXX: see unit-tests/cond-token-plain.mk */ 864*e2eeea75SSimon J. Gerraty case '\n': /* XXX: why should this end the condition? */ 865*e2eeea75SSimon J. Gerraty /* Probably obsolete now, from 1993-03-21. */ 8663955d011SMarcel Moolenaar case '\0': 8673955d011SMarcel Moolenaar return TOK_EOF; 8683955d011SMarcel Moolenaar 8693955d011SMarcel Moolenaar case '"': 8703955d011SMarcel Moolenaar case '$': 871956e45f6SSimon J. Gerraty return CondParser_Comparison(par, doEval); 8723955d011SMarcel Moolenaar 8733955d011SMarcel Moolenaar default: 874*e2eeea75SSimon J. Gerraty return CondParser_LeafToken(par, doEval); 8753955d011SMarcel Moolenaar } 8763955d011SMarcel Moolenaar } 8773955d011SMarcel Moolenaar 878956e45f6SSimon J. Gerraty /* Parse a single term in the expression. This consists of a terminal symbol 879956e45f6SSimon J. Gerraty * or TOK_NOT and a term (not including the binary operators): 880956e45f6SSimon J. Gerraty * 8813955d011SMarcel Moolenaar * T -> defined(variable) | make(target) | exists(file) | symbol 8823955d011SMarcel Moolenaar * T -> ! T | ( E ) 8833955d011SMarcel Moolenaar * 8843955d011SMarcel Moolenaar * Results: 8853955d011SMarcel Moolenaar * TOK_TRUE, TOK_FALSE or TOK_ERROR. 8863955d011SMarcel Moolenaar */ 8873955d011SMarcel Moolenaar static Token 888956e45f6SSimon J. Gerraty CondParser_Term(CondParser *par, Boolean doEval) 8893955d011SMarcel Moolenaar { 8903955d011SMarcel Moolenaar Token t; 8913955d011SMarcel Moolenaar 892956e45f6SSimon J. Gerraty t = CondParser_Token(par, doEval); 8933955d011SMarcel Moolenaar 8943955d011SMarcel Moolenaar if (t == TOK_EOF) { 8953955d011SMarcel Moolenaar /* 8963955d011SMarcel Moolenaar * If we reached the end of the expression, the expression 8973955d011SMarcel Moolenaar * is malformed... 8983955d011SMarcel Moolenaar */ 8993955d011SMarcel Moolenaar t = TOK_ERROR; 9003955d011SMarcel Moolenaar } else if (t == TOK_LPAREN) { 9013955d011SMarcel Moolenaar /* 9023955d011SMarcel Moolenaar * T -> ( E ) 9033955d011SMarcel Moolenaar */ 904956e45f6SSimon J. Gerraty t = CondParser_Expr(par, doEval); 9053955d011SMarcel Moolenaar if (t != TOK_ERROR) { 906956e45f6SSimon J. Gerraty if (CondParser_Token(par, doEval) != TOK_RPAREN) { 9073955d011SMarcel Moolenaar t = TOK_ERROR; 9083955d011SMarcel Moolenaar } 9093955d011SMarcel Moolenaar } 9103955d011SMarcel Moolenaar } else if (t == TOK_NOT) { 911956e45f6SSimon J. Gerraty t = CondParser_Term(par, doEval); 9123955d011SMarcel Moolenaar if (t == TOK_TRUE) { 9133955d011SMarcel Moolenaar t = TOK_FALSE; 9143955d011SMarcel Moolenaar } else if (t == TOK_FALSE) { 9153955d011SMarcel Moolenaar t = TOK_TRUE; 9163955d011SMarcel Moolenaar } 9173955d011SMarcel Moolenaar } 9183841c287SSimon J. Gerraty return t; 9193955d011SMarcel Moolenaar } 9202c3632d1SSimon J. Gerraty 921956e45f6SSimon J. Gerraty /* Parse a conjunctive factor (nice name, wot?) 922956e45f6SSimon J. Gerraty * 9233955d011SMarcel Moolenaar * F -> T && F | T 9243955d011SMarcel Moolenaar * 9253955d011SMarcel Moolenaar * Results: 9263955d011SMarcel Moolenaar * TOK_TRUE, TOK_FALSE or TOK_ERROR 9273955d011SMarcel Moolenaar */ 9283955d011SMarcel Moolenaar static Token 929956e45f6SSimon J. Gerraty CondParser_Factor(CondParser *par, Boolean doEval) 9303955d011SMarcel Moolenaar { 9313955d011SMarcel Moolenaar Token l, o; 9323955d011SMarcel Moolenaar 933956e45f6SSimon J. Gerraty l = CondParser_Term(par, doEval); 9343955d011SMarcel Moolenaar if (l != TOK_ERROR) { 935956e45f6SSimon J. Gerraty o = CondParser_Token(par, doEval); 9363955d011SMarcel Moolenaar 9373955d011SMarcel Moolenaar if (o == TOK_AND) { 9383955d011SMarcel Moolenaar /* 9393955d011SMarcel Moolenaar * F -> T && F 9403955d011SMarcel Moolenaar * 941956e45f6SSimon J. Gerraty * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we 942956e45f6SSimon J. Gerraty * have to parse the r.h.s. anyway (to throw it away). 943956e45f6SSimon J. Gerraty * If T is TOK_TRUE, the result is the r.h.s., be it a TOK_ERROR 944956e45f6SSimon J. Gerraty * or not. 9453955d011SMarcel Moolenaar */ 9463955d011SMarcel Moolenaar if (l == TOK_TRUE) { 947956e45f6SSimon J. Gerraty l = CondParser_Factor(par, doEval); 9483955d011SMarcel Moolenaar } else { 949956e45f6SSimon J. Gerraty (void)CondParser_Factor(par, FALSE); 9503955d011SMarcel Moolenaar } 9513955d011SMarcel Moolenaar } else { 9523955d011SMarcel Moolenaar /* 9533955d011SMarcel Moolenaar * F -> T 9543955d011SMarcel Moolenaar */ 955956e45f6SSimon J. Gerraty CondParser_PushBack(par, o); 9563955d011SMarcel Moolenaar } 9573955d011SMarcel Moolenaar } 9583841c287SSimon J. Gerraty return l; 9593955d011SMarcel Moolenaar } 9602c3632d1SSimon J. Gerraty 961956e45f6SSimon J. Gerraty /* Main expression production. 962956e45f6SSimon J. Gerraty * 9633955d011SMarcel Moolenaar * E -> F || E | F 9643955d011SMarcel Moolenaar * 9653955d011SMarcel Moolenaar * Results: 9663955d011SMarcel Moolenaar * TOK_TRUE, TOK_FALSE or TOK_ERROR. 9673955d011SMarcel Moolenaar */ 9683955d011SMarcel Moolenaar static Token 969956e45f6SSimon J. Gerraty CondParser_Expr(CondParser *par, Boolean doEval) 9703955d011SMarcel Moolenaar { 9713955d011SMarcel Moolenaar Token l, o; 9723955d011SMarcel Moolenaar 973956e45f6SSimon J. Gerraty l = CondParser_Factor(par, doEval); 9743955d011SMarcel Moolenaar if (l != TOK_ERROR) { 975956e45f6SSimon J. Gerraty o = CondParser_Token(par, doEval); 9763955d011SMarcel Moolenaar 9773955d011SMarcel Moolenaar if (o == TOK_OR) { 9783955d011SMarcel Moolenaar /* 9793955d011SMarcel Moolenaar * E -> F || E 9803955d011SMarcel Moolenaar * 9813955d011SMarcel Moolenaar * A similar thing occurs for ||, except that here we make sure 9823955d011SMarcel Moolenaar * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s. 9833955d011SMarcel Moolenaar * Once again, if l is TOK_FALSE, the result is the r.h.s. and once 9843955d011SMarcel Moolenaar * again if l is TOK_TRUE, we parse the r.h.s. to throw it away. 9853955d011SMarcel Moolenaar */ 9863955d011SMarcel Moolenaar if (l == TOK_FALSE) { 987956e45f6SSimon J. Gerraty l = CondParser_Expr(par, doEval); 9883955d011SMarcel Moolenaar } else { 989956e45f6SSimon J. Gerraty (void)CondParser_Expr(par, FALSE); 9903955d011SMarcel Moolenaar } 9913955d011SMarcel Moolenaar } else { 9923955d011SMarcel Moolenaar /* 9933955d011SMarcel Moolenaar * E -> F 9943955d011SMarcel Moolenaar */ 995956e45f6SSimon J. Gerraty CondParser_PushBack(par, o); 9963955d011SMarcel Moolenaar } 9973955d011SMarcel Moolenaar } 9983841c287SSimon J. Gerraty return l; 9993955d011SMarcel Moolenaar } 10003955d011SMarcel Moolenaar 10012c3632d1SSimon J. Gerraty static CondEvalResult 1002956e45f6SSimon J. Gerraty CondParser_Eval(CondParser *par, Boolean *value) 10032c3632d1SSimon J. Gerraty { 1004956e45f6SSimon J. Gerraty Token res; 10052c3632d1SSimon J. Gerraty 1006956e45f6SSimon J. Gerraty DEBUG1(COND, "CondParser_Eval: %s\n", par->p); 10072c3632d1SSimon J. Gerraty 1008956e45f6SSimon J. Gerraty res = CondParser_Expr(par, TRUE); 1009956e45f6SSimon J. Gerraty if (res != TOK_FALSE && res != TOK_TRUE) 10102c3632d1SSimon J. Gerraty return COND_INVALID; 1011956e45f6SSimon J. Gerraty 1012956e45f6SSimon J. Gerraty if (CondParser_Token(par, TRUE /* XXX: Why TRUE? */) != TOK_EOF) 1013956e45f6SSimon J. Gerraty return COND_INVALID; 1014956e45f6SSimon J. Gerraty 1015956e45f6SSimon J. Gerraty *value = res == TOK_TRUE; 1016956e45f6SSimon J. Gerraty return COND_PARSE; 10172c3632d1SSimon J. Gerraty } 10182c3632d1SSimon J. Gerraty 1019956e45f6SSimon J. Gerraty /* Evaluate the condition, including any side effects from the variable 1020956e45f6SSimon J. Gerraty * expressions in the condition. The condition consists of &&, ||, !, 1021956e45f6SSimon J. Gerraty * function(arg), comparisons and parenthetical groupings thereof. 10223955d011SMarcel Moolenaar * 10233955d011SMarcel Moolenaar * Results: 10243955d011SMarcel Moolenaar * COND_PARSE if the condition was valid grammatically 10253955d011SMarcel Moolenaar * COND_INVALID if not a valid conditional. 10263955d011SMarcel Moolenaar * 10273955d011SMarcel Moolenaar * (*value) is set to the boolean value of the condition 10283955d011SMarcel Moolenaar */ 1029956e45f6SSimon J. Gerraty static CondEvalResult 1030956e45f6SSimon J. Gerraty CondEvalExpression(const struct If *info, const char *cond, Boolean *value, 1031956e45f6SSimon J. Gerraty Boolean eprint, Boolean strictLHS) 10323955d011SMarcel Moolenaar { 1033956e45f6SSimon J. Gerraty CondParser par; 1034*e2eeea75SSimon J. Gerraty CondEvalResult rval; 10353955d011SMarcel Moolenaar 103628a6bc81SSimon J. Gerraty lhsStrict = strictLHS; 103728a6bc81SSimon J. Gerraty 1038*e2eeea75SSimon J. Gerraty cpp_skip_hspace(&cond); 10393955d011SMarcel Moolenaar 1040*e2eeea75SSimon J. Gerraty par.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX; 1041956e45f6SSimon J. Gerraty par.p = cond; 1042956e45f6SSimon J. Gerraty par.curr = TOK_NONE; 1043956e45f6SSimon J. Gerraty par.printedError = FALSE; 10443955d011SMarcel Moolenaar 1045956e45f6SSimon J. Gerraty rval = CondParser_Eval(&par, value); 10463955d011SMarcel Moolenaar 1047956e45f6SSimon J. Gerraty if (rval == COND_INVALID && eprint && !par.printedError) 1048956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); 10493955d011SMarcel Moolenaar 10503955d011SMarcel Moolenaar return rval; 10513955d011SMarcel Moolenaar } 10523955d011SMarcel Moolenaar 1053*e2eeea75SSimon J. Gerraty /* Evaluate a condition in a :? modifier, such as 1054*e2eeea75SSimon J. Gerraty * ${"${VAR}" == value:?yes:no}. */ 1055956e45f6SSimon J. Gerraty CondEvalResult 1056956e45f6SSimon J. Gerraty Cond_EvalCondition(const char *cond, Boolean *out_value) 1057956e45f6SSimon J. Gerraty { 1058956e45f6SSimon J. Gerraty return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE); 1059956e45f6SSimon J. Gerraty } 10603955d011SMarcel Moolenaar 1061*e2eeea75SSimon J. Gerraty /* Evaluate the conditional directive in the line, which is one of: 10623955d011SMarcel Moolenaar * 1063*e2eeea75SSimon J. Gerraty * .if <cond> 1064*e2eeea75SSimon J. Gerraty * .ifmake <cond> 1065*e2eeea75SSimon J. Gerraty * .ifnmake <cond> 1066*e2eeea75SSimon J. Gerraty * .ifdef <cond> 1067*e2eeea75SSimon J. Gerraty * .ifndef <cond> 1068*e2eeea75SSimon J. Gerraty * .elif <cond> 1069*e2eeea75SSimon J. Gerraty * .elifmake <cond> 1070*e2eeea75SSimon J. Gerraty * .elifnmake <cond> 1071*e2eeea75SSimon J. Gerraty * .elifdef <cond> 1072*e2eeea75SSimon J. Gerraty * .elifndef <cond> 1073*e2eeea75SSimon J. Gerraty * .else 1074*e2eeea75SSimon J. Gerraty * .endif 1075*e2eeea75SSimon J. Gerraty * 1076*e2eeea75SSimon J. Gerraty * In these directives, <cond> consists of &&, ||, !, function(arg), 1077*e2eeea75SSimon J. Gerraty * comparisons, expressions, bare words, numbers and strings, and 1078*e2eeea75SSimon J. Gerraty * parenthetical groupings thereof. 1079956e45f6SSimon J. Gerraty * 1080956e45f6SSimon J. Gerraty * Results: 1081*e2eeea75SSimon J. Gerraty * COND_PARSE to continue parsing the lines that follow the 1082*e2eeea75SSimon J. Gerraty * conditional (when <cond> evaluates to TRUE) 1083956e45f6SSimon J. Gerraty * COND_SKIP to skip the lines after the conditional 1084*e2eeea75SSimon J. Gerraty * (when <cond> evaluates to FALSE, or when a previous 1085956e45f6SSimon J. Gerraty * branch has already been taken) 1086956e45f6SSimon J. Gerraty * COND_INVALID if the conditional was not valid, either because of 1087956e45f6SSimon J. Gerraty * a syntax error or because some variable was undefined 1088956e45f6SSimon J. Gerraty * or because the condition could not be evaluated 10893955d011SMarcel Moolenaar */ 10902c3632d1SSimon J. Gerraty CondEvalResult 1091*e2eeea75SSimon J. Gerraty Cond_EvalLine(const char *const line) 10923955d011SMarcel Moolenaar { 1093*e2eeea75SSimon J. Gerraty typedef enum IfState { 1094*e2eeea75SSimon J. Gerraty 1095*e2eeea75SSimon J. Gerraty /* None of the previous <cond> evaluated to TRUE. */ 1096*e2eeea75SSimon J. Gerraty IFS_INITIAL = 0, 1097*e2eeea75SSimon J. Gerraty 1098*e2eeea75SSimon J. Gerraty /* The previous <cond> evaluated to TRUE. 1099*e2eeea75SSimon J. Gerraty * The lines following this condition are interpreted. */ 1100*e2eeea75SSimon J. Gerraty IFS_ACTIVE = 1 << 0, 1101*e2eeea75SSimon J. Gerraty 1102*e2eeea75SSimon J. Gerraty /* The previous directive was an '.else'. */ 1103*e2eeea75SSimon J. Gerraty IFS_SEEN_ELSE = 1 << 1, 1104*e2eeea75SSimon J. Gerraty 1105*e2eeea75SSimon J. Gerraty /* One of the previous <cond> evaluated to TRUE. */ 1106*e2eeea75SSimon J. Gerraty IFS_WAS_ACTIVE = 1 << 2 1107*e2eeea75SSimon J. Gerraty 1108*e2eeea75SSimon J. Gerraty } IfState; 1109*e2eeea75SSimon J. Gerraty 1110*e2eeea75SSimon J. Gerraty static enum IfState *cond_states = NULL; 1111*e2eeea75SSimon J. Gerraty static unsigned int cond_states_cap = 128; 11123955d011SMarcel Moolenaar 11133955d011SMarcel Moolenaar const struct If *ifp; 11143955d011SMarcel Moolenaar Boolean isElif; 11153955d011SMarcel Moolenaar Boolean value; 1116*e2eeea75SSimon J. Gerraty IfState state; 1117*e2eeea75SSimon J. Gerraty const char *p = line; 11183955d011SMarcel Moolenaar 1119*e2eeea75SSimon J. Gerraty if (cond_states == NULL) { 1120*e2eeea75SSimon J. Gerraty cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states); 1121*e2eeea75SSimon J. Gerraty cond_states[0] = IFS_ACTIVE; 112259a02420SSimon J. Gerraty } 11233955d011SMarcel Moolenaar 1124*e2eeea75SSimon J. Gerraty p++; /* skip the leading '.' */ 1125*e2eeea75SSimon J. Gerraty cpp_skip_hspace(&p); 1126*e2eeea75SSimon J. Gerraty 1127*e2eeea75SSimon J. Gerraty /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ 1128*e2eeea75SSimon J. Gerraty if (p[0] == 'e') { 1129*e2eeea75SSimon J. Gerraty if (p[1] != 'l') { 1130*e2eeea75SSimon J. Gerraty if (!is_token(p + 1, "ndif", 4)) { 1131*e2eeea75SSimon J. Gerraty /* Unknown directive. It might still be a transformation 1132*e2eeea75SSimon J. Gerraty * rule like '.elisp.scm', therefore no error message here. */ 11333955d011SMarcel Moolenaar return COND_INVALID; 1134*e2eeea75SSimon J. Gerraty } 1135*e2eeea75SSimon J. Gerraty 1136*e2eeea75SSimon J. Gerraty /* It is an '.endif'. */ 1137*e2eeea75SSimon J. Gerraty /* TODO: check for extraneous <cond> */ 1138*e2eeea75SSimon J. Gerraty 11393955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1140956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less endif"); 11413955d011SMarcel Moolenaar return COND_PARSE; 11423955d011SMarcel Moolenaar } 1143*e2eeea75SSimon J. Gerraty 11443955d011SMarcel Moolenaar /* Return state for previous conditional */ 11453955d011SMarcel Moolenaar cond_depth--; 1146*e2eeea75SSimon J. Gerraty return cond_states[cond_depth] & IFS_ACTIVE 11472c3632d1SSimon J. Gerraty ? COND_PARSE : COND_SKIP; 11483955d011SMarcel Moolenaar } 11493955d011SMarcel Moolenaar 11503955d011SMarcel Moolenaar /* Quite likely this is 'else' or 'elif' */ 1151*e2eeea75SSimon J. Gerraty p += 2; 1152*e2eeea75SSimon J. Gerraty if (is_token(p, "se", 2)) { /* It is an 'else'. */ 1153*e2eeea75SSimon J. Gerraty 1154*e2eeea75SSimon J. Gerraty if (opts.lint && p[2] != '\0') 1155*e2eeea75SSimon J. Gerraty Parse_Error(PARSE_FATAL, 1156*e2eeea75SSimon J. Gerraty "The .else directive does not take arguments."); 1157*e2eeea75SSimon J. Gerraty 11583955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1159956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less else"); 11603955d011SMarcel Moolenaar return COND_PARSE; 11613955d011SMarcel Moolenaar } 11623955d011SMarcel Moolenaar 1163*e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1164*e2eeea75SSimon J. Gerraty if (state == IFS_INITIAL) { 1165*e2eeea75SSimon J. Gerraty state = IFS_ACTIVE | IFS_SEEN_ELSE; 1166*e2eeea75SSimon J. Gerraty } else { 1167*e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) 11683955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "extra else"); 1169*e2eeea75SSimon J. Gerraty state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 11703955d011SMarcel Moolenaar } 1171*e2eeea75SSimon J. Gerraty cond_states[cond_depth] = state; 1172*e2eeea75SSimon J. Gerraty 1173*e2eeea75SSimon J. Gerraty return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; 11743955d011SMarcel Moolenaar } 11753955d011SMarcel Moolenaar /* Assume for now it is an elif */ 11763955d011SMarcel Moolenaar isElif = TRUE; 11773955d011SMarcel Moolenaar } else 11783955d011SMarcel Moolenaar isElif = FALSE; 11793955d011SMarcel Moolenaar 1180*e2eeea75SSimon J. Gerraty if (p[0] != 'i' || p[1] != 'f') { 1181*e2eeea75SSimon J. Gerraty /* Unknown directive. It might still be a transformation rule like 1182*e2eeea75SSimon J. Gerraty * '.elisp.scm', therefore no error message here. */ 1183*e2eeea75SSimon J. Gerraty return COND_INVALID; /* Not an ifxxx or elifxxx line */ 1184*e2eeea75SSimon J. Gerraty } 11853955d011SMarcel Moolenaar 11863955d011SMarcel Moolenaar /* 11873955d011SMarcel Moolenaar * Figure out what sort of conditional it is -- what its default 11883955d011SMarcel Moolenaar * function is, etc. -- by looking in the table of valid "ifs" 11893955d011SMarcel Moolenaar */ 1190*e2eeea75SSimon J. Gerraty p += 2; 11913955d011SMarcel Moolenaar for (ifp = ifs;; ifp++) { 1192*e2eeea75SSimon J. Gerraty if (ifp->form == NULL) { 1193*e2eeea75SSimon J. Gerraty /* TODO: Add error message about unknown directive, 1194*e2eeea75SSimon J. Gerraty * since there is no other known directive that starts with 'el' 1195*e2eeea75SSimon J. Gerraty * or 'if'. 1196*e2eeea75SSimon J. Gerraty * Example: .elifx 123 */ 11973955d011SMarcel Moolenaar return COND_INVALID; 1198*e2eeea75SSimon J. Gerraty } 1199*e2eeea75SSimon J. Gerraty if (is_token(p, ifp->form, ifp->formlen)) { 1200*e2eeea75SSimon J. Gerraty p += ifp->formlen; 12013955d011SMarcel Moolenaar break; 12023955d011SMarcel Moolenaar } 12033955d011SMarcel Moolenaar } 12043955d011SMarcel Moolenaar 12053955d011SMarcel Moolenaar /* Now we know what sort of 'if' it is... */ 12063955d011SMarcel Moolenaar 12073955d011SMarcel Moolenaar if (isElif) { 12083955d011SMarcel Moolenaar if (cond_depth == cond_min_depth) { 1209956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "if-less elif"); 12103955d011SMarcel Moolenaar return COND_PARSE; 12113955d011SMarcel Moolenaar } 1212*e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 1213*e2eeea75SSimon J. Gerraty if (state & IFS_SEEN_ELSE) { 12143955d011SMarcel Moolenaar Parse_Error(PARSE_WARNING, "extra elif"); 1215*e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; 12163955d011SMarcel Moolenaar return COND_SKIP; 12173955d011SMarcel Moolenaar } 1218*e2eeea75SSimon J. Gerraty if (state != IFS_INITIAL) { 1219*e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12203955d011SMarcel Moolenaar return COND_SKIP; 12213955d011SMarcel Moolenaar } 12223955d011SMarcel Moolenaar } else { 12233955d011SMarcel Moolenaar /* Normal .if */ 1224*e2eeea75SSimon J. Gerraty if (cond_depth + 1 >= cond_states_cap) { 122559a02420SSimon J. Gerraty /* 122659a02420SSimon J. Gerraty * This is rare, but not impossible. 122759a02420SSimon J. Gerraty * In meta mode, dirdeps.mk (only runs at level 0) 122859a02420SSimon J. Gerraty * can need more than the default. 122959a02420SSimon J. Gerraty */ 1230*e2eeea75SSimon J. Gerraty cond_states_cap += 32; 1231*e2eeea75SSimon J. Gerraty cond_states = bmake_realloc(cond_states, 1232*e2eeea75SSimon J. Gerraty cond_states_cap * sizeof *cond_states); 12333955d011SMarcel Moolenaar } 1234*e2eeea75SSimon J. Gerraty state = cond_states[cond_depth]; 12353955d011SMarcel Moolenaar cond_depth++; 1236*e2eeea75SSimon J. Gerraty if (!(state & IFS_ACTIVE)) { 12373955d011SMarcel Moolenaar /* If we aren't parsing the data, treat as always false */ 1238*e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12393955d011SMarcel Moolenaar return COND_SKIP; 12403955d011SMarcel Moolenaar } 12413955d011SMarcel Moolenaar } 12423955d011SMarcel Moolenaar 1243956e45f6SSimon J. Gerraty /* And evaluate the conditional expression */ 1244*e2eeea75SSimon J. Gerraty if (CondEvalExpression(ifp, p, &value, TRUE, TRUE) == COND_INVALID) { 12453955d011SMarcel Moolenaar /* Syntax error in conditional, error message already output. */ 12463955d011SMarcel Moolenaar /* Skip everything to matching .endif */ 1247*e2eeea75SSimon J. Gerraty /* XXX: An extra '.else' is not detected in this case. */ 1248*e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_WAS_ACTIVE; 12493955d011SMarcel Moolenaar return COND_SKIP; 12503955d011SMarcel Moolenaar } 12513955d011SMarcel Moolenaar 12523955d011SMarcel Moolenaar if (!value) { 1253*e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_INITIAL; 12543955d011SMarcel Moolenaar return COND_SKIP; 12553955d011SMarcel Moolenaar } 1256*e2eeea75SSimon J. Gerraty cond_states[cond_depth] = IFS_ACTIVE; 12573955d011SMarcel Moolenaar return COND_PARSE; 12583955d011SMarcel Moolenaar } 12593955d011SMarcel Moolenaar 12603955d011SMarcel Moolenaar void 12613955d011SMarcel Moolenaar Cond_restore_depth(unsigned int saved_depth) 12623955d011SMarcel Moolenaar { 1263956e45f6SSimon J. Gerraty unsigned int open_conds = cond_depth - cond_min_depth; 12643955d011SMarcel Moolenaar 12653955d011SMarcel Moolenaar if (open_conds != 0 || saved_depth > cond_depth) { 1266956e45f6SSimon J. Gerraty Parse_Error(PARSE_FATAL, "%u open conditional%s", open_conds, 12673955d011SMarcel Moolenaar open_conds == 1 ? "" : "s"); 12683955d011SMarcel Moolenaar cond_depth = cond_min_depth; 12693955d011SMarcel Moolenaar } 12703955d011SMarcel Moolenaar 12713955d011SMarcel Moolenaar cond_min_depth = saved_depth; 12723955d011SMarcel Moolenaar } 12733955d011SMarcel Moolenaar 12743955d011SMarcel Moolenaar unsigned int 12753955d011SMarcel Moolenaar Cond_save_depth(void) 12763955d011SMarcel Moolenaar { 1277956e45f6SSimon J. Gerraty unsigned int depth = cond_min_depth; 12783955d011SMarcel Moolenaar 12793955d011SMarcel Moolenaar cond_min_depth = cond_depth; 12803955d011SMarcel Moolenaar return depth; 12813955d011SMarcel Moolenaar } 1282