Bullet-form one-pager for converting raw C into ABC idioms. Each section states the goal it serves, then the concrete moves. References the canonical docs — read them for full coverage:
CLAUDE.md — coding guidelines (esp. §1, §5, §10, §16)abc/S.md — slice types and typed-function tableabc/B.md — buffer types and operationsabc/INDEX.md — boundary-movers and module indexdog/INDEX.md — tokenizer + dog/ shared infraGoal: express every read and write as a typed slice/buffer op so data consumption is tracked and no pointer ever escapes into arithmetic.
sFeed / sFeed1 (all-or-nothing, returns
SNOROOM if input doesn't fit). Examples: u64sFeed1(slice, val),
u8sFeed(into, from).sDrain / sDrain1 (write what
fits, advance both sides). Use when input may exceed remaining
room and progress matters.a_dup(T, name, src) or *sMv(dst,
src) (e.g. u8csMv, u8sMv). Never slice[0] = ptr; slice[1] =
ptr + len;.a_pad(T, name, len), not raw T name[len].a_cstr(name, "lit").$for / $rof / $eat, sUsed1 / sUsed,
sShed (S.md). No raw *p++, ptr + N, end - start.u8csLen over $len,
u8bReset over Breset, u8sMv over $mv. Reach for a generic
only when the typed form doesn't exist.abc/INDEX.md §"Slice / buffer boundary
movers".u64b, u8cs.u64 to long long, u16 to short, i32 to
int unless mandated by external API.a_carve and friendsGoal: take per-call scratch from the BASS arena so it dies at the call()/try() boundary, never leaking via heap or a fixed stack buffer. Prefer BASS over heap u8bAllocate or fixed a_pad() for scratch.
a_carve(T, name, cap) — acquire a writable buffer of cap
elements (the workhorse, e.g. a_carve(u8, buf, 1UL<<20)). Use for
scratch you feed into.a_lign(T, gauge) … a_cquire(T, slice) — open a gauge on
BASS, fill it, then close it into a slice. The bracketing pair for
"build a slice of unknown length in place".a_rent(T, news, src) / a_ren(news, src) — one-shot copy of
src into a fresh BASS slice news (a_ren = the u8 case).a_pad(T, name, len) (fixed T name[len]
buffer), a_dup(T, name, src) (alias a slice), a_cstr(name,
"lit") (literal slice), a_path(name, …) (path buffer).Rules (PRO.h §"BASS-implicit arena macros"):
call() —
call() snapshots+restores BASS and would undo the acquisition.
Acquire in the op's own sane() frame; a call(...) rewinds BASS to
its entry, freeing whatever the callee carved (so a per-iteration
call/try frees that iteration's carves).__ + return __, exactly like call().a_carve / a_rent between an a_lign and its matching
a_cquire — those advance both DATA and IDLE and corrupt the
in-flight gauge (a must() guard enforces DATA-empty at entry).wh128 etc.) must zerob(name) after
acquire — BASS reuses memory that carries leftover bytes from
previously-rewound carves (see graf/INDEX.md).Goal: keep paths as owned, NUL-terminated buffers built with the PATH helpers, never a hand-assembled char[N].
path8b — owned path buffer, NUL-terminated by
construction. The right type whenever code currently materialises
a char[N] to satisfy a NUL contract.a_path(name, base?, seg…) — stack path buffer, optionally
pre-fed.PATHu8bFeed, PATHu8bAdd, PATHu8bPush.$path(buf) — u8cs view over a path buffer's NUL-terminated
DATA. Use this when handing a path8b to a function expecting
u8cs.PATHu8bAlloc / PATHu8bFree (4 KiB).Goal: carry SHAs in the typed records and accept any 6–40 hex hashlet prefix, never a raw byte array plus a manual length.
sha1 (20 raw bytes) and sha1hex (40 hex ASCII)
from dog/WHIFF.h. Conversions: sha1hexFromSha1,
sha1FromSha1hex, sha1hexFromHex, sha1hexSlice.len != 40 is too strict.u8[20] / char[40] plus manual length.Goal: zero structs with the typed helpers so intent is explicit and never a hand-rolled memset.
zero(x) / zerop(p) from abc/S.h. Don't hand-roll
memset(&x, 0, sizeof(x)) or memset(p, 0, sizeof(*p)).
Also, typed fns are preferred, e.g. sha1Zero(&hash)Goal: when a callee only reads bytes, change its signature to take a slice and pass it directly, killing the copy-into-scratch dance.
memcpy(buf, slice[0], len); buf[len]=0; foo(buf) is a
refactor candidate. Change foo's signature to take u8csc (or
path8sc) and pass the slice directly. The truncate-cap +
memcpy + manual NUL goes away — and so does the hidden info
loss when paths exceed FILE_PATH_MAX_LEN.const char *line,
switch to u8csc line and format with U8SFMT / u8sFmt.Goal: route all parsing through a ragel machine; hand-written byte scanners are forbidden.
Never ever do manual parsing in C Never ever do manual parsing in C Never ever do manual parsing in C Never ever do manual parsing in C Use ragel parsers for that
Goal: allocate, map, and open at the top of the call chain; worker functions borrow resources and never own them.
cli-shaped main entry points the canonical pattern is
wrapper + worker so a single allocation/free pair covers every
exit path the worker takes:static ok64 xxxcli_inner(cli *c) { /* call(...)/done; throughout */ }
ok64 xxxcli() {
sane(1);
cli c = {};
call(PATHu8bAlloc, c.repo);
try(xxxcli_inner, &c);
PATHu8bFree(c.repo);
done;
}
try runs the worker without short-circuiting; done; returns
the worker's status after cleanup.Goal: drive control flow and error propagation through the sane()/call()/try()/done cycle, confined to .c files.
call/done must start with sane(...) —
it declares the implicit __ carrier. Two sane() calls in the
same scope re-declare; combine the conditions.call(f, …) — invoke; on non-OK, propagate via early return.try(f, …) — invoke; capture status without returning. Pair
with then / nedo / on(code) or proceed to cleanup + done;.fail(code) — return that code with a trace.MAIN(fn) / TEST(fn) / FUZZ(...) declare PRO.h globals;
non-MAIN entry points must not use PRO.h macros..c files only.Goal: names follow MOD typ8 VerbStuff, record types end in their bit width, and error codes are RON60-encoded.
MOD typ8 VerbStuff — e.g. HEXu8sFeed, KEEPSync,
URIutf8Drain.sha256, u64, tok32.Stuff is combinatorial flavor (FeedSome, DrainAll,
FromHex).abc/ok64.Goal: reuse before you add — check the INDEX.md files first, fix bugs repro-first, and keep the indexes current.
INDEX.md files first (CLAUDE.md §13, §14): a helper
may already exist. Check abc/INDEX.md, dog/INDEX.md, and the
module's own INDEX.md before adding anything.u8cs (consumed
drain target) vs u8csc (immutable input) — choose accordingly.INDEX.md when you add or rename
headers (CLAUDE.md §14).