;;;; The prelude's second tier: the functions that return new storage. ;;;; ;;;; Every one of these was refused by name in prelude.ml until there was an ;;;; allocator to return a Vec from, and this file is the corpus that says the ;;;; refusals are lifted. The cases are chosen the way the slice-algorithm ;;;; tests were: each is an input a plausible wrong version gets wrong. ;;;; ;;;; Everything allocated here is freed, even though leaking is defined ;;;; 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 ;;; time. as-slice borrows -- it copies ptr+len and never the elements -- so v ;;; is still the owner afterwards and is still free-able. (defn show [v (Ptr (Vec u8))] (println (string (as-slice (deref v))))) (defn main [] i32 ;; The builder. Three appends and two numbers into one Vec, which is the ;; case the shared static scratch buffer in the runtime makes impossible for ;; i64->bytes on its own: two of its results cannot be held at once, and ;; these two numbers are both in the answer. (let [b (vec-new u8)] (append! (addr b) (bytes "x=")) (append-i64! (addr b) 42) (append! (addr b) (bytes " y=")) (append-i64! (addr b) -7) (append! (addr b) (bytes " r=")) (append-f64! (addr b) 1.5) (show (addr b)) ; x=42 y=-7 r=1.5 (free b)) ;; concat over three parts, and over none -- the empty result rather than a ;; trap. (let [parts [(bytes "one") (bytes "") (bytes "two")]] (let [c (concat (slice parts 0 3))] (show (addr c)) ; onetwo (free c))) (let [parts [(bytes "unused")]] (let [c (concat (slice parts 0 0))] (println (len c)) ; 0 (free c))) ;; join: n parts, n-1 separators. The one-part case is the one that must not ;; emit a separator at all, and the zero-part case is the one a "append then ;; chop the tail" join gets wrong because there is no tail. (let [parts [(bytes "a") (bytes "b") (bytes "c")]] (let [j (join (slice parts 0 3) (bytes ", "))] (show (addr j)) ; a, b, c (free j)) (let [j (join (slice parts 0 1) (bytes ", "))] (show (addr j)) ; a (free j)) (let [j (join (slice parts 0 0) (bytes ", "))] (println (len j)) ; 0 (free j)) ;; An empty separator is concat. (let [j (join (slice parts 0 3) (bytes ""))] (show (addr j)) ; abc (free j))) ;; repeat, including zero times. (let [r (repeat-bytes (bytes "ab") 3)] (show (addr r)) ; ababab (free r)) (let [r (repeat-bytes (bytes "ab") 0)] (println (len r)) ; 0 (free r)) ;; The allocating case pair. The input is a string literal, which lives in ;; .rodata -- an in-place lower would either segfault at -O0 or be deleted at ;; -O2, and that is exactly why these exist. Digits and punctuation pass ;; through untouched, which is the range check a table-free version gets ;; wrong by shifting every byte. (let [l (to-lower (bytes "Hello, World 42!"))] (show (addr l)) ; hello, world 42! (free l)) (let [u (to-upper (bytes "Hello, World 42!"))] (show (addr u)) ; HELLO, WORLD 42! (free u)) ;; replace. "aaa" with "aa" -> "b" is the non-overlapping rule: the answer is ;; "ba", because the match consumes both a's and the scan resumes after them. (let [r (replace-bytes (bytes "aaa") (bytes "aa") (bytes "b"))] (show (addr r)) ; ba (free r)) ;; A replacement longer than what it replaces, and one that is empty. (let [r (replace-bytes (bytes "a,b,c") (bytes ",") (bytes " -- "))] (show (addr r)) ; a -- b -- c (free r)) (let [r (replace-bytes (bytes "a,b,c") (bytes ",") (bytes ""))] (show (addr r)) ; abc (free r)) ;; No occurrence is a copy, and an empty `from` is a copy -- the reading ;; where it matches everywhere is an infinite loop. (let [r (replace-bytes (bytes "abc") (bytes "z") (bytes "!"))] (show (addr r)) ; abc (free r)) (let [r (replace-bytes (bytes "abc") (bytes "") (bytes "!"))] (show (addr r)) ; abc (free r)) ;; split. n separators, n+1 fields, always -- so the trailing empty field is ;; present, which is where Odin's own iterator and its allocating split ;; disagree with each other. (let [f (split (bytes "a,b,c") \,)] (println (len f)) ; 3 (println (string (at f 0))) ; a (println (string (at f 2))) ; c (free f)) (let [f (split (bytes "a,b,") \,)] (println (len f)) ; 3 (println (len (at f 2))) ; 0 (free f)) (let [f (split (bytes ",a") \,)] (println (len f)) ; 2 (println (len (at f 0))) ; 0 (free f)) ;; No separator at all is one field, and the empty input is one empty field. (let [f (split (bytes "abc") \,)] (println (len f)) ; 1 (println (string (at f 0))) ; abc (free f)) (let [f (split (bytes "") \,)] (println (len f)) ; 1 (println (len (at f 0))) ; 0 (free f)) ;; The fields are slices of the input and nothing was copied: this one ;; round-trips through join, and the separator it rebuilds with is a ;; different one, so an implementation that handed back the original slice ;; would print the original string. (let [f (split (bytes "a,b,c") \,)] (let [j (join (as-slice f) (bytes "/"))] (show (addr j)) ; a/b/c (free j)) (free f)) ;; 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. (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. (free j)))) (free-all a) (arena-destroy a)) 0)