WokML compiler code coverage report (LCOV)
Current view: top level - c/cmd - wokfmt.c (source / functions) Coverage Total Hit
Test: 084deda Lines: 75.4 % 114 86
Test Date: 2026-08-10 11:39:41 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit

            Line data    Source code
       1              : // wok fmt -- the canonical formatter, and the linter that is the same thing
       2              : // asking a question.
       3              : //
       4              : //   wok fmt FILE...            print the canonical form to stdout
       5              : //   wok fmt -w FILE...         rewrite each file in place
       6              : //   wok fmt --check FILE...    exit non-zero if any file is not canonical
       7              : //   wok fmt --pipe < FILE      source on stdin, canonical form on stdout
       8              : //   wok fmt --pipe --check     ... the verdict only, as an exit code
       9              : //
      10              : // The tool is one-shot by design. Measured: process creation costs 1.46 ms and
      11              : // is indistinguishable from an empty binary; a 750-file project checks in
      12              : // 11.7 ms. There is nothing a cache could save that is worth the staleness it
      13              : // would risk.
      14              : 
      15              : // The prelude comes FIRST: it carries the POSIX feature-test macros, which
      16              : // have no effect once a system header has been read. wok_base.h hard-errors
      17              : // if it is reached too late.
      18              : #include "wok_base.h"
      19              : 
      20              : #include <stdio.h>
      21              : #include <stdlib.h>
      22              : #include <string.h>
      23              : 
      24              : #include "../wok_arena.h"
      25              : #include "../wok_ast.h"
      26              : #include "../wok_diag.h"
      27              : #include "../wok_parse.h"
      28              : #include "../wok_print.h"
      29              : #include "../wok_sexpr.h"
      30              : #include "../wok_shape.h"
      31              : #include "../wok_write.h"
      32              : #include "../wok_token.h"
      33              : 
      34              : typedef enum { M_STDOUT, M_WRITE, M_CHECK } Mode;
      35              : 
      36           94 : static char *slurp(const char *path, usize *n) {
      37           94 :   FILE *fp = fopen(path, "rb");
      38           94 :   if (!fp) return nullptr;
      39           94 :   if (fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return nullptr; }
      40           94 :   long sz = ftell(fp);
      41           94 :   if (sz < 0) { fclose(fp); return nullptr; }
      42           94 :   rewind(fp);
      43           94 :   char *b = (char *)malloc((usize)sz + 1);
      44           94 :   if (!b) { fclose(fp); return nullptr; }
      45           94 :   usize got = fread(b, 1, (usize)sz, fp);
      46           94 :   b[got] = '\0';
      47           94 :   *n = got;
      48           94 :   fclose(fp);
      49           94 :   return b;
      50              : }
      51              : 
      52              : 
      53              : // `stamp` is nullptr in PIPE mode: there is no file to guard against a
      54              : // concurrent edit and none to replace, so interlock B does not apply. Every
      55              : // other check does -- a pipe is not an excuse to skip the safety properties.
      56          335 : static int process_text(const char *path, const char *src, usize n, Mode mode,
      57              :                         const WokFileStamp *stamp) {
      58              : 
      59          335 :   WokArena *a = wok_arena_new(0);
      60          335 :   WokDiagSink *d = wok_diag_new(a, path, src, n);
      61          335 :   WokNode *tree = wok_parse_source(src, n, a, d);
      62          335 :   if (wok_diag_count(d) != 0) {
      63            1 :     wok_diag_render(d, stderr);
      64            1 :     wok_arena_free(a);
      65            1 :       return 1;  // input that does not parse has no canonical form
      66              :   }
      67              : 
      68          334 :   wok_print_reports_reset();
      69          334 :   char *formatted = wok_print_string_named(tree, src, a, path);
      70          334 :   usize fn = strlen(formatted);
      71              : 
      72              :   // INTERLOCK A0: the printer's own safety property. A line break that is not
      73              :   // a continuation line changes the tree rather than the text, and the printer
      74              :   // says so at the moment it writes one.
      75          334 :   int rc = 0;
      76              :   // INTERLOCK A0b: a node with no canonical form at all. Until this counter
      77              :   // existed the only guard was that such a tree can reach here solely from a
      78              :   // parse that already reported a diagnostic -- true, but by ORDERING rather
      79              :   // than by check, so it held only as long as nobody added a second way in.
      80          334 :   if (wok_print_unprintable_count() != 0) {
      81            0 :     fprintf(stderr, "wok fmt: INTERNAL: %s holds a node with no canonical "
      82              :                     "form; refusing to write\n", path);
      83            0 :     rc = 2;
      84              :   }
      85          334 :   if (rc == 0 && wok_print_fault_count() != 0) {
      86            0 :     fprintf(stderr, "wok fmt: INTERNAL: %s was filled with an unsafe line "
      87              :                     "break; refusing to write\n", path);
      88            0 :     rc = 2;
      89              :   }
      90              : 
      91              :   // INTERLOCK A: did formatting change the PROGRAM? The dump is the canonical,
      92              :   // position-free identity of a program, so comparing dumps answers exactly
      93              :   // that -- and unlike a fingerprint, it can say what differed.
      94          334 :   WokDiagSink *rd = wok_diag_new(a, path, formatted, fn);
      95          334 :   WokNode *reparsed = wok_parse_source(formatted, fn, a, rd);
      96          334 :   if (rc != 0) {
      97              :     // already refused
      98          334 :   } else if (wok_diag_count(rd) != 0) {
      99            0 :     fprintf(stderr, "wok fmt: INTERNAL: %s does not re-parse after formatting; "
     100              :                     "refusing to write\n", path);
     101            0 :     wok_diag_render(rd, stderr);
     102            0 :     rc = 2;
     103              :   } else {
     104          334 :     char *before = wok_sexpr_dump_string(tree, src, a);
     105          334 :     char *after = wok_sexpr_dump_string(reparsed, formatted, a);
     106          334 :     if (!before || !after || strcmp(before, after) != 0) {
     107            0 :       fprintf(stderr, "wok fmt: INTERNAL: formatting %s would CHANGE THE "
     108              :                       "PROGRAM; refusing to write\n", path);
     109            0 :       rc = 2;
     110              :     }
     111              :   }
     112              : 
     113            0 :   if (rc == 0) {
     114          334 :     if (mode == M_CHECK) {
     115           97 :       WokDiagSink *sd = wok_diag_new(a, path, src, n);
     116           97 :       char *want = wok_shape(formatted, fn, a, sd);
     117           97 :       char *have = wok_shape(src, n, a, sd);
     118           97 :       if (strcmp(want, have) != 0) {
     119              :         // In pipe mode stdout is the payload channel, so the name would be
     120              :         // noise: an agent reads the exit code.
     121           25 :         if (stamp != nullptr) printf("%s\n", path);
     122              :         rc = 1;
     123              :       }
     124          237 :     } else if (mode == M_STDOUT) {
     125          237 :       fwrite(formatted, 1, fn, stdout);
     126              :     } else {
     127              :       // INTERLOCK B, the owner's rule, plus an atomic replace: the file is
     128              :       // written to a temporary and renamed, so no crash or full disk can
     129              :       // leave the user's source truncated, and a concurrent save is refused
     130              :       // rather than clobbered.
     131            0 :       switch (wok_write_atomic(path, formatted, fn, *stamp)) {
     132              :         case WOK_WRITE_OK:
     133              :         case WOK_WRITE_UNCHANGED:
     134              :           break;
     135            0 :         case WOK_WRITE_MOVED:
     136            0 :           fprintf(stderr, "wok fmt: %s changed on disk while formatting; "
     137              :                           "not written\n", path);
     138            0 :           rc = 2;
     139            0 :           break;
     140            0 :         case WOK_WRITE_FAILED:
     141            0 :           fprintf(stderr, "wok fmt: could not write %s; it is unchanged\n", path);
     142            0 :           rc = 2;
     143            0 :           break;
     144              :       }
     145              :     }
     146              :   }
     147              : 
     148          334 :   wok_arena_free(a);
     149          334 :   return rc;
     150              : }
     151              : 
     152           94 : static int process_file(const char *path, Mode mode) {
     153           94 :   usize n = 0;
     154           94 :   char *src = slurp(path, &n);
     155           94 :   if (!src) {
     156            0 :     fprintf(stderr, "wok fmt: cannot read %s\n", path);
     157            0 :     return 2;
     158              :   }
     159           94 :   WokFileStamp at_read = wok_file_stamp(path);
     160           94 :   int rc = process_text(path, src, n, mode, &at_read);
     161           94 :   free(src);
     162           94 :   return rc;
     163              : }
     164              : 
     165              : // --pipe: source on stdin, canonical form on stdout. For an agent or an editor
     166              : // that has the text in hand and does not want to invent a temporary file.
     167          241 : static int process_stdin(Mode mode) {
     168          241 :   usize cap = 1 << 16, n = 0;
     169          241 :   char *src = (char *)malloc(cap);
     170          241 :   if (!src) return 2;
     171          481 :   for (;;) {
     172          481 :     if (n == cap) {
     173            0 :       cap *= 2;
     174            0 :       char *bigger = (char *)realloc(src, cap);
     175            0 :       if (!bigger) { free(src); return 2; }
     176              :       src = bigger;
     177              :     }
     178          481 :     usize got = fread(src + n, 1, cap - n, stdin);
     179          481 :     n += got;
     180          481 :     if (got == 0) break;
     181              :   }
     182          241 :   src[n < cap ? n : cap - 1] = '\0';
     183          241 :   int rc = process_text("<stdin>", src, n, mode, nullptr);
     184          241 :   free(src);
     185          241 :   return rc;
     186              : }
     187              : 
     188          337 : int main(int argc, char **argv) {
     189          337 :   Mode mode = M_STDOUT;
     190          337 :   bool pipe_mode = false;
     191          337 :   int i = 1;
     192          678 :   for (; i < argc && argv[i][0] == '-' && argv[i][1] != '\0'; i++) {
     193          341 :     if (strcmp(argv[i], "-w") == 0) mode = M_WRITE;
     194          340 :     else if (strcmp(argv[i], "--check") == 0) mode = M_CHECK;
     195          243 :     else if (strcmp(argv[i], "--pipe") == 0) pipe_mode = true;
     196              :     else {
     197            0 :       fprintf(stderr, "wok fmt: unknown flag %s\n", argv[i]);
     198            0 :       return 2;
     199              :     }
     200              :   }
     201              : 
     202          337 :   if (pipe_mode) {
     203          243 :     if (mode == M_WRITE) {
     204            1 :       fprintf(stderr, "wok fmt: --pipe has no file to write; use --pipe alone "
     205              :                       "for the formatted text, or --pipe --check for the "
     206              :                       "verdict\n");
     207            1 :       return 2;
     208              :     }
     209          242 :     if (i < argc) {
     210            1 :       fprintf(stderr, "wok fmt: --pipe reads stdin; do not also name files\n");
     211            1 :       return 2;
     212              :     }
     213          241 :     return process_stdin(mode);
     214              :   }
     215              : 
     216           94 :   if (i >= argc) {
     217            0 :     fprintf(stderr, "usage: wokfmt [-w|--check] FILE...\n"
     218              :                     "       wokfmt --pipe [--check] < FILE\n");
     219            0 :     return 2;
     220              :   }
     221              :   int worst = 0;
     222          188 :   for (; i < argc; i++) {
     223           94 :     int rc = process_file(argv[i], mode);
     224           94 :     if (rc > worst) worst = rc;
     225              :   }
     226              :   return worst;
     227              : }
        

Generated by: LCOV version 2.4-1


Machine-readable coverage data

This page is generated. If you are a tool, a script, or an LLM, read the JSON instead of scraping this HTML — it is the same measurement, exact, and it names the individual uncovered lines.

Every artifact here is stamped with the commit it measured and the run that produced it: commit 084deda, dated 2026-08-10T11:39:41Z, built by Github CI run. The totals in coverage.json are checked against lcov’s own summary before publishing, so the JSON and this page cannot disagree.