gigatron/rom/Contrib/kervinck/Forth.gcl
2025-01-28 19:17:01 +03:00

827 lines
28 KiB
Plaintext

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 start of line, indent 2 pixels}
$fe+ i= {i=$100: start of the video table}
peek >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.
<i++ <i++ {Advance to next entry in video table}
<i, 224^ if<>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}
<Pos, 155- if>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}
<j++ <\sysArgs4++ {Advance to next byte}
i 1- if>0loop] {Looping}
]
Next!
{-----------------------------------------------------------------------+
| RAM page 5 |
+-----------------------------------------------------------------------}
*=$500
{-----------------------------------------------------------------------+
| GoBack ( -- r ) Step character back on line if possible |
+-----------------------------------------------------------------------}
{ Returns 0 on success }
_GoBack=*
<Pos, 6- [if>=0 <Pos. 0]
2-- %0= Next!
##__EMIT {Link}
{-----------------------------------------------------------------------+
| KEY ( -- c ) Wait for key stroke |
+-----------------------------------------------------------------------}
__KEY=*
#@_KEY `KEY {Name}
_KEY=*
\serialRaw, W= {Reference}
2-- {Make space on stack}
[do
\serialRaw, %0= W^ {Probe and compare with reference}
if=0loop {Loop if not changed}
%0 W= 128& {Check high bit}
if<>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=*
{-----------------------------------------------------------------------+
| |
+-----------------------------------------------------------------------}