#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/usr.bin/make/cond.c,v 1.54 2005/05/25 16:06:14 harti Exp $");
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "buf.h"
#include "cond.h"
#include "dir.h"
#include "globals.h"
#include "GNode.h"
#include "make.h"
#include "parse.h"
#include "str.h"
#include "targ.h"
#include "util.h"
#include "var.h"
typedef enum {
And,
Or,
Not,
True,
False,
LParen,
RParen,
EndOfFile,
None,
Err
} Token;
typedef Boolean CondProc(int, char *);
static void CondPushBack(Token);
static int CondGetArg(char **, char **, const char *, Boolean);
static CondProc CondDoDefined;
static CondProc CondDoMake;
static CondProc CondDoExists;
static CondProc CondDoTarget;
static char *CondCvtArg(char *, double *);
static Token CondToken(Boolean);
static Token CondT(Boolean);
static Token CondF(Boolean);
static Token CondE(Boolean);
static const struct If {
Boolean doNot;
CondProc *defProc;
Boolean isElse;
} ifs[] = {
[COND_IF] = { FALSE, CondDoDefined, FALSE },
[COND_IFDEF] = { FALSE, CondDoDefined, FALSE },
[COND_IFNDEF] = { TRUE, CondDoDefined, FALSE },
[COND_IFMAKE] = { FALSE, CondDoMake, FALSE },
[COND_IFNMAKE] = { TRUE, CondDoMake, FALSE },
[COND_ELIF] = { FALSE, CondDoDefined, TRUE },
[COND_ELIFDEF] = { FALSE, CondDoDefined, TRUE },
[COND_ELIFNDEF] = { TRUE, CondDoDefined, TRUE },
[COND_ELIFMAKE] = { FALSE, CondDoMake, TRUE },
[COND_ELIFNMAKE] = { TRUE, CondDoMake, TRUE },
};
static Boolean condInvert;
static CondProc *condDefProc;
static char *condExpr;
static Token condPushBack = None;
#define MAXIF 30
static Boolean condStack[MAXIF];
static int condLineno[MAXIF];
static int condTop = MAXIF;
static int skipIfLevel = 0;
static int skipIfLineno[MAXIF];
Boolean skipLine = FALSE;
static void
CondPushBack(Token t)
{
condPushBack = t;
}
static int
CondGetArg(char **linePtr, char **argPtr, const char *func, Boolean parens)
{
char *cp;
size_t argLen;
Buffer *buf;
cp = *linePtr;
if (parens) {
while (*cp != '(' && *cp != '\0') {
cp++;
}
if (*cp == '(') {
cp++;
}
}
if (*cp == '\0') {
*argPtr = cp;
return (0);
}
while (*cp == ' ' || *cp == '\t') {
cp++;
}
buf = Buf_Init(16);
while ((strchr(" \t)&|", *cp) == NULL) && (*cp != '\0')) {
if (*cp == '$') {
char *cp2;
size_t len = 0;
Boolean doFree;
cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &doFree);
Buf_Append(buf, cp2);
if (doFree) {
free(cp2);
}
cp += len;
} else {
Buf_AddByte(buf, (Byte)*cp);
cp++;
}
}
Buf_AddByte(buf, (Byte)'\0');
*argPtr = (char *)Buf_GetAll(buf, &argLen);
Buf_Destroy(buf, FALSE);
while (*cp == ' ' || *cp == '\t') {
cp++;
}
if (parens && *cp != ')') {
Parse_Error(PARSE_WARNING,
"Missing closing parenthesis for %s()", func);
return (0);
} else if (parens) {
cp++;
}
*linePtr = cp;
return (argLen);
}
static Boolean
CondDoDefined(int argLen, char *arg)
{
char savec = arg[argLen];
Boolean result;
arg[argLen] = '\0';
if (Var_Value(arg, VAR_CMD) != NULL) {
result = TRUE;
} else {
result = FALSE;
}
arg[argLen] = savec;
return (result);
}
static Boolean
CondDoMake(int argLen, char *arg)
{
char savec = arg[argLen];
Boolean result;
const LstNode *ln;
arg[argLen] = '\0';
result = FALSE;
LST_FOREACH(ln, &create) {
if (Str_Match(Lst_Datum(ln), arg)) {
result = TRUE;
break;
}
}
arg[argLen] = savec;
return (result);
}
static Boolean
CondDoExists(int argLen, char *arg)
{
char savec = arg[argLen];
Boolean result;
char *path;
arg[argLen] = '\0';
path = Path_FindFile(arg, &dirSearchPath);
if (path != NULL) {
result = TRUE;
free(path);
} else {
result = FALSE;
}
arg[argLen] = savec;
return (result);
}
static Boolean
CondDoTarget(int argLen, char *arg)
{
char savec = arg[argLen];
Boolean result;
GNode *gn;
arg[argLen] = '\0';
gn = Targ_FindNode(arg, TARG_NOCREATE);
if ((gn != NULL) && !OP_NOP(gn->type)) {
result = TRUE;
} else {
result = FALSE;
}
arg[argLen] = savec;
return (result);
}
static char *
CondCvtArg(char *str, double *value)
{
if ((*str == '0') && (str[1] == 'x')) {
long i;
for (str += 2, i = 0; ; str++) {
int x;
if (isdigit((unsigned char)*str))
x = *str - '0';
else if (isxdigit((unsigned char)*str))
x = 10 + *str -
isupper((unsigned char)*str) ? 'A' : 'a';
else {
*value = (double)i;
return (str);
}
i = (i << 4) + x;
}
} else {
char *eptr;
*value = strtod(str, &eptr);
return (eptr);
}
}
static Token
CondToken(Boolean doEval)
{
Token t;
if (condPushBack != None) {
t = condPushBack;
condPushBack = None;
return (t);
}
while (*condExpr == ' ' || *condExpr == '\t') {
condExpr++;
}
switch (*condExpr) {
case '(':
t = LParen;
condExpr++;
break;
case ')':
t = RParen;
condExpr++;
break;
case '|':
if (condExpr[1] == '|') {
condExpr++;
}
condExpr++;
t = Or;
break;
case '&':
if (condExpr[1] == '&') {
condExpr++;
}
condExpr++;
t = And;
break;
case '!':
t = Not;
condExpr++;
break;
case '\n':
case '\0':
t = EndOfFile;
break;
case '$': {
char *lhs;
const char *op;
char *rhs;
char zero[] = "0";
size_t varSpecLen = 0;
Boolean doFree;
t = Err;
lhs = Var_Parse(condExpr, VAR_CMD, doEval,
&varSpecLen, &doFree);
if (lhs == var_Error) {
return (Err);
}
condExpr += varSpecLen;
if (!isspace((unsigned char)*condExpr) &&
strchr("!=><", *condExpr) == NULL) {
Buffer *buf;
buf = Buf_Init(0);
Buf_Append(buf, lhs);
if (doFree)
free(lhs);
for (;*condExpr &&
!isspace((unsigned char)*condExpr);
condExpr++)
Buf_AddByte(buf, (Byte)*condExpr);
Buf_AddByte(buf, (Byte)'\0');
lhs = (char *)Buf_GetAll(buf, &varSpecLen);
Buf_Destroy(buf, FALSE);
doFree = TRUE;
}
while (isspace((unsigned char)*condExpr))
condExpr++;
op = condExpr;
switch (*condExpr) {
case '!':
case '=':
case '<':
case '>':
if (condExpr[1] == '=') {
condExpr += 2;
} else {
condExpr += 1;
}
while (isspace((unsigned char)*condExpr)) {
condExpr++;
}
if (*condExpr == '\0') {
Parse_Error(PARSE_WARNING,
"Missing right-hand-side of operator");
goto error;
}
rhs = condExpr;
break;
default:
op = "!=";
rhs = zero;
break;
}
if (*rhs == '"') {
char *string;
char *cp, *cp2;
int qt;
Buffer *buf;
do_string_compare:
if (((*op != '!') && (*op != '=')) ||
(op[1] != '=')) {
Parse_Error(PARSE_WARNING,
"String comparison operator should "
"be either == or !=");
goto error;
}
buf = Buf_Init(0);
qt = *rhs == '"' ? 1 : 0;
for (cp = &rhs[qt];
((qt && (*cp != '"')) ||
(!qt && strchr(" \t)", *cp) == NULL)) &&
(*cp != '\0'); cp++) {
if ((*cp == '\\') && (cp[1] != '\0')) {
cp++;
Buf_AddByte(buf, (Byte)*cp);
} else if (*cp == '$') {
size_t len = 0;
Boolean freeIt;
cp2 = Var_Parse(cp, VAR_CMD,
doEval, &len, &freeIt);
if (cp2 != var_Error) {
Buf_Append(buf, cp2);
if (freeIt) {
free(cp2);
}
cp += len - 1;
} else {
Buf_AddByte(buf,
(Byte)*cp);
}
} else {
Buf_AddByte(buf, (Byte)*cp);
}
}
string = Buf_Peel(buf);
DEBUGF(COND, ("lhs = \"%s\", rhs = \"%s\", "
"op = %.2s\n", lhs, string, op));
if (*op == '=') {
t = strcmp(lhs, string) ? False : True;
} else {
t = strcmp(lhs, string) ? True : False;
}
free(string);
if (rhs == condExpr) {
if (*cp == '\0' || (!qt && *cp == ')'))
condExpr = cp;
else
condExpr = cp + 1;
}
} else {
double left, right;
char *string;
if (*CondCvtArg(lhs, &left) != '\0')
goto do_string_compare;
if (*rhs == '$') {
size_t len = 0;
Boolean freeIt;
string = Var_Parse(rhs, VAR_CMD, doEval,
&len, &freeIt);
if (string == var_Error) {
right = 0.0;
} else {
if (*CondCvtArg(string,
&right) != '\0') {
if (freeIt)
free(string);
goto do_string_compare;
}
if (freeIt)
free(string);
if (rhs == condExpr)
condExpr += len;
}
} else {
char *c = CondCvtArg(rhs, &right);
if (c == rhs)
goto do_string_compare;
if (rhs == condExpr) {
condExpr = c;
}
}
DEBUGF(COND, ("left = %f, right = %f, "
"op = %.2s\n", left, right, op));
switch (op[0]) {
case '!':
if (op[1] != '=') {
Parse_Error(PARSE_WARNING,
"Unknown operator");
goto error;
}
t = (left != right ? True : False);
break;
case '=':
if (op[1] != '=') {
Parse_Error(PARSE_WARNING,
"Unknown operator");
goto error;
}
t = (left == right ? True : False);
break;
case '<':
if (op[1] == '=') {
t = (left <= right?True:False);
} else {
t = (left < right?True:False);
}
break;
case '>':
if (op[1] == '=') {
t = (left >= right?True:False);
} else {
t = (left > right?True:False);
}
break;
default:
break;
}
}
error:
if (doFree)
free(lhs);
break;
}
default: {
CondProc *evalProc;
Boolean invert = FALSE;
char *arg;
int arglen;
if (strncmp(condExpr, "defined", 7) == 0) {
evalProc = CondDoDefined;
condExpr += 7;
arglen = CondGetArg(&condExpr, &arg,
"defined", TRUE);
if (arglen == 0) {
condExpr -= 7;
goto use_default;
}
} else if (strncmp(condExpr, "make", 4) == 0) {
evalProc = CondDoMake;
condExpr += 4;
arglen = CondGetArg(&condExpr, &arg,
"make", TRUE);
if (arglen == 0) {
condExpr -= 4;
goto use_default;
}
} else if (strncmp(condExpr, "exists", 6) == 0) {
evalProc = CondDoExists;
condExpr += 6;
arglen = CondGetArg(&condExpr, &arg,
"exists", TRUE);
if (arglen == 0) {
condExpr -= 6;
goto use_default;
}
} else if (strncmp(condExpr, "empty", 5) == 0) {
size_t length;
Boolean doFree;
char *val;
condExpr += 5;
for (arglen = 0;
condExpr[arglen] != '(' &&
condExpr[arglen] != '\0'; arglen += 1)
continue;
if (condExpr[arglen] != '\0') {
length = 0;
val = Var_Parse(&condExpr[arglen - 1],
VAR_CMD, FALSE, &length, &doFree);
if (val == var_Error) {
t = Err;
} else {
char *p;
for (p = val;
*p &&
isspace((unsigned char)*p);
p++)
continue;
t = (*p == '\0') ? True : False;
}
if (doFree) {
free(val);
}
condExpr += arglen + length - 1;
} else {
condExpr -= 5;
goto use_default;
}
break;
} else if (strncmp(condExpr, "target", 6) == 0) {
evalProc = CondDoTarget;
condExpr += 6;
arglen = CondGetArg(&condExpr, &arg,
"target", TRUE);
if (arglen == 0) {
condExpr -= 6;
goto use_default;
}
} else {
use_default:
invert = condInvert;
evalProc = condDefProc;
arglen = CondGetArg(&condExpr, &arg, "", FALSE);
}
t = (!doEval || (* evalProc) (arglen, arg) ?
(invert ? False : True) :
(invert ? True : False));
free(arg);
break;
}
}
return (t);
}
static Token
CondT(Boolean doEval)
{
Token t;
t = CondToken(doEval);
if (t == EndOfFile) {
t = Err;
} else if (t == LParen) {
t = CondE(doEval);
if (t != Err) {
if (CondToken(doEval) != RParen) {
t = Err;
}
}
} else if (t == Not) {
t = CondT(doEval);
if (t == True) {
t = False;
} else if (t == False) {
t = True;
}
}
return (t);
}
static Token
CondF(Boolean doEval)
{
Token l, o;
l = CondT(doEval);
if (l != Err) {
o = CondToken(doEval);
if (o == And) {
if (l == True) {
l = CondF(doEval);
} else {
CondF(FALSE);
}
} else {
CondPushBack(o);
}
}
return (l);
}
static Token
CondE(Boolean doEval)
{
Token l, o;
l = CondF(doEval);
if (l != Err) {
o = CondToken(doEval);
if (o == Or) {
if (l == False) {
l = CondE(doEval);
} else {
CondE(FALSE);
}
} else {
CondPushBack(o);
}
}
return (l);
}
void
Cond_If(char *line, int code, int lineno)
{
const struct If *ifp;
Boolean value;
ifp = &ifs[code];
if (ifp->isElse) {
if (condTop == MAXIF) {
Parse_Error(PARSE_FATAL, "if-less elif");
return;
}
if (skipIfLevel != 0) {
skipIfLineno[skipIfLevel - 1] = lineno;
return;
}
} else if (skipLine) {
skipIfLineno[skipIfLevel] = lineno;
skipIfLevel += 1;
return;
}
condDefProc = ifp->defProc;
condInvert = ifp->doNot;
while (*line == ' ' || *line == '\t') {
line++;
}
condExpr = line;
condPushBack = None;
switch (CondE(TRUE)) {
case True:
if (CondToken(TRUE) != EndOfFile)
goto err;
value = TRUE;
break;
case False:
if (CondToken(TRUE) != EndOfFile)
goto err;
value = FALSE;
break;
case Err:
err: Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", line);
return;
default:
abort();
}
if (!ifp->isElse) {
condTop -= 1;
} else if (skipIfLevel != 0 || condStack[condTop]) {
skipLine = TRUE;
return;
}
if (condTop < 0) {
Parse_Error(PARSE_FATAL, "Too many nested if's. %d max.",MAXIF);
return;
}
condStack[condTop] = value;
condLineno[condTop] = lineno;
skipLine = !value;
}
void
Cond_Else(char *line __unused, int code __unused, int lineno __unused)
{
while (isspace((u_char)*line))
line++;
if (*line != '\0' && (warn_flags & WARN_DIRSYNTAX)) {
Parse_Error(PARSE_WARNING, "junk after .else ignored '%s'",
line);
}
if (condTop == MAXIF) {
Parse_Error(PARSE_FATAL, "if-less else");
return;
}
if (skipIfLevel != 0)
return;
if (skipIfLevel != 0 || condStack[condTop]) {
skipLine = TRUE;
return;
}
condStack[condTop] = !condStack[condTop];
skipLine = !condStack[condTop];
}
void
Cond_Endif(char *line __unused, int code __unused, int lineno __unused)
{
while (isspace((u_char)*line))
line++;
if (*line != '\0' && (warn_flags & WARN_DIRSYNTAX)) {
Parse_Error(PARSE_WARNING, "junk after .endif ignored '%s'",
line);
}
if (skipIfLevel != 0) {
skipIfLevel -= 1;
return;
}
if (condTop == MAXIF) {
Parse_Error(PARSE_FATAL, "if-less endif");
return;
}
skipLine = FALSE;
condTop += 1;
}
void
Cond_End(void)
{
int level;
if (condTop != MAXIF) {
Parse_Error(PARSE_FATAL, "%d open conditional%s:",
MAXIF - condTop + skipIfLevel,
MAXIF - condTop + skipIfLevel== 1 ? "" : "s");
for (level = skipIfLevel; level > 0; level--)
Parse_Error(PARSE_FATAL, "\t%*sat line %d (skipped)",
MAXIF - condTop + level + 1, "",
skipIfLineno[level - 1]);
for (level = condTop; level < MAXIF; level++)
Parse_Error(PARSE_FATAL, "\t%*sat line %d "
"(evaluated to %s)", MAXIF - level + skipIfLevel,
"", condLineno[level],
condStack[level] ? "true" : "false");
}
condTop = MAXIF;
}