Skip to content

DosWorld/toc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Trubo Oberon Compiler (TOC)

A self-hosting Oberon-07 compiler for 16-bit MS-DOS real mode

license platform self-hosted

Wirth's Oberon-07, compiling itself, targeting a 1981 processor. Written entirely in Oberon, running on real DOS hardware — no C, no assembler front end, no host toolchain required at runtime.


Why this exists

There is no free, modern, actively-usable Pascal-family compiler that targets (and works) 16-bit MS-DOS real mode. Borland stopped at Turbo Pascal 7 / Borland Pascal 7 in the early '90s and never open-sourced it. FreePascal — the natural place to look — has supported 16-bit i8086 code generation since around 2014, but it was always the neglected back corner of the project: undermaintained, thin on runtime support, and never treated as a first-class target the way its 32/64-bit backends are. I had been waiting since the late 1990s for a serious free 16-bit target from the Pascal world (i8086-msdos in FreePascal was the closest anyone got, decades after the fact), and it never became something you could really build on.

So this project doesn't try to be that. It picks Wirth's other, smaller language — Oberon-07, Pascal's own successor — and builds a compiler for it from scratch, purpose-built for 16-bit DOS real mode, with no inherited 32-bit assumptions to work around. The compiler, linker, dependency scanner, archive manager, and test tools are all written in Oberon-07 and compile themselves, on the target architecture (8086 real mode), from a single checked-in bootstrap binary. Feed it its own source and it reproduces itself byte-for-byte — a full fixpoint, verified on every change.

That means:

  • No hidden C runtime. SYSTEM.MOD + a small SYS.ASM are the entire foundation.
  • No 32-bit protected-mode escape hatch. Real segmented 8086 memory, real 64 KB segments, real far pointers.
  • A compiler that could, in principle, have been built in 1987 — but with the discipline of zero-heap-leak, byte-reproducible modern engineering practice behind it.

Highlights

  • Self-hosting, byte-stable. toc /ENTRY=Run TOC.MOD rebuilds the whole compiler in a single process; two consecutive generations are byte-identical. See TESTS/test_selfhost.sh.
  • Zero heap leaks, guaranteed. Every compile and every link reports 0 leaked paragraphs — enforced by LeakGuard instrumentation and regression tests, not just hoped for.
  • All-in-one driver. toc.exe is the dep-scanner, incremental compiler, and smart linker in one binary and one command — no makefiles required to build an Oberon program.
  • Real DOS constraints, handled properly. Per-module 64 KB code segments, far pointers (segment:offset), a large memory model, EMS-backed temp files, and a linker that streams instead of holding everything in RAM.
  • 294-row regression manifest plus DOS-side executable and unit-test suites — every codegen change is checked against real compiled/linked/run output, not just "it compiled."
  • Turbo Debugger-compatible debug info. Compile with (*$D+*), link with /G, and get a Borland TDS/TDINFO v2.08 block appended straight onto the .EXE — inspect with the included tdinfo.exe.
  • Linkable with external assembly. The linker consumes standard RDOFF2 object files, so hand-written assembly modules assembled with NASM, YASM, or MSA2 (-f rdf) can be linked directly alongside Oberon-compiled .rdf/.om modules — no C shim required.

What is Oberon, and why does this dialect matter?

Oberon is Niklaus Wirth's successor to Pascal and Modula-2 — a small, strongly-typed systems language designed to be compilable by a single programmer in a single pass, with no preprocessor and (almost) no undefined behavior. Oberon-07 (2007) is Wirth's own later simplification of the language.

This project implements a practical DOS dialect of Oberon-07: the core language plus what real 16-bit systems programming needs — SYSTEM intrinsics for port I/O and inline machine code, FAR/NEAR procedures, typeless VAR parameters, and an INLINE mechanism for embedding raw opcodes. See DOCS/OBERON07.EBN for the authoritative grammar and every deviation from the standard.


Quick start

Hello, world:

MODULE Hello;

IMPORT Out;

PROCEDURE Run*;
BEGIN
  Out.Open;
  Out.String("Hello, world!"); Out.Ln
END Run;

END Hello.
BIN\TOC.EXE /ENTRY=Run Hello.Mod
Hello.exe

A slightly bigger taste of the language

MODULE Example;
IMPORT Out;

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.
BIN\TOC.EXE /ENTRY=Run Example.Mod
Example.exe
5 4 3 2 1

See EXAMPLES/ for larger programs, including a Forth interpreter.


Language features

Area Support
Types INTEGER, LONGINT (32-bit), WORD (16-bit unsigned), BYTE, CHAR, BOOLEAN, SET, REAL/LONGREAL (x87), ARRAY (fixed/open/multi-dim), RECORD with single inheritance, POINTER, FAR/NEAR procedure variables
Control flow IF/ELSIF/ELSE, WHILE/ELSIF, FOR/BY (negative step), REPEAT, CASE (INTEGER or CHAR, incl. ranges)
Procedures FAR/NEAR, nested, EXTERNAL, INLINE (whole-body and statement forms), FORWARD, typeless VAR params, full actual-parameter type checking
Object model POINTER TO RECORD extension, runtime IS type tests (compile-time folded where possible), NEW/DISPOSE with runtime type tags
Modules Qualified imports, namespace isolation, $L/$M/$R directives, dead-code/unused-symbol warnings
SYSTEM ADR, SEG, OFS, PTR, GET/PUT, MOVE, FILL, PORTOUT/PORTIN, Intr, LSL/LSR/ASR/ROR/AND/IOR/XOR
Expressions Constant folding, implicit INTEGERREAL coercion, SET operators, IN, array bounds checks ($R+/$R-), SIZE/LEN
Codegen Strength reduction (mul/div/mod by powers of two and small constants → shift/add), peephole cleanup, RDOFF2 object format with smart (dead-module-eliminating) linking

Full detail lives in DOCS/OBERLANG.MD and MANUAL.MD.


Architecture

SCAN.MOD    tokens (sym, id, ival, rval, sval, line)
    |
PARSER.MOD  module / declarations / statements / types  (single-pass, no AST)
    |
PEXPR.MOD   expressions: designators, actual params, SYSTEM intrinsics
    |            |                |
SYMS.MOD   CGEN.MOD --> RDOFF.MOD   IMPORT.MOD --> .rdf object file
                              |
                          LINK.MOD  --> smart-linked MZ .EXE

toc.exe bundles all of the above plus dependency scanning into one binary. toc /ENTRY=Run MyProg.Mod scans, compiles whatever's stale, and links — one command, no makefile needed for user programs.

Self-hosting bootstrap

BOOT/TOC.EXE   (checked-in seed binary — never touched by `make`)
  |-- SRC/LIB/*.MOD   -> BIN/OBERON.OM   (standard library archive)
  |-- SRC/TOC/*.MOD   -> BIN/TOC.EXE     (compiler + linker + dep-scan)

BIN/TOC.EXE
  |-- SRC/TOOLS/TOLIB.MOD   -> BIN/TOLIB.EXE    (archive manager)
  |-- SRC/TOOLS/RDFGREP.MOD -> BIN/RDFGREP.EXE  (RDOFF/.om/.exe inspector)
  |-- TESTS/TESTALL.MOD     -> BIN/TESTALL.EXE  (DOS-side test driver)

Rebuilding SRC/TOC with BOOT/TOC.EXE produces toc1.exe; rebuilding the same sources with toc1.exe produces toc2.exe. toc1.exe == toc2.exe, byte-for-byte, every time. That's the self-hosting proof, and it's run as a regression test, not just a one-time milestone.


Standard library

Fourteen modules, merged into one BIN/OBERON.OM archive:

Module Purpose
SYSTEM Runtime: allocation, halt, FPU detection, INT calls, bitwise ops
Out / Out87 Formatted stdout, including REAL/LONGREAL (x87)
In Buffered stdin: char, integer, long integer, token
Files Buffered file I/O — open, read, write, seek, close; EMS-backed temp files
Strings Length, Equal, Copy, Append, Pos, Insert, Delete, Extract
IO Raw rider-based I/O to any DOS file handle
Mem Raw heap: Alloc/Free with a 65 KB guard
Dos Args, Exec, FindFirst, date/time, interrupts
Time DOS packed date/time <-> DateTime record conversion (PackTime/UnpackTime)
Crt Terminal control via INT 10h: cursor, clear, colour
Math FPU math: sin, cos, sqrt, exp, ln, abs, …
EMS Expanded-memory page-frame management
Test Unit-test framework: AddTest, RunTests, Assert*
RdfLoad Runtime RDOFF2 loader — dynamic code loading from a running program

Test suite

make test     # 294-row manifest — codegen bytes, RDOFF structure, error messages, exit codes
make testall  # + DOS-side executables (Section 17) and unit tests (Section 18)

Every codegen or language change is checked three ways: the compile-time manifest (does it produce the right object bytes / the right diagnostic?), DOS-side executables (does the linked program actually behave correctly when run under xt?), and the self-hosting fixpoint (does the compiler still reproduce itself?).


Documentation

File Contents
MANUAL.MD Language reference, ABI, library APIs, porting guide
DOCS/OBERON07.EBN Authoritative grammar for this dialect
DOCS/OBERLANG.MD Type sizes, calling convention, codegen patterns, SYSTEM intrinsics
DOCS/IMPLRULE.MD DOS/portability rules, .def file format, emit conventions
DOCS/BUILD.MD Build instructions, toc.exe flags, linker internals
DOCS/TESTS.MD Test suite reference: running, adding, interpreting tests
DOCS/XT.MD xt emulator reference: options, trace format, debugging
DOCS/RDOFF2.MD RDOFF2 object file format specification
DOCS/TD-SPEC.MD Turbo Debugger TDS/TDINFO format notes
DOCS/PORTING.MD Guide for porting code from C/Pascal

License

Public domain. See LICENSE.TXT.

About

Self-hosted Trubo Oberon Compiler for DOS, produces pure 8086/87 code.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors