1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 10:20:55 +11:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Virgil Dupras
1efb2821e3 Make usage and dictionary documentation in-system 2020-04-14 10:04:09 -04:00
Virgil Dupras
f59cac0588 blk: first steps 2020-04-14 09:07:20 -04:00
45 changed files with 491 additions and 341 deletions

View File

@ -35,6 +35,15 @@ run Collapse OS in different contexts][jsemul].
Using those while following along with the [User Guide](doc/) is your quickest Using those while following along with the [User Guide](doc/) is your quickest
path to giving Collapse OS a try. path to giving Collapse OS a try.
## Documentation
Usage documentation is in-system. Run `0 LIST` for an introduction. You can
also open `blk/000` in a modern text editor.
See `/emul/README.md` for getting an emulated system running.
There is also `/notes.txt` for implementation notes.
## Organisation of this repository ## Organisation of this repository
* `forth`: Forth is slowly taking over this project (see issue #4). It comes * `forth`: Forth is slowly taking over this project (see issue #4). It comes
@ -66,4 +75,3 @@ A more traditional [mailing list][listserv] and IRC (#collapseos on freenode) ch
[discussion]: https://www.reddit.com/r/collapseos [discussion]: https://www.reddit.com/r/collapseos
[listserv]: http://lists.sonic.net/mailman/listinfo/collapseos [listserv]: http://lists.sonic.net/mailman/listinfo/collapseos
[forth-issue]: https://github.com/hsoft/collapseos/issues/4 [forth-issue]: https://github.com/hsoft/collapseos/issues/4

View File

@ -1,2 +1,3 @@
MASTER INDEX MASTER INDEX
2 Documentation

4
blk/002 Normal file
View File

@ -0,0 +1,4 @@
Documentation index
3 Usage
30 Dictionary

12
blk/003 Normal file
View File

@ -0,0 +1,12 @@
Collapse OS usage guide
This document is not meant to be an introduction to Forth, but
to instruct the user about the peculiarities of this Forth
implemenation. Be sure to refer to dictionary for a word
reference.
Contents
4 DOES> 6 Compilation vs meta-comp.
8 I/O 11 Chained comparisons
14 Addressed devices

15
blk/004 Normal file
View File

@ -0,0 +1,15 @@
DOES>
Used inside a colon definition that itself uses CREATE, DOES>
transforms that newly created word into a "does cell", that is,
a regular cell ( when called, puts the cell's addr on PS), but
right after that, it executes words that appear after the
DOES>.
"does cells" always allocate 4 bytes (2 for the cell, 2 for the
DOES> link) and there is no need for ALLOT in colon definition.
At compile time, colon definition stops processing words when
reaching the DOES>.
Example: ": CONSTANT CREATE HERE @ ! DOES> @ ;"

16
blk/006 Normal file
View File

@ -0,0 +1,16 @@
Compilation vs meta-compilation
Compilation vs meta-compilation. When you compile a word with
"[COMPILE] foo", its straightforward: It writes down to HERE
wither the address of the word or a number literal.
When you *meta* compile, it's a bit more mind blowing. It
fetches the address of the word specified by the caller, then
writes that number as a literal, followed by a reference to
",".
Example: ": foo [COMPILE] bar;" is the equivalent of ": foo bar
;" if bar is not an immediate. However, ": foo COMPILE bar ;"
is the equivalent of ": foo ['] bar , ;". Got it?
Meta-compile only works with real words, not number literals.

16
blk/008 Normal file
View File

@ -0,0 +1,16 @@
I/O
A little word about inputs. There are two kind of inputs:
direct and buffered. As a general rule, we read line in a
buffer, then feed words in it to the interpreter. That's what
"WORD" does. If it's at the End Of Line, it blocks and wait
until another line is entered.
KEY input, however, is direct. Regardless of the input buffer's
state, KEY will return the next typed key.
PARSING AND BOOTSTRAP: Parsing number literal is a very "core"
activity of Forth, and therefore generally seen as having to be
implemented in native code. However, Collapse OS' Forth
supports many kinds of literals: decimal, hex, char, binary.
This incurs a significant complexity penalty. (cont.)

14
blk/009 Normal file
View File

@ -0,0 +1,14 @@
(cont.) What if we could implement those parsing routines in
Forth? "But it's a core routine!" you say. Yes, but here's the
deal: at its native core, only decimal parsing is supported. It
lives in the "(parsed)" word. The interpreter's main loop is
initially set to simply call that word.
However, in core.fs, "(parsex)", "(parsec)" and "(parseb)" are
implemented, in Forth, then "(parse)", which goes through them
all is defined. Then, "(parsef)", which is the variable in
which the interpreter's word pointer is set, is updated to that
new "(parse)" word.
This way, we have a full-featured (and extensible) parsing with
a tiny native core.

16
blk/011 Normal file
View File

@ -0,0 +1,16 @@
Chained comparisons
The unit "cmp.fs" contains words to facilitate chained
comparisons with a single reference number. This allows, for
example, to easily express "a == b or a == c" or "a > b and a <
c".
The way those chained comparison words work is that, unlike
single comparison operators, they don't have a "n1 n2 -- f"
signature, but rather a "n1 f n2 -- n1 f" signature. That is,
each operator "carries over" the reference number in addition
to the latest flag.
(cont.)

16
blk/012 Normal file
View File

@ -0,0 +1,16 @@
(cont.) You open a chain with "<>{" and you close a chain with
"<>}". Then, in between those words, you can chain operators.
For example, to check whether A == B or A == C, you would
write:
A <>{ B &= C |= <>}
The first operator must be of the "&" type because the chain
starts with its flag to true. For example, "<>{ <>}" yields
true.
To check whether A is in between B and C inclusively, you would
write:
A <>{ B 1 - &> C 1 + &< <>}

16
blk/014 Normal file
View File

@ -0,0 +1,16 @@
Addressed devices
The adev unit provides a simple but powerful abstraction over
C@ and C!: A@ and A!. These work the same way as C@ and C! (but
for performance reasons, aren't used in core words), but are
indirect calls.
Upon initialization, the default to C@ and C!, but can be set
to any word through A@* and A!*.
On top of that, it provides a few core-like words such as
AMOVE.
Let's demonstrate its use through a toy example:
(cont.)

16
blk/015 Normal file
View File

@ -0,0 +1,16 @@
(cont.)
> : F! SWAP 1 + SWAP C! ;
> 8 H@ DUMP
:54 0000 0000 0000 0000 ........
> 9 H@ A!
> 8 H@ DUMP
:54 0900 0000 0000 0000 ........
> ' F! A!* !
> 9 H@ 1 + A!
> 8 H@ DUMP
:54 090a 0000 0000 0000 ........
> H@ H@ 2 + 2 AMOVE
> 8 H@ DUMP
:54 090a 0a0b 0000 0000 ........
>
(cont.)

4
blk/016 Normal file
View File

@ -0,0 +1,4 @@
(cont.) Of course, you might want to end up using adev in this
kind of ad-hoc way to have some kind of mapping function, but
what you'll mostly want to to is to plug device drivers into
those words.

11
blk/030 Normal file
View File

@ -0,0 +1,11 @@
Dictionary
Be sure to read usage guide (B3) first.
31 Glossary 34 Symbols
37 Entry management 40 Defining words
42 Flow 46 Parameter stack
48 Return stack 50 Memory
52 Addressed devices 54 Arithmetic / Bits
56 Logic 58 Strings
60 I/O 64 Disk

16
blk/031 Normal file
View File

@ -0,0 +1,16 @@
Glossary
Stack notation: "<stack before> -- <stack after>". Rightmost is
top of stack (TOS). For example, in "a b -- c d", b is TOS
before, d is TOS after. "R:" means that the Return Stack is
modified. "I:" prefix means "IMMEDIATE", that is, that this
stack transformation is made at compile time.
Word references (wordref): When we say we have a "word
reference", it's a pointer to a words *code link*. For example,
the label "PLUS:" in this unit is a word reference. Why not
refer to the beginning of the word struct? Because we actually
seldom refer to the name and prev link, except during
compilation, so defining "word reference" this way makes the
code easier to understand.
(cont.)

10
blk/032 Normal file
View File

@ -0,0 +1,10 @@
(cont.)
Atom: A word of the type compiledWord contains, in its PF, a
list of what we call "atoms". Those atoms are most of the time
word references, but they can also be references to NUMBER and
LIT.
Words between "()" are "support words" that aren't really meant
to be used directly, but as part of another word.
"*I*" in description indicates an IMMEDIATE word.

16
blk/034 Normal file
View File

@ -0,0 +1,16 @@
Symbols
Throughout words, different symbols are used in different
contexts, but we try to been consistent in their use. Here's
their definitions:
! - Store
@ - Fetch
$ - Initialize
^ - Arguments in their opposite order
< - Input
> - 1. Pointer in a buffer 2. Opposite of "<".
( - Lower boundary
) - Upper boundary
* - Word indirection (pointer to word)
(cont.)

3
blk/035 Normal file
View File

@ -0,0 +1,3 @@
(cont.)
~ - Container for native code. Usually not an executable word.
? - Is it ...? (example: IMMED?)

16
blk/037 Normal file
View File

@ -0,0 +1,16 @@
Entry management
(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 -- a Push addr of word x to a. If not found,
aborts.
['] x -- *I* Like "'", but spits the addr as a number
literal. If not found, aborts.
, n -- Write n in HERE and advance it.
ALLOT n -- Move HERE by n bytes
C, b -- Write byte b in HERE and advance it.
DELW a -- Delete wordref at a. If it shadows another
definition, that definition is unshadowed.
FORGET x -- Rewind the dictionary (both CURRENT and HERE) up to
x's previous entry. (cont.)

5
blk/038 Normal file
View File

@ -0,0 +1,5 @@
(cont.)
PREV a -- a Return a wordref's previous entry.
WHLEN a -- n Get word header length from
wordref. That is, name length + 3.
a is a wordref

16
blk/040 Normal file
View File

@ -0,0 +1,16 @@
Defining words
: x ... -- Define a new word
; R:I -- Exit a colon definition
CREATE x -- Create cell named x. Doesn't allocate a PF.
[COMPILE] x -- Compile word x and write it to HERE.
IMMEDIATE words are *not* executed.
COMPILE x -- Meta compiles. See B6.
CONSTANT x n -- Creates cell x that when called pushes its
value.
DOES> -- See B4.
IMMED? a -- f Checks whether wordref at a is immediate.
IMMEDIATE -- Flag the latest defined word as immediate.
LITA n -- Write address n as a literal.
LITN n -- Write number n as a literal.
VARIABLE c -- Creates cell x with 2 bytes allocation.

16
blk/042 Normal file
View File

@ -0,0 +1,16 @@
Flow
Note about flow words: flow words can only be used in
definitions. In the INTERPRET loop, they don't have the desired
effect because each word from the input stream is executed
immediately. In this context, branching doesn't work.
(br) -- Branches by the number specified in the 2 following
bytes. Can be negative.
(?br) f -- Branch if f is false.
( -- *I* Comment. Ignore rest of line until ")" is read.
[ -- Begin interpretative mode. In a definition, words
between here and "]" will be executed instead of
compiled.
] -- End interpretative mode.
ABORT -- Resets PS and RS and returns to interpreter. (cont.)

16
blk/043 Normal file
View File

@ -0,0 +1,16 @@
(cont.)
ABORT" x" -- *I* Compiles a ." followed by a ABORT.
AGAIN I:a -- *I* Jump backwards to preceeding BEGIN.
BEGIN -- I:a *I* Marker for backward branching with
AGAIN.
ELSE I:a -- *I* Compiles a (fbr) and set branching
cell at a.
EXECUTE a -- Execute wordref at addr a
IF -- I:a *I* Compiles a (fbr?) and pushes its
cell's addr
INTERPRET -- Get a line from stdin, compile it in tmp
memory, then execute the compiled
contents.
QUIT R:drop -- Return to interpreter prompt immediately
RECURSE R:I -- R:I-2 Run the current word again.
THEN I:a -- *I* Set branching cell at a. (cont.)

3
blk/044 Normal file
View File

@ -0,0 +1,3 @@
(cont.)
UNTIL f -- *I* Jump backwards to BEGIN if f is
false.

11
blk/046 Normal file
View File

@ -0,0 +1,11 @@
Parameter Stack
DROP a --
DUP a -- a a
OVER a b -- a b a
ROT a b c -- b c a
SWAP a b -- b a
2DROP a a --
2DUP a b -- a b a b
2OVER a b c d -- a b c d a b
2SWAP a b c d -- c d a b

7
blk/048 Normal file
View File

@ -0,0 +1,7 @@
Return Stack
>R n -- R:n Pops PS and push to RS
R> R:n -- n Pops RS and push to PS
I -- n Copy RS TOS to PS
I' -- n Copy RS second item to PS
J -- n Copy RS third item to PS

15
blk/050 Normal file
View File

@ -0,0 +1,15 @@
Memory
@ a -- n Set n to value at address a
! n a -- Store n in address a
? a -- Print value of addr a
+! n a -- Increase value of addr a by n
C@ a -- c Set c to byte at address a
C! c a -- Store byte c in address a
CURRENT -- a Set a to wordref of last added entry.
CURRENT* -- a A pointer to active CURRENT*. Useful
when we have multiple active dicts.
HERE -- a Push HERE's address
H@ -- a HERE @
MOVE a1 a2 u -- Copy u bytes from a1 to a2, starting
with a1, going up.

10
blk/052 Normal file
View File

@ -0,0 +1,10 @@
Addressed devices
See B14 for details.
ADEV$ -- Initialize adev subsystem
A@ a -- c Indirect C@
A! c a -- Indirect C!
A@* -- a Address for A@ word
A!* -- a Address for A! word
AMOVE src dst u -- Same as MOVE, but with A@ and A!

12
blk/054 Normal file
View File

@ -0,0 +1,12 @@
Arithmetic / Bits
+ a b -- c a + b -> c
- a b -- c a - b -> c
-^ a b -- c b - a -> c
* a b -- c a * b -> c
/ a b -- c a / b -> c
MOD a b -- c a % b -> c
/MOD a b -- r q r:remainder q:quotient
AND a b -- c a & b -> c
OR a b -- c a | b -> c
XOR a b -- c a ^ b -> c

8
blk/056 Normal file
View File

@ -0,0 +1,8 @@
Logic
= n1 n2 -- f Push true if n1 == n2
< n1 n2 -- f Push true if n1 < n2
> n1 n2 -- f Push true if n1 > n2
CMP n1 n2 -- n Compare n1 and n2 and set n to -1, 0, or 1.
n=0: a1=a2. n=1: a1>a2. n=-1: a1<a2.
NOT f -- f Push the logical opposite of f

10
blk/058 Normal file
View File

@ -0,0 +1,10 @@
Strings
LIT -- Write a LIT entry. You're expected to write
actual string to HERE right afterwards.
LIT< x -- Read following word and write to HERE as a
string literal.
LITS a -- Write word at addr a as a atring literal.
SCMP a1 a2 -- n Compare strings a1 and a2. See CMP
SCPY a -- Copy string at addr a into HERE.
SLEN a -- n Push length of str at a.

16
blk/060 Normal file
View File

@ -0,0 +1,16 @@
I/O
(parse) a -- n Parses string at a as a number and push the
result in n as well as whether parsing was a
success in f (false = failure, true =
success)
(parse*) -- a Variable holding the current pointer for
system number parsing. By default, (parse).
(print) a -- Print string at addr a.
. n -- Print n in its decimal form
.x n -- Print n's LSB in hex form. Always 2
characters.
.X n -- Print n in hex form. Always 4 characters.
Numbers are never considered negative.
"-1 .X" --> ffff
(cont.)

16
blk/061 Normal file
View File

@ -0,0 +1,16 @@
(cont.)
." xxx" -- *I* Compiles string literal xxx followed by a
call to (print).
C< -- c Read one char from buffered input.
DUMP n a -- Prints n bytes at addr a in a hexdump format.
Prints in chunks of 8 bytes. Doesn't do partial
lines. Output is designed to fit in 32 columns.
EMIT c -- Spit char c to output stream
IN> -- a Address of variable containing current pos in
input buffer.
KEY -- c Get char c from direct input
PC! c a -- Spit c to port a
PC@ a -- c Fetch c from port a
WORD -- a Read one word from buffered input and push its
addr.
(cont.)

9
blk/062 Normal file
View File

@ -0,0 +1,9 @@
(cont.)
There are also ascii const emitters:
BS
CR
LF
SPC

4
blk/064 Normal file
View File

@ -0,0 +1,4 @@
Disk
LIST n -- Prints the contents of the block n on screen in the
form of 16 lines of 64 columns.

View File

@ -1,209 +0,0 @@
Be sure to read "usage.txt" for a guide to Collapse OS' Forth.
*** Glossary ***
Stack notation: "<stack before> -- <stack after>". Rightmost is top of stack
(TOS). For example, in "a b -- c d", b is TOS before, d is TOS after. "R:" means
that the Return Stack is modified. "I:" prefix means "IMMEDIATE", that is, that
this stack transformation is made at compile time.
Word references (wordref): When we say we have a "word reference", it's a
pointer to a words *code link*. For example, the label "PLUS:" in this unit is a
word reference. Why not refer to the beginning of the word struct? Because we
actually seldom refer to the name and prev link, except during compilation, so
defining "word reference" this way makes the code easier to understand.
Atom: A word of the type compiledWord contains, in its PF, a list of what we
call "atoms". Those atoms are most of the time word references, but they can
also be references to NUMBER and LIT.
Words between "()" are "support words" that aren't really meant to be used
directly, but as part of another word.
"*I*" in description indicates an IMMEDIATE word.
*** Symbols ***
Throughout words, different symbols are used in different contexts, but we try
to been consistent in their use. Here's their definitions:
! - Store
@ - Fetch
$ - Initialize
^ - Arguments in their opposite order
< - Input
> - 1. Pointer in a buffer 2. Opposite of "<".
( - Lower boundary
) - Upper boundary
* - Word indirection (pointer to word)
~ - Container for native code. Usually not an executable word.
? - Is it ...? (example: IMMED?)
*** Entry management ***
(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 -- a Push addr of word x to a. If not found, aborts
['] x -- *I* Like "'", but spits the addr as a number
literal. If not found, aborts.
, n -- Write n in HERE and advance it.
ALLOT n -- Move HERE by n bytes
C, b -- Write byte b in HERE and advance it.
DELW a -- Delete wordref at a. If it shadows another
definition, that definition is unshadowed.
FORGET x -- Rewind the dictionary (both CURRENT and HERE) up to
x's previous entry.
PREV a -- a Return a wordref's previous entry.
WHLEN a -- n Get word header length from wordref. That is, name
length + 3. a is a wordref
*** Defining words ***
: x ... -- Define a new word
; R:I -- Exit a colon definition
CREATE x -- Create cell named x. Doesn't allocate a PF.
[COMPILE] x -- Compile word x and write it to HERE. IMMEDIATE
words are *not* executed.
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 in usage.txt
IMMED? a -- f Checks whether wordref at a is immediate.
IMMEDIATE -- Flag the latest defined word as immediate.
LITA n -- Write address n as a literal.
LITN n -- Write number n as a literal.
VARIABLE c -- Creates cell x with 2 bytes allocation.
*** Flow ***
Note about flow words: flow words can only be used in definitions. In the
INTERPRET loop, they don't have the desired effect because each word from the
input stream is executed immediately. In this context, branching doesn't work.
(br) -- Branches by the number specified in the 2 following
bytes. Can be negative.
(?br) f -- Branch if f is false.
( -- *I* Comment. Ignore rest of line until ")" is read.
[ -- Begin interetative mode. In a definition, words
between here and "]" will be executed instead of
compiled.
] -- End interpretative mode.
ABORT -- Resets PS and RS and returns to interpreter
ABORT" x" -- *I* Compiles a ." followed by a ABORT.
AGAIN I:a -- *I* Jump backwards to preceeding BEGIN.
BEGIN -- I:a *I* Marker for backward branching with AGAIN.
ELSE I:a -- *I* Compiles a (fbr) and set branching cell at a.
EXECUTE a -- Execute wordref at addr a
IF -- I:a *I* Compiles a (fbr?) and pushes its cell's addr
INTERPRET -- Get a line from stdin, compile it in tmp memory,
then execute the compiled contents.
QUIT R:drop -- Return to interpreter prompt immediately
RECURSE R:I -- R:I-2 Run the current word again.
THEN I:a -- *I* Set branching cell at a.
UNTIL f -- *I* Jump backwards to BEGIN if f is *false*.
*** Parameter Stack ***
DROP a --
DUP a -- a a
OVER a b -- a b a
ROT a b c -- b c a
SWAP a b -- b a
2DROP a a --
2DUP a b -- a b a b
2OVER a b c d -- a b c d a b
2SWAP a b c d -- c d a b
*** Return Stack ***
>R n -- R:n Pops PS and push to RS
R> R:n -- n Pops RS and push to PS
I -- n Copy RS TOS to PS
I' -- n Copy RS second item to PS
J -- n Copy RS third item to PS
*** Memory ***
@ a -- n Set n to value at address a
! n a -- Store n in address a
? a -- Print value of addr a
+! n a -- Increase value of addr a by n
C@ a -- c Set c to byte at address a
C! c a -- Store byte c in address a
CURRENT -- a Set a to wordref of last added entry.
CURRENT* -- a A pointer to active CURRENT*. Useful when we have
multiple active dicts.
HERE -- a Push HERE's address
H@ -- a HERE @
MOVE a1 a2 u -- Copy u bytes from a1 to a2, starting with a1, going
up.
*** Addressed devices ***
See usage.txt for details.
ADEV$ -- Initialize adev subsystem
A@ a -- c Indirect C@
A! c a -- Indirect C!
A@* -- a Address for A@ word
A!* -- a Address for A! word
AMOVE src dst u -- Same as MOVE, but with A@ and A!
*** Arithmetic / Bits ***
+ a b -- c a + b -> c
- a b -- c a - b -> c
-^ a b -- c b - a -> c
* a b -- c a * b -> c
/ a b -- c a / b -> c
MOD a b -- c a % b -> c
/MOD a b -- r q r:remainder q:quotient
AND a b -- c a & b -> c
OR a b -- c a | b -> c
XOR a b -- c a ^ b -> c
*** Logic ***
= n1 n2 -- f Push true if n1 == n2
< n1 n2 -- f Push true if n1 < n2
> n1 n2 -- f Push true if n1 > n2
CMP n1 n2 -- n Compare n1 and n2 and set n to -1, 0, or 1.
n=0: a1=a2. n=1: a1>a2. n=-1: a1<a2.
NOT f -- f Push the logical opposite of f
*** Strings ***
LIT -- Write a LIT entry. You're expected to write actual
string to HERE right afterwards.
LIT< x -- Read following word and write to HERE as a string
literal.
LITS a -- Write word at addr a as a atring literal.
SCMP a1 a2 -- n Compare strings a1 and a2. See CMP
SCPY a -- Copy string at addr a into HERE.
SLEN a -- n Push length of str at a.
*** I/O ***
(parse) a -- n Parses string at a as a number and push the result
in n as well as whether parsing was a success in f
(false = failure, true = success)
(parse.) a -- n f Sub-parsing words. They all have the same signature.
Parses string at a as a number and push the result
in n as well as whether parsing was a success in f
(0 = failure, 1 = success)
(parse*) -- a Variable holding the current pointer for system
number parsing. By default, (parse).
(print) a -- Print string at addr a.
. n -- Print n in its decimal form
.x n -- Print n's LSB in hex form. Always 2 characters.
.X n -- Print n in hex form. Always 4 characters. Numbers
are never considered negative. "-1 .X" --> ffff
." xxx" -- *I* Compiles string literal xxx followed by a call
to (print)
C< -- c Read one char from buffered input.
DUMP n a -- Prints n bytes at addr a in a hexdump format.
Prints in chunks of 8 bytes. Doesn't do partial
lines. Output is designed to fit in 32 columns.
EMIT c -- Spit char c to output stream
IN> -- a Address of variable containing current pos in input
buffer.
KEY -- c Get char c from direct input
PC! c a -- Spit c to port a
PC@ a -- c Fetch c from port a
WORD -- a Read one word from buffered input and push its addr
There are also ascii const emitters:
BS
CR
LF
SPC

1
emul/.gitignore vendored
View File

@ -4,3 +4,4 @@
/forth/forth /forth/forth
/*/*-bin.h /*/*-bin.h
/*/*.bin /*/*.bin
/blkfs

View File

@ -1,5 +1,4 @@
TARGETS = forth/forth TARGETS = forth/forth
BIN2C = ../tools/bin2c
# Those Forth source files are in a particular order # Those Forth source files are in a particular order
BOOTSRCS = ./forth/conf.fs \ BOOTSRCS = ./forth/conf.fs \
../forth/xcomp.fs \ ../forth/xcomp.fs \
@ -10,11 +9,13 @@ BOOTSRCS = ./forth/conf.fs \
./forth/xstop.fs ./forth/xstop.fs
FORTHSRCS = core.fs cmp.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs \ FORTHSRCS = core.fs cmp.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs \
link.fs link.fs blk.fs
FORTHSRC_PATHS = ${FORTHSRCS:%=../forth/%} forth/run.fs FORTHSRC_PATHS = ${FORTHSRCS:%=../forth/%} forth/run.fs
OBJS = emul.o libz80/libz80.o OBJS = emul.o libz80/libz80.o
SLATEST = ../tools/slatest SLATEST = ../tools/slatest
STRIPFC = ../tools/stripfc STRIPFC = ../tools/stripfc
BIN2C = ../tools/bin2c
BLKPACK = ../tools/blkpack
.PHONY: all .PHONY: all
all: $(TARGETS) all: $(TARGETS)
@ -22,6 +23,7 @@ all: $(TARGETS)
$(STRIPFC): $(STRIPFC):
$(SLATEST): $(SLATEST):
$(BIN2C): $(BIN2C):
$(BLKPACK):
$(MAKE) -C ../tools $(MAKE) -C ../tools
# z80c.bin is not in the prerequisites because it's a bootstrap # z80c.bin is not in the prerequisites because it's a bootstrap
@ -55,7 +57,10 @@ forth/forth1-bin.h: forth/forth1.bin $(BIN2C)
forth/stage2: forth/stage.c $(OBJS) forth/forth1-bin.h forth/stage2: forth/stage.c $(OBJS) forth/forth1-bin.h
$(CC) -DSTAGE2 forth/stage.c $(OBJS) -o $@ $(CC) -DSTAGE2 forth/stage.c $(OBJS) -o $@
forth/forth: forth/forth.c $(OBJS) forth/forth1-bin.h blkfs: $(BLKPACK)
$(BLKPACK) ../blk > $@
forth/forth: forth/forth.c $(OBJS) forth/forth1-bin.h blkfs
$(CC) forth/forth.c $(OBJS) -o $@ $(CC) forth/forth.c $(OBJS) -o $@
libz80/libz80.o: libz80/z80.c libz80/libz80.o: libz80/z80.c

View File

@ -11,10 +11,15 @@
// This binary is also used for automated tests and those tests, when // This binary is also used for automated tests and those tests, when
// failing, send a non-zero value to RET_PORT to indicate failure // failing, send a non-zero value to RET_PORT to indicate failure
#define RET_PORT 0x01 #define RET_PORT 0x01
// Port for block reads. Write 2 bytes, MSB first, on that port and then
// read 1024 bytes from the same port.
#define BLK_PORT 0x03
static int running; static int running;
static FILE *fp; static FILE *fp;
static FILE *blkfp;
static int retcode = 0; static int retcode = 0;
static uint16_t blkid = 0;
static uint8_t iord_stdio() static uint8_t iord_stdio()
{ {
@ -39,6 +44,27 @@ static void iowr_ret(uint8_t val)
retcode = val; retcode = val;
} }
static uint8_t iord_blk()
{
uint8_t res = 0;
if (blkfp != NULL) {
int c = getc(blkfp);
if (c != EOF) {
res = c;
}
}
return res;
}
static void iowr_blk(uint8_t val)
{
blkid <<= 8;
blkid |= val;
if (blkfp != NULL) {
fseek(blkfp, blkid*1024, SEEK_SET);
}
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -67,11 +93,14 @@ int main(int argc, char *argv[])
fprintf(stderr, "Usage: ./forth [filename]\n"); fprintf(stderr, "Usage: ./forth [filename]\n");
return 1; return 1;
} }
blkfp = fopen("blkfs", "r+");
Machine *m = emul_init(); Machine *m = emul_init();
m->ramstart = RAMSTART; m->ramstart = RAMSTART;
m->iord[STDIO_PORT] = iord_stdio; m->iord[STDIO_PORT] = iord_stdio;
m->iowr[STDIO_PORT] = iowr_stdio; m->iowr[STDIO_PORT] = iowr_stdio;
m->iowr[RET_PORT] = iowr_ret; m->iowr[RET_PORT] = iowr_ret;
m->iord[BLK_PORT] = iord_blk;
m->iowr[BLK_PORT] = iowr_blk;
// initialize memory // initialize memory
for (int i=0; i<sizeof(KERNEL); i++) { for (int i=0; i<sizeof(KERNEL); i++) {
m->mem[i] = KERNEL[i]; m->mem[i] = KERNEL[i];
@ -89,6 +118,7 @@ int main(int argc, char *argv[])
tcsetattr(0, TCSAFLUSH, &termInfo); tcsetattr(0, TCSAFLUSH, &termInfo);
emul_printdebug(); emul_printdebug();
} }
fclose(blkfp);
fclose(fp); fclose(fp);
return retcode; return retcode;
} }

View File

@ -1 +1,15 @@
: INIT CURRENT @ HERE ! RDLN$ Z80A$ INTERPRET ; : EFS@
256 /MOD 3 PC! 3 PC!
1024 0 DO
3 PC@
BLK( I + C!
LOOP
;
: INIT
CURRENT @ HERE !
BLK$
['] EFS@ BLK@* !
RDLN$
Z80A$
INTERPRET
;

32
forth/blk.fs Normal file
View File

@ -0,0 +1,32 @@
( I/O blocks )
: BLKMEM+ 0x57 RAM+ @ + ;
( n -- Fetches block n and write it to BLK( )
: BLK@* 0 BLKMEM+ ;
( n -- Write back BLK( to storage at block n )
: BLK!* 2 BLKMEM+ ;
( Current blk pointer in ( )
: BLK> 4 BLKMEM+ ;
: BLK( 6 BLKMEM+ ;
: BLK$
H@ 0x57 RAM+ !
1030 ALLOT
-1 BLK> !
;
: BLK@
DUP BLK> = IF DROP EXIT THEN
DUP BLK> ! BLK@* @ EXECUTE
;
: .2 DUP 10 < IF SPC THEN . ;
: LIST
BLK@
16 0 DO
I 1 + .2 SPC
64 I * BLK( + (print)
CRLF
LOOP
;

View File

@ -62,7 +62,7 @@
EMIT EMIT
1 + 1 +
LOOP LOOP
LF CRLF
; ;
( n a -- ) ( n a -- )

View File

@ -32,4 +32,5 @@
: BS 8 EMIT ; : BS 8 EMIT ;
: LF 10 EMIT ; : LF 10 EMIT ;
: CR 13 EMIT ; : CR 13 EMIT ;
: CRLF CR LF ;
: SPC 32 EMIT ; : SPC 32 EMIT ;

View File

@ -71,7 +71,7 @@ other fields, the only one they have is the "flags" field.
There are some core variables in the core system that are referred to directly 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 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 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 not. In fact, they're mostly referred to directly as their numerical offset
along with a comment indicating what this offset refers to. along with a comment indicating what this offset refers to.
This system is a bit fragile because every time we change those offsets, we This system is a bit fragile because every time we change those offsets, we
@ -91,7 +91,7 @@ RAMSTART INITIAL_SP
+51 CURRENTPTR +51 CURRENTPTR
+53 readln's variables +53 readln's variables
+55 adev's variables +55 adev's variables
+57 FUTURE USES +57 blk's variables
+59 z80a's variables +59 z80a's variables
+5b FUTURE USES +5b FUTURE USES
+70 DRIVERS +70 DRIVERS

124
usage.txt
View File

@ -1,124 +0,0 @@
Collapse OS usage guide
This document is not meant to be an introduction to Forth, but to instruct the
user about the peculiarities of this Forth implemenation. Be sure to refer to
dictionary.txt for a word reference.
*** DOES>
Used inside a colon definition that itself uses CREATE, DOES> transforms that
newly created word into a "does cell", that is, a regular cell ( when called,
puts the cell's addr on PS), but right after that, it executes words that appear
after the DOES>.
"does cells" always allocate 4 bytes (2 for the cell, 2 for the DOES> link) and
there is no need for ALLOT in colon definition.
At compile time, colon definition stops processing words when reaching the
DOES>.
Example: ": CONSTANT CREATE HERE @ ! DOES> @ ;"
*** Compilation vs meta-compilation
Compilation vs meta-compilation. When you compile a word with "[COMPILE] foo",
its straightforward: It writes down to HERE wither the address of the word or
a number literal.
When you *meta* compile, it's a bit more mind blowing. It fetches the address
of the word specified by the caller, then writes that number as a literal,
followed by a reference to ",".
Example: ": foo [COMPILE] bar;" is the equivalent of ": foo bar ;" if bar is
not an immediate. However, ": foo COMPILE bar ;" is the equivalent of
": foo ['] bar , ;". Got it?
Meta-compile only works with real words, not number literals.
*** I/O
A little word about inputs. There are two kind of inputs: direct and buffered.
As a general rule, we read line in a buffer, then feed words in it to the
interpreter. That's what "WORD" does. If it's at the End Of Line, it blocks and
wait until another line is entered.
KEY input, however, is direct. Regardless of the input buffer's state, KEY will
return the next typed key.
PARSING AND BOOTSTRAP: Parsing number literal is a very "core" activity of
Forth, and therefore generally seen as having to be implemented in native code.
However, Collapse OS' Forth supports many kinds of literals: decimal, hex, char,
binary. This incurs a significant complexity penalty.
What if we could implement those parsing routines in Forth? "But it's a core
routine!" you say. Yes, but here's the deal: at its native core, only decimal
parsing is supported. It lives in the "(parsed)" word. The interpreter's main
loop is initially set to simply call that word.
However, in core.fs, "(parsex)", "(parsec)" and "(parseb)" are implemented, in
Forth, then "(parse)", which goes through them all is defined. Then, "(parsef)",
which is the variable in which the interpreter's word pointer is set, is
updated to that new "(parse)" word.
This way, we have a full-featured (and extensible) parsing with a tiny native
core.
*** Chained comparisons
The unit "cmp.fs" contains words to facilitate chained comparisons with a single
reference number. This allows, for example, to easily express "a == b or a == c"
or "a > b and a < c".
The way those chained comparison words work is that, unlike single comparison
operators, they don't have a "n1 n2 -- f" signature, but rather a "n1 f n2 -- n1
f" signature. That is, each operator "carries over" the reference number in
addition to the latest flag.
You open a chain with "<>{" and you close a chain with "<>}". Then, in between
those words, you can chain operators. For example, to check whether A == B or A
== C, you would write:
A <>{ B &= C |= <>}
The first operator must be of the "&" type because the chain starts with its
flag to true. For example, "<>{ <>}" yields true.
To check whether A is in between B and C inclusively, you would write:
A <>{ B 1 - &> C 1 + &< <>}
*** Addressed devices
The adev unit provides a simple but powerful abstraction over C@ and C!: A@ and
A!. These work the same way as C@ and C! (but for performance reasons, aren't
used in core words), but are indirect calls.
Upon initialization, the default to C@ and C!, but can be set to any word
through A@* and A!*.
On top of that, it provides a few core-like words such as AMOVE.
Let's demonstrate its use through a toy example:
> : F! SWAP 1 + SWAP C! ;
> 8 H@ DUMP
:54 0000 0000 0000 0000 ........
> 9 H@ A!
> 8 H@ DUMP
:54 0900 0000 0000 0000 ........
> ' F! A!* !
> 9 H@ 1 + A!
> 8 H@ DUMP
:54 090a 0000 0000 0000 ........
> H@ H@ 2 + 2 AMOVE
> 8 H@ DUMP
:54 090a 0a0b 0000 0000 ........
>
Of course, you might want to end up using adev in this kind of ad-hoc way to
have some kind of mapping function, but what you'll mostly want to to is to
plug device drivers into those words.