Give the page a section on the two printers it never mentioned

print and println had no line anywhere on index.html — a builtin the reader
meets in the first example and is never told about. There is a section for
them now, between arrays and the prelude: what the walk covers, that an enum
comes back as its name and a Ptr does not get followed, that a string is raw
at the top and quoted inside a structure, and that the depth and span caps
are what keep a grid from printing a screenful. printing.flan is beside the
other examples so check.sh has been green on every line of it.

The prelude's table loses its output row, because the prelude has no output
functions left, and the fifty-odd example blocks follow the files they quote.

Two pinned things moved. sand-headless prints 15595743031174623232 where it
printed -2851001042534928384: same bits, read unsigned, because the cast that
made it signed is gone. bounds.flan's trap moved from column 28 to 19, which
is where (at xs i) now starts on that line.

And the indirection-cell illustration is defer.flan rather than hello.flan.
hello.flan cannot show a cell any more: its one call was to a prelude
function, and println is compiler-provided, so the smallest program makes no
Flan-to-Flan call at all. defer.flan's main calls work, and is already on the
page a few sections up.
This commit is contained in:
Joseph Ferano 2026-09-12 05:37:56 +07:00
parent 96ab4c9cf0
commit b8019a96d5
23 changed files with 207 additions and 125 deletions

View File

@ -9,15 +9,15 @@
(defn main []
(set (at grid 1 2) 7)
(print-i64 (i64 (at grid 1 2))) (newline) ; 7
(print-i64 (i64 (len palette))) (newline) ; 4
(print (at grid 1 2)) (println "") ; 7
(print (len palette)) (println "") ; 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
(print (at grid 1 0)) (println "") ; 5 — the same storage
(print (sum-i32 row)) (println "")) ; 12
;; (zeroed) is a memset, not an allocation.
(set grid (zeroed))
(print-i64 (i64 (at grid 1 2))) (newline)) ; 0
(print (at grid 1 2)) (println "")) ; 0

View File

@ -8,5 +8,5 @@
(retry [] 7)))
(defn main []
(print-i64 (i64 (load 1)))
(newline))
(print (load 1))
(println ""))

View File

@ -5,6 +5,6 @@
;; catches it — the same message, and the program stops where it happened.
(defn main []
(let [i 7]
(print-line "before")
(print-i64 (i64 (at xs i)))
(print-line "unreachable")))
(println "before")
(print (at xs i))
(println "unreachable")))

View File

@ -1,3 +1,3 @@
before
bounds.flan:9:28: index 7 is out of bounds for length 3
bounds.flan:9:19: index 7 is out of bounds for length 3
exit 134

View File

@ -11,5 +11,5 @@
(defn main []
(agent/start "/tmp/flan-breakdemo.sock")
(print-i64 (i64 (load 1)))
(newline))
(print (load 1))
(println ""))

View File

@ -8,9 +8,9 @@
(defn main []
(load-all) ; no handler: a no-op
(print-i64 seen) (newline) ; 0
(print seen) (println "") ; 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
(print seen) (println "")) ; 3

View File

@ -9,10 +9,10 @@
(defn countdown [n i32]
(let [i n]
(while (> i 0)
(print-i64 (i64 i))
(print-str " ")
(print i)
(print " ")
(set i (- i 1)))
(newline)))
(println "")))
(defn first-even [s [i32]] (Option i32)
(dotimes [i (len s)]
@ -21,10 +21,10 @@
None)
(defn main []
(print-line (classify -3))
(println (classify -3))
(countdown 4)
(unless false
(print-line "unless runs when the test is false"))
(println "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")))
(Some n) (do (print n) (println ""))
None (println "none")))

View File

@ -1,11 +1,11 @@
(defn work [n i32] i32
(defer (print-line "second"))
(defer (print-line "first")) ; innermost-first at exit
(defer (println "second"))
(defer (println "first")) ; innermost-first at exit
(when (< n 0)
(return 0)) ; runs both defers above it
(print-line "body")
(println "body")
n)
(defn main []
(print-i64 (i64 (work 3)))
(newline))
(print (work 3))
(println ""))

View File

@ -10,5 +10,5 @@
(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)))
(println (key-name :space))
(println (key-name :left)))

View File

@ -3,4 +3,4 @@
(declare cos-f64 [x f64] f64 "cos")
(defn main []
(print-f64 (cos-f64 0.0)) (newline))
(print (cos-f64 0.0)) (println ""))

View File

@ -6,7 +6,7 @@
(defvar grid [rows [cols u32]]) ; BSS, rows*cols*4 bytes
(defn main []
(print-i64 (i64 cell-size)) (newline)
(print-f64 (f64 gravity)) (newline)
(print-i64 (i64 current-color)) (newline)
(print-i64 (i64 (at grid 2 3))) (newline))
(print cell-size) (println "")
(print gravity) (println "")
(print current-color) (println "")
(print (at grid 2 3)) (println ""))

View File

@ -1,2 +1,2 @@
(defn main []
(print-line "hello from flan"))
(println "hello from flan"))

View File

@ -2,7 +2,7 @@
(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)
(print (+ big 2)) (println "")
(print (* x 2.5)) (println "")
(print (bit-xor (<< 1 8) 255)) (println "")
0))

View File

@ -6,8 +6,8 @@
(defn main []
(match (doubled-first (slice nums 0 4))
(Some i) (do (print-i64 (i64 i)) (newline)) ; 4
None (print-line "not found"))
(Some i) (do (print i) (println "")) ; 4
None (println "not found"))
(match (index-of-i32 (slice nums 0 4) 99)
(Some i) (do (print-i64 (i64 i)) (newline))
None (print-line "not found")))
(Some i) (do (print i) (println ""))
None (println "not found")))

View File

@ -5,5 +5,5 @@
(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)))
(print (g/length v))
(println "")))

View File

@ -14,7 +14,7 @@
(set (at room 2) 5) ; a fixed array or slice element
(set (deref p) (Enemy {:hp 3 :name "wisp"})) ; a whole-object store
(print-i64 (i64 (.hp e))) (newline)
(print-line (.name e))
(print-i64 (i64 (at room 2))) (newline)
(print-i64 (i64 spawned)) (newline)))
(print (.hp e)) (println "")
(println (.name e))
(print (at room 2)) (println "")
(print spawned) (println "")))

View File

@ -0,0 +1,13 @@
(defenum Key [space 32 left 263])
(defstruct Enemy [hp i32 name string key Key])
(defn look-up [k Key] (Option i32)
(if (= k :space) (Some 32) None))
(defn main []
(println 42) ; an i32, uncast
(println 1.5)
(println (Enemy {:hp 3 :name "wisp" :key :left}))
(println (look-up :space))
(println (look-up :left))
(print "no newline: ") (println true))

View File

@ -0,0 +1,7 @@
42
1.5
(Enemy {:hp 3 :name "wisp" :key :left})
(some 32)
none
no newline: true
exit 0

View File

@ -55,7 +55,7 @@ for pair in \
'handle:(defvar h (Handle i32))' \
'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))' \
'deferblock:(defn f [] i32 (let [x 1] (defer (println "a")) x))' \
'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
@ -66,10 +66,13 @@ do
done
rm -f "$here/.q.flan"
# The cell and the transfer channel, from a real --dev emit of hello.flan.
ir=$("$FLAN" emit --dev "$here/hello.flan" 2>/dev/null)
want "cell global" "$(printf '%s\n' "$ir" | grep '^@"flan.cell.print-line"')"
want "cell load" "$(printf '%s\n' "$ir" | grep 'load ptr, ptr @"flan.cell.print-line"')"
# The cell and the transfer channel, from a real --dev emit. hello.flan cannot
# show a cell any more: println is compiler-provided rather than a Flan
# function, so the smallest program makes no Flan-to-Flan call at all. defer.flan
# is the smallest one on the page that does — its main calls work.
ir=$("$FLAN" emit --dev "$here/defer.flan" 2>/dev/null)
want "cell global" "$(printf '%s\n' "$ir" | grep '^@"flan.cell.work"')"
want "cell load" "$(printf '%s\n' "$ir" | grep 'load ptr, ptr @"flan.cell.work"')"
want "xfer channel" "$(printf '%s\n' "$ir" | grep 'define {} @"flan.main"')"
# The generated C, from flan shim.

View File

@ -16,9 +16,9 @@
(retry [] 7)))
(defn main []
(print-i64 (i64 (fetch 1))) (newline) ; 101 — nothing handled it
(print (fetch 1)) (println "") ; 101 — nothing handled it
(handler-bind [(AssetMissing [c] (invoke-restart 'use-placeholder))]
(print-i64 (i64 (fetch 2))) (newline)) ; -1
(print (fetch 2)) (println "")) ; -1
(print-i64 cleanups) (newline)) ; 2 — the defer ran both times
(print cleanups) (println "")) ; 2 — the defer ran both times

View File

@ -3,5 +3,5 @@
(declare-c get-mouse-position [] Vector2 "GetMousePosition")
(defn main []
(print-f64 (f64 (.x (get-mouse-position))))
(newline))
(print (.x (get-mouse-position)))
(println ""))

View File

@ -12,6 +12,6 @@
(defn main []
(let [c (Cursor {:src (bytes "hi")})] ; pos omitted, so pos is 0
(print-i64 (i64 (peek (addr c)))) (newline)
(print (peek (addr c))) (println "")
(advance (addr c))
(print-i64 (i64 (peek (addr c)))) (newline)))
(print (peek (addr c))) (println "")))

View File

@ -200,6 +200,7 @@ footer { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--ru
<li><a href="#control">Control flow</a></li>
<li><a href="#defer">defer</a></li>
<li><a href="#arrays">Arrays and slices</a></li>
<li><a href="#printing">Printing</a></li>
<li><a href="#prelude">The prelude</a></li>
<li><a href="#packages">Packages</a></li>
<li><a href="#conditions">Conditions and restarts</a></li>
@ -286,7 +287,7 @@ produced. <code>run</code> builds to a temporary file and execs it.</p>
<p>The smallest program:</p>
<pre><code>(defn main []
(print-line "hello from flan"))</code></pre>
(println "hello from flan"))</code></pre>
<p>The entry point is <code>(defn main [args [string]] i32)</code>. Both the parameter
and the return type are optional: omitting <code>args</code> means the program ignores
@ -337,10 +338,10 @@ heap is involved.</p>
(set (at room 2) 5) ; a fixed array or slice element
(set (deref p) (Enemy {:hp 3 :name "wisp"})) ; a whole-object store
(print-i64 (i64 (.hp e))) (newline)
(print-line (.name e))
(print-i64 (i64 (at room 2))) (newline)
(print-i64 (i64 spawned)) (newline)))</code></pre>
(print (.hp e)) (println "")
(println (.name e))
(print (at room 2)) (println "")
(print spawned) (println "")))</code></pre>
<pre><code class="sh">3
wisp
@ -361,13 +362,13 @@ wisp
;; catches it — the same message, and the program stops where it happened.
(defn main []
(let [i 7]
(print-line "before")
(print-i64 (i64 (at xs i)))
(print-line "unreachable")))</code></pre>
(println "before")
(print (at xs i))
(println "unreachable")))</code></pre>
<pre><code class="sh">$ flan run bounds.flan
before
bounds.flan:9:28: index 7 is out of bounds for length 3
bounds.flan:9:19: index 7 is out of bounds for length 3
$ echo $?
134</code></pre>
@ -408,9 +409,9 @@ have one type, and every conversion is written as a cast:</p>
(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 (&lt;&lt; 1 8) 255))) (newline)
(print (+ big 2)) (println "")
(print (* x 2.5)) (println "")
(print (bit-xor (&lt;&lt; 1 8) 255)) (println "")
0))</code></pre>
<pre><code class="sh">42
@ -455,9 +456,9 @@ its fields, and omitted fields are zeroed.</p>
(defn main []
(let [c (Cursor {:src (bytes "hi")})] ; pos omitted, so pos is 0
(print-i64 (i64 (peek (addr c)))) (newline)
(print (peek (addr c))) (println "")
(advance (addr c))
(print-i64 (i64 (peek (addr c)))) (newline)))</code></pre>
(print (peek (addr c))) (println "")))</code></pre>
<pre><code class="sh">104
105</code></pre>
@ -466,7 +467,8 @@ its fields, and omitted fields are zeroed.</p>
byte is a <code>u8</code> — but there is a byte literal, so <code>\h</code> is 104 and
<code>\space</code> is 32, and the prelude's <code>digit?</code> reads as
<code>(and (&gt;= b \0) (&lt;= b \9))</code>. To see a byte as a letter rather than as a
number, print a slice of them with <code>print-bytes</code>.</p>
number, print a slice of them: <code>print</code> writes a <code>[u8]</code> as
its bytes.</p>
<p>An enum is an <code>i32</code> at run time and its own type in the checker. A
keyword at a call site resolves against the parameter's enum type at compile time, so a
@ -484,8 +486,8 @@ typo is an error there rather than a wrong number later.</p>
(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)))</code></pre>
(println (key-name :space))
(println (key-name :left)))</code></pre>
<pre><code class="sh">space
an arrow</code></pre>
@ -515,10 +517,10 @@ functions need no forward declaration. Globals come in two kinds:</p>
(defvar grid [rows [cols u32]]) ; BSS, rows*cols*4 bytes
(defn main []
(print-i64 (i64 cell-size)) (newline)
(print-f64 (f64 gravity)) (newline)
(print-i64 (i64 current-color)) (newline)
(print-i64 (i64 (at grid 2 3))) (newline))</code></pre>
(print cell-size) (println "")
(print gravity) (println "")
(print current-color) (println "")
(print (at grid 2 3)) (println ""))</code></pre>
<pre><code class="sh">5
0.05
@ -551,10 +553,10 @@ whose type matters is named at the top level rather than written inline.</p>
(defn countdown [n i32]
(let [i n]
(while (&gt; i 0)
(print-i64 (i64 i))
(print-str " ")
(print i)
(print " ")
(set i (- i 1)))
(newline)))
(println "")))
(defn first-even [s [i32]] (Option i32)
(dotimes [i (len s)]
@ -563,13 +565,13 @@ whose type matters is named at the top level rather than written inline.</p>
None)
(defn main []
(print-line (classify -3))
(println (classify -3))
(countdown 4)
(unless false
(print-line "unless runs when the test is false"))
(println "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")))</code></pre>
(Some n) (do (print n) (println ""))
None (println "none")))</code></pre>
<pre><code class="sh">negative
4 3 2 1
@ -604,11 +606,11 @@ on nothing else today. <code>some</code> unwraps <code>Some</code> and early-ret
(defn main []
(match (doubled-first (slice nums 0 4))
(Some i) (do (print-i64 (i64 i)) (newline)) ; 4
None (print-line "not found"))
(Some i) (do (print i) (println "")) ; 4
None (println "not found"))
(match (index-of-i32 (slice nums 0 4) 99)
(Some i) (do (print-i64 (i64 i)) (newline))
None (print-line "not found")))</code></pre>
(Some i) (do (print i) (println ""))
None (println "not found")))</code></pre>
<pre><code class="sh">4
not found</code></pre>
@ -620,16 +622,16 @@ not found</code></pre>
has not executed yet and must not fire.</p>
<pre><code>(defn work [n i32] i32
(defer (print-line "second"))
(defer (print-line "first")) ; innermost-first at exit
(defer (println "second"))
(defer (println "first")) ; innermost-first at exit
(when (&lt; n 0)
(return 0)) ; runs both defers above it
(print-line "body")
(println "body")
n)
(defn main []
(print-i64 (i64 (work 3)))
(newline))</code></pre>
(print (work 3))
(println ""))</code></pre>
<pre><code class="sh">body
first
@ -660,18 +662,18 @@ It is a place: <code>(set (at grid r c) v)</code> and <code>(addr (at grid r c))
(defn main []
(set (at grid 1 2) 7)
(print-i64 (i64 (at grid 1 2))) (newline) ; 7
(print-i64 (i64 (len palette))) (newline) ; 4
(print (at grid 1 2)) (println "") ; 7
(print (len palette)) (println "") ; 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
(print (at grid 1 0)) (println "") ; 5 — the same storage
(print (sum-i32 row)) (println "")) ; 12
;; (zeroed) is a memset, not an allocation.
(set grid (zeroed))
(print-i64 (i64 (at grid 1 2))) (newline)) ; 0</code></pre>
(print (at grid 1 2)) (println "")) ; 0</code></pre>
<pre><code class="sh">7
4
@ -682,17 +684,72 @@ It is a place: <code>(set (at grid r c) v)</code> and <code>(addr (at grid r c))
<p>A reversed range — <code>lo</code> greater than <code>hi</code> — traps, rather than
yielding a huge unsigned length.</p>
<h2 id="printing">Printing</h2>
<p><code>println</code> prints a value and a newline; <code>print</code> is the same
walk without the newline. There is one of each and they take any type, but neither is a
function and neither is overloading: the compiler walks the argument's type where the
call is written and emits the printing for it. Nothing is decided at run time — a Flan
value carries no header, so nothing at run time could say what it is — and there is no
user-supplied printer to choose between.</p>
<pre><code>(defenum Key [space 32 left 263])
(defstruct Enemy [hp i32 name string key Key])
(defn look-up [k Key] (Option i32)
(if (= k :space) (Some 32) None))
(defn main []
(println 42) ; an i32, uncast
(println 1.5)
(println (Enemy {:hp 3 :name "wisp" :key :left}))
(println (look-up :space))
(println (look-up :left))
(print "no newline: ") (println true))</code></pre>
<pre><code class="sh">42
1.5
(Enemy {:hp 3 :name "wisp" :key :left})
(some 32)
none
no newline: true</code></pre>
<p>The walk covers every integer and float type, <code>bool</code>, <code>Unit</code>,
<code>string</code>, <code>[u8]</code>, enums, <code>Ptr</code>, <code>Option</code>,
structs, fixed arrays and slices. An enum member comes back as its name: the value is
an <code>i32</code> by the time the backend sees it, so the name is recovered here from
the checker's table, and a value outside the declared members falls through to the
number, which is what you would want to see. A <code>Ptr</code> prints as
<code>&lt;ptr&gt;</code> and is never followed — it is the one thing that could make
the walk cycle, and dereferencing a pointer on someone else's behalf is not safe.</p>
<p>A string prints raw at the top level and quoted-and-escaped inside a structure.
Those are not in conflict: <code>(println "hello")</code> has to print
<code>hello</code> or it is useless, and the <code>name</code> field above has to be
quoted or it could not be told from the punctuation around it.</p>
<p>The walk is bounded in depth and in span, so a deeply nested value or a
<code>[100 [100 u32]]</code> grid prints <code>...</code> rather than a screenful — and,
since the walk is unrolled at compile time, rather than putting ten thousand printing
sites in the module.</p>
<p>That the walk takes the argument's own type matters more here than it would in a
language that widens implicitly. Nothing widens implicitly in this one, so a printer
that named a type would need a cast written at every call — and a <code>u64</code>
above 2<sup>63</sup> put through a signed one comes out negative. <code>print</code>
takes the value as it is and prints the number it holds.</p>
<h2 id="prelude">The prelude</h2>
<p>The prelude is written in Flan, all but one line of it, and prepended to every
program, so nothing in it needs importing. Printing is deliberately not a primitive:
<code>write-stdout</code> is the one output primitive and everything above it is
ordinary Flan.</p>
program, so nothing in it needs importing. It holds no printing of its own:
<code>print</code> and <code>println</code> are the compiler's, and
<code>write-stdout</code> — the one output primitive — is what they are written
over.</p>
<div class="scroll">
<table>
<tr><th>Group</th><th>Names</th></tr>
<tr><td>output</td><td><code>print-str</code>, <code>print-bytes</code>, <code>print-i64</code>, <code>print-f64</code>, <code>print-line</code>, <code>newline</code></td></tr>
<tr><td>slices of <code>i32</code></td><td><code>swap-i32!</code>, <code>reverse-i32!</code>, <code>sort-i32!</code>, <code>index-of-i32</code>, <code>min-i32</code>, <code>max-i32</code>, <code>sum-i32</code></td></tr>
<tr><td>bytes</td><td><code>bytes=?</code>, <code>starts-with?</code>, <code>ends-with?</code>, <code>index-of-byte</code>, <code>index-of-bytes</code>, <code>trim</code>, <code>digit?</code>, <code>space?</code></td></tr>
<tr><td>parsing</td><td><code>parse-i64</code>, <code>parse-f64</code></td></tr>
@ -754,8 +811,8 @@ and no ceremony.</p>
(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)))</code></pre>
(print (g/length v))
(println "")))</code></pre>
<pre><code class="sh">5</code></pre>
@ -827,12 +884,12 @@ normally leaves the signaller to carry on — the accumulation case:</p>
(defn main []
(load-all) ; no handler: a no-op
(print-i64 seen) (newline) ; 0
(print seen) (println "") ; 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</code></pre>
(print seen) (println "")) ; 3</code></pre>
<pre><code class="sh">0
3</code></pre>
@ -860,12 +917,12 @@ first, before the clause body starts.</p>
(retry [] 7)))
(defn main []
(print-i64 (i64 (fetch 1))) (newline) ; 101 — nothing handled it
(print (fetch 1)) (println "") ; 101 — nothing handled it
(handler-bind [(AssetMissing [c] (invoke-restart 'use-placeholder))]
(print-i64 (i64 (fetch 2))) (newline)) ; -1
(print (fetch 2)) (println "")) ; -1
(print-i64 cleanups) (newline)) ; 2 — the defer ran both times</code></pre>
(print cleanups) (println "")) ; 2 — the defer ran both times</code></pre>
<pre><code class="sh">101
-1
@ -947,8 +1004,8 @@ and an exit status of 134:</p>
(retry [] 7)))
(defn main []
(print-i64 (i64 (load 1)))
(newline))</code></pre>
(print (load 1))
(println ""))</code></pre>
<pre><code class="sh">$ flan run boom.flan
unhandled Missing
@ -966,7 +1023,7 @@ is generated; a Flan string crosses as ptr+len, exactly as it is stored.</p>
<pre><code>(declare cos-f64 [x f64] f64 "cos")
(defn main []
(print-f64 (cos-f64 0.0)) (newline))</code></pre>
(print (cos-f64 0.0)) (println ""))</code></pre>
<pre><code class="sh">1</code></pre>
@ -1072,20 +1129,22 @@ bound at link time cannot be made to notice one, so a <code>--dev</code> build r
every Flan-to-Flan call through a cell — a mutable global holding the address of the
function that is current.</p>
<p>Here is the whole of <code>hello.flan</code> through
<code>flan emit --dev</code>:</p>
<p>Here is the <code>defer</code> example from <a href="#defer">above</a> — its
<code>main</code> calls <code>work</code> — through <code>flan emit --dev</code>:</p>
<pre><code class="llvm">@"flan.cell.print-line" = global ptr @"flan.print-line"
<pre><code class="llvm">@"flan.cell.work" = global ptr @"flan.work"
define {} @"flan.main"(ptr %xfer) {
entry:
%t1 = load ptr, ptr @"flan.cell.print-line"
%t2 = call {} %t1(%slice { ptr @".str.36", i64 15 }, ptr %xfer)</code></pre>
%t7 = alloca %slice
%t1 = load ptr, ptr @"flan.cell.work"
%t2 = call i32 %t1(i32 3, ptr %xfer)</code></pre>
<p>The call site loads the cell rather than naming <code>@"flan.print-line"</code>
<p>The call site loads the cell rather than naming <code>@"flan.work"</code>
directly. The signature carries <code>ptr %xfer</code> — the transfer channel from
<a href="#conditions">conditions</a>, on every Flan function, release builds
included.</p>
included. <code>println</code> is not in there: it is not a Flan function, so there is
no call to route and no cell for it.</p>
<p>Redefinition is then one store, below a microsecond. Three rules follow. A
redefinition module declares every <em>global</em> external, so globals live in the host
@ -1242,10 +1301,10 @@ module, and the headless sand acceptance program prints the same 64-bit hash und
it does natively:</p>
<pre><code class="sh">$ flan run test/programs/sand-headless.flan
-2851001042534928384
15595743031174623232
$ flan build test/programs/sand-headless.flan --target=wasm32-wasi -o sand.wasm
$ node --no-warnings test/wasm-run.mjs sand.wasm
-2851001042534928384</code></pre>
15595743031174623232</code></pre>
<p>The RNG is written in Flan rather than called from libc for that number: a grid hash
is only a regression test if the sequence is byte-identical on both targets. It holds at