Mica source is written to be read. The standard library, the examples and the courses follow one style, so a reader who knows it moves through an unfamiliar file quickly, and mica --format writes the layout half of it for you. This page states the style rule by rule. Every example on it is a complete program that compiles, formatted exactly as shown.

Let the formatter do the layout

mica --format conversions.mica
mica --format --check sources/

The first command rewrites the file in the house layout and prints its path when it changed something. The second changes nothing: it names every file under sources/ that is not formatted and exits with 1 when there is one, which is what a build pipeline asks. In VS Code, Format Document does the same through the language server.

The formatter changes spaces, indentation and blank lines, nothing else. Every token keeps the spelling you wrote, so ≤ stays ≤ and 0xFF stays 0xFF, every comment keeps its words, and no line is broken or joined. A line can move when blank lines are removed, so after formatting a message’s line number refers to the formatted file.

A unit’s shape

The unit order. File comment · heading · imp · const · type · var · subprograms · body — in that order, nothing interleaved.

{
    Conversions - a table of temperatures from freezing to boiling, in Celsius and Fahrenheit.

    The file opens with the comment that tells its whole story. Below it stand the heading, the imports, the named
    values, the shapes, the program's own state, the routines, and last the body that runs them, in that order.
}
program Conversions;

imp
    WriteLn : std;

const
    { the table runs from water's freezing point to its boiling point, one row every twenty degrees }
    FreezingPoint = 0.0;
    BoilingPoint = 100.0;
    RowStep = 20.0;

    { Fahrenheit is Celsius scaled by nine fifths and moved up by thirty-two degrees }
    FahrenheitScale = 1.8;
    FahrenheitOffset = 32.0;

    RowFormat = "%6.1lf C = %6.1lf F";

type
    { A reading is one temperature, written in Celsius and in Fahrenheit. }
    Reading = record
        celsius : float64;
        fahrenheit : float64;
    end;

var
    reading : Reading;

{ The reading of a Celsius temperature derives its Fahrenheit value from it. }
function ReadingOf(celsius : float64) : Reading;
var
    result : Reading;
begin
    result.celsius := celsius;
    result.fahrenheit := celsius * FahrenheitScale + FahrenheitOffset;
    ReadingOf := result;
end;

begin
    reading := ReadingOf(FreezingPoint);

    while reading.celsius <= BoilingPoint do
    begin
        WriteLn(RowFormat, reading.celsius, reading.fahrenheit);
        reading := ReadingOf(reading.celsius + RowStep);
    end;
end.

The wall between the declarations and the routines is the compiler’s own rule: an import, a constant, a type or a variable section written after a routine is refused with error 2044. The order of the sections among themselves is the house’s, and a file that groups a topic’s type and constants together may repeat a section.

var above nested subprograms. Inside every subprogram, declarations precede any nested routine.

{
    Spread - the gap between the largest and the smallest of a few measurements.

    A routine declares its variables first and its nested routines after them, so a reader meets the state before the
    code that changes it; the compiler refuses a declaration section placed after a routine.
}
program Spread;

imp
    WriteLn : std;

const
    SpreadFormat = "spread %lld";
    LastSample = 4;
    Measurements : Samples = (12, 7, 31, 18, 25);

type
    { The samples are the measurements in the order they were taken. }
    Samples = array[0..LastSample] of int64;

{ The spread of the samples is the largest one minus the smallest one. }
function SpreadOf(samples : Samples) : int64;
var
    index, low, high : int64;

    { A sample widens the range seen so far when it lies outside it. }
    procedure Widen(sample : int64);
    begin
        if sample < low then
            low := sample;

        if sample > high then
            high := sample;
    end;

begin
    low := samples[0];
    high := samples[0];

    for index := 1 to LastSample do
        Widen(samples[index]);

    SpreadOf := high - low;
end;

begin
    WriteLn(SpreadFormat, SpreadOf(Measurements));
end.

Names

Names keep their casing promise. PascalCase exported surface, lowercase locals and fields, lowercase single-word units.

Slightly verbose, never cryptic. A name a reader must decode is wrong; a name that fills a line is too.

Mica has no methods: an operation is a free function whose name says what it does, Append(address lines, line), and a routine that changes its argument takes it through a pointer, so the change is visible where the call stands.

Named values

No body literals. Every body string lives in a named top-level const; every non-structural number too.

The example of a unit’s shape above follows it: the format of each row, the freezing and boiling points and the conversion’s two factors all stand in the constant section under names that explain them.

Formulas

Formula spellings in math-shaped code. Where the sentence is mathematics, it is written as mathematics.

{
    Descent - gradient descent on the parabola f(x) = (x - 3)², written the way the formula is written.

    Mathematics reads as mathematics: the step size and the tolerance carry their Greek names, the start carries its
    subscript, '×' multiplies, and '≥' compares. The walk halves the distance to the minimum at every step and stops
    once the slope is smaller than the tolerance.
}
program Descent;

imp
    WriteLn : std;
    Abs : math;

const
    { the step size, the tolerance the walk stops at, the starting point, and the minimum it walks toward }
    α = 0.25;
    ε = 1.0e-6;
    x₀ = 10.0;
    Minimum = 3.0;

    ResultFormat = "x = %.6lf after %lld steps";

var
    x, slope : float64;
    steps : int64;

begin
    x := x₀;
    slope := 2.0 × (x - Minimum);
    steps := 0;

    while Abs(slope) ≥ ε do
    begin
        x := x - α × slope;
        slope := 2.0 × (x - Minimum);
        steps := steps + 1;
    end;

    WriteLn(ResultFormat, x, steps);
end.

The glyphs are spellings, not new operators: × is *, and ≠, ≤ and ≥ are #, <= and >=. The math unit exports π and τ beside Pi and Tau. A minus sign pasted from typeset text, −, is not Mica’s minus -, and the compiler says so by name. The VS Code extension types the glyphs for you: \alpha becomes α, \leq becomes ≤, and w_1 becomes w₁.

Layout

Layout. mica --format --check passes over the file, and the layout rules the formatter leaves to the author hold.

{
    Grades - test scores sorted into grades, laid out the way the formatter writes every file.

    The program shows the shapes a layout settles: a record's fields one step in, a chain of 'else if' on one depth, a
    case with its arms one step in and its 'else' on the case's depth, and a repeat loop with its 'until' under the
    'repeat'.
}
program Grades;

imp
    WriteLn : std;

const
    { the lowest score of each passing grade, from the best down }
    ExcellentFrom = 90;
    GoodFrom = 75;
    SufficientFrom = 60;

    { the grades, counted from the best }
    Excellent = 1;
    Good = 2;
    Sufficient = 3;
    Insufficient = 4;

    { the scores the program sorts, and the last index of the list }
    Scores : ScoreList = (95, 82, 67, 41);
    LastScore = 3;

    GradeFormat = "score %lld is grade %lld";
    TallyFormat = "%lld passed, %lld failed";

type
    { A score list holds the scores in the order the program grades them. }
    ScoreList = array[0..LastScore] of int64;

    { A tally counts the scores that passed and the scores that failed. }
    Tally = record
        passed : int64;
        failed : int64;
    end;

var
    tally : Tally;
    index, grade : int64;

{ A score's grade is the best one whose lowest score it reaches. }
function GradeOf(score : int64) : int64;
begin
    if score >= ExcellentFrom then
        GradeOf := Excellent
    else if score >= GoodFrom then
        GradeOf := Good
    else if score >= SufficientFrom then
        GradeOf := Sufficient
    else
        GradeOf := Insufficient;
end;

begin
    tally.passed := 0;
    tally.failed := 0;
    index := 0;

    repeat
        grade := GradeOf(Scores[index]);

        case grade of
            Excellent, Good, Sufficient: tally.passed := tally.passed + 1
        else
            tally.failed := tally.failed + 1
        end;

        WriteLn(GradeFormat, Scores[index], grade);
        index := index + 1;
    until index > LastScore;

    WriteLn(TallyFormat, tally.passed, tally.failed);
end.

The formatter applies every rule of this list, so running it is all a file needs:

Indentation. Four spaces per depth, and never a tab.

Line ends. No line ends in white space, every line ends in a line feed, and the file ends with exactly one; an empty file stays empty.

Blank lines. At most one blank line in a row; none at either end of the file, none right after a line that opens a block, and none right before the word that closes one. The formatter never adds a blank line.

Sections. gen, imp, const, type and var stand on the depth of the unit or routine they belong to, and their members one step in.

Routines. A routine’s head, its sections, its begin and its closing end stand on its nesting depth: column zero at the top, one step in for each enclosing routine.

Blocks. The statements of a block stand one step in from the line that opens the block, and the word that closes it stands on that line’s depth.

begin. On a line of its own, begin stands on the depth of the statement that controls it. Whether begin ends the controlling line or stands on its own is the author’s choice, and the formatter keeps it.

Controlled statements. A statement controlled by then, do, else, an arm’s label or an on fail handler stands one step in when it starts its own line.

else. else on its own line stands on its if’s depth, and else if stays on one line, so a chain keeps one depth. Whether end and else share a line is the author’s choice.

case. Arms stand one step in from case; an arm’s statement on its own line stands one step in from its label, a begin on the label’s depth; else and end stand on the case’s depth, and an arm’s colon has no space before it.

repeat. The statements of repeat stand one step in, and until stands on the repeat’s depth.

select, concurrent and tape. Their arms and statements stand one step in; a concurrent block’s on fail and on lost sections, and every end, stand on the block’s depth.

Failure handlers. A failure handler that starts its own line - on fail with a name and do, or on lost do - stands on the depth of the statement it ends, and its statement one step in; behind a loop’s or a select’s end every failure suffix stands on that depth; behind a call, on fail use and on fail leave on a line of their own continue the statement one step in.

Labels. A label stays on its statement’s line, with one space on each side of its colon.

Records and other types. A record’s fields stand one step in from the line that holds record, and its end on that line’s depth; enumerations and error domains are spaced like any list.

Spacing. One space around := and every binary operator, none after a sign; none inside brackets, none before a comma or a semicolon and one after; one space on each side of a declaration’s colon, and none before the colon that names a field, (x: 1.5, y: 2.5); none around . and ..; none between a name and its ( or [, and one between a keyword and its (, a word that introduces a value counting as a keyword: on fail use (n + 1).

Formula glyphs. A glyph stays the glyph its author wrote, ≤ as ≤ and <= as <=, and is spaced like its ASCII twin.

Continuation lines. A line that continues a statement or a declaration stands one step in from the line holding the innermost bracket still open around it, which is the statement’s first line where no bracket is open; a line its author aligned under an open bracket stays under it; a line that starts with a closing bracket stands on the depth of the line holding its opening bracket.

Line structure. Every line break between two tokens is the author’s: the formatter never breaks or joins a line.

Comments. A comment on its own line takes the column of the code below it, unless it continues the end-of-line comment above it in that comment’s column, where it stays beside that comment; a multi-line comment moves as one piece; an end-of-line comment keeps its column, one space behind the code where the code now reaches it; no character of a comment changes.

Alignment. A run of declarations, arms or assignments its author aligned stays aligned, at its narrowest column; any other run gets the ordinary spacing.

Literals. Strings, unicode and regex literals are copied byte for byte, a string that spans lines included.

The file’s first bytes. The addressing line stays line one, byte for byte up to its line end, and a leading byte-order mark is kept.

These four are the author’s to keep, because only the author knows where a line or a blank line helps the reader:

One begin placement. Keep one choice per file: begin ends the line of its controlling statement, or stands on a line of its own.

One statement per line. Write one statement on each line.

Section keywords alone. A section keyword stands alone on its line, with its members below it.

Blank lines between groups. One blank line separates declaration groups and routines, and none appears inside a group without a reason.

Comments

Comments tell the story. Every file opens with a { } comment that tells its whole story; every routine and every type carries a comment above it, written as a sentence with a real subject; and a comment inside a body is a short lowercase phrase without a final period.

{
    Inventory - the items whose stock has fallen to the level at which they are ordered again.

    The file comment tells the program's whole story. Every routine and every type carries a comment above it, written
    as a sentence with a real subject, and a comment inside a body is a short lowercase phrase without a final period.
}
program Inventory;

imp
    WriteLn : std;

const
    { the units on each shelf, and the level at which each item is ordered again }
    OnShelf : Levels = (12, 3, 9);
    ReorderAt : Levels = (5, 5, 10);
    LastItem = 2;

    ReorderFormat = "reorder item %lld: %lld left, ordered again at %lld";

type
    { A list of levels holds one level for each item, in the order the items are listed. }
    Levels = array[0..LastItem] of int64;

var
    item : int64;

{ An item needs ordering once its shelf holds no more than its reorder level. }
function NeedsOrder(onShelf : int64, reorderAt : int64) : bool;
begin
    NeedsOrder := onShelf <= reorderAt;
end;

begin
    { every item is checked, and only those at or below their level are reported }
    for item := 0 to LastItem do
        if NeedsOrder(OnShelf[item], ReorderAt[item]) then
            WriteLn(ReorderFormat, item, OnShelf[item], ReorderAt[item]);
end.