ficl.c revision 43613
1/*******************************************************************
2** f i c l . c
3** Forth Inspired Command Language - external interface
4** Author: John Sadler (john_sadler@alum.mit.edu)
5** Created: 19 July 1997
6**
7*******************************************************************/
8/*
9** This is an ANS Forth interpreter written in C.
10** Ficl uses Forth syntax for its commands, but turns the Forth
11** model on its head in other respects.
12** Ficl provides facilities for interoperating
13** with programs written in C: C functions can be exported to Ficl,
14** and Ficl commands can be executed via a C calling interface. The
15** interpreter is re-entrant, so it can be used in multiple instances
16** in a multitasking system. Unlike Forth, Ficl's outer interpreter
17** expects a text block as input, and returns to the caller after each
18** text block, so the data pump is somewhere in external code. This
19** is more like TCL than Forth.
20**
21** Code is written in ANSI C for portability.
22*/
23
24#ifdef TESTMAIN
25#include <stdlib.h>
26#else
27#include <stand.h>
28#endif
29#include <string.h>
30#include "ficl.h"
31
32#ifdef FICL_TRACE
33int ficl_trace = 0;
34#endif
35
36
37/*
38** Local prototypes
39*/
40
41
42/*
43** System statics
44** The system builds a global dictionary during its start
45** sequence. This is shared by all interpreter instances.
46** Therefore only one instance can update the dictionary
47** at a time. The system imports a locking function that
48** you can override in order to control update access to
49** the dictionary. The function is stubbed out by default,
50** but you can insert one: #define FICL_MULTITHREAD 1
51** and supply your own version of ficlLockDictionary.
52*/
53static FICL_DICT *dp     = NULL;
54static FICL_DICT *envp   = NULL;
55#if FICL_WANT_LOCALS
56static FICL_DICT *localp = NULL;
57#endif
58static FICL_VM   *vmList = NULL;
59
60static int defaultStack = FICL_DEFAULT_STACK;
61static int defaultDict  = FICL_DEFAULT_DICT;
62
63
64/**************************************************************************
65                        f i c l I n i t S y s t e m
66** Binds a global dictionary to the interpreter system.
67** You specify the address and size of the allocated area.
68** After that, ficl manages it.
69** First step is to set up the static pointers to the area.
70** Then write the "precompiled" portion of the dictionary in.
71** The dictionary needs to be at least large enough to hold the
72** precompiled part. Try 1K cells minimum. Use "words" to find
73** out how much of the dictionary is used at any time.
74**************************************************************************/
75void ficlInitSystem(int nDictCells)
76{
77    if (dp)
78        dictDelete(dp);
79
80    if (envp)
81        dictDelete(envp);
82
83#if FICL_WANT_LOCALS
84    if (localp)
85        dictDelete(localp);
86#endif
87
88    if (nDictCells <= 0)
89        nDictCells = defaultDict;
90
91    dp     = dictCreateHashed((unsigned)nDictCells, HASHSIZE);
92    envp   = dictCreate(      (unsigned)FICL_DEFAULT_ENV);
93#if FICL_WANT_LOCALS
94    /*
95    ** The locals dictionary is only searched while compiling,
96    ** but this is where speed is most important. On the other
97    ** hand, the dictionary gets emptied after each use of locals
98    ** The need to balance search speed with the cost of the empty
99    ** operation led me to select a single-threaded list...
100    */
101    localp = dictCreate(      (unsigned)FICL_MAX_LOCALS * CELLS_PER_WORD);
102#endif
103
104    ficlCompileCore(dp);
105
106    return;
107}
108
109
110/**************************************************************************
111                        f i c l N e w V M
112** Create a new virtual machine and link it into the system list
113** of VMs for later cleanup by ficlTermSystem. If this is the first
114** VM to be created, use it to compile the words in softcore.c
115**************************************************************************/
116FICL_VM *ficlNewVM(void)
117{
118    FICL_VM *pVM = vmCreate(NULL, defaultStack, defaultStack);
119    pVM->link = vmList;
120
121    /*
122    ** Borrow the first vm to build the soft words in softcore.c
123    */
124    if (vmList == NULL)
125        ficlCompileSoftCore(pVM);
126
127    vmList = pVM;
128    return pVM;
129}
130
131
132/**************************************************************************
133                        f i c l B u i l d
134** Builds a word into the dictionary.
135** Preconditions: system must be initialized, and there must
136** be enough space for the new word's header! Operation is
137** controlled by ficlLockDictionary, so any initialization
138** required by your version of the function (if you overrode
139** it) must be complete at this point.
140** Parameters:
141** name  -- duh, the name of the word
142** code  -- code to execute when the word is invoked - must take a single param
143**          pointer to a FICL_VM
144** flags -- 0 or more of F_IMMEDIATE, F_COMPILE, use bitwise OR!
145**
146**************************************************************************/
147int ficlBuild(char *name, FICL_CODE code, char flags)
148{
149	int err = ficlLockDictionary(TRUE);
150	if (err) return err;
151
152    dictAppendWord(dp, name, code, flags);
153
154	ficlLockDictionary(FALSE);
155	return 0;
156}
157
158
159/**************************************************************************
160                        f i c l E x e c
161** Evaluates a block of input text in the context of the
162** specified interpreter. Emits any requested output to the
163** interpreter's output function.
164**
165** Contains the "inner interpreter" code in a tight loop
166**
167** Returns one of the VM_XXXX codes defined in ficl.h:
168** VM_OUTOFTEXT is the normal exit condition
169** VM_ERREXIT means that the interp encountered a syntax error
170**      and the vm has been reset to recover (some or all
171**      of the text block got ignored
172** VM_USEREXIT means that the user executed the "bye" command
173**      to shut down the interpreter. This would be a good
174**      time to delete the vm, etc -- or you can ignore this
175**      signal.
176**************************************************************************/
177int ficlExec(FICL_VM *pVM, char *pText, INT32 size)
178{
179    int        except;
180    FICL_WORD *tempFW;
181    jmp_buf    vmState;
182    TIB        saveTib;
183    FICL_VM         VM;
184    FICL_STACK      rStack;
185
186    assert(pVM);
187
188    vmPushTib(pVM, pText, size, &saveTib);
189
190    /*
191    ** Save and restore pVM and pVM->rStack to enable nested calls to ficlExec
192    */
193    memcpy((void*)&VM, (void*)pVM, sizeof(FICL_VM));
194    memcpy((void*)&rStack, (void*)pVM->rStack, sizeof(FICL_STACK));
195
196    pVM->pState = &vmState; /* This has to come before the setjmp! */
197    except = setjmp(vmState);
198
199    switch (except)
200    {
201    case 0:
202        if (pVM->fRestart)
203        {
204            pVM->fRestart = 0;
205            pVM->runningWord->code(pVM);
206        }
207
208        /*
209        ** the mysterious inner interpreter...
210        ** vmThrow gets you out of this loop with a longjmp()
211        */
212        for (;;)
213        {
214#ifdef FICL_TRACE
215	    CELL c;
216            char buffer[40];
217#endif
218            tempFW = *pVM->ip++;
219#ifdef FICL_TRACE
220            if (ficl_trace && isAFiclWord(tempFW))
221            {
222	        extern void literalParen(FICL_VM*);
223	        extern void stringLit(FICL_VM*);
224	        extern void ifParen(FICL_VM*);
225	        extern void branchParen(FICL_VM*);
226	        extern void qDoParen(FICL_VM*);
227	        extern void doParen(FICL_VM*);
228	        extern void loopParen(FICL_VM*);
229	        extern void plusLoopParen(FICL_VM*);
230
231                if      (tempFW->code == literalParen)
232                {
233               	    c = *(pVM->ip);
234               	    if (isAFiclWord(c.p))
235               	    {
236                   	    FICL_WORD *pLit = (FICL_WORD *)c.p;
237                   	    sprintf(buffer, "    literal %.*s (%#lx)",
238                       	    pLit->nName, pLit->name, c.u);
239               	    }
240               	    else
241                   	    sprintf(buffer, "    literal %ld (%#lx)", c.i, c.u);
242                }
243                else if (tempFW->code == stringLit)
244                {
245               	    FICL_STRING *sp = (FICL_STRING *)(void *)pVM->ip;
246               	    sprintf(buffer, "    s\" %.*s\"", sp->count, sp->text);
247                }
248                else if (tempFW->code == ifParen)
249                {
250               	    c = *pVM->ip;
251               	    if (c.i > 0)
252                   	    sprintf(buffer, "    if / while (branch rel %ld)", c.i);
253               	    else
254                   	    sprintf(buffer, "    until (branch rel %ld)", c.i);
255                }
256                else if (tempFW->code == branchParen)
257                {
258               	    c = *pVM->ip;
259               	    if (c.i > 0)
260                   	    sprintf(buffer, "    else (branch rel %ld)", c.i);
261               	    else
262                   	    sprintf(buffer, "    repeat (branch rel %ld)", c.i);
263                }
264                else if (tempFW->code == qDoParen)
265                {
266               	    c = *pVM->ip;
267               	    sprintf(buffer, "    ?do (leave abs %#lx)", c.u);
268                }
269                else if (tempFW->code == doParen)
270                {
271               	    c = *pVM->ip;
272               	    sprintf(buffer, "    do (leave abs %#lx)", c.u);
273                }
274                else if (tempFW->code == loopParen)
275                {
276               	    c = *pVM->ip;
277               	    sprintf(buffer, "    loop (branch rel %#ld)", c.i);
278                }
279                else if (tempFW->code == plusLoopParen)
280                {
281               	    c = *pVM->ip;
282               	    sprintf(buffer, "    +loop (branch rel %#ld)", c.i);
283                }
284                else /* default: print word's name */
285                {
286               	    sprintf(buffer, "    %.*s", tempFW->nName, tempFW->name);
287                }
288
289                vmTextOut(pVM, buffer, 1);
290            }
291            else if (ficl_trace) /* probably not a word
292				  * - punt and print value
293				  */
294            {
295           	    sprintf(buffer, "    %ld (%#lx)", ((CELL*)pVM->ip)->i, ((CELL*)pVM->ip)->u);
296           	    vmTextOut(pVM, buffer, 1);
297            }
298#endif FICL_TRACE
299            /*
300            ** inline code for
301            ** vmExecute(pVM, tempFW);
302            */
303            pVM->runningWord = tempFW;
304            tempFW->code(pVM);
305        }
306
307        break;
308
309    case VM_RESTART:
310        pVM->fRestart = 1;
311        except = VM_OUTOFTEXT;
312        break;
313
314    case VM_OUTOFTEXT:
315#ifdef TESTMAIN
316        if ((pVM->state != COMPILE) && (pVM->sourceID.i == 0))
317            ficlTextOut(pVM, FICL_PROMPT, 0);
318#endif
319        break;
320
321    case VM_USEREXIT:
322        break;
323
324    case VM_QUIT:
325        if (pVM->state == COMPILE)
326            dictAbortDefinition(dp);
327
328        memcpy((void*)pVM, (void*)&VM, sizeof(FICL_VM));
329        memcpy((void*)pVM->rStack, (void*)&rStack, sizeof(FICL_STACK));
330        break;
331
332    case VM_ERREXIT:
333    case VM_ABORT:
334    case VM_ABORTQ:
335    default:    /* user defined exit code?? */
336        if (pVM->state == COMPILE)
337        {
338            dictAbortDefinition(dp);
339#if FICL_WANT_LOCALS
340            dictEmpty(localp, localp->pForthWords->size);
341#endif
342        }
343        dictResetSearchOrder(dp);
344        memcpy((void*)pVM, (void*)&VM, sizeof(FICL_VM));
345        memcpy((void*)pVM->rStack, (void*)&rStack, sizeof(FICL_STACK));
346	stackReset(pVM->pStack);
347	pVM->base = 10;
348        break;
349   }
350
351    pVM->pState = VM.pState;
352    vmPopTib(pVM, &saveTib);
353    return (except);
354}
355
356/**************************************************************************
357                        f i c l E x e c F D
358** reads in text from file fd and passes it to ficlExec()
359 * returns VM_OUTOFTEXT on success or the ficlExec() error code on
360 * failure.
361 */
362#define nLINEBUF 256
363int ficlExecFD(FICL_VM *pVM, int fd)
364{
365    char    cp[nLINEBUF];
366    int     i, nLine = 0, rval = VM_OUTOFTEXT;
367    char    ch;
368    CELL    id;
369
370    id = pVM->sourceID;
371    pVM->sourceID.i = fd;
372
373    /* feed each line to ficlExec */
374    while (1) {
375	int status, i;
376
377	i = 0;
378	while ((status = read(fd, &ch, 1)) > 0 && ch != '\n')
379	    cp[i++] = ch;
380        nLine++;
381	if (!i) {
382	    if (status < 1)
383		break;
384	    continue;
385	}
386        rval = ficlExec(pVM, cp, i);
387	if(rval != VM_QUIT && rval != VM_USEREXIT && rval != VM_OUTOFTEXT)
388        {
389            pVM->sourceID = id;
390            return rval;
391        }
392    }
393    /*
394    ** Pass an empty line with SOURCE-ID == 0 to flush
395    ** any pending REFILLs (as required by FILE wordset)
396    */
397    pVM->sourceID.i = -1;
398    ficlExec(pVM, "", 0);
399
400    pVM->sourceID = id;
401    return rval;
402}
403
404/**************************************************************************
405                        f i c l L o o k u p
406** Look in the system dictionary for a match to the given name. If
407** found, return the address of the corresponding FICL_WORD. Otherwise
408** return NULL.
409**************************************************************************/
410FICL_WORD *ficlLookup(char *name)
411{
412    STRINGINFO si;
413    SI_PSZ(si, name);
414    return dictLookup(dp, si);
415}
416
417
418/**************************************************************************
419                        f i c l G e t D i c t
420** Returns the address of the system dictionary
421**************************************************************************/
422FICL_DICT *ficlGetDict(void)
423{
424    return dp;
425}
426
427
428/**************************************************************************
429                        f i c l G e t E n v
430** Returns the address of the system environment space
431**************************************************************************/
432FICL_DICT *ficlGetEnv(void)
433{
434    return envp;
435}
436
437
438/**************************************************************************
439                        f i c l S e t E n v
440** Create an environment variable with a one-CELL payload. ficlSetEnvD
441** makes one with a two-CELL payload.
442**************************************************************************/
443void ficlSetEnv(char *name, UNS32 value)
444{
445    STRINGINFO si;
446    FICL_WORD *pFW;
447
448    SI_PSZ(si, name);
449    pFW = dictLookup(envp, si);
450
451    if (pFW == NULL)
452    {
453        dictAppendWord(envp, name, constantParen, FW_DEFAULT);
454        dictAppendCell(envp, LVALUEtoCELL(value));
455    }
456    else
457    {
458        pFW->param[0] = LVALUEtoCELL(value);
459    }
460
461    return;
462}
463
464void ficlSetEnvD(char *name, UNS32 hi, UNS32 lo)
465{
466    FICL_WORD *pFW;
467    STRINGINFO si;
468    SI_PSZ(si, name);
469    pFW = dictLookup(envp, si);
470
471    if (pFW == NULL)
472    {
473        dictAppendWord(envp, name, twoConstParen, FW_DEFAULT);
474        dictAppendCell(envp, LVALUEtoCELL(lo));
475        dictAppendCell(envp, LVALUEtoCELL(hi));
476    }
477    else
478    {
479        pFW->param[0] = LVALUEtoCELL(lo);
480        pFW->param[1] = LVALUEtoCELL(hi);
481    }
482
483    return;
484}
485
486
487/**************************************************************************
488                        f i c l G e t L o c
489** Returns the address of the system locals dictionary. This dict is
490** only used during compilation, and is shared by all VMs.
491**************************************************************************/
492#if FICL_WANT_LOCALS
493FICL_DICT *ficlGetLoc(void)
494{
495    return localp;
496}
497#endif
498
499
500/**************************************************************************
501                        f i c l T e r m S y s t e m
502** Tear the system down by deleting the dictionaries and all VMs.
503** This saves you from having to keep track of all that stuff.
504**************************************************************************/
505void ficlTermSystem(void)
506{
507    if (dp)
508        dictDelete(dp);
509    dp = NULL;
510
511    if (envp)
512        dictDelete(envp);
513    envp = NULL;
514
515#if FICL_WANT_LOCALS
516    if (localp)
517        dictDelete(localp);
518    localp = NULL;
519#endif
520
521    while (vmList != NULL)
522    {
523        FICL_VM *pVM = vmList;
524        vmList = vmList->link;
525        vmDelete(pVM);
526    }
527
528    return;
529}
530