File: [local] / src / usr.bin / make / cond.c (download)
Revision 1.26, Tue May 29 12:53:39 2001 UTC (22 years, 11 months ago) by espie
Branch: MAIN
CVS Tags: OPENBSD_3_1_BASE, OPENBSD_3_1, OPENBSD_3_0_BASE, OPENBSD_3_0 Changes since 1.25: +2 -1 lines
Take includes out of lst.h, re-add what's needed to separate files.
Removes remaining lint stuff from lst.lib.
|
/* $OpenPackages$ */
/* $OpenBSD: cond.c,v 1.26 2001/05/29 12:53:39 espie Exp $ */
/* $NetBSD: cond.c,v 1.7 1996/11/06 17:59:02 christos Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
* Copyright (c) 1988, 1989 by Adam de Boor
* Copyright (c) 1989 by Berkeley Softworks
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Adam de Boor.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* 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
* SUCH DAMAGE.
*/
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "defines.h"
#include "dir.h"
#include "buf.h"
#include "cond.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"
/* The parsing of conditional expressions is based on this grammar:
* E -> F || E
* E -> F
* F -> T && F
* F -> T
* T -> defined(variable)
* T -> make(target)
* T -> exists(file)
* T -> empty(varspec)
* T -> target(name)
* T -> symbol
* T -> $(varspec) op value
* T -> $(varspec) == "string"
* T -> $(varspec) != "string"
* T -> ( E )
* T -> ! T
* op -> == | != | > | < | >= | <=
*
* 'symbol' is some other symbol to which the default function (condDefProc)
* is applied.
*
* Tokens are scanned from the 'condExpr' string. The scanner (CondToken)
* will return And for '&' and '&&', Or for '|' and '||', Not for '!',
* LParen for '(', RParen for ')' and will evaluate the other terminal
* symbols, using either the default function or the function given in the
* terminal, and return the result as either true or False.
*
* All Non-Terminal functions (CondE, CondF and CondT) return Err on error. */
typedef enum {
False = 0, True = 1, And, Or, Not, LParen, RParen, EndOfFile, None, Err
} Token;
/*-
* Structures to handle elegantly the different forms of #if's. The
* last two fields are stored in condInvert and condDefProc, respectively.
*/
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 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 const char *find_cond(const char *);
static struct If {
char *form; /* Form of if */
int formlen; /* Length of form */
bool doNot; /* true if default function should be negated */
bool (*defProc)(struct Name *);
/* Default function to apply */
} ifs[] = {
{ "ifdef", 5, false, CondDoDefined },
{ "ifndef", 6, true, CondDoDefined },
{ "ifmake", 6, false, CondDoMake },
{ "ifnmake", 7, true, CondDoMake },
{ "if", 2, false, CondDoDefined },
{ NULL, 0, false, NULL }
};
static bool condInvert; /* Invert the default function */
static bool (*condDefProc) /* Default function to apply */
(struct Name *);
static const char *condExpr; /* The expression to parse */
static Token condPushBack=None; /* Single push-back token used in
* parsing */
#define MAXIF 30 /* greatest depth of #if'ing */
static struct {
bool value;
unsigned long lineno;
const char *filename;
} condStack[MAXIF]; /* Stack of conditionals */
static int condTop = MAXIF; /* Top-most conditional */
static int skipIfLevel=0; /* Depth of skipped conditionals */
static bool skipLine = false; /* Whether the parse module is skipping
* lines */
static const char *
find_cond(p)
const char *p;
{
for (;;p++) {
if (strchr(" \t)&|$", *p) != NULL)
return p;
}
}
/*-
*-----------------------------------------------------------------------
* CondGetArg --
* Find the argument of a built-in function.
*
* Results:
* true if evaluation went okay
*
* Side Effects:
* The line pointer is set to point to the closing parenthesis of the
* function call. The argument is filled.
*-----------------------------------------------------------------------
*/
static bool
CondGetArg(linePtr, arg, func, parens)
const char **linePtr;
struct Name *arg;
const char *func;
bool parens; /* true if arg should be bounded by parens */
{
const char *cp;
cp = *linePtr;
if (parens) {
while (*cp != '(' && *cp != '\0')
cp++;
if (*cp == '(')
cp++;
}
if (*cp == '\0') {
/* No arguments whatsoever. Because 'make' and 'defined' aren't really
* "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... */
arg->s = cp;
arg->e = cp;
arg->tofree = false;
return false;
}
while (*cp == ' ' || *cp == '\t')
cp++;
cp = VarName_Get(cp, arg, NULL, true, find_cond);
while (*cp == ' ' || *cp == '\t')
cp++;
if (parens && *cp != ')') {
Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
func);
return false;
} else if (parens)
/* Advance pointer past close parenthesis. */
cp++;
*linePtr = cp;
return true;
}
/*-
*-----------------------------------------------------------------------
* CondDoDefined --
* Handle the 'defined' function for conditionals.
*
* Results:
* true if the given variable is defined.
*-----------------------------------------------------------------------
*/
static bool
CondDoDefined(arg)
struct Name *arg;
{
if (Var_Valuei(arg->s, arg->e) != NULL)
return true;
else
return false;
}
/*-
*-----------------------------------------------------------------------
* CondDoMake --
* Handle the 'make' function for conditionals.
*
* Results:
* true if the given target is being made.
*-----------------------------------------------------------------------
*/
static bool
CondDoMake(arg)
struct Name *arg;
{
LstNode ln;
for (ln = Lst_First(create); ln != NULL; ln = Lst_Adv(ln)) {
char *s = (char *)Lst_Datum(ln);
if (Str_Matchi(s, strchr(s, '\0'), arg->s, arg->e))
return true;
}
return false;
}
/*-
*-----------------------------------------------------------------------
* CondDoExists --
* See if the given file exists.
*
* Results:
* true if the file exists and false if it does not.
*-----------------------------------------------------------------------
*/
static bool
CondDoExists(arg)
struct Name *arg;
{
bool result;
char *path;
path = Dir_FindFilei(arg->s, arg->e, dirSearchPath);
if (path != NULL) {
result = true;
free(path);
} else {
result = false;
}
return result;
}
/*-
*-----------------------------------------------------------------------
* CondDoTarget --
* See if the given node exists and is an actual target.
*
* Results:
* true if the node exists as a target and false if it does not.
*-----------------------------------------------------------------------
*/
static bool
CondDoTarget(arg)
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;
}
/*-
*-----------------------------------------------------------------------
* CondCvtArg --
* Convert the given number into a double. If the number begins
* with 0x, it is interpreted as a hexadecimal integer
* and converted to a double from there. All other strings just have
* strtod called on them.
*
* Results:
* Sets 'value' to double value of string.
* Returns true if the string was a valid number, false o.w.
*
* Side Effects:
* Can change 'value' even if string is not a valid number.
*-----------------------------------------------------------------------
*/
static bool
CondCvtArg(str, value)
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
CondHandleVarSpec(doEval)
bool doEval;
{
Token t;
char *lhs;
const char *rhs;
const char *op;
size_t varSpecLen;
bool doFree;
/* Parse the variable spec and skip over it, saving its
* value in lhs. */
t = Err;
lhs = Var_Parse(condExpr, NULL, doEval,&varSpecLen,&doFree);
if (lhs == var_Error)
/* Even if !doEval, we still report syntax errors, which
* is what getting var_Error back with !doEval means. */
return Err;
condExpr += varSpecLen;
if (!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 = Buf_Retrieve(&buf);
doFree = true;
}
/* Skip whitespace to get to the operator. */
while (isspace(*condExpr))
condExpr++;
/* Make sure the operator is a valid one. If it isn't a
* known relational operator, pretend we got a
* != 0 comparison. */
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 == '"') {
/* Doing a string comparison. Only allow == and != for
* operators. */
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')
/* Backslash escapes things -- skip over next
* character, if it exists. */
cp++;
Buf_AddChar(&buf, *cp++);
}
string = Buf_Retrieve(&buf);
if (DEBUG(COND))
printf("lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
lhs, string, op);
/* Null-terminate rhs and perform the comparison.
* t is set to the result. */
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 {
/* rhs is either a float or an integer. Convert both the
* lhs and the rhs to a double and compare the two. */
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) {
/* Skip over the right-hand side. */
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},
{NULL, 0, NULL}
};
static Token
CondHandleDefault(doEval)
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) {
/* Use Var_Parse to parse the spec in parens and return
* True if the resulting string is empty. */
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 {
/* A variable is empty when it just contains
* spaces... 4/15/92, christos */
char *p;
for (p = val; *p && isspace(*p); p++)
continue;
t = *p == '\0' ? True : False;
}
if (doFree)
free(val);
/* Advance condExpr to beyond the closing ). Note that
* we subtract one from arglen + length b/c length
* is calculated from condExpr[arglen - 1]. */
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) {
/* The symbol is itself the argument to the default
* function. We advance condExpr to the end of the symbol
* by hand (the next whitespace, closing paren or
* binary operator) and set to invert the evaluation
* function if condInvert is true. */
invert = condInvert;
evalProc = condDefProc;
/* XXX should we ignore problems now ? */
CondGetArg(&condExpr, &arg, "", false);
}
/* Evaluate the argument using the set function. If invert
* is true, we invert the sense of the function. */
t = (!doEval || (*evalProc)(&arg) ?
(invert ? False : True) :
(invert ? True : False));
VarName_Free(&arg);
return t;
}
/*-
*-----------------------------------------------------------------------
* CondToken --
* Return the next token from the input.
*
* Results:
* A Token for the next lexical token in the stream.
*
* Side Effects:
* condPushback will be set back to None if it is used.
*-----------------------------------------------------------------------
*/
static Token
CondToken(doEval)
bool doEval;
{
if (condPushBack != None) {
Token t;
t = condPushBack;
condPushBack = None;
return t;
}
while (*condExpr == ' ' || *condExpr == '\t')
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 CondHandleVarSpec(doEval);
default:
return CondHandleDefault(doEval);
}
}
/*-
*-----------------------------------------------------------------------
* CondT --
* Parse a single term in the expression. This consists of a terminal
* symbol or Not and a terminal symbol (not including the binary
* operators):
* T -> defined(variable) | make(target) | exists(file) | symbol
* T -> ! T | ( E )
*
* Results:
* True, False or Err.
*
* Side Effects:
* Tokens are consumed.
*-----------------------------------------------------------------------
*/
static Token
CondT(doEval)
bool doEval;
{
Token t;
t = CondToken(doEval);
if (t == EndOfFile)
/* If we reached the end of the expression, the expression
* is malformed... */
t = Err;
else if (t == LParen) {
/* T -> ( E ). */
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;
}
/*-
*-----------------------------------------------------------------------
* CondF --
* Parse a conjunctive factor (nice name, wot?)
* F -> T && F | T
*
* Results:
* True, False or Err
*
* Side Effects:
* Tokens are consumed.
*-----------------------------------------------------------------------
*/
static Token
CondF(doEval)
bool doEval;
{
Token l, o;
l = CondT(doEval);
if (l != Err) {
o = CondToken(doEval);
if (o == And) {
/* F -> T && F
*
* If T is False, the whole thing will be False, but we have to
* parse the r.h.s. anyway (to throw it away).
* If T is True, the result is the r.h.s., be it an Err or no. */
if (l == True)
l = CondF(doEval);
else
(void)CondF(false);
} else
/* F -> T. */
condPushBack = o;
}
return l;
}
/*-
*-----------------------------------------------------------------------
* CondE --
* Main expression production.
* E -> F || E | F
*
* Results:
* True, False or Err.
*
* Side Effects:
* Tokens are, of course, consumed.
*-----------------------------------------------------------------------
*/
static Token
CondE(doEval)
bool doEval;
{
Token l, o;
l = CondF(doEval);
if (l != Err) {
o = CondToken(doEval);
if (o == Or) {
/* E -> F || E
*
* A similar thing occurs for ||, except that here we make sure
* the l.h.s. is False before we bother to evaluate the r.h.s.
* Once again, if l is False, the result is the r.h.s. and once
* again if l is True, we parse the r.h.s. to throw it away. */
if (l == False)
l = CondE(doEval);
else
(void)CondE(false);
} else
/* E -> F. */
condPushBack = o;
}
return l;
}
/* A conditional line looks like this:
* <cond-type> <expr>
* where <cond-type> is any of if, ifmake, ifnmake, ifdef,
* ifndef, elif, elifmake, elifnmake, elifdef, elifndef
* and <expr> consists of &&, ||, !, make(target), defined(variable)
* and parenthetical groupings thereof.
*/
int
Cond_Eval(line)
const char *line; /* Line to parse */
{
struct If *ifp;
bool isElse;
bool value = false;
int level; /* Level at which to report errors. */
level = PARSE_FATAL;
/* Stuff we are looking for can be if*, elif*, else, or endif.
* otherwise, this is not our turf. */
/* Find what type of if we're dealing with. The result is left
* in ifp and isElse is set true if it's an elif line. */
if (line[0] == 'e' && line[1] == 'l') {
line += 2;
isElse = true;
} else if (strncmp(line, "endif", 5) == 0) {
/* End of a conditional section. If skipIfLevel is non-zero, that
* conditional was skipped, so lines following it should also be
* skipped. Hence, we return COND_SKIP. Otherwise, the conditional
* was read so succeeding lines should be parsed (think about it...)
* so we return COND_PARSE, unless this endif isn't paired with
* a decent if. */
if (skipIfLevel != 0) {
skipIfLevel -= 1;
return COND_SKIP;
} else {
if (condTop == MAXIF) {
Parse_Error(level, "if-less endif");
return COND_INVALID;
} else {
skipLine = false;
condTop += 1;
return COND_PARSE;
}
}
} else
isElse = false;
/* Figure out what sort of conditional it is -- what its default
* function is, etc. -- by looking in the table of valid "ifs" */
for (ifp = ifs; ifp->form != NULL; ifp++) {
if (strncmp(ifp->form, line, ifp->formlen) == 0)
break;
}
if (ifp->form == NULL) {
/* Nothing fits. If the first word on the line is actually
* "else", it's a valid conditional whose value is the inverse
* of the previous if we parsed. */
if (isElse && line[0] == 's' && line[1] == 'e') {
if (condTop == MAXIF) {
Parse_Error(level, "if-less else");
return COND_INVALID;
} else if (skipIfLevel == 0)
value = !condStack[condTop].value;
else
return COND_SKIP;
} else
/* Not a valid conditional type. No error... */
return COND_INVALID;
} else {
if (isElse) {
if (condTop == MAXIF) {
Parse_Error(level, "if-less elif");
return COND_INVALID;
} else if (skipIfLevel != 0) {
/* If skipping this conditional, just ignore the whole thing.
* If we don't, the user might be employing a variable that's
* undefined, for which there's an enclosing ifdef that
* we're skipping... */
return COND_SKIP;
}
} else if (skipLine) {
/* Don't even try to evaluate a conditional that's not an else if
* we're skipping things... */
skipIfLevel += 1;
return COND_SKIP;
}
/* Initialize file-global variables for parsing. */
condDefProc = ifp->defProc;
condInvert = ifp->doNot;
line += ifp->formlen;
while (*line == ' ' || *line == '\t')
line++;
condExpr = line;
condPushBack = None;
switch (CondE(true)) {
case True:
if (CondToken(true) == EndOfFile) {
value = true;
break;
}
goto err;
/* FALLTHROUGH */
case False:
if (CondToken(true) == EndOfFile) {
value = false;
break;
}
/* FALLTHROUGH */
case Err:
err:
Parse_Error(level, "Malformed conditional (%s)", line);
return COND_INVALID;
default:
break;
}
}
if (!isElse)
condTop -= 1;
else if (skipIfLevel != 0 || condStack[condTop].value) {
/* If this is an else-type conditional, it should only take effect
* if its corresponding if was evaluated and false. If its if was
* true or skipped, we return COND_SKIP (and start skipping in case
* we weren't already), leaving the stack unmolested so later elif's
* don't screw up... */
skipLine = true;
return COND_SKIP;
}
if (condTop < 0) {
/* This is the one case where we can definitely proclaim a fatal
* error. If we don't, we're hosed. */
Parse_Error(PARSE_FATAL, "Too many nested if's. %d max.", MAXIF);
return COND_INVALID;
} else {
condStack[condTop].value = value;
condStack[condTop].lineno = Parse_Getlineno();
condStack[condTop].filename = Parse_Getfilename();
skipLine = !value;
return value ? COND_PARSE : COND_SKIP;
}
}
void
Cond_End()
{
int i;
if (condTop != MAXIF) {
Parse_Error(PARSE_FATAL, "%d open conditional%s", MAXIF-condTop,
MAXIF-condTop == 1 ? "" : "s");
for (i = MAXIF-1; i >= condTop; i--) {
fprintf(stderr, "\t at line %lu of %s\n", condStack[i].lineno,
condStack[i].filename);
}
}
condTop = MAXIF;
}