Expressions
Expressions compute property values from math, time, and the scene itself. Use one anywhere a value goes: this pulse breathes and drifts as it plays.
imagine pulse
opacity: 0.5 + sin(time * 2) * 0.5
animate position.y: cos(time * 2) * 20
create pulseAn expression is anything the parser recognizes as a formula: operators,
time, member access, or a function call. Writing one directly on a property
still works, and animate property: expression animates it. The evaluator
handles numeric, comparison, logical, and unary operators; tuples (groups of
numbers like (10, 20)); time; defines (named values you define in the
scene); property values; member access; and built-in math, interpolation,
noise, and random functions. An unknown function fails at runtime instead of
becoming a general scripting API.
Reading other properties
An expression can read the same instance's other properties directly, without
any prefix. borderRadius: width / 8 uses the resolved width of this
instance. Order does not matter: a property can read a sibling declared before
or after it, and chained expressions resolve dependency-first. An identifier
that matches both a property and a define resolves to the property.
Property values pass through three layers before the renderer sees them:
- Base: the value as written, a blueprint default or instance override.
- Animated: keyframes and
animateexpressions, evaluated from the base. - Computed: property expressions, applied last.
Expressions read the final computed value. halfX: position.x / 2 sees the
animated (or otherwise computed) position, not the one you wrote. When a
property has both keyframes and an expression, the expression wins.
You can also read the base layer explicitly with the .base modifier:
position.base.x is the authored position, and width.base the authored
width. A property whose written value is itself an expression has no static
base, so property.base resolves to null. The base layer only materializes
when an expression reads it, so it costs nothing otherwise.
Tuples use the same dotted path as properties (animate position.y):
position.x, size.w, fill.r. Components map x/y/z/w, r/g/b/a, and
h (height) to tuple indices; a component beyond the tuple's length resolves
to null. A self-reference or expression cycle fails as an
undefined-variable error rather than a raw value.
Saving and reloading
Expressions are saved to the DSL exactly as you type them. Anything the parser
recognizes as an expression, such as function calls, time, pi, tau, e,
member access, operators, or unary minus, reads back as-is, including an
incomplete formula:
define draftOpacity: sin(
imagine draft
opacity: time *
create draftA source that does not look like an expression, such as a bare unknown
identifier like unknownFormula, is not saved as a formula: it reloads as a
plain string. The text survives, but it is no longer flagged as a broken
expression. A source starting with # cannot be written, because it would be
read as a comment, so the editor rejects it when you commit. Hex colors typed
into the formula editor are canonicalized to static color literals instead.
Inline # comments inside an expression are ignored outside quoted strings,
so color strings survive.
Numbers, strings, tuples, and colors
Expressions are type-aware across numbers, strings, tuples, and colors:
- String concatenation:
+joins strings when either operand is a string ("Made with " + brand); otherwise it adds numbers. - Tuple arithmetic:
+,-,*, and/apply component-wise to tuples and scalars:(10, 20) * 2gives(20, 40), and(1, 2) + (3, 4)gives(4, 6). Tuples of different lengths error. - Percentages: a trailing
%is a relative length (50%). Combining a percentage with a number produces a deferred length that resolves against the parent axis:width: 50% + 20is 50% of the parent plus 20 pixels,height: 100% - 40insets by 40,25% * 2doubles to 50%. Dividing two pure percentages gives their ratio (50% / 25%is2), and percentages can be compared with each other (50% < 100%). See values for how the resolved length is used. - Colors:
mix("#ff6b35", "#72e37f", 0.5)blends two colors (OKLab, perceptually uniform), andhsl(210, 0.8, 0.5)builds a color from hue degrees and saturation and lightness from 0 to 1. Color arguments are hex strings or 3/4-number tuples (linear RGB from 0 to 1); results are hex strings.
When an expression fails
An evaluation failure is surfaced, never silent. The runtime keeps the last
good value for a formula and records the error, which the editor shows as
diagnostics (runtime.getExpressionDiagnostics()). A formula that cannot
parse is a permanent error; one that fails to evaluate keeps its last good
value until it succeeds again.
Property expressions can resolve time and defines. Expression animations
evaluate from time and built-ins; they are not a general way to read arbitrary
instance properties or external data.
Built-in values
| Value | Meaning |
|---|---|
time |
Current scene time, in seconds. |
pi, PI |
The constant π (3.14159...). |
tau, TAU |
The constant 2π. |
e, E |
Euler's number (2.71828...). |
Built-in functions
Rounding
| Function | Result |
|---|---|
abs(x) |
Absolute value of x. |
floor(x) |
Largest integer ≤ x. |
ceil(x) |
Smallest integer ≥ x. |
round(x) |
Nearest integer to x. |
sign(x) |
-1, 0, or 1 for x. |
fract(x) |
Fractional part of x. |
Trigonometry, in radians
| Function | Result |
|---|---|
sin(x) |
Sine of x. |
cos(x) |
Cosine of x. |
tan(x) |
Tangent of x. |
asin(x) |
Arc-sine of x. |
acos(x) |
Arc-cosine of x. |
atan(x) |
Arc-tangent of x. |
atan2(y, x) |
Angle to the point (x, y), from the positive x-axis. |
Angle conversion
| Function | Result |
|---|---|
degrees(x) |
Convert radians to degrees. |
radians(x) |
Convert degrees to radians. |
Powers, logarithms, and modulo
| Function | Result |
|---|---|
pow(x, y) |
x raised to the power y. |
sqrt(x) |
Square root of x. |
log(x) |
Natural logarithm of x. |
log2(x) |
Base-2 logarithm of x. |
exp(x) |
e raised to the power x. |
mod(a, b) |
a modulo b, always non-negative. |
Interpolation and mapping
| Function | Result |
|---|---|
lerp(a, b, t) |
Linearly interpolate from a to b, at t from 0 to 1. |
clamp(x, min, max) |
Clamp x between min and max. |
remap(x, fromMin, fromMax, toMin, toMax) |
Map x from one range to another. |
smoothstep(edge0, edge1, x) |
A smooth 0 to 1 transition between two edges. |
pingpong(x, length) |
Oscillate between 0 and length. |
Colors
| Function | Result |
|---|---|
hsl(h, s, l) |
A hex color from hue in degrees (0 to 360) and saturation and lightness from 0 to 1. |
mix(from, to, t) |
Mix two hex colors (or color tuples) from 0 to 1. |
Noise and random
| Function | Result |
|---|---|
noise(x, y?) |
A smooth pseudo-random value from x, and optionally y. |
noise2d(x, y, z?) |
A smooth pseudo-random value from 2D or 3D coordinates. |
random(x, y?) |
A deterministic pseudo-random value. |
Aggregates and length
| Function | Result |
|---|---|
min(...) |
The smallest argument. |
max(...) |
The largest argument. |
length(...) |
The length of the vector formed by the arguments. |
Multiline expression syntax, fetch, data loading, and general reactive
conditions aren't implemented in the LumiDSL runtime yet.