Recipes as QR Codes
A Foodlang recipe is just text — which means it fits in a QR code. Compile a
recipe to the qr target and you get a compact, self-identifying payload you
can print on packaging, a menu, a shelf tag, or a machine's display. A machine
scans it; if it speaks Foodlang, it parses the payload and runs the recipe —
no app, no account, no network.
Try it on the Playground: pick a recipe, choose the
qr target, and Compile — the scannable code renders right there.
QR codes are generated by qrpuppy.com.
The qr target
qr emits the recipe's own .food KDL, minified (indentation and blank
lines stripped) so it stays small, with a leading // foodlang/1 marker so a
scanner can recognize the format. Comments are valid strict KDL, so the whole
payload still parses and executes unchanged.
foodlang compile drink.food --target qr
// foodlang/1
drink "Iced Vanilla Latte" {
ingredients {
coffee 18 g
vanilla_syrup 20 ml
milk 10 oz
ice 140 g
}
phases {
preinfuse coffee { pressure 2 bar; flow 2 mlps; time 6 s }
brew coffee to shot { pressure 9 bar; stop 40 g; max 28 s }
build cup { vanilla_syrup; shot; stir 5 s; ice; milk }
}
}
That payload is ~300 bytes and encodes to a QR around version 13 — small enough to print at a couple of centimetres and still scan reliably. A full recipe fits comfortably inside a single QR's byte budget.
Compliance: a minified KDL profile
.food is the cute authoring format, and every .food document is valid
strict KDL — that is the higher, more abstract language it is
checked against. The foodlang/1 QR payload keeps that guarantee: it is just
.food with per-line whitespace and blank lines removed, which KDL (being
insensitive to indentation) treats as identical. So the minified payload is
itself valid strict KDL, and the leading // foodlang/1 marker is a KDL
line comment — invisible to a parser.
The minification is also lossless: re-parsing a payload yields a recipe that
recompiles to byte-for-byte the same canonical .food. Both properties are
enforced for every example recipe in the test suite
(tests/compile-qr.test.ts):
foodlang/1 payload → isStrictKdl()⟶ valid, for every example.foodlang/1 payload → parse → compile(kdl)⟶ equals the original.
In short: .food ⊂ KDL and foodlang/1 ⊂ .food ⊂ KDL. A scanner that only
knows KDL can still parse the code; one that knows Foodlang can run it.
| Fidelity | executable — a Foodlang-speaking machine runs it directly |
| Category | Distribution & QR |
| Marker | // foodlang/1 on the first line |
| Body | minified strict-KDL .food, byte-for-byte re-parseable |
Going smaller: the compressed foodlang/1z variant
The readable payload above is already the smallest a recipe gets while staying
valid KDL text — there is no separate "minified KDL" dialect (KDL's only
compaction is the ; node terminator, which is byte-for-byte the same as a
newline). To go smaller you have to give up KDL-as-text, so it's opt-in.
The qrz target DEFLATE-compresses the readable payload. The QR then carries
the raw compressed bytes with a short foodlang/1z marker — no base64 tax — and
a scanner inflates it back to the exact // foodlang/1 KDL before running it.
foodlang compile examples/crunchy-roll.food --target qrz
# foodlang/1z:<base64url> (the copy-pasteable text token)
| recipe | readable foodlang/1 |
compressed QR bytes |
|---|---|---|
| iced-vanilla-latte | 298 B | 214 B (−28%) |
| crunchy-roll | 390 B | 240 B (−38%) |
The saving grows with recipe size. It is still lossless: both the ASCII
foodlang/1z: token and the raw-byte QR inflate to byte-for-byte the same
readable KDL (enforced for every example in tests/compile-qr.test.ts). The
trade-off: the compressed QR no longer contains human-readable KDL — only a
Foodlang-aware scanner can read it, whereas the default foodlang/1 degrades
gracefully to plain text. Use qr by default; reach for qrz on tiny labels or
long recipes.
In the Playground, pick the qr or qrz target to
see each rendered as a live QR with its byte count.
How a machine executes a scanned recipe
┌───────────┐ scan ┌───────────────┐ parse ┌──────────────┐
│ QR code │ ────────▶ │ // foodlang/1 │ ────────▶ │ Foodlang │
│ (printed) │ │ recipe text │ │ interpreter │
└───────────┘ └───────────────┘ └──────┬───────┘
│ compile to the
│ machine's native
▼ control target
┌──────────────┐
│ espresso / │
│ PLC / robot │ ▶ runs
└──────────────┘
- Scan. The camera reads the QR and recovers the text payload.
- Recognize. The
// foodlang/1marker tells the device this is a Foodlang recipe, not a URL or plain text. - Parse. The device parses the strict-KDL body into the recipe AST.
- Execute. The device compiles the AST to whatever it speaks natively — a flow-profiling espresso profile, an S88 phase sequence, an OPC UA program, a robot plan — and runs it. These are the same compiler targets the CLI and playground expose.
A device that doesn't speak Foodlang still gets human-readable text, so the code degrades gracefully: worst case, a person reads the recipe.
Ordering vs. recipes
Two different QR flows share the same idea — put Foodlang in a QR:
- Recipe QR (this page, the
qrtarget): the whole executable recipe. Scanned by a machine to make the item. - Order QR (the Order page, see the Order Format & Tokens spec): just item ids + the customer's variant choices, assuming each id already has its own recipe on the kitchen's side. Scanned by a clerk to take the order.
Both are generated client-side and both are rendered by qrpuppy.com.
Related exports
The qr target sits alongside the other distribution-minded exports:
- BevFacts (
bevfactstarget) — an FDA-style Nutrition Facts label estimated from the drink's ingredients, for the printed side of the package the QR rides on. - Schema.org Recipe, Cooklang, ORF — human/web publishing formats.
See the full list on the Targets page.