S6) Composing with Conditions
Flags and Paths
Guards, Mutations, and Failure Management
A grammar that always produces the same thing offers few surprises. Flags transform a static grammar into a compositional path — the structure evolves as it is derived.
Where does this article fit in?
In S2, we saw [] guards and flag operators (six comparison, three calculation). Here, we assemble them: how to write compositions that change direction during derivation, that count, that react. For the foundations of this mechanism in BP3, see B4.
[guard] — the declarative guard
A guard in square brackets [] before the LHS (Left Hand Side) tests a flag (global integer variable) and determines if a rule exists:
[phase==1] S -> Sa Re Ga Pa // active only if phase is 1
[phase==2] S -> Ga Pa Dha Ni // active only if phase is 2
[count>3] A -> B C // active if count exceeds 3
[tension!=0] A -> B C // active if tension is non-zero
Available test operators: ==, !=, >, <, >=, <=.
Bare flag and multiple guard
Two shortcuts simplify common guards:
[Ideas] S -> A B C // bare flag: active if Ideas != 0 (non-zero)
[phase==1] [count<3] S -> A B // multiple guard: both conditions must be true
The bare flag ([Ideas]) is a shortcut for [Ideas!=0] — the flag is treated as a boolean (true if non-zero). The multiple guard (several consecutive []) combines conditions with a logical AND — the rule exists only if all conditions are met simultaneously.
The crucial point: the guard is declarative, not imperative. It’s not an if/else executed step-by-step: it’s a condition for the rule’s existence. The rule exists when the condition is true; it doesn’t exist when the condition is false. There is no “else”.
This changes the way of thinking: we don’t program branches, we declare possible worlds. When phase is 1, the world is that of the ālāp. When phase is 2, the world changes — and with it, all available rules.
Envisioned evolution: explicit boolean combinations
Today, several consecutive guards combine with an implicit AND (the multiple guard above), and OR is circumvented by writing multiple rules. A language evolution is being studied to make these combinations explicit: boolean operators between guards — && (AND), || (OR) — and parentheses () for grouping and setting priority.
[a] && [b] // explicit AND (equivalent to the current multiple guard)
[a] || [b] // OR: active if at least one of the conditions is true
[a] && ([b] || [c]) // grouping with parentheses
Under study: this syntax is not yet implemented. For now, only AND by juxtaposition (
[a] [b]) is available in BP3; OR is handled by several distinct rules.
Mutations — changing state during derivation
Flags are useless if they never change. A mutation is written with [] at the end of the rule, after the RHS. It is out-of-time: it applies when the rule is triggered, during derivation — not at a point in the sequence being played. Its position therefore carries no temporal meaning.
S -> A B [count-1] // applies count-1 when this rule is derived
S -> A B [phase=1] [count=2] // multiple mutations, at the end of the rule
On the BP3 side, these mutations become /…/ markers placed at the end of the rule:
| BPscript | BP3 |
|---|---|
S -> A B [count-1] |
S --> A B /count-1/ |
S -> A B [phase=1] [count=2] |
S --> A B /phase=1/ /count=2/ |
No “mid-rule” mutation: you cannot insert a mutation between two elements for it to take effect “after the 3rd sound, before the 4th”. It acts at the rule level, at the moment of its derivation — never at a precise instant in the sound flow.
Three mutation operators:
=— assign:[phase=2]+— increment:[count+1]-— decrement:[tension-1]
Be careful not to confuse with the ! operator: ! is exclusively temporal (triggers and simultaneity, see S7 (coming soon)). A flag mutation is never written !phase=2 — always [phase=2] at the end of the rule. The distinction is syntactic and clear:
Sa!dha→!followed by a symbol → temporal trigger[phase=2]→ brackets withname=value→ flag mutation (engine state)
Flags can also be compared to each other:
[flag1==flag2]— test the equality of two flags[flag1=flag2]— copy the value of one flag into another
Example 1: a raga in three phases
An illustration: an Indian raga structured in three phases (ālāp → jor → jhālā), with automatic transition:
@alphabet.raga:supercollider
@tempo:60
// Guards: each phase has its own rules
[phase==1] S -> alap S
[phase==2] S -> jor S
[phase==3] S -> jhala
// The ālāp explores slowly; deriving this rule sets phase to 2
alap -> Sa _ Re _ Ga _ [phase=2]
// The jor accelerates; deriving this rule sets phase to 3
jor -> {Sa Re Ga Pa}[speed:2] [phase=3]
// The jhālā concludes
jhala -> {Sa Re Ga Pa Dha Ni Sa}[speed:4]
What happens:
- Initially,
phaseis 1 (by default, flags are 0 — here it must be initialized to 1) - The
[phase==1]guard is active →Sderives intoalap S - Deriving the
alaprule producesSa _ Re _ Ga _and setsphaseto 2 — the mutation acts at the rule level, not after the sounds - In the next cycle,
[phase==2]is active →Sderives intojor S - Deriving
jorproduces the faster sequence ([speed:2]) and setsphaseto 3 [phase==3]→jhalaconcludes (no recursiveS→ derivation stops)
Three phases, three atmospheres, zero imperative logic. The structure is traversed.
Example 2: a cyclic counter
Flags allow building cyclic sequences — each derivation of the same non-terminal produces a different result:
[count==0] A -> Sa Re [count+1]
[count==1] A -> Ga Pa [count+1]
[count==2] A -> Dha Ni [count=0] // reset → the cycle restarts
Three successive derivations of A yield:
Sa Re(count becomes 1)Ga Pa(count becomes 2)Dha Ni(count returns to 0)Sa Re(the cycle restarts)
This is more powerful than lin mode (random leftmost derivation — see S5): with flags, you can have complex conditions, jumps, conditional resets. You build true compositional paths, not just positional variations.
Failure Management: on_fail
When a derivation fails (no rule applies for a given non-terminal), BP3 has low-level flow control mechanisms (_goto, _failed). BPscript plans to expose them in a more readable form, on_fail:
// Global directive — applies to the entire grammar
@on_fail:skip // skip the failing rule
// Local override on a rule
[on_fail:retry(3)] S -> A B C // retry 3 times
[on_fail:fallback(B)] S -> A B C // switch to sub-grammar B
Three strategies available:
| Strategy | Syntax | Effect |
|---|---|---|
| skip | @on_fail:skip |
Skip the failing rule, continue |
| retry | [on_fail:retry(3)] |
Retry N times (useful with @mode:random) |
| fallback | [on_fail:fallback(X)] |
Switch to sub-grammar X |
on_fail is part of the @core library and on_fail is a reserved keyword of the language. The goal: a safety net, useful in live coding where grammars are modified in real-time and can be temporarily inconsistent — the compilation target being _goto/_failed.
Current status: the
on_failsyntax (skip / retry / fallback) is defined and recognized by the parser, but its behavior is not yet operational (see S12). The underlying BP3 controls_gotoand_faileddo exist.
Flags vs backticks: who manages the logic?
A legitimate question: why use flags (limited to integers and simple comparisons) rather than Python backticks with real logic?
The answer lies in who holds the clock:
| Mechanism | Managed by | Visible to BP3 | Time |
|---|---|---|---|
| Flags | BP3 (the engine) | Yes — BP3 knows which rules are active | Synchronous with derivation |
| Backticks | The runtime (SC, Python…) | No — BP3 does not see the runtime state | Asynchronous, evaluated at execution by the runtime |
Flags are within the derivation engine. When [phase=2] executes, BP3 knows it immediately and activates the new rules at the next derivation step. A Python backtick that would modify a Python variable would change nothing in the derivation — BP3 does not see Python variables.
Simple rule: if the condition affects the structure (which rules apply), it’s a flag. If the condition affects the sound (which synthesizer to play, at what volume), it’s a backtick.
Key Takeaways
[]guards are declarative: the rule exists or not — noif/else- Mutations are written at the end of the rule with
[](out-of-time, at the rule level): assignment ([flag=N]), increment ([flag+1]), decrement ([flag-1]) — never with! - Flags are global integers managed by BP3, not by runtimes
- We declare possible worlds, not branches — each flag value defines a set of active rules
- on_fail manages derivation failures: skip, retry(N), fallback(X)
- Flags = structure (synchronous with derivation), backticks = behavior (asynchronous, evaluated by the runtime)
Glossary
- Flag: Global integer variable in BP3, testable by
[flag_expr]and modifiable by[flag=value]— controls which grammar rules are active - Guard: Condition
[flag_expr]before the LHS that determines a rule’s existence — the rule is active if the guard is true - Mutation: Modification of a flag via
[flag=value],[flag+1]or[flag-1]at the end of the rule — applied when the rule is triggered (out-of-time), not at a point in the played flow - on_fail: Failure management directive — what to do when no rule applies (skip, retry, fallback)
- Compositional path: Sequence of flag states that defines a trajectory through the grammar — each state activates a different set of rules
- Ālāp: Opening phase of an Indian raga, unmetered, exploratory — paradigm of smooth time
- Jor: Intermediate phase of a raga, progressively rhythmic
- Jhālā: Concluding phase of a raga, fast and virtuosic
Links in the series
- S2 —
[]guards and flag operators - S5 — Derivation modes — the interaction between mode and flags
- S7 (coming soon) — The
!operator — temporal simultaneity (distinct from flag mutations) - B4 — Flags and weights in BP3 — the foundations
Prerequisites: S2, B4
Reading time: 12 min
Tags: #BPscript #flags #guards #conditional-composition #BP3
Next article: S7 — The Shared Instant: ! and <!