diff options
Diffstat (limited to 'contrib/bmake/cond.c')
-rw-r--r-- | contrib/bmake/cond.c | 477 |
1 files changed, 218 insertions, 259 deletions
diff --git a/contrib/bmake/cond.c b/contrib/bmake/cond.c index 3dd177d5acb8..b3613bbadf5d 100644 --- a/contrib/bmake/cond.c +++ b/contrib/bmake/cond.c @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.334 2022/04/15 09:33:20 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.378 2025/07/06 07:56:16 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -77,16 +77,11 @@ * '.if <cond>', '.elifnmake <cond>', '.else', '.endif'. * * Cond_EvalCondition - * Evaluate the conditional, which is either the argument - * of one of the .if directives or the condition in a - * ':?then:else' variable modifier. + * Evaluate a condition, either from one of the .if + * directives, or from a ':?then:else' modifier. * - * Cond_save_depth - * Cond_restore_depth - * Save and restore the nesting of the conditions, at - * the start and end of including another makefile, to - * ensure that in each makefile the conditional - * directives are well-balanced. + * Cond_EndFile At the end of reading a makefile, ensure that the + * conditional directives are well-balanced. */ #include <errno.h> @@ -95,14 +90,14 @@ #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.334 2022/04/15 09:33:20 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.378 2025/07/06 07:56:16 rillig Exp $"); /* * Conditional expressions conform to this grammar: * Or -> And ('||' And)* * And -> Term ('&&' Term)* * Term -> Function '(' Argument ')' - * Term -> Leaf Operator Leaf + * Term -> Leaf ComparisonOp Leaf * Term -> Leaf * Term -> '(' Or ')' * Term -> '!' Term @@ -110,7 +105,7 @@ MAKE_RCSID("$NetBSD: cond.c,v 1.334 2022/04/15 09:33:20 rillig Exp $"); * Leaf -> Number * Leaf -> VariableExpression * Leaf -> BareWord - * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' + * ComparisonOp -> '==' | '!=' | '>' | '<' | '>=' | '<=' * * BareWord is an unquoted string literal, its evaluation depends on the kind * of '.if' directive. @@ -139,10 +134,10 @@ typedef struct CondParser { /* * The plain '.if ${VAR}' evaluates to true if the value of the - * expression has length > 0. The other '.if' variants delegate - * to evalBare instead, for example '.ifdef ${VAR}' is equivalent to - * '.if defined(${VAR})', checking whether the variable named by the - * expression '${VAR}' is defined. + * expression has length > 0 and is not numerically zero. The other + * '.if' variants delegate to evalBare instead, for example '.ifdef + * ${VAR}' is equivalent to '.if defined(${VAR})', checking whether + * the variable named by the expression '${VAR}' is defined. */ bool plain; @@ -157,29 +152,20 @@ typedef struct CondParser { * expanded before it is evaluated, due to ease of implementation. * This means that at the point where the condition is evaluated, * make cannot know anymore whether the left-hand side had originally - * been a variable expression or a plain word. + * been an expression or a plain word. * * In conditional directives like '.if', the left-hand side must - * either be a variable expression, a quoted string or a number. + * either be a defined expression, a quoted string or a number. */ bool leftUnquotedOK; const char *p; /* The remaining condition to parse */ - Token curr; /* Single push-back token used in parsing */ - - /* - * Whether an error message has already been printed for this - * condition. The first available error message is usually the most - * specific one, therefore it makes sense to suppress the standard - * "Malformed conditional" message. - */ - bool printedError; + Token curr; /* The push-back token, or TOK_NONE */ } CondParser; -static CondResult CondParser_Or(CondParser *par, bool); +static CondResult CondParser_Or(CondParser *, bool); -static unsigned int cond_depth = 0; /* current .if nesting level */ -static unsigned int cond_min_depth = 0; /* depth at makefile open */ +unsigned cond_depth = 0; /* current .if nesting level */ /* Names for ComparisonOp. */ static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" }; @@ -216,45 +202,33 @@ ParseWord(const char **pp, bool doEval) { const char *p = *pp; Buffer word; - int paren_depth; + int depth; - Buf_InitSize(&word, 16); + Buf_Init(&word); - paren_depth = 0; + depth = 0; for (;;) { char ch = *p; if (ch == '\0' || ch == ' ' || ch == '\t') break; - if ((ch == '&' || ch == '|') && paren_depth == 0) + if ((ch == '&' || ch == '|') && depth == 0) break; if (ch == '$') { - /* - * Parse the variable expression and install it as - * part of the argument if it's valid. We tell - * Var_Parse to complain on an undefined variable, - * (XXX: but Var_Parse ignores that request) - * so we don't need to do it. Nor do we return an - * error, though perhaps we should. - */ - VarEvalMode emode = doEval - ? VARE_UNDEFERR - : VARE_PARSE_ONLY; - FStr nestedVal; - (void)Var_Parse(&p, SCOPE_CMDLINE, emode, &nestedVal); + VarEvalMode emode = doEval ? VARE_EVAL : VARE_PARSE; + FStr nestedVal = Var_Parse(&p, SCOPE_CMDLINE, emode); /* TODO: handle errors */ Buf_AddStr(&word, nestedVal.str); FStr_Done(&nestedVal); continue; } if (ch == '(') - paren_depth++; - else if (ch == ')' && --paren_depth < 0) + depth++; + else if (ch == ')' && --depth < 0) break; Buf_AddByte(&word, ch); p++; } - cpp_skip_hspace(&p); *pp = p; return Buf_DoneData(&word); @@ -262,14 +236,16 @@ ParseWord(const char **pp, bool doEval) /* Parse the function argument, including the surrounding parentheses. */ static char * -ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) +ParseFuncArg(const char **pp, bool doEval, const char *func) { - const char *p = *pp; + const char *p = *pp, *argStart, *argEnd; char *res; - p++; /* Skip opening '(' - verified by caller */ + p++; /* skip the '(' */ cpp_skip_hspace(&p); + argStart = p; res = ParseWord(&p, doEval); + argEnd = p; cpp_skip_hspace(&p); if (*p++ != ')') { @@ -278,8 +254,8 @@ ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) len++; Parse_Error(PARSE_FATAL, - "Missing closing parenthesis for %.*s()", len, func); - par->printedError = true; + "Missing \")\" after argument \"%.*s\" for \"%.*s\"", + (int)(argEnd - argStart), argStart, len, func); free(res); return NULL; } @@ -300,10 +276,20 @@ static bool FuncMake(const char *targetPattern) { StringListNode *ln; - - for (ln = opts.create.first; ln != NULL; ln = ln->next) - if (Str_Match(ln->datum, targetPattern)) + bool warned = false; + + for (ln = opts.create.first; ln != NULL; ln = ln->next) { + StrMatchResult res = Str_Match(ln->datum, targetPattern); + if (res.error != NULL && !warned) { + warned = true; + Parse_Error(PARSE_WARNING, + "%s in pattern argument \"%s\" " + "to function \"make\"", + res.error, targetPattern); + } + if (res.matched) return true; + } return false; } @@ -315,14 +301,16 @@ FuncExists(const char *file) char *path; path = Dir_FindFile(file, &dirSearchPath); - DEBUG2(COND, "exists(%s) result is \"%s\"\n", - file, path != NULL ? path : ""); result = path != NULL; + if (result) + DEBUG2(COND, "\"%s\" exists in \"%s\"\n", file, path); + else + DEBUG1(COND, "\"%s\" does not exist\n", file); free(path); return result; } -/* See if the given node exists and is an actual target. */ +/* See if the given node is an actual target. */ static bool FuncTarget(const char *node) { @@ -330,10 +318,7 @@ FuncTarget(const char *node) return gn != NULL && GNode_IsTarget(gn); } -/* - * See if the given node exists and is an actual target with commands - * associated with it. - */ +/* See if the given node is an actual target with commands. */ static bool FuncCommands(const char *node) { @@ -343,7 +328,7 @@ FuncCommands(const char *node) } /* - * Convert the string into a floating-point number. Accepted formats are + * Convert the string to a floating point number. Accepted formats are * base-10 integer, base-16 integer and finite floating point numbers. */ static bool @@ -383,78 +368,55 @@ is_separator(char ch) } /* - * In a quoted or unquoted string literal or a number, parse a variable - * expression. + * In a quoted or unquoted string literal or a number, parse an + * expression and add its value to the buffer. * - * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} + * Return whether to continue parsing the leaf. + * + * Examples: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} */ static bool CondParser_StringExpr(CondParser *par, const char *start, bool doEval, bool quoted, - Buffer *buf, FStr *inout_str) + Buffer *buf, FStr *out_str) { VarEvalMode emode; const char *p; - bool atStart; - VarParseResult parseResult; + bool outsideQuotes; - emode = doEval && quoted ? VARE_WANTRES - : doEval ? VARE_UNDEFERR - : VARE_PARSE_ONLY; + emode = doEval && quoted ? VARE_EVAL + : doEval ? VARE_EVAL_DEFINED_LOUD + : VARE_PARSE; p = par->p; - atStart = p == start; - parseResult = Var_Parse(&p, SCOPE_CMDLINE, emode, inout_str); - /* TODO: handle errors */ - if (inout_str->str == var_Error) { - if (parseResult == VPR_ERR) { - /* - * FIXME: Even if an error occurs, there is no - * guarantee that it is reported. - * - * See cond-token-plain.mk $$$$$$$$. - */ - par->printedError = true; - } - /* - * XXX: Can there be any situation in which a returned - * var_Error needs to be freed? - */ - FStr_Done(inout_str); - /* - * Even if !doEval, we still report syntax errors, which is - * what getting var_Error back with !doEval means. - */ - *inout_str = FStr_InitRefer(NULL); + outsideQuotes = p == start; + *out_str = Var_Parse(&p, SCOPE_CMDLINE, emode); + if (out_str->str == var_Error) { + FStr_Done(out_str); + *out_str = FStr_InitRefer(NULL); return false; } par->p = p; - /* - * If the '$' started the string literal (which means no quotes), and - * the variable expression is followed by a space, looks like a - * comparison operator or is the end of the expression, we are done. - */ - if (atStart && is_separator(par->p[0])) + if (outsideQuotes && is_separator(par->p[0])) return false; - Buf_AddStr(buf, inout_str->str); - FStr_Done(inout_str); - *inout_str = FStr_InitRefer(NULL); /* not finished yet */ + Buf_AddStr(buf, out_str->str); + FStr_Done(out_str); + *out_str = FStr_InitRefer(NULL); /* not finished yet */ return true; } /* - * Parse a string from a variable expression or an optionally quoted string, + * Parse a string from an expression or an optionally quoted string, * on the left-hand and right-hand sides of comparisons. * - * Results: - * Returns the string without any enclosing quotes, or NULL on error. - * Sets out_quoted if the leaf was a quoted string literal. + * Return the string without any enclosing quotes, or NULL on error. + * Set out_quoted if the leaf was a quoted string literal. */ -static void +static FStr CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, - FStr *out_str, bool *out_quoted) + bool *out_quoted) { Buffer buf; FStr str; @@ -475,12 +437,14 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, if (par->p[0] != '\0') { Buf_AddByte(&buf, par->p[0]); par->p++; - } + } else + Parse_Error(PARSE_FATAL, + "Unfinished backslash escape sequence"); continue; case '"': par->p++; if (quoted) - goto return_buf; /* skip the closing quote */ + goto return_buf; Buf_AddByte(&buf, '"'); continue; case ')': /* see is_separator */ @@ -503,10 +467,6 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, default: if (!unquotedOK && !quoted && *start != '$' && !ch_isdigit(*start)) { - /* - * The left-hand side must be quoted, - * a variable expression or a number. - */ str = FStr_InitRefer(NULL); goto return_str; } @@ -515,12 +475,15 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, continue; } } + if (quoted) + Parse_Error(PARSE_FATAL, + "Unfinished string literal \"%s\"", start); return_buf: str = FStr_InitOwn(buf.data); buf.data = NULL; return_str: Buf_Done(&buf); - *out_str = str; + return str; } /* @@ -528,30 +491,16 @@ return_str: * ".if 0". */ static bool -EvalNotEmpty(CondParser *par, const char *value, bool quoted) +EvalTruthy(CondParser *par, const char *value, bool quoted) { double num; - /* For .ifxxx "...", check for non-empty string. */ if (quoted) return value[0] != '\0'; - - /* For .ifxxx <number>, compare against zero */ if (TryParseNumber(value, &num)) return num != 0.0; - - /* - * For .if ${...}, check for non-empty string. This is different - * from the evaluation function from that .if variant, which would - * test whether a variable of the given name were defined. - */ - /* - * XXX: Whitespace should count as empty, just as in - * CondParser_FuncCallEmpty. - */ if (par->plain) return value[0] != '\0'; - return par->evalBare(value) != par->negateEvalBare; } @@ -570,21 +519,21 @@ EvalCompareNum(double lhs, ComparisonOp op, double rhs) return lhs > rhs; case GE: return lhs >= rhs; - case NE: - return lhs != rhs; - default: + case EQ: return lhs == rhs; + default: + return lhs != rhs; } } static Token -EvalCompareStr(CondParser *par, const char *lhs, - ComparisonOp op, const char *rhs) +EvalCompareStr(const char *lhs, ComparisonOp op, const char *rhs) { if (op != EQ && op != NE) { Parse_Error(PARSE_FATAL, - "String comparison operator must be either == or !="); - par->printedError = true; + "Comparison with \"%s\" requires both operands " + "\"%s\" and \"%s\" to be numeric", + opname[op], lhs, rhs); return TOK_ERROR; } @@ -594,7 +543,7 @@ EvalCompareStr(CondParser *par, const char *lhs, /* Evaluate a comparison, such as "${VAR} == 12345". */ static Token -EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, +EvalCompare(const char *lhs, bool lhsQuoted, ComparisonOp op, const char *rhs, bool rhsQuoted) { double left, right; @@ -603,7 +552,7 @@ EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) return ToToken(EvalCompareNum(left, op, right)); - return EvalCompareStr(par, lhs, op, rhs); + return EvalCompareStr(lhs, op, rhs); } static bool @@ -642,15 +591,14 @@ CondParser_Comparison(CondParser *par, bool doEval) ComparisonOp op; bool lhsQuoted, rhsQuoted; - CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhs, &lhsQuoted); + lhs = CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhsQuoted); if (lhs.str == NULL) goto done_lhs; CondParser_SkipWhitespace(par); if (!CondParser_ComparisonOp(par, &op)) { - /* Unknown operator, compare against an empty string or 0. */ - t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); + t = ToToken(doEval && EvalTruthy(par, lhs.str, lhsQuoted)); goto done_lhs; } @@ -658,15 +606,14 @@ CondParser_Comparison(CondParser *par, bool doEval) if (par->p[0] == '\0') { Parse_Error(PARSE_FATAL, - "Missing right-hand side of operator '%s'", opname[op]); - par->printedError = true; + "Missing right-hand side of operator \"%s\"", opname[op]); goto done_lhs; } - CondParser_Leaf(par, doEval, true, &rhs, &rhsQuoted); + rhs = CondParser_Leaf(par, doEval, true, &rhsQuoted); t = rhs.str == NULL ? TOK_ERROR : !doEval ? TOK_FALSE - : EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); + : EvalCompare(lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); FStr_Done(&rhs); done_lhs: @@ -681,20 +628,19 @@ done_lhs: static bool CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) { - const char *cp = par->p; + const char *p = par->p; Token tok; FStr val; - if (!skip_string(&cp, "empty")) + if (!skip_string(&p, "empty")) return false; - cpp_skip_whitespace(&cp); - if (*cp != '(') + cpp_skip_whitespace(&p); + if (*p != '(') return false; - cp--; /* Make cp[1] point to the '('. */ - (void)Var_Parse(&cp, SCOPE_CMDLINE, - doEval ? VARE_WANTRES : VARE_PARSE_ONLY, &val); + p--; /* Make p[1] point to the '('. */ + val = Var_Parse(&p, SCOPE_CMDLINE, doEval ? VARE_EVAL : VARE_PARSE); /* TODO: handle errors */ if (val.str == var_Error) @@ -706,7 +652,7 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) FStr_Done(&val); *out_token = tok; - par->p = cp; + par->p = p; return true; } @@ -736,7 +682,7 @@ CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) if (*p != '(') return false; - arg = ParseFuncArg(par, &p, doEval, fn_name); + arg = ParseFuncArg(&p, doEval, fn_name); *out_token = ToToken(doEval && arg != NULL && arg[0] != '\0' && fn(arg)); free(arg); @@ -748,7 +694,7 @@ CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) /* * Parse a comparison that neither starts with '"' nor '$', such as the * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without - * operator, which is a number, a variable expression or a string literal. + * operator, which is a number, an expression or a string literal. * * TODO: Can this be merged into CondParser_Comparison? */ @@ -757,37 +703,39 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) { Token t; char *arg; - const char *cp; + const char *p; - /* Push anything numeric through the compare expression */ - cp = par->p; - if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') + p = par->p; + if (ch_isdigit(p[0]) || p[0] == '-' || p[0] == '+') return CondParser_Comparison(par, doEval); /* - * Most likely we have a naked token to apply the default function to. - * However ".if a == b" gets here when the "a" is unquoted and doesn't - * start with a '$'. This surprises people. + * Most likely we have a bare word to apply the default function to. + * However, ".if a == b" gets here when the "a" is unquoted and + * doesn't start with a '$'. This surprises people. * If what follows the function argument is a '=' or '!' then the * syntax would be invalid if we did "defined(a)" - so instead treat * as an expression. */ /* - * XXX: In edge cases, a variable expression may be evaluated twice, + * XXX: In edge cases, an expression may be evaluated twice, * see cond-token-plain.mk, keyword 'twice'. */ - arg = ParseWord(&cp, doEval); + arg = ParseWord(&p, doEval); assert(arg[0] != '\0'); + cpp_skip_hspace(&p); - if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>') + if (*p == '=' || *p == '!' || *p == '<' || *p == '>') { + free(arg); return CondParser_Comparison(par, doEval); - par->p = cp; + } + par->p = p; /* * Evaluate the argument using the default function. * This path always treats .if as .ifdef. To get here, the character * after .if must have been taken literally, so the argument cannot - * be empty - even if it contained a variable expansion. + * be empty - even if it contained an expression. */ t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); free(arg); @@ -822,9 +770,8 @@ CondParser_Token(CondParser *par, bool doEval) par->p++; if (par->p[0] == '|') par->p++; - else if (opts.strict) { - Parse_Error(PARSE_FATAL, "Unknown operator '|'"); - par->printedError = true; + else { + Parse_Error(PARSE_FATAL, "Unknown operator \"|\""); return TOK_ERROR; } return TOK_OR; @@ -833,9 +780,8 @@ CondParser_Token(CondParser *par, bool doEval) par->p++; if (par->p[0] == '&') par->p++; - else if (opts.strict) { - Parse_Error(PARSE_FATAL, "Unknown operator '&'"); - par->printedError = true; + else { + Parse_Error(PARSE_FATAL, "Unknown operator \"&\""); return TOK_ERROR; } return TOK_AND; @@ -882,7 +828,7 @@ CondParser_Skip(CondParser *par, Token t) /* * Term -> '(' Or ')' * Term -> '!' Term - * Term -> Leaf Operator Leaf + * Term -> Leaf ComparisonOp Leaf * Term -> Leaf */ static CondResult @@ -890,12 +836,13 @@ CondParser_Term(CondParser *par, bool doEval) { CondResult res; Token t; + bool neg = false; - t = CondParser_Token(par, doEval); - if (t == TOK_TRUE) - return CR_TRUE; - if (t == TOK_FALSE) - return CR_FALSE; + while ((t = CondParser_Token(par, doEval)) == TOK_NOT) + neg = !neg; + + if (t == TOK_TRUE || t == TOK_FALSE) + return neg == (t == TOK_FALSE) ? CR_TRUE : CR_FALSE; if (t == TOK_LPAREN) { res = CondParser_Or(par, doEval); @@ -903,16 +850,7 @@ CondParser_Term(CondParser *par, bool doEval) return CR_ERROR; if (CondParser_Token(par, doEval) != TOK_RPAREN) return CR_ERROR; - return res; - } - - if (t == TOK_NOT) { - res = CondParser_Term(par, doEval); - if (res == CR_TRUE) - res = CR_FALSE; - else if (res == CR_FALSE) - res = CR_TRUE; - return res; + return neg == (res == CR_FALSE) ? CR_TRUE : CR_FALSE; } return CR_ERROR; @@ -960,22 +898,8 @@ CondParser_Or(CondParser *par, bool doEval) return res; } -static CondResult -CondParser_Eval(CondParser *par) -{ - CondResult res; - - DEBUG1(COND, "CondParser_Eval: %s\n", par->p); - - res = CondParser_Or(par, true); - if (res != CR_ERROR && CondParser_Token(par, false) != TOK_EOF) - return CR_ERROR; - - return res; -} - /* - * Evaluate the condition, including any side effects from the variable + * Evaluate the condition, including any side effects from the * expressions in the condition. The condition consists of &&, ||, !, * function(arg), comparisons and parenthetical groupings thereof. */ @@ -986,6 +910,7 @@ CondEvalExpression(const char *cond, bool plain, { CondParser par; CondResult rval; + int parseErrorsBefore = parseErrors; cpp_skip_hspace(&cond); @@ -995,12 +920,16 @@ CondEvalExpression(const char *cond, bool plain, par.leftUnquotedOK = leftUnquotedOK; par.p = cond; par.curr = TOK_NONE; - par.printedError = false; - rval = CondParser_Eval(&par); + DEBUG1(COND, "CondParser_Eval: %s\n", par.p); + rval = CondParser_Or(&par, true); + if (par.curr != TOK_EOF) + rval = CR_ERROR; - if (rval == CR_ERROR && eprint && !par.printedError) - Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); + if (parseErrors != parseErrorsBefore) + rval = CR_ERROR; + else if (rval == CR_ERROR && eprint) + Parse_Error(PARSE_FATAL, "Malformed conditional \"%s\"", cond); return rval; } @@ -1048,12 +977,6 @@ DetermineKindOfConditional(const char **pp, bool *out_plain, return true; unknown_directive: - /* - * TODO: Add error message about unknown directive, since there is no - * other known directive that starts with 'el' or 'if'. - * - * Example: .elifx 123 - */ return false; } @@ -1082,7 +1005,7 @@ unknown_directive: * conditional (when <cond> evaluates to true) * CR_FALSE to skip the lines after the conditional * (when <cond> evaluates to false, or when a previous - * branch has already been taken) + * branch was already taken) * CR_ERROR if the conditional was not valid, either because of * a syntax error or because some variable was undefined * or because the condition could not be evaluated @@ -1110,7 +1033,7 @@ Cond_EvalLine(const char *line) } IfState; static enum IfState *cond_states = NULL; - static unsigned int cond_states_cap = 128; + static unsigned cond_states_cap = 128; bool plain; bool (*evalBare)(const char *); @@ -1129,33 +1052,28 @@ Cond_EvalLine(const char *line) p++; /* skip the leading '.' */ cpp_skip_hspace(&p); - if (IsEndif(p)) { /* It is an '.endif'. */ + if (IsEndif(p)) { if (p[5] != '\0') { Parse_Error(PARSE_FATAL, "The .endif directive does not take arguments"); } - if (cond_depth == cond_min_depth) { + if (cond_depth == CurFile_CondMinDepth()) { Parse_Error(PARSE_FATAL, "if-less endif"); return CR_TRUE; } /* Return state for previous conditional */ cond_depth--; + Parse_GuardEndif(); return cond_states[cond_depth] & IFS_ACTIVE ? CR_TRUE : CR_FALSE; } /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ if (p[0] == 'e') { - if (p[1] != 'l') { - /* - * Unknown directive. It might still be a - * transformation rule like '.err.txt', - * therefore no error message here. - */ + if (p[1] != 'l') return CR_ERROR; - } /* Quite likely this is 'else' or 'elif' */ p += 2; @@ -1165,10 +1083,11 @@ Cond_EvalLine(const char *line) "The .else directive " "does not take arguments"); - if (cond_depth == cond_min_depth) { + if (cond_depth == CurFile_CondMinDepth()) { Parse_Error(PARSE_FATAL, "if-less else"); return CR_TRUE; } + Parse_GuardElse(); state = cond_states[cond_depth]; if (state == IFS_INITIAL) { @@ -1188,22 +1107,18 @@ Cond_EvalLine(const char *line) } else isElif = false; - if (p[0] != 'i' || p[1] != 'f') { - /* - * Unknown directive. It might still be a transformation rule - * like '.elisp.scm', therefore no error message here. - */ - return CR_ERROR; /* Not an ifxxx or elifxxx line */ - } + if (p[0] != 'i' || p[1] != 'f') + return CR_ERROR; if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) return CR_ERROR; if (isElif) { - if (cond_depth == cond_min_depth) { + if (cond_depth == CurFile_CondMinDepth()) { Parse_Error(PARSE_FATAL, "if-less elif"); return CR_TRUE; } + Parse_GuardElse(); state = cond_states[cond_depth]; if (state & IFS_SEEN_ELSE) { Parse_Error(PARSE_WARNING, "extra elif"); @@ -1230,16 +1145,11 @@ Cond_EvalLine(const char *line) state = cond_states[cond_depth]; cond_depth++; if (!(state & IFS_ACTIVE)) { - /* - * If we aren't parsing the data, - * treat as always false. - */ cond_states[cond_depth] = IFS_WAS_ACTIVE; return CR_FALSE; } } - /* And evaluate the conditional expression */ res = CondEvalExpression(p, plain, evalBare, negate, true, false); if (res == CR_ERROR) { /* Syntax error, error message already output. */ @@ -1253,25 +1163,74 @@ Cond_EvalLine(const char *line) return res; } -void -Cond_restore_depth(unsigned int saved_depth) +static bool +ParseVarnameGuard(const char **pp, const char **varname) { - unsigned int open_conds = cond_depth - cond_min_depth; + const char *p = *pp; - if (open_conds != 0 || saved_depth > cond_depth) { - Parse_Error(PARSE_FATAL, "%u open conditional%s", - open_conds, open_conds == 1 ? "" : "s"); - cond_depth = cond_min_depth; + if (ch_isalpha(*p) || *p == '_') { + while (ch_isalnum(*p) || *p == '_') + p++; + *varname = *pp; + *pp = p; + return true; } + return false; +} - cond_min_depth = saved_depth; +/* Extracts the multiple-inclusion guard from a conditional, if any. */ +Guard * +Cond_ExtractGuard(const char *line) +{ + const char *p, *varname; + Substring dir; + Guard *guard; + + p = line + 1; /* skip the '.' */ + cpp_skip_hspace(&p); + + dir.start = p; + while (ch_isalpha(*p)) + p++; + dir.end = p; + cpp_skip_hspace(&p); + + if (Substring_Equals(dir, "if")) { + if (skip_string(&p, "!defined(")) { + if (ParseVarnameGuard(&p, &varname) + && strcmp(p, ")") == 0) + goto found_variable; + } else if (skip_string(&p, "!target(")) { + const char *arg_p = p; + free(ParseWord(&p, false)); + if (strcmp(p, ")") == 0) { + guard = bmake_malloc(sizeof(*guard)); + guard->kind = GK_TARGET; + guard->name = ParseWord(&arg_p, true); + return guard; + } + } + } else if (Substring_Equals(dir, "ifndef")) { + if (ParseVarnameGuard(&p, &varname) && *p == '\0') + goto found_variable; + } + return NULL; + +found_variable: + guard = bmake_malloc(sizeof(*guard)); + guard->kind = GK_VARIABLE; + guard->name = bmake_strsedup(varname, p); + return guard; } -unsigned int -Cond_save_depth(void) +void +Cond_EndFile(void) { - unsigned int depth = cond_min_depth; + unsigned open_conds = cond_depth - CurFile_CondMinDepth(); - cond_min_depth = cond_depth; - return depth; + if (open_conds != 0) { + Parse_Error(PARSE_FATAL, "%u open conditional%s", + open_conds, open_conds == 1 ? "" : "s"); + cond_depth = CurFile_CondMinDepth(); + } } |