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.