/*
 * symbol - global and local symbol routines
 *
 * Copyright (C) 1999-2007,2021-2023,2026  David I. Bell and Ernest Bowen
 *
 * 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:   1990/02/15 01:48:23
 * File existed as early as:    before 1990
 *
 * 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 <stdint.h>
#include <stdbool.h>

/*
 * calc local src includes
 */
#include "value.h"
#include "calc.h"
#include "token.h"
#include "symbol.h"
#include "opcodes.h"
#include "label.h"
#include "func.h"
#include "attribute.h"
#include "errtbl.h"

#include "banned.h" /* include after all other includes */

#define HASHSIZE 37 /* size of hash table */

extern FILE *f_open(char *name, char *mode);

static int filescope;                /* file scope level for static variables */
static int funcscope;                /* function scope level for static variables */
static STRINGHEAD localnames;        /* list of local variable names */
static STRINGHEAD globalnames;       /* list of global variable names */
static STRINGHEAD paramnames;        /* list of parameter variable names */
static GLOBAL *globalhash[HASHSIZE]; /* hash table for globals */

static void printtype(VALUE *);
static void unscope(void);
static void addstatic(GLOBAL *);
static long staticcount = 0;
static long staticavail = 0;
static GLOBAL **statictable;

/*
 * Hash a symbol name so we can find it in the hash table.
 * Args are the symbol name and the symbol name size.
 */
#define HASHSYM(n, s) ((unsigned)((n)[0] * 123 + (n)[s - 1] * 135 + (s) * 157) % HASHSIZE)

/*
 * Initialize the global symbol table.
 */
void
initglobals(void)
{
    int i; /* index counter */

    for (i = 0; i < HASHSIZE; i++) {
        globalhash[i] = NULL;
    }
    initstr(&globalnames);
    filescope = SCOPE_STATIC;
    funcscope = 0;
}

/*
 * Define a possibly new global variable which may or may not be static.
 * If it did not already exist, it is created with a value of zero.
 * The address of the global symbol structure is returned.
 *
 * given:
 *      name            name of global variable
 *      isstatic        true if symbol is static
 */
GLOBAL *
addglobal(char *name, bool isstatic)
{
    GLOBAL *sp;       /* current symbol pointer */
    GLOBAL **hp;      /* hash table head address */
    size_t len;       /* length of string */
    int newfilescope; /* file scope being looked for */
    int newfuncscope; /* function scope being looked for */

    newfilescope = SCOPE_GLOBAL;
    newfuncscope = 0;
    if (isstatic) {
        newfilescope = filescope;
        newfuncscope = funcscope;
    }
    len = strlen(name);
    if (len <= 0) {
        return NULL;
    }
    hp = &globalhash[HASHSYM(name, len)];
    for (sp = *hp; sp; sp = sp->g_next) {
        if ((sp->g_len == len) && (strncmp(sp->g_name, name, len + 1) == 0) && (sp->g_filescope == newfilescope) &&
            (sp->g_funcscope == newfuncscope)) {
            return sp;
        }
    }
    sp = (GLOBAL *)calloc(1, sizeof(GLOBAL));
    if (sp == NULL) {
        return sp;
    }
    sp->g_name = addstr(&globalnames, name);
    sp->g_len = len;
    sp->g_filescope = newfilescope;
    sp->g_funcscope = newfuncscope;
    sp->g_value.v_num = qlink(&_qzero_);
    sp->g_value.v_type = V_NUM;
    sp->g_value.v_subtype = V_NOSUBTYPE;
    sp->g_next = *hp;
    *hp = sp;
    return sp;
}

/*
 * Look for the highest-scope global variable with a specified name.
 * Returns the address of the variable or NULL according as the search
 * succeeds or fails.
 */
GLOBAL *
findglobal(char *name)
{
    GLOBAL *sp;     /* current symbol pointer */
    GLOBAL *bestsp; /* found symbol with highest scope */
    size_t len;     /* length of string */

    bestsp = NULL;
    len = strlen(name);
    for (sp = globalhash[HASHSYM(name, len)]; sp != NULL; sp = sp->g_next) {
        if ((sp->g_len == len) && (strncmp(sp->g_name, name, len + 1) == 0)) {
            if ((bestsp == NULL) || (sp->g_filescope > bestsp->g_filescope) || (sp->g_funcscope > bestsp->g_funcscope)) {
                bestsp = sp;
            }
        }
    }
    return bestsp;
}

/*
 * Return the name of a global variable given its address.
 *
 * given:
 *      sp              address of global pointer
 */
char *
globalname(GLOBAL *sp)
{
    if (sp) {
        return sp->g_name;
    }
    return "";
}

/*
 * Show the value of all real-number valued global variables, displaying
 * only the head and tail of very large numerators and denominators.
 * Static variables are not included.
 */
void
showglobals(void)
{
    GLOBAL **hp;         /* hash table head address */
    register GLOBAL *sp; /* current global symbol pointer */
    long count;          /* number of global variables shown */

    count = 0;
    for (hp = &globalhash[HASHSIZE - 1]; hp >= globalhash; hp--) {
        for (sp = *hp; sp; sp = sp->g_next) {
            if (sp->g_value.v_type != V_NUM) {
                continue;
            }
            if (count++ == 0) {
                printf("\nName    Digits           Value\n");
                printf("----    ------           -----\n");
            }
            printf("%-8s", sp->g_name);
            if (sp->g_filescope != SCOPE_GLOBAL) {
                printf(" (s)");
            }
            fitprint(sp->g_value.v_num, 50);
            printf("\n");
        }
    }
    if (count == 0) {
        printf("No real-valued global variables\n");
    }
    putchar('\n');
}

void
showallglobals(void)
{
    GLOBAL **hp;         /* hash table head address */
    register GLOBAL *sp; /* current global symbol pointer */
    long count;          /* number of global variables shown */

    count = 0;
    for (hp = &globalhash[HASHSIZE - 1]; hp >= globalhash; hp--) {
        for (sp = *hp; sp; sp = sp->g_next) {
            if (count++ == 0) {
                printf("\nName    Level    Type\n");
                printf("----    -----    -----\n");
            }
            printf("%-8s%4d     ", sp->g_name, sp->g_filescope);
            printtype(&sp->g_value);
            printf("\n");
        }
    }
    if (count > 0) {
        printf("\nNumber: %ld\n", count);
    } else {
        printf("No global variables\n");
    }
}

static void
printtype(VALUE *vp)
{
    int type;
    char *s;
    char *errsym;
    bool alloced;

    type = vp->v_type;
    if (type < 0) {
        errsym = errnum_2_errsym(-type, &alloced);
        if (errsym == NULL) {
            printf("Error %d", -type);
        } else {
            printf("Error %s", errsym);
            if (alloced == true) {
                free(errsym);
            }
        }
        return;
    }
    switch (type) {
    case V_NUM:
        printf("real = ");
        fitprint(vp->v_num, 32);
        return;
    case V_COM:
        printf("complex = ");
        fitprint(vp->v_com->real, 8);
        if (!vp->v_com->imag->num.sign) {
            printf("+");
        }
        fitprint(vp->v_com->imag, 8);
        printf("i");
        return;
    case V_STR:
        printf("string = \"");
        fitstring(vp->v_str->s_str, vp->v_str->s_len, 50);
        printf("\"");
        return;
    case V_NULL:
        s = "null";
        break;
    case V_MAT:
        s = "matrix";
        break;
    case V_LIST:
        s = "list";
        break;
    case V_ASSOC:
        s = "association";
        break;
    case V_OBJ:
        printf("%s ", objtypename(vp->v_obj->o_actions->oa_index));
        s = "object";
        break;
    case V_FILE:
        s = "file id";
        break;
    case V_RAND:
        s = "subtractive 100 random state";
        break;
    case V_RANDOM:
        s = "Blum random state";
        break;
    case V_CONFIG:
        s = "config state";
        break;
    case V_HASH:
        s = "hash state";
        break;
    case V_BLOCK:
        s = "unnamed block";
        break;
    case V_NBLOCK:
        s = "named block";
        break;
    case V_VPTR:
        s = "value pointer";
        break;
    case V_OPTR:
        s = "octet pointer";
        break;
    case V_SPTR:
        s = "string pointer";
        break;
    case V_NPTR:
        s = "number pointer";
        break;
    default:
        s = "???";
        break;
    }
    printf("%s", s);
}

/*
 * Write all normal global variables to an output file.
 * Note: Currently only simple types are saved.
 * Returns nonzero on error.
 */
int
writeglobals(char *name)
{
    FILE *fp;
    GLOBAL **hp;         /* hash table head address */
    register GLOBAL *sp; /* current global symbol pointer */
    int savemode;        /* saved output mode */
    extern void math_setfp(FILE * fp);

    fp = f_open(name, "w");
    if (fp == NULL) {
        return 1;
    }
    math_setfp(fp);
    for (hp = &globalhash[HASHSIZE - 1]; hp >= globalhash; hp--) {
        for (sp = *hp; sp; sp = sp->g_next) {
            switch (sp->g_value.v_type) {
            case V_NUM:
            case V_COM:
            case V_STR:
                break;
            default:
                continue;
            }
            math_fmt("%s = ", sp->g_name);
            savemode = math_setmode(MODE_HEX);
            printvalue(&sp->g_value, PRINT_UNAMBIG);
            math_setmode(savemode);
            math_str(";\n");
        }
    }
    math_setfp(stdout);
    if (fclose(fp)) {
        return 1;
    }
    return 0;
}

/*
 * Free all non-null global and visible static variables
 */
void
freeglobals(void)
{
    GLOBAL **hp; /* hash table head address */
    GLOBAL *sp;  /* current global symbol pointer */

    /*
     * We prevent the hp pointer from walking behind globalhash
     * by stopping one short of the end and running the loop one
     * more time.
     *
     * We could stop the loop with just hp >= globalhash, but stopping
     * short and running the loop one last time manually helps make
     * code checkers such as insure happy.
     */
    for (hp = &globalhash[HASHSIZE - 1]; hp > globalhash; hp--) {
        for (sp = *hp; sp; sp = sp->g_next) {
            if (sp->g_value.v_type != V_NULL) {
                freevalue(&sp->g_value);
            }
        }
    }
    /* run the loop manually one last time */
    for (sp = *hp; sp; sp = sp->g_next) {
        if (sp->g_value.v_type != V_NULL) {
            freevalue(&sp->g_value);
        }
    }
}

/*
 * Free all invisible static variables
 */
void
freestatics(void)
{
    GLOBAL **stp;
    GLOBAL *sp;
    long count;

    stp = statictable;
    count = staticcount;
    while (count-- > 0) {
        sp = *stp++;
        freevalue(&sp->g_value);
    }
}

/*
 * Reset the file and function scope levels back to the original values.
 * This is called on errors to forget any static variables which were being
 * defined.
 */
void
resetscopes(void)
{
    filescope = SCOPE_STATIC;
    funcscope = 0;
    unscope();
}

/*
 * Enter a new file scope level so that newly defined static variables
 * will have the appropriate scope, and so that previously defined static
 * variables will temporarily be unaccessible.  This should only be called
 * when the function scope level is zero.
 */
void
enterfilescope(void)
{
    filescope++;
    funcscope = 0;
}

/*
 * Exit from a file scope level.  This deletes from the global symbol table
 * all of the static variables that were defined within this file scope level.
 * The function scope level is also reset to zero.
 */
void
exitfilescope(void)
{
    if (filescope > SCOPE_STATIC) {
        filescope--;
    }
    funcscope = 0;
    unscope();
}

/*
 * Enter a new function scope level within the current file scope level.
 * This allows newly defined static variables to override previously defined
 * static variables in the same file scope level.
 */
void
enterfuncscope(void)
{
    funcscope++;
}

/*
 * Exit from a function scope level.  This deletes static symbols which were
 * defined within the current function scope level, and makes previously
 * defined symbols with the same name within the same file scope level
 * accessible again.
 */
void
exitfuncscope(void)
{
    if (funcscope > 0) {
        funcscope--;
    }
    unscope();
}

/*
 * To end the scope of any static variable with identifier id when
 * id is being declared as global, or when id is declared as static and the
 * variable is at the same file and function level.
 */
void
endscope(char *name, bool isglobal)
{
    GLOBAL *sp;
    GLOBAL *prevsp;
    GLOBAL **hp;
    size_t len;

    len = strlen(name);
    prevsp = NULL;
    hp = &globalhash[HASHSYM(name, len)];
    for (sp = *hp; sp; sp = sp->g_next) {
        if (sp->g_len == len && !strcmp(sp->g_name, name) && sp->g_filescope > SCOPE_GLOBAL) {
            if (isglobal || (sp->g_filescope == filescope && sp->g_funcscope == funcscope)) {
                addstatic(sp);
                if (prevsp) {
                    prevsp->g_next = sp->g_next;
                } else {
                    *hp = sp->g_next;
                }
                continue;
            }
        }
        prevsp = sp;
    }
}

/*
 * To store in a table a static variable whose scope is being ended
 */
void
addstatic(GLOBAL *sp)
{
    GLOBAL **stp;

    if (staticavail <= 0) {
        if (staticcount <= 0) {
            stp = (GLOBAL **)calloc(20, sizeof(GLOBAL *));
        } else {
            stp = (GLOBAL **)realloc(statictable, (20 + staticcount) * sizeof(GLOBAL *));
        }
        if (stp == NULL) {
            math_error("Cannot allocate static-variable table");
            not_reached();
        }
        statictable = stp;
        staticavail = 20;
    }
    statictable[staticcount++] = sp;
    staticavail--;
}

/*
 * To display all static variables whose scope has been ended
 */
void
showstatics(void)
{
    long count;
    GLOBAL **stp;
    GLOBAL *sp;

    for (count = 0, stp = statictable; count < staticcount; count++) {
        sp = *stp++;
        if (count == 0) {
            printf("\nName    Scopes    Type\n");
            printf("----    ------    -----\n");
        }
        printf("%-8s", sp->g_name);
        printf("%3d", sp->g_filescope);
        printf("%3d    ", sp->g_funcscope);
        printtype(&sp->g_value);
        printf("\n");
    }
    if (count > 0) {
        printf("\nNumber: %ld\n", count);
    } else {
        printf("No un-scoped static variables\n");
    }
}

/*
 * Remove all the symbols from the global symbol table which have file or
 * function scopes larger than the current scope levels.  Their memory
 * remains allocated since their values still actually exist.
 */
static void
unscope(void)
{
    GLOBAL **hp;         /* hash table head address */
    register GLOBAL *sp; /* current global symbol pointer */
    GLOBAL *prevsp;      /* previous kept symbol pointer */

    /*
     * We prevent the hp pointer from walking behind globalhash
     * by stopping one short of the end and running the loop one
     * more time.
     *
     * We could stop the loop with just hp >= globalhash, but stopping
     * short and running the loop one last time manually helps make
     * code checkers such as insure happy.
     */
    for (hp = &globalhash[HASHSIZE - 1]; hp > globalhash; hp--) {
        prevsp = NULL;
        for (sp = *hp; sp; sp = sp->g_next) {
            if ((sp->g_filescope == SCOPE_GLOBAL) || (sp->g_filescope < filescope) ||
                ((sp->g_filescope == filescope) && (sp->g_funcscope <= funcscope))) {
                prevsp = sp;
                continue;
            }

            /*
             * This symbol needs removing.
             */
            addstatic(sp);
            if (prevsp) {
                prevsp->g_next = sp->g_next;
            } else {
                *hp = sp->g_next;
            }
        }
    }
    /* run the loop manually one last time */
    prevsp = NULL;
    for (sp = *hp; sp; sp = sp->g_next) {
        if ((sp->g_filescope == SCOPE_GLOBAL) || (sp->g_filescope < filescope) ||
            ((sp->g_filescope == filescope) && (sp->g_funcscope <= funcscope))) {
            prevsp = sp;
            continue;
        }

        /*
         * This symbol needs removing.
         */
        addstatic(sp);
        if (prevsp) {
            prevsp->g_next = sp->g_next;
        } else {
            *hp = sp->g_next;
        }
    }
}

/*
 * Initialize the local and parameter symbol table information.
 */
void
initlocals(void)
{
    initstr(&localnames);
    initstr(&paramnames);
    curfunc->f_localcount = 0;
    curfunc->f_paramcount = 0;
}

/*
 * Add a possibly new local variable definition.
 * Returns the index of the variable into the local symbol table.
 * Minus one indicates the symbol could not be added.
 *
 * given:
 *      name            name of local variable
 */
long
addlocal(char *name)
{
    long index; /* current symbol index */

    index = findstr(&localnames, name);
    if (index >= 0) {
        return index;
    }
    index = localnames.h_count;
    (void)addstr(&localnames, name);
    curfunc->f_localcount++;
    return index;
}

/*
 * Find a local variable name and return its index.
 * Returns minus one if the variable name is not defined.
 *
 * given:
 *      name            name of local variable
 */
long
findlocal(char *name)
{
    return findstr(&localnames, name);
}

/*
 * Return the name of a local variable.
 */
char *
localname(long n)
{
    return namestr(&localnames, n);
}

/*
 * Add a possibly new parameter variable definition.
 * Returns the index of the variable into the parameter symbol table.
 * Minus one indicates the symbol could not be added.
 *
 * given:
 *      name            name of parameter variable
 */
long
addparam(char *name)
{
    long index; /* current symbol index */

    index = findstr(&paramnames, name);
    if (index >= 0) {
        return index;
    }
    index = paramnames.h_count;
    (void)addstr(&paramnames, name);
    curfunc->f_paramcount++;
    return index;
}

/*
 * Find a parameter variable name and return its index.
 * Returns minus one if the variable name is not defined.
 *
 * given:
 *      name            name of parameter variable
 */
long
findparam(char *name)
{
    return findstr(&paramnames, name);
}

/*
 * Return the name of a parameter variable.
 */
char *
paramname(long n)
{
    return namestr(&paramnames, n);
}

/*
 * Return the type of a variable name.
 * This is either local, parameter, global, static, or undefined.
 *
 * given:
 *      name            variable name to find
 */
int
symboltype(char *name)
{
    GLOBAL *sp;

    if (findparam(name) >= 0) {
        return SYM_PARAM;
    }
    if (findlocal(name) >= 0) {
        return SYM_LOCAL;
    }
    sp = findglobal(name);
    if (sp) {
        if (sp->g_filescope == SCOPE_GLOBAL) {
            return SYM_GLOBAL;
        }
        return SYM_STATIC;
    }
    return SYM_UNDEFINED;
}

/* END CODE */
