generated from arpitbatra123/eleventy-blog-mnml
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ (post): Added a post about my Lox bytecode VM
- Loading branch information
Showing
30 changed files
with
2,044 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
--- | ||
title: My bytecode VM Lox interpreter | ||
date: "2024-11-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](/posts/jlox). In this part I tried to do as many challenges as possible and really understand how a VM bytecode works. | ||
|
||
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. | ||
|
||
```c | ||
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. | ||
|
||
```text | ||
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](https://github.com/theobori/lox-virtual-machine) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# A toy to visualize recursive function calls | ||
## 2024-06-29 | ||
Recently, I did a little project in | ||
=> https://python.org Python | ||
to visualise function calls, especially recursive functions. | ||
|
||
It takes the form of a | ||
=> https://python.org Python | ||
decorator applied to the desired functions. The data structure used is fairly basic, a tree with nodes that have a parent and an indefinite number of children. Each node represents a function call, and the nodes also include the arguments passed to the function when it is called and, optionally, a return value. | ||
|
||
To generate a visual and have an overview of all the function calls, I used | ||
=> https://graphviz.org/ Graphviz | ||
to manage a graph and save it as a file (DOT, SVG, PNG, etc.). | ||
|
||
The decorator also supports memoization, which can also be represented on the final visual. | ||
|
||
## How is it used? | ||
|
||
These are two clear examples of how the decorator is used. | ||
|
||
```python | ||
from callviz.core import callviz, set_output_dir | ||
|
||
set_output_dir("out") | ||
|
||
@callviz( | ||
_format="png", | ||
memoization=True, | ||
open_file=True, | ||
show_node_result=True, | ||
) | ||
def fib(n: int): | ||
if n < 2: | ||
return n | ||
|
||
return fib(n - 2) + fib(n - 1) | ||
|
||
@callviz(_format="png", show_link_value=False) | ||
def rev(arr, new): | ||
if arr == []: | ||
return new | ||
|
||
return rev(arr[1:], [arr[0]] + new) | ||
|
||
fib(5) | ||
rev(list(range(6, 0, -1)), []) | ||
``` | ||
|
||
## Links | ||
|
||
=> https://github.com/theobori/callviz https://github.com/theobori/callviz |
Oops, something went wrong.