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
59 * $FreeBSD: src/sys/boot/ficl/ficl.c,v 1.18 2002/04/09 17:45:11 dcs Exp $
60 * $DragonFly: src/sys/boot/ficl/ficl.c,v 1.3 2003/11/10 06:08:33 dillon Exp $
74 ** Each FICL_SYSTEM builds a global dictionary during its start
75 ** sequence. This is shared by all virtual machines of that system.
76 ** Therefore only one VM can update the dictionary
77 ** at a time. The system imports a locking function that
78 ** you can override in order to control update access to
79 ** the dictionary. The function is stubbed out by default,
80 ** but you can insert one: #define FICL_MULTITHREAD 1
81 ** and supply your own version of ficlLockDictionary.
83 static int defaultStack
= FICL_DEFAULT_STACK
;
86 static void ficlSetVersionEnv(FICL_SYSTEM
*pSys
);
89 /**************************************************************************
90 f i c l I n i t S y s t e m
91 ** Binds a global dictionary to the interpreter system.
92 ** You specify the address and size of the allocated area.
93 ** After that, ficl manages it.
94 ** First step is to set up the static pointers to the area.
95 ** Then write the "precompiled" portion of the dictionary in.
96 ** The dictionary needs to be at least large enough to hold the
97 ** precompiled part. Try 1K cells minimum. Use "words" to find
98 ** out how much of the dictionary is used at any time.
99 **************************************************************************/
100 FICL_SYSTEM
*ficlInitSystemEx(FICL_SYSTEM_INFO
*fsi
)
104 FICL_SYSTEM
*pSys
= ficlMalloc(sizeof (FICL_SYSTEM
));
107 assert(fsi
->size
== sizeof (FICL_SYSTEM_INFO
));
109 memset(pSys
, 0, sizeof (FICL_SYSTEM
));
111 nDictCells
= fsi
->nDictCells
;
113 nDictCells
= FICL_DEFAULT_DICT
;
115 nEnvCells
= fsi
->nEnvCells
;
117 nEnvCells
= FICL_DEFAULT_DICT
;
119 pSys
->dp
= dictCreateHashed((unsigned)nDictCells
, HASHSIZE
);
120 pSys
->dp
->pForthWords
->name
= "forth-wordlist";
122 pSys
->envp
= dictCreate((unsigned)nEnvCells
);
123 pSys
->envp
->pForthWords
->name
= "environment";
125 pSys
->textOut
= fsi
->textOut
;
126 pSys
->pExtend
= fsi
->pExtend
;
130 ** The locals dictionary is only searched while compiling,
131 ** but this is where speed is most important. On the other
132 ** hand, the dictionary gets emptied after each use of locals
133 ** The need to balance search speed with the cost of the 'empty'
134 ** operation led me to select a single-threaded list...
136 pSys
->localp
= dictCreate((unsigned)FICL_MAX_LOCALS
* CELLS_PER_WORD
);
140 ** Build the precompiled dictionary and load softwords. We need a temporary
141 ** VM to do this - ficlNewVM links one to the head of the system VM list.
142 ** ficlCompilePlatform (defined in win32.c, for example) adds platform specific words.
144 ficlCompileCore(pSys
);
145 ficlCompilePrefix(pSys
);
147 ficlCompileFloat(pSys
);
149 #if FICL_PLATFORM_EXTEND
150 ficlCompilePlatform(pSys
);
152 ficlSetVersionEnv(pSys
);
155 ** Establish the parse order. Note that prefixes precede numbers -
156 ** this allows constructs like "0b101010" which might parse as a
157 ** hex value otherwise.
159 ficlAddPrecompiledParseStep(pSys
, "?prefix", ficlParsePrefix
);
160 ficlAddPrecompiledParseStep(pSys
, "?number", ficlParseNumber
);
162 ficlAddPrecompiledParseStep(pSys
, ">float", ficlParseFloatNumber
);
166 ** Now create a temporary VM to compile the softwords. Since all VMs are
167 ** linked into the vmList of FICL_SYSTEM, we don't have to pass the VM
168 ** to ficlCompileSoftCore -- it just hijacks whatever it finds in the VM list.
169 ** ficl 2.05: vmCreate no longer depends on the presence of INTERPRET in the
170 ** dictionary, so a VM can be created before the dictionary is built. It just
174 ficlCompileSoftCore(pSys
);
175 ficlFreeVM(pSys
->vmList
);
182 FICL_SYSTEM
*ficlInitSystem(int nDictCells
)
184 FICL_SYSTEM_INFO fsi
;
186 fsi
.nDictCells
= nDictCells
;
187 return ficlInitSystemEx(&fsi
);
191 /**************************************************************************
192 f i c l A d d P a r s e S t e p
193 ** Appends a parse step function to the end of the parse list (see
194 ** FICL_PARSE_STEP notes in ficl.h for details). Returns 0 if successful,
195 ** nonzero if there's no more room in the list.
196 **************************************************************************/
197 int ficlAddParseStep(FICL_SYSTEM
*pSys
, FICL_WORD
*pFW
)
200 for (i
= 0; i
< FICL_MAX_PARSE_STEPS
; i
++)
202 if (pSys
->parseList
[i
] == NULL
)
204 pSys
->parseList
[i
] = pFW
;
214 ** Compile a word into the dictionary that invokes the specified FICL_PARSE_STEP
215 ** function. It is up to the user (as usual in Forth) to make sure the stack
216 ** preconditions are valid (there needs to be a counted string on top of the stack)
217 ** before using the resulting word.
219 void ficlAddPrecompiledParseStep(FICL_SYSTEM
*pSys
, char *name
, FICL_PARSE_STEP pStep
)
221 FICL_DICT
*dp
= pSys
->dp
;
222 FICL_WORD
*pFW
= dictAppendWord(dp
, name
, parseStepParen
, FW_DEFAULT
);
223 dictAppendCell(dp
, LVALUEtoCELL(pStep
));
224 ficlAddParseStep(pSys
, pFW
);
229 ** This word lists the parse steps in order
231 void ficlListParseSteps(FICL_VM
*pVM
)
234 FICL_SYSTEM
*pSys
= pVM
->pSys
;
237 vmTextOut(pVM
, "Parse steps:", 1);
238 vmTextOut(pVM
, "lookup", 1);
240 for (i
= 0; i
< FICL_MAX_PARSE_STEPS
; i
++)
242 if (pSys
->parseList
[i
] != NULL
)
244 vmTextOut(pVM
, pSys
->parseList
[i
]->name
, 1);
252 /**************************************************************************
254 ** Create a new virtual machine and link it into the system list
255 ** of VMs for later cleanup by ficlTermSystem.
256 **************************************************************************/
257 FICL_VM
*ficlNewVM(FICL_SYSTEM
*pSys
)
259 FICL_VM
*pVM
= vmCreate(NULL
, defaultStack
, defaultStack
);
260 pVM
->link
= pSys
->vmList
;
262 pVM
->pExtend
= pSys
->pExtend
;
263 vmSetTextOut(pVM
, pSys
->textOut
);
270 /**************************************************************************
272 ** Removes the VM in question from the system VM list and deletes the
273 ** memory allocated to it. This is an optional call, since ficlTermSystem
274 ** will do this cleanup for you. This function is handy if you're going to
275 ** do a lot of dynamic creation of VMs.
276 **************************************************************************/
277 void ficlFreeVM(FICL_VM
*pVM
)
279 FICL_SYSTEM
*pSys
= pVM
->pSys
;
280 FICL_VM
*pList
= pSys
->vmList
;
284 if (pSys
->vmList
== pVM
)
286 pSys
->vmList
= pSys
->vmList
->link
;
288 else for (; pList
!= NULL
; pList
= pList
->link
)
290 if (pList
->link
== pVM
)
292 pList
->link
= pVM
->link
;
303 /**************************************************************************
305 ** Builds a word into the dictionary.
306 ** Preconditions: system must be initialized, and there must
307 ** be enough space for the new word's header! Operation is
308 ** controlled by ficlLockDictionary, so any initialization
309 ** required by your version of the function (if you overrode
310 ** it) must be complete at this point.
312 ** name -- duh, the name of the word
313 ** code -- code to execute when the word is invoked - must take a single param
314 ** pointer to a FICL_VM
315 ** flags -- 0 or more of F_IMMEDIATE, F_COMPILE, use bitwise OR!
317 **************************************************************************/
318 int ficlBuild(FICL_SYSTEM
*pSys
, char *name
, FICL_CODE code
, char flags
)
321 int err
= ficlLockDictionary(TRUE
);
323 #endif /* FICL_MULTITHREAD */
325 assert(dictCellsAvail(pSys
->dp
) > sizeof (FICL_WORD
) / sizeof (CELL
));
326 dictAppendWord(pSys
->dp
, name
, code
, flags
);
328 ficlLockDictionary(FALSE
);
333 /**************************************************************************
334 f i c l E v a l u a t e
335 ** Wrapper for ficlExec() which sets SOURCE-ID to -1.
336 **************************************************************************/
337 int ficlEvaluate(FICL_VM
*pVM
, char *pText
)
340 CELL id
= pVM
->sourceID
;
341 pVM
->sourceID
.i
= -1;
342 returnValue
= ficlExecC(pVM
, pText
, -1);
348 /**************************************************************************
350 ** Evaluates a block of input text in the context of the
351 ** specified interpreter. Emits any requested output to the
352 ** interpreter's output function.
354 ** Contains the "inner interpreter" code in a tight loop
356 ** Returns one of the VM_XXXX codes defined in ficl.h:
357 ** VM_OUTOFTEXT is the normal exit condition
358 ** VM_ERREXIT means that the interp encountered a syntax error
359 ** and the vm has been reset to recover (some or all
360 ** of the text block got ignored
361 ** VM_USEREXIT means that the user executed the "bye" command
362 ** to shut down the interpreter. This would be a good
363 ** time to delete the vm, etc -- or you can ignore this
365 **************************************************************************/
366 int ficlExec(FICL_VM
*pVM
, char *pText
)
368 return ficlExecC(pVM
, pText
, -1);
371 int ficlExecC(FICL_VM
*pVM
, char *pText
, FICL_INT size
)
373 FICL_SYSTEM
*pSys
= pVM
->pSys
;
374 FICL_DICT
*dp
= pSys
->dp
;
382 assert(pSys
->pInterp
[0]);
385 size
= strlen(pText
);
387 vmPushTib(pVM
, pText
, size
, &saveTib
);
390 ** Save and restore VM's jmp_buf to enable nested calls to ficlExec
392 oldState
= pVM
->pState
;
393 pVM
->pState
= &vmState
; /* This has to come before the setjmp! */
394 except
= setjmp(vmState
);
401 pVM
->runningWord
->code(pVM
);
405 { /* set VM up to interpret text */
406 vmPushIP(pVM
, &(pSys
->pInterp
[0]));
414 except
= VM_OUTOFTEXT
;
420 if ((pVM
->state
!= COMPILE
) && (pVM
->sourceID
.i
== 0))
421 ficlTextOut(pVM
, FICL_PROMPT
, 0);
431 if (pVM
->state
== COMPILE
)
433 dictAbortDefinition(dp
);
435 dictEmpty(pSys
->localp
, pSys
->localp
->pForthWords
->size
);
444 default: /* user defined exit code?? */
445 if (pVM
->state
== COMPILE
)
447 dictAbortDefinition(dp
);
449 dictEmpty(pSys
->localp
, pSys
->localp
->pForthWords
->size
);
452 dictResetSearchOrder(dp
);
457 pVM
->pState
= oldState
;
458 vmPopTib(pVM
, &saveTib
);
463 /**************************************************************************
465 ** Given a pointer to a FICL_WORD, push an inner interpreter and
466 ** execute the word to completion. This is in contrast with vmExecute,
467 ** which does not guarantee that the word will have completed when
468 ** the function returns (ie in the case of colon definitions, which
469 ** need an inner interpreter to finish)
471 ** Returns one of the VM_XXXX exception codes listed in ficl.h. Normal
472 ** exit condition is VM_INNEREXIT, ficl's private signal to exit the
473 ** inner loop under normal circumstances. If another code is thrown to
474 ** exit the loop, this function will re-throw it if it's nested under
475 ** itself or ficlExec.
477 ** NOTE: this function is intended so that C code can execute ficlWords
478 ** given their address in the dictionary (xt).
479 **************************************************************************/
480 int ficlExecXT(FICL_VM
*pVM
, FICL_WORD
*pWord
)
485 FICL_WORD
*oldRunningWord
;
488 assert(pVM
->pSys
->pExitInner
);
491 ** Save the runningword so that RESTART behaves correctly
492 ** over nested calls.
494 oldRunningWord
= pVM
->runningWord
;
496 ** Save and restore VM's jmp_buf to enable nested calls
498 oldState
= pVM
->pState
;
499 pVM
->pState
= &vmState
; /* This has to come before the setjmp! */
500 except
= setjmp(vmState
);
505 vmPushIP(pVM
, &(pVM
->pSys
->pExitInner
));
510 vmExecute(pVM
, pWord
);
525 default: /* user defined exit code?? */
528 pVM
->pState
= oldState
;
529 vmThrow(pVM
, except
);
534 pVM
->pState
= oldState
;
535 pVM
->runningWord
= oldRunningWord
;
540 /**************************************************************************
542 ** Look in the system dictionary for a match to the given name. If
543 ** found, return the address of the corresponding FICL_WORD. Otherwise
545 **************************************************************************/
546 FICL_WORD
*ficlLookup(FICL_SYSTEM
*pSys
, char *name
)
550 return dictLookup(pSys
->dp
, si
);
554 /**************************************************************************
555 f i c l G e t D i c t
556 ** Returns the address of the system dictionary
557 **************************************************************************/
558 FICL_DICT
*ficlGetDict(FICL_SYSTEM
*pSys
)
564 /**************************************************************************
566 ** Returns the address of the system environment space
567 **************************************************************************/
568 FICL_DICT
*ficlGetEnv(FICL_SYSTEM
*pSys
)
574 /**************************************************************************
576 ** Create an environment variable with a one-CELL payload. ficlSetEnvD
577 ** makes one with a two-CELL payload.
578 **************************************************************************/
579 void ficlSetEnv(FICL_SYSTEM
*pSys
, char *name
, FICL_UNS value
)
583 FICL_DICT
*envp
= pSys
->envp
;
586 pFW
= dictLookup(envp
, si
);
590 dictAppendWord(envp
, name
, constantParen
, FW_DEFAULT
);
591 dictAppendCell(envp
, LVALUEtoCELL(value
));
595 pFW
->param
[0] = LVALUEtoCELL(value
);
601 void ficlSetEnvD(FICL_SYSTEM
*pSys
, char *name
, FICL_UNS hi
, FICL_UNS lo
)
605 FICL_DICT
*envp
= pSys
->envp
;
607 pFW
= dictLookup(envp
, si
);
611 dictAppendWord(envp
, name
, twoConstParen
, FW_DEFAULT
);
612 dictAppendCell(envp
, LVALUEtoCELL(lo
));
613 dictAppendCell(envp
, LVALUEtoCELL(hi
));
617 pFW
->param
[0] = LVALUEtoCELL(lo
);
618 pFW
->param
[1] = LVALUEtoCELL(hi
);
625 /**************************************************************************
627 ** Returns the address of the system locals dictionary. This dict is
628 ** only used during compilation, and is shared by all VMs.
629 **************************************************************************/
631 FICL_DICT
*ficlGetLoc(FICL_SYSTEM
*pSys
)
639 /**************************************************************************
640 f i c l S e t S t a c k S i z e
641 ** Set the stack sizes (return and parameter) to be used for all
642 ** subsequently created VMs. Returns actual stack size to be used.
643 **************************************************************************/
644 int ficlSetStackSize(int nStackCells
)
646 if (nStackCells
>= FICL_DEFAULT_STACK
)
647 defaultStack
= nStackCells
;
649 defaultStack
= FICL_DEFAULT_STACK
;
655 /**************************************************************************
656 f i c l T e r m S y s t e m
657 ** Tear the system down by deleting the dictionaries and all VMs.
658 ** This saves you from having to keep track of all that stuff.
659 **************************************************************************/
660 void ficlTermSystem(FICL_SYSTEM
*pSys
)
663 dictDelete(pSys
->dp
);
667 dictDelete(pSys
->envp
);
672 dictDelete(pSys
->localp
);
676 while (pSys
->vmList
!= NULL
)
678 FICL_VM
*pVM
= pSys
->vmList
;
679 pSys
->vmList
= pSys
->vmList
->link
;
689 /**************************************************************************
690 f i c l S e t V e r s i o n E n v
691 ** Create a double cell environment constant for the version ID
692 **************************************************************************/
693 static void ficlSetVersionEnv(FICL_SYSTEM
*pSys
)
695 ficlSetEnvD(pSys
, "ficl-version", FICL_VER_MAJOR
, FICL_VER_MINOR
);
696 ficlSetEnv (pSys
, "ficl-robust", FICL_ROBUST
);