/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1987 Wayne A. Christopher, U. C. Berkeley CAD Group
**********/
//#define TRACE

#include "ngspice/ngspice.h"
#include "ngspice/compatmode.h"
#include "ngspice/ifsim.h"
#include "ngspice/iferrmsg.h"
#include "ngspice/inpdefs.h"
#include "ngspice/inpptree.h"
#include "inpxx.h"

#include "inpptree-parser.h"
#include "inpptree-parser-y.h"

extern bool ft_stricterror;

#ifdef OLD_BISON
int PTparse(char **line, INPparseNode **p, CKTcircuit *ckt);
#endif

static INPparseNode *mkcon(double value);
static INPparseNode *mkb(int type, INPparseNode * left,
                         INPparseNode * right);
static INPparseNode *mkf(int type, INPparseNode * arg);
static int PTcheck(INPparseNode * p, char* tline);
static INPparseNode *mkvnode(char *name);
static INPparseNode *mkinode(char *name);

static INPparseNode *PTdifferentiate(INPparseNode * p, int varnum);

static void free_tree(INPparseNode *);
static void printTree(INPparseNode *);


/*
 * LAW for INPparseNode* generator and consumer functions:
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *   Newly allocated structs shall be initialized with `usecnt' = 0
 *   When filling INPparseNode * slots of newly initialized structs
 *     their `usecnt' shall be incremented
 *   Generators pass the responsibility `to free' return values
 *     on to their invokers.
 *   Functions generally process args with exactly one of:
 *     - inc_usage(arg) if they insert an argument into a struct
 *     - release_tree(arg) if they don't make any use of it
 *     - pass it on to another function()
 *   Functions use the the result of a function invocations with one of:
 *     - inc_usage(result) if they insert the result into a struct
 *     - release_tree(result) if they don't make any use of it
 *     - pass it on to another function()
 *     - simply return the result
 *
 * mkfirst(first, second)
 *   is used to safely release its second argument,
 *   and return its first
 *
 */


static inline INPparseNode *
inc_usage(INPparseNode *p)
{
    if(p)
        p->usecnt ++;
    return p;
}


static void
dec_usage(INPparseNode *p)
{
    if(p && --p->usecnt <= 0)
        free_tree(p);
}


static void
release_tree(INPparseNode *p)
{
    if(p && p->usecnt <= 0)
        free_tree(p);
}


static INPparseNode *
mkfirst(INPparseNode *fst, INPparseNode *snd)
{
    if(fst) {
        fst->usecnt ++;
        release_tree(snd);
        fst->usecnt --;
    } else {
        release_tree(snd);
    }

    return fst;
}


static IFvalue *values = NULL;
static int *types;
static int numvalues;
static CKTcircuit *circuit;
static INPtables *tables;

extern IFsimulator *ft_sim;        /* XXX */

/* Some tables that the parser uses. */

static struct op {
    int number;
    char *name;
    void (*funcptr)(void);
} ops[] = {
    {
    PT_COMMA,  ",", NULL}, {
    PT_PLUS,   "+", (void(*)(void)) PTplus}, {
    PT_MINUS,  "-", (void(*)(void)) PTminus}, {
    PT_TIMES,  "*", (void(*)(void)) PTtimes}, {
    PT_DIVIDE, "/", (void(*)(void)) PTdivide}, {
    PT_POWER,  "^", (void(*)(void)) PTpower}
};

#define NUM_OPS (int)NUMELEMS(ops)

static struct func {
    char *name;
    int number;
    void (*funcptr)(void);
} funcs[] = {
    { "abs",    PTF_ABS,    (void(*)(void)) PTabs } ,
    { "acos",   PTF_ACOS,   (void(*)(void)) PTacos } ,
    { "acosh",  PTF_ACOSH,  (void(*)(void)) PTacosh } ,
    { "asin",   PTF_ASIN,   (void(*)(void)) PTasin } ,
    { "asinh",  PTF_ASINH,  (void(*)(void)) PTasinh } ,
    { "atan",   PTF_ATAN,   (void(*)(void)) PTatan } ,
    { "atanh",  PTF_ATANH,  (void(*)(void)) PTatanh } ,
    { "cos",    PTF_COS,    (void(*)(void)) PTcos } ,
    { "cosh",   PTF_COSH,   (void(*)(void)) PTcosh } ,
    { "exp",    PTF_EXP,    (void(*)(void)) PTexp } ,
    { "ln",     PTF_LOG,    (void(*)(void)) PTlog } ,
    { "log",    PTF_LOG,    (void(*)(void)) PTlog } ,
    { "log10",  PTF_LOG10,  (void(*)(void)) PTlog10 } ,
    { "sgn",    PTF_SGN,    (void(*)(void)) PTsgn } ,
    { "sin",    PTF_SIN,    (void(*)(void)) PTsin } ,
    { "sinh",   PTF_SINH,   (void(*)(void)) PTsinh } ,
    { "sqrt",   PTF_SQRT,   (void(*)(void)) PTsqrt } ,
    { "tan",    PTF_TAN,    (void(*)(void)) PTtan } ,
    { "tanh",   PTF_TANH,   (void(*)(void)) PTtanh } ,
    { "u",      PTF_USTEP,  (void(*)(void)) PTustep } ,
    { "uramp",  PTF_URAMP,  (void(*)(void)) PTuramp } ,
    { "ceil",   PTF_CEIL,   (void(*)(void)) PTceil } ,
    { "floor",  PTF_FLOOR,  (void(*)(void)) PTfloor } ,
    { "nint",   PTF_NINT,   (void(*)(void)) PTnint } ,
    { "-",      PTF_UMINUS, (void(*)(void)) PTuminus },
    /* MW. cif function added */
    { "u2",     PTF_USTEP2, (void(*)(void)) PTustep2},
    { "pwl",    PTF_PWL,    (void(*)(void)) PTpwl},
    { "pwl_derivative", PTF_PWL_DERIVATIVE, (void(*)(void)) PTpwl_derivative},
    { "eq0",    PTF_EQ0,    (void(*)(void)) PTeq0},
    { "ne0",    PTF_NE0,    (void(*)(void)) PTne0},
    { "gt0",    PTF_GT0,    (void(*)(void)) PTgt0},
    { "lt0",    PTF_LT0,    (void(*)(void)) PTlt0},
    { "ge0",    PTF_GE0,    (void(*)(void)) PTge0},
    { "le0",    PTF_LE0,    (void(*)(void)) PTle0},
    { "pow",    PTF_POW,    (void(*)(void)) PTpower},
    { "pwr",    PTF_PWR,    (void(*)(void)) PTpwr},
    { "min",    PTF_MIN,    (void(*)(void)) PTmin},
    { "max",    PTF_MAX,    (void(*)(void)) PTmax},
} ;

#define NUM_FUNCS (int)NUMELEMS(funcs)

/* These are all the constants any sane person needs. */

static struct constant {
    char *name;
    double value;
} constants[] = {
    {
    "e", M_E}, {
    "pi", M_PI}
};

#define NUM_CONSTANTS (int)NUMELEMS(constants)

/* Parse the expression in *line as far as possible, and return the parse
 * tree obtained.  If there is an error, *pt will be set to NULL and an error
 * message will be printed.
 */

void
INPgetTree(char **line, INPparseTree ** pt, CKTcircuit *ckt, INPtables * tab)
{
    INPparseNode *p = NULL;
    int i, rv;
    char* treeline = *line;

    values = NULL;
    types = NULL;
    numvalues = 0;

    circuit = ckt;
    tables = tab;

#ifdef TRACE
    fprintf(stderr,"%s, line = \"%s\"\n", __func__, *line);
#endif

    rv = PTparse(line, &p, ckt);

    if (rv || !p || !PTcheck(p, treeline)) {

        *pt = NULL;
        release_tree(p);

    } else {

        (*pt) = TMALLOC(INPparseTree, 1);

        (*pt)->p.numVars = numvalues;
        (*pt)->p.varTypes = types;
        (*pt)->p.vars = values;
        (*pt)->p.IFeval = IFeval;
        (*pt)->tree = inc_usage(p);

        (*pt)->derivs = TMALLOC(INPparseNode *, numvalues);

        for (i = 0; i < numvalues; i++)
            (*pt)->derivs[i] = inc_usage(PTdifferentiate(p, i));

    }

    values = NULL;
    types = NULL;
    numvalues = 0;

    circuit = NULL;
    tables = NULL;
}

/* This routine takes the partial derivative of the parse tree with respect to
 * the i'th variable.  We try to do optimizations like getting rid of 0-valued
 * terms.
 *
 *** Note that in the interests of simplicity we share some subtrees between
 *** the function and its derivatives.  This means that you can't free the
 *** trees.
 */

static INPparseNode *PTdifferentiate(INPparseNode * p, int varnum)
{
    INPparseNode *arg1 = NULL, *arg2 = NULL, *newp = NULL;

    switch (p->type) {
    case PT_TIME:
    case PT_TEMPERATURE:
    case PT_FREQUENCY:
    case PT_CONSTANT:
        newp = mkcon(0.0);
        break;

    case PT_VAR:
        /* Is this the variable we're differentiating wrt? */
        if (p->valueIndex == varnum)
            newp = mkcon(1.0);
        else
            newp = mkcon(0.0);
        break;

    case PT_PLUS:
    case PT_MINUS:
        arg1 = PTdifferentiate(p->left, varnum);
        arg2 = PTdifferentiate(p->right, varnum);
        newp = mkb(p->type, arg1, arg2);
        break;

    case PT_TIMES:
        /* d(a * b) = d(a) * b + d(b) * a */
        arg1 = PTdifferentiate(p->left, varnum);
        arg2 = PTdifferentiate(p->right, varnum);

        newp = mkb(PT_PLUS, mkb(PT_TIMES, arg1, p->right),
                   mkb(PT_TIMES, p->left, arg2));
        break;

    case PT_DIVIDE:
        /* d(a / b) = (d(a) * b - d(b) * a) / b^2 */
        arg1 = PTdifferentiate(p->left, varnum);
        arg2 = PTdifferentiate(p->right, varnum);

        newp = mkb(PT_DIVIDE, mkb(PT_MINUS, mkb(PT_TIMES, arg1,
                                                p->right), mkb(PT_TIMES,
                                                               p->left,
                                                               arg2)),
                   mkb(PT_POWER, p->right, mkcon(2.0)));
        break;

    case PT_POWER:
        /*
         * ^ : a^b -> |a| math^ b
         *
         * D(pow(a,b))
         *   = D(exp(b*log(abs(a))))
         *   = exp(b*log(abs(a))) * D(b*log(abs(a)))
         *   = pow(a,b) * (D(b)*log(abs(a)) + b*D(abs(a))/abs(a))
         *   = pow(a,b) * (D(b)*log(abs(a)) + b*sgn(a)*D(a)/abs(a))
         *   = pow(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
         *
         * when D(b) == 0, then
         *
         * D(pow(a,b))
         *    = pow(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
         *    = pow(a,b) * b * D(a)/a
         *    = pow(a,b) * b * D(a)/(signum(a) * abs(a))
         *    = pow(a, b-1) * b * D(a) / signum(a)
         *    = pwr(a, b-1) * b * D(a)
         *
         * when D(a) == 0, then
         *
         * D(pow(a,b))
         *    = pow(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
         *    = pow(a,b) * D(b)*log(abs(a))
         */
#define a  p->left
#define b  p->right
        if (b->type == PT_CONSTANT) {
            arg1 = PTdifferentiate(a, varnum);
            newp = mkb(PT_TIMES,
                       mkb(PT_TIMES,
                           mkcon(b->constant),
                           mkf(PTF_PWR,
                               mkb(PT_COMMA, a, mkcon(b->constant - 1.0)))),
                       arg1);
        }
        else if (a->type == PT_CONSTANT){
            arg2 = PTdifferentiate(b, varnum);
            newp = mkb(PT_TIMES,
                       mkf(PTF_POW, mkb(PT_COMMA, a, b)),
                           mkb(PT_TIMES, arg2, mkf(PTF_LOG, mkf(PTF_ABS, a))));
        }
        else {
            arg1 = PTdifferentiate(a, varnum);
            arg2 = PTdifferentiate(b, varnum);
            newp = mkb(PT_TIMES,
                       mkf(PTF_POW, mkb(PT_COMMA, a, b)),
                       mkb(PT_PLUS,
                           mkb(PT_TIMES, b,
                               mkb(PT_DIVIDE, arg1, a)),
                           mkb(PT_TIMES, arg2, mkf(PTF_LOG, mkf(PTF_ABS, a)))));
        }
#undef b
#undef a
        break;

    case PT_TERN: /* ternary_fcn(cond,exp1,exp2) */
      // naive:
      //   d/d ternary_fcn(cond,exp1,exp2) --> ternary_fcn(cond, d/d exp1, d/d exp2)
      {
//        extern void printTree(INPparseNode *);
//
//        printf("debug: %s, PT_TERN: ", __func__);
//        printTree(p);
//        printf("\n");

        newp = mkb(PT_TERN, p->left, mkb(PT_COMMA,
                                         PTdifferentiate(p->right->left, varnum),
                                         PTdifferentiate(p->right->right, varnum)));

//        printf("debug, %s, returns; ", __func__);
//        printTree(newp);
//        printf("\n");

        return mkfirst(newp, p);
      }

    case PT_FUNCTION:
        /* Many cases.  Set arg1 to the derivative of the function,
         * and arg2 to the derivative of the argument.
         */
        switch (p->funcnum) {
        case PTF_ABS:                /* sgn(u) */
            arg1 = mkf(PTF_SGN, p->left);
            break;

        case PTF_SGN:
            arg1 = mkcon(0.0);
            break;

        case PTF_ACOS:                /* - 1 / sqrt(1 - u^2) */
            arg1 = mkb(PT_DIVIDE, mkcon(-1.0), mkf(PTF_SQRT,
                                                          mkb(PT_MINUS,
                                                              mkcon(1.0),
                                                              mkb(PT_POWER,
                                                                  p->left,
                                                                  mkcon(2.0)))));
            break;

        case PTF_ACOSH:        /* 1 / sqrt(u^2 - 1) */
            arg1 = mkb(PT_DIVIDE, mkcon(1.0), mkf(PTF_SQRT,
                                                         mkb(PT_MINUS,
                                                             mkb(PT_POWER,
                                                                 p->left,
                                                                 mkcon(2.0)),
                                                             mkcon(1.0))));

            break;

        case PTF_ASIN:                /* 1 / sqrt(1 - u^2) */
            arg1 = mkb(PT_DIVIDE, mkcon(1.0), mkf(PTF_SQRT,
                                                         mkb(PT_MINUS,
                                                             mkcon(1.0),
                                                             mkb(PT_POWER,
                                                                 p->left,
                                                                 mkcon(2.0)))));
            break;

        case PTF_ASINH:        /* 1 / sqrt(u^2 + 1) */
            arg1 = mkb(PT_DIVIDE, mkcon(1.0), mkf(PTF_SQRT,
                                                         mkb(PT_PLUS,
                                                             mkb(PT_POWER,
                                                                 p->left,
                                                                 mkcon(2.0)),
                                                             mkcon(1.0))));
            break;

        case PTF_ATAN:                /* 1 / (1 + u^2) */
            arg1 = mkb(PT_DIVIDE, mkcon(1.0), mkb(PT_PLUS,
                                                         mkb(PT_POWER,
                                                             p->left,
                                                             mkcon(2.0)),
                                                         mkcon(1.0)));
            break;

        case PTF_ATANH:        /* 1 / (1 - u^2) */
            arg1 = mkb(PT_DIVIDE, mkcon(1.0), mkb(PT_MINUS,
                                                         mkcon(1.0),
                                                         mkb(PT_POWER,
                                                             p->left,
                                                             mkcon(2.0))));
            break;

        case PTF_COS:                /* - sin(u) */
            arg1 = mkf(PTF_UMINUS, mkf(PTF_SIN, p->left));
            break;

        case PTF_COSH:                /* sinh(u) */
            arg1 = mkf(PTF_SINH, p->left);
            break;

        case PTF_EXP:                /* u > EXPARGMAX -> EXPMAX, that is exp(EXPARGMAX), else exp(u) */
            if (newcompat.ps) {
                arg1 = mkb(PT_TERN,
                    mkf(PTF_GT0, mkb(PT_MINUS, p->left, mkcon(EXPARGMAX))),
                    mkb(PT_COMMA,
                        mkcon(EXPMAX),
                        mkf(PTF_EXP, p->left)));
            }
            else {                   /* exp(u) */
                arg1 = mkf(PTF_EXP, p->left);
            }

#ifdef TRACE1
            printf("debug exp, %s, returns; ", __func__);
            printTree(arg1);
            printf("\n");
#endif
            break;

        case PTF_LOG:               /* 1 / u */
            arg1 = mkb(PT_DIVIDE, mkcon(1.0), p->left);
            break;

        case PTF_LOG10:              /* log(e) / u */
            arg1 = mkb(PT_DIVIDE, mkcon(M_LOG10E), p->left);
            break;

        case PTF_SIN:                /* cos(u) */
            arg1 = mkf(PTF_COS, p->left);
            break;

        case PTF_SINH:                /* cosh(u) */
            arg1 = mkf(PTF_COSH, p->left);
            break;

        case PTF_SQRT:                /* 1 / (2 * sqrt(u)) */
            arg1 = mkb(PT_DIVIDE, mkcon(1.0), mkb(PT_TIMES,
                                                         mkcon(2.0),
                                                         mkf(PTF_SQRT,
                                                             p->left)));
            break;

        case PTF_TAN:                /* 1 + (tan(u) ^ 2) */
            arg1 = mkb(PT_PLUS, mkcon(1.0), mkb(PT_POWER,
                                                         mkf(PTF_TAN,
                                                             p->left),
                                                         mkcon(2.0)));
            break;

        case PTF_TANH:                /* 1 - (tanh(u) ^ 2) */
            arg1 = mkb(PT_MINUS, mkcon(1.0), mkb(PT_POWER,
                                                         mkf(PTF_TANH,
                                                             p->left),
                                                         mkcon(2.0)));
            break;

        case PTF_USTEP:
        case PTF_EQ0:
        case PTF_NE0:
        case PTF_GT0:
        case PTF_LT0:
        case PTF_GE0:
        case PTF_LE0:
            arg1 = mkcon(0.0);
            break;

        case PTF_URAMP:
            arg1 = mkf(PTF_USTEP, p->left);
            break;

        case PTF_FLOOR:                /* naive: D(floor(u)) = 0 */
            arg1 = mkcon(0.0);
            break;

        case PTF_CEIL:                /* naive: D(ceil(u)) = 0 */
            arg1 = mkcon(0.0);
            break;

        case PTF_NINT:                /* naive: D(nint(u)) = 0 */
            arg1 = mkcon(0.0);
            break;


            /* MW. PTF_CIF for new cif function */
        case PTF_USTEP2: /* ustep2=uramp(x)-uramp(x-1) ustep2'=ustep(x)-ustep(x-1) */
            arg1 = mkb(PT_MINUS,
                       mkf(PTF_USTEP, p->left),
                       mkf(PTF_USTEP,
                           mkb(PT_MINUS,
                               p->left,
                               mkcon(1.0))));
            break;

        case PTF_UMINUS:    /* - 1 ; like a constant (was 0 !) */
            arg1 = mkcon(-1.0);
            break;

        case PTF_PWL: /* PWL(var, x1, y1, x2, y2, ... a const list) */
            arg1 = mkf(PTF_PWL_DERIVATIVE, p->left);
            arg1->data = p->data;
            break;

        case PTF_PWL_DERIVATIVE: /* d/dvar PWL(var, ...) */
            arg1 = mkcon(0.0);
            break;

        case PTF_MIN:
        case PTF_MAX:
        /* min(a,b) -->   (a<b)       ? a : b
        *           -->   ((a-b) < 0) ? a : b
        */
        {
            INPparseNode *a = p->left->left;
            INPparseNode *b = p->left->right;
            int comparison = (p->funcnum == PTF_MIN) ? PTF_LT0 : PTF_GT0;
#ifdef TRACE1
            printf("debug: %s, PTF_MIN: ", __func__);
            printTree(p);
            printf("\n");
            printf("debug: %s, PTF_MIN, a: ", __func__);
            printTree(a);
            printf("\n");
            printf("debug: %s, PTF_MIN, b: ", __func__);
            printTree(b);
            printf("\n");
#endif
            newp = mkb(PT_TERN,
                       mkf(comparison, mkb(PT_MINUS, a, b)),
                       mkb(PT_COMMA,
                           PTdifferentiate(a, varnum),
                           PTdifferentiate(b, varnum)));
#ifdef TRACE1
            printf("debug, %s, returns; ", __func__);
            printTree(newp);
            printf("\n");
#endif
            return mkfirst(newp, p);
        }

        break;

        case PTF_POW:
            /*
             * pow : pow(a,b) -> |a| math^ b
             *
             * D(pow(a,b))
             *   = D(exp(b*log(abs(a))))
             *   = exp(b*log(abs(a))) * D(b*log(abs(a)))
             *   = pow(a,b) * (D(b)*log(abs(a)) + b*D(abs(a))/abs(a))
             *   = pow(a,b) * (D(b)*log(abs(a)) + b*sgn(a)*D(a)/abs(a))
             *   = pow(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
             *
             * when D(b) == 0, then
             *
             * D(pow(a,b))
             *    = pow(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
             *    = pow(a,b) * b * D(a)/a
             *    = pow(a,b) * b * D(a)/(signum(a) * abs(a))
             *    = pow(a, b-1) * b * D(a) / signum(a)
             *    = pwr(a, b-1) * b * D(a)
             *
             * when D(a) == 0, then
             *
             * D(pow(a,b))
             *    = pow(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
             *    = pow(a,b) * D(b)*log(abs(a))
             */
        {
        /*
        pow(a,b)
        p->left: ','    p->left->left: a       p->left->right: b
        */
#define a  p->left->left
#define b  p->left->right

            if (b->type == PT_CONSTANT) {
                arg1 = PTdifferentiate(a, varnum);
                newp = mkb(PT_TIMES,
                           mkb(PT_TIMES,
                               mkcon(b->constant),
                               mkf(PTF_PWR,
                                   mkb(PT_COMMA, a, mkcon(b->constant - 1)))),
                           arg1);
            } else if (a->type == PT_CONSTANT) {
                arg2 = PTdifferentiate(b, varnum);
                newp = mkb(PT_TIMES,
                    mkf(PTF_POW, mkb(PT_COMMA, a, b)),
                    mkb(PT_TIMES, arg2, mkf(PTF_LOG, mkf(PTF_ABS, a))));

            } else {
                arg1 = PTdifferentiate(a, varnum);
                arg2 = PTdifferentiate(b, varnum);
                newp = mkb(PT_TIMES,
                           mkf(PTF_POW, mkb(PT_COMMA, a, b)),
                           mkb(PT_PLUS,
                               mkb(PT_TIMES,
                                   b,
                                   mkb(PT_DIVIDE, arg1, a)),
                               mkb(PT_TIMES,
                                   arg2,
                                   mkf(PTF_LOG, mkf(PTF_ABS, a)))));
            }
#ifdef TRACE
            printf("debug pow, %s, returns; ", __func__);
            printTree(newp);
            printf("\n");
#endif
            return mkfirst(newp, p);
#undef b
#undef a
        }

        break;

        case PTF_PWR:
            /*
             * pwr : pwr(a,b) -> signum(a) * (|a| math^ b)
             *                -> signum(a) * pow(a, b)
             *
             * Note:
             *   D(pow(a,b)) = pow(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
             *
             * D(pwr(a,b))
             *   = D(signum(a) * pow(a,b))
             *   = D(signum(a)) * pow(a,b) + signum(a) * D(pow(a,b))
             *   = 0 + signum(a) * pow(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
             *   = pwr(a,b) * (D(b)*log(abs(a)) + b*D(a)/a)
             *
             * with D(b) == 0
             *
             * D(pwr(a,b))
             *   = pwr(a,b) * b * D(a)/a
             *   = signum(a) * pow(a,b) * b * D(a)/(signum(a) * abs(a))
             *   = pow(a, b-1) * b * D(a)
             */
        {
        /*
        pwr(a,b)
        p->left: ','    p->left->left: a       p->left->right: b
        */
#define a  p->left->left
#define b  p->left->right
            if (b->type == PT_CONSTANT) {
                arg1 = PTdifferentiate(a, varnum);

                newp = mkb(PT_TIMES,
                           mkb(PT_TIMES,
                               mkcon(b->constant),
                               mkf(PTF_POW,
                                   mkb(PT_COMMA, a, mkcon(b->constant - 1.0)))),
                           arg1);

            } else {
                arg1 = PTdifferentiate(a, varnum);
                arg2 = PTdifferentiate(b, varnum);
                newp = mkb(PT_TIMES,
                           mkf(PTF_PWR, mkb(PT_COMMA, a, b)),
                           mkb(PT_PLUS,
                               mkb(PT_TIMES, b, mkb(PT_DIVIDE, arg1, a)),
                               mkb(PT_TIMES, arg2, mkf(PTF_LOG, mkf(PTF_ABS, a)))));
            }
#ifdef TRACE
                printf("debug pwr, %s, returns; ", __func__);
                printTree(newp);
                printf("\n");
#endif
            return mkfirst(newp, p);
#undef b
#undef a
        }

        default:
            fprintf(stderr, "Internal Error: bad function # %d\n",
                    p->funcnum);
            return mkfirst(NULL, p);
        }

        arg2 = PTdifferentiate(p->left, varnum);

        newp = mkb(PT_TIMES, arg1, arg2);

        break;

    default:
        fprintf(stderr, "Internal error: bad node type %d\n", p->type);
        newp = NULL;
        break;
    }

    return mkfirst(newp, p);
}

static INPparseNode *mkcon(double value)
{
    INPparseNode *p = TMALLOC(INPparseNode, 1);

    p->type = PT_CONSTANT;
    p->constant = value;
    p->usecnt = 0;

    return (p);
}

static INPparseNode *mkb(int type, INPparseNode * left,
                         INPparseNode * right)
{
    INPparseNode *p;
    int i;

    if ((right->type == PT_CONSTANT) && (left->type == PT_CONSTANT)) {
        double value;
        switch (type) {
        case PT_TIMES:
            value = left->constant * right->constant;
            return mkfirst(mkcon(value), mkfirst(left, right));

        case PT_DIVIDE:
            value = left->constant / right->constant;
            return mkfirst(mkcon(value), mkfirst(left, right));

        case PT_PLUS:
            value = left->constant + right->constant;
            return mkfirst(mkcon(value), mkfirst(left, right));

        case PT_MINUS:
            value = left->constant - right->constant;
            return mkfirst(mkcon(value), mkfirst(left, right));

        case PT_POWER:
            value = pow(left->constant, right->constant);
            return mkfirst(mkcon(value), mkfirst(left, right));
        }
    }
    switch (type) {
    case PT_TIMES:
        if ((left->type == PT_CONSTANT) && (left->constant == 0))
            return mkfirst(left, right);
        else if ((right->type == PT_CONSTANT) && (right->constant == 0))
            return mkfirst(right, left);
        else if ((left->type == PT_CONSTANT) && (left->constant == 1))
            return mkfirst(right, left);
        else if ((right->type == PT_CONSTANT) && (right->constant == 1))
            return mkfirst(left, right);
        break;

    case PT_DIVIDE:
        if ((left->type == PT_CONSTANT) && (left->constant == 0))
            return mkfirst(left, right);
        else if ((right->type == PT_CONSTANT) && (right->constant == 1))
            return mkfirst(left, right);
        break;

    case PT_PLUS:
        if ((left->type == PT_CONSTANT) && (left->constant == 0))
            return mkfirst(right, left);
        else if ((right->type == PT_CONSTANT) && (right->constant == 0))
            return mkfirst(left, right);
        break;

    case PT_MINUS:
        if ((right->type == PT_CONSTANT) && (right->constant == 0))
            return mkfirst(left, right);
        else if ((left->type == PT_CONSTANT) && (left->constant == 0))
            return mkfirst(mkf(PTF_UMINUS, right), left);
        break;

    case PT_POWER:
        if (right->type == PT_CONSTANT) {
            if (right->constant == 0)
                return mkfirst(mkcon(1.0), mkfirst(left, right));
            else if (right->constant == 1)
                return mkfirst(left, right);
        }
        break;

    case PT_TERN:
        if (left->type == PT_CONSTANT) {
            /*FIXME > 0.0, >= 0.5, != 0.0 or what ? */
            p = (left->constant != 0.0) ? right->left : right->right;
            return mkfirst(p, mkfirst(right, left));
        }
        if((right->left->type == PT_CONSTANT) &&
           (right->right->type == PT_CONSTANT) &&
           (right->left->constant == right->right->constant))
            return mkfirst(right->left, mkfirst(right, left));
        break;
     }

     p = TMALLOC(INPparseNode, 1);

     p->type = type;
     p->usecnt = 0;

     p->left = inc_usage(left);
     p->right = inc_usage(right);

    if(type == PT_TERN) {
        p->function = NULL;
        p->funcname = NULL;
        return (p);
    }


    for (i = 0; i < NUM_OPS; i++)
        if (ops[i].number == type)
            break;
    if (i == NUM_OPS) {
        fprintf(stderr, "Internal Error: bad type %d\n", type);
        return (NULL);
    }
    p->function = ops[i].funcptr;
    p->funcname = ops[i].name;

    return (p);
}

static INPparseNode *mkf(int type, INPparseNode * arg)
{
    INPparseNode *p;
    int i;

    for (i = 0; i < NUM_FUNCS; i++)
        if (funcs[i].number == type)
            break;
    if (i == NUM_FUNCS) {
        fprintf(stderr, "Internal Error: bad type %d\n", type);
        return (NULL);
    }

    if (arg->type == PT_CONSTANT) {
        double constval = PTunary(funcs[i].funcptr) (arg->constant);
        return mkfirst(mkcon(constval), arg);
    }

    p = TMALLOC(INPparseNode, 1);

    p->type = PT_FUNCTION;
    p->usecnt = 0;

    p->left = inc_usage(arg);

    p->funcnum = funcs[i].number;
    p->function = funcs[i].funcptr;
    p->funcname = funcs[i].name;

    p->data = NULL;

    return (p);
}

/* Check for remaining PT_PLACEHOLDERs in the parse tree.  Returns 1 if ok. 
   Returns 0 and error message containing expression to parsed, if not ok. */

static int PTcheck(INPparseNode * p, char *tline)
{
    int ret;
    static bool msgsent = FALSE;
    switch (p->type) {
    case PT_PLACEHOLDER:
        return (0);

    case PT_TIME:
    case PT_TEMPERATURE:
    case PT_FREQUENCY:
    case PT_CONSTANT:
    case PT_VAR:
        return (1);

    case PT_FUNCTION:
        ret = (PTcheck(p->left, tline));
        if (ret == 0 && !msgsent) {
            fprintf(stderr, "\nError: The internal check of parse tree \n%s\nfailed\n", tline);
            msgsent = TRUE;
        }
        return ret;

    case PT_PLUS:
    case PT_MINUS:
    case PT_TIMES:
    case PT_DIVIDE:
    case PT_POWER:
    case PT_COMMA:
        ret = (PTcheck(p->left, tline) && PTcheck(p->right, tline));
        if (ret == 0 && !msgsent) {
            fprintf(stderr, "\nError: The internal check of parse tree \n%s\nfailed\n", tline);
            msgsent = TRUE;
        }
        return ret;
    case PT_TERN:
        ret = (PTcheck(p->left, tline) && PTcheck(p->right->left, tline) && PTcheck(p->right->right, tline));
        if (ret == 0 && !msgsent) {
            fprintf(stderr, "\nError: The internal check of parse tree \n%s\nfailed\n", tline);
            msgsent = TRUE;
        }
        return ret;

    default:
        fprintf(stderr, "Internal error: bad node type %d\n", p->type);
        return (0);
    }
}

/* Binop node. */

INPparseNode *PT_mkbnode(const char *opstr, INPparseNode * arg1,
                             INPparseNode * arg2)
{
    INPparseNode *p;
    int i;

    for (i = 0; i < NUM_OPS; i++)
        if (!strcmp(ops[i].name, opstr))
            break;

    if (i == NUM_OPS) {
        fprintf(stderr, "Internal Error: no such op num %s\n", opstr);
        return mkfirst(NULL, mkfirst(arg1, arg2));
    }

    p = TMALLOC(INPparseNode, 1);

    p->type = ops[i].number;
    p->usecnt = 0;

    p->funcname = ops[i].name;
    p->function = ops[i].funcptr;
    p->left = inc_usage(arg1);
    p->right = inc_usage(arg2);

    return (p);
}

/*
 * prepare_PTF_PWL()
 *   for the PWL(expr, points...) function
 *     collect the given points, which are expected to be given
 *       literal constant
 *   strip them from the INPparseNode
 *     and pass them as an opaque struct alongside the
 *     INPparseNode for the PWL(expr) function call
 *
 * Note:
 *  the functionINPgetTree() is missing a recursive decending simplifier
 *  as a consequence we can meet a PTF_UMINUS->PTF_CONSTANT
 *    instead of a plain PTF_CONSTANT here
 *  of course this can get arbitrarily more complex
 *    for example PTF_TIMES -> PTF_CONSTANT, PTF_CONSTANT
 *      etc.
 *  currently we support only PFT_CONST and PTF_UMINUS->PTF_CONST
 */

#define Breakpoint do { __asm__ __volatile__ ("int $03"); } while(0)

static INPparseNode *prepare_PTF_PWL(INPparseNode *p)
{
    INPparseNode *w;
    struct pwldata { int n; double *vals; } *data;
    int i;

    if (p->funcnum != PTF_PWL) {
        fprintf(stderr, "PWL-INFO: %s, very unexpected\n", __func__);
        controlled_exit(1);
    }

#ifdef TRACE
    fprintf(stderr, "PWL-INFO: %s  building a PTF_PWL\n", __func__);
#endif
    i = 0;
    for(w = p->left; w->type == PT_COMMA; w = w->left)
        i++;

    if (i<2 || (i%1)) {
        fprintf(stderr, "Error: PWL(expr, points...) needs an even and >=2 number of constant args\n");
        return mkfirst(NULL, p);
    }

    data = TMALLOC(struct pwldata, 1);
    data->vals = TMALLOC(double, i);

    data->n = i;

    p->data = (void *) data;

    for (w = p->left ; --i >= 0 ; w = w->left)
        if (w->right->type == PT_CONSTANT) {
            data->vals[i] = w->right->constant;
        } else if (w->right->type == PT_FUNCTION &&
                   w->right->funcnum == PTF_UMINUS &&
                   w->right->left->type == PT_CONSTANT) {
            data->vals[i] = - w->right->left->constant;
        } else {
            fprintf(stderr, "PWL-ERROR: %s, not a constant\n", __func__);
            fprintf(stderr, "   type = %d\n", w->right->type);
            //Breakpoint;
            fprintf(stderr, "Error: PWL(expr, points...) only *literal* points are supported\n");
            return mkfirst(NULL, p);
        }

#ifdef TRACE
    for (i = 0 ; i < data->n ; i += 2)
        fprintf(stderr, "  (%lf %lf)\n", data->vals[i], data->vals[i+1]);
#endif

    for (i = 2 ; i < data->n ; i += 2)
        if(data->vals[i-2] >= data->vals[i]) {
            fprintf(stderr, "Error: PWL(expr, points...) the abscissa of points must be ascending\n");
            return mkfirst(NULL, p);
        }

    /* strip all but the first arg,
     *   and attach the rest as opaque data to the INPparseNode
     */

    w = inc_usage(w);
    dec_usage(p->left);
    p->left = w;

    return (p);
}


INPparseNode *PT_mkfnode(const char *fname, INPparseNode * arg)
{
    int i;
    INPparseNode *p;
    char buf[128];

    /* Make sure the case is ok. */
    (void) strcpy(buf, fname);
    strtolower(buf);

    if(!strcmp("ternary_fcn", buf)) {

        if(arg->type == PT_COMMA && arg->left->type == PT_COMMA) {

            INPparseNode *arg1 = arg->left->left;
            INPparseNode *arg2 = arg->left->right;
            INPparseNode *arg3 = arg->right;

            p = TMALLOC(INPparseNode, 1);

            p->type = PT_TERN;
            p->usecnt = 0;

            p->left = inc_usage(arg1);
            p->right = inc_usage(mkb(PT_COMMA, arg2, arg3));

            return mkfirst(p, arg);
        }

        fprintf(stderr, "Error: bogus ternary_fcn form\n");
        return mkfirst(NULL, arg);
    }

    for (i = 0; i < NUM_FUNCS; i++)
        if (!strcmp(funcs[i].name, buf))
            break;

    if (i == NUM_FUNCS) {
        fprintf(stderr, "Error: no such function '%s'\n", buf);
        if (ft_stricterror)
            controlled_exit(EXIT_BAD);

        return mkfirst(NULL, arg);
    }

    p = TMALLOC(INPparseNode, 1);

    p->type = PT_FUNCTION;
    p->usecnt = 0;

    p->left = inc_usage(arg);
    p->funcname = funcs[i].name;
    p->funcnum = funcs[i].number;
    p->function = funcs[i].funcptr;
    p->data = NULL;

    if(p->funcnum == PTF_PWL)
        p = prepare_PTF_PWL(p);

    return (p);
}

static INPparseNode *mkvnode(char *name)
{
    INPparseNode *p = TMALLOC(INPparseNode, 1);

    int i;
    CKTnode *temp;

    INPtermInsert(circuit, &name, tables, &temp);
    for (i = 0; i < numvalues; i++)
        if ((types[i] == IF_NODE) && (values[i].nValue == temp))
            break;
    if (i == numvalues) {
        if (numvalues) {
            values = TREALLOC(IFvalue, values, numvalues + 1);
            types = TREALLOC(int, types, numvalues + 1);
        } else {
            values = TMALLOC(IFvalue, 1);
            types = TMALLOC(int, 1);
        }
        values[i].nValue = temp;
        types[i] = IF_NODE;
        numvalues++;
    }
    p->valueIndex = i;
    p->type = PT_VAR;
    p->usecnt = 0;

    return (p);
}

static INPparseNode *mkinode(char *name)
{
    INPparseNode *p = TMALLOC(INPparseNode, 1);

    int i;

    INPinsert(&name, tables);
    for (i = 0; i < numvalues; i++)
        if ((types[i] == IF_INSTANCE) && (values[i].uValue == name))
            break;
    if (i == numvalues) {
        if (numvalues) {
            values = TREALLOC(IFvalue, values, numvalues + 1);
            types = TREALLOC(int, types, numvalues + 1);
        } else {
            values = TMALLOC(IFvalue, 1);
            types = TMALLOC(int, 1);
        }
        values[i].uValue = name;
        types[i] = IF_INSTANCE;
        numvalues++;
    }
    p->valueIndex = i;
    p->type = PT_VAR;
    p->usecnt = 0;

    return (p);
}

/* Number node. */

INPparseNode *PT_mknnode(double number)
{
    struct INPparseNode *p;

    p = TMALLOC(INPparseNode, 1);

    p->type = PT_CONSTANT;
    p->usecnt = 0;

    p->constant = number;

    return (p);
}

/* String node. */

INPparseNode *PT_mksnode(const char *string, void *ckt)
{
    int i, j;
    char buf[128];
    INPparseNode *p;

    /* Make sure the case is ok. */
    (void) strcpy(buf, string);
    strtolower(buf);

    p = TMALLOC(INPparseNode, 1);

    p->usecnt = 0;

    if(!strcmp("time", buf)) {
        p->type = PT_TIME;
        p->data = ckt;
        return p;
    }

    if(!strcmp("temper", buf)) {
        p->type = PT_TEMPERATURE;
        p->data = ckt;
        return p;
    }

    if(!strcmp("hertz", buf)) {
        p->type = PT_FREQUENCY;
        p->data = ckt;
        return p;
    }

    /* First see if it's something special. */
    for (i = 0; i < ft_sim->numSpecSigs; i++)
        if (!strcmp(ft_sim->specSigs[i], buf))
            break;
    if (i < ft_sim->numSpecSigs) {
        for (j = 0; j < numvalues; j++)
            if ((types[j] == IF_STRING) && !strcmp(buf, values[i].sValue))
                break;
        if (j == numvalues) {
            if (numvalues) {
                values = TREALLOC(IFvalue, values, numvalues + 1);
                types = TREALLOC(int, types, numvalues + 1);
            } else {
                values = TMALLOC(IFvalue, 1);
                types = TMALLOC(int, 1);
            }
            values[i].sValue = TMALLOC(char, strlen(buf) + 1);
            strcpy(values[i].sValue, buf);
            types[i] = IF_STRING;
            numvalues++;
        }
        p->valueIndex = i;
        p->type = PT_VAR;
        return (p);
    }

    for (i = 0; i < NUM_CONSTANTS; i++)
        if (!strcmp(constants[i].name, buf))
            break;

    if (i == NUM_CONSTANTS) {
        /* We'd better save this in case it's part of i(something). */
        p->type = PT_PLACEHOLDER;
        p->funcname = copy(string);
    } else {
        p->type = PT_CONSTANT;
        p->constant = constants[i].value;
    }

    return (p);
}

/* The lexical analysis routine. */

int PTlex (YYSTYPE *lvalp, struct PTltype *llocp, char **line)
{
    double td;
    int err;
    static char *specials = " \t()^+-*/,";
    char *sbuf, *s;
    int token;

    sbuf = *line;


#ifdef TRACE
//    printf("entering lexer, sbuf = '%s', lastoken = %d, lasttype = %d\n",
//        sbuf, lasttoken, lasttype);
#endif
    while ((*sbuf == ' ') || (*sbuf == '\t'))
        sbuf++;

    llocp->start = sbuf;

    switch (*sbuf) {
    case '\0':
        token = 0;
        break;

    case '?':
    case ':':
    case ',':
    case '-':
    case '+':
    case '/':
    case '^':
    case '(':
    case ')':
        token = *sbuf++;
        break;

    case '*':
      if(sbuf[1] == '*') {
        sbuf += 2;
        token = '^';            /* `**' is exponentiation */
        break;
      } else {
        token = *sbuf++;
        break;
      }

    case '&':
      if(sbuf[1] == '&') {
        sbuf += 2;
        token = TOK_AND;
        break;
      } else {
        token = *sbuf++;
        break;
      }

    case '|':
      if(sbuf[1] == '|') {
        sbuf += 2;
        token = TOK_OR;
        break;
      } else {
        token = *sbuf++;
        break;
      }

    case '=':
      if(sbuf[1] == '=') {
        sbuf += 2;
        token = TOK_EQ;
        break;
      } else {
        token = *sbuf++;
        break;
      }

    case '!':
      if(sbuf[1] == '=') {
        sbuf += 2;
        token = TOK_NE;
        break;
      } else {
        token = *sbuf++;
        break;
      }

    case '>':
      if(sbuf[1] == '=') {
        sbuf += 2;
        token = TOK_GE;
        break;
      } else {
        sbuf += 1;
        token = TOK_GT;
        break;
      }

    case '<':
      if(sbuf[1] == '>') {
        sbuf += 2;
        token = TOK_NE;
        break;
      }
      else if(sbuf[1] == '=') {
        sbuf += 2;
        token = TOK_LE;
        break;
      } else {
        sbuf += 1;
        token = TOK_LT;
        break;
      }
    /* Don't parse the B source instance parameters, thus prevent memory leak.
       As soon as we meet such parameter, token=0 is returned. */
    case 't':
        if (ciprefix("tc1=", sbuf) || ciprefix("tc2=", sbuf) || ciprefix("temp=", sbuf)) {
            token = 0;
            break;
        }
        /* FALLTHROUGH */
    case 'd':
        if (ciprefix("dtemp=", sbuf)) {
            token = 0;
            break;
        }
        /* FALLTHROUGH */
    case 'r':
        if (ciprefix("reciproctc=", sbuf)) {
            token = 0;
            break;
        }
        /* FALLTHROUGH */
    default:
        {
            int n1 = -1;
            int n2 = -1;
            int n3 = -1;
            int n4 = -1;
            int n  = -1;

            sscanf(sbuf, "%*1[vV] ( %n%*[^ \t,()]%n , %n%*[^ \t,()]%n )%n",
                   &n1, &n2, &n3, &n4, &n);
            if(n != -1) {
                token = TOK_pnode;
                lvalp->pnode = mkb(PT_MINUS,
                                   mkvnode(copy_substring(sbuf+n1, sbuf+n2)),
                                   mkvnode(copy_substring(sbuf+n3, sbuf+n4)));
                sbuf += n;
                break;
            }
        }

        {
            int n1 = -1;
            int n2 = -1;
            int n  = -1;

            sscanf(sbuf, "%*1[vV] ( %n%*[^ \t,()]%n )%n", &n1, &n2, &n);
            if(n != -1) {
                token = TOK_pnode;
                lvalp->pnode = mkvnode(copy_substring(sbuf+n1, sbuf+n2));
                sbuf += n;
                break;
            }
        }

        {
            int n1 = -1;
            int n2 = -1;
            int n  = -1;

            sscanf(sbuf, "%*1[iI] ( %n%*[^ \t,()]%n )%n", &n1, &n2, &n);
            if(n != -1) {
                token = TOK_pnode;
                lvalp->pnode = mkinode(copy_substring(sbuf+n1, sbuf+n2));
                sbuf += n;
                break;
            }
        }

        td = INPevaluate(&sbuf, &err, 1);
        if (err == OK) {
            token = TOK_NUM;
            lvalp->num = td;
        } else {
        char *tmp;
            token = TOK_STR;
            for (s = sbuf; *s; s++)
                if (strchr(specials, *s))
                    break;
            tmp = TMALLOC(char, s - sbuf + 1);
            strncpy(tmp, sbuf, (size_t) (s - sbuf));
            tmp[s - sbuf] = '\0';
            lvalp->str = tmp;
            sbuf = s;
        }
    }

    *line = sbuf;

#ifdef TRACE
//    printf("PTlexer: token = %d, type = %d, left = '%s'\n",
//        el.token, el.type, sbuf); */
#endif
    llocp->stop = sbuf;
    return (token);
}


void INPfreeTree(IFparseTree *ptree)
{
    INPparseTree *pt = (INPparseTree *) ptree;

    int i;

    if (!pt)
        return;

    for (i = 0; i < pt->p.numVars; i++)
        dec_usage(pt->derivs[i]);

    dec_usage(pt->tree);

    txfree(pt->derivs);
    txfree(pt->p.varTypes);
    txfree(pt->p.vars);
    txfree(pt);
}


void free_tree(INPparseNode *pt)
{
    if(!pt)
        return;

    if(pt->usecnt) {
        fprintf(stderr, "ERROR: fatal internal error, %s\n", __func__);
        controlled_exit(1);
    }

    /* FALLTHROUGH added to suppress GCC warning due to
     * -Wimplicit-fallthrough flag */
    switch (pt->type) {
    case PT_TIME:
    case PT_TEMPERATURE:
    case PT_FREQUENCY:
    case PT_CONSTANT:
    case PT_VAR:
        break;

    case PT_PLUS:
    case PT_MINUS:
    case PT_TIMES:
    case PT_DIVIDE:
    case PT_POWER:
    case PT_COMMA:
    case PT_TERN:
        dec_usage(pt->right);
        /* FALLTHROUGH */
    case PT_FUNCTION:
        dec_usage(pt->left);
        break;

    default:
        printf("oops ");
        break;
    }

    if(pt->type == PT_FUNCTION && pt->funcnum == PTF_PWL) {
        struct pwldata { int n; double *vals; } *data = (struct pwldata*)(pt->data);
        if(data) {
            txfree(data->vals);
            txfree(data);
        }
    }

    txfree(pt);
}


/* Debugging stuff. */

void INPptPrint(char *str, IFparseTree * ptree)
{
    int i;

    printf("%s\n\t", str);
    printTree(((INPparseTree *) ptree)->tree);
    printf("\n");
    for (i = 0; i < ptree->numVars; i++) {
        printf("d / d v%d : ", i);
        printTree(((INPparseTree *) ptree)->derivs[i]);
        printf("\n");
    }
    return;
}

void printTree(INPparseNode * pt)
{
    switch (pt->type) {
    case PT_TIME:
        printf("time(ckt = %p)", pt->data);
        break;

    case PT_TEMPERATURE:
        printf("temperature(ckt = %p)", pt->data);
        break;

    case PT_FREQUENCY:
        printf("frequency(ckt = %p)", pt->data);
        break;

    case PT_CONSTANT:
        printf("%g", pt->constant);
        break;

    case PT_VAR:
        printf("v%d", pt->valueIndex);
        break;

    case PT_PLUS:
        printf("(");
        printTree(pt->left);
        printf(") + (");
        printTree(pt->right);
        printf(")");
        break;

    case PT_MINUS:
        printf("(");
        printTree(pt->left);
        printf(") - (");
        printTree(pt->right);
        printf(")");
        break;

    case PT_TIMES:
        printf("(");
        printTree(pt->left);
        printf(") * (");
        printTree(pt->right);
        printf(")");
        break;

    case PT_DIVIDE:
        printf("(");
        printTree(pt->left);
        printf(") / (");
        printTree(pt->right);
        printf(")");
        break;

    case PT_POWER:
        printf("(");
        printTree(pt->left);
        printf(") ^ (");
        printTree(pt->right);
        printf(")");
        break;

    case PT_COMMA:
        printf("(");
        printTree(pt->left);
        printf(") , (");
        printTree(pt->right);
        printf(")");
        break;

    case PT_FUNCTION:
        printf("%s (", pt->funcname);
        printTree(pt->left);
        printf(")");
        break;

    case PT_TERN:
        printf("ternary_fcn (");
        printTree(pt->left);
        printf(") , (");
        printTree(pt->right);
        printf(")");
        break;

    default:
        printf("oops ");
        break;
    }
    return;
}

