Systems
J-- to MIPS Compiler
A Python compiler that parses J-- programs, resolves names and types, and generates MIPS assembly with a custom stack calling convention.
- Python
- ANTLR4
- Compilers
- MIPS
- Semantic Analysis
Turning language rules into assembly
I built the stages that turn a J-- source program into MIPS assembly: parsing, semantic analysis, storage layout, and code generation.
The compiler carries resolved names and types into the backend, where they determine how variables, expressions, and function calls map to registers and stack storage.
From source code to execution
J-- source
main() { printi(42); }Parse the program and resolve the call.
MIPS instructions
li $v0, 42 addiu $sp, $sp, -4 sw $v0, 0($sp) jal printi nop addiu $sp, $sp, 4Push the argument and call the print routine.
Expected output
42
Parsing and shaping the program
I implemented a compiler for J--, a small language with integer and Boolean variables, functions, and structured control flow. ANTLR4 grammars define its syntax and operator precedence. Tree-shaping rules then remove punctuation and flatten grammar wrappers into an abstract syntax tree for analysis.
One subtle case is the minimum signed 32-bit integer. Its positive magnitude is outside the signed range, so I fold unary minus into numeric literals before checking bounds. This lets the compiler accept the valid negative value without accepting an out-of-range positive literal.
Resolving names and checking meaning
Four passes build up the information code generation needs. The first collects global declarations and function signatures, allowing functions to refer to others declared later. The second walks lexical scopes and connects each name use to its declaration.
The next pass propagates types through expressions and checks operators, call arguments, conditions, and return values. A final pass checks rules such as whether break appears inside a loop and whether integer literals fit in range.
Those declaration links connect the frontend to the backend. A variable reference retains the identity and type established during analysis; storage layout adds the label or stack offset needed to access it in assembly.
Generating calls and control flow
The backend assigns labels to globals, frame offsets to local variables, and shared storage to string literals. A custom stack calling convention evaluates arguments left to right, preserves frame and return registers, and returns values in $v0. Stack temporaries keep intermediate expression values safe across function calls.
Labels and branches implement conditionals, loops, and nested break statements. Boolean operators short-circuit, so the right-hand expression runs only when needed. The emitter inserts delay-slot no-ops and generates six runtime wrappers for simulator input, output, and termination.
Catching invalid programs
Syntax and semantic diagnostics stop invalid programs before assembly emission. Generated code also checks division errors and catches a non-void function that reaches its end without returning. The latter complements the simpler semantic check that a return statement exists somewhere in the function.
The source implements the full set of compiler stages. End-to-end execution remains unverified in the available review environment because the external AST support library was unavailable.
Tools and technologies
- Python
- ANTLR4
- MIPS assembly
- Make
- cpsc411 AST support
Python implements semantic analysis, storage layout, and MIPS emission. ANTLR4 supplies generated lexer and parser machinery from my grammars; the external cpsc411 library supplies tree shaping and traversal support. Make coordinates generation and compiler use.