This is an interpreter for a pretty simple custom programming language similar to BASIC, which includes a subset of the OpenGL 1.1 API. Simple games (or perhaps even complex ones, given enough determination and patience!) can be created, as the language provides a way to capture keyboard state. Provided examples include a navigable procedurally-generated terrain and an implementation of insertion sort. The terrain demo includes a way to create textures, despite the language not supporting pointers (however, it does support arrays) and it does this using a built-in utility function and glCopyTexImage2D. The interpreter itself is a register-based virtual machine and was written in C. Although the VM is register-based, the compiler uses a stack to track register usage. The stack is only used when parsing the input program source.
Here is the full example of insertion sort which is included with the download, and can be run with the compiled interpreter. It should give you a feeling for what the syntax is like.
; Step one: Fill array with pseudorandom numbers
while i < SortArray.length
SortArray[i] = (i*23) % 31
; Step two: Perform in-place insertion sort
while i < SortArray.length
while (j > 0) & (SortArray[j - 1] > val)
SortArray[j] = SortArray[j - 1]
j = j - 1
; Step three: Print out sorted array
while i < SortArray.length
Programs can easily be written in this language, as there is no requirement for variables to be declared with a strict datatype or for the program code to be modular. Furthermore, the written programs can be run on any platform to which the interpreter has been ported. Porting of the interpreter should not be too troublesome as it was written in portable C and many platforms provide support for OpenGL. Even if real OpenGL support is not available, the interpreter can be re-written to target any graphics API without requiring any changes to the interpreted programs.
The interpreter has also been designed to allow the binding of other C APIs to BASIC function calls, not just OpenGL.
I used a separate program to automatically produce OpenGL-binding code, which made it easy to provide a subset of the OpenGL API. While this has resulted in a number of GL functions effectively having no use but still being accepted due to their signature, those functions should not hinder the program in any way. The symbol table used during compilation is a hash table. The hashing expression is quite simple, so I added a histogram display feature to check the distribution wasn't terrible. Last time I checked, there were 700 or so symbols (this would include GL functions and enumerations, plus any custom things) and the symbol lookup would at most perform a full check on about 30 of them for a given string.
One of the example programs, a game similar to Asteroids
Another example program
What follows is a list of the language's features and currently missing functionality. As previously mentioned, please see the included sample programs if you are interested in writing your own.
- Upon first assignment, a variable will be globally visible from that line to the bottom of the source file. The keyword 'local' can be used to create a local variable (function parameters are implicitly local) as can be seen in the example programs.
- Function parameters are local to their function and only visible within the function body.
- Functions cannot be called recursively, but this is only because a function will only be known to return a value once it has been parsed completely. If this was not a restriction then recursion would work fine as execution state is pushed and popped through a stack.
- Use of a return statement is the only indication that a function returns some value.
- Any function which has a return statement must end with a return statement.
- The language is case insensitive.
- Arrays are declared by beginning a statement with the word 'array' following by the name of the array being declared, and then any number of dimension sizes. A dimension size is a number with square brackets either side. The array declaration terminates at the end of the line, or if a comma is encountered following a dimension size. A comma begins a new array declaration, however the word 'array' is not required and the next token after the comma is the name of the next array to be declared. Array declarations require at least one dimension size.
- An array element is identified by the array's name followed by an expression in squared brackets. The expression is truncated to an integer value after evaluation. Further dimension indices are specified by additional expressions in square brackets.
- The dimension sizes of an array can be obtained by specifying the array's name followed by the member lookup operator (a period) and one of the following words: 'length', 'width', 'height', and 'depth'. 'length' and 'width' both return the size of the first dimension, 'height' returns the size of the second, and 'depth' returns the size of the third.
- If a semicolon is encountered, this is taken to mean that the rest of the line the semicolon appears on is to be ignored. In other words, the semicolon marks the start of a single-line comment.
- Binary operators do not differ in precedence, and always associate left.
- There is no restriction on numeric literals, however intrinsic functions will truncate to integer where necessary. If any expression contains a mix of integer and explicitly decimal evaluations, the integer evaluations will be converted to decimal.
- Char literals are supported, but there is no support for strings. This is so the alphanumeric keys can be queried with KeyDown and KeyUp
- Blocks beginning with 'do' are repeated indefinitely and must end with 'loop'.
- Blocks beginning with 'while' will be repeated whilst the expression following the token 'while' evaluates to any value other than zero, and must end with 'wend'.
- Functions are declared with 'function' followed by the name of the function being declared and optionally a list of arguments enclosed in brackets and separated by commas. The function body is terminated with the token 'endfunction'. The list of arguments may be empty, as in "()" or may not be present at all in which case the function will be nullary.
- Forward references are not supported. The compiler only performs one pass, and does not create an abstract syntax tree.
- All functions must be defined before the main program code.
- Functions without a return statement cannot be used in an expression.
- If a function does not return a value, then parentheses around arguments is optional. However, if an open bracket is encountered after the function's name then a closing bracket will be expected after the arguments.
- There is no for-loop construct.
Available functions include math, functions to update the display and the input state, query the input state, print a number to standard output (for debugging), return the current number of milliseconds since the engine was initialized, and create a 2D texture. The language does not support pointers, so any OpenGL function with an address parameter is not included. However I added CreateTexture2D which has the exact same parameters as glTexImage2D save for the last which would be a pointer. This last parameter is not needed and is set to NULL by the interpreter. As there is no way to load an image file, a texture must be created procedurally as demonstrated in one of the sample programs.
The following commandline switches can be used:
o Lists all the operations in the compiled program. Effectively a disassembly.
h Lists some hash table statistics. Only useful to anyone developing the interpreter itself.
s Lists the entire symbol table after compilation has completed. This includes all internally-defined
symbols and those defined by the interpreted program source.
f Runs the program fullscreen
The interpreter internally uses either C float or C int depending on context, but it would be great to convert it to use a multiprecision format instead. For example, Java's BigDecimal class covers both floating and fixed formats plus a greatly extended range and arbitrary precision at the cost of reduced performance. A C equivalent of this would be ideal.
Download (23/Aug/2011) - Includes source, Windows 32-bit executable, and example programs. Source code is licensed under the zlib license.