Attributes
The parenthesised group every construct may carry: how values are written, and the types that parse them.
#The attribute group
Attributes live in a parenthesised group, written after the body and detail. On one line they separate with semicolons; across several, one per line with no separator. The two forms are equivalent, and a group may be empty — () — which is how a bare node head stops being prose.
point(x: 1; y: 2; colour: orange)
canvas.graph{
curve(expression: x^2)
}(
x-axis-label: t
y-axis-label: height
)The control-flow directives are the one exception to the group coming last — (audience: teacher){…} and (n: 0..4){…} put theirs first.
#Values as text, parsed by type
Every value is text until the attribute it lands in parses it, and a value the attribute's type cannot parse is a compile error — nothing is coerced or quietly defaulted. A type may be narrower than its name suggests — a number restricted to whole values, or to a range, or a list held to a fixed length — and a refused value's error names the restriction it broke rather than just the type. The lists what each type accepts.
This is also why a dataset column needs no type of its own: a cell reaches an attribute as text and is parsed there, failing exactly as the same text typed by hand would.
#Shorthands & derived attributes
Two pages hang beneath this one. covers the four ways an attribute is written shorter than key: value — the implicit :value, bare booleans and ! negation, spaced names, and aliases. covers the attributes a construct computes while it renders — read-only, reached with #id.name or !value, never set.