Merge branch 'worktree-agent-a604237a7cbd45379' into dev-loop

# Conflicts:
#	FIX.org
#	lib/check.ml
This commit is contained in:
Joseph Ferano 2026-09-20 22:02:57 +07:00
commit df89869962
30 changed files with 322 additions and 111 deletions

48
FIX.org
View File

@ -3977,3 +3977,51 @@ A *compound constant expression* at a bounded ~$t~ — ~(+ x (+ 1 2))~ where
at a type variable, and nothing folds the compound to a bare one before the at a type variable, and nothing folds the compound to a bare one before the
ask. Walk-backable (admitting more programs later invalidates nothing ask. Walk-backable (admitting more programs later invalidates nothing
written now), so it waits until a body actually wants it. written now), so it waits until a body actually wants it.
* Enum keyword prefixes, 2026-09-20
** The decision, author's words
raylib's enum keywords carry a disambiguating prefix, because bare members
collide across enums and with user code. Key members are ~:key-r~,
~:key-space~, ~:key-left-shift~; MouseButton members are ~:mouse-left~,
~:mouse-right~ and so on. ~mouse-~ over ~button-~ because gamepads have
buttons too. Only Key and MouseButton are decided; the rest of the survey is
below, awaiting a ruling per enum.
** The bindings directive extension
The `enum` line in vendor/raylib/bindings grew an optional third column: the
prefix the members carry on the Flan side, stripped before the C prefix is
applied. `enum Key KEY_ key-` checks ~key-r~ against KEY_R rather than
KEY_KEY_R; `enum MouseButton MOUSE_BUTTON_ mouse-` reaches MOUSE_BUTTON_LEFT
from ~mouse-left~. A member that does not carry the declared prefix is
reported, not checked under a guessed name — ~null~ beside a declared ~key-~
would otherwise build KEY_NULL, which the header happens to have, and the
naming rule would erode silently. A name the rule builds that the header
lacks is still reported, never skipped. `flan generate-c vendor/raylib` runs
green against raylib-5.5.h with both lines in place.
The checker also grew a did-you-mean for enum members: one edit away, and the
bare name of a prefixed member — ~:r~ suggests ~:key-r~, ~:left~ suggests
~:mouse-left~ at a MouseButton site.
** Open, author's call — the survey of the other nine enums
None are renamed; these are the collision-prone bare members found:
- TraceLogLevel: nearly all generic — ~all~, ~trace~, ~debug~, ~info~,
~warning~, ~error~, ~fatal~, ~none~. ~none~ also collides with Gesture's.
- Gesture: ~none~ (collides with TraceLogLevel's), ~tap~, ~hold~, ~drag~.
- CameraMode: ~custom~, ~free~ (also the name of the language's free).
- MouseCursor: ~default~, ~arrow~, ~crosshair~.
- TextureFilter: ~point~.
- GamepadButton: ~unknown~, ~middle~ (plus ~middle-left~/~middle-right~).
- GamepadAxis: ~left-x~/~left-y~/~right-x~/~right-y~ read gamepad-ish
already, but ~left-trigger~/~right-trigger~ sit one hyphen from
GamepadButton's ~left-trigger-1~/~2~ — a prefix ruling should take the two
enums together.
- CameraProjection (~perspective~, ~orthographic~) and PixelFormat
(~uncompressed-*~, ~compressed-*~) are effectively self-naming; low risk.
** Open, author's call — sand.flan
sand.flan calls ~(rl/key-pressed? :r)~ and ~(rl/mouse-button-down? :left)~
(lines 161166), and the default suite compiles it (test_session, and
test/programs/sand-headless.flan imports it). The file is the author's live
WIP and was not touched, so those two tests are red on this branch until the
three keywords there become ~:key-r~ / ~:mouse-left~.

View File

@ -956,8 +956,8 @@ header of `vendor/raylib/raylib.flan`:
queue in two places, once to prime the loop and once at the bottom of the body, and the Option shape reads it in queue in two places, once to prime the loop and once at the bottom of the body, and the Option shape reads it in
one. one.
- **An enum where the header says `int`.** `key-up?`, `key-pressed-repeat?`, `mouse-button-up?` — holes in families - **An enum where the header says `int`.** `key-up?`, `key-pressed-repeat?`, `mouse-button-up?` — holes in families
whose other halves already took a `Key` or a `MouseButton`, so `(rl/key-down? :space)` compiled and whose other halves already took a `Key` or a `MouseButton`, so `(rl/key-down? :key-space)` compiled and
`(rl/key-up? :space)` did not. **These are not wrappers.** A C enum parameter has an int's ABI, so the hand-written `(rl/key-up? :key-space)` did not. **These are not wrappers.** A C enum parameter has an int's ABI, so the hand-written
`declare-c` with the Flan type on it is the whole fix and a `defn` around it would be a rename. `declare-c` with the Flan type on it is the whole fix and a `defn` around it would be a rename.
The mechanism for the first two is the `name` directive in `vendor/raylib/bindings`: the generated declaration keeps The mechanism for the first two is the `name` directive in `vendor/raylib/bindings`: the generated declaration keeps
@ -1241,7 +1241,7 @@ second.
One smaller thing found and worth not re-deriving: an enum parameter imports as `i32`, because the header says One smaller thing found and worth not re-deriving: an enum parameter imports as `i32`, because the header says
`KeyboardKey` and nothing tells the importer the package calls that `Key`. The ABI is identical, the face is worse, `KeyboardKey` and nothing tells the importer the package calls that `Key`. The ABI is identical, the face is worse,
and it is why `(rl/key-down? :space)` keeps its hand-written line. The idiomatic-layer lane closed the three holes and it is why `(rl/key-down? :key-space)` keeps its hand-written line. The idiomatic-layer lane closed the three holes
this left — `key-up?`, `key-pressed-repeat?` and `mouse-button-up?` were generated and therefore took an `i32`, so the this left — `key-up?`, `key-pressed-repeat?` and `mouse-button-up?` were generated and therefore took an `i32`, so the
sibling of a call that worked did not — by excluding them and hand-writing the enum type, which is all it takes. sibling of a call that worked did not — by excluding them and hand-writing the enum type, which is all it takes.

View File

@ -222,7 +222,7 @@ hand-written lines are untouched.
Worth noting what migration would actually lose, since it is small but real: the hand-written names are Worth noting what migration would actually lose, since it is small but real: the hand-written names are
better than the rule's. `IsKeyPressed` is `key-pressed?` by hand and `is-key-pressed` by rule; better than the rule's. `IsKeyPressed` is `key-pressed?` by hand and `is-key-pressed` by rule;
`CheckCollisionRecs` is `collision-recs?`. And an enum parameter imports as `i32`, because the header says `CheckCollisionRecs` is `collision-recs?`. And an enum parameter imports as `i32`, because the header says
`KeyboardKey` and nothing tells the importer the package calls that `Key` — so `(rl/key-down? :space)` `KeyboardKey` and nothing tells the importer the package calls that `Key` — so `(rl/key-down? :key-space)`
would become an integer at the call site. A migration is therefore not a deletion; it is a deletion plus a would become an integer at the call site. A migration is therefore not a deletion; it is a deletion plus a
kept list of the lines whose face is deliberately nicer than the header's. kept list of the lines whose face is deliberately nicer than the header's.
@ -238,7 +238,7 @@ What actually decides it is that migration would gut the check. Everything the g
by construction, so diffing generated output against its own source proves nothing; the hand-written lines have a by construction, so diffing generated output against its own source proves nothing; the hand-written lines have a
different author, so they are the only declarations a header can contradict — and all ten of the 5.1-dev differences different author, so they are the only declarations a header can contradict — and all ten of the 5.1-dev differences
came from them. Delete them and the signature half of the check silently becomes a tautology. The enum point above came from them. Delete them and the signature half of the check silently becomes a tautology. The enum point above
survives intact as a second reason: `(rl/key-down? :space)` keeps its `Key` parameter only because that line is survives intact as a second reason: `(rl/key-down? :key-space)` keeps its `Key` parameter only because that line is
hand-written. hand-written.
## 7. Watching variables ## 7. Watching variables

View File

@ -230,7 +230,7 @@
("(rl/Vector2 {.x 1.0 .y 2.0})" ".y" "and the second one") ("(rl/Vector2 {.x 1.0 .y 2.0})" ".y" "and the second one")
("(set total (.bytes c))" ".bytes" "a field accessor") ("(set total (.bytes c))" ".bytes" "a field accessor")
;; The keyword rule is still there: an enum member is one. ;; The keyword rule is still there: an enum member is one.
("(rl/mouse-button-down? :left)" ":left" "an enum member"))) ("(rl/mouse-button-down? :mouse-left)" ":mouse-left" "an enum member")))
(test-flan--check (format "%s is a constant" (nth 2 case)) (test-flan--check (format "%s is a constant" (nth 2 case))
(eq (test-flan-mode--face-at (nth 0 case) (nth 1 case)) (eq (test-flan-mode--face-at (nth 0 case) (nth 1 case))
'font-lock-constant-face))) 'font-lock-constant-face)))

View File

@ -77,24 +77,24 @@
;; Update ;; Update
;; The C's `if (RIGHT) ... else if (LEFT) ...`: both held cancels to the ;; The C's `if (RIGHT) ... else if (LEFT) ...`: both held cancels to the
;; right, which is the else-if and not an accident. ;; right, which is the else-if and not an accident.
(if (rl/key-down? :right) (if (rl/key-down? :key-right)
(set (.x player) (+ (.x player) 2.0)) (set (.x player) (+ (.x player) 2.0))
(when (rl/key-down? :left) (set (.x player) (- (.x player) 2.0)))) (when (rl/key-down? :key-left) (set (.x player) (- (.x player) 2.0))))
;; The camera follows the player's centre. ;; The camera follows the player's centre.
(set (.target camera) (rl/Vector2 {.x (+ (.x player) 20.0) (set (.target camera) (rl/Vector2 {.x (+ (.x player) 20.0)
.y (+ (.y player) 20.0)})) .y (+ (.y player) 20.0)}))
(if (rl/key-down? :a) (if (rl/key-down? :key-a)
(set (.rotation camera) (- (.rotation camera) 1.0)) (set (.rotation camera) (- (.rotation camera) 1.0))
(when (rl/key-down? :s) (when (rl/key-down? :key-s)
(set (.rotation camera) (+ (.rotation camera) 1.0)))) (set (.rotation camera) (+ (.rotation camera) 1.0))))
(set (.rotation camera) (clamp (.rotation camera) -40.0 40.0)) (set (.rotation camera) (clamp (.rotation camera) -40.0 40.0))
(set (.zoom camera) (+ (.zoom camera) (* (rl/get-mouse-wheel-move) 0.05))) (set (.zoom camera) (+ (.zoom camera) (* (rl/get-mouse-wheel-move) 0.05)))
(set (.zoom camera) (clamp (.zoom camera) 0.1 3.0)) (set (.zoom camera) (clamp (.zoom camera) 0.1 3.0))
(when (rl/key-pressed? :r) (when (rl/key-pressed? :key-r)
(set (.zoom camera) 1.0) (set (.zoom camera) 1.0)
(set (.rotation camera) 0.0)) (set (.rotation camera) 0.0))

View File

@ -97,10 +97,10 @@
;; could not be used to aim at anything. ;; could not be used to aim at anything.
(when (rl/cursor-hidden?) (rl/update-camera (addr camera) :first-person)) (when (rl/cursor-hidden?) (rl/update-camera (addr camera) :first-person))
(when (rl/mouse-button-pressed? :right) (when (rl/mouse-button-pressed? :mouse-right)
(if (rl/cursor-hidden?) (rl/enable-cursor) (rl/disable-cursor))) (if (rl/cursor-hidden?) (rl/enable-cursor) (rl/disable-cursor)))
(when (rl/mouse-button-pressed? :left) (when (rl/mouse-button-pressed? :mouse-left)
(if (not (.hit collision)) (if (not (.hit collision))
;; The pixel under the pointer, as a line through the scene, tested ;; The pixel under the pointer, as a line through the scene, tested
;; against the cube's bounds. ;; against the cube's bounds.

View File

@ -65,7 +65,7 @@
(when (> (.x delta-circle) (f32 screen-width)) (set (.x delta-circle) 0.0)) (when (> (.x delta-circle) (f32 screen-width)) (set (.x delta-circle) 0.0))
(when (> (.x frame-circle) (f32 screen-width)) (set (.x frame-circle) 0.0)) (when (> (.x frame-circle) (f32 screen-width)) (set (.x frame-circle) 0.0))
(when (rl/key-pressed? :r) (when (rl/key-pressed? :key-r)
(set (.x delta-circle) 0.0) (set (.x delta-circle) 0.0)
(set (.x frame-circle) 0.0)) (set (.x frame-circle) 0.0))

View File

@ -173,9 +173,9 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update ;; Update
(when (and (rl/key-pressed? :left) (> gamepad 0)) (when (and (rl/key-pressed? :key-left) (> gamepad 0))
(set gamepad (- gamepad 1))) (set gamepad (- gamepad 1)))
(when (rl/key-pressed? :right) (set gamepad (+ gamepad 1))) (when (rl/key-pressed? :key-right) (set gamepad (+ gamepad 1)))
(let [axis-count (min 6 (rl/get-gamepad-axis-count gamepad)) (let [axis-count (min 6 (rl/get-gamepad-axis-count gamepad))
vibrate-rect (rl/Rectangle {.x 10.0 vibrate-rect (rl/Rectangle {.x 10.0

View File

@ -299,7 +299,7 @@
(set last-gesture g)) (set last-gesture g))
;; The two mode buttons toggle one bit each of log-mode. ;; The two mode buttons toggle one bit each of log-mode.
(when (rl/mouse-button-released? :left) (when (rl/mouse-button-released? :mouse-left)
(let [m (rl/get-mouse-position)] (let [m (rl/get-mouse-position)]
(when (rl/collision-point-rec? m b1) (when (rl/collision-point-rec? m b1)
(set log-mode (cond (= log-mode 3) 2 (set log-mode (cond (= log-mode 3) 2

View File

@ -34,10 +34,10 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update. key-down? and not key-pressed?: this is meant to repeat for as ;; Update. key-down? and not key-pressed?: this is meant to repeat for as
;; long as the key is held, which is the whole difference between the two. ;; long as the key is held, which is the whole difference between the two.
(when (rl/key-down? :right) (set (.x ball) (+ (.x ball) 2.0))) (when (rl/key-down? :key-right) (set (.x ball) (+ (.x ball) 2.0)))
(when (rl/key-down? :left) (set (.x ball) (- (.x ball) 2.0))) (when (rl/key-down? :key-left) (set (.x ball) (- (.x ball) 2.0)))
(when (rl/key-down? :up) (set (.y ball) (- (.y ball) 2.0))) (when (rl/key-down? :key-up) (set (.y ball) (- (.y ball) 2.0)))
(when (rl/key-down? :down) (set (.y ball) (+ (.y ball) 2.0))) (when (rl/key-down? :key-down) (set (.y ball) (+ (.y ball) 2.0)))
;; Draw ;; Draw
(rl/with-drawing (rl/with-drawing

View File

@ -33,19 +33,19 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update ;; Update
(when (rl/key-pressed? :h) (when (rl/key-pressed? :key-h)
(if (rl/cursor-hidden?) (rl/show-cursor) (rl/hide-cursor))) (if (rl/cursor-hidden?) (rl/show-cursor) (rl/hide-cursor)))
(let [ball (rl/get-mouse-position)] (let [ball (rl/get-mouse-position)]
(set ball-color (set ball-color
(cond (cond
(rl/mouse-button-pressed? :left) rl/maroon (rl/mouse-button-pressed? :mouse-left) rl/maroon
(rl/mouse-button-pressed? :middle) rl/lime (rl/mouse-button-pressed? :mouse-middle) rl/lime
(rl/mouse-button-pressed? :right) rl/darkblue (rl/mouse-button-pressed? :mouse-right) rl/darkblue
(rl/mouse-button-pressed? :side) rl/purple (rl/mouse-button-pressed? :mouse-side) rl/purple
(rl/mouse-button-pressed? :extra) rl/yellow (rl/mouse-button-pressed? :mouse-extra) rl/yellow
(rl/mouse-button-pressed? :forward) rl/orange (rl/mouse-button-pressed? :mouse-forward) rl/orange
(rl/mouse-button-pressed? :back) rl/beige (rl/mouse-button-pressed? :mouse-back) rl/beige
:else ball-color)) :else ball-color))
;; Draw ;; Draw

View File

@ -165,7 +165,7 @@
input (if touching input (if touching
(rl/get-touch-position 0) (rl/get-touch-position 0)
(rl/get-mouse-position)) (rl/get-mouse-position))
pressed (if (or touching (rl/mouse-button-down? :left)) pressed (if (or touching (rl/mouse-button-down? :mouse-left))
(nearest-button input) (nearest-button input)
button-none)] button-none)]

View File

@ -44,7 +44,7 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update ;; Update
(when (rl/key-pressed? :s) (set scissor-mode (not scissor-mode))) (when (rl/key-pressed? :key-s) (set scissor-mode (not scissor-mode)))
;; Centre the scissor area on the mouse. ;; Centre the scissor area on the mouse.
(set (.x scissor) (- (f32 (rl/get-mouse-x)) (/ (.width scissor) 2.0))) (set (.x scissor) (- (f32 (rl/get-mouse-x)) (/ (.width scissor) 2.0)))

View File

@ -79,18 +79,18 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update ;; Update
(when (rl/key-pressed? :f) (rl/toggle-fullscreen)) (when (rl/key-pressed? :key-f) (rl/toggle-fullscreen))
(when (rl/key-pressed? :r) (toggle-flag rl/flag-window-resizable)) (when (rl/key-pressed? :key-r) (toggle-flag rl/flag-window-resizable))
(when (rl/key-pressed? :d) (toggle-flag rl/flag-window-undecorated)) (when (rl/key-pressed? :key-d) (toggle-flag rl/flag-window-undecorated))
(when (rl/key-pressed? :u) (toggle-flag rl/flag-window-unfocused)) (when (rl/key-pressed? :key-u) (toggle-flag rl/flag-window-unfocused))
(when (rl/key-pressed? :t) (toggle-flag rl/flag-window-topmost)) (when (rl/key-pressed? :key-t) (toggle-flag rl/flag-window-topmost))
(when (rl/key-pressed? :a) (toggle-flag rl/flag-window-always-run)) (when (rl/key-pressed? :key-a) (toggle-flag rl/flag-window-always-run))
(when (rl/key-pressed? :v) (toggle-flag rl/flag-vsync-hint)) (when (rl/key-pressed? :key-v) (toggle-flag rl/flag-vsync-hint))
;; Hidden and minimized are not toggles: the window they hide is also the ;; Hidden and minimized are not toggles: the window they hide is also the
;; window the key would have to be pressed in, so each comes back on a ;; window the key would have to be pressed in, so each comes back on a
;; timer instead. ;; timer instead.
(when (rl/key-pressed? :h) (when (rl/key-pressed? :key-h)
(unless (rl/window-state? rl/flag-window-hidden) (unless (rl/window-state? rl/flag-window-hidden)
(rl/set-window-state rl/flag-window-hidden)) (rl/set-window-state rl/flag-window-hidden))
(set frames 0)) (set frames 0))
@ -100,7 +100,7 @@
(when (>= frames restore-after) (when (>= frames restore-after)
(rl/clear-window-state rl/flag-window-hidden))) (rl/clear-window-state rl/flag-window-hidden)))
(when (rl/key-pressed? :n) (when (rl/key-pressed? :key-n)
(unless (rl/window-state? rl/flag-window-minimized) (unless (rl/window-state? rl/flag-window-minimized)
(rl/minimize-window)) (rl/minimize-window))
(set frames 0)) (set frames 0))
@ -110,7 +110,7 @@
(when (>= frames restore-after) (rl/restore-window))) (when (>= frames restore-after) (rl/restore-window)))
;; Maximize needs FLAG_WINDOW_RESIZABLE first, which is what R is for. ;; Maximize needs FLAG_WINDOW_RESIZABLE first, which is what R is for.
(when (rl/key-pressed? :m) (when (rl/key-pressed? :key-m)
(if (rl/window-state? rl/flag-window-maximized) (if (rl/window-state? rl/flag-window-maximized)
(rl/restore-window) (rl/restore-window)
(rl/maximize-window))) (rl/maximize-window)))

View File

@ -5,7 +5,7 @@
;;;; KEY_NULL in raylib.h. The binding is hand-written rather than taken from ;;;; KEY_NULL in raylib.h. The binding is hand-written rather than taken from
;;;; the generated half because the Flan face differs — raylib declares ;;;; the generated half because the Flan face differs — raylib declares
;;;; `void SetExitKey(int key)` and the generated line therefore takes an i32, ;;;; `void SetExitKey(int key)` and the generated line therefore takes an i32,
;;;; where this one takes a Key, so `(rl/set-exit-key :null)` is checked ;;;; where this one takes a Key, so `(rl/set-exit-key :key-null)` is checked
;;;; against the enum and `:nul` is a compile error instead of a 0. ;;;; against the enum and `:nul` is a compile error instead of a 0.
;;;; ;;;;
;;;; The whole example is about what window-should-close? means. It is not a ;;;; The whole example is about what window-should-close? means. It is not a
@ -34,7 +34,7 @@
(defer (rl/close-window)) (defer (rl/close-window))
;; No key closes the window any more. ESC is an ordinary key from here on. ;; No key closes the window any more. ESC is an ordinary key from here on.
(rl/set-exit-key :null) (rl/set-exit-key :key-null)
(rl/set-target-fps 60) (rl/set-target-fps 60)
@ -42,16 +42,16 @@
;; Update. Either way of asking to leave raises the question; only Y and N ;; Update. Either way of asking to leave raises the question; only Y and N
;; answer it. window-should-close? is false again on the next frame, so ;; answer it. window-should-close? is false again on the next frame, so
;; the request has to be remembered in a variable. ;; the request has to be remembered in a variable.
(when (or (rl/window-should-close?) (rl/key-pressed? :escape)) (when (or (rl/window-should-close?) (rl/key-pressed? :key-escape))
(set exit-requested true)) (set exit-requested true))
;; The C's `if (Y) ... else if (N) ...`, which is an `if` with a `when` ;; The C's `if (Y) ... else if (N) ...`, which is an `if` with a `when`
;; in its else and not a `cond`: a `cond` needs an `:else` arm and there ;; in its else and not a `cond`: a `cond` needs an `:else` arm and there
;; is nothing to do when neither key was pressed. ;; is nothing to do when neither key was pressed.
(when exit-requested (when exit-requested
(if (rl/key-pressed? :y) (if (rl/key-pressed? :key-y)
(set exiting true) (set exiting true)
(when (rl/key-pressed? :n) (set exit-requested false)))) (when (rl/key-pressed? :key-n) (set exit-requested false))))
;; Draw ;; Draw
(rl/with-drawing (rl/with-drawing

View File

@ -93,10 +93,10 @@
;; means only one direction applies per frame — diagonal movement is not ;; means only one direction applies per frame — diagonal movement is not
;; possible, deliberately. ;; possible, deliberately.
(cond (cond
(rl/key-down? :right) (set (.x player-position) (+ (.x player-position) 0.2)) (rl/key-down? :key-right) (set (.x player-position) (+ (.x player-position) 0.2))
(rl/key-down? :left) (set (.x player-position) (- (.x player-position) 0.2)) (rl/key-down? :key-left) (set (.x player-position) (- (.x player-position) 0.2))
(rl/key-down? :down) (set (.z player-position) (+ (.z player-position) 0.2)) (rl/key-down? :key-down) (set (.z player-position) (+ (.z player-position) 0.2))
(rl/key-down? :up) (set (.z player-position) (- (.z player-position) 0.2))) (rl/key-down? :key-up) (set (.z player-position) (- (.z player-position) 0.2)))
(let [player-box (box-around player-position player-size) (let [player-box (box-around player-position player-size)
hit (or (rl/check-collision-boxes player-box hit (or (rl/check-collision-boxes player-box

View File

@ -90,7 +90,7 @@
;; rather than reading that back. This follows it. ;; rather than reading that back. This follows it.
(when collision (set box-collision (rl/get-collision-rec box-a box-b))) (when collision (set box-collision (rl/get-collision-rec box-a box-b)))
(when (rl/key-pressed? :space) (set paused (not paused))) (when (rl/key-pressed? :key-space) (set paused (not paused)))
;; Draw ;; Draw
(rl/with-drawing (rl/with-drawing

View File

@ -219,14 +219,14 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update ;; Update
(when (rl/key-pressed? :space) (set show-font-atlas (not show-font-atlas))) (when (rl/key-pressed? :key-space) (set show-font-atlas (not show-font-atlas)))
;; The C's "testing code": walk the text and throw the answer away. Kept ;; The C's "testing code": walk the text and throw the answer away. Kept
;; because it is what exercises the codepoint walk, which is now symmetric ;; because it is what exercises the codepoint walk, which is now symmetric
;; here the way it is in the C — a raylib call in each direction. ;; here the way it is in the C — a raylib call in each direction.
(cond (cond
(rl/key-pressed? :right) (set cursor (step-forward cursor)) (rl/key-pressed? :key-right) (set cursor (step-forward cursor))
(rl/key-pressed? :left) (set cursor (step-back cursor))) (rl/key-pressed? :key-left) (set cursor (step-back cursor)))
;; Draw ;; Draw
(rl/with-drawing (rl/with-drawing

View File

@ -100,7 +100,7 @@
(set (at name letter-count) (u8 key)) (set (at name letter-count) (u8 key))
(set letter-count (+ letter-count 1)))))) (set letter-count (+ letter-count 1))))))
(when (rl/key-pressed? :backspace) (when (rl/key-pressed? :key-backspace)
(set letter-count (- letter-count 1)) (set letter-count (- letter-count 1))
(when (< letter-count 0) (set letter-count 0)))) (when (< letter-count 0) (set letter-count 0))))
(rl/set-mouse-cursor :default)) (rl/set-mouse-cursor :default))

View File

@ -184,7 +184,7 @@
(rl/set-target-fps 60) (rl/set-target-fps 60)
(until (rl/window-should-close?) (until (rl/window-should-close?)
(when (rl/key-pressed? :space) (set word-wrap? (not word-wrap?))) (when (rl/key-pressed? :key-space) (set word-wrap? (not word-wrap?)))
(let [mouse (rl/get-mouse-position)] (let [mouse (rl/get-mouse-position)]
;; The border fades while the pointer is over the container. ;; The border fades while the pointer is over the container.
@ -195,14 +195,14 @@
(if resizing? (if resizing?
(do (do
(when (rl/mouse-button-released? :left) (set resizing? false)) (when (rl/mouse-button-released? :mouse-left) (set resizing? false))
(let [w (+ (.width container) (- (.x mouse) (.x last-mouse))) (let [w (+ (.width container) (- (.x mouse) (.x last-mouse)))
h (+ (.height container) (- (.y mouse) (.y last-mouse)))] h (+ (.height container) (- (.y mouse) (.y last-mouse)))]
(set (.width container) (set (.width container)
(clamp w min-width max-width)) (clamp w min-width max-width))
(set (.height container) (set (.height container)
(clamp h min-height max-height)))) (clamp h min-height max-height))))
(when (and (rl/mouse-button-down? :left) (when (and (rl/mouse-button-down? :mouse-left)
(rl/collision-point-rec? mouse resizer)) (rl/collision-point-rec? mouse resizer))
(set resizing? true))) (set resizing? true)))

View File

@ -54,11 +54,11 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update. Holding space runs the counter eight times faster; enter starts ;; Update. Holding space runs the counter eight times faster; enter starts
;; it over. ;; it over.
(if (rl/key-down? :space) (if (rl/key-down? :key-space)
(set frames-counter (+ frames-counter 8)) (set frames-counter (+ frames-counter 8))
(set frames-counter (+ frames-counter 1))) (set frames-counter (+ frames-counter 1)))
(when (rl/key-pressed? :enter) (set frames-counter 0)) (when (rl/key-pressed? :key-enter) (set frames-counter 0))
;; Draw ;; Draw
(rl/with-drawing (rl/with-drawing

View File

@ -97,10 +97,10 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update ;; Update
(when (rl/key-down? :right) (set (.x player-position) (+ (.x player-position) 5.0))) (when (rl/key-down? :key-right) (set (.x player-position) (+ (.x player-position) 5.0)))
(when (rl/key-down? :left) (set (.x player-position) (- (.x player-position) 5.0))) (when (rl/key-down? :key-left) (set (.x player-position) (- (.x player-position) 5.0)))
(when (rl/key-down? :down) (set (.y player-position) (+ (.y player-position) 5.0))) (when (rl/key-down? :key-down) (set (.y player-position) (+ (.y player-position) 5.0)))
(when (rl/key-down? :up) (set (.y player-position) (- (.y player-position) 5.0))) (when (rl/key-down? :key-up) (set (.y player-position) (- (.y player-position) 5.0)))
;; Keep the player inside the tilemap. The far edge is measured against ;; Keep the player inside the tilemap. The far edge is measured against
;; the player's far side, which is why player-size is subtracted. ;; the player's far side, which is why player-size is subtracted.

View File

@ -100,7 +100,7 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update ;; Update
(when (or (rl/mouse-button-pressed? :left) (rl/key-pressed? :right)) (when (or (rl/mouse-button-pressed? :mouse-left) (rl/key-pressed? :key-right))
(set current-texture (% (+ current-texture 1) num-textures))) (set current-texture (% (+ current-texture 1) num-textures)))
;; Draw ;; Draw

View File

@ -212,7 +212,7 @@
(dotimes [i num-processes] (dotimes [i num-processes]
(when (rl/collision-point-rec? (rl/get-mouse-position) (at toggle-recs i)) (when (rl/collision-point-rec? (rl/get-mouse-position) (at toggle-recs i))
(set mouse-hover-rec i) (set mouse-hover-rec i)
(when (rl/mouse-button-released? :left) (when (rl/mouse-button-released? :mouse-left)
(set current-process i) (set current-process i)
(set reload true)))) (set reload true))))
@ -222,13 +222,13 @@
;; as it is because changing it here would make this file disagree with ;; as it is because changing it here would make this file disagree with
;; the example it claims to be. ;; the example it claims to be.
(cond (cond
(rl/key-pressed? :down) (rl/key-pressed? :key-down)
(do (set current-process (+ current-process 1)) (do (set current-process (+ current-process 1))
(when (> current-process (- num-processes 1)) (when (> current-process (- num-processes 1))
(set current-process 0)) (set current-process 0))
(set reload true)) (set reload true))
(rl/key-pressed? :up) (rl/key-pressed? :key-up)
(do (set current-process (- current-process 1)) (do (set current-process (- current-process 1))
(when (< current-process 0) (set current-process 7)) (when (< current-process 0) (set current-process 7))
(set reload true))) (set reload true)))

View File

@ -125,9 +125,9 @@
(until (rl/window-should-close?) (until (rl/window-should-close?)
;; Update ;; Update
(let [mouse-pos (rl/get-mouse-position)] (let [mouse-pos (rl/get-mouse-position)]
(if (rl/key-pressed? :right) (if (rl/key-pressed? :key-right)
(set color-selected (+ color-selected 1)) (set color-selected (+ color-selected 1))
(when (rl/key-pressed? :left) (when (rl/key-pressed? :key-left)
(set color-selected (- color-selected 1)))) (set color-selected (- color-selected 1))))
(set color-selected (clamp color-selected 0 (- max-colors-count 1))) (set color-selected (clamp color-selected 0 (- max-colors-count 1)))
@ -139,20 +139,20 @@
(set color-mouse-hover i) (set color-mouse-hover i)
(break))) (break)))
(when (and (>= color-mouse-hover 0) (rl/mouse-button-pressed? :left)) (when (and (>= color-mouse-hover 0) (rl/mouse-button-pressed? :mouse-left))
(set color-selected color-mouse-hover) (set color-selected color-mouse-hover)
(set color-selected-prev color-selected)) (set color-selected-prev color-selected))
(set brush-size (clamp (+ brush-size (* (rl/get-mouse-wheel-move) 5.0)) (set brush-size (clamp (+ brush-size (* (rl/get-mouse-wheel-move) 5.0))
2.0 50.0)) 2.0 50.0))
(when (rl/key-pressed? :c) (when (rl/key-pressed? :key-c)
(rl/with-texture-mode target (rl/with-texture-mode target
(rl/clear-background (at colors 0)))) (rl/clear-background (at colors 0))))
;; Paint. The gesture test is what makes the example work on a ;; Paint. The gesture test is what makes the example work on a
;; touchscreen, where there is no mouse button to hold. ;; touchscreen, where there is no mouse button to hold.
(when (or (rl/mouse-button-down? :left) (when (or (rl/mouse-button-down? :mouse-left)
(= (rl/get-gesture-detected) :drag)) (= (rl/get-gesture-detected) :drag))
(rl/with-texture-mode target (rl/with-texture-mode target
;; Above y=50 is the palette strip, and a stroke there would paint ;; Above y=50 is the palette strip, and a stroke there would paint
@ -164,7 +164,7 @@
;; Right button erases, which is painting in the clear colour. The ;; Right button erases, which is painting in the clear colour. The
;; selected swatch is parked while the button is held so the toolbar ;; selected swatch is parked while the button is held so the toolbar
;; shows what is being drawn, and restored on release. ;; shows what is being drawn, and restored on release.
(if (rl/mouse-button-down? :right) (if (rl/mouse-button-down? :mouse-right)
(do (do
(when (not mouse-was-pressed) (when (not mouse-was-pressed)
(set color-selected-prev color-selected) (set color-selected-prev color-selected)
@ -174,7 +174,7 @@
(when (> (.y mouse-pos) 50.0) (when (> (.y mouse-pos) 50.0)
(rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos)) (rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
brush-size (at colors 0))))) brush-size (at colors 0)))))
(when (and (rl/mouse-button-released? :right) mouse-was-pressed) (when (and (rl/mouse-button-released? :mouse-right) mouse-was-pressed)
(set color-selected color-selected-prev) (set color-selected color-selected-prev)
(set mouse-was-pressed false))) (set mouse-was-pressed false)))
@ -182,8 +182,8 @@
;; The round trip. See the header comment for why the flip is here and ;; The round trip. See the header comment for why the flip is here and
;; not on the draw. ;; not on the draw.
(when (or (and btn-save-mouse-hover (rl/mouse-button-released? :left)) (when (or (and btn-save-mouse-hover (rl/mouse-button-released? :mouse-left))
(rl/key-pressed? :s)) (rl/key-pressed? :key-s))
(let [image (rl/load-image-from-texture (.texture target))] (let [image (rl/load-image-from-texture (.texture target))]
(rl/image-flip-vertical (addr image)) (rl/image-flip-vertical (addr image))
(rl/export-image image "my_amazing_texture_painting.png") (rl/export-image image "my_amazing_texture_painting.png")
@ -210,7 +210,7 @@
;; The brush preview, drawn on the screen and not into the canvas. ;; The brush preview, drawn on the screen and not into the canvas.
(when (> (.y mouse-pos) 50.0) (when (> (.y mouse-pos) 50.0)
(if (rl/mouse-button-down? :right) (if (rl/mouse-button-down? :mouse-right)
(rl/draw-circle-lines (i32 (.x mouse-pos)) (i32 (.y mouse-pos)) (rl/draw-circle-lines (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
brush-size rl/gray) brush-size rl/gray)
(rl/draw-circle (rl/get-mouse-x) (rl/get-mouse-y) (rl/draw-circle (rl/get-mouse-x) (rl/get-mouse-y)

View File

@ -666,6 +666,45 @@ let predicate_names = [ "ordered?"; "equal?"; "hashable?"; "numeric?"; "integer?
walk could read an untagged union is treated as owning nothing, and what walk could read an untagged union is treated as owning nothing, and what
its members point at is the program's, through whatever tag it keeps its members point at is the program's, through whatever tag it keeps
beside the union. *) beside the union. *)
(* One edit apart — a substitution, an insertion, a deletion or a
transposition of neighbours. Bounded at one, because two edits is no longer
a typo, it is a guess. Shared by the unknown-type near miss and the enum
member one. *)
let one_edit a b =
let la = String.length a and lb = String.length b in
if abs (la - lb) > 1 then false
else begin
(* Walk both until they diverge, then require the tails to match with the
single edit applied. *)
let i = ref 0 in
while !i < la && !i < lb && a.[!i] = b.[!i] do incr i done;
let ta s k = String.sub s k (String.length s - k) in
if la = lb then
!i < la
&& (ta a (!i + 1) = ta b (!i + 1)
(* stirng/string: two neighbours swapped. *)
|| (!i + 1 < la && a.[!i] = b.[!i + 1] && a.[!i + 1] = b.[!i]
&& ta a (!i + 2) = ta b (!i + 2)))
else if la < lb then ta a !i = ta b (!i + 1)
else ta a (!i + 1) = ta b !i
end
(* The enum members' own near miss. One edit away is the usual typo; the
second rule is for a package whose members carry a disambiguating prefix
raylib's Key spells them [key-r], [key-space] where the natural mistake
is writing the bare name. [:r] against [key-r] is four edits and one
thought, so the rule is the thought: a member whose last segment is exactly
the name written. *)
let member_near_miss members k =
List.find_opt
(fun (m, _) ->
one_edit k m
|| (let lm = String.length m and lk = String.length k in
lm > lk + 1
&& m.[lm - lk - 1] = '-'
&& String.sub m (lm - lk) lk = k))
members
let owning_fields env n = let owning_fields env n =
match Hashtbl.find_opt env.structs n with match Hashtbl.find_opt env.structs n with
| Some s -> [ s.Tast.fields ] | Some s -> [ s.Tast.fields ]
@ -2895,10 +2934,21 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
let members = Hashtbl.find ctx.env.enums name in let members = Hashtbl.find ctx.env.enums name in
(match List.assoc_opt k members with (match List.assoc_opt k members with
| Some v -> mk loc (Types.Enum name) (Tast.Int (v, Types.I32)) | Some v -> mk loc (Types.Enum name) (Tast.Int (v, Types.I32))
| None ->
(* The near miss first, because the raylib enums carry a
disambiguating member prefix [:r] is four edits from [:key-r]
and one thought, and the list alone makes the reader do the
thought. The list still follows: the suggestion can be wrong. *)
(match member_near_miss members k with
| Some (m, _) ->
fail loc "%s has no member :%s — did you mean :%s? It has %s"
name k m
(String.concat " "
(List.map (fun (m, _) -> ":" ^ m) members))
| None -> | None ->
fail loc "%s has no member :%s — it has %s" name k fail loc "%s has no member :%s — it has %s" name k
(String.concat " " (String.concat " "
(List.map (fun (m, _) -> ":" ^ m) members))) (List.map (fun (m, _) -> ":" ^ m) members))))
| Some Types.Dyn | None -> | Some Types.Dyn | None ->
expect ctx loc ~want expect ctx loc ~want
(rt loc Types.Dyn "flan_dyn_kw" [ mk loc Types.String (Tast.Str k) ]) (rt loc Types.Dyn "flan_dyn_kw" [ mk loc Types.String (Tast.Str k) ])

View File

@ -628,6 +628,15 @@ type config = {
prefix ["-"] means the header has nothing to check this enum against and prefix ["-"] means the header has nothing to check this enum against and
that is deliberate. that is deliberate.
[enum_flan_prefixes]: the prefix the same defenum's members carry on the
*Flan* side, when they carry one. Members can be prefixed there too
[key-r] rather than [r], so a keyword cannot collide across enums and
the C name is built by stripping the Flan prefix first and then applying
the C one: [("Key", "key-")] against [("Key", "KEY_")] checks [key-r] as
[KEY_R] rather than [KEY_KEY_R]. Declared as a fourth column on the
`enum` line, and a member that does not carry the declared prefix is a
finding, not a member checked under a guessed name.
[const_prefixes]: a prefix of [defconst] names, and the prefix the [const_prefixes]: a prefix of [defconst] names, and the prefix the
corresponding C enumerators carry. [("flag-", "FLAG_")] checks corresponding C enumerators carry. [("flag-", "FLAG_")] checks
[flag-vsync-hint] against [FLAG_VSYNC_HINT]. [flag-vsync-hint] against [FLAG_VSYNC_HINT].
@ -638,13 +647,14 @@ type config = {
[GESTURE_DOUBLETAP] against a member the package spells [double-tap]. It [GESTURE_DOUBLETAP] against a member the package spells [double-tap]. It
also brings a name under the check that no prefix rule covers. *) also brings a name under the check that no prefix rule covers. *)
enum_prefixes : (string * string) list; enum_prefixes : (string * string) list;
enum_flan_prefixes : (string * string) list;
const_prefixes : (string * string) list; const_prefixes : (string * string) list;
constants : (string * string) list; constants : (string * string) list;
} }
let no_config = let no_config =
{ excludes = []; renames = []; enum_prefixes = []; const_prefixes = []; { excludes = []; renames = []; enum_prefixes = []; enum_flan_prefixes = [];
constants = [] } const_prefixes = []; constants = [] }
(* [*] stands for any run of characters and nothing else does anything. Enough (* [*] stands for any run of characters and nothing else does anything. Enough
for [rl*] or [*Callback], and small enough to read at a glance; a package for [rl*] or [*Callback], and small enough to read at a glance; a package
@ -679,7 +689,8 @@ let read_config path : config =
else begin else begin
let ch = open_in path in let ch = open_in path in
let excludes = ref [] and renames = ref [] in let excludes = ref [] and renames = ref [] in
let enum_prefixes = ref [] and const_prefixes = ref [] and constants = ref [] in let enum_prefixes = ref [] and enum_flan_prefixes = ref []
and const_prefixes = ref [] and constants = ref [] in
let rec go n = let rec go n =
match input_line ch with match input_line ch with
| line -> | line ->
@ -694,15 +705,29 @@ let read_config path : config =
| [ "exclude"; p ] -> excludes := p :: !excludes | [ "exclude"; p ] -> excludes := p :: !excludes
| [ "name"; sym; flan ] -> renames := (sym, flan) :: !renames | [ "name"; sym; flan ] -> renames := (sym, flan) :: !renames
| [ "enum"; flan; c ] -> enum_prefixes := (flan, c) :: !enum_prefixes | [ "enum"; flan; c ] -> enum_prefixes := (flan, c) :: !enum_prefixes
(* The optional fourth column: the prefix the members carry on the
Flan side, stripped before the C prefix is applied. Refused on
`enum Foo -` rather than ignored a column that does nothing on
one shape of line is a column somebody will trust on both. *)
| [ "enum"; flan; c; fp ] ->
if String.equal c "-" then begin
close_in ch;
fail (Loc.make path n 0)
"`enum %s -` says the header has nothing to check %s against, \
so a Flan member prefix on that line would strip for no \
comparison drop `%s`, or name the C prefix" flan flan fp
end;
enum_prefixes := (flan, c) :: !enum_prefixes;
enum_flan_prefixes := (flan, fp) :: !enum_flan_prefixes
| [ "const"; flan; c ] -> const_prefixes := (flan, c) :: !const_prefixes | [ "const"; flan; c ] -> const_prefixes := (flan, c) :: !const_prefixes
| [ "constant"; flan; c ] -> constants := (flan, c) :: !constants | [ "constant"; flan; c ] -> constants := (flan, c) :: !constants
| _ -> | _ ->
close_in ch; close_in ch;
fail (Loc.make path n 0) fail (Loc.make path n 0)
"a line here is `exclude <C symbol or pattern>`, `name <C \ "a line here is `exclude <C symbol or pattern>`, `name <C \
symbol> <flan-name>`, `enum <FlanEnum> <C_PREFIX>`, `const \ symbol> <flan-name>`, `enum <FlanEnum> <C_PREFIX> \
<flan-prefix> <C_PREFIX>` or `constant <flan-name> <C_NAME>`, \ [<flan-prefix>]`, `const <flan-prefix> <C_PREFIX>` or \
and this is neither: %s" t `constant <flan-name> <C_NAME>`, and this is neither: %s" t
end; end;
go (n + 1) go (n + 1)
| exception End_of_file -> () | exception End_of_file -> ()
@ -711,6 +736,7 @@ let read_config path : config =
close_in ch; close_in ch;
{ excludes = List.rev !excludes; renames = List.rev !renames; { excludes = List.rev !excludes; renames = List.rev !renames;
enum_prefixes = List.rev !enum_prefixes; enum_prefixes = List.rev !enum_prefixes;
enum_flan_prefixes = List.rev !enum_flan_prefixes;
const_prefixes = List.rev !const_prefixes; const_prefixes = List.rev !const_prefixes;
constants = List.rev !constants } constants = List.rev !constants }
end end
@ -1209,15 +1235,31 @@ let check_constants ~config
| Some "-" -> Hashtbl.replace used ("enum:" ^ ename) () | Some "-" -> Hashtbl.replace used ("enum:" ^ ename) ()
| Some prefix -> | Some prefix ->
Hashtbl.replace used ("enum:" ^ ename) (); Hashtbl.replace used ("enum:" ^ ename) ();
(* The Flan-side member prefix, when the `enum` line declares one:
[key-r] is KEY_R and not KEY_KEY_R, because the prefix is stripped
before the C one is applied. A member that does not carry the
declared prefix is a finding rather than a member checked under a
guessed name [null] beside a declared [key-] would otherwise
build KEY_NULL, which the header happens to have, and the naming
rule the line declares would erode silently. *)
let fprefix = List.assoc_opt ename config.enum_flan_prefixes in
List.iter List.iter
(fun (m, v) -> (fun (m, v) ->
let flan = ename ^ "/" ^ m in let flan = ename ^ "/" ^ m in
let cname =
match List.assoc_opt flan explicit with match List.assoc_opt flan explicit with
| Some c -> c | Some c -> compare_one flan v c
| None -> prefix ^ screaming m | None ->
in (match fprefix with
compare_one flan v cname) | None -> compare_one flan v (prefix ^ screaming m)
| Some fp ->
(match strip_prefix fp m with
| Some stem -> compare_one flan v (prefix ^ screaming stem)
| None ->
mapping_say flan
"the `enum %s` line in the package's `bindings` says \
its members carry the prefix %s on the Flan side, \
and %s does not rename the member, or fix the line"
ename fp m)))
members) members)
enums; enums;
(* Plain constants, and only the ones a rule or a [constant] line reaches. *) (* Plain constants, and only the ones a rule or a [constant] line reaches. *)

View File

@ -2890,6 +2890,16 @@ let () =
rejects_check "a keyword that is not a member" rejects_check "a keyword that is not a member"
"(defenum Key [space 32]) (defn g [k Key] ()) (defn f [] () (g :spcae))" "(defenum Key [space 32]) (defn g [k Key] ()) (defn f [] () (g :spcae))"
~needle:"has no member :spcae"; ~needle:"has no member :spcae";
(* The near miss. A typo one edit away is suggested, and so is the bare
name of a member that carries a disambiguating prefix raylib's Key
spells its members key-r, key-space, and :r is the natural mistake. *)
rejects_check "a member one edit away is suggested"
"(defenum Key [space 32]) (defn g [k Key] ()) (defn f [] () (g :spcae))"
~needle:"did you mean :space?";
rejects_check "a bare name suggests the prefixed member"
"(defenum Key [key-space 32 key-r 82]) (defn g [k Key] ()) \
(defn f [] () (g :r))"
~needle:"did you mean :key-r?";
accepts "a keyword that is a member" accepts "a keyword that is a member"
"(defenum Key [space 32 r 82]) (defn g [k Key] ()) (defn f [] () (g :r))"; "(defenum Key [space 32 r 82]) (defn g [k Key] ()) (defn f [] () (g :r))";
(* Converting an enum, explicitly, in both directions. The point of the (* Converting an enum, explicitly, in both directions. The point of the
@ -3880,6 +3890,17 @@ let () =
(match Cimport.read_config (config_file "rename Foo bar\n") with (match Cimport.read_config (config_file "rename Foo bar\n") with
| _ -> false | _ -> false
| exception Loc.Error { Loc.dmsg = m; _ } -> contains m "and this is neither"); | exception Loc.Error { Loc.dmsg = m; _ } -> contains m "and this is neither");
(* The enum line's fourth column: the prefix the members carry on the Flan
side, so `key-r` is checked as KEY_R and not KEY_KEY_R. *)
let c = Cimport.read_config (config_file "enum Key KEY_ key-\n") in
check "read_config reads an enum line's Flan member prefix"
(c.Cimport.enum_prefixes = [ ("Key", "KEY_") ]
&& c.Cimport.enum_flan_prefixes = [ ("Key", "key-") ]);
check "a Flan member prefix on `enum Foo -` is refused"
(match Cimport.read_config (config_file "enum Foo - key-\n") with
| _ -> false
| exception Loc.Error { Loc.dmsg = m; _ } ->
contains m "nothing to check Foo against");
(* A refused name is a name that exists and cannot be had — Zig's failDecl, (* A refused name is a name that exists and cannot be had — Zig's failDecl,
which Load.refuse_hidden already implements for main. Nothing may be in which Load.refuse_hidden already implements for main. Nothing may be in
@ -4153,6 +4174,37 @@ let () =
contains why "no constant named SHADE_HALF_DARK" contains why "no constant named SHADE_HALF_DARK"
| _ -> false); | _ -> false);
(* The Flan-side member prefix, declared as the enum line's fourth column.
raylib's Key spells its members key-r, key-space a bare member name
collides across enums and the C name is built by stripping that prefix
first, so key-r is KEY_R and not KEY_KEY_R. *)
let prefixed =
{ mapping with Cimport.enum_flan_prefixes = [ ("Mood", "mood-") ] }
in
check "a declared Flan member prefix is stripped before the C name is built"
(constants ~config:prefixed
(const_fixture ~mood:"[mood-calm 0 mood-cross 1]" ())
= []);
check "a wrong value is still caught through the stripped prefix"
(match
constants ~config:prefixed
(const_fixture ~mood:"[mood-calm 0 mood-cross 2]" ())
with
| [ ("Mood/mood-cross", why) ] ->
contains why "MOOD_CROSS" && contains why "is 2 here"
| _ -> false);
(* A member that does not carry the declared prefix is a finding, not a
member checked under a guessed name: `calm` beside a declared `mood-`
would otherwise build MOOD_CALM, which the header has, and the naming
rule the line declares would erode silently. *)
check "a member without the declared Flan prefix is a mapping finding"
(match raw_constants ~config:prefixed (const_fixture ()) with
| [ a; b ] ->
a.Cimport.cmapping && b.Cimport.cmapping
&& contains a.Cimport.cwhy "carry the prefix mood-"
&& contains b.Cimport.cwhy "carry the prefix mood-"
| _ -> false);
(* Coverage itself must not go quiet. A defenum nobody mapped would be (* Coverage itself must not go quiet. A defenum nobody mapped would be
silently unchecked, which is the same hole one level up. *) silently unchecked, which is the same hole one level up. *)
check "a defenum with no enum line is itself a finding" check "a defenum with no enum line is itself a finding"

View File

@ -194,7 +194,7 @@ name ImageDrawTriangleStrip image-draw-triangle-strip-raw
# ── What the package's constants are called in C ──────────────────── # ── What the package's constants are called in C ────────────────────
# #
# enum <FlanEnum> <C_PREFIX> every member of that defenum # enum <FlanEnum> <C_PREFIX> [<flan-prefix>] every member of that defenum
# const <flan-prefix> <C_PREFIX> every defconst whose name starts so # const <flan-prefix> <C_PREFIX> every defconst whose name starts so
# constant <flan-name> <C_NAME> one name, exactly # constant <flan-name> <C_NAME> one name, exactly
# #
@ -212,11 +212,20 @@ name ImageDrawTriangleStrip image-draw-triangle-strip-raw
# skipped; a mapping that quietly matched nothing would read as coverage and # skipped; a mapping that quietly matched nothing would read as coverage and
# provide none. # provide none.
# #
# An enum line's optional third column is a prefix the members carry on the
# *Flan* side. Key and MouseButton spell their members `key-r` and
# `mouse-left`, because the bare names collide — across the two enums and
# with a user's own — and without the column the rule above would double the
# prefix: KEY_KEY_R. The declared Flan prefix is stripped first and the C one
# applied to what is left, so `key-r` checks against KEY_R and `mouse-left`
# against MOUSE_BUTTON_LEFT. A member that does not carry the declared prefix
# is reported, not checked under a guessed name.
#
# Every defenum needs a line, including one the header cannot check, which # Every defenum needs a line, including one the header cannot check, which
# says so with `-`. That is the same rule one level up: an enum nobody mapped # says so with `-`. That is the same rule one level up: an enum nobody mapped
# would be silently unchecked, which is the hole this closes. # would be silently unchecked, which is the hole this closes.
enum Key KEY_ enum Key KEY_ key-
enum MouseButton MOUSE_BUTTON_ enum MouseButton MOUSE_BUTTON_ mouse-
enum TraceLogLevel LOG_ enum TraceLogLevel LOG_
enum CameraProjection CAMERA_ enum CameraProjection CAMERA_
enum CameraMode CAMERA_ enum CameraMode CAMERA_

View File

@ -70,22 +70,32 @@
;; KeyboardKey, the subset sand.flan uses. A keyword at a call site resolves ;; KeyboardKey, the subset sand.flan uses. A keyword at a call site resolves
;; against these members at compile time and a typo is an error there. ;; against these members at compile time and a typo is an error there.
;;
;; Every member carries the `key-` prefix, and MouseButton's carry `mouse-`,
;; because the bare names collide — :left would mean a key here and a button
;; there, and a plain word like :space is one a user's own enum wants too.
;; The prefix is declared in `bindings` (the enum line's third column), which
;; is what keeps :key-r checking against KEY_R rather than KEY_KEY_R.
(defenum Key (defenum Key
[space 32 apostrophe 39 comma 44 minus 45 period 46 slash 47 [key-space 32 key-apostrophe 39 key-comma 44 key-minus 45
zero 48 one 49 two 50 three 51 four 52 key-period 46 key-slash 47
five 53 six 54 seven 55 eight 56 nine 57 key-zero 48 key-one 49 key-two 50 key-three 51 key-four 52
a 65 b 66 c 67 d 68 e 69 f 70 g 71 h 72 i 73 key-five 53 key-six 54 key-seven 55 key-eight 56 key-nine 57
j 74 k 75 l 76 m 77 n 78 o 79 p 80 q 81 r 82 key-a 65 key-b 66 key-c 67 key-d 68 key-e 69 key-f 70 key-g 71
s 83 t 84 u 85 v 86 w 87 x 88 y 89 z 90 key-h 72 key-i 73 key-j 74 key-k 75 key-l 76 key-m 77 key-n 78
escape 256 enter 257 tab 258 backspace 259 key-o 79 key-p 80 key-q 81 key-r 82 key-s 83 key-t 84 key-u 85
right 262 left 263 down 264 up 265 key-v 86 key-w 87 key-x 88 key-y 89 key-z 90
left-shift 340 key-escape 256 key-enter 257 key-tab 258 key-backspace 259
key-right 262 key-left 263 key-down 264 key-up 265
key-left-shift 340
;; KEY_NULL is not a key. It is the value set-exit-key takes to mean "no ;; KEY_NULL is not a key. It is the value set-exit-key takes to mean "no
;; key closes the window", which is the only thing that ever passes it. ;; key closes the window", which is the only thing that ever passes it.
null 0]) key-null 0])
;; `mouse-` and not `button-`: gamepads have buttons too.
(defenum MouseButton (defenum MouseButton
[left 0 right 1 middle 2 side 3 extra 4 forward 5 back 6]) [mouse-left 0 mouse-right 1 mouse-middle 2 mouse-side 3 mouse-extra 4
mouse-forward 5 mouse-back 6])
(defenum TraceLogLevel (defenum TraceLogLevel
[all 0 trace 1 debug 2 info 3 warning 4 error 5 fatal 6 none 7]) [all 0 trace 1 debug 2 info 3 warning 4 error 5 fatal 6 none 7])
@ -144,8 +154,8 @@
;; Which key closes the window, ESCAPE by default. Hand-written rather than ;; Which key closes the window, ESCAPE by default. Hand-written rather than
;; left to the generated half because the Flan face is the difference: raylib ;; left to the generated half because the Flan face is the difference: raylib
;; declares it `void SetExitKey(int key)` and the generated line therefore ;; declares it `void SetExitKey(int key)` and the generated line therefore
;; takes an i32, where this one takes a Key and so accepts :escape and refuses ;; takes an i32, where this one takes a Key and so accepts :key-escape and refuses
;; a typo. `:null` is how a program says "no key does that" and takes the ;; a typo. `:key-null` is how a program says "no key does that" and takes the
;; close over itself. ;; close over itself.
(declare-c set-exit-key [key Key] "SetExitKey") (declare-c set-exit-key [key Key] "SetExitKey")
@ -155,7 +165,7 @@
;; The other two halves of that family, hand-written for exactly the reason ;; The other two halves of that family, hand-written for exactly the reason
;; above and added late: they were generated, so they took an i32, so ;; above and added late: they were generated, so they took an i32, so
;; `(rl/key-up? :space)` did not compile while `(rl/key-down? :space)` did. ;; `(rl/key-up? :key-space)` did not compile while `(rl/key-down? :key-space)` did.
;; That is a hole in a family rather than a missing convenience — a caller ;; That is a hole in a family rather than a missing convenience — a caller
;; who has used key-down? has no reason to expect the sibling to be spelled ;; who has used key-down? has no reason to expect the sibling to be spelled
;; differently, and what they get instead of a keyword is a number nobody ;; differently, and what they get instead of a keyword is a number nobody
@ -192,7 +202,7 @@
;; get-key-pressed answers an i32 and not a Key. A Key is a *closed* set the ;; get-key-pressed answers an i32 and not a Key. A Key is a *closed* set the
;; package names a subset of, and this queue reports every key on the ;; package names a subset of, and this queue reports every key on the
;; keyboard including the ones no member covers, so the enum would be a ;; keyboard including the ones no member covers, so the enum would be a
;; promise the value does not keep. Comparing the answer against `:space` ;; promise the value does not keep. Comparing the answer against `:key-space`
;; would be the reason to want it, and that is what key-pressed? is for. ;; would be the reason to want it, and that is what key-pressed? is for.
(defn get-key-pressed [] (Option i32) (defn get-key-pressed [] (Option i32)
(let [k (get-key-pressed-raw)] (let [k (get-key-pressed-raw)]