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 invalidBuilds the abstract syntax tree from the token stream and reports grammatical errors with their position in the source.
03Contextual analysis
72 valid · 103 invalidWalks 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 invalidEmits 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?