Welcome to CoreLox, your gateway to a minimalist, bytecode-driven Lox interpreter written in C. If you’ve ever wanted to see how a language like Lox works “closer to the metal,” you’re in the right place.
In a world where high-level safety nets (like Rust’s) can shield you from the finer details of memory management, CoreLox strips everything down to the essentials—giving you direct control over every byte. Lox itself is a small, elegant language taught in the seminal Crafting Interpreters book. By building it in C, we get:
If RustyLox is the safe, robust older sibling, CoreLox is the thrill-seeking younger one, designed for speed and maximum control.
Lox is a dynamically typed language with a JavaScript-like syntax. A “Hello, World!” in CoreLox looks like this:
print "Hello from CoreLox!";
Under the hood, CoreLox compiles this single statement into bytecode and hands it to a small but powerful virtual machine that runs your code instruction by instruction.
git clone https://github.com/mvishiu11/CoreLox.gitcd CoreLoxmake./corelox path/to/your_file.loxIf everything worked, you’ll see the output right in your terminal. For deeper internals, compile with debug flags to watch chunk creation, memory usage, etc. Details in that are available on the GitHub repo.
CoreLox compiles your Lox code into a series of Chunks. Each chunk is an array of instructions (bytecode) representing operations like arithmetic, control flow (if, while), or function calls.
var x = 3; print x + 1;
The compiler parses that, then emits instructions for:
== <script> == 0000 1 OP_CONSTANT 1 '3' 0002 | OP_DEFINE_GLOBAL 0 'x' 0004 2 OP_GET_GLOBAL 2 'x' 0006 | OP_CONSTANT 3 '1' 0008 | OP_ADD 0009 | OP_PRINT 0010 | OP_NIL 0011 | OP_RETURN
Unlike Rust, where the compiler ensures memory safety automatically, C puts you in the driver’s seat. CoreLox uses macros like GROW_ARRAY or FREE_ARRAY to expand or shrink memory for chunks. Great power, great responsibility, etc. ;)
Once CoreLox has your bytecode, it hands it off to a single-threaded VM. If you want concurrency, check out RustyLox—but if raw speed is your jam, CoreLox suffices.
Here’s a snippet of the grammar. Variables, loops, conditionals, etc.:
program = { declaration }, EOF ;
declaration = varDecl
| funDecl
| statement ;
funDecl = "fun", function ;
function = IDENTIFIER, "(", [ parameters ], ")", block ;
parameters = IDENTIFIER, { ",", IDENTIFIER } ;
varDecl = "var", IDENTIFIER, [ "=" expression ], ";" ;
statement = exprStmt
| forStmt
| ifStmt
| printStmt
| returnStmt
| whileStmt
| switchStmt
| block
| breakStmt
| continueStmt ;
returnStmt = "return", [ expression ], ";" ;
breakStmt = "break", ";" ;
continueStmt = "continue", ";" ;
block = "{", { declaration }, "}" ;
exprStmt = expression, ";" ;
printStmt = "print", expression, ";" ;
whileStmt = "while", "(", expression, ")", statement ;
forStmt = "for", "(", ( varDecl | exprStmt | ";" ),
[expression], ";",
[expression], ")", statement ;
switchStmt = "switch", "(", expression, ")",
"{", { switchCase }, [ defaultCase ], "}" ;
switchCase = "case", expression, ":", { statement } [ "fall" ];
defaultCase = "default", ":", { statement } ;
ifStmt = "if", "(", expression, ")" statement,
{ "elif", "(", expression, ")", statement },
[ "else", statement ]
| "if", expression, "then", statement,
{ "elif", expression, "then", statement },
[ "else", statement ] ;
expression = assignment ;
assignment = IDENTIFIER, "=", assignment
| ternary ;
ternary = logic_or, "?", expression, ":" ternary
| logic_or ;
logic_or = logic_and, { "or", logic_and } ;
logic_and = equality, { "and", equality } ;
equality = comparison { ( "!=" | "==" ) comparison } ;
comparison = term { ( ">" | ">=" | "<" | "<=" ) term } ;
term = factor { ( "-" | "+" ) factor } ;
factor = unary { ( "/" "*" "%" ) unary } ;
unary = ( "!" | "-" ) unary
| call ;
call = primary, { "(", [ arguments ], ")" } ;
arguments = expression, { ",", expression } ;
primary = NUMBER
| STRING
| "true"
| "false"
| "nil"
| "(" expression ")"
| IDENTIFIER ;For full details, see the Language Overview tab or the official source.
makeprint "Hello", then run ./coreloxFork the repo, create a branch, and submit a PR. Tweak memory macros, add new opcodes—whatever you like!
CoreLox brings Lox to the low-level arena. With manual memory and a straightforward VM, you can deeply understand how an interpreter works—while still writing user code in a clean, dynamic language. Enjoy!
CoreLox implements a full scope of Lox. Let’s walk through the key constructs:
varUse var to declare a variable, optionally with an initializer:
var x = 10; print x; // 10 var y; y = 42; print y; // 42
funDefine a function with fun, name, parameters, and a block:
fun greet(name) {
print "Hello, " + name;
}
greet("World"); // "Hello, World"
if / elif / elseCoreLox uses elif for multiple branches:
var x = 15;
if (x < 10) {
print "x < 10";
} elif (x < 20) {
print "x < 20";
} else {
print "x >= 20";
}
whilevar i = 0;
while (i < 3) {
print i;
i = i + 1;
}For more, see examples in Playground From var to fun, from switch toreturn, you’ve got all the building blocks of Lox at your disposal. We hope this overview helps you start coding in CoreLox quickly!