/*******************************************************************1** f i c l . c2** Forth Inspired Command Language - external interface3** Author: John Sadler ([email protected])4** Created: 19 July 19975** $Id: ficl.c,v 1.16 2001/12/05 07:21:34 jsadler Exp $6*******************************************************************/7/*8** This is an ANS Forth interpreter written in C.9** Ficl uses Forth syntax for its commands, but turns the Forth10** model on its head in other respects.11** Ficl provides facilities for interoperating12** with programs written in C: C functions can be exported to Ficl,13** and Ficl commands can be executed via a C calling interface. The14** interpreter is re-entrant, so it can be used in multiple instances15** in a multitasking system. Unlike Forth, Ficl's outer interpreter16** expects a text block as input, and returns to the caller after each17** text block, so the data pump is somewhere in external code in the18** style of TCL.19**20** Code is written in ANSI C for portability.21*/22/*23** Copyright (c) 1997-2001 John Sadler ([email protected])24** All rights reserved.25**26** Get the latest Ficl release at http://ficl.sourceforge.net27**28** I am interested in hearing from anyone who uses ficl. If you have29** a problem, a success story, a defect, an enhancement request, or30** if you would like to contribute to the ficl release, please31** contact me by email at the address above.32**33** L I C E N S E and D I S C L A I M E R34**35** Redistribution and use in source and binary forms, with or without36** modification, are permitted provided that the following conditions37** are met:38** 1. Redistributions of source code must retain the above copyright39** notice, this list of conditions and the following disclaimer.40** 2. Redistributions in binary form must reproduce the above copyright41** notice, this list of conditions and the following disclaimer in the42** documentation and/or other materials provided with the distribution.43**44** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND45** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE46** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE47** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE48** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL49** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS50** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)51** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT52** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY53** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF54** SUCH DAMAGE.55*/565758#ifdef TESTMAIN59#include <stdlib.h>60#else61#include <stand.h>62#endif63#include <string.h>64#include "ficl.h"656667/*68** System statics69** Each FICL_SYSTEM builds a global dictionary during its start70** sequence. This is shared by all virtual machines of that system.71** Therefore only one VM can update the dictionary72** at a time. The system imports a locking function that73** you can override in order to control update access to74** the dictionary. The function is stubbed out by default,75** but you can insert one: #define FICL_MULTITHREAD 176** and supply your own version of ficlLockDictionary.77*/78static int defaultStack = FICL_DEFAULT_STACK;798081static void ficlSetVersionEnv(FICL_SYSTEM *pSys);828384/**************************************************************************85f i c l I n i t S y s t e m86** Binds a global dictionary to the interpreter system.87** You specify the address and size of the allocated area.88** After that, ficl manages it.89** First step is to set up the static pointers to the area.90** Then write the "precompiled" portion of the dictionary in.91** The dictionary needs to be at least large enough to hold the92** precompiled part. Try 1K cells minimum. Use "words" to find93** out how much of the dictionary is used at any time.94**************************************************************************/95FICL_SYSTEM *ficlInitSystemEx(FICL_SYSTEM_INFO *fsi)96{97int nDictCells;98int nEnvCells;99FICL_SYSTEM *pSys = ficlMalloc(sizeof (FICL_SYSTEM));100101assert(pSys);102assert(fsi->size == sizeof (FICL_SYSTEM_INFO));103104memset(pSys, 0, sizeof (FICL_SYSTEM));105106nDictCells = fsi->nDictCells;107if (nDictCells <= 0)108nDictCells = FICL_DEFAULT_DICT;109110nEnvCells = fsi->nEnvCells;111if (nEnvCells <= 0)112nEnvCells = FICL_DEFAULT_DICT;113114pSys->dp = dictCreateHashed((unsigned)nDictCells, HASHSIZE);115pSys->dp->pForthWords->name = "forth-wordlist";116117pSys->envp = dictCreate((unsigned)nEnvCells);118pSys->envp->pForthWords->name = "environment";119120pSys->textOut = fsi->textOut;121pSys->pExtend = fsi->pExtend;122123#if FICL_WANT_LOCALS124/*125** The locals dictionary is only searched while compiling,126** but this is where speed is most important. On the other127** hand, the dictionary gets emptied after each use of locals128** The need to balance search speed with the cost of the 'empty'129** operation led me to select a single-threaded list...130*/131pSys->localp = dictCreate((unsigned)FICL_MAX_LOCALS * CELLS_PER_WORD);132#endif133134/*135** Build the precompiled dictionary and load softwords. We need a temporary136** VM to do this - ficlNewVM links one to the head of the system VM list.137** ficlCompilePlatform (defined in win32.c, for example) adds platform specific words.138*/139ficlCompileCore(pSys);140ficlCompilePrefix(pSys);141#if FICL_WANT_FLOAT142ficlCompileFloat(pSys);143#endif144#if FICL_PLATFORM_EXTEND145ficlCompilePlatform(pSys);146#endif147ficlSetVersionEnv(pSys);148149/*150** Establish the parse order. Note that prefixes precede numbers -151** this allows constructs like "0b101010" which might parse as a152** hex value otherwise.153*/154ficlAddPrecompiledParseStep(pSys, "?prefix", ficlParsePrefix);155ficlAddPrecompiledParseStep(pSys, "?number", ficlParseNumber);156#if FICL_WANT_FLOAT157ficlAddPrecompiledParseStep(pSys, ">float", ficlParseFloatNumber);158#endif159160/*161** Now create a temporary VM to compile the softwords. Since all VMs are162** linked into the vmList of FICL_SYSTEM, we don't have to pass the VM163** to ficlCompileSoftCore -- it just hijacks whatever it finds in the VM list.164** ficl 2.05: vmCreate no longer depends on the presence of INTERPRET in the165** dictionary, so a VM can be created before the dictionary is built. It just166** can't do much...167*/168ficlNewVM(pSys);169ficlCompileSoftCore(pSys);170ficlFreeVM(pSys->vmList);171172173return pSys;174}175176177FICL_SYSTEM *ficlInitSystem(int nDictCells)178{179FICL_SYSTEM_INFO fsi;180ficlInitInfo(&fsi);181fsi.nDictCells = nDictCells;182return ficlInitSystemEx(&fsi);183}184185186/**************************************************************************187f i c l A d d P a r s e S t e p188** Appends a parse step function to the end of the parse list (see189** FICL_PARSE_STEP notes in ficl.h for details). Returns 0 if successful,190** nonzero if there's no more room in the list.191**************************************************************************/192int ficlAddParseStep(FICL_SYSTEM *pSys, FICL_WORD *pFW)193{194int i;195for (i = 0; i < FICL_MAX_PARSE_STEPS; i++)196{197if (pSys->parseList[i] == NULL)198{199pSys->parseList[i] = pFW;200return 0;201}202}203204return 1;205}206207208/*209** Compile a word into the dictionary that invokes the specified FICL_PARSE_STEP210** function. It is up to the user (as usual in Forth) to make sure the stack211** preconditions are valid (there needs to be a counted string on top of the stack)212** before using the resulting word.213*/214void ficlAddPrecompiledParseStep(FICL_SYSTEM *pSys, char *name, FICL_PARSE_STEP pStep)215{216FICL_DICT *dp = pSys->dp;217FICL_WORD *pFW = dictAppendWord(dp, name, parseStepParen, FW_DEFAULT);218dictAppendCell(dp, LVALUEtoCELL(pStep));219ficlAddParseStep(pSys, pFW);220}221222223/*224** This word lists the parse steps in order225*/226void ficlListParseSteps(FICL_VM *pVM)227{228int i;229FICL_SYSTEM *pSys = pVM->pSys;230assert(pSys);231232vmTextOut(pVM, "Parse steps:", 1);233vmTextOut(pVM, "lookup", 1);234235for (i = 0; i < FICL_MAX_PARSE_STEPS; i++)236{237if (pSys->parseList[i] != NULL)238{239vmTextOut(pVM, pSys->parseList[i]->name, 1);240}241else break;242}243return;244}245246247/**************************************************************************248f i c l N e w V M249** Create a new virtual machine and link it into the system list250** of VMs for later cleanup by ficlTermSystem.251**************************************************************************/252FICL_VM *ficlNewVM(FICL_SYSTEM *pSys)253{254FICL_VM *pVM = vmCreate(NULL, defaultStack, defaultStack);255pVM->link = pSys->vmList;256pVM->pSys = pSys;257pVM->pExtend = pSys->pExtend;258vmSetTextOut(pVM, pSys->textOut);259260pSys->vmList = pVM;261return pVM;262}263264265/**************************************************************************266f i c l F r e e V M267** Removes the VM in question from the system VM list and deletes the268** memory allocated to it. This is an optional call, since ficlTermSystem269** will do this cleanup for you. This function is handy if you're going to270** do a lot of dynamic creation of VMs.271**************************************************************************/272void ficlFreeVM(FICL_VM *pVM)273{274FICL_SYSTEM *pSys = pVM->pSys;275FICL_VM *pList = pSys->vmList;276277assert(pVM != NULL);278279if (pSys->vmList == pVM)280{281pSys->vmList = pSys->vmList->link;282}283else for (; pList != NULL; pList = pList->link)284{285if (pList->link == pVM)286{287pList->link = pVM->link;288break;289}290}291292if (pList)293vmDelete(pVM);294return;295}296297298/**************************************************************************299f i c l B u i l d300** Builds a word into the dictionary.301** Preconditions: system must be initialized, and there must302** be enough space for the new word's header! Operation is303** controlled by ficlLockDictionary, so any initialization304** required by your version of the function (if you overrode305** it) must be complete at this point.306** Parameters:307** name -- duh, the name of the word308** code -- code to execute when the word is invoked - must take a single param309** pointer to a FICL_VM310** flags -- 0 or more of F_IMMEDIATE, F_COMPILE, use bitwise OR!311**312**************************************************************************/313int ficlBuild(FICL_SYSTEM *pSys, char *name, FICL_CODE code, char flags)314{315#if FICL_MULTITHREAD316int err = ficlLockDictionary(TRUE);317if (err) return err;318#endif /* FICL_MULTITHREAD */319320assert(dictCellsAvail(pSys->dp) > sizeof (FICL_WORD) / sizeof (CELL));321dictAppendWord(pSys->dp, name, code, flags);322323ficlLockDictionary(FALSE);324return 0;325}326327328/**************************************************************************329f i c l E v a l u a t e330** Wrapper for ficlExec() which sets SOURCE-ID to -1.331**************************************************************************/332int ficlEvaluate(FICL_VM *pVM, char *pText)333{334int returnValue;335CELL id = pVM->sourceID;336pVM->sourceID.i = -1;337returnValue = ficlExecC(pVM, pText, -1);338pVM->sourceID = id;339return returnValue;340}341342343/**************************************************************************344f i c l E x e c345** Evaluates a block of input text in the context of the346** specified interpreter. Emits any requested output to the347** interpreter's output function.348**349** Contains the "inner interpreter" code in a tight loop350**351** Returns one of the VM_XXXX codes defined in ficl.h:352** VM_OUTOFTEXT is the normal exit condition353** VM_ERREXIT means that the interp encountered a syntax error354** and the vm has been reset to recover (some or all355** of the text block got ignored356** VM_USEREXIT means that the user executed the "bye" command357** to shut down the interpreter. This would be a good358** time to delete the vm, etc -- or you can ignore this359** signal.360**************************************************************************/361int ficlExec(FICL_VM *pVM, char *pText)362{363return ficlExecC(pVM, pText, -1);364}365366int ficlExecC(FICL_VM *pVM, char *pText, FICL_INT size)367{368FICL_SYSTEM *pSys = pVM->pSys;369FICL_DICT *dp = pSys->dp;370371int except;372jmp_buf vmState;373jmp_buf *oldState;374TIB saveTib;375376assert(pVM);377assert(pSys->pInterp[0]);378379if (size < 0)380size = strlen(pText);381382vmPushTib(pVM, pText, size, &saveTib);383384/*385** Save and restore VM's jmp_buf to enable nested calls to ficlExec386*/387oldState = pVM->pState;388pVM->pState = &vmState; /* This has to come before the setjmp! */389except = setjmp(vmState);390391switch (except)392{393case 0:394if (pVM->fRestart)395{396pVM->runningWord->code(pVM);397pVM->fRestart = 0;398}399else400{ /* set VM up to interpret text */401vmPushIP(pVM, &(pSys->pInterp[0]));402}403404vmInnerLoop(pVM);405break;406407case VM_RESTART:408pVM->fRestart = 1;409except = VM_OUTOFTEXT;410break;411412case VM_OUTOFTEXT:413vmPopIP(pVM);414#ifdef TESTMAIN415if ((pVM->state != COMPILE) && (pVM->sourceID.i == 0))416ficlTextOut(pVM, FICL_PROMPT, 0);417#endif418break;419420case VM_USEREXIT:421case VM_INNEREXIT:422case VM_BREAK:423break;424425case VM_QUIT:426if (pVM->state == COMPILE)427{428dictAbortDefinition(dp);429#if FICL_WANT_LOCALS430dictEmpty(pSys->localp, pSys->localp->pForthWords->size);431#endif432}433vmQuit(pVM);434break;435436case VM_ERREXIT:437case VM_ABORT:438case VM_ABORTQ:439default: /* user defined exit code?? */440if (pVM->state == COMPILE)441{442dictAbortDefinition(dp);443#if FICL_WANT_LOCALS444dictEmpty(pSys->localp, pSys->localp->pForthWords->size);445#endif446}447dictResetSearchOrder(dp);448vmReset(pVM);449break;450}451452pVM->pState = oldState;453vmPopTib(pVM, &saveTib);454return (except);455}456457458/**************************************************************************459f i c l E x e c X T460** Given a pointer to a FICL_WORD, push an inner interpreter and461** execute the word to completion. This is in contrast with vmExecute,462** which does not guarantee that the word will have completed when463** the function returns (ie in the case of colon definitions, which464** need an inner interpreter to finish)465**466** Returns one of the VM_XXXX exception codes listed in ficl.h. Normal467** exit condition is VM_INNEREXIT, ficl's private signal to exit the468** inner loop under normal circumstances. If another code is thrown to469** exit the loop, this function will re-throw it if it's nested under470** itself or ficlExec.471**472** NOTE: this function is intended so that C code can execute ficlWords473** given their address in the dictionary (xt).474**************************************************************************/475int ficlExecXT(FICL_VM *pVM, FICL_WORD *pWord)476{477int except;478jmp_buf vmState;479jmp_buf *oldState;480FICL_WORD *oldRunningWord;481482assert(pVM);483assert(pVM->pSys->pExitInner);484485/*486** Save the runningword so that RESTART behaves correctly487** over nested calls.488*/489oldRunningWord = pVM->runningWord;490/*491** Save and restore VM's jmp_buf to enable nested calls492*/493oldState = pVM->pState;494pVM->pState = &vmState; /* This has to come before the setjmp! */495except = setjmp(vmState);496497if (except)498vmPopIP(pVM);499else500vmPushIP(pVM, &(pVM->pSys->pExitInner));501502switch (except)503{504case 0:505vmExecute(pVM, pWord);506vmInnerLoop(pVM);507break;508509case VM_INNEREXIT:510case VM_BREAK:511break;512513case VM_RESTART:514case VM_OUTOFTEXT:515case VM_USEREXIT:516case VM_QUIT:517case VM_ERREXIT:518case VM_ABORT:519case VM_ABORTQ:520default: /* user defined exit code?? */521if (oldState)522{523pVM->pState = oldState;524vmThrow(pVM, except);525}526break;527}528529pVM->pState = oldState;530pVM->runningWord = oldRunningWord;531return (except);532}533534535/**************************************************************************536f i c l L o o k u p537** Look in the system dictionary for a match to the given name. If538** found, return the address of the corresponding FICL_WORD. Otherwise539** return NULL.540**************************************************************************/541FICL_WORD *ficlLookup(FICL_SYSTEM *pSys, char *name)542{543STRINGINFO si;544SI_PSZ(si, name);545return dictLookup(pSys->dp, si);546}547548549/**************************************************************************550f i c l G e t D i c t551** Returns the address of the system dictionary552**************************************************************************/553FICL_DICT *ficlGetDict(FICL_SYSTEM *pSys)554{555return pSys->dp;556}557558559/**************************************************************************560f i c l G e t E n v561** Returns the address of the system environment space562**************************************************************************/563FICL_DICT *ficlGetEnv(FICL_SYSTEM *pSys)564{565return pSys->envp;566}567568569/**************************************************************************570f i c l S e t E n v571** Create an environment variable with a one-CELL payload. ficlSetEnvD572** makes one with a two-CELL payload.573**************************************************************************/574void ficlSetEnv(FICL_SYSTEM *pSys, char *name, FICL_UNS value)575{576STRINGINFO si;577FICL_WORD *pFW;578FICL_DICT *envp = pSys->envp;579580SI_PSZ(si, name);581pFW = dictLookup(envp, si);582583if (pFW == NULL)584{585dictAppendWord(envp, name, constantParen, FW_DEFAULT);586dictAppendCell(envp, LVALUEtoCELL(value));587}588else589{590pFW->param[0] = LVALUEtoCELL(value);591}592593return;594}595596void ficlSetEnvD(FICL_SYSTEM *pSys, char *name, FICL_UNS hi, FICL_UNS lo)597{598FICL_WORD *pFW;599STRINGINFO si;600FICL_DICT *envp = pSys->envp;601SI_PSZ(si, name);602pFW = dictLookup(envp, si);603604if (pFW == NULL)605{606dictAppendWord(envp, name, twoConstParen, FW_DEFAULT);607dictAppendCell(envp, LVALUEtoCELL(lo));608dictAppendCell(envp, LVALUEtoCELL(hi));609}610else611{612pFW->param[0] = LVALUEtoCELL(lo);613pFW->param[1] = LVALUEtoCELL(hi);614}615616return;617}618619620/**************************************************************************621f i c l G e t L o c622** Returns the address of the system locals dictionary. This dict is623** only used during compilation, and is shared by all VMs.624**************************************************************************/625#if FICL_WANT_LOCALS626FICL_DICT *ficlGetLoc(FICL_SYSTEM *pSys)627{628return pSys->localp;629}630#endif631632633634/**************************************************************************635f i c l S e t S t a c k S i z e636** Set the stack sizes (return and parameter) to be used for all637** subsequently created VMs. Returns actual stack size to be used.638**************************************************************************/639int ficlSetStackSize(int nStackCells)640{641if (nStackCells >= FICL_DEFAULT_STACK)642defaultStack = nStackCells;643else644defaultStack = FICL_DEFAULT_STACK;645646return defaultStack;647}648649650/**************************************************************************651f i c l T e r m S y s t e m652** Tear the system down by deleting the dictionaries and all VMs.653** This saves you from having to keep track of all that stuff.654**************************************************************************/655void ficlTermSystem(FICL_SYSTEM *pSys)656{657if (pSys->dp)658dictDelete(pSys->dp);659pSys->dp = NULL;660661if (pSys->envp)662dictDelete(pSys->envp);663pSys->envp = NULL;664665#if FICL_WANT_LOCALS666if (pSys->localp)667dictDelete(pSys->localp);668pSys->localp = NULL;669#endif670671while (pSys->vmList != NULL)672{673FICL_VM *pVM = pSys->vmList;674pSys->vmList = pSys->vmList->link;675vmDelete(pVM);676}677678ficlFree(pSys);679pSys = NULL;680return;681}682683684/**************************************************************************685f i c l S e t V e r s i o n E n v686** Create a double cell environment constant for the version ID687**************************************************************************/688static void ficlSetVersionEnv(FICL_SYSTEM *pSys)689{690ficlSetEnvD(pSys, "ficl-version", FICL_VER_MAJOR, FICL_VER_MINOR);691ficlSetEnv (pSys, "ficl-robust", FICL_ROBUST);692return;693}694695696697