/*
 * config - configuration routines
 *
 * Copyright (C) 1999-2007,2021-2023  David I. Bell and Landon Curt Noll
 *
 * Primary author:  David I. Bell
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:   1991/07/20 00:21:56
 * File existed as early as:    1991
 *
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */


#include <stdio.h>

#include "have_times.h"
#if defined(HAVE_TIME_H)
#include <time.h>
#endif

#if defined(HAVE_TIMES_H)
#include <times.h>
#endif

#if defined(HAVE_SYS_TIME_H)
#include <sys/time.h>
#endif

#if defined(HAVE_SYS_TIMES_H)
#include <sys/times.h>
#endif

#include "calc.h"
#include "alloc.h"
#include "token.h"
#include "zrand.h"
#include "block.h"
#include "nametype.h"
#include "config.h"
#include "str.h"
#include "custom.h"
#include "strl.h"

#include "have_strdup.h"
#if !defined(HAVE_STRDUP)
# define strdup(x) calc_strdup((CONST char *)(x))
#endif /* HAVE_STRDUP */


#include "errtbl.h"
#include "banned.h"     /* include after system header <> includes */


/*
 * deal with systems that lack a defined CLK_TCK
 */
#if defined(CLK_TCK)
# define CALC_HZ ((long)(CLK_TCK))
#else
# define CALC_HZ (0L)   /* no defined clock tick rate */
#endif


/*
 * Table of configuration types that can be set or read.
 */
NAMETYPE configs[] = {
        {"all",         CONFIG_ALL},
        {"mode",        CONFIG_MODE},
        {"mode2",       CONFIG_MODE2},
        {"display",     CONFIG_DISPLAY},
        {"epsilon",     CONFIG_EPSILON},
        /*epsilonprec -- tied to epsilon not a configuration type*/
        {"trace",       CONFIG_TRACE},
        {"maxprint",    CONFIG_MAXPRINT},
        {"mul2",        CONFIG_MUL2},
        {"sq2",         CONFIG_SQ2},
        {"pow2",        CONFIG_POW2},
        {"redc2",       CONFIG_REDC2},
        {"tilde",       CONFIG_TILDE},
        {"tilde_space", CONFIG_TILDE_SPACE},
        {"fraction_space",      CONFIG_FRACTION_SPACE},
        {"complex_space",       CONFIG_COMPLEX_SPACE},
        {"tab",         CONFIG_TAB},
        {"quomod",      CONFIG_QUOMOD},
        {"quo",         CONFIG_QUO},
        {"mod",         CONFIG_MOD},
        {"sqrt",        CONFIG_SQRT},
        {"appr",        CONFIG_APPR},
        {"cfappr",      CONFIG_CFAPPR},
        {"cfsim",       CONFIG_CFSIM},
        {"outround",    CONFIG_OUTROUND},
        {"round",       CONFIG_ROUND},
        {"triground",   CONFIG_TRIGROUND},
        {"leadzero",    CONFIG_LEADZERO},
        {"fullzero",    CONFIG_FULLZERO},
        {"maxscan",     CONFIG_MAXSCAN},
            {"maxerr",  CONFIG_MAXSCAN},        /* old name for maxscan */
        {"prompt",      CONFIG_PROMPT},
        {"more",        CONFIG_MORE},
        {"blkmaxprint", CONFIG_BLKMAXPRINT},
        {"blkverbose",  CONFIG_BLKVERBOSE},
        {"blkbase",     CONFIG_BLKBASE},
        {"blkfmt",      CONFIG_BLKFMT},
        {"resource_debug", CONFIG_RESOURCE_DEBUG},
        {"lib_debug",   CONFIG_RESOURCE_DEBUG},
        {"calc_debug",  CONFIG_CALC_DEBUG},
        {"user_debug",  CONFIG_USER_DEBUG},
        {"verbose_quit",CONFIG_VERBOSE_QUIT},
        {"ctrl_d",      CONFIG_CTRL_D},
            {"ctrl-d",  CONFIG_CTRL_D},         /* alias for ctrl_d */
        {"program",     CONFIG_PROGRAM},
        {"basename",    CONFIG_BASENAME},
        {"windows",     CONFIG_WINDOWS},
        {"cygwin",      CONFIG_CYGWIN},
        {"compile_custom",      CONFIG_COMPILE_CUSTOM},
        {"allow_custom",        CONFIG_ALLOW_CUSTOM},
        {"version",     CONFIG_VERSION},
        {"baseb",       CONFIG_BASEB},
        {"redecl_warn", CONFIG_REDECL_WARN},
        {"dupvar_warn", CONFIG_DUPVAR_WARN},
        {"hz",          CONFIG_HZ},
        {NULL,          0}
};


/*
 * configurations
 */
CONFIG oldstd = {       /* backward compatible standard configuration */
        MODE_INITIAL,           /* current output mode */
        MODE2_INITIAL,          /* current secondary output mode */
        20,                     /* current output digits for float or exp */
        NULL,   /* loaded in at startup - default error for real functions */
        EPSILONPREC_DEFAULT,    /* binary precision of epsilon */
        false,                  /* tracing flags */
        MAXPRINT_DEFAULT,       /* number of elements to print */
        MUL_ALG2,               /* size of number to use multiply alg 2 */
        SQ_ALG2,                /* size of number to use square alg 2 */
        POW_ALG2,               /* size of modulus to use REDC for powers */
        REDC_ALG2,              /* size of modulus to use REDC algorithm 2 */
        true,                   /* true ==> print a tilde on approximations */
        false,                  /* true ==> print a space after tilde on approximations */
        false,                  /* true ==> print spaces around / in fractions */
        false,                  /* true ==> print spaces around + or - in complex values */
        true,                   /* true ==> print tab before numeric values */
        0,                      /* quomod() default rounding mode */
        2,                      /* quotient // default rounding mode */
        0,                      /* mod % default rounding mode */
        24,                     /* sqrt() default rounding mode */
        24,                     /* appr() default rounding mode */
        0,                      /* cfappr() default rounding mode */
        8,                      /* cfsim() default rounding mode */
        2,                      /* output default rounding mode */
        24,                     /* round()/bround() default rounding mode */
        24,                     /* trigonometric and hyperbolic function rounding mode */
        false,                  /* true ==> print leading 0 before decimal pt */
        0,                      /* true ==> print trailing 0's */
        MAXSCANCOUNT,           /* max scan errors before abort */
        PROMPT1,                /* normal prompt */
        PROMPT2,                /* prompt when inside multi-line input */
        BLK_DEF_MAXPRINT,       /* number of octets of a block to print */
        false,                  /* skip duplicate block output lines */
        BLK_BASE_HEX,           /* block octet print base */
        BLK_FMT_HD_STYLE,       /* block output format */
        0,                      /* internal calc debug level */
        3,                      /* calc resource file debug level */
        0,                      /* user defined debug level */
        false,                  /* true ==> print Quit or abort executed messages */
        CTRL_D_VIRGIN_EOF,      /* ^D only exits on virgin lines */
        NULL,                   /* our name */
        NULL,                   /* basename of our name */
#if defined(_WIN32) || defined(_WIN64)
        true,                   /* true ==> running under windows */
#else
        false,                  /* false ==> not using windows */
#endif
#if defined(__CYGWIN__)
        true,                   /* true ==> compiled under cygwin */
#else
        false,                  /* false ==> not compiled with cygwin */
#endif
#if defined(CUSTOM)
        true,                   /* true ==> compiled with -DCUSTOM */
#else
        false,                  /* false ==> compiled without -DCUSTOM */
#endif
        &allow_custom,          /* *true=> custom functions are enabled */
        NULL,                   /* version */
        BASEB,                  /* base for calculations */
        true,                   /* true ==> warn when redeclaring */
        true,                   /* true ==> warn when variable names collide */
};
CONFIG newstd = {       /* new non-backward compatible configuration */
        MODE_INITIAL,           /* current output mode */
        MODE2_INITIAL,          /* current output mode */
        20,                     /* current output digits for float or exp */
        NULL,   /* loaded in at startup - default error for real functions */
        EPSILONPREC_DEFAULT,    /* binary precision of epsilon */
        false,                  /* tracing flags */
        MAXPRINT_DEFAULT,       /* number of elements to print */
        MUL_ALG2,               /* size of number to use multiply alg 2 */
        SQ_ALG2,                /* size of number to use square alg 2 */
        POW_ALG2,               /* size of modulus to use REDC for powers */
        REDC_ALG2,              /* size of modulus to use REDC algorithm 2 */
        true,                   /* true ==> print a tilde on approximations */
        false,                  /* true ==> print spaces around / in fractions */
        false,                  /* true ==> print a space after tilde on approximations */
        false,                  /* true ==> print spaces around + or - in complex values */
        true,                   /* true ==> print tab before numeric values */
        0,                      /* quomod() default rounding mode */
        2,                      /* quotient // default rounding mode */
        0,                      /* mod % default rounding mode */
        24,                     /* sqrt() default rounding mode */
        24,                     /* appr() default rounding mode */
        0,                      /* cfappr() default rounding mode */
        8,                      /* cfsim() default rounding mode */
        24,                     /* output default rounding mode */
        24,                     /* round()/bround() default rounding mode */
        24,                     /* trigonometric and hyperbolic function rounding mode */
        true,                   /* true ==> print leading 0 before decimal pt */
        0,                      /* true ==> print trailing 0's */
        MAXSCANCOUNT,           /* max scan errors before abort */
        "; ",                   /* normal prompt */
        ";; ",                  /* prompt when inside multi-line input */
        BLK_DEF_MAXPRINT,       /* number of octets of a block to print */
        false,                  /* skip duplicate block output lines */
        BLK_BASE_HEX,           /* block octet print base */
        BLK_FMT_HD_STYLE,       /* block output format */
        0,                      /* internal calc debug level */
        3,                      /* calc resource file debug level */
        0,                      /* user defined debug level */
        false,                  /* true ==> print Quit or abort executed messages */
        CTRL_D_VIRGIN_EOF,      /* ^D only exits on virgin lines */
        NULL,                   /* our name */
        NULL,                   /* basename of our name */
#if defined(_WIN32) || defined(_WIN64)
        true,                   /* true ==> running under windows */
#else
        false,                  /* false ==> not using windows */
#endif
#if defined(__CYGWIN__)
        true,                   /* true ==> compiled under cygwin */
#else
        false,                  /* false ==> not compiled with cygwin */
#endif
#if defined(CUSTOM)
        true,                   /* true ==> compiled with -DCUSTOM */
#else
        false,                  /* false ==> compiled without -DCUSTOM */
#endif
        &allow_custom,          /* *true ==> custom functions are enabled */
        NULL,                   /* version */
        BASEB,                  /* base for calculations */
        true,                   /* true ==> warn when redeclaring */
        true,                   /* true ==> warn when variable names collide */
};
CONFIG *conf = NULL;    /* loaded in at startup - current configuration */


/*
 * Possible output modes.
 */
STATIC NAMETYPE modes[] = {
        {"fraction",    MODE_FRAC},
        {"frac",        MODE_FRAC},
        {"integer",     MODE_INT},
        {"int",         MODE_INT},
        {"real",        MODE_REAL},
        {"float",       MODE_REAL},
        {"default",     MODE_INITIAL},  /* MODE_REAL */
        {"scientific",  MODE_EXP},
        {"sci",         MODE_EXP},
        {"exp",         MODE_EXP},
        {"engineering", MODE_ENG},
        {"eng",         MODE_ENG},
        {"hexadecimal", MODE_HEX},
        {"hex",         MODE_HEX},
        {"octal",       MODE_OCTAL},
        {"oct",         MODE_OCTAL},
        {"binary",      MODE_BINARY},
        {"bin",         MODE_BINARY},
        {"float-auto",  MODE_REAL_AUTO},
        {"off",         MODE2_OFF},
        {NULL,          0}
};


/*
 * Possible block base output modes
 */
STATIC NAMETYPE blk_base[] = {
        {"hexadecimal", BLK_BASE_HEX},
        {"hex",         BLK_BASE_HEX},
        {"default",     BLK_BASE_HEX},
        {"octal",       BLK_BASE_OCT},
        {"oct",         BLK_BASE_OCT},
        {"character",   BLK_BASE_CHAR},
        {"char",        BLK_BASE_CHAR},
        {"binary",      BLK_BASE_BINARY},
        {"bin",         BLK_BASE_BINARY},
        {"raw",         BLK_BASE_RAW},
        {"none",        BLK_BASE_RAW},
        {NULL,          0}
};


/*
 * Possible block output formats
 */
STATIC NAMETYPE blk_fmt[] = {
        {"lines",       BLK_FMT_LINE},
        {"line",        BLK_FMT_LINE},
        {"strings",     BLK_FMT_STRING},
        {"string",      BLK_FMT_STRING},
        {"str",         BLK_FMT_STRING},
        {"od_style",    BLK_FMT_OD_STYLE},
        {"odstyle",     BLK_FMT_OD_STYLE},
        {"od",          BLK_FMT_OD_STYLE},
        {"hd_style",    BLK_FMT_HD_STYLE},
        {"hdstyle",     BLK_FMT_HD_STYLE},
        {"hd",          BLK_FMT_HD_STYLE},
        {"default",     BLK_FMT_HD_STYLE},
        {NULL,          0}
};


/*
 * Possible ctrl_d styles
 */
STATIC NAMETYPE ctrl_d[] = {
        {"virgin_eof",  CTRL_D_VIRGIN_EOF},
        {"virgineof",   CTRL_D_VIRGIN_EOF},
        {"virgin",      CTRL_D_VIRGIN_EOF},
        {"default",     CTRL_D_VIRGIN_EOF},
        {"never_eof",   CTRL_D_NEVER_EOF},
        {"nevereof",    CTRL_D_NEVER_EOF},
        {"never",       CTRL_D_NEVER_EOF},
        {"empty_eof",   CTRL_D_EMPTY_EOF},
        {"emptyeof",    CTRL_D_EMPTY_EOF},
        {"empty",       CTRL_D_EMPTY_EOF},
        {NULL,          0}
};


/*
 * Possible binary config state values
 */
#define true_STRING     "true"
#define false_STRING    "false"
STATIC NAMETYPE truth[] = {
        {true_STRING,   true},
        {"t",           true},
        {"on",          true},
        {"yes",         true},
        {"y",           true},
        {"set",         true},
        {"1",           true},
        {false_STRING,  false},
        {"f",           false},
        {"off",         false},
        {"no",          false},
        {"n",           false},
        {"unset",       false},
        {"0",           false},
        {NULL,          0}
};


/*
 * declare static functions
 */
S_FUNC long lookup_long(NAMETYPE *set, char *name);
S_FUNC char *lookup_name(NAMETYPE *set, long val);
S_FUNC int getlen(VALUE *vp, LEN *lp);


/*
 * Given a string value which represents a configuration name, return
 * the configuration type for that string.  Returns negative type if
 * the string is unknown.
 *
 * given:
 *      name                    configuration name
 */
int
configtype(char *name)
{
        NAMETYPE *cp;           /* current config pointer */

        for (cp = configs; cp->name; cp++) {
                if (strcmp(cp->name, name) == 0)
                        return cp->type;
        }
        return -1;
}


/*
 * lookup_long - given a name and a NAMETYPE, return the int
 *
 * given:
 *      set             the NAMESET array of name/int pairs
 *      name            mode name
 *
 * returns:
 *      numeric value of the name or -1 if not found
 */
S_FUNC long
lookup_long(NAMETYPE *set, char *name)
{
        NAMETYPE *cp;           /* current config pointer */

        for (cp = set; cp->name; cp++) {
                if (strcmp(cp->name, name) == 0)
                        return cp->type;
        }
        return -1;
}


/*
 * lookup_name - given numeric value and a NAMETYPE, return the name
 *
 * given:
 *      set             the NAMESET array of name/int pairs
 *      val             numeric value to lookup
 *
 * returns:
 *      name of the value found of NULL
 */
S_FUNC char *
lookup_name(NAMETYPE *set, long val)
{
        NAMETYPE *cp;           /* current config pointer */

        for (cp = set; cp->name; cp++) {
                if (val == cp->type)
                        return cp->name;
        }
        return NULL;
}


/*
 * Check whether VALUE at vp is a LEN (32-bit signed integer) and if so,
 * copy that integer to lp.
 *
 * Return: 1 ==> not an integer, 2 ==> int > 2^31, 0 ==> OK, -1 ==> error
 */

S_FUNC int
getlen(VALUE *vp, LEN *lp)
{
        if (vp->v_type != V_NUM || !qisint(vp->v_num))
                return 1;
        if (zge31b(vp->v_num->num))
                return 2;
        *lp = ztoi(vp->v_num->num);
        if (*lp < 0)
                return -1;
        return 0;
}


/*
 * Set the specified configuration type to the specified value.
 * An error is generated if the type number or value is illegal.
 */
void
setconfig(int type, VALUE *vp)
{
        NUMBER *q;
        CONFIG *newconf;        /* new configuration to set */
        long temp;
        LEN len = 0;
        char *p;

        switch (type) {
        case CONFIG_ALL:
                newconf = NULL; /* firewall */
                if (vp->v_type == V_STR) {
                        if (strcmp(vp->v_str->s_str, "oldstd") == 0) {
                                newconf = &oldstd;
                        } else if (strcmp(vp->v_str->s_str, "newstd") == 0) {
                                newconf = &newstd;
                        } else {
                                math_error("CONFIG alias not oldstd or newstd");
                                not_reached();
                        }
                } else if (vp->v_type != V_CONFIG) {
                        math_error("non-CONFIG for all");
                        not_reached();
                } else {
                        newconf = vp->v_config;
                }

                /* free the current configuration */
                config_free(conf);

                /* set the new configuration */
                conf = config_copy(newconf);
                break;

        case CONFIG_TRACE:
                if (vp->v_type != V_NUM) {
                        math_error("Non-numeric for trace");
                        not_reached();
                }
                q = vp->v_num;
                temp = qtoi(q);
                if (qisfrac(q) || !zistiny(q->num) ||
                        ((unsigned long) temp > TRACE_MAX)) {
                                math_error("Bad trace value");
                                not_reached();
                }
                conf->traceflags = (FLAG)temp;
                break;

        case CONFIG_DISPLAY:
                if (getlen(vp, &len)) {
                        math_error("Bad value for display");
                        not_reached();
                }
                math_setdigits(len);
                break;

        case CONFIG_MODE:
                if (vp->v_type != V_STR) {
                        math_error("Non-string for mode");
                        not_reached();
                }
                temp = lookup_long(modes, vp->v_str->s_str);
                if (temp < 0) {
                        math_error("Unknown mode \"%s\"", vp->v_str->s_str);
                        not_reached();
                }
                math_setmode((int) temp);
                break;

        case CONFIG_MODE2:
                if (vp->v_type != V_STR) {
                        math_error("Non-string for mode");
                        not_reached();
                }
                temp = lookup_long(modes, vp->v_str->s_str);
                if (temp < 0) {
                        math_error("Unknown mode \"%s\"", vp->v_str->s_str);
                        not_reached();
                }
                math_setmode2((int) temp);
                break;

        case CONFIG_EPSILON:
                if (vp->v_type != V_NUM) {
                        math_error("Non-numeric for epsilon");
                        not_reached();
                }
                setepsilon(vp->v_num);
                break;

        case CONFIG_MAXPRINT:
                if (getlen(vp, &len)) {
                        math_error("Bad value for maxprint");
                        not_reached();
                }
                conf->maxprint = len;
                break;

        case CONFIG_MUL2:
                if (getlen(vp, &len) || len < 0 || len == 1) {
                        math_error("Bad value for mul2");
                        not_reached();
                }
                if (len == 0)
                        len = MUL_ALG2;
                conf->mul2 = len;
                break;

        case CONFIG_SQ2:
                if (getlen(vp, &len) || len < 0 || len == 1) {
                        math_error("Bad value for sq2");
                        not_reached();
                }
                if (len == 0)
                        len = SQ_ALG2;
                conf->sq2 = len;
                break;

        case CONFIG_POW2:
                if (getlen(vp, &len) || len < 0 || len == 1) {
                        math_error("Bad value for pow2");
                        not_reached();
                }
                if (len == 0)
                        len = POW_ALG2;
                conf->pow2 = len;
                break;

        case CONFIG_REDC2:
                if (getlen(vp, &len) || len < 0 || len == 1) {
                        math_error("Bad value for redc2");
                        not_reached();
                }
                if (len == 0)
                        len = REDC_ALG2;
                conf->redc2 = len;
                break;

        case CONFIG_TILDE:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->tilde_ok = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value for tilde");
                                not_reached();
                        }
                        conf->tilde_ok = (int)temp;
                }
                break;

        case CONFIG_TILDE_SPACE:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->tilde_space = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value for tilde_space");
                                not_reached();
                        }
                        conf->tilde_space = (int)temp;
                }
                break;

        case CONFIG_FRACTION_SPACE:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->fraction_space = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value for fraction_space");
                                not_reached();
                        }
                        conf->fraction_space = (int)temp;
                }
                break;

        case CONFIG_COMPLEX_SPACE:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->complex_space = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value for complex_space");
                                not_reached();
                        }
                        conf->complex_space = (int)temp;
                }
                break;

        case CONFIG_TAB:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->tab_ok = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value for tab");
                                not_reached();
                        }
                        conf->tab_ok = (int)temp;
                }
                break;

        case CONFIG_QUOMOD:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for quomod");
                        not_reached();
                }
                conf->quomod = len;
                break;

        case CONFIG_QUO:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for quo");
                        not_reached();
                }
                conf->quo = len;
                break;

        case CONFIG_MOD:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for mod");
                        not_reached();
                }
                conf->mod = len;
                break;

        case CONFIG_SQRT:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for sqrt");
                        not_reached();
                }
                conf->sqrt = len;
                break;

        case CONFIG_APPR:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for appr");
                        not_reached();
                }
                conf->appr = len;
                break;

        case CONFIG_CFAPPR:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for cfappr");
                        not_reached();
                }
                conf->cfappr = len;
                break;

        case CONFIG_CFSIM:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for cfsim");
                        not_reached();
                }
                conf->cfsim = len;
                break;

        case CONFIG_OUTROUND:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for outround");
                        not_reached();
                }
                conf->outround = len;
                break;

        case CONFIG_ROUND:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for round");
                        not_reached();
                }
                conf->round = len;
                break;

        case CONFIG_TRIGROUND:
                if (getlen(vp, &len)) {
                        math_error("Illegal value for triground");
                        not_reached();
                }
                conf->triground = len;
                break;

        case CONFIG_LEADZERO:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->leadzero = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value for leadzero");
                                not_reached();
                        }
                        conf->leadzero = (int)temp;
                }
                break;

        case CONFIG_FULLZERO:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->fullzero = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) { {
                                math_error("Illegal truth value for fullzero");
                                not_reached();
                        }
                        }
                        conf->fullzero = (int)temp;
                }
                break;

        case CONFIG_MAXSCAN:
                if (vp->v_type != V_NUM) {
                        math_error("Non-numeric for maxscancount");
                        not_reached();
                }
                q = vp->v_num;
                temp = qtoi(q);
                if (qisfrac(q) || qisneg(q) || !zistiny(q->num))
                        temp = -1;
                if (temp < 0) {
                        math_error("Maxscan value is out of range");
                        not_reached();
                }
                conf->maxscancount = temp;
                break;

        case CONFIG_PROMPT:
                if (vp->v_type != V_STR) {
                        math_error("Non-string for prompt");
                        not_reached();
                }
                p = (char *)malloc(vp->v_str->s_len + 1);
                if (p == NULL) {
                        math_error("Cannot duplicate new prompt");
                        not_reached();
                }
                strlcpy(p, vp->v_str->s_str, vp->v_str->s_len + 1);
                free(conf->prompt1);
                conf->prompt1 = p;
                break;

        case CONFIG_MORE:
                if (vp->v_type != V_STR) {
                        math_error("Non-string for more prompt");
                        not_reached();
                }
                p = (char *)malloc(vp->v_str->s_len + 1);
                if (p == NULL) {
                        math_error("Cannot duplicate new more prompt");
                        not_reached();
                }
                strlcpy(p, vp->v_str->s_str, vp->v_str->s_len + 1);
                free(conf->prompt2);
                conf->prompt2 = p;
                break;

        case CONFIG_BLKMAXPRINT:
                if (vp->v_type != V_NUM) {
                        math_error("Non-numeric for blkmaxprint");
                        not_reached();
                }
                q = vp->v_num;
                temp = qtoi(q);
                if (qisfrac(q) || qisneg(q) || !zistiny(q->num))
                        temp = -1;
                if (temp < 0) {
                        math_error("Blkmaxprint value is out of range");
                        not_reached();
                }
                conf->blkmaxprint = temp;
                break;

        case CONFIG_BLKVERBOSE:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->blkverbose = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                            math_error("Illegal truth value for blkverbose");
                            not_reached();
                        }
                        conf->blkverbose = (int)temp;
                }
                break;

        case CONFIG_BLKBASE:
                if (vp->v_type != V_STR) {
                        math_error("Non-string for blkbase");
                        not_reached();
                }
                temp = lookup_long(blk_base, vp->v_str->s_str);
                if (temp < 0) {
                        math_error("Unknown mode \"%s\" for blkbase",
                            vp->v_str->s_str);
                        not_reached();
                }
                conf->blkbase = temp;
                break;

        case CONFIG_BLKFMT:
                if (vp->v_type != V_STR) {
                        math_error("Non-string for blkfmt");
                        not_reached();
                }
                temp = lookup_long(blk_fmt, vp->v_str->s_str);
                if (temp < 0) {
                        math_error("Unknown mode \"%s\" for blkfmt",
                            vp->v_str->s_str);
                        not_reached();
                }
                conf->blkfmt = temp;
                break;

        case CONFIG_CALC_DEBUG:
                if (vp->v_type != V_NUM) {
                        math_error("Non numeric for calc_debug");
                        not_reached();
                }
                q = vp->v_num;
                temp = qtoi(q);
                if (qisfrac(q) || !zistiny(q->num)) {
                        math_error("Illegal calc_debug parameter value");
                        not_reached();
                }
                conf->calc_debug = temp;
                break;

        case CONFIG_RESOURCE_DEBUG:
                if (vp->v_type != V_NUM) {
                        math_error("Non numeric for resource_debug");
                        not_reached();
                }
                q = vp->v_num;
                temp = qtoi(q);
                if (qisfrac(q) || !zistiny(q->num)) {
                        math_error("Illegal resource_debug parameter value");
                        not_reached();
                }
                conf->resource_debug = temp;
                break;

        case CONFIG_USER_DEBUG:
                if (vp->v_type != V_NUM) {
                        math_error("Non numeric for user_debug");
                        not_reached();
                }
                q = vp->v_num;
                temp = qtoi(q);
                if (qisfrac(q) || !zistiny(q->num)) {
                        math_error("Illegal user_debug parameter value");
                        not_reached();
                }
                conf->user_debug = temp;
                break;

        case CONFIG_VERBOSE_QUIT:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->verbose_quit = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value "
                                           "for verbose_quit");
                                not_reached();
                        }
                        conf->verbose_quit = (int)temp;
                }
                break;

        case CONFIG_CTRL_D:
                if (vp->v_type != V_STR) {
                        math_error("Non-string for ctrl_d");
                        not_reached();
                }
                temp = lookup_long(ctrl_d, vp->v_str->s_str);
                if (temp < 0) {
                        math_error("Unknown mode \"%s\" for ctrl_d",
                            vp->v_str->s_str);
                        not_reached();
                }
                conf->ctrl_d = temp;
                break;

        case CONFIG_PROGRAM:
                math_error("The program config parameter is read-only");
                not_reached();
                break;

        case CONFIG_BASENAME:
                math_error("The basename config parameter is read-only");
                not_reached();
                break;

        case CONFIG_WINDOWS:
                math_error("The windows config parameter is read-only");
                not_reached();
                break;

        case CONFIG_CYGWIN:
                math_error("The cygwin config parameter is read-only");
                not_reached();
                break;

        case CONFIG_COMPILE_CUSTOM:
                math_error("The custom config parameter is read-only");
                not_reached();
                break;

        case CONFIG_ALLOW_CUSTOM:
                math_error("The allow_custom config parameter is read-only");
                not_reached();
                break;

        case CONFIG_VERSION:
                math_error("The version config parameter is read-only");
                not_reached();
                break;

        case CONFIG_BASEB:
                math_error("The baseb config parameter is read-only");
                not_reached();
                break;

        case CONFIG_REDECL_WARN:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->redecl_warn = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value for "
                                           "redecl_warn");
                                not_reached();
                        }
                        conf->redecl_warn = (int)temp;
                }
                break;

        case CONFIG_DUPVAR_WARN:
                if (vp->v_type == V_NUM) {
                        q = vp->v_num;
                        conf->dupvar_warn = !qiszero(q);
                } else if (vp->v_type == V_STR) {
                        temp = lookup_long(truth, vp->v_str->s_str);
                        if (temp < 0) {
                                math_error("Illegal truth value for "
                                           "dupvar_warn");
                                not_reached();
                        }
                        conf->dupvar_warn = (int)temp;
                }
                break;

        case CONFIG_HZ:
                math_error("The clock tick rate config parameter is read-only");
                not_reached();
                break;

        default:
                math_error("Setting illegal config parameter");
                not_reached();
                break;
        }
}


/*
 * config_copy - copy the configuration from one value to another
 *
 * given:
 *      src     copy this configuration
 *
 * returns:
 *      pointer to the configuration copy
 */
CONFIG *
config_copy(CONFIG *src)
{
        CONFIG *dest;           /* the new CONFIG to return */

        /*
         * firewall
         */
        if (src == NULL || src->epsilon == NULL || src->prompt1 == NULL ||
            src->prompt2 == NULL) {
                math_error("bad CONFIG value");
                not_reached();
        }

        /*
         * malloc the storage
         */
        dest = (CONFIG *)malloc(sizeof(CONFIG));
        if (dest == NULL) {
                math_error("malloc of CONFIG failed");
                not_reached();
        }

        /*
         * copy over the values
         */
        memcpy((void *)dest, (void *)src, sizeof(CONFIG));

        /*
         * clone the pointer values
         */
        dest->epsilon = qlink(src->epsilon);
        dest->prompt1 = strdup(src->prompt1);
        dest->prompt2 = strdup(src->prompt2);
        if (src->program == NULL) {
                dest->program = strdup(program);
        } else {
                dest->program = strdup(src->program);
        }
        if (src->base_name == NULL) {
                dest->base_name = strdup(base_name);
        } else {
                dest->base_name = strdup(src->base_name);
        }
        /* NOTE: allow_custom points to a global variable, so do not clone it */
        if (src->version == NULL) {
                dest->version = strdup(version());
        } else {
                dest->version = strdup(src->version);
        }

        /*
         * return the new value
         */
        return dest;
}


/*
 * config_free - free a CONFIG value
 *
 * given:
 *      cfg             the CONFIG value to free
 */
void
config_free(CONFIG *cfg)
{
        /*
         * firewall
         */
        if (cfg == NULL) {
                return;
        }

        /*
         * free pointer values
         */
        if (cfg->epsilon != NULL) {
                qfree(cfg->epsilon);
        }
        if (cfg->prompt1 != NULL) {
                free(cfg->prompt1);
        }
        if (cfg->prompt2 != NULL) {
                free(cfg->prompt2);
        }
        if (cfg->program != NULL) {
                free(cfg->program);
        }
        if (cfg->base_name != NULL) {
                free(cfg->base_name);
        }
        /* NOTE: allow_custom points to a global variable, so do not free it */
        if (cfg->version != NULL) {
                free(cfg->version);
        }

        /*
         * free the CONFIG value itself
         */
        free(cfg);
        return;
}


/*
 * config_value - return a CONFIG element as a value
 *
 * given:
 *      cfg             CONFIG from which an element will be returned
 *      type            the type of CONFIG element to print
 *      ret             where to return the value
 *
 * returns:
 *      ret points to the VALUE returned
 */
void
config_value(CONFIG *cfg, int type, VALUE *vp)
{
        long i=0;
        char *p;

        /*
         * firewall
         */
        if (cfg == NULL || cfg->epsilon == NULL || cfg->prompt1 == NULL ||
            cfg->prompt2 == NULL) {
                math_error("bad CONFIG value");
                not_reached();
        }

        /*
         * convert element to value
         */
        vp->v_type = V_NUM;
        vp->v_subtype = V_NOSUBTYPE;
        switch (type) {
        case CONFIG_ALL:
                vp->v_type = V_CONFIG;
                vp->v_config = config_copy(conf);
                return;

        case CONFIG_TRACE:
                i = cfg->traceflags;
                break;

        case CONFIG_DISPLAY:
                i = cfg->outdigits;
                break;

        case CONFIG_MODE:
                vp->v_type = V_STR;
                p = lookup_name(modes, cfg->outmode);
                if (p == NULL) {
                        math_error("invalid output mode: %d", cfg->outmode);
                        not_reached();
                }
                vp->v_str = makenewstring(p);
                return;

        case CONFIG_MODE2:
                vp->v_type = V_STR;
                p = lookup_name(modes, cfg->outmode2);
                if (p == NULL) {
                        math_error("invalid secondary output mode: %d",
                                   cfg->outmode2);
                        not_reached();
                }
                vp->v_str = makenewstring(p);
                return;

        case CONFIG_EPSILON:
                vp->v_num = qlink(cfg->epsilon);
                return;

        case CONFIG_MAXPRINT:
                i = cfg->maxprint;
                break;

        case CONFIG_MUL2:
                i = cfg->mul2;
                break;

        case CONFIG_SQ2:
                i = cfg->sq2;
                break;

        case CONFIG_POW2:
                i = cfg->pow2;
                break;

        case CONFIG_REDC2:
                i = cfg->redc2;
                break;

        case CONFIG_TILDE:
                i = (cfg->tilde_ok ? 1 : 0);
                break;

        case CONFIG_TILDE_SPACE:
                i = (cfg->tilde_space ? 1 : 0);
                break;

        case CONFIG_FRACTION_SPACE:
                i = (cfg->fraction_space ? 1 : 0);
                break;

        case CONFIG_COMPLEX_SPACE:
                i = (cfg->complex_space ? 1 : 0);
                break;

        case CONFIG_TAB:
                i = (cfg->tab_ok ? 1 : 0);
                break;

        case CONFIG_QUOMOD:
                i = cfg->quomod;
                break;

        case CONFIG_QUO:
                i = cfg->quo;
                break;

        case CONFIG_MOD:
                i = cfg->mod;
                break;

        case CONFIG_SQRT:
                i = cfg->sqrt;
                break;

        case CONFIG_APPR:
                i = cfg->appr;
                break;

        case CONFIG_CFAPPR:
                i = cfg->cfappr;
                break;

        case CONFIG_CFSIM:
                i = cfg->cfsim;
                break;

        case CONFIG_OUTROUND:
                i = cfg->outround;
                break;

        case CONFIG_ROUND:
                i = cfg->round;
                break;

        case CONFIG_TRIGROUND:
                i = cfg->triground;
                break;

        case CONFIG_LEADZERO:
                i = (cfg->leadzero ? 1 : 0);
                break;

        case CONFIG_FULLZERO:
                i = (cfg->fullzero ? 1 : 0);
                break;

        case CONFIG_MAXSCAN:
                i = cfg->maxscancount;
                break;

        case CONFIG_PROMPT:
                vp->v_type = V_STR;
                vp->v_str = makenewstring(cfg->prompt1);
                return;

        case CONFIG_MORE:
                vp->v_type = V_STR;
                vp->v_str = makenewstring(cfg->prompt2);
                return;

        case CONFIG_BLKMAXPRINT:
                i = cfg->blkmaxprint;
                break;

        case CONFIG_BLKVERBOSE:
                i = (cfg->blkverbose ? 1 : 0);
                break;

        case CONFIG_BLKBASE:
                vp->v_type = V_STR;
                p = lookup_name(blk_base, cfg->blkbase);
                if (p == NULL) {
                        math_error("invalid block base: %d", cfg->blkbase);
                        not_reached();
                }
                vp->v_str = makenewstring(p);
                return;

        case CONFIG_BLKFMT:
                vp->v_type = V_STR;
                p = lookup_name(blk_fmt, cfg->blkfmt);
                if (p == NULL) {
                        math_error("invalid block format: %d", cfg->blkfmt);
                        not_reached();
                }
                vp->v_str = makenewstring(p);
                return;

        case CONFIG_CALC_DEBUG:
                i = cfg->calc_debug;
                break;

        case CONFIG_RESOURCE_DEBUG:
                i = cfg->resource_debug;
                break;

        case CONFIG_USER_DEBUG:
                i = cfg->user_debug;
                break;

        case CONFIG_VERBOSE_QUIT:
                i = (cfg->verbose_quit ? 1 : 0);
                break;

        case CONFIG_CTRL_D:
                vp->v_type = V_STR;
                p = lookup_name(ctrl_d, cfg->ctrl_d);
                if (p == NULL) {
                        math_error("invalid Control-D: %d", cfg->ctrl_d);
                        not_reached();
                }
                vp->v_str = makenewstring(p);
                return;

        case CONFIG_PROGRAM:
                vp->v_type = V_STR;
                if (cfg->base_name == NULL) {
                        vp->v_str = makestring(strdup(program));
                } else {
                        vp->v_str = makenewstring(cfg->program);
                }
                return;

        case CONFIG_BASENAME:
                vp->v_type = V_STR;
                if (cfg->base_name == NULL) {
                        vp->v_str = makestring(strdup(base_name));
                } else {
                        vp->v_str = makenewstring(cfg->base_name);
                }
                return;

        case CONFIG_WINDOWS:
                i = (cfg->windows ? 1 : 0);
                break;

        case CONFIG_CYGWIN:
                i = (cfg->cygwin ? 1 : 0);
                break;

        case CONFIG_COMPILE_CUSTOM:
                i = (cfg->compile_custom ? 1 : 0);
                break;

        case CONFIG_ALLOW_CUSTOM:
                /* firewall */
                if (cfg->allow_custom == NULL) {
                        cfg->allow_custom = &allow_custom;
                }
                i = (*(cfg->allow_custom) ? 1 : 0);
                break;

        case CONFIG_VERSION:
                vp->v_type = V_STR;
                if (cfg->version == NULL) {
                        vp->v_str = makestring(strdup(version()));
                } else {
                        vp->v_str = makenewstring(cfg->version);
                }
                return;

        case CONFIG_BASEB:
                i = BASEB;
                break;

        case CONFIG_REDECL_WARN:
                i = (cfg->redecl_warn ? 1 : 0);
                break;

        case CONFIG_DUPVAR_WARN:
                i = (cfg->dupvar_warn ? 1 : 0);
                break;

        case CONFIG_HZ:
                i = CALC_HZ;
                break;

        default:
                math_error("Getting illegal CONFIG element");
                not_reached();
        }

        /*
         * if we got this far, we have a V_NUM in i
         */
        vp->v_num = itoq(i);
        return;
}


/*
 * config_cmp - compare two CONFIG states
 *
 * given:
 *      cfg1 - 1st CONFIG to compare
 *      cfg2 - 2nd CONFIG to compare
 *
 * return:
 *      true if configurations differ
 */
bool
config_cmp(CONFIG *cfg1, CONFIG *cfg2)
{
        /*
         * firewall
         */
        if (cfg1 == NULL || cfg1->epsilon == NULL || cfg1->prompt1 == NULL ||
            cfg1->prompt2 == NULL) {
                math_error("CONFIG #1 value is invalid");
                not_reached();
        }
        if (cfg2 == NULL || cfg2->epsilon == NULL || cfg2->prompt1 == NULL ||
            cfg2->prompt2 == NULL) {
                math_error("CONFIG #2 value is invalid");
                not_reached();
        }

        /*
         * compare
         */
        return cfg1->traceflags != cfg2->traceflags ||
               cfg1->outdigits != cfg2->outdigits ||
               cfg1->outmode != cfg2->outmode ||
               cfg1->outmode2 != cfg2->outmode2 ||
               qcmp(cfg1->epsilon, cfg2->epsilon) ||
               cfg1->epsilonprec != cfg2->epsilonprec ||
               cfg1->maxprint != cfg2->maxprint ||
               cfg1->mul2 != cfg2->mul2 ||
               cfg1->sq2 != cfg2->sq2 ||
               cfg1->pow2 != cfg2->pow2 ||
               cfg1->redc2 != cfg2->redc2 ||
               cfg1->tilde_ok != cfg2->tilde_ok ||
               cfg1->tab_ok != cfg2->tab_ok ||
               cfg1->quomod != cfg2->quomod ||
               cfg1->quo != cfg2->quo ||
               cfg1->mod != cfg2->mod ||
               cfg1->sqrt != cfg2->sqrt ||
               cfg1->appr != cfg2->appr ||
               cfg1->cfappr != cfg2->cfappr ||
               cfg1->cfsim != cfg2->cfsim ||
               cfg1->outround != cfg2->outround ||
               cfg1->round != cfg2->round ||
               cfg1->triground != cfg2->triground ||
               cfg1->leadzero != cfg2->leadzero ||
               cfg1->fullzero != cfg2->fullzero ||
               cfg1->maxscancount != cfg2->maxscancount ||
               strcmp(cfg1->prompt1, cfg2->prompt1) != 0 ||
               strcmp(cfg1->prompt2, cfg2->prompt2) != 0 ||
               cfg1->blkmaxprint != cfg2->blkmaxprint ||
               cfg1->blkverbose != cfg2->blkverbose ||
               cfg1->blkbase != cfg2->blkbase ||
               cfg1->blkfmt != cfg2->blkfmt ||
               cfg1->calc_debug != cfg2->calc_debug ||
               cfg1->resource_debug != cfg2->resource_debug ||
               cfg1->user_debug != cfg2->user_debug ||
               cfg1->verbose_quit != cfg2->verbose_quit ||
               cfg1->ctrl_d != cfg2->ctrl_d ||

               (cfg1->program == NULL && cfg2->program != NULL) ||
               (cfg1->program != NULL && cfg2->program == NULL) ||
               (cfg1->program != NULL && cfg2->program != NULL &&
                strcmp(cfg1->program, cfg2->program) != 0) ||

               (cfg1->base_name == NULL && cfg2->base_name != NULL) ||
               (cfg1->base_name != NULL && cfg2->base_name == NULL) ||
               (cfg1->base_name != NULL && cfg2->base_name != NULL &&
                strcmp(cfg1->base_name, cfg2->base_name) != 0) ||

               cfg1->windows != cfg2->windows ||

               cfg1->cygwin != cfg2->cygwin ||

               cfg1->compile_custom != cfg2->compile_custom ||

               (cfg1->allow_custom == NULL && cfg2->allow_custom != NULL) ||
               (cfg1->allow_custom != NULL && cfg2->allow_custom == NULL) ||
               (cfg1->allow_custom != NULL && cfg2->allow_custom != NULL &&
                *(cfg1->allow_custom) != *(cfg2->allow_custom)) ||

               (cfg1->version == NULL && cfg2->version != NULL) ||
               (cfg1->version != NULL && cfg2->version == NULL) ||
               (cfg1->version != NULL && cfg2->version != NULL &&
                strcmp(cfg1->version, cfg2->version) != 0) ||

               cfg1->baseb != cfg2->baseb;
}
