Utility: fmt - format a script to the GodspeedOS standard

Status: BUILT and MERGED - the .gsh formatter, one canonical layout, non-negotiable, applied in place. STREAMING (no file-size cap), semantics-preserving, and idempotent. Pinned by osdev test files (207/0), osdev test fmt-demo (6/0 - jar before/after, a medium multi-flush file, and a 10 MB file formatted with fmt check = Ok), and osdev test fmt-idem (format-twice diff = IDENTICAL). Trails CLAUDE.md; does not amend it.


1. What it is

fmt <path> rewrites a .gsh script in place to the single GodspeedOS canonical layout. You run it knowing exactly what happens - there is nothing to decide. It is the gofmt / terraform fmt / rustfmt idea, for gsh: the point is not prettiness, it is uniformity. A canonical format kills bikeshedding and makes diffs mean something - you can see what changed instead of squinting past ten differently-cramped lines.

# before  (valid gsh, but jarring - crammed onto lines with ; and inline blocks)
let n = 7 ; if $n > 5 { echo big } else { echo small }
  for i in range 1 5{echo $i}

# after   fmt /script.gsh
let n = 7
if $n > 5 {
    echo big
} else {
    echo small
}
for i in range 1 5 {
    echo $i
}

(fmt fixes layout - the ; becomes newlines, inline blocks expand and indent, a space is inserted before {, runs of whitespace between tokens collapse. It does not re-space inside a token, so a $n>5 written without spaces would stay $n>5 - see the scope note in §2.)

Boring on purpose - zero style options

There is exactly one canonical layout and no knobs to change it. The second fmt grows a style flag, you have re-introduced the argument it existed to end. fmt has subcommands (below), but none of them change how the code is formatted - only what fmt does with the result.

2. The canonical standard (what "formatted" means)

  1. Indent 4 spaces per block depth. No tabs.
  2. Braces, K&R. header { on one line, body indented, } at the block's own indent. } else { and } else if <cond> { stay on one line.
  3. One statement per line. A ; statement separator becomes a newline (the biggest de-jarring win). cmd ; cmd ; cmd becomes three lines.
  4. One space between tokens. Runs of whitespace between tokens collapse to a single space; leading/trailing whitespace per line is stripped.
  5. # comments keep one space after the #; a full-line comment sits at the current indent, a trailing comment is one space after the statement.
  6. Blank lines collapse (2+ become 1); no leading or trailing blank lines; the file ends in exactly one newline.
  7. Strings are sacred. Content inside '…' / "…" is never touched - not its spacing, not its case, nothing.
  8. Idempotent. fmt of already-formatted text is a no-op. fmt(fmt(x)) == fmt(x) is an acceptance test - met, proven from 8 KB to 10 MB by osdev test fmt-idem (format a chunk-spanning file twice and diff = IDENTICAL).

Scope note (honest). fmt canonicalizes layout - indentation, statement-per-line, brace placement, inter-token whitespace, blank lines. It does not re-space the insides of a token (blk-16, val-$x), because gsh uses -/+ inside words as well as as operators, and guessing which is which would risk changing meaning. Micro-spacing of arithmetic is a later slice, gated on the tokenizer's exact rules. Layout is the jarring part; that is what ships first.

3. Surface

Subcommands are words and come before the path (fmt is a subcommand tool like drives, not a file-first verb like write). There is no write subcommand - the default is the write.

fmt <path>            format the script IN PLACE (the default; no subcommand)
    e.g. fmt /script.gsh
fmt check <path>      is it already canonical? Ok + silent if yes; Err + loud if no. Never writes.
    e.g. fmt check /script.gsh
fmt <a>,<b>,...       format (or check) several files - comma-separated, done one at a time
    e.g. fmt /x.gsh,/y.gsh
fmt version           print the version
fmt help              print usage
  • fmt <path> - the default. Format in place, on the fly. Safe to run blind: it only moves whitespace and newlines, so it can never change what the script does (§2.7-2.8).
  • fmt check <path> - the discipline / CI gate. Returns Err (via the Result model, 32_result.md) and names the file if it is not canonical, so a script can gate on it (fmt check /s.gsh ; if result == Err { fail "run fmt" }). Changes nothing on disk.
  • Bare fmt prints usage (a path is the actionable arg; without one there is nothing to do).

4. Guardrails & safety (loud, never silent - §26.7)

Because the default writes in place with no preview, fmt is all-or-nothing and never leaves a damaged file. It streams (constant memory, no file-size cap), and the write is temp-then-rename, so a failed write can never touch the original:

  • Won't parse -> refuse, file untouched. A syntactically broken script (unbalanced braces, a bad block header) gets a loud error and no write. fmt never lays a half-formatted result over a broken one; fix the syntax, then format.
  • One statement too long -> refuse, file untouched. The streamer holds one statement across a read boundary; a single statement bigger than the hold buffer is refused loudly. This is a per-statement bound, not a per-file one - the file itself can be any size (a 10 MB script formats fine).

Neither guardrail is an option - they are the safety that lets the zero-preview default be trusted.

5. Later (separate so it can grow)

  • fmt diff <path> - print the changes fmt would make without writing (needs a bounded diff).
  • fmt <dir> - format every .gsh under a directory in one call.
  • Host osdev fmt + VS Code format-on-save. The single definition of "GodspeedOS standard" is factored into a no_std core crate so the shell built-in and a host osdev fmt share it - one canonical format, never two that drift. This is where the gofmt/terraform muscle-memory lands.

6. Implementation shape & conformance

A shell built-in (0_conventions.md §2), using caps the shell already holds - no widened authority. It is a fully streaming, token-level re-emitter: fmt_stream_pass reads the source in chunks (fs_read_at) and fmt_walk_window formats each, emitting to a closure - holding only ONE partial statement + the indent depth across a read boundary (no native recursion). Constant memory, any file size. It never evaluates the script; it only re-lays-out its tokens, which is why it is provably semantics-preserving.

The write is two streaming passes: pass 1 counts the output size (for OP_WRITE_NEW), pass 2 stream-writes 508-aligned chunks (OP_WRITE_AT) into a temp, which is then renamed over the original - so a failed write leaves the original intact. fmt check formats to a temp and compares the two files sequentially (never two concurrent reads of the same file). Pinned by osdev test fmt-idem (the format-twice boundary diff) and osdev test fmt-demo (the 10 MB).

Has its own help_block and is listed under Console in the top-level help, and conforms to 0_conventions.md rules 1 and 5: fmt is in the shell's UTILS list, so fmt help and fmt version are intercepted before cmd_fmt. (Found 2026-10-09 missing from UTILS, so fmt help took help as a PATH to format; fixed in backlog/fixed/80 H8.)