@@ -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 | |||
@@ -1,2 +1,3 @@ | |||
MASTER INDEX | |||
2 Documentation |
@@ -0,0 +1,4 @@ | |||
Documentation index | |||
3 Usage | |||
30 Dictionary |
@@ -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 |
@@ -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> @ ;" |
@@ -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. |
@@ -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.) |
@@ -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. |
@@ -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.) |
@@ -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 + &< <>} | |||
@@ -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.) |
@@ -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.) |
@@ -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. |
@@ -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 |
@@ -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.) |
@@ -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. |
@@ -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.) |
@@ -0,0 +1,3 @@ | |||
(cont.) | |||
~ - Container for native code. Usually not an executable word. | |||
? - Is it ...? (example: IMMED?) |
@@ -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.) |
@@ -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 |
@@ -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. |
@@ -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.) |
@@ -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.) |
@@ -0,0 +1,3 @@ | |||
(cont.) | |||
UNTIL f -- *I* Jump backwards to BEGIN if f is | |||
false. |
@@ -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 |
@@ -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 |
@@ -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. |
@@ -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! |
@@ -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 |
@@ -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 |
@@ -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. |
@@ -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.) |
@@ -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.) |
@@ -0,0 +1,9 @@ | |||
(cont.) | |||
There are also ascii const emitters: | |||
BS | |||
CR | |||
LF | |||
SPC | |||
@@ -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. |
@@ -1,213 +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 | |||
*** Disk *** | |||
LIST n -- Prints the contents of the block n on screen in the | |||
form of 16 lines of 64 columns. |
@@ -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. |