/* * forth.c * * Portable FORTH interpreter in C * * Author: Allan Pratt, Indiana University (iuvax!apratt) * Spring, 1984 * References: 8080 and 6502 fig-FORTH source listings (not the greatest refs * in the world...) * * This program is intended to be compact, portable, and pretty complete. * It is also intended to be in the public domain, and distribution should * include this notice to that effect. * * This file contains the support code for all interpreter functions. * the file prims.c contains code for the C-coded primitives, and the * file forth.h connects the two with definitions. * * The program nf.c generates a new forth.core file from the dictionary * forth.dict, using common.h to tie it together with this program. */ #include #ifndef AMIGA #include #endif #include /* only for isxdigit */ #include "common.h" #include "forth.h" #include "prims.h" /* macro-defined primitives */ /* declare globals which are defined in forth.h */ unsigned short csp, rsp, ip, w; short *mem; int trace, tracedepth, debug, breakenable, breakpoint, qtermflag, forceip; int nobuf; FILE *blockfile; long bfilesize; char *bfilename; /* block file name (change with -f ) */ char *cfilename; /* core file name (change with -l ) */ char *sfilename; /* save file name (change with -s ) */ /* ---------------------------------------------------- SYSTEM FUNCTIONS ---------------------------------------------------- */ errexit(s,p1,p2) /* An error occurred -- clean up (?) and exit. */ { printf(s,p1,p2); printf("ABORT FORTH!\nDumping to %s... ",DUMPFILE); fflush(stdout); memdump(); puts("done."); exit(1); } Callot (n) /* allot n words in the dictionary */ short n; { unsigned newsize; mem[DP] += n; /* move DP */ if (mem[DP] + GULPFRQ > mem[LIMIT]) { /* need space */ newsize = mem[DP] + GULPSIZE; if (newsize > MAXMEM && MAXMEM) errexit("ATTEMPT TO GROW PAST MAXMEM (%d) WORDS\n",MAXMEM); #ifdef AMIGA /* * Fake realloc by doing a malloc and copy to the new area. * Since we are always just growing the area, this should work. * Note that this has the disadvantage of requiring at least 2N * bytes to grow an area of N bytes. */ { register char *new, *out; register char *in = mem; register int count = mem[LIMIT]; new = out = (short *) malloc ((char *)mem, newsize*sizeof(*mem)); if (new == NULL) errexit("REALLOC FAILED\n"); while (count-- > 0) { *out++ = *in++; } free (mem); mem = new; } #else mem = (short *)realloc((char *)mem, newsize*sizeof(*mem)); if (mem == NULL) errexit("REALLOC FAILED\n"); #endif /* AMIGA */ mem[LIMIT] = newsize; } } push(v) /* push value v to cstack */ short v; { if (csp <= TIB_END) errexit("PUSH TO FULL CALC. STACK\n"); mem[--csp] = v; } short pop() /* pop a value from comp. stack, and return it as the value of the function */ { if (csp >= INITS0) { puts("Empty Stack!"); return 0; } return (mem[csp++]); } rpush(v) short v; { if (rsp <= INITS0) errexit("PUSH TO FULL RETURN STACK"); mem[--rsp] = v; } short rpop() { if (rsp >= INITR0) errexit("POP FROM EMPTY RETURN STACK!"); return (mem[rsp++]); } pkey() /* (KEY) -- wait for a key & return it */ { int c; if ((c = getchar()) == EOF) errexit("END-OF-FILE ENCOUNTERED"); return(c); } pqterm() /* (?TERMINAL): return true if BREAK has been hit */ { if (qtermflag) { push(TRUE); qtermflag = FALSE; /* this influences ^C handling */ } else push(FALSE); } pemit() /* (EMIT): c -- emit a character */ { putchar(pop() & 0x7f); /* stdout is unbuffered */ } next() /* instruction processor: control goes here almost right away, and cycles through here until you leave. */ /* * This is the big kabloona. What it does is load the value at mem[ip] * into w, increment ip, and invoke prim. number w. This implies that * mem[ip] is the CFA of a word. What's in the CF of a word is the number * of the primitive which should be executed. For a word written in FORTH, * that primitive is "docol", which pushes ip to the return stack, then * uses w+2 (the PFA of the word) as the new ip. See "interp.doc" for * more. */ /* * There is an incredible hack going on here: the SPECIAL CASE mentioned in * the code is for the word EXECUTE, which must set W itself and jump INSIDE * the "next" loop, by-passing the first instruction. This has been made a * special case: if the primitive to execute is zero, the special case is * invoked, and the code for EXECUTE is put right in the NEXT loop. For this * reason, "EXECUTE" MUST BE THE FIRST WORD IN THE DICTIONARY. */ { short p; while (1) { if (forceip) { /* force ip to this value -- used by sig_int */ ip = forceip; forceip = FALSE; } #ifdef TRACE if (trace) dotrace(); #endif TRACE #ifdef BREAKPOINT if (breakenable && ip == breakpoint) dobreak(); #endif BREAKPOINT w = mem[ip]; ip++; /* w, mem, and ip are all global. W is now a POINTER TO the primitive number to execute, and ip points to the NEXT thread to follow. */ next1: /* This is for the SPECIAL CASE */ p = mem[w]; /* p is the actual number of the primitive */ if (p == 0) { /* SPECIAL CASE FOR EXECUTE! */ w = pop(); /* see above for explanation */ goto next1; } /* else */ switch(p) { case LIT : lit(); break; case BRANCH : branch(); break; case ZBRANCH : zbranch(); break; case PLOOP : ploop(); break; case PPLOOP : pploop(); break; case PDO : pdo(); break; case I : i(); break; case R : r(); break; case DIGIT : digit(); break; case PFIND : pfind(); break; case ENCLOSE : enclose(); break; case KEY : key(); break; case PEMIT : pemit(); break; case QTERMINAL : qterminal(); break; case CMOVE : cmove(); break; case USTAR : ustar(); break; case USLASH : uslash(); break; case AND : and(); break; case OR : or(); break; case XOR : xor(); break; case SPFETCH : spfetch(); break; case SPSTORE : spstore(); break; case RPFETCH : rpfetch(); break; case RPSTORE : rpstore(); break; case SEMIS : semis(); break; case LEAVE : leave(); break; case TOR : tor(); break; case FROMR : fromr(); break; case ZEQ : zeq(); break; case ZLESS : zless(); break; case PLUS : plus(); break; case DPLUS : dplus(); break; case MINUS : minus(); break; case DMINUS : dminus(); break; case OVER : over(); break; case DROP : drop(); break; case SWAP : swap(); break; case DUP : dup(); break; case TDUP : tdup(); break; case PSTORE : pstore(); break; case TOGGLE : toggle(); break; case FETCH : fetch(); break; case CFETCH : cfetch(); break; case TFETCH : tfetch(); break; case STORE : store(); break; case CSTORE : cstore(); break; case TSTORE : tstore(); break; case DOCOL : docol(); break; case DOCON : docon(); break; case DOVAR : dovar(); break; case DOUSE : douse(); break; case SUBTRACT : subtract(); break; case EQUAL : equal(); break; case NOTEQ : noteq(); break; case LESS : less(); break; case ROT : rot(); break; case DODOES : dodoes(); break; case DOVOC : dovoc(); break; case ALLOT : allot(); break; case PBYE : pbye(); break; case TRON : tron(); break; case TROFF : troff(); break; case DOTRACE : dotrace(); break; case PRSLW : prslw(); break; case PSAVE : psave(); break; case PCOLD : pcold(); break; default : errexit("Bad execute-code %d\n",p); break; } } } dotrace() { short worka, workb, workc; putchar('\n'); if (tracedepth) { /* show any stack? */ printf("sp: %04x (", csp); worka = csp; for (workb = tracedepth; workb; workb--) printf("%04x ",(unsigned short) mem[worka++]); putchar(')'); } printf(" ip=%04x ",ip); if (mem[R0]-rsp < RS_SIZE && mem[R0] - rsp > 0) /* if legal rsp */ for (worka = mem[R0]-rsp; worka; worka--) { /* indent */ putchar('>'); putchar(' '); } worka = mem[ip] - 3; /* this is second-to-last letter, or the count byte */ while (!(mem[worka] & 0x80)) worka--; /* skip back to count byte */ workc = mem[worka] & 0x2f; /* workc is count value */ worka++; while (workc--) putchar(mem[worka++] & 0x7f); fflush(stdout); if (debug) { /* wait for \n -- any other input will dump */ char buffer[10]; if (*gets(buffer) != '\0') { printf("dumping core... "); fflush(stdout); memdump(); puts("done."); } } } #ifdef BREAKPOINT dobreak() { int temp; puts("Breakpoint."); printf("Stack pointer = %x:\n",csp); for (temp = csp; temp < INITS0; temp++) printf("\t%04x",mem[temp]); putchar('\n'); } #endif BREAKPOINT main(argc,argv) int argc; char *argv[]; { FILE *fp; unsigned short size; int i = 1; cfilename = COREFILE; /* "forth.core" */ bfilename = BLOCKFILE; /* "forth.block" */ sfilename = SAVEFILE; /* "forth.newcore" */ trace = debug = breakenable = nobuf = 0; while (i < argc) { if (*argv[i] == '-') { switch (*(argv[i]+1)) { #ifdef TRACE case 'd': /* -d[n] */ debug = 1; /* ...and fall through */ case 't': /* -t[n] */ trace = TRUE; if (argv[i][2]) tracedepth = (argv[i][2] - '0'); else tracedepth = 0; break; #else !TRACE case 'd': case 't': fprintf(stderr, "Must compile with TRACE defined for -t or -d\n"); break; #endif TRACE case 'c': if (++i == argc) usage(argv[0]); cfilename = argv[i]; /* -c file */ break; case 's': if (++i == argc) usage(argv[0]); sfilename = argv[i]; /* -s file */ break; #ifdef BREAKPOINT case 'p': if (++i == argc) usage(argv[0]); breakenable = TRUE; /* -p xxxx */ breakpoint = xtoi(argv[i]); break; #else !BREAKPOINT case 'p': fprintf(stderr, "Must compile with BREAKPOINT defined for -p"); break; #endif BREAKPOINT case 'b': if (++i == argc) usage(); bfilename = argv[i]; /* -b blockfile */ break; case 'n': nobuf = TRUE; break; default: usage(argv[0]); exit(1); } } else usage(argv[0]); /* not a dash */ i++; } if ((fp = fopen(cfilename,"r")) == NULL) { fprintf(stderr,"Forth: Could not open %s\n", cfilename); exit(1); } if (fread(&size, sizeof(size), 1, fp) != 1) { fprintf(stderr,"Forth: %s is empty.\n",cfilename); exit(1) ; } if ((mem = (short *)calloc(size, sizeof(*mem))) == NULL) { fprintf(stderr, "Forth: unable to malloc(%d,%d)\n", size, sizeof(*mem)); exit(1); } mem[LIMIT] = size; if (fread(mem+1, sizeof(*mem), size-1, fp) != size-1) { fprintf(stderr, "Forth: not %d bytes on %s.\n", size, cfilename); exit(1); } fclose(fp); initsignals(); getblockfile(); if (!nobuf) setbuf(stdout,NULL); if (ip = mem[SAVEDIP]) { /* if savedip != 0, that is */ csp = mem[SAVEDSP]; rsp = mem[SAVEDRP]; puts("restarting a saved FORTH image"); } else { ip = mem[COLDIP]; /* this is the ip passed from nf.c */ /* ip now points to a word holding the CFA of COLD */ rsp = INITR0; /* initialize return stack */ csp = INITS0; } next(); /* never returns */ } usage(s) char *s; { fprintf(stderr, "usage:\n"); fprintf(stderr, "%s [-t[n]] [-d[n]] [-p xxxx] [-n]\n",s); fputs(stderr, "\t[-c corename] [-b blockname] [-s savename]\n"); fputs(stderr, "Where:\n"); fputs(stderr, "-t[n]\t\tsets trace mode\n"); fputs(stderr, "-d[n]\t\tsets trace mode and debug mode (waits for newline)"); fputs(stderr, "\t\t[n] above sets stack depth to display. Single digit, 0-9. Default 0.\n"); fputs(stderr, "-p xxxx\t\tsets a breakpoint at xxxx (in hex), shows stack when reached\n"); fputs(stderr, "-n\t\tleaves stdout line-buffered\n"); fprintf(stderr, "-c corename\tuses corename as the core image (default %s without -c)\n", COREFILE); fprintf(stderr, "-b blockname\tuses blockname as the blockfile (default %s without -b)\n", BLOCKFILE); fprintf(stderr, "-s savename\tuses savename as the save-image file (default %s without -s)\n", SAVEFILE); } memdump() /* dump core. */ { int i; /* top of RAM */ int temp, tempb, firstzero, nonzero; char chars[9], outline[80], tstr[6]; FILE *dumpfile; dumpfile = fopen(DUMPFILE,"w"); fprintf(dumpfile, "CSP = 0x%x RSP = 0x%x IP = 0x%x W = 0x%x DP = 0x%x\n", csp, rsp, ip, w, mem[DP]); for (temp = 0; temp < mem[LIMIT]; temp += 8) { nonzero = FALSE; sprintf(outline, "%04x:", temp); for (i=temp; i= ' ') chars[i%8] = tempb; else chars[i%8] = '.'; nonzero |= mem[i]; } if (nonzero) { fprintf(dumpfile,"%s %s\n",outline,chars); firstzero = TRUE; } else if (firstzero) { fprintf(dumpfile, "----- ZERO ----\n"); firstzero = FALSE; } } fclose(dumpfile); } /* here is where ctype.h is used */ xtoi(s) char *s; { /* convert hex ascii to integer */ int temp = 0; while (isxdigit (*s)) { /* first non-hex char ends */ temp <<= 4; /* mul by 16 */ if (isupper (*s)) temp += (*s - 'A') + 10; else if (islower (*s)) temp += (*s - 'a') + 10; else temp += (*s - '0'); s++; } return temp; } /* * Interrupt (^C) handling: If the user hits ^C once, the next pqterm call * will return TRUE. If he hits ^C again before pqterm is called, there will * be a forced jump to ABORT next time we hit next(). If it is a primitive * that is caught in an infinite loop, this won't help any. */ sig_int() { if (qtermflag) { /* second time? */ forceip = mem[ABORTIP]; /* checked each time through next */ qtermflag = FALSE; trace = FALSE; /* stop tracing; reset */ } else qtermflag = TRUE; } initsignals() { #ifdef AMIGA /* just ignore it for now, maybe it will go away :-) */ #else signal(SIGINT,sig_int); #endif } getblockfile() { /* recall that opening with mode "a+" opens for reading and writing */ /* with the pointer positioned at the end; this is so ftell returns */ /* the size of the file. */ if ((blockfile = fopen(bfilename, "a+")) == NULL) errexit("Can't open blockfile \"%s\"\n", bfilename); bfilesize = ftell(blockfile); printf("Block file has %d blocks.\n",(int) (bfilesize/1024) - 1); }