A compiler for MicroJava — a Java-like teaching language — targeting the MicroJava virtual machine.
It runs the full pipeline: lexing, parsing with error recovery, AST
construction, semantic analysis, and bytecode generation. The output is a
.obj file the MicroJava VM executes directly.
MicroJava is a cut-down Java: static typing, one program per file, no inheritance, no exceptions, no strings as a first-class type. What it does have is enough to be a real compilation target.
program referentni_program
const int size = 10;
int pos[];
{
void main()
int x, i;
{
pos = new int[size];
i = 0;
do {
pos[i] = 0;
i++;
} while (i < size);
read(x);
do {
if (x < size) {
pos[x]++;
}
read(x);
} while (x >= 0);
}
}Supported: constants, global and local variables, classes with fields, enums,
methods with formal parameters, arrays, if/else, do/while, switch,
break, continue, new, and the built-in read / print.
Lexing — JFlex generates the scanner from
spec/mjlexer.flex.
Parsing — CUP generates an LALR
parser from spec/mjparser_astbuild.cup. The grammar carries 15 error
productions, so a malformed program doesn't stop at the first mistake — the
parser resynchronises and keeps going, reporting several errors per run.
spec/mjparser.cup is the earlier version without AST actions, kept for
reference.
AST — parser actions build a typed node per production into
rs.ac.bg.etf.pp1.ast, with a generated VisitorAdaptor base class. Every
later phase is a visitor over that tree rather than another pass over tokens,
which is why semantic analysis and code generation stay independent of the
grammar.
Semantic analysis — SemanticAnalyzer walks the AST against a symbol
table: declaration before use, no redeclaration in scope, type compatibility
in assignments and expressions, correct argument counts and types at call
sites, main present with the right signature, break/continue only inside
loops. CounterVisitor collects the counts needed later — formal parameters,
local variables, class fields — so the code generator knows the frame sizes
before it emits anything.
Code generation — CodeGenerator visits the annotated AST and emits
MicroJava bytecode: stack operations, jumps patched once their targets are
known, method prologues sized from the counter pass, and virtual function
tables for classes.
Requires a JDK and Apache Ant. Everything it needs is vendored in lib/:
JFlex, CUP, log4j, plus the course-provided symboltable-1-1.jar and
mj-runtime-1.1.jar (the VM).
ant make-lexer # JFlex -> MJLexer.java
ant make-parser # CUP -> MJParser.java, sym.java, ast/
ant compile # compile a MicroJava source file to .obj
ant run-obj # execute the .obj on the MicroJava VM
ant disasm # disassemble the generated bytecode
ant cleanmake-lexer and make-parser regenerate MJLexer.java, MJParser.java,
sym.java and the ast/ package. Those are generated artifacts that happen to
be committed — edit the .flex and .cup specs, not the Java they produce.
test/ is organised by pipeline phase, so each stage can be exercised in
isolation:
01_lexical_tests/ tokenisation
02_syntax_tests/ parsing and error recovery
03_semantics_tests/ type and scope checking
04_code_generation_tests/ emitted bytecode
public_tests/ course-provided suite
Files paired as testN_good.mj / testN_bad.mj check that valid programs
compile and invalid ones are rejected — the second half being the part that's
easy to get wrong. referentni_program.mj is the reference program the
compiler is expected to handle end to end.
todo.txt records the sharp edges found while building it, in Serbian. Two
worth repeating in English:
- The lexer splits
923aintoINTEGER_LITERAL(923)andIDENTIFIER(a)rather than rejecting it, which pushes the error to the parser. read(c)for acharconsumes the following character too, so a subsequentread(c)picks up the wrong input — the same buffering problem C'sgetchar()has.
Coursework for Compiler Construction (Programski prevodioci 1) at the School of Electrical Engineering, University of Belgrade.