Check the quoted blocks too, since a paraphrase reads exactly like a quotation
The blocks that are not programs were the ones that had drifted: the usage text had lost its indentation and the refusal table had trimmed "(see plan.org)" off every message, so the page was showing wording the compiler does not print.
This commit is contained in:
parent
86ef557433
commit
a5e0c01224
62
web/examples/quotes.sh
Normal file
62
web/examples/quotes.sh
Normal file
@ -0,0 +1,62 @@
|
||||
#!/bin/sh
|
||||
# The blocks on index.html that are not programs in this directory are quoted
|
||||
# from somewhere: a repository file, or the output of a command. Each one is
|
||||
# re-derived here and looked for in the page, so a quote cannot go stale
|
||||
# quietly. check.sh covers the runnable blocks; this covers the rest.
|
||||
#
|
||||
# $ dune build && sh web/examples/quotes.sh
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
root=$(cd "$here/../.." && pwd)
|
||||
FLAN=${FLAN:-$root/_build/default/bin/main.exe}
|
||||
page=$here/../index.html
|
||||
fail=0
|
||||
|
||||
# Look for a literal string in the page, allowing for HTML escaping of < and >.
|
||||
want() {
|
||||
esc=$(printf '%s' "$2" | sed -e 's/&/\&/g' -e 's/</\</g' -e 's/>/\>/g')
|
||||
if grep -qF -- "$esc" "$page"; then echo "ok $1"; else
|
||||
echo "FAIL $1"
|
||||
echo " not on the page: $2"
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
|
||||
# The usage text, from the binary itself.
|
||||
want "flan usage" "$("$FLAN" 2>&1 | sed -n 2p)"
|
||||
|
||||
# calc-me, the first acceptance program, still answers what the page says.
|
||||
want "calc-me" "$("$FLAN" run "$root/calc-me.flan" '1 + 2 * (3 - 0.5) / 2')"
|
||||
|
||||
# The refusal messages quoted in "Not implemented yet" and under defer.
|
||||
for pair in \
|
||||
'vec:(defvar xs (Vec i32))' \
|
||||
'map:(defvar m (Map string i32))' \
|
||||
'result:(defn f [] (Result i32 i32) None)' \
|
||||
'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))'
|
||||
do
|
||||
name=${pair%%:*}; src=${pair#*:}
|
||||
printf '%s\n' "$src" > "$here/.q.flan"
|
||||
msg=$("$FLAN" check "$here/.q.flan" 2>&1 | sed 's/^[^ ]*: //')
|
||||
want "refusal: $name" "$msg"
|
||||
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"')"
|
||||
want "xfer channel" "$(printf '%s\n' "$ir" | grep 'define {} @"flan.main"')"
|
||||
|
||||
# The generated C, from flan shim.
|
||||
want "shim wrapper" "$("$FLAN" shim "$here/shimdemo.flan" | grep 'GetMousePosition();')"
|
||||
|
||||
# Lines quoted verbatim from repository files.
|
||||
want "raylib binding" "$(grep -F 'unload-texture' "$root/vendor/raylib/raylib.flan")"
|
||||
want "agent declare" "$(grep -F 'flan_agent_poll' "$root/vendor/agent/agent.flan" | head -1)"
|
||||
want "conditions.org" "$(grep -F 'Innermost frame offering the name wins' "$root/conditions.org")"
|
||||
want "renderer" "$(grep -F ':r 17 :g 34 :b 51 :a 68' "$root/NEXT.md")"
|
||||
|
||||
exit $fail
|
||||
146
web/index.html
146
web/index.html
@ -211,11 +211,12 @@ $ ./_build/default/bin/main.exe run calc-me.flan "1 + 2 * (3 - 0.5) / 2"
|
||||
|
||||
<p>Call that binary <code>flan</code>. Its subcommands:</p>
|
||||
|
||||
<pre><code class="sh">flan (read|parse|check|emit|shim) <file.flan>...
|
||||
flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] [--target=wasm32-wasi]
|
||||
flan run <file.flan> [args...]
|
||||
flan reload <program.flan> <forms.flan> [-o out.so]
|
||||
flan dev <program.flan> [-s socket]</code></pre>
|
||||
<pre><code class="sh">$ flan
|
||||
usage: flan (read|parse|check|emit|shim) <file.flan>...
|
||||
flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] [--target=wasm32-wasi]
|
||||
flan run <file.flan> [args...]
|
||||
flan reload <program.flan> <forms.flan> [-o out.so]
|
||||
flan dev <program.flan> [-s socket]</code></pre>
|
||||
|
||||
<p><code>read</code>, <code>parse</code>, <code>check</code>, <code>emit</code> and
|
||||
<code>shim</code> each stop the pipeline one stage further along and print what it
|
||||
@ -260,14 +261,36 @@ struct is shared mutably by passing its address down the call chain.</p>
|
||||
<p>Places — the forms <code>set</code> accepts — are a fixed list, not an extensible
|
||||
<code>setf</code>:</p>
|
||||
|
||||
<pre><code>(set x v) ; a local or a defvar
|
||||
(set (.field x) v) ; x may be a struct or a (Ptr S)
|
||||
(set (at a i ...) v) ; a fixed array or a slice element
|
||||
(set (deref p) v) ; a whole-object store through a pointer</code></pre>
|
||||
<pre><code>(defstruct Enemy [hp i32 name string])
|
||||
|
||||
(defvar spawned i32)
|
||||
(defconst room-size 4)
|
||||
(defvar room [room-size i32])
|
||||
|
||||
;; `set` takes a fixed list of forms, not an extensible setf.
|
||||
(defn main []
|
||||
(let [e (Enemy {:hp 10 :name "slime"})
|
||||
p (addr e)]
|
||||
(set spawned (+ spawned 1)) ; a local or a defvar
|
||||
(set (.hp e) 7) ; a struct field
|
||||
(set (.hp p) 8) ; through a (Ptr Enemy) — derefs one level
|
||||
(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>
|
||||
|
||||
<pre><code class="sh">3
|
||||
wisp
|
||||
5
|
||||
1</code></pre>
|
||||
|
||||
<p><code>.field</code> and <code>at</code> dereference exactly one pointer level, which
|
||||
is why <code>(set (.pos c) …)</code> is legal when <code>c</code> is a
|
||||
<code>(Ptr Cursor)</code>.</p>
|
||||
is why <code>(set (.hp p) 8)</code> above is legal when <code>p</code> is a
|
||||
<code>(Ptr Enemy)</code>. Note the last two stores: the whole-object store through
|
||||
<code>p</code> overwrote <code>e</code> itself, so <code>hp</code> reads 3 and not 8.</p>
|
||||
|
||||
<h3>Bounds are checked</h3>
|
||||
|
||||
@ -375,6 +398,8 @@ number later.</p>
|
||||
: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)))</code></pre>
|
||||
|
||||
@ -401,7 +426,20 @@ functions need no forward declaration. Globals come in two kinds:</p>
|
||||
<pre><code>(defconst cell-size 5) ; a compile-time constant
|
||||
(defconst gravity f32 0.05) ; with its type named
|
||||
(defvar current-color i32) ; zeroed storage
|
||||
(defvar grid [rows [cols u32]]) ; BSS, rows*cols*4 bytes</code></pre>
|
||||
(defconst rows 3)
|
||||
(defconst cols 4)
|
||||
(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>
|
||||
|
||||
<pre><code class="sh">5
|
||||
0.05
|
||||
0
|
||||
0</code></pre>
|
||||
|
||||
<p>A <code>defconst</code> the checker consumed — an array length, for instance — is
|
||||
part of the shape of the program. One it did not is only ever bytes in memory, which
|
||||
@ -450,7 +488,7 @@ whose type matters is named at the top level rather than written inline.</p>
|
||||
None (print-line "none")))</code></pre>
|
||||
|
||||
<pre><code class="sh">negative
|
||||
4 3 2 1
|
||||
4 3 2 1
|
||||
unless runs when the test is false
|
||||
8</code></pre>
|
||||
|
||||
@ -513,8 +551,7 @@ second
|
||||
<code>let</code>, a loop or a branch, rather than accepted with surprising scope. Block
|
||||
scoping it is real work and is not done:</p>
|
||||
|
||||
<pre><code class="sh">defer must be a top-level form in a function body — block-scoped defer
|
||||
is not implemented yet (milestone 4)</code></pre>
|
||||
<pre><code class="sh">defer must be a top-level form in a function body — block-scoped defer is not implemented yet (milestone 4)</code></pre>
|
||||
|
||||
<h2 id="arrays">Arrays and slices</h2>
|
||||
|
||||
@ -612,7 +649,8 @@ and no ceremony.</p>
|
||||
(defn length [v V2] f32
|
||||
(sqrt-f32 (+ (* (.x v) (.x v)) (* (.y v) (.y v)))))</code></pre>
|
||||
|
||||
<pre><code>;; pkg.flan
|
||||
<pre><code>;; pkg.flan — the directory is the package, and everything it declares
|
||||
;; arrives qualified by the alias this import chose.
|
||||
(import g "geom")
|
||||
|
||||
(defn main []
|
||||
@ -668,12 +706,12 @@ signalling end says <em>here is something notable, here is the data</em>, and an
|
||||
caller decides what to do about it — or decides nothing, in which case the signaller
|
||||
carries on.</p>
|
||||
|
||||
<pre><code>(signal c) ; Unit. Handler returns → carry on. No handler → no-op.
|
||||
<pre><code>(signal c) ; Unit. Handler returns -> carry on. No handler -> no-op.
|
||||
(error c) ; Never. Only a transfer gets past; else the program stops.
|
||||
|
||||
(handler-bind [(Type [c] body ...) ...] body ...)
|
||||
(handler-bind [(Type [c] body ...) ...] body ...) ; match by type, no hierarchy
|
||||
|
||||
(restart-case BODY ; BODY and every clause have the same type
|
||||
(restart-case BODY ; BODY and every clause have the same type = the form's
|
||||
(name [] CLAUSE) ...)
|
||||
|
||||
(invoke-restart 'name) ; Never. Innermost frame offering the name wins.</code></pre>
|
||||
@ -841,7 +879,11 @@ is generated; a Flan string crosses as ptr+len, exactly as it is stored.</p>
|
||||
signature, and the compiler writes the wrapper. This is what raylib's package is made
|
||||
of — one line per binding:</p>
|
||||
|
||||
<pre><code>(declare-c draw-texture [t Texture2D x i32 y i32 tint Color] "DrawTexture")</code></pre>
|
||||
<pre><code>(declare-c unload-texture [texture Texture2D] "UnloadTexture")
|
||||
|
||||
(declare-c draw-texture
|
||||
[texture Texture2D x i32 y i32 tint Color]
|
||||
"DrawTexture")</code></pre>
|
||||
|
||||
<p>The reason for the wrapper is that <strong>an aggregate's calling convention is not
|
||||
part of its layout</strong>. On x86-64, clang gives raylib's own prototypes
|
||||
@ -932,9 +974,20 @@ 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>
|
||||
|
||||
<pre><code class="llvm">@"flan.cell.bump" = global ptr @"flan.bump" ; the host defines it
|
||||
%p = load ptr, ptr @"flan.cell.bump" ; every call site
|
||||
%r = call i64 %p()</code></pre>
|
||||
<p>Here is the whole of <code>hello.flan</code> through
|
||||
<code>flan emit --dev</code>, which is the shortest thing that shows it:</p>
|
||||
|
||||
<pre><code class="llvm">@"flan.cell.print-line" = global ptr @"flan.print-line"
|
||||
|
||||
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>
|
||||
|
||||
<p>Two things are visible there at once. The call site loads the cell rather than naming
|
||||
<code>@"flan.print-line"</code> directly, and the signature carries <code>ptr %xfer</code>
|
||||
— the transfer channel from <a href="#conditions">conditions</a>, which every Flan
|
||||
function has, release builds included.</p>
|
||||
|
||||
<p>Redefinition is then one store, below a microsecond, which is what makes a
|
||||
frame-boundary swap a non-event. Three rules fall out of it. A redefinition module
|
||||
@ -946,12 +999,24 @@ address inside a module's text, so unloading it would leave call sites pointing
|
||||
unmapped memory. Old code is never unloaded, which is also why a thread mid-execution
|
||||
finishes safely in the old version.</p>
|
||||
|
||||
<p><strong>The agent.</strong> <code>vendor/agent</code> is a package like any other:
|
||||
three calls, a listener thread, and a single-producer ring.</p>
|
||||
<p><strong>The agent.</strong> <code>vendor/agent</code> is a package like any other: a
|
||||
listener thread, a single-producer ring, and three calls. This is the whole of
|
||||
<code>agent.flan</code> — no aggregate crosses the boundary, so a plain
|
||||
<code>declare</code> does it and there is no shim.</p>
|
||||
|
||||
<pre><code>(agent/start path) ; listen on a unix socket; once, at startup
|
||||
(agent/poll) ; install whatever has arrived; returns how many
|
||||
(agent/wait ms) ; the same, but waits for something first</code></pre>
|
||||
<pre><code>(declare start-raw [path string] i32 "flan_agent_start")
|
||||
(declare poll-raw [] i32 "flan_agent_poll")
|
||||
(declare wait-raw [ms i32] i32 "flan_agent_wait")
|
||||
|
||||
(defn start [path string] i32 (start-raw path))
|
||||
(defn poll [] i32 (poll-raw))
|
||||
(defn wait [ms i32] i32 (wait-raw ms))</code></pre>
|
||||
|
||||
<p><code>(agent/start path)</code> listens on a unix socket, once, at startup.
|
||||
<code>(agent/poll)</code> installs whatever has arrived and returns how many.
|
||||
<code>(agent/wait ms)</code> is the same but waits for something first, which is what a
|
||||
headless test uses so that a reload is deterministic rather than a race against the
|
||||
frame rate.</p>
|
||||
|
||||
<p>The split between loading and installing is the design. <code>dlopen</code> relocates
|
||||
a module and takes the loader lock — milliseconds, unbounded — so it happens on the
|
||||
@ -1014,9 +1079,10 @@ there, in the thunk. What comes back looks like this:</p>
|
||||
<pre><code class="sh">big 18446744073709551615
|
||||
col :blue
|
||||
(.pos b) (V {:x 1.5 :y 0})
|
||||
b (Blob {:id 7 :name "sandy \"quoted\"" :pos (V {:x 1.5 :y 0})})
|
||||
b (Blob {:id 7 :name "sandy \"quoted\"" :pos (V {:x 1.5 :y 0}) :tags [ 0 42 0]})
|
||||
(slice (.tags b) 0 3) [ 0 42 0]
|
||||
(rl/get-color 0x11223344) (rl/Color {:r 17 :g 34 :b 51 :a 68})</code></pre>
|
||||
(rl/get-color 0x11223344) (rl/Color {:r 17 :g 34 :b 51 :a 68})
|
||||
sim/grid [ [ 0 0 0 0 0 0 0 0 ...] [ 0 ... ] ...]</code></pre>
|
||||
|
||||
<p>A pointer is never followed — it renders as <code><ptr></code> — because it is
|
||||
the only thing that could make the walk cycle, and dereferencing one a REPL was handed is
|
||||
@ -1102,16 +1168,16 @@ to, and the tests assert on the reason.</p>
|
||||
<div class="scroll">
|
||||
<table>
|
||||
<tr><th>You write</th><th>The compiler says</th></tr>
|
||||
<tr><td><code>(Vec T)</code></td><td>(Vec T) is not implemented yet — milestone 6</td></tr>
|
||||
<tr><td><code>(Map K V)</code></td><td>(Map K V) is not implemented yet — milestone 6</td></tr>
|
||||
<tr><td><code>(Result T E)</code></td><td>(Result T E) is not implemented yet — milestone 6</td></tr>
|
||||
<tr><td><code>(Handle T)</code></td><td>(Handle T) is not implemented yet — milestone 6</td></tr>
|
||||
<tr><td><code>(try …)</code></td><td>try (Result) is not implemented yet — milestone 6</td></tr>
|
||||
<tr><td>a union type</td><td>the union type Shape is not implemented yet — milestone 6</td></tr>
|
||||
<tr><td><code>(Fn [T] R)</code></td><td>a function type is not implemented yet — milestone 5</td></tr>
|
||||
<tr><td><code>(fn [x i32] …)</code></td><td>calling something other than a named function is not implemented yet — milestone 5</td></tr>
|
||||
<tr><td>a type variable</td><td>generic code over the type variable a is not implemented yet — milestone 5</td></tr>
|
||||
<tr><td><code>'sym</code></td><td>a quoted symbol (restart names) is not implemented yet — milestone 6</td></tr>
|
||||
<tr><td><code>(Vec T)</code></td><td>(Vec T) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||
<tr><td><code>(Map K V)</code></td><td>(Map K V) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||
<tr><td><code>(Result T E)</code></td><td>(Result T E) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||
<tr><td><code>(Handle T)</code></td><td>(Handle T) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||
<tr><td><code>(try …)</code></td><td>try (Result) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||
<tr><td>a union type</td><td>the union type Shape is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||
<tr><td><code>(Fn [T] R)</code></td><td>a function type is not implemented yet — milestone 5 (see plan.org)</td></tr>
|
||||
<tr><td><code>(fn [x i32] …)</code></td><td>calling something other than a named function is not implemented yet — milestone 5 (see plan.org)</td></tr>
|
||||
<tr><td>a type variable</td><td>generic code over the type variable a is not implemented yet — milestone 5 (see plan.org)</td></tr>
|
||||
<tr><td><code>'sym</code></td><td>a quoted symbol (restart names) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||
<tr><td><code>(defmacro …)</code></td><td>parses, but is not expanded: running a macro means compiling it and loading it into the compiler, which is not wired up yet</td></tr>
|
||||
<tr><td><code>`(a ~b)</code></td><td>is read, but not expanded: macro expansion is not wired up yet</td></tr>
|
||||
<tr><td><code>handler-case</code></td><td>handler-case is not implemented yet</td></tr>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user