froth
attempts to stick close to the original
conventions of Froth. Let’s walk through the basics using the built-in
commandline.
library(froth)
## start the command line
froth()
You should see your console change to look like the following:
> library(froth)
> froth()
fr>
This fr>
signals that you’re currently in the
froth
environment. Let’s test it by pressing
ENTER
:
fr>
ok.
fr>
froth
will acknowledge successfully completed commands
with ok.
. In this case, we didn’t provide any commands, so
froth
will simply do nothing and acknowledge you.
Let’s try a new command:
fr> 45 emit
-ok.
Notice how the return is now -ok.
? This is because
froth
ran the commands 45
and
emit
before returning. 45
pushes the number 45
onto the stack, and emit
prints the top value of the stack
interpreting it as an ASCII value. 45 happens to correspond to a dash in
ASCII, so we get a dash printed!
Now, it would certainly be possible to print out lines by just repeating this command over and over:
fr> 45 emit 45 emit 45 emit 45 emit 45 emit
-----ok.
…but this gets a little tiresome. Instead, let’s tell
froth
a shortcut for when we want to print a dash:
fr> : DASH 45 emit ;
ok.
fr> DASH
-ok.
What happened here? The colon :
tells froth
that we’re starting a definition. The next word (DASH
)
gives the name for this definition, and the remaining words define what
it does. The semicolon ;
ends the definition. In this case,
we’re telling froth
: “whenever you see the word ‘dash’,
replace it with 45 emit
.” After that, we call
dash
and it prints out a dash. Note that words are not
case-sensitive; dash
, dAsH
, and
DASH
will all work. These names with associated definitions
are called words!
We can even make words that include words. This word prints out an arbitrary number of dashes (I’ll explain how it works later):
fr> : DASHES 0 do dash loop ;
ok.
fr> 10 dashes
----------ok.
Now, let’s make a word that prints out an ASCII letter
F
, for froth. We’re going to add in a few more new words:
CR
, which prints a new line, and 124 emit
,
which prints a pipe (|
).
fr> : PIPE 124 emit ;
ok.
fr> : LETTER_F 47 emit 4 dashes cr pipe 2 dashes cr pipe cr ;
/----
|--
|
ok.
It’s not as pretty as the asterisk F in Starting FORTH, but what can ya do.
The dictionary in FORTH is a list of words defined sequentially.
froth
initializes some words when it first loads, and then
user-defined words are added sequentially to it. When you input a line
of text, froth
splits the command by spaces and searches
for each word in the dictionary. If it finds the word, it executes the
related code. If not, it returns an error message.
This means you must separate each command by a space! For example:
fr> :star 42 emit ;
:star ?
froth
couldn’t understand this command, and responded
with :star ?
. This is because :star
isn’t a
defined word–in order to make this definition work, we have to
space-separate the colon from the other words.
fr> : star 42 emit ;
ok.
fr> star
*ok.
Emitting values character by character gets old pretty fast. We can
use the words ."
and "
to print whole
strings:
fr> ." Hello, world!"
Hello, world! ok.
Note the space separation between ."
and the string. The
ending "
is part of the string, so it doesn’t need to be
space separated.
froth
also has some special words. We encountered one of
these before when we input 45
–this put the number 45 onto
the stack. If froth
can’t find a word in the dictionary, it
checks to see if you’ve input a number. If so, it executes a special
command that puts it onto the stack.
You can redefine a word at any time by just writing a new definition
for it. froth
will always use the most recent definition
for a word that you’ve given it, but it remembers old ones. If you
wanted to go back to a previous definition, you can use the
FORGET
word.
fr> : add_two 2 + ;
ok.
fr> : add_two 2 2 + + ;
ok.
fr> 5 add_two .
9 ok.
fr> forget add_two
ok.
fr> 5 add_two
7 ok.
fr> forget add_two
ok.
fr> 5 add_two
add_two ?
Here we define add_two
twice–the first defines it as
adding two to a number, and the second as adding four. When we call
forget add_two
, it reverts to the first definition. Calling
it a second time removes the definition entirely.
If you’re curious what words are defined, you can list them all out
using the WORDS
words.
We’ve been talking a lot about the stack, but…what is it? If you’re unfamiliar with the stack data structure, the concept is essentially the same as a tower of bricks. Each time we “add” to the stack, we place a brick on top of the tower. Each time we remove from the stack, we have to take the brick off the top, otherwise the entire stack would fall apart! This means that the most recently added brick is always the first one we remove. This concept is typically referred to as “Last In, First Out” (LIFO).
When we talk about stacks, we use the word “push” to refer to adding an item to the stack, and “pop” to refer to taking an item off the stack.
Let’s showcase this with an example. We’re going to use a new word
called .
. The period takes the first element off the stack
and tells you what it was (it pops an item).
fr> 1
ok.
fr> 2
ok.
fr> 3
ok.
fr> . . .
3 2 1 ok.
Note how the last element we added (3) was the first element we got back. LIFO in action.
Stacks lend themselves well to a style of expression notation called
reverse Polish notation (RPN), also called postfix
notation. Most people are used to infix notation, in
which operators are found in between their operands. This means that if
you wanted to add two numbers a
and b
, you’d
write a + b
. Postfix notation instead puts the operator
after the operands, meaning that we’d write the sum of
a
and b
as a b +
.
While this may feel unintuitive, it does have a number of benefits. First, postfix notation has no need for parentheses or order of operations; the operations define the order they should be applied. For example:
(infix) a * (b+c) = a b c + * (postfix)
(infix) (a*b) + (c*d) = a b * c d * + (postfix)
This works especially well for froth
, since we have a
stack already! If we write any operation in postfix notation, we’ll get
the result. Let’s try that with some simple arithmetic:
fr> : pop_result . cr ;
fr> 1 2 + pop_result
3
ok.
fr> 2 3 4 + * pop_result
14
ok.
fr> 2 3 * 4 5 * + pop_result
26
Walking through each of these expressions:
1 2 + => 1 + 2 + 3
2 3 4 + * => 2 * (3+4) = 2*7 = 14
2 3 * 4 5 * + => (2*3) + (4*5) = 6 + 20 = 26
What is actually going on under the hood? Let’s walk through
1 2 + pop_result
:
froth
reads 1
, and pushes a 1 onto the
stack.froth
reads 2
, and pushes a 2 onto the
stack.froth
reads +
.+
pops two items off the stack (2, 1).+
adds the two items it just popped (1 + 2).+
pushes the result of the operation (3) back onto the
stack.froth
reads pop_result
, and looks up the
definition (. cr
).pop_result
first calls .
, which pops the
first element of the stack (3).pop_result
then calls cr
, which prints a
new line.froth
sees no more commands, so it acknowledges with
ok.
A nice thing about postfix operators is we can implicitly act on whatever is on the stack. For example, it’s relatively easy to define a word that doubles whatever is on the stack:
fr> : double 2 * ;
ok.
Wait a second–doesn’t *
pop two values and then return?
Here we’ve only defined a single value, 2!
This construction is by design. *
pops whatever the top
two values of the stack are, multiplies them, then pushes the result.
This means that, since we’re only pushing a single value in
double
, the function will multiply whatever is on top of
the stack by two and then return it.
fr> 1
ok.
fr> double double double double .
16 ok.
It’s important to note a major concern with this style of
architecture. Let’s return to our definition of double
:
fr> : double 2 * ;
ok.
I mentioned before that this allows us to arbitrarily double whatever
is on top of the stack. However, what happens if there’s nothing on top
of the stack? Let’s use the clear
word to remove all
elements from the stack, then run double
:
fr> clear
ok.
fr> double
Error: stack is empty.
>
This is called a stack underflow error, and it kills our froth
session. We can reinitialize it with froth()
(and this will
preserve all of our defined words), but it’s important to exercise
caution when dealing with the stack. Make sure that you’re aware of what
state your words expect and what state they leave the stack in!
Conventional Forth communicates this by adding comments to words.
Comments are added using the ( )
words, and are typically
of the form ( before_state -- after_state )
(note space
separation of parentheses; they’re also words!).
For example:
fr> : DOUBLE ( n1 -- n2 ) 2 * ;
ok.
In this case, DOUBLE
expects to find a single number
n1
on the stack, and it replaces it with n2
.
In essence, we expect to find at least one value, and we end with one
value.
Another example:
fr> : SUM ( a b -- res ) + ;
ok.
SUM
is equivalent to the +
operation.
+
expects to find at least two elements on the stack
(a,b
), and replaces them with res
.
Operations don’t have to replace. The notation for emit
is simply : emit ( n -- )
, since it pops an element off the
stack but doesn’t replace it.
If you find yourself in a real pickle, the RESET
word
will completely reset the froth
environment to when its
first initialized. You can also use CTRL+C
to kill any
running processes, and CLEAR
to delete the contents of the
stack.
<NUMBER> ( -- n )
: pushes a number onto the
stackemit ( n -- )
: prints the top number of the stack,
interpreting as ASCIIcr ( -- )
: prints a new line." xxx" ( -- )
: prints xxx
on the
terminal. Note that "
terminates the string.: xxx yyy ; ( -- )
: defines a word xxx
comprised of words yyy
+ (a b -- n)
: adds a+b
* (a b -- n)
: multiplies a*b
. (n -- )
: pops the top element of stack and prints
itclear ( x1 x2 ... -- )
: removes all elements of the
stackreset ( -- )
: reset froth
to defaults
(wipe all user definitions, reinitialize all built-in definitions, reset
all stacks)