flan/examples/digits.flan
Joseph Ferano afec482722 Ten raylib examples, and what they could not say
The first ten of raylib's core list, ported. Seven new bindings and the
named colour palette; nothing else was added, because a binding called
by nothing is the same as not having bound it.

The gaps they found are the point. No number reaches draw-text: i64->bytes
answers [u8], draw-text wants a string, and nothing bridges — five of the
ten wanted TextFormat and got a glyph table instead. And an enum parameter
cannot be driven by a loop variable: the index is an i32, the parameter is
an enum, neither converts, and a second declare-c with an i32 face is
refused because one C function gets one binding. Two correct rules that
compose into a wall.

None of the gaps expected blocked anything: no generics, no allocator, no
Vec, no escaping closure, no block-scoped defer. These are input-and-draw
programs over fixed-size state, which is the shape the language has.
2026-09-12 05:06:01 +07:00

128 lines
5.5 KiB
Plaintext

;;;; Drawing a number, because the language cannot make one into a string.
;;;;
;;;; Five of the ten ported examples call raylib's `TextFormat` to put a number
;;;; on the screen. Flan has no string formatting and no way to reach it:
;;;;
;;;; - `i64->bytes` is a builtin and answers a `[u8]`;
;;;; - `draw-text` takes a `string`;
;;;; - nothing converts a `[u8]` into a `string`. A string is a compile-time
;;;; literal or a parameter, and there is no allocator to build one in.
;;;;
;;;; `TextFormat` itself is not bindable either, and not because of the FFI
;;;; rules: it is variadic, so its signature is not a signature — declaring it
;;;; with fixed arguments would be a claim about the ABI that is false on every
;;;; target at once, and it returns a `char *` into a rotating static buffer,
;;;; which `declare-c` refuses by name anyway ("a string only crosses as a
;;;; parameter — a C function that *returns* one returns something Flan has no
;;;; owner for").
;;;;
;;;; So a number reaches the screen one digit at a time, each digit drawn as a
;;;; one-character `string` out of the table below. sand.flan already does this
;;;; for a single digit with `draw-text-codepoint`; this is the same trick
;;;; generalised, in one place, so the gap shows up in the report as one gap
;;;; rather than as five separate improvisations.
;;;;
;;;; Everything here needs a window: `measure-text` answers 0 for every string
;;;; until init-window has loaded the default font, and a zero advance would
;;;; stack every digit on top of the first.
(import rl "vendor:raylib")
;; A `[10 string]` — a fixed array whose element type is `string`. That works,
;; which is worth recording: a string is ptr+len and the array is ten of those
;; laid out flat, with the bytes themselves in the module's constant data.
(defconst digit-glyphs [10 string]
["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"])
(defconst minus-glyph "-")
(defconst dot-glyph ".")
;; One glyph, and how far the pen moved. raylib's default font is not
;; monospaced — "1" is narrower than "8" — so the advance is measured rather
;; than assumed, which is also what keeps the spacing identical to what
;; draw-text would have produced for the whole string at once.
(defn draw-glyph [g string x i32 y i32 size i32 color rl/Color] i32
(rl/draw-text g x y size color)
(rl/measure-text g size))
;; How many decimal digits `n` has, for n >= 0. 0 has one.
(defn digit-count [n i32] i32
(let [d 1
r (/ n 10)]
(while (> r 0)
(set d (+ d 1))
(set r (/ r 10)))
d))
(defn pow10 [e i32] i32
(let [p 1]
(dotimes [i e]
(set p (* p 10)))
p))
;; The whole point of the file. Answers the width drawn, so a caller can put
;; something after it — which is how the `TextFormat("%s: %i", ...)` shapes in
;; the C are reassembled here: draw the literal part with draw-text, then this
;; at x plus its width.
;;
;; Negative numbers get the sign and then the magnitude. i32's most negative
;; value is NOT handled: negating it wraps to itself, so it would print its own
;; bit pattern with a minus in front. Nothing here ever reaches it — these are
;; screen coordinates, frame counts and axis readings — and guarding it would
;; be a branch that no call site can take.
(defn draw-int [n i32 x i32 y i32 size i32 color rl/Color] i32
(let [cx x
v n]
(when (< v 0)
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
(set v (- 0 v)))
(let [count (digit-count v)]
(dotimes [i count]
(let [d (% (/ v (pow10 (- (- count 1) i))) 10)]
(set cx (+ cx (draw-glyph (at digit-glyphs d) cx y size color))))))
(- cx x)))
;; The same with a fixed number of leading zeroes — the C's "%03i". A number
;; wider than `width` is drawn in full rather than truncated, which is what
;; printf does too.
(defn draw-int-padded [n i32 width i32 x i32 y i32 size i32 color rl/Color]
i32
(let [cx x
v n]
(when (< v 0)
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
(set v (- 0 v)))
(let [count (max width (digit-count v))]
(dotimes [i count]
(let [d (% (/ v (pow10 (- (- count 1) i))) 10)]
(set cx (+ cx (draw-glyph (at digit-glyphs d) cx y size color))))))
(- cx x)))
;; "%.02f" and friends. `places` digits after the point, rounded by adding a
;; half at that scale before the split — so 0.999 at two places is "1.00" and
;; not "0.99", which is what the C's printf would have done and what a reader
;; comparing the two screens would expect.
;;
;; f32 and not f64 deliberately: every number this draws comes out of raylib,
;; and raylib's are floats. Widening them here would suggest a precision the
;; value does not have.
(defn draw-f32 [v f32 places i32 x i32 y i32 size i32 color rl/Color] i32
(let [cx x
av v]
(when (< av 0.0)
(set cx (+ cx (draw-glyph minus-glyph cx y size color)))
(set av (- 0.0 av)))
(let [scale (pow10 places)
;; The rounding and the split happen in one integer so the two halves
;; cannot disagree: rounding them separately is how "0.999" becomes
;; "0.100" — the fraction carries and the whole part does not hear
;; about it.
total (i32 (+ (* av (f32 scale)) 0.5))
whole (/ total scale)
frac (% total scale)]
(set cx (+ cx (draw-int whole cx y size color)))
(when (> places 0)
(set cx (+ cx (draw-glyph dot-glyph cx y size color)))
(set cx (+ cx (draw-int-padded frac places cx y size color)))))
(- cx x)))