#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ohash.h>
#include "defines.h"
#include "dir.h"
#include "buf.h"
#include "cond.h"
#include "cond_int.h"
#include "condhashconsts.h"
#include "error.h"
#include "var.h"
#include "varname.h"
#include "targ.h"
#include "lowparse.h"
#include "str.h"
#include "main.h"
#include "gnode.h"
#include "lst.h"
typedef enum {
False = 0, True = 1, And, Or, Not, LParen, RParen, EndOfFile, None, Err
} Token;
static bool CondGetArg(const char **, struct Name *,
const char *, bool);
static bool CondDoDefined(struct Name *);
static bool CondDoMake(struct Name *);
static bool CondDoExists(struct Name *);
static bool CondDoTarget(struct Name *);
static bool CondDoTargetWithCommands(struct Name *);
static bool CondCvtArg(const char *, double *);
static Token CondToken(bool);
static Token CondT(bool);
static Token CondF(bool);
static Token CondE(bool);
static Token CondHandleVarSpec(bool);
static Token CondHandleDefault(bool);
static Token CondHandleComparison(char *, bool, bool);
static Token CondHandleString(bool);
static Token CondHandleNumber(bool);
static const char *find_cond(const char *);
struct If {
bool isElse;
bool doNot;
bool (*defProc)(struct Name *);
};
static struct If ifs[] = {
{ false,false, CondDoDefined },
{ false,true, CondDoDefined },
{ false,false, CondDoMake },
{ false,true, CondDoMake },
{ true, false, CondDoDefined },
{ true, true, CondDoDefined },
{ true, false, CondDoMake },
{ true, true, CondDoMake },
{ true, false, NULL }
};
#define COND_IF_INDEX 0
#define COND_IFDEF_INDEX 0
#define COND_IFNDEF_INDEX 1
#define COND_IFMAKE_INDEX 2
#define COND_IFNMAKE_INDEX 3
#define COND_ELIF_INDEX 4
#define COND_ELIFDEF_INDEX 4
#define COND_ELIFNDEF_INDEX 5
#define COND_ELIFMAKE_INDEX 6
#define COND_ELIFNMAKE_INDEX 7
#define COND_ELSE_INDEX 8
static bool condInvert;
static bool (*condDefProc)(struct Name *);
static const char *condExpr;
static Token condPushBack=None;
#define MAXIF 30
static struct {
bool value;
Location origin;
} condStack[MAXIF];
static int condTop = MAXIF;
static int skipIfLevel=0;
static bool skipLine = false;
static const char *
find_cond(const char *p)
{
for (;;p++) {
if (strchr(" \t)&|$", *p) != NULL)
return p;
}
}
static bool
CondGetArg(const char **linePtr, struct Name *arg, const char *func,
bool parens)
{
const char *cp;
cp = *linePtr;
arg->s = cp;
arg->e = cp;
arg->tofree = false;
if (parens) {
while (ISSPACE(*cp))
cp++;
if (*cp == '(')
cp++;
else
return false;
}
if (*cp == '\0')
return false;
while (ISSPACE(*cp))
cp++;
cp = VarName_Get(cp, arg, NULL, true, find_cond);
while (ISSPACE(*cp))
cp++;
if (parens) {
if (*cp == ')')
cp++;
else {
Parse_Error(PARSE_WARNING,
"Missing closing parenthesis for %s()", func);
return false;
}
}
*linePtr = cp;
return true;
}
static bool
CondDoDefined(struct Name *arg)
{
return Var_Definedi(arg->s, arg->e);
}
static bool
CondDoMake(struct Name *arg)
{
LstNode ln;
for (ln = Lst_First(create); ln != NULL; ln = Lst_Adv(ln)) {
char *s = Lst_Datum(ln);
if (Str_Matchi(s, strchr(s, '\0'), arg->s, arg->e))
return true;
}
return false;
}
static bool
CondDoExists(struct Name *arg)
{
bool result;
char *path;
if (arg->s == arg->e)
Parse_Error(PARSE_FATAL, "Empty file name in .if exists()");
path = Dir_FindFilei(arg->s, arg->e, defaultPath);
if (path != NULL) {
result = true;
free(path);
} else {
result = false;
}
return result;
}
static bool
CondDoTarget(struct Name *arg)
{
GNode *gn;
gn = Targ_FindNodei(arg->s, arg->e, TARG_NOCREATE);
if (gn != NULL && !OP_NOP(gn->type))
return true;
else
return false;
}
static bool
CondDoTargetWithCommands(struct Name *arg)
{
GNode *gn;
gn = Targ_FindNodei(arg->s, arg->e, TARG_NOCREATE);
if (gn != NULL && !OP_NOP(gn->type) && (gn->type & OP_HAS_COMMANDS))
return true;
else
return false;
}
static bool
CondCvtArg(const char *str, double *value)
{
if (*str == '0' && str[1] == 'x') {
long i;
for (str += 2, i = 0; *str; str++) {
int x;
if (ISDIGIT(*str))
x = *str - '0';
else if (ISXDIGIT(*str))
x = 10 + *str - (ISUPPER(*str) ? 'A' : 'a');
else
return false;
i = (i << 4) + x;
}
*value = (double) i;
return true;
}
else {
char *eptr;
*value = strtod(str, &eptr);
return *eptr == '\0';
}
}
static Token
CondHandleNumber(bool doEval)
{
const char *end;
char *lhs;
end = condExpr;
while (*end != '\0' && !ISSPACE(*end) && strchr("!=><", *end) == NULL)
end++;
lhs = Str_dupi(condExpr, end);
condExpr = end;
return CondHandleComparison(lhs, true, doEval);
}
static Token
CondHandleVarSpec(bool doEval)
{
char *lhs;
size_t varSpecLen;
bool doFree;
lhs = Var_Parse(condExpr, NULL, doEval,&varSpecLen,&doFree);
if (lhs == var_Error)
return Err;
condExpr += varSpecLen;
if (*condExpr && !ISSPACE(*condExpr) &&
strchr("!=><", *condExpr) == NULL) {
BUFFER buf;
Buf_Init(&buf, 0);
Buf_AddString(&buf, lhs);
if (doFree)
free(lhs);
for (;*condExpr && !ISSPACE(*condExpr); condExpr++)
Buf_AddChar(&buf, *condExpr);
lhs = Var_Subst(Buf_Retrieve(&buf), NULL, doEval);
Buf_Destroy(&buf);
doFree = true;
}
return CondHandleComparison(lhs, doFree, doEval);
}
static Token
CondHandleString(bool doEval)
{
char *lhs;
const char *begin;
BUFFER buf;
begin = ++condExpr;
while (*condExpr && *condExpr != '"') {
condExpr++;
}
Buf_Init(&buf, 0);
Buf_Addi(&buf, begin, condExpr);
if (*condExpr == '"')
condExpr++;
lhs = Var_Subst(Buf_Retrieve(&buf), NULL, doEval);
Buf_Destroy(&buf);
return CondHandleComparison(lhs, true, doEval);
}
static Token
CondHandleComparison(char *lhs, bool doFree, bool doEval)
{
Token t;
const char *rhs;
const char *op;
t = Err;
while (ISSPACE(*condExpr))
condExpr++;
op = condExpr;
switch (*condExpr) {
case '!':
case '=':
case '<':
case '>':
if (condExpr[1] == '=')
condExpr += 2;
else
condExpr += 1;
break;
default:
op = "!=";
rhs = "0";
goto do_compare;
}
while (ISSPACE(*condExpr))
condExpr++;
if (*condExpr == '\0') {
Parse_Error(PARSE_WARNING,
"Missing right-hand-side of operator");
goto error;
}
rhs = condExpr;
do_compare:
if (*rhs == '"') {
char *string;
const char *cp;
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_Init(&buf, 0);
qt = *rhs == '"' ? 1 : 0;
for (cp = &rhs[qt]; ((qt && *cp != '"') ||
(!qt && strchr(" \t)", *cp) == NULL)) && *cp != '\0';) {
if (*cp == '$') {
size_t len;
if (Var_ParseBuffer(&buf, cp, NULL, doEval,
&len)) {
cp += len;
continue;
}
} else if (*cp == '\\' && cp[1] != '\0')
cp++;
Buf_AddChar(&buf, *cp++);
}
string = Buf_Retrieve(&buf);
if (DEBUG(COND))
printf("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 (!qt && *cp == ')')
condExpr = cp;
else if (*cp == '\0')
condExpr = cp;
else
condExpr = cp + 1;
}
} else {
double left, right;
char *string;
if (!CondCvtArg(lhs, &left))
goto do_string_compare;
if (*rhs == '$') {
size_t len;
bool freeIt;
string = Var_Parse(rhs, NULL, doEval,&len,&freeIt);
if (string == var_Error)
right = 0.0;
else {
if (!CondCvtArg(string, &right)) {
if (freeIt)
free(string);
goto do_string_compare;
}
if (freeIt)
free(string);
if (rhs == condExpr)
condExpr += len;
}
} else {
if (!CondCvtArg(rhs, &right))
goto do_string_compare;
if (rhs == condExpr) {
while (!ISSPACE(*condExpr) && *condExpr != '\0')
condExpr++;
}
}
if (DEBUG(COND))
printf("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;
}
}
error:
if (doFree)
free(lhs);
return t;
}
#define S(s) s, sizeof(s)-1
static struct operator {
const char *s;
size_t len;
bool (*proc)(struct Name *);
} ops[] = {
{S("defined"), CondDoDefined},
{S("make"), CondDoMake},
{S("exists"), CondDoExists},
{S("target"), CondDoTarget},
{S("commands"), CondDoTargetWithCommands},
{NULL, 0, NULL}
};
static Token
CondHandleDefault(bool doEval)
{
bool t;
bool (*evalProc)(struct Name *);
bool invert = false;
struct Name arg;
size_t arglen;
evalProc = NULL;
if (strncmp(condExpr, "empty", 5) == 0) {
size_t length;
bool doFree;
char *val;
condExpr += 5;
for (arglen = 0; condExpr[arglen] != '(' &&
condExpr[arglen] != '\0';)
arglen++;
if (condExpr[arglen] != '\0') {
val = Var_Parse(&condExpr[arglen - 1], NULL,
doEval, &length, &doFree);
if (val == var_Error)
t = Err;
else {
char *p;
for (p = val; ISSPACE(*p); p++)
continue;
t = *p == '\0' ? True : False;
}
if (doFree)
free(val);
condExpr += arglen + length - 1;
return t;
} else
condExpr -= 5;
} else {
struct operator *op;
for (op = ops; op != NULL; op++)
if (strncmp(condExpr, op->s, op->len) == 0) {
condExpr += op->len;
if (CondGetArg(&condExpr, &arg, op->s, true))
evalProc = op->proc;
else
condExpr -= op->len;
break;
}
}
if (evalProc == NULL) {
invert = condInvert;
evalProc = condDefProc;
CondGetArg(&condExpr, &arg, "", false);
}
t = (!doEval || (*evalProc)(&arg) ?
(invert ? False : True) :
(invert ? True : False));
VarName_Free(&arg);
return t;
}
static Token
CondToken(bool doEval)
{
if (condPushBack != None) {
Token t;
t = condPushBack;
condPushBack = None;
return t;
}
while (ISSPACE(*condExpr))
condExpr++;
switch (*condExpr) {
case '(':
condExpr++;
return LParen;
case ')':
condExpr++;
return RParen;
case '|':
if (condExpr[1] == '|')
condExpr++;
condExpr++;
return Or;
case '&':
if (condExpr[1] == '&')
condExpr++;
condExpr++;
return And;
case '!':
condExpr++;
return Not;
case '\n':
case '\0':
return EndOfFile;
case '"':
return CondHandleString(doEval);
case '$':
return CondHandleVarSpec(doEval);
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
return CondHandleNumber(doEval);
default:
return CondHandleDefault(doEval);
}
}
static Token
CondT(bool 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(bool doEval)
{
Token l, o;
l = CondT(doEval);
if (l != Err) {
o = CondToken(doEval);
if (o == And) {
if (l == True)
l = CondF(doEval);
else
(void)CondF(false);
} else
condPushBack = o;
}
return l;
}
static Token
CondE(bool doEval)
{
Token l, o;
l = CondF(doEval);
if (l != Err) {
o = CondToken(doEval);
if (o == Or) {
if (l == False)
l = CondE(doEval);
else
(void)CondE(false);
} else
condPushBack = o;
}
return l;
}
int
Cond_Eval(const char *line)
{
const char *end;
uint32_t k;
size_t len;
struct If *ifp;
bool value = false;
int level;
level = PARSE_FATAL;
for (end = line; ISLOWER(*end); end++)
;
if (*end == '.' || *end == ':')
return COND_INVALID;
len = end - line;
k = ohash_interval(line, &end);
switch(k % MAGICSLOTS2) {
case K_COND_IF % MAGICSLOTS2:
if (k == K_COND_IF && len == strlen(COND_IF) &&
strncmp(line, COND_IF, len) == 0) {
ifp = ifs + COND_IF_INDEX;
} else
return COND_INVALID;
break;
case K_COND_IFDEF % MAGICSLOTS2:
if (k == K_COND_IFDEF && len == strlen(COND_IFDEF) &&
strncmp(line, COND_IFDEF, len) == 0) {
ifp = ifs + COND_IFDEF_INDEX;
} else
return COND_INVALID;
break;
case K_COND_IFNDEF % MAGICSLOTS2:
if (k == K_COND_IFNDEF && len == strlen(COND_IFNDEF) &&
strncmp(line, COND_IFNDEF, len) == 0) {
ifp = ifs + COND_IFNDEF_INDEX;
} else
return COND_INVALID;
break;
case K_COND_IFMAKE % MAGICSLOTS2:
if (k == K_COND_IFMAKE && len == strlen(COND_IFMAKE) &&
strncmp(line, COND_IFMAKE, len) == 0) {
ifp = ifs + COND_IFMAKE_INDEX;
} else
return COND_INVALID;
break;
case K_COND_IFNMAKE % MAGICSLOTS2:
if (k == K_COND_IFNMAKE && len == strlen(COND_IFNMAKE) &&
strncmp(line, COND_IFNMAKE, len) == 0) {
ifp = ifs + COND_IFNMAKE_INDEX;
} else
return COND_INVALID;
break;
case K_COND_ELIF % MAGICSLOTS2:
if (k == K_COND_ELIF && len == strlen(COND_ELIF) &&
strncmp(line, COND_ELIF, len) == 0) {
ifp = ifs + COND_ELIF_INDEX;
} else
return COND_INVALID;
break;
case K_COND_ELIFDEF % MAGICSLOTS2:
if (k == K_COND_ELIFDEF && len == strlen(COND_ELIFDEF) &&
strncmp(line, COND_ELIFDEF, len) == 0) {
ifp = ifs + COND_ELIFDEF_INDEX;
} else
return COND_INVALID;
break;
case K_COND_ELIFNDEF % MAGICSLOTS2:
if (k == K_COND_ELIFNDEF && len == strlen(COND_ELIFNDEF) &&
strncmp(line, COND_ELIFNDEF, len) == 0) {
ifp = ifs + COND_ELIFNDEF_INDEX;
} else
return COND_INVALID;
break;
case K_COND_ELIFMAKE % MAGICSLOTS2:
if (k == K_COND_ELIFMAKE && len == strlen(COND_ELIFMAKE) &&
strncmp(line, COND_ELIFMAKE, len) == 0) {
ifp = ifs + COND_ELIFMAKE_INDEX;
} else
return COND_INVALID;
break;
case K_COND_ELIFNMAKE % MAGICSLOTS2:
if (k == K_COND_ELIFNMAKE && len == strlen(COND_ELIFNMAKE) &&
strncmp(line, COND_ELIFNMAKE, len) == 0) {
ifp = ifs + COND_ELIFNMAKE_INDEX;
} else
return COND_INVALID;
break;
case K_COND_ELSE % MAGICSLOTS2:
if (k == K_COND_ELSE && len == strlen(COND_ELSE) &&
strncmp(line, COND_ELSE, len) == 0) {
if (condTop == MAXIF) {
Parse_Error(level, "if-less else");
return COND_INVALID;
} else if (skipIfLevel == 0) {
value = !condStack[condTop].value;
ifp = ifs + COND_ELSE_INDEX;
} else
return COND_SKIP;
} else
return COND_INVALID;
break;
case K_COND_ENDIF % MAGICSLOTS2:
if (k == K_COND_ENDIF && len == strlen(COND_ENDIF) &&
strncmp(line, COND_ENDIF, len) == 0) {
if (skipIfLevel != 0) {
skipIfLevel--;
return COND_SKIP;
} else {
if (condTop == MAXIF) {
Parse_Error(level, "if-less endif");
return COND_INVALID;
} else {
skipLine = false;
condTop++;
return COND_PARSE;
}
}
} else
return COND_INVALID;
break;
case K_COND_FOR % MAGICSLOTS2:
if (k == K_COND_FOR && len == strlen(COND_FOR) &&
strncmp(line, COND_FOR, len) == 0)
return COND_ISFOR;
else
return COND_INVALID;
case K_COND_UNDEF % MAGICSLOTS2:
if (k == K_COND_UNDEF && len == strlen(COND_UNDEF) &&
strncmp(line, COND_UNDEF, len) == 0)
return COND_ISUNDEF;
else
return COND_INVALID;
case K_COND_POISON % MAGICSLOTS2:
if (k == K_COND_POISON && len == strlen(COND_POISON) &&
strncmp(line, COND_POISON, len) == 0)
return COND_ISPOISON;
else
return COND_INVALID;
case K_COND_INCLUDE % MAGICSLOTS2:
if (k == K_COND_INCLUDE && len == strlen(COND_INCLUDE) &&
strncmp(line, COND_INCLUDE, len) == 0)
return COND_ISINCLUDE;
else
return COND_INVALID;
default:
return COND_INVALID;
}
if (ifp->isElse) {
if (condTop == MAXIF) {
Parse_Error(level, "if-less elif");
return COND_INVALID;
} else if (skipIfLevel != 0 || condStack[condTop].value) {
skipLine = true;
return COND_SKIP;
}
} else if (skipLine) {
skipIfLevel++;
return COND_SKIP;
} else
condTop--;
if (condTop < 0) {
Parse_Error(PARSE_FATAL, "Too many nested if's. %d max.",
MAXIF);
condTop = 0;
return COND_INVALID;
}
if (ifp->defProc) {
condDefProc = ifp->defProc;
condInvert = ifp->doNot;
line += len;
while (*line == ' ' || *line == '\t')
line++;
condExpr = line;
condPushBack = None;
switch (CondE(true)) {
case True:
if (CondToken(true) == EndOfFile) {
value = true;
break;
}
goto err;
case False:
if (CondToken(true) == EndOfFile) {
value = false;
break;
}
case Err:
err:
Parse_Error(level, "Malformed conditional (%s)", line);
return COND_INVALID;
default:
break;
}
}
condStack[condTop].value = value;
Parse_FillLocation(&condStack[condTop].origin);
skipLine = !value;
return value ? COND_PARSE : COND_SKIP;
}
void
Cond_End(void)
{
int i;
if (condTop != MAXIF) {
Parse_Error(PARSE_FATAL, "%s%d open conditional%s",
condTop == 0 ? "at least ": "", MAXIF-condTop,
MAXIF-condTop == 1 ? "" : "s");
for (i = MAXIF-1; i >= condTop; i--) {
fprintf(stderr, "\t(%s:%lu)\n",
condStack[i].origin.fname,
condStack[i].origin.lineno);
}
}
condTop = MAXIF;
}