CoreLox Documentation

CoreLox Tutorial: Lox Meets C, Head-On

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.

1. Why CoreLox?

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:

  • A bytecode approach that compiles Lox scripts before executing them.
  • Manual memory management for those who want to get their hands dirty with pointers and buffers.
  • A single-threaded, streamlined VM that focuses on performance and simplicity.

If RustyLox is the safe, robust older sibling, CoreLox is the thrill-seeking younger one, designed for speed and maximum control.

2. Lox Quick Start

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.

2.1 Installing CoreLox

  • git clone https://github.com/mvishiu11/CoreLox.git
    cd CoreLox
  • Build the project: make
  • Run a .lox file: ./corelox path/to/your_file.lox

If 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.

3. Under the Hood: Bytecode & Chunks

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.

3.1 Compilation

var x = 3;
print x + 1;

The compiler parses that, then emits instructions for:

  • Declaring a variable
  • Loading the value 3
  • Adding 1
  • Printing the result
  • Resulting bytecode might look like:
== <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

3.2 Memory Management

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. ;)

4. The Virtual Machine

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.

5. Lox Grammar & Basics

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.

6. Differences from RustyLox

  • Manual memory vs. Rust’s ownership
  • Single-threaded vs. concurrency in Rust
  • Manual error checks vs. built-in error handling
  • Potentially faster compile times

7. Getting Started with CoreLox

  • Have a C compiler (gcc/clang) + make
  • Clone & make
  • Write Lox code print "Hello", then run ./corelox

8. Contributing

Fork the repo, create a branch, and submit a PR. Tweak memory macros, add new opcodes—whatever you like!

9. Conclusion: Forge Ahead!

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!