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
| Pattern | Matches |
|---|---|
| _ | Anything (wildcard) |
| name | Anything, binding the value to name |
| name @ inner | Match 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::Variant | Unit variant |
| pat1 | pat2 | Either 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)
}