~vdupras/collapseos

ref: ac3629b817115b1af75096856ee9499cfbbd3392 collapseos/doc/primer.txt -rw-r--r-- 7.9 KiB
ac3629b8Virgil Dupras Make BLK@* and BLK!* into ialiases 1 year, 10 months ago
`                                                                                `
```1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250```
```# Forth Primer

# First steps

Before you read this primer, let's try a few commands, just for
fun.

42 .

This will push the number 42 to the stack, then print the number
at the top of the stack.

4 2 + .

This pushes 4, then 2 to the stack, then adds the 2 numbers on
the top of the stack, then prints the result.

42 0x8000 C! 0x8000 C@ .

This writes the byte "42" at address 0x8000, and then reads
back that byte from the same address and print it.

# Interpreter loop

Forth's main interpeter loop is very simple:

1. Read a word from input
2. Look it up in the dictionary
3. Found? Execute.
4.1. Is it a number?
4.2. Yes? Parse and push on the Parameter Stack.
4.3. No? Error.
5. Repeat

# Word

A word is a string of non-whitepace characters. We consider that
we're finished reading a word when we encounter a whitespace
after having read at least one non-whitespace character.

# Character encoding

Collapse OS doesn't support any other encoding than 7bit ASCII.
A character smaller than 0x21 is considered a whitespace,
others are considered non-whitespace.

Characters above 0x7f have no special meaning and can be used in
words (if your system has glyphs for them).

# Dictionary

Forth's dictionary link words to code. On boot, this dictionary
contains the system's words (look in dict.txt for a list of
them), but you can define new words with the ":" word. For
example:

: FOO 42 . ;

defines a new word "FOO" with the code "42 ." linked to it. The
word ";" closes the definition. Once defined, a word can be
executed like any other word.

You can define a word that already exists. In that case, the new
definition will overshadow the old one. However, any word def-
ined *before* the overshadowing took place will still use the
old word.

# Cell size

The cell size in Collapse OS is 16 bit, that is, each item in
stacks is 16 bit, @ and ! read and write 16 bit numbers.
Whenever we refer to a number, a pointer, we speak of 16 bit.

To read and write bytes, use C@ and C!.

# Number literals

Traditional Forth often uses HEX/DEC switches to go from deci-
mal to hexadecimal parsing. Collapse OS parses literals in a
way that is closer to C.

Straight numbers are decimals, numbers starting with "0x"
are hexadecimals (example "0x12ef"), "0b" prefixes indicate
binary (example "0b1010"), char literals are single characters
surrounded by ' (example 'X'). Char literals can't be used for
whitespaces.

# Parameter Stack

Unlike most programming languages, Forth execute words directly,
without arguments. The Parameter Stack (PS) replaces them. There
is only one, and we're constantly pushing to and popping from
it. All the time.

For example, the word "+" pops the 2 number on the Top Of Stack
(TOS), adds them, then pushes back the result on the same stack.
It thus has the "stack signature" of "a b -- n". Every word in
a dictionary specifies that signature because stack balance, as
you can guess, is paramount. It's easy to get confused so you
need to know the stack signature of words you use very well.

# Return Stack

There's a second stack, the Return Stack (RS), which is used to
keep track of execution, that is, to know where to go back after
we've executed a word. It is also used in other contexts, but
this is outside of the scope of this primer.

# Conditional execution

Code can be executed conditionally with IF/ELSE/THEN. IF pops
PS and checks whether its nonzero. If it is, it does nothing.
If it's zero, it jumps to the following ELSE or the following
THEN. Similarly, when ELSE is encountered in the context of a
nonzero IF, we jump to the following THEN.

Because IFs involve jumping, they only work inside word defin-
itions. You can't use IF directly in the interpreter loop.

Example usage:

: FOO IF 42 ELSE 43 THEN . ;
0 FOO --> 42
1 FOO --> 43

# Loops

Loops work a bit like conditionals, and there's 3 forms:

BEGIN..AGAIN --> Loop forever
BEGIN..UNTIL --> Loop conditionally
DO..LOOP --> Loop X times

UNTIL works exactly like IF, but instead of jumping forward to
THEN, it jumps backward to BEGIN.

DO pops the lower, then the higher bounds of the loop to be
executed, then pushes them on RS. Then, each time LOOP is
encountered, RS' TOS is increased. As long as the 2 numbers at
RS' TOS aren't equal, we jump back to DO.

The word "I" copies RS' TOS to PS, which can be used to get our
"loop counter".

Beware: the bounds arguments for DO are unintuitive. We begin
with the upper bound. Example:

42 0 DO I . SPC LOOP

Will print numbers 0 to 41, separated by a space.

# Memory access and HERE

We can read and write to arbitrary memory address with @ and !
(C@ and C! for bytes). For example, "1234 0x8000 !" writes the
word 1234 to address 0x8000. We call the @ and ! actions
"fetch" and "store".

There's a 3rd kind of memory-related action: "," (write). This
action stores value on PS at where a special variable called
"HERE" points to, and then advances HERE by 2 (there's also
"C," for bytes).

Note that the HERE word returns the address containing the
pointer (it doesn't change). There's a shortcut word for
"HERE @" named "H@", which is used much more often.

HERE is initialized at the first writable address in RAM, often
directly following the latest entry in the dictionary. Explain-
ing the "culture of HERE" is beyond the scope of this primer,
but know that it's a very important concept in Forth. For examp-
le, new word definitions are written to HERE.

# Variables

The word "VARIABLE" links a name to an address. For example,
"VARIABLE FOO" defines the word "FOO" and "reserves" 2 bytes of
memory. Then, when FOO is executed, it pushes the address of the
"reserved" area to PS.

For example, "1234 FOO !" writes 1234 to memory address reserved
for FOO.

Another way to create a variable is with the word CREATE, which
creates a variable entry without reserving anything for it: it's
your responsibility to reserve memory for it after you call it.
It can be useful for arrays. For example, look at VARIABLE's
definition:

: VARIABLE CREATE 2 ALLOT ;

# DOES>

Calling DOES> makes the newly created entry into a special
"does word" which behaves like a variable, that is, it pushes
the address of the "reserved" space to PS, but with additional
behavior attached to it.

DOES> must be called in the context of a word definition and
calling it stops the definition right there. Every word follow-
ing the DOES> is our new entry's behavior. For example, let's
look at CONSTANT's definition:

: CONSTANT CREATE , DOES> @ ;

A constant is created with "42 CONSTANT FOO" and FOO, instead
of putting FOO's address on PS, puts 42 on it.

You can see above that after we've created our FOO entry, we
write it to HERE and then assign the behavior "@" to it, which
means that it will transform the address currently on PS to its
value.

# IMMEDIATE

We approach the end of our primer. So far, we've covered the
"cute and cuddly" parts of the language. However, that's not
what makes Forth powerful. Forth becomes mind-bending when we
throw IMMEDIATE into the mix.

A word can be declared immediate thus:

: FOO ; IMMEDIATE

That is, when the IMMEDIATE word is executed, it makes the
latest defined word immediate.

An immediate word, when used in a definition, is executed
immediately instead of being compiled. This seemingly simple
mechanism (and it *is* simple) has very wide implications.

For example, The words "(" and ")" are comment indicators. In
the definition:

: FOO 42 ( this is a comment ) . ;

The word "(" is read like any other word. What prevents us from
trying to compile "this" and generate an error because the word
doesn't exist? Because "(" is immediate. Then, that word reads
from input stream until a ")" is met, and then returns to word
compilation.

Words like "IF", "DO", ";" are all regular Forth words, but
their "power" come from the fact that they're immediate.

Starting Forth by Leo Brodie explains all of this in detail.
Read this if you can. If you can't, well, let this sink in for
a while, browse the dictionary (dict.txt) and try to understand
why this or that word is immediate. Good luck!
```