ReferenceControlArcReferenceSyntax

Syntax

Arc language syntax reference for comments, identifiers, literals, and statement structure

Comments

Single-line comments start with //. Multi-line comments use /* */.

// This is a single-line comment

/* This is a
   multi-line comment */

x := 42  // Comments can follow code

Identifiers

Identifiers name variables, functions, sequences, stages, and channels.

Rules:

  • Start with a letter (a-z, A-Z) or underscore (_)
  • Contain letters, digits (0-9), or underscores
  • Case-sensitive (ox_pt_1 and Ox_Pt_1 are different)
valid_name
_private
sensor1
ox_pt_1

Reserved keywords cannot be used as identifiers:

Keywords
funcifelsereturn
sequencestagenextchan
seriesandornot
authorityi8 i16 i32 i64u8 u16 u32 u64f32 f64 str

Literals

Numeric Literals

Integer literals default to i64. Float literals default to f64.

42         // i64
3.14       // f64
0.5        // f64
.25        // f64 (leading dot allowed)

Numeric Literals with Units

Unit suffixes attach directly to numbers (no whitespace):

100ms      // 100 milliseconds (i64)
5s         // 5 seconds (i64)
1min       // 1 minute (i64)
2h         // 2 hours (i64)
10hz       // 10 Hz frequency (converts to 100ms period)
1khz       // 1 kHz frequency (converts to 1ms period)
Temporal UnitsMeaning
nsnanoseconds
usmicroseconds
msmilliseconds
sseconds
minminutes
hhours
Frequency UnitsMeaning
hzhertz
khzkilohertz
mhzmegahertz

String Literals

Strings are enclosed in double quotes. Escape sequences are supported.

"hello"
"line1\nline2"     // newline
"tab\there"        // tab
"quote: \""        // escaped quote
EscapeMeaning
\nnewline
\ttab
\rcarriage return
\\backslash
\"double quote

Series Literals

Series (arrays) use square brackets:

[1, 2, 3]           // series i64
[1.0, 2.0, 3.0]     // series f64
[]                  // empty (requires type annotation)

Empty series require an explicit type:

empty series f64 := []

Variables

Constants

Top-level declarations are compile-time constants. Use them for configuration values and to avoid magic numbers.

MAX_PRESSURE := 500.0       // f64 constant
SAMPLE_COUNT := 100         // i64 constant
TIMEOUT := 30s              // with unit suffix
SCALE f32 := 2.5            // explicit type

Constants can be used in expressions and function config parameters:

pressure > MAX_PRESSURE -> alarm{}

sensor -> scale{gain=SCALE, offset=0.1} -> output

Local Variables

Local variables are declared with := inside functions and reset each invocation.

x := 42                  // type inferred as i64
y f64 := 3.14           // explicit type

Stateful Variables

Stateful variables are declared with $= and persist across invocations.

count $= 0               // persists between calls
total f64 $= 0.0        // explicit type

Assignment

Assign to existing variables with =:

x = x + 1

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

Variable Scope

Variables are visible from their declaration point to the end of their enclosing block. Nested blocks can access variables from enclosing scopes.

func example() {
    x := 10              // visible for rest of function
    if x > 5 {
        y := x + 1       // visible only inside if block
    }
    // y is not visible here
}

No Shadowing

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

func example() {
    x i32 := 10
    if x > 0 {
        x i32 := 20      // Error: conflicts with outer x
    }
}

Use a different name or reassign the existing variable:

func example() {
    x i32 := 10
    if x > 0 {
        inner_x i32 := 20    // different name
        x = 20               // reassign existing
    }
}

Local variables can shadow built-in functions and constants:

func example() {
    min := 10    // shadows built-in min function
}

Authority Declarations

Authority declarations set the control authority for channels written by the program. They must appear before any func, sequence, or flow declarations.

Default Authority

Set a default authority for all channels:

authority 200

When omitted, the system default is 255 (maximum).

Per-Channel Authority

Set authority for specific channels using a grouped form:

authority (200 valve_cmd 100 vent_cmd 150)

This sets a default of 200, then overrides valve_cmd to 100 and vent_cmd to 150.

You can also set per-channel authority without a default:

authority (valve_cmd 100 vent_cmd 150)

Summary

FormSyntaxDescription
Default onlyauthority 200Sets authority for all channels
Grouped with defaultauthority (200 valve_cmd 100)Default plus per-channel overrides
Per-channel onlyauthority (valve_cmd 100 vent_cmd 150)Only named channels

Authority values are u8 integers in the range 0-255.

To change authority at runtime, use the set_authority built-in function inside stage bodies.

Statements

If Statements

if condition {
    // body
}

if condition {
    // body
} else {
    // alternative
}

if condition1 {
    // body
} else if condition2 {
    // alternative
} else {
    // fallback
}

Conditions evaluate as u8: 0 is false, non-zero is true.

Return Statements

Functions with return types require explicit return statements.

func compute(x f64) f64 {
    if x < 0 {
        return 0.0       // early return
    }
    return x * 2.0       // required
}

Functions without return types can use return for early exit:

func process() {
    if skip {
        return           // early exit
    }
    // more work
}

Arc has no loops. Use stateful variables with reactive execution for iterative patterns.

Flow Statements

Flow statements connect channels and functions in the reactive scope.

Continuous Flow (->)

Executes every cycle while active:

sensor -> filter{} -> output

One-Shot Flow (=>)

Fires once when condition becomes true, then stops until reset:

pressure > 500 => next

Routing Tables

Map multiple inputs to function parameters:

{ sensor1: a, sensor2: b } -> averager{}

Route multiple outputs to different targets:

sensor -> split{} -> {
    high: alarm{},
    low: logger{}
}

Expressions in Flows

Inline expressions act as implicit functions:

temperature > 100 -> alarm{}
(sensor1 + sensor2) / 2.0 -> display

Blocks

Braces {} delimit blocks. Blocks contain statements (function bodies) or flow statements (stage bodies).

func example() {
    // statements in function block
}

stage pressurize {
    // flow statements in stage block
}

Stage bodies use commas to separate flow statements:

stage pressurize {
    1 -> valve,
    sensor -> controller{},
    pressure > 500 => next
}