mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-26 17:48:05 +11:00
Compare commits
No commits in common. "1efb2821e31a720b815901d3c9cbe67371a077c8" and "d4cdb659b44c4ca9fce01d450a57f2207968d6df" have entirely different histories.
1efb2821e3
...
d4cdb659b4
10
README.md
10
README.md
@ -35,15 +35,6 @@ 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
|
||||||
@ -75,3 +66,4 @@ 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
12
blk/003
@ -1,12 +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 for a word
|
|
||||||
reference.
|
|
||||||
|
|
||||||
Contents
|
|
||||||
|
|
||||||
4 DOES> 6 Compilation vs meta-comp.
|
|
||||||
8 I/O 11 Chained comparisons
|
|
||||||
14 Addressed devices
|
|
15
blk/004
15
blk/004
@ -1,15 +0,0 @@
|
|||||||
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
16
blk/006
@ -1,16 +0,0 @@
|
|||||||
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
16
blk/008
@ -1,16 +0,0 @@
|
|||||||
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
14
blk/009
@ -1,14 +0,0 @@
|
|||||||
(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
16
blk/011
@ -1,16 +0,0 @@
|
|||||||
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
16
blk/012
@ -1,16 +0,0 @@
|
|||||||
(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
16
blk/014
@ -1,16 +0,0 @@
|
|||||||
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
16
blk/015
@ -1,16 +0,0 @@
|
|||||||
(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
4
blk/016
@ -1,4 +0,0 @@
|
|||||||
(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
11
blk/030
@ -1,11 +0,0 @@
|
|||||||
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
16
blk/031
@ -1,16 +0,0 @@
|
|||||||
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
10
blk/032
@ -1,10 +0,0 @@
|
|||||||
(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
16
blk/034
@ -1,16 +0,0 @@
|
|||||||
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
3
blk/035
@ -1,3 +0,0 @@
|
|||||||
(cont.)
|
|
||||||
~ - Container for native code. Usually not an executable word.
|
|
||||||
? - Is it ...? (example: IMMED?)
|
|
16
blk/037
16
blk/037
@ -1,16 +0,0 @@
|
|||||||
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
5
blk/038
@ -1,5 +0,0 @@
|
|||||||
(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
16
blk/040
@ -1,16 +0,0 @@
|
|||||||
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
16
blk/042
@ -1,16 +0,0 @@
|
|||||||
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
16
blk/043
@ -1,16 +0,0 @@
|
|||||||
(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.)
|
|
11
blk/046
11
blk/046
@ -1,11 +0,0 @@
|
|||||||
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
7
blk/048
@ -1,7 +0,0 @@
|
|||||||
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
15
blk/050
@ -1,15 +0,0 @@
|
|||||||
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
10
blk/052
@ -1,10 +0,0 @@
|
|||||||
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
12
blk/054
@ -1,12 +0,0 @@
|
|||||||
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
8
blk/056
@ -1,8 +0,0 @@
|
|||||||
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
10
blk/058
@ -1,10 +0,0 @@
|
|||||||
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
16
blk/060
@ -1,16 +0,0 @@
|
|||||||
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
16
blk/061
@ -1,16 +0,0 @@
|
|||||||
(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.)
|
|
4
blk/064
4
blk/064
@ -1,4 +0,0 @@
|
|||||||
Disk
|
|
||||||
|
|
||||||
LIST n -- Prints the contents of the block n on screen in the
|
|
||||||
form of 16 lines of 64 columns.
|
|
209
dictionary.txt
Normal file
209
dictionary.txt
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
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,4 +4,3 @@
|
|||||||
/forth/forth
|
/forth/forth
|
||||||
/*/*-bin.h
|
/*/*-bin.h
|
||||||
/*/*.bin
|
/*/*.bin
|
||||||
/blkfs
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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 \
|
||||||
@ -9,13 +10,11 @@ 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 blk.fs
|
link.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)
|
||||||
@ -23,7 +22,6 @@ 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
|
||||||
@ -57,10 +55,7 @@ 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 $@
|
||||||
|
|
||||||
blkfs: $(BLKPACK)
|
forth/forth: forth/forth.c $(OBJS) forth/forth1-bin.h
|
||||||
$(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,15 +11,10 @@
|
|||||||
// 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()
|
||||||
{
|
{
|
||||||
@ -44,27 +39,6 @@ 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[])
|
||||||
{
|
{
|
||||||
@ -93,14 +67,11 @@ 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];
|
||||||
@ -118,7 +89,6 @@ 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,15 +1 @@
|
|||||||
: EFS@
|
: INIT CURRENT @ HERE ! RDLN$ Z80A$ INTERPRET ;
|
||||||
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
32
forth/blk.fs
@ -1,32 +0,0 @@
|
|||||||
( 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
|
||||||
CRLF
|
LF
|
||||||
;
|
;
|
||||||
|
|
||||||
( n a -- )
|
( n a -- )
|
||||||
|
@ -32,5 +32,4 @@
|
|||||||
: 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 mostly referred to directly as their numerical offset
|
not. In fact, they're mostlly 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 blk's variables
|
+57 FUTURE USES
|
||||||
+59 z80a's variables
|
+59 z80a's variables
|
||||||
+5b FUTURE USES
|
+5b FUTURE USES
|
||||||
+70 DRIVERS
|
+70 DRIVERS
|
||||||
|
124
usage.txt
Normal file
124
usage.txt
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
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