A complete, self-hosting compiler toolchain written entirely in Oberon-07, targeting 16-bit MS-DOS real-mode on the 8086/8087.
oberonc is a self-hosting Oberon-07 compiler that runs on MS-DOS (and on Linux/macOS via the xt emulator). It compiles itself — the entire toolchain is written in Oberon-07 and built from a single checked-in seed binary.
The compiler produces RDOFF2 object files, packs them into .om archive libraries, and links them into standard DOS MZ executables. All tools run natively on real MS-DOS hardware and under xt on modern systems.
- Full Oberon-07 (Wirth 2007) with practical DOS extensions
LONGINT(32-bit signed),WORD(16-bit unsigned),REAL/LONGREAL(x87 FPU)SETtype (16-bit),CASEwith ranges, multi-branchWHILE/ELSIFPOINTER TO RECORDwith single inheritance, runtimeIStype tests,NEW/DISPOSE- Implicit pointer dereference:
p.f==p^.f,p[i]==p^[i] - Nested procedures,
FORWARDdeclarations, procedural variables (FAR/NEAR) INLINEprocedures (raw bytes at call site),EXTERNAL(link to assembly)- Typeless
VARparameters (pass any variable by far address) SYSTEMintrinsics:ADR,SEG,OFS,PTR,MOVE,FILL,LSL/LSR/ASR/ROR,AND/IOR/XOR,PORTOUT/PORTIN,Intr- Implicit
INTEGER→REAL/LONGREALpromotion in binary expressions - Constant folding for
CONST,ARRAYdimensions, andCASElabels
- 8086 real-mode, large memory model (CS ≠ DS ≠ SS at all times)
- Pascal calling convention (callee cleans), FAR/NEAR per-procedure
- RDOFF2 object format with smart linking — only reachable modules emitted
- Per-module code segments (~64 KB limit enforced), one combined data segment
- Runtime array bounds checking (
$R+directive) - Dead-code and unused-symbol warnings (non-fatal, stderr)
- All-in-one
oc.exe: dep-scan + incremental compile + smart link — one command builds everything - Self-hosted: the compiler is written in Oberon and compiles itself; byte-stable fixpoint verified
- 247-row manifest test suite + Section 17/18 unit tests, all run under
xt $Mdirective for stack and heap sizing;$Ldirective to embed assembly.rdffiles
Requirements: GNU make, xt DOS emulator (runs boot/oc.exe on Linux/macOS)
# Build everything from source
make
# Run the 247-row manifest test suite (fast, host-side)
make test
# Full DOS-side test suite including unit tests (~9 min under xt)
make testallCompile and run a program:
# Write Hello.Mod
cat > Hello.Mod << 'EOF'
MODULE Hello;
IMPORT Out;
PROCEDURE Run*;
BEGIN Out.Open; Out.String("Hello, world!"); Out.Ln END Run;
END Hello.
EOF
# Compile + link in one step (output auto-named Hello.exe)
xt run --max=50000000 -c . bin/oc.exe -entry Run Hello.Mod
# Run it
xt run --max=5000000 -c . Hello.exeOn real MS-DOS:
oc.exe -entry Run Hello.Mod
Hello.exe
bin/oc.exe is all-in-one: it scans transitive dependencies, recompiles only stale modules, and links the final executable — no separate linker or build-script generator needed.
# Compile-only (library mode)
xt run --max=50000000 -c . bin/oc.exe MyLib.Mod
# Compile + link to executable (auto-named MyProg.exe)
xt run --max=50000000 -c . bin/oc.exe -entry Run MyProg.ModThe compiler (src/oc/*.Mod), archive manager (src/olib/), and tools (src/tools/) are all written in Oberon-07. The build sequence:
boot/oc.exe (checked-in seed binary, never modified by make)
└─ src/lib/*.mod → bin/OBERON.OM (standard library)
└─ src/oc/*.Mod → bin/oc.exe (compiler + linker)
└─ src/olib/*.Mod → bin/olib.exe (archive manager)
bin/oc.exe
└─ src/tools/rdfgrep.Mod → bin/rdfgrep.exe
└─ tests/testall.Mod → bin/testall.exe
Byte-stable fixpoint: boot/oc.exe → compile sources → oc_gen1.exe → compile same sources → oc_gen2.exe, and oc_gen1.exe == oc_gen2.exe byte-for-byte. This is verified by tests/test_selfhost.sh.
oberonc/
boot/ Bootstrap MS-DOS binaries — immutable seeds
oc.exe All-in-one compiler/linker/dep-scan
olib.exe Archive manager
OBERON.OM Standard library archive
src/
oc/ Compiler + linker source (Oberon-07)
olib/ Archive manager source (Oberon-07)
lib/ Standard library source (Oberon-07 + SYS.ASM)
tools/ rdfgrep.Mod, testall.Mod
bin/ Built binaries: oc.exe, olib.exe, rdfgrep.exe, testall.exe, OBERON.OM
tests/
MANIFEST.TSV 247-row integration test manifest
T*.Mod Section 18 unit tests (Oberon)
testall.Mod DOS-side test driver
Makefile.man Host-side manifest runner
docs/ Reference documentation
MANUAL.MD Programmer manual (language, ABI, library API)
CLAUDE.md Developer guide for Claude Code sessions
All 13 modules are merged into bin/OBERON.OM after make.
| Module | Purpose |
|---|---|
SYSTEM |
Runtime: alloc, halt, FPU detect, INT calls, bitwise ops |
Out |
stdout: char, string, integer, hex, address, newline |
Out87 |
REAL/LONGREAL formatted output (x87 FPU required) |
In |
stdin: char, integer, long integer, token |
Files |
Buffered file I/O: open, read, write, seek, close |
Strings |
Length, Equal, Copy, Append, Pos, Insert, Delete, Extract |
IO |
Raw rider-based I/O to any DOS file handle (stdout/stderr/file) |
Mem |
Raw heap: Alloc(size) / Free(p) with 65 KB guard |
Dos |
DOS interface: args, Exec, FindFirst, date/time, interrupts |
Crt |
Terminal: cursor, clear, colour (INT 10h) |
Math |
FPU math: sin, cos, sqrt, exp, ln, abs, … |
Test |
Unit test framework: AddTest, RunTests, Assert* |
RdfLoad |
Runtime RDOFF2 loader (dynamic code loading) |
MODULE Example;
IMPORT Out, Strings;
TYPE
PNode = POINTER TO Node;
Node = RECORD val: INTEGER; next: PNode END;
VAR head*: PNode;
PROCEDURE Push*(val: INTEGER);
VAR n: PNode;
BEGIN
NEW(n); n^.val := val; n^.next := head; head := n
END Push;
PROCEDURE Pop*(): INTEGER;
VAR v: INTEGER;
BEGIN
v := head^.val; head := head^.next;
RETURN v
END Pop;
PROCEDURE Run*;
VAR i: INTEGER;
BEGIN
Out.Open;
FOR i := 1 TO 5 DO Push(i) END;
WHILE head # NIL DO
Out.Int(Pop(), 0); Out.Char(" ")
END;
Out.Ln
END Run;
END Example.xt run --max=50000000 -c . bin/oc.exe -entry Run Example.Mod
xt run --max=5000000 -c . Example.exe
# → 5 4 3 2 1make test # 247 manifest tests — compile-output checks (bytes, RDOFF structure, errors)
make testall # full DOS-side: manifest + Section 17 executable + Section 18 unit tests| Suite | Rows | What it covers |
|---|---|---|
MANIFEST.TSV |
247 | Codegen bytes, RDOFF structure, error messages, exit codes |
| Section 17 | 15+ | Executable tests: types, control flow, procedures, records, pointers, SYSTEM |
| Section 18 | 9 | Unit tests for compiler modules: Scan, Syms, Cgen, Rdoff, Tar, Def, Parser, TADDR, TFBUF |
| File | Contents |
|---|---|
MANUAL.MD |
Language reference, ABI, library APIs, porting guide |
docs/Porting.md |
Detailed porting guide for converting code from C/Pascal |
docs/oberon07.ebnf |
Authoritative grammar for this dialect |
docs/oberon-language.md |
Type sizes, calling convention, codegen patterns, SYSTEM intrinsics |
docs/implementation-rules.md |
DOS/portability rules, .def file format, emit conventions |
docs/build.md |
Detailed build instructions, oc.exe flags, linker internals |
docs/tests.md |
Test suite reference: how to run, add, and interpret tests |
docs/xt.md |
xt emulator reference: options, trace format, debugging |
docs/rdoff2-spec.md |
RDOFF2 object file format specification |
Public domain. See LICENSE.TXT.