ReferenceControlArcReferenceVariables

Variables

Arc variable reference for literal, stateful, channel-read, and channel read/write variables, assignment, and scope

Declaration

A variable names a value. Declare one with :=, or $= to make it stateful. In the flow context, a variable takes one of three kinds.

Table

Declaration Kind Behavior
x := 5 Literal Holds a value; resets to its initial value on entry
x $= 0 Literal (stateful) Holds a value; keeps it across scope entries
scaled := raw * 2 Channel read Read-only; follows the channels it reads
valve := valve_cmd Channel read/write Reads and writes the channel; can be rebound

Assignment

Assign to existing variables with =:

x = x + 1

Supported compound assignment:

count += 1      // count = count + 1
value -= 10     // value = value - 10
total *= 2      // total = total * 2
ratio /= 4      // ratio = ratio / 4
remainder %= 3  // remainder = remainder % 3

Scope

A variable is visible from its declaration to the end of its enclosing block, and nested blocks can read variables from enclosing scopes. A variable is usable only after its declaration, including across stages.

sequence main {
    // visible for the rest of the sequence
    x := 10
    stage s1 {
        // visible only inside the stage
        y := x + 1
    }
    // y is not visible here
}

Top-level variables are global constants and cannot be reassigned or written to. Stateful variable declarations are not allowed at the top level.

Variable types

Literal

A literal variable holds a value. Declare it with :=; the type is inferred from the value, or you can annotate it. A literal resets to its initial value on each entry into its scope.

MAX_PRESSURE := 500.0 // f64
SAMPLE_COUNT := 100   // i64
SETPOINT f64 := 50.0  // explicit type
TIMEOUT := 30s        // with unit suffix
ENABLED := true       // bool

Stateful

A stateful variable keeps its value across scope entries instead of resetting. Declare it with $=. Only literal variables can be stateful.

count $= 0 // persists across entries

Stateful variables drive counters, accumulators, and edge detection.

Counter

Track how many times a condition has occurred.

stage {
    count $= 0
    // Triggered on new `value`
    value > threshold -> select{} -> {
        true: count + 1 -> count
    }
}

Accumulator

Sum values over time.

stage accumulate {
    total $= 0.0
    // Triggered on new `value`
    value + total -> total
}

Previous value

Compare the current value to the previous one.

sequence phase_delta {
    prev $= 0.0
    // Triggered on new `value`
    value - prev -> delta
    value -> prev
}

Running maximum

Track the highest value seen.

stage running_max {
    max $= 0.0
    // Triggered on new `value`
    value > max -> select{} -> {
        true: value -> max
    }
}

State toggle

Maintain an on/off state.

stage toggle {
    state $= false

    // Triggered on new `trigger`
    trigger -> not state -> select{} -> {
        true: true -> state,
        false: false -> state
    }
}

Channel read/write

A channel read/write variable names a channel directly. Reads and writes pass through to that channel, so it behaves exactly like the channel it names. Reassign it with = to rebind it to another channel of the same type.

Readable names

A short local name for a long or deeply namespaced channel.

cpu := sy_node_1_metrics_cpu
cpu > 90.0 => throttle

Actuator failover

Command an actuator through an alias; on a fault, rebind it to the backup. Writes stay wired to the alias and follow whichever channel is bound.

stage main {
    pump := pump_a_cmd

    sequence run {
        stage primary {
            pump_a_fault => backup
        }
        stage backup {
            pump = pump_b_cmd // reroute commands to the backup pump
        }
    }

    // writes follow whichever pump is bound
    run_cmd -> pump
}

Clamp a setpoint

Read and write the same channel: watch an operator-writable setpoint and write a safe limit back when it exceeds a bound.

setpoint := setpoint_cmd

sequence guard {
    stage watch {
        setpoint > 500.0 => clamp
    }
    stage clamp {
        500.0 -> setpoint // write a safe limit back
        setpoint <= 500.0 => watch
    }
}

Device selection

Point the same routine at a chosen unit; rebind the alias to switch targets without rewiring the flow.

stage main {
    dut := dut_a_cmd // default target

    sequence run {
        stage select {
            use_b => on_b // pick unit B instead
        }
        stage on_b {
            dut = dut_b_cmd
        }
    }

    run_cmd -> dut // commands follow whichever unit is bound
}

Channel read

A channel read variable is a read-only expression over one or more channels. It is reactive: when any channel it reads produces new data, the variable updates and every flow that reads it runs again with the new value.

Writing to a channel-read variable throws a compile error (value -> scaled). Reassigning it with = is not a plain value replacement; it rebinds the expression, and the variable follows the new rule from its next trigger onward.

Logging

A status label that reflects the current stage and the live reading, logged as it streams.

stage monitor {
    reactive_expr := "Nominal: " + str(ai_0)

    sequence main {
        stage nominal {
            reactive_expr = "Nominal: " + str(ai_0)
        }
        stage low {
            reactive_expr = "Low: " + str(ai_0)
        }
        stage high {
            reactive_expr = "High: " + str(ai_0)
        }
    }

    // Updates on underlying `ai_0`
    reactive_expr -> log
}

Gain scheduling

A proportional command whose gain changes with the phase, feeding the proportional valve directly.

stage control {
    // proportional command; the gain changes with the phase
    command := coarse_gain * (setpoint - pressure)

    sequence pressurize {
        stage fill {
            pressure > 400.0 => trim
        }
        stage trim {
            // gentler gain near the target
            command = fine_gain * (setpoint - pressure)
        }
    }

    command -> valve_cmd
}

Sensor failover

Read a scaled sensor; on a fault, reassign the same variable to a backup probe. Downstream flows stay wired to the variable.

stage monitor {
    temp := probe_a * 0.1 - 40.0

    sequence acquire {
        stage primary {
            probe_a_fault => failover
        }
        stage failover {
            // identical scaling, backup probe
            temp = probe_b * 0.1 - 40.0
        }
    }

    temp -> temperature
}

Shadowing

Arc does not allow shadowing. A variable cannot have the same name as one in an enclosing scope:

sequence example {
    x i32 := 10
    stage s1 {
        x i32 := 20 // Error: conflicts with outer x
    }
}

Use a different name or reassign the existing variable:

sequence example {
    x i32 := 10
    stage s1 {
        inner_x i32 := 20 // different name
        x = 20 // reassign existing
    }
}

Local variables can shadow built-in functions and constants:

sequence example {
    min := 10 // shadows built-in min function
}