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