Chapter 1

The step rule

An Ether program is a list of words separated by spaces. That is the whole syntax: there are no parentheses to balance, no operators with precedence, no statements and expressions. Everything else you will meet is built from one rule that the interpreter applies over and over.

Every example on these pages runs. Change it and press Run (or ⌘/Ctrl + Enter) to see what your version does.

hello world
values: ( "world" "hello" )

The program pushed two words onto a stack called the values stack. The output shows the values a program leaves behind, top first: world was pushed last, so it is on top and comes first. This page always writes a stack the same way, top first, inside ( ).

A process

The interpreter runs a process, which has:

When a program starts, all of its words go onto the instructions stack with the first word on top. Then the interpreter repeats one step until no instructions are left:

The step rule. Take the top instruction.

  1. If it is a function, run it. A function is a piece of the interpreter itself; it can do anything to the process.
  2. Otherwise, if the macro table has an entry for it, put that entry's list on the instructions stack.
  3. Otherwise, push it onto the values stack.

hello and world have no macros, so rule 3 pushes them. A word like debug has a macro: its list holds a single function, which removes the top value and prints it.

hello debug
hello
values: ( )

Nothing is left on the values stack: debug took hello off it. Every built-in word works this way: it is a macro whose list holds one function. Words you define yourself are macros too, so a word you write and a word the interpreter provides look the same to the step rule.

Anything between << and >> is a comment and is skipped. Comments can be nested.

<< this is not run >> hello
values: ( "hello" )

Numbers and text

A word is just bytes: 3 on its own is the one-byte word "3", not a number. The word # reads the word after it and pushes the number it spells.

# 3 3
values: ( "3" 3 )

Both print as 3, but they are different things: the top one is the word "3" (shown in quotes), the one below is the number 3. Numbers are whole numbers, positive or negative. Both a number and a piece of text are stored as a block of bytes, which Ether calls a rom; you will see that name in error messages.

To write text with spaces in it, put it between " words. The quote marks are words themselves, so they need spaces around them, and the words between them are joined with single spaces.

" hello,   world " debug
hello, world
values: ( )

Inside the quotes, three words stand for characters you cannot type as a word: \n a new line, \t a tab and \s a space. Each must be a word of its own, with spaces around it, and it replaces the spaces on both sides of it. So " one \n two " has no spaces around the new line, and \s is how you put a space where it would otherwise be dropped: at either end, or next to another escape. The second line below is 8 bytes long, the six letters and a space at each end.

" one \n two " debug
" \s padded \s " size debug
one
two
8
values: ( )

Arithmetic, and which value comes first

+, -, * and / take two numbers off the values stack and push the result.

# 2 # 3 + debug
5
values: ( )

For - and / the order matters, and Ether has one rule for it that holds for every word in the language:

The subject is on top. A word works on the value on top of the stack, its subject; anything else it needs is below. Read x y word as "y word x".

So # 3 # 10 - is 10 − 3, and # 2 # 10 / is 10 ÷ 2. Division keeps the whole part and drops any fraction.

# 3 # 10 - debug
# 2 # 10 / debug
# 2 # 7 / debug
7
5
3
values: ( )

less-than pushes true or false, by the same rule: # 10 # 3 less-than reads "3 less-than 10".

# 10 # 3 less-than debug
# 3 # 10 less-than debug
true
false
values: ( )

Getting this order backwards is the most common mistake in Ether, and it fails quietly: - gives a negative number and less-than gives the opposite answer. When a result surprises you, check the order first.

Moving values around

Four words rearrange the top of the values stack. Here, and everywhere on this site, a stack is written top first, as in the output, so ( a b ) means a is on top. The word list gives every word in the same notation.

wordbeforeafter
dup( a … )( a a … )copies the top
del( a … )( … )drops the top
swap( a b … )( b a … )exchanges the top two
swap2( a b c … )( c a b … )brings the third to the top
a b c swap2
values: ( "a" "c" "b" )

The program pushed a, b, c, so the stack was ( "c" "b" "a" ); swap2 brought the third value, a, to the top. With these words, a number can be used twice:

# 7 dup * debug
49
values: ( )

When something goes wrong

A word that cannot do its job stops the program with an exception. The output says so, and shows the exception (a table: its message says what went wrong and its trace which word failed) followed by the values that were on the stack:

# 1 + debug
Unhandled exception
( { "data":{ "expected":"rom rom" "found":( 1 ) } "message":"Expected rom and rom from the values stack but the values stack only contains one rom" "trace":( "+" ) "type":"check" } 1 )

+ needs two numbers and found one. Chapter 6 shows how a program can catch exceptions and carry on.

Exercise 1.1

Print the number 12 by adding 5 and 7.

A correct answer prints:

12
values: ( )
One answer
# 5 # 7 + debug
12
values: ( )

Exercise 1.2

Starting from # 4 and using no other number, print 4 × 4 × 4.

A correct answer prints:

64
values: ( )
One answer
# 4 dup dup * * debug
64
values: ( )

Exercise 1.3

Push a b c, then use only swap and swap2 to reverse them, so the program leaves ( "a" "b" "c" ): a on top and c at the bottom.

A correct answer leaves:

values: ( "a" "b" "c" )
One answer
a b c swap swap2
values: ( "a" "b" "c" )

Exercise 1.4

Compute 100 − 7 × 3 and print it.

A correct answer prints:

79
values: ( )
One answer
# 7 # 3 * # 100 - debug
79
values: ( )