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 with name=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:

  1. Initially, phase is 1 (by default, flags are 0 — here it must be initialized to 1)
  2. The [phase==1] guard is active → S derives into alap S
  3. Deriving the alap rule produces Sa _ Re _ Ga _ and sets phase to 2 — the mutation acts at the rule level, not after the sounds
  4. In the next cycle, [phase==2] is active → S derives into jor S
  5. Deriving jor produces the faster sequence ([speed:2]) and sets phase to 3
  6. [phase==3]jhala concludes (no recursive S → 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:

  1. Sa Re (count becomes 1)
  2. Ga Pa (count becomes 2)
  3. Dha Ni (count returns to 0)
  4. 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_fail syntax (skip / retry / fallback) is defined and recognized by the parser, but its behavior is not yet operational (see S12). The underlying BP3 controls _goto and _failed do 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

  1. [] guards are declarative: the rule exists or not — no if/else
  2. 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 !
  3. Flags are global integers managed by BP3, not by runtimes
  4. We declare possible worlds, not branches — each flag value defines a set of active rules
  5. on_fail manages derivation failures: skip, retry(N), fallback(X)
  6. 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 <!


Back to index