/*       spicenum.c                Copyright (C)  2002    Georg Post
 *
 *  This file is part of Numparam, see:  readme.txt
 *  Free software under the terms of the GNU Lesser General Public License
 */

/* number parameter add-on for Spice.
   to link with mystring.o, xpressn.o (math formula interpreter),
   and with Spice frontend src/lib/fte.a .
   Interface function nupa_signal to tell us about automaton states.
Buglist (some are 'features'):
  blank lines get category '*'
  inserts conditional blanks before or after  braces
  between .control and .endc, flags all lines as 'category C', dont touch.
  there are reserved magic numbers (1e9 + n) as placeholders
  control lines must not contain {} .
  ignores the '.option numparam' line planned to trigger the actions
  operation of .include certainly doesnt work
  there are frozen maxima for source and expanded circuit size.
Todo:
  add support for nested .if .elsif .else .endif controls.
*/

#include "ngspice/ngspice.h"

#include "general.h"
#include "numparam.h"

#include "ngspice/fteext.h"


extern bool ft_batchmode;

void dump_symbols(dico_t *);

char *nupa_inst_name;

/* number of parameter substitutions, available only after the substitution */
extern long dynsubst; /* spicenum.c:144 */

/* number of lines in input deck */
extern int dynmaxline; /* inpcom.c:1529 */

/* Uncomment this line to allow debug tracing */
/* #define TRACE_NUMPARAMS */

/*  the nupa_signal arguments sent from Spice:

    sig=1: Start of the subckt expansion.
    sig=2: Stop of the subckt expansion.
    sig=3: Stop of the evaluation phase.
    sig=0: Start of a deck copy operation

    After sig=1 until sig=2, nupa_copy does no transformations.
    At sig=2, we prepare for nupa_eval loop.
    After sig=3, we assume the initial state (clean).

    In Clean state, a lot of deckcopy operations come in and we
    overwrite any line pointers, or we start a new set after each sig=0 ?
    Anyway, we neutralize all & and .param lines  (category[] array!)
    and we substitute all {} &() and &id placeholders by dummy identifiers.
    those look like numparm__________XXXXXXXX (8 hexadecimal digits)

*/
/**********  string handling ***********/

static long placeholder = 0;


static void
stripsomespace(SPICE_DSTRINGPTR dstr_p, unsigned char incontrol)
{
    /* if s starts with one of some markers, strip leading space */
    int i, ls;
    char *sstr;                 /* string contained in s */
    SPICE_DSTRING markers;

    spice_dstring_init(&markers);
    scopys(&markers, "*.&+#$");

    if (!incontrol)
        sadd(&markers, "xX");

    sstr = spice_dstring_value(dstr_p);
    ls = spice_dstring_length(dstr_p);

    i = 0;
    while ((i < ls) && (sstr[i] <= ' '))
        i++;

    if ((i > 0) && (i < ls) && (cpos(sstr[i], spice_dstring_value(&markers)) >= 0))
        pscopy(dstr_p, sstr, i, ls);
}


static int
stripbraces(SPICE_DSTRINGPTR dstr_p)
/* puts the funny placeholders. returns the number of {...} substitutions */
{
    int n, i, nest, ls, j;
    char *s;                    /* value of dynamic string */
    char *t_p;                  /* value of t dynamic string */
    SPICE_DSTRING tstr;         /* temporary dynamic string */

    n = 0;
    spice_dstring_init(&tstr);
    s = spice_dstring_value(dstr_p);
    ls = spice_dstring_length(dstr_p);
    i = 0;

    while (i < ls) {

        if (s[i] == '{') {

            /* something to strip */
            j = i + 1;
            nest = 1;
            n++;

            while ((nest > 0) && (j < ls)) {
                if (s[j] == '{')
                    nest++;
                else if (s[j] == '}')
                    nest--;
                j++;
            }

            pscopy(&tstr, s, 0, i);
            placeholder++;

            t_p = spice_dstring_value(&tstr);

            if (t_p[i - 1] > ' ')
                cadd(&tstr, ' ');

            cadd(&tstr, ' ');
            {
                char buf[25+1];
                sprintf(buf, "numparm__________%08lx", placeholder);
                sadd(&tstr, buf);
            }
            cadd(&tstr, ' ');

            if (s[j] >= ' ')
                cadd(&tstr, ' ');

            i = spice_dstring_length(&tstr);
            pscopy(dstr_p, s, j, ls);
            sadd(&tstr, s);
            scopyd(dstr_p, &tstr);
            s = spice_dstring_value(dstr_p);
            ls = spice_dstring_length(dstr_p);

        } else {

            i++;

        }
    }

    dynsubst = placeholder;
    spice_dstring_free(&tstr);

    return n;
}


static int
findsubname(dico_t *dico, SPICE_DSTRINGPTR dstr_p)
/* truncate the parameterized subckt call to regular old Spice */
/* scan a string from the end, skipping non-idents and {expressions} */
/* then truncate s after the last subckt(?) identifier */
{
    SPICE_DSTRING name;         /* extract a name */
    char *s;                    /* current dstring */
    int h, j, k, nest, ls;
    int found;

    h = 0;

    ls = spice_dstring_length(dstr_p);
    s = spice_dstring_value(dstr_p);
    k = ls - 1;                 /* now a C - string */
    found = 0;
    spice_dstring_init(&name);

    while ((k >= 0) && (!found)) {

        /* skip space, then non-space */
        while ((k >= 0) && (s[k] <= ' '))
            k--;

        h = k + 1;              /* at h: space */
        while ((k >= 0) && (s[k] > ' ')) {

            if (s[k] == '}') {
                nest = 1;
                k--;

                while ((nest > 0) && (k >= 0)) {
                    if (s[k] == '{')
                        nest--;
                    else if (s[k] == '}')
                        nest++;

                    k--;
                }
                h = k + 1;      /* h points to '{' */

            } else {
                k--;
            }
        }

        found = (k >= 0) && alfanum(s[k + 1]); /* suppose an identifier */
        if (found) {
            /* check for known subckt name */
            spice_dstring_reinit(&name);
            j = k + 1;
            while (alfanum(s[j])) {
                cadd(&name, upcase(s[j]));
                j++;
            }
            found = (getidtype(dico, spice_dstring_value(&name)) == 'U');
        }
    }

    if (found && (h < ls))
        pscopy(dstr_p, s, 0, h);

    return h;
}


static void
modernizeex(SPICE_DSTRINGPTR dstr_p)
/* old style expressions &(..) and &id --> new style with braces. */
{
    int i, state, ls;
    char c, d;
    char *s;                    /* current string */
    SPICE_DSTRING t;            /* temporary dyna string */

    i = 0;
    state = 0;
    ls = spice_dstring_length(dstr_p);
    s = spice_dstring_value(dstr_p);

    /* check if string might need modernizing */
    if (!memchr(s, Intro, (size_t) ls))
        return;

    spice_dstring_init(&t);

    while (i < ls) {
        c = s[i];
        d = s[i + 1];
        if ((!state) && (c == Intro) && (i > 0)) {
            if (d == '(') {
                state = 1;
                i++;
                c = '{';
            } else if (alfa(d)) {
                cadd(&t, '{');
                i++;
                while (alfanum(s[i])) {
                    cadd(&t, s[i]);
                    i++;
                }
                c = '}';
                i--;
            }
        } else if (state) {
            if (c == '(')
                state++;
            else if (c == ')')
                state--;

            if (!state)         /* replace--) by terminator */
                c = '}';
        }

        cadd(&t, c);
        i++;
    }

    scopyd(dstr_p, &t);
    spice_dstring_free(&t);
}


static char
transform(dico_t *dico, SPICE_DSTRINGPTR dstr_p, unsigned char nostripping,
          SPICE_DSTRINGPTR u_p)
/*         line s is categorized and crippled down to basic Spice
 *         returns in u control word following dot, if any
 *
 * any + line is copied as-is.
 * any & or .param line is commented-out.
 * any .subckt line has params section stripped off
 * any X line loses its arguments after sub-circuit name
 * any &id or &() or {} inside line gets a 10-digit substitute.
 *
 * strip  the new syntax off the codeline s, and
 * return the line category as follows:
 *   '*'  comment line
 *   '+'  continuation line
 *   ' '  other untouched netlist or command line
 *   'P'  parameter line, commented-out; (name,linenr)-> symbol table.
 *   'S'  subckt entry line, stripped;   (name,linenr)-> symbol table.
 *   'U'  subckt exit line
 *   'X'  subckt call line, stripped
 *   'C'  control entry line
 *   'E'  control exit line
 *   '.'  any other dot line
 *   'B'  netlist (or .model ?) line that had Braces killed
 */
{
    int k, a, n;
    char *s;                    /* dstring value of dstr_p */
    char *t;                    /* dstring value of tstr */
    char category;
    SPICE_DSTRING tstr;         /* temporary string */

    spice_dstring_init(&tstr);
    spice_dstring_reinit(u_p);
    stripsomespace(dstr_p, nostripping);
    modernizeex(dstr_p);        /* required for stripbraces count */

    s = spice_dstring_value(dstr_p);

    if (s[0] == '.') {
        /* check PS parameter format */
        scopy_up(&tstr, spice_dstring_value(dstr_p));
        k = 1;

        t = spice_dstring_value(&tstr);
        while (t[k] > ' ') {
            cadd(u_p, t[k]);
            k++;
        }

        if (ci_prefix(".PARAM", t) == 1) {
            /* comment it out */
            /* s[0] = '*'; */
            category = 'P';
        } else if (ci_prefix(".SUBCKT", t) == 1) {
            /* split off any "params" tail */
            a = spos_("PARAMS:", t);
            if (a >= 0)
                pscopy(dstr_p, s, 0, a);
            category = 'S';
        } else if (ci_prefix(".CONTROL", t) == 1) {
            category = 'C';
        } else if (ci_prefix(".ENDC", t) == 1) {
            category = 'E';
        } else if (ci_prefix(".ENDS", t) == 1) {
            category = 'U';
        } else {
            category = '.';
            n = stripbraces(dstr_p);
            if (n > 0)
                category = 'B'; /* priority category ! */
        }
    } else if (s[0] == Intro) {
        /* private style preprocessor line */
        s[0] = '*';
        category = 'P';
    } else if (upcase(s[0]) == 'X') {
        /* strip actual parameters */
        findsubname(dico, dstr_p); /* i= index following last identifier in s */
        category = 'X';
    } else if (s[0] == '+') {   /* continuation line */
        category = '+';
    } else if (cpos(s[0], "*$#") < 0) {
        /* not a comment line! */
        n = stripbraces(dstr_p);
        if (n > 0)
            category = 'B';     /* line that uses braces */
        else
            category = ' ';     /* ordinary code line */
    } else {
        category = '*';
    }

    spice_dstring_free(&tstr);
    return category;
}


/************ core of numparam **************/

/* some day, all these nasty globals will go into the dico_t structure
   and everything will get hidden behind some "handle" ...
   For the time being we will rename this variable to end in S so we know
   they are statics within this file for easier reading of the code.
*/

static int linecountS = 0;      /* global: number of lines received via nupa_copy */
static int evalcountS = 0;      /* number of lines through nupa_eval() */
static int nblogS = 0;          /* serial number of (debug) logfile */
static unsigned char inexpansionS = 0;  /* flag subckt expansion phase */
static unsigned char incontrolS = 0;    /* flag control code sections */
static unsigned char dologfileS = 0;    /* for debugging */
static unsigned char firstsignalS = 1;
static FILE *logfileS = NULL;
static dico_t *dicoS = NULL;


/*  already part of dico : */
/*
  Open ouput to a log file.
  takes no action if logging is disabled.
  Open the log if not already open.
*/
static void
putlogfile(char c, int num, char *t)
{
    if (!dologfileS)
        return;

    if (!logfileS) {
        char *fname = tprintf("logfile.%d", ++nblogS);
        logfileS = fopen(fname, "w");
        tfree(fname);
    }

    if (logfileS)
        fprintf(logfileS, "%c%d: %s\n", c, num, t);
}


static void
nupa_init(char *srcfile)
{
    int i;

    /* init the symbol table and so on, before the first  nupa_copy. */
    evalcountS = 0;
    linecountS = 0;
    incontrolS = 0;
    placeholder = 0;
    dicoS = (dico_t *) new(sizeof(dico_t));
    initdico(dicoS);

    dicoS->dynrefptr = TMALLOC(char*, dynmaxline + 1);
    dicoS->dyncategory = TMALLOC(char, dynmaxline + 1);

    for (i = 0; i <= dynmaxline; i++) {
        dicoS->dynrefptr[i] = NULL;
        dicoS->dyncategory[i] = '?';
    }

    if (srcfile != NULL)
        scopys(&dicoS->srcfile, srcfile);
}


/* free dicoS (called from com_remcirc()) */
void
nupa_del_dicoS(void)
{
    int i;

    if(!dicoS)
        return;

    for (i = dynmaxline; i >= 0; i--)
        dispose(dicoS->dynrefptr[i]);

    dispose(dicoS->dynrefptr);
    dispose(dicoS->dyncategory);
    dispose(dicoS->inst_name);
    nghash_free(dicoS->symbols[0], del_attrib, NULL);
    dispose(dicoS->symbols);
    dispose(dicoS);
    dicoS = NULL;
}


static void
nupa_done(void)
{
    int nerrors = dicoS->errcount;
    int dictsize = donedico(dicoS);

    if (logfileS) {
        fclose(logfileS);
        logfileS = NULL;
    }

    /* We cannot remove dicoS here because numparam is used by
       the .measure statements, which are invoked only after the
       simulation has finished. */

    if (nerrors) {
        printf(" Copies=%d Evals=%d Placeholders=%ld Symbols=%d Errors=%d\n",
               linecountS, evalcountS, placeholder, dictsize, nerrors);
        /* debug: ask if spice run really wanted */
        if (ft_batchmode)
            controlled_exit(EXIT_FAILURE);
        for (;;) {
            int c;
            printf("Numparam expansion errors: Run Spice anyway? y/n ?\n");
            c = yes_or_no();
            if (c == 'n' || c == EOF)
                controlled_exit(EXIT_FAILURE);
            if (c == 'y')
                break;
        }
    }

    linecountS = 0;
    evalcountS = 0;
    placeholder = 0;
    /* release symbol table data */
}


/* SJB - Scan the line for subcircuits */
void
nupa_scan(char *s, int linenum, int is_subckt)
{
    if (is_subckt)
        defsubckt(dicoS, s, linenum, 'U');
    else
        defsubckt(dicoS, s, linenum, 'O');
}


/* -----------------------------------------------------------------
 * Dump the contents of a symbol table.
 * ----------------------------------------------------------------- */
static void
dump_symbol_table(dico_t *dico, NGHASHPTR htable_p, FILE *cp_out)
{
    char *name;                 /* current symbol */
    entry_t *entry;             /* current entry */
    NGHASHITER iter;            /* hash iterator - thread safe */

    NGHASH_FIRST(&iter);
    for (entry = (entry_t *) nghash_enumerateRE(htable_p, &iter);
         entry;
         entry = (entry_t *) nghash_enumerateRE(htable_p, &iter))
    {
        if (entry->tp == 'R') {
            spice_dstring_reinit(& dico->lookup_buf);
            scopy_lower(& dico->lookup_buf, entry->symbol);
            name = spice_dstring_value(& dico->lookup_buf);
            fprintf(cp_out, "       ---> %s = %g\n", name, entry->vl);
            spice_dstring_free(& dico->lookup_buf);
        }
    }
}


/* -----------------------------------------------------------------
 * Dump the contents of the symbol table.
 * ----------------------------------------------------------------- */
void
nupa_list_params(FILE *cp_out)
{
    int depth;                  /* nested subcircit depth */
    dico_t *dico;               /* local copy for speed */

    dico = dicoS;
    if (dico == NULL) {
        fprintf(cp_err, "\nWarning: No symbol table available for 'listing param'\n");
        return;
    }

    fprintf(cp_out, "\n\n");

    for (depth = dico->stack_depth; depth >= 0; depth--) {
        NGHASHPTR htable_p = dico->symbols[depth];
        if (htable_p) {
            if (depth > 0)
                fprintf(cp_out, " local symbol definitions for: %s\n", dico->inst_name[depth]);
            else
                fprintf(cp_out, " global symbol definitions:\n");
            dump_symbol_table(dico, htable_p, cp_out);
        }
    }
}


/* -----------------------------------------------------------------
 * Lookup a parameter value in the symbol tables.   This involves
 * multiple lookups in various hash tables in order to get the scope
 * correct.  Each subcircuit instance will have its own local hash
 * table if it has parameters.   We can return whenever we get a hit.
 * Otherwise, we have to exhaust all of the tables including the global
 * table.
 * ----------------------------------------------------------------- */
double
nupa_get_param(char *param_name, int *found)
{
    int depth;                  /* nested subcircit depth */
    char *up_name;              /* current parameter upper case */
    entry_t *entry;             /* current entry */
    dico_t *dico;               /* local copy for speed */
    double result = 0;          /* parameter value */

    dico = dicoS;
    spice_dstring_reinit(& dico->lookup_buf);
    scopy_up(& dico->lookup_buf, param_name);
    up_name = spice_dstring_value(& dico->lookup_buf);

    *found = 0;
    for (depth = dico->stack_depth; depth >= 0; depth--) {
        NGHASHPTR htable_p = dico->symbols[depth];
        if (htable_p) {
            entry = (entry_t *) nghash_find(htable_p, up_name);
            if (entry) {
                result = entry->vl;
                *found = 1;
                break;
            }
        }
    }

    spice_dstring_free(& dico->lookup_buf);
    return result;
}


void
nupa_add_param(char *param_name, double value)
{
    char *up_name;              /* current parameter upper case */
    entry_t *entry;             /* current entry */
    dico_t *dico;               /* local copy for speed */
    NGHASHPTR htable_p;         /* hash table of interest */

    dico = dicoS;
    /* -----------------------------------------------------------------
     * We use a dynamic string here because most of the time we will
     * be using short names and no memory allocation will occur.
     * ----------------------------------------------------------------- */
    spice_dstring_reinit(& dico->lookup_buf);
    scopy_up(& dico->lookup_buf, param_name);
    up_name = spice_dstring_value(& dico->lookup_buf);

    /* can't be lazy anymore */
    if (!(dico->symbols[dico->stack_depth]))
        dico->symbols[dico->stack_depth] = nghash_init(NGHASH_MIN_SIZE);

    htable_p = dico->symbols[dico->stack_depth];

    entry = attrib(dico, htable_p, up_name, 'N');
    if (entry) {
        entry->vl = value;
        entry->tp = 'R';
        entry->ivl = 0;
        entry->sbbase = NULL;
    }

    spice_dstring_free(& dico->lookup_buf);
}


void
nupa_add_inst_param(char *param_name, double value)
{
    char *up_name;              /* current parameter upper case */
    entry_t *entry;             /* current entry */
    dico_t *dico;               /* local copy for speed */

    dico = dicoS;
    spice_dstring_reinit(& dico->lookup_buf);
    scopy_up(& dico->lookup_buf, param_name);
    up_name = spice_dstring_value(& dico->lookup_buf);

    if (!(dico->inst_symbols))
        dico->inst_symbols = nghash_init(NGHASH_MIN_SIZE);

    entry = attrib(dico, dico->inst_symbols, up_name, 'N');
    if (entry) {
        entry->vl = value;
        entry->tp = 'R';
        entry->ivl = 0;
        entry->sbbase = NULL;
    }

    spice_dstring_free(& dico->lookup_buf);
}


/* -----------------------------------------------------------------
 * This function copies any definitions in the inst_symbols hash
 * table which are qualified symbols and makes them available at
 * the global level.  Afterwards, the inst_symbols table is freed.
 * ----------------------------------------------------------------- */
void
nupa_copy_inst_dico(void)
{
    entry_t *entry;             /* current entry */
    dico_t *dico;               /* local copy for speed */
    NGHASHITER iter;            /* hash iterator - thread safe */

    dico = dicoS;
    if (dico->inst_symbols) {
        /* We we perform this operation we should be in global scope */
        if (dico->stack_depth > 0)
            fprintf(stderr, "stack depth should be zero.\n");

        NGHASH_FIRST(&iter);
        for (entry = (entry_t *) nghash_enumerateRE(dico->inst_symbols, &iter);
             entry;
             entry = (entry_t *) nghash_enumerateRE(dico->inst_symbols, &iter))
        {
            nupa_add_param(entry->symbol, entry->vl);
            dico_free_entry(entry);
        }

        nghash_free(dico->inst_symbols, NULL, NULL);
        dico->inst_symbols = NULL;
    }
}


char *
nupa_copy(char *s, int linenum)
/* returns a copy (not quite) of s in freshly allocated memory.
   linenum, for info only, is the source line number.
   origin pointer s is kept, memory is freed later in nupa_done.
   must abort all Spice if malloc() fails.
   :{ called for the first time sequentially for all spice deck lines.
   :{ then called again for all X invocation lines, top-down for
   subckts defined at the outer level, but bottom-up for local
   subcircuit expansion, but has no effect in that phase.
   we steal a copy of the source line pointer.
   - comment-out a .param or & line
   - substitute placeholders for all {..} --> 10-digit numeric values.
*/
{
    char *t;
    int ls;
    char c, d;
    SPICE_DSTRING u;
    SPICE_DSTRING keywd;

    spice_dstring_init(&u);
    spice_dstring_init(&keywd);
    ls = length(s);

    while ((ls > 0) && (s[ls - 1] <= ' '))
        ls--;

    pscopy(&u, s, 0, ls);       /* strip trailing space, CrLf and so on */
    dicoS->srcline = linenum;

    if ((!inexpansionS) && (linenum >= 0) && (linenum <= dynmaxline)) {
        linecountS++;
        dicoS->dynrefptr[linenum] = s;
        c = transform(dicoS, &u, incontrolS, &keywd);
        if (c == 'C')
            incontrolS = 1;
        else if (c == 'E')
            incontrolS = 0;

        if (incontrolS)
            c = 'C';            /* force it */

        d = dicoS->dyncategory[linenum]; /* warning if already some strategic line! */

        if ((d == 'P') || (d == 'S') || (d == 'X'))
            fprintf(stderr,
                    " Numparam warning: overwriting P,S or X line (linenum == %d).\n",
                    linenum);
        dicoS->dyncategory[linenum] = c;
    } /* keep a local copy and mangle the string */

    ls = spice_dstring_length(&u);
    t = strdup(spice_dstring_value(&u));

    if (t == NULL) {
        fputs("Fatal: String malloc crash in nupa_copy()\n", stderr);
        controlled_exit(EXIT_FAILURE);
    } else {
        if (!inexpansionS)
            putlogfile(dicoS->dyncategory[linenum], linenum, t);
    }

    spice_dstring_free(&u);
    return t;
}


int
nupa_eval(char *s, int linenum, int orig_linenum)
/* s points to a partially transformed line.
   compute variables if linenum points to a & or .param line.
   if ( the original is an X line,  compute actual params.;
   } else {  substitute any &(expr) with the current values.
   All the X lines are preserved (commented out) in the expanded circuit.
*/
{
    int idef;                   /* subckt definition line */
    char c, keep, *ptr;
    SPICE_DSTRING subname;      /* dynamic string for subcircuit name */
    bool err = 1;

    spice_dstring_init(&subname);
    dicoS->srcline = linenum;
    dicoS->oldline = orig_linenum;

    c = dicoS->dyncategory[linenum];

#ifdef TRACE_NUMPARAMS
    fprintf(stderr, "** SJB - in nupa_eval()\n");
    fprintf(stderr, "** SJB - processing line %3d: %s\n", linenum, s);
    fprintf(stderr, "** SJB - category '%c'\n", c);
#endif

    if (c == 'P') {                     /* evaluate parameters */
        // err = nupa_substitute(dico, dico->dynrefptr[linenum], s, 0);
        nupa_assignment(dicoS, dicoS->dynrefptr[linenum], 'N');
    } else if (c == 'B') {              /* substitute braces line */
        err = nupa_substitute(dicoS, dicoS->dynrefptr[linenum], s, 0);
    } else if (c == 'X') {
        /* compute args of subcircuit, if required */
        ptr = s;
        while (!isspace(*ptr))
            ptr++;
        keep = *ptr;
        *ptr = '\0';
        nupa_inst_name = strdup(s);
        *nupa_inst_name = 'x';
        *ptr = keep;

        strtoupper(nupa_inst_name);

        idef = findsubckt(dicoS, s, &subname);
        if (idef > 0)
            nupa_subcktcall(dicoS, dicoS->dynrefptr[idef], dicoS->dynrefptr[linenum], 0);
        else
            putlogfile('?', linenum, "  illegal subckt call.");
    } else if (c == 'U') {              /*  release local symbols = parameters */
        nupa_subcktexit(dicoS);
    }

    putlogfile('e', linenum, s);
    evalcountS++;

#ifdef TRACE_NUMPARAMS
    fprintf(stderr, "** SJB - leaving nupa_eval(): %s   %d\n", s, err);
    printf("** SJB -                  --> %s\n", s);
    printf("** SJB - leaving nupa_eval()\n\n");
#endif

    if (err)
        return 0;
    else
        return 1;
}


int
nupa_signal(int sig, char *info)
/* warning: deckcopy may come inside a recursion ! substart no! */
/* info is context-dependent string data */
{
    putlogfile('!', sig, " Nupa Signal");

    if (sig == NUPADECKCOPY) {
        if (firstsignalS) {
            nupa_init(info);
            firstsignalS = 0;
        }
    } else if (sig == NUPASUBSTART) {
        inexpansionS = 1;
    } else if (sig == NUPASUBDONE) {
        inexpansionS = 0;
        nupa_inst_name = NULL;
    } else if (sig == NUPAEVALDONE) {
        nupa_done();
        firstsignalS = 1;
    }

    return 1;
}


#ifdef USING_NUPATEST
/* This is use only by the nupatest program */
dico_t *
nupa_fetchinstance(void)
{
    return dico;
}
#endif


void
dump_symbols(dico_t *dico)
{
    NG_IGNORE(dico);

    fprintf(stderr, "Symbol table\n");
    nupa_list_params(stderr);
}
