Skip to content

Program Structure

A Nilakan file is a sequence of top-level statements. Function definitions are collected before execution, but top-level code is still checked in source order.

Top-level code may contain:

def inc(x: num) -> num:
return x + 1
value: num = 1
items = [1, 2]
items[0] = 9
left, right = (1, 2)
inc(value)
print(inc(value))
if value > 0:
print("positive")

Top-level statements run from top to bottom. Function definitions register the function name and do not execute the body.

Functions can call functions defined later in the file:

def first(x: num) -> num:
return second(x)
def second(x: num) -> num:
return x + 1

Top-level calls can only call functions that have already appeared earlier in source order.

Function call statements are allowed when the return value is not needed. Effectful built-ins and user procedures must use this statement form.

def observe(x: num) -> num:
return x
observe(1)

Function calls can pass positional arguments, named arguments, and defaulted parameters. Function parameter defaults are evaluated in the function’s own scope when the call runs, after earlier parameters have been bound.

Functions use parameters and local variables. They cannot read top-level variables.

Intentional failure — NLK2001 top-level capture
x = 1
def bad() -> num:
return x

Outside commit-loop bodies, an if/elif/else branch may update a name that already exists in the surrounding scope. A new name first introduced inside a branch is branch-local and does not become available after the conditional—even if every branch happens to introduce the same spelling. Initialize the name before the conditional when later code needs it:

flag = True
label = ""
if flag:
label = "yes"
else:
label = "no"
print(label)

This version is invalid because label was first introduced inside a branch:

Intentional failure — NLK2001 branch-local name
flag = True
if flag:
label = "yes"
print(label)

Commit-loop bodies use round-local temporary bindings. Assignments from a selected branch can be used later in that round; a read fails if its executed path has not initialized the value. See Commit Loops.

Function calls also start with their own prime-depth layer. A function called from inside a commit loop does not inherit the caller’s primed state reads.

These names cannot be used for values, functions, or parameters:

| Kind | Names | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | Built-ins | print, str, bool, num, len, ord, chr, list, tuple, set, dict, err, is_err, err_name, err_message, err_code |

string is not reserved.

Top-level code does not support return, primed assignment, standalone while, or standalone init.

Function restrictions:

  • function definitions are only allowed at top level
  • duplicate function names are rejected
  • duplicate parameters are rejected
  • direct and mutual recursion are allowed, but every call is bounded by the runtime call-depth limit
  • every function must end with a final return
  • return inside if branches is not supported
  • top-level values cannot use a function name, regardless of declaration order
def pair(x: num) -> tuple[num, num]:
return x, x + 1
left, right = pair(2)
print(left)
print(right)