The reference page says what the language is
# Conflicts: # FIX.org
This commit is contained in:
commit
91ba4a0743
73
FIX.org
73
FIX.org
@ -6467,3 +6467,76 @@ location on a macro call pins one raised *before* expansion — the four
|
|||||||
parameter-list refusals in ~Expand.check_call~ — or one on a node the macro
|
parameter-list refusals in ~Expand.check_call~ — or one on a node the macro
|
||||||
built, and both still report at the call. The suite was green on the first run
|
built, and both still report at the call. The suite was green on the first run
|
||||||
after the change.
|
after the change.
|
||||||
|
* The reference page says what the language is, 2026-09-21
|
||||||
|
|
||||||
|
~web/index.html~ had drifted behind the compiler, mostly in one direction: it
|
||||||
|
described the language as it was before ~dyn~ existed and before ownership
|
||||||
|
tracking was repealed. A correction pass, not a rewrite — structure, section
|
||||||
|
ids and voice kept, claims fixed.
|
||||||
|
|
||||||
|
What was false. The tagline, the lede and ~Values and memory~ each said there
|
||||||
|
is no garbage collector; the ~What it is not~ list refused immutable
|
||||||
|
collections because "clear ownership is the only thing that removes the need
|
||||||
|
for a collector" and refused dynamic typing outright; the types table gave
|
||||||
|
~Vec~, ~Map~ and ~Pool~ as moving on assignment, which the 2026-09-18 repeal
|
||||||
|
removed; ~Printing~ and the dev loop's expression evaluation both said a Flan
|
||||||
|
value carries no header with no exception for a ~dyn~; the usage block was
|
||||||
|
missing ~-O0..-O3~, ~--warn-memory~ and the ~js~ target and still gave ~flan
|
||||||
|
run~'s old argument shape; the structs example quoted ~(bytes "hi")~ where the
|
||||||
|
file beside it says ~bytes-view~; the ~Not implemented yet~ table quoted a
|
||||||
|
milestone-5 message for a bare lowercase type name that the compiler no longer
|
||||||
|
prints; ~Further reading~ called spec-memory.md frozen and about ownership;
|
||||||
|
the footer named the retired ~dev-loop~ branch.
|
||||||
|
|
||||||
|
What was added. A ~dyn~ section after ~Types~ (id ~dyn~, one TOC entry, no
|
||||||
|
existing id touched): one word the runtime knows the contents of and the
|
||||||
|
checker does not, writing the type is the opt-in, only those values are
|
||||||
|
collected, a numeric cast moves the question to run time where
|
||||||
|
~flan_dyn_cast_kind~ answers it, ~--no-gc~ names every ~dyn~ and refuses, and
|
||||||
|
the roots are an explicit shadow stack because wasm32 cannot be made to scan
|
||||||
|
its own. Also: the operator arities (the orderings chain, ~!=~ is
|
||||||
|
all-distinct, ~%~ and the shifts take two), ~slice~'s three arities and its
|
||||||
|
string case with the ~as-slice~ note, ~def~/~defonce~/~defconst~ as a table of
|
||||||
|
what a re-run does, an untyped global being a ~dyn~, the parameter-vector
|
||||||
|
pairing rule, ~bytes~ against ~bytes-view~, and a ~Function values~ subsection
|
||||||
|
covering ~fn~, capture, ~Fn~ against ~CFn~ and the one-way widening.
|
||||||
|
|
||||||
|
The class facility was the other thing the page got wrong, and it got it
|
||||||
|
wrong the other way round: ~Not implemented yet~ said the managed ~class~
|
||||||
|
plan.org describes "is a plan and not a feature", and the Emacs section said
|
||||||
|
managed classes were the eventual way through a changed layout. Both landed
|
||||||
|
on 2026-09-20. So the ~dyn~ section carries ~Classes and generic functions~ —
|
||||||
|
the four forms, the two dispatch styles as one mechanism, ~:else~,
|
||||||
|
~NoMethod~, the ~#point{ :x 3 :y 4}~ rendering, a generic being one function
|
||||||
|
so a method reaches the call sites already compiled — and the Emacs section
|
||||||
|
says a ~defclass~ is the shape that evolves, with the lazy per-instance
|
||||||
|
migration and why a flat ~defstruct~ cannot have it. What is left unbuilt of
|
||||||
|
it, and now named as such, is the named-slot constructor spelling and the
|
||||||
|
user migration hook. The ~dyn~ section also gained maps, vectors, keywords
|
||||||
|
and ~nil~, since a class's slots are map keys and none of that was on the
|
||||||
|
page either.
|
||||||
|
|
||||||
|
Four new example programs under ~web/examples/~ — compare.flan, dyn.flan,
|
||||||
|
fnvalues.flan, classes.flan — and a ~no-gc~ needle in quotes.sh over
|
||||||
|
dyn.flan, so the refusal the page quotes is re-derived like every other
|
||||||
|
transcript on it.
|
||||||
|
|
||||||
|
** @page was green over a needle that could not be derived
|
||||||
|
~sand hash~ had been reporting "nothing to compare against" since sand.flan
|
||||||
|
grew ~(import edn "vendor:edn")~: the alias's deps never named ~vendor/edn~,
|
||||||
|
so the headless program could not resolve the package in ~_build~ and the
|
||||||
|
needle came up empty. quotes.sh reports an empty needle as a failure, which is
|
||||||
|
why it was visible at all, but it reads as "whatever this quotes has moved"
|
||||||
|
rather than as a missing dependency. One ~glob_files~ in test/dune.
|
||||||
|
|
||||||
|
** Not fixed, because it cannot be checked from here
|
||||||
|
The ~Targets and builds~ section still says survey.sh reports 103 MATCH.
|
||||||
|
FIX.org's 2026-09-20 session close says 130. Running @x86 was out of scope for
|
||||||
|
this pass, so the number is left as it stands rather than replaced with one
|
||||||
|
nobody ran.
|
||||||
|
|
||||||
|
** The top-level usage does not mention --no-gc
|
||||||
|
~flan build --no-gc~ works and ~flan build~'s own usage line lists it;
|
||||||
|
the usage that bare ~flan~ prints does not. The page quotes the latter
|
||||||
|
verbatim, so it does not name the flag either. A compiler change, not a page
|
||||||
|
change.
|
||||||
|
|||||||
@ -350,6 +350,11 @@
|
|||||||
; alias was green only because `dune test` had already put the file in
|
; alias was green only because `dune test` had already put the file in
|
||||||
; _build, and in CI it sits inside @checks where the same thing hides it.
|
; _build, and in CI it sits inside @checks where the same thing hides it.
|
||||||
(file %{workspace_root}/sand.flan)
|
(file %{workspace_root}/sand.flan)
|
||||||
|
; And the package sand.flan imports, for the same reason: without it the
|
||||||
|
; headless program cannot resolve vendor:edn in _build and the sand-hash
|
||||||
|
; needle comes up empty, which quotes.sh reports as "whatever this quotes
|
||||||
|
; has moved" rather than as a missing dependency.
|
||||||
|
(glob_files %{workspace_root}/vendor/edn/*)
|
||||||
; breakdemo.flan is built --dev, so it imports the agent; raylib is here for
|
; breakdemo.flan is built --dev, so it imports the agent; raylib is here for
|
||||||
; the binding line quotes.sh greps out of it.
|
; the binding line quotes.sh greps out of it.
|
||||||
(glob_files %{workspace_root}/vendor/agent/*)
|
(glob_files %{workspace_root}/vendor/agent/*)
|
||||||
|
|||||||
29
web/examples/classes.flan
Normal file
29
web/examples/classes.flan
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
;; A class is a named dyn map with a shape tag. Its slots are names and
|
||||||
|
;; carry no types, and its constructor is the class's own name, positional.
|
||||||
|
(defclass point [x y])
|
||||||
|
(defclass circle [r])
|
||||||
|
|
||||||
|
;; CLOS-style: the generic states the return type once, and each method
|
||||||
|
;; dispatches on the class of its first argument.
|
||||||
|
(defgeneric area [self] dyn)
|
||||||
|
(defmethod area point [p] (* (get p :x) (get p :y)))
|
||||||
|
(defmethod area circle [c] (* 3 (get c :r) (get c :r)))
|
||||||
|
|
||||||
|
;; Clojure-style: the generic's body is the dispatch value, and a method
|
||||||
|
;; names the value it answers for. :else is the arm everything falls to.
|
||||||
|
(defmulti describe [x] dyn (get x :kind))
|
||||||
|
(defmethod describe :square [s] (get s :side))
|
||||||
|
(defmethod describe :else [s] "something else")
|
||||||
|
|
||||||
|
(defn main [] ()
|
||||||
|
(let [p (point 3 4)]
|
||||||
|
(println (area p))
|
||||||
|
(println (area (circle 2)))
|
||||||
|
;; A slot is a map key: get, put and has-key? are how one is read.
|
||||||
|
(println (get p :y))
|
||||||
|
;; class-of answers the tag, and nil for anything that is not an instance.
|
||||||
|
(println (class-of p))
|
||||||
|
(println (class-of 7))
|
||||||
|
(println p)
|
||||||
|
(println (describe {:kind :square :side 9}))
|
||||||
|
(println (describe {:kind :blob}))))
|
||||||
9
web/examples/classes.out
Normal file
9
web/examples/classes.out
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
12
|
||||||
|
12
|
||||||
|
4
|
||||||
|
:point
|
||||||
|
nil
|
||||||
|
#point{ :x 3 :y 4}
|
||||||
|
9
|
||||||
|
something else
|
||||||
|
exit 0
|
||||||
11
web/examples/compare.flan
Normal file
11
web/examples/compare.flan
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
;; The arithmetic operators and the comparisons both take a run of operands.
|
||||||
|
;; The orderings chain: each neighbouring pair is compared, and every pair has
|
||||||
|
;; to hold. != is the one that does not chain — it asks whether the operands
|
||||||
|
;; are all distinct, so a value repeated anywhere in the run makes it false.
|
||||||
|
(defn main [] ()
|
||||||
|
(println (+ 1 2 3 4))
|
||||||
|
(println (< 1 2 3))
|
||||||
|
(println (< 1 3 2))
|
||||||
|
(println (= 4 4 4))
|
||||||
|
(println (!= 1 2 3))
|
||||||
|
(println (!= 1 2 1)))
|
||||||
7
web/examples/compare.out
Normal file
7
web/examples/compare.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
10
|
||||||
|
true
|
||||||
|
false
|
||||||
|
true
|
||||||
|
true
|
||||||
|
false
|
||||||
|
exit 0
|
||||||
13
web/examples/dyn.flan
Normal file
13
web/examples/dyn.flan
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
;; Writing dyn is how a value opts in. The parameter has no type written, so
|
||||||
|
;; it is a dyn; the return type says dyn too, and both ends of twice are then
|
||||||
|
;; a word the runtime knows the contents of and the checker does not.
|
||||||
|
(defn twice [x] dyn
|
||||||
|
(+ x x))
|
||||||
|
|
||||||
|
;; A numeric cast opens the box. (i64 d) compiles for any dyn d, and what the
|
||||||
|
;; box actually holds is answered when the program runs.
|
||||||
|
(defn main [] ()
|
||||||
|
(println (twice 21))
|
||||||
|
(println (twice 1.5))
|
||||||
|
(let [d (twice 21)]
|
||||||
|
(println (+ (i64 d) 1))))
|
||||||
4
web/examples/dyn.out
Normal file
4
web/examples/dyn.out
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
42
|
||||||
|
3
|
||||||
|
43
|
||||||
|
exit 0
|
||||||
15
web/examples/fnvalues.flan
Normal file
15
web/examples/fnvalues.flan
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
;; An Fn is {code, env}: it may have captured, and the caller neither knows
|
||||||
|
;; nor cares. A CFn is the bare code address, one word, and cannot capture.
|
||||||
|
(defn apply-fn [f (Fn [i32] i32) x i32] i32 (f x))
|
||||||
|
(defn apply-cfn [f (CFn [i32] i32) x i32] i32 (f x))
|
||||||
|
|
||||||
|
(defn bump [x i32] i32 (+ x 1))
|
||||||
|
|
||||||
|
(defn main [] ()
|
||||||
|
(let [n 10]
|
||||||
|
;; An fn takes its types from the position it is written in.
|
||||||
|
(println (apply-fn (fn [x] (+ x n)) 5)) ; captures n
|
||||||
|
(println (apply-fn bump 5)) ; a defn captures nothing
|
||||||
|
(println (apply-cfn bump 5)) ; so it fits either position
|
||||||
|
;; Widening is one way: a CFn goes where an Fn is wanted.
|
||||||
|
(println (apply-fn (fn [x] (* x 2)) 21))))
|
||||||
5
web/examples/fnvalues.out
Normal file
5
web/examples/fnvalues.out
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
15
|
||||||
|
6
|
||||||
|
6
|
||||||
|
42
|
||||||
|
exit 0
|
||||||
@ -47,6 +47,14 @@ want "calc-me" "$("$FLAN" run "$root/calc-me.flan" '1 + 2 * (3 - 0.5) / 2')"
|
|||||||
# that the two agree; only the native half is cheap enough to check here.
|
# that the two agree; only the native half is cheap enough to check here.
|
||||||
want "sand hash" "$("$FLAN" run "$root/test/programs/sand-headless.flan")"
|
want "sand hash" "$("$FLAN" run "$root/test/programs/sand-headless.flan")"
|
||||||
|
|
||||||
|
# --no-gc, which is the page's claim that a program can have "this carries no
|
||||||
|
# collector" checked rather than believed. dyn.flan holds one on purpose, so
|
||||||
|
# the flag must refuse it; the page quotes the first line of what it says.
|
||||||
|
nogc=${TMPDIR:-/tmp}/flan-page-nogc.$$
|
||||||
|
want "no-gc" "$("$FLAN" build "$here/dyn.flan" --no-gc -o "$nogc" 2>&1 \
|
||||||
|
| sed 's/^[^ ]*: //' | sed -n 1p)"
|
||||||
|
rm -f "$nogc"
|
||||||
|
|
||||||
# The two cross-target refusals, in the compiler's own words.
|
# The two cross-target refusals, in the compiler's own words.
|
||||||
want "run --target" "$("$FLAN" run "$here/hello.flan" --target=wasm32-wasi 2>&1)"
|
want "run --target" "$("$FLAN" run "$here/hello.flan" --target=wasm32-wasi 2>&1)"
|
||||||
|
|
||||||
|
|||||||
431
web/index.html
431
web/index.html
@ -224,6 +224,7 @@ footer { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--ru
|
|||||||
<li><a href="#start">Getting started</a></li>
|
<li><a href="#start">Getting started</a></li>
|
||||||
<li><a href="#values">Values and memory</a></li>
|
<li><a href="#values">Values and memory</a></li>
|
||||||
<li><a href="#types">Types</a></li>
|
<li><a href="#types">Types</a></li>
|
||||||
|
<li><a href="#dyn">dyn</a></li>
|
||||||
<li><a href="#structs">Structs and enums</a></li>
|
<li><a href="#structs">Structs and enums</a></li>
|
||||||
<li><a href="#functions">Functions</a></li>
|
<li><a href="#functions">Functions</a></li>
|
||||||
<li><a href="#control">Control flow</a></li>
|
<li><a href="#control">Control flow</a></li>
|
||||||
@ -276,14 +277,15 @@ footer { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--ru
|
|||||||
fill="currentColor">flan<tspan fill="var(--accent)">.</tspan></text>
|
fill="currentColor">flan<tspan fill="var(--accent)">.</tspan></text>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="tagline">A statically typed Lisp for game development. Clojure's brackets,
|
<p class="tagline">A statically typed Lisp for game development. Clojure's brackets,
|
||||||
C's memory and value model, no garbage collector.</p>
|
C's memory and value model, and a collector only <code>dyn</code> values reach.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<p class="lede">Flan compiles s-expressions to LLVM IR and then to a native binary.
|
<p class="lede">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
|
A typed value carries no header, so a Flan struct is exactly its C struct, and a
|
||||||
collector, so nothing runs between frames that you did not write. And a running
|
program that writes <code>dyn</code> nowhere has no collector in it — nothing runs
|
||||||
program can be edited: a function recompiled in Emacs is installed into the live
|
between frames that you did not write. And a running program can be edited: a
|
||||||
process at its next frame boundary, in about twenty milliseconds.</p>
|
function recompiled in Emacs is installed into the live process at its next frame
|
||||||
|
boundary, in about twenty milliseconds.</p>
|
||||||
|
|
||||||
<p>This page describes the compiler <em>as it is</em>, not as it is planned. Where
|
<p>This page describes the compiler <em>as it is</em>, not as it is planned. Where
|
||||||
something is designed but not built, it says so and gives the message the compiler
|
something is designed but not built, it says so and gives the message the compiler
|
||||||
@ -297,7 +299,8 @@ rather than programs.</p>
|
|||||||
<h2 id="what">What Flan is</h2>
|
<h2 id="what">What Flan is</h2>
|
||||||
|
|
||||||
<p>A minimal Lisp for games. In one line: Odin with a Lisp frontend and a live REPL.
|
<p>A minimal Lisp for games. In one line: Odin with a Lisp frontend and a live REPL.
|
||||||
Types are mandatory and inference makes them feel optional; memory is manual; the
|
Types are written at function boundaries and inferred everywhere else, and a
|
||||||
|
parameter left unwritten is <a href="#dyn"><code>dyn</code></a>; memory is manual; the
|
||||||
frontend is OCaml, and the default backend writes LLVM IR as text and hands it to
|
frontend is OCaml, and the default backend writes LLVM IR as text and hands it to
|
||||||
<code>clang</code>. There is a second one, off by default, that emits x86-64 by hand —
|
<code>clang</code>. There is a second one, off by default, that emits x86-64 by hand —
|
||||||
see <a href="#targets">targets and builds</a>.</p>
|
see <a href="#targets">targets and builds</a>.</p>
|
||||||
@ -307,11 +310,11 @@ see <a href="#targets">targets and builds</a>.</p>
|
|||||||
<ul>
|
<ul>
|
||||||
<li><strong>Not a Common Lisp and not a Clojure.</strong> No numeric tower, no CLOS,
|
<li><strong>Not a Common Lisp and not a Clojure.</strong> No numeric tower, no CLOS,
|
||||||
no <code>format</code>, no lazy seqs, no persistent collections, no JVM.</li>
|
no <code>format</code>, no lazy seqs, no persistent collections, no JVM.</li>
|
||||||
<li><strong>No immutable collection types.</strong> Structure sharing destroys clear
|
<li><strong>No immutable collection types.</strong> No persistent maps or vectors
|
||||||
ownership, and clear ownership is the only thing that removes the need for a
|
and no structure sharing. Value structs that copy on assignment replace them.</li>
|
||||||
collector. Value structs that copy on assignment replace them.</li>
|
<li><strong>Not dynamically typed by default.</strong> A type is written or
|
||||||
<li><strong>No dynamic typing.</strong> A tag word on every value is exactly the
|
inferred, and a value of one carries no tag. <code>dyn</code> is the opt-in, and
|
||||||
header cost that dropping the GC was meant to avoid.</li>
|
the tag and the collector are what it costs — see <a href="#dyn">dyn</a>.</li>
|
||||||
<li><strong>No consoles</strong>, and no live-image development at SBCL's level.</li>
|
<li><strong>No consoles</strong>, and no live-image development at SBCL's level.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@ -332,8 +335,8 @@ usage: flan (read|parse|check|emit|shim) <file.flan>...
|
|||||||
flan emit <file.flan> [--x86] [--dev] [--debug] [--no-bounds-checks]
|
flan emit <file.flan> [--x86] [--dev] [--debug] [--no-bounds-checks]
|
||||||
flan import-c <header.h> [package.flan...] [clang flags...]
|
flan import-c <header.h> [package.flan...] [clang flags...]
|
||||||
flan generate-c <package-dir>
|
flan generate-c <package-dir>
|
||||||
flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] [--debug] [--sanitize] [--x86] [--target=wasm32-wasi|web]
|
flan build <file.flan> [-o out] [-O0|-O1|-O2|-O3] [--no-bounds-checks] [--dev] [--debug] [--sanitize] [--x86] [--warn-memory] [--target=wasm32-wasi|web|js]
|
||||||
flan run <file.flan> [args...]
|
flan run <file.flan> [build flags...] [--] [program args...]
|
||||||
flan reload <program.flan> <forms.flan> [-o out.so] [--x86]
|
flan reload <program.flan> <forms.flan> [-o out.so] [--x86]
|
||||||
flan dev <program.flan> [-s socket] [--x86]</code></pre>
|
flan dev <program.flan> [-s socket] [--x86]</code></pre>
|
||||||
|
|
||||||
@ -364,9 +367,12 @@ type is not: <code>()</code> is unit, and a <code>main</code> that returns it ex
|
|||||||
|
|
||||||
<h2 id="values">Values and memory</h2>
|
<h2 id="values">Values and memory</h2>
|
||||||
|
|
||||||
<p>There is no garbage collector and no hidden allocation. Every local is a stack
|
<p>There is no hidden allocation. Every local is a stack slot; reading one is a load,
|
||||||
slot; reading one is a load, assigning to one is a store, and a store of an aggregate
|
assigning to one is a store, and a store of an aggregate <em>is</em> the copy. A
|
||||||
<em>is</em> the copy. A struct is its C layout with nothing added.</p>
|
struct is its C layout with nothing added. The one value that is not laid out this
|
||||||
|
way is a <code>dyn</code>, which is allocated by the runtime and collected; writing
|
||||||
|
the type is how a program asks for that, and <a href="#dyn">dyn</a> is where it is
|
||||||
|
described.</p>
|
||||||
|
|
||||||
<p>Four rules carry most of the model:</p>
|
<p>Four rules carry most of the model:</p>
|
||||||
|
|
||||||
@ -513,14 +519,15 @@ notation reads as exactly one data item.</p>
|
|||||||
<tr><td><code>string</code></td><td>a byte slice with no NUL</td><td>ptr + len</td></tr>
|
<tr><td><code>string</code></td><td>a byte slice with no NUL</td><td>ptr + len</td></tr>
|
||||||
<tr><td><code>[T]</code></td><td>slice, non-owning</td><td>ptr + len</td></tr>
|
<tr><td><code>[T]</code></td><td>slice, non-owning</td><td>ptr + len</td></tr>
|
||||||
<tr><td><code>[n T]</code></td><td>fixed array, a value</td><td>n inline items</td></tr>
|
<tr><td><code>[n T]</code></td><td>fixed array, a value</td><td>n inline items</td></tr>
|
||||||
<tr><td><code>(Vec T)</code></td><td>growable, owning — <em>moves</em> on assignment</td><td>ptr + len + cap + its allocator</td></tr>
|
<tr><td><code>(Vec T)</code></td><td>growable, owning — copies as its header, so the copies alias one buffer</td><td>ptr + len + cap + its allocator</td></tr>
|
||||||
<tr><td><code>(Map K V)</code></td><td>open addressing, owning — <em>moves</em>. The only map spelling: braces in type position are not a type</td><td>data + len + log2cap + its allocator</td></tr>
|
<tr><td><code>(Map K V)</code></td><td>open addressing, owning, copies the same way. The only map spelling: braces in type position are not a type</td><td>data + len + log2cap + its allocator</td></tr>
|
||||||
<tr><td><code>(Pool T)</code></td><td>generational slab storage, owning — <em>moves</em></td><td>items + slots + its allocator</td></tr>
|
<tr><td><code>(Pool T)</code></td><td>generational slab storage, owning, copies the same way</td><td>items + slots + its allocator</td></tr>
|
||||||
<tr><td><code>(Handle T)</code></td><td>a reference into a pool that reports a dead referent</td><td>index and generation packed into an <code>i64</code></td></tr>
|
<tr><td><code>(Handle T)</code></td><td>a reference into a pool that reports a dead referent</td><td>index and generation packed into an <code>i64</code></td></tr>
|
||||||
<tr><td><code>(Ptr T)</code></td><td>raw pointer</td><td>a pointer</td></tr>
|
<tr><td><code>(Ptr T)</code></td><td>raw pointer</td><td>a pointer</td></tr>
|
||||||
<tr><td><code>(Option T)</code></td><td><code>Some</code> / <code>None</code></td><td>tag byte + T</td></tr>
|
<tr><td><code>(Option T)</code></td><td><code>Some</code> / <code>None</code></td><td>tag byte + T</td></tr>
|
||||||
<tr><td><code>(Fn [T ...] R)</code></td><td>a function value, which may have captured</td><td>a code address and an environment pointer</td></tr>
|
<tr><td><code>(Fn [T ...] R)</code></td><td>a function value, which may have captured</td><td>a code address and an environment pointer</td></tr>
|
||||||
<tr><td><code>(CFn [T ...] R)</code></td><td>a function value that cannot capture — the <code>C</code> is what a C function pointer would need, not a way to reach C today</td><td>a pointer</td></tr>
|
<tr><td><code>(CFn [T ...] R)</code></td><td>a function value that cannot capture — the <code>C</code> is what a C function pointer would need, not a way to reach C today</td><td>a pointer</td></tr>
|
||||||
|
<tr><td><code>dyn</code></td><td>a value the runtime knows the type of and the checker does not — see <a href="#dyn">dyn</a></td><td>one word, on a collected heap</td></tr>
|
||||||
<tr><td><code>Allocator</code></td><td>an opaque builtin: a proc, its data and a capability set</td><td>a pointer to that</td></tr>
|
<tr><td><code>Allocator</code></td><td>an opaque builtin: a proc, its data and a capability set</td><td>a pointer to that</td></tr>
|
||||||
<tr><td><code>$t</code></td><td>a type variable — see <a href="#generics">generics</a></td><td>whatever it is instantiated at</td></tr>
|
<tr><td><code>$t</code></td><td>a type variable — see <a href="#generics">generics</a></td><td>whatever it is instantiated at</td></tr>
|
||||||
<tr><td>a struct</td><td>value type</td><td>fields in declaration order</td></tr>
|
<tr><td>a struct</td><td>value type</td><td>fields in declaration order</td></tr>
|
||||||
@ -532,8 +539,8 @@ notation reads as exactly one data item.</p>
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><strong>There is no implicit widening.</strong> Both operands of a binary operator
|
<p><strong>There is no implicit widening.</strong> Every operand of an arithmetic or
|
||||||
have one type, and every conversion is written as a cast:</p>
|
comparison form has one type, and every conversion is written as a cast:</p>
|
||||||
|
|
||||||
<pre><code>(defn main [] i32
|
<pre><code>(defn main [] i32
|
||||||
(let [n 40 ; i32, inferred
|
(let [n 40 ; i32, inferred
|
||||||
@ -558,6 +565,44 @@ operand's width is a compile error, and a computed one is masked to the width.
|
|||||||
<code>>></code> is arithmetic on a signed type and logical on an unsigned
|
<code>>></code> is arithmetic on a signed type and logical on an unsigned
|
||||||
one.</p>
|
one.</p>
|
||||||
|
|
||||||
|
<h3>Operators take a run of operands</h3>
|
||||||
|
|
||||||
|
<p>Two operands is the floor and there is no ceiling.
|
||||||
|
<code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>min</code>,
|
||||||
|
<code>max</code> and the three bitwise combining operators fold left over as many
|
||||||
|
arguments as they are given. <code>%</code> and the shifts are not in that set and
|
||||||
|
take two: a chain of remainders or of shifts has no reading agreed on in advance, so
|
||||||
|
there an arity error is the useful answer. The orderings — <code><</code>, <code><=</code>, <code>></code>,
|
||||||
|
<code>>=</code> — <em>chain</em>: each neighbouring pair is compared and every
|
||||||
|
pair has to hold, so <code>(< 1 2 3)</code> asks whether the run is increasing.
|
||||||
|
<code>=</code> chains the same way. <code>!=</code> does not: it asks whether the
|
||||||
|
operands are <strong>all distinct</strong>, comparing every pair rather than the
|
||||||
|
neighbouring ones, so <code>(!= 1 2 1)</code> is false.</p>
|
||||||
|
|
||||||
|
<pre><code>;; The arithmetic operators and the comparisons both take a run of operands.
|
||||||
|
;; The orderings chain: each neighbouring pair is compared, and every pair has
|
||||||
|
;; to hold. != is the one that does not chain — it asks whether the operands
|
||||||
|
;; are all distinct, so a value repeated anywhere in the run makes it false.
|
||||||
|
(defn main [] ()
|
||||||
|
(println (+ 1 2 3 4))
|
||||||
|
(println (< 1 2 3))
|
||||||
|
(println (< 1 3 2))
|
||||||
|
(println (= 4 4 4))
|
||||||
|
(println (!= 1 2 3))
|
||||||
|
(println (!= 1 2 1)))</code></pre>
|
||||||
|
|
||||||
|
<pre><code class="sh">10
|
||||||
|
true
|
||||||
|
false
|
||||||
|
true
|
||||||
|
true
|
||||||
|
false</code></pre>
|
||||||
|
|
||||||
|
<p><strong>Why the two differ.</strong> "Is this sequence increasing" and "are these
|
||||||
|
values all different" are the two questions a run of operands is actually asked, and
|
||||||
|
they need different comparisons. Chaining <code>!=</code> would answer neither: it
|
||||||
|
would be true of <code>1 2 1</code>, which is not a run of distinct values.</p>
|
||||||
|
|
||||||
<p>An index converts from a narrower integer and never from a wider one. A
|
<p>An index converts from a narrower integer and never from a wider one. A
|
||||||
<code>u32</code> index is fine — anything above 2<sup>31</sup> truncates to a negative
|
<code>u32</code> index is fine — anything above 2<sup>31</sup> truncates to a negative
|
||||||
<code>i32</code> and the unsigned bounds check rejects it. An <code>i64</code> index is
|
<code>i32</code> and the unsigned bounds check rejects it. An <code>i64</code> index is
|
||||||
@ -567,6 +612,185 @@ refused:</p>
|
|||||||
not fit truncates to one that does and would read the wrong element without
|
not fit truncates to one that does and would read the wrong element without
|
||||||
tripping the bounds check</code></pre>
|
tripping the bounds check</code></pre>
|
||||||
|
|
||||||
|
<h2 id="dyn">dyn</h2>
|
||||||
|
|
||||||
|
<p>A <code>dyn</code> is one machine word whose contents the runtime knows and the
|
||||||
|
type checker does not. Every other type on this page is decided where it is written;
|
||||||
|
this one is decided when the program runs, and the runtime keeps a tag beside the
|
||||||
|
value to decide it with.</p>
|
||||||
|
|
||||||
|
<p>Writing <code>dyn</code> is how a value opts in. It is written like any other type
|
||||||
|
— a parameter's type, a return type, a global's type — and a parameter left with no
|
||||||
|
type written is a <code>dyn</code>, which is the shortest way to ask for one. Those
|
||||||
|
values, and only those, live on a collected heap. A program that writes it nowhere
|
||||||
|
pays nothing: no tag, no allocation, no collector.</p>
|
||||||
|
|
||||||
|
<pre><code>;; Writing dyn is how a value opts in. The parameter has no type written, so
|
||||||
|
;; it is a dyn; the return type says dyn too, and both ends of twice are then
|
||||||
|
;; a word the runtime knows the contents of and the checker does not.
|
||||||
|
(defn twice [x] dyn
|
||||||
|
(+ x x))
|
||||||
|
|
||||||
|
;; A numeric cast opens the box. (i64 d) compiles for any dyn d, and what the
|
||||||
|
;; box actually holds is answered when the program runs.
|
||||||
|
(defn main [] ()
|
||||||
|
(println (twice 21))
|
||||||
|
(println (twice 1.5))
|
||||||
|
(let [d (twice 21)]
|
||||||
|
(println (+ (i64 d) 1))))</code></pre>
|
||||||
|
|
||||||
|
<pre><code class="sh">42
|
||||||
|
3
|
||||||
|
43</code></pre>
|
||||||
|
|
||||||
|
<p>One <code>twice</code> is compiled, not one per argument type, and the
|
||||||
|
<code>+</code> in it is the runtime's addition over two tagged words. The second
|
||||||
|
call prints <code>3</code> rather than <code>3.0</code> by the whole-number rule
|
||||||
|
in <a href="#types">types</a>, not by anything <code>dyn</code> does.</p>
|
||||||
|
|
||||||
|
<p>The return type is always written, <code>dyn</code> included. The parameter vector
|
||||||
|
is where the types may be left out, and a vector of bare names is read as names: in
|
||||||
|
<code>(defn f [x y] ...)</code>, <code>y</code> is a second <code>dyn</code>
|
||||||
|
parameter when nothing declares a type called <code>y</code>, and is
|
||||||
|
<code>x</code>'s type when something does. The pairing is decided after every file is
|
||||||
|
loaded and every macro has expanded, so the set of type names it consults is the
|
||||||
|
whole set — but it is the set at a point in time, and writing a
|
||||||
|
<code>(defstruct y ...)</code> elsewhere changes <code>f</code>'s signature with no
|
||||||
|
edit to <code>f</code>.</p>
|
||||||
|
|
||||||
|
<p>A global left without a type is a <code>dyn</code> too:
|
||||||
|
<code>(def x 5)</code> declares one and <code>(def x i32 5)</code> does not. See
|
||||||
|
<a href="#functions">functions</a> for the three global forms.</p>
|
||||||
|
|
||||||
|
<h3>A cast opens the box</h3>
|
||||||
|
|
||||||
|
<p>A numeric cast is how a <code>dyn</code> comes back to a type the checker can
|
||||||
|
see. <code>(i64 d)</code> compiles for any <code>dyn</code> <code>d</code>, because
|
||||||
|
the question of what the box holds is not one the checker can answer; the question
|
||||||
|
moves to run time, where <code>flan_dyn_cast_kind</code> answers it. A box holding
|
||||||
|
something the cast cannot take stops the program where it happened, with the file
|
||||||
|
and the line, the way a bad index does.</p>
|
||||||
|
|
||||||
|
<h3>--no-gc</h3>
|
||||||
|
|
||||||
|
<p><code>flan build --no-gc</code> names every <code>dyn</code> in the program, with
|
||||||
|
its location, and refuses to build. That is how a program has "this carries no
|
||||||
|
collector" checked rather than believed. It is a refusal and not a different
|
||||||
|
lowering: nothing downstream is told the flag was given, so the output of a build
|
||||||
|
that passes it is byte for byte the output of a build that does not.</p>
|
||||||
|
|
||||||
|
<pre><code class="sh">$ flan build dyn.flan --no-gc
|
||||||
|
dyn.flan:4:7: parameter 1 of twice holds a dyn, and --no-gc says this program
|
||||||
|
carries no collector. A dyn value is one the runtime allocates and the collector
|
||||||
|
owns, so there is nothing smaller to compile it to — write the type</code></pre>
|
||||||
|
|
||||||
|
<p>The check runs before reachability, so a <code>dyn</code> in a function nothing
|
||||||
|
calls is still a <code>dyn</code> somebody wrote. A refusal that came and went as the
|
||||||
|
program was edited elsewhere would not be worth having.</p>
|
||||||
|
|
||||||
|
<h3>Maps, vectors and keywords</h3>
|
||||||
|
|
||||||
|
<p>Braces and brackets in expression position write dyn literals:
|
||||||
|
<code>{:a 1 :b "two"}</code> is a dyn map and <code>[1 2 3]</code> is a dyn vector.
|
||||||
|
<code>get</code>, <code>put</code>, <code>has-key?</code>, <code>at</code> and
|
||||||
|
<code>length</code> read and write them, the same names the typed
|
||||||
|
<code>Map</code> and <code>Vec</code> answer to. A keyword is a value here rather
|
||||||
|
than only a way to name an enum member: keywords are interned, so comparing two is
|
||||||
|
comparing two pointers.</p>
|
||||||
|
|
||||||
|
<p>The absent dyn value is written <code>nil</code>. It is not <code>()</code> —
|
||||||
|
unit carries nothing for a dyn word to hold, and boxing it is refused — and
|
||||||
|
<code>(Some nil)</code> cannot be built, because a present absence would make
|
||||||
|
<code>nil</code> and <code>None</code> the same case of an
|
||||||
|
<code>(Option dyn)</code>.</p>
|
||||||
|
|
||||||
|
<h3>Classes and generic functions</h3>
|
||||||
|
|
||||||
|
<p>A class is a named dyn map with a shape tag. <code>defclass</code> names its
|
||||||
|
slots, which carry no types; the constructor is the class's own name and is
|
||||||
|
positional; and <code>class-of</code> answers the tag, or <code>nil</code> for
|
||||||
|
anything that is not an instance. The slots are map keys, so nothing was added to
|
||||||
|
read or write one.</p>
|
||||||
|
|
||||||
|
<p>Dispatch comes in the two styles and they are one mechanism.
|
||||||
|
<code>defgeneric</code> dispatches on the class of the first argument, which is
|
||||||
|
CLOS's rule. <code>defmulti</code> takes a body whose value is the dispatch value,
|
||||||
|
which is Clojure's. Either way a <code>defmethod</code> names the value it answers
|
||||||
|
for — a class name, a keyword, a string, an integer, <code>true</code>,
|
||||||
|
<code>false</code>, or <code>:else</code> for the arm everything falls through to,
|
||||||
|
which is last whatever order it was written in. The generic states the return type
|
||||||
|
once, for every method; a method has no return slot; and every parameter of both is
|
||||||
|
<code>dyn</code>, written or not.</p>
|
||||||
|
|
||||||
|
<pre><code>;; A class is a named dyn map with a shape tag. Its slots are names and
|
||||||
|
;; carry no types, and its constructor is the class's own name, positional.
|
||||||
|
(defclass point [x y])
|
||||||
|
(defclass circle [r])
|
||||||
|
|
||||||
|
;; CLOS-style: the generic states the return type once, and each method
|
||||||
|
;; dispatches on the class of its first argument.
|
||||||
|
(defgeneric area [self] dyn)
|
||||||
|
(defmethod area point [p] (* (get p :x) (get p :y)))
|
||||||
|
(defmethod area circle [c] (* 3 (get c :r) (get c :r)))
|
||||||
|
|
||||||
|
;; Clojure-style: the generic's body is the dispatch value, and a method
|
||||||
|
;; names the value it answers for. :else is the arm everything falls to.
|
||||||
|
(defmulti describe [x] dyn (get x :kind))
|
||||||
|
(defmethod describe :square [s] (get s :side))
|
||||||
|
(defmethod describe :else [s] "something else")
|
||||||
|
|
||||||
|
(defn main [] ()
|
||||||
|
(let [p (point 3 4)]
|
||||||
|
(println (area p))
|
||||||
|
(println (area (circle 2)))
|
||||||
|
;; A slot is a map key: get, put and has-key? are how one is read.
|
||||||
|
(println (get p :y))
|
||||||
|
;; class-of answers the tag, and nil for anything that is not an instance.
|
||||||
|
(println (class-of p))
|
||||||
|
(println (class-of 7))
|
||||||
|
(println p)
|
||||||
|
(println (describe {:kind :square :side 9}))
|
||||||
|
(println (describe {:kind :blob}))))</code></pre>
|
||||||
|
|
||||||
|
<pre><code class="sh">12
|
||||||
|
12
|
||||||
|
4
|
||||||
|
:point
|
||||||
|
nil
|
||||||
|
#point{ :x 3 :y 4}
|
||||||
|
9
|
||||||
|
something else</code></pre>
|
||||||
|
|
||||||
|
<p>An instance renders as <code>#point{ :x 3 :y 4}</code>, Clojure's spelling for a
|
||||||
|
record, and the tag is why two instances of one class compare by their slots while an
|
||||||
|
instance is never equal to a plain map with the same entries. A dispatch that matches
|
||||||
|
no method signals <code>NoMethod</code>, carrying the generic's name and the value
|
||||||
|
the dispatch produced. It is a condition rather than a trap because a miss is
|
||||||
|
something a program can be written to answer; no restart is established at it, which
|
||||||
|
is <code>BoundsError</code>'s decision taken for <code>BoundsError</code>'s
|
||||||
|
reason.</p>
|
||||||
|
|
||||||
|
<p><strong>A generic is one function.</strong> The method bodies are inlined into a
|
||||||
|
chain in its body rather than lifted into functions of their own, so adding a method
|
||||||
|
to a running program is the ordinary redefinition of one name, through the cell the
|
||||||
|
call site already goes through. A method does declare a name of its own —
|
||||||
|
<code>area@:circle</code> — which is what makes re-evaluating one a replacement and
|
||||||
|
evaluating a new one an append; no function is emitted under it.</p>
|
||||||
|
|
||||||
|
<p>The four forms are a pass over the whole declaration list rather than macros:
|
||||||
|
<code>lib/classes.ml</code> runs where <code>declare-c</code>'s shim generation runs.
|
||||||
|
A macro sees one form, and a method may be written above its generic, below it, or
|
||||||
|
arrive at a reload an hour later.</p>
|
||||||
|
|
||||||
|
<h3>Rationale: an explicit root stack</h3>
|
||||||
|
|
||||||
|
<p>The collector is mark-sweep, and it finds its roots from a shadow stack the
|
||||||
|
compiler pushes to rather than by scanning the C stack. Scanning is what a collector
|
||||||
|
normally does and it is cheaper to write, but it needs to know where the stack is and
|
||||||
|
what on it might be a pointer. wasm32 does not let a program look at its own call
|
||||||
|
stack at all, and the language builds for wasm32. An explicit root stack is the same
|
||||||
|
code on every target, which is what makes the collector work there.</p>
|
||||||
|
|
||||||
<h2 id="structs">Structs and enums</h2>
|
<h2 id="structs">Structs and enums</h2>
|
||||||
|
|
||||||
<p>A <code>defstruct</code> is a list of inline name/type pairs. A struct literal names
|
<p>A <code>defstruct</code> is a list of inline name/type pairs. A struct literal names
|
||||||
@ -585,7 +809,7 @@ its fields, and omitted fields are zeroed.</p>
|
|||||||
(set (.pos c) (+ (.pos c) 1))) ; field access derefs one level
|
(set (.pos c) (+ (.pos c) 1))) ; field access derefs one level
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(let [c (Cursor {.src (bytes "hi")})] ; pos omitted, so pos is 0
|
(let [c (Cursor {.src (bytes-view "hi")})] ; pos omitted, so pos is 0
|
||||||
(print (peek (addr c))) (println "")
|
(print (peek (addr c))) (println "")
|
||||||
(advance (addr c))
|
(advance (addr c))
|
||||||
(print (peek (addr c))) (println "")))</code></pre>
|
(print (peek (addr c))) (println "")))</code></pre>
|
||||||
@ -600,6 +824,14 @@ byte is a <code>u8</code> — but there is a byte literal, so <code>\h</code> is
|
|||||||
number, print a slice of them: <code>print</code> writes a <code>[u8]</code> as
|
number, print a slice of them: <code>print</code> writes a <code>[u8]</code> as
|
||||||
its bytes.</p>
|
its bytes.</p>
|
||||||
|
|
||||||
|
<p>There are two ways to see a string's bytes and the difference is whether anything
|
||||||
|
is allocated. <code>(bytes-view s)</code> is the string's own storage seen as a
|
||||||
|
<code>[u8]</code> and costs nothing; it aliases the string, so a literal's view points
|
||||||
|
into <code>.rodata</code> and writing through it traps. <code>(bytes s)</code> and
|
||||||
|
<code>(bytes s allocator)</code> make a writable copy through the allocator — never a
|
||||||
|
hidden <code>malloc</code>, which is the rule every allocating operation follows. The
|
||||||
|
example above wants a view and takes one.</p>
|
||||||
|
|
||||||
<p>An enum is an <code>i32</code> at run time and its own type in the checker. A
|
<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
|
keyword at a call site resolves against the parameter's enum type at compile time, so a
|
||||||
typo is an error there rather than a wrong number later.</p>
|
typo is an error there rather than a wrong number later.</p>
|
||||||
@ -645,7 +877,27 @@ body as the function's return type. Writing the type removes the guess, and a
|
|||||||
mistyped one now says <em>did you mean f64</em> rather than <em>unknown name</em>.</p>
|
mistyped one now says <em>did you mean f64</em> rather than <em>unknown name</em>.</p>
|
||||||
|
|
||||||
<p>Top-level names are order-independent within a package, so mutually recursive
|
<p>Top-level names are order-independent within a package, so mutually recursive
|
||||||
functions need no forward declaration. Globals come in two kinds:</p>
|
functions need no forward declaration. Globals come in three kinds:</p>
|
||||||
|
|
||||||
|
<div class="scroll">
|
||||||
|
<table>
|
||||||
|
<tr><th>Form</th><th>What it is</th><th>What a re-run does</th></tr>
|
||||||
|
<tr><td><code>defconst</code></td><td>a compile-time constant</td><td>nothing to do</td></tr>
|
||||||
|
<tr><td><code>defonce</code></td><td>storage, initialised once</td><td>keeps the value it has</td></tr>
|
||||||
|
<tr><td><code>def</code></td><td>storage</td><td>runs the initialiser again</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Each names its own type, and the type is what makes the pair legible: a
|
||||||
|
<code>defonce</code> is for state the program builds up and a <code>def</code> is for
|
||||||
|
a value the source decides, so re-evaluating a file leaves the first alone and stores
|
||||||
|
into the second. The three forms decide that between them; the dev session does not.
|
||||||
|
There is no <code>defvar</code> — the compiler catches the name and gives both
|
||||||
|
spellings rather than guessing which was meant.</p>
|
||||||
|
|
||||||
|
<p>A third element that is not a type is the value of a <strong>dyn</strong> global:
|
||||||
|
<code>(def x 5)</code> declares a <code>dyn</code>, and <code>(def x i32 5)</code>
|
||||||
|
declares an <code>i32</code>. The same holds for <code>defonce</code>.</p>
|
||||||
|
|
||||||
<pre><code>(defconst cell-size 5) ; a compile-time constant
|
<pre><code>(defconst cell-size 5) ; a compile-time constant
|
||||||
(defconst gravity f32 0.05) ; with its type named
|
(defconst gravity f32 0.05) ; with its type named
|
||||||
@ -672,6 +924,62 @@ two reload differently; see <a href="#devloop">the dev loop</a>.</p>
|
|||||||
<p><code>let</code> binds name/value pairs and takes no type annotation, so a constant
|
<p><code>let</code> binds name/value pairs and takes no type annotation, so a constant
|
||||||
whose type matters is named at the top level rather than written inline.</p>
|
whose type matters is named at the top level rather than written inline.</p>
|
||||||
|
|
||||||
|
<h3>Function values</h3>
|
||||||
|
|
||||||
|
<p>There are two function types and the difference between them is what a value of
|
||||||
|
each one <em>is</em>, not what it may do. <code>(Fn [T ...] R)</code> is a code
|
||||||
|
address and the environment it is called with: two words, and it may have captured.
|
||||||
|
<code>(CFn [T ...] R)</code> is the bare address: one word, no environment, and
|
||||||
|
therefore nothing that can capture.</p>
|
||||||
|
|
||||||
|
<p><code>fn</code> writes a function value inline. It takes its parameter and return
|
||||||
|
types from the position it is written in, so it goes in an argument slot whose
|
||||||
|
parameter names them; a bare <code>(let [f (fn [x] x)])</code> is refused saying so.
|
||||||
|
An <code>fn</code> sees the locals it was written among and copies the ones it uses,
|
||||||
|
and that is what makes it an <code>Fn</code>. A name declared by <code>defn</code>
|
||||||
|
captures nothing and fits either type.</p>
|
||||||
|
|
||||||
|
<pre><code>;; An Fn is {code, env}: it may have captured, and the caller neither knows
|
||||||
|
;; nor cares. A CFn is the bare code address, one word, and cannot capture.
|
||||||
|
(defn apply-fn [f (Fn [i32] i32) x i32] i32 (f x))
|
||||||
|
(defn apply-cfn [f (CFn [i32] i32) x i32] i32 (f x))
|
||||||
|
|
||||||
|
(defn bump [x i32] i32 (+ x 1))
|
||||||
|
|
||||||
|
(defn main [] ()
|
||||||
|
(let [n 10]
|
||||||
|
;; An fn takes its types from the position it is written in.
|
||||||
|
(println (apply-fn (fn [x] (+ x n)) 5)) ; captures n
|
||||||
|
(println (apply-fn bump 5)) ; a defn captures nothing
|
||||||
|
(println (apply-cfn bump 5)) ; so it fits either position
|
||||||
|
;; Widening is one way: a CFn goes where an Fn is wanted.
|
||||||
|
(println (apply-fn (fn [x] (* x 2)) 21))))</code></pre>
|
||||||
|
|
||||||
|
<pre><code class="sh">15
|
||||||
|
6
|
||||||
|
6
|
||||||
|
42</code></pre>
|
||||||
|
|
||||||
|
<p><strong>Widening is one way.</strong> A <code>CFn</code> is accepted where an
|
||||||
|
<code>Fn</code> is wanted — the environment word is filled in and nothing is lost.
|
||||||
|
The reverse cannot work: an <code>Fn</code> put into a <code>CFn</code> would have
|
||||||
|
nowhere to keep its captures. An <code>fn</code> written into a <code>CFn</code>
|
||||||
|
position is checked as an <code>Fn</code> and then refused if the finished body
|
||||||
|
turns out to capture, which is the only point at which that is decidable:</p>
|
||||||
|
|
||||||
|
<pre><code class="sh">this fn captures n, so it is a (Fn [i32] i32) and not a (CFn [i32] i32): a CFn
|
||||||
|
is the bare address, one word, with nowhere for the copies to live. Widen the
|
||||||
|
position to Fn, or pass n in as a parameter</code></pre>
|
||||||
|
|
||||||
|
<p><strong>Rationale: two types rather than one.</strong> A uniform environment would
|
||||||
|
tax every function in every program for something most of them never use. With two
|
||||||
|
types an ordinary <code>defn</code> keeps exactly the signature it always had, and
|
||||||
|
the <code>C</code> in <code>CFn</code> names where the one-word form is going: a
|
||||||
|
value with no environment is the only kind that could ever be a C function pointer.
|
||||||
|
It is not a capability that exists today — a <code>declare</code> cannot take a
|
||||||
|
function type at all, because a Flan signature ends with the transfer channel and a
|
||||||
|
C caller knows nothing about one.</p>
|
||||||
|
|
||||||
<h2 id="control">Control flow</h2>
|
<h2 id="control">Control flow</h2>
|
||||||
|
|
||||||
<p><code>if</code>, <code>when</code>, <code>unless</code>, <code>cond</code>,
|
<p><code>if</code>, <code>when</code>, <code>unless</code>, <code>cond</code>,
|
||||||
@ -836,8 +1144,23 @@ extent, because nothing is released at scope exit)</code></pre>
|
|||||||
<p><code>at</code> indexes a fixed array or a slice, and takes any number of
|
<p><code>at</code> indexes a fixed array or a slice, and takes any number of
|
||||||
indices, so <code>(at grid r c)</code> indexes a two-dimensional fixed array directly.
|
indices, so <code>(at grid r c)</code> indexes a two-dimensional fixed array directly.
|
||||||
It is a place: <code>(set (at grid r c) v)</code> and <code>(addr (at grid r c))</code> both work.
|
It is a place: <code>(set (at grid r c) v)</code> and <code>(addr (at grid r c))</code> both work.
|
||||||
<code>length</code> works on a fixed array, a slice or a string.
|
<code>length</code> works on a fixed array, a slice, a string, a <code>Vec</code> or a
|
||||||
<code>(slice s lo hi)</code> takes a half-open range and never copies.</p>
|
<code>Map</code>.</p>
|
||||||
|
|
||||||
|
<p><code>slice</code> takes the view and never copies. It has three arities —
|
||||||
|
<code>(slice a)</code>, <code>(slice a lo)</code> and <code>(slice a lo hi)</code> —
|
||||||
|
and the short ones are written out into the long one, so nothing is added at run
|
||||||
|
time: <code>lo</code> is 0 and <code>hi</code> is the length, which an array already
|
||||||
|
folds to a constant and a slice or a string is carrying anyway. The range is
|
||||||
|
half-open. A string slices to a string rather than to a <code>[u8]</code>, because
|
||||||
|
the result views the same bytes and is read-only for the same reason the source
|
||||||
|
is.</p>
|
||||||
|
|
||||||
|
<p><strong>One name, because the input type decides the semantics.</strong> There
|
||||||
|
used to be a second, <code>as-slice</code>, for the <code>Vec</code> alone. A
|
||||||
|
<code>Vec</code> can only be borrowed and an array can only be viewed, and no call
|
||||||
|
site picks between the two, so the second name expressed nothing. Writing it now says
|
||||||
|
there is no <code>as-slice</code> and names the one that exists.</p>
|
||||||
|
|
||||||
<pre><code>(defconst rows 3)
|
<pre><code>(defconst rows 3)
|
||||||
(defconst cols 4)
|
(defconst cols 4)
|
||||||
@ -994,9 +1317,11 @@ bind. <code>test/programs/generics.flan</code> exercises the whole of it.</p>
|
|||||||
<p><code>println</code> prints a value and a newline; <code>print</code> is the same
|
<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
|
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
|
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
|
call is written and emits the printing for it. Nothing is decided at run time — a
|
||||||
value carries no header, so nothing at run time could say what it is — and there is no
|
typed Flan value carries no header, so nothing at run time could say what it is — and
|
||||||
user-supplied printer to choose between.</p>
|
there is no user-supplied printer to choose between. A <code>dyn</code> is the one
|
||||||
|
argument whose printing <em>is</em> decided at run time, because it is the one value
|
||||||
|
that carries a tag to decide it with.</p>
|
||||||
|
|
||||||
<pre><code>(defenum Key [space 32 left 263])
|
<pre><code>(defenum Key [space 32 left 263])
|
||||||
(defstruct Enemy [hp i32 name string key Key])
|
(defstruct Enemy [hp i32 name string key Key])
|
||||||
@ -1020,8 +1345,8 @@ none
|
|||||||
no newline: true</code></pre>
|
no newline: true</code></pre>
|
||||||
|
|
||||||
<p>The walk covers every integer and float type, <code>bool</code>, <code>()</code>,
|
<p>The walk covers every integer and float type, <code>bool</code>, <code>()</code>,
|
||||||
<code>string</code>, <code>[u8]</code>, enums, <code>Ptr</code>, <code>Option</code>,
|
<code>string</code>, <code>[u8]</code>, <code>dyn</code>, enums, <code>Ptr</code>,
|
||||||
structs, unions, fixed arrays and slices. An owning container has no printer for its
|
<code>Option</code>, structs, unions, fixed arrays and slices. An owning container has no printer for its
|
||||||
contents and comes back as a marker instead — <code><vec></code>,
|
contents and comes back as a marker instead — <code><vec></code>,
|
||||||
<code><pool></code>, <code><allocator></code> — while a <code>Handle</code>
|
<code><pool></code>, <code><allocator></code> — while a <code>Handle</code>
|
||||||
shows its index and generation, and a <code>Map</code> has no printer at all.
|
shows its index and generation, and a <code>Map</code> has no printer at all.
|
||||||
@ -1135,7 +1460,7 @@ here; a whole file at a time is the surface.</p>
|
|||||||
<p>The primitives underneath are few — a primitive is the only thing implemented
|
<p>The primitives underneath are few — a primitive is the only thing implemented
|
||||||
twice per backend: <code>argv</code>,
|
twice per backend: <code>argv</code>,
|
||||||
<code>write-stdout</code>, <code>exit</code>, <code>length</code>, <code>at</code>,
|
<code>write-stdout</code>, <code>exit</code>, <code>length</code>, <code>at</code>,
|
||||||
<code>slice</code>, <code>bytes</code>, <code>bytes->f64</code>,
|
<code>slice</code>, <code>bytes</code>, <code>bytes-view</code>, <code>bytes->f64</code>,
|
||||||
<code>bytes->i64</code>, <code>f64->bytes</code>, <code>i64->bytes</code>,
|
<code>bytes->i64</code>, <code>f64->bytes</code>, <code>i64->bytes</code>,
|
||||||
<code>addr</code>, and arithmetic, comparison and casts.</p>
|
<code>addr</code>, and arithmetic, comparison and casts.</p>
|
||||||
|
|
||||||
@ -1715,9 +2040,10 @@ from. The module says <em>run this once</em>, and the agent calls it after the i
|
|||||||
the game thread, at a frame boundary. An expression reading the program's state therefore
|
the game thread, at a frame boundary. An expression reading the program's state therefore
|
||||||
sees a consistent one.</p>
|
sees a consistent one.</p>
|
||||||
|
|
||||||
<p>Nothing is marshalled back, because nothing could be: a Flan value carries no header,
|
<p>Nothing is marshalled back, because for all but one type nothing could be: a typed
|
||||||
so no code at run time can say what it is. The compiler knows the type and renders it
|
Flan value carries no header, so no code at run time can say what it is. The compiler
|
||||||
there, in the thunk. What comes back looks like this:</p>
|
knows the type and renders it there, in the thunk. A <code>dyn</code> is rendered
|
||||||
|
there too, from its tag. What comes back looks like this:</p>
|
||||||
|
|
||||||
<pre><code class="sh">big 18446744073709551615
|
<pre><code class="sh">big 18446744073709551615
|
||||||
col :blue
|
col :blue
|
||||||
@ -1869,9 +2195,16 @@ to a new body. The refusal is a limitation with a date on it, not a rule.</p>
|
|||||||
|
|
||||||
<p><strong>A changed struct layout is the genuinely hard case</strong>, and is rejected
|
<p><strong>A changed struct layout is the genuinely hard case</strong>, and is rejected
|
||||||
while live values of that struct exist: storage already allocated has the old shape, and a
|
while live values of that struct exist: storage already allocated has the old shape, and a
|
||||||
new body would read its fields at the wrong offsets with nothing to say so. plan.org keeps
|
new body would read its fields at the wrong offsets with nothing to say so.</p>
|
||||||
this one as a rejection, and gives managed classes an explicit migration at a frame
|
|
||||||
boundary as the eventual way through.</p>
|
<p>A <a href="#dyn"><code>defclass</code></a> is the shape that does evolve, and the
|
||||||
|
reason is the whole difference between the two: an instance carries a header naming
|
||||||
|
its class and a flat struct does not. Redefining one re-registers the class and
|
||||||
|
bumps a generation counter, which is O(1) and walks no heap; every live instance
|
||||||
|
migrates at its next touch. Slots matched by name keep their values, a gained slot
|
||||||
|
appears as <code>nil</code>, a dropped one goes, the object is the same object, and
|
||||||
|
<code>class-of</code> still answers the same tag, so every method still reaches it.
|
||||||
|
That is CLHS 4.3.6's protocol without the user hook, which is not built.</p>
|
||||||
|
|
||||||
<p><kbd>C-c C-x</kbd> rebuilds, relaunches and reconnects, and is the way out while the
|
<p><kbd>C-c C-x</kbd> rebuilds, relaunches and reconnects, and is the way out while the
|
||||||
above is true. It costs the program's state, which is why it is a key you press rather than
|
above is true. It costs the program's state, which is why it is a key you press rather than
|
||||||
@ -2025,7 +2358,7 @@ name, with the milestone it belongs to, and the tests assert on the reason.</p>
|
|||||||
<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>(Result T E)</code></td><td>(Result T E) 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><code>(try …)</code></td><td>try (Result) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||||
<tr><td><code>'sym</code> as a value</td><td>a quoted symbol (restart names) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
<tr><td><code>'sym</code> as a value</td><td>a quoted symbol (restart names) is not implemented yet — milestone 6 (see plan.org)</td></tr>
|
||||||
<tr><td>a bare lowercase type name</td><td>generic code over the type variable a is not implemented yet — milestone 5 (see plan.org)</td></tr>
|
<tr><td>a bare lowercase type name</td><td>unknown type a. A lowercase name is a type variable only where a defn signature introduced it — write $a in the parameter vector to introduce one, and a reads it from there</td></tr>
|
||||||
<tr><td><code>errdefer</code></td><td>errdefer is not implemented yet (see the build sequence in plan.org)</td></tr>
|
<tr><td><code>errdefer</code></td><td>errdefer is not implemented yet (see the build sequence in plan.org)</td></tr>
|
||||||
<tr><td><code>await</code></td><td>await is not implemented yet (see the build sequence in plan.org)</td></tr>
|
<tr><td><code>await</code></td><td>await is not implemented yet (see the build sequence in plan.org)</td></tr>
|
||||||
<tr><td><code>find-restart</code>, <code>compute-restarts</code></td><td>… is not implemented yet (see the build sequence in plan.org)</td></tr>
|
<tr><td><code>find-restart</code>, <code>compute-restarts</code></td><td>… is not implemented yet (see the build sequence in plan.org)</td></tr>
|
||||||
@ -2047,8 +2380,9 @@ extent); <code>find-restart</code> and <code>compute-restarts</code> are blocked
|
|||||||
<code>Restart</code> type rather than on effort; a restart with parameters cannot be
|
<code>Restart</code> type rather than on effort; a restart with parameters cannot be
|
||||||
taken from the break loop, which aims at a frame by position and has nothing to fill them
|
taken from the break loop, which aims at a frame by position and has nothing to fill them
|
||||||
with; there is no package-private marker other than <code>main</code> not being exported;
|
with; there is no package-private marker other than <code>main</code> not being exported;
|
||||||
there are no threads in the language; and the managed <code>class</code> facility that
|
and there are no threads in the language. The class facility plan.org describes is
|
||||||
plan.org describes is a plan and not a feature.</p>
|
built — see <a href="#dyn">dyn</a> — and what is not built of it is the named-slot
|
||||||
|
constructor spelling and the user-written migration hook.</p>
|
||||||
|
|
||||||
<p>One of these is settled rather than pending. There is <strong>no interpreter</strong>
|
<p>One of these is settled rather than pending. There is <strong>no interpreter</strong>
|
||||||
and there is not going to be one: compiling is the only way a form is ever run. The
|
and there is not going to be one: compiling is the only way a form is ever run. The
|
||||||
@ -2066,8 +2400,8 @@ disagree with the first.</p>
|
|||||||
<li><code>plan.org</code> — the design, the build sequence, and the open decisions.</li>
|
<li><code>plan.org</code> — the design, the build sequence, and the open decisions.</li>
|
||||||
<li><code>NEXT.md</code> — the project's memory, and the authority on what is actually
|
<li><code>NEXT.md</code> — the project's memory, and the authority on what is actually
|
||||||
built.</li>
|
built.</li>
|
||||||
<li><code>spec-memory.md</code> — ownership, containers, places, generics, function
|
<li><code>spec-memory.md</code> — containers, places, generics, function values,
|
||||||
values. Frozen.</li>
|
allocators, and the 2026-09-18 repeal of static ownership tracking.</li>
|
||||||
<li><code>spec-conditions.md</code> — conditions and restarts, operational semantics.
|
<li><code>spec-conditions.md</code> — conditions and restarts, operational semantics.
|
||||||
Frozen.</li>
|
Frozen.</li>
|
||||||
<li><code>conditions.org</code> — a cheatsheet for driving conditions.</li>
|
<li><code>conditions.org</code> — a cheatsheet for driving conditions.</li>
|
||||||
@ -2078,9 +2412,9 @@ disagree with the first.</p>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Flan is a custard. This page describes the compiler on branch
|
<p>Flan is a custard. This page describes the compiler on <code>master</code>;
|
||||||
<code>dev-loop</code>; where a document and the compiler disagree, the compiler is what
|
where a document and the compiler disagree, the compiler is what is written
|
||||||
is written here.</p>
|
here.</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -2148,12 +2482,13 @@ disagree with the first.</p>
|
|||||||
<script>
|
<script>
|
||||||
// A small hand-written highlighter for the Flan blocks. One pass, no library.
|
// A small hand-written highlighter for the Flan blocks. One pass, no library.
|
||||||
(function () {
|
(function () {
|
||||||
var FORMS = new Set(("defn defstruct defenum defdata defunion defconst defonce defalias " +
|
var FORMS = new Set(("defn defstruct defenum defdata defunion defconst defonce def defalias " +
|
||||||
"declare declare-c import package let if when unless cond do and or not while " +
|
"declare declare-c import package let if when unless cond do and or not while " +
|
||||||
"until dotimes match set return some try defer signal error handler-bind " +
|
"until dotimes match set return some try defer signal error handler-bind " +
|
||||||
"restart-case invoke-restart fn quote defmacro gensym").split(" "));
|
"restart-case invoke-restart fn quote defmacro gensym " +
|
||||||
|
"defclass defgeneric defmulti defmethod").split(" "));
|
||||||
// Capitalised names are types; these are the ones that are not capitalised.
|
// Capitalised names are types; these are the ones that are not capitalised.
|
||||||
var TYPES = new Set(("i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 bool string").split(" "));
|
var TYPES = new Set(("i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 bool string dyn").split(" "));
|
||||||
var TOKEN = /(;[^\n]*)|("(?:\\.|[^"\\])*")|(\\[A-Za-z0-9]+)|(:[A-Za-z][\w?!*<>=+-]*)|(\b0[xX][0-9a-fA-F]+\b|\b\d[\d.]*\b)|([A-Za-z][\w*?!<>=./+-]*)/g;
|
var TOKEN = /(;[^\n]*)|("(?:\\.|[^"\\])*")|(\\[A-Za-z0-9]+)|(:[A-Za-z][\w?!*<>=+-]*)|(\b0[xX][0-9a-fA-F]+\b|\b\d[\d.]*\b)|([A-Za-z][\w*?!<>=./+-]*)/g;
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user