From dfd64d89ea8f62ee03b48bbd29026f37a37bafce Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 03:40:03 +0700 Subject: [PATCH] The examples are files that run, not prose, so the page cannot drift from them Copying a snippet into HTML is where a documented language stops being the real one. Each block on the page is a program here with its recorded output beside it, and check.sh is what says the page is still true after a change. --- web/examples/arrays.flan | 23 +++++++++++++++++++++++ web/examples/arrays.out | 6 ++++++ web/examples/boom.flan | 12 ++++++++++++ web/examples/boom.out | 2 ++ web/examples/check.sh | 37 +++++++++++++++++++++++++++++++++++++ web/examples/conds.flan | 16 ++++++++++++++++ web/examples/conds.out | 3 +++ web/examples/control.flan | 30 ++++++++++++++++++++++++++++++ web/examples/control.out | 5 +++++ web/examples/defer.flan | 11 +++++++++++ web/examples/defer.out | 5 +++++ web/examples/enums.flan | 14 ++++++++++++++ web/examples/enums.out | 3 +++ web/examples/ffi.flan | 6 ++++++ web/examples/ffi.out | 2 ++ web/examples/geom/len.flan | 4 ++++ web/examples/geom/vec.flan | 5 +++++ web/examples/hello.flan | 2 ++ web/examples/hello.out | 2 ++ web/examples/numbers.flan | 8 ++++++++ web/examples/numbers.out | 4 ++++ web/examples/option.flan | 13 +++++++++++++ web/examples/option.out | 3 +++ web/examples/pkg.flan | 8 ++++++++ web/examples/pkg.out | 2 ++ web/examples/restart.flan | 24 ++++++++++++++++++++++++ web/examples/restart.out | 4 ++++ web/examples/shimdemo.flan | 7 +++++++ web/examples/structs.flan | 17 +++++++++++++++++ web/examples/structs.out | 3 +++ 30 files changed, 281 insertions(+) create mode 100644 web/examples/arrays.flan create mode 100644 web/examples/arrays.out create mode 100644 web/examples/boom.flan create mode 100644 web/examples/boom.out create mode 100644 web/examples/check.sh create mode 100644 web/examples/conds.flan create mode 100644 web/examples/conds.out create mode 100644 web/examples/control.flan create mode 100644 web/examples/control.out create mode 100644 web/examples/defer.flan create mode 100644 web/examples/defer.out create mode 100644 web/examples/enums.flan create mode 100644 web/examples/enums.out create mode 100644 web/examples/ffi.flan create mode 100644 web/examples/ffi.out create mode 100644 web/examples/geom/len.flan create mode 100644 web/examples/geom/vec.flan create mode 100644 web/examples/hello.flan create mode 100644 web/examples/hello.out create mode 100644 web/examples/numbers.flan create mode 100644 web/examples/numbers.out create mode 100644 web/examples/option.flan create mode 100644 web/examples/option.out create mode 100644 web/examples/pkg.flan create mode 100644 web/examples/pkg.out create mode 100644 web/examples/restart.flan create mode 100644 web/examples/restart.out create mode 100644 web/examples/shimdemo.flan create mode 100644 web/examples/structs.flan create mode 100644 web/examples/structs.out diff --git a/web/examples/arrays.flan b/web/examples/arrays.flan new file mode 100644 index 0000000..336972f --- /dev/null +++ b/web/examples/arrays.flan @@ -0,0 +1,23 @@ +(defconst rows 3) +(defconst cols 4) + +;; A fixed array is a value: inline storage, copies on assignment. +(defconst palette [4 u32] [0xE6B800FF 0x3B6E8CFF 0xA83232FF 0xCC6B1FFF]) + +;; No initialiser means all-bytes-zero, so this is BSS and costs nothing. +(defvar grid [rows [cols i32]]) + +(defn main [] + (set (at grid 1 2) 7) + (print-i64 (i64 (at grid 1 2))) (newline) ; 7 + (print-i64 (i64 (len palette))) (newline) ; 4 + + ;; A slice is ptr+len and non-owning: it views the array, it does not copy it. + (let [row (slice (at grid 1) 0 cols)] + (set (at row 0) 5) + (print-i64 (i64 (at grid 1 0))) (newline) ; 5 — the same storage + (print-i64 (sum-i32 row)) (newline)) ; 12 + + ;; (zeroed) is a memset, not an allocation. + (set grid (zeroed)) + (print-i64 (i64 (at grid 1 2))) (newline)) ; 0 diff --git a/web/examples/arrays.out b/web/examples/arrays.out new file mode 100644 index 0000000..fbc2cae --- /dev/null +++ b/web/examples/arrays.out @@ -0,0 +1,6 @@ +7 +4 +5 +12 +0 +exit 0 diff --git a/web/examples/boom.flan b/web/examples/boom.flan new file mode 100644 index 0000000..f4dcbd8 --- /dev/null +++ b/web/examples/boom.flan @@ -0,0 +1,12 @@ +(defstruct Missing [id i32]) + +(defn load [n i32] i32 + (restart-case + (do (error (Missing {:id n})) ; Never — only a transfer gets past + 0) + (use-placeholder [] -1) + (retry [] 7))) + +(defn main [] + (print-i64 (i64 (load 1))) + (newline)) diff --git a/web/examples/boom.out b/web/examples/boom.out new file mode 100644 index 0000000..a1914b5 --- /dev/null +++ b/web/examples/boom.out @@ -0,0 +1,2 @@ +unhandled Missing +exit 134 diff --git a/web/examples/check.sh b/web/examples/check.sh new file mode 100644 index 0000000..8e78563 --- /dev/null +++ b/web/examples/check.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# Every Flan program shown on index.html is in this directory, and this script +# runs all of them and compares what they print against the .out file beside +# them. An example that does not compile is worse than no example, so the page +# quotes only what this script has been green on. +# +# $ dune build && sh web/examples/check.sh +# +# FLAN overrides the compiler; the default is the one dune just built. +here=$(cd "$(dirname "$0")" && pwd) +root=$(cd "$here/../.." && pwd) +FLAN=${FLAN:-$root/_build/default/bin/main.exe} + +cd "$here" || exit 1 +fail=0 +for f in *.flan; do + # shimdemo.flan calls raylib, so it is not run. What it demonstrates is the + # C the compiler writes, which is checked below by generating that instead. + [ "$f" = shimdemo.flan ] && continue + got=$( { "$FLAN" run "$f"; echo "exit $?"; } 2>&1 ) + if [ "$got" = "$(cat "${f%.flan}.out")" ]; then + echo "ok $f" + else + echo "FAIL $f" + printf '%s\n' "$got" | diff -u "${f%.flan}.out" - || true + fail=1 + fi +done + +if "$FLAN" shim shimdemo.flan | grep -q 'GetMousePosition(void)'; then + echo "ok shimdemo.flan (flan shim)" +else + echo "FAIL shimdemo.flan (flan shim)" + fail=1 +fi + +exit $fail diff --git a/web/examples/conds.flan b/web/examples/conds.flan new file mode 100644 index 0000000..0f49b99 --- /dev/null +++ b/web/examples/conds.flan @@ -0,0 +1,16 @@ +(defstruct AssetMissing [id i32]) + +(defvar seen i64) + +(defn load-all [] + (signal (AssetMissing {:id 1})) ; Unit — the caller carries on + (signal (AssetMissing {:id 2}))) + +(defn main [] + (load-all) ; no handler: a no-op + (print-i64 seen) (newline) ; 0 + + ;; A handler that returns normally accumulates and lets the signaller run on. + (handler-bind [(AssetMissing [c] (set seen (+ seen (i64 (.id c)))))] + (load-all)) + (print-i64 seen) (newline)) ; 3 diff --git a/web/examples/conds.out b/web/examples/conds.out new file mode 100644 index 0000000..5e34bdb --- /dev/null +++ b/web/examples/conds.out @@ -0,0 +1,3 @@ +0 +3 +exit 0 diff --git a/web/examples/control.flan b/web/examples/control.flan new file mode 100644 index 0000000..56b4b6c --- /dev/null +++ b/web/examples/control.flan @@ -0,0 +1,30 @@ +(defconst nums [5 i32] [1 3 8 9 10]) + +(defn classify [n i32] string + (cond + (< n 0) "negative" + (= n 0) "zero" + :else "positive")) + +(defn countdown [n i32] + (let [i n] + (while (> i 0) + (print-i64 (i64 i)) + (print-str " ") + (set i (- i 1))) + (newline))) + +(defn first-even [s [i32]] (Option i32) + (dotimes [i (len s)] + (when (= 0 (% (at s i) 2)) + (return (Some (at s i))))) + None) + +(defn main [] + (print-line (classify -3)) + (countdown 4) + (unless false + (print-line "unless runs when the test is false")) + (match (first-even (slice nums 0 (len nums))) + (Some n) (do (print-i64 (i64 n)) (newline)) + None (print-line "none"))) diff --git a/web/examples/control.out b/web/examples/control.out new file mode 100644 index 0000000..8650d0f --- /dev/null +++ b/web/examples/control.out @@ -0,0 +1,5 @@ +negative +4 3 2 1 +unless runs when the test is false +8 +exit 0 diff --git a/web/examples/defer.flan b/web/examples/defer.flan new file mode 100644 index 0000000..57cf9cf --- /dev/null +++ b/web/examples/defer.flan @@ -0,0 +1,11 @@ +(defn work [n i32] i32 + (defer (print-line "second")) + (defer (print-line "first")) ; innermost-first at exit + (when (< n 0) + (return 0)) ; runs both defers above it + (print-line "body") + n) + +(defn main [] + (print-i64 (i64 (work 3))) + (newline)) diff --git a/web/examples/defer.out b/web/examples/defer.out new file mode 100644 index 0000000..18217b4 --- /dev/null +++ b/web/examples/defer.out @@ -0,0 +1,5 @@ +body +first +second +3 +exit 0 diff --git a/web/examples/enums.flan b/web/examples/enums.flan new file mode 100644 index 0000000..9745ea9 --- /dev/null +++ b/web/examples/enums.flan @@ -0,0 +1,14 @@ +(defenum Key + [space 32 escape 256 left 263 right 262]) + +(defn key-name [k Key] string + (cond + (= k :space) "space" + (= k :escape) "escape" + :else "an arrow")) + +(defn main [] + ;; :space resolves against the parameter's enum at compile time. + ;; A typo is an error here, not a wrong number later. + (print-line (key-name :space)) + (print-line (key-name :left))) diff --git a/web/examples/enums.out b/web/examples/enums.out new file mode 100644 index 0000000..f9cf7fe --- /dev/null +++ b/web/examples/enums.out @@ -0,0 +1,3 @@ +space +an arrow +exit 0 diff --git a/web/examples/ffi.flan b/web/examples/ffi.flan new file mode 100644 index 0000000..57c2d7a --- /dev/null +++ b/web/examples/ffi.flan @@ -0,0 +1,6 @@ +;; A plain `declare` names a C symbol in a signature Flan can already spell: +;; no aggregate crosses, so no wrapper is generated. +(declare cos-f64 [x f64] f64 "cos") + +(defn main [] + (print-f64 (cos-f64 0.0)) (newline)) diff --git a/web/examples/ffi.out b/web/examples/ffi.out new file mode 100644 index 0000000..bed8d1b --- /dev/null +++ b/web/examples/ffi.out @@ -0,0 +1,2 @@ +1 +exit 0 diff --git a/web/examples/geom/len.flan b/web/examples/geom/len.flan new file mode 100644 index 0000000..50d577e --- /dev/null +++ b/web/examples/geom/len.flan @@ -0,0 +1,4 @@ +;; A second file in the same directory shares one top-level scope: it does not +;; import vec.flan, and the order of the two files does not matter. +(defn length [v V2] f32 + (sqrt-f32 (+ (* (.x v) (.x v)) (* (.y v) (.y v))))) diff --git a/web/examples/geom/vec.flan b/web/examples/geom/vec.flan new file mode 100644 index 0000000..5f5c019 --- /dev/null +++ b/web/examples/geom/vec.flan @@ -0,0 +1,5 @@ +;; No package declaration: the name comes from the directory. +(defstruct V2 [x f32 y f32]) + +(defn add [a V2 b V2] V2 + (V2 {:x (+ (.x a) (.x b)) :y (+ (.y a) (.y b))})) diff --git a/web/examples/hello.flan b/web/examples/hello.flan new file mode 100644 index 0000000..118a283 --- /dev/null +++ b/web/examples/hello.flan @@ -0,0 +1,2 @@ +(defn main [] + (print-line "hello from flan")) diff --git a/web/examples/hello.out b/web/examples/hello.out new file mode 100644 index 0000000..4632956 --- /dev/null +++ b/web/examples/hello.out @@ -0,0 +1,2 @@ +hello from flan +exit 0 diff --git a/web/examples/numbers.flan b/web/examples/numbers.flan new file mode 100644 index 0000000..14c58e2 --- /dev/null +++ b/web/examples/numbers.flan @@ -0,0 +1,8 @@ +(defn main [] i32 + (let [n 40 ; i32, inferred + big (i64 n) ; every widening is written + x 1.5] ; f64 + (print-i64 (+ big 2)) (newline) + (print-f64 (* x 2.5)) (newline) + (print-i64 (i64 (bit-xor (<< 1 8) 255))) (newline) + 0)) diff --git a/web/examples/numbers.out b/web/examples/numbers.out new file mode 100644 index 0000000..87b87d3 --- /dev/null +++ b/web/examples/numbers.out @@ -0,0 +1,4 @@ +42 +3.75 +511 +exit 0 diff --git a/web/examples/option.flan b/web/examples/option.flan new file mode 100644 index 0000000..b0db48f --- /dev/null +++ b/web/examples/option.flan @@ -0,0 +1,13 @@ +(defconst nums [4 i32] [4 8 15 16]) + +;; `some` unwraps Some and early-returns None from *this* function. +(defn doubled-first [s [i32]] (Option i32) + (Some (* 2 (some (index-of-i32 s 15))))) + +(defn main [] + (match (doubled-first (slice nums 0 4)) + (Some i) (do (print-i64 (i64 i)) (newline)) ; 4 + None (print-line "not found")) + (match (index-of-i32 (slice nums 0 4) 99) + (Some i) (do (print-i64 (i64 i)) (newline)) + None (print-line "not found"))) diff --git a/web/examples/option.out b/web/examples/option.out new file mode 100644 index 0000000..ec95506 --- /dev/null +++ b/web/examples/option.out @@ -0,0 +1,3 @@ +4 +not found +exit 0 diff --git a/web/examples/pkg.flan b/web/examples/pkg.flan new file mode 100644 index 0000000..d2a5986 --- /dev/null +++ b/web/examples/pkg.flan @@ -0,0 +1,8 @@ +;; The directory is the package. Everything it declares arrives qualified. +(import g "geom") + +(defn main [] + (let [v (g/add (g/V2 {:x 3.0 :y 0.0}) + (g/V2 {:x 0.0 :y 4.0}))] + (print-f64 (f64 (g/length v))) + (newline))) diff --git a/web/examples/pkg.out b/web/examples/pkg.out new file mode 100644 index 0000000..db7e0b9 --- /dev/null +++ b/web/examples/pkg.out @@ -0,0 +1,2 @@ +5 +exit 0 diff --git a/web/examples/restart.flan b/web/examples/restart.flan new file mode 100644 index 0000000..5a54698 --- /dev/null +++ b/web/examples/restart.flan @@ -0,0 +1,24 @@ +(defstruct AssetMissing [id i32]) + +(defvar cleanups i64) + +(defn load [n i32] i32 + (signal (AssetMissing {:id n})) + 100) + +(defn middle [n i32] i32 + (defer (set cleanups (+ cleanups 1))) ; runs on the transfer too + (+ (load n) 1)) + +(defn fetch [n i32] i32 + (restart-case (middle n) ; its value if nothing transfers + (use-placeholder [] -1) + (retry [] 7))) + +(defn main [] + (print-i64 (i64 (fetch 1))) (newline) ; 101 — nothing handled it + + (handler-bind [(AssetMissing [c] (invoke-restart 'use-placeholder))] + (print-i64 (i64 (fetch 2))) (newline)) ; -1 + + (print-i64 cleanups) (newline)) ; 2 — the defer ran both times diff --git a/web/examples/restart.out b/web/examples/restart.out new file mode 100644 index 0000000..8f9fc1b --- /dev/null +++ b/web/examples/restart.out @@ -0,0 +1,4 @@ +101 +-1 +2 +exit 0 diff --git a/web/examples/shimdemo.flan b/web/examples/shimdemo.flan new file mode 100644 index 0000000..4be3119 --- /dev/null +++ b/web/examples/shimdemo.flan @@ -0,0 +1,7 @@ +(defstruct Vector2 [x f32 y f32]) + +(declare-c get-mouse-position [] Vector2 "GetMousePosition") + +(defn main [] + (print-f64 (f64 (.x (get-mouse-position)))) + (newline)) diff --git a/web/examples/structs.flan b/web/examples/structs.flan new file mode 100644 index 0000000..e24d6dd --- /dev/null +++ b/web/examples/structs.flan @@ -0,0 +1,17 @@ +(defstruct Cursor + [src [u8] ; a non-owning slice + pos i32]) ; no initialiser means zeroed + +(defn peek [c (Ptr Cursor)] u8 + (if (< (.pos c) (len (.src c))) + (at (.src c) (.pos c)) + 0)) + +(defn advance [c (Ptr Cursor)] + (set (.pos c) (+ (.pos c) 1))) ; field access derefs one level + +(defn main [] + (let [c (Cursor {:src (bytes "hi")})] ; pos omitted, so pos is 0 + (print-i64 (i64 (peek (addr c)))) (newline) + (advance (addr c)) + (print-i64 (i64 (peek (addr c)))) (newline))) diff --git a/web/examples/structs.out b/web/examples/structs.out new file mode 100644 index 0000000..5a91d35 --- /dev/null +++ b/web/examples/structs.out @@ -0,0 +1,3 @@ +104 +105 +exit 0