donotturnoff

Rex syntax documentation

Introduction

This document aims to provide a syntax reference for those who wish to write programs in Rex. For information about specific functions, refer to the library reference.

Function declaration

All functions in a Rex program must be declared before they are defined. The function declaration gives the interpreter information about the types of the parameters and the return type, the language in which the function body is written (it can be written in Python as well as Rex, so that certain operations which cannot be easily performed solely in Rex, such as pseudorandom number generation, can be turned into Rex functions) and optionally, if the function body is written in Python, allows for specific Python modules to be imported.

Standard

The syntax for standard function declaration in Rex is as follows:

$ [name] [param 1 type] [param 2 type] […] [param n type] -> [return type]

A function can take any amount of parameters, including none. The parameter types must be listed in the same order as the parameters themselves do when the function is defined.

Examples
$ e -> num

This function has no parameters and simply returns a number.

$ square num -> num

This function takes one number as an argument and returns another number.

$ ackermann num num -> num

This function takes two numbers as arguments and returns a number.

$ pretty_print str -> null

This function takes a string as an argument and returns null - perhaps its purpose is just to output a string.

$ menu str null -> null

This function takes a string and null as arguments and returns null. The null argument can be used for intermediate output.

Python-based

There are some operations which cannot be defined purely in Rex, such as file operations. There are also some operations which are possible to define in Rex, but you may not want to for one reason or another (usually laziness). In this case, the Rex interpreter provides integration with Python so that Python code can be used as the body of a function, with the rest of the function written in the usual Rex syntax. To do this, you must mark the function as being a Python function when you declare it. This is done by replacing the $ at the beginning of the declaration with a &. The syntax therefore looks like this:

& [name] [param 1 type] [param 2 type] […] [param n type] -> [return type]

There may be cases where a specific Python module is needed to compute the function body (e.g. the random module when producing pseudorandom numbers). This can be indicated within the declaration itself as follows:

& [name] [param 1 type] [param 2 type] […] [param n type] -> [return type] : [module 1] [module 2] […] [module n]

The modules to import are listed at the end of the delcaration, separated from the rest of the declaration by a colon, and delimited by spaces.

Examples
& charAt whole -> str

This function will use Python in its body to perform some operation on a whole number value to produce a string. It requires no external Python modules.

& random -> num : random

This function makes use of Python's random module to produce a numerical output. It takes no arguments.

Function definition

Once a function has been declared, it can be defined. This is where the actual operation of the function is programmed. Functions can be written on one line, or, if conditionals and/or error catching are used, over multiple lines.

One-line

Functions which always perform the same operation no matter what the input is can be written on one line using the following syntax:

[name] [param 1] [param 2] […] [param n] = [body]

The definition begins with the function name, then is followed by the parameter names (whose order must correspond to the order of the types in the declaration), then an equals sign. After the equals sign comes the function body, which is the code to be executed when the function is called. If the function was declared to be Python (using & instead of $ at the beginning of the declaration), this body must be written in Python; otherwise, it must be written in Rex.

If the function body is written in Rex, it will usually consist of further function applications (although a constant value can be returned if desired).

Examples (including function declarations)
$ e -> num
e = 2.718281828459045

This function takes no arguments and returns a numerical constant (the value of e)

$ square num -> num
square x = * x x

This function takes a numerical argument and returns its square by multiplying it by itself. This is an example of function application in a function body.

& random -> num : random
random = random.random()

This function takes no arguments and produces a numerical output. Its body is written in Python and depends on the random module, which has been imported using the colon syntax. Notice that even though the function body is written in Python, everything else still uses the Rex syntax, until you pass the equals sign. Also notice that even though the Python function called in the body is also named random, there is no namespace pollution, so the Rex function can also be called random.

Conditionals

When you wish to take a different action depending on some condition, you can use Rex's conditional syntax. This syntax defines multiple function bodies, each on a new line, with the condition at the end of the line. The condition is separated from the function body by the if keyword. An optional fallback case can be defined using the otherwise keyword in place of the if [condition] combination. The syntax for a multi-branch function definition without the fallback case is as follows:

[name] [param 1] [param 2] […] [param n] =
    | [branch 1] if [condition 1]
    | [branch 2] if [condition 2]
    ⋮
    | [branch n] if [condition n]

And for a function definition with a fallback case, the syntax is:

[name] [param 1] [param 2] […] [param n] =
    | [branch 1] if [condition 1]
    | [branch 2] if [condition 2]
    ⋮
    | [branch n] if [condition n]
    | [fallback branch] otherwise

Note that you can technically put the fallback branch anywhere - it doesn't have to be the last one. However, the interpreter will attempt to match the condition of each branch one-by-one, in the order that they are listed, so as soon as a branch whose "condition" is otherwise, the interpreter will treat it as a match and will execute that branch. This means that any branches following the fallback branch will never be executed. In essence, otherwise means if True

Also note that the indentation of the branches isn't necessary - it is simply for readability purposes. More on whitespace.

Also note that even if your function body is written in Python, any conditions should still be written in Rex.

Examples (including function declarations)
$ abs num -> num
abs x =
    | x if >= x 0
    | - 0 x if < x 0

This function calculates the absolute value of its sole argument by returning the argument unchanged if it is greater than or equal to zero, and returns zero minus the argument if it is less than zero. This covers all cases, so no additional fallback branch is needed, but the final branch could take on the role of a fallback branch, executing if the first branch doesn't, like so:

$ abs num -> num
abs x =
    | x if >= x 0
    | - 0 x otherwise

Error handling

When your program is running, it may produce errors, usually from invalid user input. If unhandled, these will cause your program to halt. However, if they are caught, they can be handled appropriately without halting the program, and execution can continue as normal afterwards. The syntax for error handling is very similar to that of conditionals - the branch body is followed by the catch keyword, then the error type to catch. Multiple catch statements can be added to a function, and if an error occurs, the first one to match the error type will handle the error. The syntax for a function definition with error handling is as follows:

[name] [param 1] [param 2] […] [param n] =
    | [branch 1] if [condition 1]
    | [branch 2] if [condition 2]
    ⋮
    | [branch n] if [condition n]
    | [fallback branch] otherwise
    | [error branch 1] catch [error type 1]
    | [error branch 2] catch [error type 2]
    ⋮
    | [error branch m] catch [error type m]

The catch branches can appear anywhere in the function body - they needn't be at the end. However, as stated above, the order in which they appear does matter - whichever branch is the first to match the error type will handle it.

If you don't need multiple conditions in your function, but do want to handle errors, you can simply end the main branch with the otherwise keyword, and it will execute as normal, and any errors will be handled by whichever error handler branches you include.

Examples (including function declarations)
$ safe_div num num -> num
safe_div x y =
    | / x y if True
    | 0 catch zeroDivisionErr

This function performs division on two numbers, but if division by 0 is attempted, it simply returns 0. I know this doesn't make mathematical sense, but it's just an example. zeroDivisionErr is a subtype of mathErr, so the following code would do the same job:

$ safe_div num num -> num
safe_div x y =
    | / x y if True
    | 0 catch mathErr

If there are multiple matching catch branches, the first one will handle it. For instance:

$ safe_div num num -> num
safe_div x y =
    | / x y if True
    | 0 catch zeroDivisionErr
    | 1 catch mathErr
    | 2 catch err

In this function, if divison by 0 is attempted, the zeroDivisonErr branch will handle the error, even though all three error-handling branches match the error.

Also, in any of the above functions, the if True clause can be replaced with otherwise, like so:

$ safe_div num num -> num
safe_div x y =
    | / x y otherwise
    | 0 catch zeroDivisionErr

and

$ safe_div num num -> num
safe_div x y =
    | / x y otherwise
    | 0 catch mathErr

and

$ safe_div num num -> num
safe_div x y =
    | / x y otherwise
    | 0 catch zeroDivisionErr
    | 1 catch mathErr
    | 2 catch err

Function application

Function application in Rex always uses prefix (Polish) notation (this includes for operations such as addition: + x y not x + y). This means that brackets are never needed. The arguments to a function are delimited by spaces, not commas as in many C-like languages.

Often, you will call a function with arguments which are themselves the results of other function calls, with their own arguments. This means that there may be many more elements on a line than there are arguments to the outermost function call, but the interpreter will compute the innermost functions first, so by the time it reaches the outermost function, there will be just the right amount of tokens left to use as arguments (assuming the program is written correctly, that is). For instance, consider the following function:

$ square_sum num num -> num
square_sum x y = * + x y + x y

In this function, the arguments are added together and the result is squared. Take a look at the function body:

* + x y + x y

* and + are both functions of two parameters, and x and y are the values to perform the calculation on (parameters of the square_sum function). Looking at the function body with no knowledge of Rex's syntax and prefix notation, you may think that there are far too many arguments being passed to the * function - 6 instead of 2. However, when the interpreter executes this function it will first execute the two + functions, which each take two arguments. The results of these calculations are then used as the arguments to *, so that * ends up with 2 arguments in the end. You can mentally insert brackets if you wish, converting the function body into:

* (+ x y) (+ x y)

Now you can clearly see that * does indeed only have two arguments. However, do not include brackets in actual Rex programs! The interpreter will not understand them.

Including libraries

External libraries can be included into your program. External libraries are simply files containing Rex functions, written as you would write a normal Rex program.

To include an external library, write the line # include [path], where [path] is the path to the library, either relative or absolute.

Miscellaneous

Strings

String literals are denoted by surrounding them by double quotes ("). If you want to use a literal double quote mark in your string, you can escape it using a backslash (e.g. "\"" represents a double quote mark character). I may add support for wrapping strings in single quote marks in the future.

Comments

Comments must be on their own line. They are denoted by two forward slashes at the start of the line (e.g. //This is a comment).

Program entry point

When running a program, the interpreter will look for the function called main. main can return any data type but cannot have any parameters. The usual return type is null, because the returned data has nowhere to go so it will be lost unless it is output in main.

Whitespace

The Rex interpreter ignores whitespace in general (lines can be padded by as many spaces as you wish, the $, &, etc. at the beginning of lines can be separated from the rest of the line or it can be right next to it, the conditions in function branches can be separated from the body by as many spaces as you wish, function arguments can be separated by as many spaces as you wish, etc.). The only limitation is that function arguments and branches must be separated by at least one space, and branches must be on separate lines, but they are fairly obvious restrictions.

Intermediate output

In your programs, you may want to produce output along the way, rather than just at the end. This can be done by declaring functions with null arguments, to which you can pass the "result" of out functions. For example:

$ out_loop null -> null
out_loop _ = out_loop out "Hello world"

This will print "Hello world" forever (until the stack gets full). This works by passing the "result" of the out function (null) to the next function call, which expects one null argument. By declaring the function to have one argument, it forces it to evaluate the body and hence execute the out function. I have used _ as the parameter name to signify that it is of type null - this isn't necessary, it just makes it easy to read.