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

/*
 * User-defined functions. The user defines the function with
 *  define func(arg1, arg2, arg3) <expression involving args...>
 * Then when he types "func(1, 2, 3)", the commas are interpreted as
 * binary operations of the lowest priority by the parser, and ft_substdef()
 * below is given a chance to fill things in and return what the parse tree
 * would have been had the entire thing been typed.
 * Note that we have to take some care to distinguish between functions
 * with the same name and different arities.
 */

#include "ngspice/ngspice.h"
#include "ngspice/cpdefs.h"
#include "ngspice/ftedefs.h"
#include "ngspice/dvec.h"
#include "ngspice/fteparse.h"
#include "define.h"

#include "completion.h"


static void savetree(struct pnode *pn);
static void prdefs(char *name);
static void prtree(struct udfunc *ud);
static void prtree1(struct pnode *pn, FILE *fp);
static struct pnode *trcopy(struct pnode *tree, char *args, struct pnode *nn);
static struct pnode *ntharg(int num, struct pnode *args);

static struct udfunc *udfuncs = NULL;


/* Set up a function definition. */

void
com_define(wordlist *wlist)
{
    int arity = 0, i;
    char buf[BSIZE_SP], tbuf[BSIZE_SP], *s, *t, *b;
    wordlist *wl;
    struct pnode *names;
    struct udfunc *udf;

    /* If there's nothing then print all the definitions. */
    if (wlist == NULL) {
        prdefs(NULL);
        return;
    }

    /* Accumulate the function head in the buffer, w/out spaces. A
     * useful thing here would be to check to make sure that there
     * are no formal parameters here called "list". But you have
     * to try really hard to break this here.
     */
    buf[0] = '\0';

    for (wl = wlist; wl && (strchr(wl->wl_word, /* ( */ ')') == NULL);
         wl = wl->wl_next)
        (void) strcat(buf, wl->wl_word);

    if (wl) {
        for (t = buf; *t; t++)
            ;
        for (s = wl->wl_word; *s && (*s != /* ( */ ')'); s++, t++)
            *t = *s;
        *t++ = /* ( */ ')';
        *t = '\0';
        if (*++s)
            wl->wl_word = copy(s);
        else
            wl = wl->wl_next;
    }

    /* If that's all, then print the definition. */
    if (wl == NULL) {
        prdefs(buf);
        return;
    }

    /* Now check to see if this is a valid name for a function (i.e,
     * there isn't a predefined function of the same name).
     */
    (void) strcpy(tbuf, buf);

    for (b = tbuf; *b; b++)
        if (isspace(*b) || (*b == '(' /* ) */)) {
            *b = '\0';
            break;
        }

    for (i = 0; ft_funcs[i].fu_name; i++)
        if (eq(ft_funcs[i].fu_name, tbuf)) {
            fprintf(cp_err, "Error: %s is a predefined function.\n",
                    tbuf);
            return;
        }

    /* Parse the rest of it. We can't know if there are the right
     * number of undefined variables in the expression.
     */
    if ((names = ft_getpnames(wl, FALSE)) == NULL)
        return;

    /* This is a pain -- when things are garbage-collected, any
     * vectors that may have been mentioned here will be thrown
     * away. So go down the tree and save any vectors that aren't
     * formal parameters.
     */
    savetree(names);

    /* Format the name properly and add to the list. */
    b = copy(buf);
    for (s = b; *s; s++) {
        if (*s == '(') { /*)*/
            *s = '\0';
            if (s[1] != /*(*/ ')')
                arity++;    /* It will have been 0. */
        } else if (*s == /*(*/ ')') {
            *s = '\0';
        } else if (*s == ',') {
            *s = '\0';
            arity++;
        }
    }

    for (udf = udfuncs; udf; udf = udf->ud_next)
        if (prefix(b, udf->ud_name) && (arity == udf->ud_arity))
            break;

    if (udf == NULL) {
        udf = alloc(struct udfunc);
        if (udfuncs == NULL) {
            udfuncs = udf;
            udf->ud_next = NULL;
        } else {
            udf->ud_next = udfuncs;
            udfuncs = udf;
        }
    }

    udf->ud_text = names;
    udf->ud_name = b;
    udf->ud_arity = arity;

    cp_addkword(CT_UDFUNCS, b);
}


/* Kludge. */

static void
savetree(struct pnode *pn)
{
    struct dvec *d;

    if (pn->pn_value) {
        /* We specifically don't add this to the plot list
         * so it won't get gc'ed.
         */
        d = pn->pn_value;
        if ((d->v_length != 0) || eq(d->v_name, "list")) {
            pn->pn_value = alloc(struct dvec);
            ZERO(pn->pn_value, struct dvec);
            pn->pn_value->v_name = copy(d->v_name);
            pn->pn_value->v_length = d->v_length;
            pn->pn_value->v_type = d->v_type;
            pn->pn_value->v_flags = d->v_flags;
            pn->pn_value->v_plot = NULL; /* this dvec isn't member of any plot */
            if (isreal(d)) {
                pn->pn_value->v_realdata = TMALLOC(double, d->v_length);
                bcopy(d->v_realdata,
                      pn->pn_value->v_realdata,
                      sizeof(double) * (size_t) d->v_length);
            } else {
                pn->pn_value->v_compdata = TMALLOC(ngcomplex_t, d->v_length);
                bcopy(d->v_compdata,
                      pn->pn_value->v_compdata,
                      sizeof(ngcomplex_t) * (size_t) d->v_length);
            }
        }
    } else if (pn->pn_op) {
        savetree(pn->pn_left);
        if (pn->pn_op->op_arity == 2)
            savetree(pn->pn_right);
    } else if (pn->pn_func) {
        savetree(pn->pn_left);
    }
}


/* A bunch of junk to print out nodes. */

static void
prdefs(char *name)
{
    struct udfunc *udf;
    char *s;

    if (name) {
        s = strchr(name, '(' /* ) */);
        if (s)
            *s = '\0';
    }

    if (name && *name) {    /* You never know what people will do */
        for (udf = udfuncs; udf; udf = udf->ud_next)
            if (eq(name, udf->ud_name))
                prtree(udf);
    } else {
        for (udf = udfuncs; udf; udf = udf->ud_next)
            prtree(udf);
    }
}


/* Print out one definition. */

static void
prtree(struct udfunc *ud)
{
    char *s, buf[BSIZE_SP];

    /* Print the head. */
    buf[0] = '\0';
    (void) strcat(buf, ud->ud_name);
    for (s = ud->ud_name; *s; s++)
        ;
    (void) strcat(buf, " (");
    s++;
    while (*s) {
        (void) strcat(buf, s);
        while (*s)
            s++;
        if (s[1])
            (void) strcat(buf, ", ");
        s++;
    }
    (void) strcat(buf, ") = ");
    fputs(buf, cp_out);
    prtree1(ud->ud_text, cp_out);
    (void) putc('\n', cp_out);
}


static void
prtree1(struct pnode *pn, FILE *fp)
{
    if (pn->pn_value) {
        fputs(pn->pn_value->v_name, fp);
    } else if (pn->pn_func) {
        fprintf(fp, "%s (", pn->pn_func->fu_name);
        prtree1(pn->pn_left, fp);
        fputs(")", fp);
    } else if (pn->pn_op && (pn->pn_op->op_arity == 2)) {
        fputs("(", fp);
        prtree1(pn->pn_left, fp);
        fprintf(fp, ")%s(", pn->pn_op->op_name);
        prtree1(pn->pn_right, fp);
        fputs(")", fp);
    } else if (pn->pn_op && (pn->pn_op->op_arity == 1)) {
        fprintf(fp, "%s(", pn->pn_op->op_name);
        prtree1(pn->pn_left, fp);
        fputs(")", fp);
    } else {
        fputs("<something strange>", fp);
    }
}


struct pnode *
ft_substdef(const char *name, struct pnode *args)
{
    struct udfunc *udf;
    struct pnode *tp;
    char *s;
    int arity = 0, rarity = 0;
    bool found = FALSE;

    if (args)
        arity = 1;

    for (tp = args; tp && tp->pn_op && (tp->pn_op->op_num == PT_OP_COMMA); tp =
             tp->pn_right)
        arity++;

    for (udf = udfuncs; udf; udf = udf->ud_next)
        if (eq(name, udf->ud_name)) {
            if (arity == udf->ud_arity) {
                break;
            } else {
                found = TRUE;
                rarity = udf->ud_arity;
            }
        }

    if (udf == NULL) {
        if (found)
            fprintf(cp_err,
                    "Warning: the user-defined function %s has %d args\n",
                    name, rarity);
        return (NULL);
    }

    for (s = udf->ud_name; *s; s++)
        ;
    s++;

    /* Now we have to traverse the tree and copy it over,
     * substituting args.
     */
    return (trcopy(udf->ud_text, s, args));
}


/* Copy the tree and replace formal args with the right stuff. The way
 * we know that something might be a formal arg is when it is a dvec
 * with length 0 and a name that isn't "list". I hope nobody calls their
 * formal parameters "list".
 */

static struct pnode *
trcopy(struct pnode *tree, char *args, struct pnode *nn)
{
    struct pnode *pn;
    struct dvec *d;
    char *s;
    int i;

    if (tree->pn_value) {

        d = tree->pn_value;

        if ((d->v_length == 0) && strcmp(d->v_name, "list")) {
            /* Yep, it's a formal parameter. Substitute for it.
             * IMPORTANT: we never free parse trees, so we
             * needn't worry that they aren't trees here.
             */
            s = args;
            i = 1;
            while (*s) {
                if (eq(s, d->v_name))
                    break;
                else
                    i++;
                while (*s++)   /* Get past the last '\0'. */
                    ;
            }

            if (*s)
                return (ntharg(i, nn));
            else
                return (tree);

        } else {

            return (tree);

        }

    } else if (tree->pn_func) {

        pn = alloc(struct pnode);
        pn->pn_use = 0;
        pn->pn_name = NULL;
        pn->pn_value = NULL;
        /* pn_func are pointers to a global constant struct */
        pn->pn_func = tree->pn_func;
        pn->pn_op = NULL;
        pn->pn_left = trcopy(tree->pn_left, args, nn);
        pn->pn_left->pn_use++;
        pn->pn_right = NULL;
        pn->pn_next = NULL;

    } else if (tree->pn_op) {

        pn = alloc(struct pnode);
        pn->pn_use = 0;
        pn->pn_name = NULL;
        pn->pn_value = NULL;
        pn->pn_func = NULL;
        /* pn_op are pointers to a global constant struct */
        pn->pn_op = tree->pn_op;
        pn->pn_left = trcopy(tree->pn_left, args, nn);
        pn->pn_left->pn_use++;
        if (pn->pn_op->op_arity == 2) {
            pn->pn_right = trcopy(tree->pn_right, args, nn);
            pn->pn_right->pn_use++;
        } else {
            pn->pn_right = NULL;
        }
        pn->pn_next = NULL;

    } else {
        fprintf(cp_err, "trcopy: Internal Error: bad parse node\n");
        return (NULL);
    }

    return (pn);
}


/* Find the n'th arg in the arglist, returning NULL if there isn't one.
 * Since comma has such a low priority and associates to the right,
 * we can just follow the right branch of the tree num times.
 * Note that we start at 1 when numbering the args.
 */

static struct pnode *
ntharg(int num, struct pnode *args)
{
    struct pnode *ptry;

    ptry = args;

    if (num > 1)
        while (--num > 0) {
            if (ptry && ptry->pn_op &&
                (ptry->pn_op->op_num != PT_OP_COMMA)) {
                if (num == 1)
                    break;
                else
                    return (NULL);
            }
            ptry = ptry->pn_right;
        }

    if (ptry && ptry->pn_op && (ptry->pn_op->op_num == PT_OP_COMMA))
        ptry = ptry->pn_left;

    return (ptry);
}


void
com_undefine(wordlist *wlist)
{
    struct udfunc *udf, *ludf;

    if (!wlist)
        return;

    if (*wlist->wl_word == '*') {
        for (udf = udfuncs; udf;) {
            struct udfunc *next = udf->ud_next;
            cp_remkword(CT_UDFUNCS, udf->ud_name);
            free_pnode(udf->ud_text);
            free(udf->ud_name);
            free(udf);
            udf = next;
        }
        udfuncs = NULL;
        return;
    }

    for (; wlist; wlist = wlist->wl_next) {
        ludf = NULL;
        for (udf = udfuncs; udf;) {
            struct udfunc *next = udf->ud_next;
            if (eq(wlist->wl_word, udf->ud_name)) {
                if (ludf)
                    ludf->ud_next = udf->ud_next;
                else
                    udfuncs = udf->ud_next;
                cp_remkword(CT_UDFUNCS, wlist->wl_word);
                free_pnode(udf->ud_text);
                free(udf->ud_name);
                free(udf);
            } else {
                ludf = udf;
            }
            udf = next;
        }
    }
}


#ifndef LINT

/* Watch out, this is not at all portable.  It's only here so I can
 * call it from dbx with an int value (all you can give with "call")...
 */

void
ft_pnode(struct pnode *pn)
{
    prtree1(pn, cp_err);
}

#endif
