diff --git a/FIX.org b/FIX.org index 2ffc1fa..41b2ef7 100644 --- a/FIX.org +++ b/FIX.org @@ -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 ask. Walk-backable (admitting more programs later invalidates nothing 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 161–166), 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~. diff --git a/NEXT.md b/NEXT.md index 57325ad..5c98104 100644 --- a/NEXT.md +++ b/NEXT.md @@ -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 one. - **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 - `(rl/key-up? :space)` did not. **These are not wrappers.** A C enum parameter has an int's ABI, so the hand-written + whose other halves already took a `Key` or a `MouseButton`, so `(rl/key-down? :key-space)` compiled and + `(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. 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 `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 sibling of a call that worked did not — by excluding them and hand-writing the enum type, which is all it takes. diff --git a/docs/DISCUSS.md b/docs/DISCUSS.md index e78b11d..e5fdb28 100644 --- a/docs/DISCUSS.md +++ b/docs/DISCUSS.md @@ -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 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 -`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 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 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 -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. ## 7. Watching variables diff --git a/emacs/test-flan-mode.el b/emacs/test-flan-mode.el index 25435c3..7b0bffd 100644 --- a/emacs/test-flan-mode.el +++ b/emacs/test-flan-mode.el @@ -230,7 +230,7 @@ ("(rl/Vector2 {.x 1.0 .y 2.0})" ".y" "and the second one") ("(set total (.bytes c))" ".bytes" "a field accessor") ;; 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)) (eq (test-flan-mode--face-at (nth 0 case) (nth 1 case)) 'font-lock-constant-face))) diff --git a/examples/core-2d-camera.flan b/examples/core-2d-camera.flan index 00bb21b..70802f5 100644 --- a/examples/core-2d-camera.flan +++ b/examples/core-2d-camera.flan @@ -77,24 +77,24 @@ ;; Update ;; The C's `if (RIGHT) ... else if (LEFT) ...`: both held cancels to the ;; 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)) - (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. (set (.target camera) (rl/Vector2 {.x (+ (.x 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)) - (when (rl/key-down? :s) + (when (rl/key-down? :key-s) (set (.rotation camera) (+ (.rotation camera) 1.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) (clamp (.zoom camera) 0.1 3.0)) - (when (rl/key-pressed? :r) + (when (rl/key-pressed? :key-r) (set (.zoom camera) 1.0) (set (.rotation camera) 0.0)) diff --git a/examples/core-3d-picking.flan b/examples/core-3d-picking.flan index 3cc1c2e..0292342 100644 --- a/examples/core-3d-picking.flan +++ b/examples/core-3d-picking.flan @@ -97,10 +97,10 @@ ;; could not be used to aim at anything. (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))) - (when (rl/mouse-button-pressed? :left) + (when (rl/mouse-button-pressed? :mouse-left) (if (not (.hit collision)) ;; The pixel under the pointer, as a line through the scene, tested ;; against the cube's bounds. diff --git a/examples/core-delta-time.flan b/examples/core-delta-time.flan index 4ae1b4c..f01eb9b 100644 --- a/examples/core-delta-time.flan +++ b/examples/core-delta-time.flan @@ -65,7 +65,7 @@ (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 (rl/key-pressed? :r) + (when (rl/key-pressed? :key-r) (set (.x delta-circle) 0.0) (set (.x frame-circle) 0.0)) diff --git a/examples/core-input-gamepad.flan b/examples/core-input-gamepad.flan index c7c8e6b..5752a46 100644 --- a/examples/core-input-gamepad.flan +++ b/examples/core-input-gamepad.flan @@ -173,9 +173,9 @@ (until (rl/window-should-close?) ;; Update - (when (and (rl/key-pressed? :left) (> gamepad 0)) + (when (and (rl/key-pressed? :key-left) (> gamepad 0)) (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)) vibrate-rect (rl/Rectangle {.x 10.0 diff --git a/examples/core-input-gestures-testbed.flan b/examples/core-input-gestures-testbed.flan index 1ed7eba..6b90d74 100644 --- a/examples/core-input-gestures-testbed.flan +++ b/examples/core-input-gestures-testbed.flan @@ -299,7 +299,7 @@ (set last-gesture g)) ;; 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)] (when (rl/collision-point-rec? m b1) (set log-mode (cond (= log-mode 3) 2 diff --git a/examples/core-input-keys.flan b/examples/core-input-keys.flan index 2d86b84..feb5a36 100644 --- a/examples/core-input-keys.flan +++ b/examples/core-input-keys.flan @@ -34,10 +34,10 @@ (until (rl/window-should-close?) ;; 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. - (when (rl/key-down? :right) (set (.x ball) (+ (.x ball) 2.0))) - (when (rl/key-down? :left) (set (.x ball) (- (.x ball) 2.0))) - (when (rl/key-down? :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-right) (set (.x ball) (+ (.x ball) 2.0))) + (when (rl/key-down? :key-left) (set (.x ball) (- (.x ball) 2.0))) + (when (rl/key-down? :key-up) (set (.y ball) (- (.y ball) 2.0))) + (when (rl/key-down? :key-down) (set (.y ball) (+ (.y ball) 2.0))) ;; Draw (rl/with-drawing diff --git a/examples/core-input-mouse.flan b/examples/core-input-mouse.flan index e533488..d3e57e7 100644 --- a/examples/core-input-mouse.flan +++ b/examples/core-input-mouse.flan @@ -33,19 +33,19 @@ (until (rl/window-should-close?) ;; Update - (when (rl/key-pressed? :h) + (when (rl/key-pressed? :key-h) (if (rl/cursor-hidden?) (rl/show-cursor) (rl/hide-cursor))) (let [ball (rl/get-mouse-position)] (set ball-color (cond - (rl/mouse-button-pressed? :left) rl/maroon - (rl/mouse-button-pressed? :middle) rl/lime - (rl/mouse-button-pressed? :right) rl/darkblue - (rl/mouse-button-pressed? :side) rl/purple - (rl/mouse-button-pressed? :extra) rl/yellow - (rl/mouse-button-pressed? :forward) rl/orange - (rl/mouse-button-pressed? :back) rl/beige + (rl/mouse-button-pressed? :mouse-left) rl/maroon + (rl/mouse-button-pressed? :mouse-middle) rl/lime + (rl/mouse-button-pressed? :mouse-right) rl/darkblue + (rl/mouse-button-pressed? :mouse-side) rl/purple + (rl/mouse-button-pressed? :mouse-extra) rl/yellow + (rl/mouse-button-pressed? :mouse-forward) rl/orange + (rl/mouse-button-pressed? :mouse-back) rl/beige :else ball-color)) ;; Draw diff --git a/examples/core-input-virtual-controls.flan b/examples/core-input-virtual-controls.flan index 8bdbab9..c4d1829 100644 --- a/examples/core-input-virtual-controls.flan +++ b/examples/core-input-virtual-controls.flan @@ -165,7 +165,7 @@ input (if touching (rl/get-touch-position 0) (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) button-none)] diff --git a/examples/core-scissor-test.flan b/examples/core-scissor-test.flan index 9e6b55c..a9f6a2e 100644 --- a/examples/core-scissor-test.flan +++ b/examples/core-scissor-test.flan @@ -44,7 +44,7 @@ (until (rl/window-should-close?) ;; 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. (set (.x scissor) (- (f32 (rl/get-mouse-x)) (/ (.width scissor) 2.0))) diff --git a/examples/core-window-flags.flan b/examples/core-window-flags.flan index ed30bd5..21496b6 100644 --- a/examples/core-window-flags.flan +++ b/examples/core-window-flags.flan @@ -79,18 +79,18 @@ (until (rl/window-should-close?) ;; Update - (when (rl/key-pressed? :f) (rl/toggle-fullscreen)) - (when (rl/key-pressed? :r) (toggle-flag rl/flag-window-resizable)) - (when (rl/key-pressed? :d) (toggle-flag rl/flag-window-undecorated)) - (when (rl/key-pressed? :u) (toggle-flag rl/flag-window-unfocused)) - (when (rl/key-pressed? :t) (toggle-flag rl/flag-window-topmost)) - (when (rl/key-pressed? :a) (toggle-flag rl/flag-window-always-run)) - (when (rl/key-pressed? :v) (toggle-flag rl/flag-vsync-hint)) + (when (rl/key-pressed? :key-f) (rl/toggle-fullscreen)) + (when (rl/key-pressed? :key-r) (toggle-flag rl/flag-window-resizable)) + (when (rl/key-pressed? :key-d) (toggle-flag rl/flag-window-undecorated)) + (when (rl/key-pressed? :key-u) (toggle-flag rl/flag-window-unfocused)) + (when (rl/key-pressed? :key-t) (toggle-flag rl/flag-window-topmost)) + (when (rl/key-pressed? :key-a) (toggle-flag rl/flag-window-always-run)) + (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 ;; window the key would have to be pressed in, so each comes back on a ;; timer instead. - (when (rl/key-pressed? :h) + (when (rl/key-pressed? :key-h) (unless (rl/window-state? rl/flag-window-hidden) (rl/set-window-state rl/flag-window-hidden)) (set frames 0)) @@ -100,7 +100,7 @@ (when (>= frames restore-after) (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) (rl/minimize-window)) (set frames 0)) @@ -110,7 +110,7 @@ (when (>= frames restore-after) (rl/restore-window))) ;; 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) (rl/restore-window) (rl/maximize-window))) diff --git a/examples/core-window-should-close.flan b/examples/core-window-should-close.flan index 1a636d4..afcf88d 100644 --- a/examples/core-window-should-close.flan +++ b/examples/core-window-should-close.flan @@ -5,7 +5,7 @@ ;;;; KEY_NULL in raylib.h. The binding is hand-written rather than taken from ;;;; the generated half because the Flan face differs — raylib declares ;;;; `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. ;;;; ;;;; The whole example is about what window-should-close? means. It is not a @@ -34,7 +34,7 @@ (defer (rl/close-window)) ;; 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) @@ -42,16 +42,16 @@ ;; 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 ;; 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)) ;; 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 ;; is nothing to do when neither key was pressed. (when exit-requested - (if (rl/key-pressed? :y) + (if (rl/key-pressed? :key-y) (set exiting true) - (when (rl/key-pressed? :n) (set exit-requested false)))) + (when (rl/key-pressed? :key-n) (set exit-requested false)))) ;; Draw (rl/with-drawing diff --git a/examples/models-box-collisions.flan b/examples/models-box-collisions.flan index fbf024e..ce50da2 100644 --- a/examples/models-box-collisions.flan +++ b/examples/models-box-collisions.flan @@ -93,10 +93,10 @@ ;; means only one direction applies per frame — diagonal movement is not ;; possible, deliberately. (cond - (rl/key-down? :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? :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-right) (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? :key-down) (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) hit (or (rl/check-collision-boxes player-box diff --git a/examples/shapes-collision-area.flan b/examples/shapes-collision-area.flan index 48f14da..613b3d7 100644 --- a/examples/shapes-collision-area.flan +++ b/examples/shapes-collision-area.flan @@ -90,7 +90,7 @@ ;; rather than reading that back. This follows it. (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 (rl/with-drawing diff --git a/examples/text-codepoints-loading.flan b/examples/text-codepoints-loading.flan index e317108..0fa883e 100644 --- a/examples/text-codepoints-loading.flan +++ b/examples/text-codepoints-loading.flan @@ -219,14 +219,14 @@ (until (rl/window-should-close?) ;; 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 ;; 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. (cond - (rl/key-pressed? :right) (set cursor (step-forward cursor)) - (rl/key-pressed? :left) (set cursor (step-back cursor))) + (rl/key-pressed? :key-right) (set cursor (step-forward cursor)) + (rl/key-pressed? :key-left) (set cursor (step-back cursor))) ;; Draw (rl/with-drawing diff --git a/examples/text-input-box.flan b/examples/text-input-box.flan index c5c34df..ec9337d 100644 --- a/examples/text-input-box.flan +++ b/examples/text-input-box.flan @@ -100,7 +100,7 @@ (set (at name letter-count) (u8 key)) (set letter-count (+ letter-count 1)))))) - (when (rl/key-pressed? :backspace) + (when (rl/key-pressed? :key-backspace) (set letter-count (- letter-count 1)) (when (< letter-count 0) (set letter-count 0)))) (rl/set-mouse-cursor :default)) diff --git a/examples/text-rectangle-bounds.flan b/examples/text-rectangle-bounds.flan index 25e4da0..0dc1a30 100644 --- a/examples/text-rectangle-bounds.flan +++ b/examples/text-rectangle-bounds.flan @@ -184,7 +184,7 @@ (rl/set-target-fps 60) (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)] ;; The border fades while the pointer is over the container. @@ -195,14 +195,14 @@ (if resizing? (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))) h (+ (.height container) (- (.y mouse) (.y last-mouse)))] (set (.width container) (clamp w min-width max-width)) (set (.height container) (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)) (set resizing? true))) diff --git a/examples/text-writing-anim.flan b/examples/text-writing-anim.flan index 0aeab0a..255ab3a 100644 --- a/examples/text-writing-anim.flan +++ b/examples/text-writing-anim.flan @@ -54,11 +54,11 @@ (until (rl/window-should-close?) ;; Update. Holding space runs the counter eight times faster; enter starts ;; it over. - (if (rl/key-down? :space) + (if (rl/key-down? :key-space) (set frames-counter (+ frames-counter 8)) (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 (rl/with-drawing diff --git a/examples/textures-fog-of-war.flan b/examples/textures-fog-of-war.flan index 8356d53..15d1059 100644 --- a/examples/textures-fog-of-war.flan +++ b/examples/textures-fog-of-war.flan @@ -97,10 +97,10 @@ (until (rl/window-should-close?) ;; Update - (when (rl/key-down? :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? :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-right) (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? :key-down) (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 ;; the player's far side, which is why player-size is subtracted. diff --git a/examples/textures-image-generation.flan b/examples/textures-image-generation.flan index 516d370..bf6e541 100644 --- a/examples/textures-image-generation.flan +++ b/examples/textures-image-generation.flan @@ -100,7 +100,7 @@ (until (rl/window-should-close?) ;; 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))) ;; Draw diff --git a/examples/textures-image-processing.flan b/examples/textures-image-processing.flan index 67ee9f5..22b4841 100644 --- a/examples/textures-image-processing.flan +++ b/examples/textures-image-processing.flan @@ -212,7 +212,7 @@ (dotimes [i num-processes] (when (rl/collision-point-rec? (rl/get-mouse-position) (at toggle-recs i)) (set mouse-hover-rec i) - (when (rl/mouse-button-released? :left) + (when (rl/mouse-button-released? :mouse-left) (set current-process i) (set reload true)))) @@ -222,13 +222,13 @@ ;; as it is because changing it here would make this file disagree with ;; the example it claims to be. (cond - (rl/key-pressed? :down) + (rl/key-pressed? :key-down) (do (set current-process (+ current-process 1)) (when (> current-process (- num-processes 1)) (set current-process 0)) (set reload true)) - (rl/key-pressed? :up) + (rl/key-pressed? :key-up) (do (set current-process (- current-process 1)) (when (< current-process 0) (set current-process 7)) (set reload true))) diff --git a/examples/textures-mouse-painting.flan b/examples/textures-mouse-painting.flan index dad18e6..8ea0292 100644 --- a/examples/textures-mouse-painting.flan +++ b/examples/textures-mouse-painting.flan @@ -125,9 +125,9 @@ (until (rl/window-should-close?) ;; Update (let [mouse-pos (rl/get-mouse-position)] - (if (rl/key-pressed? :right) + (if (rl/key-pressed? :key-right) (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 (clamp color-selected 0 (- max-colors-count 1))) @@ -139,20 +139,20 @@ (set color-mouse-hover i) (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-prev color-selected)) (set brush-size (clamp (+ brush-size (* (rl/get-mouse-wheel-move) 5.0)) 2.0 50.0)) - (when (rl/key-pressed? :c) + (when (rl/key-pressed? :key-c) (rl/with-texture-mode target (rl/clear-background (at colors 0)))) ;; Paint. The gesture test is what makes the example work on a ;; 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/with-texture-mode target ;; 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 ;; selected swatch is parked while the button is held so the toolbar ;; shows what is being drawn, and restored on release. - (if (rl/mouse-button-down? :right) + (if (rl/mouse-button-down? :mouse-right) (do (when (not mouse-was-pressed) (set color-selected-prev color-selected) @@ -174,7 +174,7 @@ (when (> (.y mouse-pos) 50.0) (rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos)) 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 mouse-was-pressed false))) @@ -182,8 +182,8 @@ ;; The round trip. See the header comment for why the flip is here and ;; not on the draw. - (when (or (and btn-save-mouse-hover (rl/mouse-button-released? :left)) - (rl/key-pressed? :s)) + (when (or (and btn-save-mouse-hover (rl/mouse-button-released? :mouse-left)) + (rl/key-pressed? :key-s)) (let [image (rl/load-image-from-texture (.texture target))] (rl/image-flip-vertical (addr image)) (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. (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)) brush-size rl/gray) (rl/draw-circle (rl/get-mouse-x) (rl/get-mouse-y) diff --git a/lib/check.ml b/lib/check.ml index 152c071..32207aa 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -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 its members point at is the program's, through whatever tag it keeps 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 = match Hashtbl.find_opt env.structs n with | Some s -> [ s.Tast.fields ] @@ -2896,9 +2935,20 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr = (match List.assoc_opt k members with | Some v -> mk loc (Types.Enum name) (Tast.Int (v, Types.I32)) | None -> - fail loc "%s has no member :%s — it has %s" name k - (String.concat " " - (List.map (fun (m, _) -> ":" ^ m) members))) + (* 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 -> + fail loc "%s has no member :%s — it has %s" name k + (String.concat " " + (List.map (fun (m, _) -> ":" ^ m) members)))) | Some Types.Dyn | None -> expect ctx loc ~want (rt loc Types.Dyn "flan_dyn_kw" [ mk loc Types.String (Tast.Str k) ]) diff --git a/lib/cimport.ml b/lib/cimport.ml index e4dad44..8d8473e 100644 --- a/lib/cimport.ml +++ b/lib/cimport.ml @@ -628,6 +628,15 @@ type config = { prefix ["-"] means the header has nothing to check this enum against and 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 corresponding C enumerators carry. [("flag-", "FLAG_")] checks [flag-vsync-hint] against [FLAG_VSYNC_HINT]. @@ -638,13 +647,14 @@ type config = { [GESTURE_DOUBLETAP] against a member the package spells [double-tap]. It also brings a name under the check that no prefix rule covers. *) enum_prefixes : (string * string) list; + enum_flan_prefixes : (string * string) list; const_prefixes : (string * string) list; constants : (string * string) list; } let no_config = - { excludes = []; renames = []; enum_prefixes = []; const_prefixes = []; - constants = [] } + { excludes = []; renames = []; enum_prefixes = []; enum_flan_prefixes = []; + const_prefixes = []; constants = [] } (* [*] 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 @@ -679,7 +689,8 @@ let read_config path : config = else begin let ch = open_in path 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 = match input_line ch with | line -> @@ -694,15 +705,29 @@ let read_config path : config = | [ "exclude"; p ] -> excludes := p :: !excludes | [ "name"; sym; flan ] -> renames := (sym, flan) :: !renames | [ "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 | [ "constant"; flan; c ] -> constants := (flan, c) :: !constants | _ -> close_in ch; fail (Loc.make path n 0) "a line here is `exclude `, `name `, `enum `, `const \ - ` or `constant `, \ - and this is neither: %s" t + symbol> `, `enum \ + []`, `const ` or \ + `constant `, and this is neither: %s" t end; go (n + 1) | exception End_of_file -> () @@ -711,6 +736,7 @@ let read_config path : config = close_in ch; { excludes = List.rev !excludes; renames = List.rev !renames; enum_prefixes = List.rev !enum_prefixes; + enum_flan_prefixes = List.rev !enum_flan_prefixes; const_prefixes = List.rev !const_prefixes; constants = List.rev !constants } end @@ -1209,15 +1235,31 @@ let check_constants ~config | Some "-" -> Hashtbl.replace used ("enum:" ^ ename) () | Some prefix -> 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 (fun (m, v) -> let flan = ename ^ "/" ^ m in - let cname = - match List.assoc_opt flan explicit with - | Some c -> c - | None -> prefix ^ screaming m - in - compare_one flan v cname) + match List.assoc_opt flan explicit with + | Some c -> compare_one flan v c + | None -> + (match fprefix with + | 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) enums; (* Plain constants, and only the ones a rule or a [constant] line reaches. *) diff --git a/test/test_flan.ml b/test/test_flan.ml index 31b3572..67e8d85 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -2890,6 +2890,16 @@ let () = rejects_check "a keyword that is not a member" "(defenum Key [space 32]) (defn g [k Key] ()) (defn f [] () (g :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" "(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 @@ -3880,6 +3890,17 @@ let () = (match Cimport.read_config (config_file "rename Foo bar\n") with | _ -> false | 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, 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" | _ -> 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 silently unchecked, which is the same hole one level up. *) check "a defenum with no enum line is itself a finding" diff --git a/vendor/raylib/bindings b/vendor/raylib/bindings index 549ffb7..a896d39 100644 --- a/vendor/raylib/bindings +++ b/vendor/raylib/bindings @@ -194,7 +194,7 @@ name ImageDrawTriangleStrip image-draw-triangle-strip-raw # ── What the package's constants are called in C ──────────────────── # -# enum every member of that defenum +# enum [] every member of that defenum # const every defconst whose name starts so # constant 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 # 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 # 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. -enum Key KEY_ -enum MouseButton MOUSE_BUTTON_ +enum Key KEY_ key- +enum MouseButton MOUSE_BUTTON_ mouse- enum TraceLogLevel LOG_ enum CameraProjection CAMERA_ enum CameraMode CAMERA_ diff --git a/vendor/raylib/raylib.flan b/vendor/raylib/raylib.flan index c4f1189..f365d08 100644 --- a/vendor/raylib/raylib.flan +++ b/vendor/raylib/raylib.flan @@ -70,22 +70,32 @@ ;; 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. +;; +;; 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 - [space 32 apostrophe 39 comma 44 minus 45 period 46 slash 47 - zero 48 one 49 two 50 three 51 four 52 - five 53 six 54 seven 55 eight 56 nine 57 - a 65 b 66 c 67 d 68 e 69 f 70 g 71 h 72 i 73 - j 74 k 75 l 76 m 77 n 78 o 79 p 80 q 81 r 82 - s 83 t 84 u 85 v 86 w 87 x 88 y 89 z 90 - escape 256 enter 257 tab 258 backspace 259 - right 262 left 263 down 264 up 265 - left-shift 340 + [key-space 32 key-apostrophe 39 key-comma 44 key-minus 45 + key-period 46 key-slash 47 + key-zero 48 key-one 49 key-two 50 key-three 51 key-four 52 + key-five 53 key-six 54 key-seven 55 key-eight 56 key-nine 57 + key-a 65 key-b 66 key-c 67 key-d 68 key-e 69 key-f 70 key-g 71 + key-h 72 key-i 73 key-j 74 key-k 75 key-l 76 key-m 77 key-n 78 + key-o 79 key-p 80 key-q 81 key-r 82 key-s 83 key-t 84 key-u 85 + key-v 86 key-w 87 key-x 88 key-y 89 key-z 90 + 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 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 - [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 [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 ;; left to the generated half because the Flan face is the difference: raylib ;; 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 -;; a typo. `:null` is how a program says "no key does that" and takes the +;; takes an i32, where this one takes a Key and so accepts :key-escape and refuses +;; a typo. `:key-null` is how a program says "no key does that" and takes the ;; close over itself. (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 ;; 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 ;; 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 @@ -192,7 +202,7 @@ ;; 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 ;; 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. (defn get-key-pressed [] (Option i32) (let [k (get-key-pressed-raw)]