petal

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.

01

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.

02

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.

frame.ptl
// runs once per frame
state t = 0.0
t += dt() // dt() = seconds since last frame
 
clear(10, 12, 20) // wipe the canvas this frame
let r = 100.0 + sin(t) * 60.0
draw_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 →

starfield.ptl
compiling petal…
03

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.

signaturedraws
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.
draw.ptl
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)
04

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.

signaturereturns
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.
input.ptl
clear(14, 16, 28)
 
// follow the cursor; click to flash
let r = 18
if mouse_down(1) then r = 36 end
draw_circle(mouse_x(), mouse_y(), r, 224, 169, 59)
 
if key_down("space") then
draw_text("space held", 16, 16, 14, 233, 226, 207)
end
05

language 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.

vars.ptl
let x = 10
x = 20 // reassign
x += 5 // compound: x is now 25
 
let name = "petal"
let ratio = 0.5
let on = true

functions

Functions return their last expression implicitly. Use the block form with end, or a single-line brace form.

fns.ptl
fn add(a, b)
a + b // implicit return
end
 
fn square(x) { x * x }
 
add(2, 3) // 5

control flow

Conditionals use if / elsif / else / end. Loop with for ... in range(...) or while.

control.ptl
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()
end

match & enums

Enums can carry data, and match destructures them by variant.

match.ptl
enum Shape
Circle(radius)
Rect(w, h)
end
 
match shape
when Circle(r) -> 3.14159 * r * r
when Rect(w, h) -> w * h
end

records

Records are lightweight keyed values; read fields with dot access.

records.ptl
let p = {name: "Alice", age: 30}
p.name // "Alice"
p.age // 30

lists & higher-order functions

Lists come with the usual higher-order helpers, plus push and len. Closures use the fn(x){ ... } form.

lists.ptl
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 }) // 6
push(xs, 4)
len(xs) // 4

strings

Interpolate with { } inside a string; concatenate with ++.

strings.ptl
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.

pipe.ptl
[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.

state.ptl
fn counter()
state count = 0
count += 1
count
end
 
counter() // 1
counter() // 2
06

next 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.