This chapter describes how a Mica program computes a value: the operands an expression is made of, the operators that join them and how tightly each binds, the types each operator takes and gives, integer arithmetic in the width of its type and what the checked build traps, the comparisons, the boolean, set and string operators, conversions written with as, the expressions the compiler computes while it compiles, and calls. Each construct names its rule in the grammar, says what it means, and quotes what the compiler answers when a source breaks the rule. The types themselves, promotion and the conversions each pair of types admits are chapter 4, and the capabilities that decide which operator a type takes are the type system, which this chapter links rather than repeats.

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.

Operands

An operand is a factor (factor): one primary, the selectors behind it, and one optional prefix before it. A primary (primary) is one of these:

PrimaryWrittenRule
a nametotal, Budgetan identifier
a literal12, 2.5, 'A', "good", nilliteral, whose forms are chapter 1
a call of a functionLength(greeting)function-call
a value in parentheses(quantity + shipping)parenthesized-value
a composite value at once(x: 1.0, y: 2.0)parenthesized-value with a comma or a field name
a set[Mon..Fri], [Sat, Sun]set-constructor
an allocationnew Nodeallocation, taught in heap end to end

A selector (selector) picks a part of the value before it: .name a field, [i] an element, [i, j] an element by several indices and [low..high] a slice, and selectors follow one another, orders[i].price. The prefix is a sign, + or -, or one of the words address and value, which take a variable’s address and read through a pointer, as value and pointer teaches. A sign belongs to its factor, so it may follow an operator: price * -quantity is written as it reads. value reads through the pointer before the selectors apply, and a sign and address apply to the selected value.

The elements of a set constructor are values of the set’s element type, and low..high stands for every value from low to high: [Tue..Thu, Sat] over the days of the week holds four of them. Sets teaches them.

Operators and their precedence

Operators bind from the weakest to the strongest in five levels, and the operators of one level group from the left, so total - 4 - 2 is (total - 4) - 2:

LevelOperatorsRule
weakestor, xorexpression
andconjunction
=, #, <, <=, >, >=, in, is, and not and odd before an operandrelation, comparison-operator
ascast-expression
+, -simple-expression
strongest*, /, mod, shl, shr, ·, ⊙term, multiplying-operator

≠, ≤ and ≥ are the same operators as #, <= and >=, and × is the same operator as *. · is the dot product and ⊙ the elementwise product, which vectors and matrices teaches.

Three consequences read straight from the levels. A comparison binds more tightly than and and or, so total > 0 and total <= Budget needs no parentheses. not negates the whole comparison behind it, so not total > Budget is not (total > Budget), and odd tests the whole sum behind it. And shl and shr stand with the multiplying operators, so price + quantity shl 2 shifts quantity before the sum.

A comparison does not chain: a relation holds one comparison, so a < b < c is not an expression, and the program writes the two comparisons it means, a < b and b < c, which the levels already group. For a < b < c the compiler answers at the second < with parser error 2097: “a comparison does not chain, so ‘<’ cannot follow the comparison in front of it: a relation holds one comparison, and the comparisons bind more tightly than ‘and’, so the two comparisons the chain means are written ‘a < b and b < c’”.

{
    Invoice - how the operators of an expression bind. The multiplying operators, 'shl' among them, bind more tightly
    than the adding operators, the adding operators more tightly than 'as', 'as' more tightly than the comparisons, the
    comparisons more tightly than 'and', and 'and' more tightly than 'or'. Operators of one level group from the left,
    a sign belongs to the factor it stands before, and 'not' negates the whole comparison behind it.
}
program Invoice;

imp
    WriteLn, Ord : std;

const
    UnitPrice = 12;
    Quantity = 3;
    Shipping = 5;
    Discount = 4;
    Coupon = 2;
    Budget = 50;
    Places = 2;
    ItemCount = 7;
    GroupingLine = "%lld %lld";
    LeftLine = "%lld %lld";
    SignLine = "%lld %lld";
    ShiftLine = "%lld %lld";
    TruthLine = "%lld %lld %lld";
    CastLine = "%lld";

var
    price : int64;
    quantity : int64;
    shipping : int64;
    total : int64;
    items : int32;

begin
    price := UnitPrice;
    quantity := Quantity;
    shipping := Shipping;
    total := price * quantity + shipping;

    { the multiplying operators bind more tightly than the adding operators }
    WriteLn(GroupingLine, price * quantity + shipping, price * (quantity + shipping));

    { operators of one level group from the left }
    WriteLn(LeftLine, total - Discount - Coupon, total / quantity / Coupon);

    { a sign belongs to its factor, so it may follow an operator }
    WriteLn(SignLine, -price * quantity, price * -quantity);

    { 'shl' is a multiplying operator }
    WriteLn(ShiftLine, price + quantity shl Places, (price + quantity) shl Places);

    { comparisons bind more tightly than 'and', and 'and' more tightly than 'or' }
    WriteLn(TruthLine, Ord(total > 0 and total <= Budget), Ord(not total > Budget),
        Ord(quantity = Quantity or total > Budget and price = 0));

    { 'as' converts the whole sum before it }
    items := ItemCount;
    WriteLn(CastLine, items + 1 as int64);
end.

It prints:

41 96
35 6
-36 -36
24 60
1 1 1
8

The types of the operators

An operator takes operands of the types whose capability grants it, and two operands of different types meet in the type one of them promotes to, as chapter 4 states, where a constant without a type takes the type of the operand beside it:

OperatorsTakeGive
+, -, *, /two integers or two realsthe operands’ type
modtwo integersthe operands’ type
a sign, -xa signed integer or a realthe operand’s type
and, or, xor, nottwo bool values, or one for notbool
and, or, xor, nottwo integers of one type, or one for notthe bits combined in that type
shl, shran integer and a shift countthe operand’s type
oddan integerbool
=, #two values of one type that carries equalitybool
<, <=, >, >=two values of one ordered typebool
ina member and a set of its typebool
+, -, *two sets of one typethat set type
+two stringsstring

The bit words and, or, xor and not take two integers of one type and compute in that type’s own width, so the operands do not meet in a wider one.

WrittenCodeThe compiler’s words
two integers of different widths in and, a and small over an int64 and an int325117incompatible data types in logical operation 'and': int64 and int32
a truth value and an integer in and, flag and a5117incompatible data types in logical operation 'and': bool and int64
a real shifted, ratio shl 15117incompatible data types in logical operation 'shl': float64 and int32
a real negated with not, not ratio5118data type 'float64' cannot be used in logical operation 'not' (requires logical)
odd of a real, odd ratio5114data type 'float64' cannot be used in unary arithmetic operation 'odd' (requires integral)
a sign before a truth value, -flag5114data type 'bool' cannot be used in unary arithmetic operation 'negate' (requires negatable)
a sign before an unsigned integer, -level over a uint85114data type 'uint8' cannot be used in unary arithmetic operation 'negate' (requires negatable)
a string subtracted, greeting - "x"5113data type 'string' cannot be used in arithmetic operation 'subtraction' (requires numeric)

Integer arithmetic in its width

An integer operation computes in its operands’ type and gives a value of that type. Over two integers / drops the fraction toward zero, and mod gives the remainder with the sign of the dividend, so -7 / 2 is -3 and -7 mod 2 is -1. A shift moves the bits within the operand’s width, and the bits moved past it are gone: a uint8 shifted left by six keeps its low eight bits.

An add, a subtract or a multiply whose result leaves its type wraps around in the type’s width, so a uint8 at 250 plus 10 is 4. The checked build, --optimize debug,checked or O2,checked (optimization), plants a guard on each of them instead, and the program stops at the operation with a trap report. A division or mod by zero stops the program on every tier, checked or not:

WrittenWhere it trapsThe report’s reason
an int64 sum past its largest value, big + onethe checked buildreason=signed_arithmetic_overflow (2)
a uint8 sum past 255, level + 10the checked buildreason=unsigned_arithmetic_wrap (3)
an int64 that a uint8 cannot hold, n as uint8 with n at 300the checked buildreason=cast_out_of_range (4)
a division by a zero variable, a / devery tierreason=division_by_zero (1)

The report’s first line begins Mica runtime failure: and names the reason, the next names the file, the line and the column of the operation, and the third shows its source line, as reading a trap report explains. The traps list every trap and the tier it is guarded on.

{
    Odometer - integer arithmetic in the width of its type. A 'uint8' sum that passes 255 wraps when the build is not
    checked, and a constant beside a 'uint8' takes its type. Division drops the fraction toward zero and 'mod' takes
    the sign of the dividend. The bit words compute in the operand's own width, so 'not' flips eight bits of a 'uint8'
    and 'shl' moves bits out past the eighth. A constant expression is computed exactly while the program compiles.
}
program Odometer;

imp
    WriteLn, Ord : std;

const
    Rim = 250;
    Step = 10;
    Seven = 7;
    Two = 2;
    LowNibble = 0b0000_1111;
    Places = 2;
    FarPlaces = 6;
    FirstBit = 1;
    WidePlaces = 40;
    SecondsPerDay = 60 * 60 * 24;
    NanosecondsPerYear = SecondsPerDay * 365 * 1000000000;
    WrapLine = "%lld %lld";
    DivisionLine = "%lld %lld %lld %lld";
    BitLine = "%lld %lld %lld %lld";
    WideLine = "%lld %lld";
    ConstantLine = "%lld %lld";

var
    level : uint8;
    distance : int64;
    flags : uint8;
    wide : uint64;
    day : int64;

begin
    { the sum wraps in eight bits, and the constant one adds in 'uint8' }
    level := Rim;
    level := level + Step;
    WriteLn(WrapLine, level as int64, (level + 1) as int64);

    { division toward zero, the remainder with the sign of the dividend }
    distance := Seven;
    WriteLn(DivisionLine, -distance / Two, -distance mod Two, distance / -Two, distance mod -Two);

    { the bit words in the eight bits of 'uint8' }
    flags := LowNibble;
    WriteLn(BitLine, (not flags) as int64, (flags shl Places) as int64, (flags shl FarPlaces) as int64,
        (flags shr Places) as int64);

    { a shift in the sixty-four bits of 'uint64', and 'odd' }
    wide := (FirstBit as uint64) shl WidePlaces;
    WriteLn(WideLine, wide as int64, Ord(odd level));

    { constant expressions, computed exactly while the program compiles }
    day := SecondsPerDay;
    WriteLn(ConstantLine, day, NanosecondsPerYear);
end.

It prints:

4 5
-3 -1 -3 1
240 60 192 3
1099511627776 0
86400 31536000000000000

Where the compiler sees the fault in the source, it refuses the program rather than trapping:

WrittenCodeThe compiler’s words
a division by the constant zero, a / 05530the 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 shift past the width, a shl 64 over an int645234shift count 64 is outside 0..63, the bit positions of 'int64'
a constant sum a uint8 cannot hold, level := 200 + 100;5233constant value 300 is outside the domain 0..255 of 'uint8'

a mod 0 is refused with 5530 in the same words, with modulo in place of division.

Comparisons and the case test

= and # compare two values of one type that carries equality, and <, <=, > and >= two values of one ordered type: the integers, the reals, the ordinal types and strings. Every comparison answers a bool. An integer and a real do not meet in a comparison, since a float64 holds every int64 exactly only up to 2^53: the program writes the conversion, a as float64 = ratio, which as groups before the comparison. Two records or two arrays do not compare whole, as chapter 4 states.

is tests a value of a sum type for one of its cases, shape is Circle, which sum types teaches with the patterns a case test takes.

WrittenCodeThe compiler’s words
an integer compared with a real, a = ratio5599comparison operation 'equal': int64 to float64 is written, never implicit, because float64 holds every integer exactly only up to 2^53 and int64 reaches past it — convert the int64 value with 'as float64', which rounds once where the program asks for it
a case test of an integer, a is 35573'is' tests a sum value for one of its cases, and this operand's type is 'int64'

The boolean operators

and, or and xor take two bool values and not one. and evaluates its right operand only when the left one is true, and or only when the left one is false, since otherwise the left one decides the answer; a test that guards the operation behind it is therefore written in one condition, count # 0 and total / count > Two. xor always evaluates both operands, and not negates the relation behind it.

{
    Guard - the boolean operators. 'and' evaluates its right operand only when the left one is true, and 'or' only
    when the left one is false, so a test that a count is not zero guards the division behind it. 'xor' always
    evaluates both operands.
}
program Guard;

imp
    WriteLn, Ord : std;

const
    Total = 12;
    Readings = 4;
    Two = 2;
    LeftOfAnd = 1;
    RightOfAnd = 2;
    LeftOfOr = 3;
    RightOfOr = 4;
    LeftOfXor = 5;
    RightOfXor = 6;
    NoAverageLine = "no readings, so no average";
    AverageLine = "the average %lld is above two";
    AskedLine = "asked %lld";
    AnswerLine = "%lld";

var
    total : int64;
    count : int64;

{ An operand that says it was evaluated, then answers 'answer'. }
function Asked(tag : int64, answer : bool) : bool;
begin
    WriteLn(AskedLine, tag);
    Asked := answer;
end;

{ The average of 'count' readings that add up to 'total', reported only when it is above two. }
procedure Report();
begin
    if count # 0 and total / count > Two then
        WriteLn(AverageLine, total / count)
    else
        WriteLn(NoAverageLine);
end;

begin
    { with no readings the division is never evaluated }
    total := Total;
    count := 0;
    Report();
    count := Readings;
    Report();

    { which operands each operator evaluates }
    WriteLn(AnswerLine, Ord(Asked(LeftOfAnd, False) and Asked(RightOfAnd, True)));
    WriteLn(AnswerLine, Ord(Asked(LeftOfOr, True) or Asked(RightOfOr, False)));
    WriteLn(AnswerLine, Ord(Asked(LeftOfXor, True) xor Asked(RightOfXor, True)));
end.

It prints:

no readings, so no average
the average 3 is above two
asked 1
0
asked 3
1
asked 5
asked 6
0

Sets and strings

Over two sets of one type, + is the union, * the intersection and - the difference, and = and # compare two sets whole. in asks whether a member belongs to a set of its type and answers a bool; not (Sun in work) negates the question. A set is not ordered, so <= and its kin do not take sets.

Over two strings + joins the text, and = and # compare it whole. Text teaches the string types.

{
    Timetable - the operators of sets and strings. Over two sets of one type '+' is the union, '*' the intersection
    and '-' the difference, 'in' asks whether a member belongs, and '=' and '#' compare two sets whole. Over strings
    '+' joins the text, and '=' compares it.
}
program Timetable;

imp
    WriteLn, Ord, Length : std;

const
    Good = "good";
    Space = " ";
    Morning = "morning";
    Expected = "good morning";
    MemberLine = "%lld %lld %lld %lld";
    CompareLine = "%lld %lld %lld";
    TextLine = "%ls %lld %lld";

type
    { The days of the week, Monday first. }
    Day = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);

    { Any choice of days. }
    Days = set of Day;

var
    work : Days;
    weekend : Days;
    every : Days;
    middle : Days;
    rest : Days;
    greeting : string;

begin
    { union, intersection and difference }
    work := [Mon..Fri];
    weekend := [Sat, Sun];
    every := work + weekend;
    middle := work * [Tue..Thu, Sat];
    rest := work - [Mon];
    WriteLn(MemberLine, Ord(Sat in every), Ord(Sat in middle), Ord(Wed in middle), Ord(Mon in rest));
    WriteLn(CompareLine, Ord(rest = [Tue..Fri]), Ord(work # every), Ord(not (Sun in work)));

    { a string joined from three and compared whole }
    greeting := Good + Space + Morning;
    WriteLn(TextLine, greeting, Length(greeting), Ord(greeting = Expected));
end.

It prints:

1 0 1 0
1 1 1
good morning 12 1
WrittenCodeThe compiler’s words
an integer tested against a set of days, a in work5115incompatible data types in comparison operation 'in': int64 and Days
a set ordered, work <= [Mon, Tue]5116data type 'Days' cannot be used in comparison operation 'less equal' (requires ordered)
a set divided, work / [Mon]5112incompatible data types in arithmetic operation 'division': Days and set[0..6] of Day

Conversions with as

as converts the value before it to the type named after it (cast-expression). It stands between the adding operators and the comparisons, so it converts a whole sum, items + 1 as int64, and a converted value compares without parentheses, count as float64 > limit. A cast that is an operand of an arithmetic operator stands in parentheses, (level + 1) as int64 inside a larger sum, or (large as float64) / 2.0. Which conversions each pair of types admits, and what each does to the value, is chapter 4 with its refusals; a narrowing conversion that loses the value traps in the checked build, as the table of traps above shows.

Constant expressions

An expression of constants is computed while the program compiles, exactly and alike on every tier: SecondsPerDay * 365 * 1000000000 is the integer 31536000000000000 in the program, with no rounding and no wrap on the way. A constant takes its type where it is used, as the type system states, and a constant value its use cannot hold is refused with 5233, as the table under integer arithmetic shows. The same table shows the constant divisor of zero and the constant shift count past the width, which the compiler refuses rather than leaving to run time.

Calls

A call of a function is an operand (function-call): its value is the function’s result, and it stands wherever a value of the result’s type may. The arguments are separated by commas, and each is evaluated before the function is entered. Which operands and and or evaluate at all is the rule of the boolean operators above. How routines are declared and what their parameters take is chapter 3.