/*******************************************************************
** v m . c
** Forth Inspired Command Language - virtual machine methods
** Author: John Sadler (john_sadler@alum.mit.edu)
** Created: 19 July 1997
** 
*******************************************************************/
/*
** This file implements the virtual machine of FICL. Each virtual
** machine retains the state of an interpreter. A virtual machine
** owns a pair of stacks for parameters and return addresses, as
** well as a pile of state variables and the two dedicated registers
** of the interp.
*/

#ifdef TESTMAIN
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#else
#include <stand.h>
#endif
#include <stdarg.h>
#include <string.h>
#include "ficl.h"

static char digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";


/**************************************************************************
                        v m B r a n c h R e l a t i v e 
** 
**************************************************************************/
void vmBranchRelative(FICL_VM *pVM, int offset)
{
    pVM->ip += offset;
    return;
}


/**************************************************************************
                        v m C r e a t e
** 
**************************************************************************/
FICL_VM *vmCreate(FICL_VM *pVM, unsigned nPStack, unsigned nRStack)
{
    if (pVM == NULL)
    {
        pVM = (FICL_VM *)ficlMalloc(sizeof (FICL_VM));
        pVM->pStack = NULL;
        pVM->rStack = NULL;
        pVM->link   = NULL;
    }
    assert (pVM);

    if (pVM->pStack)
        stackDelete(pVM->pStack);
    pVM->pStack = stackCreate(nPStack);

    if (pVM->rStack)
        stackDelete(pVM->rStack);
    pVM->rStack = stackCreate(nRStack);

    pVM->textOut = ficlTextOut;

    vmReset(pVM);
    return pVM;
}


/**************************************************************************
                        v m D e l e t e
** 
**************************************************************************/
void vmDelete (FICL_VM *pVM)
{
    if (pVM)
    {
        ficlFree(pVM->pStack);
        ficlFree(pVM->rStack);
        ficlFree(pVM);
    }

    return;
}


/**************************************************************************
                        v m E x e c u t e
** 
**************************************************************************/
void vmExecute(FICL_VM *pVM, FICL_WORD *pWord)
{
    pVM->runningWord = pWord;
    pWord->code(pVM);
    return;
}


/**************************************************************************
                        v m G e t S t r i n g
** Parses a string out of the VM input buffer and copies up to the first
** FICL_STRING_MAX characters to the supplied destination buffer, a
** FICL_STRING. The destination string is NULL terminated.
** 
** Returns the address of the first unused character in the dest buffer.
**************************************************************************/
char *vmGetString(FICL_VM *pVM, FICL_STRING *spDest, char delimiter)
{
    STRINGINFO si = vmParseString(pVM, delimiter);

    if (SI_COUNT(si) > FICL_STRING_MAX)
    {
        SI_SETLEN(si, FICL_STRING_MAX);
    }

    strncpy(spDest->text, SI_PTR(si), SI_COUNT(si));
    spDest->text[SI_COUNT(si)] = '\0';
    spDest->count = (FICL_COUNT)SI_COUNT(si);

    return spDest->text + SI_COUNT(si) + 1;
}


/**************************************************************************
                        v m G e t W o r d
** vmGetWord calls vmGetWord0 repeatedly until it gets a string with 
** non-zero length.
**************************************************************************/
STRINGINFO vmGetWord(FICL_VM *pVM)
{
    STRINGINFO si = vmGetWord0(pVM);

    if (SI_COUNT(si) == 0)
    {
        vmThrow(pVM, VM_RESTART);
    }

    return si;
}


/**************************************************************************
                        v m G e t W o r d 0
** Skip leading whitespace and parse a space delimited word from the tib.
** Returns the start address and length of the word. Updates the tib
** to reflect characters consumed, including the trailing delimiter.
** If there's nothing of interest in the tib, returns zero. This function
** does not use vmParseString because it uses isspace() rather than a
** single  delimiter character.
**************************************************************************/
STRINGINFO vmGetWord0(FICL_VM *pVM)
{
    char *pSrc  = vmGetInBuf(pVM);
    STRINGINFO si;
    UNS32 count = 0;
    char ch;

    pSrc = skipSpace(pSrc);
    SI_SETPTR(si, pSrc);

    for (ch = *pSrc; ch != '\0' && !isspace(ch); ch = *++pSrc)
    {
        count++;
    }

    SI_SETLEN(si, count);

    if (isspace(ch))    /* skip one trailing delimiter */
        pSrc++;

    vmUpdateTib(pVM, pSrc);

    return si;
}


/**************************************************************************
                        v m G e t W o r d T o P a d
** Does vmGetWord0 and copies the result to the pad as a NULL terminated
** string. Returns the length of the string. If the string is too long 
** to fit in the pad, it is truncated.
**************************************************************************/
int vmGetWordToPad(FICL_VM *pVM)
{
    STRINGINFO si;
    char *cp = (char *)pVM->pad;
    si = vmGetWord0(pVM);

    if (SI_COUNT(si) > nPAD)
        SI_SETLEN(si, nPAD);

    strncpy(cp, SI_PTR(si), SI_COUNT(si));
    cp[SI_COUNT(si)] = '\0';
    return (int)(SI_COUNT(si));
}


/**************************************************************************
                        v m P a r s e S t r i n g
** Parses a string out of the input buffer using the delimiter
** specified. Skips leading delimiters, marks the start of the string,
** and counts characters to the next delimiter it encounters. It then 
** updates the vm input buffer to consume all these chars, including the
** trailing delimiter. 
** Returns the address and length of the parsed string, not including the
** trailing delimiter.
**************************************************************************/
STRINGINFO vmParseString(FICL_VM *pVM, char delim)
{
    STRINGINFO si;
    char *pSrc      = vmGetInBuf(pVM);
    char ch;

    while (*pSrc == delim)  /* skip lead delimiters */
        pSrc++;

    SI_SETPTR(si, pSrc);    /* mark start of text */

    for (ch = *pSrc; (ch != delim)
                  && (ch != '\0') 
                  && (ch != '\r') 
                  && (ch != '\n'); ch = *++pSrc)
    {
        ;                   /* find next delimiter or end of line */
    }

                            /* set length of result */
    SI_SETLEN(si, pSrc - SI_PTR(si));

    if (*pSrc == delim)     /* gobble trailing delimiter */
        pSrc++;

    vmUpdateTib(pVM, pSrc);
    return si;
}


/**************************************************************************
                        v m P o p I P
** 
**************************************************************************/
void vmPopIP(FICL_VM *pVM)
{
    pVM->ip = (IPTYPE)(stackPopPtr(pVM->rStack));
    return;
}


/**************************************************************************
                        v m P u s h I P
** 
**************************************************************************/
void vmPushIP(FICL_VM *pVM, IPTYPE newIP)
{
    stackPushPtr(pVM->rStack, (void *)pVM->ip);
    pVM->ip = newIP;
    return;
}


/**************************************************************************
                        v m P u s h T i b
** Binds the specified input string to the VM and clears >IN (the index)
**************************************************************************/
void vmPushTib(FICL_VM *pVM, char *text, TIB *pSaveTib)
{
    if (pSaveTib)
    {
        *pSaveTib = pVM->tib;
    }

    pVM->tib.cp = text;
    pVM->tib.index = 0;
}


void vmPopTib(FICL_VM *pVM, TIB *pTib)
{
    if (pTib)
    {
        pVM->tib = *pTib;
    }
    return;
}


/**************************************************************************
                        v m Q u i t
** 
**************************************************************************/
void vmQuit(FICL_VM *pVM)
{
    static FICL_WORD *pInterp = NULL;
    if (!pInterp)
        pInterp = ficlLookup("interpret");
    assert(pInterp);

    stackReset(pVM->rStack);
    pVM->fRestart    = 0;
    pVM->ip          = &pInterp;
    pVM->runningWord = pInterp;
    pVM->state       = INTERPRET;
    pVM->tib.cp      = NULL;
    pVM->tib.index   = 0;
    pVM->pad[0]      = '\0';
    pVM->sourceID.i  = 0;
    return;
}


/**************************************************************************
                        v m R e s e t 
** 
**************************************************************************/
void vmReset(FICL_VM *pVM)
{
    vmQuit(pVM);
    stackReset(pVM->pStack);
    pVM->base        = 10;
    return;
}


/**************************************************************************
                        v m S e t T e x t O u t
** Binds the specified output callback to the vm. If you pass NULL,
** binds the default output function (ficlTextOut)
**************************************************************************/
void vmSetTextOut(FICL_VM *pVM, OUTFUNC textOut)
{
    if (textOut)
        pVM->textOut = textOut;
    else
        pVM->textOut = ficlTextOut;

    return;
}


/**************************************************************************
                        v m T e x t O u t
** Feeds text to the vm's output callback
**************************************************************************/
void vmTextOut(FICL_VM *pVM, char *text, int fNewline)
{
    assert(pVM);
    assert(pVM->textOut);
    (pVM->textOut)(pVM, text, fNewline);

    return;
}


/**************************************************************************
                        v m T h r o w
** 
**************************************************************************/
void vmThrow(FICL_VM *pVM, int except)
{
    longjmp(*(pVM->pState), except);
}


void vmThrowErr(FICL_VM *pVM, char *fmt, ...)
{
    va_list va;
    va_start(va, fmt);
    vsprintf(pVM->pad, fmt, va);
    vmTextOut(pVM, pVM->pad, 1);
    va_end(va);
    longjmp(*(pVM->pState), VM_ERREXIT);
}


/**************************************************************************
                        w o r d I s I m m e d i a t e
** 
**************************************************************************/
int wordIsImmediate(FICL_WORD *pFW)
{
    return ((pFW != NULL) && (pFW->flags & FW_IMMEDIATE));
}


/**************************************************************************
                        w o r d I s C o m p i l e O n l y
** 
**************************************************************************/
int wordIsCompileOnly(FICL_WORD *pFW)
{
    return ((pFW != NULL) && (pFW->flags & FW_COMPILE));
}


/**************************************************************************
                        s t r r e v
** 
**************************************************************************/
char *strrev( char *string )    
{                               /* reverse a string in-place */
    int i = strlen(string);
    char *p1 = string;          /* first char of string */
    char *p2 = string + i - 1;  /* last non-NULL char of string */
    char c;

    if (i > 1)
    {
        while (p1 < p2)
        {
            c = *p2;
            *p2 = *p1;
            *p1 = c;
            p1++; p2--;
        }
    }
        
    return string;
}


/**************************************************************************
                        d i g i t _ t o _ c h a r
** 
**************************************************************************/
char digit_to_char(int value)
{
    return digits[value];
}


/**************************************************************************
                        l t o a
** 
**************************************************************************/
char *ltoa( INT32 value, char *string, int radix )
{                               /* convert long to string, any base */
    char *cp = string;
    int sign = ((radix == 10) && (value < 0));
    UNSQR result;
    UNS64 v;

    assert(radix > 1);
    assert(radix < 37);
    assert(string);

    if (sign)
        value = -value;

    if (value == 0)
        *cp++ = '0';
    else
    {
        v.hi = 0;
        v.lo = (UNS32)value;
        while (v.lo)
        {
            result = ficlLongDiv(v, (UNS32)radix);
            *cp++ = digits[result.rem];
            v.lo = result.quot;
        }
    }

    if (sign)
        *cp++ = '-';

    *cp++ = '\0';

    return strrev(string);
}


/**************************************************************************
                        u l t o a
** 
**************************************************************************/
char *ultoa(UNS32 value, char *string, int radix )
{                               /* convert long to string, any base */
    char *cp = string;
    UNS64 ud;
    UNSQR result;

    assert(radix > 1);
    assert(radix < 37);
    assert(string);

    if (value == 0)
        *cp++ = '0';
    else
    {
        ud.hi = 0;
        ud.lo = value;
        result.quot = value;

        while (ud.lo)
        {
            result = ficlLongDiv(ud, (UNS32)radix);
            ud.lo = result.quot;
            *cp++ = digits[result.rem];
        }
    }

    *cp++ = '\0';

    return strrev(string);
}


/**************************************************************************
                        c a s e F o l d
** Case folds a NULL terminated string in place. All characters
** get converted to lower case.
**************************************************************************/
char *caseFold(char *cp)
{
    char *oldCp = cp;

    while (*cp)
    {
        if (isupper(*cp))
            *cp = (char)tolower(*cp);
        cp++;
    }

    return oldCp;
}


/**************************************************************************
                        s t r i n c m p
** 
**************************************************************************/
int strincmp(char *cp1, char *cp2, FICL_COUNT count)
{
    int i = 0;
    char c1, c2;

    for (c1 = *cp1, c2 = *cp2;
        ((i == 0) && count && c1 && c2);
        c1 = *++cp1, c2 = *++cp2, count--)
    {
        i = tolower(c1) - tolower(c2);
    }

    return i;
}



/**************************************************************************
                        s k i p S p a c e
** Given a string pointer, returns a pointer to the first non-space
** char of the string, or to the NULL terminator if no such char found.
**************************************************************************/
char *skipSpace(char *cp)
{
    assert(cp);

    while (isspace(*cp))
        cp++;

    return cp;
}


