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 ** $Id: ficl.c,v 1.16 2001/12/05 07:21:34 jsadler Exp $ 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 in the 19 ** style of TCL. 20 ** 21 ** Code is written in ANSI C for portability. 22 */ 23 /* 24 ** Copyright (c) 1997-2001 John Sadler (john_sadler@alum.mit.edu) 25 ** All rights reserved. 26 ** 27 ** Get the latest Ficl release at http://ficl.sourceforge.net 28 ** 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. 33 ** 34 ** L I C E N S E and D I S C L A I M E R 35 ** 36 ** Redistribution and use in source and binary forms, with or without 37 ** modification, are permitted provided that the following conditions 38 ** are met: 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. 44 ** 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 55 ** SUCH DAMAGE. 56 */ 57 58 /* $FreeBSD$ */ 59 60 #ifdef TESTMAIN 61 #include <stdlib.h> 62 #else 63 #include <stand.h> 64 #endif 65 #include <string.h> 66 #include "ficl.h" 67 68 69 /* 70 ** System statics 71 ** Each FICL_SYSTEM builds a global dictionary during its start 72 ** sequence. This is shared by all virtual machines of that system. 73 ** Therefore only one VM can update the dictionary 74 ** at a time. The system imports a locking function that 75 ** you can override in order to control update access to 76 ** the dictionary. The function is stubbed out by default, 77 ** but you can insert one: #define FICL_MULTITHREAD 1 78 ** and supply your own version of ficlLockDictionary. 79 */ 80 static int defaultStack = FICL_DEFAULT_STACK; 81 82 83 static void ficlSetVersionEnv(FICL_SYSTEM *pSys); 84 85 86 /************************************************************************** 87 f i c l I n i t S y s t e m 88 ** Binds a global dictionary to the interpreter system. 89 ** You specify the address and size of the allocated area. 90 ** After that, ficl manages it. 91 ** First step is to set up the static pointers to the area. 92 ** Then write the "precompiled" portion of the dictionary in. 93 ** The dictionary needs to be at least large enough to hold the 94 ** precompiled part. Try 1K cells minimum. Use "words" to find 95 ** out how much of the dictionary is used at any time. 96 **************************************************************************/ 97 FICL_SYSTEM *ficlInitSystemEx(FICL_SYSTEM_INFO *fsi) 98 { 99 int nDictCells; 100 int nEnvCells; 101 FICL_SYSTEM *pSys = ficlMalloc(sizeof (FICL_SYSTEM)); 102 103 assert(pSys); 104 assert(fsi->size == sizeof (FICL_SYSTEM_INFO)); 105 106 memset(pSys, 0, sizeof (FICL_SYSTEM)); 107 108 nDictCells = fsi->nDictCells; 109 if (nDictCells <= 0) 110 nDictCells = FICL_DEFAULT_DICT; 111 112 nEnvCells = fsi->nEnvCells; 113 if (nEnvCells <= 0) 114 nEnvCells = FICL_DEFAULT_DICT; 115 116 pSys->dp = dictCreateHashed((unsigned)nDictCells, HASHSIZE); 117 pSys->dp->pForthWords->name = "forth-wordlist"; 118 119 pSys->envp = dictCreate((unsigned)nEnvCells); 120 pSys->envp->pForthWords->name = "environment"; 121 122 pSys->textOut = fsi->textOut; 123 pSys->pExtend = fsi->pExtend; 124 125 #if FICL_WANT_LOCALS 126 /* 127 ** The locals dictionary is only searched while compiling, 128 ** but this is where speed is most important. On the other 129 ** hand, the dictionary gets emptied after each use of locals 130 ** The need to balance search speed with the cost of the 'empty' 131 ** operation led me to select a single-threaded list... 132 */ 133 pSys->localp = dictCreate((unsigned)FICL_MAX_LOCALS * CELLS_PER_WORD); 134 #endif 135 136 /* 137 ** Build the precompiled dictionary and load softwords. We need a temporary 138 ** VM to do this - ficlNewVM links one to the head of the system VM list. 139 ** ficlCompilePlatform (defined in win32.c, for example) adds platform specific words. 140 */ 141 ficlCompileCore(pSys); 142 ficlCompilePrefix(pSys); 143 #if FICL_WANT_FLOAT 144 ficlCompileFloat(pSys); 145 #endif 146 #if FICL_PLATFORM_EXTEND 147 ficlCompilePlatform(pSys); 148 #endif 149 ficlSetVersionEnv(pSys); 150 151 /* 152 ** Establish the parse order. Note that prefixes precede numbers - 153 ** this allows constructs like "0b101010" which might parse as a 154 ** hex value otherwise. 155 */ 156 ficlAddPrecompiledParseStep(pSys, "?prefix", ficlParsePrefix); 157 ficlAddPrecompiledParseStep(pSys, "?number", ficlParseNumber); 158 #if FICL_WANT_FLOAT 159 ficlAddPrecompiledParseStep(pSys, ">float", ficlParseFloatNumber); 160 #endif 161 162 /* 163 ** Now create a temporary VM to compile the softwords. Since all VMs are 164 ** linked into the vmList of FICL_SYSTEM, we don't have to pass the VM 165 ** to ficlCompileSoftCore -- it just hijacks whatever it finds in the VM list. 166 ** ficl 2.05: vmCreate no longer depends on the presence of INTERPRET in the 167 ** dictionary, so a VM can be created before the dictionary is built. It just 168 ** can't do much... 169 */ 170 ficlNewVM(pSys); 171 ficlCompileSoftCore(pSys); 172 ficlFreeVM(pSys->vmList); 173 174 175 return pSys; 176 } 177 178 179 FICL_SYSTEM *ficlInitSystem(int nDictCells) 180 { 181 FICL_SYSTEM_INFO fsi; 182 ficlInitInfo(&fsi); 183 fsi.nDictCells = nDictCells; 184 return ficlInitSystemEx(&fsi); 185 } 186 187 188 /************************************************************************** 189 f i c l A d d P a r s e S t e p 190 ** Appends a parse step function to the end of the parse list (see 191 ** FICL_PARSE_STEP notes in ficl.h for details). Returns 0 if successful, 192 ** nonzero if there's no more room in the list. 193 **************************************************************************/ 194 int ficlAddParseStep(FICL_SYSTEM *pSys, FICL_WORD *pFW) 195 { 196 int i; 197 for (i = 0; i < FICL_MAX_PARSE_STEPS; i++) 198 { 199 if (pSys->parseList[i] == NULL) 200 { 201 pSys->parseList[i] = pFW; 202 return 0; 203 } 204 } 205 206 return 1; 207 } 208 209 210 /* 211 ** Compile a word into the dictionary that invokes the specified FICL_PARSE_STEP 212 ** function. It is up to the user (as usual in Forth) to make sure the stack 213 ** preconditions are valid (there needs to be a counted string on top of the stack) 214 ** before using the resulting word. 215 */ 216 void ficlAddPrecompiledParseStep(FICL_SYSTEM *pSys, char *name, FICL_PARSE_STEP pStep) 217 { 218 FICL_DICT *dp = pSys->dp; 219 FICL_WORD *pFW = dictAppendWord(dp, name, parseStepParen, FW_DEFAULT); 220 dictAppendCell(dp, LVALUEtoCELL(pStep)); 221 ficlAddParseStep(pSys, pFW); 222 } 223 224 225 /* 226 ** This word lists the parse steps in order 227 */ 228 void ficlListParseSteps(FICL_VM *pVM) 229 { 230 int i; 231 FICL_SYSTEM *pSys = pVM->pSys; 232 assert(pSys); 233 234 vmTextOut(pVM, "Parse steps:", 1); 235 vmTextOut(pVM, "lookup", 1); 236 237 for (i = 0; i < FICL_MAX_PARSE_STEPS; i++) 238 { 239 if (pSys->parseList[i] != NULL) 240 { 241 vmTextOut(pVM, pSys->parseList[i]->name, 1); 242 } 243 else break; 244 } 245 return; 246 } 247 248 249 /************************************************************************** 250 f i c l N e w V M 251 ** Create a new virtual machine and link it into the system list 252 ** of VMs for later cleanup by ficlTermSystem. 253 **************************************************************************/ 254 FICL_VM *ficlNewVM(FICL_SYSTEM *pSys) 255 { 256 FICL_VM *pVM = vmCreate(NULL, defaultStack, defaultStack); 257 pVM->link = pSys->vmList; 258 pVM->pSys = pSys; 259 pVM->pExtend = pSys->pExtend; 260 vmSetTextOut(pVM, pSys->textOut); 261 262 pSys->vmList = pVM; 263 return pVM; 264 } 265 266 267 /************************************************************************** 268 f i c l F r e e V M 269 ** Removes the VM in question from the system VM list and deletes the 270 ** memory allocated to it. This is an optional call, since ficlTermSystem 271 ** will do this cleanup for you. This function is handy if you're going to 272 ** do a lot of dynamic creation of VMs. 273 **************************************************************************/ 274 void ficlFreeVM(FICL_VM *pVM) 275 { 276 FICL_SYSTEM *pSys = pVM->pSys; 277 FICL_VM *pList = pSys->vmList; 278 279 assert(pVM != NULL); 280 281 if (pSys->vmList == pVM) 282 { 283 pSys->vmList = pSys->vmList->link; 284 } 285 else for (; pList != NULL; pList = pList->link) 286 { 287 if (pList->link == pVM) 288 { 289 pList->link = pVM->link; 290 break; 291 } 292 } 293 294 if (pList) 295 vmDelete(pVM); 296 return; 297 } 298 299 300 /************************************************************************** 301 f i c l B u i l d 302 ** Builds a word into the dictionary. 303 ** Preconditions: system must be initialized, and there must 304 ** be enough space for the new word's header! Operation is 305 ** controlled by ficlLockDictionary, so any initialization 306 ** required by your version of the function (if you overrode 307 ** it) must be complete at this point. 308 ** Parameters: 309 ** name -- duh, the name of the word 310 ** code -- code to execute when the word is invoked - must take a single param 311 ** pointer to a FICL_VM 312 ** flags -- 0 or more of F_IMMEDIATE, F_COMPILE, use bitwise OR! 313 ** 314 **************************************************************************/ 315 int ficlBuild(FICL_SYSTEM *pSys, char *name, FICL_CODE code, char flags) 316 { 317 #if FICL_MULTITHREAD 318 int err = ficlLockDictionary(TRUE); 319 if (err) return err; 320 #endif /* FICL_MULTITHREAD */ 321 322 assert(dictCellsAvail(pSys->dp) > sizeof (FICL_WORD) / sizeof (CELL)); 323 dictAppendWord(pSys->dp, name, code, flags); 324 325 ficlLockDictionary(FALSE); 326 return 0; 327 } 328 329 330 /************************************************************************** 331 f i c l E v a l u a t e 332 ** Wrapper for ficlExec() which sets SOURCE-ID to -1. 333 **************************************************************************/ 334 int ficlEvaluate(FICL_VM *pVM, char *pText) 335 { 336 int returnValue; 337 CELL id = pVM->sourceID; 338 pVM->sourceID.i = -1; 339 returnValue = ficlExecC(pVM, pText, -1); 340 pVM->sourceID = id; 341 return returnValue; 342 } 343 344 345 /************************************************************************** 346 f i c l E x e c 347 ** Evaluates a block of input text in the context of the 348 ** specified interpreter. Emits any requested output to the 349 ** interpreter's output function. 350 ** 351 ** Contains the "inner interpreter" code in a tight loop 352 ** 353 ** Returns one of the VM_XXXX codes defined in ficl.h: 354 ** VM_OUTOFTEXT is the normal exit condition 355 ** VM_ERREXIT means that the interp encountered a syntax error 356 ** and the vm has been reset to recover (some or all 357 ** of the text block got ignored 358 ** VM_USEREXIT means that the user executed the "bye" command 359 ** to shut down the interpreter. This would be a good 360 ** time to delete the vm, etc -- or you can ignore this 361 ** signal. 362 **************************************************************************/ 363 int ficlExec(FICL_VM *pVM, char *pText) 364 { 365 return ficlExecC(pVM, pText, -1); 366 } 367 368 int ficlExecC(FICL_VM *pVM, char *pText, FICL_INT size) 369 { 370 FICL_SYSTEM *pSys = pVM->pSys; 371 FICL_DICT *dp = pSys->dp; 372 373 int except; 374 jmp_buf vmState; 375 jmp_buf *oldState; 376 TIB saveTib; 377 378 assert(pVM); 379 assert(pSys->pInterp[0]); 380 381 if (size < 0) 382 size = strlen(pText); 383 384 vmPushTib(pVM, pText, size, &saveTib); 385 386 /* 387 ** Save and restore VM's jmp_buf to enable nested calls to ficlExec 388 */ 389 oldState = pVM->pState; 390 pVM->pState = &vmState; /* This has to come before the setjmp! */ 391 except = setjmp(vmState); 392 393 switch (except) 394 { 395 case 0: 396 if (pVM->fRestart) 397 { 398 pVM->runningWord->code(pVM); 399 pVM->fRestart = 0; 400 } 401 else 402 { /* set VM up to interpret text */ 403 vmPushIP(pVM, &(pSys->pInterp[0])); 404 } 405 406 vmInnerLoop(pVM); 407 break; 408 409 case VM_RESTART: 410 pVM->fRestart = 1; 411 except = VM_OUTOFTEXT; 412 break; 413 414 case VM_OUTOFTEXT: 415 vmPopIP(pVM); 416 #ifdef TESTMAIN 417 if ((pVM->state != COMPILE) && (pVM->sourceID.i == 0)) 418 ficlTextOut(pVM, FICL_PROMPT, 0); 419 #endif 420 break; 421 422 case VM_USEREXIT: 423 case VM_INNEREXIT: 424 case VM_BREAK: 425 break; 426 427 case VM_QUIT: 428 if (pVM->state == COMPILE) 429 { 430 dictAbortDefinition(dp); 431 #if FICL_WANT_LOCALS 432 dictEmpty(pSys->localp, pSys->localp->pForthWords->size); 433 #endif 434 } 435 vmQuit(pVM); 436 break; 437 438 case VM_ERREXIT: 439 case VM_ABORT: 440 case VM_ABORTQ: 441 default: /* user defined exit code?? */ 442 if (pVM->state == COMPILE) 443 { 444 dictAbortDefinition(dp); 445 #if FICL_WANT_LOCALS 446 dictEmpty(pSys->localp, pSys->localp->pForthWords->size); 447 #endif 448 } 449 dictResetSearchOrder(dp); 450 vmReset(pVM); 451 break; 452 } 453 454 pVM->pState = oldState; 455 vmPopTib(pVM, &saveTib); 456 return (except); 457 } 458 459 460 /************************************************************************** 461 f i c l E x e c X T 462 ** Given a pointer to a FICL_WORD, push an inner interpreter and 463 ** execute the word to completion. This is in contrast with vmExecute, 464 ** which does not guarantee that the word will have completed when 465 ** the function returns (ie in the case of colon definitions, which 466 ** need an inner interpreter to finish) 467 ** 468 ** Returns one of the VM_XXXX exception codes listed in ficl.h. Normal 469 ** exit condition is VM_INNEREXIT, ficl's private signal to exit the 470 ** inner loop under normal circumstances. If another code is thrown to 471 ** exit the loop, this function will re-throw it if it's nested under 472 ** itself or ficlExec. 473 ** 474 ** NOTE: this function is intended so that C code can execute ficlWords 475 ** given their address in the dictionary (xt). 476 **************************************************************************/ 477 int ficlExecXT(FICL_VM *pVM, FICL_WORD *pWord) 478 { 479 int except; 480 jmp_buf vmState; 481 jmp_buf *oldState; 482 FICL_WORD *oldRunningWord; 483 484 assert(pVM); 485 assert(pVM->pSys->pExitInner); 486 487 /* 488 ** Save the runningword so that RESTART behaves correctly 489 ** over nested calls. 490 */ 491 oldRunningWord = pVM->runningWord; 492 /* 493 ** Save and restore VM's jmp_buf to enable nested calls 494 */ 495 oldState = pVM->pState; 496 pVM->pState = &vmState; /* This has to come before the setjmp! */ 497 except = setjmp(vmState); 498 499 if (except) 500 vmPopIP(pVM); 501 else 502 vmPushIP(pVM, &(pVM->pSys->pExitInner)); 503 504 switch (except) 505 { 506 case 0: 507 vmExecute(pVM, pWord); 508 vmInnerLoop(pVM); 509 break; 510 511 case VM_INNEREXIT: 512 case VM_BREAK: 513 break; 514 515 case VM_RESTART: 516 case VM_OUTOFTEXT: 517 case VM_USEREXIT: 518 case VM_QUIT: 519 case VM_ERREXIT: 520 case VM_ABORT: 521 case VM_ABORTQ: 522 default: /* user defined exit code?? */ 523 if (oldState) 524 { 525 pVM->pState = oldState; 526 vmThrow(pVM, except); 527 } 528 break; 529 } 530 531 pVM->pState = oldState; 532 pVM->runningWord = oldRunningWord; 533 return (except); 534 } 535 536 537 /************************************************************************** 538 f i c l L o o k u p 539 ** Look in the system dictionary for a match to the given name. If 540 ** found, return the address of the corresponding FICL_WORD. Otherwise 541 ** return NULL. 542 **************************************************************************/ 543 FICL_WORD *ficlLookup(FICL_SYSTEM *pSys, char *name) 544 { 545 STRINGINFO si; 546 SI_PSZ(si, name); 547 return dictLookup(pSys->dp, si); 548 } 549 550 551 /************************************************************************** 552 f i c l G e t D i c t 553 ** Returns the address of the system dictionary 554 **************************************************************************/ 555 FICL_DICT *ficlGetDict(FICL_SYSTEM *pSys) 556 { 557 return pSys->dp; 558 } 559 560 561 /************************************************************************** 562 f i c l G e t E n v 563 ** Returns the address of the system environment space 564 **************************************************************************/ 565 FICL_DICT *ficlGetEnv(FICL_SYSTEM *pSys) 566 { 567 return pSys->envp; 568 } 569 570 571 /************************************************************************** 572 f i c l S e t E n v 573 ** Create an environment variable with a one-CELL payload. ficlSetEnvD 574 ** makes one with a two-CELL payload. 575 **************************************************************************/ 576 void ficlSetEnv(FICL_SYSTEM *pSys, char *name, FICL_UNS value) 577 { 578 STRINGINFO si; 579 FICL_WORD *pFW; 580 FICL_DICT *envp = pSys->envp; 581 582 SI_PSZ(si, name); 583 pFW = dictLookup(envp, si); 584 585 if (pFW == NULL) 586 { 587 dictAppendWord(envp, name, constantParen, FW_DEFAULT); 588 dictAppendCell(envp, LVALUEtoCELL(value)); 589 } 590 else 591 { 592 pFW->param[0] = LVALUEtoCELL(value); 593 } 594 595 return; 596 } 597 598 void ficlSetEnvD(FICL_SYSTEM *pSys, char *name, FICL_UNS hi, FICL_UNS lo) 599 { 600 FICL_WORD *pFW; 601 STRINGINFO si; 602 FICL_DICT *envp = pSys->envp; 603 SI_PSZ(si, name); 604 pFW = dictLookup(envp, si); 605 606 if (pFW == NULL) 607 { 608 dictAppendWord(envp, name, twoConstParen, FW_DEFAULT); 609 dictAppendCell(envp, LVALUEtoCELL(lo)); 610 dictAppendCell(envp, LVALUEtoCELL(hi)); 611 } 612 else 613 { 614 pFW->param[0] = LVALUEtoCELL(lo); 615 pFW->param[1] = LVALUEtoCELL(hi); 616 } 617 618 return; 619 } 620 621 622 /************************************************************************** 623 f i c l G e t L o c 624 ** Returns the address of the system locals dictionary. This dict is 625 ** only used during compilation, and is shared by all VMs. 626 **************************************************************************/ 627 #if FICL_WANT_LOCALS 628 FICL_DICT *ficlGetLoc(FICL_SYSTEM *pSys) 629 { 630 return pSys->localp; 631 } 632 #endif 633 634 635 636 /************************************************************************** 637 f i c l S e t S t a c k S i z e 638 ** Set the stack sizes (return and parameter) to be used for all 639 ** subsequently created VMs. Returns actual stack size to be used. 640 **************************************************************************/ 641 int ficlSetStackSize(int nStackCells) 642 { 643 if (nStackCells >= FICL_DEFAULT_STACK) 644 defaultStack = nStackCells; 645 else 646 defaultStack = FICL_DEFAULT_STACK; 647 648 return defaultStack; 649 } 650 651 652 /************************************************************************** 653 f i c l T e r m S y s t e m 654 ** Tear the system down by deleting the dictionaries and all VMs. 655 ** This saves you from having to keep track of all that stuff. 656 **************************************************************************/ 657 void ficlTermSystem(FICL_SYSTEM *pSys) 658 { 659 if (pSys->dp) 660 dictDelete(pSys->dp); 661 pSys->dp = NULL; 662 663 if (pSys->envp) 664 dictDelete(pSys->envp); 665 pSys->envp = NULL; 666 667 #if FICL_WANT_LOCALS 668 if (pSys->localp) 669 dictDelete(pSys->localp); 670 pSys->localp = NULL; 671 #endif 672 673 while (pSys->vmList != NULL) 674 { 675 FICL_VM *pVM = pSys->vmList; 676 pSys->vmList = pSys->vmList->link; 677 vmDelete(pVM); 678 } 679 680 ficlFree(pSys); 681 pSys = NULL; 682 return; 683 } 684 685 686 /************************************************************************** 687 f i c l S e t V e r s i o n E n v 688 ** Create a double cell environment constant for the version ID 689 **************************************************************************/ 690 static void ficlSetVersionEnv(FICL_SYSTEM *pSys) 691 { 692 ficlSetEnvD(pSys, "ficl-version", FICL_VER_MAJOR, FICL_VER_MINOR); 693 ficlSetEnv (pSys, "ficl-robust", FICL_ROBUST); 694 return; 695 } 696 697