bx

In 1990 Tom Markson and I developed a compiler for a new language we had designed, called 'bx'. The main features were:

Generators

We included generators since they could be used to provide an abstract iteration over a data structure without resorting to hacks like cursors. (Although our actual data structures often ended up having cursor pointers anyway...)

The standard for loop in bx called a library generator "upto":

    for int i := upto(1, 10) do
        ...
    end for;
The definition of the library upto generator was:
    gen int upto (int from, to) is
        while from <= to do
            yield from;
            from := from + 1;
        end while;
    end gen;
Generators could return references, so the calling code could alter the value of the variable being returned.

Functions as variables

bx also had the ability to have functions which could appear on the left side of an assignment statement. Perl can do this now through the tie directive.

From the reference manual:

A take is the inverse of a function. The value assigned to the take is brought into the take body. For example:
    func int max(int x,y) {         # func returns an int
	if (x > y)
	    max:=x;                 # if x > y return x
	else
	    max:=y;                 # else return y
    }

    take int max(var int x,y) {     # take accepts an int
	if (x>y)
	    x:=max;                 # will assign value to whichever variable
	else                        # is greater
	    y:=max;
    }

    proc main {
    int x:=1, y:=3;                 # declare and initialize

	printf("%d",max(x,y));      # func call
	max(x,y):=100;              # take call
	assert (x == 1);
	assert (y == 100);
    }
Take and func may have the same name and the compiler will differentiate between them. In the take call "max(x,y):=100", the value 100 is bound to max; so x:=max actually becomes "x:=100" when the take is called.

Take together with func (or give, described later) create a virtual variable. That is, a pair of func/takes may actually appear as, and operate as a standard variable. For example:

       func int foo {
               printf("foo was called");
               foo:=1;
       }
       take int foo {
               printf("foo was assigned to %d",foo);
       }
       foo:=1; # will generate:
       foo was assigned to 1
       x:=foo; # will result in:
       foo was called
Thus, with take, we have created the possibility of two-way functions. These may be used in place of standard variables. Take/func pairs are used extensively in BX even if the programmer is unaware of it. For example, an array makes use of take and func to generalize the accessing of its values by index:
    x[15]:=12;      is identical to   x.index(15):=12;
    y:=x[15];       is identical to   y:=x.index(15);
So, when you access an array, you may actually be calling a take or func to perform the work. This allows any box which has a take/func pair called index to be treated as an array, no matter what else the box contains. In other words, the programmer may allow any data structure to be treated as an array simply by providing a take/func pair called "index". Since take/func pairs are treated as if they are variables, the following are legal:
    x[15] := x[15] + 1;           # x.index(15):=x.index(15)+1;
    max(x,y) := max(x,y) / 100;

	# becomes:
	#
	#   if (x > y)
	#       x := 100;
	#   else
	#       y := 100;
Thus, take/func pairs may be used in expressions as long as take appears on the left side of the assignment and func appears on the right. Take and func may appear alone, however. One does not require the other.

Other eccentricities

bx had an odd feature which allowed procedures to returned a set of parameters associated with a built-in case tag, which the caller would have to handle.
    proc do_something (int x)
	ret ERROR(int,int);
	ret WARNING(int);
	ret SUCCESS(char);
    {
    const int errnum := 10, severity := 20;

	if (x < 0)
	    return ERROR(errnum,severity);
	else if (x == 0)
	    return WARNING(10);
	else
	    return SUCCESS('a');
    }

    do_something(0) {
	case ERROR(int errno,sever):
	    printf("Error number %d, severity %d",errno,sever);
	case WARNING(int warning_num):
	    printf("Warning number: %d",warning_num);
	case SUCCESS(char c):
	    printf("got the char: %c",c);
    }

Some code samples from bx:


Rich Skrenta (skrenta@rt.com)