Conditionals
Grammar
conditional_decl = "conditional" expression "{" { conditional_branch } "}".
conditional_branch = (expression | "_") "=>" "{" { relationship_decl } "}" [ "," ].
A conditional evaluates its match subject (the expression right after the
conditional keyword) and activates the one branch, if any, whose literal equals the current
match value. Only that branch’s relationship blocks participate in the round’s solve; every
other branch’s relationships are invisible to the planner, exactly as if they weren’t declared.
A branch’s own body holds nothing but relationship declarations: a conditional cannot
declare cells, and cannot nest another conditional directly inside a branch.
The match subject
A bare cell name (conditional mode { ... }) is the common case: the match value is just that
cell’s current value. The match subject can also be a general expression over several
already-declared cells, deduced exactly like a
relationship binding’s body:
sheet resample_demo {
source resample: bool = true;
source constrain: bool = true;
cell locked: bool = false;
conditional resample && constrain {
true => {
relationship {
locked := true;
}
}
false => {
relationship {
locked := false;
}
}
}
}
Every branch literal’s type must match the match subject’s own inferred type exactly; a sheet
mixing an i32 match subject with a bool branch literal fails to parse, not to propagate.
The match subject can also be tuple-valued, in which case each branch literal is a tuple
expression of the same shape.
Forced cells
A relationship with exactly one method has no alternative binding to choose: its output cell
is claimed every time the sheet resolves, regardless of strength. Such a cell is forced
— Sheet::is_forced reports this, and it’s false for a cell whose relationship has two or
more methods, even if strength happens to pick the same direction every round.
A common convention in a host UI is to disable the editable widget for a forced cell:
writing it would have no lasting effect once the sheet next resolves, so there’s nothing
useful for the user to type into. This is a UI convention, not a language rule — adam-lang
and adam-rs never disable anything themselves; a host is always free to accept the write
anyway (see shadow state: forced and self-referencing cells below for what happens if it does).
Shadow state: forced and self-referencing cells
The raw value is never lost section’s filters and the self-referencing methods section’s self-referencing methods both keep a cell’s own raw source value forever, underneath whatever derived value a live correction currently computes. A forced cell works the same way: forcing it shadows its own source, never overwrites it.
sheet range_bounds {
source mode: i32 = 0;
cell low: i32 = 4;
cell high: i32 = 9;
conditional mode {
0i32 => {
relationship {
low := min(low, high);
high := max(low, high);
}
}
1i32 => {
relationship {
low := high;
}
}
}
}
With mode == 0 (the declared default), the self-referencing branch is active — two methods,
low := min(low, high) and high := max(low, high), exactly like the triangle from the relationships chapter: only one is ever selected, and which one is decided by strength. low, declared
first, is stalest, so the solver picks low := min(low, high); high is left alone as an
ordinary source, its own max method never invoked. low and high start at 4 and 9,
already satisfying low <= high, so nothing visibly changes.
Writing high to 42 and switching to mode == 1 activates the single-method branch,
forcing low from high: low reads 42, but its own source is still 4 — the write
that actually changed something (high) never touched low‘s source at all. Switching back
to mode == 0 reselects low := min(low, high) (still the stalest cell) and recomputes it
fresh from both cells’ own sources — 4 and 42 — not from the stale forced 42: low
reads back down to 4; high, never written since the first round, is still the one left as
a source, at 42.
Writing low to 100 promotes it to freshest, flipping the solver’s choice: now
high := max(low, high) is the one selected, deriving high and leaving low as the
source instead — high reads 100, pulled up to match. Either binding can fire; which one
does is strength’s call, exactly as in the relationships chapter,
never both at once.
The default branch and reverting to source
_ => { ... } matches whatever value none of the named branches list and (because it’s
defined that way) must be the last branch textually; a named branch written after _ is a
syntax error. Leaving off _ entirely is legal: if the match value doesn’t equal any branch’s
literal, none of the conditional’s relationships are active that round, and every cell that
would otherwise be one of their outputs stays free, reverting to its own last
externally-written value (or its declared default), not to whatever the branch last computed
for it. This is the same shadow-state mechanism the previous section just showed for a forced cell,
triggered here by no branch matching at all rather than by switching branches: a relationship
that stops being active can never have written a cell’s source, so the cell has nothing to
revert to except that untouched source.
sheet no_default {
source mode: i32 = 0;
cell x: f64 = 1.0;
conditional mode {
0i32 => {
relationship {
x := 100.0;
}
}
}
}
Nested and chained conditionals
Two conditionals in the same sheet compose freely at the sheet level: one conditional’s
output cells can be another’s match subject or a relationship input, exactly like any other
cell, even though a branch body itself can only hold relationships, not another
conditional. A resize dialog, for example, might have two sibling conditionals this way: one
gated on resample, the other on resample && constrain.