Skip to main content

Control Flow

Dryft provides minimal but powerful control flow constructs: when for conditionals, then for simple branches, and cycle for loops. This simplicity eliminates the need for multiple overlapping control structures.

Why then, when, and cycle?

Dryft’s philosophy is to keep syntax as simple as possible. Traditional languages have many control structures that are just syntactic sugar. Dryft consolidates them:
  • then - Simple conditional statements
  • when - Multi-way branching (if/else, switch, ternary)
  • cycle - All loop types (while, do-while, for, infinite)

Conditionals with then

The then keyword creates a simple conditional that executes when the top of stack is true.

Syntax

condition then: body ;

# Or with explicit terminator
condition then:
    body
:then

Examples

# Simple conditional
true then: "Yes!" prints ;

# With comparison
10 10 + 20 =? then: "equals!" prints ;

# Nested conditionals
true then:
    true then:
        "nested conditionals\n" prints
    :then
:then

Type Requirement

then expects a Binary (boolean) value on the stack:
5 3 >? then: "5 is greater" prints ;  # OK

# 5 then: "oops" prints ;  # ERROR: expects Binary, got Number

Multi-way Branching with when

The when keyword handles if/else, switch statements, and ternary operations.

Syntax

when:
    condition then: branch1 ;
    condition then: branch2 ;
    default_value ;
The when block evaluates branches in order and executes the first matching condition. The final clause (without then) is the default case.

If/Else Pattern

when: condition then:
    # if branch
    true_value ;
    # else branch
    false_value ;
Example:
when: 18 20 >=? then: 
    "18 >= 20!\n" ;
    "18 < 20\n" ;
prints

Switch/Match Pattern

2 var: x
when:
    $x 1 =? then: "one\n" ;
    $x 2 =? then: "two\n" ;
    $x 3 =? then: "three\n" ;
    "something else\n" ;
prints

Without Default Case

when: 
    $x 15 divby then: "FizzBuzz!" prints ;
    $x  3 divby then: "Fizz" prints ;
    $x  5 divby then: "Buzz" prints ;
    $x printi ;

Loops with cycle

The cycle keyword creates loops. All loop types (while, do-while, for, infinite) are implemented using cycle with break.

Syntax

cycle:
    # loop body
    condition then: break ;
:cycle
Or with semicolon:
cycle: body ;  # single-line loop

Breaking Loops

Use the break keyword to exit a cycle:
cycle:
    condition then: break ;
    # loop body
:cycle
The statement “Break the cycle” feels natural for loop exits.

Pre-check Loop (while)

10 var: max
0 var: i
cycle:
    $i $max =? then: break ;  # Check at start
    $i printi
    $i 1 + i!
:cycle

Post-check Loop (do-while)

cycle:
    # loop body
    condition then: break ;  # Check at end
:cycle

Infinite Loop

cycle:
    # runs forever (or until break)
:cycle

Counting Loop (for)

1 var: x
cycle: $x $max >? then: break ;
    $x printi
    $x 1 + x!
:cycle

Variables

Variables store values within a scope. They work with Dryft’s linear type system.

Declaring Variables

value var: name
The value is popped from the stack and stored:
10 var: x          # x = 10
"hello" var: msg   # msg = "hello"
true var: flag     # flag = true

Reading Variables

Prefix with $ to read a variable’s value:
10 var: x
$x 5 +    # Reads x (10), adds 5

Writing Variables

Suffix with ! to write a new value:
10 var: x
20 x!     # x is now 20

Variable Scope

Variables are scoped to their defining block:
act: example
    10 var: x        # x in outer scope
    true then:
        20 var: y    # y only exists in this block
        $x $y +      # Can access outer x
    :then
    # $y not accessible here
:act

Variables in Loops

fun: factorial var: n
    1 var: result
    cycle: $n 0 =? then: break ;
        $result $n * result!  # Update result
        $n 1 - n!             # Decrement n
    :cycle
    $result
:fun

The return Keyword

Use return to exit early from a function or action:
fun: early_exit var: n
    $n 0 =? then: return ;
    # more code
:fun

Complete Examples

FizzBuzz

include: std/io 

(Int Int -> Bool) 
fun: divby
    mod 0 =? ;

(Int ->)
act: fizzbuzz var: max
    1 var: x
    cycle: $x $max >? then: break ;
        when: 
            $x 15 divby then: "FizzBuzz!" prints ;
            $x  3 divby then: "Fizz" prints ;
            $x  5 divby then: "Buzz" prints ;
            $x printi ;
        "\n" prints
        $x 1 + x! 
    :cycle
:act

act: main 100 fizzbuzz ;

Factorial

(Int -> Int)
fun: factorial var: n
    1 var: result
    cycle: $n 0 =? then: break ;
        $result $n * result!
        $n 1 - n!
    :cycle
    $result
:fun

Exponentiation

(Int Int -> Int)
fun: expo 
    var: x var: y
    1 var: total
    cycle: $x 0 =? then: break ;
        $total $y * total!
        $x 1 - x! 
    :cycle
    $total
:fun

Safe Division

act: safediv 
    var: x var: y
    when:
        $x 0 equals? then: "Division by zero" prints ; 
        $y 0 equals? then: 0 ;
        $x 1 equals? then: $y ;
        $y $x / ;
:act

Type Footprint

All blocks must have a balanced type footprint:
# then block must consume condition and produce nothing extra
condition then: body ;

# cycle must not change stack types
cycle: body ;

# when branches must produce same types
when:
    cond then: value1 ;  # produces Type A
    value2 ;              # must also produce Type A

Summary

  • then - Execute code when condition is true
  • when - Multi-way branching for if/else and switch
  • cycle - Universal loop construct with break
  • var: - Declare variables ($read, write!)
  • return - Early exit from functions/actions
These simple constructs replace dozens of control structures in traditional languages while maintaining clarity and expressiveness.

Build docs developers (and LLMs) love