|
|
|
@ -0,0 +1,723 @@
|
|
|
|
|
#+Starting Forth
|
|
|
|
|
|
|
|
|
|
This file contains my notes reading the book Starting Forth by Leo Brodie. In
|
|
|
|
|
this directory are also several files containing Forth source code. These are my
|
|
|
|
|
solutions to the problems found in the book.
|
|
|
|
|
|
|
|
|
|
* Chapter 1
|
|
|
|
|
** Some operations defined in this chapter
|
|
|
|
|
- spaces - ( n -- ) - prints n spaces ('15 spaces' will print 15 spaces to the
|
|
|
|
|
screen).
|
|
|
|
|
- emit - ( c -- ) - prints a number as an ascii character to the screen.
|
|
|
|
|
- : xxx yyy ; - ( -- ) - define new words like this: : star 42 emit ;
|
|
|
|
|
- cr - ( -- ) - print a carriage return to the screen (newline).
|
|
|
|
|
- ." <text> " - ( -- ) - print the string <text> to the screen.
|
|
|
|
|
- . - ( n -- ) - pop a number off the stack and print it.
|
|
|
|
|
- Forth features arithmetic with +, -, / and *.
|
|
|
|
|
|
|
|
|
|
** The dictionary
|
|
|
|
|
Forth has a "dictionary" where all the word definitions are stored (words =
|
|
|
|
|
functions). When you create a new word with : and ;, the definition is stored
|
|
|
|
|
in the dictionary under the given name.
|
|
|
|
|
|
|
|
|
|
** The text interpreter
|
|
|
|
|
When a word is entered, Forth will "activate a word called INTERPRET" which
|
|
|
|
|
will look up that word in the dictionary. If it finds a definition, it will
|
|
|
|
|
pass it onto another word called "EXECUTE" which will perform the
|
|
|
|
|
action. Otherwise, it will give it to the numbers-runner called "NUMBER" which
|
|
|
|
|
will push the word onto the stack if it is indeed a number. Otherwise, the
|
|
|
|
|
interpreter will throw an error. It's interesting that all the parts of the
|
|
|
|
|
compilation and interpretation process are described as simple words that are
|
|
|
|
|
part of the Forth system, I wonder if these are redefinable.
|
|
|
|
|
|
|
|
|
|
** Stack notation
|
|
|
|
|
Stack notation is used to communicate the effects a function has on the stack.
|
|
|
|
|
The basic form is like this: ( before -- after )
|
|
|
|
|
The left side indicates what should be on the stack before you execute a word,
|
|
|
|
|
the right side indicates the things that will be on the stack afterwards.
|
|
|
|
|
The stack notation for +, for example, is ( n1 n2 -- sum ).
|
|
|
|
|
The rightmost item on the left side is the top of the stack (in the previous
|
|
|
|
|
example, this means that n2 is on top of the stack).
|
|
|
|
|
|
|
|
|
|
** General thoughts
|
|
|
|
|
The end of the chapter features a review of terminology and the words defined
|
|
|
|
|
so far.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 2
|
|
|
|
|
** General notes
|
|
|
|
|
This chapter is mostly about stack based arithmetic and a few stack
|
|
|
|
|
operations. These are documented tersely and well in the book, so I won't
|
|
|
|
|
write about them here.
|
|
|
|
|
|
|
|
|
|
The non-desktructive stack print (.s) is already provided with gforth, but
|
|
|
|
|
that definition looks very inviting. I'm excited for when I can understand it.
|
|
|
|
|
|
|
|
|
|
I also find it's a bit difficult to decide on the proper stack layout for a
|
|
|
|
|
function. I guess it will come for practice (or pracicality; when you chain
|
|
|
|
|
multiple words together, I'm guessing there will become a preferred stack
|
|
|
|
|
order).
|
|
|
|
|
|
|
|
|
|
I find it weird to use /; I often forget which argument is the dividend and
|
|
|
|
|
which is the divisor. - is more straight forward, weirdly.
|
|
|
|
|
|
|
|
|
|
** "Problems"
|
|
|
|
|
1. The difference between 'dup dup' and '2dup' is that the former will produce
|
|
|
|
|
two copies of the value on top of the stack, while the latter will produce
|
|
|
|
|
a copy of both the second value of the stack as well as the first.
|
|
|
|
|
|
|
|
|
|
2. : 4reverse swap 2swap swap ;
|
|
|
|
|
|
|
|
|
|
3. : 3dup dup 2over rot ;
|
|
|
|
|
|
|
|
|
|
4. I start by factoring the expression like so:
|
|
|
|
|
a² + ab + c = a(a + b) + c
|
|
|
|
|
Then, the answer is trivial.
|
|
|
|
|
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
( c a b -- result )
|
|
|
|
|
: answer over + * + ;
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
5.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
( a b -- result )
|
|
|
|
|
: answer 2dup - rot rot + / ;
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
6.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
: homicide 20 + ;
|
|
|
|
|
: arson 10 + ;
|
|
|
|
|
: bookmaking 2 + ;
|
|
|
|
|
: tax-evasion 5 + ;
|
|
|
|
|
|
|
|
|
|
: convicted-of 0 ;
|
|
|
|
|
|
|
|
|
|
: will-serve . ." years" ;
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
7.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
( eggs -- )
|
|
|
|
|
: egg.cartons 6 /mod
|
|
|
|
|
cr . ." carton(s), with " . ." left-over egg(s).";
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 3
|
|
|
|
|
** General notes
|
|
|
|
|
The chapter starts off by talking about how redefining words doesn't actually
|
|
|
|
|
erase the previous definition, but adds a new entry at the "back of the
|
|
|
|
|
dictionary". This means you can use FORGET to remove the newest version of the
|
|
|
|
|
word and get back the old definition. I learned this when I made the forth
|
|
|
|
|
interpreter for Pansy Linux; the dictionary is a reversed linked list with
|
|
|
|
|
this very property.
|
|
|
|
|
|
|
|
|
|
The rest of the chapter describes the Forth file system as well as the
|
|
|
|
|
editor. Neither of these are available on modern systems, but it might be fun
|
|
|
|
|
to re-read this chapter for ideas if I ever want to create a virtual machine
|
|
|
|
|
operating system.
|
|
|
|
|
|
|
|
|
|
** Forget
|
|
|
|
|
As it turns out, gforth doesn't have forget! I'm switching implementation...
|
|
|
|
|
|
|
|
|
|
Looks like most implementations don't implement forget. pforth is the only one
|
|
|
|
|
I found so far... I guess I'll use that then?
|
|
|
|
|
|
|
|
|
|
NOTE: Forget not only erases the newest version of the word provided, it also
|
|
|
|
|
removes all other words defined after that word.
|
|
|
|
|
|
|
|
|
|
** The block system
|
|
|
|
|
The book describes how you can interface with files from the Forth
|
|
|
|
|
system. Source files can be stored in "blocks" of 16 lines with 64 characters
|
|
|
|
|
on each line (1024 bytes). To load, say, block 12, you type '12 load' at the
|
|
|
|
|
interpreter.
|
|
|
|
|
|
|
|
|
|
This sounds very neat and facilitates Forth as a system in addition to just a
|
|
|
|
|
programming language (and it sounds pretty aesthetic~. I know CollapseOS
|
|
|
|
|
adopted the block model from Forth).
|
|
|
|
|
|
|
|
|
|
** Style
|
|
|
|
|
There's some style tips here as well:
|
|
|
|
|
|
|
|
|
|
1. Separate the name of a word definition from the contents by three spaces.
|
|
|
|
|
|
|
|
|
|
2. Break definitions up into phrases, separated by double spaces.
|
|
|
|
|
|
|
|
|
|
3. If the definition takes more than one line, indent all but the first line.
|
|
|
|
|
|
|
|
|
|
4. Don't put more than one definition on a single line unless the definitions
|
|
|
|
|
are very short and logically related.
|
|
|
|
|
|
|
|
|
|
** Handy word (just one!)
|
|
|
|
|
depth ( -- n ) - Places the number of items on the stack onto the stack.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 4
|
|
|
|
|
** General notes
|
|
|
|
|
This chapter is about branching (if/else). It starts with number comparison
|
|
|
|
|
and the basic 'if' form which is like follows:
|
|
|
|
|
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
if <consequent> then <rest of the program>
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
The part after 'then' will always be executed. Special comparison words like =
|
|
|
|
|
will push a true value (which is -1 by the way) on the stack, which will be
|
|
|
|
|
popped off by the conditional check.
|
|
|
|
|
|
|
|
|
|
If can also take an else branch:
|
|
|
|
|
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
if <consequent> else <alternative> then <rest of the program>
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
The if form can only be used in a word definition, for some reason. Weird.
|
|
|
|
|
|
|
|
|
|
Not reverses the boolean on the stack.
|
|
|
|
|
|
|
|
|
|
If's can be nested, but you'll end up having to close off each if with a
|
|
|
|
|
respective then. This leads to weird situations ilke
|
|
|
|
|
if <something> else
|
|
|
|
|
if <something else> else
|
|
|
|
|
...
|
|
|
|
|
then then then then then ;
|
|
|
|
|
|
|
|
|
|
Turns out, any value other than 0 counts as true (like in C).
|
|
|
|
|
|
|
|
|
|
This means the logical or operator in Forth is simply +! Ah, but what if the
|
|
|
|
|
values are each other's inverse? If you add -1 and 1 you get 0, which is the
|
|
|
|
|
wrong answer. Because of this, Forth has the or operator which works like
|
|
|
|
|
you'd expect.
|
|
|
|
|
|
|
|
|
|
** New words
|
|
|
|
|
*** Words with built-in if
|
|
|
|
|
?dup - dup only if the argument is non-zero.
|
|
|
|
|
abort - abort execution and return to the interpreter, clearing the stack in
|
|
|
|
|
the process.
|
|
|
|
|
The book mentions ?stack for checking stack underflows, but it doesn't exist
|
|
|
|
|
in my implementation (fforth). I guess it can be implemented with depth and 0=.
|
|
|
|
|
*** Logical operators
|
|
|
|
|
(self explanatory)
|
|
|
|
|
and
|
|
|
|
|
or
|
|
|
|
|
*** Comparing numbers
|
|
|
|
|
(these are self explanatory)
|
|
|
|
|
**** Two arguments
|
|
|
|
|
=
|
|
|
|
|
<
|
|
|
|
|
>
|
|
|
|
|
**** One argument
|
|
|
|
|
0= - this one is actually equivalent to not, but may be used for readability.
|
|
|
|
|
0<
|
|
|
|
|
0>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
** "Problems"
|
|
|
|
|
1. Note - My implementation uses -1 as true, instead of 1 as the book.
|
|
|
|
|
1. 1 (not not 1 = -1)
|
|
|
|
|
2. 0 (not not 0 = 0)
|
|
|
|
|
3. 1 (not not 200 = not 0 = -1)
|
|
|
|
|
|
|
|
|
|
2. Huh?
|
|
|
|
|
|
|
|
|
|
3.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
: card 17 > if ." Alcoholic beverages permitted" else ." Under age" then ;
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
4.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
: sign.test dup 0= if ." ZERO" else
|
|
|
|
|
dup 0< if ." NEGATIVE" else
|
|
|
|
|
." POSITIVE" then then drop ;
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
5. Simply wrapping the loop in an if statement works!
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
: stars dup if 0 do 42 emit loop then ;
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
6. Probably not a good solution, but it works well.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
( n low-limit hi-limit -- )
|
|
|
|
|
: within rot ( l h n )
|
|
|
|
|
swap ( l n h )
|
|
|
|
|
over ( l n h n )
|
|
|
|
|
swap ( l n n h )
|
|
|
|
|
< ( l n t/f )
|
|
|
|
|
rot rot ( t/f l n )
|
|
|
|
|
<=
|
|
|
|
|
and
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
( The version provided by the book is much nicer )
|
|
|
|
|
: <rot rot rot ;
|
|
|
|
|
: within <rot over > not <rot > and ;
|
|
|
|
|
( But my implementation doesn't have not )
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
7.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
: guess 2dup = if ." CORRECT" drop else
|
|
|
|
|
2dup < if ." TOO HIGH" else
|
|
|
|
|
." TOO LOW" then then drop ;
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
8.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
: n= over = ;
|
|
|
|
|
|
|
|
|
|
: speller dup if
|
|
|
|
|
dup 0< if ." NEGATIVE " then
|
|
|
|
|
abs 1 n= if ." ONE" else
|
|
|
|
|
2 n= if ." TWO" else
|
|
|
|
|
3 n= if ." THREE" else
|
|
|
|
|
4 n= if ." FOUR" else
|
|
|
|
|
." OUT OF RANGE" then then then then
|
|
|
|
|
else ." ZERO" then drop ;
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
9.
|
|
|
|
|
#+begin_src forth
|
|
|
|
|
( TODO )
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 5
|
|
|
|
|
** General notes
|
|
|
|
|
This chapter starts off by introducing a few new arithmetic words and the
|
|
|
|
|
return stack along with a few words for manipulating this. The return stack
|
|
|
|
|
looks really powerful for easing the stack manipulation bit of Forth.
|
|
|
|
|
|
|
|
|
|
One thing to note is that the return stack must be empty before the end of a
|
|
|
|
|
word definition.
|
|
|
|
|
|
|
|
|
|
The chapter also describes how to deal with floating point numbers in
|
|
|
|
|
Forth. Don't. Instead, use fixed-point numbers but externally represent them
|
|
|
|
|
as floating points. Generally, instead of thinking of expressions like
|
|
|
|
|
(x / y) * z, translate them into (x * z) / y for greater accuracy (which uses
|
|
|
|
|
the */ word in Forth). In general, one can use the expression 3 2 */ to mean
|
|
|
|
|
rational numbers (3/2 in this case) which you can multiply some other
|
|
|
|
|
fixed-point number.
|
|
|
|
|
** New words
|
|
|
|
|
*** Arithmetic
|
|
|
|
|
These are self explanatory.
|
|
|
|
|
1+
|
|
|
|
|
1-
|
|
|
|
|
2+
|
|
|
|
|
2-
|
|
|
|
|
2*
|
|
|
|
|
2/
|
|
|
|
|
*** Miscellaneous math
|
|
|
|
|
abs ( n -- |n| )
|
|
|
|
|
negate ( n -- -n )
|
|
|
|
|
min ( n1 n2 -- n-min )
|
|
|
|
|
max ( n1 n2 -- n-max )
|
|
|
|
|
*** Return stack manipulation
|
|
|
|
|
>R ( n -- ) - Pops the first value off the stack and pushes it to the
|
|
|
|
|
parameter stack.
|
|
|
|
|
R> ( -- n ) - The opposite
|
|
|
|
|
I ( -- n ) - Copies the first item off the return stack
|
|
|
|
|
I' ( -- n ) - --"-- second --"--
|
|
|
|
|
J ( -- n ) - --"-- third --"--
|
|
|
|
|
*** Scaling operators
|
|
|
|
|
*/ ( x y z -- x*y/z ) - This is a word which uses a 32-bit integer as the
|
|
|
|
|
intermediate result of mutliplying x by y. This makes it better to use for
|
|
|
|
|
scaling than simply * and / alone.
|
|
|
|
|
*/mod ( x y z -- remainder ) - The same as /*, but returns the remainder
|
|
|
|
|
rather than the quotient.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 6
|
|
|
|
|
** General thoughts
|
|
|
|
|
This chapter is all about looping!
|
|
|
|
|
** Handy hint
|
|
|
|
|
Typing in an unrecognised word will clear the stack since the interpreter
|
|
|
|
|
will call abort.
|
|
|
|
|
|
|
|
|
|
Making quit the last word of a word definition will silence the "ok."
|
|
|
|
|
** Looping constructs
|
|
|
|
|
*** Definite loops -- do...loop
|
|
|
|
|
This is your basic for loop in other languages. The syntax is the following:
|
|
|
|
|
limit index do ... loop
|
|
|
|
|
So you put the limit first, then the starting index, then do, the body and
|
|
|
|
|
finally loop.
|
|
|
|
|
The do loop works by first pushing the limit and the index onto the return
|
|
|
|
|
stack and incrementing the index until it is the same as the limit. This
|
|
|
|
|
means you can use the i word to get a copy of the index at any given moment
|
|
|
|
|
in the loop!
|
|
|
|
|
Since we can nest loops, the words i and j make a bit more sense. You can
|
|
|
|
|
use j to access the index of the outer loop and i to access the index of the
|
|
|
|
|
inner one.
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < 10; j++)
|
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
Looks familiar?
|
|
|
|
|
|
|
|
|
|
+loop can make the index go up by a specific increment. It pops a value off
|
|
|
|
|
the stack and increments the index by that number.
|
|
|
|
|
|
|
|
|
|
Do loops terminate when (going up) the index reaches or passes the limit and
|
|
|
|
|
(when going down) it passes the limit. A do loop always executes at least
|
|
|
|
|
once.
|
|
|
|
|
|
|
|
|
|
One can use the word leave to set the limit to equal the index, so that the
|
|
|
|
|
loop will immediately terminate on the next time loop is executed. This can
|
|
|
|
|
be useful for early exits.
|
|
|
|
|
*** Indefinite loops
|
|
|
|
|
Indefinite loops are like a do while loop, they loop if a condition is true
|
|
|
|
|
by the end of the body execution.
|
|
|
|
|
|
|
|
|
|
The basic indefinite loop is on the form:
|
|
|
|
|
|
|
|
|
|
begin xxx f until
|
|
|
|
|
|
|
|
|
|
Where xxx is the body of the loop and f is the boolean flag indicating if
|
|
|
|
|
the loop should, well, loop.
|
|
|
|
|
|
|
|
|
|
A simple infinite loop can then be defined like so:
|
|
|
|
|
|
|
|
|
|
begin xxx 0 until
|
|
|
|
|
|
|
|
|
|
There's another form of the loop as well, with syntax like this:
|
|
|
|
|
|
|
|
|
|
begin xxx f while yyy repeat
|
|
|
|
|
|
|
|
|
|
This one's weird. It performs xxx, then checks for a boolean true. If it's
|
|
|
|
|
true, it performs yyy and repeats again. The begin...until loop will loop on
|
|
|
|
|
the opposite condition. That is, it will only loop if the condition is
|
|
|
|
|
false.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 7
|
|
|
|
|
** General notes
|
|
|
|
|
This chapter is about number representations, like unsigned, double width,
|
|
|
|
|
hexadecimal etc.
|
|
|
|
|
|
|
|
|
|
Turns out that the and and or words defined earlier are actually bitwise
|
|
|
|
|
operators. They work fine as logical and/or regardless.
|
|
|
|
|
** Double length numbers
|
|
|
|
|
To push a double length number onto the stack, use punctuation in the number.
|
|
|
|
|
Punctuation works with , and . and a few other characters.
|
|
|
|
|
So 4.000.000.000.000.000.000.000 pushes that number as two cells.
|
|
|
|
|
** Number formatting
|
|
|
|
|
One can create number formatting words with the special words <# and #>. The
|
|
|
|
|
book has good sections on this, I won't bother trying to word them myself
|
|
|
|
|
here. NOTE! These only work on unsigned, double length words. To make them
|
|
|
|
|
work on single length words, put a 0 on the stack before calling it.
|
|
|
|
|
|
|
|
|
|
This is a sort of DSL, kinda like format in Common Lisp. I wonder if there
|
|
|
|
|
are more of these in Forth, and how easy it is to make one...
|
|
|
|
|
** New words
|
|
|
|
|
*** Unsigned number manipulation
|
|
|
|
|
The numbers on the stack are both unsigned and signed at the same time. The
|
|
|
|
|
programmer decides what representation they are by the operations performed
|
|
|
|
|
on them. Most arithmetic operations have an unsigned equivalent:
|
|
|
|
|
u. ( u -- ) Prints the unsigned number u.
|
|
|
|
|
u*, u/mod, u< does what you'd expect.
|
|
|
|
|
do ... /loop is your normal loop, but with unsigned index, limit and
|
|
|
|
|
increment. It takes an increment value, like +loop.
|
|
|
|
|
*** Changing the base
|
|
|
|
|
One can change the base of the program with the words hex (hexadecimal),
|
|
|
|
|
octal and decimal.
|
|
|
|
|
You can also change the base to whatever you please with 'n base !'. That
|
|
|
|
|
means you can declare : binary 2 base ! ; and see numbers in binary form!
|
|
|
|
|
That's pretty cool.
|
|
|
|
|
*** Double length numbers
|
|
|
|
|
d. ( d -- ) - Prints a signed, double length number.
|
|
|
|
|
d+, d-, dnegate, dabs, dmax, dmin, d=, d0=, d<, du< do what you'd expect.
|
|
|
|
|
d.r ( d width -- ) - Print the signed 32 bit number, right justified within
|
|
|
|
|
the field width.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 8
|
|
|
|
|
** General notes
|
|
|
|
|
This chapter is called "Variables, constants and arrays". My normie
|
|
|
|
|
programming brain thinks these are good things, and will hopefully make the
|
|
|
|
|
code a bit more readable?
|
|
|
|
|
** Variables
|
|
|
|
|
You declare a variable like so:
|
|
|
|
|
|
|
|
|
|
variable <var>
|
|
|
|
|
|
|
|
|
|
One can set the variable like so:
|
|
|
|
|
|
|
|
|
|
<value> <var> !
|
|
|
|
|
|
|
|
|
|
This means that the earlier base changing trick (<num> base !) is actually
|
|
|
|
|
just changing a global "base" variable!
|
|
|
|
|
|
|
|
|
|
! is pronounced "store".
|
|
|
|
|
|
|
|
|
|
@ is the opposite of !, and is used for getting the values stored in a
|
|
|
|
|
variable. It's pronounced "fetch" and works thusly:
|
|
|
|
|
|
|
|
|
|
<variable> @
|
|
|
|
|
|
|
|
|
|
Printing the value of a variable (<var> @ .) is so common that Forth has a
|
|
|
|
|
separate word for it, ?.
|
|
|
|
|
|
|
|
|
|
Since variables are accessed at run time, it's good to store often changed
|
|
|
|
|
values there instead of in words since words that use those words would have
|
|
|
|
|
to be recompiled to work with the new definition. Variable declaration is
|
|
|
|
|
similar to word declaration, but it compiles a new word which when called
|
|
|
|
|
pushes its value address onto the stack. That's how <value> <var> ! works, it
|
|
|
|
|
pushes a value, pushes an address, and stores that value into that address.
|
|
|
|
|
** Constants
|
|
|
|
|
Constants are declared like this:
|
|
|
|
|
|
|
|
|
|
<value> constant <name>
|
|
|
|
|
|
|
|
|
|
The value can be retrieved by executing the constant name.
|
|
|
|
|
** Arrays
|
|
|
|
|
Arrays are simply variables with extra memory. What does that mean? To make
|
|
|
|
|
an array called "arr", you do this:
|
|
|
|
|
|
|
|
|
|
variable arr 8 allot
|
|
|
|
|
|
|
|
|
|
The "8 allot" part means that we want arr to contain 8 additional bytes, that
|
|
|
|
|
is, 1 extra cell on modern machines.
|
|
|
|
|
|
|
|
|
|
The new fields can be accessed with simple pointer arithmetic.
|
|
|
|
|
|
|
|
|
|
2 arr 8 + ! - Sets the second element of the array to 2 (on a 64-bit machine).
|
|
|
|
|
|
|
|
|
|
You can make an array with initial elements using create.
|
|
|
|
|
|
|
|
|
|
create xs 1 , 2 , 3 ,
|
|
|
|
|
|
|
|
|
|
will create an array called xs with 1, 2 and 3 as its initial elements. They
|
|
|
|
|
are each a cell big. Create creates an array which can be manipulated as
|
|
|
|
|
normal (the book has a notice for polyFORTH, but I don't think it's
|
|
|
|
|
relevant).
|
|
|
|
|
** Byte arrays
|
|
|
|
|
In addition to arrays, Forth also has byte arrays which operate on bytes
|
|
|
|
|
instead of cells. I think this one's probably better for portability since
|
|
|
|
|
you don't have to consider the cell size on different systems when using
|
|
|
|
|
it. You also don't have to double the index of an array since each value
|
|
|
|
|
correspond to a single byte.
|
|
|
|
|
|
|
|
|
|
Byte arrays can also be created with initial values, like you can for normal
|
|
|
|
|
arrays with create. The difference is that you use c, instead of ,.
|
|
|
|
|
|
|
|
|
|
create bytes 1 c, 2 c, 3 c,
|
|
|
|
|
** New words
|
|
|
|
|
!, @ and ? are described in the variables section.
|
|
|
|
|
+! ( n adr -- ) - Increments the value at adr with n.
|
|
|
|
|
Constant and variable operations can be prefixed with a 2 to mean a
|
|
|
|
|
double-length variable/constant.
|
|
|
|
|
fill ( adr n b -- ) - Fill n bytes of memory beginning at the address with
|
|
|
|
|
value b.
|
|
|
|
|
erase ( adr n -- ) - Like '<adr> <n> 0 fill'
|
|
|
|
|
c! and c@ are like ! and @ but for byte values.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 9
|
|
|
|
|
** General notes
|
|
|
|
|
This looks to be a very interesting chapter. It's all about how Forth works
|
|
|
|
|
internally, which is extremely captivating.
|
|
|
|
|
|
|
|
|
|
In the section "vectored execution" the book goes over a powerful
|
|
|
|
|
strategy. Since the ' word gives the address of a word, you can store it in a
|
|
|
|
|
variable. Then, later, you can execute this word with <var> @ execute. This
|
|
|
|
|
means you can have a word have different meanings in different parts of the
|
|
|
|
|
program! This makes it possible to compile words and place them into
|
|
|
|
|
variables, which bypasses the need for recompiling every word that uses that
|
|
|
|
|
word.
|
|
|
|
|
** Dictionary entries
|
|
|
|
|
Dictionary entries are described in the book. They have the following
|
|
|
|
|
structure:
|
|
|
|
|
|
|
|
|
|
Name
|
|
|
|
|
Link (to previous word, the dictionary is a backwards linked list)
|
|
|
|
|
Code pointer
|
|
|
|
|
Parameter field
|
|
|
|
|
|
|
|
|
|
The code pointer is what makes variables, constants and colon definitions
|
|
|
|
|
different. Variables have a code pointer pointing to code which pushes the
|
|
|
|
|
variable's address onto the stack, and constants have a similar one but
|
|
|
|
|
pushes a value rather than an address. For other words, it points to machine
|
|
|
|
|
code to be executed when the word is invoked.
|
|
|
|
|
|
|
|
|
|
The parameter field contains data, and the size of it depends on the type of
|
|
|
|
|
variable, constant or colond definition. Normal variables/constants have a 1
|
|
|
|
|
cell sized parameter field, 2constant/2variable declares a constant/variable
|
|
|
|
|
with a 2 cell sized parameter field and colon definitions can have
|
|
|
|
|
arbitrarily long parameter fields. The address provided by tick and used by
|
|
|
|
|
execute is the parameter field address (pfa).
|
|
|
|
|
|
|
|
|
|
The name and the link is called the 'header' and the code pointer and the
|
|
|
|
|
parameter field is called the 'body'.
|
|
|
|
|
|
|
|
|
|
In colon defined words, the parameter field contaiins the addresses of the
|
|
|
|
|
words that comprise the defintion. These addresses are called code field
|
|
|
|
|
addresses.
|
|
|
|
|
** How colon defined words work
|
|
|
|
|
At the end of any colon defined word sits a call to exit. When executing a
|
|
|
|
|
word, the interpreter will first push the current interpreter pointer to the
|
|
|
|
|
return stack. Exit will pop this address and return to it. Therefore, you can
|
|
|
|
|
change where a colon defined word will return to by manipulating the return
|
|
|
|
|
stack directly! This also means that you can use exit as an 'early return' to
|
|
|
|
|
a word.
|
|
|
|
|
** The pad
|
|
|
|
|
The pad is some memory location a fixed place after here which is used for
|
|
|
|
|
text processing. Since it starts a fixed point after here, it moves as
|
|
|
|
|
definitions are added.
|
|
|
|
|
** The parameter stack
|
|
|
|
|
The parameter stack (remember, this is the thing referred to as just "the
|
|
|
|
|
stack") resides far above the pad in memory. The stack is in reality just
|
|
|
|
|
memory with a pointer that changes as we push and pop items off it. The stack
|
|
|
|
|
grows downwards, that is it begins at high memory and grows towards low
|
|
|
|
|
memory. The stack pointer memory value can be fetched with 's. The current
|
|
|
|
|
top value of the stack is therefore the same as 's @ (which is the same as
|
|
|
|
|
dup!). The point right before the bottom of the stack is pointed to by s0
|
|
|
|
|
(which doesn't seem to be defined in gforth!). 's, s0 and h are not required
|
|
|
|
|
by the standard, which explains why they're not available in gforth.
|
|
|
|
|
** Input buffer
|
|
|
|
|
The text retrieved from the command line interface is stored in the input
|
|
|
|
|
message buffer, which is located just above the stack and grows upwards.
|
|
|
|
|
** Return stack
|
|
|
|
|
Above the input buffer is the return stack. It works the same as the
|
|
|
|
|
parameter stack, but doesn't have words like 's and s0 defined for it.
|
|
|
|
|
** User variables
|
|
|
|
|
Variables available to the user (like base) is stored above the return
|
|
|
|
|
stack. User variables are different from the ones defined by the user (with
|
|
|
|
|
variable). They're kept in an array called the "user table". This was
|
|
|
|
|
probably more relevant back in the day with multi-user systems, but each user
|
|
|
|
|
has their own user table, meaning that if one person changes a base, it won't
|
|
|
|
|
affect others.
|
|
|
|
|
** Vocabularies
|
|
|
|
|
Vocabularies work as namespaces for word definitions. The book describes
|
|
|
|
|
three vocabularies, the standard Forth vocabulary, editor (not present on
|
|
|
|
|
modern systems) and assembly (which allows for making words in the computers
|
|
|
|
|
assembly language??). Now I want to know how to use this assembly
|
|
|
|
|
vocabulary. Vocabularies are just a part of the dictionary, but words of
|
|
|
|
|
particular vocabularies only point to words of the same vocabulary in the
|
|
|
|
|
dictionary. The current vocabulary is called the 'context' (it can be
|
|
|
|
|
accessed with the word context, but it's just an address). The current
|
|
|
|
|
vocabulary new words are linked to is found in the variable current. To
|
|
|
|
|
change current, use the word definitions (which will take the current context
|
|
|
|
|
and put it in current).
|
|
|
|
|
** New words
|
|
|
|
|
' ( -- adr ) - Takes the next word of the input stream and pushes the address onto the
|
|
|
|
|
stack. Since ' takes the *next word in the input stream* you can do stuff
|
|
|
|
|
like
|
|
|
|
|
|
|
|
|
|
: set ' <var> ! ;
|
|
|
|
|
|
|
|
|
|
And invoke it like "set <word>". The input stream is visible, even to words.
|
|
|
|
|
|
|
|
|
|
dump ( adr n -- ) - Prints the first n bytes of data found at adr.
|
|
|
|
|
|
|
|
|
|
['] - Takes the next word inside the definition and puts the address on the
|
|
|
|
|
stack. It ignores the input stream, which will be handy.
|
|
|
|
|
|
|
|
|
|
h ( -- adr ) - Returns the current address in the dictionary. This increases
|
|
|
|
|
as more words are defined.
|
|
|
|
|
|
|
|
|
|
here ( -- n ) - The same as h @. You can use this to see how much memory
|
|
|
|
|
something takes by first performing here, defining something and comparing
|
|
|
|
|
the new value of here to the one on the stack.
|
|
|
|
|
|
|
|
|
|
, is explained to be the same as 'here ! 2 allot'
|
|
|
|
|
|
|
|
|
|
user - define a new user variable.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 10
|
|
|
|
|
** General notes
|
|
|
|
|
Ah, a practical chapter (it's about i/o).
|
|
|
|
|
|
|
|
|
|
It starts off with some (now) irrelevant information about blocks.
|
|
|
|
|
|
|
|
|
|
It's a difficult chapter to read, since half the information is in this way
|
|
|
|
|
irrelevant.
|
|
|
|
|
** "New" words (some old)
|
|
|
|
|
*** Output
|
|
|
|
|
emit ( c -- ) - Prints a value on the stack as an ascii character (using the
|
|
|
|
|
lower byte).
|
|
|
|
|
|
|
|
|
|
type ( adr n -- ) - Prints n bytes starting at adr. Used for printing
|
|
|
|
|
strings.
|
|
|
|
|
*** Input
|
|
|
|
|
key ( -- c ) - Waits for a key to be pressed, then pushes that ascii value
|
|
|
|
|
onto the stack. NOTE: This works pretty bad in emacs since it doesn't
|
|
|
|
|
support terminal codes that well.
|
|
|
|
|
|
|
|
|
|
expect ( adr u -- ) - Reads u bytes as a string and stores it at adr.
|
|
|
|
|
|
|
|
|
|
word ( c -- adr ) - Read a word from the input, using c as a delimiter. The
|
|
|
|
|
address returned has the count as the first value and the string follows.
|
|
|
|
|
|
|
|
|
|
text ( c -- ) - Reads a string using c as a delimiter, but places the string
|
|
|
|
|
in the pad. Text is non-standard and doesn't exist in gforth.
|
|
|
|
|
|
|
|
|
|
query ( -- ) - Expect, but read 80 chars and store them in the input message
|
|
|
|
|
buffer. The input has to be valid character.
|
|
|
|
|
*** Memory manipulation
|
|
|
|
|
move ( adr1 adr2 u -- ) - Copies u cells from adr1 to adr2.
|
|
|
|
|
|
|
|
|
|
cmove ( adr1 adr2 u -- ) - Like move, but for bytes instead of cells.
|
|
|
|
|
*** String operations
|
|
|
|
|
count ( adr -- adr+1 u ) - Takes the address of a string and returns the
|
|
|
|
|
length of the string (u) as well as the incremented address.
|
|
|
|
|
|
|
|
|
|
-text ( adr1 u adr2 -- f ) - Compares two strings of length u (adr1 and
|
|
|
|
|
adr2).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Chapter 11
|
|
|
|
|
** General notes
|
|
|
|
|
What is this chapter about? Metaprogramming? The chapter name is "extending
|
|
|
|
|
the compiler" which makes my Lisp brain tingle.
|
|
|
|
|
** Compile time and run time
|
|
|
|
|
One of the first things the chapter tackles is the difference between run
|
|
|
|
|
time and compile time. You see, some words have both run time behaviour and
|
|
|
|
|
compile time behaviour. These types of words fall in one of two categories;
|
|
|
|
|
defining words and compiling words.
|
|
|
|
|
|
|
|
|
|
Constant is a defining word, as it takes an identifier at compile time and
|
|
|
|
|
exhibits run time behaviour in that, every time you call that word, you get
|
|
|
|
|
a value on the stack.
|
|
|
|
|
|
|
|
|
|
A compiling word is a word used inside a colon definition which does
|
|
|
|
|
different stuff when the word is defined from what is shown at run time. For
|
|
|
|
|
example, ." will at compile time embed the string into the word's parameter
|
|
|
|
|
field, but at run time print the string. Other examples include if and loop.
|
|
|
|
|
|
|
|
|
|
The first example of metaprogramming uses create and does>. I'm not really
|
|
|
|
|
seeing the power yet, and it still looks a bit limiting.
|
|
|
|
|
|
|
|
|
|
The next section covers how to change the behaviour of the colon
|
|
|
|
|
compiler. Now we're talking! Any word can be made immediate (word that will
|
|
|
|
|
be executed at definition time) by simply placing the immediate word behind a
|
|
|
|
|
definition.
|
|
|
|
|
|
|
|
|
|
: say-hello ." Hello " ; immediaten
|
|
|
|
|
|
|
|
|
|
Now, if this word is placed in a colon definition, it will execute at compile
|
|
|
|
|
time rather than run time. It can still be executed interactively.
|
|
|
|
|
** New words
|
|
|
|
|
does> ( -- pfa ) - Marks the end of the compile time behaviour in a definition and
|
|
|
|
|
the start of the run time behaviour it will exhibit. It pushes the pfa of the
|
|
|
|
|
word defined onto the stack.
|
|
|
|
|
|
|
|
|
|
compile ( -- ) - Used in immediate definitions. Places the following words
|
|
|
|
|
address inside the definition, making it possible to describe run time
|
|
|
|
|
behaviour. Not available in gforth.
|
|
|
|
|
|
|
|
|
|
[compile] ( -- ) - Used in definitions to move the compile time behaviour of
|
|
|
|
|
an immediate word to run time.
|
|
|
|
|
|
|
|
|
|
literal Compile time ( n -- ) Run time ( -- n ) - Compiles a literal value on
|
|
|
|
|
the stack by embedding the run time code for pushing the value onto the stack
|
|
|
|
|
and the value itself inside a definition.
|
|
|
|
|
|
|
|
|
|
[ ( -- ) - Stop compilation and start executing words.
|
|
|
|
|
|
|
|
|
|
] ( -- ) - Start compilation mode again. These words can be used in
|
|
|
|
|
conjuction with literal to do i.e. arithmetic at compile time.
|