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

#include "ngspice/ngspice.h"
#include "ngspice/bool.h"
#include "ngspice/wordlist.h"
#include "ngspice/defines.h"
#include "ngspice/macros.h"
#include "ngspice/cpdefs.h"
#include "ngspice/memory.h"
#include "ngspice/inpdefs.h"
#include "ngspice/fteext.h"

#include "circuits.h"
#include "com_history.h"
#include "ngspice/cpextern.h"
#include "variable.h"


bool cp_noglob = FALSE;
bool cp_nonomatch = FALSE;
bool cp_noclobber = FALSE;
bool cp_echo = FALSE;   /* CDHW */

struct variable *variables = NULL;



static void update_option_variables(const char *sz_var_name,
    struct variable *p_v);



wordlist *cp_varwl(struct variable *var)
{
    wordlist *wl = NULL, *w, *wx = NULL;
    char *buf;
    struct variable *vt;
    int csnump = 0;

    switch (var->va_type) {
    case CP_BOOL:
        /* Can't ever be FALSE. */
        buf = copy(var->va_bool ? "TRUE" : "FALSE");
        break;
    case CP_NUM:
        buf = tprintf("%d", var->va_num);
        break;
    case CP_REAL:
        if (cp_getvar("csnumprec", CP_NUM, &csnump, 0) && csnump > 0)
            buf = tprintf("%.*g",csnump, var->va_real); /* csnumprec is set */
        else
            buf = tprintf("%G", var->va_real); /* standard behavior */
        break;
    case CP_STRING:
        buf = copy(var->va_string);
        break;
    case CP_LIST:   /* The tricky case. */
        for (vt = var->va_vlist; vt; vt = vt->va_next) {
            w = cp_varwl(vt); /* recursive call */
            if (wl == NULL) {
                wl = wx = w;
            }
            else {
                wx->wl_next = w;
                w->wl_prev = wx;
                wx = w;
            }
        }
        return wl;
    default:
        fprintf(cp_err,
                "cp_varwl: Internal Error: bad variable type %d\n",
                var->va_type);
        return NULL;
    }

    return wl_cons(buf, NULL);
} /* end of function cp_varwl */



/* Set a variable. */
void cp_vset(const char *varname, enum cp_types type,
        const void *value)
{
    struct variable *v, *u, *w;
    int i;
    bool alreadythere = FALSE, v_free = FALSE;
    char *copyvarname;

    /* varname = cp_unquote(varname);  DG: Memory leak old varname is lost*/

    copyvarname = cp_unquote(varname);

    w = NULL;
    for (v = variables; v; v = v->va_next) {
        if (eq(copyvarname, v->va_name)) {
            alreadythere = TRUE;
            break;
        }
        w = v;
    }

    if (alreadythere) {
        if (v->va_type == CP_LIST)
            free_struct_variable(v->va_vlist);
        if (v->va_type == CP_STRING)
            tfree(v->va_string);
    }

    if (!v) {
        v = var_alloc(copy(copyvarname), NULL);
        v_free = TRUE;
    }

    switch (type) {
    case CP_BOOL:
        if (* ((bool *) value) == FALSE) {
            cp_remvar(copyvarname);
            if (v_free) {
                tfree(v->va_name);
                tfree(v);
            }
            tfree(copyvarname);
            return;
        }
        else {
            /* The variable only exists in TRUE state */
            var_set_bool(v, TRUE);
        }
        break;

    case CP_NUM:
        var_set_num(v, * (int *) value);
        break;

    case CP_REAL:
        var_set_real(v, * (double *) value);
        break;

    case CP_STRING:
        var_set_string(v, copy((char*) value));
        break;

    case CP_LIST:
        var_set_vlist(v, (struct variable *) value);
        break;

    default:
        fprintf(cp_err,
                "cp_vset: Internal Error: bad variable type %d.\n",
                type);
        tfree(copyvarname);
        return;
    }

    /* Update variables controlling options */
    update_option_variables(copyvarname, v);

    switch (i = cp_usrset(v, TRUE)) {

    case US_OK:
        /* Normal case. */
        if (!alreadythere) {
            v->va_next = variables;
            variables = v;
        }
        else if (v_free)
            free_struct_variable(v);
        break;

    case US_DONTRECORD:
        /* 'curplot' 'curplotname' 'curplottitle' 'curplotdate' */
        /* Do nothing... */
        if (alreadythere) {
            fprintf(cp_err, "cp_vset: Internal Error: "
                    "%s already there, but 'dont record'\n", v->va_name);
        }
        if (v_free)
            free_struct_variable(v);
        break;

    case US_READONLY:
        /* 'plots' and any var in plot_cur->pl_env */
        fprintf(cp_err, "Error: %s is a read-only variable.\n", v->va_name);
        if (alreadythere)
            fprintf(cp_err, "cp_vset: Internal Error: "
                    "it was already there too!!\n");
        break;

    case US_SIMVAR:
        /* variables processed by if_option(ft_curckt->ci_ckt, ...) */
        if (alreadythere) {
            /* somehow it got into the front-end list of variables */
            if (w) {
                w->va_next = v->va_next;
            } else {
                variables = v->va_next;
            }
        }
        alreadythere = FALSE;
        if (ft_curckt) {
            for (u = ft_curckt->ci_vars; u; u = u->va_next)
                if (eq(copyvarname, u->va_name)) {
                    alreadythere = TRUE;
                    break;
                }
            if (!alreadythere) {
                v->va_next = ft_curckt->ci_vars;
                ft_curckt->ci_vars = v;
            } else {
                if (u->va_type == CP_STRING)
                    tfree(u->va_string);
                else if (u->va_type == CP_LIST)
                    tfree(u->va_vlist);
                u->va_V = v->va_V;
                u->va_type = v->va_type;
                /* va_name is the same string */
                tfree(u->va_name);
                u->va_name = v->va_name;
                /* va_next left unchanged */
                tfree(v);
            }
        }
        break;

    case US_NOSIMVAR:
        /* variables processed by if_option(NULL, ...) */
        /* What do you do? */
        free_struct_variable(v);
        break;

    default:
        fprintf(cp_err, "cp_vset: Internal Error: bad US val %d\n", i);
        break;
    }

    tfree(copyvarname);
} /* end of function cp_vset */



/* Process special variables: noglob, nonomatch, history,
 * noclobber, echo, prompt, cpdebug, and no_histsubst
 * by setting the values of associated option variables.
 *
 * Parmeters
 * sz_var_name: Name of variable
 * p_v: Variable if it is being added or NULL if being removed.
 */
static void update_option_variables(const char *sz_var_name,
    struct variable *p_v)
{
    static const unsigned char p_ch0['p' - 'a' + 1] = {
        ['n' - 'a'] = 1, /* noglob, nonomatch, noclobber, no_histsubst */
        ['h' - 'a'] = 2, /* history */
        ['e' - 'a'] = 3, /* echo */
        ['p' - 'a'] = 4, /* prompt, program */
        ['c' - 'a'] = 5  /* cpdebug */
    };

    unsigned int index0 = (unsigned int) sz_var_name[0] - 'a';

    /* Check if first char of is in range of interest.
     * Note that if < 0, as unsigned, it will be very large so this
     * single compare checks both < 'a' and > 'p' */
    if (index0 >= sizeof p_ch0) {
        return;
    }

    unsigned int id0 = (unsigned int) p_ch0[index0];
    if (id0 == 0) { /* not of interest */
        return;
    }

    /* Flag that bool values should be set is based on if the
     * variable is being added (via a set) or removed */
    const bool f_set = p_v != (struct variable *) NULL;

    switch (id0) {
    case 1:
        /* noglob, nonomatch, noclobber, no_histsubst */
        if (sz_var_name[1] != 'o') {
            return;
        }
        {
            bool *p_var;
            const char *sz_rest = sz_var_name + 2;
            if (eq(sz_rest, "glob")) {
                p_var = &cp_noglob;
            }
            else if (eq(sz_rest, "nomatch")) {
                p_var = &cp_nonomatch;
            }
            else if (eq(sz_rest, "clobber")) {
                p_var = &cp_noclobber;
            }
            else if (eq(sz_rest, "_histsubst")) {
                p_var = &cp_no_histsubst;
            }
            else { /* not a variable of interest */
                return;
            }
            *p_var = f_set;
        }
        return;
    case 2: /* history */
        if (eq(sz_var_name + 1, "istory")) {
            if (f_set) {
                int n = -1;
                enum cp_types type = p_v->va_type;
                if (type == CP_NUM) {
                    n = p_v->va_num;
                }
                else if (type == CP_REAL) {
                    n = (int) round(p_v->va_real);
                }
                if (n >= 0) {
                    cp_maxhistlength = n;
                }
            }
            /* Note that 'unset history' doesn't do anything here... Causes
             * trouble...  */
        }
        return;
    case 3: /* echo */
        if (eq(sz_var_name + 1, "cho")) {
            cp_echo = f_set;
        }
        return;
    case 4: /* prompt, program */
        if (sz_var_name[1] != 'r') {
            return;
        }
        if (sz_var_name[2] != 'o') {
            return;
        }
        const char *sz_rest = sz_var_name + 3;
        if (eq(sz_rest, "mpt")) { /* prompt */
            if (f_set && p_v->va_type == CP_STRING) {
                cp_promptstring = p_v->va_string;
            }
            else {
                /* Use a default string since prompt is not a string or the
                 * previous prompt string was freed */
                cp_promptstring = "-> ";
            }
            return;
        }
        if (eq(sz_rest, "gram")) { /* program */
            if (f_set && p_v->va_type == CP_STRING) {
                cp_program = p_v->va_string;
            }
            else {
                /* Use a default string since program is not a string or the
                 * previous program string was freed */
                cp_program = "";
            }
            return;
        }
        return; /* not of interest */
    case 5:
        if (eq(sz_var_name + 1, "pdebug")) { /* cpdebug */
            cp_debug = f_set;
#ifndef CPDEBUG
            if (cp_debug) {
                fprintf(cp_err, "Warning: program not compiled "
                        "with cshpar debug messages\n");
            }
#endif
        }
    } /* end of switch over index for first char */
} /* end of function update_option_variables */



/* Read a wordlist, e.g. from the options or set commands 
   e.g. set myvar=myval or set myvar="myval" or myvar=( "myval1" myval2 ) or
   set myvar1=myval1 myvar2=myval2 myvar3="myval3"
   Separate into name and value(s)
   Generate variables (real, string or list) 
   Value in double quotes will always become string variable.
   Without quotes tokens like 2N5401_C will be evaluated as real number 2n, i.e. 2e-9 */
struct variable *cp_setparse(wordlist *wl)
{
    char *name = NULL, *val, *copyval, *s, *ss, *printout = NULL;
    struct variable *listv = NULL, *vv, *lv = NULL;
    struct variable *vars = NULL;

    if (wl)
        printout = wl_flatten(wl);

    /* Step through the list of words. Words may be various combinations of
     * the information needed to set a variable. For example, to set x to
     * the value 3, the data could be supplied as one word x=3, two words
     * x= 3 or x =3 or three words x = 3. Additionally words may be quoted
     * or unquoted. Each iteration through the loop handles one variable */
    while (wl) {

        if (name) {
            txfree(name);
        }

        name = cp_unquote(wl->wl_word);

        wl = wl->wl_next;
        if ((!wl || (*wl->wl_word != '=')) && !strchr(name, '=')) {
            vars = var_alloc_bool(name, TRUE, vars);
            name = (char *) NULL; /* Given to variable vars */
            continue;
        }

        if (wl && eq(wl->wl_word, "=")) { /* name<space>= */
            wl = wl->wl_next;
            if (wl == NULL) {
                fprintf(cp_err, "Error: bad set form in line\n    %s", printout);
                tfree(name);
                tfree(printout);
                if (ft_stricterror)
                    controlled_exit(EXIT_BAD);
                return NULL;
            }
            val = wl->wl_word;
            wl = wl->wl_next;
        } else if (wl && (*wl->wl_word == '=')) { /* name<space>=val */
            val = wl->wl_word + 1;
            wl = wl->wl_next;
        } else if ((s = strchr(name, '=')) != NULL) {
            /* name=<space>value or name=value */
            val = s + 1;
            *s = '\0';
            if (*val == '\0') {
                if (!wl) {
                    fprintf(cp_err, "Error:  %s equals what?.\n", name);
                    tfree(name); /*DG: cp_unquote Memory leak: free name before exiting*/
                    if (ft_stricterror)
                        controlled_exit(EXIT_BAD);
                    return NULL;
                } else {
                    val = wl->wl_word;
                    wl = wl->wl_next;
                }
            }
        }
        else {
            fprintf(cp_err, "Error: bad set form in line\n    %s", printout);
            tfree(name);
            tfree(printout);
            if (ft_stricterror)
                controlled_exit(EXIT_BAD);
            return NULL;
        }

        /* if val is in double quotes, treat as string */
        copyval = cp_unquote(val);
        if (!eq(val, copyval)) {
            vars = var_alloc_string(copy(name), copy(copyval), vars);
            tfree(name);
            tfree(copyval);
            continue;
        }
        strcpy(val, copyval);
        tfree(copyval);

        /* Test for a list variable */
        if (eq(val, "(")) {
            /* The beginning of a list... We have to walk down the
             * list until we find a close paren... If there are nested
             * ()'s, treat them as tokens...  */
            int balance = 1;
            while (wl && wl->wl_word) {
                if (eq(wl->wl_word, "(")) {
                    balance++;
                } else if (eq(wl->wl_word, ")")) {
                    if (!--balance)
                        break;
                }
                copyval = ss = cp_unquote(wl->wl_word);
                /* if val is in double quotes, treat as string */
                if (!eq(wl->wl_word, copyval)) {
                    vv = var_alloc_string(NULL, copy(ss), NULL);
                }
                else {
                    double dbl_val;
                    switch (ft_numparse(&ss, FALSE, &dbl_val)) {
                    case 0: /* CP_REAL */
                        vv = var_alloc_real(NULL, dbl_val, NULL);
                        break;
                    case +1: /* CP_NUM */
                        vv = var_alloc_num(NULL, (int) dbl_val, NULL);
                        break;
                    default: /* CP_STRING */
                        vv = var_alloc_string(NULL, copy(ss), NULL);
                    }
                }
                tfree(copyval);
                if (listv) {
                    lv->va_next = vv;
                    lv = vv;
                } else {
                    listv = lv = vv;
                }
                wl = wl->wl_next;
            }
            if (balance && !wl) {
                fprintf(cp_err, "Error: bad set form in line\n    %s", printout);
                tfree(name);
                tfree(printout);
                if (ft_stricterror)
                    controlled_exit(EXIT_BAD);
                return NULL;
            }

            /* Add list variable to linked list of variables. */
            vars = var_alloc_vlist(copy(name), listv, vars);

            wl = wl->wl_next;
            continue;
        }

        copyval = ss = cp_unquote(val);
        /* if val is in double quotes, treat as string */
        if (!eq(val, copyval)) {
            vars = var_alloc_string(copy(name), copy(copyval), vars);
        }
        else {
            double dbl_val;
            switch (ft_numparse(&ss, FALSE, &dbl_val)) {
            case 0: /* CP_REAL */
                vars = var_alloc_real(name, dbl_val, vars);
                break;
            case +1: /* CP_NUM */
                vars = var_alloc_num(name, (int) dbl_val, vars);
                break;
            default: /* CP_STRING */
                vars = var_alloc_string(name, copy(val), vars);
            }


        }
        name = (char *) NULL; /* name given to variable via var_alloc_* */
        tfree(copyval); /*DG: must free ss any way to avoid cp_unquote memory leak */
    }

    if (name) {
        tfree(name);
    }
    if (printout) {
        tfree(printout);
    }
    return vars;
} /* end of function cp_setparse */



/* free the struct variable. The type of the union is given by va_type */
void
free_struct_variable(struct variable *v)
{
    while (v) {
        struct variable *next_v = v->va_next;
        if (v->va_name)
            txfree(v->va_name);
        if (v->va_type == CP_LIST)
            free_struct_variable(v->va_vlist);
        if (v->va_type == CP_STRING)
            txfree(v->va_string);
        txfree(v);
        v = next_v;
    }
} /* end of function free_struct_variable */


void cp_remvar(char *varname)
{
    struct variable *v, **p;
    struct variable *uv1;
    int i;

    uv1 = cp_usrvars();

    for (p = &variables; *p; p = &(*p)->va_next) {
        if (eq((*p)->va_name, varname)) {
            break;
        }
    }

    if (*p == NULL) {
        for (p = &uv1; *p; p = &(*p)->va_next) {
            if (eq((*p)->va_name, varname)) {
                break;
            }
        }
    }

    if (*p == NULL && plot_cur) {
        for (p = &plot_cur->pl_env; *p; p = &(*p)->va_next) {
            if (eq((*p)->va_name, varname)) {
                break;
            }
        }
    }

    if (*p == NULL && ft_curckt) {
        for (p = &ft_curckt->ci_vars; *p; p = &(*p)->va_next) {
            if (eq((*p)->va_name, varname)) {
                break;
            }
        }
    }

    v = *p;

    /* make up an auxiliary struct variable for cp_usrset() */
    if (!v) {
        v = var_alloc_num(copy(varname), 0, NULL);
    }

    /* Update options that depend on variables */
    update_option_variables(varname, (struct variable *) NULL);

    switch (i = cp_usrset(v, FALSE)) {

    case US_OK:
        /* Normal case. */
        if (*p) {
            *p = v->va_next;
        }
        break;

    case US_DONTRECORD:
        /* 'curplot' 'curplotname' 'curplottitle' 'curplotdate' */
        /* Do nothing... */
        if (*p)
            fprintf(cp_err, "cp_remvar: Internal Error: var %d\n", *varname);
        break;

    case US_READONLY:
        /* 'plots' and any var in plot_cur->pl_env */
        /* Badness... */
        fprintf(cp_err, "Error: %s is read-only.\n", v->va_name);
        if (*p)
            fprintf(cp_err, "cp_remvar: Internal Error: var %d\n", *varname);
        break;

    case US_SIMVAR:
        /* variables processed by if_option(ft_curckt->ci_ckt, ...) */
        fprintf(stderr, "it's a US_SIMVAR!\n");
        if (ft_curckt) {
            for (p = &ft_curckt->ci_vars; *p; p = &(*p)->va_next)
                if (eq(varname, (*p)->va_name))
                    break;
            if (*p) {
                struct variable *u = *p;
                *p = u->va_next;
                tfree(u);
            }
        }
        break;

    case US_NOSIMVAR:
    default:
        /* variables processed by if_option(NULL, ...) */
        fprintf(cp_err, "cp_remvar: Internal Error: US val %d\n", i);
        break;
    }

    v->va_next = NULL;
    free_struct_variable(v);

    free_struct_variable(uv1);
} /* end of function cp_remvar */



/* Determine the value of a variable.  Fail if the variable is unset,
 * and if the type doesn't match, try and make it work...  */
bool
cp_getvar(char *name, enum cp_types type, void *retval, size_t rsize)
{
    struct variable *v;
    struct variable *uv1;

    uv1 = cp_usrvars();

#ifdef TRACE
    /* SDB debug statement */
    fprintf(stderr, "in cp_getvar, trying to get value of variable %s.\n", name);
#endif

    for (v = variables; v; v = v->va_next)
        if (eq(name, v->va_name))
            break;

    if (!v)
        for (v = uv1; v; v = v->va_next)
            if (eq(name, v->va_name))
                break;

    if (!v && plot_cur)
        for (v = plot_cur->pl_env; v; v = v->va_next)
            if (eq(name, v->va_name))
                break;

    if (!v && ft_curckt)
        for (v = ft_curckt->ci_vars; v; v = v->va_next)
            if (eq(name, v->va_name))
                break;

    if (!v) {
        if (type == CP_BOOL && retval)
            *(bool *) retval = FALSE;
        free_struct_variable(uv1);
        return (FALSE);
    }

    if (v->va_type == type) {

        if (retval)
            switch (type) {
            case CP_BOOL:
                *(bool *) retval = TRUE;
                break;
            case CP_NUM:
                *(int *) retval = v->va_num;
                break;
            case CP_REAL:
                *(double *) retval = v->va_real;
                break;
            case CP_STRING: {   /* Gotta be careful to have room. */
                char *s = cp_unquote(v->va_string);
                if (strlen(s) > rsize) {
                    fprintf(stderr, "Warning: string length for variable %s is limited to %zu chars\n", v->va_name, rsize);
                    /* limit the string length */
                    s[rsize] = '\0';
                }
                strcpy((char*) retval, s);
                tfree(s);
                break;
            }
            case CP_LIST:       /* Funny case... */
                *(struct variable **) retval = v->va_vlist;
                break;
            default:
                fprintf(cp_err,
                        "cp_getvar: Internal Error: bad var type %d.\n", type);
                break;
            }

        free_struct_variable(uv1);
        return (TRUE);
    }

    /* Try to coerce it.. */
    if ((type == CP_NUM) && (v->va_type == CP_REAL)) {
        *(int *) retval = (int) v->va_real;
    } else if ((type == CP_REAL) && (v->va_type == CP_NUM)) {
        *(double *) retval = (double) v->va_num;
    } else if ((type == CP_STRING) && (v->va_type == CP_NUM)) {
        sprintf((char*) retval, "%d", v->va_num);
    } else if ((type == CP_STRING) && (v->va_type == CP_REAL)) {
        sprintf((char*) retval, "%f", v->va_real);
    } else {
        free_struct_variable(uv1);
        return (FALSE);
    }

    free_struct_variable(uv1);
    return (TRUE);
}


/* A variable substitution is indicated by a $, and the variable name
 * is the following string of non-special characters. All variable
 * values are inserted as a single word, except for lists, which are a
 * list of words.  A routine cp_usrset must be supplied by the host
 * program to deal with variables that aren't used by cshpar -- it
 * should be cp_usrset(var, isset), where var is a variable *, and
 * isset is TRUE if the variable is being set, FALSE if unset.  Also
 * required is a routine cp_enqvar(name) which returns a struct
 * variable *, which allows the host program to provide values for
 * non-cshpar variables.  */

/* Non-alphanumeric characters that may appear in variable names. < is very
 * special...
 */
#define VALIDCHARS "$-_<#?@.()[]&"

/* This function determines the first character after a variable name and
 * returns its address.
 *
 * Parameter
 * t: Address of the variable name whose end is to be found. This is the
 *      address of the first character following the leading $
 *
 * Return value
 * Address of the first character after the variable name.
 */
char *span_var_expr(char *s)
{
    char *t = s;
    int   parenthesis = 0;
    int   brackets = 0;

    while (*t && (isalnum_c(*t) || strchr(VALIDCHARS, *t)))
        switch (*t++)
        {
        case '[':
            brackets++;
            break;
        case '(':
            parenthesis++;
            break;
        case ']':
            if (brackets <= 0)
                return t-1;
            if (--brackets <= 0)
                return t;
            break;
        case ')':
            if (parenthesis <= 0)
                return t-1;
            if (--parenthesis <= 0)
                return t;
            break;
        case '$':
            if (brackets <= 0 && parenthesis <= 0) {
                if (t == s + 1) // Special case: "$$".
                    return t;
                else
                    return t-1;
            }
            break;
        default:
            break;
        }

    return t;
} /* end of function span_var_expr */


/* Substitute variable name by its value and restore to wordlist */
wordlist *cp_variablesubst(wordlist *wlist)
{
    wordlist *wl;

    for (wl = wlist; wl; wl = wl->wl_next) {

        char *s_dollar;
        int i = 0;

        while ((s_dollar = strchr(wl->wl_word + i, '$')) != NULL) {

            /* Prevent vectors carrying the E POLY source current from being disaggregated */
            if (ciprefix("a$poly$", wl->wl_word))
                break;

            int prefix_len = (int) (s_dollar - wl->wl_word);

            char *tail = span_var_expr(s_dollar + 1);
            char *var = copy_substring(s_dollar + 1, tail);

            wordlist *nwl = vareval(var);
            tfree(var);

            if (nwl) {
                char *x = nwl->wl_word;
                char *tail_ = copy(tail);
                nwl->wl_word = tprintf("%.*s%s", prefix_len, wl->wl_word, nwl->wl_word);
                tfree(x);
                if (wlist == wl)
                    wlist = nwl;
                wl = wl_splice(wl, nwl);
                i = (int) strlen(wl->wl_word);
                x = wl->wl_word;
                wl->wl_word = tprintf("%s%s", wl->wl_word, tail_);
                tfree(x);
                tfree(tail_);
            } else if (prefix_len || *tail) {
                char *x = wl->wl_word;
                wl->wl_word = tprintf("%.*s%s", prefix_len, wl->wl_word, tail);
                i = prefix_len;
                tfree(x);
            } else {
                wordlist *next = wl->wl_next;
                if (wlist == wl) {
                    wlist = next;
                }
                wl_delete_slice(wl, next);
                if (!next) { /* wordlist ends after wl */
                    return wlist;
                }
                wl = next;
                i = 0;
            }
        } /* end of loop over parts of wordlist node */
    } /* end of loop over words in wordlist */

    return wlist;
} /* end of function cp_variablesubst */



/* Evaluate a variable. */
wordlist *vareval(/* NOT const */ char *string)
{
    struct variable *v, *vfree = NULL;
    wordlist *wl;
    char buf[BSIZE_SP], *s;
    char *oldstring = copy(string);
    char *range = NULL;
    int i, up, low, tbfreed;

    /* usage of vfree: variable v has to be freed only if created by cp_enqvar()! */

    if ((s = strchr(string, '[')) != NULL) {
        *s = '\0';
        range = s + 1;
    }

    switch (*string) {

    case '$':
        wl = wl_cons(tprintf("%d", getpid()), NULL);
        txfree(oldstring);
        return wl;

    case '<':
        (void) fflush(cp_out);
        if (!fgets(buf, BSIZE_SP, cp_in)) {
            clearerr(cp_in);
            (void) strcpy(buf, "EOF");
        }
        for (s = buf; *s && (*s != '\n'); s++)
            ;
        *s = '\0';
        wl = cp_lexer(buf);
        /* This is a hack. */
        if (!wl->wl_word)
            wl->wl_word = copy("");
        txfree(oldstring);
        return wl;

    case '?':
        string++;
        for (v = variables; v; v = v->va_next)
            if (eq(v->va_name, string))
                break;
        if (!v) {
            v = cp_enqvar(string, &tbfreed);
            if (tbfreed) {
                vfree = v;
            }
        }
        wl = wl_cons(copy(v ? "1" : "0"), NULL);
        free_struct_variable(vfree);
        txfree(oldstring);
        return wl;

    case '#':
        string++;
        for (v = variables; v; v = v->va_next) {
            if (eq(v->va_name, string)) {
                break;
            }
        }
        if (!v) {
            v = cp_enqvar(string, &tbfreed);
            if (tbfreed) {
                vfree = v;
            }
        }
        if (!v) {
            fprintf(cp_err, "Error: %s: no such variable.\n", string);
            txfree(oldstring);
            return NULL;
        }
        if (v->va_type == CP_LIST) {
            for (v = v->va_vlist, i = 0; v; v = v->va_next) {
                i++;
            }
        }
        else {
            i = (v->va_type != CP_BOOL);
        }
        wl = wl_cons(tprintf("%d", i), NULL);
        txfree(oldstring);
        free_struct_variable(vfree);
        return wl;

    case '\0':
        wl = wl_cons(copy("$"), NULL);
        txfree(oldstring);
        return wl;
    }

    vfree = NULL; //just in case ...
    /* The notation var[stuff] has two meanings...  If this is a real
     * variable, then the [] denotes range, but if this is a strange
     * (e.g, device parameter) variable, it could be anything...
     */
    for (v = variables; v; v = v->va_next)
        if (eq(v->va_name, string))
            break;
    if (!v && isdigit_c(*string)) {
        /* Treat $i for some integer i as argv[i] - positional parameters. */

        for (v = variables; v; v = v->va_next) {
            if (eq(v->va_name, "argv")) {
                break;
            }
        }
        range = string;
    }
    if (!v) {
        range = NULL;
        string = oldstring;
        v = cp_enqvar(string, &tbfreed);
        if (tbfreed) {
            vfree = v;
        }
    }
    if (!v && (s = getenv(string)) != NULL) {
        wl = wl_cons(copy(s), NULL);
        tfree(oldstring);
        return (wl);
    }
    if (!v) {
        fprintf(cp_err, "Error: %s: no such variable.\n", string);
        txfree(oldstring);
        return NULL;
    }
    wl = cp_varwl(v);
    free_struct_variable(vfree);

    /* Now parse and deal with 'range' ... */
    if (range) {
        /* rather crude fix when range itself is a $expression */
        wordlist *r = NULL;
        if (*range == '$') {
            char *t = ++range;
            if (*t == '&') {
                t++;
            }
            while (isalnum_c(*t)) {
                t++;
            }
            *t = '\0';
            r = vareval(range);
            if (!r || r->wl_next) {
                fprintf(cp_err, "Error: %s: illegal index.\n", string);
                txfree(oldstring);
                wl_free(r);
                return NULL;
            }
            range = r->wl_word;
        }
        for (low = 0; isdigit_c(*range); range++) {
            low = low * 10 + *range - '0';
        }
        if ((*range == '-') && isdigit_c(range[1])) {
            for (up = 0, range++; isdigit_c(*range); range++) {
                up = up * 10 + *range - '0';
            }
        }
        else if (*range == '-') {
            up = wl_length(wl);
        }
        else {
            up = low;
        }
        up--, low--;
        wl = wl_range(wl, low, up);
        wl_free(r);
    } /* end of case of range given for variable */

    txfree(oldstring);
    return (wl);
}


struct xxx {
    struct variable *x_v;
    char x_char;
};


static int vcmp(const void *a, const void *b)
{
    int i;
    struct xxx *v1 = (struct xxx *) a;
    struct xxx *v2 = (struct xxx *) b;

    if ((i = strcmp(v1->x_v->va_name, v2->x_v->va_name)) != 0) {
        return i;
    }
    else {
        return v1->x_char - v2->x_char;
    }
}


/* Print the values of currently defined variables. */
void cp_vprint(void)
{
    struct variable *v;
    struct variable *uv1;
    wordlist *wl;
    int i, j;
    char *s;
    struct xxx *vars;

    uv1 = cp_usrvars();

    for (v = variables, i = 0; v; v = v->va_next) {
        i++;
    }
    for (v = uv1; v; v = v->va_next) {
        i++;
    }
    if (plot_cur) {
        for (v = plot_cur->pl_env; v; v = v->va_next) {
            i++;
        }
    }
    if (ft_curckt) {
        for (v = ft_curckt->ci_vars; v; v = v->va_next) {
            i++;
        }
    }

    vars = TMALLOC(struct xxx, i);

    out_init();
    for (v = variables, i = 0; v; v = v->va_next, i++) {
        vars[i].x_v = v;
        vars[i].x_char = ' ';
    }
    for (v = uv1; v; v = v->va_next, i++) {
        vars[i].x_v = v;
        vars[i].x_char = '*';
    }
    if (plot_cur) {
        for (v = plot_cur->pl_env; v; v = v->va_next, i++) {
            vars[i].x_v = v;
            vars[i].x_char = '*';
        }
    }
    if (ft_curckt) {
        for (v = ft_curckt->ci_vars; v; v = v->va_next, i++) {
            vars[i].x_v = v;
            vars[i].x_char = '+';
        }
    }

    qsort(vars, (size_t) i, sizeof(*vars), vcmp);

    for (j = 0; j < i; j++) {
        if (j && eq(vars[j].x_v->va_name, vars[j-1].x_v->va_name)) {
            continue;
        }
        v = vars[j].x_v;
        if (v->va_type == CP_BOOL) {
            out_printf("%c %s\n", vars[j].x_char, v->va_name);
        }
        else {
            out_printf("%c %s\t", vars[j].x_char, v->va_name);
            wl = vareval(v->va_name);
            s = wl_flatten(wl);
            if (v->va_type == CP_LIST)
                out_printf("( %s )\n", s);
            else
                out_printf("%s\n", s);
            wl_free(wl);
            tfree(s);
        }
    }

    free_struct_variable(uv1);
    tfree(vars);
} /* end of function cp_vprint */



struct variable *var_alloc(char *name, struct variable *next)
{
    struct variable * const v = TMALLOC(struct variable, 1);
    ZERO(v, struct variable);
    v -> va_name = name;
    v -> va_next = next;
    return v;
}



struct variable *var_alloc_bool(char *name, bool value,
        struct variable *next)
{
    struct variable *v = var_alloc(name, next);
    var_set_bool(v, value);
    return v;
}

struct variable *var_alloc_num(char *name, int value,
        struct variable *next)
{
    struct variable *v = var_alloc(name, next);
    var_set_num(v, value);
    return v;
}

struct variable *var_alloc_real(char *name, double value,
        struct variable *next)
{
    struct variable *v = var_alloc(name, next);
    var_set_real(v, value);
    return v;
}

struct variable *var_alloc_string(char *name, char * value,
        struct variable *next)
{
    struct variable *v = var_alloc(name, next);
    var_set_string(v, value);
    return v;
}

struct variable * var_alloc_vlist(char *name, struct variable * value,
        struct variable *next)
{
    struct variable *v = var_alloc(name, next);
    var_set_vlist(v, value);
    return v;
}

void var_set_bool(struct variable *v, bool value)
{
  v->va_type = CP_BOOL;
  v->va_bool = value;
}

void var_set_num(struct variable *v, int value)
{
  v->va_type = CP_NUM;
  v->va_num = value;
}

void var_set_real(struct variable *v, double value)
{
  v->va_type = CP_REAL;
  v->va_real = value;
}

void var_set_string(struct variable *v, char *value)
{
  v->va_type = CP_STRING;
  v->va_string = value;
}

void var_set_vlist(struct variable *v, struct variable *value)
{
  v->va_type = CP_LIST;
  v->va_vlist = value;
}
