© 2003: Jan Wolter, Steve Weiss
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.
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 * printThat's pretty weird looking. Let's take a simpler example:
2 1 + 5 * printThis says:
Returning to our more complex example:
"The answer is " x abs 1 + 5 * printwhat this does is
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:
Operation | Resulting 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:
% This is my beautiful program 1 2 + print % This should print the number 3
1 2 + print 1 2 + print 1 2+print
So the classic hello world program is:
(Hello world!\n) printHere '\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? ) printIf you have a parenthesis in a string, you usually need to backslash it:
(Hello world! \(copyright 1978, Kernighan and Ritchie\)\n) printActually, 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) printHere 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.
/year 1978 def /name (Tom) defWhen 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.
/year 1978 defconstant /name (Tom) defconstantIt 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) printThen the compiler will translate this to:
(My name is )(Tom)( and I was born in )1978(\n) printThis 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)' defthen the compiled copy of the program will actually do
/msg (I was five years old in 1983\n) defHere the addition and the concatination could all be done at compile time. Appropriate use of constants can improve performance significantly.
/weekname [ (Sun) (Mon) (Tue) (Wed) (Thu) (Fri) (Sat) ] defThis 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.
/addone { 1 + } defThe { 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 printThen 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) printThe 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)' } ifThen 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".
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.