Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

microjava-compiler

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.

The language

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.

Pipeline

LexingJFlex generates the scanner from spec/mjlexer.flex.

ParsingCUP 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 analysisSemanticAnalyzer 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 generationCodeGenerator 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.

Building and running

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 clean

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

Tests

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.

Notes

todo.txt records the sharp edges found while building it, in Serbian. Two worth repeating in English:

  • The lexer splits 923a into INTEGER_LITERAL(923) and IDENTIFIER(a) rather than rejecting it, which pushes the error to the parser.
  • read(c) for a char consumes the following character too, so a subsequent read(c) picks up the wrong input — the same buffering problem C's getchar() has.

Origin

Coursework for Compiler Construction (Programski prevodioci 1) at the School of Electrical Engineering, University of Belgrade.

About

a compiler for the MicroJava teaching language, targeting the MicroJava VM

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages