/*******************************************************************1** v m . c2** Forth Inspired Command Language - virtual machine methods3** Author: John Sadler ([email protected])4** Created: 19 July 19975** $Id: vm.c,v 1.13 2001/12/05 07:21:34 jsadler Exp $6*******************************************************************/7/*8** This file implements the virtual machine of FICL. Each virtual9** machine retains the state of an interpreter. A virtual machine10** owns a pair of stacks for parameters and return addresses, as11** well as a pile of state variables and the two dedicated registers12** of the interp.13*/14/*15** Copyright (c) 1997-2001 John Sadler ([email protected])16** All rights reserved.17**18** Get the latest Ficl release at http://ficl.sourceforge.net19**20** I am interested in hearing from anyone who uses ficl. If you have21** a problem, a success story, a defect, an enhancement request, or22** if you would like to contribute to the ficl release, please23** contact me by email at the address above.24**25** L I C E N S E and D I S C L A I M E R26**27** Redistribution and use in source and binary forms, with or without28** modification, are permitted provided that the following conditions29** are met:30** 1. Redistributions of source code must retain the above copyright31** notice, this list of conditions and the following disclaimer.32** 2. Redistributions in binary form must reproduce the above copyright33** notice, this list of conditions and the following disclaimer in the34** documentation and/or other materials provided with the distribution.35**36** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND37** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE38** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE39** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE40** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL41** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS42** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)43** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT44** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY45** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF46** SUCH DAMAGE.47*/484950#ifdef TESTMAIN51#include <stdlib.h>52#include <stdio.h>53#include <ctype.h>54#else55#include <stand.h>56#endif57#include <stdarg.h>58#include <string.h>59#include "ficl.h"6061static char digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";626364/**************************************************************************65v m B r a n c h R e l a t i v e66**67**************************************************************************/68void vmBranchRelative(FICL_VM *pVM, int offset)69{70pVM->ip += offset;71return;72}737475/**************************************************************************76v m C r e a t e77** Creates a virtual machine either from scratch (if pVM is NULL on entry)78** or by resizing and reinitializing an existing VM to the specified stack79** sizes.80**************************************************************************/81FICL_VM *vmCreate(FICL_VM *pVM, unsigned nPStack, unsigned nRStack)82{83if (pVM == NULL)84{85pVM = (FICL_VM *)ficlMalloc(sizeof (FICL_VM));86assert (pVM);87memset(pVM, 0, sizeof (FICL_VM));88}8990if (pVM->pStack)91stackDelete(pVM->pStack);92pVM->pStack = stackCreate(nPStack);9394if (pVM->rStack)95stackDelete(pVM->rStack);96pVM->rStack = stackCreate(nRStack);9798#if FICL_WANT_FLOAT99if (pVM->fStack)100stackDelete(pVM->fStack);101pVM->fStack = stackCreate(nPStack);102#endif103104pVM->textOut = ficlTextOut;105106vmReset(pVM);107return pVM;108}109110111/**************************************************************************112v m D e l e t e113** Free all memory allocated to the specified VM and its subordinate114** structures.115**************************************************************************/116void vmDelete (FICL_VM *pVM)117{118if (pVM)119{120ficlFree(pVM->pStack);121ficlFree(pVM->rStack);122#if FICL_WANT_FLOAT123ficlFree(pVM->fStack);124#endif125ficlFree(pVM);126}127128return;129}130131132/**************************************************************************133v m E x e c u t e134** Sets up the specified word to be run by the inner interpreter.135** Executes the word's code part immediately, but in the case of136** colon definition, the definition itself needs the inner interp137** to complete. This does not happen until control reaches ficlExec138**************************************************************************/139void vmExecute(FICL_VM *pVM, FICL_WORD *pWord)140{141pVM->runningWord = pWord;142pWord->code(pVM);143return;144}145146147/**************************************************************************148v m I n n e r L o o p149** the mysterious inner interpreter...150** This loop is the address interpreter that makes colon definitions151** work. Upon entry, it assumes that the IP points to an entry in152** a definition (the body of a colon word). It runs one word at a time153** until something does vmThrow. The catcher for this is expected to exist154** in the calling code.155** vmThrow gets you out of this loop with a longjmp()156** Visual C++ 5 chokes on this loop in Release mode. Aargh.157**************************************************************************/158#if INLINE_INNER_LOOP == 0159void vmInnerLoop(FICL_VM *pVM)160{161M_INNER_LOOP(pVM);162}163#endif164#if 0165/*166** Recast inner loop that inlines tokens for control structures, arithmetic and stack operations,167** as well as create does> : ; and various literals168*/169typedef enum170{171PATCH = 0,172L0,173L1,174L2,175LMINUS1,176LMINUS2,177DROP,178SWAP,179DUP,180PICK,181ROLL,182FETCH,183STORE,184BRANCH,185CBRANCH,186LEAVE,187TO_R,188R_FROM,189EXIT;190} OPCODE;191192typedef CELL *IPTYPE;193194void vmInnerLoop(FICL_VM *pVM)195{196IPTYPE ip = pVM->ip;197FICL_STACK *pStack = pVM->pStack;198199for (;;)200{201OPCODE o = (*ip++).i;202CELL c;203switch (o)204{205case L0:206stackPushINT(pStack, 0);207break;208case L1:209stackPushINT(pStack, 1);210break;211case L2:212stackPushINT(pStack, 2);213break;214case LMINUS1:215stackPushINT(pStack, -1);216break;217case LMINUS2:218stackPushINT(pStack, -2);219break;220case DROP:221stackDrop(pStack, 1);222break;223case SWAP:224stackRoll(pStack, 1);225break;226case DUP:227stackPick(pStack, 0);228break;229case PICK:230c = *ip++;231stackPick(pStack, c.i);232break;233case ROLL:234c = *ip++;235stackRoll(pStack, c.i);236break;237case EXIT:238return;239}240}241242return;243}244#endif245246247248/**************************************************************************249v m G e t D i c t250** Returns the address dictionary for this VM's system251**************************************************************************/252FICL_DICT *vmGetDict(FICL_VM *pVM)253{254assert(pVM);255return pVM->pSys->dp;256}257258259/**************************************************************************260v m G e t S t r i n g261** Parses a string out of the VM input buffer and copies up to the first262** FICL_STRING_MAX characters to the supplied destination buffer, a263** FICL_STRING. The destination string is NULL terminated.264**265** Returns the address of the first unused character in the dest buffer.266**************************************************************************/267char *vmGetString(FICL_VM *pVM, FICL_STRING *spDest, char delimiter)268{269STRINGINFO si = vmParseStringEx(pVM, delimiter, 0);270271if (SI_COUNT(si) > FICL_STRING_MAX)272{273SI_SETLEN(si, FICL_STRING_MAX);274}275276strncpy(spDest->text, SI_PTR(si), SI_COUNT(si));277spDest->text[SI_COUNT(si)] = '\0';278spDest->count = (FICL_COUNT)SI_COUNT(si);279280return spDest->text + SI_COUNT(si) + 1;281}282283284/**************************************************************************285v m G e t W o r d286** vmGetWord calls vmGetWord0 repeatedly until it gets a string with287** non-zero length.288**************************************************************************/289STRINGINFO vmGetWord(FICL_VM *pVM)290{291STRINGINFO si = vmGetWord0(pVM);292293if (SI_COUNT(si) == 0)294{295vmThrow(pVM, VM_RESTART);296}297298return si;299}300301302/**************************************************************************303v m G e t W o r d 0304** Skip leading whitespace and parse a space delimited word from the tib.305** Returns the start address and length of the word. Updates the tib306** to reflect characters consumed, including the trailing delimiter.307** If there's nothing of interest in the tib, returns zero. This function308** does not use vmParseString because it uses isspace() rather than a309** single delimiter character.310**************************************************************************/311STRINGINFO vmGetWord0(FICL_VM *pVM)312{313char *pSrc = vmGetInBuf(pVM);314char *pEnd = vmGetInBufEnd(pVM);315STRINGINFO si;316FICL_UNS count = 0;317char ch = 0;318319pSrc = skipSpace(pSrc, pEnd);320SI_SETPTR(si, pSrc);321322/*323for (ch = *pSrc; (pEnd != pSrc) && !isspace(ch); ch = *++pSrc)324{325count++;326}327*/328329/* Changed to make Purify happier. --lch */330for (;;)331{332if (pEnd == pSrc)333break;334ch = *pSrc;335if (isspace(ch))336break;337count++;338pSrc++;339}340341SI_SETLEN(si, count);342343if ((pEnd != pSrc) && isspace(ch)) /* skip one trailing delimiter */344pSrc++;345346vmUpdateTib(pVM, pSrc);347348return si;349}350351352/**************************************************************************353v m G e t W o r d T o P a d354** Does vmGetWord and copies the result to the pad as a NULL terminated355** string. Returns the length of the string. If the string is too long356** to fit in the pad, it is truncated.357**************************************************************************/358int vmGetWordToPad(FICL_VM *pVM)359{360STRINGINFO si;361char *cp = (char *)pVM->pad;362si = vmGetWord(pVM);363364if (SI_COUNT(si) > nPAD)365SI_SETLEN(si, nPAD);366367strncpy(cp, SI_PTR(si), SI_COUNT(si));368cp[SI_COUNT(si)] = '\0';369return (int)(SI_COUNT(si));370}371372373/**************************************************************************374v m P a r s e S t r i n g375** Parses a string out of the input buffer using the delimiter376** specified. Skips leading delimiters, marks the start of the string,377** and counts characters to the next delimiter it encounters. It then378** updates the vm input buffer to consume all these chars, including the379** trailing delimiter.380** Returns the address and length of the parsed string, not including the381** trailing delimiter.382**************************************************************************/383STRINGINFO vmParseString(FICL_VM *pVM, char delim)384{385return vmParseStringEx(pVM, delim, 1);386}387388STRINGINFO vmParseStringEx(FICL_VM *pVM, char delim, char fSkipLeading)389{390STRINGINFO si;391char *pSrc = vmGetInBuf(pVM);392char *pEnd = vmGetInBufEnd(pVM);393char ch;394395if (fSkipLeading)396{ /* skip lead delimiters */397while ((pSrc != pEnd) && (*pSrc == delim))398pSrc++;399}400401SI_SETPTR(si, pSrc); /* mark start of text */402403for (ch = *pSrc; (pSrc != pEnd)404&& (ch != delim)405&& (ch != '\r')406&& (ch != '\n'); ch = *++pSrc)407{408; /* find next delimiter or end of line */409}410411/* set length of result */412SI_SETLEN(si, pSrc - SI_PTR(si));413414if ((pSrc != pEnd) && (*pSrc == delim)) /* gobble trailing delimiter */415pSrc++;416417vmUpdateTib(pVM, pSrc);418return si;419}420421422/**************************************************************************423v m P o p424**425**************************************************************************/426CELL vmPop(FICL_VM *pVM)427{428return stackPop(pVM->pStack);429}430431432/**************************************************************************433v m P u s h434**435**************************************************************************/436void vmPush(FICL_VM *pVM, CELL c)437{438stackPush(pVM->pStack, c);439return;440}441442443/**************************************************************************444v m P o p I P445**446**************************************************************************/447void vmPopIP(FICL_VM *pVM)448{449pVM->ip = (IPTYPE)(stackPopPtr(pVM->rStack));450return;451}452453454/**************************************************************************455v m P u s h I P456**457**************************************************************************/458void vmPushIP(FICL_VM *pVM, IPTYPE newIP)459{460stackPushPtr(pVM->rStack, (void *)pVM->ip);461pVM->ip = newIP;462return;463}464465466/**************************************************************************467v m P u s h T i b468** Binds the specified input string to the VM and clears >IN (the index)469**************************************************************************/470void vmPushTib(FICL_VM *pVM, char *text, FICL_INT nChars, TIB *pSaveTib)471{472if (pSaveTib)473{474*pSaveTib = pVM->tib;475}476477pVM->tib.cp = text;478pVM->tib.end = text + nChars;479pVM->tib.index = 0;480}481482483void vmPopTib(FICL_VM *pVM, TIB *pTib)484{485if (pTib)486{487pVM->tib = *pTib;488}489return;490}491492493/**************************************************************************494v m Q u i t495**496**************************************************************************/497void vmQuit(FICL_VM *pVM)498{499stackReset(pVM->rStack);500pVM->fRestart = 0;501pVM->ip = NULL;502pVM->runningWord = NULL;503pVM->state = INTERPRET;504pVM->tib.cp = NULL;505pVM->tib.end = NULL;506pVM->tib.index = 0;507pVM->pad[0] = '\0';508pVM->sourceID.i = 0;509return;510}511512513/**************************************************************************514v m R e s e t515**516**************************************************************************/517void vmReset(FICL_VM *pVM)518{519vmQuit(pVM);520stackReset(pVM->pStack);521#if FICL_WANT_FLOAT522stackReset(pVM->fStack);523#endif524pVM->base = 10;525return;526}527528529/**************************************************************************530v m S e t T e x t O u t531** Binds the specified output callback to the vm. If you pass NULL,532** binds the default output function (ficlTextOut)533**************************************************************************/534void vmSetTextOut(FICL_VM *pVM, OUTFUNC textOut)535{536if (textOut)537pVM->textOut = textOut;538else539pVM->textOut = ficlTextOut;540541return;542}543544545/**************************************************************************546v m T e x t O u t547** Feeds text to the vm's output callback548**************************************************************************/549void vmTextOut(FICL_VM *pVM, char *text, int fNewline)550{551assert(pVM);552assert(pVM->textOut);553(pVM->textOut)(pVM, text, fNewline);554555return;556}557558559/**************************************************************************560v m T h r o w561**562**************************************************************************/563void vmThrow(FICL_VM *pVM, int except)564{565if (pVM->pState)566longjmp(*(pVM->pState), except);567}568569570void vmThrowErr(FICL_VM *pVM, char *fmt, ...)571{572va_list va;573va_start(va, fmt);574vsprintf(pVM->pad, fmt, va);575vmTextOut(pVM, pVM->pad, 1);576va_end(va);577longjmp(*(pVM->pState), VM_ERREXIT);578}579580581/**************************************************************************582w o r d I s I m m e d i a t e583**584**************************************************************************/585int wordIsImmediate(FICL_WORD *pFW)586{587return ((pFW != NULL) && (pFW->flags & FW_IMMEDIATE));588}589590591/**************************************************************************592w o r d I s C o m p i l e O n l y593**594**************************************************************************/595int wordIsCompileOnly(FICL_WORD *pFW)596{597return ((pFW != NULL) && (pFW->flags & FW_COMPILE));598}599600601/**************************************************************************602s t r r e v603**604**************************************************************************/605char *strrev( char *string )606{ /* reverse a string in-place */607int i = strlen(string);608char *p1 = string; /* first char of string */609char *p2 = string + i - 1; /* last non-NULL char of string */610char c;611612if (i > 1)613{614while (p1 < p2)615{616c = *p2;617*p2 = *p1;618*p1 = c;619p1++; p2--;620}621}622623return string;624}625626627/**************************************************************************628d i g i t _ t o _ c h a r629**630**************************************************************************/631char digit_to_char(int value)632{633return digits[value];634}635636637/**************************************************************************638i s P o w e r O f T w o639** Tests whether supplied argument is an integer power of 2 (2**n)640** where 32 > n > 1, and returns n if so. Otherwise returns zero.641**************************************************************************/642int isPowerOfTwo(FICL_UNS u)643{644int i = 1;645FICL_UNS t = 2;646647for (; ((t <= u) && (t != 0)); i++, t <<= 1)648{649if (u == t)650return i;651}652653return 0;654}655656657/**************************************************************************658l t o a659**660**************************************************************************/661char *ltoa( FICL_INT value, char *string, int radix )662{ /* convert long to string, any base */663char *cp = string;664int sign = ((radix == 10) && (value < 0));665int pwr;666667assert(radix > 1);668assert(radix < 37);669assert(string);670671pwr = isPowerOfTwo((FICL_UNS)radix);672673if (sign)674value = -value;675676if (value == 0)677*cp++ = '0';678else if (pwr != 0)679{680FICL_UNS v = (FICL_UNS) value;681FICL_UNS mask = (FICL_UNS) ~(-1 << pwr);682while (v)683{684*cp++ = digits[v & mask];685v >>= pwr;686}687}688else689{690UNSQR result;691DPUNS v;692v.hi = 0;693v.lo = (FICL_UNS)value;694while (v.lo)695{696result = ficlLongDiv(v, (FICL_UNS)radix);697*cp++ = digits[result.rem];698v.lo = result.quot;699}700}701702if (sign)703*cp++ = '-';704705*cp++ = '\0';706707return strrev(string);708}709710711/**************************************************************************712u l t o a713**714**************************************************************************/715char *ultoa(FICL_UNS value, char *string, int radix )716{ /* convert long to string, any base */717char *cp = string;718DPUNS ud;719UNSQR result;720721assert(radix > 1);722assert(radix < 37);723assert(string);724725if (value == 0)726*cp++ = '0';727else728{729ud.hi = 0;730ud.lo = value;731result.quot = value;732733while (ud.lo)734{735result = ficlLongDiv(ud, (FICL_UNS)radix);736ud.lo = result.quot;737*cp++ = digits[result.rem];738}739}740741*cp++ = '\0';742743return strrev(string);744}745746747/**************************************************************************748c a s e F o l d749** Case folds a NULL terminated string in place. All characters750** get converted to lower case.751**************************************************************************/752char *caseFold(char *cp)753{754char *oldCp = cp;755756while (*cp)757{758if (isupper(*cp))759*cp = (char)tolower(*cp);760cp++;761}762763return oldCp;764}765766767/**************************************************************************768s t r i n c m p769** (jws) simplified the code a bit in hopes of appeasing Purify770**************************************************************************/771int strincmp(char *cp1, char *cp2, FICL_UNS count)772{773int i = 0;774775for (; 0 < count; ++cp1, ++cp2, --count)776{777i = tolower(*cp1) - tolower(*cp2);778if (i != 0)779return i;780else if (*cp1 == '\0')781return 0;782}783return 0;784}785786/**************************************************************************787s k i p S p a c e788** Given a string pointer, returns a pointer to the first non-space789** char of the string, or to the NULL terminator if no such char found.790** If the pointer reaches "end" first, stop there. Pass NULL to791** suppress this behavior.792**************************************************************************/793char *skipSpace(char *cp, char *end)794{795assert(cp);796797while ((cp != end) && isspace(*cp))798cp++;799800return cp;801}802803804805806