MiniLangCompiler
A fully functional Mini Language Compiler built from scratch in Python — complete with a Lexer, Parser, AST, Code Generator, and a Virtual Machine (VM) that executes the generated bytecode.
Features
- Hand-written Lexer and Parser
- Custom AST (Abstract Syntax Tree) structure
- Code generation to low-level bytecode
- Virtual Machine execution with a stack-based model
- While loops, assignments, arithmetic operations, and print statements
- Optional CLI for quick expression evaluation (
3 + 5→8)
Project Structure
MiniLangCompiler/
│
├── ast_nodes.py # Defines the AST structure
├── lexer.py # Converts source code into tokens
├── parser.py # Builds the AST from tokens
├── codegen.py # Generates bytecode from AST
├── vm.py # Executes bytecode using a virtual machine
├── test_codegen.py # Unit tests for compiler components
├── run_bytecode.py # Full compilation & execution pipeline
└── compiler_cli.py # CLI version for simple expressions (3 + 5)
Compilation Flow
Source Code → Lexer → Tokens → Parser → AST → CodeGen → Bytecode → VM → Output
Examples
Input (program.txt):
let x = 10;
let y = 0;
while x {
y = y + x;
x = x - 1;
}
print y;
Run:
python run_bytecode.pyOutput:
55
Run:
python compiler_cli.pyThen input:
3 + 5
Output:
8
- Install dependencies:
pip install -r requirements.txt- Start server (local):
uvicorn app:app --reload- POST to
http://127.0.0.1:8000/executewith JSON:
{
"source": "let x = 1; let y = 2; print(x + y);"
}- Response includes AST, bytecode, output, stack, and env.
- Build image:
docker build -t minilang-compiler .- Run container:
docker run -p 8000:80 minilang-compiler- Call via
http://localhost:8000/execute.
docker-compose up --build- Config added in
.github/workflows/ci.yml. - Runs tests on
pushandpull_requestonmain.
Theory Summary
The compiler is divided into five key stages:
- Lexical Analysis — Tokenizes the raw input.
- Parsing — Builds an AST from tokens.
- Semantic Representation (AST) — Encodes structure.
- Code Generation — Produces bytecode instructions.
- Execution (VM) — Interprets the bytecode sequentially.
Example Bytecode Output
For the above program:
000: ('PUSH', 10)
001: ('STORE', 'x')
002: ('PUSH', 0)
003: ('STORE', 'y')
004: ('LABEL', 'L0')
005: ('LOAD', 'x')
006: ('JUMP_IF_FALSE', 'L1')
007: ('LOAD', 'y')
008: ('LOAD', 'x')
009: ('ADD',)
010: ('STORE', 'y')
011: ('LOAD', 'x')
012: ('PUSH', 1)
013: ('SUB',)
014: ('STORE', 'x')
015: ('JUMP', 'L0')
016: ('LABEL', 'L1')
017: ('LOAD', 'y')
018: ('PRINT',)