The 2E Programming Language

Getting Started

1.1 Compiling

Download the tar file ee-version.tar.gz, and extract the source tree, run make, and copy the executable ee into a directory in your path:
tar -zxvf ee-version.tar.gz
cp ee /usr/local/bin
or on a Windows system (with a gcc toolchain installed, such as MinGW), use make -f Makefile.win32

1.2 The Infamous Greeting

ee -c 'print("Hello, World\n")'
Hello, World

Ok, now that we got that out of the way...
2e programs are composed of expressions (actually, one large expression consisting of one or more sub-expressions). White space is mostly ignored, including line breaks (so they can appear pretty much anywhere to improve readability). Also, anything on a line following a "#" character is treated as a comment and ignored. An expression is something like this:
2 + 3 * 5
where "3 * 5" is a sub-expression of the full expression, "2 + 3 * 5".

An expression can be evaluated via ee in a couple ways; either on the command line:
ee [ -p | -c] 'expression'
-- or --
ee filename
In the first form, if "-p" is used, the expression is evaluated and the result printed. On the other hand, "-c" will just evaluate the expression. Use this if your expression contains output functions such as print(). The second form will execute the program in the given filename. This is useful for longer programs, or for placing the line #!/usr/local/bin/ee at the top of the script to automatically run it under ee when called, similar to other interpreters on Unix type environments.

For the first several examples, we'll use the first form:
$ ee -p '(12 + 144 + 20 + 3 * 4 ** .5) / 7 + 5 * 11'

1.3 More Meat

Special operators, variables, assignments
Values can be assigned to variables using the "=" operator. Variable names can contain alpha, numeric, or the underscore "_" characters, and are case sensitive. They also must not begin with a numeric. The assignment returns the value of the right side of the operator. This works just like C.
$ ee -p 'a = 7; b = 11; x = a * b'
The semicolon ";" is used to join multiple sub expressions. It is a binary operator, similar to the standard math operators, but has a lower precedence. It evaluates the expression on the left (including side effects, such as assignment and function calls), then it returns the result of what is on the right. Since it is a normal operator, it can be used wherever needed, including within parentheses, so the expression foo = (expr1 ; expr2) will assign the result of expr2 to the variable foo. Without the parentheses, foo will be set to expr1, since "=" has a higher precedence then ";" does (i.e., a higher precedence operator binds "tighter" than a lower precedence, so it is evaluated first).

Another special purpose operator is actually a pair of operators, "?" and ":", called the conditional operator. Similar to the C conditional, it is used like this:

exprc ? exprt : exprf
The way it works is exprc is evaluated (which is typically a comparison expression), and if true (i.e., returns a non-zero result) then exprt is evaluated and it's result returned as the value of the whole expression. Otherwise, if false, then exprf is evaluated and returned.
$ ee -c 'a = 1; a < 10 ? print("True\n") : print("False\n")'
may contain several sub-expressions spread over multiple lines, each sub-expression separated by a semicolon operator. That way, the conditional expression can be used similar to an "if / else" clause. However, if a semicolon is used within either the true or false targets, then the target expression must be enclosed in parentheses, since a semicolon is of lower precedence than the conditional. Which leads to a shortcut implemented in 2e:
$ ee -c 'x = 8; x % 2 == 0 ? print(x, " is even\n"); print("End of program\n")'
8 is even
End of program
The rule is, that if an operator of lower precedence than "?" is reached prior to the ":" operator, then the conditional is terminated automatically with a ": 0" as the false condition (unless, of course, the operator in question is part of a parentheses enclosed expression). This also occurs if an unmatched closing parenthesis is encountered, or the end of the program is reached prior to an ":" (else) operator. Therefore, the following three are all equivalent:
x = 2 > 3 ? 4 : 0; ...
x = 2 > 3 ? 4; ...
x = (2 > 3 ? 4); ...
There is also an iterative (looping) form of the conditional operator, "?? :", which functions as follows:

$ ee -c 'a = 1; a <= 5 ?? (print(a, "\n"); a++) : print("Error...\n")'

$ ee -c 'a = 6; a <= 5 ?? (print(a, "\n"); a++) : print("Error...\n")'
In the first example the true target was evaluated, which printed out the value and incremented the variable "a". This was repeated until the condition, "a <= 5" was false, in which case the whole conditional exited. The second example, however, set "a" to be a value which cause the condition "a <= 5" to start off false. In this case, and this case only, the false target (after the ":") is evaluated. Normally, this would be a good place to put some type of error handling expression if you would typically expect the condition to be true at least once. But usually you don't need the false target, so you can use the above mention shortcut and terminate the iterative conditional expression with an operator of lower precedence than "??", such as ";" or a closing parenthesis if appropriate.

Also, these examples are starting to get harder to read, so we'll switch to a more appropriate coding style and start putting the larger scripts into an executable file that would look like this:

a = 6;
a <= 5 ?? (
    print(a, "\n");
) : (
Of course, once you start doing this, you will have to remember that the ";" operator isn't a statement terminator but a normal binary operator. This means that in the above example, you don't have a ";" after the a++ line or the print("Error...\n") line, since they are immediately followed by a closing parenthesis.

1.4 Variable types, Arrays

Variables can have one of the following types: integer, floating point, character, and string. The type is set automatically whenever a value is assigned to it. If a different value type is later assigned to the variable, the variable type is automatically adjusted. So,
foo = 3
foo is an integer in this case. And
foo = 7.3
foo is then changed to a floating point after it is reassigned. Also:
x = 7.4;
y = 2;
result = x + y
Since the value of 7.4 + 2 ends up being a floating point, then result is a floating point type. Same as if you had "result = 7.4 + 2.6", result would still be a float even though it's value, 10, is a whole number.

Single character values are created with single quotes, such as:

c = 'A';
t = '\t'
Special characters are referenced when prefixed with a backslash. The one above, '\t', is a tab. Other special characters are '\b' (backspace), '\n' (newline), '\r' (return), '\\' (backslash), and '\'' (single quote). Character strings are enclosed in double quotes, and can contain the same escape sequences mentioned above...
str = "This is a test\nSecond line of the test";
Array variables are automatically created when either it is referenced, or another array is assigned to a variable. If a variable previously contained another data type, then as soon as it is referenced as an array the variable is destroyed and re-created as an array. Arrays are indexed, starting at 0, by following a variable with the index enclosed in brackets:

my_array[0] = 2;
my_array[1] = 3;
my_array[2] = 5;
Multi-dimensional arrays are also supported: my_array[3][4] = x;
By default, arrays are set up as generic arrays. Any given value can be any data type, floating point, integer, string, or even another array. In fact, that's how multi-dimensional arrays are implemented -- in the previous example, my_array[3] is an array element with a data type of generic array. So in reality, a 2-dimensional array is actually an array of arrays, which means that the dimensions are infinite in all directions (up to memory limits, of course).

In addition to the general purpose generic arrays, single data type arrays are also supported. For example, a string is actually an array of characters:

$ ee -c ' bla = "This is a test"; print(bla[8], "\n")'
The main thing to note about typed arrays is they can only be one dimensional, and you can't assign a value of a different data type to one of it's elements. You can, however, have a general purpose array set up as an array of character arrays, which will give you the same effect as a two dimensional array.

If you have a number of items you want to initialize in an array, you can either do them one at a time via a series of assignments:

ary[0] = 1.3;
ary[1] = 3.7;
ary[2] = 19;
ary[3] = 4;
Or, you can enclose the values in curly braces "{ ... }", which is an array initializer:
ary = { 1.3, 3.7, 19, 4 };
The array initializer returns a generic array which can be used in place, or assigned to a variable, like above. So it is technically legal to code:
print({ 1.3, 3.7, 19, 4}[2]) which would return the number 19 as it's value. Not that this is actually useful, but it is legal.

Multi dimensional arrays can also be initialized:

ary = {
    { 1; 2; 3 };
    { 4; 5; 6 };
print(ary[0][1], " ", ary[1][2], " ", ary[2], "\n") 

Which returns:
2 6 99
as it's output.

You can also initialize typed arrays. That is, you can create an array of integers, floating point, or character values, by prefixing the opening brace with an i, f, or c, such as

intary = i{ 1, 2, 3};
floatary = f{ 2.7, 3.4, 7.9};
chararray = c{ 'a', 'b', 'c' } # same thing as chararray = "abc"
Note that typed arrays can only hold values of the given type, and type conversions are not automatic at this time. So you may have to use the totype() functions, as in myarray = c{ tochar(65), tochar(66) }

1.5 Functions

Both internal (built-in) and user-defined functions are available. User defined functions are ones that are declared within the 2e code. Internal functions are pre-defined and built into the into the interpreter (such as the print() function used in the previous examples). These internal functions can be supplemented with external function libraries by using the function call loadlib() , as documented in the reference manual.

Here's a sample user defined function, which raises a number to a given power. Of course, this simple pow() function only works with integer exponents greater than 1. There is a built-in exponent operator, "**" which is part of the base language, which works on floating point exponents also.

print(pow(2, 3));

    b = $1;  #base
    e = $2;  #exponent
    r = 1;   #result
    i = 1;
    result = (
        i <= e ?? (   # while "i" is less than or equal to "e"
	    r = r * b # once "i <= e" returns false, this will be the expression value
        ) : (         # otherwise if "i <= e" starts off false,
	    r = 0     # then this becomes the return value
    result;;           # ;; is a unary postfix operator, and causes the
                       # function to return the value on its left
Function definitions begin with the "@" symbol followed by the function name, then followed by an expression (composed of one or more sub expressions) enclosed in parentheses. When the function flow encounters a ";;", the function is terminated and the value of the expression to the left of ";;" is returned as a result. The ";;" (return) operator is a unary operator with left/right association (i.e., a postfix unary operator), with a precedence equal to ";". It is legal to use it anywhere a unary postfix operator is usable, and if it is missing from the end of the function, then it will be automatically assumed. Finally, function parameters are passed in the function call by separating them with "," operators and parameters are referenced by "$x", where "x" is an integer starting at 1 (and can also be an integer variable). $0 will return the number of parameters passed to the function. Note that this is the same syntax used to retrieve parameters that have been passed from the command line.

In the above example, "i <= e ?? ( ... ) : ( ...)" is an iterative conditional expression. As you recall, the first and middle expressions are evaluated repeatedly until the first expression is no longer true, then the final result of the middle expression (i++ ; r = r * b returns r = r * b as it's result) is the value of the conditional. It is this value then that gets assigned to "result", which is returned with the ";;" (return) unary operator. The third expression of the conditional is ignored, unless the condition in the first expression started out false. In that case only, this third expression is evaluated and it becomes the value of the iterative conditional expression, and assigned to "result".

A number of built-in functions are provided. Some are necessary as they provide functions that aren't part of the base language (such as file i/o and certain variable manipulation). Others are general utility type functions which could be implimented within 2e, however their usage is common enough that they have been also provided. See the reference section of this manual for a complete list.

Function name assignments and Anonymous functions
Function names can be assigned to other variable names, or array values, which then act as an alias for that function. For example, you can code

mypow = pow
and then use mypow(3, 2) to call the pow() function. Not very useful in itself, however this works with array values too. So lets say you have a process that returns an integer value from 0 thru 2, and you wanted to call a function based on that value. You code write the following:
    result == 0 ?      # if result is equal to 0,
        foo(arguments) # call function foo()
    : result == 1 ?    # else if result is 1, then
        bar(arguments) # call function bar(), etc...
    : result == 2 ?
but that can be fairly inefficient, especially if you end up with a fairly long list of functions. A better way is to build a "state machine", which is a programming construct that immediately calls a particular section of code based on the state of a given variable, without going throug a whole if/else if/... comparison table. You can do that like this:
    handler = { foo, bar, xyzzy }; # these are functions defined elsewhere
    # sometime later after "result" is set ...
A couple of real-world examples of where you'd use this syntax is perhaps writing a terminal emulator, in which you have to jump to a handler routine based on encountering specific screen control sequences, or perhapse when writing an interpreter where you have a list of handler functions -- one for each operator in an expression.

But what if you only have a short example, and you didn't want to set up the handler functions in a different part of the code? Well, you can also code up handler functions at the time of assignment by using anonymous functions. An anonymous function is a function that doesn't have a name, but the definition of it returns a value that can be assigned to a variable and used later.

    myfunc = @@(expression );
- or -
    handler = {
The syntax looks very much like a normal function definition, but there is no function name after the "@" symbol. But, the contents are like any other function - what appears between the parentheses can be as simple or complex as needed. If a single "@" symbol is used, then variables within share their scope with the parent function. If two are used "@@", then variable scope is local to that function.