This chapter describes how the tokens of chapter 1 form a unit: a program, a library or an object, with the sections, routines and body of its block, the value every variable starts at, and the imports that bring names from other units into it. Each construct names its rule in the grammar, says what it means, and quotes what the compiler answers when a source breaks the rule. What a declaration inside the sections declares is the grammar’s units and declarations and the type system.
Every example is complete, compiled and run by the compiler’s own test suite on AMD64 and AArch64, and the output shown beneath it is the output those runs are held to.
A compilation unit
A source is one unit: its kind, its name, a semicolon, its block and a final period (compilation-unit). The kind says what the unit becomes when it is linked (unit-kind):
| Kind | Becomes |
|---|---|
program | an executable, named after the program, whose body is what runs |
library | an archive or a shared object, built on its own under a version, whose contract carries its surface to every program built against it later |
object | a part of the build it is compiled in, beside a program or a library, which imports it by the unit’s name |
A build holds one program or one library, and any number of object units beside it. The name in the heading is what another unit imports by; the name of the file that holds the source does not matter, and the binary a program links to takes the program’s name.
| The source or the build | Code | The compiler’s words |
|---|---|---|
| a source without a heading | 2005 | expected keyword to declare the output kind, found import, for a source that opens with imp |
a source whose last end has no period | 2007 | expected period at end of the program, found end of file |
| a program and a library together | Multiple program or library statements found in source code; only one is allowed | |
| object units alone | The sources hold no program or library: an object unit is built beside the program or library that imports it, named with it in one --source list; compilation aborted | |
| two headings in one source | 2082 | a source file holds one unit, and this 'program' heading begins a second one: put each unit in a file of its own and name the files together in the --source list |
The block
A block is its sections of declarations, then its routines, then the statement
it runs (block). The
sections are the type parameters behind gen, the imports behind imp, the
constants behind const, the types behind type and the variables behind
var (declaration-section).
They stand in any order and as often as the unit likes; the order of
the house style is imports, constants, types,
variables.
A constant may read any constant its block or an enclosing block declares, above or below it, in its own section or in another: each constant is computed after the constants it reads, and a type’s bound may read any of them. A name means the nearest declaration of it, so a routine’s constant hides the program’s constant of the same name even where the routine writes it below the constant that reads it.
const
Area = Width * Height; { 20, read from the two constants below it }
Width = 4;
Height = Width + 1;Constants that read each other have no first one to compute, so they are refused once, at the one written first, by the names of all of them:
| Written | Code | The compiler’s words |
|---|---|---|
constants that read each other, First = Second; Second = First; | 5658 | 'First' and 'Second' read each other, so none of them can be computed before the others: give one of them a value, a bound or a type that reads none of the others |
a constant that reads itself, Again = Again + 1; | 5665 | 'Again' reads its own value, so it cannot be computed: give it a value that does not read 'Again' |
A section after a routine of the same block is refused, because every declaration of a block stands above its routines:
| Written after a routine | Code | The compiler’s words |
|---|---|---|
const, type or var | 2044 | a constant declaration cannot follow a function or procedure; move all of the block's constant, type, and variable declarations above its functions and procedures, naming the kind it met |
imp | 2069 | an import declaration cannot follow a function or procedure; move all of the block's import declarations above its functions and procedures |
A routine has a block of its own, so routines nest, at most sixteen deep. A
seventeenth is refused with parser error 2004: routines nest at most 16 deep, and this one stands at depth 17: declare it in a routine further out, and hand it what it reads as parameters.
The statement a block runs is any one statement
(action), and in practice a compound
statement, begin, statements separated by semicolons, and end. A section of
declarations goes on as long as a line begins with a name, so a body that
follows a section directly and begins with a name is read as one more
declaration: write the body as begin ... end.
{
Distances - a program whole: its heading, its sections, its routines and its body.
The sections stand in the house order, imports, constants, types and variables, and the routines below them. A
routine has a block of its own, so it may declare variables and routines of its own before its body. The body at
the end is the one statement the program runs, and the program ends when that statement does.
}
program Distances;
imp
WriteLn : std;
Sqrt : math;
const
PointCount = 3;
DistanceLine = "point %lld lies %lf from the origin";
type
{ A point of the plane. }
Point = record
x : float64;
y : float64;
end;
{ The points the program measures, counted from one. }
Points = array[1..PointCount] of Point;
var
points : Points;
index : int64;
{ The distance of a point from the origin, read from the squares of its two coordinates. }
function Distance(p : Point) : float64;
var
squares : float64;
{ The square of one coordinate, a routine nested in the routine that needs it. }
function Square(coordinate : float64) : float64;
begin
Square := coordinate * coordinate;
end;
begin
squares := Square(p.x) + Square(p.y);
Distance := Sqrt(squares);
end;
begin
points[1].x := 3.0;
points[1].y := 4.0;
points[2].x := 6.0;
points[2].y := 8.0;
points[3].x := 0.0;
points[3].y := 2.5;
for index := 1 to PointCount do
WriteLn(DistanceLine, index, Distance(points[index]));
end.It prints:
point 1 lies 5.000000 from the origin
point 2 lies 10.000000 from the origin
point 3 lies 2.500000 from the originVariables and their start values
Every variable starts at its type’s start value, so a program may read a
variable before it assigns one, and no read ever meets bytes that an earlier
call or an earlier owner of the memory left behind. A program’s variables take
their start values before its body runs, a routine’s at every call, a task’s and
a generator’s when each begins, and the variables a compound statement declares
at every entry of the compound, so a loop body’s variables start afresh every
round. A cell that new makes starts the same way, and so does every element
that reserve or Resize adds to a dynamic array.
| A variable of | Starts as |
|---|---|
an integer type, int8 to uint64, or a wide integer | 0 |
float32, float64, float16 or bfloat16 | 0.0 |
bool | False |
unicode | the character whose code is 0 |
descriptor | the number 0 |
| a subrange | 0 where its range holds 0, and otherwise the bound nearest 0: 1..12 starts at 1, -5..-1 at -1 and 'a'..'z' at 'a' |
| an enumeration | its first member, and a subrange of one, Green..Blue, its low member |
| an error domain | its first code |
| a set or a bitset | empty |
| a bitrecord | every field at its own start value |
| a record, a fixed array, a vector, a matrix or a tensor | every field and every element at its own start value |
| a sum type | its first case, with that case’s fields at their start values |
string, stringbuffer or stringpart | empty |
| a dynamic array, a dynamic vector, a dynamic matrix or a map | empty |
| a span | the empty view: length 0, Low 0 and High -1 |
| a pointer | nil |
a file | closed |
| a stream | ended, so pulling from it yields nothing |
tracked T | the start value of T |
A variable declared with an initial value, limit : int64 := 5;, starts at its
start value as well and takes the initial value where its declaration stands. A
parameter holds what the caller handed over. A variable declared absolute
names a device register: it holds whatever the device holds there, and the
program never writes a start value to it.
The program below reads its variables before it assigns any of them, and its function counts with a counter it never sets to zero:
{
Readings - every variable starts at its type's start value, so a program may read one before it assigns it.
The body prints its variables before it assigns any of them: the month starts at 1, the low bound of its range,
the colour at its first member, the reading's value at zero and its hour at 1, the name empty and the list without
values. 'Tally' counts the values above a limit and never writes 'count := 0', because its counter starts at zero
at every call.
}
program Readings;
imp
WriteLn, Ord, Length, Append : std;
const
StartLine = "month %lld, colour %lld, reading %lld at hour %lld, name of %lld runes, list of %lld values";
TallyLine = "%lld of %lld values above %d";
Limit = 20;
type
Month = 1..12;
Colour = (Red, Green, Blue);
Hour = 1..24;
{ One measured value and the hour it was taken. }
Reading = record
level : int64;
hour : Hour;
end;
Values = array of int64;
var
month : Month;
colour : Colour;
reading : Reading;
name : string;
values : Values;
{ The number of values above the limit; the counter starts at zero at every call. }
function Tally(list : Values, limit : int64) : int64;
var
count : int64;
item : int64;
begin
for item in list do
if item > limit then
count := count + 1;
Tally := count;
end;
begin
WriteLn(StartLine, month as int64, Ord(colour), reading.level, reading.hour as int64, Length(name), Length(values));
Append(address values, 12);
Append(address values, 25);
Append(address values, 31);
WriteLn(TallyLine, Tally(values, Limit), Length(values), Limit);
end.It prints:
month 1, colour 0, reading 0 at hour 1, name of 0 runes, list of 0 values
2 of 3 values above 20Writing a start value out costs nothing. An assignment that stores a local’s
start value while the local still holds it, as count := 0; does as a routine’s
first statement, emits no store in a debug build, where a breakpoint on its line
still stops, and an optimized build reads it as the counter’s first value, which
the counter’s register needs in any case. This holds for a local that the
routine neither lends with address nor shares with a routine nested in it; any
other store of a start value, a reset at the head of a loop body among them,
runs as written.
A pointer starts at nil, so a pointer variable that no statement assigns can
only ever be nil, and a use of one is refused:
| Written | Code | The compiler’s words |
|---|---|---|
| a read of a pointer variable that no statement assigns, or a write through one | 5167 | identifier with pointer data type is used before its initialization: p |
| a function returning a pointer whose result no statement assigns | 5168 | function return variable with pointer data type is uninitialized: Find |
The program body
A program’s body is the statement the program runs. It starts after the body of
every library and every object unit the program uses has run, and the program
ends when the body does, with exit code 0. A leave statement in the body ends
the program there. A trap ends the program with exit code 1, as
the traps reference describes.
Exports
The mark exp in front of a declaration at the root of a library or an object
unit exports it: a constant, a type, a function, a procedure, a task or a
generator (constant-entry,
type-declaration,
routine-declaration).
What the marks export is the unit’s whole surface. A declaration without the mark
is private to the unit, and a variable is never exported: an importer reaches a
unit’s state through the routines it exports.
A program has no importers, and a declaration nested in a routine is invisible
outside it, so the mark on either is refused with analyzer error 5397: the 'exp' mark exports a library's root declaration, so 'Twice' cannot carry it here: a program has no importers, and a nested declaration is invisible outside its enclosing body.
The mark is refused by name on a callback (5511). A sum type takes it as any
other type does: the contract states each case with the fields it carries, and an
importer names the cases it uses beside the type, as it names an enumeration’s
literals. A
function with a conformant array parameter has no shape in a library’s interop
contract, so a build asked for the contract (--emit-contract) refuses it by
name: published function "Total" in library "conf" carries a conformant array parameter, which has no interop contract shape. An exported generic routine is
compiled into each unit that imports it, so its signature and its body may name
only what the exporting unit exports (5612 and 5557).
Libraries
A library is built on its own, as an archive with --output-modifier archive or
as a shared object with --output-modifier object, and --emit-contract writes
its contract, mica.external, beside it: the surface its exp marks state, which
a program built later imports through. A shared object names the shared runtime
floor installed beside the archives instead of carrying a copy of it, so every
Mica library one process loads, linked or loaded with dlopen, runs on one floor
with one scheduler and one carrier pool. The floor stays loaded once loaded, and
the Mica libraries of one process are built alike, by one release at one tier with
one tasking flavor and memory class: a library that names another floor is refused
by name as it loads. A library that exports a task, or that
places work on processes, links as an archive, as the
known limitations
state.
A library is linked only under a version, read from the version of the
mica.project that --project names; a library build without one is refused
before it links:
A library as output binary states its version as "version" in the mica.project it is built from: linking abortedThe version travels into the contract, into the build of every program built
against it (Using library 'gauge' 1.0.0) and into that program’s
manifest. A program states which versions of a
library it accepts under requires in its own project file, and a library
outside them is refused before a source is read against its contract:
Failed to resolve imported libraries: interop error 562: the project requires the library 'gauge' at '2.0', which accepts the versions from 2.0.0 up to 3.0.0, and the library is at version '1.0.0'A library’s body runs once when a program that uses it starts, before the
program’s own body: it sets up the library’s state. A statement registered in it
with defer until program runs once when the program has ended, after its body,
and several such statements run in the reverse order of their registration.
Such a statement stands at the top level of the library’s body, outside every
choice and loop, so every run of the body reaches it; one inside an if, a
case, a loop or a select is refused (5799), as the
deferred statements
section shows.
The example is two directories side by side, the library in gauge and a program
built against it in meter:
gauge/gauge.mica
{
gauge - a library: built on its own, its surface is what 'exp' marks, and its body runs before the program's.
The constant 'FullScale', the type 'Reading' and the function 'Measure' are exported; 'Limited' is not, so no importer
can reach it. The library counts its readings in a unit variable, which its body sets up when the program starts and
its farewell reads when the program has ended.
}
library gauge;
imp
WriteLn : std;
const
exp FullScale = 100;
type
{ One reading of the gauge, with the scale it was taken on. }
exp Reading = record
level : int64;
scale : int64;
end;
var
taken : int64;
{ A value held inside the gauge's scale, private to the library. }
function Limited(amount : int64) : int64;
begin
Limited := amount;
if amount > FullScale then
Limited := FullScale;
end;
{ A reading of a value on the gauge's full scale, counted among the readings taken. }
exp function Measure(amount : int64) : Reading;
var
reading : Reading;
begin
taken := taken + 1;
reading.level := Limited(amount);
reading.scale := FullScale;
Measure := reading;
end;
{ The farewell the library says once the program has ended. }
procedure Farewell();
begin
WriteLn("gauge: %lld readings taken", taken);
end;
begin
taken := 0;
WriteLn("gauge: ready");
defer until program Farewell();
end.gauge/mica.project
{
"version": "1.0.0",
"sources": [
"gauge.mica"
]
}meter/Meter.mica
{
Meter - a program built against the contract the gauge library's build emitted, which its project file names.
}
program Meter;
imp
WriteLn : std;
FullScale, Reading, Measure : gauge;
var
reading : Reading;
begin
WriteLn("meter: start");
reading := Measure(42);
WriteLn("meter: %lld of %lld", reading.level, reading.scale);
reading := Measure(250);
WriteLn("meter: %lld of %d", reading.level, FullScale);
end.meter/mica.project
{
"version": "1.0.0",
"sources": [
"Meter.mica"
],
"contracts": [
"../gauge/build"
],
"requires": {
"gauge": "1.0"
}
}Built from the directory that holds the two, the library first:
mica --compile --link --output-modifier archive --emit-contract --project gauge --build gauge/build
mica --compile --link --project meter --build meter/build
meter/build/MeterAmong the lines the two builds print, the first names the contract it emitted and the second the library it was built against, each with its version:
Emitted interop contract 'gauge/build/mica.external' for library 'gauge' 1.0.0
Using library 'gauge' 1.0.0The program prints, the library’s body first and its farewell last:
gauge: ready
meter: start
meter: 42 of 100
meter: 100 of 100
gauge: 2 readings takenLimited stands in the library’s source without the mark, so a program that
imports it is refused: no declarations found in namespace 'gauge' for identifier: Limited — a library exports only its 'exp'-marked declarations, so a name that exists in its source may still be private (5021).
Object units
An object unit is compiled in the same build as the program or the library that
uses it, and imported by the unit’s name. Its variables live in the unit’s own
storage, and its body runs once when the program starts, before the program’s
body. Its surface is what its exp marks state, as a library’s is.
counter.mica
{
counter - an object unit: compiled in the same build as the program that imports it, by the unit's name.
The unit's variable lives in the unit's own storage. Its body runs once when the program starts, before the
program's own body, so it is where the unit sets its state up. The two routines marked 'exp' are the unit's whole
surface.
}
object counter;
imp
WriteLn : std;
const
StartCount = 10;
var
count : int64;
{ The count goes up by one. }
exp procedure Bump();
begin
count := count + 1;
end;
{ The count as it stands. }
exp function Current() : int64;
begin
Current := count;
end;
begin
count := StartCount;
WriteLn("counter starts at %lld", count);
end.Counting.mica
{
Counting - a program built together with the object unit 'counter', whose two routines it imports by the unit's name.
}
program Counting;
imp
WriteLn : std;
Bump, Current : counter;
begin
WriteLn("the program starts");
Bump();
Bump();
WriteLn("the count is %lld", Current());
end.Both sources are compiled in one build, --source counter.mica,Counting.mica, and
the program prints:
counter starts at 10
the program starts
the count is 12Imports
An import section names the names a unit takes from other units
(import-section).
Nothing reaches a unit unimported: WriteLn, Length and Ord arrive by imp
as Sqrt does. Each group of the section ends with the name of the unit it
imports from, in one of four forms
(import-group,
import-member):
| Written | Imports |
|---|---|
WriteLn, Length : std; | the listed names, each under its own name |
Root = Sqrt : math; | Sqrt under the name Root, which the unit uses in its place |
* : strings; | every name the unit exports; * stands alone in its group |
math; | no name: the namespace becomes addressable, so math.Pi names its member |
An import section may stand in a routine’s block as well as at the root, and its
names are then visible in that routine and the routines nested in it, as the
block’s own declarations are. A generic routine is the exception: imported inside
a routine, it is not found there, so Sqrt imported by Sqrt : math in a
routine’s section is answered at its call with identifier not found: Sqrt
(5018), and Sort of arrays likewise. Import a generic routine at the root.
{
Imports - the four forms an import group takes, and the qualified name a whole namespace gives.
'WriteLn, Length : std' imports two names by name. 'Root = Sqrt : math' imports a name under one of the program's
own choosing. '* : strings' imports every name the strings unit exports. 'math' alone imports no name at all: it
makes the namespace addressable, so 'math.Pi' reaches a member without importing it.
}
program Imports;
imp
WriteLn, Length : std;
Root = Sqrt : math;
MaxInt32 : limits;
* : strings;
math;
const
Word = "import";
begin
WriteLn("%lf", Root(2.0));
WriteLn("%lf", math.Pi);
WriteLn("%d", MaxInt32);
WriteLn("%ls %lld", UpperCase(Word), Length(Word));
end.It prints:
1.414214
3.141593
2147483647
IMPORT 6Where imported names come from
A unit imports from six kinds of source:
| Source | Namespaces | Where its surface is read |
|---|---|---|
| the language’s own units | std, limits, bits | inside the compiler |
| the standard library | arrays, files, flags, integers, linalg, maps, math, net, paths, process, random, regex, streams, strings, time, utf | inside the compiler |
| the C library, POSIX and Linux | cstd, posix, linux | inside the compiler |
| the Dragon backend | dragon | inside the compiler |
| the object units of the build | each object’s name | the build’s sources |
| libraries built on their own | each library’s name | the library’s contract, mica.external |
The code behind a surface joins the program’s link by itself: the standard
library’s archive, which the compiler finds beside itself and then under
/usr/lib/; the system’s own C library; and the Dragon backend’s archive, found
beside the compiler, beside the standard library and under /usr/lib/, in that
order.
A library’s contract is found in one of three ways: a mica.external beside the
sources, one file for all of them; the paths of --external-contract, which
replace it; or the contracts of the project file, each a contract or the
directory holding one. A path a build names that holds no contract is refused
rather than skipped.
The surfaces of the C library, POSIX, Linux and the Dragon backend travel inside the compiler, and a program imports from them by name as from any unit:
{
Surfaces - names imported from the C library, from POSIX and from Linux, as names from any unit are.
The three surfaces travel inside the compiler, each a namespace of its own: 'cstd' holds the C library's functions
under Mica names, 'posix' the POSIX calls and constants, and 'linux' the kernel's own. An import names what it uses
and nothing else arrives.
}
program Surfaces;
imp
WriteLn : std;
Atoi, IsDigit : cstd;
Htons : posix;
EPOLLIN : linux;
const
Digits = "1234";
Seven = 55;
begin
WriteLn("Atoi(%ls) is %d", Digits, Atoi(Digits));
if IsDigit(Seven) # 0 then
WriteLn("IsDigit(%d) holds", Seven);
WriteLn("Htons(258) is %u", Htons(258));
WriteLn("EPOLLIN is %u", EPOLLIN);
end.It prints:
Atoi(1234) is 1234
IsDigit(55) holds
Htons(258) is 513
EPOLLIN is 1The dragon namespace drives the backend that compiles Mica itself, and its
archive joins the link of a program that imports from it:
{
Engine - a program that opens a session with the Dragon backend, the engine that compiles Mica itself.
The 'dragon' namespace travels inside the compiler, and its archive links into the program by itself: the program
imports the four entries it calls, opens a context, makes an empty unit of intermediate code in it, has the backend
check the unit, and frees the context, which frees everything made in it.
}
program Engine;
imp
WriteLn : std;
ContextNew, UnitNew, Validate, ContextFree : dragon;
var
context : uint64;
unit : uint64;
findings : int32;
status : int32;
begin
status := ContextNew(address context);
WriteLn("context opened: %d", status);
status := UnitNew(context, address unit);
WriteLn("unit made: %d", status);
status := Validate(context, unit, address findings);
WriteLn("unit checked: %d, findings: %d", status, findings);
ContextFree(context);
end.It prints, the backend’s one finding being what an empty unit lacks, an entry point:
context opened: 0
unit made: 0
unit checked: 0, findings: 1How an imported name is found
An imported name joins the scope of the block whose section imports it, beside that block’s own declarations, and the block uses it as it uses its own. Three rules keep that one lookup unambiguous:
- A unit’s declaration and an imported name may not share a name: the
declaration is refused as already declared,
function identifier already declared: Sqrt(5019). - One name is imported once. A second import of it, from the same unit or from
another, is refused with 5415,
'Copy' is already imported from library 'strings'; give one of the two imports an alias. - An import reaches only what the unit it names exports. A name that unit imports from a third is not passed on, so each unit imports what it uses itself.
A qualified name, math.Pi, reaches the member of a namespace the unit imported
whole: a namespace not imported is refused with 5436, the qualified name 'math.Sqrt' needs its namespace imported first: 'imp math;' makes the whole namespace addressable, and importing the member by name is the unqualified road, a name the namespace does not export with 5437, and a generic routine,
which is imported by name, with 5438.
| The import | Code | The compiler’s words |
|---|---|---|
| a namespace no source holds | 5020 | namespace not found: stdio |
| a name the unit does not export | 5021 | no declarations found in namespace 'std' for identifier: Shout — a library exports only its 'exp'-marked declarations, so a name that exists in its source may still be private |
* from a unit that exports nothing | 5022 | no declarations found in namespace 'helpers' for wildcard identifier |
| the unit’s own name | 5023 | cannot self-import from the current namespace: Probe |
an alias for * | 2011 | aliases are not allowed for a wildcard identifier '*' |
| an alias that is a name the language declares | 5088 | imported identifier cannot use built-in reserved name: True |
the language’s mathematics from std | 5596 | 'Sqrt' is not exported by 'std', but 'math' exports it: write 'imp Sqrt : math;' — the language's mathematics lives in one namespace, and 'std' keeps the language core |
The project file
A mica.project beside the sources states the facts of a build that belong to
the source tree: its sources, the encoding they are written against, the
contracts of the libraries they import, the version of the program or the
library they are, and the versions of those libraries it accepts under
requires. --project names the file or the directory that holds it, and every
flag of the command line overrides the entry it names. A
program compiled from its sources with flags alone needs no project file; a
library needs one for its version. The file’s entries and their refusals are
taught whole in the project file.
Where chapter 3 begins
The constants, types, variables and routines a unit declares in its sections, what each of them may refer to and the scopes their names are looked up in are chapter 3, and what each type carries is the type system.