From formal languages to executable code — a complete, runnable compiler in Java.
FRISCcc compiles a deterministic subset of C all the way down to FRISC assembly (the educational instruction-set architecture used at the University of Zagreb), runs it on a bundled simulator, and can also execute its typed intermediate representation on a tree-walking interpreter and a bytecode virtual machine. It is a complete teaching compiler: nothing is hand-waved, every phase is real, and every example in this repository compiles and runs.
C source → Lexer → Parser → Semantic analysis → Typed IR
→ Optimization (IR→IR passes) → FRISC codegen → Simulation
↘ IR interpreter
↘ Bytecode VM
This compiler is the running example dissected, line by line, throughout the book:
Building a C-Subset Compiler for the FRISC Architecture: From Formal Languages to Executable Code Dr. Karlo Knežević · Self-published, Zagreb, 2026 ISBN 978-953-47198-0-0 · DOI 10.5281/zenodo.20511074 📕 Read it here: doi.org/10.5281/zenodo.20511074 · 📄 Local PDF
The book is a narrative monograph: it builds this exact compiler from first
principles — automata and formal languages, LR(1) parsing, type systems, a
typed IR, an optimizer with semantic-preservation proofs, a FRISC back end, and
two alternative execution engines. The in-repo documentation under
docs/ mirrors the book chapter by chapter; the book is the
authoritative, full-length treatment. See Citing this work.
You don't even need to build — a ready-to-run JAR ships in dist/.
git clone https://github.com/KarloKnezevic/ccompiler.git
cd ccompiler
# Compile a C program to FRISC and run it on the simulator:
java -jar dist/ccompiler.jar --all --run examples/real_world/math_fibonacci_iter/program.c
# → Program output: 6765Run commands from the repository root so the compiler can find the bundled FRISC simulator (
node_modules/friscjs) and theexamples/tree.
The convenience wrapper run.sh does the same and prints a tidy
per-phase report:
./run.sh --all --run examples/real_world/math_fibonacci_iter/program.c| Tool | Version | Used for |
|---|---|---|
| JDK | 21 or newer | building and running the compiler |
| Maven | 3.9 or newer | building from source (not needed to run dist/ccompiler.jar) |
| Node.js | 18 or newer | the bundled FRISC simulator (only for --run) |
Check your toolchain:
java -version # 21+
mvn -version # 3.9+
node --version # 18+The FRISC simulator (friscjs) is vendored under node_modules/, so no npm install is required. It is also an independent package — you can install it on
its own with npm install friscjs and use its assembler/simulator API directly;
see Using friscjs directly.
./build.sh # build all modules, produce cli/target/ccompiler.jar (tests skipped)
./build.sh -t # build and run the full test suite
./build.sh -c # clean buildEquivalently, with Maven directly:
mvn clean package # builds the fat JAR at cli/target/ccompiler.jar
mvn test # runs the test suite (89 tests across the modules)The build produces a single self-contained executable JAR via the Maven Shade
plugin. After building, copy it over the shipped one if you like:
cp cli/target/ccompiler.jar dist/ccompiler.jar.
Invoke either through ./run.sh <flags> <file> or java -jar dist/ccompiler.jar <flags> <file>. Later-stage flags automatically imply the earlier phases.
| Flag | Stops after | Output (in compiler-bin/) |
|---|---|---|
--lex |
lexical analysis | tokens.txt |
--parse |
parsing | ast.txt |
--sem |
semantic analysis | typed tree |
--ir |
IR generation | intermediate.ir |
--frisc |
code generation | a.out (FRISC assembly) |
--all |
full pipeline | all of the above |
--run |
+ simulation | program output on stdout |
| Flag | Meaning |
|---|---|
--O0 |
no optimization — IR passes straight to the back end (default) |
--O1 |
baseline optimizing pipeline (constant folding, copy/value propagation, dead-code elimination, CSE, LICM, strength reduction, …) |
--dump-ir |
also dump pre- and post-optimization IR into compiler-bin/ir-dumps |
Skip code generation entirely and execute the typed IR:
# Tree-walking interpreter:
java -jar dist/ccompiler.jar run-ir examples/real_world/math_fibonacci_iter/program.ir
# → Return value: 6765 (Executed steps: 478)
# Bytecode virtual machine (lowers IR → bytecode, runs on a stack VM):
java -jar dist/ccompiler.jar run-vm examples/real_world/math_fibonacci_iter/program.ir
# → Return value: 6765 (Dispatched instructions: 1264)
# Inspect the lowered bytecode:
java -jar dist/ccompiler.jar run-vm --dump-bytecode examples/real_world/math_fibonacci_iter/program.irAll three back ends — native FRISC, interpreter, and VM — agree on every result; this equivalence is enforced by the test suite across all example IR files.
Run ./run.sh --help for the complete flag reference (tracing, step/dispatch
watchdogs, custom output directories, and batch IR execution).
A deliberately constrained, deterministic subset of C:
- scalar types
int,char,float; arrays; selectedstructusage; - control flow:
if/else,while,for,return; - functions, globals, locals, and recursion;
- self-contained programs with no standard-library dependence.
Floating-point and 32-bit integer arithmetic that the FRISC ISA lacks in
hardware is lowered to software helper routines (F_MUL, F_DIV, F_MOD,
F_FMUL, F_FDIV, F_I2F, F_F2I).
FRISCcc is a Maven multi-module project. Each phase is its own module with a clean boundary:
| Module | Responsibility |
|---|---|
compiler-common |
shared diagnostics, source locations |
compiler-lexer |
tokenization via ε-NFA → DFA with maximal munch |
compiler-parser |
LR(1) table construction and parsing |
compiler-semantics |
symbol tables, typing, semantic legality |
compiler-ir |
lowering the typed tree to a typed three-address IR |
compiler-opt |
the IR-to-IR optimization passes |
compiler-codegen-frisc |
typed IR → FRISC assembly |
cli |
orchestration, IR interpreter, bytecode VM, simulator driver |
The typed IR is the backbone: every value and operation carries a type,
control flow is explicit through basic blocks (br, jmp, ret), and frame
and slot metadata make the back-end ABI stable. Its canonical grammar lives in
config/ir_definition.txt.
R0 result · R1–R4 scratch/arguments · R5 frame pointer · R6 return
value · R7 stack pointer (grows down from 40000). Entry sequence:
MOVE 40000, R7
CALL F_MAIN
HALT523 ready-to-compile programs live under examples/:
| Category | Count | What it holds |
|---|---|---|
valid/ |
218 | small, focused programs grouped by feature |
real_world/ |
31 | larger end-to-end programs with expected.txt golden output |
fer/ |
186 | course-style test programs |
invalid/ |
88 | programs that must be rejected (diagnostics tests) |
Every real_world example carries its expected output, its generated IR, and
its FRISC assembly, so you can diff against a known-good result.
docs/— the in-repo technical documentation, organized to mirror the book chapter by chapter (lexer, parser, semantics, IR, optimization, codegen, runtime, simulator, performance).docs/book/— the full book PDF.- The book itself, doi.org/10.5281/zenodo.20511074, is the complete, authoritative narrative.
If FRISCcc or its book helped your work, please cite the book (a
CITATION.cff is included for automatic citation):
Knežević, K. (2026). Building a C-Subset Compiler for the FRISC Architecture: From Formal Languages to Executable Code. Zenodo. https://doi.org/10.5281/zenodo.20511074 — ISBN 978-953-47198-0-0.
The work is archived on Zenodo under DOI 10.5281/zenodo.20511074
(ISBN 978-953-47198-0-0).
The FRISCcc compiler source and its in-repo documentation are released under the
MIT License, © 2026 Karlo Knežević. The accompanying book is a
separate work under CC BY-NC-ND 4.0. The bundled FRISC simulator
(node_modules/friscjs) is distributed under its own license.