Reading a header need not mean linking libclang

This commit is contained in:
Joseph Ferano 2026-09-12 14:01:12 +07:00
parent 99db8ce73f
commit 693661c4dd

View File

@ -182,22 +182,47 @@ ability to run new code in a stopped program. wasm cannot have this — `--dev`
`dlopen` and the browser has no sockets. JS has no such problem: it evaluates new code trivially. A JS target might be
the *only* place the live dev loop and the browser coexist.
**Reader conditionals, decided in conversation.** A cljc-style subset is accepted, and the author chose Clojure's
inline reader conditionals over file-level target naming. Note what that changes: **this language has no conditional
compilation today**, and that absence drove a decision earlier the same day — `barf` signals on web rather than being
compiled out, precisely because "this bit is desktop-only" is not expressible. Note also that the sand-on-web work
introduced file-level target replacement for *C* sources (`foo.web.c` replaces `foo.c`), so the repo now has a
file-level precedent that this decision deliberately does not follow. Both were put; inline was chosen.
**Also wanted, and independent of all of the above:** `#_` to discard the next form, and repeated (`#_#_`) to discard
that many following forms, as in Clojure. Reader-only, small, and useful immediately rather than whenever a JS backend
happens.
**One dependency:** hiccup is a macro, and the macro expander is blocked on `Form` being a Flan union, which is union
values. The backend can start before that; the DSL cannot.
## 6. C interop as seamless as Zig's
Today: `declare-c` names one C function per line, and the compiler generates the wrapper, the typedefs and the flattened
declaration. 175 of them for raylib. **No header is ever read, deliberately** — which means nothing can check that a
declaration matches the real signature, and that is written down as trusted rather than guaranteed.
Today: `declare-c` names one C function per line and the compiler generates the wrapper, the typedefs and the flattened
declaration — 175 lines for raylib. **No header is ever read, deliberately**, which means nothing verifies that a
declaration matches the real signature. That is written down as trusted rather than guaranteed.
The proposal is to read the header, prefix a namespace, and get `rl/InitWindow` for free — plus possibly automatic
kebab-casing to `rl/init-window`.
The proposal: read the header, prefix a namespace, get `rl/InitWindow` for free, possibly kebab-cased to
`rl/init-window`.
This is a large change in kind, not just in size: it means a C parser or a libclang dependency in the build, and it
trades an explicit, checkable list for an implicit surface. Worth weighing against what `declare-c` already buys, which
is that a binding is one line and the wrapper is generated. The auto-kebab-case question is separable and much smaller —
and note the FFI currently keeps C's own spelling on purpose, so the mapping would need to be reversible.
**The dependency is the crux, and this project already answered the same question once.** Zig's `@cImport` runs clang as
a *library*. That is exactly the dependency rejected in plan.org's "Why LLVM IR as text": a version-pinned C++ library
breaks routinely on upgrade, while a binary on `PATH` does not. Linking libclang walks back into it.
**The middle path that keeps the property:** clang will dump a parsed header as JSON from the command line
(`-Xclang -ast-dump=json`). Still only `clang` on `PATH`, still no library linkage, and it yields *real* signatures
instead of hand-transcribed ones — which closes the "trusted, not guaranteed" gap that is the strongest argument for
doing this at all.
**The design question is how much to import.** Zig imports everything a header declares. For raylib that is several
hundred functions plus every struct and macro, nearly all unused. The current 175 lines are deliberate, and the
declaration site is also the checkpoint where the compiler *refuses* a signature it cannot safely flatten — an
aggregate return, a variadic, a `string` coming back. A wholesale import removes that checkpoint, or has to reproduce
it as a filter. Generating the list from the header while keeping it explicit in the source is a third option: generate
once, commit the result, regenerate when the library moves.
**Kebab-casing is separable and small**, with one constraint: it must be reversible, because the generated C wrapper
needs the library's own spelling.
## 7. Where `defclass` stands