The author: "I think I prefer length over len, because then I'll use len as the variable name". One arm in check.ml, one row in the table beside it, and every (len x) in lib, test, examples, vendor, spike, docs, web, emacs, plan.org and NEXT.md rewritten. Shadowing and builtin/ had already taken most of the sting out: a (defn len ...) was legal and won in its own file, and builtin/len reached past it. What was left is that len was still a builtin — the defn earned a warning, and a wrapper had to say builtin/ at every inner call. Now there is nothing under the short name: len is an ordinary identifier in every position, which is what (let [len (length xs)] ...) wants. length takes over as shadowing's worked example rather than the feature losing one. shadow-builtin.flan, builtin-qualified.flan, pkgs/shadowed and the builtin/ rows in test_flan move to it and go on testing shadowing. A call to a len nothing defines is answered where an unknown function is, after every table and after the shadowing guard, so a program with its own len never reaches it. The sentence is said rather than guessed at — len and length are three edits apart and the did-you-mean's net is one — and the call is written back out through spell_arg, as-slice's spelling lifted out of it and now shared, so what is printed compiles. sand.flan:33 still calls the old name and is the author's to change; until it does, test_acceptance and test_session abort there. Both were run green against a copy with that one line changed. FIX.org says so.
148 lines
7.7 KiB
Plaintext
148 lines
7.7 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 docs/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.
|
|
|
|
;; Each guard below asks the same question twice over, and the second half is
|
|
;; the one worth explaining. A body that was not written can arrive two ways:
|
|
;; as no argument at all — (with-drawing) — and as a single bare () —
|
|
;; (with-drawing ()). The second used to slip past, because one argument is one
|
|
;; argument however empty it is: the () was spliced into the expansion
|
|
;; verbatim, and the report came out of the middle of the expanded (do) saying
|
|
;; that () is not an expression, several forms away from the line anyone wrote.
|
|
;; () has no value-position meaning in the language at all, so a lone one here
|
|
;; is never a body and can be answered with the same message the missing-body
|
|
;; case gets. (do) is what to write for a body that really is meant to be
|
|
;; empty, and it is an ordinary expression that needs none of this.
|
|
;;
|
|
;; Only a *lone* () is caught, and only where the body goes. () anywhere else —
|
|
;; as a camera, as a render target — is left to fail on its own, because
|
|
;; nothing here could say anything truer about it than the compiler already
|
|
;; does.
|
|
|
|
;; 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 (or (< (length args) 1)
|
|
(and (= (length args) 1) (form-empty-list? (at args 0))))
|
|
`(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 (or (< (length args) 2)
|
|
(and (= (length args) 2) (form-empty-list? (at args 1))))
|
|
`(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 (or (< (length args) 2)
|
|
(and (= (length args) 2) (form-empty-list? (at args 1))))
|
|
`(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 (or (< (length args) 2)
|
|
(and (= (length args) 2) (form-empty-list? (at args 1))))
|
|
`(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 (or (< (length args) 5)
|
|
(and (= (length args) 5) (form-empty-list? (at args 4))))
|
|
`(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))))
|