Magscript
Magscript is Magnetite’s synchronous dataflow language. Block diagrams use the
same source format. A .mag file describes a model’s:
- parameters
- inputs
- outputs
- equations
A complete model
Section titled “A complete model”Save this as motor.mag:
model Motor { k: f64, tau: f64,}
step Motor(throttle: f64) -> (omega: f64) { let omega_dot = (self.k * throttle - omega) / self.tau; omega = 0.0 fby omega + omega_dot * dt;}
params Motor(k: f64 = 1.0, tau: f64 = 0.2) { Self { k, tau }}modeldeclares stored parameters and child model instances.paramsconstructs those fields from typed arguments and defaults.stepdeclares named ports and the equations evaluated on each tick.self.kreads a parameter, andself.child.ka child’s;dtis the step duration in seconds.initial fby nextsuppliesinitialon the first tick, then the previous tick’snextvalue.
Equations describe dependencies, not execution order. Each output is assigned once per tick; the compiler schedules equations and rejects feedback without a delay.
Syntax
Section titled “Syntax”Magscript uses:
- commas between fields and arguments
- braces for blocks
- semicolons after bindings and assignments
Whitespace does not affect meaning; // starts a comment that continues to the
end of the line.
enum Mode { Idle, Running }
model Controller { gain: f64, mode: Mode }step Controller(u: f64) -> (y: f64) { let scaled: f64 = self.gain * u; y = scaled;}fn twice(x: f64) -> f64 { x * 2.0 }params Controller(gain: f64 = 1.0) { Self { gain, mode: Mode::Idle }}Equations and values
Section titled “Equations and values”let x = expression;binds a local whose type is its value’s.let x: T = expression;states the type, and the value must have it. Equations may be written in any order, annotated or not: a local may be read before the line that binds it.- Locals that read one another in a loop take their type from where the loop is entered: an input, or a typed local. A loop nothing typed enters is an error that lists every local in it; annotate one of them.
y = expression;assigns a declared output. Each output must be assigned once; assignment does not mutate an earlier binding.let (a, b) = self.child(x);binds multiple child outputs.(a, b) = ...;assigns existing output targets.if condition { a } else { b }is a value expression. The condition must bebool; both branches must return the same type.elseis required.- Expression blocks contain local
letbindings and a final value without a semicolon. Step bodies contain equations and have no return expression.
Identifiers are case-sensitive letters, digits, and underscores, beginning with
a letter or underscore. Numbers use decimal notation, optionally with a fraction
or exponent (1.0, 1e-3). Radix prefixes, digit separators, and type suffixes
are unsupported; specify the type on a declaration.
Operators
Section titled “Operators”Binary operators at the same level associate left to right, except fby,
which associates right to left. Prefix operators apply right to left.
| Precedence (high to low) | Operators |
|---|---|
| 1 | postfix call, field access, indexing |
| 2 | unary - |
| 3 | *, /, .*, ./, % |
| 4 | +, - |
| 5 | >, <, >=, <=, ==, != |
| 6 | unary not |
| 7 | and, xor, nand, nor (one level) |
| 8 | or |
| 9 | fby (right associative) |
Parentheses override precedence. Array literals use [a, b]; type arrays use
[T; N]. See types and state.
The rest of the reference: