/*
 * addop - add opcodes to a function being compiled
 *
 * Copyright (C) 1999-2007,2021-2023  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/
 */


#include <stdio.h>
#include "calc.h"
#include "alloc.h"
#include "opcodes.h"
#include "str.h"
#include "func.h"
#include "token.h"
#include "label.h"
#include "symbol.h"


#include "errtbl.h"
#include "banned.h"     /* include after system header <> 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 *) malloc(funcsize(maxopcodes));
        if (functemplate == NULL) {
                math_error("Cannot allocate function template");
                not_reached();
        }
        functions = (FUNC **) malloc(sizeof(FUNC *) * FUNCALLOCSIZE);
        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 *) malloc(funcsize(maxopcodes));
                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 *) malloc(size);
        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 *) malloc(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);
}
