Update 3d chart mode, add US heatmap, move kjol-web -> kjol-website

This commit is contained in:
2026-07-16 12:40:49 -04:00
parent 550e97aa9b
commit 2477c2d6a2
75 changed files with 701 additions and 416 deletions

View File

@@ -0,0 +1,600 @@
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); // <exe dir>/config.ini — not $HOME
static void config_save(void);`