Control Flow

WCL's expression-level constructs — if/else, match with patterns, if let, let bindings, and block expressions — combine to compute values from values. Every construct here is an expression: it returns a value, never a statement.

if / else

An if is an expression. The branches must agree on a type. else if chains for multi-way branches.

sign = if x < 0 { :neg } else if x > 0 { :pos } else { :zero }

match

match tests a value against a sequence of patterns, returning the body of the first one that matches. It's WCL's primary tool for destructuring unions, optionals, and any other shape with multiple cases.

area = match shape {
  Shape::Circle { radius, .. } => pi() * radius * radius,
  Shape::Polygon(n) if n > 2   => 0.0,
  Shape::Empty                 => 0.0,
  _                            => 0.0,
}

Patterns

PatternMatches
_Anything (wildcard)
nameAnything, binding the value to name
name @ innerMatch inner, also bind whole as name
Literal (42, "hi", :red)Equality with a literal
Union::Variant {…}A specific variant; .. ignores remaining fields
Union::Variant(x)Typeref variant, binding payload to x
Union::VariantUnit variant
pat1 | pat2Either pattern matches

Guards

An if expr after a pattern adds a runtime test; the arm fires only when both the pattern matches and the guard is true.

classify = match n {
  k if k < 0  => :neg,
  0           => :zero,
  k if k > 10 => :big,
  _           => :small,
}

if let

if let pattern = value { … } else { … } binds and branches in a single step. Use it when you only care about a single variant or pattern.

label = if let Shape::Circle { radius, .. } = s {
  format("circle r={}", radius)
} else {
  "other"
}

let bindings

A let names a reusable value or function. It resolves by name in sibling and descendant expressions but is not document data — it never appears in the output, JSON, or schema validation. Lexically scoped (inner shadows outer), lazily evaluated, cycle-detected.

Item form

At file (global) scope or inside any block, let name = expr introduces a name. No terminator.

let base_port = 8080u32

service "web" {
  port = base_port
}
service "api" {
  port = base_port + 1u32
}

Because a let can bind a function, it doubles as a composition helper:

let scale = fn(p: f64) -> f64 p * 2.0

a = scale(3.0)    // 6.0
b = scale(4.5)    // 9.0

Expression form

Inside a { … } block expression, let name = expr; introduces a binding scoped to the block's tail. The two forms are distinct: the item form lives in the document's namespace; the expression form is local to one block.

Block expressions

A { … } expression holds zero or more let … ; bindings followed by a tail expression, which is the block's value.

result = {
  let a = to_upper("x");
  let b = to_upper("y");
  len(a) + len(b)
}