/*
 * config - configuration routines
 *
 * Copyright (C) 1999-2007,2021-2023,2026  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/
 */

/*
 * important <system> header includes
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>
#include <stdint.h>
#include <stdbool.h>

/*
 * conditional <system> head includes
 */
#include "value.h"
#include "calc.h"
#include "token.h"
#include "custom.h"
#include "strl.h"
#include "attribute.h"
#include "errtbl.h"

#include "banned.h" /* include after all other 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
 */
static long lookup_long(NAMETYPE *set, char *name);
static char *lookup_name(NAMETYPE *set, long val);
static 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
 */
static 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
 */
static 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
 */

static 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 *)calloc(vp->v_str->s_len + 1, 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 *)calloc(vp->v_str->s_len + 1, 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 *)calloc(1, 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;
}
