mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 08:30:55 +11:00
Compare commits
2 Commits
d4cdb659b4
...
1efb2821e3
Author | SHA1 | Date | |
---|---|---|---|
|
1efb2821e3 | ||
|
f59cac0588 |
10
README.md
10
README.md
@ -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
|
||||||
|
|
||||||
|
12
blk/003
Normal file
12
blk/003
Normal 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
15
blk/004
Normal 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
16
blk/006
Normal 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
16
blk/008
Normal 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
14
blk/009
Normal 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
16
blk/011
Normal 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
16
blk/012
Normal 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
16
blk/014
Normal 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
16
blk/015
Normal 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
4
blk/016
Normal 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
11
blk/030
Normal 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
16
blk/031
Normal 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
10
blk/032
Normal 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
16
blk/034
Normal 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
3
blk/035
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
(cont.)
|
||||||
|
~ - Container for native code. Usually not an executable word.
|
||||||
|
? - Is it ...? (example: IMMED?)
|
16
blk/037
Normal file
16
blk/037
Normal 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
5
blk/038
Normal 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
16
blk/040
Normal 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
16
blk/042
Normal 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
16
blk/043
Normal 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
3
blk/044
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
(cont.)
|
||||||
|
UNTIL f -- *I* Jump backwards to BEGIN if f is
|
||||||
|
false.
|
11
blk/046
Normal file
11
blk/046
Normal 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
7
blk/048
Normal 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
15
blk/050
Normal 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
10
blk/052
Normal 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
12
blk/054
Normal 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
8
blk/056
Normal 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
10
blk/058
Normal 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
16
blk/060
Normal 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
16
blk/061
Normal 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
9
blk/062
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
(cont.)
|
||||||
|
There are also ascii const emitters:
|
||||||
|
BS
|
||||||
|
CR
|
||||||
|
LF
|
||||||
|
SPC
|
||||||
|
|
||||||
|
|
||||||
|
|
4
blk/064
Normal file
4
blk/064
Normal 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.
|
209
dictionary.txt
209
dictionary.txt
@ -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
1
emul/.gitignore
vendored
@ -4,3 +4,4 @@
|
|||||||
/forth/forth
|
/forth/forth
|
||||||
/*/*-bin.h
|
/*/*-bin.h
|
||||||
/*/*.bin
|
/*/*.bin
|
||||||
|
/blkfs
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
32
forth/blk.fs
Normal 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
|
||||||
|
;
|
@ -62,7 +62,7 @@
|
|||||||
EMIT
|
EMIT
|
||||||
1 +
|
1 +
|
||||||
LOOP
|
LOOP
|
||||||
LF
|
CRLF
|
||||||
;
|
;
|
||||||
|
|
||||||
( n a -- )
|
( n a -- )
|
||||||
|
@ -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 ;
|
||||||
|
@ -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
124
usage.txt
@ -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.
|
|
Loading…
Reference in New Issue
Block a user