GridLang is a language created for the GridControl game. It is an interpreted, procedural, stack-based language at the core having just integar and float datatypes.
The VM it runs on is also interesting in that it was designed so that the execution state can be suspended and serialized to be reanimated at a later time.
GridLang runs in a VM with the following components:
- DataStack (primary data stack) - the stack on which all operations operate
- ExecStack (internal executable stack) - used currently for GOTOs, and eventually function calls
- Registry (Key-Value store) - named value store, useful for binding some value to a label
python gridlang.py somefile.gridlang
GridLang is stack-based, and most operations are designed to operate from
values on the stack, so instead of writing 1 + 2
like most other
languages, in GridLang it looks like this:
PUSH 1 PUSH 2 PLUS PRINT
Execution starts at the top of the code, with an empty stack ([]
).
PUSH 1
pushes a 1
on top of the stack (stack is now [1]
).
PUSH 2
pushes a 2
on the stack (stack is now [1, 2]
).
PLUS
pulls two values off the stack (stack becomes []
again), adds them
together and pushes the resulting value on the stack (so stack is now [3]
).
THe last line takes the top value (3
) and prints it.
GridLang supports named variables using the registry. To store a value in the registry, do the following:
PUSH 1 STORE foo
After this executes, the stack is now empty ([]
, since the value STORE
stores is popped off the stack). After registering foo
, you can now
PUSH
it's value:
PUSH foo PRINT
This will print 1
. Keys can be anything that isn't a literal.
Since PUSH
is a very common operation, GridLang supports a simple sugar
using <<
. Any value following <<
is pushed onto the stack before
the operation procedes. Thus, the following:
ADD << 1 1 SUB << foo bar
Is exactly equivalent to:
PUSH 1 PUSH 1 ADD PUSH foo PUSH bar SUB
Note that <<
does not care about the arity of the operation, thus it is
legal to do ADD << 1 2 3
(ADD
will add 2 + 3) or ADD << 1
(add will
add 1 + whatever is on top of the stack).
Constants are determined at compile time, either introduced to the VM from
an outside source (in the case of running code in GridControl, the game
introduces some constants to represent bot commands and the cardinal
directions), or introduced in code. A constant is just any label prefixed
with @
:
@MYCONSTANT
A constant by itself on a line defines the constant. In this case, since
@MYCONSTANT
is on line 1 of the code, its value is now set to 1
, and
any reference to @MYCONSTANT
is replaced at compile time with 1
. Thus,
the following code:
@MYCONSTANT PRINT << @MYCONSTANT
compiles to:
(empty line) PRINT << 1
Compilation does two passes to determine constants, so constants can be used
even if they are defined later in the code. This makes constants very useful
as labels for GOTO
and other control flow operations:
GOTO << @MAIN PRINT << 0 @MAIN EXIT
In the first pass, @MAIN
is set to 4, and in the second pass, the GOTO
is compiled to GOTO << 4
(technically PUSH 4; GOTO
, remember that
<<
is sugar). In this example, no output is actually printed, since
execution jumps to the end of the program right away.
A user defined constant is done by appending a scalar value after the constant definition.
@MY_CONSTANT 10 PRINT << @MY_CONSTANT
Will print out 10
.
Call them functions or macros, gridlang supports jumps using CALL
and
RETURN
operators that can jump to some piece of code and later return
back to where it jumped from. Because gridlang keeps these jump states in
a separate stack (the ExecStack), these calls can be nested as far as the
stack allows.
This enables compartmentalizing or reusing code. Here is a contrived example:
@MAIN PUSH 1 CALL << @MYOWNPRINT PUSH 2 CALL << @MYOWNPRINT EXIT @MYOWNPRINT PRINT RETURN
Loops are done using the DO
and LOOP
operators. DO
takes
two arguments, the limit and the index. The loop begins at DO
with the
provided index, which is increment when the code approaches LOOP
. If
the index equals the limit, execution continues, else it jumps back to the
DO
PUSH 1 DO << 10 0 # do ten times MUL << 2 # double number every loop LOOP PRINT # outputs 1024 (i.e., 2 ^ 10)
Gridlang has limited support for text. In general, gridlang supports ascii
by representing each character using their byte value. PRINTSTR
takes
a length, pops that number of values from the stack, convert to characters
before outputting to console. Thus, the following is a way to write a
helloworld program:
<< 72 101 108 108 111 32 87 111 114 108 100 33 12 PRINTSTR << 13
Gridlang also supports char literals, which in the compiler get replaced by their byte value. Thus, this is a read-friendly way to write helloworld:
<< 'H' 'e' 'l' 'l' 'o' ' ' 'W' 'o' 'r' 'l' 'd' '!' PRINTSTR << 13
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
PUSH | <VAL> | 0 | 1 | PUSH <VAL> on stack |
POP | -- | 1 | 0 | Discard top value from stack |
POPN | -- | 1+x | 0 | Take value x from stack, and then discard top x values from stack |
SWAP | -- | 2 | 2 | Take top two values from stack and swap them |
DUP | -- | 1 | 2 | Take top value of stack and duplicate it |
DUPN | -- | 2 | ? | Take v, n from stack. Pushes n numbers of v |
HERE | -- | 0 | 1 | Returns current location of stack |
PEEK | -- | 1 | 1 | Push value at given location in stack |
POKE | -- | 2 | 0 | Take x, addr from stack, and set location addr in stack to value x |
PEEKN | -- | 2 | 1 | Take addr, l from stack, and push a slice of data at location addr in stack (with length l) to the top of the stack |
POKEN | -- | 2 | 0 | Take addr, l from stack, then take a slice of data (length l) from top of stack and write it into the stack at location addr |
RAND | -- | 1 | 1 | Take x from stack, and push random integer between (0, x) inclusive |
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
STORE | <KEY> | 1 | 0 | Takes value from stack and stores it in the registry under <KEY> |
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
PLUS | -- | 2 | 1 | Add two values from stack |
MINUS | -- | 2 | 1 | Subtract two values from stack |
MUL | -- | 2 | 1 | Multiply two values from stack |
DIV | -- | 2 | 1 | Take a, b from stack and push a / b |
MIN | -- | 2 | 1 | Take a, b from stack and push the lesser value |
MAX | -- | 2 | 1 | Take a, b from stack and push the larger value |
MODULO | -- | 2 | 1 | Take a, b from stack and push a % b |
ABS | -- | 1 | 1 | Take a from stack and push abs(a) |
NEG | -- | 1 | 1 | Take a from stack and push -a |
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
GREATER | -- | 2 | 1 | Take a, b from stack and push 1 if a > b else 0 |
LESS | -- | 2 | 1 | Push 1 if a < b else 0 |
EQUAL | -- | 2 | 1 | Push 1 if a == b else 0 |
NEQUAL | -- | 2 | 1 | Push 1 if a != b else 0 |
AND | -- | 2 | 1 | Push 1 if a && b else 0 |
OR | -- | 2 | 1 | Push 1 if a || b else 0 |
All operands must be integers.
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
BNOT | -- | 1 | 1 | Take a from stack and push ~a |
BAND | -- | 2 | 1 | Take a, b from stack and push a & b |
BOR | -- | 2 | 1 | Take a, b from stack and push a | b |
BXOR | -- | 2 | 1 | Take a, b from stack and push a ^ b |
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
GOTO | -- | 1 | 0 | Take value from stack and jump to that line |
IFTGOTO | -- | 2 | 0 | If v, j from stack. if v > 0, jump to j |
IFFGOTO | -- | 2 | 0 | If v, j from stack, if v <= 0, jump to j |
CALL | -- | 1 | 0 | Take value from stack and call to that line |
IFTCALL | -- | 2 | 0 | If v, j from stack, if v > 0, call j |
IFFCALL | -- | 2 | 0 | If v, j from stack, if v <= 0, call j |
RETURN | -- | Pops from the exec stack, returning to where you
last CALL -ed from. |
||
DO | -- | 2 | 0 | Takes limit, index from stack, and beings a loop |
LOOP | -- | Increments loop index, if it is less than limit, jump back to matching DO, else continue execution |
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
-- | 1 | 0 | Take top value from stack and output it | |
PRINTSTR | -- | 1+? | 0 | Take l from stack, then take top l values from stack and output as string |
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
PANIC | -- | 0 | 0 | Raise an exception and provide a trace |
END | -- | 0 | 0 | Stop execution |
Command | Args | Pops | Pushes | Description |
---|---|---|---|---|
CALLFF | -- | 1+? | 0 | Take top value from stack as n. Then takes n values from stack and sends that to FFI |