This chapter describes the declarations a block holds (chapter 2): constants, types, variables, procedures and functions, the routines nested in them, what each declaration may refer to, and the scopes every name is looked up in. 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 each type is, which values it holds and how a value converts from one type to another is 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.

Constants

A constant section names values (constant-section, constant-entry). A constant’s value is computed as the program compiles, from literals, other constants, operators and the functions of std the compiler computes, among them Ord, Chr, Pred, Succ, Low, High, SizeOf and Length of a constant string. Every build computes it alike, whatever its optimization level.

A constant written without a type has none of its own: it keeps its exact value until a use gives it a type, as the type system describes. Its arithmetic is exact, wider than any machine integer where a step needs it: Huge = 100000000000000000000; divided by ten is 10000000000000000000, which a uint64 holds. A use that names no type takes the first of int32, int64 and uint64 that holds the value, and a real without a type computes in float64.

A constant declared with a type, Tray : int16 = 250;, is held to that type: its value lies in the type, and an operation with a typed operand computes in that type. The type is a scalar, a string or an array of one dimension of scalars or strings, and a table lists the array’s elements in parentheses, Loads : LoadTable = (5, 10, 25);.

A constant reads every constant of its block and of the blocks around it, above or below it, and chapter 2 quotes the refusal of constants that read each other. A routine declares constants as the program does, and inside the routine its constant hides the program’s constant of the same name.

{
    Shelves - the constants of a block: numbers, reals, characters and strings, constants that read other constants, a
    constant declared with a type and a table of them, each computed as the program compiles.

    A constant without a type keeps its exact value until a use gives it a type: 'Slots' is small enough to print as an
    'int32', 'Far' is too large for one and computes as an 'int64', and 'Share', a real without a type, computes in
    'float64'. A constant declared with a type is held to that type, and a table lists its elements in parentheses. A
    constant may read every constant its block declares, above or below it, and the functions the compiler computes as
    it compiles: 'Length' of a constant string, 'Ord' of a character and 'High' of a type.
}
program Shelves;

imp
    WriteLn, Length, Ord, High : std;

const
    Slots = Rows * Columns;
    Rows = 3;
    Columns = Rows + 1;
    Far = 5000000000;
    Share = 1.0 / Slots;
    Label = "shelf";
    LabelLength = Length(Label);
    Initial = 'S';
    InitialCode = Ord(Initial);
    Largest = High(int8);
    Tray : int16 = 250;
    Loads : LoadTable = (5, 10, 25);
    SlotLine = "%d slots in %d rows of %d";
    ShareLine = "%lld is far, and one slot is %lf of a shelf";
    LabelLine = "%ls has %lld characters and begins with %lc, code %lld";
    TypedLine = "a tray holds %lld, an int8 at most %lld, the second load is %lld";

type
    { The loads a shelf is rated for, the lightest first. }
    LoadTable = array[1..3] of int64;

begin
    WriteLn(SlotLine, Slots, Rows, Columns);
    WriteLn(ShareLine, Far, Share);
    WriteLn(LabelLine, Label, LabelLength, Initial, InitialCode);
    WriteLn(TypedLine, Tray as int64, Largest as int64, Loads[2]);
end.

It prints:

12 slots in 3 rows of 4
5000000000 is far, and one slot is 0.083333 of a shelf
shelf has 5 characters and begins with S, code 83
a tray holds 250, an int8 at most 127, the second load is 10

A constant’s value is refused where the compiler cannot compute it, or where it leaves its type:

WrittenCodeThe compiler’s words
a constant that reads a variable, Twice = count * 2;5657the constant 'Twice' reads 'count', a variable, which holds its value only while the program runs, while a constant's value is computed as the program compiles: write the value with literals, other constants and operators, or declare 'Twice' as a variable and assign the value to it
a constant that calls a function the program declares, Four = Twice(2);5643the constant 'Four' takes its value from a call of 'Twice', which runs only when the program runs, while a constant's value is computed as the program compiles: write the value with literals, other constants and operators, or declare 'Four' as a variable and assign the call's result to it
a value outside the constant’s type, Small : int8 = 300;5233constant value 300 is outside the domain -128..127 of 'int8'
an operation whose result leaves its typed operand’s type, Sum = Small + Small;5421this constant expression computes 200, which does not fit the 'int8' it computes in: a typed operand holds the operation to its type, and the value would wrap there silently. Give the operation the width its result needs — write an operand 'as int32', or declare the constant with that type or with none — so the expression computes where its value fits
a division by a constant zero, Broken = 10 / Zero;5530the divisor of this division is the constant zero, so the operation cannot produce a value: the program is refused here rather than at run time, and at every optimization level alike
a typed constant of a record type, Origin : Point = (x: 1.5, y: 2.5);5355typed constant 'Origin' requires a scalar type, a string, or a one-dimensional array of integral, unicode, floating-point, bool, enumeration, or string elements: Point

A constant is never assigned: Limit := 11; is refused with analyzer error 5099, 'Limit' is a constant, whose value is fixed as the program compiles, so it cannot be assigned: declare a variable for a value the program changes, and so is an assignment to a member of an enumeration. A constant no statement reads draws warning 5106, constant declared but not used: Huge, and the program still builds.

Type declarations

A type section names types (type-section, type-declaration): each declaration gives the type on the right of = the name on its left, and the block uses the type by that name.

A declaration whose right side is the name of a type gives that type a second name. Litres = float64; makes Litres and float64 one type, so a value of either is a value of both, and Spot = Point; does the same for a record. A record declaration makes a new type, so two records with the same fields are two types, and a value of one is refused where the other is expected.

An enumeration declares its members as constants of the block it stands in, so a member’s name is taken in that block as any declaration’s is: a variable, or a member of a second enumeration, of the same name is refused as already declared.

A declaration may name a type or a constant declared below it: a pointer type the record it points at, a field a record declared further down, a variable its type and an array type its bound. A record reaches another record of its own type through a pointer, as Bed does through next below.

{
    Garden - the types a block declares: a second name for a type, an enumeration, a subrange, a set, a pointer, a
    record and an array, each declared once and used by its name.

    'Litres' is a second name for 'float64', so a value of either is a value of both. 'Plant' is an enumeration, whose
    members are constants of the block, 'Week' a subrange of the integers and 'Plants' a set of the enumeration's
    members. 'BedLink' points at 'Bed', a record declared below it, and 'Beds' is an array of such records. The chain
    the body builds through 'BedLink' is made of cells that live until the program ends.
}
program Garden;

imp
    WriteLn, Ord : std;

const
    BedCount = 2;
    Rainfall = 2.5;
    TomatoWeek = 3;
    BeanWeek = 5;
    BedLine = "bed %lld grows plant %lld, sown in week %lld, with %lf litres";
    SetLine = "tomato planted %lld, lettuce planted %lld";
    ChainLine = "the chain holds %lld beds";

type
    { A volume of water, a second name for 'float64'. }
    Litres = float64;

    { What a bed grows. }
    Plant = (Bean, Lettuce, Tomato);

    { The weeks of a season, counted from one. }
    Week = 1..26;

    { The plants a gardener has set. }
    Plants = set of Plant;

    { A pointer to a bed, declared above the record it points at. }
    BedLink = pointer Bed;

    { One bed: what it grows, the week it was sown, its water and the next bed of a chain. }
    Bed = record
        crop : Plant;
        sown : Week;
        water : Litres;
        next : BedLink;
    end;

    { The beds of the garden, counted from one. }
    Beds = array[1..BedCount] of Bed;

var
    beds : Beds;
    planted : Plants;
    rain : float64;
    first : BedLink;
    walk : BedLink;
    index : int64;
    count : int64;

begin
    rain := Rainfall;
    beds[1].crop := Tomato;
    beds[1].sown := TomatoWeek;
    beds[1].water := rain;
    beds[2].crop := Bean;
    beds[2].sown := BeanWeek;
    beds[2].water := beds[1].water * 2.0;
    planted := [Tomato, Bean];

    for index := 1 to BedCount do
        WriteLn(BedLine, index, Ord(beds[index].crop), beds[index].sown as int64, beds[index].water);

    WriteLn(SetLine, Ord(Tomato in planted), Ord(Lettuce in planted));

    first := new Bed until program;
    first.next := new Bed until program;
    walk := first;
    while walk # nil do
    begin
        count := count + 1;
        walk := walk.next;
    end;
    WriteLn(ChainLine, count);
end.

It prints:

bed 1 grows plant 2, sown in week 3, with 2.500000 litres
bed 2 grows plant 0, sown in week 5, with 5.000000 litres
tomato planted 1, lettuce planted 0
the chain holds 2 beds
WrittenCodeThe compiler’s words
a type no declaration names, x : Widget;5075variable data type not found: Widget
a record with a field of its own type, next : Node; in Node5686the record 'Node' cannot hold a field 'next' of its own type: a record that contains itself would never end, while a field can point at a record of its own type, written 'next : pointer Node'
two names that name each other, A = B; and B = A;5685'A' and 'B' are defined through each other, so none of them has a definition: define one of them by a type that names none of the others
a record assigned to a variable of another record with the same fields5143incompatible data types in assignment to o: Point to Other

A type no declaration or statement uses draws warning 5111, data type declared but not used: Size. A type declared with type parameters behind of, Box of T, is generic over parameters a gen section declares, as generics teaches.

Variables

A variable section declares variables, one name or several of one type to a line (variable-section, variable-group). A program’s variables live as long as the program, and a routine’s for one call of it, each call with its own. Every variable starts at its type’s start value, as chapter 2 states type by type.

A variable declared alone on its line may carry an initial value, balance : int64 := OpeningBalance;, which it takes where its declaration stands. A program’s variables take theirs before its body runs, so their initial values are constant expressions. A routine’s take theirs at every call, so an initial value there may read the routine’s parameters and the variables declared above it.

A compound statement declares variables of its own with var where a statement may stand (inline-variable-section). They belong to the compound: every statement of it sees them, those above the var as well, no statement after its end does, and they start afresh at every entry of the compound, so a loop’s body finds its variable at the start value in every round. Their initial values may read whatever the statements around them read.

A variable declared absolute names a device register at a fixed address, status : uint32 absolute 0x2000_0050;. The address is a constant expression, and the device gives the variable its value, so it takes no initial value. A register of a subrange or an enumeration type is checked at every read: a state the device reserves outside the type stops the program with ordinal_out_of_domain at the read.

A cell that new makes lives as long as the owner its until names: new Kept until program makes a cell that lives until the program ends, beyond the call that made it. A cell without an owner is released, or returned, on every path that leaves the routine that made it. Heap, end to end teaches cells and their owners.

{
    Ledger - the variables of a program, of a routine and of a compound statement, their initial values and how long
    each of them lives.

    The program's variables live as long as the program, and the initial value of 'balance' is a constant expression
    the program starts with. A routine's variables live for one call, and the initial value of 'twice' reads the
    parameter of the call it starts in. The loop's body declares 'counted' for itself, so the variable starts afresh at
    every round. 'Keep' makes a cell bound to the program with 'until program', so the cell outlives the call that made
    it and the body reads it afterwards.
}
program Ledger;

imp
    WriteLn : std;

const
    OpeningBalance = 100;
    FirstAmount = 20;
    SecondAmount = 30;
    EntryLine = "entry %lld books %lld, twice that is %lld, the balance is %lld";
    RoundLine = "round %lld counted %lld";
    KeptLine = "the kept balance is %lld";
    Rounds = 3;

type
    { A balance kept beyond the call that records it. }
    Kept = record
        amount : int64;
    end;

    { A pointer to a kept balance. }
    KeptLink = pointer Kept;

var
    balance : int64 := OpeningBalance;
    first, second : int64;
    kept : KeptLink;
    round : int64;

{ One entry booked: the local 'twice' starts from the parameter at every call. }
procedure Book(entry : int64, amount : int64);
var
    twice : int64 := amount * 2;
begin
    balance := balance + amount;
    WriteLn(EntryLine, entry, amount, twice, balance);
end;

{ A balance kept in a cell that lives until the program ends, beyond this call. }
procedure Keep(amount : int64);
begin
    kept := new Kept until program;
    kept.amount := amount;
end;

begin
    first := FirstAmount;
    second := SecondAmount;
    Book(1, first);
    Book(2, second);

    for round := 1 to Rounds do
    begin
        var counted : int64;
        counted := counted + 1;
        WriteLn(RoundLine, round, counted);
    end;

    Keep(balance);
    WriteLn(KeptLine, kept.amount);
end.

It prints:

entry 1 books 20, twice that is 40, the balance is 120
entry 2 books 30, twice that is 60, the balance is 150
round 1 counted 1
round 2 counted 1
round 3 counted 1
the kept balance is 150
WrittenCodeThe compiler’s words
an initial value for a group, a, b : int64 := 5;2046an initializer names one variable: declare 'name : type := expression' on its own line instead of inside a comma group
a program’s variable whose initial value reads a variable, b : int64 := a + 1;5231the initializer of program-level variable 'b' must be a constant expression: program-level state is materialized before the body runs, so there is no earlier moment for run-time code
a routine’s variable whose initial value reads a variable declared below it, a : int64 := b + 1;5232the initializer of 'a' reads 'b' before that variable has received a value: an initializer may only read variables declared before its own
a compound’s variable read after the compound’s end5018identifier not found: inner
an absolute variable with an initial value, port : uint32 absolute 0x1000 := 5;2050an absolute variable cannot carry an initializer: the device at the pinned address owns the value, so there is nothing for the program to preset
an absolute address read from a variable, status : uint32 absolute where;5286the absolute address of variable 'status' must be a constant expression: the address is compiled into every access, so there is no moment to compute it
until naming a routine that does not enclose the statement5195'until' must name a lexically enclosing function, but 'Other' does not enclose this site
a cell that a path leaves unreleased, first := new Node;13102heap cell is not released on every path: last use at line 15, the path leaving at line 16 releases nothing — dispose it, defer the dispose, return it, or bind it to an owner with 'until'

A variable no statement uses draws warning 5107, variable declared but not used: idle.

Procedures and functions

A routine is its heading, a semicolon, its block and a semicolon (routine-declaration), and its heading says which of five kinds it is (routine-heading): a procedure answers nothing, a function answers a value of its result type, and a task, a generator and a callback are taught on pages of their own, tasks, generators and callbacks, and declared the same way.

The parameters stand in parentheses, and the parentheses stand even where there are none, in the heading and at every call (formal-parameters):

WrittenCodeThe compiler’s words
a heading without parentheses, procedure Hello;2029expected left parenthesis, found semicolon
a call without parentheses, Hello;2091'Hello' cannot stand alone as a statement: a call is written with its parentheses, 'Hello()' where it passes no argument, and an assignment as 'Hello :=' and a value
a call with one argument too many, Show(1, 2);5178parameter and argument count mismatch in call to Show: expected 1, got 2

A parameter is handed over in one of three ways (parameter-group, parameter-name):

DeclaredThe routine receives
number : int64its own copy of the argument, which it may change while the caller’s variable keeps its value
const last : int64the argument to read, never to assign
counter : pointer int64an address, which the caller hands over with address count and the routine reads and writes through with value counter

An assignment to a const parameter is refused with analyzer error 5146: the parameter 'count' is declared 'const', so the routine cannot assign it: drop 'const' to give the routine a copy of its own to change, or assign the value to a variable of the routine. A conformant view, a : array[lo..hi : int64] of int64 or a : span of int64, is read-only without the word, and the same refusal names it as a view the routine reads in place and names the pointer view that writes.

Inside a function, its name is a variable of the result type: it starts at the type’s start value, the function reads and writes it as any variable, and the function answers the value it holds when the function ends. A parameter may not take the function’s name: function Area(Area : int64) : int64; is refused with 5019, parameter identifier already declared: Area. Outside the function the name only calls it: Five := 4; is refused with 5103, 'Five' names a function here and cannot stand as a value: a routine is called with its argument list, never read by name — if a variable of this name lives in an outer scope, this declaration shadows it, so rename whichever one you did not mean.

leave ends the routine where it stands, and leave when ends it where its condition holds; a function then answers the value its name holds.

Every routine of a block sees every routine of that block, above or below it, so routines call each other in any order: a routine calls one declared below it, two routines call each other, and a routine calls itself.

{
    Counters - procedures and functions, the three ways a parameter is handed over, a function's result and 'leave'.

    'Report' calls the routines declared below it, which a block allows: every routine sees every routine its block
    declares. A value parameter is the routine's own copy of the argument, so 'Double' changes its copy and the caller's
    variable keeps its value. A 'const' parameter is read and never written. A parameter of a pointer type receives an
    address, 'address count' at the call, and 'Bump' writes the caller's variable through it with 'value'. A function
    answers the value last assigned to its name, which starts at its type's start value and reads like a variable inside
    the function, and 'leave' ends a routine where it stands. 'Factorial' calls itself.
}
program Counters;

imp
    WriteLn : std;

const
    CopyLine = "Double answers %lld, and the caller's count is still %lld";
    BumpLine = "Bump wrote %lld through the address";
    SumLine = "the numbers 1 to %lld add up to %lld";
    SquareLine = "the first square past %lld is %lld";
    FactorialLine = "the factorial of %lld is %lld";
    StartCount = 5;
    SquareLimit : int64 = 50;

var
    count : int64;

{ The report of what each routine below answers, which calls them above their declarations. }
procedure Report();
begin
    count := StartCount;
    WriteLn(CopyLine, Double(count), count);
    Bump(address count);
    WriteLn(BumpLine, count);
    WriteLn(SumLine, count, Sum(count));
    WriteLn(SquareLine, SquareLimit, FirstSquarePast(SquareLimit));
    WriteLn(FactorialLine, count, Factorial(count));
end;

{ Twice a number, computed in the routine's own copy of the argument. }
function Double(number : int64) : int64;
begin
    number := number * 2;
    Double := number;
end;

{ One added to the caller's variable, which the parameter points at. }
procedure Bump(counter : pointer int64);
begin
    value counter := value counter + 1;
end;

{ The sum of the numbers from 1 to 'last': the result starts at zero and adds every number to itself. }
function Sum(const last : int64) : int64;
var
    index : int64;
begin
    for index := 1 to last do
        Sum := Sum + index;
end;

{ The first square above 'limit', answered by 'leave' as soon as it is found. }
function FirstSquarePast(const limit : int64) : int64;
var
    root : int64;
begin
    for root := 1 to limit do
    begin
        FirstSquarePast := root * root;
        leave when FirstSquarePast > limit;
    end;
end;

{ The product of the numbers from 1 to 'number', computed by a function that calls itself. }
function Factorial(number : int64) : int64;
begin
    if number <= 1 then
        Factorial := 1
    else
        Factorial := number * Factorial(number - 1);
end;

begin
    Report();
end.

It prints:

Double answers 10, and the caller's count is still 5
Bump wrote 6 through the address
the numbers 1 to 6 add up to 21
the first square past 50 is 64
the factorial of 6 is 720

Defaults

A parameter may state a default behind its type: := and a constant expression of the parameter’s type. A call may then leave out that parameter together with every parameter behind it:

function Clip(x : float64, low : float64 := 0.0, high : float64 := 1.0) : float64;

Clip(x) passes 0.0 and 1.0, Clip(x, -1.0) passes 1.0, and Clip(x, -1.0, 2.0) passes what it writes. A call that names its values may leave out any parameter with a default, wherever it stands: Clip(x, high: 2.0) passes 0.0 for low, as chapter 7 describes. The default is computed once, where the heading stands, so a constant’s name in it means the constant the heading sees. A default stands on a parameter passed by value whose type a constant can have: an integer of any width, a float of any width, bool, unicode, an enumeration, a subrange, a string, or a one-dimensional array of such elements. An array’s default is a typed array constant, named or written as a list in place, and a call that leaves the parameter out reads that constant as a call that writes it does. A record has no constant, so it takes no default:

procedure Greet(text : string := "hello");
function Shade(c : Color := Blue) : int64;
function Total(table : PrimeTable := Primes) : int64;
procedure Speak(words : WordRow := ("one", "two", "three"));

A parameter without a default may stand behind one with a default. The values a call writes by position fill the parameters from the left, so a call by position writes a value for the defaulted parameter as well, and a call that names the values behind it leaves it to its default:

function Pick(a : int64 := 1, b : int64) : int64;

n := Pick(b: 2);        { a takes its default, 1 }
n := Pick(5, 2);        { by position the call writes both }

A call that gives a parameter without a default no value is refused by that parameter’s name, by position as by name: Pick(5) gives a its value and leaves out b.

A parameter whose type is a type parameter takes a default as well. Its constant has no type until a call binds one, so each call converts the constant to the type it binds, and a call that binds a type the constant cannot convert to is refused there. The default takes no part in binding the type: below, Scaled(whole) with an int64 binds T to int64 and passes 3 as an int64, and Scaled(1.5) passes 3.0.

function Scaled(value : T, factor : T := 3) : T;
gen
    T is numeric;

A library’s contract carries each default to the programs that import the routine, spelled as Mica writes it, a string with its escapes, an enumeration value by its name and an array as its list, and the C header the library emits shows it in a comment beside the parameter, since a C caller passes every argument. A call through a routine value passes every argument too: a default belongs to the routine’s declaration, not to its type.

WrittenCodeThe compiler’s words
a default that reads a variable5744the default of parameter 'factor' of 'Scaled' is not a constant expression: a default is a value the compiler computes, such as a literal or a constant's name, so write one there or pass the value at every call
a call that leaves out a parameter without a default, Pick(5)5780the call to 'Pick' leaves out parameter 'b', which has no default: write its value by position in its place, or by its name, 'b:' and the value
a default on a parameter of a record type5746parameter 'at' of 'Place' cannot take a default: the type of 'at' is Point, and a constant's type is a scalar, a string or a one-dimensional array of integral, unicode, floating-point, bool, enumeration or string elements
a list default on an array parameter that reads a variable5744the default of parameter 'loads' of 'Show' is not a constant expression: a default is a value the compiler computes, such as a literal or a constant's name, so write one there or pass the value at every call
a call that binds a type parameter to a type its default cannot convert to5746parameter 'other' of 'Same' cannot take a default: its constant, of type string, does not convert to float64, the type this call binds 'T' to

Nested routines

A routine’s block may declare routines of its own after its sections (block). A nested routine sees every name of the routines around it as it sees its own: their parameters, their variables and a function’s result, which it reads and writes, and the routines declared beside it, above or below. A nested routine is a name of the routine that declares it, so that routine’s statements call it and nothing outside does: Inner(); in the program’s body is refused with 5018, identifier not found: Inner. Routines nest at most sixteen deep, as chapter 2 states.

A generator nested in a routine reads that routine’s parameters and variables as it emits. A callback is called by a C library, which knows nothing of the routine around it, so a callback reads no variable of that routine:

WrittenCodeThe compiler’s words
a callback that reads a variable of the routine around it5513the callback 'Poke' reaches 'running' in an enclosing routine's frame: a callback's whole environment is its explicit context parameter, because the foreign caller carries no static link — move the state into the record the context parameter points at
a nested routine that takes the name of its routine’s variable5019variable identifier already declared: Inner
{
    Scores - routines nested in a routine, and what they see of the routines around them.

    'Grade' declares a variable and then two routines of its own. 'Add' reads and writes the parameter and the variable
    of 'Grade', and it calls 'Settle', declared below it, since routines declared side by side see each other. 'Settle'
    assigns the result of 'Grade'. 'Running' holds a generator, 'Sums', that reads the parameters of 'Running' as it
    emits. A nested routine is a name of the routine that declares it, so the program's body calls 'Grade' and
    'Running', never 'Add' or 'Sums'.
}
program Scores;

imp
    WriteLn : std;

const
    GradeLine = "scores %lld and %lld with a bonus of %lld grade %lld";
    SumLine = "running sum %lld";
    GradeStep = 10;
    FirstScore : int64 = 31;
    SecondScore : int64 = 44;
    Bonus : int64 = 3;
    Step : int64 = 5;
    Rounds : int64 = 3;

{ The grade of two scores and a bonus added to each: one grade for every ten points. }
function Grade(first : int64, second : int64, bonus : int64) : int64;
var
    total : int64;

    { The step that adds one score and the bonus to the total, then settles the grade the total has reached. }
    procedure Add(points : int64);
    begin
        total := total + points + bonus;
        Settle();
    end;

    { The answer of 'Grade', whose result is the total in whole steps. }
    procedure Settle();
    begin
        Grade := total / GradeStep;
    end;

begin
    Add(first);
    Add(second);
end;

{ The running sums of 'count' numbers that grow by 'step', printed as a generator nested here emits them. }
procedure Running(step : int64, count : int64);
var
    pulled : int64;

    { The running sums, emitted as the generator reads 'step' and 'count' from the routine around it. }
    generator Sums() : stream of int64;
    var
        index : int64;
        sum : int64;
    begin
        for index := 1 to count do
        begin
            sum := sum + step;
            emit sum;
        end;
    end;

begin
    for pulled in Sums() do
        WriteLn(SumLine, pulled);
end;

begin
    WriteLn(GradeLine, FirstScore, SecondScore, Bonus, Grade(FirstScore, SecondScore, Bonus));
    Running(Step, Rounds);
end.

It prints:

scores 31 and 44 with a bonus of 3 grade 8
running sum 5
running sum 10
running sum 15

What a declaration may refer to

A block’s declarations stand in its sections, and its routines after them, as chapter 2 states. Within that order a declaration reads the names it needs from above or below it, with one exception, the initial value of a routine’s variable:

A declarationMay refer to
a constantthe constants of its block and of the blocks around it, above or below it
a typethe types and constants of its block and of the blocks around it, above or below it: a pointer, a field and a bound name ones declared below
a variablea type declared above or below it
a program’s variable’s initial valuea constant expression, whose constants stand above or below it
a routine’s variable’s initial valuethe routine’s parameters, the variables declared above it, and constants
a routineevery routine of its block, above or below it, itself among them, and every name of the blocks around it
a statementevery name of its block and of the blocks around it, and the variables of the compound statements it stands in

Scopes

A name is looked up from the place that uses it outwards: the compound statements around the use, the innermost first, then the routine it stands in, then each routine around that one, and then the unit with the names it imports. The first declaration the lookup meets is the one the name means, so a declaration hides every declaration of its name further out, in the whole of its block. A routine’s variable, parameter, constant or nested routine may take the name of a declaration of the program, a compound’s variable the name of its routine’s variable or parameter, and a routine’s variable the name of an imported one.

A record’s fields are names of the record, reached through a value of it, so a field may share its name with a variable of the block, as the field level of Floor does below.

A block’s own declarations share one scope, and so do the variables of one compound statement. A second declaration of a name in one scope is refused with analyzer error 5019, whose words name the kind of the first declaration:

Declared in one scopeCodeThe compiler’s words
two variables, count : int64; twice5019variable identifier already declared: count
a parameter and a variable of its routine5019parameter identifier already declared: count
two procedures, procedure Show(); twice5019procedure identifier already declared: Show
a variable, then a procedure5019variable identifier already declared: Show
a type, then a variable5019data type identifier already declared: Point
a constant, then a type5019constant identifier already declared: Limit
a member of an enumeration, then a variable5019constant identifier already declared: Red

A variable may not take the name of a built-in type, in a routine as at the root: int64 : int32; is refused with 5091, variable identifier cannot use built-in reserved name: int64. The reserved words and the names the language declares in every unit are chapter 1’s.

{
    Nearest - every name means its nearest declaration, looked up from the place that uses it outwards.

    The program declares 'level' and 'Describe'. 'Inspect' declares a 'level' and a 'Describe' of its own, which hide the
    program's inside 'Inspect' and every routine nested in it. A compound statement declares a third 'level', which hides
    the routine's for the statements of the compound; after the compound the routine's 'level' is the nearest again. A
    record's field lives in its record, so 'level' names a field of 'Floor' beside the program's variable.
}
program Nearest;

imp
    WriteLn : std;

const
    InspectLine = "Inspect sees level %lld";
    CompoundLine = "the compound sees level %lld";
    NestedLine = "the Describe of Inspect sees level %lld";
    ProgramLine = "the Describe of the program sees level %lld";
    FieldLine = "the field level is %lld, the variable level %lld";
    ProgramLevel = 1;
    InspectLevel = 2;
    CompoundLevel = 3;
    FloorLevel = 4;

type
    { A floor of a building, whose field shares its name with the program's variable. }
    Floor = record
        level : int64;
    end;

var
    level : int64;
    floor : Floor;

{ The program's description, which sees the program's 'level'. }
procedure Describe();
begin
    WriteLn(ProgramLine, level);
end;

{ A routine whose own 'level' and 'Describe' hide the program's. }
procedure Inspect();
var
    level : int64;

    { The description of 'Inspect', which sees the 'level' of 'Inspect', the nearest one. }
    procedure Describe();
    begin
        WriteLn(NestedLine, level);
    end;

begin
    level := InspectLevel;
    WriteLn(InspectLine, level);
    begin
        var level : int64;
        level := CompoundLevel;
        WriteLn(CompoundLine, level);
    end;
    Describe();
end;

begin
    level := ProgramLevel;
    Inspect();
    Describe();
    floor.level := FloorLevel;
    WriteLn(FieldLine, floor.level, level);
end.

It prints:

Inspect sees level 2
the compound sees level 3
the Describe of Inspect sees level 2
the Describe of the program sees level 1
the field level is 4, the variable level 1

What the types mean

Every declaration of this chapter names a type or holds a value of one. What each type is, which values it holds and how a value converts from one type to another is the type system.

Elemental routines

A function declared elemental is written for a single value and applies to every element of a vector, a matrix or a tensor. Its heading names one element type for every parameter and for the result, and a call that hands it shaped values in place of single values answers a value of the same shape, each element the answer the routine gives for the elements at that position:

program Steps;

imp
    WriteLn : std;

const
    Line = "%lf %lf %lf";
    Half = 0.5;

var
    i : int64;
    v, w : vector[3] of float64;

{ The step of one value is its square plus itself, written once for any numeric type. }
elemental function Step(x : T) : T;
gen
    T is numeric;
begin
    Step := x * x + x;
end;

begin
    for i := 0 to 2 do
        v[i] := (i as float64) + Half;

    w := Step(v);
    WriteLn(Line, w[0], w[1], w[2]);
end.

The program prints 0.750000 3.750000 8.750000. The same routine applies to a matrix[2, 3] of float32 or a tensor[2, 2, 2] of float64, and a call with single values, Step(Half), stays an ordinary call. The math unit’s elementwise verbs are elemental routines too: Tanh, Exp, Relu, Hadamard and their kin each apply to a vector, a matrix or a tensor under one name. The rules:

  • An elemental routine is a function with at least one parameter, and every parameter and the result are of one element type: a type parameter of its gen block, or a single value such as float64. A procedure, a routine without a parameter, a parameter that is itself a vector, and a result of another type than the parameters are refused where the routine is declared.
  • Every argument of a call over shaped values is a vector, a matrix or a tensor of one shape and one element type, and a routine over a named type such as float64 takes elements of exactly that type. Arguments of two shapes, or a single value beside a matrix, are refused at the call.
  • The routine applies on the host to values of a static shape, and to vectors and matrices whose extents are known only at run time, such as a vector of float64 after Resize. Every vector or matrix argument of such a call holds the extents of the first: a call whose arguments differ in extent stops the program with a message naming the routine, before an element is read. A value placed on a GPU is refused at the call.
  • Over a tracked value of a tape window, the tape records the routine’s body operation by operation, each operation as the verb that applies it to the whole tensor: in Swish := x * Sigmoid(x) the sigmoid of the vector and the elementwise product of the two, so Backward and Gradient give the derivative at every element. A body whose path depends on the element, such as an if on it, and an operation with no such verb - a division, a comparison, a single value added to the element, or a call of a routine that is not elemental - are refused at the call, each by name.