"8th" - grokking the REPL

Introduction

This post is intended to give you a feel for using 8th, but it is not a tutorial per se. If you're not familiar with "8th", please view my previous blog post.

Resources:

If you don't yet have a copy of 8th, you should get one now and follow the installation instructions in the manual.

I assume you do have a copy of 8th, and have followed the installation instructions as presented in the manual, and know how to start and stop it at least. If that's not true, take care of it now so you can follow along!

Help!

The word "word" is used a lot in 8th (and in Forth, in general), to describe what other languages call a "function" or "procedure". Unlike most other languages, 8th lets you use any sequence of UTF-8 characters not containing whitespace to name a word. So "I'm-doing-fine" is a valid word-name, but "I'm doing fine" is not. The reason for this lies with the way 8th parses input, which we'll describe later on.

You can get "inline help" when you're using the 8th console, by typing the word "help" followed by the word you want help on. For example:

ok> help mod

ns: n
name: mod
stack: n m -- r
desc: Divide the numbers "n" by "m", returning the remainder as a number "r"

This gives you all the information you need to use the "n:mod" word properly: that it is in the "n" namespace, what its "stack-effect diagram" (a.k.a. "SED") is, and a concise description of what the word does. If there is more than one word with the name you give (for example, "+") then the help system will display the help for all the words with that name. In that case, you can specify the "fully-qualified name" of the word; for example, "n:mod" instead of "mod".

To list all the words 8th currently knows, type "words". That spews a very long list, though. You might be more interested in just knowing what words are in a particular namespace:

ok> words/ a:

a
! + - @ bsearch clear close dot each exists?
filter indexof insert join len map new op op!
open pop push reduce rev shift shuffle slice
slide sort when when! y zip

The word "words/" returns the words which match the text you type after it. In this example, you typed "a:", which it interprets as "find all the words in the 'a' namespace". You can get a different kind of help by typing "apropos". That will search for the given text across all the different help text, and will return help on anything which mentions that text. It's like "grep" for the inline help system.

What did you SED?

We mentioned the "stack-effect diagram", or SED; now we'll explain it. All it is, really, is a more or less standardized way of documenting what effect a word has on the stack. That is to say, what items the word consumes from the stack, and what items it puts on the stack.

To remind you: 8th (and Forths in general) utilize a "stack" as the central means of passing parameters to, and getting results from, the words invoked. So a correct SED is really crucial to understand how to use a word.

The general SED format is:

in3 in2 in1 -- out2 out1

This is understood to mean that on entry, the word expects "in1" on TOS (top of stack), and then "in2" underneath it and "in3" underneath that. It consumes those, then pushes "out2" on the stack and leaves "out1" on TOS.

It takes a bit of getting used to and you must learn to read it properly, because it is a complete documentation of how the stack should be used by that word.

Note that the SED doesn't tell you what the semantics of the word are, or what it's good for. That's what the help system, manual, and sample code are for.

Please REPLy!

The 8th interpreter is also known as a "REPL". It works like this, if you type "10 5 / . cr":

ok> 10 5 / . cr
2

The REPL parses whitespace-delimited strings of text from the input, and attempts to interpret what it finds. In this case, 8th looks up "10" in its dictionary of known words and can't find it. It then tries to interpret "10" as a number. That succeeds, so it "pushes" the number 10 on the stack, and similarly the "5". It then looks up "/" in its dictionary and finds code for it, which it executes - and that does what you expect: it divides 10 by 5. Then 8th looks up "." executes it. Its effect is to print whatever is on TOS. The final "cr" was likewise looked up and executed; its effect is to print a line-termination appropriate for the OS.

That's why you see the "2" on the line under the one you typed. If you hadn't put the ". cr" there, 8th would have kept mum. It's good at that; you have to tell it when you want to see anything.

Interpreter or compiler?

You may be forgiven for thinking 8th is solely an interpreted language, given the previous examples. In fact, it is both an interpreted and a compiled language: the REPL interprets input from the console, or a file, or some other input stream, and when a word is found in the dictionary it's executed. But by using the ":" and ";" words, you can create new named words:

ok> : div5 5 / ;

ok> 10 div5 . cr
2

The "compiler" word ":" tells the REPL to enter compilation mode, so instead of immediately executing the words it parses, it compiles what it finds into the word being created. By the way: if you want an "anonymous" word, you use "(" and ")" instead of ":" and ";".

A "named word" is one which has a name, and can be found in the dictionary using the word "w:find" (among others). An "anonymous word" is invisible to the dictionary, and is widely used in iterators. For example:

ok> [1,2,3] ( . cr drop ) a:each
1
2
3

Note that in the 'div5' example you could also have created a new word '10' which did something other than push the number 10:

ok> : 10 "ten" ;

ok> 10 div5
Exception: Expected Number but got String: at offset 8 in console: G:>n

8th threw an exception here, because the "/" word in "div5" expected to be given two numbers, but instead you gave it "ten" and 5, and 8th considers that a fatal error and throws an exception.

Let's see what '10' does now:

ok> 10 .s

1 s: 00000000038d6580 3 ten

The ".s" word displays the top ten items on the stack. Here it's showing that after invoking "10", a string "ten" is on the stack. That's because we created the word "10", and the stack-effect is what we would expect. It's just a really bad idea to create a word like this - but 8th doesn't prevent you from doing it anyway!

You are exceptional!

This brings us to the final topic we'll discuss today: "exceptions".

Unlike most other languages with exceptions (C++, Java etc), 8th throws exceptions when something really bad happens, something which really can't be handled. Thus, exceptions are considered "fatal errors" and generally cause your 8th program to terminate. You can, however, override that behavior by intercepting "handler". From the "debug/nicer" library:

( 
"\nException: '%s'\nBacktrace:\n" s:strfmt .
backtrace cr bye
) w:is handler

This also terminates the application, but it prints a backtrace for debugging purposes. This utilizes a feature of 8th (and Forths) called "deferred words". Those are words whose name is fixed at declaration time, but whose code can be modified at a later time. That's what the "w:is" does: it assigns the code in the anonymous word to the "handler" deferred word.

Remember: in 8th, exceptions are fatal and almost always indicate a programming error which you need to correct!

In conclusion

This post gave you a peek at some of the internal workings of 8th, and a feel for some of what it can do. The next post will talk about solving real problems, with a particular emphasis on embedded Linux on Raspberry Pi platforms.

Anonymous