Lyric v2.4.0

Formatting

One command, one shape:

lyric fmt src/

Files are rewritten in place; a directory means every .lyr under it. There are no style options — the shape is the tool's contract, so no project ever argues about it. What the formatter enforces is what this guide has shown all along:

import std.io.console { println };

fn main(): int {
    let greeting = "four spaces, spaces around operators, one hundred columns";
    println(greeting);
    return 0;
}

What it keeps

Your comments — all three forms, where you put them, trailing ones trailing. Your blank lines between statements and declarations, capped at one. Your literal spellings: 0xFF stays hexadecimal, 1_000_000 keeps its underscores. A file that does not parse is reported and left exactly as it was: the formatter never writes a guess over your text.

What it decides

Line breaks against the 100-column limit: what fits stays on its line, what does not breaks with one element per line — and a broken list gets a trailing comma exactly where the grammar allows one. A blank line always follows the module header and separates declarations with bodies; imports sit together.

Two things it decides that are worth knowing in advance. Parentheses that only restate the precedence table go away — (a * b) + c becomes a * b + c, and (a && b) || c becomes a && b || c, because && binds tighter; parentheses that change the parse always stay. And a comment written INSIDE an expression surfaces at the end of its statement: comment placement is line-level, never lost.

In an editor and in CI

lyrfmt --stdin < file.lyr

reads one stream and writes the result to stdout — the form a save hook wants. And

lyric fmt --check .

writes nothing, lists every file that would change, and exits nonzero when any would — the form a CI job wants. This repository holds itself to that: the standard library, the examples and the templates are formatted, and a test fails when they stop being it.