Core syntax Control flow

Statements and Control Flow

Klyn uses indentation-defined blocks and a compact statement vocabulary. This page covers the control-flow statements you will use constantly in real code.

Assignment
count = 10
name as String = "Ada"
count += 1
count--

Assignment updates the nearest visible variable while preserving its established type. If no visible variable has that name, the assignment declares it in the current lexical block. Such a declaration is not visible after that block ends.

A nested scope cannot declare another local variable with the same name as a visible local or parameter. This includes explicit local declarations, loop declarations, catch variables and lambda parameters. Reusing a name in separate sibling scopes is allowed. Assignment to an existing variable remains legal: it updates that variable rather than declaring a second one.

Comprehensions reuse visible variables instead of declaring a second variable with the same name. Only iteration names that do not already exist are local to the comprehension. For example, after x = 100 and values = [x * 2 for x in [1, 2]], x is 2 and values is [2, 4].

If, Elif, Else
score = 72
if score >= 90:
    print("excellent")
elif score >= 50:
    print("pass")
else:
    print("retry")

Every branch owns a lexical scope. A variable declared before the conditional can be updated by any branch; a variable first declared in an if, elif, or else branch remains local to that branch.

Conditions are strictly typed as Boolean. Klyn does not apply truthiness to numbers, strings, collections, objects, or null; the same rule applies to while, assert, conditional expressions, Boolean operators, and comprehension filters.

score = 72
label = "unknown"

if score >= 50:
    label = "pass"
    congratulation = "well done"
else:
    label = "retry"

print(label)
# print(congratulation)  # TypeError: the name belongs to the if branch.
Match
value = 2
match value:
    case 0:
        print("zero")
    case 1 | 2:
        print("small")
    case 3 to 10:
        print("range")
    default:
        print("other")

match compares one subject against ordered cases. A case can use an exact value, several alternatives separated by |, or an inclusive interval with to. default handles the fallback branch.

NaN is not a valid match pattern

The compiler rejects case nan and case nanf with a SyntaxError. IEEE 754 defines every equality comparison involving NaN, including NaN with itself, as false, so these constant patterns could never match. Test a value with is nan or is nanf before the match instead.

text = "dominique@example.com"
match text:
    case /^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}$/:
        print("email")
    case /^[0-9]{2}-[0-9]{2}-[0-9]{4}$/:
        print("date")
    default:
        print("unknown")

Regex cases use the same literal syntax as regular expressions and test the subject with pattern matching.

command = "ls"
action = match command:
    case "ls" | "dir":
        "listing"
    case "ps":
        "processes"
    default:
        "unknown"

A match expression returns the expression produced by the first matching branch. Use this form when a value must remain available after the match. Variables first declared inside a case or default block remain local to that branch.

value = 1
result = 0

match value:
    case 0:
        result = 10
        explanation = "zero"
    case 1:
        result = 20
    default:
        result = 30

print(result)
# print(explanation)  # TypeError: explanation belongs to one case only.
While Loops
i = 0
while i < 3:
    print(i)
    i += 1

Use while when the continuation condition depends on values updated in the body. Variables declared before the loop can be updated by it; names first declared in the body are local to the body.

Klyn has no separate do or until loop form. When a body must execute before its termination test, use while true with an explicit conditional break.

Range Loops
for i = 0 to 5:
    print(i)

Both bounds are inclusive. The example prints 0 through 5. The binding i exists only in the loop body. When iterating over indices, use an explicit upper bound such as values.size - 1.

Collection Iteration
for value in [10, 20, 30]:
    print(value)

config = {"theme": "dark", "fullscreen": true}

for key in config:
    print(key)

for key, value in config:
    print(key + " = " + value)

records = [
    Tuple(1, "ready", true),
    Tuple(2, "waiting", false)
]
for identifier as Int, label as String, enabled as Boolean in records:
    print(f"{identifier}: {label} ({enabled})")

Iterating directly over a map yields keys. Use keys(), values(), or items() when you want a specific view, or provide two bindings to destructure its key and value directly. An iterable of positional tuples can be destructured into any number of bindings; the number of bindings must equal the tuple arity. Each iteration binding is local to its loop and cannot be read after that loop ends.

Break and Continue
while true:
    text = input("> ")
    if text == "":
        continue
    if text == "quit":
        break
    print(text)
Assert and Pass
count = 1
featurePending = true

assert count > 0

if featurePending:
    pass

assert is appropriate for invariants and tests. pass is an explicit placeholder that leaves a block intentionally empty.