A begin/end pair that cannot come apart, and what it still cannot promise
vendor/raylib/modes.flan: five macros over the five pairs the package binds -- with-drawing, with-mode-2d, with-mode-3d, with-texture-mode, with-scissor-mode. A second file with no declare-c in it, split out on vector.flan's reasoning: raylib.flan is the package's statement about C and nothing here names C, so nothing here can be made wrong by raylib changing. Each expands to (do (begin-... args) body... (end-...)) -- the calls the author used to type, in the order they typed them. No let, no gensym: nothing binds a name, so there is nothing for a caller's name to collide with. What it removes is the End* that is missing, wrong, or no longer beside its Begin*. What it cannot remove is a body leaving through the unwind path: a return or an invoke-restart skips the rest of the do and the End* with it. defer is the obvious fix and is refused inside a loop body, which is where a pair always lives -- checked, not assumed. So sand.flan's discipline stays: keep the restart boundary outside the pair. 35 call sites converted across examples/ and sand.flan. The one left is core-scissor-test.flan, whose Begin and End sit in two separate `when`s with the drawing between them -- a conditional pair is a shape a bracketing macro cannot express. test/programs/rl-with.flan covers with-scissor-mode, which no example can, with a frame function unreachable from main so it needs no libraylib; rl-with-reject.flan is the arity half.
This commit is contained in:
parent
e903e220fb
commit
25f56c24a8
59
BUILT.md
59
BUILT.md
@ -5229,3 +5229,62 @@ route neither covers, and says so rather than claiming to be a design.
|
||||
|
||||
`test/programs/generics.flan` runs one written body at two key types; `test/programs/generic-map-reject.flan` is
|
||||
the other half, a call site at `f64` refused against the clause.
|
||||
|
||||
## The begin/end pairs are macros now, and the pair is all they can promise
|
||||
|
||||
`vendor/raylib/modes.flan` — a second file in the raylib package with no `declare-c` in it, split out on
|
||||
`vector.flan`'s reasoning exactly: `raylib.flan` is the package's statement about C and the file the header check
|
||||
reads signatures out of, and nothing here can be made wrong by raylib changing because nothing here names C.
|
||||
|
||||
Five macros, one per pair the package binds: `with-drawing`, `with-mode-2d`, `with-mode-3d`, `with-texture-mode`,
|
||||
`with-scissor-mode`. Each expands to `(do (begin-… args) body… (end-…))` — the calls the author used to type, in the
|
||||
order they used to type them, with the argument evaluated once where it always was. No `let` and no `gensym`
|
||||
anywhere: nothing binds a name, so there is no name a caller's could collide with, and introducing one in a
|
||||
deliberately non-hygienic macro that does not need it is a step backwards. NEXT.md called these "three-line macros
|
||||
once the expander lands" when it rejected `unwind-protect`, and they are.
|
||||
|
||||
They arrive qualified, the rule every declaration in a package follows: the importer writes `(rl/with-drawing …)`
|
||||
and a bare `(with-drawing …)` is an unknown name. The expansions name `begin-drawing` and friends unqualified and
|
||||
the expander qualifies them on the way out — `test/programs/pkgs/mac/mac.flan` is that rule's worked example, and
|
||||
these are its first non-test use.
|
||||
|
||||
**What the macro removes is not what a reader assumes it removes.** It removes *the `End*` is missing, or is the
|
||||
wrong one, or drifted away from its `Begin*` during an edit* — which is the whole of the class that actually bites,
|
||||
and `EndMode2D` closing a `BeginMode3D` type-checks fine and corrupts the matrix stack. It does **not** remove a
|
||||
body that leaves through the unwind path: a `return`, or an `invoke-restart` reaching an enclosing `restart-case`,
|
||||
skips the rest of the `do` and the `End*` with it, exactly as it skipped a hand-written one.
|
||||
|
||||
`defer` is the obvious fix and is not available. It is a compile-time construct — the forms are copied into the
|
||||
function's exit paths — so it is refused inside a loop body, and a begin/end pair lives inside the game loop
|
||||
essentially always. The refusal was checked rather than assumed:
|
||||
|
||||
```
|
||||
defer is not allowed inside a loop body — a defer is copied into every exit path of the function, so it
|
||||
always registers and always runs at function exit.
|
||||
```
|
||||
|
||||
Were it permitted there it would be wrong in the worse direction: one `EndDrawing` at function exit for N
|
||||
`BeginDrawing`s, which is the silent-wrongness class the refusal exists for. A macro expanding to a `defer` would
|
||||
therefore compile at a function body's top level and be refused in the one place anybody writes it. So the
|
||||
discipline `sand.flan` already wrote down stays the discipline — **keep the restart boundary outside the pair**, so
|
||||
choosing `continue` for a frame abandons the update and still reaches the drawing. spec-conditions §5 is the rule
|
||||
behind that: a transfer runs the intervening frames' `defer`s and moves control, and there are none here to run.
|
||||
`sand.flan`'s comment says so at the loop, and the macro's header says so at the definition.
|
||||
|
||||
**35 call sites converted** across `examples/` and `sand.flan`: 26 `with-drawing`, 5 `with-texture-mode`, 3
|
||||
`with-mode-3d`, 1 `with-mode-2d`. Every begin/end pair in the corpus except one — `examples/core-scissor-test.flan`
|
||||
turns its scissor on and off from a flag, so its `Begin` and its `End` sit in two separate `when`s with the drawing
|
||||
between them and there is no body to wrap. That site is left hand-written, and it is the honest illustration of the
|
||||
macro's limit rather than an oversight: a conditional pair is a shape a bracketing macro cannot express without
|
||||
duplicating the body.
|
||||
|
||||
`sand.flan` converted its draw pair and stays at parity — `lisp/sand.lisp` writes `rl:with-drawing` there, which is
|
||||
what makes it a port of the reference version rather than an addition to it. Its header note "no user-written
|
||||
macros" now reads "no macros of its own", because `rl/with-drawing` is the binding package's, as in the Lisp.
|
||||
|
||||
`with-scissor-mode` is the one no example exercises, so `test/programs/rl-with.flan` does: all five expanded in a
|
||||
`frame` function deliberately **unreachable from `main`**, which is what lets the case run with no libraylib at all
|
||||
— `reach.ml` answers the link from the checked program, and `check` still walks every line. Nothing there draws,
|
||||
because nothing headless can. `rl-with-reject.flan` is the arity half: a macro cannot signal while it expands, so
|
||||
the prelude's idiom is to answer a symbol that is not a name and reads as the sentence the caller needs
|
||||
(`with-mode-2d-takes-a-camera-and-a-body`), with the expander's note beneath it naming the macro at the call site.
|
||||
|
||||
@ -99,14 +99,13 @@
|
||||
(set (.rotation camera) 0.0))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
;; Everything between these two is in world space and goes through the
|
||||
;; camera. The ground and the crosshair lines are drawn far outside the
|
||||
;; window on purpose: at zoom 0.1 they still have to reach the edges.
|
||||
(rl/begin-mode-2d camera)
|
||||
|
||||
;; Everything inside with-mode-2d is in world space and goes through
|
||||
;; the camera. The ground and the crosshair lines are drawn far outside
|
||||
;; the window on purpose: at zoom 0.1 they still have to reach the edges.
|
||||
(rl/with-mode-2d camera
|
||||
(rl/draw-rectangle -6000 320 13000 8000 rl/darkgray)
|
||||
|
||||
(dotimes [i max-buildings]
|
||||
@ -117,9 +116,7 @@
|
||||
(rl/draw-line (i32 (.x (.target camera))) (* screen-height -10)
|
||||
(i32 (.x (.target camera))) (* screen-height 10) rl/green)
|
||||
(rl/draw-line (* screen-width -10) (i32 (.y (.target camera)))
|
||||
(* screen-width 10) (i32 (.y (.target camera))) rl/green)
|
||||
|
||||
(rl/end-mode-2d)
|
||||
(* screen-width 10) (i32 (.y (.target camera))) rl/green))
|
||||
|
||||
;; And everything after it is in screen space again — the frame drawn
|
||||
;; around the window is how the example shows the difference.
|
||||
@ -137,6 +134,4 @@
|
||||
(rl/draw-text "- Right/Left to move Offset" 40 40 10 rl/darkgray)
|
||||
(rl/draw-text "- Mouse Wheel to Zoom in-out" 40 60 10 rl/darkgray)
|
||||
(rl/draw-text "- A / S to Rotate" 40 80 10 rl/darkgray)
|
||||
(rl/draw-text "- R to reset Zoom and Rotation" 40 100 10 rl/darkgray)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-text "- R to reset Zoom and Rotation" 40 100 10 rl/darkgray))))
|
||||
|
||||
@ -113,11 +113,10 @@
|
||||
(set (.hit collision) false)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/begin-mode-3d camera)
|
||||
|
||||
(rl/with-mode-3d camera
|
||||
(if (.hit collision)
|
||||
(do
|
||||
(rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size)
|
||||
@ -138,9 +137,7 @@
|
||||
;; raylib draws the ray a thousand units long, so it reads as a line
|
||||
;; crossing the scene rather than as a segment with an end.
|
||||
(rl/draw-ray ray rl/maroon)
|
||||
(rl/draw-grid 10 1.0)
|
||||
|
||||
(rl/end-mode-3d)
|
||||
(rl/draw-grid 10 1.0))
|
||||
|
||||
(rl/draw-text "Try clicking on the box with your mouse!" 240 10 20
|
||||
rl/darkgray)
|
||||
@ -153,6 +150,4 @@
|
||||
(rl/draw-text "Right click mouse to toggle camera controls" 10 430 10
|
||||
rl/gray)
|
||||
|
||||
(rl/draw-fps 10 10)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-fps 10 10))))
|
||||
|
||||
@ -25,8 +25,7 @@
|
||||
(rl/set-target-fps 60)
|
||||
|
||||
(until (rl/window-should-close?)
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
(rl/draw-text "Congrats! You created your first window!" 190 200 20
|
||||
rl/lightgray)
|
||||
(rl/end-drawing)))
|
||||
rl/lightgray))))
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
(set (.x frame-circle) 0.0))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/draw-circle-v delta-circle circle-radius rl/red)
|
||||
@ -114,6 +114,4 @@
|
||||
10 70 20 rl/darkgray)
|
||||
|
||||
(rl/draw-text "FUNC: x += GetFrameTime()*speed" 10 110 20 rl/red)
|
||||
(rl/draw-text "FUNC: x += speed" 10 260 20 rl/blue)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-text "FUNC: x += speed" 10 260 20 rl/blue))))
|
||||
|
||||
@ -183,7 +183,7 @@
|
||||
.width 75.0 .height 24.0})]
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(if (rl/gamepad-available? gamepad)
|
||||
@ -275,6 +275,4 @@
|
||||
(+ x (d/draw-int gamepad x 10 10 rl/gray))
|
||||
10 10 rl/gray))
|
||||
(rl/draw-text "left/right arrows select another pad" 10 30 10
|
||||
rl/lightgray)))
|
||||
|
||||
(rl/end-drawing))))
|
||||
rl/lightgray)))))))
|
||||
|
||||
@ -328,7 +328,7 @@
|
||||
(set (at touch-positions i) (rl/get-touch-position i)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/draw-text "*" 165 12 10 rl/black)
|
||||
@ -359,6 +359,4 @@
|
||||
(if (= g :pinch-out) 8.0 12.0) gesture-color)))
|
||||
(let [m (rl/get-mouse-position)]
|
||||
(rl/draw-circle-v m 35.0 (rl/fade gesture-color 0.5))
|
||||
(rl/draw-circle-v m 5.0 gesture-color))))
|
||||
|
||||
(rl/end-drawing)))))
|
||||
(rl/draw-circle-v m 5.0 gesture-color)))))))))
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
(set gestures-count 0)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/draw-rectangle-rec touch-area rl/gray)
|
||||
@ -105,6 +105,4 @@
|
||||
(rl/draw-text "DETECTED GESTURES" 50 15 10 rl/gray)
|
||||
|
||||
(unless (= current-gesture :none)
|
||||
(rl/draw-circle-v touch 30.0 rl/maroon))
|
||||
|
||||
(rl/end-drawing)))))
|
||||
(rl/draw-circle-v touch 30.0 rl/maroon)))))))
|
||||
|
||||
@ -40,8 +40,7 @@
|
||||
(when (rl/key-down? :down) (set (.y ball) (+ (.y ball) 2.0)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
(rl/draw-text "move the ball with arrow keys" 10 10 20 rl/darkgray)
|
||||
(rl/draw-circle-v ball 50.0 rl/maroon)
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-circle-v ball 50.0 rl/maroon))))
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
(set box-y (- box-y (i32 (* (rl/get-mouse-wheel-move) (f32 scroll-speed)))))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
(rl/draw-rectangle (- (/ screen-width 2) 40) box-y 80 80 rl/maroon)
|
||||
(rl/draw-text "Use mouse wheel to move the cube up and down!" 10 10 20
|
||||
@ -51,5 +51,4 @@
|
||||
;; and then the number starting where it ended.
|
||||
(rl/draw-text "Box position Y: " 10 40 20 rl/lightgray)
|
||||
(d/draw-int-padded box-y 3 (+ 10 (rl/measure-text "Box position Y: " 20))
|
||||
40 20 rl/lightgray)
|
||||
(rl/end-drawing)))
|
||||
40 20 rl/lightgray))))
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
:else ball-color))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
(rl/draw-circle-v ball 40.0 ball-color)
|
||||
(rl/draw-text "move ball with mouse and click mouse button to change color"
|
||||
@ -57,5 +57,4 @@
|
||||
(rl/draw-text "Press 'H' to toggle cursor visibility" 10 30 20 rl/darkgray)
|
||||
(if (rl/cursor-hidden?)
|
||||
(rl/draw-text "CURSOR HIDDEN" 20 60 20 rl/red)
|
||||
(rl/draw-text "CURSOR VISIBLE" 20 60 20 rl/lime))
|
||||
(rl/end-drawing))))
|
||||
(rl/draw-text "CURSOR VISIBLE" 20 60 20 rl/lime))))))
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
(set (at touch-positions i) (rl/get-touch-position i)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(dotimes [i count]
|
||||
@ -65,6 +65,4 @@
|
||||
rl/black))))
|
||||
|
||||
(rl/draw-text "touch the screen at multiple locations to get multiple balls"
|
||||
10 10 20 rl/darkgray)
|
||||
|
||||
(rl/end-drawing))))
|
||||
10 10 20 rl/darkgray)))))
|
||||
|
||||
@ -172,7 +172,7 @@
|
||||
(move-player pressed (rl/get-frame-time))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/draw-circle-v player 50.0 rl/maroon)
|
||||
@ -190,6 +190,4 @@
|
||||
(rl/draw-text "button: " 10 34 20 rl/lightgray)
|
||||
(rl/draw-text (string (i64->bytes (i64 pressed)))
|
||||
(+ 10 (rl/measure-text "button: " 20)) 34 20
|
||||
rl/lightgray)
|
||||
|
||||
(rl/end-drawing))))
|
||||
rl/lightgray)))))
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
(set (.y scissor) (- (f32 (rl/get-mouse-y)) (/ (.height scissor) 2.0)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(when scissor-mode
|
||||
@ -67,6 +67,4 @@
|
||||
(when scissor-mode (rl/end-scissor-mode))
|
||||
|
||||
(rl/draw-rectangle-lines-ex scissor 1.0 rl/black)
|
||||
(rl/draw-text "Press S to toggle scissor test" 10 10 20 rl/black)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-text "Press S to toggle scissor test" 10 10 20 rl/black))))
|
||||
|
||||
@ -127,7 +127,7 @@
|
||||
(set (.y ball-speed) (* (.y ball-speed) -1.0)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
|
||||
;; A transparent framebuffer wants a transparent clear; anything else
|
||||
;; paints over the desktop the flag exists to show.
|
||||
@ -174,6 +174,4 @@
|
||||
10 300 10 rl/gray)
|
||||
(draw-flag "FLAG_WINDOW_HIGHDPI:" rl/flag-window-highdpi 320)
|
||||
(draw-flag "FLAG_WINDOW_TRANSPARENT:" rl/flag-window-transparent 340)
|
||||
(draw-flag "FLAG_MSAA_4X_HINT:" rl/flag-msaa-4x-hint 360)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(draw-flag "FLAG_MSAA_4X_HINT:" rl/flag-msaa-4x-hint 360))))
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
(when (rl/key-pressed? :n) (set exit-requested false))))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(if exit-requested
|
||||
@ -63,6 +63,4 @@
|
||||
(rl/draw-text "Are you sure you want to exit program? [Y/N]" 40 180 30
|
||||
rl/white))
|
||||
(rl/draw-text "Try to close the window to get confirmation message!"
|
||||
120 200 20 rl/lightgray))
|
||||
|
||||
(rl/end-drawing)))
|
||||
120 200 20 rl/lightgray)))))
|
||||
|
||||
@ -80,14 +80,13 @@
|
||||
camera)]
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/begin-mode-3d camera)
|
||||
(rl/with-mode-3d camera
|
||||
(rl/draw-cube cube 2.0 2.0 2.0 rl/red)
|
||||
(rl/draw-cube-wires cube 2.0 2.0 2.0 rl/maroon)
|
||||
(rl/draw-grid 10 1.0)
|
||||
(rl/end-mode-3d)
|
||||
(rl/draw-grid 10 1.0))
|
||||
|
||||
;; And the 2D half, drawn after end-mode-3d and therefore on top of the
|
||||
;; cube whatever the depth buffer says — which is the second half of
|
||||
@ -109,6 +108,4 @@
|
||||
(rl/draw-text "]" x 10 20 rl/lime))
|
||||
|
||||
(rl/draw-text "Text 2d should be always on top of the cube" 10 40 20
|
||||
rl/gray)
|
||||
|
||||
(rl/end-drawing))))
|
||||
rl/gray)))))
|
||||
|
||||
@ -106,11 +106,10 @@
|
||||
(set player-color (if hit rl/red rl/green)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/begin-mode-3d camera)
|
||||
|
||||
(rl/with-mode-3d camera
|
||||
(rl/draw-cube enemy-box-pos (.x enemy-box-size) (.y enemy-box-size)
|
||||
(.z enemy-box-size) rl/gray)
|
||||
(rl/draw-cube-wires enemy-box-pos (.x enemy-box-size) (.y enemy-box-size)
|
||||
@ -123,12 +122,8 @@
|
||||
|
||||
(rl/draw-cube-v player-position player-size player-color)
|
||||
|
||||
(rl/draw-grid 10 1.0)
|
||||
|
||||
(rl/end-mode-3d)
|
||||
(rl/draw-grid 10 1.0))
|
||||
|
||||
(rl/draw-text "Move player with arrow keys to collide" 220 40 20 rl/gray)
|
||||
|
||||
(rl/draw-fps 10 10)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-fps 10 10))))
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
(set rotation (+ rotation 0.2))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/draw-text "some basic shapes available on raylib" 20 20 20 rl/darkgray)
|
||||
@ -97,6 +97,4 @@
|
||||
;; The C draws every LINES-based shape together so raylib can batch them
|
||||
;; into one pass. This last line is part of that comment and not an
|
||||
;; afterthought, so it stays where the C has it.
|
||||
(rl/draw-line 18 42 (- screen-width 18) 42 rl/black)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-line 18 42 (- screen-width 18) 42 rl/black))))
|
||||
|
||||
@ -93,7 +93,7 @@
|
||||
(when (rl/key-pressed? :space) (set paused (not paused)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/draw-rectangle 0 0 screen-width screen-upper-limit
|
||||
@ -124,6 +124,4 @@
|
||||
(rl/draw-text "Press SPACE to PAUSE/RESUME" 20 (- screen-height 35) 20
|
||||
rl/lightgray)
|
||||
|
||||
(rl/draw-fps 10 10)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-fps 10 10))))
|
||||
|
||||
@ -95,7 +95,7 @@
|
||||
(set iris-right (track mouse sclera-right)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
;; White of the eye, iris, pupil — in that order, each over the last.
|
||||
@ -107,6 +107,4 @@
|
||||
(rl/draw-circle-v iris-right iris-radius rl/darkgreen)
|
||||
(rl/draw-circle-v iris-right 10.0 rl/black)
|
||||
|
||||
(rl/draw-fps 10 10)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-fps 10 10))))
|
||||
|
||||
@ -112,7 +112,7 @@
|
||||
(set frames-counter 0))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/draw-text "PLACE MOUSE OVER INPUT BOX!" 240 140 20 rl/gray)
|
||||
@ -146,6 +146,4 @@
|
||||
(+ (i32 (.x text-box)) 8 (rl/measure-text typed 40))
|
||||
(+ (i32 (.y text-box)) 12) 40 rl/maroon))
|
||||
(rl/draw-text "Press BACKSPACE to delete chars..." 230 300 20
|
||||
rl/gray))))
|
||||
|
||||
(rl/end-drawing)))
|
||||
rl/gray)))))))
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
(when (rl/key-pressed? :enter) (set frames-counter 0))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
;; One character every ten frames. The clamp is the whole difference from
|
||||
@ -71,6 +71,4 @@
|
||||
(rl/draw-text (string (slice b 0 n)) 210 160 20 rl/maroon))
|
||||
|
||||
(rl/draw-text "PRESS [ENTER] to RESTART!" 240 260 20 rl/lightgray)
|
||||
(rl/draw-text "HOLD [SPACE] to SPEED UP!" 239 300 20 rl/lightgray)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-text "HOLD [SPACE] to SPEED UP!" 239 300 20 rl/lightgray))))
|
||||
|
||||
@ -138,7 +138,7 @@
|
||||
;; Draw the fog into its own little target first, at one pixel per tile.
|
||||
;; blank is alpha 0, so a tile that is neither unseen nor remembered
|
||||
;; leaves nothing behind and the map below shows through unmodified.
|
||||
(rl/begin-texture-mode fog-of-war)
|
||||
(rl/with-texture-mode fog-of-war
|
||||
(rl/clear-background rl/blank)
|
||||
(dotimes [y tiles-y]
|
||||
(dotimes [x tiles-x]
|
||||
@ -146,10 +146,9 @@
|
||||
(let [f (at tile-fog (+ (* y tiles-x) x))]
|
||||
(when (!= f 1)
|
||||
(rl/draw-rectangle x y 1 1
|
||||
(if (= f 0) rl/black (rl/fade rl/black 0.8)))))))
|
||||
(rl/end-texture-mode)
|
||||
(if (= f 0) rl/black (rl/fade rl/black 0.8))))))))
|
||||
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
;; The map itself, in full size.
|
||||
@ -192,6 +191,4 @@
|
||||
(set x (+ x (d/draw-int player-tile-y x 10 20 rl/raywhite)))
|
||||
(d/draw-piece "]" x 10 20 rl/raywhite))
|
||||
|
||||
(rl/draw-text "ARROW KEYS to move" 10 (- screen-height 25) 20 rl/raywhite)
|
||||
|
||||
(rl/end-drawing)))
|
||||
(rl/draw-text "ARROW KEYS to move" 10 (- screen-height 25) 20 rl/raywhite))))
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
(set current-texture (% (+ current-texture 1) num-textures)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/draw-texture (at textures current-texture) 0 0 rl/white)
|
||||
@ -125,6 +125,4 @@
|
||||
(= current-texture 5) (rl/draw-text "CHECKED" 680 10 20 rl/raywhite)
|
||||
(= current-texture 6) (rl/draw-text "WHITE NOISE" 640 10 20 rl/red)
|
||||
(= current-texture 7) (rl/draw-text "PERLIN NOISE" 640 10 20 rl/red)
|
||||
:else (rl/draw-text "CELLULAR" 670 10 20 rl/raywhite))
|
||||
|
||||
(rl/end-drawing)))
|
||||
:else (rl/draw-text "CELLULAR" 670 10 20 rl/raywhite)))))
|
||||
|
||||
@ -114,9 +114,8 @@
|
||||
(set target (rl/load-render-texture screen-width screen-height))
|
||||
(defer (rl/unload-render-texture target))
|
||||
|
||||
(rl/begin-texture-mode target)
|
||||
(rl/clear-background (at colors 0))
|
||||
(rl/end-texture-mode)
|
||||
(rl/with-texture-mode target
|
||||
(rl/clear-background (at colors 0)))
|
||||
|
||||
;; 120 and not 60: the stroke is a circle stamped at the mouse position once
|
||||
;; per frame, so the gaps between stamps during a fast drag are a function
|
||||
@ -148,21 +147,19 @@
|
||||
2.0 50.0))
|
||||
|
||||
(when (rl/key-pressed? :c)
|
||||
(rl/begin-texture-mode target)
|
||||
(rl/clear-background (at colors 0))
|
||||
(rl/end-texture-mode))
|
||||
(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)
|
||||
(= (rl/get-gesture-detected) :drag))
|
||||
(rl/begin-texture-mode target)
|
||||
(rl/with-texture-mode target
|
||||
;; Above y=50 is the palette strip, and a stroke there would paint
|
||||
;; under the toolbar where it could never be seen.
|
||||
(when (> (.y mouse-pos) 50.0)
|
||||
(rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
|
||||
brush-size (at colors color-selected)))
|
||||
(rl/end-texture-mode))
|
||||
brush-size (at colors color-selected)))))
|
||||
|
||||
;; Right button erases, which is painting in the clear colour. The
|
||||
;; selected swatch is parked while the button is held so the toolbar
|
||||
@ -173,11 +170,10 @@
|
||||
(set color-selected-prev color-selected)
|
||||
(set color-selected 0))
|
||||
(set mouse-was-pressed true)
|
||||
(rl/begin-texture-mode target)
|
||||
(rl/with-texture-mode target
|
||||
(when (> (.y mouse-pos) 50.0)
|
||||
(rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
|
||||
brush-size (at colors 0)))
|
||||
(rl/end-texture-mode))
|
||||
brush-size (at colors 0)))))
|
||||
(when (and (rl/mouse-button-released? :right) mouse-was-pressed)
|
||||
(set color-selected color-selected-prev)
|
||||
(set mouse-was-pressed false)))
|
||||
@ -201,7 +197,7 @@
|
||||
(set save-message-counter 0)))
|
||||
|
||||
;; Draw
|
||||
(rl/begin-drawing)
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
;; The canvas, flipped by the negative source height.
|
||||
@ -250,6 +246,4 @@
|
||||
(rl/fade rl/raywhite 0.8))
|
||||
(rl/draw-rectangle 0 150 (rl/get-screen-width) 80 rl/black)
|
||||
(rl/draw-text "IMAGE SAVED: my_amazing_texture_painting.png"
|
||||
150 180 20 rl/raywhite))
|
||||
|
||||
(rl/end-drawing))))
|
||||
150 180 20 rl/raywhite))))))
|
||||
|
||||
15
sand.flan
15
sand.flan
@ -15,7 +15,10 @@
|
||||
;;;; offers.
|
||||
;;;;
|
||||
;;;; Note what it deliberately does not use: no Vec, no Map, no generics, no
|
||||
;;;; user-written macros, no allocator other than the stack and static storage.
|
||||
;;;; macros of its own, no allocator other than the stack and static storage.
|
||||
;;;; It does call rl/with-drawing, which is the raylib package's macro over
|
||||
;;;; the BeginDrawing/EndDrawing pair — the parity rule is what the reference
|
||||
;;;; versions do, and lisp/sand.lisp writes rl:with-drawing there.
|
||||
;;;;
|
||||
;;;; Notation reminders (see plan.org and spec-memory.md):
|
||||
;;;; [n T] fixed array, length n, element T — a VALUE, copies
|
||||
@ -191,11 +194,13 @@
|
||||
;; leaves one CL-style escape hatch: choose `continue' in the editor to
|
||||
;; abandon this frame and return to the next one with the game still live.
|
||||
;; Keep drawing outside it. Skipping between BeginDrawing and EndDrawing
|
||||
;; would leave raylib's frame unbalanced.
|
||||
;; would leave raylib's frame unbalanced, and rl/with-drawing does not
|
||||
;; change that — it guarantees the two calls stay together and stay
|
||||
;; matched, not that a transfer out of the body reaches the second one.
|
||||
;; The restart being out here is still what makes `continue' safe.
|
||||
(restart-case
|
||||
(do (agent/poll)
|
||||
(game-update))
|
||||
(continue [] (do)))
|
||||
(rl/begin-drawing)
|
||||
(game-draw)
|
||||
(rl/end-drawing)))
|
||||
(rl/with-drawing
|
||||
(game-draw))))
|
||||
|
||||
16
test/programs/rl-with-reject.flan
Normal file
16
test/programs/rl-with-reject.flan
Normal file
@ -0,0 +1,16 @@
|
||||
;;;; with-mode-2d without a camera.
|
||||
;;;;
|
||||
;;;; The prelude's idiom for a macro's own arity check, which clamp and unless
|
||||
;;;; and into all use: answer a symbol that is not a name, spelled as the
|
||||
;;;; sentence the author needed to read. There is no way for a macro to signal
|
||||
;;;; at expansion time, so the message travels as the unknown function — and
|
||||
;;;; what makes it land usefully is the note the expander adds beneath it,
|
||||
;;;; naming the macro and the call site.
|
||||
;;;;
|
||||
;;;; Being refused is the whole test; this is never built.
|
||||
|
||||
(import rl "vendor:raylib")
|
||||
|
||||
(defn main [] i32
|
||||
(rl/with-mode-2d)
|
||||
0)
|
||||
43
test/programs/rl-with.flan
Normal file
43
test/programs/rl-with.flan
Normal file
@ -0,0 +1,43 @@
|
||||
;;;; The raylib package's five begin/end macros, expanded.
|
||||
;;;;
|
||||
;;;; They cannot be *run* here. Every one of them brackets calls that need a
|
||||
;;;; window and a GL context, so a headless case can only assert what the
|
||||
;;;; expansion is, not what it draws — which is the whole of what a macro
|
||||
;;;; over a pair can get wrong. The four examples/ uses of with-drawing and
|
||||
;;;; friends already prove they compile; what this adds is with-scissor-mode,
|
||||
;;;; which no example can use (examples/core-scissor-test.flan turns its
|
||||
;;;; scissor on and off from a flag, so its Begin and its End sit inside two
|
||||
;;;; separate `when`s and there is no body to wrap). The arity refusals are
|
||||
;;;; rl-with-reject.flan, beside this.
|
||||
;;;;
|
||||
;;;; [frame] is deliberately not reached from [main]. lib/reach.ml answers the
|
||||
;;;; link from the checked program, so an unreachable frame means this case
|
||||
;;;; needs no libraylib to run and goes in the suite unconditionally — while
|
||||
;;;; [check] still walks every line of it, which is where a wrong expansion
|
||||
;;;; would show.
|
||||
|
||||
(import rl "vendor:raylib")
|
||||
|
||||
(defn frame [cam rl/Camera2D cam3 rl/Camera3D target rl/RenderTexture2D] ()
|
||||
(rl/with-drawing
|
||||
(rl/clear-background rl/raywhite)
|
||||
|
||||
(rl/with-mode-2d cam
|
||||
(rl/draw-rectangle 0 0 10 10 rl/red))
|
||||
|
||||
(rl/with-mode-3d cam3
|
||||
(rl/draw-fps 0 0))
|
||||
|
||||
(rl/with-scissor-mode 10 10 100 100
|
||||
(rl/draw-rectangle 0 0 320 240 rl/blue)
|
||||
(rl/draw-text "clipped" 20 20 10 rl/black))
|
||||
|
||||
(rl/draw-fps 10 10))
|
||||
|
||||
;; Off-screen, outside the frame, which is where a render texture belongs.
|
||||
(rl/with-texture-mode target
|
||||
(rl/clear-background rl/blank)))
|
||||
|
||||
(defn main [] i32
|
||||
(println "expanded")
|
||||
0)
|
||||
@ -884,6 +884,16 @@ let () =
|
||||
outputs ~opt:"-O0" "raylib, bindings generated from the header, -O0"
|
||||
"programs/raylib-imported.flan" out;
|
||||
|
||||
(* The raylib package's begin/end macros — vendor/raylib/modes.flan.
|
||||
Nothing here draws: every one of the five brackets calls that want a
|
||||
window and a GL context, so what can be asserted headless is that the
|
||||
expansion compiles, which is the whole of what a macro over a pair can
|
||||
get wrong. The program keeps the frame function unreachable from main
|
||||
on purpose, so reach.ml links no libraylib and this runs unconditionally
|
||||
alongside the cases that need one. See the file's header. *)
|
||||
outputs "raylib begin/end macros expand" "programs/rl-with.flan"
|
||||
"expanded\n";
|
||||
|
||||
(* raylib's Image family, headless, and the strongest FFI case here: an
|
||||
Image is pixels in RAM, so raylib *computes* with it rather than
|
||||
storing and returning it.
|
||||
@ -1376,6 +1386,17 @@ let () =
|
||||
name m needle
|
||||
end
|
||||
in
|
||||
(* A package macro's arity check. A macro cannot signal while it expands,
|
||||
so the prelude's idiom — clamp's, unless's, into's — is to answer a
|
||||
symbol that is not a name and reads as the sentence the caller needs.
|
||||
What makes that usable rather than baffling is the expander's note
|
||||
beneath it, naming rl/with-mode-2d at the call site rather than
|
||||
pointing into the package — that half is rendering and is not asserted
|
||||
here. *)
|
||||
refuses "with-mode-2d written without a camera"
|
||||
"programs/rl-with-reject.flan"
|
||||
"with-mode-2d-takes-a-camera-and-a-body";
|
||||
|
||||
(* Visibility: main is not a name a package offers, and saying so is the
|
||||
point — "unknown name sand/main" would be true and useless. *)
|
||||
(* Generics, at the definition rather than at a call site. Both of these
|
||||
|
||||
125
vendor/raylib/modes.flan
vendored
Normal file
125
vendor/raylib/modes.flan
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
;;;; The begin/end pairs, as macros that cannot come apart.
|
||||
;;;;
|
||||
;;;; raylib has five pairs this package binds — drawing, 2D camera, 3D camera,
|
||||
;;;; render texture, scissor — and every one of them is the same hazard: two
|
||||
;;;; calls that have to bracket a body, written by hand, with nothing checking
|
||||
;;;; that the second one is there or that it is the matching one. An
|
||||
;;;; EndDrawing that is missing hangs the frame; an EndMode2D that should have
|
||||
;;;; been EndMode3D corrupts the matrix stack; a pair that drifted apart during
|
||||
;;;; an edit does either. NEXT.md put `unwind-protect` for this and rejected
|
||||
;;;; it, with the note that these are "three-line macros once the expander
|
||||
;;;; lands". They are, and this is them.
|
||||
;;;;
|
||||
;;;; (rl/with-drawing
|
||||
;;;; (rl/clear-background rl/raywhite)
|
||||
;;;; (rl/draw-fps 10 10))
|
||||
;;;;
|
||||
;;;; expands to exactly what the caller used to type, in the order they used
|
||||
;;;; to type it:
|
||||
;;;;
|
||||
;;;; (do (rl/begin-drawing)
|
||||
;;;; (rl/clear-background rl/raywhite)
|
||||
;;;; (rl/draw-fps 10 10)
|
||||
;;;; (rl/end-drawing))
|
||||
;;;;
|
||||
;;;; The argument-taking ones put the argument where it always went, evaluated
|
||||
;;;; once, in place. There is no `let` and no `gensym` anywhere below: nothing
|
||||
;;;; here binds a name, so there is no name for a caller's to collide with,
|
||||
;;;; and introducing one in a deliberately non-hygienic macro that does not
|
||||
;;;; need it would be a step backwards.
|
||||
;;;;
|
||||
;;;; ── What this does not fix, and why it cannot ───────────────────────
|
||||
;;;;
|
||||
;;;; The bug class removed is "the End* is missing, wrong, or no longer
|
||||
;;;; beside its Begin*". The bug class NOT removed is a body that leaves
|
||||
;;;; through the unwind path instead of the bottom: a `return`, or an
|
||||
;;;; `invoke-restart` reaching an enclosing `restart-case`. Both of those skip
|
||||
;;;; the rest of the `do` and the `End*` with it, exactly as they would skip a
|
||||
;;;; hand-written one.
|
||||
;;;;
|
||||
;;;; The obvious fix is `defer`, and it is not available here. `defer` is a
|
||||
;;;; compile-time construct — the forms are copied into the function's exit
|
||||
;;;; paths — so BUILT.md refuses it inside a loop or a branch, and a
|
||||
;;;; begin/end pair lives inside the game loop essentially always. Were it
|
||||
;;;; permitted there it would be wrong in the worse direction: one EndDrawing
|
||||
;;;; at function exit for N BeginDrawings. A macro expanding to a defer would
|
||||
;;;; therefore compile at a function body's top level and be refused in the
|
||||
;;;; one place anybody writes it, which is a worse thing to hand somebody than
|
||||
;;;; a macro that is honest about its extent.
|
||||
;;;;
|
||||
;;;; So the discipline sand.flan already writes down stays the discipline:
|
||||
;;;; keep the restart boundary OUTSIDE the pair, so choosing `continue` for a
|
||||
;;;; frame abandons the update and still reaches the drawing. spec-conditions
|
||||
;;;; §5 is the rule behind that — a transfer runs the intervening frames'
|
||||
;;;; defers and moves control, and there are no defers here to run.
|
||||
;;;;
|
||||
;;;; ── Why this is a file of its own ───────────────────────────────────
|
||||
;;;;
|
||||
;;;; vector.flan's reasoning, unchanged: the split is on `declare-c`.
|
||||
;;;; raylib.flan is the package's statement about C, it is the file the header
|
||||
;;;; check reads hand-written signatures out of, and a wrong line in it stops
|
||||
;;;; the build. There is not one `declare-c` below — every macro here expands
|
||||
;;;; into names raylib.flan already declares — so there is nothing for the
|
||||
;;;; header check to read and nothing raylib can make wrong. A package is a
|
||||
;;;; directory, so this is another .flan beside the others and is qualified
|
||||
;;;; `rl/` like the rest of it.
|
||||
;;;;
|
||||
;;;; The names come out qualified: the importer writes `(rl/with-drawing …)`
|
||||
;;;; and a bare `(with-drawing …)` is an unknown name there, the rule every
|
||||
;;;; declaration in this package follows. The expansions name this package's
|
||||
;;;; own functions unqualified and the expander qualifies them on the way out
|
||||
;;;; — see test/programs/pkgs/mac/mac.flan, which is that rule's worked
|
||||
;;;; example.
|
||||
;;;;
|
||||
;;;; Each macro answers the value of its `End*` call, which is (). A pair was
|
||||
;;;; never an expression worth reading anyway.
|
||||
|
||||
;; The frame. Everything drawn lands on the back buffer; end-drawing swaps it
|
||||
;; and waits out the frame time set by set-target-fps.
|
||||
(defmacro with-drawing [args]
|
||||
(if (< (len args) 1)
|
||||
`(with-drawing-takes-a-body)
|
||||
`(do (begin-drawing)
|
||||
~@args
|
||||
(end-drawing))))
|
||||
|
||||
;; The 2D camera. The argument is a Camera2D value, evaluated once where it
|
||||
;; always was. Remember that a fresh (Camera2D {}) has zoom 0.0 and is not
|
||||
;; usable as an identity — raylib.flan says so beside the struct.
|
||||
(defmacro with-mode-2d [args]
|
||||
(if (< (len args) 2)
|
||||
`(with-mode-2d-takes-a-camera-and-a-body)
|
||||
`(do (begin-mode-2d ~(at args 0))
|
||||
~@(form-rest args 1)
|
||||
(end-mode-2d))))
|
||||
|
||||
;; The 3D camera. Same shape, same argument-once rule, and the pair matters
|
||||
;; more here than anywhere: ending a 3D mode with end-mode-2d type-checks
|
||||
;; fine and leaves the projection matrix wrong for everything after it.
|
||||
(defmacro with-mode-3d [args]
|
||||
(if (< (len args) 2)
|
||||
`(with-mode-3d-takes-a-camera-and-a-body)
|
||||
`(do (begin-mode-3d ~(at args 0))
|
||||
~@(form-rest args 1)
|
||||
(end-mode-3d))))
|
||||
|
||||
;; Render to a texture instead of the screen. The target's texture comes out
|
||||
;; of the GPU upside down, so drawing it back wants a negative source height —
|
||||
;; that correction is the caller's and is deliberately not hidden here, since
|
||||
;; it belongs with the draw and not with the mode.
|
||||
(defmacro with-texture-mode [args]
|
||||
(if (< (len args) 2)
|
||||
`(with-texture-mode-takes-a-target-and-a-body)
|
||||
`(do (begin-texture-mode ~(at args 0))
|
||||
~@(form-rest args 1)
|
||||
(end-texture-mode))))
|
||||
|
||||
;; Clip to a rectangle, in screen pixels with y down from the top. Four
|
||||
;; scalars rather than a Rectangle, because that is what BeginScissorMode
|
||||
;; takes and this file is not the place to invent a second spelling.
|
||||
(defmacro with-scissor-mode [args]
|
||||
(if (< (len args) 5)
|
||||
`(with-scissor-mode-takes-x-y-width-height-and-a-body)
|
||||
`(do (begin-scissor-mode ~(at args 0) ~(at args 1) ~(at args 2) ~(at args 3))
|
||||
~@(form-rest args 4)
|
||||
(end-scissor-mode))))
|
||||
Loading…
x
Reference in New Issue
Block a user