Backtalk Script Language
Tutorial

Version 1.3.30

© 2003: Jan Wolter, Steve Weiss

1. Introduction

Backtalk's script language is, well, unusual. It's a stack-based language, which means everything is written backwards. If you know the Postscript language then Backtalk's language will be very familiar. If you know the Forth language, then you have the basic idea. However, most likely you don't and probably Backtalk scripts will look totally weird to you. If you've used an old Hewlett-Packard RPN calculator or have a reasonably good grounding in computer science then at least you have the advantage of knowing about postfix notation.

This document is meant to give a enough of a basic introduction to the Backtalk script language to help you figure out what you are seeing when you look at a script, and make simple changes.

For full details of the language, see the Backtalk Script Language Reference Manual.

2. Postfix Notation and Stacks

This section introduces the basic idea of the Reverse Polish or Infix Notation used by Backtalk.

In a typical procedural computer language, you might write a statement like this:

   print("The answer is ",(abs(x) + 1) * 5);
This is a mixture of prefix notation and infix notation. The print and abs function are in prefix notation. The operator name preceeds a list of arguments. The + and * functions are in infix notation, with function name in between its arguments.

Postfix notation writes the operator after the arguments. So the statement above would be written as:

   "The answer is " x abs 1 + 5 * print
That's pretty weird looking. Let's take a simpler example:
   2 1 + 5 * print
This says:
  1. Take the number 2.
  2. Take the number 1.
  3. Add the two things you have and keep the result.
  4. Take the number 5.
  5. Multiply the two things you have and keep the result.
  6. Print everything you have.
So this prints the number 15. Note that we didn't need parenthesis to distinguish between (1 + 2)*5 and 1 + (2 * 5). That kind of ambiguity happens only with infix operators.

Returning to our more complex example:

   "The answer is " x abs 1 + 5 * print
what this does is
  1. Take the string "The answer is ".
  2. Take the value of the variable x.
  3. Find the absolute value of the thing you have and keep the result.
  4. Take the number 1.
  5. Add the two things you have and keep the result.
  6. Take the number 5.
  7. Multiply the two things you have and keep the result.
  8. Print everything you have.
Note that we start by taking the string and just hold onto it without operating on it at all. The abs function operates on the most recent thing we took, and the + and * times functions operate on the two most recent things we took. So clearly the computer needs to be able to keep track of a large number of things and their order.

The data structure used by computer to do this is a stack. It's just a linear pile of values which allows us to add a value onto the top, or take a value off the top. Adding a value is called pushing it. Deleting a value is called popping it.

So we'll run the above command again, this time using stack terminology and showing the stack after each step. We'll assume that the value of x is -7 and we'll write the stack with the bottom at the left and the top at the right:
OperationResulting Stack
(1)Push the string "The answer is ". "The answer is "
(2)Push the value of x. "The answer is " -7
(3)Pop a number, push its absolute value. "The answer is " 7
(4)Push 1. "The answer is " 7 1
(5)Pop two numbers, push their sum. "The answer is " 8
(6)Push 5. "The answer is " 8 5
(7)Pop two numbers, push their product. "The answer is " 40
(8)Print everything on the stack from bottom up. empty

So the execution of a Backtalk program is really very simple. Backtalk starts at the beginning of the program and executes one item at a time according to the following rules:

That's pretty much all there is. This is very simple and computers can run such scripts very quickly.

3. Backtalk Syntax

The examples above aren't actually quite in correct Backtalk syntax. We'll set that straight here.

3.1. Comments

First, comments start with percent signs:
    % This is my beautiful program

    1 2 + print   % This should print the number 3

3.2. White Space

White space doesn't matter. Occasionally it can be omitted. All of the following are equivalent:
    1 2 + print

    1   2
        +
        print

    1 2+print

3.3. Strings

Since we don't need parenthesis for their usual function of grouping arguments to operators, we use them instead of quotation marks to quote strings. Quoting strings with parenthesis seems a bit odd at least, but it actually makes things easier to read, since it is clear what is inside the parenthesis and what is outside.

So the classic hello world program is:

    (Hello world!\n) print
Here '\n' represents a newline. You can also just put a literal linebreak inside a string. The following two forms are equivalent:
    (Hello world!\nHow you doing?\n) print

    (Hello world!
    How you doing?
    ) print
If you have a parenthesis in a string, you usually need to backslash it:
   (Hello world! \(copyright 1978, Kernighan and Ritchie\)\n) print 
Actually, in this case you don't have to Backslash the parentheses because they balance. But you definately would in the following case:
   (Hello world! \(copyright )
     year
     (, Kernighan and Ritchie\)\n) print 
Here the parentheses inside each string don't match up, so they must be backslashed. In general it is good style to backslash them all.

You can concatinate strings with the + function. "(Hello )(World)+" gives "(Hello World)". Sometimes this gets big and ugly:

  (Hello )(World!)(\n)(How are )(you)(?/n)+++++
An alternate syntax is often more convenient:
  ` (Hello )(World!)(\n)(How are )(you)(?/n) '
Here everything between the ` and the ' will be concatinated together into a string. The ` really just pushes a mark on the stack, and the ' is a function that concatinates everything after the last mark on the stack.

3.4. Variables

We can define variable as follows:
   /year 1978 def
   /name (Tom) def 
When a variable name is proceded by a slash, then the name itself is pushed on the stack instead of it's value. So the def function pops the name and a value off the stack and defines that variable to have that value. We get the value of a variable just by using its name without the slash.
   (My name is )name( and I was born in )year(\n) print

You'll see several other forms of this. The xdef function does that same thing but takes the arguments in reverse order:

   1978 /year xdef
   (Tom) /name xdef 

The store and xstore functions are similar to def and xdef but they can only store new values in previously existing variables, while def can either update or create variables. In most Backtalk scripts the difference doesn't matter, but Backtalk does have a notion of local and global sets of variables that we won't get into here, and it may matter when those are being used.

3.5. Constants

The defconstant function looks a lot like def:
   /year 1978 defconstant
   /name (Tom) defconstant
It behaves very differently though. All Backtalk scripts are compiled before being executed, with the compiled copy being saved to a file. Constants are evaluated at compile time instead of run time. So if we write
   (My name is )name( and I was born in )year(\n) print
Then the compiler will translate this to:
   (My name is )(Tom)( and I was born in )1978(\n) print
This means when we actually run the program Backtalk doesn't have to look up the values of name and year because it already has the values. In fact, it can't look up those values, because they don't exist at run time. This is faster, but it also means that you can't change those values at run time. A lot of system configuration settings are done with constants.

Backtalk will even do some computation on constants at compile time. If we write:

   /msg `(I was five years old in )year 5 +(\n)' def 
then the compiled copy of the program will actually do
   /msg (I was five years old in 1983\n) def
Here the addition and the concatination could all be done at compile time. Appropriate use of constants can improve performance significantly.

3.6. Arrays

You can define an array in Backtalk by doing
  /weekname [ (Sun) (Mon) (Tue) (Wed) (Thu) (Fri) (Sat) ] def
This actually works much the way the concatination operators do - [ pushs a mark on the stack, and ] is a function that pops things off the stack until it finds a mark, then forms everything it popped into an array. Array can contain any mixture of any kind of data, including other arrays. There are operators to do various normal things with arrays, like extract particular values, but you most often see them used in Backtalk to simply group data. If you print an array, it just prints all the elements in it. So if I want to generate a control panel to be displayed at the top and the bottom of the screen, I can generate it once, save it in an array, and print it twice.

3.7. Procedures

A procedure is similar to an array, but functions aren't executed in it:
   /addone { 1 + } def
The { 1 +} part of this doesn't get executed. Instead, a special array is created with the elements 1 and +. The array becomes the value of the addone variable. Variables whose values are procedures are special. If we later do:
   7 addone print
Then we don't just push the value of addone onto the stack as we usually would for a variable. Instead, Backtalk notices that it is a procedure and executes it. So the 1 is pushed and the + is executed and we print 8. This is how new functions are defined in Backtalk.

Procedures are also used to create conditionals and loops. Here is a conditional:

   (The value is )
   x 0 lt {
     (negative.)
   } {
     (postive.)
   } ifelse
   (\n) print
The ifelse takes three values off the stack. The first is a flag, and the second two are procedures. If the flag is a non-zero number or a non-empty string, it executes the first of the two procedures. Otherwise it executes the second.

There are a number of different loop functions, all of which take a procedure argument and execute it repeatedly until some condition arises.

Conditionals interact in a special way with constants. If I do:

   name constant {
      `(My name is )name(\n)'
   } if
Then if name is defined as a constant, then the compiler will produce
   (My name is Tom\n)'
but if name is not defined, nothing will be generated. In this way entire chunks of code can be caused to vanish if they are not needed in a particular installation. This is Backtalk's equivalent of a C-language "#ifdef".

3.8. Implicit Print

When a Backtalk script finishs execution, it automatically does a print causing everything on the stack to be printed from bottom up. A lot of scripts work by just letting all the stuff they intend to output pile up on the stack and never do an explicit print.

This has a number of advantages. One of the odder ones is that you can put the name of a variable on the stack fairly early on, by doing, say /number_of_rows. Then you can generate a whole page of data, keeping count of the number of rows of data generate with the number_of_rows varable. When you finally output the page, when the /number_of_rows literal is printed, it's current value is substituted in. This way I can print the count at the top of the page although I don't know it's value until the bottom of the page.

For large pages, however, it is generally better to do regular prints calls to keep from using up too much memory, and to allow output to begin transferring to the user's browser.


backtalk@hvcn.org Wed Feb 6 09:35:00 EST 2002