# lisp-cc — a K&R C compiler in Lisp, the Lisp it runs on, and the Unix V7 C library Three programs that prove each other: - **A K&R C compiler written in Bruce Lisp**, a usable subset of MacLisp — full K&R 1978 plus the V7 struct extensions, generating arm64 assembly for macOS and Linux, leaning on the system toolchain only below the assembly line. - **brucelisp**, the Bruce Lisp interpreter — thirteen files of K&R C, just enough MacLisp to host the compiler, and compilable *by* the compiler. - **The Unix V7 C library** (Bell Labs, 1979), compiled by that compiler, linked into the programs it builds — including brucelisp itself. It began as a student assignment: a Tiny C front end written for *Compiler Techniques* (Computer Science Honours, 1987), Franz Lisp on a VAX. What survived — three parsers, a semantic analyser, and the lecturer's bulletin-board postings — grew into everything above. The whole story, with the 1987 material and the bugs that hid for 38 years, is in [HISTORY.md](HISTORY.md). > Author: Chris Tham (`christie`), SID 8444828 > Course: CSHons Compiler Techniques / Advanced Compiler Techniques, 1987 > Original platform: Franz Lisp on a VAX running BSD Unix **The proof is the bootstrap.** The seed interpreter (built by the host C compiler) hosts the 1987 compiler compiling the whole world — every module of the V7 library and of the interpreter. The result is an interpreter standing on the 1979 libc, which must pass every suite and recompile the world byte-identically: ```bash make bootstrap ``` ``` rung 1: bl0 (host cc) compiles the v7 library and every bl module rung 2a: bl1 runs the Lisp suite tests run: 175 FAILURES: 0 rung 2b: bl1 hosts the compiler compiled and ran: 36 FAILURES: 0 rung 2c: bl1 runs the maclisp subset tests run: 33 FAILURES: 0 rung 3: bl1 recompiles the world all modules byte-identical BOOTSTRAP FIXED POINT REACHED ``` ## Quick start ```bash make # build brucelisp (seconds, needs only a C compiler) make test # 175 Lisp tests make test-cc # compile C to arm64, link, RUN it, check what it did make test-elf # every unit cross-assembled for Linux/arm64 make test-ml # the MacLisp-flavoured subset make libv7 # the V7 C library, compiled by the 1987 compiler make test-v7cmd # the V7 user commands, compiled AND RUN make bootstrap # the compiler compiles the world; fixed point make test-all # everything above except bootstrap make repl # interactive session with the compiler loaded ``` Compile a C file the v7 way: ```bash bin/cc -o prog prog.c && ./prog ``` Or against the real 1979 C library: ```bash bin/cc -lv7 -o prog prog.c && ./prog ``` `bin/cc` is system cpp → the 1987 Lisp compiler (brucelisp hosts it; `TCLISP` overrides) → system assembler and linker. `-c` and `-S` behave as they always have. Headers are this repository's own [`include/`](include/) — five tiny K&R files shared verbatim with clang — or, under `-lv7`, Seventh Edition's own. In the REPL, the 1987 phases are live: ```lisp (c:opens "int n; f(x) { return x + n; }") (c:xdefns) ; parse tree and typed semantic form ``` ## The compiler The pipeline is three Lisp stages between two system tools: ``` cpp → scanner → parsers (declaration, expression, statement) → semantic analysis (typed, explicitly-coerced forms) → arm64 code generation → as/ld ``` It accepts **K&R 1978, whole**: floating point (all arithmetic in double, constants carried as text so the compiler itself never computes a float), `unsigned`, `short`, bitfields, `goto`, the ternary, array decay in every rvalue context, `sizeof` of expressions, initialised data with constant expressions and address constants, arrays sized by their initialisers, separate compilation with the declaration-then-definition rule, and the **V7 extensions**: whole-struct assignment, struct arguments and returns by value the way pcc did them — callee copies, static return buffer, faithfully non-reentrant. The 1987 core is still the 1987 code — the declaration parser needed six lines of change in 38 years. Everything added is marked `EXTENSION` at the site. The design has not moved either: types are hash-consed so equality is `eq`; the symbol table is the symbols' property lists; codegen is an accumulator and a stack, nothing in registers across a statement — which is why `setjmp` needs no special case and the bootstrap is deterministic. Deliberately absent: general varargs (the printf family exists in the V7 library under a documented register-file convention instead), the *external* struct-by-value ABI, and an optimizer. [PORTING.md](PORTING.md) has the full catalogue of extensions and omissions. ## brucelisp — Bruce Lisp, a usable subset of MacLisp [`brucelisp/`](brucelisp/) is the reference host: about 2,200 lines of K&R C in thirteen modules, laid out the way a Unix programmer would have done it in 1984, troff man page included. It is a *useful subset with no compatibility claim*: dynamically scoped, symbols with property lists, `prog`/`go`/ `return`, lexprs, fexprs, macros in the Franz convention, read-time backquote, `*catch`/`*throw`, interned small fixnums — and deliberately no garbage collector, no flonums, no arrays: it exists to run one compiler, and numbers that matter to the compiler travel as text. The same K&R C compiles three ways: by clang against the host libc (the seed `bl0`), by the 1987 compiler against the host libc, and by the 1987 compiler against the V7 library (`bl1`, the bootstrap generations). [`brucelisp/README`](brucelisp/README) explains the layout and what the 1987 bulletin postings prove about the original BruceLisp it reconstructs. ## The V7 C library [`libv7/`](libv7/) is the C library of Seventh Edition Unix, from the Henry Spencer tape: the real `struct _iobuf` stdio with `getc`/`putc` as macros, the 1979 first-fit `malloc`, `doscan`, `ecvt`, the strings and ctype — **47 files compiled byte-for-byte as they came off the tape**. The host libc is only the floor: a dozen fd system calls (v7's `open` flags are the host's, unchanged) and `modf`/`ldexp`. The pdp-11 assembly corners are rewritten in period C: `_doprnt`, printf and scanf entries that name seven integer and seven floating parameters — exactly what the two arm64 register files hold — and an `sbrk` that moves the break through a static arena. Every departure is marked at the site and catalogued in [`libv7/README`](libv7/README). ## The v7 commands The conformance suite is not a feature list — it is [`v7cmd/`](v7cmd/): **twenty-six user commands of Seventh Edition Unix**, compiled by the 1987 compiler against the 1979 library, then run and judged. `grep` greps — anchors, classes, closures; `sort` puts two thousand lines in the order the host's sort agrees with; `diff -e` emits an ed script that replays the first file into the second; `od`, `cal`, `units`, `tsort`, `join`, `deroff`, `cb` and the rest behave. Twenty-two compile from verbatim tape source; `cat`, `units`, `diff` and `sort` carry marked one-line adaptations where they touched `fstat`, `signal` or `exec` — below this library's floor ([`v7cmd/README`](v7cmd/README) has the list). Real 1979 programs taught the compiler its last lessons: array parameters adjusting to pointers, enums as integrals, one member name in several structs, `unsigned short` as its own base type, a goto label riding against `default:`, `sizeof(x)` keeping the declared type, and `main` getting the crt0 contract — its return value leaves through `exit`, so the 1979 stdio always flushes. ## Verified platforms All runs on real hardware or real arm64 VMs (Linux via Apple `container`), judged by executed binaries and the bootstrap fixed point — never by inspection: | Platform | libc / cc | Lisp suite | compile-and-run | v7 commands | bootstrap fixed point | |---|---|---|---|---|---| | macOS 15 arm64 | Apple clang 21 | 175/175 | 36/36 | 34/34 | ✓ byte-identical | | Debian 13 arm64 | glibc, gcc 14.4 | 175/175 | 36/36 | 34/34 | ✓ byte-identical | | Ubuntu 24.04 arm64 | glibc, gcc 13.3 | 175/175 | 36/36 | 34/34 | ✓ byte-identical | | Alpine 3.20 arm64 | **musl**, gcc 13.2 | 175/175 | 36/36 | 34/34 | ✓ byte-identical | ## Repository map | Path | What it is | |---|---| | `*.l`, [`msgs`](msgs), [`bl0`](bl0), [`echo.s`](echo.s) | **The 1987 coursework**, bug-fixed in place, extensions marked. The postings and the VAX warm-up exercise, untouched | | [`framework/`](framework/) | Reconstructions of the lecturer's course framework, plus the new arm64 `asm.l`. Deductions documented in [`framework/README.md`](framework/README.md) | | [`brucelisp/`](brucelisp/) | The Bruce Lisp interpreter | | [`libv7/`](libv7/) | The V7 C library | | [`v7cmd/`](v7cmd/) | The V7 user commands — the conformance suite | | [`include/`](include/) | Five K&R headers shared verbatim with clang | | [`bin/cc`](bin/cc) | The v7-flavoured compiler driver | | [`lib/`](lib/) | Franz prelude, MacLisp layer, REPL boot | | [`test/`](test/) | 175 Lisp tests, 36 compile-and-run C programs, 34 v7-command checks, 33 MacLisp tests, the ELF sweep, the bootstrap | | [`doc/`](doc/) | The Franz Lisp Manual (1983) | | [HISTORY.md](HISTORY.md) | 1987, the bulletin board, the revival, then-vs-now | | [PORTING.md](PORTING.md) | What was supplied, what was extended, what cannot work | ## Licence No licence specified. Coursework from 1987, preserved as-is.