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.
print-str, print-i64, print-f64, print-bytes, print-line and newline leave
the prelude. print and println are the whole printing surface now, and print
is the better call at every one of the sites that used them: it is the same
structural walk without the newline, so the no-newline case the family was
kept for is covered, and it takes the value as it is. The old print-i64
forced an explicit (i64 x) at every call site, because this language widens
nothing implicitly; that cast is gone from 127 places.
Dropping it moves one answer. hash-grid returns u64, and the cast through
the signed printer showed sand-headless's hash as -2851001042534928384.
print routes a u64 through flan_u64_to_bytes, so it now prints
15595743031174623232 — the same 64 bits, read as the unsigned number they
are. The pinned expectation follows the correction.
test-flan-dev.el and test_session.ml both reached for print-line as "a name
the prelude has"; they reach for rand-seed instead.
(string b) is the mirror of (bytes s) and costs nothing: emit.ml already
lowers Types.String and Types.Slice _ to the same %slice, 16 bytes at
align 8, so a string and a [u8] are the identical value at run time and
both directions emit as the argument itself. What changes is only what
the checker will let the value be passed to — which was the whole gap.
Two decisions, both written into check.ml's comment.
It does not check UTF-8, because `string` does not claim UTF-8. The
prelude settles it: valid-utf8? is an ordinary function you call when you
care, decode-rune / rune-at / rune-count all take [u8] and not string,
and decode-rune answers {:ok false :width 1} on a malformed byte rather
than assuming well-formed input. The one place the runtime treats a
string differently from a byte slice is flan_escape_bytes, for a string
nested in a printed structure, and that is a byte-wise escape table with
no decoding in it. A check here would be the only enforcement point in
the language, which is a claim the rest of it does not make.
It does not widen the literal-write hole. That hole is the other
direction — (bytes "Hi") hands back a writable-looking slice over
constant data — and this direction only loses the ability to write, so
the result reaches strictly fewer stores than its argument could.
Provenance is still what the other direction needs; nothing here waits
on it.
The one sharp edge is not new but is easier to trip over now, and is
recorded in both the checker and digits.flan: i64->bytes, f64->bytes and
u64->bytes all view the same static buffer in the runtime, overwritten
by the next call, and calling it a string does not copy it. Format, draw,
then format the next one.
examples/digits.flan keeps its three signatures and loses its middle: the
[10 string] table, the per-glyph pen and the digit arithmetic are gone,
and draw-int is one draw-text. What survives is the part (string ...)
does not answer — i64->bytes has no field width, so "%03i" is still
assembled, and f64->bytes is "%g", so fixed decimal places are still a
split into two integers. core-input-multitouch and
core-input-virtual-controls ignored the width they were given, so both
inline the draw and stop importing digits.flan entirely.
test/programs/string-of-bytes.flan at -O2 and -O0: a number round-tripped,
an empty slice, sub-views whose length is not the underlying storage's,
and the result across a declare-c boundary. The last is the one that
could have been wrong — "hello world" cut to five bytes has a space where
C wants a NUL, so a shim that trusted the bytes would print all eleven.