/*
 * addop - add opcodes to a function being compiled
 *
 * 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:10
 * 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 "opcodes.h"
#include "label.h"
#include "func.h"
#include "token.h"
#include "symbol.h"
#include "attribute.h"
#include "errtbl.h"

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

#define FUNCALLOCSIZE 20    /* reallocate size for functions */
#define OPCODEALLOCSIZE 100 /* reallocate size for opcodes in functions */

static unsigned long maxopcodes; /* number of opcodes available */
static long newindex;            /* index of new function */
static char *newname;            /* name of new function */
static long oldop;               /* previous opcode */
static long oldoldop;            /* opcode before previous opcode */
static long debugline;           /* line number of latest debug opcode */
static long funccount;           /* number of functions */
static long funcavail;           /* available number of functions */
static FUNC *functemplate;       /* function definition template */
static FUNC **functions;         /* table of functions */
static STRINGHEAD funcnames;     /* function names */

/*
 * Initialize the table of user defined functions.
 */
void
initfunctions(void)
{
    initstr(&funcnames);
    maxopcodes = OPCODEALLOCSIZE;
    functemplate = (FUNC *)calloc(1, funcsize(maxopcodes));
    if (functemplate == NULL) {
        math_error("Cannot allocate function template");
        not_reached();
    }
    functions = (FUNC **)calloc(FUNCALLOCSIZE, sizeof(FUNC *));
    if (functions == NULL) {
        math_error("Cannot allocate function table");
        not_reached();
    }
    funccount = 0;
    funcavail = FUNCALLOCSIZE;
}

/*
 * Show the list of user defined functions.
 */
void
showfunctions(void)
{
    FUNC *fp; /* current function */
    long count;
    long index;

    count = 0;
    if (funccount > 0) {
        if (conf->resource_debug & RSCDBG_FUNC_INFO) {
            math_str("Index\tName        \tArgs\tOpcodes\n"
                     "-----\t------     \t---- \t------\n");
        } else {
            math_str("Name\tArguments\n"
                     "----\t---------\n");
        }
        for (index = 0; index < funccount; index++) {
            fp = functions[index];
            if (conf->resource_debug & RSCDBG_FUNC_INFO) {

                math_fmt("%5ld\t%-12s\t", index, namestr(&funcnames, index));
                if (fp) {
                    count++;
                    math_fmt("%-5d\t%-5ld\n", fp->f_paramcount, fp->f_opcodecount);
                } else {
                    math_str("null\t0\n");
                }
            } else {
                if (fp == NULL) {
                    continue;
                }
                count++;
                math_fmt("%-12s\t%-2d\n", namestr(&funcnames, index), fp->f_paramcount);
            }
        }
    }
    if (conf->resource_debug & RSCDBG_FUNC_INFO) {
        math_fmt("\nNumber non-null: %ld\n", count);
        math_fmt("Number null: %ld\n", funccount - count);
        math_fmt("Total number: %ld\n", funccount);
    } else {
        if (count > 0) {
            math_fmt("\nNumber: %ld\n", count);
        } else {
            math_str("No user functions defined\n");
        }
    }
}

/*
 * Initialize a function for definition.
 * Newflag is true if we should allocate a new function structure,
 * instead of the usual overwriting of the template function structure.
 * The new structure is returned in the global curfunc variable.
 *
 * given:
 *      name            name of function
 *      newflag         true if need new structure
 */
void
beginfunc(char *name, bool newflag)
{
    register FUNC *fp; /* current function */

    newindex = adduserfunc(name);
    maxopcodes = OPCODEALLOCSIZE;
    fp = functemplate;
    if (newflag) {
        fp = (FUNC *)calloc(funcsize(maxopcodes), 1);
        if (fp == NULL) {
            math_error("Cannot allocate temporary function");
            not_reached();
        }
    }
    fp->f_next = NULL;
    fp->f_localcount = 0;
    fp->f_opcodecount = 0;
    fp->f_savedvalue.v_type = V_NULL;
    fp->f_savedvalue.v_subtype = V_NOSUBTYPE;
    newname = namestr(&funcnames, newindex);
    fp->f_name = newname;
    curfunc = fp;
    initlocals();
    initlabels();
    oldop = OP_NOP;
    oldoldop = OP_NOP;
    debugline = 0;
    errorcount = 0;
}

/*
 * Commit the just defined function for use.
 * This replaces any existing definition for the function.
 * This should only be called for normal user-defined functions.
 */
void
endfunc(void)
{
    register FUNC *fp; /* function just finished */
    size_t size;       /* size of just created function */
    unsigned long index;

    if (oldop != OP_RETURN) {
        addop(OP_UNDEF);
        addop(OP_RETURN);
    }

    checklabels();

    if (errorcount) {
        scanerror(T_NULL, "Compilation of \"%s\" failed: %ld error(s)", newname, errorcount);
        return;
    }
    size = funcsize(curfunc->f_opcodecount);
    fp = (FUNC *)calloc(size, 1);
    if (fp == NULL) {
        math_error("Cannot commit function");
        not_reached();
    }
    memcpy((char *)fp, (char *)curfunc, size);
    if (curfunc != functemplate) {
        free(curfunc);
    }
    if (newname[0] != '*' && (conf->traceflags & TRACE_FNCODES)) {
        dumpnames = true;
        for (size = 0; size < fp->f_opcodecount;) {
            printf("%ld: ", (unsigned long)size);
            size += dumpop(&fp->f_opcodes[size]);
        }
    }
    if ((inputisterminal() && conf->resource_debug & RSCDBG_STDIN_FUNC) ||
        (!inputisterminal() && conf->resource_debug & RSCDBG_FILE_FUNC)) {
        printf("%s(", newname);
        for (index = 0; index < fp->f_paramcount; index++) {
            if (index) {
                putchar(',');
            }
            printf("%s", paramname(index));
        }
        printf(") ");
        if (functions[newindex]) {
            printf("re");
        }
        printf("defined\n");
    }
    if (functions[newindex]) {
        freenumbers(functions[newindex]);
        free(functions[newindex]);
    }
    functions[newindex] = fp;
}

/*
 * Find the user function with the specified name, and return its index.
 * If the function does not exist, its name is added to the function table
 * and an error will be generated when it is called if it is still undefined.
 *
 * given:
 *      name            name of function
 */
long
adduserfunc(char *name)
{
    long index; /* index of function */

    index = findstr(&funcnames, name);
    if (index >= 0) {
        return index;
    }
    if (funccount >= funcavail) {
        functions = (FUNC **)realloc(functions, sizeof(FUNC *) * (funcavail + FUNCALLOCSIZE));
        if (functions == NULL) {
            math_error("Failed to reallocate function table");
            not_reached();
        }
        funcavail += FUNCALLOCSIZE;
    }
    if (addstr(&funcnames, name) == NULL) {
        math_error("Cannot save function name");
        not_reached();
    }
    index = funccount++;
    functions[index] = NULL;
    return index;
}

/*
 * Remove user defined function
 */
void
rmuserfunc(char *name)
{
    long index; /* index of function */

    index = findstr(&funcnames, name);
    if (index < 0) {
        warning("No function named \"%s\" to be undefined", name);
        return;
    }
    if (functions[index] == NULL) {
        warning("No defined function \"%s\" to be undefined", name);
        return;
    }
    freenumbers(functions[index]);
    free(functions[index]);
    if ((inputisterminal() && conf->resource_debug & RSCDBG_STDIN_FUNC) ||
        (!inputisterminal() && conf->resource_debug & RSCDBG_FILE_FUNC)) {
        printf("%s() undefined\n", name);
    }
    functions[index] = NULL;
}

/*
 * Free memory used to store function and its constants
 */
void
freefunc(FUNC *fp)
{
    long index;
    unsigned long i;

    if (fp == NULL) {
        return;
    }
    if (fp == curfunc) {
        index = newindex;
    } else {
        for (index = 0; index < funccount; index++) {
            if (functions[index] == fp) {
                break;
            }
        }
        if (index == funccount) {
            math_error("Bad call to freefunc!!!");
            not_reached();
        }
    }
    if (newname[0] != '*' && (conf->traceflags & TRACE_FNCODES)) {
        printf("Freeing function \"%s\"\n", namestr(&funcnames, index));
        dumpnames = false;
        for (i = 0; i < fp->f_opcodecount;) {
            printf("%ld: ", i);
            i += dumpop(&fp->f_opcodes[i]);
        }
    }
    freenumbers(fp);
    if (fp != functemplate) {
        free(fp);
    }
}

void
rmalluserfunc(void)
{
    FUNC *fp;
    long index;

    for (index = 0; index < funccount; index++) {
        fp = functions[index];
        if (fp) {
            freefunc(fp);
            functions[index] = NULL;
        }
    }
}

/*
 * get index of defined user function with specified name, or -1 if there
 * is none or if it has been undefined
 */
long
getuserfunc(char *name)
{
    long index;

    index = findstr(&funcnames, name);
    if (index >= 0 && functions[index] != NULL) {
        return index;
    }
    return -1L;
}

/*
 * Clear any optimization that may be done for the next opcode.
 * This is used when defining a label.
 */
void
clearopt(void)
{
    oldop = OP_NOP;
    oldoldop = OP_NOP;
    debugline = 0;
}

/*
 * Find a function structure given its index.
 */
FUNC *
findfunc(long index)
{
    if (index >= funccount) {
        math_error("Undefined function");
        not_reached();
    }
    return functions[index];
}

/*
 * Return the name of a function given its index.
 */
char *
namefunc(long index)
{
    return namestr(&funcnames, index);
}

/*
 * Let a matrix indexing operation know that it will be treated as a write
 * reference instead of just as a read reference.
 */
void
writeindexop(void)
{
    if (oldop == OP_INDEXADDR) {
        curfunc->f_opcodes[curfunc->f_opcodecount - 1] = true;
    }
}

/*
 * Add an opcode to the current function being compiled.
 * Note: This can change the curfunc global variable when the
 * function needs expanding.
 */
void
addop(long op)
{
    register FUNC *fp; /* current function */
    NUMBER *q, *q1, *q2;
    unsigned long count;
    bool cut;
    int diff;

    fp = curfunc;
    count = fp->f_opcodecount;
    cut = true;
    diff = 2;
    q = NULL;
    if ((count + 5) >= maxopcodes) {
        maxopcodes += OPCODEALLOCSIZE;
        fp = (FUNC *)calloc(1, funcsize(maxopcodes));
        if (fp == NULL) {
            math_error("cannot malloc function");
            not_reached();
        }
        memcpy((char *)fp, (char *)curfunc, funcsize(curfunc->f_opcodecount));
        if (curfunc != functemplate) {
            free(curfunc);
        }
        curfunc = fp;
    }

    /*
     * Check the current opcode against the previous opcode and try to
     * slightly optimize the code depending on the various combinations.
     */
    switch (op) {
    case OP_GETVALUE:
        switch (oldop) {
        case OP_NUMBER:
        case OP_ZERO:
        case OP_ONE:
        case OP_IMAGINARY:
        case OP_GETEPSILON:
        case OP_SETEPSILON:
        case OP_STRING:
        case OP_UNDEF:
        case OP_GETCONFIG:
        case OP_SETCONFIG:
            return;
        case OP_DUPLICATE:
            diff = 1;
            oldop = OP_DUPVALUE;
            break;
        case OP_FIADDR:
            diff = 1;
            oldop = OP_FIVALUE;
            break;
        case OP_GLOBALADDR:
            diff = 1 + PTR_SIZE;
            oldop = OP_GLOBALVALUE;
            break;
        case OP_LOCALADDR:
            oldop = OP_LOCALVALUE;
            break;
        case OP_PARAMADDR:
            oldop = OP_PARAMVALUE;
            break;
        case OP_ELEMADDR:
            oldop = OP_ELEMVALUE;
            break;
        default:
            cut = false;
        }
        if (cut) {
            fp->f_opcodes[count - diff] = oldop;
            return;
        }
        break;
    case OP_POP:
        switch (oldop) {
        case OP_ASSIGN:
            fp->f_opcodes[count - 1] = OP_ASSIGNPOP;
            oldop = OP_ASSIGNPOP;
            return;
        case OP_NUMBER:
        case OP_IMAGINARY:
            q = constvalue(fp->f_opcodes[count - 1]);
            qfree(q);
            break;
        case OP_STRING:
            sfree(findstring((long)fp->f_opcodes[count - 1]));
            break;
        case OP_LOCALADDR:
        case OP_PARAMADDR:
            break;
        case OP_GLOBALADDR:
            diff = 1 + PTR_SIZE;
            break;
        case OP_UNDEF:
            fp->f_opcodecount -= 1;
            oldop = OP_NOP;
            oldoldop = OP_NOP;
            return;
        default:
            cut = false;
        }
        if (cut) {
            fp->f_opcodecount -= diff;
            oldop = OP_NOP;
            oldoldop = OP_NOP;
            warning("Constant before comma operator");
            return;
        }
        break;
    case OP_NEGATE:
        if (oldop == OP_NUMBER) {
            q = constvalue(fp->f_opcodes[count - 1]);
            fp->f_opcodes[count - 1] = addqconstant(qneg(q));
            qfree(q);
            return;
        }
    }
    if (oldop == OP_NUMBER) {
        if (oldoldop == OP_NUMBER) {
            q1 = constvalue(fp->f_opcodes[count - 3]);
            q2 = constvalue(fp->f_opcodes[count - 1]);
            switch (op) {
            case OP_DIV:
                if (qiszero(q2)) {
                    cut = false;
                    break;
                }
                q = qqdiv(q1, q2);
                break;
            case OP_MUL:
                q = qmul(q1, q2);
                break;
            case OP_ADD:
                q = qqadd(q1, q2);
                break;
            case OP_SUB:
                q = qsub(q1, q2);
                break;
            case OP_POWER:
                if (qisfrac(q2) || qisneg(q2)) {
                    cut = false;
                } else {
                    q = qpowi(q1, q2);
                }
                break;
            default:
                cut = false;
            }
            if (cut) {
                qfree(q1);
                qfree(q2);
                fp->f_opcodes[count - 3] = addqconstant(q);
                fp->f_opcodecount -= 2;
                oldoldop = OP_NOP;
                return;
            }
        } else if (op != OP_NUMBER) {
            q = constvalue(fp->f_opcodes[count - 1]);
            if (op == OP_POWER) {
                if (qcmpi(q, 2L) == 0) {
                    fp->f_opcodecount--;
                    fp->f_opcodes[count - 2] = OP_SQUARE;
                    qfree(q);
                    oldop = OP_SQUARE;
                    return;
                }
                if (qcmpi(q, 4L) == 0) {
                    fp->f_opcodes[count - 2] = OP_SQUARE;
                    fp->f_opcodes[count - 1] = OP_SQUARE;
                    qfree(q);
                    oldop = OP_SQUARE;
                    return;
                }
            }
            if (qiszero(q)) {
                qfree(q);
                fp->f_opcodes[count - 2] = OP_ZERO;
                fp->f_opcodecount--;
            } else if (qisone(q)) {
                qfree(q);
                fp->f_opcodes[count - 2] = OP_ONE;
                fp->f_opcodecount--;
            }
        }
    }
    /*
     * No optimization possible, so store the opcode.
     */
    fp->f_opcodes[fp->f_opcodecount] = op;
    fp->f_opcodecount++;
    oldoldop = oldop;
    oldop = op;
}

/*
 * Add an opcode and and one integer argument to the current function
 * being compiled.
 */
void
addopone(long op, long arg)
{
    if (op == OP_DEBUG) {
        if ((conf->traceflags & TRACE_NODEBUG) || (arg == debugline)) {
            return;
        }
        debugline = arg;
        if (oldop == OP_DEBUG) {
            curfunc->f_opcodes[curfunc->f_opcodecount - 1] = arg;
            return;
        }
    }
    addop(op);
    curfunc->f_opcodes[curfunc->f_opcodecount] = arg;
    curfunc->f_opcodecount++;
}

/*
 * Add an opcode and and two integer arguments to the current function
 * being compiled.
 */
void
addoptwo(long op, long arg1, long arg2)
{
    addop(op);
    curfunc->f_opcodes[curfunc->f_opcodecount++] = arg1;
    curfunc->f_opcodes[curfunc->f_opcodecount++] = arg2;
}

/*
 * Add an opcode and a character pointer to the function being compiled.
 */
void
addopptr(long op, char *ptr)
{
    char **ptraddr;

    addop(op);
    ptraddr = (char **)&curfunc->f_opcodes[curfunc->f_opcodecount];
    *ptraddr = ptr;
    curfunc->f_opcodecount += PTR_SIZE;
}

/*
 * Add an opcode and an index and an argument count for a function call.
 */
void
addopfunction(long op, long index, int count)
{
    long newop;

    if ((op == OP_CALL) && ((newop = builtinopcode(index)) != OP_NOP)) {
        if ((newop == OP_SETCONFIG) && (count == 1)) {
            newop = OP_GETCONFIG;
        }
        if ((newop == OP_SETEPSILON) && (count == 0)) {
            newop = OP_GETEPSILON;
        }
        if ((newop == OP_ABS) && (count == 1)) {
            addop(OP_GETEPSILON);
        }
        addop(newop);
        return;
    }
    addop(op);
    curfunc->f_opcodes[curfunc->f_opcodecount++] = index;
    curfunc->f_opcodes[curfunc->f_opcodecount++] = count;
}

/*
 * Add a jump-type opcode and a label to the function being compiled.
 *
 * given:
 *      label           label to be added
 */
void
addoplabel(long op, LABEL *label)
{
    addop(op);
    uselabel(label);
}
