One slice over everything with elements, and the warning at the push
as-slice was a warning, not an operation. The input type already decides which of the two things happens — a Vec can only be borrowed, an array or a string can only be viewed, and no call site picks between them — so the second name expressed no choice a reader could make. And it warned at the moment the view is taken, which is the one moment nothing is wrong; the danger arrives later, at the push. slice now takes a Vec at all three arities and as-slice is gone. (slice v lo) was free, and is the arity the Vec never had: the runtime already reads a hi of -1 as "to the end", so the tail form passes the caller's lo and the same -1 — no slot, no length read, no second evaluation. The merge is entirely in the checker; the Vec path builds the flan_vec_as_slice call it always built and neither backend has a line about any of it. A Vec a call returned is refused at every arity, and not for the array's reason. (slice (mk)) over an array dangles. (slice (make-vec)) does not — the storage outlives the expression — but the header is a temporary, so nothing can ever free the block. The refusal says that and names the let. The name's own refusal sits in ordinary_call after every table, so a program that defines an as-slice still reaches its own. It reads for somebody who has never heard of the old name and writes the call back out, spelling each argument that is a name or a number. The warning moved to where it bites: BUILT.md gains a section beside the Vec table and the push row points at it, spec-memory.md's Borrowing says the same. Investigated and deliberately not built — a diagnostic for a live view at the push. (reserve v 100) then a slice, a push and a read is correct code under the contract the spec chose, so any flag on it is a false positive by the language's own semantics rather than by an approximation. FIX.org has the finding and the syntactic sketch that does not work.
This commit is contained in:
parent
10b1abbf09
commit
9ce51ba94e
112
FIX.org
112
FIX.org
@ -4734,3 +4734,115 @@ So it is gone, with the call respelled, at the merge: sand.flan:121 says
|
|||||||
~:log-warning~, the ~warning 4~ member and its paragraph are out of
|
~:log-warning~, the ~warning 4~ member and its paragraph are out of
|
||||||
raylib.flan, and the ~constant~ line is out of bindings. Every member of every
|
raylib.flan, and the ~constant~ line is out of bindings. Every member of every
|
||||||
mapped enum now carries its prefix, with no exception.
|
mapped enum now carries its prefix, with no exception.
|
||||||
|
|
||||||
|
* One slice, and the warning moved to the push — 2026-09-21
|
||||||
|
The ruling, in the author's words:
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
merge them into one slice. as-slice goes away.
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
** Why a second name was the wrong shape
|
||||||
|
[as-slice] existed as a warning. check.ml said so beside it: a view of a Vec
|
||||||
|
is a borrow from storage a push, a put or a reserve may reallocate out from
|
||||||
|
under you — the explicit Zig/Odin contract spec-memory.md chose instead of a
|
||||||
|
borrow checker — and a different word at the call site was how a reader was
|
||||||
|
meant to be told.
|
||||||
|
|
||||||
|
It does not work, for two reasons and the second is the one that decides it.
|
||||||
|
|
||||||
|
The input type already determines the semantics completely. A Vec can only be
|
||||||
|
borrowed; a fixed array, a slice or a string can only be viewed. There is no
|
||||||
|
call site anywhere at which a reader would want to pick between the two
|
||||||
|
behaviours for one input, so the second name expressed no choice. It was a
|
||||||
|
label, not an operation.
|
||||||
|
|
||||||
|
And it was a label in the wrong place. It warns at the moment the view is
|
||||||
|
taken, which is the one moment nothing is wrong: the view is correct when it
|
||||||
|
is made. The danger arrives later, at the push. A warning stapled to the safe
|
||||||
|
end of the story is a warning nobody reads at the unsafe end.
|
||||||
|
|
||||||
|
** What landed
|
||||||
|
One [slice], over a fixed array, a slice, a string and now a Vec, at all three
|
||||||
|
arities. The Vec's half is [vec_slice] in check.ml; everything the previous
|
||||||
|
lane built — the backwards-literal refusal, the static out-of-range refusal
|
||||||
|
where a length is known, the runtime trap, single evaluation of a non-trivial
|
||||||
|
target, the zero-cost implicit length on a fixed array — is untouched, and a
|
||||||
|
Vec reaches none of the static ones because it has no static length to reach
|
||||||
|
them with.
|
||||||
|
|
||||||
|
The merge is entirely in the checker. Neither backend has an arity case, a
|
||||||
|
type case, or a line about this: the Vec path builds the same
|
||||||
|
[flan_vec_as_slice] call [as-slice] built, and the C symbol keeps its name.
|
||||||
|
|
||||||
|
*(slice v lo) was free.* The runtime already reads a [hi] of -1 as "to the
|
||||||
|
end", which is what the one-argument form passes, so the tail form passes the
|
||||||
|
caller's [lo] and the same -1. No slot, no length read, no second evaluation of
|
||||||
|
the target, nothing computed that was not computed before. The Vec had no
|
||||||
|
two-argument spelling only because the name it had was not the name that grew
|
||||||
|
the arities.
|
||||||
|
|
||||||
|
*A Vec a call returned is refused, at every arity*, and for a different reason
|
||||||
|
than the array. [(slice (mk))] over an array dangles: the view outlives a
|
||||||
|
temporary the frame reuses. [(slice (make-vec))] does not dangle — the storage
|
||||||
|
a returned Vec owns lives until its allocator's free-all or destroy. What is
|
||||||
|
lost is the *owner*: the header is a temporary, so nothing can ever free that
|
||||||
|
block, and the program has written a leak it cannot spell the fix for. The
|
||||||
|
refusal says that rather than borrowing the array's sentence, and names the
|
||||||
|
[let] that keeps the owner.
|
||||||
|
|
||||||
|
The refusal for the name itself is in [ordinary_call], after every table, so a
|
||||||
|
program that defines an [as-slice] of its own still reaches its own. It reads
|
||||||
|
for somebody who has never heard of the old name — "there is no as-slice" —
|
||||||
|
and writes the call back out with the arguments the reader wrote, spelling any
|
||||||
|
argument that is a name or a number and standing in for one that is not, so
|
||||||
|
the suggestion is always a form that compiles.
|
||||||
|
|
||||||
|
** The warning, moved
|
||||||
|
docs/BUILT.md gains a section next to the Vec surface table, and the [push]
|
||||||
|
row points at it: a view of a Vec is invalidated by [push], [put] or [reserve],
|
||||||
|
nothing checks it, and the rule is to take the view again afterwards.
|
||||||
|
spec-memory.md's "Borrowing" says the same in its own register and drops the
|
||||||
|
old two-spelling line.
|
||||||
|
|
||||||
|
** Investigated and NOT built: a live view at the push
|
||||||
|
The brief asked whether a [push] with a live view of the same Vec in scope is
|
||||||
|
detectable cheaply, and said to build it only if the obvious case is catchable
|
||||||
|
with no false positives. It is not, and the reason is not analysis cost.
|
||||||
|
|
||||||
|
The decisive case is one line of *correct* code:
|
||||||
|
|
||||||
|
#+begin_src flan
|
||||||
|
(reserve v 100)
|
||||||
|
(let [s (slice v)]
|
||||||
|
(push v 1)
|
||||||
|
(println (at s 0)))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
The reserve is exactly how a program says "this push will not reallocate", and
|
||||||
|
under the contract the spec chose that promise is the program's to make. Any
|
||||||
|
flag on this — error or warning — is a false positive by the language's own
|
||||||
|
semantics, not by an approximation the check settled for. The bar the brief
|
||||||
|
set therefore cannot be met by a cheaper check, because the obstacle is not
|
||||||
|
precision.
|
||||||
|
|
||||||
|
The syntactic sketch is worth writing down so nobody re-derives it. To avoid
|
||||||
|
flagging the common and harmless [(let [s (slice v)] (println (len s))
|
||||||
|
(push v 1))] — where the view is dead by the push — the check must find a use
|
||||||
|
of the view *after* the push, which is liveness. Textual order is not
|
||||||
|
execution order across an [if] or a loop; a [set] of the view's binding or a
|
||||||
|
shadowing of either name breaks it; and narrowing it to one straight-line
|
||||||
|
statement list to make the order real shrinks it to almost nothing. Meanwhile
|
||||||
|
static flow tracking was deliberately repealed on 2026-09-18, and this is a
|
||||||
|
borrow checker's question wearing a smaller hat. Two reasons to stop, and the
|
||||||
|
first one is sufficient on its own.
|
||||||
|
|
||||||
|
What was built instead is the sentence, in the two places a reader meets the
|
||||||
|
operation that breaks the view.
|
||||||
|
|
||||||
|
** Swept
|
||||||
|
Every spelling in the repo: lib/ (check.ml, prelude.ml, render.ml, shim.ml),
|
||||||
|
runtime/flan_rt.c comments, test/ (test_flan.ml, test_acceptance.ml,
|
||||||
|
test_valgrind.ml and fifteen programs), vendor/edn and vendor/json, web,
|
||||||
|
docs/BUILT.md, docs/overview.md, docs/SPIKE-DYNAMIC.md, spec-memory.md,
|
||||||
|
NEXT.md and syntax-sketch.flan. sand.flan never used it.
|
||||||
|
|||||||
8
NEXT.md
8
NEXT.md
@ -483,7 +483,7 @@ sign-extended to i64 walks straight through them. It is behind `f.md.checks` lik
|
|||||||
and -O2 alike and off only when checks as a whole were asked off.
|
and -O2 alike and off only when checks as a whole were asked off.
|
||||||
|
|
||||||
**It owns nothing, and needed no analysis to say so.** The result is a `Types.Slice`, which is not move-only and
|
**It owns nothing, and needed no analysis to say so.** The result is a `Types.Slice`, which is not move-only and
|
||||||
carries no allocator, so `free` refuses it by the rule it already had — the same rule that refuses `(as-slice v)`.
|
carries no allocator, so `free` refuses it by the rule it already had — the same rule that refuses `(slice v)`.
|
||||||
|
|
||||||
**Where the promise is written.** `rl/font-recs` and `rl/font-glyphs` in `vendor/raylib/raylib.flan`, because that
|
**Where the promise is written.** `rl/font-recs` and `rl/font-glyphs` in `vendor/raylib/raylib.flan`, because that
|
||||||
is the one place raylib's invariant (both arrays hold `glyph-count` entries) is knowable. `examples/text-rectangle-bounds.flan`
|
is the one place raylib's invariant (both arrays hold `glyph-count` entries) is knowable. `examples/text-rectangle-bounds.flan`
|
||||||
@ -1706,7 +1706,7 @@ What landed: `append`/`append-i64`/`append-f64` (the builder), `concat`, `join`,
|
|||||||
Tests: `programs/strings.flan`, `programs/format.flan`, `programs/algorithms.flan`, `programs/math2.flan`.
|
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
|
`string-from-bytes`, refused in that block, turned out to already exist: `string` is a builtin and
|
||||||
`(string (as-slice v))` is the round trip.
|
`(string (slice v))` is the round trip.
|
||||||
|
|
||||||
**What could not be built, and why each one could not.** All four want a compiler or runtime change, and none of
|
**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.
|
them wants a language decision.
|
||||||
@ -1764,7 +1764,7 @@ to avoid rather than inherit. Four, with what to do instead.
|
|||||||
**1. One argument-order rule, held everywhere.** Clojure's sequence functions take the collection *last*
|
**1. One argument-order rule, held everywhere.** Clojure's sequence functions take the collection *last*
|
||||||
(`(map f coll)`) and its collection functions take it *first* (`(assoc m k v)`). The split is deliberate there, and it
|
(`(map f coll)`) and its collection functions take it *first* (`(assoc m k v)`). The split is deliberate there, and it
|
||||||
is why Clojure needs **two** threading macros instead of one. **Our rule: the thing being operated on comes first.**
|
is why Clojure needs **two** threading macros instead of one. **Our rule: the thing being operated on comes first.**
|
||||||
That is already what the language does — `(at a i)`, `(len xs)`, `(push v x)`, `(as-slice v)` — and `into` follows it
|
That is already what the language does — `(at a i)`, `(len xs)`, `(push v x)`, `(slice v)` — and `into` follows it
|
||||||
with the source first. Hold it; do not ship two of anything to paper over a split.
|
with the source first. Hold it; do not ship two of anything to paper over a split.
|
||||||
|
|
||||||
**2. A membership test says which thing it tests.** Clojure's `contains?` checks *keys*, so `(contains? [1 2 3] 1)` is
|
**2. A membership test says which thing it tests.** Clojure's `contains?` checks *keys*, so `(contains? [1 2 3] 1)` is
|
||||||
@ -2465,7 +2465,7 @@ expander last, on 6's unions.
|
|||||||
rather than a literal calling-convention parameter; both are stated as amendments in `docs/BUILT.md`. A user-written
|
rather than a literal calling-convention parameter; both are stated as amendments in `docs/BUILT.md`. A user-written
|
||||||
allocator is refused by name with milestone 5 as the reason.
|
allocator is refused by name with milestone 5 as the reason.
|
||||||
|
|
||||||
~~2. **`(Vec T)`**~~ **Done**, over the type-erased runtime, with `push`, `reserve`, `at`, `len`, `as-slice`, `free` and
|
~~2. **`(Vec T)`**~~ **Done**, over the type-erased runtime, with `push`, `reserve`, `at`, `len`, `slice`, `free` and
|
||||||
`clone`, and with move-only enforced by a dead set that unions at an `if` or a `match` join. `at` and `len` were
|
`clone`, and with move-only enforced by a dead set that unions at an `if` or a `match` join. `at` and `len` were
|
||||||
extended rather than duplicated. The header is six words in *every* build, not four in release — a layout that
|
extended rather than duplicated. The header is six words in *every* build, not four in release — a layout that
|
||||||
changes with a build flag can disagree silently across the reload boundary — and that is the third amendment.
|
changes with a build flag can disagree silently across the reload boundary — and that is the third amendment.
|
||||||
|
|||||||
@ -2117,7 +2117,7 @@ hand-off dropping a number, which is what the note left behind said it was.
|
|||||||
|
|
||||||
**A `(Vec T)` shows as `<vec>` and a `(Ptr T)` as `<ptr>`**, because that is what `render.ml` already does for them
|
**A `(Vec T)` shows as `<vec>` and a `(Ptr T)` as `<ptr>`**, because that is what `render.ml` already does for them
|
||||||
everywhere else: following a pointer a REPL was handed is not a safe thing to do on someone's behalf, and walking a
|
everywhere else: following a pointer a REPL was handed is not a safe thing to do on someone's behalf, and walking a
|
||||||
`Vec` structurally is a walk over storage the frame does not own — `(print (as-slice v))` is how that is asked for,
|
`Vec` structurally is a walk over storage the frame does not own — `(print (slice v))` is how that is asked for,
|
||||||
and it says at the call site that it borrowed.
|
and it says at the call site that it borrowed.
|
||||||
|
|
||||||
Each slot is rendered **from its address** rather than copied into the thunk first. A copy would be one `alloca` the
|
Each slot is rendered **from its address** rather than copied into the thunk first. A copy would be one `alloca` the
|
||||||
@ -2424,14 +2424,30 @@ fires.
|
|||||||
| `context/allocator` / `context/temp` | the current implicit allocator, and the per-frame arena |
|
| `context/allocator` / `context/temp` | the current implicit allocator, and the per-frame arena |
|
||||||
| `(with-allocator a body...)` | rebinds for a dynamic extent and releases nothing |
|
| `(with-allocator a body...)` | rebinds for a dynamic extent and releases nothing |
|
||||||
| `(vec-new T)` / `(vec-new T a)` / `(vec-new)` | a Vec, against the context or a named allocator |
|
| `(vec-new T)` / `(vec-new T a)` / `(vec-new)` | a Vec, against the context or a named allocator |
|
||||||
| `(push v x)` / `(reserve v n)` | `()`, both |
|
| `(push v x)` / `(reserve v n)` | `()`, both. **Either may reallocate, and any `[T]` view of this `Vec` is stale afterwards** — see below |
|
||||||
| `(at v i)` / `(len v)` | the array names, extended — not a parallel pair |
|
| `(at v i)` / `(len v)` | the array names, extended — not a parallel pair |
|
||||||
| `(as-slice v)` / `(as-slice v lo hi)` | a non-owning `[T]` view |
|
| `(slice v)` / `(slice v lo)` / `(slice v lo hi)` | a non-owning `[T]` view — the array names again, extended |
|
||||||
| `(clone v)` / `(clone v a)` | the only copy; assignment moves |
|
| `(clone v)` / `(clone v a)` | the only copy; assignment moves |
|
||||||
| `(free v)` | consumes its argument |
|
| `(free v)` | consumes its argument |
|
||||||
| `(bytes s)` / `(bytes s a)` | a writable copy of a string's bytes, against the context or a named allocator — an allocating operation like `vec-new`: StorageExhausted with retry, a registry note in dev builds. The answer is a `[u8]` view of the block, so nothing can `free` it through the slice; it lives until its allocator's `free-all` or destroy |
|
| `(bytes s)` / `(bytes s a)` | a writable copy of a string's bytes, against the context or a named allocator — an allocating operation like `vec-new`: StorageExhausted with retry, a registry note in dev builds. The answer is a `[u8]` view of the block, so nothing can `free` it through the slice; it lives until its allocator's `free-all` or destroy |
|
||||||
| `(bytes-view s)` | the string's own storage as a `[u8]`, costing nothing — the old `(bytes s)` reinterpret, renamed. Read-only by convention: a literal's view points into `.rodata` and a store through it traps |
|
| `(bytes-view s)` | the string's own storage as a `[u8]`, costing nothing — the old `(bytes s)` reinterpret, renamed. Read-only by convention: a literal's view points into `.rodata` and a store through it traps |
|
||||||
|
|
||||||
|
### A view of a `Vec` goes stale at the `push`, and nothing checks it
|
||||||
|
|
||||||
|
`(slice v)` copies a pointer and a length out of the `Vec` and keeps neither the `Vec` nor its allocator. `push`,
|
||||||
|
`put` and `reserve` are the three operations that may hand the block back and take a bigger one, and when one of them
|
||||||
|
does, every view taken before it holds the old address and the old length. Reading through such a view reads freed
|
||||||
|
memory; writing through it writes to memory the program no longer owns. **The compiler does not check this and will
|
||||||
|
not tell you.** That is `spec-memory.md`'s explicit Zig/Odin contract, chosen instead of a borrow checker, and it is
|
||||||
|
one rule: take the view again after the `push`.
|
||||||
|
|
||||||
|
It is stated here, beside `push`, rather than at `slice`, because the `slice` is not the moment anything goes wrong —
|
||||||
|
the `push` is. There used to be a second name, `as-slice`, for taking a view of a `Vec`, on the theory that a
|
||||||
|
different word at that call site was the warning. It was not one. The value handed to `slice` already decides what
|
||||||
|
the result means — a `Vec` can only be borrowed, a fixed array or a string can only be viewed, and no call site ever
|
||||||
|
chooses between the two — so the second name expressed no choice a reader could make. One name, `slice`, over
|
||||||
|
everything that has elements; the warning is where it bites.
|
||||||
|
|
||||||
### Three amendments to a frozen spec, and one addition
|
### Three amendments to a frozen spec, and one addition
|
||||||
|
|
||||||
**1. `free-all` is retain-capacity, and `arena-destroy` is the operation that hands pages back.** The spec's table has
|
**1. `free-all` is retain-capacity, and `arena-destroy` is the operation that hands pages back.** The spec's table has
|
||||||
@ -2512,7 +2528,7 @@ what was built and why. Move-only as a *type* property (what may be copied, what
|
|||||||
|
|
||||||
Reading a move-only local is a move unless the site said it was a borrow. That is the conservative direction: passing
|
Reading a move-only local is a move unless the site said it was a borrow. That is the conservative direction: passing
|
||||||
one to a function, binding it, returning it and `free`ing it are all moves and all reach one place, and the handful of
|
one to a function, binding it, returning it and `free`ing it are all moves and all reach one place, and the handful of
|
||||||
operations that only look at a container (`at`, `len`, `as-slice`, `push`, `reserve`, `clone`) say so. Only a
|
operations that only look at a container (`at`, `len`, `slice`, `push`, `reserve`, `clone`) say so. Only a
|
||||||
*syntactically simple* target counts as a borrow — in `(len (f v))` the call still moves `v`.
|
*syntactically simple* target counts as a borrow — in `(len (f v))` the call still moves `v`.
|
||||||
|
|
||||||
At an `if` and at a `match`, every arm is checked from the state before the form and the **union** of what they moved
|
At an `if` and at a `match`, every arm is checked from the state before the form and the **union** of what they moved
|
||||||
@ -2539,7 +2555,7 @@ three shapes are refused where they are declared, each naming `drop`:
|
|||||||
- **a `Vec` of a `Vec`**, because the type-erased runtime copies and releases elements bytewise: `clone` would
|
- **a `Vec` of a `Vec`**, because the type-erased runtime copies and releases elements bytewise: `clone` would
|
||||||
duplicate inner headers instead of copying what they own and `free` would drop their buffers.
|
duplicate inner headers instead of copying what they own and `free` would drop their buffers.
|
||||||
|
|
||||||
And a `Vec` does not cross to C: handing a header that owns storage to C hands out an owner. `(as-slice v)` as
|
And a `Vec` does not cross to C: handing a header that owns storage to C hands out an owner. `(slice v)` as
|
||||||
`(Ptr T)` plus `(len v)` is the shape that does cross, and the refusal says so.
|
`(Ptr T)` plus `(len v)` is the shape that does cross, and the refusal says so.
|
||||||
|
|
||||||
### `StorageExhausted` went in *with* `Vec`, not after it
|
### `StorageExhausted` went in *with* `Vec`, not after it
|
||||||
@ -3006,7 +3022,7 @@ a slice is invalidated by a `push`. The handle survives that and the pointer doe
|
|||||||
explicit Zig/Odin borrowing contract one level down, and it is worth naming rather than implying, because it
|
explicit Zig/Odin borrowing contract one level down, and it is worth naming rather than implying, because it
|
||||||
reintroduces the silent-wrong-answer mode the handle just removed for anyone who keeps a resolved pointer across an
|
reintroduces the silent-wrong-answer mode the handle just removed for anyone who keeps a resolved pointer across an
|
||||||
insert. Chunked never-moving storage is the fix and it costs code; taking the contract is the smaller correct thing,
|
insert. Chunked never-moving storage is the fix and it costs code; taking the contract is the smaller correct thing,
|
||||||
given `as-slice` already established it.
|
given `slice` already established it.
|
||||||
|
|
||||||
### `len` is the slot high-water and `live` is the count, in that direction
|
### `len` is the slot high-water and `live` is the count, in that direction
|
||||||
|
|
||||||
@ -3675,7 +3691,7 @@ be wrong in a way worth being able to see: the prelude *was* reaching the expand
|
|||||||
The list at the foot of `prelude.ml` used to be one sentence — every entry needed to produce bytes that did not exist
|
The list at the foot of `prelude.ml` used to be one sentence — every entry needed to produce bytes that did not exist
|
||||||
in its input, and there was no allocator. `join`, `concat`, `split`, `to-lower`, `to-upper`, `repeat` and `replace`
|
in its input, and there was no allocator. `join`, `concat`, `split`, `to-lower`, `to-upper`, `repeat` and `replace`
|
||||||
have moved up into the code; `string-from-bytes` turned out to be the `string` builtin all along, and
|
have moved up into the code; `string-from-bytes` turned out to be the `string` builtin all along, and
|
||||||
`(string (as-slice v))` is the round trip, free precisely because the layouts are identical.
|
`(string (slice v))` is the round trip, free precisely because the layouts are identical.
|
||||||
|
|
||||||
What remains is refused for four different reasons, and is now written that way: `pad`/`center` for *nothing at all*
|
What remains is refused for four different reasons, and is now written that way: `pad`/`center` for *nothing at all*
|
||||||
except that no caller has asked; `format`/`sprintf` for variadics of mixed type; `map`/`filter`/`reduce`/`sort-by`
|
except that no caller has asked; `format`/`sprintf` for variadics of mixed type; `map`/`filter`/`reduce`/`sort-by`
|
||||||
|
|||||||
@ -335,7 +335,7 @@ this one too.
|
|||||||
|
|
||||||
**A typed `Vec` prints as `<vec>` and a dyn vec does not.** That looks like a mismatch and is not. The
|
**A typed `Vec` prints as `<vec>` and a dyn vec does not.** That looks like a mismatch and is not. The
|
||||||
typed printer's refusal is about walking storage it does not own — `render.ml` says so, and directs you to
|
typed printer's refusal is about walking storage it does not own — `render.ml` says so, and directs you to
|
||||||
`(print (as-slice v))`, which borrows explicitly at the call site. A dyn vec's storage belongs to the
|
`(print (slice v))`, which borrows explicitly at the call site. A dyn vec's storage belongs to the
|
||||||
collector, the printer is inside the runtime that owns it, and there is nobody to ask permission from. So
|
collector, the printer is inside the runtime that owns it, and there is nobody to ask permission from. So
|
||||||
a dyn vec prints the way a *slice* does, which is also the only rendering that carries any information.
|
a dyn vec prints the way a *slice* does, which is also the only rendering that carries any information.
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ accurate**. Read instead:
|
|||||||
| "C-like with Roc syntax" | S-expressions, Clojure's brackets; C's value model |
|
| "C-like with Roc syntax" | S-expressions, Clojure's brackets; C's value model |
|
||||||
| "Start with interpreter, output C later" | Frontend → typed IR → LLVM IR as text → `clang`. An interpreter is acceptable for milestone 2 only |
|
| "Start with interpreter, output C later" | Frontend → typed IR → LLVM IR as text → `clang`. An interpreter is acceptable for milestone 2 only |
|
||||||
| "lists, slice, fixed-length array, matrices" | Four container types, distinct ownership: `[n T]`, `[T]`, `(Vec T)`, `(Map K V)` — see spec-memory.md |
|
| "lists, slice, fixed-length array, matrices" | Four container types, distinct ownership: `[n T]`, `[T]`, `(Vec T)`, `(Map K V)` — see spec-memory.md |
|
||||||
| `arr[4]`, `arr[1..]` index syntax | `(at a i)`, `(as-slice a lo hi)` — no infix, no bracket indexing |
|
| `arr[4]`, `arr[1..]` index syntax | `(at a i)`, `(slice a lo hi)` — no infix, no bracket indexing |
|
||||||
| `const by default?` | Locals are assignable places; parameters are not; `const` qualifies slices and pointers |
|
| `const by default?` | Locals are assignable places; parameters are not; `const` qualifies slices and pointers |
|
||||||
| Rust-style iterator chains ending in `.collect()` | `->>` threading over slices; every collecting operation allocates from an explicit allocator, usually the frame arena |
|
| Rust-style iterator chains ending in `.collect()` | `->>` threading over slices; every collecting operation allocates from an explicit allocator, usually the frame arena |
|
||||||
| `Ptr a` | Kept, as `(Ptr a)`. Cross-references use `(Handle a)` instead |
|
| `Ptr a` | Kept, as `(Ptr a)`. Cross-references use `(Handle a)` instead |
|
||||||
|
|||||||
166
lib/check.ml
166
lib/check.ml
@ -6005,6 +6005,74 @@ and vec_at ctx loc (target : Tast.expr) (idx : Ast.expr list) =
|
|||||||
"a Vec takes exactly one index — (at v i) — and its element is indexed \
|
"a Vec takes exactly one index — (at v i) — and its element is indexed \
|
||||||
separately"
|
separately"
|
||||||
|
|
||||||
|
(* [(slice v)], [(slice v lo)] and [(slice v lo hi)] over a Vec — the arm for
|
||||||
|
it is in [slice], and this is the half that differs from an array's.
|
||||||
|
|
||||||
|
The result is a non-owning view: copying it copies ptr+len and never the
|
||||||
|
elements, and it carries no allocator, so freeing through one is not
|
||||||
|
expressible. What makes a Vec's view different from an array's is that the
|
||||||
|
storage it names can move — a [push], a [put] or a [reserve] may reallocate
|
||||||
|
and leave the view addressing the old block. Nothing checks that, which is
|
||||||
|
the explicit Zig/Odin contract spec-memory.md chose over a borrow checker,
|
||||||
|
and it is written down where a reader meets it: beside [push] in BUILT.md
|
||||||
|
and in spec-memory.md's "Borrowing".
|
||||||
|
|
||||||
|
It used to be spelled [as-slice], on the theory that a second name warns
|
||||||
|
about that. It does not: the input type already decides which of the two
|
||||||
|
things happens, so there was no choice at the call site for the name to
|
||||||
|
express — and the moment it warned about was the moment the view is taken,
|
||||||
|
while the danger arrives later, at the push.
|
||||||
|
|
||||||
|
No slot and no length read at any arity: -1 is the runtime's "to the end",
|
||||||
|
so the short forms pass a constant where an array passes its length, and
|
||||||
|
the target appears exactly once in all three. *)
|
||||||
|
and vec_slice ctx ~want loc (target : Tast.expr) elem (bounds : Ast.expr list) =
|
||||||
|
(* The mirror of the array rule one screen down, for a different reason. An
|
||||||
|
array a call returned is storage the frame reuses, so the view dangles;
|
||||||
|
a Vec a call returned owns storage that outlives the expression, and what
|
||||||
|
is lost is the owner — the header is a temporary, so nothing can ever
|
||||||
|
[free] it and the block lives to the allocator's free-all or destroy. A
|
||||||
|
[let] keeps the owner, so that is what the refusal names. *)
|
||||||
|
(match target.Tast.e with
|
||||||
|
| Tast.Call _ | Tast.CallPtr _ ->
|
||||||
|
fail loc
|
||||||
|
"this slices a Vec a call returned, and the header a call answers is a \
|
||||||
|
temporary — the view outlives it, and the storage it names can no \
|
||||||
|
longer be freed, because there is no Vec left to free it through. \
|
||||||
|
Bind it first: (let [v (…)] (slice v …))"
|
||||||
|
| _ -> ());
|
||||||
|
let int k = mk loc index_ty (Tast.Int (k, Types.I32)) in
|
||||||
|
(* -1 is "to the end". A Vec's length is not static, so unlike an array
|
||||||
|
there is no constant to fold and the runtime reads the length word it is
|
||||||
|
carrying anyway. *)
|
||||||
|
let to_end () = int (-1L) in
|
||||||
|
let lo, hi =
|
||||||
|
match bounds with
|
||||||
|
| [] -> int 0L, to_end ()
|
||||||
|
| [ lo ] -> index_expr ctx lo, to_end ()
|
||||||
|
| [ lo; hi ] -> index_expr ctx lo, index_expr ctx hi
|
||||||
|
| _ -> assert false
|
||||||
|
in
|
||||||
|
(* The same refusal the other targets get. There is no static length to
|
||||||
|
check a single bound against, but a literal pair that runs backwards is
|
||||||
|
wrong without one — and [hi] here may be the -1 sentinel, which is not a
|
||||||
|
bound anybody wrote. *)
|
||||||
|
(match literal lo, literal hi with
|
||||||
|
| Some a, Some b when b >= 0L && a > b ->
|
||||||
|
fail loc "slice [%Ld %Ld) runs backwards — lo must not exceed hi" a b
|
||||||
|
| _ -> ());
|
||||||
|
let out = fresh_slot ctx (Types.Slice elem) in
|
||||||
|
let fill =
|
||||||
|
rt loc Types.Unit "flan_vec_as_slice"
|
||||||
|
[ target; addr_of loc (mk loc (Types.Slice elem) (Tast.Local out));
|
||||||
|
lo; hi; size_of loc elem; here loc ]
|
||||||
|
in
|
||||||
|
expect ctx loc ~want
|
||||||
|
(mk loc (Types.Slice elem)
|
||||||
|
(Tast.Let ([ (out, mk loc (Types.Slice elem)
|
||||||
|
(Tast.Zero (Types.Slice elem))) ],
|
||||||
|
[ fill; mk loc (Types.Slice elem) (Tast.Local out) ])))
|
||||||
|
|
||||||
(* Every arm below is a name an editor can be asked about and no program ever
|
(* Every arm below is a name an editor can be asked about and no program ever
|
||||||
wrote down, so each one needs a line in [builtins] further down this file.
|
wrote down, so each one needs a line in [builtins] further down this file.
|
||||||
A new arm without an entry fails the build — test_flan reads both. *)
|
A new arm without an entry fails the build — test_flan reads both. *)
|
||||||
@ -6689,37 +6757,6 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
(region_check ctx.env loc target
|
(region_check ctx.env loc target
|
||||||
(with_note loc (alloc_guard ctx loc attempt) note))
|
(with_note loc (alloc_guard ctx loc attempt) note))
|
||||||
| _ -> assert false)
|
| _ -> assert false)
|
||||||
(* (as-slice v) and (as-slice v lo hi) — spec-memory.md, "Borrowing". The
|
|
||||||
result is a non-owning view: copying it copies ptr+len and never the
|
|
||||||
elements, and it carries no allocator, so freeing through one is not
|
|
||||||
expressible. A push, a put or a reserve may invalidate it; that is the
|
|
||||||
explicit Zig/Odin contract the spec chose over a borrow checker. *)
|
|
||||||
| "as-slice" ->
|
|
||||||
(match args with
|
|
||||||
| target :: rest when List.length rest = 0 || List.length rest = 2 ->
|
|
||||||
let target = check ctx target in
|
|
||||||
let elem = vec_elem loc "as-slice" target.Tast.ty in
|
|
||||||
let lo, hi =
|
|
||||||
match rest with
|
|
||||||
| [] ->
|
|
||||||
mk loc index_ty (Tast.Int (0L, Types.I32)),
|
|
||||||
(* -1 is "to the end": (as-slice v) has no static length to pass. *)
|
|
||||||
mk loc index_ty (Tast.Int (-1L, Types.I32))
|
|
||||||
| [ lo; hi ] -> index_expr ctx lo, index_expr ctx hi
|
|
||||||
| _ -> assert false
|
|
||||||
in
|
|
||||||
let out = fresh_slot ctx (Types.Slice elem) in
|
|
||||||
let fill =
|
|
||||||
rt loc Types.Unit "flan_vec_as_slice"
|
|
||||||
[ target; addr_of loc (mk loc (Types.Slice elem) (Tast.Local out));
|
|
||||||
lo; hi; size_of loc elem; here loc ]
|
|
||||||
in
|
|
||||||
expect ctx loc ~want
|
|
||||||
(mk loc (Types.Slice elem)
|
|
||||||
(Tast.Let ([ (out, mk loc (Types.Slice elem)
|
|
||||||
(Tast.Zero (Types.Slice elem))) ],
|
|
||||||
[ fill; mk loc (Types.Slice elem) (Tast.Local out) ])))
|
|
||||||
| _ -> fail loc "as-slice is (as-slice v) or (as-slice v lo hi)")
|
|
||||||
(* spec-memory.md's first release point. Since the repeal, what it consumes
|
(* spec-memory.md's first release point. Since the repeal, what it consumes
|
||||||
it consumes at run time only: nothing marks the binding dead, so a second
|
it consumes at run time only: nothing marks the binding dead, so a second
|
||||||
[free] or a read after this one type-checks and misbehaves at run time —
|
[free] or a read after this one type-checks and misbehaves at run time —
|
||||||
@ -7476,7 +7513,13 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
The target is read twice when [hi] is the implicit length, so a target
|
The target is read twice when [hi] is the implicit length, so a target
|
||||||
that is not already a name goes into a slot first: [(slice (f x))] must
|
that is not already a name goes into a slot first: [(slice (f x))] must
|
||||||
call [f] once. An array target never needs the slot, because its length
|
call [f] once. An array target never needs the slot, because its length
|
||||||
is a constant and the target appears exactly once. *)
|
is a constant and the target appears exactly once.
|
||||||
|
|
||||||
|
One name over every target that has elements, a Vec included. There used
|
||||||
|
to be a second, [as-slice], for the Vec alone; the input type already
|
||||||
|
decides which semantics apply — a Vec can only be borrowed, an array can
|
||||||
|
only be viewed, and no call site chooses — so the second name expressed
|
||||||
|
nothing and is gone. [vec_slice] above is the Vec's half. *)
|
||||||
| "slice" ->
|
| "slice" ->
|
||||||
(match args with
|
(match args with
|
||||||
| [] | _ :: _ :: _ :: _ :: _ ->
|
| [] | _ :: _ :: _ :: _ :: _ ->
|
||||||
@ -7486,6 +7529,11 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
| target :: bounds ->
|
| target :: bounds ->
|
||||||
let target = check ctx target in
|
let target = check ctx target in
|
||||||
let ty = target.Tast.ty in
|
let ty = target.Tast.ty in
|
||||||
|
match ty with
|
||||||
|
(* A Vec leaves here: everything below is written around a length the
|
||||||
|
compiler can see, and a Vec's is a word the runtime reads. *)
|
||||||
|
| Types.Vec elem -> vec_slice ctx ~want loc target elem bounds
|
||||||
|
| _ ->
|
||||||
(* A string slices to a string, not to a [u8]: the result views the
|
(* A string slices to a string, not to a [u8]: the result views the
|
||||||
same bytes and is read-only for the same reason the source is, and
|
same bytes and is read-only for the same reason the source is, and
|
||||||
calling it a byte slice would hand out a writable-looking view of
|
calling it a byte slice would hand out a writable-looking view of
|
||||||
@ -7494,7 +7542,8 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
| Types.Array (_, t) | Types.Slice t -> Types.Slice t
|
| Types.Array (_, t) | Types.Slice t -> Types.Slice t
|
||||||
| Types.String -> Types.String
|
| Types.String -> Types.String
|
||||||
| other ->
|
| other ->
|
||||||
fail loc "slice takes an array, a slice or a string, found %s"
|
fail loc
|
||||||
|
"slice takes an array, a slice, a string or a Vec, found %s"
|
||||||
(Types.to_string other)
|
(Types.to_string other)
|
||||||
in
|
in
|
||||||
(* An array that came back from a call is a value in a temporary this
|
(* An array that came back from a call is a value in a temporary this
|
||||||
@ -7582,7 +7631,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
from.
|
from.
|
||||||
|
|
||||||
**It owns nothing.** The result is a [Types.Slice], which carries no
|
**It owns nothing.** The result is a [Types.Slice], which carries no
|
||||||
allocator and is the same non-owning view (as-slice v) answers — so
|
allocator and is the same non-owning view (slice v) answers — so
|
||||||
[free] refuses it by the rule it already had ("free takes an owning
|
[free] refuses it by the rule it already had ("free takes an owning
|
||||||
container"). *)
|
container"). *)
|
||||||
| "slice-from-ptr" ->
|
| "slice-from-ptr" ->
|
||||||
@ -7690,7 +7739,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
The lowering mirrors [vec-new]: a hidden (Vec u8) temp holds the block so
|
The lowering mirrors [vec-new]: a hidden (Vec u8) temp holds the block so
|
||||||
the allocation registry can read its extent, the attempt sits under
|
the allocation registry can read its extent, the attempt sits under
|
||||||
[alloc_guard] so a failure signals StorageExhausted with retry, and the
|
[alloc_guard] so a failure signals StorageExhausted with retry, and the
|
||||||
answer is the [as-slice] of the whole of it. The slice carries no
|
answer is the [slice] of the whole of it. The slice carries no
|
||||||
allocator, so nothing can [free] this block through it — it lives until
|
allocator, so nothing can [free] this block through it — it lives until
|
||||||
its allocator's free-all or destroy, which is the story every borrowed
|
its allocator's free-all or destroy, which is the story every borrowed
|
||||||
view already has and is written down in BUILT.md's surface table.
|
view already has and is written down in BUILT.md's surface table.
|
||||||
@ -8095,6 +8144,41 @@ and ordinary_call ctx ~want loc name args =
|
|||||||
else if String.contains name '/' then
|
else if String.contains name '/' then
|
||||||
unimplemented loc
|
unimplemented loc
|
||||||
(Printf.sprintf "the call %s into an imported package" name) 4
|
(Printf.sprintf "the call %s into an imported package" name) 4
|
||||||
|
else if name = "as-slice" then
|
||||||
|
(* A name nothing defines, near enough to [slice] to be worth a
|
||||||
|
sentence rather than a did-you-mean: whatever [slice] is given
|
||||||
|
decides what the view means, so there is one word for it and this
|
||||||
|
says which. Asked here, after every table, so that a program that
|
||||||
|
defines an [as-slice] of its own still reaches its own. *)
|
||||||
|
(* The call is written back out rather than described, and every
|
||||||
|
argument the reader wrote that can be spelled is spelled. One that
|
||||||
|
cannot — a call, a field, anything with structure — becomes the
|
||||||
|
name it stands for, so the suggestion is always a form that
|
||||||
|
compiles rather than a form with a hole in it. *)
|
||||||
|
let spell stand_for (a : Ast.expr) =
|
||||||
|
match a.Ast.e with
|
||||||
|
| Ast.Var v -> v
|
||||||
|
| Ast.Int n -> Int64.to_string n
|
||||||
|
| _ -> stand_for
|
||||||
|
in
|
||||||
|
let call =
|
||||||
|
match args with
|
||||||
|
| [] -> "(slice v)"
|
||||||
|
| t :: bounds ->
|
||||||
|
let names = [ "lo"; "hi" ] in
|
||||||
|
let bounds =
|
||||||
|
List.mapi
|
||||||
|
(fun i b ->
|
||||||
|
" " ^ spell (try List.nth names i with _ -> "n") b)
|
||||||
|
bounds
|
||||||
|
in
|
||||||
|
"(slice " ^ spell "v" t ^ String.concat "" bounds ^ ")"
|
||||||
|
in
|
||||||
|
Loc.failk "check/unknown-function" loc
|
||||||
|
"there is no as-slice. slice takes the view, and what it is given \
|
||||||
|
says what the view is: over a Vec it borrows the storage the Vec \
|
||||||
|
owns, over an array or a string it looks at the value itself. \
|
||||||
|
Write %s" call
|
||||||
else
|
else
|
||||||
(* The did-you-mean comes first, and for a capitalised head it is asked
|
(* The did-you-mean comes first, and for a capitalised head it is asked
|
||||||
of the *type* tables as well: [(Piont 1 2)] with [Point] declared is
|
of the *type* tables as well: [(Piont 1 2)] with [Point] declared is
|
||||||
@ -8388,7 +8472,7 @@ and generic_call ctx ~want loc name vars pats pret args =
|
|||||||
[dyn] is an ordinary case of [Types.t], so it substituted like any other
|
[dyn] is an ordinary case of [Types.t], so it substituted like any other
|
||||||
type and a copy was generated at it. The copy then reached whatever the
|
type and a copy was generated at it. The copy then reached whatever the
|
||||||
body did with the value, and the dyn answers are not all there — [(Option
|
body did with the value, and the dyn answers are not all there — [(Option
|
||||||
dyn)] has no descriptor the collector can find, [as-slice] over a
|
dyn)] has no descriptor the collector can find, [slice] over a
|
||||||
[(Vec dyn)] refuses. So the refusal existed, it just arrived from inside
|
[(Vec dyn)] refuses. So the refusal existed, it just arrived from inside
|
||||||
the generic's own source: [(or-else (Some d) e)] over two dyns is reported
|
the generic's own source: [(or-else (Some d) e)] over two dyns is reported
|
||||||
against [<prelude>:385], a line the caller did not write and cannot act
|
against [<prelude>:385], a line the caller did not write and cannot act
|
||||||
@ -8939,10 +9023,6 @@ let builtins : (string * string * string) list =
|
|||||||
"Makes room for n more. For a map the number is entries rather than \
|
"Makes room for n more. For a map the number is entries rather than \
|
||||||
slots — the block is sized so that n still sits under the load \
|
slots — the block is sized so that n still sits under the load \
|
||||||
factor.");
|
factor.");
|
||||||
("as-slice", "as-slice [(Vec T) i32? i32?] [T]",
|
|
||||||
"A non-owning view of the whole Vec, or of the half-open range \
|
|
||||||
[lo hi). It carries no allocator, and a push, a put or a reserve may \
|
|
||||||
invalidate it.");
|
|
||||||
("free", "free [(Vec T)|(Map K V)] ()",
|
("free", "free [(Vec T)|(Map K V)] ()",
|
||||||
"Releases the container's block. It does not recurse into elements that \
|
"Releases the container's block. It does not recurse into elements that \
|
||||||
own storage — such a container is refused here, and releasing its \
|
own storage — such a container is refused here, and releasing its \
|
||||||
@ -9029,11 +9109,13 @@ let builtins : (string * string * string) list =
|
|||||||
allocator's epoch checked first. On a string it is the byte, a u8. It \
|
allocator's epoch checked first. On a string it is the byte, a u8. It \
|
||||||
is also a place, so (set (at v i) x) goes through the same check; a \
|
is also a place, so (set (at v i) x) goes through the same check; a \
|
||||||
string is the exception, being a view it does not own.");
|
string is the exception, being a view it does not own.");
|
||||||
("slice", "slice [[n T]|[T]|string i32? i32?] [T]|string",
|
("slice", "slice [[n T]|[T]|string|(Vec T) i32? i32?] [T]|string",
|
||||||
"The half-open range [lo hi) as a non-owning view. lo defaults to 0 and \
|
"The half-open range [lo hi) as a non-owning view. lo defaults to 0 and \
|
||||||
hi to the length, so (slice a) is the whole of it and (slice a n) is \
|
hi to the length, so (slice a) is the whole of it and (slice a n) is \
|
||||||
the tail from n. A bound may sit one past the end; a literal pair that \
|
the tail from n. A bound may sit one past the end; a literal pair that \
|
||||||
runs backwards is refused here. A string slices to a string.");
|
runs backwards is refused here. A string slices to a string. A view of \
|
||||||
|
a Vec is a borrow from storage the Vec owns, and a push, a put or a \
|
||||||
|
reserve on that Vec may invalidate it.");
|
||||||
("slice-from-ptr", "slice-from-ptr [(Ptr T) i32] [T]",
|
("slice-from-ptr", "slice-from-ptr [(Ptr T) i32] [T]",
|
||||||
"Puts a length on a pointer that came back from C. The caller promises \
|
"Puts a length on a pointer that came back from C. The caller promises \
|
||||||
it addresses that many initialised T and that they outlive the result; \
|
it addresses that many initialised T and that they outlive the result; \
|
||||||
|
|||||||
@ -231,7 +231,7 @@ let source = {flan|
|
|||||||
;; to return a new sequence from — and it stays the right shape now that there
|
;; to return a new sequence from — and it stays the right shape now that there
|
||||||
;; is one, because sorting a thing you already own should not allocate. The
|
;; is one, because sorting a thing you already own should not allocate. The
|
||||||
;; allocating tier is further down, and a caller sorts a Vec by sorting
|
;; allocating tier is further down, and a caller sorts a Vec by sorting
|
||||||
;; (as-slice v).
|
;; (slice v).
|
||||||
;;
|
;;
|
||||||
;; **map, filter, reduce and a sort taking a comparator are here now**, in a
|
;; **map, filter, reduce and a sort taking a comparator are here now**, in a
|
||||||
;; section of their own after the f32 family. They were blocked on *function
|
;; section of their own after the f32 family. They were blocked on *function
|
||||||
@ -1816,7 +1816,7 @@ let source = {flan|
|
|||||||
;; stopped being true when `Vec` landed, and most of the list has moved up into
|
;; stopped being true when `Vec` landed, and most of the list has moved up into
|
||||||
;; the building section above: join, concat, split, to-lower, to-upper, repeat
|
;; the building section above: join, concat, split, to-lower, to-upper, repeat
|
||||||
;; and replace are all written now, and `string-from-bytes` turned out to be
|
;; and replace are all written now, and `string-from-bytes` turned out to be
|
||||||
;; the `string` builtin all along — (string (as-slice v)) is the round trip,
|
;; the `string` builtin all along — (string (slice v)) is the round trip,
|
||||||
;; and the layouts being identical is exactly why it is free.
|
;; and the layouts being identical is exactly why it is free.
|
||||||
;;
|
;;
|
||||||
;; What is left is refused for four *different* reasons, which is why they are
|
;; What is left is refused for four *different* reasons, which is why they are
|
||||||
@ -2025,14 +2025,14 @@ let source = {flan|
|
|||||||
;; change this, and it does not exist.
|
;; change this, and it does not exist.
|
||||||
(defn form-nil [] [Form]
|
(defn form-nil [] [Form]
|
||||||
(let [v (vec-new Form)]
|
(let [v (vec-new Form)]
|
||||||
(as-slice v)))
|
(slice v)))
|
||||||
|
|
||||||
(defn form-cons [x Form rest [Form]] [Form]
|
(defn form-cons [x Form rest [Form]] [Form]
|
||||||
(let [v (vec-new Form)]
|
(let [v (vec-new Form)]
|
||||||
(push v x)
|
(push v x)
|
||||||
(dotimes [i (len rest)]
|
(dotimes [i (len rest)]
|
||||||
(push v (at rest i)))
|
(push v (at rest i)))
|
||||||
(as-slice v)))
|
(slice v)))
|
||||||
|
|
||||||
(defn form-append [a [Form] b [Form]] [Form]
|
(defn form-append [a [Form] b [Form]] [Form]
|
||||||
(let [v (vec-new Form)]
|
(let [v (vec-new Form)]
|
||||||
@ -2040,7 +2040,7 @@ let source = {flan|
|
|||||||
(push v (at a i)))
|
(push v (at a i)))
|
||||||
(dotimes [i (len b)]
|
(dotimes [i (len b)]
|
||||||
(push v (at b i)))
|
(push v (at b i)))
|
||||||
(as-slice v)))
|
(slice v)))
|
||||||
|
|
||||||
;; The rest of a macro's arguments, which is what a variadic body is: a macro
|
;; The rest of a macro's arguments, which is what a variadic body is: a macro
|
||||||
;; takes one parameter, the slice of the forms at its call site.
|
;; takes one parameter, the slice of the forms at its call site.
|
||||||
@ -2050,7 +2050,7 @@ let source = {flan|
|
|||||||
(while (< i (len xs))
|
(while (< i (len xs))
|
||||||
(push v (at xs i))
|
(push v (at xs i))
|
||||||
(set i (+ i 1)))
|
(set i (+ i 1)))
|
||||||
(as-slice v)))
|
(slice v)))
|
||||||
|
|
||||||
;; The elements of a vector form, which is what a [ ] pattern in a macro's
|
;; The elements of a vector form, which is what a [ ] pattern in a macro's
|
||||||
;; parameter list unwraps. The other arm is unreachable from a generated
|
;; parameter list unwraps. The other arm is unreachable from a generated
|
||||||
@ -2083,7 +2083,7 @@ let source = {flan|
|
|||||||
(let [d (i64->bytes gensym-n)]
|
(let [d (i64->bytes gensym-n)]
|
||||||
(dotimes [i (len d)]
|
(dotimes [i (len d)]
|
||||||
(push v (at d i))))
|
(push v (at d i))))
|
||||||
(Form.Sym {.s (string (as-slice v))})))
|
(Form.Sym {.s (string (slice v))})))
|
||||||
|
|
||||||
;; ── The first special form to stop being one ──────────────────────────
|
;; ── The first special form to stop being one ──────────────────────────
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -208,8 +208,8 @@ let rec render c depth (e : Tast.expr) : Tast.expr list =
|
|||||||
would make an acceptance test's output depend on the heap. *)
|
would make an acceptance test's output depend on the heap. *)
|
||||||
| Types.Alloc -> [ lit "<allocator>" ]
|
| Types.Alloc -> [ lit "<allocator>" ]
|
||||||
(* Printing a Vec structurally would be a walk over storage this function
|
(* Printing a Vec structurally would be a walk over storage this function
|
||||||
does not own, and the walk is what [as-slice] is for: (print (as-slice
|
does not own, and the walk is what [slice] is for: (print (slice v))
|
||||||
v)) prints the elements and says at the call site that it borrowed. *)
|
prints the elements and says at the call site that it borrowed. *)
|
||||||
| Types.Vec _ -> [ lit "<vec>" ]
|
| Types.Vec _ -> [ lit "<vec>" ]
|
||||||
| Types.Fn _ as ft -> [ lit ("<" ^ Types.to_string ft ^ ">") ]
|
| Types.Fn _ as ft -> [ lit ("<" ^ Types.to_string ft ^ ">") ]
|
||||||
| Types.Option t ->
|
| Types.Option t ->
|
||||||
|
|||||||
@ -248,7 +248,7 @@ let rec cty env ~needed ~loc ~what (t : Ast.texpr) : string =
|
|||||||
| Ast.Tapp ("Vec", _) ->
|
| Ast.Tapp ("Vec", _) ->
|
||||||
fail loc
|
fail loc
|
||||||
"%s is a Vec, which owns its storage — handing its header to C hands out \
|
"%s is a Vec, which owns its storage — handing its header to C hands out \
|
||||||
an owner. Pass (as-slice v) as (Ptr T) and (len v), the same shape a \
|
an owner. Pass (slice v) as (Ptr T) and (len v), the same shape a \
|
||||||
slice crosses in"
|
slice crosses in"
|
||||||
what
|
what
|
||||||
| Ast.Tfn _ ->
|
| Ast.Tfn _ ->
|
||||||
|
|||||||
@ -1802,7 +1802,7 @@ void *flan_vec_at(flan_vec *v, int32_t i, int64_t size, const uint8_t *loc,
|
|||||||
return (uint8_t *)v->ptr + (int64_t)i * size;
|
return (uint8_t *)v->ptr + (int64_t)i * size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* [hi] of -1 means "to the end": (as-slice v) has no static length to write. */
|
/* [hi] of -1 means "to the end": (slice v) has no static length to write. */
|
||||||
void flan_vec_as_slice(flan_vec *v, void *out, int32_t lo, int32_t hi,
|
void flan_vec_as_slice(flan_vec *v, void *out, int32_t lo, int32_t hi,
|
||||||
int64_t size, const uint8_t *loc, int64_t loclen,
|
int64_t size, const uint8_t *loc, int64_t loclen,
|
||||||
void *xfer) {
|
void *xfer) {
|
||||||
@ -2692,7 +2692,7 @@ int64_t flan_map_len(flan_map *m, const uint8_t *loc, int64_t loclen) {
|
|||||||
* backward shift moves entries to lower slots, and a cursor already past them
|
* backward shift moves entries to lower slots, and a cursor already past them
|
||||||
* steps over entries it has not answered. The epoch check below catches a
|
* steps over entries it has not answered. The epoch check below catches a
|
||||||
* released arena and nothing catches either of these, which is the same
|
* released arena and nothing catches either of these, which is the same
|
||||||
* bargain [as-slice] already makes.
|
* bargain [slice] already makes.
|
||||||
*
|
*
|
||||||
* The layout is the one the geometry describes and is worth restating because
|
* The layout is the one the geometry describes and is worth restating because
|
||||||
* it is the thing most likely to be got wrong here: [data] is *one* allocation
|
* it is the thing most likely to be got wrong here: [data] is *one* allocation
|
||||||
|
|||||||
@ -98,12 +98,18 @@ region" for the rule that stands in its place and for what it costs.
|
|||||||
|
|
||||||
## Borrowing
|
## Borrowing
|
||||||
|
|
||||||
- `(as-slice v)` / `(as-slice v lo hi)` view a `Vec` or fixed array as `[T]`.
|
- `(slice x)` / `(slice x lo)` / `(slice x lo hi)` view a `Vec`, a fixed array,
|
||||||
|
a slice or a string. One name, because the thing handed to it decides what
|
||||||
|
the view means: a `Vec` can only be borrowed, an array can only be viewed,
|
||||||
|
and no call site picks between the two.
|
||||||
|
- **A view of a `Vec` is invalidated by `push`, `put` or `reserve` on that
|
||||||
|
`Vec`, and nothing checks it.** Those are the operations that may reallocate,
|
||||||
|
and a view holds the old address and the old length. This is the whole of the
|
||||||
|
contract: the view goes stale at the `push`, not at the `slice`, and it is
|
||||||
|
the program's business to take the view again afterwards.
|
||||||
- **The first implementation follows Zig/Odin's explicit model, not Rust's
|
- **The first implementation follows Zig/Odin's explicit model, not Rust's
|
||||||
borrow checker.** A slice is invalidated by any operation that may reallocate
|
borrow checker.** Dev builds carry a generation word on `Vec` and trap on use
|
||||||
its owner (`push`, `put`, `reserve`); its user is responsible for respecting
|
of a stale slice. `Ptr` is the explicit lower-level escape hatch and has the
|
||||||
that contract. Dev builds carry a generation word on `Vec` and trap on use of
|
|
||||||
a stale slice. `Ptr` is the explicit lower-level escape hatch and has the
|
|
||||||
same lifetime contract.
|
same lifetime contract.
|
||||||
- A future lightweight provenance pass may reject the obvious mistakes (a
|
- A future lightweight provenance pass may reject the obvious mistakes (a
|
||||||
borrow of a local escaping, use after an owner moves, and reallocation with a
|
borrow of a local escaping, use after an owner moves, and reallocation with a
|
||||||
|
|||||||
@ -211,7 +211,7 @@
|
|||||||
;; individually. `each` borrows it as a slice.
|
;; individually. `each` borrows it as a slice.
|
||||||
(defn draw-frame [w (Ptr World) dt f32] ()
|
(defn draw-frame [w (Ptr World) dt f32] ()
|
||||||
(with-allocator context/temp
|
(with-allocator context/temp
|
||||||
(->> (as-slice (.enemies w))
|
(->> (slice (.enemies w))
|
||||||
(filter (fn [e] (on-screen? (.pos e))))
|
(filter (fn [e] (on-screen? (.pos e))))
|
||||||
(each (fn [e] (rl/draw-texture (.spr e) (.pos e))))))
|
(each (fn [e] (rl/draw-texture (.spr e) (.pos e))))))
|
||||||
(free-all context/temp))
|
(free-all context/temp))
|
||||||
|
|||||||
@ -106,15 +106,15 @@
|
|||||||
;; the bytes never do, so this sorts a borrowed view of a string literal --
|
;; 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.
|
;; which an in-place byte sort could not, since a literal lives in .rodata.
|
||||||
(let [f (split (bytes-view "pear,apple,Fig,apple,banana") \,)]
|
(let [f (split (bytes-view "pear,apple,Fig,apple,banana") \,)]
|
||||||
(sort-bytes (as-slice f))
|
(sort-bytes (slice f))
|
||||||
(show-fields (as-slice f)) ; Fig apple apple banana pear
|
(show-fields (slice f)) ; Fig apple apple banana pear
|
||||||
(free f))
|
(free f))
|
||||||
|
|
||||||
;; And the round trip the whole second tier is for: split, sort, join.
|
;; And the round trip the whole second tier is for: split, sort, join.
|
||||||
(let [f (split (bytes-view "delta,alpha,charlie,bravo") \,)]
|
(let [f (split (bytes-view "delta,alpha,charlie,bravo") \,)]
|
||||||
(sort-bytes (as-slice f))
|
(sort-bytes (slice f))
|
||||||
(let [j (join (as-slice f) (bytes-view " < "))]
|
(let [j (join (slice f) (bytes-view " < "))]
|
||||||
(println (string (as-slice j))) ; alpha < bravo < charlie < delta
|
(println (string (slice j))) ; alpha < bravo < charlie < delta
|
||||||
(free j))
|
(free j))
|
||||||
(free f))
|
(free f))
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
;;;; is why there is one condition type and not two.
|
;;;; is why there is one condition type and not two.
|
||||||
;;;; 4. Every route to a bad index signals: reading a fixed array, writing
|
;;;; 4. Every route to a bad index signals: reading a fixed array, writing
|
||||||
;;;; one (a different lowering — place/Pindex, not At), a slice, a Vec
|
;;;; one (a different lowering — place/Pindex, not At), a slice, a Vec
|
||||||
;;;; element, and a Vec's as-slice. A Vec's check lives inside the
|
;;;; element, and a Vec's slice. A Vec's check lives inside the
|
||||||
;;;; runtime rather than in emitted IR, so those two are plumbed
|
;;;; runtime rather than in emitted IR, so those two are plumbed
|
||||||
;;;; separately and are the ones most likely to be left behind.
|
;;;; separately and are the ones most likely to be left behind.
|
||||||
|
|
||||||
@ -166,11 +166,11 @@
|
|||||||
(show "low" low)
|
(show "low" low)
|
||||||
(show "length" length)
|
(show "length" length)
|
||||||
(restart-case
|
(restart-case
|
||||||
(do (show "vec-slice" (i64 (len (as-slice v 0 2))))
|
(do (show "vec-slice" (i64 (len (slice v 0 2))))
|
||||||
(set frames (+ frames 1)))
|
(set frames (+ frames 1)))
|
||||||
(continue [] (set skipped (+ skipped 1))))
|
(continue [] (set skipped (+ skipped 1))))
|
||||||
(restart-case
|
(restart-case
|
||||||
(do (show "vec-slice" (i64 (len (as-slice v 0 9))))
|
(do (show "vec-slice" (i64 (len (slice v 0 9))))
|
||||||
(set frames (+ frames 1)))
|
(set frames (+ frames 1)))
|
||||||
(continue [] (set skipped (+ skipped 1))))
|
(continue [] (set skipped (+ skipped 1))))
|
||||||
(show "high" high))
|
(show "high" high))
|
||||||
|
|||||||
@ -67,11 +67,11 @@
|
|||||||
|
|
||||||
;; A document in a buffer this program owns and can write to. (bytes-view "literal")
|
;; A document in a buffer this program owns and can write to. (bytes-view "literal")
|
||||||
;; is not that — a literal is constant data behind a writable-looking slice —
|
;; 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 slice.
|
||||||
(defn survives-its-buffer [] ()
|
(defn survives-its-buffer [] ()
|
||||||
(let [buf (vec-new u8)]
|
(let [buf (vec-new u8)]
|
||||||
(append (addr buf) (bytes-view "{:name \"level-1\" :xs [1 2]}"))
|
(append (addr buf) (bytes-view "{:name \"level-1\" :xs [1 2]}"))
|
||||||
(let [src (as-slice buf)
|
(let [src (slice buf)
|
||||||
v (edn/read src)]
|
v (edn/read src)]
|
||||||
(dotimes [i (len src)]
|
(dotimes [i (len src)]
|
||||||
(set (at src i) \x))
|
(set (at src i) \x))
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
(defn show [x f64 p i32] ()
|
(defn show [x f64 p i32] ()
|
||||||
(let [v (format-f64 x p)]
|
(let [v (format-f64 x p)]
|
||||||
(println (string (as-slice v)))
|
(println (string (slice v)))
|
||||||
(free v)))
|
(free v)))
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
@ -89,12 +89,12 @@
|
|||||||
(let [b (vec-new u8)]
|
(let [b (vec-new u8)]
|
||||||
(append (addr b) (bytes-view "fps "))
|
(append (addr b) (bytes-view "fps "))
|
||||||
(let [f (format-f64 59.94 1)]
|
(let [f (format-f64 59.94 1)]
|
||||||
(append (addr b) (as-slice f))
|
(append (addr b) (slice f))
|
||||||
(free f))
|
(free f))
|
||||||
(append (addr b) (bytes-view " / frame "))
|
(append (addr b) (bytes-view " / frame "))
|
||||||
(let [f (format-f64 0.0166667 4)]
|
(let [f (format-f64 0.0166667 4)]
|
||||||
(append (addr b) (as-slice f))
|
(append (addr b) (slice f))
|
||||||
(free f))
|
(free f))
|
||||||
(println (string (as-slice b))) ; fps 59.9 / frame 0.0167
|
(println (string (slice b))) ; fps 59.9 / frame 0.0167
|
||||||
(free b))
|
(free b))
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -246,8 +246,8 @@
|
|||||||
(let [a (arena-new 4096)
|
(let [a (arena-new 4096)
|
||||||
keep (filter (slice ns 0 4) (fn [x] (> x 5)))
|
keep (filter (slice ns 0 4) (fn [x] (> x 5)))
|
||||||
one (one-of 4.5)]
|
one (one-of 4.5)]
|
||||||
(println (len (as-slice keep)))
|
(println (len (slice keep)))
|
||||||
(println (at (as-slice one) 0))
|
(println (at (slice one) 0))
|
||||||
(free one)
|
(free one)
|
||||||
(free keep)
|
(free keep)
|
||||||
(free-all a))))
|
(free-all a))))
|
||||||
|
|||||||
@ -73,7 +73,7 @@
|
|||||||
;; program's when main runs.
|
;; program's when main runs.
|
||||||
(push names 11)
|
(push names 11)
|
||||||
(push names 22)
|
(push names 22)
|
||||||
(println (len (as-slice names)))
|
(println (len (slice names)))
|
||||||
(println (at names 1))
|
(println (at names 1))
|
||||||
(free names)
|
(free names)
|
||||||
;; And the arena, used the way sand.flan means to use it.
|
;; And the arena, used the way sand.flan means to use it.
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
;;;; (defn read-file [path string] dyn
|
;;;; (defn read-file [path string] dyn
|
||||||
;;;; (let [src (slurp path (heap-allocator))]
|
;;;; (let [src (slurp path (heap-allocator))]
|
||||||
;;;; (defer (free src))
|
;;;; (defer (free src))
|
||||||
;;;; (read (as-slice src))))
|
;;;; (read (slice src))))
|
||||||
;;;;
|
;;;;
|
||||||
;;;; `slurp` signals FileError and the handler further out unwinds. The
|
;;;; `slurp` signals FileError and the handler further out unwinds. The
|
||||||
;;;; transfer leaves this frame through its defers — and `src` was never
|
;;;; transfer leaves this frame through its defers — and `src` was never
|
||||||
|
|||||||
@ -46,20 +46,20 @@
|
|||||||
;; No transforms: a copy into the destination named in the form.
|
;; No transforms: a copy into the destination named in the form.
|
||||||
(let [xs [7 8 9]
|
(let [xs [7 8 9]
|
||||||
v (into xs (vec-new i32))]
|
v (into xs (vec-new i32))]
|
||||||
(show (as-slice v)) ; 7 8 9
|
(show (slice v)) ; 7 8 9
|
||||||
(free v))
|
(free v))
|
||||||
|
|
||||||
;; map then filter.
|
;; map then filter.
|
||||||
(let [xs [1 2 3 4 5 6]
|
(let [xs [1 2 3 4 5 6]
|
||||||
v (into xs (vec-new i32) (map double) (filter even?))]
|
v (into xs (vec-new i32) (map double) (filter even?))]
|
||||||
(show (as-slice v)) ; 2 4 6 8 10 12
|
(show (slice v)) ; 2 4 6 8 10 12
|
||||||
(free v))
|
(free v))
|
||||||
|
|
||||||
;; filter then map, over the same source: a different answer, because the
|
;; filter then map, over the same source: a different answer, because the
|
||||||
;; stages are in the order they were written.
|
;; stages are in the order they were written.
|
||||||
(let [xs [1 2 3 4 5 6]
|
(let [xs [1 2 3 4 5 6]
|
||||||
v (into xs (vec-new i32) (filter even?) (map double))]
|
v (into xs (vec-new i32) (filter even?) (map double))]
|
||||||
(show (as-slice v)) ; 4 8 12
|
(show (slice v)) ; 4 8 12
|
||||||
(free v))
|
(free v))
|
||||||
|
|
||||||
;; One pass and no intermediate collection. The two chains above pulled
|
;; One pass and no intermediate collection. The two chains above pulled
|
||||||
@ -69,8 +69,8 @@
|
|||||||
;; A name as the source is read, not moved: src is still alive here.
|
;; A name as the source is read, not moved: src is still alive here.
|
||||||
(let [src (into [3 1 2] (vec-new i32))
|
(let [src (into [3 1 2] (vec-new i32))
|
||||||
v (into src (vec-new i32) (map double))]
|
v (into src (vec-new i32) (map double))]
|
||||||
(show (as-slice v)) ; 6 2 4
|
(show (slice v)) ; 6 2 4
|
||||||
(show (as-slice src)) ; 3 1 2
|
(show (slice src)) ; 3 1 2
|
||||||
(free v)
|
(free v)
|
||||||
(free src))
|
(free src))
|
||||||
|
|
||||||
@ -88,7 +88,7 @@
|
|||||||
;; elements come out of it.
|
;; elements come out of it.
|
||||||
(let [xs [1 2 3 4]
|
(let [xs [1 2 3 4]
|
||||||
v (into (source (slice xs 0 4)) (vec-new i32) (filter even?))]
|
v (into (source (slice xs 0 4)) (vec-new i32) (filter even?))]
|
||||||
(show (as-slice v)) ; 2 4
|
(show (slice v)) ; 2 4
|
||||||
(free v))
|
(free v))
|
||||||
(print builds) (println "") ; 1
|
(print builds) (println "") ; 1
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -407,7 +407,7 @@
|
|||||||
(let [buf (vec-new u8)]
|
(let [buf (vec-new u8)]
|
||||||
(append (addr buf) (bytes-view doc))
|
(append (addr buf) (bytes-view doc))
|
||||||
(with-allocator frame
|
(with-allocator frame
|
||||||
(let [v (read-doc (as-slice buf))]
|
(let [v (read-doc (slice buf))]
|
||||||
(println (describe v)) ; object
|
(println (describe v)) ; object
|
||||||
(println (count-leaves v)) ; every leaf in the graph
|
(println (count-leaves v)) ; every leaf in the graph
|
||||||
(println (sum-ints v)) ; [1 2 3]
|
(println (sum-ints v)) ; [1 2 3]
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
(push v 104) ; h
|
(push v 104) ; h
|
||||||
(push v 105) ; i
|
(push v 105) ; i
|
||||||
(println "before")
|
(println "before")
|
||||||
(c-puts (string (as-slice v)))
|
(c-puts (string (slice v)))
|
||||||
(println "unreachable")
|
(println "unreachable")
|
||||||
(free v))
|
(free v))
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
;; ── The happy path ────────────────────────────────────────────────
|
;; ── The happy path ────────────────────────────────────────────────
|
||||||
(let [v (slurp "programs/assets/a.txt")]
|
(let [v (slurp "programs/assets/a.txt")]
|
||||||
(println (len v)) ; 13
|
(println (len v)) ; 13
|
||||||
(print (string (as-slice v))) ; hello from a
|
(print (string (slice v))) ; hello from a
|
||||||
(free v))
|
(free v))
|
||||||
|
|
||||||
;; Byte-exact, the same as an embed: nothing here decodes anything.
|
;; Byte-exact, the same as an embed: nothing here decodes anything.
|
||||||
@ -52,7 +52,7 @@
|
|||||||
(invoke-restart 'use-value "programs/assets/b.bin"))]
|
(invoke-restart 'use-value "programs/assets/b.bin"))]
|
||||||
(let [v (slurp "programs/assets/does-not-exist")]
|
(let [v (slurp "programs/assets/does-not-exist")]
|
||||||
(println (len v)) ; 3
|
(println (len v)) ; 3
|
||||||
(println (string (as-slice v))) ; BBB
|
(println (string (slice v))) ; BBB
|
||||||
(free v)))
|
(free v)))
|
||||||
(println seen) ; 1
|
(println seen) ; 1
|
||||||
(println (= last-reason file-missing)) ; true
|
(println (= last-reason file-missing)) ; true
|
||||||
@ -65,7 +65,7 @@
|
|||||||
(barf "slurp-out.txt" (bytes-view "round trip\n"))
|
(barf "slurp-out.txt" (bytes-view "round trip\n"))
|
||||||
(let [v (slurp "slurp-out.txt")]
|
(let [v (slurp "slurp-out.txt")]
|
||||||
(println (len v)) ; 11
|
(println (len v)) ; 11
|
||||||
(print (string (as-slice v))) ; round trip
|
(print (string (slice v))) ; round trip
|
||||||
(free v))
|
(free v))
|
||||||
|
|
||||||
;; barf's own failure signals the same condition with op = write. A directory
|
;; barf's own failure signals the same condition with op = write. A directory
|
||||||
@ -80,7 +80,7 @@
|
|||||||
(println seen) ; 1
|
(println seen) ; 1
|
||||||
(println (= last-op file-op-write)) ; true
|
(println (= last-op file-op-write)) ; true
|
||||||
(let [v (slurp "slurp-out.txt")]
|
(let [v (slurp "slurp-out.txt")]
|
||||||
(print (string (as-slice v))) ; second
|
(print (string (slice v))) ; second
|
||||||
(free v))
|
(free v))
|
||||||
|
|
||||||
;; ── retry: the file was not there, so the handler makes it ────────
|
;; ── retry: the file was not there, so the handler makes it ────────
|
||||||
@ -96,7 +96,7 @@
|
|||||||
(barf "slurp-made.txt" (bytes-view "made by the handler\n"))
|
(barf "slurp-made.txt" (bytes-view "made by the handler\n"))
|
||||||
(invoke-restart 'retry))]
|
(invoke-restart 'retry))]
|
||||||
(let [v (slurp "slurp-made.txt")]
|
(let [v (slurp "slurp-made.txt")]
|
||||||
(print (string (as-slice v))) ; made by the handler
|
(print (string (slice v))) ; made by the handler
|
||||||
(free v)))
|
(free v)))
|
||||||
(println seen) ; 1
|
(println seen) ; 1
|
||||||
(println (= last-reason file-missing)) ; true
|
(println (= last-reason file-missing)) ; true
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
;; fire -- what answers here is the byte loop, or the length check first
|
;; fire -- what answers here is the byte loop, or the length check first
|
||||||
;; ruling nothing out since both are three bytes.
|
;; ruling nothing out since both are three bytes.
|
||||||
(let [heap (to-lower (bytes-view "ABC"))]
|
(let [heap (to-lower (bytes-view "ABC"))]
|
||||||
(let [h (string (as-slice heap))]
|
(let [h (string (slice heap))]
|
||||||
(println (= "abc" h)) ; true
|
(println (= "abc" h)) ; true
|
||||||
(println (!= "abc" h)))
|
(println (!= "abc" h)))
|
||||||
(free heap))
|
(free heap))
|
||||||
|
|||||||
@ -9,10 +9,10 @@
|
|||||||
;;;; behaviour (spec-memory.md), because this file is the example people copy.
|
;;;; behaviour (spec-memory.md), because this file is the example people copy.
|
||||||
|
|
||||||
;;; A (Vec u8) printed as text, without the caller writing the two-step every
|
;;; A (Vec u8) printed as text, without the caller writing the two-step every
|
||||||
;;; time. as-slice borrows -- it copies ptr+len and never the elements -- so v
|
;;; time. slice borrows -- it copies ptr+len and never the elements -- so v
|
||||||
;;; is still the owner afterwards and is still free-able.
|
;;; is still the owner afterwards and is still free-able.
|
||||||
(defn show [v (Ptr (Vec u8))] ()
|
(defn show [v (Ptr (Vec u8))] ()
|
||||||
(println (string (as-slice (deref v)))))
|
(println (string (slice (deref v)))))
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
;; The builder. Three appends and two numbers into one Vec, which is the
|
;; The builder. Three appends and two numbers into one Vec, which is the
|
||||||
@ -130,7 +130,7 @@
|
|||||||
;; different one, so an implementation that handed back the original slice
|
;; different one, so an implementation that handed back the original slice
|
||||||
;; would print the original string.
|
;; would print the original string.
|
||||||
(let [f (split (bytes-view "a,b,c") \,)]
|
(let [f (split (bytes-view "a,b,c") \,)]
|
||||||
(let [j (join (as-slice f) (bytes-view "/"))]
|
(let [j (join (slice f) (bytes-view "/"))]
|
||||||
(show (addr j)) ; a/b/c
|
(show (addr j)) ; a/b/c
|
||||||
(free j))
|
(free j))
|
||||||
(free f))
|
(free f))
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
;; The second run. Nothing re-initialises the global between them, so the
|
;; The second run. Nothing re-initialises the global between them, so the
|
||||||
;; length keeps climbing and the loaded data is the same block it was.
|
;; length keeps climbing and the loaded data is the same block it was.
|
||||||
(entry)
|
(entry)
|
||||||
(println (len (as-slice the-data)))
|
(println (len (slice the-data)))
|
||||||
;; A copy is the one thing something else may own, and freeing that copy
|
;; A copy is the one thing something else may own, and freeing that copy
|
||||||
;; leaves the global untouched.
|
;; leaves the global untouched.
|
||||||
(let [c (clone the-data)]
|
(let [c (clone the-data)]
|
||||||
|
|||||||
@ -42,12 +42,18 @@
|
|||||||
(set (at v 1) 99)
|
(set (at v 1) 99)
|
||||||
(println (at v 1)) ; 99
|
(println (at v 1)) ; 99
|
||||||
|
|
||||||
;; as-slice is a non-owning view: it copies ptr+len and never the
|
;; slice over a Vec is a non-owning view: it copies ptr+len and never the
|
||||||
;; elements, and it carries no allocator, so nothing can be freed through
|
;; elements, and it carries no allocator, so nothing can be freed through
|
||||||
;; one. [at] and [len] over it are the array operations, unchanged.
|
;; one. [at] and [len] over it are the array operations, unchanged, and
|
||||||
(println (sum (as-slice v))) ; 139
|
;; the three arities are the ones every other target has -- the tail form
|
||||||
(println (len (as-slice v 1 3))) ; 2
|
;; included, which the Vec had no spelling for while it had a name of its
|
||||||
(println (at (as-slice v 1 3) 0)) ; 99
|
;; own. The view is of storage v owns: a push here would move it, and
|
||||||
|
;; nothing would say so.
|
||||||
|
(println (sum (slice v))) ; 139
|
||||||
|
(println (len (slice v 1 3))) ; 2
|
||||||
|
(println (at (slice v 1 3) 0)) ; 99
|
||||||
|
(println (len (slice v 1))) ; 2
|
||||||
|
(println (at (slice v 1) 1)) ; 30
|
||||||
|
|
||||||
;; clone is the only copy: assignment moves. The copy is independent, and
|
;; clone is the only copy: assignment moves. The copy is independent, and
|
||||||
;; freeing it leaves the original alone.
|
;; freeing it leaves the original alone.
|
||||||
@ -65,7 +71,7 @@
|
|||||||
(println (len v)) ; 4
|
(println (len v)) ; 4
|
||||||
|
|
||||||
;; The structural printer reaches both new types. Neither is followed: a
|
;; The structural printer reaches both new types. Neither is followed: a
|
||||||
;; Vec's elements are printed through (as-slice v), which says at the call
|
;; Vec's elements are printed through (slice v), which says at the call
|
||||||
;; site that it borrowed, and an allocator's contents are the runtime's and
|
;; site that it borrowed, and an allocator's contents are the runtime's and
|
||||||
;; its address is not stable across runs.
|
;; its address is not stable across runs.
|
||||||
(println v) ; <vec>
|
(println v) ; <vec>
|
||||||
|
|||||||
@ -1344,7 +1344,7 @@ let () =
|
|||||||
moves are here too — into a call and out of one — because a Vec that
|
moves are here too — into a call and out of one — because a Vec that
|
||||||
cannot be handed to a function is not a container anyone can use. *)
|
cannot be handed to a function is not a container anyone can use. *)
|
||||||
let vec_out =
|
let vec_out =
|
||||||
"0\n3\n10\n30\n99\n139\n2\n99\n-1\n10\n3\n4\n\
|
"0\n3\n10\n30\n99\n139\n2\n99\n2\n30\n-1\n10\n3\n4\n\
|
||||||
<vec>\n<allocator>\n2\n4\n7\nfalse\n11\n5\n"
|
<vec>\n<allocator>\n2\n4\n7\nfalse\n11\n5\n"
|
||||||
in
|
in
|
||||||
outputs "vec" "programs/vec.flan" vec_out;
|
outputs "vec" "programs/vec.flan" vec_out;
|
||||||
@ -2392,7 +2392,7 @@ let () =
|
|||||||
Three claims, and the second is the one that had to be decided rather
|
Three claims, and the second is the one that had to be decided rather
|
||||||
than inherited. (1) Five frames finish and seven are abandoned, out of
|
than inherited. (1) Five frames finish and seven are abandoned, out of
|
||||||
five routes to a bad index — fixed-array read, fixed-array write (a
|
five routes to a bad index — fixed-array read, fixed-array write (a
|
||||||
different lowering), slice, Vec element and Vec as-slice, the last two
|
different lowering), slice, Vec element and Vec slice, the last two
|
||||||
checked inside the runtime rather than in emitted IR. (2) `cleaned` is
|
checked inside the runtime rather than in emitted IR. (2) `cleaned` is
|
||||||
5, which is every defer on every one of those paths: an answered bounds
|
5, which is every defer on every one of those paths: an answered bounds
|
||||||
failure leaves through the same unwind path a `return` uses and runs
|
failure leaves through the same unwind path a `return` uses and runs
|
||||||
|
|||||||
@ -1959,9 +1959,50 @@ let () =
|
|||||||
~needle:"(slice a lo hi)";
|
~needle:"(slice a lo hi)";
|
||||||
rejects_check "slice of four" (arr ^ "(defn f [] [i32] (slice a 0 1 2))")
|
rejects_check "slice of four" (arr ^ "(defn f [] [i32] (slice a 0 1 2))")
|
||||||
~needle:"given 4 arguments";
|
~needle:"given 4 arguments";
|
||||||
rejects_check "slice of a Vec"
|
rejects_check "slice of something with no elements"
|
||||||
"(defn f [v (Vec i32)] [i32] (slice v))"
|
"(defn f [n i32] [i32] (slice n))"
|
||||||
~needle:"slice takes an array, a slice or a string";
|
~needle:"slice takes an array, a slice, a string or a Vec";
|
||||||
|
(* A Vec is the fourth thing slice takes, and it is the one that borrows:
|
||||||
|
the view is of storage the Vec owns and a push may move it. That is a
|
||||||
|
rule about the push and is written down beside the push; here what is
|
||||||
|
pinned is that all three arities reach a Vec, the two-argument one
|
||||||
|
included — it had no spelling at all while the Vec had a name of its
|
||||||
|
own. There is no static length to check a bound against, so the only
|
||||||
|
refusal left is the literal pair that runs backwards. *)
|
||||||
|
let vec = "(defn f [v (Vec i32)] " in
|
||||||
|
accepts "the whole of a Vec" (vec ^ "[i32] (slice v))");
|
||||||
|
accepts "the tail of a Vec" (vec ^ "[i32] (slice v 2))");
|
||||||
|
accepts "a range of a Vec" (vec ^ "[i32] (slice v 1 3))");
|
||||||
|
accepts "a Vec bound is not known here" (vec ^ "[i32] (slice v 0 9999))");
|
||||||
|
rejects_check "reversed slice of a Vec" (vec ^ "[i32] (slice v 2 1))")
|
||||||
|
~needle:"runs backwards";
|
||||||
|
rejects_check "slice of a Vec of four"
|
||||||
|
(vec ^ "[i32] (slice v 0 1 2))") ~needle:"given 4 arguments";
|
||||||
|
(* The mirror of the returned-array refusal, for a different loss. The
|
||||||
|
storage a returned Vec owns outlives the expression, so the view does not
|
||||||
|
dangle — what is gone is the owner, and with it any way to free the
|
||||||
|
block. *)
|
||||||
|
rejects_check "slice of a returned Vec"
|
||||||
|
"(defn mk [] (Vec i32) (vec-new i32)) (defn f [] [i32] (slice (mk)))"
|
||||||
|
~needle:"the header a call answers is a temporary";
|
||||||
|
rejects_check "slice of a returned Vec, three arguments"
|
||||||
|
"(defn mk [] (Vec i32) (vec-new i32)) (defn f [] [i32] (slice (mk) 0 1))"
|
||||||
|
~needle:"the header a call answers is a temporary";
|
||||||
|
(* There is no [as-slice]: one word takes the view and what it is given says
|
||||||
|
what the view is. The refusal names [slice] and writes the call back out,
|
||||||
|
because a reader meeting it has no reason to know there was ever a second
|
||||||
|
name. *)
|
||||||
|
rejects_check "as-slice is not a name"
|
||||||
|
(vec ^ "[i32] (as-slice v))") ~needle:"there is no as-slice";
|
||||||
|
rejects_check "as-slice suggests the call that replaces it"
|
||||||
|
(vec ^ "[i32] (as-slice v 1 3))") ~needle:"Write (slice v 1 3)";
|
||||||
|
rejects_check "as-slice over an expression suggests a form that compiles"
|
||||||
|
"(defn mk [] (Vec i32) (vec-new i32)) \
|
||||||
|
(defn f [] [i32] (as-slice (mk) 1 3))"
|
||||||
|
~needle:"Write (slice v 1 3)";
|
||||||
|
(* A program's own definition of the name still reaches its own. *)
|
||||||
|
accepts "a program may define as-slice"
|
||||||
|
"(defn as-slice [n i32] i32 n) (defn f [] i32 (as-slice 3))";
|
||||||
(* An array a call returned is a temporary the slice would outlive. It
|
(* An array a call returned is a temporary the slice would outlive. It
|
||||||
dangled silently on both backends before, at the three-argument
|
dangled silently on both backends before, at the three-argument
|
||||||
spelling; (slice (mk)) is the spelling that would have made it
|
spelling; (slice (mk)) is the spelling that would have made it
|
||||||
@ -2970,7 +3011,7 @@ let () =
|
|||||||
accepts "a global Vec is borrowed, mutated and cloned"
|
accepts "a global Vec is borrowed, mutated and cloned"
|
||||||
"(defonce g (Vec u8)) \
|
"(defonce g (Vec u8)) \
|
||||||
(defn f [] () (set g (vec-new u8)) (push g 1) (set (at g 0) 2) \
|
(defn f [] () (set g (vec-new u8)) (push g 1) (set (at g 0) 2) \
|
||||||
(println (len (as-slice g))) (let [c (clone g)] (free c)))";
|
(println (len (slice g))) (let [c (clone g)] (free c)))";
|
||||||
rejects_check "try is milestone 6" "(defn f [] i32 (try 1))"
|
rejects_check "try is milestone 6" "(defn f [] i32 (try 1))"
|
||||||
~needle:"milestone 6";
|
~needle:"milestone 6";
|
||||||
(* dotimes and defer are implemented, and a defer in a [let] is now one of
|
(* dotimes and defer are implemented, and a defer in a [let] is now one of
|
||||||
|
|||||||
@ -316,7 +316,7 @@ let heap_oob =
|
|||||||
\ (let [v (vec-new i32)]\n\
|
\ (let [v (vec-new i32)]\n\
|
||||||
\ (push v 1)\n\
|
\ (push v 1)\n\
|
||||||
\ (push v 2)\n\
|
\ (push v 2)\n\
|
||||||
\ (let [s (as-slice v)] (print (at s 4000)) (println \"\"))\n\
|
\ (let [s (slice v)] (print (at s 4000)) (println \"\"))\n\
|
||||||
\ (free v))\n\
|
\ (free v))\n\
|
||||||
\ 0)\n"
|
\ 0)\n"
|
||||||
|
|
||||||
@ -334,7 +334,7 @@ let heap_uninit =
|
|||||||
\ (let [v (vec-new i32)]\n\
|
\ (let [v (vec-new i32)]\n\
|
||||||
\ (push v 1)\n\
|
\ (push v 1)\n\
|
||||||
\ (push v 2)\n\
|
\ (push v 2)\n\
|
||||||
\ (let [s (as-slice v)] (print (at s 3)) (println \"\"))\n\
|
\ (let [s (slice v)] (print (at s 3)) (println \"\"))\n\
|
||||||
\ (free v))\n\
|
\ (free v))\n\
|
||||||
\ 0)\n"
|
\ 0)\n"
|
||||||
|
|
||||||
@ -397,7 +397,7 @@ let arena_reuse =
|
|||||||
\ (with-allocator frame\n\
|
\ (with-allocator frame\n\
|
||||||
\ (let [w (vec-new i32)]\n\
|
\ (let [w (vec-new i32)]\n\
|
||||||
\ (push w 5) (push w 6)\n\
|
\ (push w 5) (push w 6)\n\
|
||||||
\ (let [t (as-slice w)] (print (at t 3)) (println \"\"))))\n\
|
\ (let [t (slice w)] (print (at t 3)) (println \"\"))))\n\
|
||||||
\ 0)\n"
|
\ 0)\n"
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
|
|||||||
16
vendor/edn/provide.flan
vendored
16
vendor/edn/provide.flan
vendored
@ -61,7 +61,7 @@
|
|||||||
(let [v (vec-new u8)]
|
(let [v (vec-new u8)]
|
||||||
(append (addr v) (bytes-view a))
|
(append (addr v) (bytes-view a))
|
||||||
(append (addr v) (bytes-view b))
|
(append (addr v) (bytes-view b))
|
||||||
(string (as-slice v))))
|
(string (slice v))))
|
||||||
|
|
||||||
(defn joined3 [a string b string c string] string
|
(defn joined3 [a string b string c string] string
|
||||||
(joined a (joined b c)))
|
(joined a (joined b c)))
|
||||||
@ -74,7 +74,7 @@
|
|||||||
(defn i64->string [n i64] string
|
(defn i64->string [n i64] string
|
||||||
(let [v (vec-new u8)]
|
(let [v (vec-new u8)]
|
||||||
(append-i64 (addr v) n)
|
(append-i64 (addr v) n)
|
||||||
(string (as-slice v))))
|
(string (slice v))))
|
||||||
|
|
||||||
;; The tokenizer answers byte offsets, because that is what a slice into the
|
;; The tokenizer answers byte offsets, because that is what a slice into the
|
||||||
;; buffer costs nothing to produce. A person reading a refusal wants a line and
|
;; buffer costs nothing to produce. A person reading a refusal wants a line and
|
||||||
@ -150,7 +150,7 @@
|
|||||||
(let [t (expect c tok-string)
|
(let [t (expect c tok-string)
|
||||||
b (vec-new u8 a)]
|
b (vec-new u8 a)]
|
||||||
(append (addr b) (.text t))
|
(append (addr b) (.text t))
|
||||||
(string (as-slice b))))
|
(string (slice b))))
|
||||||
|
|
||||||
;; Whether the next thing, past trivia, is this byte. Enough of a peek for
|
;; Whether the next thing, past trivia, is this byte. Enough of a peek for
|
||||||
;; every loop below — "is the collection over" is the only lookahead a reader
|
;; every loop below — "is the collection over" is the only lookahead a reader
|
||||||
@ -463,7 +463,7 @@
|
|||||||
(return out))))
|
(return out))))
|
||||||
(let [sname (Form.Sym {.s name})
|
(let [sname (Form.Sym {.s name})
|
||||||
rname (Form.Sym {.s (joined "read-" name)})
|
rname (Form.Sym {.s (joined "read-" name)})
|
||||||
struct `(defstruct ~sname ~(Form.Vec {.xs (as-slice fields)}))
|
struct `(defstruct ~sname ~(Form.Vec {.xs (slice fields)}))
|
||||||
reader
|
reader
|
||||||
`(defn ~rname [c (Ptr Cursor) a Allocator] ~sname
|
`(defn ~rname [c (Ptr Cursor) a Allocator] ~sname
|
||||||
(let [out (~sname {})
|
(let [out (~sname {})
|
||||||
@ -472,16 +472,16 @@
|
|||||||
(while (ok? c)
|
(while (ok? c)
|
||||||
(let [k (next c)]
|
(let [k (next c)]
|
||||||
(when (or (not (ok? c)) (= (.kind k) tok-map-close))
|
(when (or (not (ok? c)) (= (.kind k) tok-map-close))
|
||||||
~@(as-slice missing)
|
~@(slice missing)
|
||||||
(return out))
|
(return out))
|
||||||
(when (!= (.kind k) tok-keyword)
|
(when (!= (.kind k) tok-keyword)
|
||||||
(fail c err-unexpected-token (.pos k))
|
(fail c err-unexpected-token (.pos k))
|
||||||
(return out))
|
(return out))
|
||||||
(cond ~@(as-slice clauses))))
|
(cond ~@(slice clauses))))
|
||||||
out))]
|
out))]
|
||||||
(push decls struct)
|
(push decls struct)
|
||||||
(push decls reader)
|
(push decls reader)
|
||||||
(ok-derived sname (as-slice decls) `(~rname c a)))))
|
(ok-derived sname (slice decls) `(~rname c a)))))
|
||||||
|
|
||||||
;; ── Comparing and rendering a type form ─────────────────────────────
|
;; ── Comparing and rendering a type form ─────────────────────────────
|
||||||
|
|
||||||
@ -572,7 +572,7 @@
|
|||||||
out))
|
out))
|
||||||
(defn ~fname [p string a Allocator] ~sname
|
(defn ~fname [p string a Allocator] ~sname
|
||||||
(let [b (slurp p a)]
|
(let [b (slurp p a)]
|
||||||
(~bname (as-slice b) a)))))))))
|
(~bname (slice b) a)))))))))
|
||||||
|
|
||||||
;; A refusal, as a declaration. `compile-error` is an expression and a
|
;; A refusal, as a declaration. `compile-error` is an expression and a
|
||||||
;; top-level position takes a declaration, so it goes in the body of a function
|
;; top-level position takes a declaration, so it goes in the body of a function
|
||||||
|
|||||||
4
vendor/edn/read.flan
vendored
4
vendor/edn/read.flan
vendored
@ -68,7 +68,7 @@
|
|||||||
(defn copy-text [s [u8]] string
|
(defn copy-text [s [u8]] string
|
||||||
(let [b (vec-new u8)]
|
(let [b (vec-new u8)]
|
||||||
(append (addr b) s)
|
(append (addr b) s)
|
||||||
(string (as-slice b))))
|
(string (slice b))))
|
||||||
|
|
||||||
;; ── Reading ─────────────────────────────────────────────────────────
|
;; ── Reading ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -158,4 +158,4 @@
|
|||||||
(defn read-file [path string] dyn
|
(defn read-file [path string] dyn
|
||||||
(let [src (slurp path (heap-allocator))]
|
(let [src (slurp path (heap-allocator))]
|
||||||
(defer (free src))
|
(defer (free src))
|
||||||
(read (as-slice src))))
|
(read (slice src))))
|
||||||
|
|||||||
4
vendor/json/json.flan
vendored
4
vendor/json/json.flan
vendored
@ -661,7 +661,7 @@
|
|||||||
;; free-all and under the heap it is a block nobody has freed. There is no
|
;; free-all and under the heap it is a block nobody has freed. There is no
|
||||||
;; third owner.
|
;; third owner.
|
||||||
;;
|
;;
|
||||||
;; The builder is a (Vec u8) and the answer is (string (as-slice b)) taken ONCE
|
;; The builder is a (Vec u8) and the answer is (string (slice b)) taken ONCE
|
||||||
;; at the end. That ordering is load-bearing rather than stylistic: a push
|
;; at the end. That ordering is load-bearing rather than stylistic: a push
|
||||||
;; after the slice has been taken can grow the Vec into a new block, and in an
|
;; after the slice has been taken can grow the Vec into a new block, and in an
|
||||||
;; arena the old block is still mapped, so the already-taken string would go on
|
;; arena the old block is still mapped, so the already-taken string would go on
|
||||||
@ -718,7 +718,7 @@
|
|||||||
;; because a dropped character is not worth a crash and the
|
;; because a dropped character is not worth a crash and the
|
||||||
;; tokenizer is the place that already said no.
|
;; tokenizer is the place that already said no.
|
||||||
None (do)))))))))
|
None (do)))))))))
|
||||||
(Some (string (as-slice b)))))
|
(Some (string (slice b)))))
|
||||||
|
|
||||||
;; ── Reading past a value ────────────────────────────────────────────
|
;; ── Reading past a value ────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
16
vendor/json/provide.flan
vendored
16
vendor/json/provide.flan
vendored
@ -46,7 +46,7 @@
|
|||||||
(let [v (vec-new u8)]
|
(let [v (vec-new u8)]
|
||||||
(append (addr v) (bytes-view a))
|
(append (addr v) (bytes-view a))
|
||||||
(append (addr v) (bytes-view b))
|
(append (addr v) (bytes-view b))
|
||||||
(string (as-slice v))))
|
(string (slice v))))
|
||||||
|
|
||||||
(defn joined3 [a string b string c string] string
|
(defn joined3 [a string b string c string] string
|
||||||
(joined a (joined b c)))
|
(joined a (joined b c)))
|
||||||
@ -58,7 +58,7 @@
|
|||||||
(defn i64->string [n i64] string
|
(defn i64->string [n i64] string
|
||||||
(let [v (vec-new u8)]
|
(let [v (vec-new u8)]
|
||||||
(append-i64 (addr v) n)
|
(append-i64 (addr v) n)
|
||||||
(string (as-slice v))))
|
(string (slice v))))
|
||||||
|
|
||||||
;; The tokenizer answers byte offsets. A person reading a refusal wants a line
|
;; The tokenizer answers byte offsets. A person reading a refusal wants a line
|
||||||
;; and a column, so the newlines before the offset are counted here — once per
|
;; and a column, so the newlines before the offset are counted here — once per
|
||||||
@ -324,7 +324,7 @@
|
|||||||
(return out))))
|
(return out))))
|
||||||
(let [sname (Form.Sym {.s name})
|
(let [sname (Form.Sym {.s name})
|
||||||
rname (Form.Sym {.s (joined "read-" name)})
|
rname (Form.Sym {.s (joined "read-" name)})
|
||||||
struct `(defstruct ~sname ~(Form.Vec {.xs (as-slice fields)}))
|
struct `(defstruct ~sname ~(Form.Vec {.xs (slice fields)}))
|
||||||
reader
|
reader
|
||||||
`(defn ~rname [c (Ptr Cursor) a Allocator] ~sname
|
`(defn ~rname [c (Ptr Cursor) a Allocator] ~sname
|
||||||
(let [out (~sname {})
|
(let [out (~sname {})
|
||||||
@ -333,14 +333,14 @@
|
|||||||
(while (and (ok? c) (not (at-byte? c \})))
|
(while (and (ok? c) (not (at-byte? c \})))
|
||||||
(let [k (expect c tok-string)]
|
(let [k (expect c tok-string)]
|
||||||
(expect c tok-colon)
|
(expect c tok-colon)
|
||||||
(cond ~@(as-slice clauses))
|
(cond ~@(slice clauses))
|
||||||
(comma c)))
|
(comma c)))
|
||||||
(let [close (expect c tok-object-close)]
|
(let [close (expect c tok-object-close)]
|
||||||
~@(as-slice missing))
|
~@(slice missing))
|
||||||
out))]
|
out))]
|
||||||
(push decls struct)
|
(push decls struct)
|
||||||
(push decls reader)
|
(push decls reader)
|
||||||
(ok-derived sname (as-slice decls) `(~rname c a)))))
|
(ok-derived sname (slice decls) `(~rname c a)))))
|
||||||
|
|
||||||
;; ── The small predicates the refusals are written against ───────────
|
;; ── The small predicates the refusals are written against ───────────
|
||||||
|
|
||||||
@ -375,7 +375,7 @@
|
|||||||
(defn copy-of [s [u8]] string
|
(defn copy-of [s [u8]] string
|
||||||
(let [b (vec-new u8)]
|
(let [b (vec-new u8)]
|
||||||
(append (addr b) s)
|
(append (addr b) s)
|
||||||
(string (as-slice b))))
|
(string (slice b))))
|
||||||
|
|
||||||
;; ── Comparing and rendering a type form ─────────────────────────────
|
;; ── Comparing and rendering a type form ─────────────────────────────
|
||||||
|
|
||||||
@ -453,7 +453,7 @@
|
|||||||
out))
|
out))
|
||||||
(defn ~fname [p string a Allocator] ~sname
|
(defn ~fname [p string a Allocator] ~sname
|
||||||
(let [b (slurp p a)]
|
(let [b (slurp p a)]
|
||||||
(~bname (as-slice b) a)))))))))
|
(~bname (slice b) a)))))))))
|
||||||
|
|
||||||
;; A refusal, as a declaration. `compile-error` is an expression and a top-level
|
;; A refusal, as a declaration. `compile-error` is an expression and a top-level
|
||||||
;; position takes a declaration, so it goes in the body of a function nothing
|
;; position takes a declaration, so it goes in the body of a function nothing
|
||||||
|
|||||||
@ -911,7 +911,7 @@ type as an argument — <code>(vec-new t)</code>, <code>(map-new t i32)</code>,
|
|||||||
(let [ns [5 3 9 1]
|
(let [ns [5 3 9 1]
|
||||||
one (one-of 4.5)]
|
one (one-of 4.5)]
|
||||||
(println (first-or (slice ns 0 4) -1))
|
(println (first-or (slice ns 0 4) -1))
|
||||||
(println (at (as-slice one) 0))
|
(println (at (slice one) 0))
|
||||||
(free one)))</code></pre>
|
(free one)))</code></pre>
|
||||||
|
|
||||||
<pre><code class="sh">3
|
<pre><code class="sh">3
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user