package app import ( . "kjol/vdom" ) // The C layer's documentation. // // One page, like the component pages: a reader looking for the arena API should not have // to guess which of six pages it was filed under. The sidebar calls the sections // SUBSYSTEMS, which is what they are — each is a directory of C, and each has a different // idea of what it depends on. // // Everything here is read off the source. Where a subsystem needs something the reader has // to supply — a unity translation unit, a vendored ImGui, a resource script pointed at his // own product — the page says what it needs and what to write, rather than filing it as a // defect. This is a base layer: it is COPIED INTO a project and compiled with it, so "what // you have to provide" is not a caveat, it is the interface. type subsystem struct { ID string Label string Icon string Blurb string } // cSubsystems is the single source for the page's sections AND the sidebar that jumps to // them — so the sidebar cannot offer a jump to a section that does not exist. func cSubsystems() []subsystem { return []subsystem{ {ID: "base", Label: "base", Icon: "cube", Blurb: "The vocabulary: fixed-width types, a bump allocator, counted strings, and 2D rectangle math."}, {ID: "build", Label: "build", Icon: "bolt", Blurb: "A single-header build system. Your build script is a C program that recompiles itself."}, {ID: "platform", Label: "platform", Icon: "server", Blurb: "A window, its input, the clipboard. Win32, and it hands every message to Dear ImGui first."}, {ID: "lexer", Label: "lexer", Icon: "code", Blurb: "Syntax highlighting. Five languages, thirteen themes, and a paint array rather than a token list."}, {ID: "config", Label: "config", Icon: "file", Blurb: "An INI file beside the executable. Program-managed state, not user-authored settings."}, {ID: "installer", Label: "installer", Icon: "download", Blurb: "A Win32 wizard that carries the product inside itself as a resource."}, } } // cNav is the sidebar while you are reading /c. The group is called Subsystems. func cNav() []docsGroup { items := make([]docsItem, 0, len(cSubsystems())) for _, s := range cSubsystems() { items = append(items, docsItem{ Path: "/c#" + s.ID, Label: s.Label, Icon: s.Icon, Blurb: s.Blurb, }) } return []docsGroup{ { Title: "Introduction", Items: []docsItem{ {Path: "/c", Label: "Overview", Icon: "book-open", Blurb: "What this layer is, and where it came from."}, {Path: "/c#compiling", Label: "Compiling it", Icon: "bolt", Blurb: "The unity translation unit: what you write, and what each subsystem needs from you."}, }, }, {Title: "Subsystems", Items: items}, } } //gowasm:page /c static layout=app func CPage(d Deps) func() *VNode { return func() *VNode { return docPage("Layers", "Kjøl C", "A base layer in C — a bump allocator, counted strings, a syntax highlighter, a Win32 window, "+ "and a build system that is itself a C program. It was lifted out of codeMAX, a code editor, "+ "which is why the pieces are the pieces an editor needs.", docSection("what-this-is", "What this is", prose("Not a library you link against. A set of headers and translation units you COPY INTO "+ "a project and compile with it — the base-layer style you will recognise if you have read "+ "Ryan Fleury's RAD Debugger or watched Handmade Hero. The headers say as much: base_core, "+ "base_arena and base_strings all credit raddebugger, and the naming (U32, Str8, Rng2F32) "+ "comes straight from it."), prose("There is no package manager, no version, and no ABI to keep stable — because there is "+ "nothing to keep stable BETWEEN. The code and its consumer are compiled together. Which is "+ "also why the next section is about the file YOU write: the layer does not build on its "+ "own, by design. It builds as part of your program."), note("It arrived from codeMAX, and still carries its names", "The whole directory came into Kjøl in one commit, out of codeMAX, a code editor. Its "+ "fingerprints are still on it and they are worth knowing before you go looking: the "+ "Win32 window class is codemax_wc, the installer installs codeMAX, and installer.rc "+ "names codeMAX's icon, manifest and payload. Those are the strings to change when you "+ "adopt it — they are the product's name, not the layer's."), ), cCompiling(), cBase(), cBuild(), cPlatform(), cLexer(), cConfig(), cInstaller(), ) } } // ---- compiling ----------------------------------------------------------- // How you actually compile the thing. It is second on the page, before any API, because a // base layer's first question is "how does this get into my program" and the answer here is // not the one a reader arriving from a package-manager language expects. func cCompiling() *VNode { return docSection("compiling", "Compiling it", prose("One translation unit. You write a single .c that #includes the layer's .c files in order, "+ "and hand THAT to the compiler — the whole layer is one TU, compiled with your program. "+ "base/base_inc.c is the pattern in miniature: three lines, pulling in the arena and the "+ "strings. Your own unity file is the same idea, one level up."), codeLang("app_inc.c — the file you write", "c", unityTUSnippet), prose("This is not a workaround for the absence of a build system; it IS the build system. There is "+ "no per-file compilation, so there are no object files, no link order and no header guard "+ "archaeology — and the compiler sees the whole layer at once, which is what makes every helper "+ "in it `internal` and every call to one a candidate for inlining."), docSubheading("What each subsystem needs from you"), prose("They are not equally self-contained, and the differences are worth knowing before you pick "+ "the ones you want:"), apiTable( apiRow{"base", "Nothing. It stands on its own — base_inc.c is a complete TU, and everything above depends on it."}, apiRow{"lexer", "Include lexer.c AND the five backends in the same TU. Their helpers are `internal`, so separate compilation would leave lexer.c's dispatch with nothing to call."}, apiRow{"config", "Include config.h above config.c. config.c uses Config without including its own header — which is exactly what a unity build lets it do."}, apiRow{"platform", "Dear ImGui, vendored, and a C++ compiler for it. See the subsystem below — the window procedure hands it every message before it looks at any of them."}, apiRow{"installer", "Its own program and its own .rc, pointed at your icon, your manifest and your payload."}, apiRow{"build", "Nothing — it is the thing that runs the compiler. Write a build.c, compile it once, and never compile it again."}, ), note("There is no build.c in Kjøl yet, and that is the next job", "build.h is here, complete and self-contained, and nothing in the repository includes it — so "+ "the layer currently has a build system and no build. Writing that build.c is what turns "+ "this directory into something you can type one command at, and it is the single most "+ "useful thing anyone could add to the C layer. The build subsystem below documents the "+ "header it would be written against, and the snippet there is a working sketch of it."), ) } // ---- base ---------------------------------------------------------------- func cBase() *VNode { return docSection("base", "base", prose("The vocabulary. Fixed-width integers with short names, a bump allocator, a counted string, "+ "and the rectangle math a user interface actually spends its time on. Everything else in the "+ "layer is written in these — which is why there is no malloc below this line, and no char* "+ "pretending to be a string."), docSubheading("The types (base_core.h)"), prose("U8 through U64, S8 through S64, B32 for a boolean, F32 and F64 — upper case, not the u8/i32 "+ "of the Rust-adjacent style. Sizes are written KB(4) and MB(64) rather than as a number of "+ "zeroes you have to count. And the three meanings of `static` in C get three different names: "+ "`internal` for a file-local function, `global` for a translation-unit variable, `local_persist` "+ "for a local that survives the call. They all expand to `static`; they do not all mean the same "+ "thing, and the code says which one it meant."), codeLang("c/base/base_core.h", "c", baseCoreSnippet), prose("There is more in the header than the types: Min/Max/Clamp, the nil-aware doubly and singly "+ "linked-list macros from raddebugger (DLLPushBack, SLLQueuePush and friends), DeferLoop — which "+ "is `defer` written as a for-loop, because C has no defer — and an Assert that traps on MSVC "+ "with __debugbreak."), docSubheading("The arena (base_arena.h)"), prose("One allocator, and it is a bump pointer: arena_push moves a cursor and hands you the memory. "+ "There is no free. You release the whole arena, or you roll it back to a saved position — which "+ "is what Temp is. A function that wants scratch space opens a Temp, allocates as freely as it "+ "likes, and closes it. Nothing has to be individually released, so nothing can be individually "+ "forgotten."), codeLang("c/base/base_arena.h", "c", baseArenaSnippet), note("It is malloc-backed and fixed-capacity — and that has a sharp edge", "The header says so: this is raddebugger's arena with the virtual-memory reserve/commit taken "+ "out. arena_alloc is one malloc of the capacity you asked for, and that capacity is final — "+ "the arena does not grow and does not chain. Overflow hits Assert(!\"Arena overflow\") and "+ "returns NULL. But Assert compiles to nothing unless _DEBUG is defined, so in a release build "+ "an over-full arena hands you a silent NULL. Alignment is a hard-coded 8 bytes, so there is "+ "no SIMD guarantee. And there is no thread-local scratch pool — if you came here expecting "+ "raddebugger's scratch_begin, it is not in this snapshot."), docSubheading("Strings (base_strings.h)"), prose("Str8 is a pointer and a length. Not NUL-terminated, not owned, does not allocate — so a "+ "substring is free, and a Str8 can point into the middle of a file you mapped. The two "+ "operations that MUST allocate take the arena, and so they say so in their signature. That is "+ "the whole API: eight functions. It is early, and there is no split, join, trim or "+ "case-insensitive compare yet."), codeLang("c/base/base_strings.h", "c", baseStringsSnippet), docSubheading("Math (base_math.h)"), prose("Look at the shape of this file and it tells you what it is for. Vec3F32 has a constructor "+ "and no operations. Rng2F32 — a rectangle — has nine: width, height, dim, center, contains, "+ "pad, shift, intersect. There are no matrices, no quaternions, no Mat4. This is 2D interface "+ "math, and a layout written in it is a sequence of rectangle operations rather than eight lines "+ "of x + w arithmetic with an off-by-one hiding in them."), prose("The Axis2 / Side / Corner enums plus v2f32_axis are the raddebugger trick for axis-generic "+ "code: one code path handles both X and Y by indexing into the vector instead of naming .x "+ "and .y, so a horizontal and a vertical layout are the same function with a different argument."), apiTable( apiRow{"arena_alloc / arena_release", "One malloc, fixed capacity. It does not grow."}, apiRow{"arena_push / arena_push_no_zero", "Bump the cursor. Zeroed by default; the fast one has to be asked for by name."}, apiRow{"temp_begin / temp_end", "A scratch scope: save the position, allocate freely, roll it all back."}, apiRow{"push_array(arena, T, n)", "Typed sugar over arena_push. The macro you will actually use."}, apiRow{"str8 / str8_cstr / str8_lit", "Make a counted string. None of them allocate."}, apiRow{"str8_pushf / str8_push_copy", "The two that DO allocate — and so they take the arena."}, apiRow{"Rng2F32 + rng2f32_intersect / _pad / _shift", "Rectangles, and the three things a layout does to them."}, ), ) } // ---- build --------------------------------------------------------------- func cBuild() *VNode { return docSection("build", "build", prose("The build system is a C program, and the build system's build system is a C compiler. "+ "build.h is a single header in the stb style: define BUILD_IMPLEMENTATION in one file, include "+ "it, and write your build as a main() that shells out to a compiler. The lineage is nob.h — the "+ "self-rebuild macro here is called GO_REBUILD_URSELF, which is a straight nod to it."), docSubheading("Bootstrapping"), prose("You compile the build script once, by hand. After that you never compile it again, because "+ "the first thing it does when it runs is compare its own source against its own executable — "+ "and if the source is newer, it rebuilds itself, swaps the binary, and re-executes. Edit the "+ "build script and just run it. It will notice."), codeLang("terminal", "sh", buildBootstrapSnippet), note("On Windows you cannot overwrite a running .exe — so it doesn't", "go_rebuild_urself RENAMES the current binary to build.exe.old, compiles the new one in its "+ "place, and re-execs. If the compile FAILS, it renames the old one back. That is the whole "+ "reason the dance exists, and it is the difference between a build script you can edit and "+ "one that bricks itself the first time you make a typo."), docSubheading("Writing one"), prose("GO_REBUILD_URSELF is the first line of main. Cmd is a growable argv you append to and run. "+ "needs_rebuild compares timestamps, so a target whose inputs have not changed is skipped. That "+ "is the whole of it — no rule syntax, no DSL, no dependency graph, because a C program already "+ "has if-statements and for-loops and you already know how to write them."), codeLang("build.c — the file you write", "c", buildUsageSnippet), note("What it deliberately does NOT do", "There is no compiler detection: the compiler is cl.exe on Windows and cc everywhere else, "+ "hard-coded, and only for the self-rebuild — for your own targets you assemble the command "+ "yourself. There is no parallelism: cmd_run is synchronous, start to finish. There is no "+ "globbing and no header-dependency scanning: needs_rebuild compares mtimes against the input "+ "list YOU pass it, so if you edit a header that is not in that list, nothing rebuilds. Know "+ "that going in and it is a fine tool; expect make and you will be bitten."), docSubheading("What else is in the header"), prose("More than a build system strictly needs, and all of it is there because a real build wanted "+ "it. A temp allocator — an 8 MB ring buffer that silently wraps, so temp_sprintf can hand you a "+ "formatted path that you never free. A string builder. File I/O that reports its own errors. "+ "And two functions that are squarely about shipping a graphical program: embed_file, which "+ "turns any binary into a C array in a header, and compile_shader / embed_spirv, which run glslc "+ "over a GLSL source and embed the SPIR-V the same way. That last pair is a fossil of a Vulkan "+ "renderer, and it is the clearest evidence in the layer of what codeMAX was becoming."), apiTable( apiRow{"GO_REBUILD_URSELF(argc, argv)", "First line of main. Rebuilds and re-executes the script if its source changed."}, apiRow{"Cmd + cmd_append + cmd_run", "A growable argv, appended variadically, run synchronously. cmd_run resets it for reuse."}, apiRow{"needs_rebuild(out, inputs, n)", "1 if any input is newer than the output, 0 if up to date, -1 on error. Mtimes only."}, apiRow{"temp_sprintf / temp_reset", "Formatted strings from a ring buffer. You never free them; you reset the ring."}, apiRow{"sb_read_file / write_entire_file", "Whole-file I/O into a String_Builder, and back out."}, apiRow{"embed_file(in, out, var)", "Turn a binary into a C header: an array, and its size."}, apiRow{"compile_shader / embed_spirv", "glslc a .glsl to .spv, then embed the .spv as a C array."}, ), ) } // ---- platform ------------------------------------------------------------ func cPlatform() *VNode { return docSection("platform", "platform", prose("The operating system, behind one header — and it is a narrower header than the name "+ "suggests. There is no file I/O here, no clock, no threads, and no virtual memory (which is why "+ "the arena is malloc-backed). What there is: a window, the input that arrives at it, the "+ "clipboard, and a way to spawn a terminal."), prose("Input comes in two shapes on purpose. PlatformRawInput is what the OS actually said — UTF-16 "+ "characters, virtual key codes, mouse coordinates. PlatformInput is what the application wants "+ "to hear. platform_adapt_input converts one into the other, and that function is the seam a "+ "second backend would be written behind."), note("It expects Dear ImGui in the build, and Windows underneath", "platform_win32.c is the only backend, and its window procedure forward-declares "+ "ImGui_ImplWin32_WndProcHandler and calls it on every message BEFORE it looks at any of "+ "them — so ImGui gets first refusal on the input, which is what makes an ImGui text field "+ "inside the window behave like a text field rather than a hole the editor's keybindings "+ "fall through. To use this subsystem you vendor ImGui and compile it (it is C++) alongside; "+ "to use it WITHOUT ImGui you cut that one call, and everything else in the file is C and "+ "stands. A second backend would replace the PKEY_ enum — whose values ARE Windows virtual "+ "key codes, passed straight through — and find a home for platform_spawn_terminal, which "+ "hard-codes cmd.exe /k."), docSubheading("Two decisions worth stealing"), prose("WM_SIZE calls the frame callback synchronously, from inside the window procedure. That looks "+ "wrong and is the standard Win32 fix for a real problem: while you drag a window's edge, "+ "Windows runs a modal resize loop that never returns to your main loop, so the window goes "+ "blank or smears. Rendering from inside the message handler is the only way to keep drawing. "+ "platform_set_frame_callback exists for exactly this."), prose("And platform_get_input drains its accumulator on read — it memsets the buffered keys and "+ "characters to zero on the way out, and returns was_mouse_down beside mouse_down so the caller "+ "can see an edge without keeping its own copy of last frame."), apiTable( apiRow{"platform_create_window / _destroy_window", "A window from a PlatformWindowDesc; and its teardown."}, apiRow{"platform_poll_events", "Pump the message queue. False means the user wants to close."}, apiRow{"platform_get_input / platform_adapt_input", "The raw OS input for this frame; and the conversion that is the portability seam."}, apiRow{"platform_set_frame_callback", "Called per frame — including from inside a live resize, which is the point."}, apiRow{"platform_get_dpi_scale", "GetDpiForWindow / 96. The layer is per-monitor DPI aware throughout."}, apiRow{"platform_clipboard_get / _set", "Get returns a pointer into a static 64 KB buffer. Do not free it, do not keep it."}, apiRow{"platform_get_native_handle", "The HWND, for the one place that genuinely needs it."}, ), ) } // ---- lexer --------------------------------------------------------------- func cLexer() *VNode { return docSection("lexer", "lexer", prose("Syntax highlighting, and the most complete thing in this layer — about 2,800 lines of it. "+ "The interesting decision is the output shape, and the header credits it to the Focus editor: a "+ "tokenizer here does not return a list of tokens. It takes the buffer and an out_tokens array "+ "of the SAME LENGTH, and paints one token-type byte per source byte."), codeLang("c/lexer/lexer.h", "c", lexerSnippet), prose("That sounds wasteful and is the opposite. An editor never wants to know what the tokens are; "+ "it wants to know what colour to draw the character at offset N — and with a parallel array "+ "that is one indexed read, not a search through a token list. The enum value IS the index into "+ "the theme's colour table, which is why TOK_DEFAULT has to be zero. Painting is a memset. The "+ "cost is one byte per byte of source, for a file you already have in memory."), note("It lets you rewrite the past, which is why it can find function names", "All five backends share one trick. When the tokenizer emits a `(` and the PREVIOUS token was "+ "an identifier, it goes back and re-paints that identifier as TOK_FUNCTION. With a token "+ "list you would have to look ahead, or fix up afterwards. With a paint array the past is "+ "just an array range, and repainting it costs a memset."), docSubheading("Languages"), prose("Five backends — C, Go, JavaScript, Lua, SQL — plus plain text, each a single .c file behind "+ "the same LexerTokenizeFn signature, selected by a hard-coded switch. There is no registry and "+ "no plugin: adding a language means editing four things in lexer.c, deliberately."), prose("They are not toys. The JavaScript one carries an explicit depth stack for nested tagged "+ "template literals, so an html`…${css`…${x}`}…` lexes correctly at arbitrary nesting, and it "+ "has a regex-versus-division heuristic — after a keyword or an operator a slash starts a regex, "+ "after an identifier it is a divide. The Lua one counts the equals signs in a long bracket so "+ "[==[ is closed by ]==] and not by ]]. The SQL one is case-insensitive and treats \"quoted\" as "+ "an identifier rather than a string."), docSubheading("Themes"), prose("A theme is a colour per token type, and there are thirteen: Default, Default Light, Focus, "+ "Handmade Hero, Witness Classic, Witness, VS Classic, RAD Debugger, 4coder, Ryan Fleury, Gruber "+ "Darker, VS Dark, Freshcut Contrast. The file is pure data — not one function in five hundred "+ "lines."), note("The Theme struct is a fossil, and you can date the rock", "Every theme carries a 24-bit colour table AND a 256-colour ANSI fallback, with a use_truecolor "+ "flag — because this started life in a TERMINAL editor, where you cannot assume truecolor. "+ "But the platform layer beside it is a DPI-aware Win32 GUI window driven by ImGui, and the "+ "struct has since grown status_bg, minibuffer and file-browser colours that mean nothing to "+ "a terminal. It is a terminal-era struct with GUI-era fields bolted on. That is a seam, not "+ "a design."), apiTable( apiRow{"LexerTokenizeFn(data, len, out_tokens)", "The one signature every backend implements. One byte of token type per byte of source."}, apiRow{"lexer_get_tokenize_fn(lang)", "The backend for a language. A switch, not a registry."}, apiRow{"lexer_detect_lang(filename)", "Language from the file extension."}, apiRow{"Tokenizer + tokenizer_init / _eat_whitespace", "The shared cursor the backends are written against."}, apiRow{"g_themes / theme_active()", "Thirteen built-in themes, and the active one."}, ), ) } // ---- config -------------------------------------------------------------- func cConfig() *VNode { return docSection("config", "config", prose("An INI file — [sections], key = value — that lives NEXT TO THE EXECUTABLE rather than in a "+ "home directory, which makes the program portable: copy the folder, keep your settings. It "+ "parses into one global Config struct and saves back out of it."), codeLang("c/config/config.h", "c", configSnippet), prose("The struct is the schema, and it is a fixed-size one: ten recent directories, every path a "+ "flat 1024 bytes. Nothing in this subsystem allocates — which is what lets it be loaded before "+ "an arena exists. If the file is absent, config_load writes a default one."), note("It stores what the PROGRAM knows, not what the user wants", "The header is explicit about the split, and it is a good one: this file is managed by the "+ "program — your window size, your recent folders, your active project — and per-project "+ "settings belong in .editorconfig, where other tools can read them. A config file that tries "+ "to be both ends up being neither."), note("It needs a writable directory beside the executable", "That is the one requirement the design carries, and it is easy to miss: the file is written "+ "NEXT TO the .exe, and config_save does not check whether the write succeeded. Run out of a "+ "folder you can write to — which is what \"copy the folder, keep your settings\" means — and "+ "it does exactly what it says. Install the same binary into C:\\Program Files and a "+ "non-elevated process cannot write there, so the save quietly does nothing. If you ship it "+ "through the installer, either keep the config beside the exe and expect an elevated write, "+ "or point config_get_path at %APPDATA% and give up the portability."), apiTable( apiRow{"config_load / config_save", "Read the INI into g_config; write g_config back out. Writes defaults if the file is absent."}, apiRow{"config_get_path", "Beside the executable — GetModuleFileNameA, then strip the filename."}, apiRow{"config_push_recent_dir", "Dedupes case-insensitively, moves the entry to the front, caps at ten, and saves itself."}, apiRow{"g_config", "The one global. There is no second one."}, ), ) } // ---- installer ----------------------------------------------------------- func cInstaller() *VNode { return docSection("installer", "installer", prose("A Windows installer AND uninstaller, in one executable, with the thing it installs embedded "+ "inside it as a resource. No NSIS, no WiX, no MSI: it is a Win32 property-sheet wizard — "+ "welcome, directory, progress, finish — that extracts the payload, copies itself to "+ "uninstall.exe, optionally appends itself to the system PATH, writes a Start Menu shortcut via "+ "COM, and registers under HKLM so it shows up in Add/Remove Programs."), prose("installer.manifest is what makes Windows ask for elevation UP FRONT rather than failing on "+ "the first write to Program Files. installer.h exists solely so the resource script and the C "+ "agree on the resource ids."), note("The wizard has no dialog resources — it writes the DLGTEMPLATEs by hand, at runtime", "There is a small serializer in installer.c that assembles the Win32 DLGTEMPLATE and "+ "DLGITEMTEMPLATE binary layout byte by byte, alignment padding and all, and hands the result "+ "to the property sheet with PSP_DLGINDIRECT. Which means the installer's entire user "+ "interface needs no resource compiler: only the icon, the manifest and the payload go "+ "through the .rc. That is either magnificent or deranged, and it is certainly deliberate."), note("A running .exe cannot delete itself, so the uninstaller doesn't", "It spawns a detached cmd.exe that waits two seconds, deletes uninstall.exe, and removes the "+ "directory. The process outlives the program that started it, which is the only way this can "+ "be done on Windows."), docSubheading("Pointing it at your own product"), prose("The .rc is the part you edit. It names codeMAX's icon, codeMAX's manifest and codeMAX's "+ "payload, at the paths the original repository had them at — so adopting the installer means "+ "pointing those three lines at your icon, your manifest and your executable, and changing the "+ "product name and the HKLM key the uninstall entry is written under. Nothing else in installer.c "+ "knows what it is installing."), prose("There is also visible residue of a two-binary product that was collapsed into one, and it is "+ "worth recognising rather than copying: IDR_EXE_TERMINAL is still declared though nothing embeds "+ "or extracts it, do_install's numbered steps skip step 3, and the uninstaller still removes a "+ "second executable and a shortcut that the installer no longer creates. Those are the lines to "+ "delete on the way in."), ) } // ---- helpers ------------------------------------------------------------- // docSubheading is a heading INSIDE a section. The subsystems each have several parts, and // six sections with no internal structure is a wall. func docSubheading(text string) *VNode { return H3(Attr("class", "mt-8 text-base font-semibold text-text-heading"), Text(text)) } // ---- snippets ------------------------------------------------------------ // // Every one of these is copied from the source, except two — unityTUSnippet and // buildUsageSnippet, which are the two files the layer expects its CONSUMER to write and // so cannot be copied from a layer that has no consumer yet. Both are written against the // real API and both are captioned as the file you write, not as a file that is here. // // If you change the source, change these. A snippet that has drifted from the code it // claims to show is the most expensive documentation there is, because it is believed. const unityTUSnippet = `// The whole layer, as one translation unit — the only .c you hand to the // compiler. Order matters: it is textual inclusion, not linking. #include "base/base_inc.c" // core, arena, strings. Everything below needs it. // The five backends declare their helpers `+ "`internal`" + ` (file-static), so they // belong in the SAME TU as the dispatch that calls them. #include "lexer/lexer.c" #include "lexer/lexer_c.c" #include "lexer/lexer_go.c" #include "lexer/lexer_js.c" #include "lexer/lexer_lua.c" #include "lexer/lexer_sql.c" // config.c uses Config without including its own header — which is exactly the // thing a unity build is for. Put the header above it. #include "config/config.h" #include "config/config.c" // ...and then your own program, compiled with all of it: #include "app/app.c"` const baseCoreSnippet = `// The three meanings of `+ "`static`" + ` in C, given three names. #define internal static // a function private to this file #define global static // a variable owned by this translation unit #define local_persist static // a local that survives the call typedef uint8_t U8; typedef int8_t S8; typedef uint32_t U32; typedef int32_t S32; typedef uint64_t U64; typedef int64_t S64; typedef S32 B32; // a boolean, sized so it packs predictably typedef float F32; typedef double F64; #define KB(n) (((U64)(n)) << 10) #define MB(n) (((U64)(n)) << 20)` const baseArenaSnippet = `typedef struct Arena { U8 *base; U64 pos; U64 cap; } Arena; typedef struct Temp { Arena *arena; U64 pos; } Temp; // One malloc, of exactly cap. It does not grow and it does not chain. Arena *arena_alloc(U64 cap); void *arena_push(Arena *arena, U64 size); // zeroed void *arena_push_no_zero(Arena *arena, U64 size); // not void arena_pop_to(Arena *arena, U64 pos); #define push_array(arena, T, count) ((T *)arena_push((arena), sizeof(T) * (count))) // A scratch scope. Allocate as freely as you like inside it; none of it needs // releasing, because temp_end rolls the cursor back over all of it at once. Temp scratch = temp_begin(arena); Node *nodes = push_array(arena, Node, 1024); temp_end(scratch);` const baseStringsSnippet = `// A pointer and a length. Not NUL-terminated, not owned, does not allocate — // so a substring is free, and a Str8 can point into a file you mapped. typedef struct Str8 { const char *str; U64 size; } Str8; static inline Str8 str8_lit(const char *s); static inline B32 str8_match(Str8 a, Str8 b); static inline B32 str8_is_empty(Str8 s); // The two that must allocate take the arena, and so they say so: Str8 str8_pushf(Arena *arena, const char *fmt, ...); Str8 str8_push_copy(Arena *arena, Str8 s);` const buildBootstrapSnippet = `# Once, by hand. (On Windows, from a Visual Studio developer prompt — # it shells out to cl.exe.) cl /nologo build.c # Windows cc build.c -o build # macOS / Linux # Ever after, just run it. If build.c is newer than the binary, the binary # rebuilds itself, swaps in the new one, and re-executes: ./build` const buildUsageSnippet = `#define BUILD_IMPLEMENTATION #include "build.h" int main(int argc, char **argv) { // Renames the running binary to .old, recompiles, and re-execs. If the // compile fails it puts the old one back — so a typo in your build // script cannot brick your build script. GO_REBUILD_URSELF(argc, argv); mkdir_if_not_exists("out"); // needs_rebuild compares MTIMES against the list you pass it. There is // no header scanning: if a .h you depend on is not in this list, editing // it will not trigger a rebuild. const char *srcs[] = {"base/base_inc.c", "lexer/lexer.c"}; if (needs_rebuild("out/app.exe", srcs, ARRAY_LEN(srcs)) > 0) { Cmd cmd = {0}; cmd_append(&cmd, "cl", "/nologo", "/I.", "/Fe:out/app.exe"); cmd_append(&cmd, srcs[0], srcs[1]); if (!cmd_run(&cmd)) return 1; // synchronous; resets cmd for reuse cmd_free(&cmd); } build_log(LOG_INFO, "done"); return 0; }` const lexerSnippet = `// A tokenizer does not RETURN tokens. It paints one token-type byte per // source byte, into an array the same length as the buffer. // // The editor never asks "what are the tokens". It asks "what colour is the // character at offset N" — and this answers that with one indexed read. // The enum value IS the index into the theme's colour table, which is why // TOK_DEFAULT has to be 0. typedef void (*LexerTokenizeFn)(const char *data, S32 length, U8 *out_tokens); typedef enum Lang { LANG_PLAIN_TEXT, LANG_C, LANG_GO, LANG_JS, LANG_LUA, LANG_SQL } Lang; LexerTokenizeFn lexer_get_tokenize_fn(Lang lang); // a switch, not a registry Lang lexer_detect_lang(const char *filename);` const configSnippet = `// The struct IS the schema, and it is fixed-size throughout: this subsystem // allocates nothing, so it can be loaded before an arena exists. typedef struct Config { char theme[64]; char recent_dirs[CONFIG_MAX_RECENT_DIRS][CONFIG_PATH_MAX]; // most recent first S32 recent_dir_count; B32 show_line_numbers; B32 syntax_enabled; F32 ui_scale; char editor_font[64]; char active_project[CONFIG_PATH_MAX]; } Config; extern Config g_config; // the one global static void config_load(void); // /config.ini — not $HOME static void config_save(void);`