Operators
Every expression gives a Bag. Some operators work on whole Bags; others work on each combination of their Values. The same symbol can also have a different meaning in a Set, a contract, or a parser rule.
Numbers
Section titled “Numbers”| Form | Meaning |
|---|---|
-x |
Negate each number. |
x + y |
Add. |
x - y |
Subtract. |
x * y |
Multiply. |
x / y |
Divide, giving a float. |
x div y |
Divide integers, rounding down. |
x modulo y |
Remainder with the divisor’s sign. |
x rem y |
Remainder with the dividend’s sign. |
x band y |
Bitwise and of integers. |
x bor y |
Bitwise or of integers. |
x bxor y |
Bitwise exclusive or of integers. |
x shl n |
Shift an integer left by n bits: x times 2 to the n, rounded down. |
x shr n |
Shift an integer right by n bits, rounding down. |
These operators run for every combination of occurrences. Repeats count, and an empty operand gives an empty result:
{1, 2'2} + 10 // {11, 12'2}-7 div 2 // {-4}-7 modulo 2 // {1}-7 rem 2 // {-1}-8 band 13 // {8}16 shl -2 // {4}int holds integers of any size, and its arithmetic is exact and never overflows. band, bor, and bxor treat an integer as two’s complement with infinitely many sign bits. shl and shr take a count of any sign, and a negative count shifts the other way. div, the bit operators, and the shifts take only ints. The Set of an integer result comes from its operands’ Sets, as Sets of results describes, and wrap and saturate bring a result back into a narrower one.
f64 and f32 are 64-bit and 32-bit floating-point numbers, which round each result once. Float division by zero can give inf, -inf, or nan; it does not fail. nan sorts after inf, and nan == nan is true.
/ gives the float nearest the exact quotient, even of two ints, so 7 / 2 is 3.5 and 1 / 0 is inf. On ints, div, modulo, and rem need a divisor whose Set leaves out 0, which the checker proves before anything runs, as in Integer division.
An int and a float meet at +, -, *, modulo, and rem where the float holds every Value of the int’s Set, and the result is the float; elsewhere, f64::from or f32::from converts. f64 and f32 never mix.
Comparisons and Booleans
Section titled “Comparisons and Booleans”| Form | Meaning |
|---|---|
x == y |
Equal. |
x != y |
Not equal. |
x < y |
Less than. |
x <= y |
Less than or equal. |
x > y |
Greater than. |
x >= y |
Greater than or equal. |
x min y |
The lesser Value. |
x max y |
The greater Value. |
x and y |
Both Booleans are true. |
x xor y |
Exactly one Boolean is true. |
x or y |
At least one Boolean is true. |
Comparisons, min, and max take Values of the same shape: two numbers, two strings, or two Records with matching fields, for example. They compare one pair at a time, rather than comparing two whole Bags:
{1, 2} == {1, 2} // {false'2, true'2}{1, 5} min 3 // {1, 3}Strings sort by their Unicode scalar values, Booleans as false before true, and enum members in declaration order. Tuples compare their positions from the first; Records compare their fields in alphabetical order. The first unequal part decides.
not(x) negates each Boolean. It is a standard library function, as are min(x) and max(x), which reduce a whole Bag to at most one Value.
| Form | Meaning |
|---|---|
x | y |
Add the occurrence counts. |
x |% y |
Keep one occurrence of each Value in either Bag. |
x \+ y |
Subtract the right count from the left, down to zero. |
x \% y |
Keep one occurrence where the left count is greater than the right. |
x & y |
Multiply the counts of shared Values. |
x &% y |
Keep one occurrence of each shared Value. |
x% |
Keep one occurrence of each Value. |
x'n |
Repeat each occurrence n times. |
x ?? y |
Use x if it is nonempty, and otherwise use y. |
The two Bags must have the same shape. The count after ' is one whole, nonnegative number. Repeating zero times gives an empty Bag.
{1'3, 2} \+ {1, 2'2} // {1'2}{1'2, 2} & {1'3, 3} // {1'6}{1, 2}'2 // {1'2, 2'2}?? tests for presence, so false and 0 both keep the left operand. The right operand runs only when the left one is empty:
f64:{} ?? {7, 8} // {7, 8}See Bags for more examples.
Choices
Section titled “Choices”condition then yes else no chooses a Bag for each Boolean occurrence. Only the chosen branch runs for that occurrence. Both branches are required in an expression:
{true'2, false} then "yes" else "no" // {"no", "yes"'2}subject match {Set ~> result, ...} chooses the first arm whose Set holds each Value. ~> separates an arm’s Set from its result, and _ takes anything left. The arms must cover every possible input:
{1, 2, 3} match { 1 ~> "one" _ ~> "many"} // {"many"'2, "one"}Inside an arm, . is the Value it matched. match is also a pipeline operator.
| Form | Meaning |
|---|---|
key -> value |
Make a Map, the Record (k: key, v: value). |
key >> map |
Read the values of Maps whose keys match key. |
map << value |
Read the keys of Maps whose values match value. |
left_map -- right_map |
Join Maps where a left value matches a right key. |
>> requires a Bag of Maps on the right; << requires one on the left. The other operand is a Bag of Values with the same shape as the Maps’ keys or values, respectively. -- requires Bags of Maps on both sides, with the left values and right keys of the same shape.
-> takes any two Bags and makes one Map for every combination of their Values. Each matching pair contributes to a traversal or composition, so repeats multiply:
"pen"'2 >> {"pen" -> 3} // {3'2}{1 -> 2, 2 -> 3} -- {2 -> "a", 3 -> "b"} // {1 -> "a", 2 -> "b"}A missing match gives an empty Bag. See Maps for lookups in both directions.
Pipelines
Section titled “Pipelines”A pipeline passes a Bag through steps from left to right. The expression after an operator is its kernel.
| Form | The kernel receives | Result |
|---|---|---|
x map kernel |
Each Value. | All the kernel’s results. |
x if condition |
Each Value. | Values whose condition contains true. |
x agg kernel |
The whole Bag. | The kernel’s result. |
x to kernel |
Each Value. | Input -> result. |
x by kernel |
Each Value. | Result -> input. |
x mapv kernel |
Each Map’s value. | Key -> result. |
x ifv condition |
Each Map’s value. | Maps whose condition contains true. |
x aggv kernel |
The Bag of values for each distinct key. | Key -> result. |
x match {Set ~> result} |
Each Value. | The first matching arm’s result. |
x |> f(arguments) is not a kernel form: it calls f(x, arguments), with the whole Bag as the first argument, and binds no .. A pipeline inside a kernel takes parentheses, as in rows map (.text |> decode()).
. is the current Value, or the whole Bag in agg. In the v forms, . is the value or value Bag. . belongs to the innermost kernel.
name => names the kernel’s input instead. In a v form, key -> value => names the key and the value; _ leaves either one unnamed:
{"pen" -> 2, "book" -> 5} mapv name -> price => (name: name, price: price * 2)// {"book" -> (name: "book", price: 10), "pen" -> (name: "pen", price: 4)}See Pipeline operators and Grouping and summarizing.
Names, fields, and calls
Section titled “Names, fields, and calls”| Form | Meaning |
|---|---|
module::name |
Reach a declaration in a module or namespace. Role::property names the whole Bag of Maps from owners to property values. |
value.field |
Read a Record field, or a ref’s property. |
value.0 |
Read a Tuple position, starting at zero. |
function(arguments) |
Call a function or procedure. |
function[Sets](arguments) |
Choose a generic function’s Set arguments explicitly. |
bag |> function(arguments) |
Call a function with bag as its first argument. |
Set:member |
Write a member of a Set, such as an enum member. |
:Member |
Write an enum member where the context supplies the enum. |
Set:expression |
Check which Values an expression may hold. |
Field and position selection reads from every occurrence in a Bag. A Set can also select a field’s or position’s Set, as in Product.price.
{(name: "Ada"), (name: "Alan")}.name // {"Ada", "Alan"}After a Set’s colon, a bare name names a member. Put a named expression in parentheses to check it, as in f64:(amount). f64:{} supplies the Set of an empty Bag. This check keeps the expression’s own Set; it does not broaden it or convert its Values.
Sets and contracts
Section titled “Sets and contracts”| Form | Meaning |
|---|---|
A | B |
Unite Sets of the same shape. |
A & B |
Intersect Sets of the same shape. |
-A |
Negate the members of a numeric Set. |
A -> B |
The Set of Maps with keys in A and values in B. |
Set'cardinality |
Pair a Set with an allowed occurrence count. |
x as contract |
Give x a broader contract, keeping its Values. |
x expect contract |
Check x’s Values and count when it runs, keeping its Values. |
as requires the original contract to fit the new one. Either part of its target may be _ to keep the original part. expect narrows instead: its target’s Set is _ or a Set that shares a Value with x’s, and its cardinality is _, a count, a range, or a formula over a function’s parameters, such as _'(2 * xs). A Value outside the target’s Set or a count outside its range fails the evaluation, and the result holds the Values of both Sets. A target that holds all of x’s contract checks nothing.
{1, 2} as f64'* // {1, 2}"pen" >> {"pen" -> 2} expect _'1 // {2}After a declaration’s name, !, ?, +, and * are short markers for exactly one, zero or one, one or more, and any number of occurrences. name: Set'count writes a contract in full. Immediately after ', ! also means one.
See Sets and Cardinality and contracts.
Cardinality formulas
Section titled “Cardinality formulas”qnt names a cardinality. These operators combine ranges of occurrence counts:
| Form | Meaning |
|---|---|
low..high |
A range, including both ends; * is an unbounded end. |
low..<high |
A range that includes low and excludes high. |
A + B |
Add the lower bounds and the upper bounds. |
A * B |
Multiply the lower bounds and the upper bounds. |
A | B |
The smallest range containing both ranges. |
A min B |
The lesser of each bound. |
A max B |
The greater of each bound. |
A# |
Turn zero into zero, and each positive count into one. |
A% |
The range of counts after removing repeated Values. |
# describes whether a count is nonzero; it does not operate on a data Bag. min and max in cardinalities take ranges that do not depend on function parameters. A function’s result contract can use a value parameter’s name for its cardinality.
qnt small_count = 0..3qnt added_count = small_count + 1qnt present_count = small_count#def one_more(values* f64): f64'(values + 1) = {values, 0}one_more({1, 2}) // {0, 1, 2}Updates
Section titled “Updates”An update changes stored data or a mutable local. These are actions, rather than expressions:
| Form | Change |
|---|---|
x = value |
Replace x. |
x |= value |
Add every occurrence of value. |
x \= value |
Remove every occurrence of value; fail if any is missing. |
x %= value |
Replace x with value%. |
x |%= value |
Replace x with x |% value. |
x \+= value |
Replace x with x \+ value, ignoring missing occurrences. |
x \%= value |
Replace x with x \% value. |
x &= value |
Replace x with x & value. |
x &%= value |
Replace x with x &% value. |
x += value |
Replace x with x + value. |
x -= value |
Replace x with x - value. |
x *= value |
Replace x with x * value. |
x /= value |
Replace x with x / value. |
Roles and properties accept =, |=, and \=. A par block accepts those three forms too and makes its changes together. Other update operators stand outside the block.
var visits! f64 = 0visits += 1visits // {1}+=, -=, *=, and /= compute their result exactly, and the update checks it against x’s Set when it runs, as every update does. Over a var hits! u32, hits += 1 fails once hits is 4294967295, while hits = wrap[u32](hits + 1) wraps it to 0.
= also gives a declaration its expression, as in def doubled = visits * 2. See Changing data for updates and procedures.
Patterns and parser rules
Section titled “Patterns and parser rules”reg declares a regular pattern, and peg declares a grammar rule. Their operators describe what text matches, rather than computing Bags:
| Form | Meaning |
|---|---|
. |
Match any one character, or any one token when reading a lexer. |
"a".."z" |
Match one character in the range, including both ends. |
"a"..<"{" |
Match one character in the range, excluding its end. |
-pattern |
Match one character that the pattern does not match. |
(p, q) |
Match p followed by q. |
{p, q}, p | q |
Match either regular pattern, without ordering the alternatives. |
p'counts |
Repeat p a count allowed by counts. |
[p, q], p / q |
Choose a grammar alternative using a bounded look ahead, then commit to it. |
&p |
Check that p matches here, consuming nothing. |
!p |
Check that p does not match here, consuming nothing. |
name = p |
Capture the text matched by p; a later name matches that text. |
:token, Lexer:token |
Match a named token from a lexer. |
A regular repetition has no greed of its own; the function using the pattern decides which match it selects. A grammar rule commits to a match and does not backtrack. Choices must have distinguishable starts or leading lookaheads, so overlapping alternatives need an explicit check. Lookaheads must have bounded length.
reg lower_word = ("a".."z")'+re::full_match("saka", lower_word) // {true}rep names allowed repetition counts. ?, +, and * mean zero or one, one or more, and any number; 2..5 is a range, 2..<5 a range without its end, and 0 | 2..* unites counts. Unlike a cardinality range, a repetition set may have gaps. In a pattern type, as in reg word+ char, the short marker describes the number of characters or tokens consumed.
In a lexer-based rule, :eof checks the end, :gap and :no_gap check for trivia, and :same_line and :new_line check line breaks between tokens. These checks consume nothing. A | B can also unite lexer inputs in a lex declaration.
Precedence and spacing
Section titled “Precedence and spacing”Higher rows bind more tightly. Operators in one row group to the left unless stated otherwise. Parentheses choose a different grouping.
| Operators | Grouping |
|---|---|
:: |
Left. |
Calls f(...) and Set arguments f[...] |
Left. |
. |
Left. |
Postfix %, # |
Left. |
: |
Left. |
Prefix -, &, ! |
Right. |
.., ..< |
Left; an endpoint must be a number, so 0..<1..<2 is an error. |
' |
Left. |
*, /, div, modulo, rem, band, shl, shr |
Left. |
+, -, bor, bxor |
Left. |
min, max |
Left. |
==, !=, <, <=, >, >= |
Left. |
and |
Left. |
xor |
Left. |
or |
Left. |
&, &% |
Left. |
|, |%, \+, \% |
Left. |
?? |
Right. |
then … else |
Right. |
@ |
Right; name a whole input beside its binder. |
-> |
Right. |
-- |
Left. |
<< |
Right. |
>> |
Left. |
as, expect |
Left. The target is a contract, so x as f64'* + 1 is an error. |
=> |
Left; a kernel header must be a binder. |
map, if, agg, mapv, ifv, aggv, by, to, match, |> |
Left. A |> call ends at its ), and what follows continues the expression. |
~> |
Right. |
1 + 2 * 3 // {7}(1 + 2) * 3 // {9}(1 -> 2 -> 3).v.k // {2}Write spaces on both sides of binary operators, as in a + b. Prefix and postfix operators touch their operands: -x, x%, A#. Calls touch their arguments: sum(values).
., :, ::, ', .., and ..< have either no space on either side or space on both sides. A colon with space only after it introduces a field or contract, as in (price: 2) or price: f64'1. A line beginning or ending with a binary operator continues the expression across the line break.