## title: My bytecode VM Lox interpreter
## date: "2024-10-06"
The aim of this post is to describe the general operation of
the program and some of the mechanisms that we consider to
be of interest. For full details, a link to the source code
is available at the bottom of the page.
So I continued with “Crafting Interpreters” by Robert
Nystrom after making My Tree-Walker Lox interpreter. In this
part I tried to do as many challenges as possible and really
understand how a VM bytecode works.
(DIR) My Tree-Walker Lox interpreter
This version is written in C, which means we have to write a
lot of code ourselves, but we don't use any external
libraries.
## Compiler
The primary purpose of our compiler is to generate a chunk
of code in bytecode form for interpretation by our bytecode
virtual machine. Here are a few interesting features of the
front end.
### Scanner
The token scanner is very classic, with only one thing to
say: the function responsible for identifying the language's
native keywords is very dirty. The author has chosen to use
a large switch statement instead of implementing a sorting
function, which is certainly powerful but not very elegant.
### Parser
An interesting point to note is that the author chose not to
use a syntax tree for the front end. We therefore
implemented a single-pass compiler (directly converts compile
units into bytecode).
We also implemented a Vaughan Pratt's parser, in our case a
“top-down operator precedence parser”. This means we have to
define operator precedence in advance. Here's what it looks
like in code.
typedef enum {
PREC_NONE,
PREC_ASSIGNMENT, // =
PREC_OR, // or
PREC_AND, // and
PREC_EQUALITY, // == !=
PREC_COMPARISON, // < > <= >=
PREC_TERM, // + -
PREC_FACTOR, // * /
PREC_UNARY, // ! -
PREC_CALL, // . ()
PREC_PRIMARY
} Precedence;
This precedence is simply used to control the parsing of
expressions. A rule with a lower precedence than the last
parsed expression is not allowed.
## Bytecode
To manage conditions, we emit OP_JUMP operation code for
conditions. If a condition expression is evaluated to false,
it jumps to the end of the conditionnal block / expression.
To do this, we use the concept of backpatching: we overwrite
the immediate value of the instruction in the chunk during
compilation.
In my implementation, all immediate values are encoded on 8
bits, with the exception of constants, which have a size of
24 bits.
## Virtual Machine
The VM is centered on a stack where we push operands, local
variables, etc..
Everything at runtime is managed by callframes, even the top-
level code is embed within a function object.
## Example
Here is a simple Lox example that can be evaluated by my
interpreter.
fun fib(n) {
if (n < 2) {
return n;
}
return fib(n - 2) + fib(n - 1);
}
print fib(10);
## Links
https://github.com/theobori/lox-virtual-machine
(HTM) https://github.com/theobori/lox-virtual-machine