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.
126 lines
6.2 KiB
Plaintext
126 lines
6.2 KiB
Plaintext
;;;; 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))))
|