flan/test/programs/string-of-bytes.flan
Joseph Ferano 26c53e0a19 Every defn in the tree states its return type, and Unit is written ()
The mechanical half, ahead of the parser change that needs it. tools/unit-return.py
fills the empty slot with () and rewrites Unit as () wherever a type is spelled --
(Fn [i32] Unit), (Map i32 Unit), a return type written out.

Deciding whether a defn already had a return type is the whole difficulty, and
the script does it the way parse.ml did: is_type_form is transcribed rather than
improved, because being identical to the parser it replaces is what makes the
sweep meaning-preserving. It is re-runnable, so the lanes that branched before
this can have the same pass at merge:

    python3 tools/unit-return.py .
    python3 tools/unit-return.py --in-strings test/test_flan.ml test/test_acceptance.ml \
        test/test_session.ml emacs/test-flan-dev.el emacs/test-flan-mode.el
    python3 tools/unit-return.py --raw-ml lib/prelude.ml
    python3 tools/unit-return.py --in-html web/index.html

-v logs every defn it saw and what it decided, which is how a sweep of 440 sites
gets reviewed at all. Embedded modes pool a file's type declarations across all
its fragments, because a snippet split across concatenation -- decls ^ "(defn f
[s [u8]] Cursor ...)" -- cannot see the names the other half declared; pooled
names count only in bare-symbol position, for the same reason the prelude's do.
A fragment that cuts off mid-form is skipped rather than guessed at. Five sites
in test_flan.ml still needed a hand, and they are in this commit.

Two things ride along because the sweep needs them: parse.ml reads a lone () as
the return type of a function with no body, which was not a shape the old
optional slot could produce; and the map refusals name () rather than Unit, since
that is now the spelling a caller wrote.
2026-09-12 23:06:40 +07:00

64 lines
2.6 KiB
Plaintext

;;;; (string b) — a [u8] seen as a string.
;;;;
;;;; The conversion emits no instructions: String and Slice _ are both %slice,
;;;; 16 bytes at align 8. So what is worth testing is not arithmetic, it is the
;;;; four places a zero-instruction reinterpretation could still be wrong about
;;;; the *length* or about who owns the bytes.
;;;;
;;;; Run at -O2 and at -O0. The pair matters here for the same reason it
;;;; matters for the literal-write sharp edge: a conversion that accidentally
;;;; produced undefined behaviour would be a SIGSEGV at one level and a silent
;;;; deletion at the other, and agreeing at one level alone proves nothing.
;; puts and not a Flan printer: the point of this declaration is the *shim*,
;; which takes ptr+len and NUL-terminates a copy. A shim that instead trusted
;; the bytes to already be terminated would print the rest of the buffer for
;; every sub-view below, and nothing inside Flan would notice.
;;
;; Its return is C's "some nonnegative value", not a number worth printing, so
;; it is only shown as a sign — and it is printed through Flan's own writer,
;; which shares stdout's buffer with puts, so the interleaving is stable.
(declare-c c-puts [s string] i32 "puts")
(defn shows [s string] ()
(print "[")
(print s)
(print "] ")
(print (len (bytes s)))
(println ""))
(defn main [] i32
;; A number. The gap this closes: i64->bytes answers a [u8], every text
;; parameter wants a string, and until now nothing joined them.
(shows (string (i64->bytes 42)))
(shows (string (i64->bytes -7)))
(shows (string (i64->bytes 0)))
;; An empty slice. Length 0, and no read of the pointer.
(shows (string (slice (bytes "abc") 1 1)))
;; A sub-view, whose length is not the underlying storage's. The bytes after
;; index 5 are still there and must not appear.
(let [s (bytes "hello world")]
(shows (string (slice s 0 5)))
(shows (string (slice s 6 11)))
(shows (string (slice s 11 11))))
;; Round trip: (bytes (string b)) is b, and both directions are the identity.
(let [b (i64->bytes 1234567)]
(print (len (bytes (string b))))
(println ""))
;; Across the declare-c boundary. The first is a sub-view — five bytes out of
;; eleven, the sixth of which is a space and not a NUL — so a shim that did
;; not copy would print "hello world" here.
(let [s (bytes "hello world")]
(print (if (>= (c-puts (string (slice s 0 5))) 0) "ok" "no"))
(println ""))
(print (if (>= (c-puts (string (i64->bytes 12345))) 0) "ok" "no"))
(println "")
;; And an empty one: the shim's copy of a zero-length slice is "".
(print (if (>= (c-puts (string (slice (bytes "abc") 1 1))) 0) "ok" "no"))
(println "")
0)