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.
175 lines
9.3 KiB
Plaintext
175 lines
9.3 KiB
Plaintext
(import rl "vendor:raylib")
|
|
|
|
;; raylib's Font family, headless.
|
|
;;
|
|
;; A previous lane refused this whole family by name and gave a real reason: a
|
|
;; Font is three ints beside a Texture2D, a Rectangle* and a GlyphInfo*, a
|
|
;; GlyphInfo embeds an Image, and that was two more aggregates and two owned
|
|
;; arrays "for something with no headless test at the end of it". The second
|
|
;; half of that turned out to be wrong, and this file is the counter-example.
|
|
;;
|
|
;; **Nothing here makes a font.** That is the whole trick. Every raylib call
|
|
;; that produces one needs something a headless run has not got —
|
|
;; get-font-default needs init-window, load-font and load-font-ex need a TTF
|
|
;; on disk and a GL context to upload the atlas to — so this file *builds* a
|
|
;; Font out of Flan arrays, field by field, and hands it to raylib to compute
|
|
;; with. raylib's text measuring is pure CPU arithmetic: it walks the glyph
|
|
;; array for a codepoint, takes the advance out of the glyph or the width out
|
|
;; of the atlas rectangle, and scales by the base size.
|
|
;;
|
|
;; Which makes this the scalars-in/fields-out shape rather than the
|
|
;; store-and-return one. There is no raylib-produced struct anywhere in the
|
|
;; file for a permutation to cancel against: Flan chose every field, and what
|
|
;; comes back is a number raylib worked out from them.
|
|
;;
|
|
;; What is pinned, and by what:
|
|
;;
|
|
;; - **base-size** by the scale factor. Measuring "AB" at size 10 against a
|
|
;; base size of 10 is scale 1 and at size 20 is scale 2, and both are
|
|
;; asserted, so the field cannot be a constant that happens to fit.
|
|
;; - **glyph-count** by the search bound. 'Z' is not in the font, and
|
|
;; raylib's miss falls back to index 0 rather than reading past the array.
|
|
;; - **recs and glyphs** as pointers, by being read through at all.
|
|
;; - **advance-x against offset-x**, because glyph C deliberately has an
|
|
;; advance of 0: raylib then falls back to the atlas rectangle's width
|
|
;; plus offset-x, so measuring "ABC" reaches three more fields than
|
|
;; measuring "AB" does.
|
|
;; - **where the Texture2D sits inside the Font**, by a trap in raylib
|
|
;; rather than a choice here: MeasureTextEx returns (0,0) at once when
|
|
;; `texture.id` is 0. The hand-built font claims an id of 1 — there is no
|
|
;; texture — and if the texture landed anywhere else in the struct the id
|
|
;; would read 0 and every measurement below would collapse to zero.
|
|
;;
|
|
;; And what is NOT pinned, which matters as much: `glyph-padding` is read by
|
|
;; nothing raylib computes on the CPU, `offset-y` only moves a glyph when one
|
|
;; is actually drawn, and of each atlas rectangle only `width` is ever looked
|
|
;; at — `x`, `y` and `height` come back from get-glyph-atlas-rec exactly as
|
|
;; they were stored, which is the symmetric trap and proves nothing. Those
|
|
;; four rest on raylib's header and on sand.flan looking right. This is the
|
|
;; same limit raylib-ffi.flan records for Texture2D's width, height and
|
|
;; mipmaps, and it is written down for the same reason.
|
|
;;
|
|
;; The numbers are chosen so that no two are equal: base size 10, glyph count
|
|
;; 3, padding 2, advances 11 and 13, offsets 1, 2 and 3, atlas widths 5, 7
|
|
;; and 9. Everything prints exactly — no trigonometry, so unlike the rotated
|
|
;; camera in raylib-ffi.flan this can be compared as text.
|
|
|
|
;; The atlas rectangles, one per glyph. x and width differ per glyph so that
|
|
;; a fallback to the wrong index is a wrong number; height is the same 10 for
|
|
;; all three because raylib never reads it here.
|
|
(defvar glyph-recs [3 rl/Rectangle])
|
|
|
|
;; The glyphs themselves. `image` is raylib's own pixels for the glyph and is
|
|
;; left zeroed — it is present so the four ints in front of it are at the
|
|
;; right offsets and so a GlyphInfo is 40 bytes rather than 16. That claim is
|
|
;; checked: moving `image` to the front of the defstruct shifts the four ints
|
|
;; by 24 bytes, and the glyph search then finds nothing — every index reads 0
|
|
;; and glyph C answers with A's numbers.
|
|
(defvar glyphs [3 rl/GlyphInfo])
|
|
|
|
(defn build-glyphs [] ()
|
|
(set (at glyph-recs 0) (rl/Rectangle {.x 0.0 .y 0.0 .width 5.0 .height 10.0}))
|
|
(set (at glyph-recs 1) (rl/Rectangle {.x 5.0 .y 0.0 .width 7.0 .height 10.0}))
|
|
(set (at glyph-recs 2) (rl/Rectangle {.x 12.0 .y 0.0 .width 9.0 .height 10.0}))
|
|
;; 65 66 67 are A B C. Advances 11 and 13 for the first two; C's advance is
|
|
;; 0 on purpose, which is what sends raylib down the other branch.
|
|
(set (at glyphs 0) (rl/GlyphInfo {.value 65 .offset-x 1 .offset-y 0
|
|
.advance-x 11 .image (rl/Image {})}))
|
|
(set (at glyphs 1) (rl/GlyphInfo {.value 66 .offset-x 2 .offset-y 0
|
|
.advance-x 13 .image (rl/Image {})}))
|
|
(set (at glyphs 2) (rl/GlyphInfo {.value 67 .offset-x 3 .offset-y 0
|
|
.advance-x 0 .image (rl/Image {})})))
|
|
|
|
(defn show-bool [name string b bool] ()
|
|
(print name) (print " ")
|
|
(println (if b "yes" "no")))
|
|
|
|
(defn show-i [name string v i32] ()
|
|
(print name) (print " ") (print v) (println ""))
|
|
|
|
(defn show-v [name string v rl/Vector2] ()
|
|
(print name)
|
|
(print " ") (print (.x v))
|
|
(print " ") (print (.y v))
|
|
(println ""))
|
|
|
|
(defn show-rect [name string r rl/Rectangle] ()
|
|
(print name)
|
|
(print " ") (print (.x r))
|
|
(print " ") (print (.y r))
|
|
(print " ") (print (.width r))
|
|
(print " ") (print (.height r))
|
|
(println ""))
|
|
|
|
(defn main [] i32
|
|
(rl/set-trace-log-level :warning)
|
|
(build-glyphs)
|
|
|
|
;; The font. `texture` is a lie in every field but `id`, and the id is 1 for
|
|
;; the reason in the header note: at 0, raylib refuses to measure anything.
|
|
;;
|
|
;; It is built here rather than by a helper because a `defn` cannot say it
|
|
;; returns one: the parser decides whether the form after a parameter list
|
|
;; is a return type or the first body expression by looking the name up in
|
|
;; the set of types THIS FILE declares, and a package's types are not in it
|
|
;; — imports are resolved after parsing. `(defn the-font [] rl/Font ...)`
|
|
;; therefore parses rl/Font as an expression and fails with "unknown name".
|
|
;; That is a parser limit and not something this file wanted; it applies to
|
|
;; rl/Vector2 just as much as to rl/Font.
|
|
(let [f (rl/Font {.base-size 10 .glyph-count 3 .glyph-padding 2
|
|
.texture (rl/Texture2D {.id 1 .width 32 .height 16
|
|
.mipmaps 1 .format 7})
|
|
.recs (addr (at glyph-recs 0))
|
|
.glyphs (addr (at glyphs 0))})]
|
|
;; font-valid? reads the texture id and both array pointers, so it is a
|
|
;; null check on the three things everything below dereferences.
|
|
(show-bool "valid" (rl/font-valid? f))
|
|
|
|
;; ── The search ──────────────────────────────────────────────────────
|
|
;;
|
|
;; A linear walk of `glyph-count` entries comparing `value`. Three hits at
|
|
;; three different indices, and one miss: 'Z' is not in the font and
|
|
;; raylib answers index 0 rather than running off the end. Shrink
|
|
;; glyph-count — or land the field somewhere else — and 'C' misses too.
|
|
(show-i "index A" (rl/get-glyph-index f 65))
|
|
(show-i "index B" (rl/get-glyph-index f 66))
|
|
(show-i "index C" (rl/get-glyph-index f 67))
|
|
(show-i "index Z" (rl/get-glyph-index f 90))
|
|
|
|
;; ── Reading back through the two pointers ───────────────────────────
|
|
;;
|
|
;; The atlas rectangle for B is the second entry of `recs`, and the glyph
|
|
;; for C is the third of `glyphs`. Both are indexed by the search above,
|
|
;; so an exchange of the two pointers is a Rectangle read as a GlyphInfo.
|
|
(show-rect "atlas B" (rl/get-glyph-atlas-rec f 66))
|
|
(let [g (rl/get-glyph-info f 67)]
|
|
(show-i "glyph C value" (.value g))
|
|
(show-i "glyph C offset" (.offset-x g))
|
|
(show-i "glyph C advance" (.advance-x g)))
|
|
|
|
;; ── Measuring, which is where the arithmetic is ─────────────────────
|
|
;;
|
|
;; "AB" is 11 + 13 = 24 wide and base-size tall, at scale 1.
|
|
(show-v "measure AB" (rl/measure-text-ex f "AB" 10.0 0.0))
|
|
|
|
;; "ABC" adds C, whose advance is 0, so raylib falls back to the atlas
|
|
;; width plus the offset: 9 + 3 = 12, for 36. This line and the one above
|
|
;; disagree by exactly the amount that proves the fallback branch ran, and
|
|
;; that branch is the only thing in the file that reads Rectangle.width
|
|
;; out of the recs array — exchange x and width in the Rectangle defstruct
|
|
;; and this line reads 39 while the one above stays 24.
|
|
(show-v "measure ABC" (rl/measure-text-ex f "ABC" 10.0 0.0))
|
|
|
|
;; The same string at twice the size and a spacing of 3. Scale is 20/10,
|
|
;; so 24 doubles to 48, and spacing is added once per gap and not once per
|
|
;; glyph: 48 + 3 = 51. Exchange base-size and glyph-count and the scale
|
|
;; becomes 20/3 and the search bound becomes 10 — both wrong, in different
|
|
;; directions, on the same line.
|
|
(show-v "measure AB big" (rl/measure-text-ex f "AB" 20.0 3.0))
|
|
|
|
;; One glyph, so there is no gap for the spacing to land in: 11 at scale
|
|
;; 1, and the 3 does not appear. Without this line a spacing added per
|
|
;; glyph rather than per gap would pass everything above.
|
|
(show-v "measure A spaced" (rl/measure-text-ex f "A" 10.0 3.0)))
|
|
0)
|