Noir was a project I started because I wanted to learn more about parsing and assemblers. Here is a basic "Hello World" program.

  lea R0 message
  eef puts
$puts #eef "eef_puts"  
$message #str "Hello, world!\n"

We define a @function called main, this will be the entry point of our script. Next are our data $directives:

  1. #eef specifies that the string is a function written in C/C++ and is linked to the VM. When this script is parsed, the word gets filled out with the function ID.
  2. #str places the string into memory, each character taking up a word, including null terminator.

Next we will start executing. lea loads the address of $message into register R0. Next eef is called, it stands for "execute external function" and like I mentioned before this will call a function you define yourself in C/C++.

Our puts function looks like the following:

void eef_puts(Vm* vm) {
  uint32_t addr = vm->r[0];
  char string_buff[1024] = {};
  for (int i = addr; vm->program[i] != 0; ++i) {
    string_buff[i - addr] = (char)vm->program[i];
  printf("%s", string_buff);

It reads the string at R0 until a null terminator is found and then printfs it.

Here is a more complicated function, a string compare function:

; R0: str0
; R1: str1
; R2: 0 if not equal, 1 if equal
  ; Store str0's char into R3
  mov R3 R0
  ; Store str0's char into R4
  mov R4 R1
  jeq strcmp_char_equal R3 R4
  ; Not equal!
  liv R2 0
  ; Check for null terminator
  jeq strcmp_string_match R3 '\0'
  ; Next character
  add R0 R0 1
  add R1 R1 1
  jmp strcmp_loop_start
  ; Equal!
  liv R2 1

Most of it should seem fairly familiar to other opcodes, here we use $directives without any #data attached to them as labels to jump to. Other than that it should seem fairly self-explanatory.