1 /*******************************************************************
3 ** Forth Inspired Command Language - external interface
4 ** Author: John Sadler (john_sadler@alum.mit.edu)
5 ** Created: 19 July 1997
6 ** $Id: ficl.c,v 1.16 2001/12/05 07:21:34 jsadler Exp $
7 *******************************************************************/
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 in the
21 ** Code is written in ANSI C for portability.
24 ** Copyright (c) 1997-2001 John Sadler (john_sadler@alum.mit.edu)
25 ** All rights reserved.
27 ** Get the latest Ficl release at http://ficl.sourceforge.net
29 ** I am interested in hearing from anyone who uses ficl. If you have
30 ** a problem, a success story, a defect, an enhancement request, or
31 ** if you would like to contribute to the ficl release, please
32 ** contact me by email at the address above.
34 ** L I C E N S E and D I S C L A I M E R
36 ** Redistribution and use in source and binary forms, with or without
37 ** modification, are permitted provided that the following conditions
39 ** 1. Redistributions of source code must retain the above copyright
40 ** notice, this list of conditions and the following disclaimer.
41 ** 2. Redistributions in binary form must reproduce the above copyright
42 ** notice, this list of conditions and the following disclaimer in the
43 ** documentation and/or other materials provided with the distribution.
45 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
46 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48 ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
49 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
70 ** Each FICL_SYSTEM builds a global dictionary during its start
71 ** sequence. This is shared by all virtual machines of that system.
72 ** Therefore only one VM can update the dictionary
73 ** at a time. The system imports a locking function that
74 ** you can override in order to control update access to
75 ** the dictionary. The function is stubbed out by default,
76 ** but you can insert one: #define FICL_MULTITHREAD 1
77 ** and supply your own version of ficlLockDictionary.
79 static int defaultStack
= FICL_DEFAULT_STACK
;
82 static void ficlSetVersionEnv(FICL_SYSTEM
*pSys
);
85 /**************************************************************************
86 f i c l I n i t S y s t e m
87 ** Binds a global dictionary to the interpreter system.
88 ** You specify the address and size of the allocated area.
89 ** After that, ficl manages it.
90 ** First step is to set up the static pointers to the area.
91 ** Then write the "precompiled" portion of the dictionary in.
92 ** The dictionary needs to be at least large enough to hold the
93 ** precompiled part. Try 1K cells minimum. Use "words" to find
94 ** out how much of the dictionary is used at any time.
95 **************************************************************************/
96 FICL_SYSTEM
*ficlInitSystemEx(FICL_SYSTEM_INFO
*fsi
)
100 FICL_SYSTEM
*pSys
= ficlMalloc(sizeof (FICL_SYSTEM
));
103 assert(fsi
->size
== sizeof (FICL_SYSTEM_INFO
));
105 memset(pSys
, 0, sizeof (FICL_SYSTEM
));
107 nDictCells
= fsi
->nDictCells
;
109 nDictCells
= FICL_DEFAULT_DICT
;
111 nEnvCells
= fsi
->nEnvCells
;
113 nEnvCells
= FICL_DEFAULT_DICT
;
115 pSys
->dp
= dictCreateHashed((unsigned)nDictCells
, HASHSIZE
);
116 pSys
->dp
->pForthWords
->name
= "forth-wordlist";
118 pSys
->envp
= dictCreate((unsigned)nEnvCells
);
119 pSys
->envp
->pForthWords
->name
= "environment";
121 pSys
->textOut
= fsi
->textOut
;
122 pSys
->pExtend
= fsi
->pExtend
;
126 ** The locals dictionary is only searched while compiling,
127 ** but this is where speed is most important. On the other
128 ** hand, the dictionary gets emptied after each use of locals
129 ** The need to balance search speed with the cost of the 'empty'
130 ** operation led me to select a single-threaded list...
132 pSys
->localp
= dictCreate((unsigned)FICL_MAX_LOCALS
* CELLS_PER_WORD
);
136 ** Build the precompiled dictionary and load softwords. We need a temporary
137 ** VM to do this - ficlNewVM links one to the head of the system VM list.
138 ** ficlCompilePlatform (defined in win32.c, for example) adds platform specific words.
140 ficlCompileCore(pSys
);
141 ficlCompilePrefix(pSys
);
143 ficlCompileFloat(pSys
);
145 #if FICL_PLATFORM_EXTEND
146 ficlCompilePlatform(pSys
);
148 ficlSetVersionEnv(pSys
);
151 ** Establish the parse order. Note that prefixes precede numbers -
152 ** this allows constructs like "0b101010" which might parse as a
153 ** hex value otherwise.
155 ficlAddPrecompiledParseStep(pSys
, "?prefix", ficlParsePrefix
);
156 ficlAddPrecompiledParseStep(pSys
, "?number", ficlParseNumber
);
158 ficlAddPrecompiledParseStep(pSys
, ">float", ficlParseFloatNumber
);
162 ** Now create a temporary VM to compile the softwords. Since all VMs are
163 ** linked into the vmList of FICL_SYSTEM, we don't have to pass the VM
164 ** to ficlCompileSoftCore -- it just hijacks whatever it finds in the VM list.
165 ** ficl 2.05: vmCreate no longer depends on the presence of INTERPRET in the
166 ** dictionary, so a VM can be created before the dictionary is built. It just
170 ficlCompileSoftCore(pSys
);
171 ficlFreeVM(pSys
->vmList
);
178 FICL_SYSTEM
*ficlInitSystem(int nDictCells
)
180 FICL_SYSTEM_INFO fsi
;
182 fsi
.nDictCells
= nDictCells
;
183 return ficlInitSystemEx(&fsi
);
187 /**************************************************************************
188 f i c l A d d P a r s e S t e p
189 ** Appends a parse step function to the end of the parse list (see
190 ** FICL_PARSE_STEP notes in ficl.h for details). Returns 0 if successful,
191 ** nonzero if there's no more room in the list.
192 **************************************************************************/
193 int ficlAddParseStep(FICL_SYSTEM
*pSys
, FICL_WORD
*pFW
)
196 for (i
= 0; i
< FICL_MAX_PARSE_STEPS
; i
++)
198 if (pSys
->parseList
[i
] == NULL
)
200 pSys
->parseList
[i
] = pFW
;
210 ** Compile a word into the dictionary that invokes the specified FICL_PARSE_STEP
211 ** function. It is up to the user (as usual in Forth) to make sure the stack
212 ** preconditions are valid (there needs to be a counted string on top of the stack)
213 ** before using the resulting word.
215 void ficlAddPrecompiledParseStep(FICL_SYSTEM
*pSys
, char *name
, FICL_PARSE_STEP pStep
)
217 FICL_DICT
*dp
= pSys
->dp
;
218 FICL_WORD
*pFW
= dictAppendWord(dp
, name
, parseStepParen
, FW_DEFAULT
);
219 dictAppendCell(dp
, LVALUEtoCELL(pStep
));
220 ficlAddParseStep(pSys
, pFW
);
225 ** This word lists the parse steps in order
227 void ficlListParseSteps(FICL_VM
*pVM
)
230 FICL_SYSTEM
*pSys
= pVM
->pSys
;
233 vmTextOut(pVM
, "Parse steps:", 1);
234 vmTextOut(pVM
, "lookup", 1);
236 for (i
= 0; i
< FICL_MAX_PARSE_STEPS
; i
++)
238 if (pSys
->parseList
[i
] != NULL
)
240 vmTextOut(pVM
, pSys
->parseList
[i
]->name
, 1);
248 /**************************************************************************
250 ** Create a new virtual machine and link it into the system list
251 ** of VMs for later cleanup by ficlTermSystem.
252 **************************************************************************/
253 FICL_VM
*ficlNewVM(FICL_SYSTEM
*pSys
)
255 FICL_VM
*pVM
= vmCreate(NULL
, defaultStack
, defaultStack
);
256 pVM
->link
= pSys
->vmList
;
258 pVM
->pExtend
= pSys
->pExtend
;
259 vmSetTextOut(pVM
, pSys
->textOut
);
266 /**************************************************************************
268 ** Removes the VM in question from the system VM list and deletes the
269 ** memory allocated to it. This is an optional call, since ficlTermSystem
270 ** will do this cleanup for you. This function is handy if you're going to
271 ** do a lot of dynamic creation of VMs.
272 **************************************************************************/
273 void ficlFreeVM(FICL_VM
*pVM
)
275 FICL_SYSTEM
*pSys
= pVM
->pSys
;
276 FICL_VM
*pList
= pSys
->vmList
;
280 if (pSys
->vmList
== pVM
)
282 pSys
->vmList
= pSys
->vmList
->link
;
284 else for (; pList
!= NULL
; pList
= pList
->link
)
286 if (pList
->link
== pVM
)
288 pList
->link
= pVM
->link
;
299 /**************************************************************************
301 ** Builds a word into the dictionary.
302 ** Preconditions: system must be initialized, and there must
303 ** be enough space for the new word's header! Operation is
304 ** controlled by ficlLockDictionary, so any initialization
305 ** required by your version of the function (if you overrode
306 ** it) must be complete at this point.
308 ** name -- duh, the name of the word
309 ** code -- code to execute when the word is invoked - must take a single param
310 ** pointer to a FICL_VM
311 ** flags -- 0 or more of F_IMMEDIATE, F_COMPILE, use bitwise OR!
313 **************************************************************************/
314 int ficlBuild(FICL_SYSTEM
*pSys
, char *name
, FICL_CODE code
, char flags
)
317 int err
= ficlLockDictionary(TRUE
);
319 #endif /* FICL_MULTITHREAD */
321 assert(dictCellsAvail(pSys
->dp
) > sizeof (FICL_WORD
) / sizeof (CELL
));
322 dictAppendWord(pSys
->dp
, name
, code
, flags
);
324 ficlLockDictionary(FALSE
);
329 /**************************************************************************
330 f i c l E v a l u a t e
331 ** Wrapper for ficlExec() which sets SOURCE-ID to -1.
332 **************************************************************************/
333 int ficlEvaluate(FICL_VM
*pVM
, char *pText
)
336 CELL id
= pVM
->sourceID
;
337 pVM
->sourceID
.i
= -1;
338 returnValue
= ficlExecC(pVM
, pText
, -1);
344 /**************************************************************************
346 ** Evaluates a block of input text in the context of the
347 ** specified interpreter. Emits any requested output to the
348 ** interpreter's output function.
350 ** Contains the "inner interpreter" code in a tight loop
352 ** Returns one of the VM_XXXX codes defined in ficl.h:
353 ** VM_OUTOFTEXT is the normal exit condition
354 ** VM_ERREXIT means that the interp encountered a syntax error
355 ** and the vm has been reset to recover (some or all
356 ** of the text block got ignored
357 ** VM_USEREXIT means that the user executed the "bye" command
358 ** to shut down the interpreter. This would be a good
359 ** time to delete the vm, etc -- or you can ignore this
361 **************************************************************************/
362 int ficlExec(FICL_VM
*pVM
, char *pText
)
364 return ficlExecC(pVM
, pText
, -1);
367 int ficlExecC(FICL_VM
*pVM
, char *pText
, FICL_INT size
)
369 FICL_SYSTEM
*pSys
= pVM
->pSys
;
370 FICL_DICT
*dp
= pSys
->dp
;
378 assert(pSys
->pInterp
[0]);
381 size
= strlen(pText
);
383 vmPushTib(pVM
, pText
, size
, &saveTib
);
386 ** Save and restore VM's jmp_buf to enable nested calls to ficlExec
388 oldState
= pVM
->pState
;
389 pVM
->pState
= &vmState
; /* This has to come before the setjmp! */
390 except
= setjmp(vmState
);
397 pVM
->runningWord
->code(pVM
);
401 { /* set VM up to interpret text */
402 vmPushIP(pVM
, &(pSys
->pInterp
[0]));
410 except
= VM_OUTOFTEXT
;
416 if ((pVM
->state
!= COMPILE
) && (pVM
->sourceID
.i
== 0))
417 ficlTextOut(pVM
, FICL_PROMPT
, 0);
427 if (pVM
->state
== COMPILE
)
429 dictAbortDefinition(dp
);
431 dictEmpty(pSys
->localp
, pSys
->localp
->pForthWords
->size
);
440 default: /* user defined exit code?? */
441 if (pVM
->state
== COMPILE
)
443 dictAbortDefinition(dp
);
445 dictEmpty(pSys
->localp
, pSys
->localp
->pForthWords
->size
);
448 dictResetSearchOrder(dp
);
453 pVM
->pState
= oldState
;
454 vmPopTib(pVM
, &saveTib
);
459 /**************************************************************************
461 ** Given a pointer to a FICL_WORD, push an inner interpreter and
462 ** execute the word to completion. This is in contrast with vmExecute,
463 ** which does not guarantee that the word will have completed when
464 ** the function returns (ie in the case of colon definitions, which
465 ** need an inner interpreter to finish)
467 ** Returns one of the VM_XXXX exception codes listed in ficl.h. Normal
468 ** exit condition is VM_INNEREXIT, ficl's private signal to exit the
469 ** inner loop under normal circumstances. If another code is thrown to
470 ** exit the loop, this function will re-throw it if it's nested under
471 ** itself or ficlExec.
473 ** NOTE: this function is intended so that C code can execute ficlWords
474 ** given their address in the dictionary (xt).
475 **************************************************************************/
476 int ficlExecXT(FICL_VM
*pVM
, FICL_WORD
*pWord
)
481 FICL_WORD
*oldRunningWord
;
484 assert(pVM
->pSys
->pExitInner
);
487 ** Save the runningword so that RESTART behaves correctly
488 ** over nested calls.
490 oldRunningWord
= pVM
->runningWord
;
492 ** Save and restore VM's jmp_buf to enable nested calls
494 oldState
= pVM
->pState
;
495 pVM
->pState
= &vmState
; /* This has to come before the setjmp! */
496 except
= setjmp(vmState
);
501 vmPushIP(pVM
, &(pVM
->pSys
->pExitInner
));
506 vmExecute(pVM
, pWord
);
521 default: /* user defined exit code?? */
524 pVM
->pState
= oldState
;
525 vmThrow(pVM
, except
);
530 pVM
->pState
= oldState
;
531 pVM
->runningWord
= oldRunningWord
;
536 /**************************************************************************
538 ** Look in the system dictionary for a match to the given name. If
539 ** found, return the address of the corresponding FICL_WORD. Otherwise
541 **************************************************************************/
542 FICL_WORD
*ficlLookup(FICL_SYSTEM
*pSys
, char *name
)
546 return dictLookup(pSys
->dp
, si
);
550 /**************************************************************************
551 f i c l G e t D i c t
552 ** Returns the address of the system dictionary
553 **************************************************************************/
554 FICL_DICT
*ficlGetDict(FICL_SYSTEM
*pSys
)
560 /**************************************************************************
562 ** Returns the address of the system environment space
563 **************************************************************************/
564 FICL_DICT
*ficlGetEnv(FICL_SYSTEM
*pSys
)
570 /**************************************************************************
572 ** Create an environment variable with a one-CELL payload. ficlSetEnvD
573 ** makes one with a two-CELL payload.
574 **************************************************************************/
575 void ficlSetEnv(FICL_SYSTEM
*pSys
, char *name
, FICL_UNS value
)
579 FICL_DICT
*envp
= pSys
->envp
;
582 pFW
= dictLookup(envp
, si
);
586 dictAppendWord(envp
, name
, constantParen
, FW_DEFAULT
);
587 dictAppendCell(envp
, LVALUEtoCELL(value
));
591 pFW
->param
[0] = LVALUEtoCELL(value
);
597 void ficlSetEnvD(FICL_SYSTEM
*pSys
, char *name
, FICL_UNS hi
, FICL_UNS lo
)
601 FICL_DICT
*envp
= pSys
->envp
;
603 pFW
= dictLookup(envp
, si
);
607 dictAppendWord(envp
, name
, twoConstParen
, FW_DEFAULT
);
608 dictAppendCell(envp
, LVALUEtoCELL(lo
));
609 dictAppendCell(envp
, LVALUEtoCELL(hi
));
613 pFW
->param
[0] = LVALUEtoCELL(lo
);
614 pFW
->param
[1] = LVALUEtoCELL(hi
);
621 /**************************************************************************
623 ** Returns the address of the system locals dictionary. This dict is
624 ** only used during compilation, and is shared by all VMs.
625 **************************************************************************/
627 FICL_DICT
*ficlGetLoc(FICL_SYSTEM
*pSys
)
635 /**************************************************************************
636 f i c l S e t S t a c k S i z e
637 ** Set the stack sizes (return and parameter) to be used for all
638 ** subsequently created VMs. Returns actual stack size to be used.
639 **************************************************************************/
640 int ficlSetStackSize(int nStackCells
)
642 if (nStackCells
>= FICL_DEFAULT_STACK
)
643 defaultStack
= nStackCells
;
645 defaultStack
= FICL_DEFAULT_STACK
;
651 /**************************************************************************
652 f i c l T e r m S y s t e m
653 ** Tear the system down by deleting the dictionaries and all VMs.
654 ** This saves you from having to keep track of all that stuff.
655 **************************************************************************/
656 void ficlTermSystem(FICL_SYSTEM
*pSys
)
659 dictDelete(pSys
->dp
);
663 dictDelete(pSys
->envp
);
668 dictDelete(pSys
->localp
);
672 while (pSys
->vmList
!= NULL
)
674 FICL_VM
*pVM
= pSys
->vmList
;
675 pSys
->vmList
= pSys
->vmList
->link
;
685 /**************************************************************************
686 f i c l S e t V e r s i o n E n v
687 ** Create a double cell environment constant for the version ID
688 **************************************************************************/
689 static void ficlSetVersionEnv(FICL_SYSTEM
*pSys
)
691 ficlSetEnvD(pSys
, "ficl-version", FICL_VER_MAJOR
, FICL_VER_MINOR
);
692 ficlSetEnv (pSys
, "ficl-robust", FICL_ROBUST
);