Blarb
BlarbVM
Blarb is designed to be the simplest possible (esoteric) systems programming language. Being as such, there is only one logical operator: NAND. It is an assembly-like, stack & register based language.
Architecture
In Blarb, there is a stack, heap, and registers. On the stack, every element is a 64-bit word. To increase the heap size, you must use the brk system call.
When you include a file with the @ operator, the tokens in that file will be appended to the parse tree - thus, you should always exit the VM at the end of your programs by calling syscall 60
via 0 0 0 0 0 0 60 %
, or call 0 exit
if you are using the standard library. The set of includes at the top of the file (until the first non-including line) will run at include-time. All other includes are dynamic.
Registers
There are 8 registers, numbered 0-7. The 0th register is the line pointer - that is, the line that is currently being evaluated. Registers 1-3 are short term registers, that may change when you call a function or jump to a label. Registers 4-7 are guaranteed to never be used in the standard library (which I'll explain later), so you can use them for long term storage in your userspace programs.
Setting the line pointer register to a number less than -1 will terminate the VM. Alternatively, see the exit
function in the standard library.
Register # | Description |
---|---|
0 | Line Pointer |
1-3 | Temporary Registers |
4-7 | User Registers |
Literals
There are only two types of type literals in blarb:
Words
64-bit words. They are generally represented as 64-bit integers, but there technically isn't a concept of an integer in Blarb. Using the logic module of the standard library, one can perform integer operations on the words, however.
Strings
In Blarb, a string is simply a null-terminated sequence of words on the stack. Typing a string literal in will translate into a null-terminated sequence, for sanity. To escape a quotation, use \". To escape backslashes, use \\
. Newlines and tabs are escaped with \n
and \t
, as usual.
Characters
Characters are parsed like strings, but get turned into word types when parsed. Thus, like most other languages, a character can only contain a single byte (the character escapes still work).
Operations
There are currently 8 single-letter operations. Additionally, you can type a single number to push that number onto the stack (64-bit signed integer). Space between operators and integers or function calls is optional.
NAND
b a !
NANDS index a with index b and stores the result in index b
Store Register
a b ~
Store the value at index a in register b
Get Register
a$
Push register a onto the stack
Pop from Stack
a^
pop a
elements
Conditional
a?
if index a is true (non-zero), execute the rest of the line
Include File
"filename"@ Include the file "filename.blarb". Duplicates will be ignored. The "@" symbol within the included file name will be expanded into the system directory, for instance "@sys/memory"@.
System Calls
f e d c b a syscallnum %
Execute the system call with the given args
Upon syscall error (returning -1
), the Blarb VM will terminate. This may change in the future.
Set Memory Address
a b =
Swap the value at memory address at index b to the byte (8 bits) at index a of the stack
The value at stack index b should contain a valid memory address.
Labels
Labels are created by a hash:
#labelname
and called by simply writing their name.
When jumping to a label, the line that the label is on will be executed, ignoring "#labelname".
No tokens are allowed after a jump to a label, and you will get a parse error if you do so. Thus, a label call must be the last statement on a given line, if present.
Functions
Blarb functions are a loosely defined construct, like in assembly, they are just labels. Thus, you must push the line register to the stack using 0$
(e.g. 0$andi
).
Example:
; Pushes the current line to the stack ; The stack will now contain 2, 3, and the current line number ; No token are allowed after a call to a label 2 3 0$andi 0 0 0 0 0 0 60 % ; exit by calling syscall 60 ; Bitwise AND the two arguments and push the result to the stack #andi 4 3 ! ; A NAND B (the two arguments) 4 4 ! ; NAND the result with itself to bitwise NOT it ; Pop the stack frame and second argument. ; The remaining stack data will only include the return value ; The "2 0 ~" sets the line pointer to the return address ; The "2 ^" pops the return address and the second argument 2 0 ~ 2 ^ ; Run this program with "--debug" to see the result on the stack
Control Statements
Control structures are nothing special in Blarb either - they can be created via labels.
Loop example:
; This program will pop all the numbers on the stack until it hits a 0 ; Pushes a bunch of numbers onto the stack 32 52 42 0 1 52 35203 3502462 #loop 1? 1^ loop ; Pop the NULL for good measure 1^
Args
Args are pushed to the beginning of the VM stack, as "backward strings", like string literals. The arg count (argc, if you will), is always pushed onto the stack afterwards. Even when there are 0 args.
Standard Library
Note: Without the standard library, it will be extremely difficult to do anything - so you will probably want to include @sys/lib
in all your programs.
The standard library includes tons of useful functions - everything from bitwise operations to functions that will print string literals for you!
Recall: Functions are called with <args> 0$functionname
.
As of writing this document, the standard library includes:
Function | Args | Description |
---|---|---|
nandi | A B | Bitwise NANDs the two top items on the stack. |
andi | A B | Bitwise ANDs the two top items on the stack. |
noti | A | Bitwise NOTs the top item on the stack. |
ori | A B | Bitwise ORs the top two items on the stack. |
xori | A B | Bitwise XORs the top two items on the stack. |
lshiftilone | A | Bit shifts the top item left by one bit. |
lshiftil | A B | Shift A left by B bits. |
rshiftilone | A | Bit shifts the top item right by one bit. |
rshiftil | A B | Shift A right by B bits. |
addi | A B | Adds the top two elements on the stack. |
subi | A B | Subtracts B from A (A - B). |
multiplyi | A B | Multiplies the top two elements on the stack. |
dividei | A B | Divides the top two elements on the stack. |
The division and remainder are both returned. | ||
seti | V I | Set's the word on the stack at |
index I to value V. | ||
copy | I | Copy the element at stack index I. |
swap | A B | Swap the element at indices A and B. |
iseqi | A B | Checks if A is equal to B. |
Returns 1 if true, 0 if false. | ||
isgei | A B | Checks if A >= B. |
tobooli | A | Returns A as a boolean (1 or 0). |
pushbytetoheapi | A | Pushes a BYTE to the heap. |
Returns the address of the byte. | ||
pushbytearraytoheap | I L | Copy array of length L at index I to the heap. |
Returns the initial array index breakpoint. | ||
stackstrlen | A | Push the length of the string at |
stack position A to the stack. | ||
A B | Prints the string at index A of length B. | |
printline | S | Prints a null terminated string S, with a |
newline character. | ||
readchar | D | Reads a single character from descriptor D. |
brki | B | Sets the new brk address to B. If B is 0, |
the current brk will be returned. | ||
pushstringtoheap | I | Pushes the string at index I to the heap. |
Retruns the memory address of the string. | ||
openwithname | S F M | Opens the file of the null terminated string S. |
F are the open syscall flags, M is the mode. | ||
See the open syscall docs for more information. | ||
Returns the file descriptor number. | ||
closedescriptor | A | Closes file descriptor A |
exit | C | Terminates your program with status code C |
Syntax Highlighting
See the editors directory for some syntax highlighting plugins. Currently there are only Vim and Emacs plugins.
Examples
See the examples directory for worked examples. If you freshly cloned this project, run ./blarb --debug example/function.blarb
, for instance. To see how the VM is running, use the --debugger
flag.