The reference page describes the language that exists, dyn and classes included
This commit is contained in:
parent
bcfcf130f7
commit
23e272a24f
73
FIX.org
73
FIX.org
@ -6353,3 +6353,76 @@ out on ~program_asked~, so a re-run installs what is queued and then enters
|
||||
~main~. That is ~lib/dev.ml~, which another lane holds, so it is written down
|
||||
here rather than done. The test row asserts the behaviour as it is, with the
|
||||
extra re-run spelled out.
|
||||
* 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
|
||||
; _build, and in CI it sits inside @checks where the same thing hides it.
|
||||
(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
|
||||
; the binding line quotes.sh greps out of it.
|
||||
(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.
|
||||
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.
|
||||
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="#values">Values and memory</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="#functions">Functions</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>
|
||||
</svg>
|
||||
<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>
|
||||
|
||||
<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
|
||||
collector, so nothing runs between frames that you did not write. And a running
|
||||
program can be edited: a function recompiled in Emacs is installed into the live
|
||||
process at its next frame boundary, in about twenty milliseconds.</p>
|
||||
A typed value carries no header, so a Flan struct is exactly its C struct, and a
|
||||
program that writes <code>dyn</code> nowhere has no collector in it — nothing runs
|
||||
between frames that you did not write. And a running program can be edited: a
|
||||
function recompiled in Emacs is installed into the live process at its next frame
|
||||
boundary, in about twenty milliseconds.</p>
|
||||
|
||||
<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
|
||||
@ -297,7 +299,8 @@ rather than programs.</p>
|
||||
<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.
|
||||
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
|
||||
<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>
|
||||
@ -307,11 +310,11 @@ see <a href="#targets">targets and builds</a>.</p>
|
||||
<ul>
|
||||
<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>
|
||||
<li><strong>No immutable collection types.</strong> Structure sharing destroys clear
|
||||
ownership, and clear ownership is the only thing that removes the need for a
|
||||
collector. Value structs that copy on assignment replace them.</li>
|
||||
<li><strong>No dynamic typing.</strong> A tag word on every value is exactly the
|
||||
header cost that dropping the GC was meant to avoid.</li>
|
||||
<li><strong>No immutable collection types.</strong> No persistent maps or vectors
|
||||
and no structure sharing. Value structs that copy on assignment replace them.</li>
|
||||
<li><strong>Not dynamically typed by default.</strong> A type is written or
|
||||
inferred, and a value of one carries no tag. <code>dyn</code> is the opt-in, and
|
||||
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>
|
||||
</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 import-c <header.h> [package.flan...] [clang flags...]
|
||||
flan generate-c <package-dir>
|
||||
flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] [--debug] [--sanitize] [--x86] [--target=wasm32-wasi|web]
|
||||
flan run <file.flan> [args...]
|
||||
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> [build flags...] [--] [program args...]
|
||||
flan reload <program.flan> <forms.flan> [-o out.so] [--x86]
|
||||
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>
|
||||
|
||||
<p>There is no garbage collector and no hidden allocation. Every local is a stack
|
||||
slot; reading one is a load, assigning to one is a store, and a store of an aggregate
|
||||
<em>is</em> the copy. A struct is its C layout with nothing added.</p>
|
||||
<p>There is no hidden allocation. Every local is a stack slot; reading one is a load,
|
||||
assigning to one is a store, and a store of an aggregate <em>is</em> the copy. A
|
||||
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>
|
||||
|
||||
@ -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>[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>(Vec T)</code></td><td>growable, owning — <em>moves</em> on assignment</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>(Pool T)</code></td><td>generational slab storage, owning — <em>moves</em></td><td>items + slots + 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, 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, 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>(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>(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>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>$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>
|
||||
@ -532,8 +539,8 @@ notation reads as exactly one data item.</p>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p><strong>There is no implicit widening.</strong> Both operands of a binary operator
|
||||
have one type, and every conversion is written as a cast:</p>
|
||||
<p><strong>There is no implicit widening.</strong> Every operand of an arithmetic or
|
||||
comparison form has one type, and every conversion is written as a cast:</p>
|
||||
|
||||
<pre><code>(defn main [] i32
|
||||
(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
|
||||
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
|
||||
<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
|
||||
@ -567,6 +612,185 @@ refused:</p>
|
||||
not fit truncates to one that does and would read the wrong element without
|
||||
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>
|
||||
|
||||
<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
|
||||
|
||||
(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 "")
|
||||
(advance (addr c))
|
||||
(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
|
||||
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
|
||||
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>
|
||||
@ -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>
|
||||
|
||||
<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
|
||||
(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
|
||||
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>
|
||||
|
||||
<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
|
||||
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.
|
||||
<code>length</code> works on a fixed array, a slice or a string.
|
||||
<code>(slice s lo hi)</code> takes a half-open range and never copies.</p>
|
||||
<code>length</code> works on a fixed array, a slice, a string, a <code>Vec</code> or a
|
||||
<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)
|
||||
(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
|
||||
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>
|
||||
call is written and emits the printing for it. Nothing is decided at run time — a
|
||||
typed 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. 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])
|
||||
(defstruct Enemy [hp i32 name string key Key])
|
||||
@ -1020,8 +1345,8 @@ none
|
||||
no newline: true</code></pre>
|
||||
|
||||
<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>,
|
||||
structs, unions, fixed arrays and slices. An owning container has no printer for its
|
||||
<code>string</code>, <code>[u8]</code>, <code>dyn</code>, enums, <code>Ptr</code>,
|
||||
<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>,
|
||||
<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.
|
||||
@ -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
|
||||
twice per backend: <code>argv</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>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
|
||||
sees a consistent one.</p>
|
||||
|
||||
<p>Nothing is marshalled back, because nothing could be: a Flan value carries no header,
|
||||
so no code at run time can say what it is. The compiler knows the type and renders it
|
||||
there, in the thunk. What comes back looks like this:</p>
|
||||
<p>Nothing is marshalled back, because for all but one type nothing could be: a typed
|
||||
Flan value carries no header, so no code at run time can say what it is. The compiler
|
||||
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
|
||||
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
|
||||
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
|
||||
this one as a rejection, and gives managed classes an explicit migration at a frame
|
||||
boundary as the eventual way through.</p>
|
||||
new body would read its fields at the wrong offsets with nothing to say so.</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
|
||||
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>(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>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>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>
|
||||
@ -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
|
||||
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;
|
||||
there are no threads in the language; and the managed <code>class</code> facility that
|
||||
plan.org describes is a plan and not a feature.</p>
|
||||
and there are no threads in the language. The class facility plan.org describes is
|
||||
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>
|
||||
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>NEXT.md</code> — the project's memory, and the authority on what is actually
|
||||
built.</li>
|
||||
<li><code>spec-memory.md</code> — ownership, containers, places, generics, function
|
||||
values. Frozen.</li>
|
||||
<li><code>spec-memory.md</code> — containers, places, generics, function values,
|
||||
allocators, and the 2026-09-18 repeal of static ownership tracking.</li>
|
||||
<li><code>spec-conditions.md</code> — conditions and restarts, operational semantics.
|
||||
Frozen.</li>
|
||||
<li><code>conditions.org</code> — a cheatsheet for driving conditions.</li>
|
||||
@ -2078,9 +2412,9 @@ disagree with the first.</p>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>Flan is a custard. This page describes the compiler on branch
|
||||
<code>dev-loop</code>; where a document and the compiler disagree, the compiler is what
|
||||
is written here.</p>
|
||||
<p>Flan is a custard. This page describes the compiler on <code>master</code>;
|
||||
where a document and the compiler disagree, the compiler is what is written
|
||||
here.</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
@ -2148,12 +2482,13 @@ disagree with the first.</p>
|
||||
<script>
|
||||
// A small hand-written highlighter for the Flan blocks. One pass, no library.
|
||||
(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 " +
|
||||
"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.
|
||||
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;
|
||||
function esc(s) {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user