mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 10:20: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
|
||||
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
|
||||
|
||||
* `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
|
||||
[listserv]: http://lists.sonic.net/mailman/listinfo/collapseos
|
||||
[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
|
||||
/*/*-bin.h
|
||||
/*/*.bin
|
||||
/blkfs
|
||||
|
@ -1,5 +1,4 @@
|
||||
TARGETS = forth/forth
|
||||
BIN2C = ../tools/bin2c
|
||||
# Those Forth source files are in a particular order
|
||||
BOOTSRCS = ./forth/conf.fs \
|
||||
../forth/xcomp.fs \
|
||||
@ -10,11 +9,13 @@ BOOTSRCS = ./forth/conf.fs \
|
||||
./forth/xstop.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
|
||||
OBJS = emul.o libz80/libz80.o
|
||||
SLATEST = ../tools/slatest
|
||||
STRIPFC = ../tools/stripfc
|
||||
BIN2C = ../tools/bin2c
|
||||
BLKPACK = ../tools/blkpack
|
||||
|
||||
.PHONY: all
|
||||
all: $(TARGETS)
|
||||
@ -22,6 +23,7 @@ all: $(TARGETS)
|
||||
$(STRIPFC):
|
||||
$(SLATEST):
|
||||
$(BIN2C):
|
||||
$(BLKPACK):
|
||||
$(MAKE) -C ../tools
|
||||
|
||||
# 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
|
||||
$(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 $@
|
||||
|
||||
libz80/libz80.o: libz80/z80.c
|
||||
|
@ -11,10 +11,15 @@
|
||||
// This binary is also used for automated tests and those tests, when
|
||||
// failing, send a non-zero value to RET_PORT to indicate failure
|
||||
#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 FILE *fp;
|
||||
static FILE *blkfp;
|
||||
static int retcode = 0;
|
||||
static uint16_t blkid = 0;
|
||||
|
||||
static uint8_t iord_stdio()
|
||||
{
|
||||
@ -39,6 +44,27 @@ static void iowr_ret(uint8_t 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[])
|
||||
{
|
||||
@ -67,11 +93,14 @@ int main(int argc, char *argv[])
|
||||
fprintf(stderr, "Usage: ./forth [filename]\n");
|
||||
return 1;
|
||||
}
|
||||
blkfp = fopen("blkfs", "r+");
|
||||
Machine *m = emul_init();
|
||||
m->ramstart = RAMSTART;
|
||||
m->iord[STDIO_PORT] = iord_stdio;
|
||||
m->iowr[STDIO_PORT] = iowr_stdio;
|
||||
m->iowr[RET_PORT] = iowr_ret;
|
||||
m->iord[BLK_PORT] = iord_blk;
|
||||
m->iowr[BLK_PORT] = iowr_blk;
|
||||
// initialize memory
|
||||
for (int i=0; i<sizeof(KERNEL); i++) {
|
||||
m->mem[i] = KERNEL[i];
|
||||
@ -89,6 +118,7 @@ int main(int argc, char *argv[])
|
||||
tcsetattr(0, TCSAFLUSH, &termInfo);
|
||||
emul_printdebug();
|
||||
}
|
||||
fclose(blkfp);
|
||||
fclose(fp);
|
||||
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
|
||||
1 +
|
||||
LOOP
|
||||
LF
|
||||
CRLF
|
||||
;
|
||||
|
||||
( n a -- )
|
||||
|
@ -32,4 +32,5 @@
|
||||
: BS 8 EMIT ;
|
||||
: LF 10 EMIT ;
|
||||
: CR 13 EMIT ;
|
||||
: CRLF CR LF ;
|
||||
: 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
|
||||
by their address in memory throughout the code. The place where they live is
|
||||
configurable by the RAMSTART constant in conf.fs, but their relative offset is
|
||||
not. In fact, they're mostlly referred to directly as their numerical offset
|
||||
not. In fact, they're mostly referred to directly as their numerical offset
|
||||
along with a comment indicating what this offset refers to.
|
||||
|
||||
This system is a bit fragile because every time we change those offsets, we
|
||||
@ -91,7 +91,7 @@ RAMSTART INITIAL_SP
|
||||
+51 CURRENTPTR
|
||||
+53 readln's variables
|
||||
+55 adev's variables
|
||||
+57 FUTURE USES
|
||||
+57 blk's variables
|
||||
+59 z80a's variables
|
||||
+5b FUTURE USES
|
||||
+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