Types, constants and generics
Magscript is statically typed. Signal types are:
- scalars
- fixed length arrays
- named types (
compositeorenum), declared locally or imported
Numeric types do not implicitly convert: f32, f64, and i32 must agree.
A float literal such as 1.0 takes f32 or f64 from context. An integer literal is always
i32, so an f64 needs 1.0, not 1.
Scalar and array types
Section titled “Scalar and array types”| Type | Meaning |
|---|---|
f64, f32 |
64 bit or 32 bit floating point |
i32 |
32 bit signed integer |
bool |
true or false |
[T; N] |
fixed length array of N values of T |
Array lengths are part of the type. Arrays can nest, and an array literal uses
square brackets: [1.0, 2.0, 3.0]. Lengths and indexes must be integer literals;
indexes are zero based and checked against the declared length.
+and-work on matching numeric scalars or same shaped numeric arrays.*and/work on matching scalars; a scalar can also scale an array (a * vorv * a, andv / a)..*and./are same shaped array arithmetic; usedot(a, b)for an inner product.%is numeric scalar only.- Comparisons return
bool: ordering requires numeric scalars;==and!=also accept booleans and enums. Arrays and composites cannot be compared. - Boolean operators require
bool.
Named values
Section titled “Named values”composite State { position: [f64; 3], enabled: bool,}
enum Mode { Idle, Running }A composite is a value record. Construct it with State { position: p, enabled: true } and read fields with state.position or state.position[0].
Supply every field exactly once, or use State { enabled: false, ..state }
to copy omitted fields from a value of the same type. Composites can nest but
cannot contain themselves, directly or indirectly.
Enums contain named variants without payloads, written Mode::Idle. They
support equality, but no arithmetic, ordering, or ZERO value.
Named types are identified by their declaring module and name. Identical declarations in different files are different types. Model fields can also hold child model instances; see modules and generics.
Constants
Section titled “Constants”const declares a typed, immutable data value at module scope. Import it with
use to share it across files.
const RADIUS: f64 = 0.15;const SPEED: f64 = 3.6;const OMEGA: f64 = SPEED / RADIUS;const AXIS: [f64; 3] = [0.0, 0.0, 1.0];Allowed values
Section titled “Allowed values”Constants and parameter constructors use the same value rules:
| Allowed | Examples |
|---|---|
| Number and boolean literals | 2.0, 3, true |
| Constants and enum variants | OMEGA, PI, Mode::Idle |
| Arithmetic | 2.0 * OMEGA, -RADIUS |
| Comparisons and logic | SPEED > 0.0, not false |
| Arrays and records | [1.0, 2.0], State { angle: 0.0 } |
| Indexing and field access | AXIS[2], INITIAL.angle |
| Contextual zero | ZERO |
Constructor bodies can also read their arguments and earlier let bindings,
and construct child models. A const cannot have a model type.
These are not constructor values:
- signal reads
selfdtfbyif- expression blocks
- ordinary function calls, builtins included
For a builtin, compute a value such as sqrt(2.0) externally or use SQRT_2.
Values must match the declared or expected type. Constants cannot depend on
themselves, directly or indirectly. A const array is currently limited to
six elements per array dimension.
Builtin constants
Section titled “Builtin constants”These names are available without an import, at either f32 or f64:
PI TAU E SQRT_2FRAC_PI_2 FRAC_PI_3 FRAC_PI_4 FRAC_PI_6 FRAC_PI_8FRAC_1_PI FRAC_2_PI FRAC_1_SQRT_2 FRAC_2_SQRT_PILN_2 LN_10 LOG2_E LOG2_10 LOG10_E LOG10_2Names are case-sensitive and reserved: E is Euler’s number; e may be a
user binding.
ZERO takes its type from context: numeric zero, false, or an array or
composite of zeros. It requires an expected type, so write let x: f64 = ZERO;.
Enums and model instances have no ZERO value.
INFINITY and NEG_INFINITY take their type from context the same way: an
infinity at f32 or f64, and one in every element of a float array. They
suit an unbounded limit, which is how the Integrator’s defaults are written.
Generics
Section titled “Generics”A model can declare type parameters. Its step and params items use the
same parameters without redeclaring them.
model Hold<T = f64> { ic: T,}
step Hold(u: T) -> (y: T) { y = self.ic fby u;}
params Hold(ic: T = ZERO) { Self { ic }}Type arguments
Section titled “Type arguments”An instance supplies arguments after the model name. An instance that omits them takes them from what it is stepped with, and a parameter no input determines takes its declared default:
// Fields in another model; import Hold with use crate::hold::Hold.follows: Hold, // T is the type of what self.follows(..) is fedvector: Hold<[f64; 3]>, // T is [f64; 3], whatever it is fedStepping follows with an f32 makes it a Hold<f32>; stepping it with a
bare literal, self.follows(0.0), leaves it at the default f64. An instance
stepped in a loop takes the loop’s type, like a local does. Spelled arguments
are never read from the call: vector fed an f64 is a type error.
- Type parameters may represent:
- scalars
- arrays
- composites
- enums
- Parameters with defaults must follow parameters without defaults.
- Generic bodies have no trait bounds. Each used instantiation is checked with
its concrete types; arithmetic valid for
f64may fail forbool. - When all type parameters have defaults, the default instantiation is also checked.
Values and state
Section titled “Values and state”A constructor default must be valid at the instance’s type. 0.0 cannot
initialize an array; ZERO can initialize any type that
has a zero value.
Otherwise, pass an explicit value, such as Hold([1.0, 2.0, 3.0]) for the
vector field.
In a generic step, fby must be the whole right-hand side of an output
assignment or an annotated local: let previous: T = self.ic fby u;.
Probed locals also require type annotations. These declarations specify the
state and telemetry types shared by the model’s instantiations.
A generic model cannot be a simulation entry. Run a concrete wrapper model
that instantiates it. Generic models may use inline fields or an
extern step under the same boundary
rules as other models.