Mica 5.0.0 Technical Portrait · Chapter 2 of 9 · reading guide

Before the architecture, the language. Every snippet below is real, compiled code from the test suite. The point of this chapter is not a grammar reference — it is to show you how Mica reads, because readability is the whole premise.


◈ A first look

The language reads like structured intent

type
    Color = (Red, Green, Blue);
var
    c : Color;
begin
    c := Green;
    case c of
        Red:   WriteLn("red");
        Green: WriteLn("green");
        Blue:  WriteLn("blue");
    end;
end.

No integer casts. No fall-through hazards. No magic numbers. The source says exactly what the program means — and the compiler enforces the domain. A Color variable cannot accidentally hold an integer, and an integer cannot silently become a Color.

Every pointer operation is spelled out

procedure Scale(p : pointer Point; factor : float64);
begin
    p.x := p.x * factor;
    p.y := p.y * factor;
end;

pointer Point in the signature is an unambiguous statement: Scale modifies the caller’s record. Not T*, not an implicit reference — a readable description of intent, visible at every call site. Note that p.x works directly: when the base of a field-selection chain is a pointer to a record, the compiler lowers the dereference automatically. The explicit form value p.x is also valid and produces identical machine code.

Imports are platform calls, not a private ecosystem

imp
    WriteLn : std;
    Sin     : math;

WriteLn is console output layered directly on the C standard I/O; Sin is libm. The names are clean, but nothing is invented underneath — the compiled binary builds on the same C runtime every C program on the system uses. More on this in Chapter 6.

All numeric types

Mica provides the full set of fixed-width types systems programmers expect:

CategoryTypes
Signed integersint8, int16, int32, int64
Unsigned integersuint8, uint16, uint32, uint64
Floating-pointfloat32, float64 (IEEE 754)
Booleanbool
Characterunicode (32-bit code point)
Stringstring (UTF-32, descriptor-based)

Every type has a precise size, a precise ABI classification, and predictable behavior at every optimization level.


◈ Structured control flow

Mica’s statement set is small, complete, and free of traps.

{ for: ascending (to) and descending (downto); bounds evaluated exactly once }
for i := 0 to 3 do
    sum := sum + i;

for i := 3 downto 0 do
    WriteLn("i = %d", i);

{ repeat: post-test, body always runs at least once }
repeat
    WriteLn("i = %d", i);
    i := i + 1;
until i > 3;

{ case: dispatch on any ordinal selector, with an optional else }
case selector of
    1: WriteLn("one");
    2: WriteLn("two")
else
    WriteLn("other")
end;

Three guarantees the compiler enforces and the harness verifies:

  • for bounds are evaluated exactly once before the first iteration (a dedicated test counts the call invocations to prove it).
  • The for control variable is read-only inside the body — assignment, address-of, and outer-scope reuse are all compile-time errors.
  • Terminal-bound safety: when the control variable reaches the final bound and would have to overflow to continue, the loop exits before the step, so for i := MaxInt32 - 1 to MaxInt32 is safe with exactly two iterations.

leave exits the enclosing procedure. and and or short-circuit — the right-hand side is never evaluated when the left already decides the result, and the compiler proves it by emitting conditional jumps, not eager calls.

Checked arithmetic is a policy, not a library

a := MaxInt64;
b := 1;
c := a + b;     { what happens here depends on --optimize }

The same source produces different behaviour by compiler policy:

  • --optimize debug / release — hardware wrap semantics; overflow is silent.
  • --optimize checked — the compiler weaves overflow detection into every signed arithmetic site; on overflow the program terminates with a diagnostic naming the source file and line.

The harness proves all three with one source file and three expected outputs.


◈ Records, pointers, and explicit memory

type
    Point = record
        x : int32;
        y : int32;
    end;

var
    point : Point;
    ptr   : pointer Point;

begin
    point.x := 4;
    ptr := address point;

    value ptr.x := value ptr.x + point.y;   { explicit — always valid }
    ptr.x := ptr.x + point.y;                { auto-deref — also valid for pointer-to-record }
end.

The memory vocabulary is words, not punctuation:

  • pointer Point — a pointer type, declared in plain words.
  • address point — takes the address of a variable (no &).
  • value ptr — dereferences to read or write the full pointed-to value (no *).

Pragmatic auto-deref applies to one precise case: a pointer-to-record identifier at the base of a field-selection chain. Everything else stays explicit. Arrays do not decay to pointers. Aggregates carry no implicit reference semantics. The rewrite is typed, deterministic, and lowered during semantic analysis — it has no runtime cost.


◈ The ordinal universe

This is the part of Mica’s type system most worth your attention. In most languages “number” and “sequence” are the same thing: arrays start at zero, loops count upward, enumerations are secretly integers, sets are bit masks. The domain model — months, colors, grades, directions — lives only in the programmer’s head.

Mica carries that domain knowledge in the type system itself. An ordinal type is any type whose values form a finite, ordered, discrete sequence with a first element, a last element, and a successor: bool, the integer types, unicode, enumerations, and subranges. Every ordinal type has computable domain bounds the compiler knows exactly — and it uses them everywhere.

Enumerations name the domain

type
    Color = (Red, Green, Blue, Yellow);

Red is not 0. It is Red. The compiler knows the type is Color, knows the domain has four elements, and rejects assigning a Direction to a Color. The ordering follows declaration order: Red < Green < Blue < Yellow.

Subranges restrict the domain

type
    Day   = 1..31;
    Digit = 0..9;

Day is not int32; it is a type whose domain is exactly {1..31}. A function that accepts a Day is telling the truth about what it accepts — the contract is in the type, not in a comment.

Arrays carry a lower bound that means something

In most languages arrays start at zero — a hardware convention that leaked into language design. But months start at 1, building floors are numbered, a convolution kernel is centered at zero. Every time the domain doesn’t start at zero, the programmer carries an invisible translation: days[month - 1], pop[year - 1900], kernel[offset + 2]. Forget it once and you read the wrong data, silently.

Mica arrays span exactly the domain of their ordinal index type:

type
    Month       = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
    DaysInMonth = array[Jan..Dec] of int32;
    Population  = array[1900..2100] of int64;   { lower bound belongs to the type }
    Kernel      = array[-2..2] of float64;       { negative lower bound }

DaysInMonth[Jan] is January. Population[1970] is the population in 1970 — not Population[1970 - 1900]. Kernel[-1] is the value at offset −1. The compiler computes the size from upper - lower + 1; there is no programmer arithmetic to get wrong, and a constant index outside the range is a compile-time error.

Sets are membership, not bit manipulation

type
    ColorSet = set of Color;
var
    palette : ColorSet;
begin
    palette := [Red, Green, Blue];
    if Red in palette then WriteLn("Red is in the palette");
end.

Sets in Mica are mathematical sets over an ordinal domain — not int with & and |. Membership is in. Construction uses [...] with optional range expressions [lo..hi]. The element type is Color, not int, and the domain is known at compile time.

It composes

When ordinal types, ordinal-indexed arrays, and sets work together, the code says what it means:

type
    WorkDay    = (Mon, Tue, Wed, Thu, Fri);
    WorkDaySet = set of WorkDay;
    HourTable  = array[Mon..Fri] of int32;
var
    meetings  : WorkDaySet;
    workHours : HourTable;
begin
    meetings := [Mon, Wed, Fri];
    for day := Mon to Fri do
    begin
        if day in meetings then
            workHours[day] := workHours[day] - 1;
        total := total + workHours[day];
    end;
end.

No magic integers, no enum-to-int casts for indexing, no bit-mask arithmetic. A reader — or the compiler — can verify it locally, knowing only the type declarations. And it all passes by value, returns by value, and nests to arbitrary depth, with real machine code and DWARF annotations the test suite verifies.

The AI connection. This is infrastructure for Mica’s direction. Tomorrow’s matrix[Axis, Axis] of float32 is the same machine layout as today’s array[Red..Blue] of int32 — but the ordinal index carries the meaning that enables compile-time shape checking. Mica is growing tensor semantics out of a foundation designed for them, not retrofitting them onto raw integers. See Chapter 9.


◈ Generics — written once, typed for every caller

Mica has compile-time generics, and they are fully monomorphized: the compiler clones a generic body once per distinct set of concrete type arguments and compiles each clone exactly as if you had written it by hand. No boxing, no type erasure, no runtime cost — the uninstantiated template is never code-generated at all.

A type parameter is introduced and constrained in a gen block, written right after the signature:

function Maximum(left : T, right : T) : T;
gen
    T is ordered;
begin
    if left > right then
        Maximum := left
    else
        Maximum := right;
end;

There is no angle-bracket ceremony at the call site — the type argument is inferred from the arguments:

WriteLn("max = %d",   Maximum(a, b));      { T = int32 — one specialization }
WriteLn("max = %lld", Maximum(big, big2)); { T = int64 — a second specialization }

The words after is are capabilities — the same vocabulary the type system uses everywhere (ordered, numeric, equality, integral, fractional, logical, …). They are checked twice, and that is what makes the feature honest:

  • At the definition, the body is type-checked once against the declared capabilities. A body that adds two values of a parameter declared only is ordered is rejected there“data type ‘T’ cannot be used in arithmetic operation ‘addition’ (requires numeric)” — not later, at some unlucky call site.
  • At each call, the inferred concrete type must satisfy the full constraint, or the call is rejected before any code is generated.

Multiple independent parameters are allowed (T is numeric; S is ordered;), and a type argument may be any type that satisfies the constraint — a record, a string, a fixed array — not only a scalar:

function Echo(item : T) : T;
gen
    T is equality;
begin
    Echo := item;
end;
{ one Echo, instantiated for a record, a string, and an array[0..2] of int32 alike }

◈ Strings — UTF-aware, fused, and format-checked

A Mica string is a small descriptor — a pointer to its code units plus a code-point count — with value semantics. The encoding is chosen per target: UTF-32 by default, UTF-8 when the platform line asks for it, with the same source serving both. The buffer is always terminated, so a Mica string hands straight to a C API with no conversion.

Two things make strings pull their weight:

  • Concatenation is fused. A chain like s := s1 + s2 + s3 + s4; is the classic place a naive compiler allocates a cascade of throwaway intermediates (s1+s2, then (s1+s2)+s3, …). Mica flattens the whole +-tree at compile time — any parenthesisation collapses — and emits a single sized allocation filled in one pass. N operands, one allocation, zero intermediate strings.
  • Format strings are validated at compile time. The arguments of WriteLn("%d %ls", n, name) are checked against the format specifiers through the same contract system that types the rest of the call — a %d paired with a float64, or a missing argument, is a compile error, not a runtime surprise.

◈ New in 5.0.0 — nil, and linked structures

The heap (Chapter 3) needs two pieces of language surface, and both landed in 5.0.0.

nil is a first-class pointer value. It is assignable to any pointer type and comparable with = / #. A freshly heap-allocated pointer field starts as nil because allocation is zero-initialized — so a null pointer is a normal, expected state, not undefined memory.

var
    head : pointer Node;
begin
    head := nil;
    if head = nil then
        WriteLn("empty list");
end.

Dereferencing nil is caught — always. Mica classifies a nil dereference as a memory-safety event, not a checked-arithmetic option, so the guard is on in every build, debug and release alike. On Linux x86_64/ARM64 the hardware page fault backs the guarantee; on a future target without a hardware MMU the software check stays in, by stated policy.

Self-referential record types compile. A record may now contain a pointer to its own type — the prerequisite for linked lists, trees, and graphs:

type
    Node = record
        item : int64;
        next : pointer Node;   { points to its own type }
    end;

That is a real, compiled declaration; the program builds a four-node list on the heap and walks it twice — once to sum, once to release:

head := new Node;
head.item := 10;
cursor := head;
for i := 2 to 4 do
begin
    node := new Node;
    node.item := i * 10;
    cursor.next := node;     { auto-deref through the self-pointer }
    cursor := node;
end;

Self-referential types are what unblock the canonical linked-list and binary-tree programs — and they are the first step of the owned data structures work that lets a container take responsibility for freeing what it holds. That is exactly where the memory model takes over.


Where to next

You’ve seen how Mica reads and how its type system carries meaning. Now the flagship of 5.0.0: how a new becomes a heap allocation the compiler can prove is freed exactly once — without a garbage collector or a borrow checker.

Chapter 3 — Memory & the Heap · back to the reading guide