From source text to running bytecode — lexing, LALR parsing, a 157-node AST, full semantic analysis, and code generation for a stack machine. Built for a university compiler-construction course, with namespaces as a language extension.
A real program (test301.mj) with namespaces and arrays, taken from source all the way to output on the MicroJava VM. Every line below is actual compiler output.
program test301 namespace Konstante { const int jedan = 1, pet = 5; {} } namespace Nizovi { int niz[]; {} } { void main() int bodovi; { bodovi = 0; bodovi++; bodovi = bodovi + Konstante::jedan; bodovi = bodovi * Konstante::pet; Nizovi::niz = new int[3]; Nizovi::niz[1] = 2; print(bodovi); return; } }
INFO Deklarisana konstanta: const Konstante::jedan = 1 INFO Deklarisana konstanta: const Konstante::pet = 5 INFO Deklarisana promenljiva: Nizovi::niz INFO Obradjuje se funkcija main ✓ Parsiranje uspesno zavrseno!
codeSize=171 dataSize=3 mainPC=0 0: const_0 1: store_0 2: load_0 3: const_1 4: add 5: store_0 10: load_0 11: const_5 12: mul 20: print 21: const_3 22: newarray 1 ... $ run test301.obj 9 6 6bc -4 Completion took 1 ms
Reflecting what is implemented and verified in the source — not merely what the grammar accepts.
Beyond the base language, MJCompiler adds namespaces. Declarations inside
namespace X { … } are managed as qualified symbols and accessed with
X::member — for constants, variables, methods, and types alike. The
semantic pass validates that every referenced namespace exists and resolves each
qualified name against the symbol table.