Back Language
This page describes the Back programming language and the Ein stack machine. Back is a stack-based postfix programming language inspired by FORTH, RPL, Scheme, and Bash. It runs on the Ein stack machine and can be stepped line by line for debugging, executed alternately with callbacks during normal operation (20 Hz or 20 instructions per second), or collapsed to execute segments of instructions in an accelerated mode during which callbacks are not answered (kHz range).
Central to Ein and Back are the call stack and the data stack. Both
stacks are collections of words, and each stack has at any given time a top
element. One step in the Ein machine main loop consists of popping the top
word from the call stack and executing it. Words behave like functions when
executed, affecting some change on to the Ein state, the call stack, or the
data stack. Some words are more like data than functions in that they only push
individual words onto the data stack (1
and "something"
) or groups of words
onto the call stack (( "hello" print )
), while others (like +
) are more
obviously functions in that they consume words from the data stack, perform a
computation, and leave a result on the data stack.
Argument Order
Many programming languages invoke functions by first naming the function and then listing its arguments from left to right:
/* C code */
printf("Hello world.\n");
This is called prefix order.
In the same languages one can find functions which take their arguments on either side:
/* C code */
int a = b + c;
Here both =
and +
are written in infix order.
Ein, like Forth and RPL, uses postfix order, which means that argments are listed first, or to the left, of the functions to which they belong:
/* back code */
"Hello world." print
b c + "a" store
The reason for this is that words take their arguments from the data stack. In order for something to end up on the data stack, something must put it there, and it needs to be put there before it is used.
Exercise: Calculate with Ein!
Do some arithmetic with Ein! Push some numbers on the data stack, and
observe them appearing at the console. Then add them together with
+
, and observe the result on the data stack. Press enter after each
word to observe the intermediate states, e.g., 1
, 1
, +
should
leave 2
on the data stack. Then try running them all at once: 1 1 +
. q
Exercise: Hello World!
Write a program to print hello world. First you push a string on the
data stack. Then use the print
word to print it to the console.
Data and Variables
Ein words can be multiple types. Primitive types include string, integer, double, EePose (the pose of the robot’s end effector as (x, y, z, quaterion)). Additionally, compound words can be defined using parenthesis as list of other words.
To define a string literal, use quotes:
"Hello world"
This command will push the string “Hello world” on the data stack.
Variables can be defined using the store
word, which takes two
arguments: the value (any word), and a string for the variable name.
For example, 0 "x" store
will store the integer word zero in the
variable x. Aftewards, typing x
will push a 0
on the data stack,
just as if you had typed 0
.
Compound Words
Compound words are Back’s list type. They are analagous to Lisp lists.
To push a compound word on the stack, embed a sequence of words in
parentheses: ( 1 3 4 )
. The word
You can also use store to define new compound words:
( 1 + ) "inc" store
defines a new compound word, inc
which adds
1
to the argument on the data stack and pushes the result.
Finally, the define
word takes both a word and help text:
( 1 + ) "Increment the value on the data stack" "inc" define
Equals is defined for compound words if the two words are the same length and all the containing words are equal.
Compound words can also be defined using square brackets: [ 1 2 3 ]
.
Square brackets evaluate the program inside and put the result in a
compound word, something like Python list comprehensions. For example, the program [ 1 1 + 2 ]
results in the
compound word ( 2 2 )
on the data stack. In contrast, the program ( 1 1 + 2 )
results in exactly that compound word on the data stack.
To access elements of compound words, you can use car
, first
or
head
to access the first element (all synonyms). You can use cdr
,
tail
or rest
to access the rest of the list.
Exercise: Brackets
Use square brackets and replicateWord
to create a compound word with
10 zeros.
Answer (select to see):
[ ( 0 ) 10 replicateWord ]
Now use square brackets, replicateWord
, and inc
to produce a
compound word with the integers from one to ten.
Answer (select to see):
[ 0 ( dup inc ) 10 replicateWord ]
Higher-order Functions
Ein includes map
, filter
, and accumulate
to operate on compound
words. For example, map takes a compound word and a lambda word, and
applies the function to every element of the input:
( 0 0 0 ) ( 1 + ) map
produces ( 1 1 1)
, adding 1 to each element
of the input word. ( 1 1 1 ) ( + ) accumulate )
produces 3.
Exercise: Squares
Use map
to create a compound word that contains the squares of the first ten integers.
Answer (select to see):
[ 0 ( dup inc ) 10 replicateWord ] ( dup * ) map
Reactive Variables
Ein gets its power from reactive variables, defined in C, such as
truePose
, which is always set to the value of the current pose of
the end effector. This reactivity means that message passing from the
lower-level OS is hidden; one need only reference truePose
to get
the latest estimate on the robot’s current pose. One can define new
reactive variables. For example, one can access the current height of
the end effector with truePose eePosePZ
. To create a new variable,
use store
:
`( truePose eePosePZ ) “trueHeight” store
This results in a new word, trueHeight
, which, whenever accessed,
contains the current height of the arm. Note that this computation is
done lazily, when the variable is read.
Control Words
Back includes looping and condition constructs.
ift
takes two arguments and prints if its argument is true. Ein
casts words to booleans following C’s semantics, so zero is false, and
all other values are true (including the empty string).
This version prints “hello”:
( "hello" print ) 1 ift
This version does not:
( "hello" print ) 0 ift
ifte
is “if, then, else”. Here is an example:
0 ( “condition was true” print ) ( “condition was false” print ) ifte
not
negates its condition.
while
takes a condition and a compound word and executes until the
condition is true. While collapses the stack and will freeze ein
unless the condition includes a wait word. For example the following
program pulses the torso fan forever:
( 1 ) ( torsoFanOn 1 waitForSeconds torsoFanOff 1 waitForSeconds ) while
Stack Collapse
Ein is constantly processing sensor and gui messages in the backaround, and then alternating between processing words. For Back words, the robot will process messages after each word. However for certain words, it can collapse the stack and execute it all at once, without performaing message processing. This collapse will freeze the gui and stop message passing from occurring but allows the system to devote all its CPU to the computational task, exploiting cache efficiency.
For while
loops and for
loops, they do not collapse the stack by
default. However there exists a whileCollapsed
word where the inner
loop is collapsed. This means that message processing will not be
done (unless the inner loop contain words that do not collapse the
stack and instigate message processing.
( 1 ) ( torsoFanOn 1 waitForSeconds torsoFanOff 1 waitForSeconds ) while
Exercise: Stack Collapse
Experiment with a program to make an infinite set of ones on the
stack. If you use while
with ( 1 1 + ) ( 1 ) while
, you will see
the stack continue to grow as the program runs. Abort the program
with clearCallStack
.
Now try whileCollapsed
which collapses the stack. Note that Ein is
frozen, and that clearCallStack no longer stops the program.
Back files
Back programs live in the ein/back
directory, in
Exercise: Back files
Create a new back file called hello.back
. First put a “hello world”
program in it and run "hello" import
. Then write a new word that
prints “hello” and also causes the robot to wave its arm (use the
truePose
variable to read the arm’s position and moveEeToPoseWord
to move to a predefined pose) and nod its head (nod
).
File Input and Output
Files can be created with fileOpenInput
and fileOpenOutput
.
fileReadLine
, fileReadAll
take a file on the stack and leave a
string. fileWrite
and fileWriteLine
take a file and another word
and write that word to the file; if the word is a string, the string
is written; otherwise repr
is used to convert the word to a string.
If you do this, the result can be evaluated with eval
to recreate
the contents of the file on the stack. Examples are in fileio.back.