From 403e5981453b6772ed140e27cb34c2f0992d3491 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 04:11:22 +0700 Subject: [PATCH 1/3] Catch up with four facts that moved while this page sat on a branch The quote checker found three of them on the first run against the new tip: the sand hash was changed deliberately by the grid lane, break and continue now refuse by name instead of reading as unknown functions, and the usage text grew --debug. The prelude also grew a string and UTF-8 family the table did not list. --- web/examples/quotes.sh | 3 ++- web/index.html | 25 ++++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/web/examples/quotes.sh b/web/examples/quotes.sh index dd447fc..b0f50b3 100644 --- a/web/examples/quotes.sh +++ b/web/examples/quotes.sh @@ -56,7 +56,8 @@ for pair in \ 'fnty:(defn f [g (Fn [i32] i32)] i32 (g 1))' \ 'quoted:(defn f [] i32 (quote a))' \ 'deferblock:(defn f [] i32 (let [x 1] (defer (print-line "a")) x))' \ - 'i64index:(defconst xs [3 i32] [1 2 3]) (defn main [] i32 (let [i (i64 1)] (at xs i)))' + 'i64index:(defconst xs [3 i32] [1 2 3]) (defn main [] i32 (let [i (i64 1)] (at xs i)))' \ + 'break:(defn main [] (let [i 0] (while (< i 3) (break))))' do name=${pair%%:*}; src=${pair#*:} printf '%s\n' "$src" > "$here/.q.flan" diff --git a/web/index.html b/web/index.html index 08b78ce..c042f21 100644 --- a/web/index.html +++ b/web/index.html @@ -217,7 +217,7 @@ $ ./_build/default/bin/main.exe run calc-me.flan "1 + 2 * (3 - 0.5) / 2"
$ flan
 usage: flan (read|parse|check|emit|shim) <file.flan>...
-       flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] [--target=wasm32-wasi]
+       flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] [--debug] [--target=wasm32-wasi]
        flan run <file.flan> [args...]
        flan reload <program.flan> <forms.flan> [-o out.so]
        flan dev <program.flan> [-s socket]
@@ -528,10 +528,13 @@ a body that changes it cannot change the trip count, and the loop variable is no assignable.

Loops are imperative, with while, until and -return. There is no loop/recur, and there is no -break or continue either — both are planned and neither -exists, so today they report as unknown function break. An early exit out -of a loop is return, as first-even does above.

+return. There is no loop/recur. There is no +break or continue yet either; both refuse by name:

+ +
break is not implemented yet (see the build sequence in plan.org)
+ +

An early exit out of a loop is return, as first-even does +above.

Option, match and some

@@ -641,6 +644,8 @@ ordinary Flan.

slices of i32swap-i32!, reverse-i32!, sort-i32!, index-of-i32, min-i32, max-i32, sum-i32 bytesbytes=?, starts-with?, ends-with?, index-of-byte, index-of-bytes, trim, digit?, space? parsingparse-i64, parse-f64 +textsplit-on-byte, split-next!, lower-ascii, upper-ascii, bytes-ci=? +UTF-8decode-rune, rune-at, rune-count, rune-size, rune-start?, valid-utf8?, encode-rune! numberssign-f32, lerp, floor-f32, ceil-f32, round-f32, and sqrt-f32, which is the one declare in the file randomrand-seed, rand-u32, rand-f32, rand-i32-range, rand-f32-range @@ -1189,10 +1194,10 @@ module, and the headless sand acceptance program prints the same 64-bit hash und it does natively:

$ flan run test/programs/sand-headless.flan
-2256461126764447066
+-2851001042534928384
 $ flan build test/programs/sand-headless.flan --target=wasm32-wasi -o sand.wasm
 $ node --no-warnings test/wasm-run.mjs sand.wasm
-2256461126764447066
+-2851001042534928384

That number is the whole point of writing the RNG in Flan rather than calling libc's: a grid hash is only a regression test if the sequence is byte-identical on both targets. @@ -1206,6 +1211,12 @@ module to bind to; release builds call directly, emit constants as constants, an the folding back. Dev builds are not pruned by reachability, because what a REPL may redefine next is not a function of what has been called so far.

+

--debug is a third flag beside --dev and the optimisation +level. --dev asks whether you can redefine the program while it runs; +--debug asks whether you can stop it and read it. It emits DWARF, sets +-O0, and is refused by name for wasm32. lldb needs no plugin to read a +Flan struct: the struct is its C struct.

+

Some things are refused by name rather than half-supported, and both cross-target refusals say why:

From d995094b5251f1e5ab443e644d07aa6e6a145bb4 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 04:17:27 +0700 Subject: [PATCH 2/3] Put the contents beside the text instead of above it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nineteen entries at the top of a long page are scrolled past once and then unreachable. A fixed column stays put and scrolls on its own. Below 66rem there is not room for two columns, so it collapses to a bar with a toggle — a checkbox, so the page still needs no script to navigate. The wordmark's dot was a circle at a guessed x, which drifted from the n whenever the reader's serif was not the one it was measured against. It is a full stop in the same text run now. --- web/index.html | 119 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 89 insertions(+), 30 deletions(-) diff --git a/web/index.html b/web/index.html index c042f21..d99fb28 100644 --- a/web/index.html +++ b/web/index.html @@ -67,6 +67,7 @@ body { color: var(--text); font: 17px/1.65 "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif; } +main { display: block; } .wrap { max-width: 44rem; margin: 0 auto; padding: 0 1.25rem 6rem; } header { padding: 3.5rem 0 2rem; } @@ -117,12 +118,63 @@ th { font-weight: 600; color: var(--muted); } .lede { font-size: 1.1rem; } .note { color: var(--muted); font-size: .94rem; } -nav.toc { background: var(--panel); border-radius: 5px; padding: 1rem 1.25rem; margin: 2rem 0 0; } -nav.toc ul { list-style: none; padding: 0; margin: 0; columns: 2; column-gap: 2rem; } -nav.toc li { margin: .18rem 0; font-size: .93rem; break-inside: avoid; } + +/* The table of contents: a bar with a toggle when the window is narrow, a + fixed column beside the text when there is room for one. The toggle is a + checkbox so that no script is needed to open it. */ +.toc-toggle { position: absolute; width: 1px; height: 1px; opacity: 0; } +.toc-button { + display: block; cursor: pointer; user-select: none; + position: sticky; top: 0; z-index: 2; + padding: .6rem 1.25rem; + background: var(--panel); border-bottom: 1px solid var(--rule); + color: var(--muted); font-size: .92rem; letter-spacing: .02em; +} +.toc-button::before { content: "\2261\00a0\00a0"; } +.toc-toggle:focus-visible + .toc-button { outline: 2px solid var(--accent); outline-offset: -2px; } +nav.toc { + display: none; + background: var(--panel); border-bottom: 1px solid var(--rule); + padding: .75rem 1.25rem 1.1rem; +} +.toc-toggle:checked ~ nav.toc { display: block; } +.toc-title { + display: none; /* the toggle already says "Contents" */ + margin: 0 0 .5rem; font-size: .78rem; letter-spacing: .09em; + text-transform: uppercase; color: var(--muted); +} +nav.toc ul { list-style: none; padding: 0; margin: 0; } +nav.toc li { margin: 0; font-size: .93rem; } +nav.toc a { + display: block; padding: .2rem 0; color: var(--muted); + text-decoration: none; border-left: 2px solid transparent; padding-left: .6rem; +} +nav.toc a:hover { color: var(--link); border-left-color: var(--accent); } +h2, h3 { scroll-margin-top: 3.5rem; } + +/* Nineteen entries one to a line fills a phone screen, so the opened panel + goes to two columns as soon as there is room for them. */ +@media (min-width: 30rem) and (max-width: 65.99rem) { + nav.toc ul { columns: 2; column-gap: 1.5rem; } + nav.toc li { break-inside: avoid; } +} footer { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--rule); color: var(--muted); font-size: .92rem; } -@media (max-width: 34rem) { nav.toc ul { columns: 1; } } +/* Wide enough for both: the contents become a column of their own, scrolling + independently of the page, and the text moves over to sit beside it. */ +@media (min-width: 66rem) { + .toc-button { display: none; } + nav.toc { + display: block; + position: fixed; top: 0; left: 0; bottom: 0; + width: 15rem; overflow-y: auto; overscroll-behavior: contain; + background: none; border-bottom: 0; border-right: 1px solid var(--rule); + padding: 3.6rem 1rem 2rem 1.5rem; + } + main { margin-left: 15rem; } + .toc-title { display: block; } + h2, h3 { scroll-margin-top: 1rem; } +} .c { color: var(--c-comment); font-style: italic; } .s { color: var(--c-string); } @@ -133,34 +185,11 @@ footer { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--ru -
- -
- - flan - - -

A statically typed Lisp for game development. Clojure's brackets, - C's memory and value model, no garbage collector.

-
- -

Flan compiles s-expressions to LLVM IR and then to a native binary. -There are no object headers, so a Flan struct is exactly its C struct. There is no -collector, so nothing runs between frames that you did not write. And a running -program can be edited: a function recompiled in Emacs is installed into the live -process at its next frame boundary, in about twenty milliseconds.

- -

This page describes the compiler as it is, not as it is planned. Where -something is designed but not built, it says so and gives the message the compiler -prints for it. Every Flan program on this page is a file in web/examples/ -with its output recorded beside it; sh web/examples/check.sh runs them all -and compares, and quotes.sh re-derives the blocks that are transcripts -rather than programs. If the page and the compiler disagree, one of those two goes -red.

+ + +
+
+ +
+ + + flan. + +

A statically typed Lisp for game development. Clojure's brackets, + C's memory and value model, no garbage collector.

+
+ +

Flan compiles s-expressions to LLVM IR and then to a native binary. +There are no object headers, so a Flan struct is exactly its C struct. There is no +collector, so nothing runs between frames that you did not write. And a running +program can be edited: a function recompiled in Emacs is installed into the live +process at its next frame boundary, in about twenty milliseconds.

+ +

This page describes the compiler as it is, not as it is planned. Where +something is designed but not built, it says so and gives the message the compiler +prints for it. Every Flan program on this page is a file in web/examples/ +with its output recorded beside it; sh web/examples/check.sh runs them all +and compares, and quotes.sh re-derives the blocks that are transcripts +rather than programs. If the page and the compiler disagree, one of those two goes +red.

+

What Flan is

A minimal Lisp for games. In one line: Odin with a Lisp frontend and a live REPL. @@ -1304,6 +1362,7 @@ macro is a function from Form to Form, which needs

+