cond.c (bmake-20201101) | : | cond.c (bmake-20201117) | ||
---|---|---|---|---|
/* $NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $ */ | /* $NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $ */ | |||
/* | /* | |||
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. | * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. | |||
* All rights reserved. | * All rights reserved. | |||
* | * | |||
* This code is derived from software contributed to Berkeley by | * This code is derived from software contributed to Berkeley by | |||
* Adam de Boor. | * Adam de Boor. | |||
* | * | |||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | |||
skipping to change at line 75 | skipping to change at line 75 | |||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
* SUCH DAMAGE. | * SUCH DAMAGE. | |||
*/ | */ | |||
/* Handling of conditionals in a makefile. | /* Handling of conditionals in a makefile. | |||
* | * | |||
* Interface: | * Interface: | |||
* Cond_EvalLine Evaluate the conditional. | * Cond_EvalLine Evaluate the conditional directive, such as | |||
* '.if <cond>', '.elifnmake <cond>', '.else', '.endif'. | ||||
* | * | |||
* Cond_EvalCondition | * Cond_EvalCondition | |||
* Evaluate the conditional, which is either the argument | * Evaluate the conditional, which is either the argument | |||
* of one of the .if directives or the condition in a | * of one of the .if directives or the condition in a | |||
* ':?then:else' variable modifier. | * ':?then:else' variable modifier. | |||
* | * | |||
* Cond_save_depth | * Cond_save_depth | |||
* Cond_restore_depth | * Cond_restore_depth | |||
* Save and restore the nesting of the conditions, at | * Save and restore the nesting of the conditions, at | |||
* the start and end of including another makefile, to | * the start and end of including another makefile, to | |||
* ensure that in each makefile the conditional | * ensure that in each makefile the conditional | |||
* directives are well-balanced. | * directives are well-balanced. | |||
*/ | */ | |||
#include <errno.h> | #include <errno.h> | |||
#include "make.h" | #include "make.h" | |||
#include "dir.h" | #include "dir.h" | |||
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ | /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ | |||
MAKE_RCSID("$NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $"); | MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $"); | |||
/* | /* | |||
* The parsing of conditional expressions is based on this grammar: | * The parsing of conditional expressions is based on this grammar: | |||
* E -> F || E | * E -> F || E | |||
* E -> F | * E -> F | |||
* F -> T && F | * F -> T && F | |||
* F -> T | * F -> T | |||
* T -> defined(variable) | * T -> defined(variable) | |||
* T -> make(target) | * T -> make(target) | |||
* T -> exists(file) | * T -> exists(file) | |||
skipping to change at line 165 | skipping to change at line 166 | |||
static unsigned int cond_depth = 0; /* current .if nesting level */ | static unsigned int cond_depth = 0; /* current .if nesting level */ | |||
static unsigned int cond_min_depth = 0; /* depth at makefile open */ | static unsigned int cond_min_depth = 0; /* depth at makefile open */ | |||
/* | /* | |||
* Indicate when we should be strict about lhs of comparisons. | * Indicate when we should be strict about lhs of comparisons. | |||
* In strict mode, the lhs must be a variable expression or a string literal | * In strict mode, the lhs must be a variable expression or a string literal | |||
* in quotes. In non-strict mode it may also be an unquoted string literal. | * in quotes. In non-strict mode it may also be an unquoted string literal. | |||
* | * | |||
* TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) | * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) | |||
* FALSE when CondEvalExpression is called from ApplyModifier_IfElse | * FALSE when CondEvalExpression is called from ApplyModifier_IfElse | |||
* since lhs is already expanded and we cannot tell if | * since lhs is already expanded, and at that point we cannot tell if | |||
* it was a variable reference or not. | * it was a variable reference or not. | |||
*/ | */ | |||
static Boolean lhsStrict; | static Boolean lhsStrict; | |||
static int | static int | |||
is_token(const char *str, const char *tok, size_t len) | is_token(const char *str, const char *tok, size_t len) | |||
{ | { | |||
return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); | return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); | |||
} | } | |||
static Token | ||||
ToToken(Boolean cond) | ||||
{ | ||||
return cond ? TOK_TRUE : TOK_FALSE; | ||||
} | ||||
/* Push back the most recent token read. We only need one level of this. */ | /* Push back the most recent token read. We only need one level of this. */ | |||
static void | static void | |||
CondParser_PushBack(CondParser *par, Token t) | CondParser_PushBack(CondParser *par, Token t) | |||
{ | { | |||
assert(par->curr == TOK_NONE); | assert(par->curr == TOK_NONE); | |||
assert(t != TOK_NONE); | assert(t != TOK_NONE); | |||
par->curr = t; | par->curr = t; | |||
} | } | |||
skipping to change at line 203 | skipping to change at line 210 | |||
* | * | |||
* Arguments: | * Arguments: | |||
* *pp initially points at the '(', | * *pp initially points at the '(', | |||
* upon successful return it points right after the ')'. | * upon successful return it points right after the ')'. | |||
* | * | |||
* *out_arg receives the argument as string. | * *out_arg receives the argument as string. | |||
* | * | |||
* func says whether the argument belongs to an actual function, or | * func says whether the argument belongs to an actual function, or | |||
* whether the parsed argument is passed to the default function. | * whether the parsed argument is passed to the default function. | |||
* | * | |||
* Return the length of the argument. */ | * Return the length of the argument, or 0 on error. */ | |||
static size_t | static size_t | |||
ParseFuncArg(const char **pp, Boolean doEval, const char *func, | ParseFuncArg(const char **pp, Boolean doEval, const char *func, | |||
char **out_arg) { | char **out_arg) { | |||
const char *p = *pp; | const char *p = *pp; | |||
Buffer argBuf; | Buffer argBuf; | |||
int paren_depth; | int paren_depth; | |||
size_t argLen; | size_t argLen; | |||
if (func != NULL) | if (func != NULL) | |||
p++; /* Skip opening '(' - verified by caller */ | p++; /* Skip opening '(' - verified by caller */ | |||
if (*p == '\0') { | if (*p == '\0') { | |||
/* | *out_arg = NULL; /* Missing closing parenthesis: */ | |||
* No arguments whatsoever. Because 'make' and 'defined' aren't really | return 0; /* .if defined( */ | |||
* "reserved words", we don't print a message. I think this is better | ||||
* than hitting the user with a warning message every time s/he uses | ||||
* the word 'make' or 'defined' at the beginning of a symbol... | ||||
*/ | ||||
*out_arg = NULL; | ||||
return 0; | ||||
} | } | |||
while (*p == ' ' || *p == '\t') { | cpp_skip_hspace(&p); | |||
p++; | ||||
} | ||||
Buf_Init(&argBuf, 16); | Buf_InitSize(&argBuf, 16); | |||
paren_depth = 0; | paren_depth = 0; | |||
for (;;) { | for (;;) { | |||
char ch = *p; | char ch = *p; | |||
if (ch == 0 || ch == ' ' || ch == '\t') | if (ch == '\0' || ch == ' ' || ch == '\t') | |||
break; | break; | |||
if ((ch == '&' || ch == '|') && paren_depth == 0) | if ((ch == '&' || ch == '|') && paren_depth == 0) | |||
break; | break; | |||
if (*p == '$') { | if (*p == '$') { | |||
/* | /* | |||
* Parse the variable spec and install it as part of the argument | * Parse the variable spec and install it as part of the argument | |||
* if it's valid. We tell Var_Parse to complain on an undefined | * if it's valid. We tell Var_Parse to complain on an undefined | |||
* variable, so we don't need to do it. Nor do we return an error, | * variable, so we don't need to do it. Nor do we return an error, | |||
* though perhaps we should... | * though perhaps we should... | |||
*/ | */ | |||
void *nestedVal_freeIt; | void *nestedVal_freeIt; | |||
VarEvalFlags eflags = VARE_UNDEFERR | (doEval ? VARE_WANTRES : 0); | VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR | |||
: VARE_NONE; | ||||
const char *nestedVal; | const char *nestedVal; | |||
(void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal, | (void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal, | |||
&nestedVal_freeIt); | &nestedVal_freeIt); | |||
/* TODO: handle errors */ | /* TODO: handle errors */ | |||
Buf_AddStr(&argBuf, nestedVal); | Buf_AddStr(&argBuf, nestedVal); | |||
free(nestedVal_freeIt); | free(nestedVal_freeIt); | |||
continue; | continue; | |||
} | } | |||
if (ch == '(') | if (ch == '(') | |||
paren_depth++; | paren_depth++; | |||
else if (ch == ')' && --paren_depth < 0) | else if (ch == ')' && --paren_depth < 0) | |||
break; | break; | |||
Buf_AddByte(&argBuf, *p); | Buf_AddByte(&argBuf, *p); | |||
p++; | p++; | |||
} | } | |||
*out_arg = Buf_GetAll(&argBuf, &argLen); | *out_arg = Buf_GetAll(&argBuf, &argLen); | |||
Buf_Destroy(&argBuf, FALSE); | Buf_Destroy(&argBuf, FALSE); | |||
while (*p == ' ' || *p == '\t') { | cpp_skip_hspace(&p); | |||
p++; | ||||
} | ||||
if (func != NULL && *p++ != ')') { | if (func != NULL && *p++ != ')') { | |||
Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", | Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", | |||
func); | func); | |||
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | |||
return 0; | return 0; | |||
} | } | |||
*pp = p; | *pp = p; | |||
return argLen; | return argLen; | |||
skipping to change at line 312 | skipping to change at line 310 | |||
} | } | |||
/* See if the given file exists. */ | /* See if the given file exists. */ | |||
static Boolean | static Boolean | |||
FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) | FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) | |||
{ | { | |||
Boolean result; | Boolean result; | |||
char *path; | char *path; | |||
path = Dir_FindFile(arg, dirSearchPath); | path = Dir_FindFile(arg, dirSearchPath); | |||
DEBUG2(COND, "exists(%s) result is \"%s\"\n", arg, path ? path : ""); | DEBUG2(COND, "exists(%s) result is \"%s\"\n", | |||
if (path != NULL) { | arg, path != NULL ? path : ""); | |||
result = TRUE; | result = path != NULL; | |||
free(path); | free(path); | |||
} else { | ||||
result = FALSE; | ||||
} | ||||
return result; | return result; | |||
} | } | |||
/* See if the given node exists and is an actual target. */ | /* See if the given node exists and is an actual target. */ | |||
static Boolean | static Boolean | |||
FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) | FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) | |||
{ | { | |||
GNode *gn = Targ_FindNode(arg); | GNode *gn = Targ_FindNode(arg); | |||
return gn != NULL && GNode_IsTarget(gn); | return gn != NULL && GNode_IsTarget(gn); | |||
} | } | |||
/* See if the given node exists and is an actual target with commands | /* See if the given node exists and is an actual target with commands | |||
* associated with it. */ | * associated with it. */ | |||
static Boolean | static Boolean | |||
FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) | FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) | |||
{ | { | |||
GNode *gn = Targ_FindNode(arg); | GNode *gn = Targ_FindNode(arg); | |||
return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands); | return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands); | |||
} | } | |||
/*- | /* | |||
* Convert the given number into a double. | * Convert the given number into a double. | |||
* We try a base 10 or 16 integer conversion first, if that fails | * We try a base 10 or 16 integer conversion first, if that fails | |||
* then we try a floating point conversion instead. | * then we try a floating point conversion instead. | |||
* | * | |||
* Results: | * Results: | |||
* Sets 'value' to double value of string. | ||||
* Returns TRUE if the conversion succeeded. | * Returns TRUE if the conversion succeeded. | |||
* Sets 'out_value' to the converted number. | ||||
*/ | */ | |||
static Boolean | static Boolean | |||
TryParseNumber(const char *str, double *value) | TryParseNumber(const char *str, double *out_value) | |||
{ | { | |||
char *eptr, ech; | char *end; | |||
unsigned long l_val; | unsigned long ul_val; | |||
double d_val; | double dbl_val; | |||
errno = 0; | errno = 0; | |||
if (!*str) { | if (str[0] == '\0') { /* XXX: why is an empty string a number? */ | |||
*value = 0.0; | *out_value = 0.0; | |||
return TRUE; | return TRUE; | |||
} | } | |||
l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10); | ||||
ech = *eptr; | ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); | |||
if (ech == '\0' && errno != ERANGE) { | if (*end == '\0' && errno != ERANGE) { | |||
d_val = str[0] == '-' ? -(double)-l_val : (double)l_val; | *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; | |||
} else { | return TRUE; | |||
if (ech != '\0' && ech != '.' && ech != 'e' && ech != 'E') | ||||
return FALSE; | ||||
d_val = strtod(str, &eptr); | ||||
if (*eptr) | ||||
return FALSE; | ||||
} | } | |||
*value = d_val; | if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') | |||
return FALSE; /* skip the expensive strtod call */ | ||||
dbl_val = strtod(str, &end); | ||||
if (*end != '\0') | ||||
return FALSE; | ||||
*out_value = dbl_val; | ||||
return TRUE; | return TRUE; | |||
} | } | |||
static Boolean | static Boolean | |||
is_separator(char ch) | is_separator(char ch) | |||
{ | { | |||
return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; | return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; | |||
} | } | |||
/*- | /*- | |||
* Parse a string from a variable reference or an optionally quoted | * Parse a string from a variable reference or an optionally quoted | |||
* string. This is called for the lhs and rhs of string comparisons. | * string. This is called for the lhs and rhs of string comparisons. | |||
* | * | |||
* Results: | * Results: | |||
* Returns the string, absent any quotes, or NULL on error. | * Returns the string, absent any quotes, or NULL on error. | |||
* Sets quoted if the string was quoted. | * Sets out_quoted if the string was quoted. | |||
* Sets freeIt if needed. | * Sets out_freeIt. | |||
*/ | */ | |||
/* coverity:[+alloc : arg-*4] */ | /* coverity:[+alloc : arg-*4] */ | |||
static const char * | static const char * | |||
CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, | CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, | |||
Boolean *quoted, void **freeIt) | Boolean *out_quoted, void **out_freeIt) | |||
{ | { | |||
Buffer buf; | Buffer buf; | |||
const char *str; | const char *str; | |||
Boolean atStart; | Boolean atStart; | |||
const char *nested_p; | const char *nested_p; | |||
Boolean qt; | Boolean quoted; | |||
const char *start; | const char *start; | |||
VarEvalFlags eflags; | VarEvalFlags eflags; | |||
VarParseResult parseResult; | VarParseResult parseResult; | |||
Buf_Init(&buf, 0); | Buf_Init(&buf); | |||
str = NULL; | str = NULL; | |||
*freeIt = NULL; | *out_freeIt = NULL; | |||
*quoted = qt = par->p[0] == '"' ? 1 : 0; | *out_quoted = quoted = par->p[0] == '"'; | |||
start = par->p; | start = par->p; | |||
if (qt) | if (quoted) | |||
par->p++; | par->p++; | |||
while (par->p[0] && str == NULL) { | while (par->p[0] != '\0' && str == NULL) { | |||
switch (par->p[0]) { | switch (par->p[0]) { | |||
case '\\': | case '\\': | |||
par->p++; | par->p++; | |||
if (par->p[0] != '\0') { | if (par->p[0] != '\0') { | |||
Buf_AddByte(&buf, par->p[0]); | Buf_AddByte(&buf, par->p[0]); | |||
par->p++; | par->p++; | |||
} | } | |||
continue; | continue; | |||
case '"': | case '"': | |||
if (qt) { | if (quoted) { | |||
par->p++; /* we don't want the quotes */ | par->p++; /* skip the closing quote */ | |||
goto got_str; | goto got_str; | |||
} | } | |||
Buf_AddByte(&buf, par->p[0]); /* likely? */ | Buf_AddByte(&buf, par->p[0]); /* likely? */ | |||
par->p++; | par->p++; | |||
continue; | continue; | |||
case ')': | case ')': /* see is_separator */ | |||
case '!': | case '!': | |||
case '=': | case '=': | |||
case '>': | case '>': | |||
case '<': | case '<': | |||
case ' ': | case ' ': | |||
case '\t': | case '\t': | |||
if (!qt) | if (!quoted) | |||
goto got_str; | goto got_str; | |||
Buf_AddByte(&buf, par->p[0]); | Buf_AddByte(&buf, par->p[0]); | |||
par->p++; | par->p++; | |||
continue; | continue; | |||
case '$': | case '$': | |||
/* if we are in quotes, an undefined variable is ok */ | /* if we are in quotes, an undefined variable is ok */ | |||
eflags = ((!qt && doEval) ? VARE_UNDEFERR : 0) | | eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR : | |||
(doEval ? VARE_WANTRES : 0); | doEval ? VARE_WANTRES : | |||
VARE_NONE; | ||||
nested_p = par->p; | nested_p = par->p; | |||
atStart = nested_p == start; | atStart = nested_p == start; | |||
parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str, | parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str, | |||
freeIt); | out_freeIt); | |||
/* TODO: handle errors */ | /* TODO: handle errors */ | |||
if (str == var_Error) { | if (str == var_Error) { | |||
if (parseResult & VPR_ANY_MSG) | if (parseResult & VPR_ANY_MSG) | |||
par->printedError = TRUE; | par->printedError = TRUE; | |||
if (*freeIt) { | if (*out_freeIt != NULL) { | |||
free(*freeIt); | /* XXX: Can there be any situation in which a returned | |||
*freeIt = NULL; | * var_Error requires freeIt? */ | |||
free(*out_freeIt); | ||||
*out_freeIt = NULL; | ||||
} | } | |||
/* | /* | |||
* Even if !doEval, we still report syntax errors, which | * Even if !doEval, we still report syntax errors, which | |||
* is what getting var_Error back with !doEval means. | * is what getting var_Error back with !doEval means. | |||
*/ | */ | |||
str = NULL; | str = NULL; | |||
goto cleanup; | goto cleanup; | |||
} | } | |||
par->p = nested_p; | par->p = nested_p; | |||
/* | /* | |||
* If the '$' started the string literal (which means no quotes), | * If the '$' started the string literal (which means no quotes), | |||
* and the variable expression is followed by a space, looks like | * and the variable expression is followed by a space, looks like | |||
* a comparison operator or is the end of the expression, we are | * a comparison operator or is the end of the expression, we are | |||
* done. | * done. | |||
*/ | */ | |||
if (atStart && is_separator(par->p[0])) | if (atStart && is_separator(par->p[0])) | |||
goto cleanup; | goto cleanup; | |||
Buf_AddStr(&buf, str); | Buf_AddStr(&buf, str); | |||
if (*freeIt) { | if (*out_freeIt) { | |||
free(*freeIt); | free(*out_freeIt); | |||
*freeIt = NULL; | *out_freeIt = NULL; | |||
} | } | |||
str = NULL; /* not finished yet */ | str = NULL; /* not finished yet */ | |||
continue; | continue; | |||
default: | default: | |||
if (strictLHS && !qt && *start != '$' && !ch_isdigit(*start)) { | if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) { | |||
/* lhs must be quoted, a variable reference or number */ | /* lhs must be quoted, a variable reference or number */ | |||
if (*freeIt) { | ||||
free(*freeIt); | ||||
*freeIt = NULL; | ||||
} | ||||
str = NULL; | str = NULL; | |||
goto cleanup; | goto cleanup; | |||
} | } | |||
Buf_AddByte(&buf, par->p[0]); | Buf_AddByte(&buf, par->p[0]); | |||
par->p++; | par->p++; | |||
continue; | continue; | |||
} | } | |||
} | } | |||
got_str: | got_str: | |||
*freeIt = Buf_GetAll(&buf, NULL); | *out_freeIt = Buf_GetAll(&buf, NULL); | |||
str = *freeIt; | str = *out_freeIt; | |||
cleanup: | cleanup: | |||
Buf_Destroy(&buf, FALSE); | Buf_Destroy(&buf, FALSE); | |||
return str; | return str; | |||
} | } | |||
/* The different forms of .if directives. */ | struct If { | |||
static const struct If { | ||||
const char *form; /* Form of if */ | const char *form; /* Form of if */ | |||
size_t formlen; /* Length of form */ | size_t formlen; /* Length of form */ | |||
Boolean doNot; /* TRUE if default function should be negated */ | Boolean doNot; /* TRUE if default function should be negated */ | |||
Boolean (*defProc)(size_t, const char *); /* Default function to apply */ | Boolean (*defProc)(size_t, const char *); /* Default function to apply */ | |||
} ifs[] = { | }; | |||
/* The different forms of .if directives. */ | ||||
static const struct If ifs[] = { | ||||
{ "def", 3, FALSE, FuncDefined }, | { "def", 3, FALSE, FuncDefined }, | |||
{ "ndef", 4, TRUE, FuncDefined }, | { "ndef", 4, TRUE, FuncDefined }, | |||
{ "make", 4, FALSE, FuncMake }, | { "make", 4, FALSE, FuncMake }, | |||
{ "nmake", 5, TRUE, FuncMake }, | { "nmake", 5, TRUE, FuncMake }, | |||
{ "", 0, FALSE, FuncDefined }, | { "", 0, FALSE, FuncDefined }, | |||
{ NULL, 0, FALSE, NULL } | { NULL, 0, FALSE, NULL } | |||
}; | }; | |||
enum { PLAIN_IF_INDEX = 4 }; | ||||
static Boolean | ||||
If_Eval(const struct If *if_info, const char *arg, size_t arglen) | ||||
{ | ||||
Boolean res = if_info->defProc(arglen, arg); | ||||
return if_info->doNot ? !res : res; | ||||
} | ||||
/* Evaluate a "comparison without operator", such as in ".if ${VAR}" or | /* Evaluate a "comparison without operator", such as in ".if ${VAR}" or | |||
* ".if 0". */ | * ".if 0". */ | |||
static Token | static Boolean | |||
EvalNotEmpty(CondParser *par, const char *lhs, Boolean lhsQuoted) | EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) | |||
{ | { | |||
double left; | double num; | |||
/* For .ifxxx "..." check for non-empty string. */ | ||||
if (lhsQuoted) | ||||
return lhs[0] != '\0'; | ||||
/* For .ifxxx <number> compare against zero */ | ||||
if (TryParseNumber(lhs, &left)) | ||||
return left != 0.0; | ||||
/* For .if ${...} check for non-empty string (defProc is ifdef). */ | /* 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 ParseEmptyArg. */ | ||||
if (par->if_info->form[0] == '\0') | if (par->if_info->form[0] == '\0') | |||
return lhs[0] != 0; | return value[0] != '\0'; | |||
/* Otherwise action default test ... */ | /* For the other variants of .ifxxx ${...}, use its default function. */ | |||
return par->if_info->defProc(strlen(lhs), lhs) == !par->if_info->doNot; | return If_Eval(par->if_info, value, strlen(value)); | |||
} | } | |||
/* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ | /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ | |||
static Token | static Token | |||
EvalCompareNum(double lhs, const char *op, double rhs) | EvalCompareNum(double lhs, const char *op, double rhs) | |||
{ | { | |||
DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, op); | DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, op); | |||
switch (op[0]) { | switch (op[0]) { | |||
case '!': | case '!': | |||
if (op[1] != '=') { | if (op[1] != '=') { | |||
Parse_Error(PARSE_WARNING, "Unknown operator"); | Parse_Error(PARSE_WARNING, "Unknown operator"); | |||
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | |||
return TOK_ERROR; | return TOK_ERROR; | |||
} | } | |||
return lhs != rhs; | return ToToken(lhs != rhs); | |||
case '=': | case '=': | |||
if (op[1] != '=') { | if (op[1] != '=') { | |||
Parse_Error(PARSE_WARNING, "Unknown operator"); | Parse_Error(PARSE_WARNING, "Unknown operator"); | |||
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | |||
return TOK_ERROR; | return TOK_ERROR; | |||
} | } | |||
return lhs == rhs; | return ToToken(lhs == rhs); | |||
case '<': | case '<': | |||
return op[1] == '=' ? lhs <= rhs : lhs < rhs; | return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs); | |||
case '>': | case '>': | |||
return op[1] == '=' ? lhs >= rhs : lhs > rhs; | return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs); | |||
} | } | |||
return TOK_ERROR; | return TOK_ERROR; | |||
} | } | |||
static Token | static Token | |||
EvalCompareStr(const char *lhs, const char *op, const char *rhs) | EvalCompareStr(const char *lhs, const char *op, const char *rhs) | |||
{ | { | |||
if (!((op[0] == '!' || op[0] == '=') && op[1] == '=')) { | if (!((op[0] == '!' || op[0] == '=') && op[1] == '=')) { | |||
Parse_Error(PARSE_WARNING, | Parse_Error(PARSE_WARNING, | |||
"String comparison operator must be either == or !="); | "String comparison operator must be either == or !="); | |||
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | |||
return TOK_ERROR; | return TOK_ERROR; | |||
} | } | |||
DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op); | DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op); | |||
return (*op == '=') == (strcmp(lhs, rhs) == 0); | return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0)); | |||
} | } | |||
/* Evaluate a comparison, such as "${VAR} == 12345". */ | /* Evaluate a comparison, such as "${VAR} == 12345". */ | |||
static Token | static Token | |||
EvalCompare(const char *lhs, Boolean lhsQuoted, const char *op, | EvalCompare(const char *lhs, Boolean lhsQuoted, const char *op, | |||
const char *rhs, Boolean rhsQuoted) | const char *rhs, Boolean rhsQuoted) | |||
{ | { | |||
double left, right; | double left, right; | |||
if (!rhsQuoted && !lhsQuoted) | if (!rhsQuoted && !lhsQuoted) | |||
skipping to change at line 612 | skipping to change at line 621 | |||
* 0 | * 0 | |||
* ${VAR:Mpattern} | * ${VAR:Mpattern} | |||
* ${VAR} == value | * ${VAR} == value | |||
* ${VAR:U0} < 12345 | * ${VAR:U0} < 12345 | |||
*/ | */ | |||
static Token | static Token | |||
CondParser_Comparison(CondParser *par, Boolean doEval) | CondParser_Comparison(CondParser *par, Boolean doEval) | |||
{ | { | |||
Token t = TOK_ERROR; | Token t = TOK_ERROR; | |||
const char *lhs, *op, *rhs; | const char *lhs, *op, *rhs; | |||
void *lhsFree, *rhsFree; | void *lhs_freeIt, *rhs_freeIt; | |||
Boolean lhsQuoted, rhsQuoted; | Boolean lhsQuoted, rhsQuoted; | |||
rhs = NULL; | ||||
lhsFree = rhsFree = NULL; | ||||
lhsQuoted = rhsQuoted = FALSE; | ||||
/* | /* | |||
* Parse the variable spec and skip over it, saving its | * Parse the variable spec and skip over it, saving its | |||
* value in lhs. | * value in lhs. | |||
*/ | */ | |||
lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhsFree); | lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt); | |||
if (!lhs) | if (lhs == NULL) | |||
goto done; | goto done_lhs; | |||
CondParser_SkipWhitespace(par); | CondParser_SkipWhitespace(par); | |||
/* | ||||
* Make sure the operator is a valid one. If it isn't a | ||||
* known relational operator, pretend we got a | ||||
* != 0 comparison. | ||||
*/ | ||||
op = par->p; | op = par->p; | |||
switch (par->p[0]) { | switch (par->p[0]) { | |||
case '!': | case '!': | |||
case '=': | case '=': | |||
case '<': | case '<': | |||
case '>': | case '>': | |||
if (par->p[1] == '=') { | if (par->p[1] == '=') | |||
par->p += 2; | par->p += 2; | |||
} else { | else | |||
par->p++; | par->p++; | |||
} | ||||
break; | break; | |||
default: | default: | |||
t = doEval ? EvalNotEmpty(par, lhs, lhsQuoted) : TOK_FALSE; | /* Unknown operator, compare against an empty string or 0. */ | |||
goto done; | t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted)); | |||
goto done_lhs; | ||||
} | } | |||
CondParser_SkipWhitespace(par); | CondParser_SkipWhitespace(par); | |||
if (par->p[0] == '\0') { | if (par->p[0] == '\0') { | |||
Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator"); | Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator"); | |||
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ | |||
goto done; | goto done_lhs; | |||
} | } | |||
rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhsFree); | rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhs_freeIt); | |||
if (rhs == NULL) | if (rhs == NULL) | |||
goto done; | goto done_rhs; | |||
if (!doEval) { | if (!doEval) { | |||
t = TOK_FALSE; | t = TOK_FALSE; | |||
goto done; | goto done_rhs; | |||
} | } | |||
t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted); | t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted); | |||
done: | done_rhs: | |||
free(lhsFree); | free(rhs_freeIt); | |||
free(rhsFree); | done_lhs: | |||
free(lhs_freeIt); | ||||
return t; | return t; | |||
} | } | |||
/* The argument to empty() is a variable name, optionally followed by | ||||
* variable modifiers. */ | ||||
static size_t | static size_t | |||
ParseEmptyArg(const char **linePtr, Boolean doEval, | ParseEmptyArg(const char **pp, Boolean doEval, | |||
const char *func MAKE_ATTR_UNUSED, char **argPtr) | const char *func MAKE_ATTR_UNUSED, char **out_arg) | |||
{ | { | |||
void *val_freeIt; | void *val_freeIt; | |||
const char *val; | const char *val; | |||
size_t magic_res; | size_t magic_res; | |||
/* We do all the work here and return the result as the length */ | /* We do all the work here and return the result as the length */ | |||
*argPtr = NULL; | *out_arg = NULL; | |||
(*linePtr)--; /* Make (*linePtr)[1] point to the '('. */ | (*pp)--; /* Make (*pp)[1] point to the '('. */ | |||
(void)Var_Parse(linePtr, VAR_CMDLINE, doEval ? VARE_WANTRES : 0, | (void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE, | |||
&val, &val_freeIt); | &val, &val_freeIt); | |||
/* TODO: handle errors */ | /* TODO: handle errors */ | |||
/* If successful, *linePtr points beyond the closing ')' now. */ | /* If successful, *pp points beyond the closing ')' now. */ | |||
if (val == var_Error) { | if (val == var_Error) { | |||
free(val_freeIt); | free(val_freeIt); | |||
return (size_t)-1; | return (size_t)-1; | |||
} | } | |||
/* A variable is empty when it just contains spaces... 4/15/92, christos */ | /* A variable is empty when it just contains spaces... 4/15/92, christos */ | |||
cpp_skip_whitespace(&val); | cpp_skip_whitespace(&val); | |||
/* | /* | |||
skipping to change at line 717 | skipping to change at line 720 | |||
return magic_res; | return magic_res; | |||
} | } | |||
static Boolean | static Boolean | |||
FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) | FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) | |||
{ | { | |||
/* Magic values ahead, see ParseEmptyArg. */ | /* Magic values ahead, see ParseEmptyArg. */ | |||
return arglen == 1; | return arglen == 1; | |||
} | } | |||
static Token | static Boolean | |||
CondParser_Func(CondParser *par, Boolean doEval) | CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) | |||
{ | { | |||
static const struct fn_def { | static const struct fn_def { | |||
const char *fn_name; | const char *fn_name; | |||
size_t fn_name_len; | size_t fn_name_len; | |||
size_t (*fn_parse)(const char **, Boolean, const char *, char **); | size_t (*fn_parse)(const char **, Boolean, const char *, char **); | |||
Boolean (*fn_eval)(size_t, const char *); | Boolean (*fn_eval)(size_t, const char *); | |||
} fn_defs[] = { | } fns[] = { | |||
{ "defined", 7, ParseFuncArg, FuncDefined }, | { "defined", 7, ParseFuncArg, FuncDefined }, | |||
{ "make", 4, ParseFuncArg, FuncMake }, | { "make", 4, ParseFuncArg, FuncMake }, | |||
{ "exists", 6, ParseFuncArg, FuncExists }, | { "exists", 6, ParseFuncArg, FuncExists }, | |||
{ "empty", 5, ParseEmptyArg, FuncEmpty }, | { "empty", 5, ParseEmptyArg, FuncEmpty }, | |||
{ "target", 6, ParseFuncArg, FuncTarget }, | { "target", 6, ParseFuncArg, FuncTarget }, | |||
{ "commands", 8, ParseFuncArg, FuncCommands }, | { "commands", 8, ParseFuncArg, FuncCommands } | |||
{ NULL, 0, NULL, NULL }, | ||||
}; | }; | |||
const struct fn_def *fn_def; | const struct fn_def *fn; | |||
Token t; | ||||
char *arg = NULL; | char *arg = NULL; | |||
size_t arglen; | size_t arglen; | |||
const char *cp = par->p; | const char *cp = par->p; | |||
const char *cp1; | const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0]; | |||
for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) { | for (fn = fns; fn != fns_end; fn++) { | |||
if (!is_token(cp, fn_def->fn_name, fn_def->fn_name_len)) | if (!is_token(cp, fn->fn_name, fn->fn_name_len)) | |||
continue; | continue; | |||
cp += fn_def->fn_name_len; | ||||
/* There can only be whitespace before the '(' */ | cp += fn->fn_name_len; | |||
cpp_skip_whitespace(&cp); | cpp_skip_whitespace(&cp); | |||
if (*cp != '(') | if (*cp != '(') | |||
break; | break; | |||
arglen = fn_def->fn_parse(&cp, doEval, fn_def->fn_name, &arg); | arglen = fn->fn_parse(&cp, doEval, fn->fn_name, &arg); | |||
if (arglen == 0 || arglen == (size_t)-1) { | if (arglen == 0 || arglen == (size_t)-1) { | |||
par->p = cp; | par->p = cp; | |||
return arglen == 0 ? TOK_FALSE : TOK_ERROR; | *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR; | |||
return TRUE; | ||||
} | } | |||
/* Evaluate the argument using the required function. */ | /* Evaluate the argument using the required function. */ | |||
t = !doEval || fn_def->fn_eval(arglen, arg); | *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg)); | |||
free(arg); | free(arg); | |||
par->p = cp; | par->p = cp; | |||
return t; | return TRUE; | |||
} | } | |||
return FALSE; | ||||
} | ||||
/* Parse a function call, a number, a variable expression or a string | ||||
* literal. */ | ||||
static Token | ||||
CondParser_LeafToken(CondParser *par, Boolean doEval) | ||||
{ | ||||
Token t; | ||||
char *arg = NULL; | ||||
size_t arglen; | ||||
const char *cp = par->p; | ||||
const char *cp1; | ||||
if (CondParser_Func(par, doEval, &t)) | ||||
return t; | ||||
/* Push anything numeric through the compare expression */ | /* Push anything numeric through the compare expression */ | |||
cp = par->p; | cp = par->p; | |||
if (ch_isdigit(cp[0]) || strchr("+-", cp[0])) | if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') | |||
return CondParser_Comparison(par, doEval); | return CondParser_Comparison(par, doEval); | |||
/* | /* | |||
* Most likely we have a naked token to apply the default function to. | * 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 | * However ".if a == b" gets here when the "a" is unquoted and doesn't | |||
* start with a '$'. This surprises people. | * start with a '$'. This surprises people. | |||
* If what follows the function argument is a '=' or '!' then the syntax | * 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 | * would be invalid if we did "defined(a)" - so instead treat as an | |||
* expression. | * expression. | |||
*/ | */ | |||
skipping to change at line 788 | skipping to change at line 808 | |||
if (*cp1 == '=' || *cp1 == '!') | if (*cp1 == '=' || *cp1 == '!') | |||
return CondParser_Comparison(par, doEval); | return CondParser_Comparison(par, doEval); | |||
par->p = cp; | par->p = cp; | |||
/* | /* | |||
* Evaluate the argument using the default function. | * Evaluate the argument using the default function. | |||
* This path always treats .if as .ifdef. To get here, the character | * This path always treats .if as .ifdef. To get here, the character | |||
* after .if must have been taken literally, so the argument cannot | * 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 a variable expansion. | |||
*/ | */ | |||
t = !doEval || par->if_info->defProc(arglen, arg) == !par->if_info->doNot; | t = ToToken(!doEval || If_Eval(par->if_info, arg, arglen)); | |||
free(arg); | free(arg); | |||
return t; | return t; | |||
} | } | |||
/* Return the next token or comparison result from the parser. */ | /* Return the next token or comparison result from the parser. */ | |||
static Token | static Token | |||
CondParser_Token(CondParser *par, Boolean doEval) | CondParser_Token(CondParser *par, Boolean doEval) | |||
{ | { | |||
Token t; | Token t; | |||
t = par->curr; | t = par->curr; | |||
if (t != TOK_NONE) { | if (t != TOK_NONE) { | |||
par->curr = TOK_NONE; | par->curr = TOK_NONE; | |||
return t; | return t; | |||
} | } | |||
while (par->p[0] == ' ' || par->p[0] == '\t') { | cpp_skip_hspace(&par->p); | |||
par->p++; | ||||
} | ||||
switch (par->p[0]) { | switch (par->p[0]) { | |||
case '(': | case '(': | |||
par->p++; | par->p++; | |||
return TOK_LPAREN; | return TOK_LPAREN; | |||
case ')': | case ')': | |||
par->p++; | par->p++; | |||
return TOK_RPAREN; | return TOK_RPAREN; | |||
case '|': | case '|': | |||
par->p++; | par->p++; | |||
if (par->p[0] == '|') { | if (par->p[0] == '|') | |||
par->p++; | par->p++; | |||
else if (opts.lint) { | ||||
Parse_Error(PARSE_FATAL, "Unknown operator '|'"); | ||||
par->printedError = TRUE; | ||||
return TOK_ERROR; | ||||
} | } | |||
return TOK_OR; | return TOK_OR; | |||
case '&': | case '&': | |||
par->p++; | par->p++; | |||
if (par->p[0] == '&') { | if (par->p[0] == '&') | |||
par->p++; | par->p++; | |||
else if (opts.lint) { | ||||
Parse_Error(PARSE_FATAL, "Unknown operator '&'"); | ||||
par->printedError = TRUE; | ||||
return TOK_ERROR; | ||||
} | } | |||
return TOK_AND; | return TOK_AND; | |||
case '!': | case '!': | |||
par->p++; | par->p++; | |||
return TOK_NOT; | return TOK_NOT; | |||
case '#': | case '#': /* XXX: see unit-tests/cond-token-plain.mk */ | |||
case '\n': | case '\n': /* XXX: why should this end the condition? */ | |||
/* Probably obsolete now, from 1993-03-21. */ | ||||
case '\0': | case '\0': | |||
return TOK_EOF; | return TOK_EOF; | |||
case '"': | case '"': | |||
case '$': | case '$': | |||
return CondParser_Comparison(par, doEval); | return CondParser_Comparison(par, doEval); | |||
default: | default: | |||
return CondParser_Func(par, doEval); | return CondParser_LeafToken(par, doEval); | |||
} | } | |||
} | } | |||
/* Parse a single term in the expression. This consists of a terminal symbol | /* Parse a single term in the expression. This consists of a terminal symbol | |||
* or TOK_NOT and a term (not including the binary operators): | * or TOK_NOT and a term (not including the binary operators): | |||
* | * | |||
* T -> defined(variable) | make(target) | exists(file) | symbol | * T -> defined(variable) | make(target) | exists(file) | symbol | |||
* T -> ! T | ( E ) | * T -> ! T | ( E ) | |||
* | * | |||
* Results: | * Results: | |||
skipping to change at line 1006 | skipping to change at line 1033 | |||
* Results: | * Results: | |||
* COND_PARSE if the condition was valid grammatically | * COND_PARSE if the condition was valid grammatically | |||
* COND_INVALID if not a valid conditional. | * COND_INVALID if not a valid conditional. | |||
* | * | |||
* (*value) is set to the boolean value of the condition | * (*value) is set to the boolean value of the condition | |||
*/ | */ | |||
static CondEvalResult | static CondEvalResult | |||
CondEvalExpression(const struct If *info, const char *cond, Boolean *value, | CondEvalExpression(const struct If *info, const char *cond, Boolean *value, | |||
Boolean eprint, Boolean strictLHS) | Boolean eprint, Boolean strictLHS) | |||
{ | { | |||
static const struct If *dflt_info; | ||||
CondParser par; | CondParser par; | |||
int rval; | CondEvalResult rval; | |||
lhsStrict = strictLHS; | lhsStrict = strictLHS; | |||
while (*cond == ' ' || *cond == '\t') | cpp_skip_hspace(&cond); | |||
cond++; | ||||
if (info == NULL && (info = dflt_info) == NULL) { | par.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX; | |||
/* Scan for the entry for .if - it can't be first */ | ||||
for (info = ifs;; info++) | ||||
if (info->form[0] == 0) | ||||
break; | ||||
dflt_info = info; | ||||
} | ||||
assert(info != NULL); | ||||
par.if_info = info; | ||||
par.p = cond; | par.p = cond; | |||
par.curr = TOK_NONE; | par.curr = TOK_NONE; | |||
par.printedError = FALSE; | par.printedError = FALSE; | |||
rval = CondParser_Eval(&par, value); | rval = CondParser_Eval(&par, value); | |||
if (rval == COND_INVALID && eprint && !par.printedError) | if (rval == COND_INVALID && eprint && !par.printedError) | |||
Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); | Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); | |||
return rval; | return rval; | |||
} | } | |||
/* Evaluate a condition in a :? modifier, such as | ||||
* ${"${VAR}" == value:?yes:no}. */ | ||||
CondEvalResult | CondEvalResult | |||
Cond_EvalCondition(const char *cond, Boolean *out_value) | Cond_EvalCondition(const char *cond, Boolean *out_value) | |||
{ | { | |||
return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE); | return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE); | |||
} | } | |||
/* Evaluate the conditional in the passed line. The line looks like this: | /* Evaluate the conditional directive in the line, which is one of: | |||
* .<cond-type> <expr> | * | |||
* In this line, <cond-type> is any of if, ifmake, ifnmake, ifdef, ifndef, | * .if <cond> | |||
* elif, elifmake, elifnmake, elifdef, elifndef. | * .ifmake <cond> | |||
* In this line, <expr> consists of &&, ||, !, function(arg), comparisons | * .ifnmake <cond> | |||
* and parenthetical groupings thereof. | * .ifdef <cond> | |||
* | * .ifndef <cond> | |||
* Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order | * .elif <cond> | |||
* to detect spurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF), | * .elifmake <cond> | |||
* otherwise .else could be treated as '.elif 1'. | * .elifnmake <cond> | |||
* .elifdef <cond> | ||||
* .elifndef <cond> | ||||
* .else | ||||
* .endif | ||||
* | ||||
* In these directives, <cond> consists of &&, ||, !, function(arg), | ||||
* comparisons, expressions, bare words, numbers and strings, and | ||||
* parenthetical groupings thereof. | ||||
* | * | |||
* Results: | * Results: | |||
* COND_PARSE to continue parsing the lines after the conditional | * COND_PARSE to continue parsing the lines that follow the | |||
* (when .if or .else returns TRUE) | * conditional (when <cond> evaluates to TRUE) | |||
* COND_SKIP to skip the lines after the conditional | * COND_SKIP to skip the lines after the conditional | |||
* (when .if or .elif returns FALSE, or when a previous | * (when <cond> evaluates to FALSE, or when a previous | |||
* branch has already been taken) | * branch has already been taken) | |||
* COND_INVALID if the conditional was not valid, either because of | * COND_INVALID if the conditional was not valid, either because of | |||
* a syntax error or because some variable was undefined | * a syntax error or because some variable was undefined | |||
* or because the condition could not be evaluated | * or because the condition could not be evaluated | |||
*/ | */ | |||
CondEvalResult | CondEvalResult | |||
Cond_EvalLine(const char *line) | Cond_EvalLine(const char *const line) | |||
{ | { | |||
enum { MAXIF = 128 }; /* maximum depth of .if'ing */ | typedef enum IfState { | |||
enum { MAXIF_BUMP = 32 }; /* how much to grow by */ | ||||
enum if_states { | /* None of the previous <cond> evaluated to TRUE. */ | |||
IF_ACTIVE, /* .if or .elif part active */ | IFS_INITIAL = 0, | |||
ELSE_ACTIVE, /* .else part active */ | ||||
SEARCH_FOR_ELIF, /* searching for .elif/else to execute */ | /* The previous <cond> evaluated to TRUE. | |||
SKIP_TO_ELSE, /* has been true, but not seen '.else' */ | * The lines following this condition are interpreted. */ | |||
SKIP_TO_ENDIF /* nothing else to execute */ | IFS_ACTIVE = 1 << 0, | |||
}; | ||||
static enum if_states *cond_state = NULL; | /* The previous directive was an '.else'. */ | |||
static unsigned int max_if_depth = MAXIF; | IFS_SEEN_ELSE = 1 << 1, | |||
/* One of the previous <cond> evaluated to TRUE. */ | ||||
IFS_WAS_ACTIVE = 1 << 2 | ||||
} IfState; | ||||
static enum IfState *cond_states = NULL; | ||||
static unsigned int cond_states_cap = 128; | ||||
const struct If *ifp; | const struct If *ifp; | |||
Boolean isElif; | Boolean isElif; | |||
Boolean value; | Boolean value; | |||
enum if_states state; | IfState state; | |||
const char *p = line; | ||||
if (!cond_state) { | if (cond_states == NULL) { | |||
cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state)); | cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states); | |||
cond_state[0] = IF_ACTIVE; | cond_states[0] = IFS_ACTIVE; | |||
} | } | |||
/* skip leading character (the '.') and any whitespace */ | ||||
for (line++; *line == ' ' || *line == '\t'; line++) | p++; /* skip the leading '.' */ | |||
continue; | cpp_skip_hspace(&p); | |||
/* Find what type of if we're dealing with. */ | /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ | |||
if (line[0] == 'e') { | if (p[0] == 'e') { | |||
if (line[1] != 'l') { | if (p[1] != 'l') { | |||
if (!is_token(line + 1, "ndif", 4)) | if (!is_token(p + 1, "ndif", 4)) { | |||
/* Unknown directive. It might still be a transformation | ||||
* rule like '.elisp.scm', therefore no error message here. */ | ||||
return COND_INVALID; | return COND_INVALID; | |||
/* End of conditional section */ | } | |||
/* It is an '.endif'. */ | ||||
/* TODO: check for extraneous <cond> */ | ||||
if (cond_depth == cond_min_depth) { | if (cond_depth == cond_min_depth) { | |||
Parse_Error(PARSE_FATAL, "if-less endif"); | Parse_Error(PARSE_FATAL, "if-less endif"); | |||
return COND_PARSE; | return COND_PARSE; | |||
} | } | |||
/* Return state for previous conditional */ | /* Return state for previous conditional */ | |||
cond_depth--; | cond_depth--; | |||
return cond_state[cond_depth] <= ELSE_ACTIVE | return cond_states[cond_depth] & IFS_ACTIVE | |||
? COND_PARSE : COND_SKIP; | ? COND_PARSE : COND_SKIP; | |||
} | } | |||
/* Quite likely this is 'else' or 'elif' */ | /* Quite likely this is 'else' or 'elif' */ | |||
line += 2; | p += 2; | |||
if (is_token(line, "se", 2)) { | if (is_token(p, "se", 2)) { /* It is an 'else'. */ | |||
/* It is else... */ | ||||
if (opts.lint && p[2] != '\0') | ||||
Parse_Error(PARSE_FATAL, | ||||
"The .else directive does not take arguments."); | ||||
if (cond_depth == cond_min_depth) { | if (cond_depth == cond_min_depth) { | |||
Parse_Error(PARSE_FATAL, "if-less else"); | Parse_Error(PARSE_FATAL, "if-less else"); | |||
return COND_PARSE; | return COND_PARSE; | |||
} | } | |||
state = cond_state[cond_depth]; | state = cond_states[cond_depth]; | |||
switch (state) { | if (state == IFS_INITIAL) { | |||
case SEARCH_FOR_ELIF: | state = IFS_ACTIVE | IFS_SEEN_ELSE; | |||
state = ELSE_ACTIVE; | } else { | |||
break; | if (state & IFS_SEEN_ELSE) | |||
case ELSE_ACTIVE: | Parse_Error(PARSE_WARNING, "extra else"); | |||
case SKIP_TO_ENDIF: | state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; | |||
Parse_Error(PARSE_WARNING, "extra else"); | ||||
/* FALLTHROUGH */ | ||||
default: | ||||
case IF_ACTIVE: | ||||
case SKIP_TO_ELSE: | ||||
state = SKIP_TO_ENDIF; | ||||
break; | ||||
} | } | |||
cond_state[cond_depth] = state; | cond_states[cond_depth] = state; | |||
return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP; | ||||
return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; | ||||
} | } | |||
/* Assume for now it is an elif */ | /* Assume for now it is an elif */ | |||
isElif = TRUE; | isElif = TRUE; | |||
} else | } else | |||
isElif = FALSE; | isElif = FALSE; | |||
if (line[0] != 'i' || line[1] != 'f') | if (p[0] != 'i' || p[1] != 'f') { | |||
/* Not an ifxxx or elifxxx line */ | /* Unknown directive. It might still be a transformation rule like | |||
return COND_INVALID; | * '.elisp.scm', therefore no error message here. */ | |||
return COND_INVALID; /* Not an ifxxx or elifxxx line */ | ||||
} | ||||
/* | /* | |||
* Figure out what sort of conditional it is -- what its default | * Figure out what sort of conditional it is -- what its default | |||
* function is, etc. -- by looking in the table of valid "ifs" | * function is, etc. -- by looking in the table of valid "ifs" | |||
*/ | */ | |||
line += 2; | p += 2; | |||
for (ifp = ifs;; ifp++) { | for (ifp = ifs;; ifp++) { | |||
if (ifp->form == NULL) | if (ifp->form == NULL) { | |||
/* TODO: Add error message about unknown directive, | ||||
* since there is no other known directive that starts with 'el' | ||||
* or 'if'. | ||||
* Example: .elifx 123 */ | ||||
return COND_INVALID; | return COND_INVALID; | |||
if (is_token(ifp->form, line, ifp->formlen)) { | } | |||
line += ifp->formlen; | if (is_token(p, ifp->form, ifp->formlen)) { | |||
p += ifp->formlen; | ||||
break; | break; | |||
} | } | |||
} | } | |||
/* Now we know what sort of 'if' it is... */ | /* Now we know what sort of 'if' it is... */ | |||
if (isElif) { | if (isElif) { | |||
if (cond_depth == cond_min_depth) { | if (cond_depth == cond_min_depth) { | |||
Parse_Error(PARSE_FATAL, "if-less elif"); | Parse_Error(PARSE_FATAL, "if-less elif"); | |||
return COND_PARSE; | return COND_PARSE; | |||
} | } | |||
state = cond_state[cond_depth]; | state = cond_states[cond_depth]; | |||
if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) { | if (state & IFS_SEEN_ELSE) { | |||
Parse_Error(PARSE_WARNING, "extra elif"); | Parse_Error(PARSE_WARNING, "extra elif"); | |||
cond_state[cond_depth] = SKIP_TO_ENDIF; | cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; | |||
return COND_SKIP; | return COND_SKIP; | |||
} | } | |||
if (state != SEARCH_FOR_ELIF) { | if (state != IFS_INITIAL) { | |||
/* Either just finished the 'true' block, or already SKIP_TO_ELSE */ | cond_states[cond_depth] = IFS_WAS_ACTIVE; | |||
cond_state[cond_depth] = SKIP_TO_ELSE; | ||||
return COND_SKIP; | return COND_SKIP; | |||
} | } | |||
} else { | } else { | |||
/* Normal .if */ | /* Normal .if */ | |||
if (cond_depth + 1 >= max_if_depth) { | if (cond_depth + 1 >= cond_states_cap) { | |||
/* | /* | |||
* This is rare, but not impossible. | * This is rare, but not impossible. | |||
* In meta mode, dirdeps.mk (only runs at level 0) | * In meta mode, dirdeps.mk (only runs at level 0) | |||
* can need more than the default. | * can need more than the default. | |||
*/ | */ | |||
max_if_depth += MAXIF_BUMP; | cond_states_cap += 32; | |||
cond_state = bmake_realloc(cond_state, | cond_states = bmake_realloc(cond_states, | |||
max_if_depth * sizeof(*cond_state)); | cond_states_cap * sizeof *cond_states); | |||
} | } | |||
state = cond_state[cond_depth]; | state = cond_states[cond_depth]; | |||
cond_depth++; | cond_depth++; | |||
if (state > ELSE_ACTIVE) { | if (!(state & IFS_ACTIVE)) { | |||
/* If we aren't parsing the data, treat as always false */ | /* If we aren't parsing the data, treat as always false */ | |||
cond_state[cond_depth] = SKIP_TO_ELSE; | cond_states[cond_depth] = IFS_WAS_ACTIVE; | |||
return COND_SKIP; | return COND_SKIP; | |||
} | } | |||
} | } | |||
/* And evaluate the conditional expression */ | /* And evaluate the conditional expression */ | |||
if (CondEvalExpression(ifp, line, &value, TRUE, TRUE) == COND_INVALID) { | if (CondEvalExpression(ifp, p, &value, TRUE, TRUE) == COND_INVALID) { | |||
/* Syntax error in conditional, error message already output. */ | /* Syntax error in conditional, error message already output. */ | |||
/* Skip everything to matching .endif */ | /* Skip everything to matching .endif */ | |||
cond_state[cond_depth] = SKIP_TO_ELSE; | /* XXX: An extra '.else' is not detected in this case. */ | |||
cond_states[cond_depth] = IFS_WAS_ACTIVE; | ||||
return COND_SKIP; | return COND_SKIP; | |||
} | } | |||
if (!value) { | if (!value) { | |||
cond_state[cond_depth] = SEARCH_FOR_ELIF; | cond_states[cond_depth] = IFS_INITIAL; | |||
return COND_SKIP; | return COND_SKIP; | |||
} | } | |||
cond_state[cond_depth] = IF_ACTIVE; | cond_states[cond_depth] = IFS_ACTIVE; | |||
return COND_PARSE; | return COND_PARSE; | |||
} | } | |||
void | void | |||
Cond_restore_depth(unsigned int saved_depth) | Cond_restore_depth(unsigned int saved_depth) | |||
{ | { | |||
unsigned int open_conds = cond_depth - cond_min_depth; | unsigned int open_conds = cond_depth - cond_min_depth; | |||
if (open_conds != 0 || saved_depth > cond_depth) { | if (open_conds != 0 || saved_depth > cond_depth) { | |||
Parse_Error(PARSE_FATAL, "%u open conditional%s", open_conds, | Parse_Error(PARSE_FATAL, "%u open conditional%s", open_conds, | |||
End of changes. 125 change blocks. | ||||
254 lines changed or deleted | 301 lines changed or added |