5. Statements
A Korrin program is a sequence of statements, executed top to bottom. Each
statement occupies one logical line (§1.2), except the compound statements
(if, while, for, fn, class), whose header is one line and whose body is
an indented block.
5.1 Expression statements
Any expression on its own line is a statement; its value is discarded. The common case is a call:
print("hello")
# => hello
5.2 Assignment
assignment = assignable , "=" , expression ;
assignable = NAME | attribute | index ;
target = value evaluates value, then stores it:
name = value— binds per the nearest-binding rule (ADR 0011): ifnamealready exists in this or an enclosing scope, that binding is updated; otherwise a new binding is created in the current scope.object.field = value— sets a field on an instance.object[key] = value— sets a list element (index must be in range) or a map entry (creating it if absent).
There is no compound assignment (+= etc.) and no multiple assignment
(a, b = ...) in Milestone 1. Assigning to anything else — a literal, a call, an
operator expression — is E0104.
counter = 0
counter = counter + 1
print(counter)
# => 1
5.3 if / elif / else
if_statement =
"if" , expression , block ,
{ "elif" , expression , block } ,
[ "else" , block ] ;
Conditions are tested in order. The first truthy one's block runs; if none is
truthy and an else is present, its block runs. Truthiness:
only nil and false are falsy
(ADR 0009).
n = 2
if n == 1:
print("one")
elif n == 2:
print("two")
else:
print("many")
# => two
5.4 while
while_statement = "while" , expression , block ;
The condition is tested before each iteration; the block runs while it is truthy.
i = 0
while i < 3:
print(i)
i = i + 1
# => 0
# => 1
# => 2
5.5 for
for_statement = "for" , NAME , "in" , expression , block ;
NAME is bound to each element of the iterable in turn. Iterables in
Milestone 1: lists (elements), strings (characters as one-character strings),
maps (keys), and the ranges produced by the range builtin. A malformed header
is E0106.
total = 0
for x in [10, 20, 30]:
total = total + x
print(total)
# => 60
5.6 break and continue
break exits the innermost enclosing loop; continue skips to its next
iteration. Outside any loop they are E0202 / E0203,
reported by the resolver before the program runs.
5.7 pass
The do-nothing statement. Its only purpose is to be the body of a block that would otherwise be empty (ADR 0001):
fn todo():
pass
5.8 return
Valid only inside a function body (E0201 otherwise). return
with an expression yields that value; bare return yields nil. Reaching the
end of a function body without return also yields nil. See
§6.
5.9 fn and class
fn defines a function (§6) and class defines a class (§7). Both are
statements: they bind a name in the current scope when executed.
5.10 try / except / finally
try_statement =
"try" , block ,
[ "except" , [ NAME ] , block ] ,
[ "finally" , block ] ;
At least one of except and finally must be present; a bare try is
E0107. A except or finally with no preceding try is the
same error.
The try body runs. Then:
- If it raised (via
raise, §5.11, or a built-in fault) and there is anexcept, the handler runs.except NAME:binds the raised value toNAMEfor the handler body;except:catches without binding. There is exactly oneexceptpertry— it catches every exception. A caught built-in fault is bound as anErrorvalue with.messageand.code(§9). - If it raised and there is no
except, the exception keeps propagating afterfinallyruns. finally, if present, runs on every exit path: normal completion, a caught exception, an uncaught one passing through, and areturn/break/continueleaving thetry. If thefinallyblock itself raises or transfers control, that outcome replaces whatever was pending — so areturninsidefinallysuppresses a propagating exception. Usefinallyfor cleanup, not control flow.
fn classify(text):
try:
n = int(text)
return "number"
except:
return "not a number"
finally:
print("classified {text}")
print(classify("42"))
print(classify("x"))
# => classified 42
# => number
# => classified x
# => not a number
try:
total = 10 / 0
except err:
print("{err.code}: caught")
# => E0305: caught
5.11 raise
raise_statement = "raise" , expression ;
Evaluates the expression and raises it as an exception, unwinding until a try
with an except catches it (§5.10) or it reaches the top level, where it is
E0312. Any value may be raised; Error("message") is the
conventional choice. There is no bare raise — to re-raise in a handler, name
the bound value: raise err.
fn checked_sqrt(x):
if x < 0:
raise Error("cannot take the square root of {x}")
return x
try:
print(checked_sqrt(-4))
except e:
print(e.message)
# => cannot take the square root of -4