Merge: the bang leaves the names; mutation was never optional

This commit is contained in:
Joseph Ferano 2026-09-19 05:22:15 +07:00
commit 95d3da0712
40 changed files with 246 additions and 249 deletions

View File

@ -151,7 +151,7 @@ against 985ms on LLVM.
** Open, carried forward
- A transient signal -11 on the globals daemon, seen once, not reproduced.
Recorded below.
- [drop]'s handoff flags that the [clone] / [get] / [map-next!] refusals are
- [drop]'s handoff flags that the [clone] / [get] / [map-next] refusals are
needed by the arena route too — they are about a copy of a header, which an
arena does not make safe — and that a Map has no operation answering *where*
a value lives, which is what reading an arena-parsed EDN document back would

20
NEXT.md
View File

@ -550,7 +550,7 @@ constant folding has to accept that a length can stay symbolic until instantiati
### The motivating case, and it is not `$n` on its own
`$n` alone buys little. `swap!` does not need it — it takes a slice and two indices, and the length is a runtime
`$n` alone buys little. `swap` does not need it — it takes a slice and two indices, and the length is a runtime
field. Nor does a `pop` from a `Vec`. The case that wants both is **a fixed-capacity array with a count and no
allocation**, which is Odin's `Small_Array` and a good fit for a game that refuses to allocate in a frame:
@ -600,7 +600,7 @@ so `a + b` over a `$T` compiles and fails only when someone instantiates at a ty
write a second pass to get plan.org's rule.
Both options were bad in the way the other was good. Rejecting abstractly gives the error at the definition and
makes every call site carry a comparison: `(sort! xs)` becomes `(sort-by! xs (fn [a b] (< a b)))` everywhere.
makes every call site carry a comparison: `(sort xs)` becomes `(sort-by xs (fn [a b] (< a b)))` everywhere.
Checking per instantiation keeps the call short and moves the error into code the caller did not write, which is
worse here than in most languages because a hot-reload session may have been running for an hour before the call
site is reached.
@ -608,7 +608,7 @@ site is reached.
**What dissolves it is the thing plan.org ruled out while citing Odin: Odin has constraints.**
`core/slice/slice.odin:289` is `where intrinsics.type_is_ordered(T)`, and there are 41 such predicates. A `where`
clause tells the abstract pass what it may assume, so the body checks at the definition *and* the call stays
`(sort! xs)`.
`(sort xs)`.
### What is being built
@ -648,7 +648,7 @@ Measured by the spike against the real sources, and worth knowing before the wor
- **5 do not collapse and should not**`sum-i32`/`sum-f32` widen to `i64`/`f64` with an explicit cast, and "the
wider type `t` accumulates into" is a type-level function, which is a constraint system or an associated type.
A generic `sum` would have to take its accumulator and its `add`, at which point it *is* `reduce`.
`append-i64!`/`append-f64!` are two different primitives, `I64ToBytes` and `F64ToBytes`, and choosing between
`append-i64`/`append-f64` are two different primitives, `I64ToBytes` and `F64ToBytes`, and choosing between
them per instantiation is compile-time overloading, which is what multimethods are for.
The prelude keeps a per-type layer for the numeric ones. That is the honest number.
@ -1690,10 +1690,10 @@ allocation-free functions because there was nothing to allocate from; there are
name" block at the foot of `prelude.ml` is down from eight entries to four, each with a *different* reason rather
than the one shared sentence.
What landed: `append!`/`append-i64!`/`append-f64!` (the builder), `concat`, `join`, `split` returning a
What landed: `append`/`append-i64`/`append-f64` (the builder), `concat`, `join`, `split` returning a
`(Vec [u8])`, `repeat-bytes`, `replace-bytes`, `to-lower`, `to-upper`, `slices-new`; `format-f64` with a precision;
`atan2-f32` and `pow-f32`; `clamp` as a `defmacro`; and the slice family at two more element types —
`sort-f32!`, `reverse-f32!`, `swap-f32!`, `min-f32`, `max-f32`, `sum-f32`, `bytes<?`, `swap-bytes!`, `sort-bytes!`.
`sort-f32`, `reverse-f32`, `swap-f32`, `min-f32`, `max-f32`, `sum-f32`, `bytes<?`, `swap-bytes`, `sort-bytes`.
Tests: `programs/strings.flan`, `programs/format.flan`, `programs/algorithms.flan`, `programs/math2.flan`.
`string-from-bytes`, refused in that block, turned out to already exist: `string` is a builtin and
@ -1702,8 +1702,8 @@ Tests: `programs/strings.flan`, `programs/format.flan`, `programs/algorithms.fla
**What could not be built, and why each one could not.** All four want a compiler or runtime change, and none of
them wants a language decision.
- ~~**`Map` keys and values.**~~ **Iteration is built**`flan_map_next` and the `map-next!` builtin, exactly the
shape this described. See [`docs/BUILT.md`](docs/BUILT.md), "`map-next!`, the one thing a Map could not do".
- ~~**`Map` keys and values.**~~ **Iteration is built**`flan_map_next` and the `map-next` builtin, exactly the
shape this described. See [`docs/BUILT.md`](docs/BUILT.md), "`map-next`, the one thing a Map could not do".
`map-keys`/`map-values` as *prelude functions* stay refused, and the reason is now generics rather than the
iterator: a `defn` has to name its types and `(defn map-keys [m {K V}] (Vec K))` has no `K`. The loop is three
lines at the call site, where `K` is known.
@ -1941,7 +1941,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
**Generics are deliberately NOT here** — and function values landing has *sharpened* the case rather than made it,
which is the useful update. `Vec` and `Map` needed none, being type-erased. Function values needed none. What
needs them is now concrete and small: the prelude's `map!`/`filter`/`reduce`/`sort-by!` are **two copies each**,
needs them is now concrete and small: the prelude's `map-in-place`/`filter`/`reduce`/`sort-by` are **two copies each**,
i32 and f32, differing in nothing but the element type; `map-keys`/`map-values` cannot be written at all because
a `defn` must name its types and `(defn map-keys [m {K V}] (Vec K))` has no `K`; and a `map` from `[i32]` to
`[f32]` would be one copy per ordered pair. A user-written allocator is *not* on this list any more — it wants
@ -2716,7 +2716,7 @@ memcheck sweep (`@valgrind`), whose alarm is looser at 5400s because memcheck is
Neither is refused today. The first returns a view of storage the return has just released; the second pushes
ptr+len, not the bytes, and a slot reused on the next turn of a loop leaves every element reading as the last
number. Copy the bytes for anything that outlives the expression that made them — which is what the prelude's
`append-i64!` and `append-f64!` do, and the reason that shape exists: they copy into a `(Vec u8)`, so a builder
`append-i64` and `append-f64` do, and the reason that shape exists: they copy into a `(Vec u8)`, so a builder
holds as many rendered numbers as it likes, and `format-f64` answers a `Vec` rather than a view.
`rl/draw-text` is safe for a third reason: the shim's `flan_shim_cstr` copies out of ptr+len before the call.

View File

@ -2658,7 +2658,7 @@ calls per field, which has no channel to hand on.
| `(put m k v)` | upsert, `()` |
| `(get m k)` | `(Option V)` — absence is `None` |
| `(has-key? m k)` | `bool`, copying no value — **an addition; the spec does not name it** |
| `(map-remove! m k)` | `(Option V)` — the value that was there, or `None` |
| `(map-remove m k)` | `(Option V)` — the value that was there, or `None` |
| `(len m)` `(reserve m n)` `(clone m)` `(clone m a)` `(free m)` | extended, not duplicated |
`has-key?` is **not in `spec-memory.md`** and is an addition, flagged because everything else here is the spec's.
@ -3520,12 +3520,12 @@ Three rules hold across all of it, and they are stated once at the head of the s
Odin's `strings.Builder` wraps a `[dynamic]u8`. Here the `(Vec u8)` already **is** that and already has `push`, so
the struct would be a move-only wrapper whose only method is the one it wraps. What was actually missing is appending
a *run* of bytes, and `append!` is that.
a *run* of bytes, and `append` is that.
It takes a `(Ptr (Vec u8))` and not a `(Vec u8)`, and that is not style: a `Vec` parameter **moves**, so a by-value
builder would be consumed by its first append and refused on the second.
`append-i64!` and `append-f64!` are the argument for the whole shape. NEXT.md's "Sharp edges" records that
`append-i64` and `append-f64` are the argument for the whole shape. NEXT.md's "Sharp edges" records that
`flan_i64_to_bytes` and its neighbours render into one `static char scratch[64]`, so two formatted numbers cannot be
held at once; these copy out of that buffer before returning, so the hazard ends at the call and a builder holds as
many numbers as it likes. `strings.flan` puts two integers and a float on one line, which is the case that could not
@ -3539,7 +3539,7 @@ spec-memory.md, "A container of owning elements lives in a region" — and a `(V
a region allocator and nowhere else. `split` is unchanged anyway, and now by choice rather than by refusal: an owning
`split` would have to allocate one block per field and would only be usable in the tier that can never hand one back,
while the slices cost nothing and work everywhere. It follows, as before, that the result dies with whatever the input
pointed at, which is the same contract `trim` and `split-next!` already have.
pointed at, which is the same contract `trim` and `split-next` already have.
The rule is `split-on-byte`'s, unchanged: n separators always yield n+1 fields, so an empty input yields one empty
field and a trailing separator yields a trailing empty one. That is Odin's allocating `strings.split` and not Odin's
@ -3556,7 +3556,7 @@ gap rather than worked around silently.
it. A frame time of 1/60 comes back `0.0166667` and a score past a million `1.23457e+06`.
`format-f64` returns a `Vec`, so it inherits neither that nor the shared scratch buffer, and it renders the integer
part and the fraction through that buffer in strict sequence — the discipline `append-i64!` exists to make automatic.
part and the fraction through that buffer in strict sequence — the discipline `append-i64` exists to make automatic.
It rounds **half away from zero at the last digit kept**, which is `round-f32`'s rule and the rest of the prelude's.
printf rounds the *binary* value to nearest-even at the decimal digit, so `0.125` at two places is `0.13` here and
@ -3592,7 +3592,7 @@ constant-folds a `powf` of two literals and leaves nothing to link.
### What could not be built, and why it is not "no generics"
Four things on NEXT.md's list did not land, and the interesting part is that the reason differs in each case.
**Three of the four have since landed** — see "`map-next!`, the one thing a Map could not do", "Function values, with
**Three of the four have since landed** — see "`map-next`, the one thing a Map could not do", "Function values, with
no capture" and "A prelude function may call a prelude macro" below — and each was fixed by the thing named here
rather than by generics, which is the argument this list was making. The fourth, the path-insensitive dead set, is
still open. Kept as written because the diagnoses are what the later lanes worked from, and one of them turned out to
@ -3605,7 +3605,7 @@ be wrong in a way worth being able to see: the prelude *was* reaching the expand
- **`map`, `filter`, `reduce` and a comparator sort** are blocked on **function values**, which is sharper than "no
generics" and matters because generics alone would not fix it. `Types.Fn` exists; `check.ml` refuses it with "a
function type is not implemented yet — milestone 5"; there is nothing in the language to pass. The concrete answer
is the one that shipped: `sort-f32!` and `sort-bytes!` are the second and third sorts in the language, and
is the one that shipped: `sort-f32` and `sort-bytes` are the second and third sorts in the language, and
`sum-i32`/`sum-f32` already are `reduce` with the `+` written in.
- **The prelude is never macro-expanded**, so a prelude function may not call a prelude macro. `Macro.program` runs
over the file being compiled; the prelude reaches the checker through `Check.program`'s prepend. The call resolves
@ -3737,7 +3737,7 @@ backing buffer remains the parameterised allocator that does exist.
### The prelude's four
`map-i32!`/`map-f32!`, `filter-i32`/`filter-f32`, `reduce-i32`/`reduce-f32` and `sort-i32-by!`/`sort-f32-by!`. Two
`map-i32`/`map-f32`, `filter-i32`/`filter-f32`, `reduce-i32`/`reduce-f32` and `sort-i32-by`/`sort-f32-by`. Two
rules, both inherited rather than invented: the in-place ones write back into the slice they were handed, because a
slice is non-owning and transforming a thing you already own should not allocate; and `filter` allocates and the
caller frees, like everything in the building tier.
@ -3746,18 +3746,18 @@ caller frees, like everything in the building tier.
pair* of types rather than per type, which is where a per-type family stops being honest. That entry is what is left
in `prelude.ml`'s refusal block where `map, filter, reduce, sort-by` used to be, and its reason is generics.
## `map-next!`, the one thing a Map could not do
## `map-next`, the one thing a Map could not do
`flan_map_len`, `_get`, `_put`, `_has`, `_clone`, `_reserve` and `_free` was the runtime's entire map surface, and
every one of them addresses a *single* entry by hashing it. Nothing walked the block, so a map's keys and its values
could not be read out at all — the only item on the second tier's list that was blocked on nothing but a missing
function.
`flan_map_next` is that function and `map-next!` is the builtin over it.
`flan_map_next` is that function and `map-next` is the builtin over it.
```
(let [cur (i64 0) k 0 v 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
...))
```
@ -5431,9 +5431,9 @@ to name and nothing to choose between, so the abstract pass could not build the
pair would have been worse than refusing: it would hash a string's pointer and a struct's padding.
**What closes it is deferral, and what makes deferral safe is the `where` clause.** `put`, `get`, `has-key?`,
`map-remove!`, `reserve` and `clone` — the arms that reach `key_fns` — now check their arguments and then, when the
`map-remove`, `reserve` and `clone` — the arms that reach `key_fns` — now check their arguments and then, when the
key is a type variable, return a placeholder of the operation's own type: `Unit` for `put` and `reserve`, `None` for
`get` and for `map-remove!` so the `(Option V)` around either still checks, `false` for `has-key?`, a zeroed map for
`get` and for `map-remove` so the `(Option V)` around either still checks, `false` for `has-key?`, a zeroed map for
`clone`. The whole node is
thrown away with the rest of the abstract pass, exactly as `println`'s is, and the real one is built when the copy
is checked with `$t` concrete.

View File

@ -721,7 +721,7 @@ program per keystroke. It refreshes at the two moments the answer can have
changed: when you connect, and after an evaluation the daemon accepted.
The answer covers the compiler's builtins as well as the program's own names,
so `C-c C-v` on `arena-new` or `map-next!` gives you its signature and a line
so `C-c C-v` on `arena-new` or `map-next` gives you its signature and a line
about what it does. They are marked `builtin`, and they come after the
program's names in a completion list. `M-.` on one refuses rather than jumping:
it is written in the compiler, so there is no file to open.

BIN
emacs/flan-cnr.elc Normal file

Binary file not shown.

BIN
emacs/flan-dape.elc Normal file

Binary file not shown.

BIN
emacs/flan-inspect.elc Normal file

Binary file not shown.

BIN
emacs/flan-lower.elc Normal file

Binary file not shown.

BIN
emacs/flan-mode.elc Normal file

Binary file not shown.

BIN
emacs/flan-repl.elc Normal file

Binary file not shown.

BIN
emacs/flan-watch.elc Normal file

Binary file not shown.

BIN
emacs/flan.elc Normal file

Binary file not shown.

View File

@ -488,7 +488,7 @@ let pred_holds p (t : Types.t) =
admits is a number or an enum, so it is equatable. The table is only sound
while that is true an ordered type with no [=] would make it wrong so
it lives in one place and says so. The gain is real ergonomics:
[{:where (ordered? $t)}] is enough for a [sort!] that also compares,
[{:where (ordered? $t)}] is enough for a [sort] that also compares,
rather than two predicates on one line. *)
let pred_entails ~declared ~wanted =
String.equal declared wanted
@ -846,9 +846,9 @@ let rec generic_ty (t : Types.t) =
With [where] there are now two ways out and the message names both: declare
the predicate, or take the operation as a function value the way
[sort-by!] does. Declaring it is the one that keeps the call site short,
[sort-by] does. Declaring it is the one that keeps the call site short,
which is the whole reason predicates exist under the no-constraint rule
[(sort! xs)] had to become [(sort-by! xs (fn [a b] (< a b)))] at every call
[(sort xs)] had to become [(sort-by xs (fn [a b] (< a b)))] at every call
site in the corpus. *)
let unconstrained env loc op ~needs (t : Types.t) =
if generic_ty t then
@ -4096,7 +4096,7 @@ and named_call ctx ~want loc name args =
[ mk loc oty (Tast.If (cond, some, none)) ])))
| _ -> assert false)
(* (map-remove! m k) -> (Option V): the value that was there, or None when
(* (map-remove m k) -> (Option V): the value that was there, or None when
the key was not. The same answer [get] gives, for the same reason a key
that is not in the map is an answer and not a failure and the value
comes back rather than being dropped on the floor, which is what makes
@ -4108,21 +4108,18 @@ and named_call ctx ~want loc name args =
the one block the map allocated, and removal moves entries within that
block. That is what makes it mean the same thing on a map backed by an
arena or by any allocator that refuses can-free as on a heap-backed
one. Nothing is freed per entry because nothing was allocated per entry.
The [!] is the mutation the naming rule asks for ([map-next!], and the
note below on the two suffixes). *)
| "map-remove!" ->
one. Nothing is freed per entry because nothing was allocated per entry. *)
| "map-remove" ->
arity loc name 2 args;
(match args with
| [ target; k ] ->
let target = check ctx target in
let kt, vt = map_kv loc "map-remove!" target.Tast.ty in
let kt, vt = map_kv loc "map-remove" target.Tast.ty in
let k = check ctx ~want:kt k in
(* Deferred exactly as [get] is, and with [None] for the same reason:
the abstract pass still has to check whatever the body does with the
answer. *)
if deferred_key ctx.env loc "map-remove!" kt then
if deferred_key ctx.env loc "map-remove" kt then
expect loc ~want (mk loc (Types.Option vt) Tast.None_)
else
let hash, eq = key_fns ctx.env loc kt in
@ -4152,7 +4149,7 @@ and named_call ctx ~want loc name args =
[ mk loc oty (Tast.If (cond, some, none)) ])))
| _ -> assert false)
(* (map-next! m (addr cur) (addr k) (addr v)) -> bool, and the whole of map
(* (map-next m (addr cur) (addr k) (addr v)) -> bool, and the whole of map
iteration. Before it there was no way to read a map's keys or its values
at all: every other map operation addresses one entry by hashing it, and
nothing walked the block.
@ -4163,7 +4160,7 @@ and named_call ctx ~want loc name args =
i64 the caller owns and the loop reads as one:
(let [cur 0 k 0 v 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
...))
It is *not* a generic (map-keys m): a Vec of them needs a signature naming
@ -4173,12 +4170,12 @@ and named_call ctx ~want loc name args =
No hash and no equality pair go with it walking asks nothing about a
key so this is the one map entry point whose signature carries neither,
and the sizes are still needed because the runtime is type-erased. *)
| "map-next!" ->
| "map-next" ->
arity loc name 4 args;
(match args with
| [ target; cur; k; v ] ->
let target = check ctx target in
let kt, vt = map_kv loc "map-next!" target.Tast.ty in
let kt, vt = map_kv loc "map-next" target.Tast.ty in
let cur = check ctx ~want:(Types.Ptr (Types.Int Types.I64)) cur in
let k = check ctx ~want:(Types.Ptr kt) k in
let v = check ctx ~want:(Types.Ptr vt) v in
@ -4549,9 +4546,9 @@ and named_call ctx ~want loc name args =
same trust [declare-c] already extends, written at the one site where
somebody had to know the answer anyway.
No marker on the name. A [!] in this language means *mutates*
([map-next!]) and a [?] means *asks* ([font-valid?]), and this does
neither; [zeroed], the nearest neighbour a value conjured rather than
No marker on the name. A [?] in this language means *asks*
([font-valid?]), and this does not ask; [zeroed], the nearest
neighbour a value conjured rather than
derived carries no marker either. [ptr] is the marker: a (Ptr T) only
ever arrives from a [declare-c], so the word already names the C boundary,
and a reader who sees it has already been told where the promise comes
@ -4732,7 +4729,7 @@ and named_call ctx ~want loc name args =
call site these can be refused at. They are deferred and then always
succeed. That is the cheapest possible membership.
The map operations [put], [get], [has-key?], [map-remove!], [reserve], [clone],
The map operations [put], [get], [has-key?], [map-remove], [reserve], [clone],
through [deferred_key] beside [key_fns] are the other kind, and they
are here on a different argument. They *can* fail at a concrete type,
so deferring them does move a refusal. But [{:where (hashable? $t)}] is
@ -4962,7 +4959,7 @@ and generic_call ctx ~want loc name vars pats pret args =
generic call site is weaker than at a monomorphic one.
A variable already bound by an earlier argument is substituted back into
the parameters still to come, so [(sort-by! (slice ns 0 4) (fn [a b] (< a
the parameters still to come, so [(sort-by (slice ns 0 4) (fn [a b] (< a
b)))] works: by the time the [fn] is reached, [(Fn [$t $t] bool)] has
become [(Fn [i32 i32] bool)] and the literal has the position it needs to
take its types from. Left to right, which is the order Odin's operands
@ -4995,7 +4992,7 @@ and generic_call ctx ~want loc name vars pats pret args =
let cret = subst_ty !subst pret in
if List.exists generic_ty cparams || generic_ty cret then begin
(* One generic function calling another at its *own* variable, seen from
the abstract pass over the caller's body [sort-by!] calling [swap!]
the abstract pass over the caller's body [sort-by] calling [swap]
at [t]. There is no copy to make yet: [t] is not a type. The node is
built so the call still type-checks and is thrown away with the rest of
the abstract pass; the real copy is generated when the caller is
@ -5004,7 +5001,7 @@ and generic_call ctx ~want loc name vars pats pret args =
But the callee's [where] clause is answerable here, and has to be. The
whole promise of the abstract pass is that a generic's refusals arrive
at its definition; if the predicate were left to the instantiation,
[(defn f [x $t] () (sort! [x]))] would be accepted at its definition
[(defn f [x $t] () (sort [x]))] would be accepted at its definition
and refused at whichever call site first instantiated it a refusal in
code the caller did not write, which is the thing the pass exists to
avoid. So the caller has to declare at least what the callee asks for,
@ -5313,12 +5310,12 @@ let builtins : (string * string * string) list =
"The value at the key, or None. Nothing signals here — a lookup that \
finds nothing is an answer and the value comes back as a copy of \
its bytes.");
("map-remove!", "map-remove! [(Map K V) K] (Option V)",
("map-remove", "map-remove [(Map K V) K] (Option V)",
"Removes the entry and answers the value it held, or None if there was \
none.");
("map-next!", "map-next! [(Map K V) (Ptr i64) (Ptr K) (Ptr V)] bool",
("map-next", "map-next [(Map K V) (Ptr i64) (Ptr K) (Ptr V)] bool",
"Walks the map one entry per call through a cursor the caller owns, and \
is the whole of map iteration: (while (map-next! m (addr cur) (addr k) \
is the whole of map iteration: (while (map-next m (addr cur) (addr k) \
(addr v)) ...).");
("has-key?", "has-key? [(Map K V) K] bool",
"Whether the key is present, copying no value — the form a condition \
@ -6569,8 +6566,8 @@ let instantiations env gname =
| Some l -> List.rev_map (fun (_, _, sym) -> sym) !l
(* The generic a symbol came from, and the types it was asked for — [None] for
an ordinary function. What a refusal about [sort!-i32] needs in order to
say which line the programmer should look at, since [sort!-i32] appears
an ordinary function. What a refusal about [sort-i32] needs in order to
say which line the programmer should look at, since [sort-i32] appears
nowhere in the source. *)
let instantiation_origin env sym =
Hashtbl.fold

View File

@ -170,8 +170,8 @@ let at loc fmt =
(* ── Names ──────────────────────────────────────────────────────────
A Flan name is not a JS identifier: [rand-u32], [bytes=?], [append!] and
[fn/sort-bytes!/0] are all ordinary. The rule below is injective, which is
A Flan name is not a JS identifier: [rand-u32], [bytes=?], [append] and
[fn/sort-bytes/0] are all ordinary. The rule below is injective, which is
what matters two Flan names must never land on one JS name and readable
second: [-] is the common case and becomes [_], so [rand-u32] reads as
[rand_u32], and an underscore that was actually written becomes [$_] so

View File

@ -112,7 +112,7 @@ let rec fields (f : Form.t) (items : Form.t list) : Ast.field list =
(Form.to_string odd)
(* ── The constraint map at the head of a defn body ──────────────────────
[(defn sort! [s [$t]] () {:where (ordered? $t)} body ...)]. Clojure's
[(defn sort [s [$t]] () {:where (ordered? $t)} body ...)]. Clojure's
[{:pre [...] :post [...]}] is the precedent and the reason it is a map
rather than a bare keyword: it leaves room for further keys without new
syntax.

View File

@ -219,7 +219,7 @@ let source = {flan|
;;
;; What used to be a copy per element type. A [$t] binds a type variable in
;; the signature and every call site instantiates the body at the types it
;; passes, so [(sort! xs)] over a [i32] and over a [f32] are two emitted
;; passes, so [(sort xs)] over a [i32] and over a [f32] are two emitted
;; bodies from one written one.
;;
;; **Two things in the signatures are not decoration.**
@ -243,22 +243,22 @@ let source = {flan|
;; widen their element into [i64] and [f64]; "the wider type $t accumulates
;; into" is a type-level function, which is a constraint system of a different
;; kind, and a generic [sum] that took its accumulator and its [+] would just
;; be [reduce]. [append-i64!] and [append-f64!] are two different primitives.
;; [sort-bytes!] needs [bytes<?] rather than [<] a [[u8]] is not [ordered?]
;; and cannot be so it is [sort-by!] with the comparison written in, and it
;; be [reduce]. [append-i64] and [append-f64] are two different primitives.
;; [sort-bytes] needs [bytes<?] rather than [<] a [[u8]] is not [ordered?]
;; and cannot be so it is [sort-by] with the comparison written in, and it
;; keeps its name because the stability contract in its comment is worth
;; keeping attached to something.
(defn swap! [s [$t] i i32 j i32] ()
(defn swap [s [$t] i i32 j i32] ()
(let [t (at s i)]
(set (at s i) (at s j))
(set (at s j) t)))
(defn reverse! [s [$t]] ()
(defn reverse [s [$t]] ()
(let [i 0
j (- (len s) 1)]
(while (< i j)
(swap! s i j)
(swap s i j)
(set i (+ i 1))
(set j (- j 1)))))
@ -274,7 +274,7 @@ let source = {flan|
;; qsort with a naive comparator does too, and the only fix is not to have
;; NaNs in the array there is no ordering of the reals a NaN sits anywhere
;; in.
(defn sort! [s [$t]] ()
(defn sort [s [$t]] ()
{:where (ordered? $t)}
(let [i 1]
(while (< i (len s))
@ -282,7 +282,7 @@ let source = {flan|
;; `and` short-circuits, which is load-bearing: at j = 0 the left test
;; fails and (at s -1) is never evaluated, so this does not trap.
(while (and (> j 0) (> (at s (- j 1)) (at s j)))
(swap! s (- j 1) j)
(swap s (- j 1) j)
(set j (- j 1))))
(set i (+ i 1)))))
@ -291,7 +291,7 @@ let source = {flan|
;; (fn [a b] (< a b)) is ascending and reversing it is descending and a
;; caller wanting a key rather than an order writes the comparison.
;;
;; It is stable exactly as sort! is: the loop stops the moment before? says
;; It is stable exactly as sort is: the loop stops the moment before? says
;; no, so equal elements never swap past each other. A before? that is not a
;; strict weak ordering one answering true for both (a b) and (b a) is the
;; caller's mistake and shows up as an order, not as a loop: the inner while
@ -301,12 +301,12 @@ let source = {flan|
;; comparison it is given. It is the shape every generic had to take before
;; predicates existed, and it stays because passing a comparison is a real
;; thing to want and not only a workaround.
(defn sort-by! [s [$t] before? (Fn [$t $t] bool)] ()
(defn sort-by [s [$t] before? (Fn [$t $t] bool)] ()
(let [i 1]
(while (< i (len s))
(let [j i]
(while (and (> j 0) (before? (at s j) (at s (- j 1))))
(swap! s (- j 1) j)
(swap s (- j 1) j)
(set j (- j 1))))
(set i (+ i 1)))))
@ -359,7 +359,7 @@ let source = {flan|
;;
;; **Neither takes a {:where}, and that is a decision rather than an
;; oversight.** A predicate buys an *operation* on the variable [ordered?]
;; is what lets sort! write `<` and these perform no operation on their
;; is what lets sort write `<` and these perform no operation on their
;; payload at all: they move it out of the Option, or they look at the tag and
;; never touch the payload. That is the one move [ident] in
;; test/programs/generics.flan makes, which needs nothing declared, so these
@ -377,7 +377,7 @@ let source = {flan|
;; ordinary path. Whether an empty Option is an error is the
;; *caller's* question, and the caller has handler-bind if
;; the answer is yes.
;; a lazy or-else Would need a (Fn [] $t) sort-by!'s shape, available the
;; a lazy or-else Would need a (Fn [] $t) sort-by's shape, available the
;; day something wants it. A macro would get laziness for
;; free and need no generics, and costs more than it buys
;; here: a prelude macro drops every prelude defn that
@ -411,12 +411,12 @@ let source = {flan|
(defn some? [o (Option $t)] bool
(match o (Some _v) true None false))
;; map! writes back into the slice it was handed, for the same reason sort!
;; map-in-place writes back into the slice it was handed, for the same reason sort
;; does a slice is non-owning, and transforming a thing you already own
;; should not allocate. A map that produces a *different* element type is not
;; here: it is two type variables and a second signature, and nothing has
;; wanted it.
(defn map! [s [$t] f (Fn [$t] $t)] ()
(defn map-in-place [s [$t] f (Fn [$t] $t)] ()
(dotimes [i (len s)]
(set (at s i) (f (at s i)))))
@ -1248,7 +1248,7 @@ let source = {flan|
;; replacement character and reports success; the caller then finds three
;; bytes of U+FFFD in its buffer and no indication that it asked for something
;; else. Nothing is written at all when this answers None.
(defn encode-rune! [dst [u8] code i32] (Option i32)
(defn encode-rune [dst [u8] code i32] (Option i32)
(match (rune-size code)
None None
(Some w)
@ -1295,7 +1295,7 @@ let source = {flan|
(defn split-on-byte [s [u8] sep u8] Split
(Split {.rest s .sep sep .more true}))
(defn split-next! [it (Ptr Split)] (Option [u8])
(defn split-next [it (Ptr Split)] (Option [u8])
(when (not (.more it))
(return None))
(match (index-of (.rest it) (.sep it))
@ -1320,7 +1320,7 @@ let source = {flan|
;; saying why rather than shipping it. A string
;; literal is emitted `private unnamed_addr constant` (emit.ml), so (bytes
;; "Hello") is a [u8] pointing straight into read-only memory. An in-place
;; lower-ascii! type checks against that slice, and what happens next depends
;; lower-ascii type checks against that slice, and what happens next depends
;; on the optimiser which is the worst of the available answers. Measured,
;; with (set (at (bytes "Hi") 0) \h):
;;
@ -1384,20 +1384,20 @@ let source = {flan|
(return (< (at a i) (at b i)))))
(< (len a) (len b))))
;; sort-by! with the comparison written in, over the same in-place contract:
;; sort-by with the comparison written in, over the same in-place contract:
;; the *slices* move, never the bytes they point at, so this sorts a [[u8]] of
;; fields borrowed from one buffer without touching the buffer. Stable, and
;; here that is observable two equal fields are two distinct slices of
;; different parts of the input, and a caller can see which one came first.
;;
;; It keeps a name of its own rather than collapsing into sort!, and the
;; It keeps a name of its own rather than collapsing into sort, and the
;; reason is the point of the predicates: a [u8] is not ordered? and cannot
;; be, because < is defined on machine numbers and comparing two slices
;; lexicographically is a loop and not an instruction. bytes<? is that loop.
;; So this is the shape a generic takes when the operation it needs is not a
;; primitive: pass it in.
(defn sort-bytes! [s [[u8]]] ()
(sort-by! s (fn [a b] (bytes<? a b))))
(defn sort-bytes [s [[u8]]] ()
(sort-by s (fn [a b] (bytes<? a b))))
;; Building bytes, which is the tier that needed an allocator
;;
@ -1433,9 +1433,9 @@ let source = {flan|
;; bytes rather than one, and that is this.
;;
;; It takes a (Ptr (Vec u8)) and not a (Vec u8), and the difference is not
;; style: a Vec parameter *moves*, so (append! b s) taking one by value would
;; style: a Vec parameter *moves*, so (append b s) taking one by value would
;; consume the caller's builder on the first call and refuse the second.
(defn append! [b (Ptr (Vec u8)) s [u8]] ()
(defn append [b (Ptr (Vec u8)) s [u8]] ()
(dotimes [i (len s)]
(push (deref b) (at s i))))
@ -1446,11 +1446,11 @@ let source = {flan|
;; views of the same bytes the second call overwrote the first. These copy
;; out of that buffer before returning, so the hazard ends at the call: a
;; builder can hold as many numbers as it likes.
(defn append-i64! [b (Ptr (Vec u8)) n i64] ()
(append! b (i64->bytes n)))
(defn append-i64 [b (Ptr (Vec u8)) n i64] ()
(append b (i64->bytes n)))
(defn append-f64! [b (Ptr (Vec u8)) x f64] ()
(append! b (f64->bytes x)))
(defn append-f64 [b (Ptr (Vec u8)) x f64] ()
(append b (f64->bytes x)))
;; concat and join. Both take a slice of slices, which is the shape a caller
;; already has: an array literal of them, [(bytes "a") (bytes b)], slices to a
@ -1462,7 +1462,7 @@ let source = {flan|
(defn concat [parts [[u8]]] (Vec u8)
(let [b (vec-new u8)]
(dotimes [i (len parts)]
(append! (addr b) (at parts i)))
(append (addr b) (at parts i)))
b))
;; n parts yield n-1 separators, and the empty slice of parts yields the empty
@ -1473,14 +1473,14 @@ let source = {flan|
(let [b (vec-new u8)]
(dotimes [i (len parts)]
(when (> i 0)
(append! (addr b) sep))
(append! (addr b) (at parts i)))
(append (addr b) sep))
(append (addr b) (at parts i)))
b))
(defn repeat-bytes [s [u8] n i32] (Vec u8)
(let [b (vec-new u8)]
(dotimes [i n]
(append! (addr b) s))
(append (addr b) s))
b))
;; The allocating halves of the ASCII case pair. The note above lower-ascii
@ -1517,17 +1517,17 @@ let source = {flan|
(let [b (vec-new u8)
i 0]
(if (= (len from) 0)
(append! (addr b) s)
(append (addr b) s)
(while (< i (len s))
(match (index-of-bytes (slice s i (len s)) from)
(Some k)
(do
(append! (addr b) (slice s i (+ i k)))
(append! (addr b) to)
(append (addr b) (slice s i (+ i k)))
(append (addr b) to)
(set i (+ i k (len from))))
None
(do
(append! (addr b) (slice s i (len s)))
(append (addr b) (slice s i (len s)))
(set i (len s))))))
b))
@ -1554,7 +1554,7 @@ let source = {flan|
it (split-on-byte s sep)
going true]
(while going
(match (split-next! (addr it))
(match (split-next (addr it))
(Some f) (push v f)
None (set going false)))
v))
@ -1572,7 +1572,7 @@ let source = {flan|
;; This returns a Vec, so neither problem is inherited. It uses i64->bytes
;; twice and the two calls are strictly sequential the integer part is copied
;; into the Vec before the fraction is rendered which is the discipline the
;; shared buffer requires and the one append-i64! exists to make automatic.
;; shared buffer requires and the one append-i64 exists to make automatic.
;;
;; Half away from zero, the same rule round-f32 follows, applied at the last
;; digit kept. That is not bit-for-bit printf: printf rounds the *binary* value
@ -1610,16 +1610,16 @@ let source = {flan|
p (clamp prec 0 9)]
(cond
(not (= x x))
(append! (addr b) (bytes "nan"))
(append (addr b) (bytes "nan"))
(and (= x (* x 2.0)) (!= x 0.0))
(append! (addr b) (bytes (if (< x 0.0) "-inf" "inf")))
(append (addr b) (bytes (if (< x 0.0) "-inf" "inf")))
:else
(let [neg (< x 0.0)
m (if neg (- 0.0 x) x)]
(if (>= m 9.0e18)
(append! (addr b) (f64->bytes x))
(append (addr b) (f64->bytes x))
(let [scale (i64 1)]
(dotimes [i p]
(set scale (* scale 10)))
@ -1642,7 +1642,7 @@ let source = {flan|
;; i64->bytes of 0 has no sign to carry.
(when neg
(push b \-))
(append-i64! (addr b) ip)
(append-i64 (addr b) ip)
(when (> p 0)
(push b \.)
;; Left-padded with zeros to exactly p digits. fr is under
@ -1651,7 +1651,7 @@ let source = {flan|
(let [d (i64->bytes fr)]
(dotimes [i (- p (len d))]
(push b \0))
(append! (addr b) d))))))))
(append (addr b) d))))))))
b))
;; Still refused, and what the reason is now
@ -1676,8 +1676,8 @@ let source = {flan|
;; allocation one. format-f64 above is the piece of it
;; that was actually wanted, and `print`/`println` are
;; already the structural walk over any one value.
;; map that changes the Generics, and only that. map!, filter, reduce and
;; element type sort-by! landed the day function values did see
;; map that changes the Generics, and only that. map-in-place, filter, reduce and
;; element type sort-by landed the day function values did see
;; "The ones that take a function" above at i32 and
;; f32, the two element types the rest of that family
;; covers. A map from [i32] to [f32] is the one shape
@ -1686,7 +1686,7 @@ let source = {flan|
;; which is where a per-type family stops being honest.
;; map-keys, map-values Generics and the reason changed, which is the
;; point of naming them separately. It used to be the
;; missing Map iterator; `map-next!` is that iterator
;; missing Map iterator; `map-next` is that iterator
;; and walking a map is expressible now. What a defn
;; still cannot say is (defn map-keys [m {K V}] (Vec K)):
;; a prelude function has to name its types, and there
@ -1699,7 +1699,7 @@ let source = {flan|
;; already has push, so the struct would be a move-only
;; wrapper whose only method is the one it wraps. What
;; was missing was appending a run of bytes, and
;; `append!` above is that.
;; `append` above is that.
;; Files: embedding, slurp and barf
;;
;; One entry per file in an (embed-dir "...") Odin's Load_Directory_File

View File

@ -206,11 +206,11 @@ let compatible ?(origin = fun _ -> None) ~loc (old_ : Tast.program)
plan.org, Hot reload, and open decision #6. *)
if not same then
(* ── When the name is not one the programmer wrote ──────────────
A generic's instantiations are named [sort!-i32], [sort!-f32]
A generic's instantiations are named [sort-i32], [sort-f32]
and so on, and the mangling carries only the *type variables*
so editing the generic's other parameters changes every copy's
signature at once, under the same names. The refusal then
arrives about [sort!-i32], which appears nowhere in the file
arrives about [sort-i32], which appears nowhere in the file
being edited, for a reason invisible at the edited line.
So the refusal says where the name came from: which generic, at

View File

@ -230,7 +230,7 @@ and on a managed ~class~ instance. An ordinary ~struct~ never carries one.
return type, or nested as ~[$t]~ or ~(Vec $t)~ — and bare ~t~ wherever a type's
*name* is an argument in expression position: ~(vec-new t)~, ~(map-new t i32)~,
~(pool-new t)~, and the cast ~(t x)~. This is what makes
~map!~/~filter~/~reduce~ and the monomorphic containers work; it collapsed the
~map-in-place~/~filter~/~reduce~ and the monomorphic containers work; it collapsed the
prelude's per-type families into one function each.
A generic body is checked *abstractly*, with nothing substituted, so ~=~, ~<~,
~+~ and ~hash~ over an unconstrained variable are rejected at the definition
@ -243,7 +243,7 @@ and on a managed ~class~ instance. An ordinary ~struct~ never carries one.
the head of the body — ~{:where (ordered? $t)}~, or a vector for more than one,
~{:where [(copyable? $t) (copyable? $u)]}~ — on the precedent of Clojure's
~{:pre ... :post ...}~, and because a bare ~{}~ in expression position is
already refused so nothing else it could be. ~sort!~ declares ~ordered?~ of its
already refused so nothing else it could be. ~sort~ declares ~ordered?~ of its
variable, the abstract pass then allows ~<~ in the body, and each instantiation
checks the concrete type satisfies the predicate and refuses the call site if it
does not. There are *five* predicates — ~ordered?~, ~equal?~, ~hashable?~,

View File

@ -975,7 +975,7 @@ struct flan_allocator {
*
* Every size this file computes is a signed 64-bit count of bytes, and every
* one of them is a product or a sum of numbers a Flan program chose: a
* capacity from (vec-reserve!), an element size from the checker. Signed
* capacity from (reserve), an element size from the checker. Signed
* overflow is undefined, and the defined-in-practice outcome is worse than
* the undefined one a product that wraps to a small positive allocates a
* block that fits while the container records the unwrapped capacity, and the
@ -1566,7 +1566,7 @@ static int8_t flan_vec_grow(flan_vec *v, int64_t want, int64_t size,
if (cap > (int64_t)1 << 40) { cap = want; break; }
cap *= 2;
}
/* [want] arrives from (vec-reserve!) unfiltered, and the doubling loop above
/* [want] arrives from (reserve) unfiltered, and the doubling loop above
* hands a want past 1<<40 straight through as the capacity, so this product
* is the one the program picked times the one the checker did. See the note
* on flan_mul_bytes. */

View File

@ -263,7 +263,7 @@ move concept it opted out of.) plan.org's Types section has the full
account.
```
(defn sort! [s [$t]] ()
(defn sort [s [$t]] ()
{:where (ordered? $t)}
...)
```

View File

@ -1,6 +1,6 @@
;;;; The slice family at its second and third element types.
;;;;
;;;; sort! was the only sort in the language. These are the other two, and
;;;; sort was the only sort in the language. These are the other two, and
;;;; they are copies rather than an abstraction: map, filter, reduce and a sort
;;;; taking a comparator all need a *function value*, which check.ml refuses
;;;; with "a function type is not implemented yet -- milestone 5". So the
@ -22,41 +22,41 @@
(defn main [] i32
;; Every float literal is cast. A literal defaults to f64 and an array
;; literal has no context to say otherwise -- a let has no type annotation --
;; so [3.5 -1.0] is an [f64] and (sort!) refuses it by type. The cast is
;; so [3.5 -1.0] is an [f64] and (sort) refuses it by type. The cast is
;; the only spelling available today.
;;
;; sort!: duplicates, negatives, a zero and an odd length, which is the
;; sort: duplicates, negatives, a zero and an odd length, which is the
;; input shape the i32 sort is tested on for the same reasons.
(let [xs [(f32 3.5) (f32 -1.0) (f32 0.0) (f32 3.5) (f32 -2.25) (f32 10.0) (f32 0.5)]]
(sort! (slice xs 0 7))
(sort (slice xs 0 7))
(show-f32 (slice xs 0 7))) ; -2.25 -1 0 0.5 3.5 3.5 10
;; In place and ptr+len: sorting a subslice leaves its neighbours alone. That
;; is the whole content of the in-place claim, and a version that copied
;; would pass every test above and fail this one.
(let [xs [(f32 9.0) (f32 4.0) (f32 3.0) (f32 2.0) (f32 1.0) (f32 9.0)]]
(sort! (slice xs 1 5))
(sort (slice xs 1 5))
(show-f32 (slice xs 0 6))) ; 9 1 2 3 4 9
;; Already sorted, reverse sorted, and a single element -- the three inputs
;; where an insertion loop with the comparison the wrong way round still
;; looks plausible.
(let [xs [(f32 1.0) (f32 2.0) (f32 3.0)]]
(sort! (slice xs 0 3))
(sort (slice xs 0 3))
(show-f32 (slice xs 0 3))) ; 1 2 3
(let [xs [(f32 3.0) (f32 2.0) (f32 1.0)]]
(sort! (slice xs 0 3))
(sort (slice xs 0 3))
(show-f32 (slice xs 0 3))) ; 1 2 3
(let [xs [(f32 7.0)]]
(sort! (slice xs 0 1))
(sort (slice xs 0 1))
(show-f32 (slice xs 0 1))) ; 7
;; The empty slice must not read (at s -1).
(let [xs [(f32 7.0)]]
(sort! (slice xs 0 0))
(sort (slice xs 0 0))
(show-f32 (slice xs 0 0))) ;
(let [xs [(f32 1.0) (f32 2.0) (f32 3.0) (f32 4.0)]]
(reverse! (slice xs 0 4))
(reverse (slice xs 0 4))
(show-f32 (slice xs 0 4))) ; 4 3 2 1
;; min, max and sum. The empty slice is None for the first two -- there is no
@ -102,17 +102,17 @@
(print (bytes<? (slice hi 0 1) (slice lo 0 1))) ; false
(println ""))
;; sort-bytes! over the fields split out of one buffer. The slices move and
;; sort-bytes over the fields split out of one buffer. The slices move and
;; the bytes never do, so this sorts a borrowed view of a string literal --
;; which an in-place byte sort could not, since a literal lives in .rodata.
(let [f (split (bytes "pear,apple,Fig,apple,banana") \,)]
(sort-bytes! (as-slice f))
(sort-bytes (as-slice f))
(show-fields (as-slice f)) ; Fig apple apple banana pear
(free f))
;; And the round trip the whole second tier is for: split, sort, join.
(let [f (split (bytes "delta,alpha,charlie,bravo") \,)]
(sort-bytes! (as-slice f))
(sort-bytes (as-slice f))
(let [j (join (as-slice f) (bytes " < "))]
(println (string (as-slice j))) ; alpha < bravo < charlie < delta
(free j))

View File

@ -60,7 +60,7 @@
(dotimes [i (len items)]
(set n (+ n (count-leaves (at items i)))))
n)
;; map-next! fills an out-parameter with a copy of the value's bytes,
;; map-next fills an out-parameter with a copy of the value's bytes,
;; which for a Value holding a container is a second header over the same
;; block. In a region that is an alias and not a second owner — nothing
;; here owns anything, the arena does — so walking a map is the ordinary
@ -70,7 +70,7 @@
cur (i64 0)
k ""
v edn/Value.Nil]
(while (map-next! entries (addr cur) (addr k) (addr v))
(while (map-next entries (addr cur) (addr k) (addr v))
(set n (+ n (count-leaves v))))
n)
_ 1))

View File

@ -86,10 +86,10 @@
;; A document in a buffer this program owns and can write to. (bytes "literal")
;; is not that — a literal is constant data behind a writable-looking slice —
;; so the source is built with append! and the write goes through as-slice.
;; so the source is built with append and the write goes through as-slice.
(defn survives-its-buffer [] ()
(let [buf (vec-new u8)]
(append! (addr buf) (bytes "{:name \"level-1\" :xs [1 2]}"))
(append (addr buf) (bytes "{:name \"level-1\" :xs [1 2]}"))
(let [src (as-slice buf)
v (edn/read src)]
(dotimes [i (len src)]

View File

@ -10,7 +10,7 @@
;; The shape map/filter/reduce want: the function arrives as a parameter, is
;; called, and is never stored.
(defn each! [xs [i32] f (Fn [i32] i32)] ()
(defn each [xs [i32] f (Fn [i32] i32)] ()
(dotimes [i (len xs)]
(set (at xs i) (f (at xs i)))))
@ -23,11 +23,11 @@
;; A comparator, which is the other half of what was blocked: a sort that is
;; told the order rather than having it written in. Insertion sort, because the
;; point here is the parameter and not the algorithm.
(defn insertion-by! [xs [i32] before? (Fn [i32 i32] bool)] ()
(defn insertion-by [xs [i32] before? (Fn [i32 i32] bool)] ()
(dotimes [i (len xs)]
(let [j i]
(while (and (> j 0) (before? (at xs j) (at xs (- j 1))))
(swap! xs j (- j 1))
(swap xs j (- j 1))
(set j (- j 1))))))
(defn ascending [a i32 b i32] bool (< a b))
@ -62,7 +62,7 @@
(defn main [] i32
(let [xs [1 2 3 4]]
;; A name in value position, passed down.
(each! (slice xs 0 4) double)
(each (slice xs 0 4) double)
(print (at xs 0)) (print " ") (print (at xs 3)) (println "")
;; 2 + 4 + 6 + 8 negated
(println (fold (slice xs 0 4) negate))
@ -75,12 +75,12 @@
;; A comparator, and the same slice sorted both ways.
(let [ys [3 1 4 1 5 9 2 6]
s (slice ys 0 8)]
(insertion-by! s ascending)
(insertion-by s ascending)
(print (at s 0)) (print " ") (print (at s 7)) (println "")
(insertion-by! s descending)
(insertion-by s descending)
(print (at s 0)) (print " ") (print (at s 7)) (println "")
;; A returned function value, and a computed head calling it.
(insertion-by! s (pick true))
(insertion-by s (pick true))
(print (at s 0)) (println "")
(println ((pick false) 1 2)))

View File

@ -87,13 +87,13 @@
;; needs the integer part copied out before the fraction is rendered, because
;; both come through the runtime's one shared scratch buffer.
(let [b (vec-new u8)]
(append! (addr b) (bytes "fps "))
(append (addr b) (bytes "fps "))
(let [f (format-f64 59.94 1)]
(append! (addr b) (as-slice f))
(append (addr b) (as-slice f))
(free f))
(append! (addr b) (bytes " / frame "))
(append (addr b) (bytes " / frame "))
(let [f (format-f64 0.0166667 4)]
(append! (addr b) (as-slice f))
(append (addr b) (as-slice f))
(free f))
(println (string (as-slice b))) ; fps 59.9 / frame 0.0167
(free b))

View File

@ -25,11 +25,11 @@
(defn first-or [s [$t] d $t] $t
(if (= (len s) 0) d (at s 0)))
;; A generic calling a generic at its own variable: the copy of [swap!] is
;; generated when [rotate!] is instantiated and not before.
(defn rotate! [s [$t]] ()
;; A generic calling a generic at its own variable: the copy of [swap] is
;; generated when [rotate] is instantiated and not before.
(defn rotate [s [$t]] ()
(dotimes [i (- (len s) 1)]
(swap! s i (+ i 1))))
(swap s i (+ i 1))))
;; numeric? admits + - * / %.
(defn twice [x $t] $t
@ -121,7 +121,7 @@
fs [2.5 0.5 1.5]]
(println (first-or (slice ns 0 4) -1))
(println (first-or (slice ns 0 0) -1))
(rotate! (slice ns 0 4))
(rotate (slice ns 0 4))
(println (at ns 3))
(println (twice 21))
@ -136,13 +136,13 @@
(show "text")
;; The collapsed prelude family, at both element types.
(sort! (slice ns 0 4))
(sort (slice ns 0 4))
(println (at ns 0))
(sort-by! (slice fs 0 3) (fn [a b] (> a b)))
(sort-by (slice fs 0 3) (fn [a b] (> a b)))
(println (at fs 0))
(reverse! (slice ns 0 4))
(reverse (slice ns 0 4))
(println (at ns 0))
(map! (slice ns 0 4) (fn [x] (* x 2)))
(map-in-place (slice ns 0 4) (fn [x] (* x 2)))
(println (reduce (slice ns 0 4) 0 (fn [a b] (+ a b))))
(match (min-of (slice ns 0 4)) (Some m) (println m) _ (println -1))
(match (max-of (slice fs 0 3)) (Some m) (println m) _ (println -1.0))

View File

@ -1,4 +1,4 @@
;; The prelude's function-taking family: map!, filter, reduce and a comparator
;; The prelude's function-taking family: map-in-place, filter, reduce and a comparator
;; sort. These were the four the second tier could not write, and they arrived
;; the day function values did — so what this checks is that they are ordinary
;; prelude functions, called the ordinary way, with the function passed by
@ -12,10 +12,10 @@
(defn big? [x f32] bool (> x 1.0))
(defn main [] i32
;; map! writes back into the slice it was handed.
;; map-in-place writes back into the slice it was handed.
(let [xs [1 2 3 4]
s (slice xs 0 4)]
(map! s triple)
(map-in-place s triple)
(print (at s 0)) (print " ") (print (at s 3)) (println "")
;; reduce, with the accumulator first in the step. The prelude's own
@ -30,21 +30,21 @@
(free v))
;; A comparator sort, both directions off the same slice.
(sort-by! s longer-first)
(sort-by s longer-first)
(print (at s 0)) (print " ") (print (at s 3)) (println "")
(sort-by! s (fn [a b] (< a b)))
(sort-by s (fn [a b] (< a b)))
(print (at s 0)) (print " ") (print (at s 3)) (println ""))
;; The f32 half of the family, which is the same code at the other element
;; type — the copy that generics would remove.
(let [ys [(f32 4.0) (f32 1.0) (f32 8.0) (f32 2.0)]
t (slice ys 0 4)]
(map! t halve)
(map-in-place t halve)
(print (at t 0)) (print " ") (print (at t 2)) (println "")
(print (reduce t 0.0 (fn [a b] (+ a b)))) (println "")
(let [w (filter t big?)]
(print (len w)) (println "")
(free w))
(sort-by! t (fn [a b] (> a b)))
(sort-by t (fn [a b] (> a b)))
(print (at t 0)) (print " ") (print (at t 3)) (println ""))
0)

View File

@ -208,7 +208,7 @@
(dotimes [i (len items)]
(set n (+ n (count-leaves (at items i)))))
n)
;; map-next! fills an out-parameter with a copy of the value's bytes, which
;; map-next fills an out-parameter with a copy of the value's bytes, which
;; for a Value holding a container is a second header over the same block.
;; In a region that is an alias and not a second owner, so walking a map is
;; the ordinary iteration and needs no accessor of its own.
@ -217,7 +217,7 @@
cur (i64 0)
k ""
e Value.Null]
(while (map-next! entries (addr cur) (addr k) (addr e))
(while (map-next entries (addr cur) (addr k) (addr e))
(set n (+ n (count-leaves e))))
n)
_ 1))
@ -347,7 +347,7 @@
;; Strings: the escape grammar, and the two surrogate halves. A lone
;; surrogate is refused because rune-size answers None for the whole
;; D800-DFFF block, so encode-rune! would write nothing and the character
;; D800-DFFF block, so encode-rune would write nothing and the character
;; would vanish — the refusal is forced by the prelude rather than chosen.
(refusal "\"a\\vb\"") ; \v is JSON5's
(refusal "\"a\\x41b\"") ; so is \x
@ -405,7 +405,7 @@
;; two lifetimes have to be separable for the scribble below to mean
;; anything.
(let [buf (vec-new u8)]
(append! (addr buf) (bytes doc))
(append (addr buf) (bytes doc))
(with-allocator frame
(let [v (read-doc (as-slice buf))]
(println (describe v)) ; object

View File

@ -19,7 +19,7 @@
keys 0
vals 0
n 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
(set keys (+ keys k))
(set vals (+ vals v))
(set n (+ n 1)))
@ -35,12 +35,12 @@
k 0
v 0
n 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
(set n (+ n 1)))
(print "never allocated: ") (print n) (println "")
(reserve m 64)
(set cur (i64 0))
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
(set n (+ n 1)))
(print "allocated and empty: ") (print n) (println "")
(free m)))
@ -52,9 +52,9 @@
(put m 5 50)
(put m 6 60)
(let [cur (i64 0) k 0 v 0 n 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
(set n (+ n 1)))
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
(set n (+ n 1)))
(print "spent: ") (print n) (println ""))
(free m)))
@ -75,7 +75,7 @@
chars 0
xs 0
ys 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
(set chars (+ chars (len k)))
(set xs (+ xs (.x v)))
(set ys (+ ys (.y v))))
@ -94,7 +94,7 @@
v (i64 0)
n 0
doubled 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
(set n (+ n 1))
(when (= v (* k 2)) (set doubled (+ doubled 1))))
(print n) (print " ") (print doubled) (print " ") (print (len m)) (println ""))

View File

@ -1,4 +1,4 @@
;;;; (map-remove! m k) — the operation a Map has been missing.
;;;; (map-remove m k) — the operation a Map has been missing.
;;;;
;;;; Removal is the one map operation that can break the *other* ones: Robin
;;;; Hood lookups stop at the first empty slot, so a hole punched in the middle
@ -19,12 +19,12 @@
(let [m (map-new i32 i64)]
(put m 1 100)
(put m 2 200)
(match (map-remove! m 1)
(match (map-remove m 1)
(Some v) (do (print v) (println "")) ; 100
None (println "missing"))
(print (len m)) (println "") ; 1
(print (has-key? m 1)) (println "") ; false
(match (map-remove! m 1) (Some v) (do (print v) (println "")) None (println "gone"))
(match (map-remove m 1) (Some v) (do (print v) (println "")) None (println "gone"))
(println (len m)) ; gone, then 1
(free m))
@ -32,7 +32,7 @@
;; the block is still there and the slots are empty rather than poisoned.
(let [m (map-new i32 i32)]
(dotimes [i 64] (put m i i))
(dotimes [i 64] (map-remove! m i))
(dotimes [i 64] (map-remove m i))
(print (len m)) (println "") ; 0
(put m 7 77)
(match (get m 7) (Some v) (do (print v) (println "")) None (println "?")) ; 77
@ -49,7 +49,7 @@
(let [taken 0]
(dotimes [i 2000]
(if (= 0 (% i 2))
(match (map-remove! m i)
(match (map-remove m i)
(Some v) (if (= v (* (i64 i) 3)) (set taken (+ taken 1)))
None (set taken taken))))
(print taken) (println "")) ; 1000
@ -72,7 +72,7 @@
;; on the removal path too and not only on get's.
(let [g (map-new Cell i32)]
(dotimes [i 20] (dotimes [j 20] (put g (Cell {.x i .y j}) (+ (* i 100) j))))
(match (map-remove! g (Cell {.x 7 .y 9}))
(match (map-remove g (Cell {.x 7 .y 9}))
(Some v) (do (print v) (println "")) ; 709
None (println "?"))
(print (has-key? g (Cell {.x 7 .y 9}))) (println "") ; false
@ -83,7 +83,7 @@
(let [s (map-new string i32)]
(put s "alpha" 1)
(put s "beta" 2)
(match (map-remove! s "alpha")
(match (map-remove s "alpha")
(Some v) (do (print v) (println "")) ; 1
None (println "?"))
(print (has-key? s "beta")) (println "") ; true
@ -95,9 +95,9 @@
;; shift, the same way a put that grows invalidates one.
(let [m (map-new i32 i32)]
(dotimes [i 100] (put m i i))
(dotimes [i 100] (if (= 0 (% i 3)) (map-remove! m i)))
(dotimes [i 100] (if (= 0 (% i 3)) (map-remove m i)))
(let [cur (i64 0) k 0 v 0 seen 0 sum 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(while (map-next m (addr cur) (addr k) (addr v))
(set seen (+ seen 1))
(if (not (= k v)) (set sum (+ sum 1))))
(print seen) (println "") ; 66
@ -113,7 +113,7 @@
(with-allocator ar
(let [t (map-new i32 i32)]
(dotimes [i 300] (put t i (* i 2)))
(dotimes [i 300] (if (= 0 (% i 2)) (map-remove! t i)))
(dotimes [i 300] (if (= 0 (% i 2)) (map-remove t i)))
(print (len t)) (println "") ; 150
(match (get t 299) (Some v) (do (print v) (println "")) None (println "?")) ; 598
(print (has-key? t 298)) (println ""))) ; false

View File

@ -10,13 +10,13 @@
(defvar counter i64)
(defn put! [xs [$t] i i32 v $t] ()
(defn put-at [xs [$t] i i32 v $t] ()
(set (at xs i) v))
;;; Calls [put!] at its own variable, so the copy of [put!] is generated when
;;; [hold!] is instantiated and not before.
(defn hold! [xs [$t] v $t] ()
(put! xs 0 v))
;;; Calls [put-at] at its own variable, so the copy of [put-at] is generated when
;;; [hold] is instantiated and not before.
(defn hold [xs [$t] v $t] ()
(put-at xs 0 v))
(defn pick [xs [$t]] $t
{:where (ordered? $t)}
@ -28,8 +28,8 @@
(defn step [] ()
(let [ns [5 3 9 1]
fs [2.5 0.5 1.5]]
(hold! (slice ns 0 4) 7)
(hold! (slice fs 0 3) 0.25)
(hold (slice ns 0 4) 7)
(hold (slice fs 0 3) 0.25)
(set counter (+ counter (i64 (pick (slice ns 0 4)))))))
(defn main [] ()

View File

@ -50,33 +50,33 @@
(println "") ; 99
;; Reverse of an odd-length slice: the middle element stays put.
(reverse! (slice xs 0 (len xs)))
(reverse (slice xs 0 (len xs)))
(show (slice xs 0 (len xs))) ; 7 -3 12 0 5 -3 5
;; And of a two-element one, the smallest case that can actually move.
(reverse! (slice xs 0 2))
(reverse (slice xs 0 2))
(show (slice xs 0 (len xs))) ; -3 7 12 0 5 -3 5
(load-xs)
(sort! (slice xs 0 (len xs)))
(sort (slice xs 0 (len xs)))
(show (slice xs 0 (len xs))) ; -3 -3 0 5 5 7 12
;; Reverse-sorted: the case a comparison that never fires would pass.
(set (at ys 0) 5) (set (at ys 1) 4) (set (at ys 2) 3)
(set (at ys 3) 2) (set (at ys 4) 1)
(sort! (slice ys 0 (len ys)))
(sort (slice ys 0 (len ys)))
(show (slice ys 0 (len ys))) ; 1 2 3 4 5
;; A subslice, with the elements on both sides left alone.
(set (at zs 0) 100) (set (at zs 1) 9) (set (at zs 2) -1)
(set (at zs 3) 9) (set (at zs 4) 4) (set (at zs 5) 0)
(set (at zs 6) 200) (set (at zs 7) 300)
(sort! (slice zs 1 6))
(sort (slice zs 1 6))
(show (slice zs 0 (len zs))) ; 100 -1 0 4 9 9 200 300
;; Degenerate lengths must do nothing rather than run off an end.
(sort! (slice zs 0 0))
(reverse! (slice zs 0 0))
(sort! (slice zs 2 3))
(reverse! (slice zs 2 3))
(sort (slice zs 0 0))
(reverse (slice zs 0 0))
(sort (slice zs 2 3))
(reverse (slice zs 2 3))
(show (slice zs 0 (len zs))) ; 100 -1 0 4 9 9 200 300
0)

View File

@ -20,12 +20,12 @@
;; i64->bytes on its own: two of its results cannot be held at once, and
;; these two numbers are both in the answer.
(let [b (vec-new u8)]
(append! (addr b) (bytes "x="))
(append-i64! (addr b) 42)
(append! (addr b) (bytes " y="))
(append-i64! (addr b) -7)
(append! (addr b) (bytes " r="))
(append-f64! (addr b) 1.5)
(append (addr b) (bytes "x="))
(append-i64 (addr b) 42)
(append (addr b) (bytes " y="))
(append-i64 (addr b) -7)
(append (addr b) (bytes " r="))
(append-f64 (addr b) 1.5)
(show (addr b)) ; x=42 y=-7 r=1.5
(free b))

View File

@ -68,7 +68,7 @@
;; trip is the only check that catches an encoder and a decoder that are
;; wrong in the same direction — printing the bytes would not.
(defn round-trip [code i32] i32
(match (encode-rune! (slice scratch 0 4) code)
(match (encode-rune (slice scratch 0 4) code)
None -1
(Some w)
(let [r (decode-rune (slice scratch 0 w))]
@ -82,7 +82,7 @@
(let [it (split-on-byte s sep)
going true]
(while going
(match (split-next! (addr it))
(match (split-next (addr it))
(Some f) (do (print "[") (print f) (print "]"))
None (set going false)))
(print " ")))
@ -181,13 +181,13 @@
(show-i32 (round-trip 0x10ffff))
(println "")
;; Refused by encode-rune!, and nothing is written when it refuses.
(show-opt (encode-rune! (slice scratch 0 4) 0xd800)) ; -1, surrogate
(show-opt (encode-rune! (slice scratch 0 4) 0x110000)) ; -1, past the end
(show-opt (encode-rune! (slice scratch 0 4) -1)) ; -1, negative
(show-opt (encode-rune! (slice scratch 0 2) 0x65e5)) ; -1, buffer short
(show-opt (encode-rune! (slice scratch 0 0) 0x41)) ; -1, no room at all
(show-opt (encode-rune! (slice scratch 0 1) 0x41)) ; 1, exactly enough
;; Refused by encode-rune, and nothing is written when it refuses.
(show-opt (encode-rune (slice scratch 0 4) 0xd800)) ; -1, surrogate
(show-opt (encode-rune (slice scratch 0 4) 0x110000)) ; -1, past the end
(show-opt (encode-rune (slice scratch 0 4) -1)) ; -1, negative
(show-opt (encode-rune (slice scratch 0 2) 0x65e5)) ; -1, buffer short
(show-opt (encode-rune (slice scratch 0 0) 0x41)) ; -1, no room at all
(show-opt (encode-rune (slice scratch 0 1) 0x41)) ; 1, exactly enough
(println "")
;; "Nothing is written when it refuses" is a claim about the buffer, not
@ -197,9 +197,9 @@
;; passes. So put a known byte in scratch, ask for an encoding that must be
;; refused, and read the byte back.
(show-i32 (round-trip 0x41)) ; 65, scratch[0] = A
(show-opt (encode-rune! (slice scratch 0 2) 0x65e5)) ; -1, needs 3 bytes
(show-opt (encode-rune (slice scratch 0 2) 0x65e5)) ; -1, needs 3 bytes
(show-i32 (i32 (at scratch 0))) ; 65 still
(show-opt (encode-rune! (slice scratch 0 4) 0xd800)) ; -1, surrogate
(show-opt (encode-rune (slice scratch 0 4) 0xd800)) ; -1, surrogate
(show-i32 (i32 (at scratch 0))) ; 65 still
(println "")
@ -224,7 +224,7 @@
total (i64 0)
going true]
(while going
(match (split-next! (addr it))
(match (split-next (addr it))
(Some f) (set total (+ total (match (parse-i64 (trim f)) (Some v) v None 0)))
None (set going false)))
(print total)

View File

@ -797,7 +797,7 @@ let () =
outputs "a number with a precision" "programs/format.flan" format_out;
outputs ~opt:"-O0" "a number with a precision, -O0" "programs/format.flan"
format_out;
(* The slice family at its second and third element types. sort-i32! was
(* The slice family at its second and third element types. sort-i32 was
the only sort in the language; these are copies rather than an
abstraction, because map/filter/reduce and a comparator sort all need a
function value and check.ml refuses one outright.
@ -2760,9 +2760,9 @@ level "1"
and it is named because it is the natural spelling of a set. *)
(* Removal is a map's operation and says so, rather than reaching for a
[len] that a Vec would also answer. *)
refuses_src "map-remove! wants a map"
"(defn main [] i32 (let [v (vec-new i32)] (map-remove! v 1) (free v)) 0)"
"map-remove! takes a (Map K V)";
refuses_src "map-remove wants a map"
"(defn main [] i32 (let [v (vec-new i32)] (map-remove v 1) (free v)) 0)"
"map-remove takes a (Map K V)";
refuses_src "a float is not a map key"
"(defn f [m (Map f32 i32)] () 0)" "is not a map key";
refuses_src "a Ptr is not a map key"
@ -2860,7 +2860,7 @@ level "1"
outputs ~dev:true "function values, dev" "programs/fn-values.flan"
fn_values_out;
(* The prelude's four, which is the point of the whole lane: map!, filter,
(* The prelude's four, which is the point of the whole lane: map-in-place, filter,
reduce and a comparator sort were blocked on function values and not on
generics, so they arrived without generics and are still one copy per
element type, which is the generics half. The f32 rows are that copy.

View File

@ -1925,13 +1925,13 @@ let () =
let lines, _ =
with_config
{ Cimport.no_config with Cimport.renames = [ ("set_seed", "seed!") ] }
{ Cimport.no_config with Cimport.renames = [ ("set_seed", "seed") ] }
in
(* The C symbol is kept verbatim, so an override changes the Flan face and
nothing else which is what makes it safe to spell a predicate the way
Lisp spells one. *)
check "a name override is the Flan name, and the C symbol is untouched"
(List.mem "(declare-c seed! [seed u32] \"set_seed\")" lines);
(List.mem "(declare-c seed [seed u32] \"set_seed\")" lines);
(* The collision above is refused because neither Spin2D nor spin2d may take
[spin-2d] by an accident of header order. Naming one of them is the way
@ -2696,9 +2696,9 @@ let () =
write. *)
rejects_check "a predicate is not carried through a generic call"
~needle:"has to be carried by every signature"
"(defn outer [s [$t]] () {:where (equal? $t)} (sort! s))";
"(defn outer [s [$t]] () {:where (equal? $t)} (sort s))";
accepts "and is accepted when it is"
"(defn outer [s [$t]] () {:where (ordered? $t)} (sort! s))";
"(defn outer [s [$t]] () {:where (ordered? $t)} (sort s))";
(* A map key that is a type variable has no hash and no equality to emit:
they are chosen from the concrete type, which does not exist yet. So the
@ -2719,9 +2719,9 @@ let () =
(* And so does the removal, whose placeholder is [get]'s for the same reason:
it answers an (Option V), so the match around it still has to check while
the key is a variable. *)
accepts "map-remove! over a type-variable key answers an (Option V)"
accepts "map-remove over a type-variable key answers an (Option V)"
"(defn f [m (Map $t i32) k $t] i32 {:where (hashable? $t)} \
(match (map-remove! m k) (Some v) v _ 0))";
(match (map-remove m k) (Some v) v _ 0))";
accepts "and so do has-key?, reserve and clone"
"(defn f [m (Map $t i32) k $t] bool {:where (hashable? $t)} \
(do (reserve m 8) (let [c (clone m)] (free c) (has-key? m k))))";

View File

@ -801,9 +801,9 @@ let () =
(* 1. [C-c C-c] on a generic used to report [installs=false, fns=[]]: it
installed nothing and did not say anything had gone wrong. Both copies
have to be named, and the copy of [put!] that [hold!] pulls in has to be
have to be named, and the copy of [put-at] that [hold] pulls in has to be
there too, which is transitivity. *)
(match Session.eval (gen ()) "(defn hold! [xs [$t] v $t] () (put! xs 0 v) (put! xs 0 v))" with
(match Session.eval (gen ()) "(defn hold [xs [$t] v $t] () (put-at xs 0 v) (put-at xs 0 v))" with
| c ->
if not c.Session.installs then
fail "redefining a generic installed nothing";
@ -812,28 +812,28 @@ let () =
if not (List.mem want c.Session.fns) then
fail "redefining a generic did not install %s; it installed %s"
want (String.concat " " c.Session.fns))
[ "hold!-i32"; "hold!-f64" ];
(* And only its own copies: [put!] did not change, and its copies are
[ "hold-i32"; "hold-f64" ];
(* And only its own copies: [put-at] did not change, and its copies are
reached through their cells, so reinstalling them would be work with
no effect. *)
if List.mem "put!-i32" c.Session.fns then
if List.mem "put-at-i32" c.Session.fns then
fail "redefining a generic reinstalled an unchanged generic's copies"
| exception Loc.Error { Loc.dmsg = m; _ } ->
fail "redefining a generic: %s" m);
(* The callee side of the same rule: redefining [put!] reinstalls the copies
of [put!], which exist only because [hold!] asked for them the
(* The callee side of the same rule: redefining [put-at] reinstalls the copies
of [put-at], which exist only because [hold] asked for them the
instantiation that generated them was transitive, and finding them again
is one table lookup rather than a walk, because a whole-program check has
already regenerated all of them. *)
(match Session.eval (gen ()) "(defn put! [xs [$t] i i32 v $t] () (set (at xs i) v))" with
(match Session.eval (gen ()) "(defn put-at [xs [$t] i i32 v $t] () (set (at xs i) v))" with
| c ->
List.iter
(fun want ->
if not (List.mem want c.Session.fns) then
fail "redefining a called generic did not install %s; it \
installed %s" want (String.concat " " c.Session.fns))
[ "put!-i32"; "put!-f64" ]
[ "put-at-i32"; "put-at-f64" ]
| exception Loc.Error { Loc.dmsg = m; _ } ->
fail "redefining a generic: %s" m);
@ -891,20 +891,20 @@ let () =
location a better error than this one and the reason this path is
reached less often than it looks. What reaches here is a change every
call site still accepts and every *copy* does not: widening the index
from i32 to i64 leaves [(put! xs 0 v)] checking, because the literal
adapts, and changes [put!-i32]'s signature underneath every compiled
from i32 to i64 leaves [(put-at xs 0 v)] checking, because the literal
adapts, and changes [put-at-i32]'s signature underneath every compiled
caller. *)
(match
Session.eval (gen ())
"(defn put! [xs [$t] i i64 v $t] () \
"(defn put-at [xs [$t] i i64 v $t] () \
(set (at xs (i32 i)) v))"
with
| _ -> fail "a generic's changed parameter type was accepted"
| exception Loc.Error { Loc.dmsg = m; _ } ->
if not (has m "changes signature") then
fail "a generic's changed parameter type: %S" m;
if not (has m "the copy of the generic put!") then
fail "the refusal did not say the name came from put!: %S" m;
if not (has m "the copy of the generic put-at") then
fail "the refusal did not say the name came from put-at: %S" m;
if not (has m "every copy of it at once") then
fail "the refusal did not say every copy changed together: %S" m);

View File

@ -87,7 +87,7 @@
;; would be keeping a handle for an operation that never happens.
(defn copy-text [s [u8]] string
(let [b (vec-new u8)]
(append! (addr b) s)
(append (addr b) s)
(string (as-slice b))))
;; ── Structural equality ─────────────────────────────────────────────
@ -148,7 +148,7 @@
(let [cur (i64 0)
k ""
v Value.Nil]
(while (map-next! a (addr cur) (addr k) (addr v))
(while (map-next a (addr cur) (addr k) (addr v))
(match (get b k)
(Some w) (when (not (value=? v w)) (return false))
None (return false))))

10
vendor/json/json.flan vendored
View File

@ -465,7 +465,7 @@
;; the last place that knows that offset — string-of runs over a literal this
;; has already accepted, and an error out of it would have to point at the
;; token rather than at the backslash. Surrogate PAIRING is checked here too,
;; and not only escape syntax, so that string-of's encode-rune! can never be
;; and not only escape syntax, so that string-of's encode-rune can never be
;; handed a code point the prelude refuses.
(defn read-string [c (Ptr Cursor) lo i32] Token
(let [s (.src c)
@ -500,7 +500,7 @@
;; A high surrogate has to be followed by \u and a low one.
;; A low one on its own, or a high one followed by anything
;; else, encodes no character: rune-size answers None for the
;; whole D800-DFFF block and encode-rune! writes nothing, so
;; whole D800-DFFF block and encode-rune writes nothing, so
;; the alternative to refusing here is a silently dropped
;; character later. That refusal is forced by the prelude
;; rather than chosen, and it is the reason the pairing rule
@ -703,11 +703,11 @@
(set r (+ 0x10000
(bit-or (<< (- r 0xd800) 10) (- lo2 0xdc00))))))
;; A four-byte buffer and not a push per byte, because
;; encode-rune! is the prelude's answer for this and writing
;; encode-rune is the prelude's answer for this and writing
;; the shifts again here would be a second copy of UTF-8.
(let [buf (array 4 u8)]
(match (encode-rune! (slice buf 0 4) r)
(Some w) (append! (addr b) (slice buf 0 w))
(match (encode-rune (slice buf 0 4) r)
(Some w) (append (addr b) (slice buf 0 w))
;; Unreachable: read-string refuses every code point
;; rune-size refuses. Written as a no-op rather than a trap
;; because a dropped character is not worth a crash and the

View File

@ -941,7 +941,7 @@ are four predicates, and each gates builtins the compiler already has:</p>
<p>They entail each other in one direction, so one clause usually does:
<code>numeric?</code> gives <code>ordered?</code>, and <code>ordered?</code> gives
<code>equal?</code>. A <code>sort!</code> that compares its elements declares
<code>equal?</code>. A <code>sort</code> that compares its elements declares
<code>ordered?</code> and nothing else.</p>
<p><strong>Every value copies.</strong> There used to be a fifth predicate,
@ -1044,13 +1044,13 @@ over.</p>
<div class="scroll">
<table>
<tr><th>Group</th><th>Names</th></tr>
<tr><td>slice algorithms, over one type variable</td><td><code>swap!</code>, <code>reverse!</code>, <code>sort!</code>, <code>sort-by!</code>, <code>index-of</code>, <code>min-of</code>, <code>max-of</code>, <code>map!</code>, <code>reduce</code>, <code>filter</code></td></tr>
<tr><td>slice algorithms, over one type variable</td><td><code>swap</code>, <code>reverse</code>, <code>sort</code>, <code>sort-by</code>, <code>index-of</code>, <code>min-of</code>, <code>max-of</code>, <code>map-in-place</code>, <code>reduce</code>, <code>filter</code></td></tr>
<tr><td>the per-type layer that stays</td><td><code>sum-i32</code>, <code>sum-f32</code> — the element and the accumulator are different types, which one variable cannot say</td></tr>
<tr><td>bytes</td><td><code>bytes=?</code>, <code>bytes&lt;?</code>, <code>bytes-ci=?</code>, <code>starts-with?</code>, <code>ends-with?</code>, <code>index-of-bytes</code>, <code>trim</code>, <code>digit?</code>, <code>space?</code>, <code>sort-bytes!</code></td></tr>
<tr><td>bytes</td><td><code>bytes=?</code>, <code>bytes&lt;?</code>, <code>bytes-ci=?</code>, <code>starts-with?</code>, <code>ends-with?</code>, <code>index-of-bytes</code>, <code>trim</code>, <code>digit?</code>, <code>space?</code>, <code>sort-bytes</code></td></tr>
<tr><td>parsing</td><td><code>parse-i64</code>, <code>parse-f64</code></td></tr>
<tr><td>text</td><td><code>split-on-byte</code>, <code>split-next!</code>, <code>split</code>, <code>lower-ascii</code>, <code>upper-ascii</code>, <code>to-lower</code>, <code>to-upper</code></td></tr>
<tr><td>building bytes</td><td><code>append!</code>, <code>append-i64!</code>, <code>append-f64!</code>, <code>concat</code>, <code>join</code>, <code>repeat-bytes</code>, <code>replace-bytes</code>, <code>slices-new</code>, <code>format-f64</code></td></tr>
<tr><td>UTF-8</td><td><code>decode-rune</code>, <code>rune-at</code>, <code>rune-count</code>, <code>rune-size</code>, <code>rune-start?</code>, <code>valid-utf8?</code>, <code>encode-rune!</code></td></tr>
<tr><td>text</td><td><code>split-on-byte</code>, <code>split-next</code>, <code>split</code>, <code>lower-ascii</code>, <code>upper-ascii</code>, <code>to-lower</code>, <code>to-upper</code></td></tr>
<tr><td>building bytes</td><td><code>append</code>, <code>append-i64</code>, <code>append-f64</code>, <code>concat</code>, <code>join</code>, <code>repeat-bytes</code>, <code>replace-bytes</code>, <code>slices-new</code>, <code>format-f64</code></td></tr>
<tr><td>UTF-8</td><td><code>decode-rune</code>, <code>rune-at</code>, <code>rune-count</code>, <code>rune-size</code>, <code>rune-start?</code>, <code>valid-utf8?</code>, <code>encode-rune</code></td></tr>
<tr><td>numbers</td><td><code>sign-f32</code>, <code>lerp</code>, <code>clamp</code>, <code>floor-f32</code>, <code>ceil-f32</code>, <code>round-f32</code>, <code>abs-i32</code>, <code>abs-i64</code>, the constants <code>pi-f32</code>, <code>pi-f64</code>, <code>tau-f32</code>, <code>tau-f64</code>, and libm through a <code>declare</code> at both widths: <code>sqrt</code>, <code>abs</code>, <code>floor</code>, <code>ceil</code>, <code>round</code>, <code>fmod</code>, <code>sin</code>, <code>cos</code>, <code>tan</code>, <code>asin</code>, <code>acos</code>, <code>atan</code>, <code>atan2</code>, <code>log</code>, <code>log2</code>, <code>log10</code>, <code>exp</code>, <code>pow</code>, <code>hypot</code>, <code>cbrt</code> — each spelled <code>-f32</code> or <code>-f64</code></td></tr>
<tr><td>time</td><td><code>monotonic-ns</code>, <code>monotonic-seconds</code>, <code>unix-ns</code>, <code>unix-seconds</code>, <code>sleep-ns</code>, <code>sleep-seconds</code>, and <code>ns-per-second</code> and its two smaller siblings</td></tr>
<tr><td>files</td><td><code>file-exists?</code> and <code>file-size</code>, which answer a value; <code>slurp</code>, <code>barf</code>, <code>delete-file</code>, <code>rename-file</code> and <code>make-directory</code>, which signal <code>FileError</code> under <code>retry</code> and <code>use-value</code></td></tr>
@ -1062,8 +1062,8 @@ over.</p>
</div>
<p><strong>One family, not one per type.</strong> The slice algorithms used to be
<code>sort-i32!</code> beside <code>sort-f32!</code> beside <code>sort-bytes!</code>, and
generics collapsed them: <code>sort!</code> is written once and instantiated at whatever
<code>sort-i32</code> beside <code>sort-f32</code> beside <code>sort-bytes</code>, and
generics collapsed them: <code>sort</code> is written once and instantiated at whatever
element type the call passes. <code>sum-i32</code> and <code>sum-f32</code> are what did
<em>not</em> collapse, and they are the honest exception — each widens its element into a
different accumulator, which one variable cannot express.</p>