/* * demo.c — Demonstrates arena.h in a realistic program: * a tiny HTTP-like request parser that builds a config * from key=value lines, then formats a response. * * Compile: * gcc -Wall -Wextra -o demo demo.c */ /* ---- Bootstrap the implementation ONCE here ---- */ #define ARENA_IMPLEMENTATION #include "arena.h" #include #include #include /* ================================================================== */ /* Domain types — all strings point into arenas, never heap-alloc'd */ /* ================================================================== */ #define MAX_HEADERS 32 typedef struct { Str key; Str value; } Header; typedef struct { Str method; Str path; Header headers[MAX_HEADERS]; int header_count; Str body; } Request; typedef struct { int status; char *status_text; /* lives in perm arena */ char *body; /* lives in perm arena */ } Response; /* ================================================================== */ /* Parser — uses SCRATCH arena for temp work, PERM for results */ /* ================================================================== */ /* * parse_request() * * raw — the raw input bytes (not modified) * scratch — temporary arena; we'll use save/restore around this call * perm — permanent arena; parsed strings committed here */ static Request *parse_request(const char *raw, Arena *scratch, Arena *perm) { Request *req = arena_alloc_zero(perm, sizeof(Request)); /* ---- Work on a mutable copy in scratch so we can tokenise ---- */ char *work = arena_strdup(scratch, raw); /* First line: METHOD /path HTTP/1.1 */ char *line = work; char *rest = NULL; char *nl = strchr(line, '\n'); if (!nl) return NULL; *nl = '\0'; rest = nl + 1; char *sp1 = strchr(line, ' '); if (!sp1) return NULL; *sp1 = '\0'; char *sp2 = strchr(sp1 + 1, ' '); if (sp2) *sp2 = '\0'; /* Commit method + path into perm arena */ req->method = str_from(arena_strdup(perm, line)); req->path = str_from(arena_strdup(perm, sp1 + 1)); /* ---- Parse headers: Key: Value\n ---- */ line = rest; while (*line && *line != '\n' && *line != '\r') { nl = strchr(line, '\n'); if (!nl) break; *nl = '\0'; rest = nl + 1; char *colon = strchr(line, ':'); if (!colon) { line = rest; continue; } *colon = '\0'; /* Trim leading space from value */ const char *val = colon + 1; while (*val == ' ') val++; /* Trim trailing \r */ size_t vlen = strlen(val); if (vlen && val[vlen - 1] == '\r') vlen--; if (req->header_count < MAX_HEADERS) { Header *h = &req->headers[req->header_count++]; /* key and value committed to perm */ h->key = str_from(arena_strdup(perm, line)); h->value = str_from(arena_strdup_n(perm, val, vlen)); } line = rest; } /* ---- Body (everything after blank line) ---- */ if (*line == '\n') line++; else if (*line == '\r' && *(line+1) == '\n') line += 2; if (*line) req->body = str_from(arena_strdup(perm, line)); return req; } /* ================================================================== */ /* Business logic — build a Response in perm arena */ /* ================================================================== */ static Str request_header(const Request *req, const char *key) { Str k = str_from(key); for (int i = 0; i < req->header_count; i++) { if (str_eq(req->headers[i].key, k)) return req->headers[i].value; } return (Str){ NULL, 0 }; } static Response *handle_request(const Request *req, Arena *scratch, Arena *perm) { Response *res = arena_alloc_zero(perm, sizeof(Response)); Str host = request_header(req, "Host"); Str ua = request_header(req, "User-Agent"); /* Use scratch arena for intermediate formatted strings */ char *host_str = host.ptr ? arena_strdup_n(scratch, host.ptr, host.len) : arena_strdup(scratch, "(unknown)"); char *ua_str = ua.ptr ? arena_strdup_n(scratch, ua.ptr, ua.len) : arena_strdup(scratch, "(unknown)"); /* Build path display string in scratch */ char *path_cstr = arena_strdup_n(scratch, req->path.ptr, req->path.len); /* Determine status */ Str get = str_from("GET"); if (!str_eq(req->method, get)) { res->status = 405; res->status_text = arena_strdup(perm, "Method Not Allowed"); res->body = arena_sprintf(perm, "405 Method Not Allowed\nOnly GET is supported.\n"); return res; } res->status = 200; res->status_text = arena_strdup(perm, "OK"); /* Final body committed to perm */ res->body = arena_sprintf(perm, "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "\r\n" "Hello from arena land!\n" " Path : %s\n" " Host : %s\n" " User-Agent : %s\n", path_cstr, host_str, ua_str ); /* scratch work is discarded automatically when we reset it below */ (void)scratch; return res; } /* ================================================================== */ /* Simulate processing N requests in a loop */ /* ================================================================== */ static const char *FAKE_REQUESTS[] = { "GET /api/v1/farms HTTP/1.1\r\n" "Host: locci.cloud\r\n" "User-Agent: LocciScheduler/1.0\r\n" "Accept: application/json\r\n" "\r\n", "POST /api/v1/jobs HTTP/1.1\r\n" "Host: locci.cloud\r\n" "User-Agent: curl/8.5\r\n" "Content-Length: 42\r\n" "\r\n" "{\"cron\":\"0 * * * *\",\"target\":\"irrigate\"}", "GET /healthz HTTP/1.1\r\n" "Host: mt0.dev\r\n" "User-Agent: k8s-probe/1.0\r\n" "\r\n", }; int main(void) { /* * Two arenas: * perm — lives for the duration of the program (or request lifetime) * scratch — reset after each request; zero-cost cleanup */ Arena perm = arena_create(1024 * 256); /* 256 KB permanent */ Arena scratch = arena_create(1024 * 16); /* 16 KB scratch */ int n = (int)(sizeof(FAKE_REQUESTS) / sizeof(FAKE_REQUESTS[0])); for (int i = 0; i < n; i++) { printf("━━━ Request %d ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n", i + 1); /* * Save scratch position BEFORE any per-request work. * After the request, restore it — O(1) "free" of all temp strings. */ ArenaMark tick = arena_save(&scratch); Request *req = parse_request(FAKE_REQUESTS[i], &scratch, &perm); if (!req) { fprintf(stderr, "parse error\n"); continue; } Response *res = handle_request(req, &scratch, &perm); printf("Status : %d %s\n", res->status, res->status_text); printf("Body :\n%s\n", res->body); /* Blow away ALL scratch strings from this request — one assignment */ arena_restore(&scratch, tick); printf("Perm bytes used so far : %zu\n", arena_bytes_used(&perm)); printf("Scratch bytes after reset: %zu\n", arena_bytes_used(&scratch)); printf("\n"); } /* Single free for everything in perm — no per-string free() needed */ arena_destroy(&perm); arena_destroy(&scratch); return 0; }