4. Expressions
This section defines Korrin's expression forms and how they are grouped. Runtime behaviour of operators (arithmetic, comparison, truthiness) is in §3 and §10; this section is about syntax and structure.
4.1 Precedence and associativity
From loosest to tightest binding:
| Precedence | Operators | Form | Associativity |
|---|---|---|---|
| 1 | or | a or b | left |
| 2 | and | a and b | left |
| 3 | not | not a | prefix |
| 4 | == != < <= > >= | a == b | non-associative |
| 5 | + - | a + b | left |
| 6 | * / % | a * b | left |
| 7 | - | -a | prefix |
| 8 | f(...) a.b a[i] | postfix | left |
"Non-associative" means a comparison may not be chained: a < b < c is a syntax
error (E0101). Write (a < b) and (b < c).
print(2 + 3 * 4)
print(-(1 + 2) * 3)
# => 14
# => -9
There is no exponentiation operator; the full operator list is in §1.8.
4.2 Primary expressions
Literals
INT, FLOAT, STRING (§1.7), and the keyword literals true, false, nil.
String interpolation
A string literal with one or more { expression } holes (§1.7) is itself an
expression. Evaluating it evaluates each hole left to right, renders each
result with display semantics — the same form str() and print produce, so a
string is spliced as-is and not re-quoted — and concatenates the literal text
and the rendered holes into one str.
name = "Ada"
n = 3
print("{name} has {n} item{"s" }")
print("{n} + {n} = {n + n}")
# => Ada has 3 items
# => 3 + 3 = 6
A hole is a full expression, including calls, indexing, and further strings:
row = {"user": "grace", "score": 90}
print("{row["user"]}: {row["score"]}%")
# => grace: 90%
Names
A bare NAME refers to the nearest binding in scope
(ADR 0011). An unbound name is
E0301 when evaluated.
self
Inside a method, self is just the first parameter — an ordinary name
(ADR 0005). Outside a method it
is an unbound name like any other.
Parentheses
( expression ) groups without producing a distinct value or node; it only
overrides precedence.
List literals
[ e1, e2, ... ], with an optional trailing comma. [] is the empty list.
Elements are evaluated left to right.
Map literals
{ k1: v1, k2: v2, ... }, with an optional trailing comma. {} is the empty
map (there is no set type). Keys and values are arbitrary expressions, evaluated
in source order, key before value.
pair = {"x": 1 + 1, "y": [3, 4]}
print(pair["x"])
# => 2
4.3 Operator expressions
Unary
-a negates a number. not a produces true if a is falsy and false
otherwise (ADR 0009).
Binary arithmetic and comparison
+ - * / % and == != < <= > >=. Both operands are always evaluated. Types and
results are defined in §3; note / always yields a
float (ADR 0010).
and / or
These short-circuit and return one of their operands, not a coerced boolean:
a and bevaluatesa; ifais falsy it is the result, otherwisebis evaluated and is the result.a or bevaluatesa; ifais truthy it is the result, otherwiseb.
name = nil
print(name or "anonymous")
# => anonymous
4.4 Postfix expressions
Call — callee(args)
callee is evaluated, then each argument left to right, then the call happens.
callee must be a function, a builtin, or a class (§6,
§7); otherwise E0302. Argument count must
match the callable's arity (E0303). A trailing comma in the
argument list is allowed.
Attribute — object.name
Reads name from object:
The result of reading a method is a callable bound to object; it is almost
always called immediately (items.push(x)). An absent attribute is
E0309; reading a method name without calling it is allowed and
yields a function.
Index — object[index]
Reads from a list (integer index, E0307 if out of range),
a map (E0310 if the key is absent), or a string. Indexing
anything else is E0308.
super.method
Only valid inside a method whose class has a parent
(ADR 0005). super.method
resolves method starting from the parent class, bound to the current self.
It is essentially always immediately called: super.method(args).