From: vandys@lindy.stanford.edu (Andy Valencia) Newsgroups: comp.sources.misc Subject: Forth interpreter in 68000 assembler Message-ID: <2931@ncoast.UUCP> Date: 18 Jul 87 00:29:03 GMT Sender: allbery@ncoast.UUCP Lines: 2148 Approved: allbery@ncoast.UUCP X-Archive: comp.sources.misc/8707/52 [68000 assembler, yes. Motorola format, not UN*X format; you'll have to change all "bar.l"-type opcodes to "barl" (i.e. "move.l" to "movl") to use it on a Sun or Plexus box. ++bsa] Since I'm on the warpath, here's a hand-coded 68000 forth interpreter which conforms to the Forth-83 standard. As I recollect, it actually deviates from it in two areas: first, words are 32 bits. Second, I didn't do Forth- standard I/O; I just use a pushdown stack of file descriptors, and read and write streams. I don't know what the policy is concerning machine-dependent code (especially a monstrosity like this :-)), but it sure isn't doing anyone any good sitting around here, and I'm sure you'll know what to do with it. Thanks, Andy Valencia vandys@lindy.stanford.edu #!/bin/sh-----cut here-----cut here-----cut here-----cut here----- # This is a shell archive. # Run the following text with /bin/sh to extract. cat - << \Funky!Stuff! > Makefile forth: forth.o ld -N -o forth -e init forth.o -l881 -lm @size forth @echo "Forth done" forth.o: forth.s as -o forth.o forth.s Funky!Stuff! cat - << \Funky!Stuff! > forth.s ; ; forth.s--a 68K forth interpreter ; ; Register allocation: ; A7--68K stack pointer ; A6--IP ; A5--SP ; A4--RSP ; A3..A0--General ; D7--Here ; D6--Input line pointer ; D5..D0--General ; ; ; Flag bits in status field ; Priority equ 1 Smudged equ 2 ; ; control structure checking flags ; FlgDef equ 1 ; : .. ; FlgBeg equ 2 ; begin .. again, while, repeat FlgWhi equ 5 ; the "while" part flag FlgIf equ 3 ; if .. endif FlgDo equ 4 ; do .. loop, +loop ; ; Other constants/offsets ; stacksize equ 100 umem equ 96 ; K of dict. space for user rstack ds.l stacksize ; 100 Words for return stack stack ds.l stacksize ; and 100 for user's stack mstack ds.l stacksize ; and 100 for the 68K processor stack Inbufsize equ 1024+4*3 ; Input buffer record InUnit equ 1024+1 ; Unix file descriptor number InbufIdx equ 1024+4 ; Holds index into it for nesting of units InbufPrev equ 1024+8 ; Pointer to previous input unit (nesting) MaxIn equ 4 ; Max # open input units MaxOut equ MaxIn ; and output units inbufs ds.b 1024 ; Input buffer dc.b 0,0 ; <NULL>, <STDIN> ds.b 2 ; two bytes wasted ds.l 1 ; holds index dc.l 0 ; ptr to prev--is NULL for first ds.b Inbufsize*(MaxIn-1) ; The rest of the input units End_inbufs outfds dc.l 1 ; <STDOUT> ds.l MaxOut-1 ; The rest of the output units ounit dc.l outfds ; Current output unit iunit dc.l inbufs ; and current input unit ; ; init--start up forth. Set up our dictionary & use ABORT ; globl init init move.l #udict,d7 ; Set up HERE ; ; abort--clear I/O, reset stacks, clear state, enter INTERP ; abort move.l #rstack+stacksize,a4 ; Initialize return stack move.l #stack+stacksize,a5 ; and user stack move.l #mstack+stacksize,a7 ; and processor stack clr.l state1 ; Set state back to interpretive move.l #interp,a6 ; Set IP to top of INTERP move.l #inbufs,a0 ; Set up & clear input buffer clr.b (a0) clr.b 1024(a0) move.l a0,d6 move.l d6,iunit clr.b InUnit(a0) move.l #outfds,a0 ; Set up & clear output buffer move.l #1,(a0) move.l a0,ounit move.l #3,-(a7) ; Close all open files clr.l -(a7) ; Dummy place holder move.l #20,d3 ; How many units to close abor1 moveq #6,d0 ; UNIX "close" system call trap #0 addq.l #1,4(a7) ; Move to next file descriptor dbra d3,abor1 add.l #8,a7 ; Remove arguments from stack ; Fall into... ; V ; ; Next--the "fetch/execute" code of FORTH ; next move.l (a6)+,a0 ; Get CFA's addr, advance IP next2 move.l (a0)+,a1 ; Get contents of CFA jmp (a1) ; Jump to that address ; ; interp--a high level definition ; : interp ; getword lookup if ; state @ 0= or if execute else [compile] (lit) , endif ; else ; number if ; state @ if , endif ; else notfound abort endif ; endif ; ; ; interp dc.l ckstack,getword,lookup,zbranch,inter1 dc.l state,fetch,zeq,l_or,zbranch,inter2 dc.l execute,branch,interp inter2 dc.l comma,branch,interp inter1 dc.l number,zbranch,inter3 dc.l state,fetch,zbranch,interp dc.l plit,plit,comma,comma,branch,interp inter3 dc.l notfound ; ; or--bitwise "or" ; l_or2 dc.l 0 l_or dc.l l_or1,l_or1,0 dc.b 'or ' l_or1 move.l (a5)+,d0 or.l d0,(a5) jmp next ; ; and--logical bit-wise AND ; l_and2 dc.l l_or2,l_and1,l_and1,0 dc.b 'and ' l_and1 move.l (a5)+,d0 and.l d0,(a5) jmp next ; ; 0<--push whether top is less than 0 ; zlt2 dc.l l_and2,zlt1,zlt1,0 dc.b '0< ' zlt1 tst.l (a5) blt puttrue bra putfalse ; ; 0>--push whether top is greater than 0 ; zgt2 dc.l zlt2,zgt1,zgt1,0 dc.b '0> ' zgt1 tst.l (a5) bgt puttrue bra putfalse ; ; u<--unsigned version of "less than" ; ult2 dc.l zgt2,ult1,ult1,0 dc.b 'u< ' ult1 move.l (a5)+,d0 cmp.l (a5),d0 beq putfalse bcc puttrue bra putfalse ; ; 0=--a logical "not" ; zeq2 dc.l ult2 zeq dc.l zeq1,zeq1,0 dc.b '0= ' zeq1 tst.l (a5) bne putfalse puttrue move.l #-1,(a5) jmp next putfalse clr.l (a5) jmp next ; ; <--less than. Push whether second is less than top ; lt2 dc.l zeq2,lt1,lt1,0 dc.b '< ' lt1 move.l (a5)+,d0 cmp.l (a5),d0 bgt puttrue bra putfalse ; ; >--greater than. Push whether second is greater than top ; gt2 dc.l lt2,gt1,gt1,0 dc.b '> ' gt1 move.l (a5)+,d0 cmp.l (a5),d0 blt puttrue bra putfalse ; ; =--push whether top and second are equal ; equal2 dc.l gt2,equal1,equal1,0 dc.b '= ' equal1 move.l (a5)+,d0 cmp.l (a5),d0 beq puttrue bra putfalse ; ; ccomma--store a byte into the next location ; ccomma2 dc.l equal2,ccomma1,ccomma1,0 dc.b 'c, ' ccomma1 move.l d7,a0 move.l (a5)+,d0 ; Get word off stack move.b d0,(a0) ; Store its low byte addq.l #1,d7 ; Advance HERE jmp next ; ; comma--store a word into the next free location, advancing the ; current location pointer ; comma2 dc.l ccomma2 comma dc.l comma1,comma1,0 dc.b ', ' comma1 addq.l #3,d7 ; Word-align data and.l #0xFFFFFFFC,d7 move.l d7,a0 move.l (a5)+,(a0)+ move.l a0,d7 jmp next ; ; !--store second at address pointed to by top ; store2 dc.l comma2 store dc.l store1,store1,0 dc.b '! ' store1 move.l (a5)+,a0 move.l (a5)+,(a0) jmp next ; ; @--replace top of stack with what it pointed to ; fetch2 dc.l store2 fetch dc.l fetch1,fetch1,0 dc.b '@ ' fetch1 move.l (a5),a0 move.l (a0),(a5) jmp next ; ; branch--replace IP with next sequential word in execution ; branch2 dc.l fetch2 branch dc.l branch1,branch1,0 dc.b 'branch ' branch1 move.l (a6),a6 jmp next ; ; zbranch--"branch" if top of stack is zero ; zbran2 dc.l branch2 zbranch dc.l zbran1,zbran1,0 dc.b 'zbranch ' zbran1 move.l (a6)+,d0 ; Get the conditional destination tst.l (a5)+ ; Should we take it? beq zbran3 jmp next zbran3 move.l d0,a6 ; Take the branch jmp next ; ; run-time code to push the PFA to stack ; getpfa move.l (a0),-(a5) jmp next ; ; state--variable which holds the state: 0 == interp, <>0 == compiling ; state2 dc.l zbran2 state dc.l getpfa,state1,0 dc.b 'state ' state1 dc.l 0 ; ; getword--get the next word from the input stream, put it in "pad". ; getw2 dc.l state2 getword dc.l getwo1,getwo1,0 dc.b 'getword ' getwo1 jsr getw1 jmp next getw1 move.l d6,a0 ; A0 will be our line pointer jsr skipwhite ; Skip leading white space move.l #pad1,a1 ; Build into "pad" via A1 clr.b 8(a1) ; Put in Null-termination move.l #8,d1 ; Count # chars stored getw3 move.b (a0)+,(a1)+ ; Get next char bne.s getw10 ; Need to read in a new buffer? subq.l #1,a1 ; Back up destination ptr getw20 movem.l a1/d1,-(a7) ; Save registers jsr getline ; Get new line movem.l (a7)+,a1/d1 ; Restore registers move.l d6,a0 ; Update input line pointer bra.s getw4 getw10 subq.l #1,d1 ; Decrement character count beq getw5 ; If run out, truncate rest of word getw4 jsr iswhite ; See if at end of word bne getw3 tst.b (a0) ; At end of buffer? beq.s getw20 tst.l d1 ; Blank-fill word beq getw6 getw7 move.b #32,(a1)+ subq.l #1,d1 bne getw7 getw6 move.l a0,d6 ; Save input pointer rts getw5 tst.b (a0) ; Get new buffer at end of current bne.s getw11 jsr getline move.l d6,a0 bra.s getw5 getw11 jsr iswhite ; Quit when get white space beq.s getw6 addq.l #1,a0 ; Skip over characters bra.s getw5 ; ; skipwhite--skip over white space. For a number of bizarre reasons, ; this is also the best place to read in a new buffer if we run ; off the end of the current one. It is expected that all input lines ; will end in NEWLINE--if they don't, you're taking a chance. ; skipwhite jsr iswhite ; Check next char: bne skipw2 ; No white space, return tst.b (a0)+ ; At end of input buffer? bne skipwhite ; No--continue jsr getline ; Yes--get a fresh buffer move.l d6,a0 ; update our line buffer pointer bra skipwhite skipw2 rts ; ; iswhite--return via the Z flag whether the char pointed to by A0 ; is a white space character. Uses D3 to hold the char. ; iswhite move.b (a0),d3 ; Get the char cmp.b #32,d3 ; Check space beq iswh2 cmp.b #9,d3 ; ..Tab beq iswh2 cmp.b #10,d3 ; ..Newline beq iswh2 tst.b d3 ; ..NULL iswh2 rts ; ; getline--get another buffer-full from the current input unit. If no ; more input is available on it, pop back a level. If there are ; no more levels (i.e., the user typed ^D), exit. If the input is ; TTY, prompt. ; ok_msg dc.b 'Ok',10,'> ',0 even getline move.l iunit,a0 ; Get ptr to head of current input record cmp.l #inbufs,a0 ; See if it's the TTY bne getl9 move.l #ok_msg,a0 ; Print "Ok" jsr prstr move.l iunit,a0 ; restore A0 getl9 move.l a0,d6 ; Set up our input line pointer getl4 move.b InUnit(a0),d0 ; Get file descriptor ext.w d0 ext.l d0 ; Turn file descriptor into longword move.l #1024,-(a7) ; Third arg: # bytes move.l a0,-(a7) ; Second: store buffer move.l d0,-(a7) ; First arg is file descriptor clr.l -(a7) ; Dummy space holder moveq #3,d0 ; UNIX READ syscall trap #0 bcc getl2 ; On carry set, abort on an I/O error jmp io_err getl2 add.l #16,a7 ; Pop off arguments tst.l d0 ; Zero bytes read means EOF--pop up a unit! beq.s getl3 add.l d0,a0 ; Tack on the trailing NULL clr.b (a0) rts ; and return getl3 ; Hit EOF--pop back a unit, or exit move.l InbufPrev(a0),d0 ; Get previous record beq leave ; STDIN at EOF--exit move.l d0,a0 move.l a0,iunit ; Update current unit move.l InbufIdx(a0),d6 ; Get the old line index rts ; ; leave--do an "exit" syscall ; leave move.l #1,d0 ; Request 1 means "exit" clr.l -(a7) ; We will give a return code of 0 clr.l -(a7) trap #0 trap #1 ; Shouldn't reach here! ; ; pad--an area of storage to use ; pad2 dc.l getw2 pad dc.l getpfa,pad1,0 dc.b 'pad ' pad1 ds.b 84 ; ; lookup--search for the word represented by the first 8 bytes of PAD ; in the dictionary. If it's not found, push FALSE. Otherwise, ; push the CFA, the priority, and TRUE. ; look2 dc.l pad2 lookup dc.l look1,look1,0 dc.b 'lookup ' look1 jsr look99 jmp next look99 move.l latest+4,a0 ; Get pointer to latest definition move.l pad1,d3 ; Get search string move.l pad1+4,d4 look5 cmp.l 16(a0),d3 ; Compare first 4 bytes bne look3 cmp.l 20(a0),d4 ; Compare second 4 bytes bne look3 move.l 12(a0),d5 ; See if smudged and.l #Smudged,d5 bne look3 add.l #4,a0 ; turn A0 into CFA addr and push move.l a0,-(a5) move.l 8(a0),d0 ; Get status field and.l #Priority,d0 ; Push flag for priority move.l d0,-(a5) move.l #-1,-(a5) ; Push true flag--word found rts look3 move.l (a0),d0 ; Move to next entry tst.l d0 ; Check null ptr (end of chain) beq look4 move.l d0,a0 ; Move back to A0 bra look5 look4 clr.l -(a5) ; Not found--push false rts ; ; execute--pop a CFA off the stack & invoke that word ; exec2 dc.l look2 execute dc.l exec1,exec1,0 dc.b 'execute ' exec1 move.l (a5)+,a0 jmp next2 ; ; number--if the string in PAD is not a legal number, push FALSE. ; If it is, push the value and TRUE. ; num2 dc.l exec2 number dc.l num1,num1,0 dc.b 'number ' num1 move.l #pad1,a0 ; This is where our number is jsr num99 jmp next num99 clr.l d0 ; D0 accumulates the result move.l base,d5 ; D5 is the current base cmp.b #45,(a0) ; Flag negation if leading '-' there seq d3 bne num3 add.l #1,a0 num3 move.b (a0)+,d1 ; Get next char tst.b d1 ; At end of string? beq num4 cmp.b #32,d1 ; At the trailing blanks? beq num4 jsr isdig ; Legal numeric digit? bne num6 ; No, this isn't a number muls d5,d0 ; Yes, shift and add add.l d1,d0 ; ("isdigit" converts it) bra num3 num4 tst.b d3 ; See if it should be negated beq num5 neg.l d0 num5 move.l d0,-(a5) ; Push number move.l #-1,-(a5) ; and true flag rts num6 clr.l -(a5) ; Not number, push false rts ; ; isdig--check whether the character in D1 is a legal digit. If it is, ; return its value in D2, and Z set. Otherwise, return with ; Z cleared. We assume that BASE has already been put in D5, ; isdig sub.l #48,d1 ; Shift '0' down to 0 blt isdi1 ; Was lower than '0'--can't be a digit cmp.b #10,d1 ; Was it 0..9? blt isdi2 sub.b #7,d1 ; Map 'A'..'F' down to 10..15 blt isdi1 cmp.b #16,d1 ; Was it in range 10..15? blt isdi2 sub.b #32,d1 ; Finally, map 'a'..'f' down to 10..15 blt isdi1 cmp.b #16,d1 ; Was it in range 10..15? bge isdi1 isdi2 ext.w d1 ; Turn the number into a longword ext.l d1 cmp.l d5,d1 ; See if it's within the base bge isdi1 ori #4,ccr ; Set Z--we have a legal number rts isdi1 andi #0xFB,ccr ; Clear Z--not a digit! rts ; ; (lit)--run-time word to push a literal onto the stack ; plit2 dc.l num2 plit dc.l plit1,plit1,0 dc.b '(lit) ' plit1 move.l (a6)+,-(a5) jmp next base2 dc.l plit2,getpfa ; Current base for numbers dc.l base,0 dc.b 'base ' base dc.l 10 ; ; prstr--print a string to the current output unit. No management of the ; TTY is implied here--it just writes to the current output unit. ; The string to print is pointed to by A0. ; prstr clr.l d0 ; String length counter move.l a0,a1 ; Local copy of the pointer prst1 tst.b (a1)+ ; At end of string? beq prst2 add.l #1,d0 ; No, increment count bra prst1 ; and loop prst2 move.l ounit,a1 ; Build syscall parameters move.l d0,-(a7) ; Number of bytes move.l a0,-(a7) ; Buffer move.l (a1),-(a7) ; File descriptor clr.l -(a7) ; Dummy place holder move.l #4,d0 ; A write syscall trap #0 ; Do the call add.l #16,a7 ; Remove the arguments bcc prst3 jmp io_err ; Complain if the I/O failed prst3 rts ; ; io_err--complain about an I/O error ; io_err move.l #io_err_msg,a0 ; The error message jsr prstr jmp abort io_err_msg dc.b 10,'I/O error!',10,0 even ; ; notfound--routine to call when the compiler gets a word it ; doesn't know. ; notf2 dc.l base2 notfound dc.l notf1,notf1,0 dc.b 'notfound' notf1 move.l #pad1,a0 ; Print the word jsr prstr move.l #notf_msg,a0 ; Print ": not found" jsr prstr jmp abort notf_msg dc.b ': not found',10,0 even ; ; The match primitives--+, -, *, / ; plus2 dc.l notf2,plus1,plus1,0 dc.b '+ ' plus1 move.l (a5)+,d0 add.l d0,(a5) jmp next sub2 dc.l plus2,sub1,sub1,0 dc.b '- ' sub1 move.l (a5)+,d0 sub.l d0,(a5) jmp next globl _lrem mod2 dc.l sub2,mod1,mod1,0 dc.b 'mod ' mod1 move.l (a5)+,-(sp) move.l (a5),-(sp) jbsr _lrem addq.l #8,sp move.l d0,(a5) jmp next globl _ldiv div2 dc.l mod2,div1,div1,0 dc.b '/ ' div1 move.l (a5)+,-(sp) ; Divisor move.l (a5),-(sp) ; Dividend jbsr _ldiv addq #8,sp move.l d0,(a5) jmp next tdm2 dc.l div2,tdm1,tdm1,0 dc.b '*/mod ' tdm1 move.l (a5)+,d0 ; Hold divisor move.l (a5)+,d1 ; Get two multipliers move.l (a5),d2 muls d1,d2 divs d0,d2 ; Divide into the product move.l d2,d3 ; push remainder swap d3 ext.l d3 move.l d3,(a5) ext.l d2 ; now push quotient move.l d2,-(a5) jmp next td2 dc.l tdm2,td1,td1,0 dc.b '*/ ' td1 move.l (a5)+,d0 ; Divisor move.l (a5)+,d1 ; Two multipliers move.l (a5),d2 muls d1,d2 divs d0,d2 ; divide into product ext.l d2 ; Extend quotient to longword and push move.l d2,(a5) jmp next divmod2 dc.l td2,divmod1,divmod1,0 dc.b '/mod ' divmod1 move.l (a5)+,d0 ; Divisor move.l (a5),d1 ; Dividend divs d0,d1 move.l d1,d0 swap d0 ; Put remainder in low word ext.l d0 ; fill remainder to longword quantity move.l d0,(a5) ext.l d1 ; Now fill quotient to longword move.l d1,-(a5) jmp next mul2 dc.l divmod2,mul1,mul1,0 dc.b '* ' mul1 move.l (a5)+,d0 move.w d0,d1 move.w (a5)+,d0 tst.l d0 beq.s timesl1 move.w d1,a0 mulu d0,d1 swap d0 mulu (a5),d0 add.w d1,d0 swap d0 clr.w d0 move.w a0,d1 mulu (a5)+,d1 add.l d1,d0 bra.s timesl2 timesl1 move.w (a5)+,d0 mulu d1,d0 timesl2 move.l d0,-(a5) jmp next ; ; u.--due to the stupidity of the 68K divide instructions, this has ; to be just an alias for ".". ; udot2 dc.l mul2,dot1,dot1,0 dc.b 'u. ' ; ; .--pop and print the top of stack in the current base ; dot2 dc.l udot2,dot1,dot1,0 dc.b '. ' dot1 move.l (a5)+,d0 ; The number to print move.l base,d2 ; In this base move.l #pad1+20,a0 ; Where to build the number clr.b (a0) ; A terminating NULL move.b #32,-(a0) ; Add a trailing blank tst.l d0 ; Handle negative numbers slt d1 ; Flag a negative move.l d1,-(sp) bge dot3 neg.l d0 ; Negate a negative dot3 move.l d2,-(sp) move.l d0,-(sp) jbsr _lrem ; divide, getting the next digit addq.l #8,sp add.b #48,d0 ; Move 0..9 to '0'..'9' cmp.b #58,d0 ; Hex digit? blt dot4 addq.b #7,d0 dot4 move.b d0,-(a0) ; Store the digit move.l d1,d0 ; Get quotient tst.l d0 ; All of the number printed? bne dot3 move.l (sp)+,d2 tst.b d2 ; Tack on a leading '-' if it's needed beq dot7 move.b #45,-(a0) dot7 jsr prstr dot9 jmp next ; ; ckstack--check the user's stack for underflow ; cks_msg dc.b '? Stack empty',10,0 even cks2 dc.l dot2 ckstack dc.l cks1,cks1,0 dc.b '?stack ' cks1 cmp.l #stack+stacksize,a5 ble dot9 move.l #cks_msg,a0 ; Underflowed--complain jsr prstr jmp abort ; ; words--list contents of dictionary ; wrdpad dc.b ' ' word2 dc.l cks2,word1,word1,0 dc.b 'words ' word1 move.l late1,a2 ; For following the dictionary chain word3 move.l #pad1,a1 ; Set up for next line moveq #6,d0 ; Number of entries per line word4 cmp.l #0,a2 ; See if at end of chain beq word5 move.l 16(a2),(a1)+ ; Copy string move.l 20(a2),(a1)+ move.l wrdpad,(a1)+ ; Pad with 4 spaces move.l (a2),a2 ; Advance to next entry subq.l #1,d0 bne word4 word5 move.b #10,(a1)+ ; Trailing newline clr.b (a1) ; and NULL move.l #pad1,a0 ; Write it jsr prstr cmp.l #0,a2 ; All done? bne word3 jmp next ; ; make_head--build a FORTH header, return its address in ; register A0. ; make_head move.l d7,a0 ; For returning it move.l d7,a1 ; For storing sequentially move.l late1,(a1)+ ; Build this def into the chain move.l d7,late1 clr.l (a1)+ ; Empty CFA lea 24(a0),a2 ; Point PFA to the def body move.l a2,(a1)+ clr.l (a1)+ movem.l a0/a1,-(a5) ; Stash our work reg jsr getw1 ; Build the name in-line movem.l (a5)+,a0/a1 ; Stash our work reg move.l pad1,(a1)+ move.l pad1+4,(a1)+ move.l a1,d7 ; Reset D7 rts ; ; variable--allocate a variable in the dictionary ; var2 dc.l word2,var1,var1,0 dc.b 'variable' var1 addq.l #3,d7 ; Word-align HERE and.l #0xFFFFFFFC,d7 jsr make_head ; Build a header move.l #getpfa,4(a0) ; Our run-time code will push the PFA addq.l #4,d7 ; Our body starts with one word jmp next ; ; constant--allocate a constant in the dictionary ; const2 dc.l var2,const1,const1,0 dc.b 'constant' const1 addq.l #3,d7 ; Word-align HERE and.l #0xFFFFFFFC,d7 jsr make_head ; Build header move.l #getpfa,4(a0) ; run-time code pushes PFA move.l (a5)+,8(a0) ; Our PFA is the number on-stack jmp next ; ; colon--go into compilation mode ; colon2 dc.l const2,colon1,colon1,0 dc.b ': ' colon1 addq.l #3,d7 ; Word-align definitions and.l #0xFFFFFFFC,d7 move.l #1,state1 ; Go into compilation state jsr make_head ; Build our header move.l #hilev,4(a0) ; our CFA invokes a high-level def move.l #Smudged,12(a0) ; and we start Smudged move.l #FlgDef,-(a5) ; Push our flag for a definition jmp next ; ; semicolon--come out of compilation mode ; semi_msg dc.b 'control structure not matched',10,0 even semi2 dc.l colon2,semi1,semi1,Priority dc.b 59,' ' semi1 clr.l state1 ; Back to interpretive state move.l late1,a0 ; Turn off the smudge bit clr.l 12(a0) move.l d7,a0 ; Compile in a trailing ';s' move.l #popup,(a0)+ move.l a0,d7 cmp.l #FlgDef,(a5)+ ; See if control structures matched bne semi3 jmp next semi3 move.l #semi_msg,a0 ; Complain jsr prstr jmp abort ; ; hilev--the machine code which sets off a high-level definition ; hilev move.l a6,-(a4) ; Save old IP move.l (a0),a6 ; Get new IP jmp next ; ; popup--aka ';s'. Pop the IP from the return stack. For exiting ; a high-level word. ; pop2 dc.l semi2 popup dc.l pop1,pop1,0 dc.b 59,'s ' pop1 move.l (a4)+,a6 jmp next ; ; do--build the opening part of a do..loop ; do2 dc.l pop2,do1,do1,Priority dc.b 'do ' do1 move.l d7,a0 move.l #pushr,(a0)+ ; Generate code to get the loop parameters move.l #pushr,(a0)+ move.l a0,-(a4) ; Save this place for backbranching move.l #pdo,(a0)+ ; compile (do) clr.l (a0)+ ; Leave room for our forward branch move.l #FlgDo,-(a5) ; Flag our control structure move.l a0,d7 do3 jmp next ; ; (do)--run-time word to set off a do..loop ; pdo2 dc.l do2 pdo dc.l pdo1,pdo1,0 dc.b '(do) ' pdo1 move.l 4(a4),d0 ; Check for exit condition cmp.l (a4),d0 ; Check for exit condition blt pdo3 addq.l #8,a4 ; Clear the loop parameters move.l (a6),a6 ; Jump out of loop jmp next pdo3 addq.l #4,a6 ; Loop's not done--advance IP jmp next ; and continue ; ; loop--compile in the closing part of a loop ; loop2 dc.l pdo2,loop1,loop1,Priority dc.b 'loop ' loop1 cmp.l #FlgDo,(a5) ; See if they botched bne loop3 addq.l #4,a5 ; Free the flag move.l d7,a0 move.l #ploop,(a0)+ ; Compile (loop) move.l (a4)+,a1 ; Get address of "loop" move.l a1,(a0)+ ; This is our backbranch address move.l a0,4(a1) ; Give them the forward branch address move.l a0,d7 ; Restore HERE jmp next loop3 move.l #loop_msg,a0 jsr prstr jmp abort loop_msg dc.b 10,'do not matched by loop',10,0 even ; ; +loop--compile in the closing part of a loop ; aloop2 dc.l loop2,aloop1,aloop1,Priority dc.b '+loop ' aloop1 cmp.l #FlgDo,(a5) ; See if they botched bne aloop3 addq.l #4,a5 ; Free the flag move.l d7,a0 move.l #paloop,(a0)+ ; Compile (loop) move.l (a4)+,a1 ; Get address of "loop" move.l a1,(a0)+ ; This is our backbranch address move.l a0,4(a1) ; Give them the forward branch address move.l a0,d7 ; Restore HERE jmp next aloop3 move.l #loop_msg,a0 jsr prstr jmp abort aloop_msg dc.b 10,'do not matched by +loop',10,0 even ; ; (+loop)--run-time loop execution ; paloop2 dc.l aloop2 paloop dc.l paloop1,paloop1,0 dc.b '(+loop) ' paloop1 move.l (a5)+,d0 ; Add on number from user's stack add.l d0,4(a4) move.l (a6),a6 ; branch back jmp next ; ; (loop)--run-time loop execution ; ploop2 dc.l paloop2 ploop dc.l ploop1,ploop1,0 dc.b '(loop) ' ploop1 addq.l #1,4(a4) ; Increment the run-time index move.l (a6),a6 ; branch back jmp next ; ; >r--pop top of operand stack & push on return stack ; pushr2 dc.l ploop2 pushr dc.l pushr1,pushr1,0 dc.b '>r ' pushr1 move.l (a5)+,-(a4) jmp next ; ; r>--pop top of return stack & push on operand stack ; popr2 dc.l pushr2 popr dc.l popr1,popr1,0 dc.b 'r> ' popr1 move.l (a4)+,-(a5) jmp next ; ; r@--copy top of return stack to user stack ; rget2 dc.l popr2,rget1,rget1,0 dc.b 'r@ ' rget1 move.l (a4),-(a5) jmp next ; ; depth--tell how many elements are on user stack ; depth2 dc.l rget2,depth1,depth1,0 dc.b 'depth ' depth1 move.l #stack+stacksize,d0 sub.l a5,d0 asr.l #2,d0 move.l d0,-(a5) jmp next ; ; i--push index of innermost do..loop context ; push_i2 dc.l depth2,push_i1,push_i1,0 dc.b 'i ' push_i1 move.l 4(a4),-(a5) jmp next ; ; j--like i, but second most-innermost ; push_j2 dc.l push_i2,push_j1,push_j1,0 dc.b 'j ' push_j1 move.l 12(a4),-(a5) jmp next ; ; leave--jump out of the innermost loop structure. Note that control ; structure matching isn't done here, since we will probably be ; inside of multiple if..endif contexts--meaningful error checking ; would be very difficult to provide. ; leave2 dc.l push_j2,leave1,leave1,Priority dc.b 'leave ' leave1 move.l (a4),a1 ; This is the address of the (do) part move.l d7,a0 ; We will be compiling some stuff in: move.l #pleave,(a0)+ ; (leave) addq.l #4,a1 ; addr of the exit location--(do)+1 move.l a1,(a0)+ move.l a0,d7 jmp next ; ; (leave)--fetch via the word which follows us, and make that the IP ; pleave2 dc.l leave2 pleave dc.l pleave1,pleave1,0 dc.b '(leave) ' pleave1 move.l (a6),a0 ; Addr of exit address move.l (a0),a6 ; Set IP to it addq.l #8,a4 ; Clear the do..loop's parameters of rstack jmp next ; ; if--starting part of a conditional ; if2 dc.l pleave2,if1,if1,Priority dc.b 'if ' if1 move.l d7,a0 move.l #zbranch,(a0)+ ; If false, branch around move.l a0,-(a5) ; save this place for back-branch clr.l (a0)+ ; leave room for it move.l a0,d7 move.l #FlgIf,-(a5) ; Flag the control structure jmp next ; ; else--optional middle part of a conditional ; else2 dc.l if2,else1,else1,Priority dc.b 'else ' else1 cmp.l #FlgIf,(a5) ; Check control structure bne else3 move.l d7,a0 move.l 4(a5),a1 ; Save location to backpatch move.l #branch,(a0)+ ; Patch in a branch out of the conditional move.l a0,4(a5) ; the new back-patch location clr.l (a0)+ move.l a0,(a1) ; Now patch in address of false part of cond. move.l a0,d7 jmp next else3 move.l #else_msg,a0 ; Complain about bad control structure jsr prstr jmp abort else_msg dc.b 10,'else does not match an if',10,0 even ; ; endif--ending part of a conditional ; endif2 dc.l else2,endif1,endif1,Priority dc.b 'endif ' endif1 cmp.l #FlgIf,(a5) ; Check control strucure bne endif3 addq.l #4,a5 ; Pop off flag move.l (a5)+,a0 ; Get address to back-patch move.l d7,(a0) ; backpatch it jmp next endif3 move.l #endif_msg,a0 ; complain jsr prstr jmp abort endif_msg dc.b 10,'endif does not match if/else',10,0 even ; ; stack manipulation words--dup, swap, rot, -rot, drop, over ; over2 dc.l endif2,over1,over1,0 dc.b 'over ' over1 move.l 4(a5),-(a5) jmp next pick2 dc.l over2,pick1,pick1,0 dc.b 'pick ' pick1 move.l (a5)+,d0 asl.l #2,d0 ; Scale D0 for a word offset move.l 0(a5,d0.l),-(a5) jmp next roll2 dc.l pick2,roll1,roll1,0 dc.b 'roll ' roll1 move.l (a5)+,d0 asl.l #2,d0 move.l 0(a5,d0.l),d1 ; Save word rolling into roll3 tst.l d0 ; While not to top of stack... beq roll4 move.l -4(a5,d0.l),0(a5,d0.l) ; Copy down a word subq.l #4,d0 ; Advance a word bra roll3 roll4 move.l d1,(a5) ; Replace top with word jmp next dup2 dc.l roll2,dup1,dup1,0 dc.b 'dup ' dup1 move.l (a5),-(a5) jmp next qdup2 dc.l dup2,qdup1,qdup1,0 dc.b '?dup ' qdup1 move.l (a5),d0 beq qdup3 move.l d0,-(a5) qdup3 jmp next swap2 dc.l qdup2,swap1,swap1,0 dc.b 'swap ' swap1 move.l (a5)+,d0 move.l (a5),d1 move.l d0,(a5) move.l d1,-(a5) jmp next rot2 dc.l swap2,rot1,rot1,0 dc.b 'rot ' rot1 move.l (a5)+,d0 move.l (a5)+,d1 move.l (a5),d2 move.l d1,(a5) move.l d0,-(a5) move.l d2,-(a5) jmp next drot2 dc.l rot2,drot1,drot1,0 dc.b '-rot ' drot1 move.l (a5)+,d0 move.l (a5)+,d1 move.l (a5),d2 move.l d0,(a5) move.l d2,-(a5) move.l d1,-(a5) jmp next drop2 dc.l drot2,drop1,drop1,0 dc.b 'drop ' drop1 addq.l #4,a5 jmp next ; ; begin--start a structured loop ; beg2 dc.l drop2,beg1,beg1,Priority dc.b 'begin ' beg1 move.l d7,-(a5) move.l #FlgBeg,-(a5) jmp next ; ; again--unconditional branch back; an infinite loop ; again2 dc.l beg2,again1,again1,Priority dc.b 'again ' again1 cmp.l #FlgBeg,(a5) bne again3 addq.l #4,a5 move.l d7,a0 move.l #branch,(a0)+ move.l (a5)+,(a0)+ move.l a0,d7 jmp next again3 move.l #again_msg,a0 jsr prstr jmp abort again_msg dc.b 10,'again does not match a begin',10,0 even ; ; until--branch back until condition becomes true ; until2 dc.l again2,until1,until1,Priority dc.b 'until ' until1 cmp.l #FlgBeg,(a5) bne until3 addq.l #4,a5 move.l d7,a0 move.l #zbranch,(a0)+ move.l (a5)+,(a0)+ move.l a0,d7 jmp next until3 move.l #until_msg,a0 jsr prstr jmp abort until_msg dc.b 10,'until does not match a begin',10,0 even ; ; while..repeat: loop with exit check up front ; while2 dc.l until2,while1,while1,Priority dc.b 'while ' while1 cmp.l #FlgBeg,(a5) ; Check control structure bne while3 move.l d7,a0 move.l #zbranch,(a0)+ ; Branch out on false move.l a0,(a5) ; save where to backpatch clr.l (a0)+ move.l a0,d7 move.l #FlgWhi,-(a5) ; And place our own flag jmp next while3 move.l #while_msg,a0 ; Complain jsr prstr jmp abort while_msg dc.b 10,'while does not match a begin',10,0 even ; ; repeat--the closing part of a begin..while..repeat structure ; rep2 dc.l while2,rep1,rep1,Priority dc.b 'repeat ' rep1 cmp.l #FlgWhi,(a5) ; Check control structure bne rep3 addq.l #4,a5 move.l (a5)+,a1 ; Save where to backpatch move.l d7,a0 move.l #branch,(a0)+ ; Generate a backbranch move.l (a5)+,(a0)+ ; to top of loop move.l a0,d7 move.l d7,(a1) ; Backpatch exit location, HERE jmp next rep3 move.l #rep_msg,a0 ; Complain jsr prstr jmp abort rep_msg dc.b 10,'repeat does not match a while',10,0 even ; ; xor--exclusive OR ; xor2 dc.l rep2,xor1,xor1,0 dc.b 'xor ' xor1 move.l (a5)+,d0 eor d0,(a5) jmp next ; ; not--one's complement ; not2 dc.l xor2,not1,not1,0 dc.b 'not ' not1 eor #0xFFFFFFFF,(a5) jmp next ; ; 1+, 1-, 2+, 2-, 2*, 2/--common, quick math operations ; onep2 dc.l not2,onep1,onep1,0 dc.b '1+ ' onep1 addq.l #1,(a5) jmp next onem2 dc.l onep2,onem1,onem1,0 dc.b '1- ' onem1 subq.l #1,(a5) jmp next twop2 dc.l onem2,twop1,twop1,0 dc.b '2+ ' twop1 addq.l #2,(a5) jmp next twom2 dc.l twop2,twom1,twom1,0 dc.b '2- ' twom1 subq.l #2,(a5) jmp next twot2 dc.l twom2,twot1,twot1,0 dc.b '2* ' twot1 move.l (a5),d0 asl.l #1,d0 move.l d0,(a5) jmp next twod2 dc.l twot2,twod1,twod1,0 dc.b '2/ ' twod1 move.l (a5),d0 asr.l #1,d0 move.l d0,(a5) jmp next ; ; c@, c!--character fetch/store ; cfetch2 dc.l twod2,cfetch1,cfetch1,0 dc.b 'c@ ' cfetch1 move.l (a5),a0 move.b (a0),d0 ext.w d0 ext.l d0 move.l d0,(a5) jmp next cstore2 dc.l cfetch2,cstore1,cstore1,0 dc.b 'c! ' cstore1 move.l (a5)+,a0 move.l (a5)+,d0 move.b d0,(a0) jmp next pstore2 dc.l cstore2,pstore1,pstore1,0 dc.b '+! ' pstore1 move.l (a5)+,a0 move.l (a5)+,d0 add.l d0,(a0) jmp next ; ; min and max--push greater or less of two numbers ; min2 dc.l pstore2,min1,min1,0 dc.b 'min ' min1 move.l (a5)+,d0 cmp.l (a5),d0 bge min3 min4 move.l d0,(a5) min3 jmp next max2 dc.l min2,max1,max1,0 dc.b 'max ' max1 move.l (a5)+,d0 cmp.l (a5),d0 ble min3 bra min4 ; ; abs, negate--replace number with its absolute value or negation ; abs2 dc.l max2,abs1,abs1,0 dc.b 'abs ' abs1 move.l (a5),d0 bge min3 neg.l (a5) jmp next neg2 dc.l abs2,neg1,neg1,0 dc.b 'negate ' neg1 neg.l (a5) jmp next ; ; cmove--move a range of bytes ; cmov2 dc.l neg2,cmov1,cmov1,0 dc.b 'cmove ' cmov1 move.l (a5)+,d0 ; Count move.l (a5)+,a0 ; Destination move.l (a5)+,a1 ; Source tst.l d0 ; Catch case of zero-length beq cmov4 cmov3 move.b (a1)+,(a0)+ ; Move bytes dbra d0,cmov3 cmov4 jmp next ; ; cmove>--like cmove, but set up to guard against the "ripple" effect ; cmovu2 dc.l cmov2,cmovu1,cmovu1,0 dc.b 'cmove> ' cmovu1 move.l (a5)+,d0 ; Count move.l (a5)+,a0 ; Destination move.l (a5)+,a1 ; Source tst.l d0 ; Zero-length? beq cmov4 add.l d0,a0 ; Point to end of destination add.l d0,a1 ; same for source cmovu3 move.b -(a1),-(a0) ; Move bytes dbra d0,cmovu3 jmp next ; ; fill--fill a range of bytes with a constant ; fill2 dc.l cmovu2,fill1,fill1,0 dc.b 'fill ' fill1 move.l (a5)+,d0 ; Get byte constant to use move.l (a5)+,d1 ; # Bytes to fill move.l (a5)+,a0 ; Where to start tst.l d0 ; Avoid zero-length beq cmov4 fill3 move.b d0,(a0)+ ; Fill bytes subq.l #1,d1 bne fill3 jmp next ; ; count--get byte at addr, advance addr ; count2 dc.l fill2,count1,count1,0 dc.b 'count ' count1 move.l (a5),a0 ; Get addr move.b (a0)+,d0 ; Get byte at addr, advance move.l a0,(a5) ; Store back addr ext.w d0 ; and extended byte ext.l d0 move.l d0,-(a5) jmp next ; ; -trailing--trim trailing spaces ; dtrail2 dc.l count2,dtrail1,dtrail1,0 dc.b '-trailin' dtrail1 move.l (a5)+,d0 ; Current count beq dtrail4 ; handle zero-length move.l (a5),a0 ; Address of string add.l d0,a0 ; Get address of current end of string dtrail3 cmp.b #32,-(a0) ; Check next char beq dtrail4 subq.l #1,d0 bne dtrail3 dtrail4 move.l d0,-(a5) ; Push back count jmp next ; ; decimal, hex, octal--set BASE ; deci2 dc.l dtrail2,deci1,deci1,0 dc.b 'decimal ' deci1 move.l #10,base jmp next hexa2 dc.l deci2,hexa1,hexa1,0 dc.b 'hex ' hexa1 move.l #16,base jmp next octa2 dc.l hexa2,octa1,octa1,0 dc.b 'octal ' octa1 move.l #8,base jmp next ; ; The number printing words--<# # #> #s hold sign ; lsh_pos ds.l 1 ; Position in output buffer lsh2 dc.l octa2,lsh1,lsh1,0 dc.b '<# ' ; Prepare for conversion lsh1 move.l #pad1+70,lsh_pos jmp next sh2 dc.l lsh2,sh1,sh1,0 dc.b '# ' ; Convert next digit sh1 jsr sh99 jmp next sh99 move.l base,-(sp) ; get BASE--format is wrong in mem. move.l (a5),-(sp) jbsr _lrem move.l d1,(a5) ; put quotient back to stack add.l #48,d0 ; Remainder: map 0 to '0' cmp.l #58,d0 ; Check for HEX digits blt sh3 addq.l #7,d0 ; Map 10 to 'A' sh3 move.l lsh_pos,a0 ; Store character into PAD, advance move.b d0,-(a0) move.l a0,lsh_pos rts shg2 dc.l sh2,shg1,shg1,0 dc.b '#> ' ; End conversion shg1 move.l lsh_pos,d0 move.l d0,(a5) ; Push address move.l #pad1+70,d1 ; Calculate count sub.l d0,d1 move.l d1,-(a5) ; Push count jmp next shs2 dc.l shg2,shs1,shs1,0 dc.b '#s ' ; Convert all remaining digits shs1 jsr sh99 ; Do a digit tst.l (a5) ; See if done bne shs1 jmp next hold2 dc.l shs2,hold1,hold1,0 dc.b 'hold ' ; Put a char into the string hold1 move.l lsh_pos,a0 move.l (a5)+,d0 move.b d0,-(a0) move.l a0,lsh_pos hold3 jmp next sign2 dc.l hold2,sign1,sign1,0 dc.b 'sign ' ; Add a '-' if sign negative sign1 tst.l (a5)+ bge hold3 move.l #45,-(a5) bra hold1 ; ; ."--generate code to print a string at run-time ; dotq2 dc.l sign2,dotq1,dotq1,Priority dc.b '." ' dotq1 move.l d7,a0 move.l #pdotq,(a0)+ ; Compile (.") move.l d6,a1 ; Get line pointer addq.l #1,a1 ; advance past current word delimiter dotq3 move.b (a1)+,d0 ; Get next char beq dotq5 ; read a new buffer if we run out cmp.b #34,d0 ; End when we find the closing " beq dotq4 move.b d0,(a0)+ ; Add the character bra dotq3 dotq5 move.l a0,-(sp) jsr getline ; Get new buffer move.l (sp)+,a0 move.l d6,a1 bra dotq3 dotq4 clr.b (a0)+ ; Terminating NULL move.l a1,d6 ; Update line pointer move.l a0,d7 addq.l #3,d7 ; Longword-align DP and.l #0xFFFFFFFC,d7 jmp next ; ; (.")--run-time word to print a string ; pdotq2 dc.l dotq2 pdotq dc.l pdotq1,pdotq1,0 dc.b '(.") ' pdotq1 move.l a6,a0 jsr prstr pdotq3 tst.b (a6)+ ; Skip past text bne pdotq3 move.l a6,d0 addq.l #3,d0 ; Align IP and.l #0xFFFFFFFC,d0 move.l d0,a6 jmp next ; ; .(--print a message to the terminal from the input stream ; dotp2 dc.l pdotq2,dotp1,dotp1,Priority dc.b '.( ' dotp1 move.l d6,a1 ; Get line pointer addq.l #1,a1 ; advance past current word delimiter move.l #pad1,a0 ; Build message into PAD dotp3 move.b (a1)+,d0 ; Get next char beq dotp5 ; read a new buffer if we run out cmp.b #41,d0 ; End when we find the closing " beq dotp4 move.b d0,(a0)+ ; Add the character bra dotp3 dotp5 jsr getline ; Get new buffer move.l d6,a1 bra dotp3 dotp4 clr.b (a0)+ ; Terminating NULL move.l a1,d6 ; Update line pointer move.l #pad1,a0 ; Print the message jsr prstr jmp next ; ; cr--print newline ; cr_msg dc.b 10,0 cr2 dc.l dotp2,cr1,cr1,0 dc.b 'cr ' cr1 move.l #cr_msg,a0 jsr prstr jmp next ; ; emit--print out a character ; emit_buf ds.b 1 dc.b 0,0,0 ; Terminating NULL, 2 wasted emit2 dc.l cr2,emit1,emit1,0 dc.b 'emit ' emit1 move.l (a5)+,d0 move.b d0,emit_buf move.l #emit_buf,a0 jsr prstr jmp next ; ; type--print out a string given a count & a pointer ; type2 dc.l emit2,type1,type1,0 dc.b 'type ' type1 move.l (a5)+,d0 ; Count move.l (a5)+,a0 ; Addr move.l #pad1,a1 ; Where to buffer to type3 tst.l d0 ; Out of chars? beq type4 move.b (a0)+,(a1)+ ; Store a char subq.l #1,d0 ; Decrement count bra type3 type4 clr.b (a1) ; Terminating NULL move.l #pad1,a0 jsr prstr jmp next ; ; space--emit a space ; space2 dc.l type2,space1,space1,0 dc.b 'space ' space1 move.l #32,-(a5) bra emit1 ; ; spaces--emit N spaces ; spac_buf ; A printable space dc.b 32,0,0,0 spaces2 dc.l space2,spaces1,spaces1,0 dc.b 'spaces ' spaces1 tst.l (a5) ; Enough spaces? beq spaces3 move.l #spac_buf,a0 jsr prstr sub.l #1,(a5) ; Decrement count bra spaces1 spaces3 addq.l #4,a5 ; Pop count jmp next ; ; key--get a key from STDIN. Normally, this will block until a whole ; line is entered. However, if the TTY is put into RAW mode, ; this will respond on a key-by-key basis. ; keybuf ds.l 1 ; Holds the keystroke key2 dc.l spaces2,key1,key1,0 dc.b 'key ' key1 move.l #1,-(a7) ; Build READ syscall parameters--1 byte move.l #keybuf,-(a7) ; buffer address clr.l -(a7) ; 0--STDIN clr.l -(a7) ; dummy moveq #3,d0 ; UNIX READ syscall trap #0 add.l #16,a7 ; Remove the parameters from stack move.b keybuf,d0 ; Push byte ext.w d0 ext.l d0 move.l d0,-(a5) jmp next ; ; expect--read a number of chars from the terminal ; expect2 dc.l key2,expect1,expect1,0 dc.b 'expect ' expect1 move.l (a5)+,-(a7) ; UNIX syscall: N bytes move.l (a5)+,-(a7) ; to buffer clr.l -(a7) ; STDIN clr.l -(a7) ; dummy moveq #3,d0 ; UNIX READ syscall trap #0 move.l d0,span ; Store # bytes read add.l #16,a7 ; Remove the parameters from stack jmp next span2 dc.l expect2,getpfa,span,0 dc.b 'span ' span ds.l 1 ; ; abort--jump to abort ; abort2 dc.l span2 do_abort dc.l abort,abort,0 dc.b 'abort ' ; ; abort"--if top is true, print a message and abort ; qabort2 dc.l abort2,qabort1,qabort1,Priority dc.b 'abort" ' qabort1 move.l d7,a0 move.l #zbranch,(a0)+ ; Skip the whole shebang on false move.l a0,a2 ; Mark where to backpatch clr.l (a0)+ ; Leave room for the branch address move.l #pdotq,(a0)+ ; Compile (.") move.l d6,a1 ; Get line pointer addq.l #1,a1 ; advance past current word delimiter qabort3 move.b (a1)+,d0 ; Get next char beq qabort5 ; read a new buffer if we run out cmp.b #34,d0 ; End when we find the closing " beq qabort4 move.b d0,(a0)+ ; Add the character bra qabort3 qabort5 jsr getline ; Get new buffer move.l d6,a1 bra qabort3 qabort4 clr.b (a0)+ ; Terminating NULL move.l a1,d6 ; Update line pointer move.l a0,d7 addq.l #3,d7 ; Longword-align DP and.l #0xFFFFFFFC,d7 move.l d7,a0 move.l #do_abort,(a0)+ ; Put in ABORT move.l a0,d7 move.l d7,(a2) ; Backpatch false case jmp next ; ; quit--leave parameter stack alone, but return to INTERP ; quit2 dc.l qabort2,quit1,quit1,0 dc.b 'quit ' quit1 move.l #rstack+stacksize,a4 ; Clear return stack move.l #interp,a6 jmp next ; ; here--push address of next free location ; here2 dc.l quit2,here1,here1,0 dc.b 'here ' here1 move.l d7,-(a5); jmp next ; ; tib--address of text input buffer ; tib2 dc.l here2,tib1,tib1,0 dc.b 'tib ' tib1 move.l iunit,-(a5) jmp next ; ; >body--turn pointer to CFA field into pointer to parameter field ; gbod2 dc.l tib2,gbod1,gbod1,0 dc.b '>body ' gbod1 move.l (a5),a0 move.l 4(a0),(a5) jmp next ; ; (--start a forth comment ) ; paren2 dc.l gbod2,paren1,paren1,Priority dc.b '( ' ; ) paren1 move.l d6,a0 paren4 move.b (a0)+,d0 ; Get next char cmp.b #41,d0 ; End on closing paren beq paren3 tst.b d0 ; Get new buffer on end of current bne paren4 jsr getline bra paren1 paren3 move.l a0,d6 ; Restore line pointer jmp next ; ; allot--allocate N bytes off end of dictionary ; allot2 dc.l paren2,allot1,allot1,0 dc.b 'allot ' allot1 move.l (a5)+,d0 add.l d0,d7 jmp next ; ; does>--terminate execution of word which calls this, but also set it up ; so that the LATEST word has its PFA directed to after this word. ; : definer create ...1... does> ...2... ; ; Will be used as: definer <word> ; <word> will be added to the dictionary, and ...1... may do any ; actions it wishes. When <word> is later executed, it will run ; the code ...2... ; does2 dc.l allot2,does1,does1,Priority dc.b 'does> ' does1 move.l d7,a0 move.l #pdoes,(a0)+ ; Compile in (does) move.l a0,d7 jmp next pdoes2 dc.l does2 pdoes dc.l pdoes1,pdoes1,0 dc.b '(does) ' pdoes1 move.l late1,a0 ; Get LFA of latest definition move.l #hilev,4(a0) ; Make this execute as a high-level def move.l a6,8(a0) ; Fill in PFA with rest of this word's body move.l (a4)+,a6 ; Return from this word jmp next ; ; immediate--set the Priority bit of the latest definition ; immed2 dc.l pdoes2,immed1,immed1,0 dc.b 'immediat' immed1 move.l late1,a0 or.l #Priority,12(a0) ; Set Priority in SFA word jmp next ; ; [compile], compile--immediate & non-immediate versions of compile ; bcomp2 dc.l immed2,bcomp1,bcomp1,Priority dc.b '[compile' bcomp1 jsr getw1 ; Fetch next word from stream jsr look99 ; See if it can be found tst.l (a5)+ ; Error if it couldn't beq bcomp3 addq.l #4,a5 ; Drop the priority field move.l d7,a0 ; Compile in CFA move.l (a5)+,(a0)+ move.l a0,d7 jmp next bcomp3 jmp notf1 ; Not found: complain comp2 dc.l bcomp2,bcomp1,bcomp1,0 dc.b 'compile ' ; ; literal--compile a literal ; lit2 dc.l comp2,lit1,lit1,Priority dc.b 'literal ' lit1 move.l d7,a0 move.l #plit,(a0)+ move.l (a5)+,(a0)+ move.l a0,d7 jmp next ; ; [, ]--turn compilation off & on, respectively ; compon2 dc.l lit2,compon1,compon1,0 dc.b '] ' compon1 move.l #-1,state1 jmp next compof2 dc.l compon2,compof1,compof1,Priority dc.b '[ ' compof1 clr.l state1 jmp next ; ; word--get a word from the input stream, put in string ; word_buf ds.b 84 gword2 dc.l compof2,gword1,gword1,0 dc.b 'word ' gword1 move.l (a5)+,d0 ; Delimiter char move.l #word_buf+1,a0 ; Where to put the chars move.l d6,a1 ; Input line buffer clr.l d2 ; Count # chars received gword3 move.b (a1)+,d1 ; Get next char beq gword4 ; get new bufferfull if current empty cmp.b d0,d1 ; Found delimiter? beq gword5 move.b d1,(a0)+ ; Store char addq.l #1,d2 ; Increment count bra gword3 gword4 movem.l d0/a0,-(a7) ; Save d0 and a0 jsr getline ; Get next line movem.l (a7)+,d0/a0 move.l d6,a1 bra gword3 gword5 clr.b (a0) ; Add NULL termination move.b d2,word_buf ; Store count in first byte move.l a1,d6 ; Update line pointer move.l #word_buf,-(a5) ; Return pointer to it jmp next ; ; >in--give a byte offset into current buffer ; to_in2 dc.l gword2,to_in1,to_in1,0 dc.b '>in ' to_in1 move.l d6,d0 sub.l iunit,d0 move.l d0,-(a5) jmp next ; ; #tib--length of current input buffer ; ntib2 dc.l to_in2,ntib1,ntib1,0 dc.b '#tib ' ntib1 move.l iunit,a0 ; Ptr into buf clr.l d1 ; Counter of # chars ntib3 tst.b (a0)+ ; Check next byte beq ntib4 addq.l #1,d1 bra ntib3 ntib4 move.l d1,-(a5) ; Push count jmp next ; ; create--create a dictionary entry ; creat2 dc.l ntib2,creat1,creat1,0 dc.b 'create ' creat1 jsr make_head ; Build the header move.l #getpfa,4(a0) ; Set it up to be variable/constant jmp next ; ; '--push address of CFA ; tick2 dc.l creat2,tick1,tick1,0 dc.b 39,' ' tick1 jsr getw1 ; Get word jsr look99 ; Look up word tst.l (a5)+ ; Abort on error beq tick3 addq.l #4,a5 ; Drop priority flag jmp next tick3 jmp notf1 ; ; [']--for compiling in a compilation address as a literal ; btick2 dc.l tick2,btick1,btick1,Priority dc.b '[',39,'] ' btick1 jsr getw1 ; Get word jsr look99 ; Look up word tst.l (a5)+ ; Abort on error beq tick3 addq.l #4,a5 ; Drop priority flag move.l d7,a0 ; Compile in (lit) move.l #plit,(a0)+ move.l (a5)+,(a0)+ ; <compilation addr> move.l a0,d7 jmp next ; ; find--find a string in the dictionary ; find2 dc.l btick2,find1,find1,0 dc.b 'find ' find1 move.l latest+4,a0 ; Get pointer to latest definition move.l (a5),a1 ; Get search string move.l (a1),d3 move.l 4(a1),d4 jsr look5 ; Go find the string tst.l (a5) ; See if it was found beq find3 ; wasn't, can just return addq.l #4,a5 ; Was, pop boolean flag tst.l (a5)+ ; Change priority flag bne find4 move.l (a5),4(a5) ; Move comp addr over string addr move.l #-1,(a5) ; not priority, flag -1 bra find3 find4 move.l (a5),4(a5) ; Move comp addr over string addr move.l #1,(a5) ; was priority, flag 1 find3 jmp next ; ; forget--find a word in the dictionary, and remove it ; forg2 dc.l find2,forg1,forg1,0 dc.b 'forget ' forg1 jsr getw1 ; Get the name to forget jsr look99 ; Find it in the dictionary tst.l (a5)+ ; Found it? beq forg3 ; nope... addq.l #4,a5 ; Drop priority flag move.l (a5)+,a0 ; Put CFA into A0 subq.l #4,a0 ; Put A0 back to LFA move.l (a0),late1 ; Point LATEST to previous word move.l a0,d7 ; Free memory back to here jmp next forg3 jmp notf1 ; Forget WHO? ; ; input <file>--redirect input from a file ; input2 dc.l forg2,input1,input1,0 dc.b 'input ' input1 move.l iunit,a0 ; Room for more nesting? add.l #Inbufsize,a0 cmp.l #End_inbufs,a0 beq input4 move.l a0,-(a7) ; Save address of new buffer move.l d6,a0 ; Read in until end of word jsr skipwhite lea pad1,a1 ; Where to build into input10 jsr iswhite ; While not at end of word bne.s input11 tst.b (a0) ; At end of input buffer? bne.s input12 move.l a1,-(a7) ; Get new buffer-full jsr getline move.l (a7)+,a1 move.l d6,a0 bra.s input10 input11 move.b (a0)+,(a1)+ ; Store next char bra.s input10 input12 clr.b (a1) ; Trailing NULL move.l a0,d6 ; update input pointer clr.l -(a7) ; Mode 0=read pea pad1 ; Pointer to file name clr.l -(a7) ; dummy space moveq #5,d0 ; Open request trap #0 bcs input3 add.l #12,a7 ; Get rid of parameters move.l (a7)+,a0 ; Get new buffer addr again move.l iunit,a1 ; Get previous move.l a1,InbufPrev(a0) ; Save move.l d6,InbufIdx(a1) ; Save index into old buffer move.l a0,InbufIdx(a0) ; Clear the buffer move.b d0,InUnit(a0) ; Save UNIX FD to use clr.b (a0) move.l a0,d6 move.l a0,iunit ; Update current input unit jmp next input3 lea input_msg,a0 input5 jsr prstr jmp abort input4 lea input_msg2,a0 bra.s input5 input_msg asciz 'Could not open file for input' input_msg2 asciz 'Too many files nested' even ; ; exit--return from the current high-level word ; exit2 dc.l input2,pop1,pop1,0 dc.b 'exit ' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Insert new definitions above here ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; latest--pointer to most current LFA defined ; late2 dc.l exit2 latest dc.l getpfa,late1,0 dc.b 'latest ' late1 dc.l late2 ; ; The user dictionary space ; comm udict,umem*1024 ; User dictionary space ; ; The End! ; Funky!Stuff! cat - << \Funky!Stuff! > primes.fth : isprime ( n -- b | Return whether 'n' is prime ) ( dup 2 mod 0= if drop 0 exit endif ) -1 swap dup 2/ 1+ 3 do dup i mod 0= if swap drop 0 swap leave endif 2 +loop drop ; : primes 2001 5 do i isprime if i . cr endif 2 +loop ; Funky!Stuff!