--- title: "Fundamental Froth" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Fundamental Froth} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) library(froth, quietly = TRUE) ``` `froth` attempts to stick close to the original conventions of Froth. Let's walk through the basics using the built-in commandline. ```{r, eval=FALSE} 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 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. ## The Data Stack 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`: 1. `froth` reads `1`, and pushes a 1 onto the stack. 2. `froth` reads `2`, and pushes a 2 onto the stack. 3. `froth` reads `+`. 4. `+` pops two items off the stack (2, 1). 5. `+` adds the two items it just popped (1 + 2). 6. `+` pushes the result of the operation (3) back onto the stack. 7. `froth` reads `pop_result`, and looks up the definition (`. cr`). 8. `pop_result` first calls `.`, which pops the first element of the stack (3). 9. `pop_result` then calls `cr`, which prints a new line. 10. `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. ``` ## Watch out for the stack! 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. ## When all goes wrong 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. ## Words in this chapter - ` ( -- n )` : pushes a number onto the stack - `emit ( n -- )` : prints the top number of the stack, interpreting as ASCII - `cr ( -- )`: 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 it - `clear ( x1 x2 ... -- )`: removes all elements of the stack - `reset ( -- )`: reset `froth` to defaults (wipe all user definitions, reinitialize all built-in definitions, reset all stacks)