Language reference
petal
A dataflow language for live editing and creative coding. Petal compiles to WASM and draws to a canvas in your browser — programs form a dataflow graph, and a step-based evaluator lets you reshape code while it runs.
what is petal
Petal is a small language built around three ideas. Programs are dataflow graphs: values flow through expressions rather than executing as a fixed sequence of statements. Variables marked state persist across calls and frames — and survive hot reload, so you can edit a running program without losing its data. And a step-based evaluator means you can reshape code while it runs.
The result is a language tuned for sketching: physics toys, generative art, little games. It compiles to WebAssembly and renders to a canvas, with no build step and no install.
Nothing to set up — write Petal and watch it run in the playground.
the frame model
A Petal sketch is just a script. The runtime runs it once per frame, top to bottom, ~60 times a second. There is no separate setup or draw function — the whole file is the frame.
To carry data from one frame to the next, mark a variable state. It is initialized once and then keeps its value across every subsequent frame (and across hot reloads). Plain let bindings are recomputed fresh each frame.
The canvas persists between frames: it only repaints what you draw, and only resets where you call clear(). Game-style sketches clear() at the top of every frame; sketches that never clear accumulate — the trails are the drawing.
// runs once per framestate t = 0.0t += dt() // dt() = seconds since last frame clear(10, 12, 20) // wipe the canvas this framelet r = 100.0 + sin(t) * 60.0draw_circle(int(r + 120.0), 160, 24, 240, 80, 130)The sketch on the right is a complete Petal program — a 3D starfield with motion trails, about thirty lines. It keeps its stars in a state list and advances them every frame; the streaks come from drawing each star from its previous position to its current one.
this is the sketch running →
drawing api
Every drawing call takes pixel coordinates and a color as three separate channels — r, g, b, each an integer from 0 to 255. The origin is the top-left of the canvas.
| signature | draws |
|---|---|
| clear(r, g, b) | Fills the whole canvas, resetting the frame. |
| draw_rect(x, y, w, h, r, g, b) | A filled rectangle with its top-left at (x, y). |
| draw_rect_outline(x, y, w, h, r, g, b) | The outline of a rectangle, no fill. |
| draw_line(x1, y1, x2, y2, r, g, b) | A line between two points. |
| draw_circle(cx, cy, radius, r, g, b) | A filled circle centered at (cx, cy). |
| fill_triangle(x1, y1, x2, y2, x3, y3, r, g, b) | A filled triangle from three points. |
| fill_poly(points, r, g, b) | A filled polygon from a list of points. |
| draw_text(text, x, y, size, r, g, b) | A string at (x, y) in the given point size. |
clear(18, 18, 28)draw_rect(40, 40, 120, 80, 194, 24, 91)draw_circle(260, 80, 36, 224, 169, 59)draw_line(40, 160, 320, 160, 47, 107, 79)draw_text("petal", 40, 180, 18, 233, 226, 207)input & timing
Read the mouse, keyboard, clock, and canvas size with these builtins. Use _down to test whether something is held this frame, and _pressed for the single frame it goes down. Mouse buttons are 1 left, 2 middle, 3 right.
| signature | returns |
|---|---|
| mouse_x() / mouse_y() | Cursor position in canvas pixels. |
| mouse_down(button) | True while the button is held this frame. |
| mouse_pressed(button) | True only on the frame the button goes down. |
| key_down("space") | True while the named key is held. |
| key_pressed("up") | True only on the frame the key goes down. |
| dt() | Seconds elapsed since the last frame. |
| frame_count() | Number of frames since the sketch started. |
| screen_width() / screen_height() | Canvas size in pixels. |
clear(14, 16, 28) // follow the cursor; click to flashlet r = 18if mouse_down(1) then r = 36 enddraw_circle(mouse_x(), mouse_y(), r, 224, 169, 59) if key_down("space") then draw_text("space held", 16, 16, 14, 233, 226, 207)endlanguage tour
A quick pass over the syntax. Petal is expression-oriented: blocks evaluate to their last expression, and there is no semicolon ceremony.
variables
Bind with let, reassign by name, and use compound assignment to update in place. Types: int, float, bool, string, nil, list, record, color, and enum.
let x = 10x = 20 // reassignx += 5 // compound: x is now 25 let name = "petal"let ratio = 0.5let on = truefunctions
Functions return their last expression implicitly. Use the block form with end, or a single-line brace form.
fn add(a, b) a + b // implicit returnend fn square(x) { x * x } add(2, 3) // 5control flow
Conditionals use if / elsif / else / end. Loop with for ... in range(...) or while.
if score > 100 then "win"elsif score > 0 then "playing"else "start"end for i in range(0, 5) do print(i)end while running do step()endmatch & enums
Enums can carry data, and match destructures them by variant.
enum Shape Circle(radius) Rect(w, h)end match shape when Circle(r) -> 3.14159 * r * r when Rect(w, h) -> w * hendrecords
Records are lightweight keyed values; read fields with dot access.
let p = {name: "Alice", age: 30}p.name // "Alice"p.age // 30lists & higher-order functions
Lists come with the usual higher-order helpers, plus push and len. Closures use the fn(x){ ... } form.
let xs = [1, 2, 3]map(xs, fn(x){ x * 2 }) // [2, 4, 6]filter(xs, fn(x){ x > 1 }) // [2, 3]reduce(xs, 0, fn(a, x){ a + x }) // 6push(xs, 4)len(xs) // 4strings
Interpolate with { } inside a string; concatenate with ++.
let name = "world""hello, {name}!" // "hello, world!""score: " ++ str(42) // "score: 42"pipe
The pipe operator |> threads a value through a chain of calls, left to right.
[3, 1, 2] |> sort |> reverse |> print// prints [3, 2, 1]state
A state variable is initialized once and persists across calls and frames. This counter remembers its value every time it runs.
fn counter() state count = 0 count += 1 countend counter() // 1counter() // 2next steps
now go draw something
The fastest way to learn Petal is to reshape a running sketch. Open the playground to write from scratch, or browse the examples — every one is a complete program you can edit live.