Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Formatting

primate ships a normative formatter: there’s one canonical form for any source. primate fmt rewrites a file to that form; the LSP can format on save.

Run it

primate fmt path/to/file.prim       # format one file in place
primate fmt                         # format everything under `input`
primate fmt --check                 # exit non-zero if any file would change

In supported editors, format document (or format-on-save, if configured) runs primate fmt on the buffer.

Rules at a glance

  • 4 spaces. No tabs anywhere.
  • One declaration per line. No semicolons.
  • Single space around = and after :.
  • Sugared types preferred. T[] over array<T>, T? over optional<T>.
  • Trailing comma in multi-line collections (arrays, maps, tuple values, enum bodies).
  • No trailing comma in single-line collections.
  • Magic trailing comma in value literals keeps multi-line layout — see Values.

Alignment within groups

Consecutive declarations with no blank line between them form a group. Within a group, the formatter aligns the type, name, and = columns:

duration TIMEOUT     = 30s
u32      MAX_RETRIES = 5
u64      MAX_UPLOAD  = 100MiB

A /// doc block is part of the declaration that follows it and does not break the group. A blank line breaks the group. A standalone // comment on its own line breaks the group.

Enum bodies follow the same rule — variants align, and = aligns when any variant has an explicit value:

enum LogLevel: u8 {
    Debug = 0,
    Info  = 1,
    Warn  = 2,
    Error = 3,
}

Long-line wrapping

When a logical line would exceed column 100, the formatter wraps at the shallowest delimiter that lets the line fit:

// Before: 134 columns.
type ServiceConfig = map<string, tuple<duration, u64, optional<url>, regex, string>>

// After.
type ServiceConfig = map<
    string,
    tuple<duration, u64, optional<url>, regex, string>,
>

When a line is wrapped:

  • One item per line.
  • Trailing comma on the last item.
  • Inner contents indented +4 from the line that opened the delimiter.

The wrapper recurses if the inner line is also over budget.

The 100-column budget is fixed.

use block normalization

The block of use statements at the top of a file is normalized:

  • Single-item brace groups collapse: use a::b::{X}use a::b::X.
  • Same-path use lines merge: use a::b::X + use a::b::Yuse a::b::{X, Y}.
  • Top-level use lines sort by path.
  • Items inside a brace group sort lexicographically.

A leading comment on a use line pins that line — sort/merge happens within contiguous comment-free runs.

See use statements for examples.

What the formatter doesn’t do

  • It doesn’t reorder declarations (only use blocks are sorted).
  • It doesn’t fix naming-convention violations — the parser flags those as naming-convention diagnostics; you fix them by hand.
  • It doesn’t rewrite literals (100MiB stays 100MiB; not normalized to 1024 * 100).
  • It doesn’t desugar T?optional<T> (the sugar is preferred).

Config

primate fmt has no command-line knobs in v1. Output is fully determined by the formatter rules above. This is intentional: one canonical form, no .editorconfig-style negotiation.