/* Copyright (c) 1992 Regents of the University of California */

#ifndef lint
static char SCCSid[] = "@(#)calexpr.c 2.10 11/10/92 LBL";
#endif

/*
 *  Compute data values using expression parser
 *
 *  7/1/85  Greg Ward
 *
 *  11/11/85  Made channel input conditional with (INCHAN) compiles.
 *
 *  4/2/86  Added conditional compiles for function definitions (FUNCTION).
 *
 *  1/29/87  Made variables conditional (VARIABLE)
 *
 *  5/19/88  Added constant subexpression elimination (RCONST)
 */

#include  <stdio.h>

#include  <ctype.h>

#include  <errno.h>

#include  <math.h>

#include  "calcomp.h"

#define	 MAXLINE	256		/* maximum line length */

#define	 newnode()	(EPNODE *)ecalloc(1, sizeof(EPNODE))

#define	 isdecimal(c)	(isdigit(c) || (c) == '.')

#ifndef atof
extern double  atof();
#endif
extern char  *fgets(), *savestr();
extern char  *emalloc(), *ecalloc();
extern EPNODE  *curfunc;
extern double  efunc(), evariable();
static double  euminus(), echannel(), eargument(), enumber();
static double  eadd(), esubtr(), emult(), edivi(), epow();
static double  ebotch();

int  nextc;				/* lookahead character */

double	(*eoper[])() = {		/* expression operations */
	ebotch,
#ifdef	VARIABLE
	evariable,
#else
	ebotch,
#endif
	enumber,
	euminus,
#ifdef	INCHAN
	echannel,
#else
	ebotch,
#endif
#ifdef	FUNCTION
	efunc,
	eargument,
#else
	ebotch,
	ebotch,
#endif
	ebotch,
	ebotch,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	emult,
	eadd,
	0,
	esubtr,
	0,
	edivi,
	0,0,0,0,0,0,0,0,0,0,
	ebotch,
	0,0,
	ebotch,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	epow,
};

static FILE  *infp;			/* input file pointer */
static char  *linbuf;			/* line buffer */
static char  *infile;			/* input file name */
static int  lineno;			/* input line number */
static int  linepos;			/* position in buffer */


EPNODE *
eparse(expr)			/* parse an expression string */
char  *expr;
{
    EPNODE  *ep;

    initstr(expr, NULL, 0);
#if  defined(VARIABLE) && defined(FUNCTION)
    curfunc = NULL;
#endif
    ep = getE1();
    if (nextc != EOF)
	syntax("unexpected character");
    return(ep);
}


double
eval(expr)			/* evaluate an expression string */
char  *expr;
{
    register EPNODE  *ep;
    double  rval;

    ep = eparse(expr);
    rval = evalue(ep);
    epfree(ep);
    return(rval);
}


epfree(epar)			/* free a parse tree */
register EPNODE	 *epar;
{
    register EPNODE  *ep, *epn;

    switch (epar->type) {

#if  defined(VARIABLE) || defined(FUNCTION)
	case VAR:
	    varfree(epar->v.ln);
	    break;
	    
	case SYM:
	    freestr(epar->v.name);
	    break;
#endif

	case NUM:
	case CHAN:
	case ARG:
	case TICK:
	    break;

	default:
	    for (ep = epar->v.kid; ep != NULL; ep = epn) {
		epn = ep->sibling;
		epfree(ep);
	    }
	    break;

    }

    efree((char *)epar);
}

				/* the following used to be a switch */
#ifdef	FUNCTION
static double
eargument(ep)
EPNODE	*ep;
{
    return(argument(ep->v.chan));
}
#endif

static double
enumber(ep)
EPNODE	*ep;
{
    return(ep->v.num);
}

static double
euminus(ep)
EPNODE	*ep;
{
    register EPNODE  *ep1 = ep->v.kid;

    return(-evalue(ep1));
}

#ifdef	INCHAN
static double
echannel(ep)
EPNODE	*ep;
{
    return(chanvalue(ep->v.chan));
}
#endif

static double
eadd(ep)
EPNODE	*ep;
{
    register EPNODE  *ep1 = ep->v.kid;

    return(evalue(ep1) + evalue(ep1->sibling));
}

static double
esubtr(ep)
EPNODE	*ep;
{
    register EPNODE  *ep1 = ep->v.kid;

    return(evalue(ep1) - evalue(ep1->sibling));
}

static double
emult(ep)
EPNODE	*ep;
{
    register EPNODE  *ep1 = ep->v.kid;

    return(evalue(ep1) * evalue(ep1->sibling));
}

static double
edivi(ep)
EPNODE	*ep;
{
    register EPNODE  *ep1 = ep->v.kid;
    double  d;

    d = evalue(ep1->sibling);
    if (d == 0.0) {
	wputs("Division by zero\n");
	errno = ERANGE;
	return(0.0);
    }
    return(evalue(ep1) / d);
}

static double
epow(ep)
EPNODE	*ep;
{
    register EPNODE  *ep1 = ep->v.kid;
    double  d;
    int	 lasterrno;

    lasterrno = errno;
    errno = 0;
    d = pow(evalue(ep1), evalue(ep1->sibling));
#ifdef	IEEE
    if (!finite(d))
	errno = EDOM;
#endif
    if (errno) {
	wputs("Illegal power\n");
	return(0.0);
    }
    errno = lasterrno;
    return(d);
}

static double
ebotch(ep)
EPNODE	*ep;
{
    eputs("Bad expression!\n");
    quit(1);
}


EPNODE *
ekid(ep, n)			/* return pointer to a node's nth kid */
register EPNODE	 *ep;
register int  n;
{

    for (ep = ep->v.kid; ep != NULL; ep = ep->sibling)
	if (--n < 0)
	    break;

    return(ep);
}


int
nekids(ep)			/* return # of kids for node ep */
register EPNODE	 *ep;
{
    register int  n = 0;

    for (ep = ep->v.kid; ep != NULL; ep = ep->sibling)
	n++;

    return(n);
}


initfile(fp, fn, ln)		/* prepare input file */
FILE  *fp;
char  *fn;
int  ln;
{
    static char	 inpbuf[MAXLINE];

    infp = fp;
    linbuf = inpbuf;
    infile = fn;
    lineno = ln;
    linepos = 0;
    inpbuf[0] = '\0';
    scan();
}


initstr(s, fn, ln)		/* prepare input string */
char  *s;
char  *fn;
int  ln;
{
    infp = NULL;
    infile = fn;
    lineno = ln;
    linbuf = s;
    linepos = 0;
    scan();
}


getscanpos(fnp, lnp, spp, fpp)	/* return current scan position */
char  **fnp;
int  *lnp;
char  **spp;
FILE  **fpp;
{
    if (fnp != NULL) *fnp = infile;
    if (lnp != NULL) *lnp = lineno;
    if (spp != NULL) *spp = linbuf+linepos;
    if (fpp != NULL) *fpp = infp;
}


int
scan()				/* scan next character, return literal next */
{
    register int  lnext = 0;

    do {
	if (linbuf[linepos] == '\0')
	    if (infp == NULL || fgets(linbuf, MAXLINE, infp) == NULL)
		nextc = EOF;
	    else {
		nextc = linbuf[0];
		lineno++;
		linepos = 1;
	    }
	else
	    nextc = linbuf[linepos++];
	if (!lnext)
		lnext = nextc;
	if (nextc == '{') {
	    scan();
	    while (nextc != '}')
		if (nextc == EOF)
		    syntax("'}' expected");
		else
		    scan();
	    scan();
	}
    } while (isspace(nextc));
    return(lnext);
}


char *
long2ascii(l)			      /* convert long to ascii */
long  l;
{
    static char	 buf[16];
    register char  *cp;
    int	 neg = 0;

    if (l == 0)
	return("0");
    if (l < 0) {
	l = -l;
	neg++;
    }
    cp = buf + sizeof(buf);
    *--cp = '\0';
    while (l) {
	*--cp = l % 10 + '0';
	l /= 10;
    }
    if (neg)
	*--cp = '-';
    return(cp);
}


syntax(err)			/* report syntax error and quit */
char  *err;
{
    register int  i;

    if (infile != NULL || lineno != 0) {
	if (infile != NULL) eputs(infile);
	if (lineno != 0) {
	    eputs(infile != NULL ? ", line " : "line ");
	    eputs(long2ascii((long)lineno));
	}
	eputs(":\n");
    }
    eputs(linbuf);
    if (linbuf[strlen(linbuf)-1] != '\n')
	eputs("\n");
    for (i = 0; i < linepos-1; i++)
	eputs(linbuf[i] == '\t' ? "\t" : " ");
    eputs("^ ");
    eputs(err);
    eputs("\n");
    quit(1);
}


addekid(ep, ekid)			/* add a child to ep */
register EPNODE	 *ep;
EPNODE	*ekid;
{
    if (ep->v.kid == NULL)
	ep->v.kid = ekid;
    else {
	for (ep = ep->v.kid; ep->sibling != NULL; ep = ep->sibling)
	    ;
	ep->sibling = ekid;
    }
    ekid->sibling = NULL;
}


#if  defined(VARIABLE) || defined(FUNCTION)
char *
getname()			/* scan an identifier */
{
    static char	 str[MAXWORD+1];
    register int  i, lnext;

    lnext = nextc;
    for (i = 0; i < MAXWORD && isid(lnext); i++, lnext = scan())
	str[i] = lnext;
    str[i] = '\0';
    while (isid(lnext))		/* skip rest of name */
	lnext = scan();

    return(str);
}
#endif


int
getinum()			/* scan a positive integer */
{
    register int  n, lnext;

    n = 0;
    lnext = nextc;
    while (isdigit(lnext)) {
	n = n * 10 + lnext - '0';
	lnext = scan();
    }
    return(n);
}


double
getnum()			/* scan a positive float */
{
    register int  i, lnext;
    char  str[MAXWORD+1];

    i = 0;
    lnext = nextc;
    while (isdigit(lnext) && i < MAXWORD) {
	str[i++] = lnext;
	lnext = scan();
    }
    if (lnext == '.' && i < MAXWORD) {
	str[i++] = lnext;
	lnext = scan();
	while (isdigit(lnext) && i < MAXWORD) {
	    str[i++] = lnext;
	    lnext = scan();
	}
    }
    if ((lnext == 'e' || lnext == 'E') && i < MAXWORD) {
	str[i++] = lnext;
	lnext = scan();
	if ((lnext == '-' || lnext == '+') && i < MAXWORD) {
	    str[i++] = lnext;
	    lnext = scan();
	}
	while (isdigit(lnext) && i < MAXWORD) {
	    str[i++] = lnext;
	    lnext = scan();
	}
    }
    str[i] = '\0';

    return(atof(str));
}


EPNODE *
getE1()				/* E1 -> E1 ADDOP E2 */
				/*	 E2 */
{
    register EPNODE  *ep1, *ep2;

    ep1 = getE2();
    while (nextc == '+' || nextc == '-') {
	ep2 = newnode();
	ep2->type = nextc;
	scan();
	addekid(ep2, ep1);
	addekid(ep2, getE2());
#ifdef	RCONST
	if (ep1->type == NUM && ep1->sibling->type == NUM)
		ep2 = rconst(ep2);
#endif
	ep1 = ep2;
    }
    return(ep1);
}


EPNODE *
getE2()				/* E2 -> E2 MULOP E3 */
				/*	 E3 */
{
    register EPNODE  *ep1, *ep2;

    ep1 = getE3();
    while (nextc == '*' || nextc == '/') {
	ep2 = newnode();
	ep2->type = nextc;
	scan();
	addekid(ep2, ep1);
	addekid(ep2, getE3());
#ifdef	RCONST
	if (ep1->type == NUM && ep1->sibling->type == NUM)
		ep2 = rconst(ep2);
#endif
	ep1 = ep2;
    }
    return(ep1);
}


EPNODE *
getE3()				/* E3 -> E4 ^ E3 */
				/*	 E4 */
{
    register EPNODE  *ep1, *ep2;

    ep1 = getE4();
    if (nextc == '^') {
	ep2 = newnode();
	ep2->type = nextc;
	scan();
	addekid(ep2, ep1);
	addekid(ep2, getE3());
#ifdef	RCONST
	if (ep1->type == NUM && ep1->sibling->type == NUM)
		ep2 = rconst(ep2);
#endif
	return(ep2);
    }
    return(ep1);
}


EPNODE *
getE4()				/* E4 -> ADDOP E5 */
				/*	 E5 */
{
    register EPNODE  *ep1, *ep2;

    if (nextc == '-') {
	scan();
	ep2 = getE5();
	if (ep2->type == NUM) {
		ep2->v.num = -ep2->v.num;
		return(ep2);
	}
	if (ep2->type == UMINUS) {	/* don't generate -(-E5) */
	    efree((char *)ep2);
	    return(ep2->v.kid);
	}
	ep1 = newnode();
	ep1->type = UMINUS;
	addekid(ep1, ep2);
	return(ep1);
    }
    if (nextc == '+')
	scan();
    return(getE5());
}


EPNODE *
getE5()				/* E5 -> (E1) */
				/*	 VAR */
				/*	 NUM */
				/*	 $N */
				/*	 FUNC(E1,..) */
				/*	 ARG */
{
    int	 i;
    char  *nam;
    register EPNODE  *ep1, *ep2;

    if (nextc == '(') {
	scan();
	ep1 = getE1();
	if (nextc != ')')
	    syntax("')' expected");
	scan();
	return(ep1);
    }

#ifdef	INCHAN
    if (nextc == '$') {
	scan();
	ep1 = newnode();
	ep1->type = CHAN;
	ep1->v.chan = getinum();
	return(ep1);
    }
#endif

#if  defined(VARIABLE) || defined(FUNCTION)
    if (isalpha(nextc) || nextc == CNTXMARK) {
	nam = getname();
#if  defined(VARIABLE) && defined(FUNCTION)
	ep1 = NULL;
	if (curfunc != NULL)
	    for (i = 1, ep2 = curfunc->v.kid->sibling;
				ep2 != NULL; i++, ep2 = ep2->sibling)
		if (!strcmp(ep2->v.name, nam)) {
		    ep1 = newnode();
		    ep1->type = ARG;
		    ep1->v.chan = i;
		    break;
		}
	if (ep1 == NULL)
#endif
	{
	    ep1 = newnode();
	    ep1->type = VAR;
	    ep1->v.ln = varinsert(nam);
	}
#ifdef	FUNCTION
	if (nextc == '(') {
	    ep2 = newnode();
	    ep2->type = FUNC;
	    addekid(ep2, ep1);
	    ep1 = ep2;
	    do {
		scan();
		addekid(ep1, getE1());
	    } while (nextc == ',');
	    if (nextc != ')')
		syntax("')' expected");
	    scan();
	}
#ifndef	 VARIABLE
	else
	    syntax("'(' expected");
#endif
#endif
#ifdef	RCONST
	if (isconstvar(ep1))
	    ep1 = rconst(ep1);
#endif
	return(ep1);
    }
#endif

    if (isdecimal(nextc)) {
	ep1 = newnode();
	ep1->type = NUM;
	ep1->v.num = getnum();
	return(ep1);
    }
    syntax("unexpected character");
}


#ifdef	RCONST
EPNODE *
rconst(epar)			/* reduce a constant expression */
register EPNODE	 *epar;
{
    register EPNODE  *ep;

    ep = newnode();
    ep->type = NUM;
    errno = 0;
    ep->v.num = evalue(epar);
    if (errno)
	syntax("bad constant expression");
    epfree(epar);
 
    return(ep);
}


isconstvar(ep)			/* is ep linked to a constant expression? */
register EPNODE	 *ep;
{
#ifdef	VARIABLE
    register EPNODE  *ep1;
#ifdef	FUNCTION

    if (ep->type == FUNC) {
	if (!isconstfun(ep->v.kid))
		return(0);
	for (ep1 = ep->v.kid->sibling; ep1 != NULL; ep1 = ep1->sibling)
	    if (ep1->type != NUM && !isconstfun(ep1))
		return(0);
	return(1);
    }
#endif
    if (ep->type != VAR)
	return(0);
    ep1 = ep->v.ln->def;
    if (ep1 == NULL || ep1->type != ':')
	return(0);
#ifdef	FUNCTION
    if (ep1->v.kid->type != SYM)
	return(0);
#endif
    return(1);
#else
    return(ep->type == FUNC);
#endif
}


#if  defined(FUNCTION) && defined(VARIABLE)
isconstfun(ep)			/* is ep linked to a constant function? */
register EPNODE	 *ep;
{
    register EPNODE  *dp;
    register LIBR  *lp;

    if (ep->type != VAR)
	return(0);
    if ((dp = ep->v.ln->def) != NULL && dp->v.kid->type == FUNC)
	return(dp->type == ':');
    if ((lp = ep->v.ln->lib) != NULL)
	return(lp->atyp == ':');
    return(0);
}
#endif
#endif
