Compare commits

...

6 Commits

Author SHA1 Message Date
Virgil Dupras
243c70a4b7 forth: transform (find) into FIND which is an indirect call
You'll see where I'm going with this...
2020-04-02 15:12:11 -04:00
Virgil Dupras
b162ef84f5 forth: fix broken RAM+ 2020-04-02 13:42:49 -04:00
Virgil Dupras
e284081c79 forth: fix readln overflow handling
It was badly handled.
2020-04-02 13:40:22 -04:00
Virgil Dupras
941791d609 forth: remove RAMSTART and RS_ADDR from stable ABI 2020-04-02 11:39:40 -04:00
Virgil Dupras
2481ed4b66 forth: remove dummy.fs
All "stop pings" are now explicitly made at appropriate places. This
fixes a problem I've introduced in the last commit where RAM+ was
unexpectedly part of a stable ABI because of "HERE" usage in dummy.fs.

This system will, anyways, soon change because boot.bin and z80c.bin
will be built during the same process (but there's a bit of retooling
to do before I get there).
2020-04-02 11:29:23 -04:00
Virgil Dupras
68a7be3707 forth: Remove RAM offsets from stable ABI
Doing this was a bit stupid. These offsets are constants. Moreover,
having them in stable ABI had us construct the boot binary from the
stable ABI of the host, making it very difficult to change RAMSTART
for a new system.
2020-04-02 10:20:51 -04:00
12 changed files with 126 additions and 84 deletions

View File

@ -7,7 +7,7 @@ AVRABIN = zasm/avra
SHELLAPPS = zasm ed
SHELLTGTS = ${SHELLAPPS:%=cfsin/%}
# Those Forth source files are in a particular order
FORTHSRCS = core.fs str.fs parse.fs readln.fs fmt.fs z80a.fs dummy.fs
FORTHSRCS = core.fs str.fs parse.fs readln.fs fmt.fs z80a.fs
FORTHSRC_PATHS = ${FORTHSRCS:%=../forth/%}
CFSIN_CONTENTS = $(SHELLTGTS) cfsin/user.h
OBJS = emul.o libz80/libz80.o
@ -42,7 +42,7 @@ forth/stage1dbg: forth/stage.c $(OBJS) forth/forth0-bin.h
$(CC) -DDEBUG -DBOOT forth/stage.c $(OBJS) -o $@
forth/core.bin: $(FORTHSRC_PATHS) forth/stage1
cat $(FORTHSRC_PATHS) | ./forth/stage1 | tee $@ > /dev/null
cat $(FORTHSRC_PATHS) ./forth/stop.fs | ./forth/stage1 | tee $@ > /dev/null
forth/forth1.bin: forth/forth0.bin forth/core.bin
cat forth/forth0.bin forth/core.bin > $@
@ -104,7 +104,7 @@ updatebootstrap: $(ZASMBIN)
.PHONY: fbootstrap
fbootstrap: forth/stage2
cat ./forth/conf.fs ../forth/boot.fs | ./forth/stage2 > forth/boot.bin
cat ../forth/dummy.fs ../forth/z80c.fs forth/emul.fs ../forth/icore.fs ../forth/dummy.fs | ./forth/stage2 | tee forth/z80c.bin > /dev/null
cat ./forth/conf.fs ../forth/z80c.fs forth/emul.fs ../forth/icore.fs | ./forth/stage2 | tee forth/z80c.bin > /dev/null
.PHONY: clean
clean:

Binary file not shown.

2
emul/forth/stop.fs Normal file
View File

@ -0,0 +1,2 @@
(entry) _
H@ 256 /MOD 2 PC! 2 PC!

Binary file not shown.

View File

@ -25,17 +25,17 @@ JP(IY), NOP, ( 17, nativeWord )
0 JPnn, ( 1d, chkPS )
NOP, NOP, ( 20, numberWord )
NOP, NOP, ( 22, litWord )
RAMSTART , ( 24, INITIAL_SP )
RAMSTART 0x0e + , ( 26, WORDBUF )
NOP, NOP, ( 24, unused )
NOP, NOP, ( 26, unused )
0 JPnn, ( 28, flagsToBC )
0 JPnn, ( 2b, doesWord )
RS_ADDR , ( 2e, RS_ADDR )
RAMSTART 0x0c + , ( 30, CINPTR )
RAMSTART 0x2e + , ( 32, SYSVNXT )
RAMSTART 0x08 + , ( 34, FLAGS )
RAMSTART 0x0a + , ( 36, PARSEPTR )
RAMSTART 0x04 + , ( 38, HERE )
RAMSTART 0x02 + , ( 3a, CURRENT )
NOP, NOP, ( 2e, unused )
NOP, NOP, ( 30, unused )
NOP, NOP, ( 32, unused )
NOP, NOP, ( 34, unused )
NOP, NOP, ( 36, unused )
NOP, NOP, ( 38, unused )
NOP, NOP, ( 3a, unused )
( BOOT DICT
There are only 5 words in the boot dict, but these words'
@ -116,14 +116,14 @@ PC ORG @ 1 + ! ( main )
stack underflow.
)
SP 0xfffa LDddnn,
0x24 @ SP LD(nn)dd, ( 24 == INITIAL_SP )
RAMSTART SP LD(nn)dd, ( RAM+00 == INITIAL_SP )
IX RS_ADDR LDddnn,
( LATEST is a label to the latest entry of the dict. It is
written at offset 0x08 by the process or person building
Forth. )
0x08 LDHL(nn),
0x3a @ LD(nn)HL, ( 3a == CURRENT )
0x38 @ LD(nn)HL, ( 38 == HERE )
RAMSTART 0x02 + LD(nn)HL, ( RAM+02 == CURRENT )
RAMSTART 0x04 + LD(nn)HL, ( RAM+04 == HERE )
HL L1 @ LDddnn,
0x03 CALLnn, ( 03 == find )
DE PUSHqq,
@ -155,7 +155,7 @@ PC ORG @ 4 + ! ( find )
adjust. Because the compare loop pre-decrements, instead
of DECing HL twice, we DEC it once. )
HL DECss,
DE 0x3a @ LDdd(nn), ( 3a == CURRENT )
DE RAMSTART 0x02 + LDdd(nn), ( RAM+02 == CURRENT )
L3 BSET ( inner )
( DE is a wordref, first step, do our len correspond? )
HL PUSHqq, ( --> lvl 1 )
@ -249,7 +249,7 @@ L1 BSET ( abortUnderflow )
PC ORG @ 0x1e + ! ( chkPS )
HL PUSHqq,
0x24 @ LDHL(nn), ( 24 == INITIAL_SP )
RAMSTART LDHL(nn), ( RAM+00 == INITIAL_SP )
( We have the return address for this very call on the stack
and protected registers. Let's compensate )
HL DECss,

View File

@ -6,7 +6,7 @@
: LITS LIT SCPY ;
: LIT< WORD LITS ; IMMEDIATE
: _err LIT< word-not-found (print) ABORT ;
: ' WORD (find) NOT (?br) [ 4 , ] _err ;
: ' WORD FIND NOT (?br) [ 4 , ] _err ;
: ['] ' LITN ; IMMEDIATE
: COMPILE ' LITN ['] , , ; IMMEDIATE
: [COMPILE] ' , ; IMMEDIATE
@ -106,16 +106,16 @@
: (sysv)
( Get new sysv addr )
( 50 == SYSVNXT )
50 @ @
( RAM+48 (30) == SYSVNXT )
48 RAM+ @
CONSTANT
( increase current sysv counter )
2 50 @ +!
2 48 RAM+ +!
;
( Set up initial SYSVNXT value, which is 2 bytes after its
own address )
50 @ DUP 2 + SWAP !
48 RAM+ DUP 2 + SWAP !
: ."
LIT

View File

@ -32,8 +32,6 @@ directly, but as part of another word.
"*I*" in description indicates an IMMEDIATE word.
*** Defining words ***
(find) a -- a f Read at a and find it in dict. If found, f=1 and
a = wordref. If not found, f=0 and a = string addr.
: x ... -- Define a new word
; R:I -- Exit a colon definition
, n -- Write n in HERE and advance it.
@ -49,6 +47,8 @@ CREATE x -- Create cell named x. Doesn't allocate a PF.
COMPILE x -- Meta compiles. Kind of blows the mind. See below.
CONSTANT x n -- Creates cell x that when called pushes its value
DOES> -- See description at top of file
FIND a -- a f Read at a and find it in dict. If found, f=1 and
a = wordref. If not found, f=0 and a = string addr.
IMMED? a -- f Checks whether wordref at a is immediate.
IMMEDIATE -- Flag the latest defined word as immediate.
LITN n -- Write number n as a literal.

View File

@ -1,13 +0,0 @@
( When building a compiled dict, always include this unit at
the end of it so that Forth knows how to hook LATEST into
it. We don't use the word "(entry)" to avoid messing up
with icore setup. )
CREATE _
H@ 2 - HERE !
( After each dummy word like this, we poke IO port 2 with our
current HERE value. The staging executable needs it to know
what to dump. )
H@ 256 / 2 PC!
H@ 2 PC!

View File

@ -55,25 +55,14 @@
, ( write! )
; IMMEDIATE
: FLAGS
( 52 == FLAGS )
[ 52 @ LITN ]
: RAM+
[ RAMSTART LITN ] _c +
;
: (parse*)
( 54 == PARSEPTR )
[ 54 @ LITN ]
;
: HERE
( 56 == HERE )
[ 56 @ LITN ]
;
: CURRENT
( 58 == CURRENT )
[ 58 @ LITN ]
;
: FLAGS 0x08 _c RAM+ ;
: (parse*) 0x0a _c RAM+ ;
: HERE 0x04 _c RAM+ ;
: CURRENT 0x02 _c RAM+ ;
: QUIT
0 _c FLAGS _c ! _c (resRS)
@ -141,9 +130,14 @@
LIT< stack-underflow _c (print) _c ABORT
;
: FIND
( 0e == FINDPTR )
0x0e _c RAM+ _c @ EXECUTE
;
: C<
( 48 == CINPTR )
[ 48 @ LITN ] _c @ EXECUTE
( 0c == CINPTR )
0x0c _c RAM+ _c @ EXECUTE
;
: ,
@ -170,8 +164,8 @@
( Read word from C<, copy to WORDBUF, null-terminate, and
return, make HL point to WORDBUF. )
: WORD
( 38 == WORDBUF )
[ 38 @ LITN ] ( a )
( 10 == WORDBUF )
0x10 _c RAM+ ( a )
_c TOWORD ( a c )
BEGIN
( We take advantage of the fact that char MSB is
@ -184,7 +178,7 @@
( a this point, PS is: a WS )
( null-termination is already written )
_c 2DROP
[ 38 @ LITN ]
0x10 _c RAM+
;
: (entry)
@ -205,7 +199,7 @@
: INTERPRET
BEGIN
_c WORD
_c (find)
_c FIND
IF
1 _c FLAGS _c !
EXECUTE
@ -217,12 +211,16 @@
;
: BOOT
LIT< (parse) _c (find) _c DROP _c (parse*) _c !
LIT< (c<) _c (find) _c
NOT IF LIT< KEY _c (find) _c DROP THEN
( 48 == CINPTR )
[ 48 @ LITN ] _c !
LIT< (c<$) _c (find) IF EXECUTE ELSE _c DROP THEN
( write (find) in PARSEPTR, RAM+0e )
( a bit wasteful, but otherwise I have bootstrap
issues with "," )
LIT< (find) _c (find) _c DROP 0x0e _c RAM+ _c !
LIT< (parse) _c FIND _c DROP _c (parse*) _c !
LIT< (c<) _c FIND _c
NOT IF LIT< KEY _c FIND _c DROP THEN
( 0c == CINPTR )
0x0c _c RAM+ _c !
LIT< (c<$) _c FIND IF EXECUTE ELSE _c DROP THEN
_c INTERPRET
;
@ -245,7 +243,7 @@
[ 32 H@ ! 2 ALLOT 14 H@ ! 2 ALLOT ] _c ,
BEGIN
_c WORD
_c (find)
_c FIND
( is word )
IF _c DUP _c IMMED? IF EXECUTE ELSE _c , THEN
( maybe number )
@ -262,3 +260,8 @@
':' ' X 4 - C!
';' ' Y 4 - C!
( Add dummy entry. we use CREATE because (entry) is, at this
point, broken. Adjust H@ durint port 2 ping. )
CREATE _
H@ 2 - 256 /MOD 2 PC! 2 PC!

View File

@ -66,3 +66,47 @@ also "special words", for example NUMBER, LIT, FBR, that have a slightly
different structure. They're also a pointer to an executable, but as for the
other fields, the only one they have is the "flags" field.
*** System variables
There are some core variables in the core system that are referred to directly
by their address in memory throughout the code. The place where they live is
configurable by the RAMSTART constant in conf.fs, but their relative offset is
not. In fact, they're mostlly referred to directly as their numerical offset
along with a comment indicating what this offset refers to.
This system is a bit fragile because every time we change those offsets, we
have to be careful to adjust all system variables offsets, but thankfully,
there aren't many system variables. Here's a list of them:
RAMSTART INITIAL_SP
+02 CURRENT
+04 HERE
+06 IP
+08 FLAGS
+0a PARSEPTR
+0c CINPTR
+0e FINDPTR
+10 WORDBUF
+30 SYSVNXT
+50 RAMEND
INITIAL_SP holds the initial Stack Pointer value so that we know where to reset
it on ABORT
CURRENT points to the last dict entry.
HERE points to current write offset.
IP is the Interpreter Pointer
FLAGS holds global flags. Only used for prompt output control for now.
PARSEPTR holds routine address called on (parse)
CINPTR holds routine address called on C<
FINDPTR holds routine address called on FIND
WORDBUF is the buffer used by WORD
SYSVNXT is the buffer+tracker used by (sysv)

View File

@ -24,6 +24,9 @@
H@ IN( !
INBUFSZ ALLOT
H@ IN) !
( We need two extra bytes. 1 for the last typed 0x0a and
one for the following NULL. )
2 ALLOT
(infl)
;
@ -39,24 +42,22 @@
( read one char into input buffer and returns whether we
should continue, that is, whether newline was not met. )
: (rdlnc) ( -- f )
( buffer overflow? stop now )
IN> @ IN) @ = IF 0 EXIT THEN
( get and echo back )
KEY DUP ( c c )
( buffer overflow? same as if we typed a newline )
IN> @ IN) @ = IF 0x0a ELSE KEY THEN ( c )
( del? same as backspace )
DUP 0x7f = IF DROP DROP 0x8 DUP THEN
EMIT ( c )
DUP 0x7f = IF DROP 0x8 THEN
( echo back )
DUP EMIT ( c )
( bacspace? handle and exit )
DUP 0x8 = IF (inbs) EXIT THEN
( write and advance )
DUP ( keep as result ) ( c c )
( Here, we take advantage of the fact that c's MSB is
always zero and thus ! automatically null-terminates
our string )
IN> @ ! 1 IN> +! ( c )
( not newline? exit now )
DUP 0xa = NOT IF EXIT THEN ( c )
( newline? make our result 0 and write it to indicate
EOL )
DROP 0
DUP IN> @ ! ( c )
( if newline, replace with zero to indicate EOL )
DUP 0xa = IF DROP 0 THEN
;
( Read one line in input buffer and make IN> point to it )
@ -75,5 +76,5 @@
( not EOL? good, inc and return )
DUP IF 1 IN> +! EXIT THEN ( c )
( EOL ? readline. we still return typed char though )
(rdln) ( c )
(rdln) (<c) ( c )
;

View File

@ -18,8 +18,14 @@
Oh, also: KEY and EMIT are not defined here. There're
expected to be defined in platform-specific code.
This unit expects the same conf as boot.fs.
)
( dummy entry for dict hook )
(entry) _
H@ 256 /MOD 2 PC! 2 PC!
( a b c -- b c a )
CODE ROT
HL POPqq, ( C )
@ -329,13 +335,12 @@ CODE BYE
;CODE
CODE (resSP)
( INITIAL_SP == 36 )
SP 36 @ LDdd(nn),
( INITIAL_SP == RAM+0 )
SP RAMSTART LDdd(nn),
;CODE
CODE (resRS)
( RS_ADDR == 46 )
IX 46 @ LDddnn,
IX RS_ADDR LDddnn,
;CODE
CODE SCMP