diff --git a/lib/prelude.ml b/lib/prelude.ml index 760586f..d2b50bf 100644 --- a/lib/prelude.ml +++ b/lib/prelude.ml @@ -607,8 +607,11 @@ let source = {flan| ;; Ported from Odin's core/unicode/utf8/utf8.odin, which is the one corner of ;; a string library that is allocation-free by construction: decoding is ;; classification, and every answer it gives is a number. Everything else in -;; Odin's core/strings and all of core/fmt takes `allocator := -;; context.allocator`, and is therefore refused below rather than ported. +;; Odin's core/strings takes `allocator := context.allocator`, which is why +;; this corner came first and the rest waited; most of that rest is ported now +;; and lives in the building section below. core/fmt is still absent, and the +;; reason it stays absent is not allocation — see the refusal list at the foot +;; of this file. ;; ;; Odin's 256-entry accept_sizes table becomes a cond over the lead byte here. ;; The table is the cache-friendly form and the cond is the one you can check @@ -791,12 +794,13 @@ let source = {flan| ;; ── Splitting ───────────────────────────────────────────────────────── ;; -;; `split` returning a sequence of fields must allocate the sequence, and -;; there is no allocator — so it is refused by name at the bottom of this -;; file, and this is the shape that survives. It is Odin's -;; split_by_byte_iterator (strings.odin): a cursor holding the rest of the -;; input, handing back one field at a time. Every field is a slice *of the -;; caller's bytes*; nothing is copied and nothing is owned. +;; The iterator, which owns nothing. `split` returning a sequence of fields has +;; to allocate that sequence, and it does — it is in the building section below +;; — but this stays the right call whenever you do not want to own the result: +;; it is Odin's split_by_byte_iterator (strings.odin), a cursor holding the +;; rest of the input and handing back one field at a time. Every field is a +;; slice *of the caller's bytes*; nothing is copied, nothing is owned, and +;; there is no free to remember. `split` is built on exactly this. ;; ;; One divergence, and it is a wart of Odin's rather than a decision. Odin's ;; iterator stops on an empty final field, so "a,b," iterates a and b and the @@ -828,9 +832,12 @@ let source = {flan| ;; ── ASCII case ──────────────────────────────────────────────────────── ;; ;; Byte in, byte out, and *not* a function over a slice. Odin's to_lower and -;; to_upper both allocate a new string (core/strings/conversion.odin), which -;; is not available here; the obvious substitute — lowering a [u8] in place — -;; is a trap, and it is worth saying why rather than shipping it. A string +;; to_upper both allocate a new string (core/strings/conversion.odin) and so do +;; the ones in the building section below; these are the forms that allocate +;; nothing, and they stay the right call when a copy is not wanted — folding a +;; comparison over two inputs beats lowering both and comparing. What is *not* +;; on offer is the third shape, lowering a [u8] in place, and it is worth +;; saying why rather than shipping it. A string ;; literal is emitted `private unnamed_addr constant` (emit.ml), so (bytes ;; "Hello") is a [u8] pointing straight into read-only memory. An in-place ;; lower-ascii! type checks against that slice, and what happens next depends diff --git a/test/programs/strings.flan b/test/programs/strings.flan index d1e441a..17d8929 100644 --- a/test/programs/strings.flan +++ b/test/programs/strings.flan @@ -137,15 +137,17 @@ ;; The allocator is the context's, so with-allocator moves the whole tier ;; into an arena -- which is the answer to the fixed arity of a defn, and the - ;; reason none of these takes an allocator argument. free-all releases every - ;; one of them at once, including the Vec still bound below it. + ;; reason none of these takes an allocator argument. free-all is what + ;; releases the region, and arena-destroy hands it back. (let [a (arena-new 4096)] (with-allocator a (let [parts [(bytes "in") (bytes "arena")]] (let [j (join (slice parts 0 2) (bytes "-"))] (show (addr j)) ; in-arena - ;; Not freed: an arena cannot release one block, and free-all is - ;; what releases this. + ;; The free is written because the binding is dead after it either + ;; way, and it keeps the block: an arena cannot release one, which + ;; is the difference the capability set exists to state. free-all + ;; below is what actually releases this. (free j)))) (free-all a) (arena-destroy a))