Back to portfolio

Case study · ENSIMAG

DECA Compiler

A complete compiler for Deca — a Java-like object-oriented teaching language — from source text down to assembly for the IMA abstract machine. Built in a team of five over a semester.

The source lives in a private academic repository and stays private: it is coursework, and publishing it would hand the solution to future students. This page is the write-up.

Target

IMA abstract machine

Team

5 engineers, ~200 commits mine

Test corpus

440+ Deca programs

Extension

CORDIC trigonometry

The pipeline

The compiler runs the four classical phases, each one independently testable and each one wired to its own stage in continuous integration. A program that fails at any phase must fail with a precise, located message rather than a stack trace — a large share of the test corpus exists to pin exactly that behaviour.

01Lexical analysis

Turns the source text into a token stream, rejecting malformed literals and identifiers with a located error message.

02Syntax analysis

43 valid · 68 invalid

Builds the abstract syntax tree from the token stream and reports grammatical errors with their position in the source.

03Contextual analysis

72 valid · 103 invalid

Walks the tree to resolve identifiers, build the class and symbol tables, and type-check every expression — the stage where most of a program's real errors surface.

04Code generation

98 valid · 18 invalid

Emits assembly for the IMA abstract machine: register allocation, object layout and method dispatch, arithmetic overflow checks.

What I owned

My main contribution was the delivery pipeline and the validation strategy around it. The GitLab CI pipeline mirrors the compiler itself — one stage per phase, so a regression names the phase that broke instead of reporting one red build. Maven artifacts and the local repository are cached between stages, which is what keeps a four-stage pipeline from paying the dependency-resolution cost four times.

The test suite runs the compiler against 440+ Deca programs, roughly half of which are expected to fail — the invalid cases that check the compiler rejects bad input for the right reason. Running those serially made the feedback loop too slow to be useful during development, so I parallelized the launchers and added JaCoCo coverage reporting to show which parts of the compiler the corpus actually reached.

I also standardized the team's commit conventions on Commitizen — which is what led to my own multilingual fork of it, so contributors could read the prompts in their own language.

Extension: trigonometry without a math library

The IMA machine offers no transcendental functions, so sine and cosine had to be built from arithmetic the target actually has. We implemented CORDIC: the angle is first folded into [-π, π], then 27 iterations of shift-and-add rotations converge on the result, each step reusing a precomputed arctangent table and a fixed scaling constant. No multiplication by a lookup table, no floating-point library — just shifts, adds and a sign test per iteration. We validated the output against reference values across the range and plotted the residual error to confirm the iteration count was enough for the precision we claimed.

How much energy does a compiled program cost?

The part of the project I found most interesting was not in the specification. We took one identical program, wrote it three times — in Deca, in Java and in C — and measured the energy each version actually consumed, reading Intel RAPL counters (/sys/class/powercap/intel-rapl) before and after execution to get a figure in microjoules. It turns a compiler exercise into a measurable question: what does our code generation cost, in joules, against a production compiler and against hand-written C?