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