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:
| Category | Types |
|---|---|
| Signed integers | int8, int16, int32, int64 |
| Unsigned integers | uint8, uint16, uint32, uint64 |
| Floating-point | float32, float64 (IEEE 754) |
| Boolean | bool |
| Character | unicode (32-bit code point) |
| String | string (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:
forbounds are evaluated exactly once before the first iteration (a dedicated test counts the call invocations to prove it).- The
forcontrol 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 MaxInt32is 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 float32is the same machine layout as today’sarray[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 orderedis 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%dpaired with afloat64, 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