gcl0x {-----------------------------------------------------------------------+ | | | Forth74 -- Inner interpreter for a vCPU-based Forth | | | | !!! WORK IN PROGRESS !!! | | | +-----------------------------------------------------------------------} { References: https://colorforth.github.io/HOPL.html Forth - The Early Years Chuck Moore http://forth.org/OffeteStore/1010_SystemsGuideToFigForth.pdf Systems Guide to figForth C.H.Ting, Ph.D http://www.bradrodriguez.com/papers/moving1.htm Moving Forth Brad Rodriguez https://www.sifflez.org/lectures/ASE/C3.pdf ASE: Writing a forth interpreter from scratch Pablo de Oliveira https://users.ece.cmu.edu/~koopman/stack_computers/appb.html A Glossery of Forth Primitives Philip Koopman Variables: IP Instruction pointer RSP Return stack W i j Work register(s) DoColon Fall into threaded code, save IP on stack Next Fetch and dispatch next threaded instruction Pos Cursor position in video memory (initialized by CR) Other: \vSP Data stack } {-----------------------------------------------------------------------+ | RAM page 2 | +-----------------------------------------------------------------------} *=$200 {-----------------------------------------------------------------------+ | | | Inner or address interpreter | | | +-----------------------------------------------------------------------} _COLD=* { Next } [def IP 2+ IP= {Advance IP} deek call {Fetch address and dispatch} ] Next= { DoColon } [def RSP 2- RSP= IP RSP: {Push IP on return stack} \vLR; IP= {vLR points to address after CALL} deek call {Fetch and execute first word} ] DoColon= { DoVar } [def push {Same as "\vLR; 2-- %0="} Next! ] DoVar= 0 RSP= {Clear return stack: top of memory} {-----------------------------------------------------------------------+ | ABORT | +-----------------------------------------------------------------------} _ABORT=* 0 \vSP. {Clear data stack: top of page 0} DoColon! {The rest is hand compiled Forth} ##_HELLO ##_Lit ##_FreeMem {Select vocabulary} ##_CP ##_Doke ##_WORDS ##_QUIT {Doesn't return} {-----------------------------------------------------------------------+ | QUIT | +-----------------------------------------------------------------------} { QUIT embeds the outer or text interpreter, a.k.a. INTERPRET } _QUIT=* DoColon! ##_Zero ##_RspStore {Clear return stack: top of memory} _quit1=* ##_CR ##_Byte `O ##_EMIT {'Ok' prompt} ##_Byte `k ##_EMIT ##_Byte `> ##_EMIT ##_Byte #32 ##_EMIT ##_QUERY {Let user type something} ##_DUMMY {Dummy interpreter just echoes} ##_Jump ##_quit1 {-----------------------------------------------------------------------+ | Execution tokens not in dictionary | +-----------------------------------------------------------------------} _Exit=* RSP deek IP= {Address from return stack} RSP 2+ RSP= {Pop return stack} Next! _Lit=* IP 2+ deek {Fetch immediate value} 2-- %0= {Push on stack} IP 4+ IP= {Skip operand as well} deek call {Same as Next} _Byte=* IP 2+ peek {Fetch immediate byte} 2-- %0= {Push on stack} IP 3+ IP= {Skip operand as well} deek call {Same as Next} _RspStore=* %0 2++ RSP= {Set Return Stack Pointer} Next! _Escape=* {Eacape into vCPU} IP 2+ \vLR: RSP deek IP= {Same as _Exit} RSP 2+ RSP= {Pop return stack} [ret] {Note: Brackets suppress the Loader patch} _Cursor=* {Emit character and move back position} DoColon! ##_EMIT ##_GoBack ##_DROP ##_Exit _Jump=* IP 2+ deek {Target address in operand} IP= deek call {Same as Next} _JumpEq=* %0 2++ {Pop condition} [if=0 IP 2+ deek {Address in operand} else IP 4+] {Address after operand} IP= deek call {Same as Next} _JumpNe=* %0 2++ {Pop condition} [if<>0 IP 2+ deek {Address in operand} else IP 4+] {Address after operand} IP= deek call {Same as Next} {-----------------------------------------------------------------------+ | RAM page 3 | +-----------------------------------------------------------------------} *=$300 {-----------------------------------------------------------------------+ | | | Dictionary | | | +-----------------------------------------------------------------------} { From this point on, everything is part of the dictionary | | FreeMem --> | | High memory +-----------------------+ | Pointer to name 2 | ------. +-----------------------+ | | vCPU code and data 2 | | | | | | | | ~ ~ | Execution token --> | | | +-----------------------+ | | Name 2 | | ~ ~ | | Lenght of name 2 | <-----' +-----------------------+ | Pointer to name 1 | ------. +-----------------------+ | | | | | Etc. | | ~ ~ | Execution token --> | | | +-----------------------+ | | Name 1 | | ~ ~ | | Length of name 1 | <-----' +-----------------------+ | Null | +-----------------------+ | | | Primitives | Low memory | | Naming convention for word labels: _ALLCAPS Standard Forth words listed as `ALLCAPS' in dictionary _MixedCase 1. Internal word not in the dictionary, or 2. Forth word with special chars (eg. _Add1 for 1+), or 3. Gigatron-specific words in dictionary _someword1..n Internal labels for SOMEWORD/SomeWord implementation __SomeWord Internal label for linking dictionary items } ##0 {Null} {-----------------------------------------------------------------------+ | | | Constants words | | | +-----------------------------------------------------------------------} __Zero=* #@_Zero `0 {Name} _Zero=* 0 2-- %0= Next! ##__Zero {Link} __One=* #@_One `1 {Name} _One=* 1 2-- %0= Next! ##__One {Link} {-----------------------------------------------------------------------+ | | | Memory words | | | +-----------------------------------------------------------------------} __Deek=* #@_Deek `@ {Name} _Deek=* %0 deek %0= Next! ##__Deek {Link} __Doke=* #@_Doke `! {Name} _Doke=* %0 W= %2 W: 4++ Next! ##__Doke {Link} __Peek=* #@_Peek `C@ {Name} _Peek=* %0 peek %0= Next! ##__Peek {Link} __Poke=* #@_Poke `C! {Name} _Poke=* %0 W= %2 W. 4++ Next! ##__Poke {Link} {-----------------------------------------------------------------------+ | | | Operator words | | | +-----------------------------------------------------------------------} __Add=* #@_Add `+ {Name} _Add=* %0 2++ W= %0 W+ %0= Next! ##__Add {Link} __Sub=* #@_Sub `- {Name} _Sub=* %0 2++ W= %0 W- %0= Next! ##__Sub {Link} __AND=* #@_AND `AND {Name} _AND=* %0 2++ W= %0 W& %0= Next! ##__AND {Link} __OR=* #@_OR `OR {Name} _OR=* %0 2++ W= %0 W| %0= Next! ##__OR {Link} {-----------------------------------------------------------------------+ | | | Data stack words | | | +-----------------------------------------------------------------------} __DROP=* #@_DROP `DROP {Name} _DROP=* 2++ Next! {Drop word from stack} ##__DROP {Link} __DUP=* #@_DUP `DUP {Name} _DUP=* %0 2-- %0= Next! {Duplication top of stack} ##__DUP {Link} __OVER=* #@_OVER `OVER {Name} _OVER=* %2 2-- %0= Next! ##__OVER {Link} __SWAP=* #@_SWAP `SWAP {Name} _SWAP=* %2 W= %0 %2= W %0= Next! ##__SWAP {Link} __ROT=* #@_ROT `ROT {Name} _ROT=* %4 W= %2 %4= %0 %2= W %0= Next! {-----------------------------------------------------------------------+ | RAM page 4 | +-----------------------------------------------------------------------} *=$400 ##__ROT {Link} {-----------------------------------------------------------------------+ | | | Video terminal words | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ | CR ( -- ) Newline | +-----------------------------------------------------------------------} __CR=* #@_CR `CR {Name} _CR=* { Find the top text row } 2 Pos. {Go to what is now still the top row} { Clear the text row } $10 \sysArgs0. \sysArgs1. {Both colors DarkBlue} Pos 2- \sysArgs4: {Set drawing position for clearing} \SYS_VDrawBits_134 _sysFn= {Prepare SYS calls} [do 134!! {SYS call to draw 8 pixels vertically} <\sysArgs4++ {Step 1 pixel right} \sysArgs4, 160^ if<>0loop] {Loop until end of pixel line} { Scroll everything up by modifying the video indirection table. [This may be a cool effect to watch in slow motion?] } [do i 16+ j= {j looks 8 entries ahead of i} i, \sysArgs7. {Swap scanlines} j, i. \sysArgs7, j. 0loop] {Until all done} { The cleared text row has now bubbled down to the bottom } Next! ##__CR {Link} {-----------------------------------------------------------------------+ | EMIT ( c -- ) Emit ASCII to video terminal | +-----------------------------------------------------------------------} { XXX Doesn't do backspace } { XXX Doesn't do bell } __EMIT=* #@_EMIT `EMIT {Name} _EMIT=* %0 10^ [if<>0 {Test for explicit CR} 0 0] {Or implicit line wrap} [if=0 DoColon! ##_CR {Move to next line} ##_Escape] {Continue in vCPU} %0 2++ {Pop parameter} [128- if<0 96+ if>=0 {Skip non-printables to prevent crashing} { Draw ASCII character (32..127) on screen in 5x8 pixels and advance cursor position } 50- {Map ASCII code to offset in font table} [if<0 50+ i= \font32up {ASCII 32..81} else i= \font82up] {ASCII 82..127} j= i 2<< i+ {Multiply char code by 5} j+ j= {Add to page address to reach bitmap data} \SYS_VDrawBits_134 _sysFn= {Prepare SYS calls} $c10 \sysArgs0: {LightGreen on DarkBlue} Pos \sysArgs4: {Screen position for character} 6+ Pos= {Advance by 6 pixels} 5 [do i= {Draw character as 5 vertical bit slices} j 0? \sysArgs2. {Get bit slice as byte from ROM table} 134!! {SYS call to draw 8 pixels vertically} 0loop] {Looping} ] Next! {-----------------------------------------------------------------------+ | RAM page 5 | +-----------------------------------------------------------------------} *=$500 {-----------------------------------------------------------------------+ | GoBack ( -- r ) Step character back on line if possible | +-----------------------------------------------------------------------} { Returns 0 on success } _GoBack=* =0 0loop] {Loop if no ASCII or keyboard idle} Next! ##__KEY {Link} {-----------------------------------------------------------------------+ | 0< ( n -- f ) True if top number negative | +-----------------------------------------------------------------------} __IsNeg=* #@_IsNeg `0< {Name} _IsNeg=* %1 128& [if<>0 129-] %0= {Set to -1 or 0} Next! ##__IsNeg {-----------------------------------------------------------------------+ | 1+ ( n -- n+1 ) Add 1 i | +-----------------------------------------------------------------------} __Add1=* #@_Add1 `1+ {Name} _Add1=* %0 1+ %0= Next! ##__Add1 {Link} {-----------------------------------------------------------------------+ | 1- ( n -- n-1 ) Subtract 1 | +-----------------------------------------------------------------------} __Sub1=* #@_Sub1 `1- {Name} _Sub1=* %0 1- %0= Next! ##__Sub1 {Link} {-----------------------------------------------------------------------+ | MINUS ( n -- -n ) Change sign | +-----------------------------------------------------------------------} { Later Forths call this `NEGATE' } __MINUS=* #@_MINUS `MINUS {Name} _MINUS=* %0 W= 0 W- %0= Next! ##__MINUS {Link} {-----------------------------------------------------------------------+ | | | Return Stack words | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ | >R ( n -- ) Move top item to return stack | +-----------------------------------------------------------------------} __ToR=* #@_ToR `>R {Name} _ToR=* RSP 2- RSP= %0 2++ RSP: Next! {Code} ##__ToR {Link} {-----------------------------------------------------------------------+ | R> ( -- n ) Retrieve item from return stack | +-----------------------------------------------------------------------} __FromR=* #@_FromR `R> {Name} _FromR=* RSP; 2-- %0= RSP 2+ RSP= Next!{Code} ##__FromR {Link} {-----------------------------------------------------------------------+ | R ( -- n ) Copy top of return stack into stack | +-----------------------------------------------------------------------} __R=* #@_R `R {Name} _R=* RSP; 2-- %0= Next! {Code} ##__R {Link} {-----------------------------------------------------------------------+ | SPACE ( -- ) Print one space | +-----------------------------------------------------------------------} __SPACE=* #@_SPACE `SPACE {Name} _SPACE=* DoColon! ##_Byte #32 ##_EMIT ##_Exit ##__SPACE {Link} {-----------------------------------------------------------------------+ | . ( n -- ) Print number | +-----------------------------------------------------------------------} __Dot=* #@_Dot `. {Name} _Dot=* DoColon! ##_SPACE ##_DUP ##_IsNeg {Test sign} ##_JumpEq ##_dot1 ##_Byte `- ##_EMIT {Print "-"} ##_MINUS _dot1=* ##_Zero {Suppress leading zeroes} ##_Lit ##10000 ##_Digit ##_Lit ##1000 ##_Digit ##_Byte #100 ##_Digit ##_Byte #10 ##_Digit ##_Byte #1 ##_Digit ##_DROP ##_DROP ##_Exit {-----------------------------------------------------------------------+ | RAM page 6 | +-----------------------------------------------------------------------} *=$600 { Helper for _Dot } _Digit=* { n z r -- n-d*r z+d } DoColon! ##_ToR { n 0 | r } ##_SWAP { 0 n | r } _digit1=* ##_R { 0 n r | r } ##_Sub { 0 m | r } ##_DUP { 0 m m | r } ##_IsNeg { 0 m sign | r } ##_JumpNe ##_digit2 { 0 m | r } ##_SWAP ##_Add1 ##_SWAP { 1 m | r } ##_Jump ##_digit1 _digit2=* ##_FromR { 1 m r } ##_Add { 1 m } ##_SWAP { m 1 } ##_DUP ##_JumpEq ##_digit3 ##_Byte #$30 ##_OR { m '1' } _digit3=* ##_EMIT { m } ##_Byte #$30 { m 0 } ##_Exit ##__Dot {Link} {-----------------------------------------------------------------------+ | HELLO ( -- ) Send text to terminal | +-----------------------------------------------------------------------} __HELLO=* #@_HELLO `HELLO {Name} _HELLO=* DoColon! ##_CR {Activate terminal} ##_Byte `F ##_EMIT ##_Byte `o ##_EMIT ##_Byte `r ##_EMIT ##_Byte `t ##_EMIT ##_Byte `h ##_EMIT ##_Byte `7 ##_EMIT ##_Byte `4 ##_EMIT ##_CR ##_Exit ##__HELLO {Link} {-----------------------------------------------------------------------+ | TYPE ( addr u -- ) Type u characters starting at address | +-----------------------------------------------------------------------} __TYPE=* #@_TYPE `TYPE {Name} _TYPE=* DoColon! ##_Jump ##_type2 _type1=* ##_SWAP ##_DUP ##_Peek ##_EMIT ##_Add1 ##_SWAP ##_Sub1 _type2=* ##_DUP ##_JumpNe ##_type1 ##_DROP ##_DROP ##_Exit ##__TYPE {Link} {-----------------------------------------------------------------------+ | CP ( -- addr ) Variable to begin of free memory | +-----------------------------------------------------------------------} __CP=* #@_CP `CP {Name} _CP=* DoVar! ##0 ##__CP {Link} {-----------------------------------------------------------------------+ | HERE ( -- addr ) Return address above dictionary | +-----------------------------------------------------------------------} __HERE=* #@_HERE `HERE {Name} _HERE=* DoColon! ##_CP ##_Deek ##_Exit ##__HERE {Link} {-----------------------------------------------------------------------+ | | | Text interpreter words | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ | WORDS ( -- ) Print names in dictionary | +-----------------------------------------------------------------------} __WORDS=* #@_WORDS `WORDS {Name} _WORDS=* DoColon! ##_HERE ##_Jump ##_words2 _words1=* ##_SPACE ##_DUP ##_Add1 ##_OVER ##_Peek ##_TYPE _words2=* ##_Byte #2 ##_Sub ##_Deek ##_DUP ##_JumpNe ##_words1 ##_DROP ##_CR ##_Exit ##__WORDS {Link} {-----------------------------------------------------------------------+ | TIB (variable) Address of terminal input buffer | +-----------------------------------------------------------------------} __TIB=* #@_TIB `TIB {Name} _TIB=* DoVar! ##\userVars2 {Default in zero page} ##__TIB {Link} {-----------------------------------------------------------------------+ | IN (variable) Input character counter | +-----------------------------------------------------------------------} __IN=* #@_IN `IN {Name} _IN=* DoVar! ##0 {-----------------------------------------------------------------------+ | RAM page 8 | +-----------------------------------------------------------------------} *=$8a0 ##__IN {Link} {-----------------------------------------------------------------------+ | EXPECT ( addr n -- ) Let user edit line in buffer of size n | +-----------------------------------------------------------------------} { Line edditing with delete capability within the same terminal line. Delete at beginning of a non-first line restarts the line entry completely. Prevents buffer overflow and replaces final newline with a zero byte. } __EXPECT=* #@_EXPECT `EXPECT {Name} _EXPECT=* DoColon! ##_OVER ##_Add ##_Sub1 {End of buffer} ##_ToR {Place it on return stack} ##_DUP {Active buffer pointer on top of stack} _expect1=* ##_Byte #127 ##_Cursor {Show cursor} ##_KEY {Wait for key} ##_Byte #32 ##_Cursor {Hide cursor} ##_DUP ##_Byte #127 ##_Sub {Test for DEL} ##_JumpNe ##_expect2 ##_DROP ##_OVER ##_OVER ##_Sub {Ignore DEL at start of buffer} ##_JumpEq ##_expect1 ##_Sub1 ##_GoBack ##_JumpEq ##_expect1 {Continue if DEL ok} ##_Byte `? ##_EMIT ##_CR {Display error indicator} ##_DROP ##_DUP {Clear buffer} ##_Jump ##_expect1 {-----------------------------------------------------------------------+ | RAM page 9 | +-----------------------------------------------------------------------} *=$9a0 _expect2=* ##_DUP ##_Byte #10 ##_Sub {Test for '\n'} ##_JumpEq ##_expect3 ##_OVER ##_R ##_Sub {Test for buffer space} ##_JumpNe ##_expect4 ##_DROP ##_Jump ##_expect1 {Ignore if buffer full} _expect4=* ##_DUP ##_EMIT {Echo character if all good} ##_OVER ##_Poke {And store in buffer} ##_Add1 {Advance buffer pointer} ##_Jump ##_expect1 _expect3=* ##_EMIT {Emit and drop '\n'} ##_Zero ##_SWAP ##_Poke {Replace '\n' with '\0' terminator} ##_DROP ##_FromR ##_DROP ##_Exit ##__EXPECT {Link} {-----------------------------------------------------------------------+ | QUERY ( -- ) Input 80 chars of text from video terminal | +-----------------------------------------------------------------------} __QUERY=* #@_QUERY `QUERY {Name} _QUERY=* DoColon! ##_TIB ##_Deek {Address of input buffer} ##_Byte #81 ##_EXPECT {Get up to 80 characters plus '\0'} ##_Zero ##_IN ##_Doke {IN=0} ##_Exit {-----------------------------------------------------------------------+ | RAM page 10 | +-----------------------------------------------------------------------} *=$aa0 ##__QUERY {Link} {-----------------------------------------------------------------------+ | DUMMY ( -- ) Dummy interpreter | +-----------------------------------------------------------------------} { Simply type back the input buffer to screen } __DUMMY=* #@_DUMMY `DUMMY {Name} _DUMMY=* DoColon! ##_TIB ##_Deek _dummy1=* ##_DUP ##_Peek ##_JumpEq ##_dummy2 ##_DUP ##_Peek ##_EMIT ##_Add1 ##_Jump ##_dummy1 _dummy2=* ##_DROP ##_Exit ##__DUMMY {Link} _FreeMem=* {-----------------------------------------------------------------------+ | | +-----------------------------------------------------------------------}