Every reference to the removed .flan files is updated, generated.fln's stale banner line is regenerated, and the ported .fln files read like handwritten Flan.

This commit is contained in:
Joseph Ferano 2026-09-26 18:13:56 +07:00
parent e219e90446
commit c1018d87c5
61 changed files with 423 additions and 373 deletions

View File

@ -66,7 +66,7 @@ dune exec ./bin/main.exe -- build web/examples/hello.flan -o hello
The falling-sand demo uses raylib: The falling-sand demo uses raylib:
```sh ```sh
dune exec ./bin/main.exe -- run sand.flan dune exec ./bin/main.exe -- run sand.fln
``` ```
Once you are iterating regularly, put the built executable on your `PATH` if Once you are iterating regularly, put the built executable on your `PATH` if
@ -77,7 +77,7 @@ you want to use the shorter `flan` commands shown below.
Start a long-lived development session: Start a long-lived development session:
```sh ```sh
flan dev sand.flan flan dev sand.fln
``` ```
The program runs normally and publishes a local socket beside the source file. The program runs normally and publishes a local socket beside the source file.

View File

@ -2,22 +2,22 @@
;;;; ;;;;
;;;; examples/core/core_2d_camera.c. Needed no new binding at all, which is ;;;; examples/core/core_2d_camera.c. Needed no new binding at all, which is
;;;; the reason to port it: Camera2D, begin-mode-2d and end-mode-2d have been ;;;; the reason to port it: Camera2D, begin-mode-2d and end-mode-2d have been
;;;; in vendor/raylib/raylib.flan since the beginning and nothing in the ;;;; in vendor/raylib/raylib.fln since the beginning and nothing in the
;;;; corpus had ever passed a whole Camera2D across the FFI on a frame's path. ;;;; corpus had ever passed a whole Camera2D across the FFI on a frame's path.
;;;; Two Vector2s, a float and a float, by value, sixty times a second — the ;;;; Two Vector2s, a float and a float, by value, sixty times a second — the
;;;; acceptance table pins that layout with get-screen-to-world-2d, and this ;;;; acceptance table pins that layout with get-screen-to-world-2d, and this
;;;; is the same struct going into the call that actually draws with it. ;;;; is the same struct going into the call that actually draws with it.
;;;; ;;;;
;;;; The hundred buildings are a `defonce` of fixed arrays rather than a Vec: ;;;; The hundred buildings are a `once` of fixed arrays rather than a Vec:
;;;; a global cannot hold a Vec (docs/PORTING.md §3) and does not need to here, ;;;; a global cannot hold a Vec (docs/PORTING.md §3) and does not need to here,
;;;; because the count is a constant in the C too. `(array n T)` is the zeroed ;;;; because the count is a constant in the C too. `array(n, T)` is the zeroed
;;;; fixed array, and the C's `= { 0 }` is exactly that. ;;;; fixed array, and the C's `= { 0 }` is exactly that.
;;;; ;;;;
;;;; get-random-value comes from the generated half of the bindings. It is on ;;;; get-random-value comes from the generated half of the bindings. It is on
;;;; no frame's path — it runs once, before the loop — and its Flan face is ;;;; no frame's path — it runs once, before the loop — and its Flan face is
;;;; the C one, so docs/PORTING.md §1's rule does not reach it. ;;;; the C one, so docs/PORTING.md §1's rule does not reach it.
;;;; ;;;;
;;;; One deliberate difference from the C: `(- camera.rotation 1.0)` and the ;;;; One deliberate difference from the C: `camera.rotation - 1.0` and the
;;;; clamp after it are written with the prelude's `clamp` macro rather than ;;;; clamp after it are written with the prelude's `clamp` macro rather than
;;;; two ifs. It is the same arithmetic; the C spells it out because C has no ;;;; two ifs. It is the same arithmetic; the C spells it out because C has no
;;;; clamp. ;;;; clamp.
@ -38,7 +38,7 @@ fn main() -> ()
rl/init-window(screen-width, screen-height, rl/init-window(screen-width, screen-height,
"raylib [core] example - 2d camera") "raylib [core] example - 2d camera")
defer rl/close-window() defer rl/close-window()
player = rl/Rectangle{.x 400.0 .y 280.0 .width 40.0 .height 40.0} player = rl/Rectangle{.x 400.0, .y 280.0, .width 40.0, .height 40.0}
buildings = array(max-buildings, rl/Rectangle) buildings = array(max-buildings, rl/Rectangle)
build-colors = array(max-buildings, rl/Color) build-colors = array(max-buildings, rl/Color)
;; The skyline: each building is as wide as the last one left room for, so ;; The skyline: each building is as wide as the last one left room for, so
@ -47,13 +47,13 @@ fn main() -> ()
for i in range(max-buildings) for i in range(max-buildings)
let w = f32(rl/get-random-value(50, 200)) let w = f32(rl/get-random-value(50, 200))
h = f32(rl/get-random-value(100, 800)) h = f32(rl/get-random-value(100, 800))
buildings[i] = rl/Rectangle({.x -6000.0 + f32(spacing), .y (f32(screen-height) - 130.0) - h, .width w, .height h}) buildings[i] = rl/Rectangle{.x -6000.0 + f32(spacing), .y (f32(screen-height) - 130.0) - h, .width w, .height h}
spacing += i32(w) spacing += i32(w)
build-colors[i] = rl/Color({.r u8(rl/get-random-value(200, 240)) .g u8(rl/get-random-value(200, 240)) .b u8(rl/get-random-value(200, 250)) .a 255}) build-colors[i] = rl/Color{.r u8(rl/get-random-value(200, 240)) .g u8(rl/get-random-value(200, 240)) .b u8(rl/get-random-value(200, 250)) .a 255}
;; A fresh Camera2D is all zeroes and a zoom of 0 makes the transform ;; A fresh Camera2D is all zeroes and a zoom of 0 makes the transform
;; singular, so the zoom is the one field that must be set — raylib.flan's ;; singular, so the zoom is the one field that must be set — raylib.fln's
;; own comment on the struct says so. ;; own comment on the struct says so.
camera = rl/Camera2D({.offset rl/Vector2{.x f32(screen-width) / 2.0, .y f32(screen-height) / 2.0} .target rl/Vector2{.x player.x + 20.0, .y player.y + 20.0} .rotation 0.0 .zoom 1.0}) camera = rl/Camera2D{.offset rl/Vector2{.x f32(screen-width) / 2.0, .y f32(screen-height) / 2.0} .target rl/Vector2{.x player.x + 20.0, .y player.y + 20.0} .rotation 0.0 .zoom 1.0}
rl/set-target-fps(60) rl/set-target-fps(60)
until rl/window-should-close() until rl/window-should-close()
;; Update ;; Update
@ -61,16 +61,14 @@ fn main() -> ()
;; right, which is the else-if and not an accident. ;; right, which is the else-if and not an accident.
if rl/is-key-down(:key-right) if rl/is-key-down(:key-right)
player.x += 2.0 player.x += 2.0
else elif rl/is-key-down(:key-left)
if rl/is-key-down(:key-left) player.x -= 2.0
player.x -= 2.0
;; The camera follows the player's centre. ;; The camera follows the player's centre.
camera.target = rl/Vector2{.x player.x + 20.0, .y player.y + 20.0} camera.target = rl/Vector2{.x player.x + 20.0, .y player.y + 20.0}
if rl/is-key-down(:key-a) if rl/is-key-down(:key-a)
camera.rotation -= 1.0 camera.rotation -= 1.0
else elif rl/is-key-down(:key-s)
if rl/is-key-down(:key-s) camera.rotation += 1.0
camera.rotation += 1.0
camera.rotation = clamp(camera.rotation, -40.0, 40.0) camera.rotation = clamp(camera.rotation, -40.0, 40.0)
camera.zoom += rl/get-mouse-wheel-move() * 0.05 camera.zoom += rl/get-mouse-wheel-move() * 0.05
camera.zoom = clamp(camera.zoom, 0.1, 3.0) camera.zoom = clamp(camera.zoom, 0.1, 3.0)

View File

@ -5,7 +5,7 @@
;;;; anywhere upstream that uses Ray and RayCollision without also loading a ;;;; anywhere upstream that uses Ray and RayCollision without also loading a
;;;; Model, so it is the only way those two structs get a caller at all. ;;;; Model, so it is the only way those two structs get a caller at all.
;;;; ;;;;
;;;; Picking is the round trip the corpus was missing. core-world-screen.flan ;;;; Picking is the round trip the corpus was missing. core-world-screen.fln
;;;; already runs get-world-to-screen — a point in the scene to a pixel. This ;;;; already runs get-world-to-screen — a point in the scene to a pixel. This
;;;; is the inverse and it is a different shape: a pixel does not name a ;;;; is the inverse and it is a different shape: a pixel does not name a
;;;; point, it names a *line* through the scene, which is what a Ray is, and ;;;; point, it names a *line* through the scene, which is what a Ray is, and
@ -13,7 +13,7 @@
;;;; something, which is what a RayCollision is. Two structs the package could ;;;; something, which is what a RayCollision is. Two structs the package could
;;;; not describe, for one operation that cannot be written without them. ;;;; not describe, for one operation that cannot be written without them.
;;;; ;;;;
;;;; What was added, all in vendor/raylib/raylib.flan: ;;;; What was added, all in vendor/raylib/raylib.fln:
;;;; ;;;;
;;;; - `Ray` — two Vector3s, origin and direction. ;;;; - `Ray` — two Vector3s, origin and direction.
;;;; - `RayCollision` — a C `bool`, a float and two Vector3s. This is the ;;;; - `RayCollision` — a C `bool`, a float and two Vector3s. This is the
@ -21,7 +21,7 @@
;;;; `hit` is one byte and `distance` is four, so there are three padding ;;;; `hit` is one byte and `distance` is four, so there are three padding
;;;; bytes between them that a permutation destroys. BoundingBox's two ;;;; bytes between them that a permutation destroys. BoundingBox's two
;;;; Vector3s are interchangeable and nothing would catch swapping them. ;;;; Vector3s are interchangeable and nothing would catch swapping them.
;;;; - `BoundingBox` — shared with examples/models-box-collisions.flan. ;;;; - `BoundingBox` — shared with examples/models-box-collisions.fln.
;;;; - get-screen-to-world-ray and draw-ray, hand-written beside ;;;; - get-screen-to-world-ray and draw-ray, hand-written beside
;;;; get-world-to-screen and the cube draws for the reasons `bindings` ;;;; get-world-to-screen and the cube draws for the reasons `bindings`
;;;; gives: the first reads the same camera as its inverse, and the second ;;;; gives: the first reads the same camera as its inverse, and the second
@ -53,12 +53,12 @@ once ray: rl/Ray
once collision: rl/RayCollision once collision: rl/RayCollision
;; The same centre/size to min/max conversion as in ;; The same centre/size to min/max conversion as in
;; examples/models-box-collisions.flan. Written out here rather than shared ;; examples/models-box-collisions.fln. Written out here rather than shared
;; because an example is a single file the reader can follow end to end. ;; because an example is a single file the reader can follow end to end.
;; ;;
;; It used to be eight expressions of field arithmetic; it is the half-extent ;; It used to be eight expressions of field arithmetic; it is the half-extent
;; subtracted and added, which is what the C means, now that the package ;; subtracted and added, which is what the C means, now that the package
;; carries vector arithmetic — see vendor/raylib/vector.flan. That file ;; carries vector arithmetic — see vendor/raylib/vector.fln. That file
;; exists because raymath is `static inline` and has no symbol to bind, so ;; exists because raymath is `static inline` and has no symbol to bind, so
;; v3-sub and v3-add are Flan and not C. ;; v3-sub and v3-add are Flan and not C.
fn box-around(centre: rl/Vector3, size: rl/Vector3) -> rl/BoundingBox fn box-around(centre: rl/Vector3, size: rl/Vector3) -> rl/BoundingBox
@ -69,14 +69,14 @@ fn main() -> ()
rl/init-window(screen-width, screen-height, rl/init-window(screen-width, screen-height,
"raylib [core] example - 3d picking") "raylib [core] example - 3d picking")
defer rl/close-window() defer rl/close-window()
camera = rl/Camera3D({.position rl/Vector3{.x 10.0 .y 10.0 .z 10.0} .target rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .up rl/Vector3{.x 0.0 .y 1.0 .z 0.0} .fovy 45.0 .projection :projection-perspective}) camera = rl/Camera3D{.position rl/Vector3{.x 10.0 .y 10.0 .z 10.0} .target rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .up rl/Vector3{.x 0.0 .y 1.0 .z 0.0} .fovy 45.0 .projection :projection-perspective}
cube-position = rl/Vector3{.x 0.0 .y 1.0 .z 0.0} cube-position = rl/Vector3{.x 0.0 .y 1.0 .z 0.0}
cube-size = rl/Vector3{.x 2.0 .y 2.0 .z 2.0} cube-size = rl/Vector3{.x 2.0 .y 2.0 .z 2.0}
;; Both start zeroed, which is the C's `= { 0 }`. A zero Ray draws as a ;; Both start zeroed, which is the C's `= { 0 }`. A zero Ray draws as a
;; degenerate line at the origin and a zero RayCollision has hit false, so ;; degenerate line at the origin and a zero RayCollision has hit false, so
;; neither needs a "not yet" flag beside it. ;; neither needs a "not yet" flag beside it.
ray = rl/Ray({.position rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .direction rl/Vector3{.x 0.0 .y 0.0 .z 0.0}}) ray = rl/Ray{.position rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .direction rl/Vector3{.x 0.0 .y 0.0 .z 0.0}}
collision = rl/RayCollision({.hit false .distance 0.0 .point rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .normal rl/Vector3{.x 0.0 .y 0.0 .z 0.0}}) collision = rl/RayCollision{.hit false .distance 0.0 .point rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .normal rl/Vector3{.x 0.0 .y 0.0 .z 0.0}}
rl/set-target-fps(60) rl/set-target-fps(60)
until rl/window-should-close() until rl/window-should-close()
;; Update. The first-person controls only run while the cursor is ;; Update. The first-person controls only run while the cursor is

View File

@ -6,7 +6,7 @@
;;;; ;;;;
;;;; The one structural difference from the C is the loop: the C writes ;;;; The one structural difference from the C is the loop: the C writes
;;;; `while (!WindowShouldClose())` and Flan has `until`, which is the same ;;;; `while (!WindowShouldClose())` and Flan has `until`, which is the same
;;;; thing without the negation. sand.flan does likewise. ;;;; thing without the negation. sand.fln does likewise.
;;;; ;;;;
;;;; `defer` closes the window. It is function-scoped — it runs when `main` ;;;; `defer` closes the window. It is function-scoped — it runs when `main`
;;;; returns and not at the end of any inner block — which is exactly what the ;;;; returns and not at the end of any inner block — which is exactly what the

View File

@ -19,7 +19,7 @@
;;;; here rather than instead of it, so the file is not quietly asserting that ;;;; here rather than instead of it, so the file is not quietly asserting that
;;;; the original was right. ;;;; the original was right.
;;;; ;;;;
;;;; The two circle positions are `defonce`s for the usual reason: they are ;;;; The two circle positions are `once`s for the usual reason: they are
;;;; state between frames and a `let` inside the loop would reset them. ;;;; state between frames and a `let` inside the loop would reset them.
import rl "vendor:raylib" import rl "vendor:raylib"

View File

@ -21,7 +21,7 @@
;;;; branches that matched on it are the artwork's and are gone with it. ;;;; branches that matched on it are the artwork's and are gone with it.
;;;; ;;;;
;;;; **The VIBRATE button is drawn and inert.** SetGamepadVibration is the one ;;;; **The VIBRATE button is drawn and inert.** SetGamepadVibration is the one
;;;; call in the C this refuses to bind, and vendor/raylib/raylib.flan already ;;;; call in the C this refuses to bind, and vendor/raylib/raylib.fln already
;;;; carries the paragraph saying why: its arity differs between raylib ;;;; carries the paragraph saying why: its arity differs between raylib
;;;; versions with no 5.5 header here to settle it, so a guess is a corrupted ;;;; versions with no 5.5 header here to settle it, so a guess is a corrupted
;;;; stack frame rather than a link error — and the symbol in libraylib.so.550 ;;;; stack frame rather than a link error — and the symbol in libraylib.so.550
@ -51,7 +51,7 @@ fn deadzone(v: f32, limit: f32) -> f32
if v > 0.0 - limit and v < limit then 0.0 else v if v > 0.0 - limit and v < limit then 0.0 else v
;; The triggers rest at -1 rather than 0, which raylib does not normalise — ;; The triggers rest at -1 rather than 0, which raylib does not normalise —
;; see the note on GamepadAxis in vendor/raylib/raylib.flan. So their deadzone ;; see the note on GamepadAxis in vendor/raylib/raylib.fln. So their deadzone
;; is a floor near the resting end and not a band around the middle. ;; is a floor near the resting end and not a band around the middle.
fn trigger-deadzoned(v: f32) -> f32 fn trigger-deadzoned(v: f32) -> f32
if v < trigger-deadzone then -1.0 else v if v < trigger-deadzone then -1.0 else v
@ -162,7 +162,7 @@ fn main() -> ()
if rl/is-key-pressed(:key-right) if rl/is-key-pressed(:key-right)
gamepad += 1 gamepad += 1
let axis-count = min(6, rl/get-gamepad-axis-count(gamepad)) let axis-count = min(6, rl/get-gamepad-axis-count(gamepad))
let vibrate-rect = rl/Rectangle({.x 10.0, .y 90.0 + 20.0 * f32(axis-count), .width 75.0, .height 24.0}) let vibrate-rect = rl/Rectangle{.x 10.0, .y 90.0 + 20.0 * f32(axis-count), .width 75.0, .height 24.0}
;; Draw ;; Draw
rl/with-drawing: rl/with-drawing:
rl/clear-background(rl/raywhite) rl/clear-background(rl/raywhite)
@ -225,7 +225,7 @@ fn main() -> ()
;; has type i32 while its sibling has type () and the `if` will not ;; has type i32 while its sibling has type () and the `if` will not
;; typecheck — "expected i32, found ()". Both arms end in a ;; typecheck — "expected i32, found ()". Both arms end in a
;; draw-text here, which is the tidy way out; where that is awkward a ;; draw-text here, which is the tidy way out; where that is awkward a
;; trailing `(do)` is the other. ;; trailing `()` is the other.
let b = rl/get-gamepad-button-pressed() let b = rl/get-gamepad-button-pressed()
if b >= 0 if b >= 0
rl/draw-text("DETECTED BUTTON: ", 10, 430, 10, rl/red) rl/draw-text("DETECTED BUTTON: ", 10, 430, 10, rl/red)

View File

@ -24,11 +24,11 @@
;;;; The rule the refusal came from is unchanged and worth keeping: an enum is ;;;; The rule the refusal came from is unchanged and worth keeping: an enum is
;;;; its own type in the checker, so `:tpa` is an error at the call site ;;;; its own type in the checker, so `:tpa` is an error at the call site
;;;; instead of a number that is wrong later, and a bare integer still does not ;;;; instead of a number that is wrong later, and a bare integer still does not
;;;; fit an `rl/Gesture` parameter. `(i32 g)` does not weaken that — it is ;;;; fit an `rl/Gesture` parameter. `i32(g)` does not weaken that — it is
;;;; named, and it is at the site. The rule was "an integer must not arrive ;;;; named, and it is at the site. The rule was "an integer must not arrive
;;;; silently", not "an integer is dangerous". ;;;; silently", not "an integer is dangerous".
;;;; ;;;;
;;;; The `!= 4` stays a keyword comparison, `(not (= g :gesture-hold))`. That ;;;; The `!= 4` stays a keyword comparison, `not g == :gesture-hold`. That
;;;; one was never a range test; it is a single member, and the C's 4 is a ;;;; one was never a range test; it is a single member, and the C's 4 is a
;;;; magic number the keyword reads better than. ;;;; magic number the keyword reads better than.
;;;; ;;;;
@ -48,9 +48,9 @@
;;;; ;;;;
;;;; **No local fixed arrays.** The C declares `char gestureLog[20][12]` and ;;;; **No local fixed arrays.** The C declares `char gestureLog[20][12]` and
;;;; `Vector2 touchPosition[32]` inside main. A `let` binding takes no type ;;;; `Vector2 touchPosition[32]` inside main. A `let` binding takes no type
;;;; annotation, so a fixed array can only be a top-level `defonce` or a literal ;;;; annotation, so a fixed array can only be a top-level `once` or a literal
;;;; with every element written out — thirty-two Vector2s, here. They are ;;;; with every element written out — thirty-two Vector2s, here. They are
;;;; `defonce`s, which is what the C's storage amounts to anyway. ;;;; `once`s, which is what the C's storage amounts to anyway.
;;;; ;;;;
;;;; The log itself came out simpler than the C's: the names are compile-time ;;;; The log itself came out simpler than the C's: the names are compile-time
;;;; literals, so a slot holds a `string` and there is no TextCopy and no ;;;; literals, so a slot holds a `string` and there is no TextCopy and no

View File

@ -18,7 +18,7 @@
;;;; has nothing to catch. ;;;; has nothing to catch.
;;;; ;;;;
;;;; A slot that has not been written yet holds a zero-length string, because a ;;;; A slot that has not been written yet holds a zero-length string, because a
;;;; `defonce` with no initialiser is all-bytes-zero and a string is ptr+len — ;;;; `once` with no initialiser is all-bytes-zero and a string is ptr+len —
;;;; a null pointer with a length of 0. draw-text draws nothing for it. That is ;;;; a null pointer with a length of 0. draw-text draws nothing for it. That is
;;;; the C's `{ "" }` initialiser arriving by a different route, and it is why ;;;; the C's `{ "" }` initialiser arriving by a different route, and it is why
;;;; resetting the log below only has to reset the counter. ;;;; resetting the log below only has to reset the counter.
@ -67,7 +67,7 @@ fn main() -> ()
"raylib [core] example - input gestures") "raylib [core] example - input gestures")
defer rl/close-window() defer rl/close-window()
rl/set-target-fps(60) rl/set-target-fps(60)
let touch-area = rl/Rectangle({.x 220.0, .y 10.0, .width f32(screen-width) - 230.0, .height f32(screen-height) - 20.0}) let touch-area = rl/Rectangle{.x 220.0, .y 10.0, .width f32(screen-width) - 230.0, .height f32(screen-height) - 20.0}
until rl/window-should-close() until rl/window-should-close()
;; Update ;; Update
last-gesture = current-gesture last-gesture = current-gesture

View File

@ -5,13 +5,13 @@
;;;; ;;;;
;;;; The C moves the ball by writing `ballPosition.x += 2.0f` on a local ;;;; The C moves the ball by writing `ballPosition.x += 2.0f` on a local
;;;; struct. Flan has the same thing — a local IS an assignable place ;;;; struct. Flan has the same thing — a local IS an assignable place
;;;; (spec-memory.md) — but the local has to be a `defonce` here rather than a ;;;; (spec-memory.md) — but the local has to be a `once` here rather than a
;;;; `let` inside the loop, because a `let` binding is rebound every iteration ;;;; `let` inside the loop, because a `let` binding is rebound every iteration
;;;; and the position has to survive between frames. The C's variable is ;;;; and the position has to survive between frames. The C's variable is
;;;; outside its while loop for the same reason; this is that, spelled with the ;;;; outside its while loop for the same reason; this is that, spelled with the
;;;; storage the language has. ;;;; storage the language has.
;;;; ;;;;
;;;; `(set (.x ball) ...)` works on a struct field of a global: a field of an ;;;; `ball.x = ...` works on a struct field of a global: a field of an
;;;; assignable place is itself one. ;;;; assignable place is itself one.
import rl "vendor:raylib" import rl "vendor:raylib"

View File

@ -9,7 +9,7 @@
;;;; matters in both — two buttons pressed on the same frame give the earlier ;;;; matters in both — two buttons pressed on the same frame give the earlier
;;;; one, which is the C's behaviour and not an accident of the port. ;;;; one, which is the C's behaviour and not an accident of the port.
;;;; ;;;;
;;;; The colour has to be a `defonce` rather than a `let`, for the reason ;;;; The colour has to be a `once` rather than a `let`, for the reason
;;;; core-input-keys' position does: it is state between frames. ;;;; core-input-keys' position does: it is state between frames.
import rl "vendor:raylib" import rl "vendor:raylib"

View File

@ -1,19 +1,19 @@
;;;; raylib [core] example - input multitouch ;;;; raylib [core] example - input multitouch
;;;; ;;;;
;;;; examples/core/core_input_multitouch.c. No new bindings: the whole touch ;;;; examples/core/core_input_multitouch.c. No new bindings: the whole touch
;;;; surface was already there for sand.flan's read-out. ;;;; surface was already there for sand.fln's read-out.
;;;; ;;;;
;;;; The C keeps `Vector2 touchPositions[MAX_TOUCH_POINTS]`, and this is the ;;;; The C keeps `Vector2 touchPositions[MAX_TOUCH_POINTS]`, and this is the
;;;; first example that needs a fixed array whose element type is a STRUCT ;;;; first example that needs a fixed array whose element type is a STRUCT
;;;; rather than a number. That works — `[10 rl/Vector2]` is ten Vector2s laid ;;;; rather than a number. That works — `[10 rl/Vector2]` is ten Vector2s laid
;;;; out flat, no headers, and `(at touch-positions i)` is a place that can be ;;;; out flat, no headers, and `touch-positions[i]` is a place that can be
;;;; assigned a whole struct. Nothing in the repository used one before, so it ;;;; assigned a whole struct. Nothing in the repository used one before, so it
;;;; is worth saying that it does. ;;;; is worth saying that it does.
;;;; ;;;;
;;;; It is a top-level `defonce` and not a local, which is NOT a stylistic ;;;; It is a top-level `once` and not a local, which is NOT a stylistic
;;;; choice. A `let` binding takes no type annotation, so the only way to make ;;;; choice. A `let` binding takes no type annotation, so the only way to make
;;;; a fixed array inside a function is to initialise it from a literal with ;;;; a fixed array inside a function is to initialise it from a literal with
;;;; every element written out — ten `(rl/Vector2 {.x 0.0 .y 0.0})`s here, and ;;;; every element written out — ten `rl/Vector2{.x 0.0 .y 0.0}`s here, and
;;;; thirty-two in the gestures testbed. A zeroed local array of a given type ;;;; thirty-two in the gestures testbed. A zeroed local array of a given type
;;;; cannot be spelled. Static storage is what the C's `= { 0 }` gets anyway. ;;;; cannot be spelled. Static storage is what the C's `= { 0 }` gets anyway.
;;;; ;;;;

View File

@ -2,7 +2,7 @@
;;;; ;;;;
;;;; examples/core/core_input_virtual_controls.c. No new bindings. ;;;; examples/core/core_input_virtual_controls.c. No new bindings.
;;;; ;;;;
;;;; Split the way sand.flan is split: everything above the second banner is ;;;; Split the way sand.fln is split: everything above the second banner is
;;;; arithmetic with no raylib calls in it, so test/programs/virtual-controls- ;;;; arithmetic with no raylib calls in it, so test/programs/virtual-controls-
;;;; headless.flan can import this file as a package, drive the pointer over a ;;;; headless.flan can import this file as a package, drive the pointer over a
;;;; scripted path and hash where the player ended up. That is the only one of ;;;; scripted path and hash where the player ended up. That is the only one of
@ -18,7 +18,7 @@
;;;; ;;;;
;;;; for (i = 0; i < BUTTON_MAX; i++) { if (...) { pressedButton = i; break; } } ;;;; for (i = 0; i < BUTTON_MAX; i++) { if (...) { pressedButton = i; break; } }
;;;; ;;;;
;;;; and `(break)` is refused: "break is not implemented yet (see the build ;;;; and `break()` is refused: "break is not implemented yet (see the build
;;;; sequence in plan.org)". `return` from the function works and is what this ;;;; sequence in plan.org)". `return` from the function works and is what this
;;;; uses — which is why the search is its own `defn` and not written inline ;;;; uses — which is why the search is its own `defn` and not written inline
;;;; the way the C has it. That is not a loss: the function is the thing the ;;;; the way the C has it. That is not a loss: the function is the thing the
@ -26,14 +26,14 @@
;;;; function, the shape left is a flag and a compound loop condition. ;;;; function, the shape left is a flag and a compound loop condition.
;;;; ;;;;
;;;; **No `fabsf`.** The prelude has sqrt-f32 and sign-f32 but no absolute ;;;; **No `fabsf`.** The prelude has sqrt-f32 and sign-f32 but no absolute
;;;; value for floats, and the comment there says why for integers. `(max v ;;;; value for floats, and the comment there says why for integers.
;;;; (- 0.0 v))` is it, and unlike the integer case it has no edge: there is no ;;;; `max(v, 0.0 - v)` is it, and unlike the integer case it has no edge: there
;;;; float whose negation is itself except -0.0, whose absolute value is 0.0 ;;;; is no float whose negation is itself except -0.0, whose absolute value is
;;;; either way. ;;;; 0.0 either way.
;;;; ;;;;
;;;; The button geometry is `defconst` arrays of struct literals. That works — ;;;; The button geometry is `const` arrays of struct literals. That works —
;;;; and it is the only way a fixed array can be made inside anything but a ;;;; and it is the only way a fixed array can be made inside anything but a
;;;; top-level `defonce`, since a `let` binding takes no type annotation. ;;;; top-level `once`, since a `let` binding takes no type annotation.
import rl "vendor:raylib" import rl "vendor:raylib"
@ -41,7 +41,7 @@ const screen-width = 800
const screen-height = 450 const screen-height = 450
;; The C's PadButton enum. -1 is BUTTON_NONE and the four directions are 0..3, ;; The C's PadButton enum. -1 is BUTTON_NONE and the four directions are 0..3,
;; in the order the arrays below are written. A `defenum` would not help: the ;; in the order the arrays below are written. An `enum` would not help: the
;; value is used as an index and an enum does not convert to an integer ;; value is used as an index and an enum does not convert to an integer
;; ("i32 converts a number, found ..."), so it would have to be turned back ;; ("i32 converts a number, found ..."), so it would have to be turned back
;; into one at every use. ;; into one at every use.
@ -56,7 +56,7 @@ const button-radius: f32 = 30.0
;; padPosition is {100, 350} and the buttons sit a radius and a half away from ;; padPosition is {100, 350} and the buttons sit a radius and a half away from
;; it on each axis. The C computes these at run time from padPosition; they are ;; it on each axis. The C computes these at run time from padPosition; they are
;; written out here because a defconst is compile-time and the numbers are. ;; written out here because a const is compile-time and the numbers are.
const button-positions: [button-max rl/Vector2] = [rl/Vector2{.x 100.0 .y 305.0} ; up const button-positions: [button-max rl/Vector2] = [rl/Vector2{.x 100.0 .y 305.0} ; up
; left ; left
; right ; right

View File

@ -1,7 +1,7 @@
;;;; raylib [core] example - scissor test ;;;; raylib [core] example - scissor test
;;;; ;;;;
;;;; examples/core/core_scissor_test.c. Needed begin-scissor-mode and ;;;; examples/core/core_scissor_test.c. Needed begin-scissor-mode and
;;;; end-scissor-mode, now hand-written in vendor/raylib/raylib.flan: they are ;;;; end-scissor-mode, now hand-written in vendor/raylib/raylib.fln: they are
;;;; a begin/end pair inside a frame, which is the class docs/PORTING.md §1 says ;;;; a begin/end pair inside a frame, which is the class docs/PORTING.md §1 says
;;;; belongs in the hand-written half and not in the importer's. get-mouse-x ;;;; belongs in the hand-written half and not in the importer's. get-mouse-x
;;;; and get-mouse-y come from the generated half — their Flan face is the C ;;;; and get-mouse-y come from the generated half — their Flan face is the C

View File

@ -1,14 +1,14 @@
;;;; raylib [core] example - window flags ;;;; raylib [core] example - window flags
;;;; ;;;;
;;;; examples/core/core_window_flags.c. The reason to port this one is the ;;;; examples/core/core_window_flags.c. The reason to port this one is the
;;;; flags themselves: raylib.flan carried four of ConfigFlags' sixteen ;;;; flags themselves: raylib.fln carried four of ConfigFlags' sixteen
;;;; members — the four sand.flan sets before the window exists — and this ;;;; members — the four sand.fln sets before the window exists — and this
;;;; example reads and writes eleven of them after it exists. All sixteen are ;;;; example reads and writes eleven of them after it exists. All sixteen are
;;;; in raylib.flan now, read off raylib.h 5.5, in the header's order and not ;;;; in raylib.fln now, read off raylib.h 5.5, in the header's order and not
;;;; in bit order (FLAG_VSYNC_HINT is 0x40 and FLAG_FULLSCREEN_MODE is 0x02, ;;;; in bit order (FLAG_VSYNC_HINT is 0x40 and FLAG_FULLSCREEN_MODE is 0x02,
;;;; which is the kind of thing nobody remembers correctly). ;;;; which is the kind of thing nobody remembers correctly).
;;;; ;;;;
;;;; They are `defconst u32`s and not a `defenum` for the reason the file ;;;; They are `const u32`s and not an `enum` for the reason the file
;;;; already gives about gestures: raylib wants the OR of several and a ;;;; already gives about gestures: raylib wants the OR of several and a
;;;; keyword can only ever name one member. ;;;; keyword can only ever name one member.
;;;; ;;;;
@ -16,7 +16,7 @@
;;;; minimize-window, maximize-window and restore-window all come from the ;;;; minimize-window, maximize-window and restore-window all come from the
;;;; generated half. That is the line drawn in vendor/raylib/bindings and it ;;;; generated half. That is the line drawn in vendor/raylib/bindings and it
;;;; is worth saying why it falls here: the rule in docs/PORTING.md §1 exists so ;;;; is worth saying why it falls here: the rule in docs/PORTING.md §1 exists so
;;;; that a build with no header set can still draw, and generated.flan is ;;;; that a build with no header set can still draw, and generated.fln is
;;;; committed, so it can. What a hand-written line adds on top of that is a ;;;; committed, so it can. What a hand-written line adds on top of that is a
;;;; Flan face the C signature does not have — a Key instead of an int, a ;;;; Flan face the C signature does not have — a Key instead of an int, a
;;;; (Ptr Camera3D) instead of a raw pointer — and these seven have no such ;;;; (Ptr Camera3D) instead of a raw pointer — and these seven have no such
@ -71,7 +71,7 @@ fn main() -> ()
rl/init-window(screen-width, screen-height, rl/init-window(screen-width, screen-height,
"raylib [core] example - window flags") "raylib [core] example - window flags")
defer rl/close-window() defer rl/close-window()
ball-pos = rl/Vector2({.x f32(rl/get-screen-width()) / 2.0, .y f32(rl/get-screen-height()) / 2.0}) ball-pos = rl/Vector2{.x f32(rl/get-screen-width()) / 2.0, .y f32(rl/get-screen-height()) / 2.0}
ball-speed = rl/Vector2{.x 5.0 .y 4.0} ball-speed = rl/Vector2{.x 5.0 .y 4.0}
;; No set-target-fps, as in the C: with FLAG_VSYNC_HINT among the flags the ;; No set-target-fps, as in the C: with FLAG_VSYNC_HINT among the flags the
;; example toggles, a frame limiter on top of it would hide what V-Sync ;; example toggles, a frame limiter on top of it would hide what V-Sync

View File

@ -1,22 +1,22 @@
;;;; raylib [core] example - window should close ;;;; raylib [core] example - window should close
;;;; ;;;;
;;;; examples/core/core_window_should_close.c. Needed set-exit-key, and one ;;;; examples/core/core_window_should_close.c. Needed set-exit-key, and one
;;;; enum member to go with it: raylib.flan's `Key` now has `null 0`, read off ;;;; enum member to go with it: raylib.fln's `Key` now has `null 0`, read off
;;;; KEY_NULL in raylib.h. The binding is hand-written rather than taken from ;;;; KEY_NULL in raylib.h. The binding is hand-written rather than taken from
;;;; the generated half because the Flan face differs — raylib declares ;;;; the generated half because the Flan face differs — raylib declares
;;;; `void SetExitKey(int key)` and the generated line therefore takes an i32, ;;;; `void SetExitKey(int key)` and the generated line therefore takes an i32,
;;;; where this one takes a Key, so `(rl/set-exit-key :key-null)` is checked ;;;; where this one takes a Key, so `rl/set-exit-key(:key-null)` is checked
;;;; against the enum and `:nul` is a compile error instead of a 0. ;;;; against the enum and `:nul` is a compile error instead of a 0.
;;;; ;;;;
;;;; The whole example is about what window-should-close means. It is not a ;;;; The whole example is about what window-should-close means. It is not a
;;;; flag raylib latches: it is "the close button was clicked, or the exit key ;;;; flag raylib latches: it is "the close button was clicked, or the exit key
;;;; is down", recomputed each frame, and it goes back to false on its own. ;;;; is down", recomputed each frame, and it goes back to false on its own.
;;;; That is what lets this program ask for confirmation and then carry on — ;;;; That is what lets this program ask for confirmation and then carry on —
;;;; and it is also why the loop is driven by a `defonce` of its own rather ;;;; and it is also why the loop is driven by a `once` of its own rather
;;;; than by the predicate, which is the one structural difference from every ;;;; than by the predicate, which is the one structural difference from every
;;;; other example in this directory. ;;;; other example in this directory.
;;;; ;;;;
;;;; `(set-exit-key :null)` takes ESC away from raylib so the program can read ;;;; `set-exit-key(:null)` takes ESC away from raylib so the program can read
;;;; it itself. The window's X button still works and is still what ;;;; it itself. The window's X button still works and is still what
;;;; window-should-close reports. ;;;; window-should-close reports.
@ -47,9 +47,8 @@ fn main() -> ()
if exit-requested if exit-requested
if rl/is-key-pressed(:key-y) if rl/is-key-pressed(:key-y)
exiting = true exiting = true
else elif rl/is-key-pressed(:key-n)
if rl/is-key-pressed(:key-n) exit-requested = false
exit-requested = false
;; Draw ;; Draw
rl/with-drawing: rl/with-drawing:
rl/clear-background(rl/raywhite) rl/clear-background(rl/raywhite)

View File

@ -8,7 +8,7 @@
;;;; none of them, and the generated half grew the cubes, spheres, cylinders ;;;; none of them, and the generated half grew the cubes, spheres, cylinders
;;;; and billboards along with the eight lines this example needs. ;;;; and billboards along with the eight lines this example needs.
;;;; ;;;;
;;;; Hand-written in raylib.flan rather than generated, and why each: ;;;; Hand-written in raylib.fln rather than generated, and why each:
;;;; ;;;;
;;;; begin-mode-3d / end-mode-3d a begin/end pair inside a frame, the ;;;; begin-mode-3d / end-mode-3d a begin/end pair inside a frame, the
;;;; class docs/PORTING.md §1 names, and taken ;;;; class docs/PORTING.md §1 names, and taken
@ -51,7 +51,7 @@ fn main() -> ()
;; enum's members right here and a misspelt projection is a compile error ;; enum's members right here and a misspelt projection is a compile error
;; rather than a 0. raylib's field is `int` and the layout is unchanged — ;; rather than a 0. raylib's field is `int` and the layout is unchanged —
;; an enum is four bytes either way. ;; an enum is four bytes either way.
camera = rl/Camera3D({.position rl/Vector3{.x 10.0 .y 10.0 .z 10.0} .target rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .up rl/Vector3{.x 0.0 .y 1.0 .z 0.0} .fovy 45.0 .projection :projection-perspective}) camera = rl/Camera3D{.position rl/Vector3{.x 10.0 .y 10.0 .z 10.0} .target rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .up rl/Vector3{.x 0.0 .y 1.0 .z 0.0} .fovy 45.0 .projection :projection-perspective}
cube = rl/Vector3{.x 0.0 .y 0.0 .z 0.0} cube = rl/Vector3{.x 0.0 .y 0.0 .z 0.0}
;; The mouse becomes the camera's, not a pointer's. ;; The mouse becomes the camera's, not a pointer's.
rl/disable-cursor() rl/disable-cursor()

View File

@ -7,12 +7,12 @@
;;;; allocator to build one in. So a number was drawn one glyph at a time out ;;;; allocator to build one in. So a number was drawn one glyph at a time out
;;;; of a `[10 string]` table. ;;;; of a `[10 string]` table.
;;;; ;;;;
;;;; `(string b)` closed that. It is the mirror of `(bytes-view s)` and costs no ;;;; `string(b)` closed that. It is the mirror of `bytes-view(s)` and costs no
;;;; instructions — a `string` and a `[u8]` are the same 16-byte %slice — so ;;;; instructions — a `string` and a `[u8]` are the same 16-byte %slice — so
;;;; `(string (i64->bytes n))` draws in one call and the table, the per-glyph ;;;; `string(i64->bytes(n))` draws in one call and the table, the per-glyph
;;;; pen and the digit arithmetic behind them are gone. ;;;; pen and the digit arithmetic behind them are gone.
;;;; ;;;;
;;;; What is left is the part `(string ...)` does not answer, which is ;;;; What is left is the part `string(...)` does not answer, which is
;;;; *formatting*: `i64->bytes` has no field width, so "%03i" still has to be ;;;; *formatting*: `i64->bytes` has no field width, so "%03i" still has to be
;;;; assembled, and `f64->bytes` is `%g` and not "%.02f", so a fixed number of ;;;; assembled, and `f64->bytes` is `%g` and not "%.02f", so a fixed number of
;;;; decimal places still has to be split and drawn in two pieces. Those two ;;;; decimal places still has to be split and drawn in two pieces. Those two
@ -25,8 +25,8 @@
;;;; name anyway. ;;;; name anyway.
;;;; ;;;;
;;;; `i64->bytes` and `f64->bytes` put their text in the temp allocator, where ;;;; `i64->bytes` and `f64->bytes` put their text in the temp allocator, where
;;;; it lasts until the next `(free-temp)`. A program drawing these every frame ;;;; it lasts until the next `free-temp()`. A program drawing these every frame
;;;; calls `(free-temp)` once per frame, after drawing, and clones any text it ;;;; calls `free-temp()` once per frame, after drawing, and clones any text it
;;;; keeps longer. ;;;; keeps longer.
;;;; ;;;;
;;;; Everything here needs a window: `measure-text` answers 0 for every string ;;;; Everything here needs a window: `measure-text` answers 0 for every string

View File

@ -10,11 +10,11 @@
;;;; ;;;;
;;;; That aggregate is BoundingBox, and it is two Vector3s and nothing else — ;;;; That aggregate is BoundingBox, and it is two Vector3s and nothing else —
;;;; no owned pointer, no array, no lifetime. One `defstruct` in ;;;; no owned pointer, no array, no lifetime. One `defstruct` in
;;;; vendor/raylib/raylib.flan un-refuses four raylib functions at once ;;;; vendor/raylib/raylib.fln un-refuses four raylib functions at once
;;;; (CheckCollisionBoxes, CheckCollisionBoxSphere, DrawBoundingBox and ;;;; (CheckCollisionBoxes, CheckCollisionBoxSphere, DrawBoundingBox and
;;;; GetRayCollisionBox), and that is the cheapest widening of the 3D surface ;;;; GetRayCollisionBox), and that is the cheapest widening of the 3D surface
;;;; available anywhere in the tree. Ray and RayCollision went in beside it ;;;; available anywhere in the tree. Ray and RayCollision went in beside it
;;;; for examples/core-3d-picking.flan and for the same reason. ;;;; for examples/core-3d-picking.fln and for the same reason.
;;;; ;;;;
;;;; So the answer to "is a models example just a binding exercise" is: the ;;;; So the answer to "is a models example just a binding exercise" is: the
;;;; ones that need Model are, and this one is the counterexample. What ;;;; ones that need Model are, and this one is the counterexample. What
@ -57,7 +57,7 @@ const enemy-sphere-size: f32 = 1.5
;; ;;
;; It used to be eight expressions of field arithmetic here too; it is the ;; It used to be eight expressions of field arithmetic here too; it is the
;; half-extent subtracted and added now that the package carries vector ;; half-extent subtracted and added now that the package carries vector
;; arithmetic — see vendor/raylib/vector.flan, which is Flan and not C ;; arithmetic — see vendor/raylib/vector.fln, which is Flan and not C
;; because raymath is `static inline` and has no symbol to bind. ;; because raymath is `static inline` and has no symbol to bind.
fn box-around(centre: rl/Vector3, size: rl/Vector3) -> rl/BoundingBox fn box-around(centre: rl/Vector3, size: rl/Vector3) -> rl/BoundingBox
let half = rl/v3-scale(size, 0.5) let half = rl/v3-scale(size, 0.5)
@ -68,9 +68,9 @@ fn main() -> ()
"raylib [models] example - box collisions") "raylib [models] example - box collisions")
defer rl/close-window() defer rl/close-window()
;; The C writes this camera as a brace initialiser with a trailing 0 for the ;; The C writes this camera as a brace initialiser with a trailing 0 for the
;; projection, which is CAMERA_PERSPECTIVE. Named here, because the defenum ;; projection, which is CAMERA_PERSPECTIVE. Named here, because the enum
;; exists precisely so that the 0 does not have to be remembered. ;; exists precisely so that the 0 does not have to be remembered.
camera = rl/Camera3D({.position rl/Vector3{.x 0.0 .y 10.0 .z 10.0} .target rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .up rl/Vector3{.x 0.0 .y 1.0 .z 0.0} .fovy 45.0 .projection :projection-perspective}) camera = rl/Camera3D{.position rl/Vector3{.x 0.0 .y 10.0 .z 10.0} .target rl/Vector3{.x 0.0 .y 0.0 .z 0.0} .up rl/Vector3{.x 0.0 .y 1.0 .z 0.0} .fovy 45.0 .projection :projection-perspective}
player-position = rl/Vector3{.x 0.0 .y 1.0 .z 2.0} player-position = rl/Vector3{.x 0.0 .y 1.0 .z 2.0}
player-size = rl/Vector3{.x 1.0 .y 2.0 .z 1.0} player-size = rl/Vector3{.x 1.0 .y 2.0 .z 1.0}
player-color = rl/green player-color = rl/green

View File

@ -7,17 +7,17 @@
;;;; poly draws — in one frame, beside the four the core examples already ;;;; poly draws — in one frame, beside the four the core examples already
;;;; exercise. Every one of them came out of the generated half of the ;;;; exercise. Every one of them came out of the generated half of the
;;;; bindings and none needed a new line anywhere; that is the result worth ;;;; bindings and none needed a new line anywhere; that is the result worth
;;;; recording, because the whole point of a committed generated.flan is that ;;;; recording, because the whole point of a committed generated.fln is that
;;;; a build with no FLAN_RAYLIB_H set can draw with all of it. ;;;; a build with no FLAN_RAYLIB_H set can draw with all of it.
;;;; ;;;;
;;;; What it is actually a test of. The gradient calls are the first thing in ;;;; What it is actually a test of. The gradient calls are the first thing in
;;;; the corpus to pass *two* Colors in one call, and draw-triangle is the ;;;; the corpus to pass *two* Colors in one call, and draw-triangle is the
;;;; first to pass three Vector2s. A struct argument crosses the FFI by ;;;; first to pass three Vector2s. A struct argument crosses the FFI by
;;;; pointer into a generated shim (vendor/raylib/raylib.flan's header note), ;;;; pointer into a generated shim (vendor/raylib/raylib.fln's header note),
;;;; so the arity of that copying is what a call with several small structs in ;;;; so the arity of that copying is what a call with several small structs in
;;;; a row puts under load. Nothing here can be asserted headless — every line ;;;; a row puts under load. Nothing here can be asserted headless — every line
;;;; needs a GL context — so the check is the link and the screen, which is ;;;; needs a GL context — so the check is the link and the screen, which is
;;;; what the Shapes comment in raylib.flan already says about this family. ;;;; what the Shapes comment in raylib.fln already says about this family.
;;;; ;;;;
;;;; The one deliberate difference from the C: the C writes `screenWidth/4*2` ;;;; The one deliberate difference from the C: the C writes `screenWidth/4*2`
;;;; and `screenWidth/4.0f*3.0f` for the same column and gets 400 and 600 out ;;;; and `screenWidth/4.0f*3.0f` for the same column and gets 400 and 600 out

View File

@ -40,9 +40,9 @@ fn main() -> ()
rl/init-window(screen-width, screen-height, rl/init-window(screen-width, screen-height,
"raylib [shapes] example - collision area") "raylib [shapes] example - collision area")
defer rl/close-window() defer rl/close-window()
box-a = rl/Rectangle({.x 10.0, .y f32(screen-height) / 2.0 - 50.0, .width 200.0, .height 100.0}) box-a = rl/Rectangle{.x 10.0, .y f32(screen-height) / 2.0 - 50.0, .width 200.0, .height 100.0}
box-a-speed-x = 4 box-a-speed-x = 4
box-b = rl/Rectangle({.x f32(screen-width) / 2.0 - 30.0, .y f32(screen-height) / 2.0 - 30.0, .width 60.0, .height 60.0}) box-b = rl/Rectangle{.x f32(screen-width) / 2.0 - 30.0, .y f32(screen-height) / 2.0 - 30.0, .width 60.0, .height 60.0}
;; A fresh Rectangle is four zeroes, which is the C's `= { 0 }`. It is never ;; A fresh Rectangle is four zeroes, which is the C's `= { 0 }`. It is never
;; drawn before the first collision sets it. ;; drawn before the first collision sets it.
box-collision = rl/Rectangle{.x 0.0 .y 0.0 .width 0.0 .height 0.0} box-collision = rl/Rectangle{.x 0.0 .y 0.0 .width 0.0 .height 0.0}
@ -63,14 +63,12 @@ fn main() -> ()
box-b.y = f32(rl/get-mouse-y()) - box-b.height / 2.0 box-b.y = f32(rl/get-mouse-y()) - box-b.height / 2.0
if box-b.x + box-b.width >= f32(rl/get-screen-width()) if box-b.x + box-b.width >= f32(rl/get-screen-width())
box-b.x = f32(rl/get-screen-width()) - box-b.width box-b.x = f32(rl/get-screen-width()) - box-b.width
else elif box-b.x <= 0.0
if box-b.x <= 0.0 box-b.x = 0.0
box-b.x = 0.0
if box-b.y + box-b.height >= f32(rl/get-screen-height()) if box-b.y + box-b.height >= f32(rl/get-screen-height())
box-b.y = f32(rl/get-screen-height()) - box-b.height box-b.y = f32(rl/get-screen-height()) - box-b.height
else elif box-b.y <= f32(screen-upper-limit)
if box-b.y <= f32(screen-upper-limit) box-b.y = f32(screen-upper-limit)
box-b.y = f32(screen-upper-limit)
collision = rl/check-collision-recs(box-a, box-b) collision = rl/check-collision-recs(box-a, box-b)
;; Only meaningful while they touch: raylib answers a zero rectangle for ;; Only meaningful while they touch: raylib answers a zero rectangle for
;; two boxes that do not, and the C leaves the previous one in place ;; two boxes that do not, and the C leaves the previous one in place

View File

@ -18,7 +18,7 @@
;;;; file. ;;;; file.
;;;; ;;;;
;;;; That measurement is what decided the answer, and the answer landed: ;;;; That measurement is what decided the answer, and the answer landed:
;;;; vendor/raylib/vector.flan is raymath written in Flan, since a C shim ;;;; vendor/raylib/vector.fln is raymath written in Flan, since a C shim
;;;; re-exporting the inlines would have bought identical arithmetic at the ;;;; re-exporting the inlines would have bought identical arithmetic at the
;;;; price of a compilation unit and a second place raylib's semantics live. ;;;; price of a compilation unit and a second place raylib's semantics live.
;;;; The measurement is left standing rather than rewritten away — one ;;;; The measurement is left standing rather than rewritten away — one
@ -69,7 +69,7 @@ fn track(mouse: rl/Vector2, centre: rl/Vector2) -> rl/Vector2
else else
let d = rl/v2-sub(mouse, centre) let d = rl/v2-sub(mouse, centre)
angle = atan2-f32(d.y, d.x) angle = atan2-f32(d.y, d.x)
rl/Vector2({.x centre.x + limit * cos-f32(angle), .y centre.y + limit * sin-f32(angle)}) rl/Vector2{.x centre.x + limit * cos-f32(angle), .y centre.y + limit * sin-f32(angle)}
fn main() -> () fn main() -> ()
rl/init-window(screen-width, screen-height, rl/init-window(screen-width, screen-height,
@ -78,8 +78,8 @@ fn main() -> ()
;; The C reads these off get-screen-width/get-screen-height after the window ;; The C reads these off get-screen-width/get-screen-height after the window
;; is open rather than off the constants, and so does this: on a high-DPI ;; is open rather than off the constants, and so does this: on a high-DPI
;; display the two can differ. ;; display the two can differ.
sclera-left = rl/Vector2({.x f32(rl/get-screen-width()) / 2.0 - 100.0, .y f32(rl/get-screen-height()) / 2.0}) sclera-left = rl/Vector2{.x f32(rl/get-screen-width()) / 2.0 - 100.0, .y f32(rl/get-screen-height()) / 2.0}
sclera-right = rl/Vector2({.x f32(rl/get-screen-width()) / 2.0 + 100.0, .y f32(rl/get-screen-height()) / 2.0}) sclera-right = rl/Vector2{.x f32(rl/get-screen-width()) / 2.0 + 100.0, .y f32(rl/get-screen-height()) / 2.0}
iris-left = sclera-left iris-left = sclera-left
iris-right = sclera-right iris-right = sclera-right
rl/set-target-fps(60) rl/set-target-fps(60)

View File

@ -19,7 +19,7 @@
;;;; come back out of LoadCodepoints as the right 54, and as 49 distinct ones. ;;;; come back out of LoadCodepoints as the right 54, and as 49 distinct ones.
;;;; Nothing in the ;;;; Nothing in the
;;;; language claims to know what a character is — a `string` is bytes and a ;;;; language claims to know what a character is — a `string` is bytes and a
;;;; `[u8]` is the same bytes, which `(string ...)` and `(bytes-view ...)` say in ;;;; `[u8]` is the same bytes, which `string(...)` and `bytes-view(...)` say in
;;;; both directions — and that turns out to be exactly the right amount of ;;;; both directions — and that turns out to be exactly the right amount of
;;;; opinion for this. The count below is a count of codepoints because raylib ;;;; opinion for this. The count below is a count of codepoints because raylib
;;;; decoded them, not because Flan did. ;;;; decoded them, not because Flan did.
@ -37,7 +37,7 @@
;;;; the bytes in front of that pointer belong to the allocator and not ;;;; the bytes in front of that pointer belong to the allocator and not
;;;; to the text. It did not crash; it read rubbish and answered 0, which ;;;; to the text. It did not crash; it read rubbish and answered 0, which
;;;; is the worst of the available outcomes. The repair is a binding that ;;;; is the worst of the available outcomes. The repair is a binding that
;;;; says `(Ptr u8)` and means it, which the header check refused until ;;;; says `Ptr(u8)` and means it, which the header check refused until
;;;; lib/cimport.ml's `agrees` grew the pointer arm its own comment had ;;;; lib/cimport.ml's `agrees` grew the pointer arm its own comment had
;;;; been promising. `rl/get-codepoint-previous` is that binding and ;;;; been promising. `rl/get-codepoint-previous` is that binding and
;;;; `step-back` below is now one call to it. ;;;; `step-back` below is now one call to it.
@ -126,7 +126,7 @@ fn collect-unique(codepoints: [i32]) -> ()
;; The codepoint starting at `off`, and its size in bytes through `size-out`. ;; The codepoint starting at `off`, and its size in bytes through `size-out`.
;; ;;
;; `(string (slice b off (length b)))` is the whole of what the C's `ptr` ;; `string(slice(b, off, length(b)))` is the whole of what the C's `ptr`
;; is: the tail of the text from here on. It costs nothing to say — a `string` and a ;; is: the tail of the text from here on. It costs nothing to say — a `string` and a
;; `[u8]` are the same two words — and the shim NUL-terminates a copy of it ;; `[u8]` are the same two words — and the shim NUL-terminates a copy of it
;; for the duration of the call, which is all GetCodepointNext wants, because ;; for the duration of the call, which is all GetCodepointNext wants, because
@ -160,7 +160,7 @@ fn step-forward(off: i32) -> i32
;; string — which reaches C as a copy — is the one thing it must not be ;; string — which reaches C as a copy — is the one thing it must not be
;; handed. `rl/get-codepoint-previous` takes the bytes and an offset instead ;; handed. `rl/get-codepoint-previous` takes the bytes and an offset instead
;; and builds the interior pointer itself; see the note beside it in ;; and builds the interior pointer itself; see the note beside it in
;; vendor/raylib/raylib.flan. What comes back through `size` is the length of ;; vendor/raylib/raylib.fln. What comes back through `size` is the length of
;; the codepoint *behind* `off`, so the previous offset is the difference. ;; the codepoint *behind* `off`, so the previous offset is the difference.
;; ;;
;; This used to be a hand-written walk over continuation bytes — step back one ;; This used to be a hand-written walk over continuation bytes — step back one
@ -179,7 +179,7 @@ fn step-back(off: i32) -> i32
once font: rl/Font once font: rl/Font
;; Whether the TTF was there, asked once. A `defonce` and not the call itself ;; Whether the TTF was there, asked once. A `once` and not the call itself
;; in the draw loop: is-path-file is a stat, and a syscall per frame to answer a ;; in the draw loop: is-path-file is a stat, and a syscall per frame to answer a
;; question whose answer cannot change while the program runs is exactly what ;; question whose answer cannot change while the program runs is exactly what
;; the per-frame rule in docs/PORTING.md is about. ;; the per-frame rule in docs/PORTING.md is about.

View File

@ -13,7 +13,7 @@
;;;; the second character of a fast pair. ;;;; the second character of a fast pair.
;;;; ;;;;
;;;; raylib says "empty" by answering 0, and rl/get-char-pressed is now a ;;;; raylib says "empty" by answering 0, and rl/get-char-pressed is now a
;;;; Flan wrapper that says it with None instead — see raylib.flan, "Draining ;;;; Flan wrapper that says it with None instead — see raylib.fln, "Draining
;;;; raylib's two input queues". What that buys is visible below: the C ;;;; raylib's two input queues". What that buys is visible below: the C
;;;; shape, which this file had, reads the queue in *two* places, once to ;;;; shape, which this file had, reads the queue in *two* places, once to
;;;; prime the loop and once at the bottom of the body, and a `> 0` in ;;;; prime the loop and once at the bottom of the body, and a `> 0` in
@ -21,9 +21,9 @@
;;;; Option shape reads it in one place, and the case where there is no ;;;; Option shape reads it in one place, and the case where there is no
;;;; character is a branch the checker knows about rather than a comparison. ;;;; character is a branch the checker knows about rather than a comparison.
;;;; ;;;;
;;;; The second is set-mouse-cursor, which needed a new defenum. raylib's ;;;; The second is set-mouse-cursor, which needed a new enum. raylib's
;;;; header says `int cursor` and means one of eleven MOUSE_CURSOR_ values, so ;;;; header says `int cursor` and means one of eleven MOUSE_CURSOR_ values, so
;;;; the Flan face is `MouseCursor` — hand-written in raylib.flan beside the ;;;; the Flan face is `MouseCursor` — hand-written in raylib.fln beside the
;;;; other cursor calls, excluded from the generated half, and mapped in ;;;; other cursor calls, excluded from the generated half, and mapped in
;;;; `bindings` so the eleven members are checked against the header. See the ;;;; `bindings` so the eleven members are checked against the header. See the
;;;; comment there: this call is made on every frame off a hover test, and the ;;;; comment there: this call is made on every frame off a hover test, and the
@ -66,7 +66,7 @@ fn main() -> ()
defer rl/close-window() defer rl/close-window()
name = array(max-input-chars, u8) name = array(max-input-chars, u8)
letter-count = 0 letter-count = 0
text-box = rl/Rectangle({.x f32(screen-width) / 2.0 - 100.0, .y 180.0, .width 225.0, .height 50.0}) text-box = rl/Rectangle{.x f32(screen-width) / 2.0 - 100.0, .y 180.0, .width 225.0, .height 50.0}
mouse-on-text = false mouse-on-text = false
frames-counter = 0 frames-counter = 0
rl/set-target-fps(60) rl/set-target-fps(60)

View File

@ -5,9 +5,9 @@
;;;; form was built rather than an application found for it afterwards. ;;;; form was built rather than an application found for it afterwards.
;;;; ;;;;
;;;; Its inner loop reads `font.recs[index]` and `font.glyphs[index]`. Both ;;;; Its inner loop reads `font.recs[index]` and `font.glyphs[index]`. Both
;;;; fields are `(Ptr T)` — that is what raylib hands back and there is nothing ;;;; fields are `Ptr(T)` — that is what raylib hands back and there is nothing
;;;; else it could hand back — and a pointer from C carries no length, so ;;;; else it could hand back — and a pointer from C carries no length, so
;;;; `(at (.recs font) i)` answered `(Ptr rl/Rectangle) cannot be indexed` and ;;;; `font.recs[i]` answered `Ptr(rl/Rectangle) cannot be indexed` and
;;;; the whole example stopped there. Element 0 through `deref` was the entire ;;;; the whole example stopped there. Element 0 through `deref` was the entire
;;;; readable surface of a two-hundred-glyph array. ;;;; readable surface of a two-hundred-glyph array.
;;;; ;;;;
@ -35,7 +35,7 @@
;;;; wrap, which is the thing the example is about. ;;;; wrap, which is the thing the example is about.
;;;; ;;;;
;;;; - `GetCodepoint` wants a pointer into the middle of the text, and Flan has ;;;; - `GetCodepoint` wants a pointer into the middle of the text, and Flan has
;;;; the operation the C is spelling by hand: `(string (slice b i n))` is the ;;;; the operation the C is spelling by hand: `string(slice(b, i, n))` is the
;;;; tail from `i` with no copy and no pointer arithmetic. It is not free: ;;;; tail from `i` with no copy and no pointer arithmetic. It is not free:
;;;; the shim NUL-terminates a copy on the way into C, into a 256-byte stack ;;;; the shim NUL-terminates a copy on the way into C, into a 256-byte stack
;;;; buffer or a malloc when the tail is longer — and this message is 284 ;;;; buffer or a malloc when the tail is longer — and this message is 284
@ -158,7 +158,7 @@ fn main() -> ()
let text = bytes-view(message) let text = bytes-view(message)
is-resizing = false is-resizing = false
is-word-wrap = true is-word-wrap = true
let container = rl/Rectangle({.x 25.0, .y 25.0, .width f32(screen-width) - 50.0, .height f32(screen-height) - 250.0}) let container = rl/Rectangle{.x 25.0, .y 25.0, .width f32(screen-width) - 50.0, .height f32(screen-height) - 250.0}
let resizer = rl/Rectangle{.x 0.0 .y 0.0 .width 14.0 .height 14.0} let resizer = rl/Rectangle{.x 0.0 .y 0.0 .width 14.0 .height 14.0}
let min-width = f32(60.0) let min-width = f32(60.0)
min-height = f32(60.0) min-height = f32(60.0)
@ -185,9 +185,8 @@ fn main() -> ()
let h = container.height + (mouse.y - last-mouse.y) let h = container.height + (mouse.y - last-mouse.y)
container.width = clamp(w, min-width, max-width) container.width = clamp(w, min-width, max-width)
container.height = clamp(h, min-height, max-height) container.height = clamp(h, min-height, max-height)
else elif rl/is-mouse-button-down(:mouse-left) and rl/check-collision-point-rec(mouse, resizer)
if rl/is-mouse-button-down(:mouse-left) and rl/check-collision-point-rec(mouse, resizer) is-resizing = true
is-resizing = true
resizer.x = container.x + container.width - 17.0 resizer.x = container.x + container.width - 17.0
resizer.y = container.y + container.height - 17.0 resizer.y = container.y + container.height - 17.0
last-mouse = mouse last-mouse = mouse

View File

@ -6,14 +6,14 @@
;;;; DrawText(TextSubtext(message, 0, framesCounter/10), ...) ;;;; DrawText(TextSubtext(message, 0, framesCounter/10), ...)
;;;; ;;;;
;;;; TextSubtext is in raylib.h and is not in the bindings, on the rule ;;;; TextSubtext is in raylib.h and is not in the bindings, on the rule
;;;; raylib.flan states for the whole Text* family: it answers a `char *` into ;;;; raylib.fln states for the whole Text* family: it answers a `char *` into
;;;; a rotating static buffer, and declare-c refuses a returned pointer to ;;;; a rotating static buffer, and declare-c refuses a returned pointer to
;;;; memory the caller does not own. There is no missing line to add — the ;;;; memory the caller does not own. There is no missing line to add — the
;;;; binding would be wrong at any signature. ;;;; binding would be wrong at any signature.
;;;; ;;;;
;;;; And it does not matter, because Flan has the operation as a primitive. ;;;; And it does not matter, because Flan has the operation as a primitive.
;;;; `(slice a lo hi)` takes a view of an array, `(string b)` reinterprets the ;;;; `slice(a, lo, hi)` takes a view of an array, `string(b)` reinterprets the
;;;; bytes as a string at no cost, and `(string (slice message 0 n))` is ;;;; bytes as a string at no cost, and `string(slice(message, 0, n))` is
;;;; TextSubtext with the static buffer removed — no copy, no shared state, no ;;;; TextSubtext with the static buffer removed — no copy, no shared state, no
;;;; rotation to run out of. Porting the example is therefore how the gap gets ;;;; rotation to run out of. Porting the example is therefore how the gap gets
;;;; *closed* rather than reported: the C's workaround for not having slices ;;;; *closed* rather than reported: the C's workaround for not having slices
@ -28,8 +28,8 @@
;;;; the C's, and it costs one `min`. ;;;; the C's, and it costs one `min`.
;;;; ;;;;
;;;; The message is a `[u8]` and not a `string` because `slice` takes an array ;;;; The message is a `[u8]` and not a `string` because `slice` takes an array
;;;; or a slice; `(bytes-view "…")` is the bridge in the other direction from ;;;; or a slice; `bytes-view("…")` is the bridge in the other direction from
;;;; `(string …)` and costs nothing either. The embedded newline is written as ;;;; `string(…)` and costs nothing either. The embedded newline is written as
;;;; an escape, and raylib's draw-text breaks the line on it. ;;;; an escape, and raylib's draw-text breaks the line on it.
import rl "vendor:raylib" import rl "vendor:raylib"

View File

@ -8,8 +8,8 @@
;;;; bilinear sampler doing the interpolation. With the default ;;;; bilinear sampler doing the interpolation. With the default
;;;; :filter-point the same program draws 32-pixel squares. ;;;; :filter-point the same program draws 32-pixel squares.
;;;; ;;;;
;;;; That is what needed adding: `TextureFilter`, a new defenum in ;;;; That is what needed adding: `TextureFilter`, a new enum in
;;;; vendor/raylib/raylib.flan, with set-texture-filter moved from the ;;;; vendor/raylib/raylib.fln, with set-texture-filter moved from the
;;;; generated half to the hand-written one and mapped in `bindings` so its ;;;; generated half to the hand-written one and mapped in `bindings` so its
;;;; six members are checked against raylib.h. The header says `int filter` ;;;; six members are checked against raylib.h. The header says `int filter`
;;;; and there is nothing in an `int` to say that 1 is the interesting value; ;;;; and there is nothing in an `int` to say that 1 is the interesting value;
@ -117,14 +117,14 @@ fn main() -> ()
;; Light the square around the player, skipping anything off the map. ;; Light the square around the player, skipping anything off the map.
;; Without that test this reads and writes outside the array, which in the ;; Without that test this reads and writes outside the array, which in the
;; C is undefined and here is a bounds trap — the same bug, reported. ;; C is undefined and here is a bounds trap — the same bug, reported.
let y-2 = player-tile-y - player-tile-visibility let vis-y = player-tile-y - player-tile-visibility
while y-2 < player-tile-y + player-tile-visibility while vis-y < player-tile-y + player-tile-visibility
let x = player-tile-x - player-tile-visibility let x = player-tile-x - player-tile-visibility
while x < player-tile-x + player-tile-visibility while x < player-tile-x + player-tile-visibility
if x >= 0 and x < tiles-x and y-2 >= 0 and y-2 < tiles-y if x >= 0 and x < tiles-x and vis-y >= 0 and vis-y < tiles-y
tile-fog[y-2 * tiles-x + x] = 1 tile-fog[vis-y * tiles-x + x] = 1
x += 1 x += 1
y-2 += 1 vis-y += 1
;; Draw the fog into its own little target first, at one pixel per tile. ;; 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 ;; blank is alpha 0, so a tile that is neither unseen nor remembered
;; leaves nothing behind and the map below shows through unmodified. ;; leaves nothing behind and the map below shows through unmodified.

View File

@ -8,7 +8,7 @@
;;;; of its pixels. ;;;; of its pixels.
;;;; ;;;;
;;;; What it puts under load that nothing else does. An Image is the one ;;;; What it puts under load that nothing else does. An Image is the one
;;;; struct in raylib.flan that carries a pointer to memory raylib owns — ;;;; struct in raylib.fln that carries a pointer to memory raylib owns —
;;;; `data (Ptr u8)` — and it is returned by value, so every one of these nine ;;;; `data (Ptr u8)` — and it is returned by value, so every one of these nine
;;;; calls hands back a 24-byte aggregate through the shim's out-pointer with ;;;; calls hands back a 24-byte aggregate through the shim's out-pointer with
;;;; a live heap block inside it. The corpus had crossed an Image before ;;;; a live heap block inside it. The corpus had crossed an Image before

View File

@ -9,16 +9,16 @@
;;;; had ever handed raylib a buffer and asked it to rewrite the buffer. ;;;; had ever handed raylib a buffer and asked it to rewrite the buffer.
;;;; ;;;;
;;;; What that puts under load, and it is a different thing from the nine ;;;; What that puts under load, and it is a different thing from the nine
;;;; Gen* calls in examples/textures-image-generation.flan: **in-place ;;;; Gen* calls in examples/textures-image-generation.fln: **in-place
;;;; mutation of an Image through a (Ptr Image)**. Eight of the nine filters ;;;; mutation of an Image through a (Ptr Image)**. Eight of the nine filters
;;;; take the image by pointer and change `data` under the caller — ;;;; take the image by pointer and change `data` under the caller —
;;;; image-format and image-blur-gaussian *reallocate* it, so the pointer the ;;;; image-format and image-blur-gaussian *reallocate* it, so the pointer the
;;;; caller held before the call is freed by it. raylib.flan's Images section ;;;; caller held before the call is freed by it. raylib.fln's Images section
;;;; already says that the by-value/by-pointer split is raylib's own and is ;;;; already says that the by-value/by-pointer split is raylib's own and is
;;;; kept deliberately so a caller can see which calls change what they are ;;;; kept deliberately so a caller can see which calls change what they are
;;;; given; this is the example that depends on it being right. ;;;; given; this is the example that depends on it being right.
;;;; ;;;;
;;;; What needed adding: `PixelFormat`, a defenum in vendor/raylib/raylib.flan, ;;;; What needed adding: `PixelFormat`, a enum in vendor/raylib/raylib.fln,
;;;; with image-format moved from the generated half to the hand-written one ;;;; with image-format moved from the generated half to the hand-written one
;;;; and mapped in `bindings` so its twenty-four members are checked against ;;;; and mapped in `bindings` so its twenty-four members are checked against
;;;; raylib.h. The C's line is ;;;; raylib.h. The C's line is
@ -67,9 +67,9 @@ const screen-height = 450
const num-processes = 9 const num-processes = 9
;; The C's ImageProcess enum. Flan's defenum lowers to an i32 for C's benefit ;; The C's ImageProcess enum. Flan's enum lowers to an i32 for C's benefit
;; and these never cross to C, so they are defconsts — the same choice ;; and these never cross to C, so they are consts — the same choice
;; examples/textures-image-generation.flan made for its texture index. ;; examples/textures-image-generation.fln made for its texture index.
const proc-none = 0 const proc-none = 0
const proc-color-grayscale = 1 const proc-color-grayscale = 1
const proc-color-tint = 2 const proc-color-tint = 2
@ -109,7 +109,7 @@ fn make-source-image() -> rl/Image
img img
;; The C's `switch (currentProcess)`. A cond here, as in ;; The C's `switch (currentProcess)`. A cond here, as in
;; examples/textures-image-generation.flan, and for the same reason: Flan has ;; examples/textures-image-generation.fln, and for the same reason: Flan has
;; no switch and the chain reads the same. ;; no switch and the chain reads the same.
;; ;;
;; Every arm takes the image by pointer and every arm rewrites the buffer the ;; Every arm takes the image by pointer and every arm rewrites the buffer the
@ -188,14 +188,14 @@ fn main() -> ()
mouse-hover-rec = -1 mouse-hover-rec = -1
toggle-recs = array(num-processes, rl/Rectangle) toggle-recs = array(num-processes, rl/Rectangle)
for i in range(num-processes) for i in range(num-processes)
toggle-recs[i] = rl/Rectangle({.x 40.0 .y f32(50 + 32 * i) .width 150.0 .height 30.0}) toggle-recs[i] = rl/Rectangle{.x 40.0 .y f32(50 + 32 * i) .width 150.0 .height 30.0}
rl/set-target-fps(60) rl/set-target-fps(60)
until rl/window-should-close() until rl/window-should-close()
;; Update ;; Update
;; ;;
;; The C computes mouseHoverRec with a loop whose `else` clause resets it ;; The C computes mouseHoverRec with a loop whose `else` clause resets it
;; on every miss and whose `break` leaves it set on a hit. As in ;; on every miss and whose `break` leaves it set on a hit. As in
;; examples/textures-mouse-painting.flan the reset is lifted out in front ;; examples/textures-mouse-painting.fln the reset is lifted out in front
;; and the loop only ever sets it, which is the same answer said plainly — ;; and the loop only ever sets it, which is the same answer said plainly —
;; the nine rectangles do not overlap, so there is no first-hit-wins rule ;; the nine rectangles do not overlap, so there is no first-hit-wins rule
;; to preserve. ;; to preserve.

View File

@ -7,14 +7,14 @@
;;;; load-image-from-texture → image-flip-vertical → export-image ;;;; load-image-from-texture → image-flip-vertical → export-image
;;;; ;;;;
;;;; Nothing in the corpus had run it. image-from-image has a headless ;;;; Nothing in the corpus had run it. image-from-image has a headless
;;;; acceptance case and sand.flan loads images the other way, but the path ;;;; acceptance case and sand.fln loads images the other way, but the path
;;;; that reads a render target's pixels back into CPU memory, corrects for ;;;; that reads a render target's pixels back into CPU memory, corrects for
;;;; GL's bottom-up rows and encodes a PNG had never been exercised at all. ;;;; GL's bottom-up rows and encodes a PNG had never been exercised at all.
;;;; All three came out of the generated half of the bindings; nothing needed ;;;; All three came out of the generated half of the bindings; nothing needed
;;;; adding for this file. ;;;; adding for this file.
;;;; ;;;;
;;;; The flip is the same fact as the negative source height in ;;;; The flip is the same fact as the negative source height in
;;;; examples/textures-fog-of-war.flan, met from the other side: on screen the ;;;; examples/textures-fog-of-war.fln, met from the other side: on screen the
;;;; canvas is drawn with a negative-height source rectangle so GL's bottom-up ;;;; canvas is drawn with a negative-height source rectangle so GL's bottom-up
;;;; rows come out the right way up, and on save there is no source rectangle ;;;; rows come out the right way up, and on save there is no source rectangle
;;;; to negate, so the pixels are turned over in memory instead. A program ;;;; to negate, so the pixels are turned over in memory instead. A program
@ -92,7 +92,7 @@ fn main() -> ()
;; 30 wide with a 2-pixel gap, starting 10 from the left. ;; 30 wide with a 2-pixel gap, starting 10 from the left.
colors-recs = array(max-colors-count, rl/Rectangle) colors-recs = array(max-colors-count, rl/Rectangle)
for i in range(max-colors-count) for i in range(max-colors-count)
colors-recs[i] = rl/Rectangle({.x 10.0 + 32.0 * f32(i), .y 10.0, .width 30.0, .height 30.0}) colors-recs[i] = rl/Rectangle{.x 10.0 + 32.0 * f32(i), .y 10.0, .width 30.0, .height 30.0}
color-selected = 0 color-selected = 0
color-selected-prev = 0 color-selected-prev = 0
color-mouse-hover = 0 color-mouse-hover = 0
@ -117,9 +117,8 @@ fn main() -> ()
let mouse-pos = rl/get-mouse-position() let mouse-pos = rl/get-mouse-position()
if rl/is-key-pressed(:key-right) if rl/is-key-pressed(:key-right)
color-selected += 1 color-selected += 1
else elif rl/is-key-pressed(:key-left)
if rl/is-key-pressed(:key-left) color-selected -= 1
color-selected -= 1
color-selected = clamp(color-selected, 0, max-colors-count - 1) color-selected = clamp(color-selected, 0, max-colors-count - 1)
;; Which swatch the pointer is over, or -1. See the header comment: the ;; Which swatch the pointer is over, or -1. See the header comment: the
;; reset is here rather than in an else branch inside the loop. ;; reset is here rather than in an else branch inside the loop.
@ -156,10 +155,9 @@ fn main() -> ()
if mouse-pos.y > 50.0 if mouse-pos.y > 50.0
rl/draw-circle(i32(mouse-pos.x), i32(mouse-pos.y), brush-size, rl/draw-circle(i32(mouse-pos.x), i32(mouse-pos.y), brush-size,
colors[0]) colors[0])
else elif rl/is-mouse-button-released(:mouse-right) and mouse-was-pressed
if rl/is-mouse-button-released(:mouse-right) and mouse-was-pressed color-selected = color-selected-prev
color-selected = color-selected-prev mouse-was-pressed = false
mouse-was-pressed = false
btn-save-mouse-hover = rl/check-collision-point-rec(mouse-pos, btn-save-rec) btn-save-mouse-hover = rl/check-collision-point-rec(mouse-pos, btn-save-rec)
;; The round trip. See the header comment for why the flip is here and ;; The round trip. See the header comment for why the flip is here and
;; not on the draw. ;; not on the draw.

View File

@ -919,7 +919,7 @@ let executable ?(opts = default) ?(csrcs = []) ?(lflags = []) ?(pnames = [])
a release build calls into it — the compiler only emits a registry lookup a release build calls into it — the compiler only emits a registry lookup
for a name the host was not built with, which cannot arise without cells — for a name the host was not built with, which cannot arise without cells —
but the agent package's C refers to it, and a package's C sources are but the agent package's C refers to it, and a package's C sources are
collected whatever [main] does. Leaving it out made [flan build sand.flan] collected whatever [main] does. Leaving it out made [flan build sand.fln]
fail at the link with an undefined symbol, which reads as a compiler bug fail at the link with an undefined symbol, which reads as a compiler bug
rather than as a missing flag. The table is BSS, so this costs address rather than as a missing flag. The table is BSS, so this costs address
space and not binary size, and [-rdynamic] and the cells are still what space and not binary size, and [-rdynamic] and the cells are still what

View File

@ -1427,7 +1427,7 @@ let eval_expr_at t ~code ~origin ~pause ~at =
and its only other reader is the select in [accept_loop] — and its only other reader is the select in [accept_loop] —
which is not running, because it is further up this very call which is not running, because it is further up this very call
stack, inside [serve]. A program that prints as it goes (and a stack, inside [serve]. A program that prints as it goes (and a
game loop prints as it goes; sand.flan does) fills those 64K game loop prints as it goes; sand.fln does) fills those 64K
while the module below was being built, and the game thread is while the module below was being built, and the game thread is
then stopped inside [flan_write_stdout], in an [fwrite] that then stopped inside [flan_write_stdout], in an [fwrite] that
will not return until somebody reads. It never reaches the will not return until somebody reads. It never reaches the
@ -3344,7 +3344,7 @@ let reg_listing t ~verb ~note =
Locals were the half the shadow stack was built for; these are arguably the Locals were the half the shadow stack was built for; these are arguably the
more useful half in this language. A game keeps most of its state in more useful half in this language. A game keeps most of its state in
top-level [defonce]s and sand.flan holds its entire grid that way, so "what top-level [defonce]s and sand.fln holds its entire grid that way, so "what
is the program's state right now" is a question about globals and there was is the program's state right now" is a question about globals and there was
nowhere to ask it. nowhere to ask it.

View File

@ -45,7 +45,7 @@ type t = {
owns. A file on disk does not say what it is called from outside — the owns. A file on disk does not say what it is called from outside — the
*importer* chooses that — so this is the only place the answer exists, and *importer* chooses that — so this is the only place the answer exists, and
a REPL editing a package's source needs it to know that [poll] typed in a REPL editing a package's source needs it to know that [poll] typed in
vendor/agent/agent.flan means [agent/poll] to the running program. *) vendor/agent/agent.fln means [agent/poll] to the running program. *)
pkgs : pkg list; pkgs : pkg list;
(* Every [defmacro] the imported packages declare, qualified under the alias (* Every [defmacro] the imported packages declare, qualified under the alias
each was imported as and quasiquote-desugared, ready for each was imported as and quasiquote-desugared, ready for
@ -95,7 +95,7 @@ let rec find_collection dir name =
if String.equal parent dir then None else find_collection parent name if String.equal parent dir then None else find_collection parent name
(* A package is a directory, or a single source file named outright. The file (* A package is a directory, or a single source file named outright. The file
form is for the program that is also a library: sand.flan sits beside three form is for the program that is also a library: sand.fln sits beside three
other loose .flan files, so naming its directory would import all four, and other loose .flan files, so naming its directory would import all four, and
moving it into one of its own would be arranging the tree around a moving it into one of its own would be arranging the tree around a
limitation. A file carries no [.c] and no [link] — those belong to a limitation. A file carries no [.c] and no [link] — those belong to a

View File

@ -106,7 +106,7 @@ let source = {flan|
;; what it buys is a *different element*, silently. ;; what it buys is a *different element*, silently.
;; ;;
;; The restarts that matter are the ones the program already established — a ;; The restarts that matter are the ones the program already established — a
;; frame loop's `continue`, sand.flan's shape — and they are on the restart ;; frame loop's `continue`, sand.fln's shape — and they are on the restart
;; stack and reachable from a handler or from the break loop without anything ;; stack and reachable from a handler or from the break loop without anything
;; being pushed here. That is plan.org's "restarts go at the resync point, ;; being pushed here. That is plan.org's "restarts go at the resync point,
;; once", with allocation and file failure as the named exceptions and this on ;; once", with allocation and file failure as the named exceptions and this on
@ -985,7 +985,7 @@ let source = {flan|
;; ;;
;; They are here anyway, because the alternative on offer today is worse: a ;; They are here anyway, because the alternative on offer today is worse: a
;; caller that wants an angle writes the same two `declare` lines at the top ;; caller that wants an angle writes the same two `declare` lines at the top
;; of its own file (examples/core-input-gestures-testbed.flan did, before ;; of its own file (examples/core-input-gestures-testbed.fln did, before
;; this), which is the identical libm call with the identical caveat and ;; this), which is the identical libm call with the identical caveat and
;; nobody's name on it. One copy with the caveat written down beats a copy per ;; nobody's name on it. One copy with the caveat written down beats a copy per
;; file with none. ;; file with none.
@ -1217,7 +1217,7 @@ let source = {flan|
;; **Which face to use.** The i64 of nanoseconds is exact and is what a ;; **Which face to use.** The i64 of nanoseconds is exact and is what a
;; difference should be taken in. The f64 of seconds is what a frame loop ;; difference should be taken in. The f64 of seconds is what a frame loop
;; wants, and it is the shape raylib's `get-time` already answers with ;; wants, and it is the shape raylib's `get-time` already answers with
;; (vendor/raylib/raylib.flan, `(declare-c get-time [] f64 "GetTime")`), so the ;; (vendor/raylib/raylib.fln, `(declare-c get-time [] f64 "GetTime")`), so the
;; two mix without a conversion at every site. The monotonic origin is latched ;; two mix without a conversion at every site. The monotonic origin is latched
;; at the first read rather than being boot — see runtime/flan_rt.c — so that ;; at the first read rather than being boot — see runtime/flan_rt.c — so that
;; the f64 stays integer-exact in nanoseconds for a hundred days of process ;; the f64 stays integer-exact in nanoseconds for a hundred days of process
@ -2469,7 +2469,7 @@ let source = {flan|
;; Whether a form is the empty list, (). [form-items] cannot answer this: it ;; Whether a form is the empty list, (). [form-items] cannot answer this: it
;; returns the empty slice for a non-list too, so "no items" and "not a list" ;; returns the empty slice for a non-list too, so "no items" and "not a list"
;; arrive the same. A macro that has to tell `()` from a name needs the ;; arrive the same. A macro that has to tell `()` from a name needs the
;; difference — see vendor/raylib/modes.flan, where a lone () argument is a ;; difference — see vendor/raylib/modes.fln, where a lone () argument is a
;; body that was not written rather than a body of one form. ;; body that was not written rather than a body of one form.
(defn is-form-empty-list [f Form] bool (defn is-form-empty-list [f Form] bool
(match f (match f

View File

@ -398,7 +398,7 @@ let package_decls t =
(* Which package a file being edited belongs to, if any. (* Which package a file being edited belongs to, if any.
A form typed into vendor/agent/agent.flan declares [poll], but the running A form typed into vendor/agent/agent.fln declares [poll], but the running
program only ever knew it as [agent/poll]: the alias is chosen by whatever program only ever knew it as [agent/poll]: the alias is chosen by whatever
imported the directory, and is written nowhere in the file itself. Without imported the directory, and is written nowhere in the file itself. Without
this the form splices as a brand-new unrelated name, the evaluation reports this the form splices as a brand-new unrelated name, the evaluation reports

View File

@ -116,7 +116,7 @@ int8_t flan_vec_push(void *v, const void *elem, int64_t size, int64_t align,
* *
* NaN-boxed, in a word. A double is *itself*: the 2^64 minus a NaN's worth of * NaN-boxed, in a word. A double is *itself*: the 2^64 minus a NaN's worth of
* bit patterns that are not quiet NaNs are read straight back as f64, at no * bit patterns that are not quiet NaNs are read straight back as f64, at no
* cost, which is what a language where f64 is first class and where sand.flan * cost, which is what a language where f64 is first class and where sand.fln
* runs a physics loop wants. Everything else hides inside the quiet-NaN space. * runs a physics loop wants. Everything else hides inside the quiet-NaN space.
* *
* The box is sign bit + all-ones exponent + quiet bit, which is * The box is sign bit + all-ones exponent + quiet bit, which is
@ -2927,7 +2927,7 @@ uint8_t flan_dyn_need_bool(flan_dyn v) {
* box's kind was not what the program apparently expected. * box's kind was not what the program apparently expected.
* *
* Once per *site*, not per value. These casts sit in per-cell-per-frame * Once per *site*, not per value. These casts sit in per-cell-per-frame
* loops — sand.flan runs at 120fps — and a per-occurrence line would be a * loops — sand.fln runs at 120fps — and a per-occurrence line would be a
* flood rather than a diagnostic. The site is the [loc] text [check.ml] * flood rather than a diagnostic. The site is the [loc] text [check.ml]
* passes in, and the table below is keyed on its *bytes* rather than its * passes in, and the table below is keyed on its *bytes* rather than its
* address: the two backends emit their own constants for it and neither * address: the two backends emit their own constants for it and neither

View File

@ -64,8 +64,10 @@ fn settle(row: i32, col: i32) -> ()
1 1
elif not is-right elif not is-right
-1 -1
elif f32(rand()) < 0.5
1
else else
if f32(rand()) < 0.5 then 1 else -1 -1
grid[y, col + side] = grid[row, col] grid[y, col + side] = grid[row, col]
grid[row, col] = 0 grid[row, col] = 0
velocity[y, col + side] = vel velocity[y, col + side] = vel

View File

@ -1,11 +1,11 @@
;;;; A name the import did not bring. ;;;; A name the import did not bring.
;;;; ;;;;
;;;; sand.flan declares a main and this imports it, so sand/main is a name ;;;; sand.fln declares a main and this imports it, so sand/main is a name
;;;; somebody might reasonably write — and is not one. Left to the checker it ;;;; somebody might reasonably write — and is not one. Left to the checker it
;;;; would be "unknown name", which is true and unhelpful; the refusal has to ;;;; would be "unknown name", which is true and unhelpful; the refusal has to
;;;; say the name is missing on purpose. Never built: the refusal is the test. ;;;; say the name is missing on purpose. Never built: the refusal is the test.
(import sand "../../sand.flan") (import sand "../../sand.fln")
(defn main [] i32 (defn main [] i32
(sand/main) (sand/main)

View File

@ -1,11 +1,11 @@
;;;; One package reached along two routes. ;;;; One package reached along two routes.
;;;; ;;;;
;;;; raylib is imported here and again by sand.flan, which this also imports. ;;;; raylib is imported here and again by sand.fln, which this also imports.
;;;; Loading it twice would declare every binding twice and be refused as a ;;;; Loading it twice would declare every binding twice and be refused as a
;;;; collision, so a directory is read once and keyed by its real path. Nothing ;;;; collision, so a directory is read once and keyed by its real path. Nothing
;;;; calls into raylib, so nothing links it either. ;;;; calls into raylib, so nothing links it either.
(import sand "../../sand.flan") (import sand "../../sand.fln")
(import rl "vendor:raylib") (import rl "vendor:raylib")
(defn main [] i32 (defn main [] i32

View File

@ -36,7 +36,7 @@
;;;; ;;;;
;;;; Nothing here draws, so nothing here needs the TTF the example looks for. ;;;; Nothing here draws, so nothing here needs the TTF the example looks for.
(import cp "../../examples/text-codepoints-loading.flan") (import cp "../../examples/text-codepoints-loading.fln")
(import rl "vendor:raylib") (import rl "vendor:raylib")
(defconst max-walk 128) (defconst max-walk 128)

View File

@ -37,7 +37,7 @@
;;;; at all, so there is nothing in them for an implementation to have an ;;;; at all, so there is nothing in them for an implementation to have an
;;;; opinion about. ;;;; opinion about.
(import ip "../../examples/textures-image-processing.flan") (import ip "../../examples/textures-image-processing.fln")
(import rl "vendor:raylib") (import rl "vendor:raylib")
;; Three probes, chosen so that between them every shape and the background ;; Three probes, chosen so that between them every shape and the background

View File

@ -26,7 +26,7 @@
;;;; the four directions move the player the four different ways. A crossed ;;;; the four directions move the player the four different ways. A crossed
;;;; axis anywhere in nearest-button or move-player changes the hash. ;;;; axis anywhere in nearest-button or move-player changes the hash.
(import vc "../../examples/core-input-virtual-controls.flan") (import vc "../../examples/core-input-virtual-controls.fln")
(import rl "vendor:raylib") (import rl "vendor:raylib")
;; A sixtieth of a second, fixed. The real program uses get-frame-time, which ;; A sixtieth of a second, fixed. The real program uses get-frame-time, which

View File

@ -278,28 +278,21 @@ module Pool = struct
done done
end end
(* sand.flan is the workspace's own program, edited by hand and not by this (* sand.fln is the workspace's own program, edited by hand and not by this
suite, and three cases here compile it. It still calls the randomness suite, and several cases here compile it. It once called randomness
functions by names the prelude does not have, so those three cannot check functions by names the prelude did not have, and was skipped until it
until it is brought up to date — and the fixture, rather than a comment, is called real ones; it now calls `rand`, which the prelude does have, so
what says so: the moment sand.flan calls (rand) every case below runs again these cases run unconditionally. The one thing that is not automatic is
with nothing to undo. The one thing that is not automatic is the sand hash, the sand hash, which is stale whatever happens because the generator now
which is stale whatever happens because the generator now answers 64 bits answers 64 bits per draw; its row says so where the number is. *)
per draw; its row says so where the number is. *)
let sand_checks = let sand_checks =
match In_channel.with_open_bin "../sand.flan" In_channel.input_all with match In_channel.with_open_bin "../sand.fln" In_channel.input_all with
(* The open paren is the test and not the bare name: sand.flan explains | _ -> true
itself in a comment that says rand-f32 too, and a guard that matched that
would go on skipping after the calls were fixed — a guard that can never
release is worse than no guard. *)
| src -> not (contains src "(rand-f32")
| exception _ -> false | exception _ -> false
let () = let () =
if not sand_checks then if not sand_checks then
print_endline print_endline "acceptance: skipping the cases that compile sand.fln — it is not there";
"acceptance: skipping the cases that compile sand.flan — it calls \
rand-f32, which is not a name";
match Sys.command "command -v clang > /dev/null 2>&1" with match Sys.command "command -v clang > /dev/null 2>&1" with
| 0 -> | 0 ->
let exe = compile "../calc-me.flan" in let exe = compile "../calc-me.flan" in
@ -2778,9 +2771,9 @@ let () =
This used to be skipped without FLAN_RAYLIB_H, because the generated This used to be skipped without FLAN_RAYLIB_H, because the generated
bindings only existed when a header was read. They are committed now — bindings only existed when a header was read. They are committed now —
vendor/raylib/generated.flan — so it runs on the same terms as every vendor/raylib/generated.fln — so it runs on the same terms as every
other raylib case here: libraylib linkable, and no raylib-devel. That is other raylib case here: libraylib linkable, and no raylib-devel. That is
the change stated as a test rather than as a claim. If generated.flan the change stated as a test rather than as a claim. If generated.fln
were ever regenerated empty or stale, this is what would say so, and it were ever regenerated empty or stale, this is what would say so, and it
would say so on an ordinary machine rather than only on one with a would say so on an ordinary machine rather than only on one with a
header exported. *) header exported. *)
@ -3126,7 +3119,7 @@ let () =
Texture2D notes above say it: glyph-padding is read by nothing raylib Texture2D notes above say it: glyph-padding is read by nothing raylib
computes on the CPU, offset-y only moves a glyph when it is drawn, and computes on the CPU, offset-y only moves a glyph when it is drawn, and
of each atlas rectangle only `width` is ever looked at. Those four of each atlas rectangle only `width` is ever looked at. Those four
fields rest on the header agreeing with raylib's and on sand.flan fields rest on the header agreeing with raylib's and on sand.fln
looking right, and on nothing else. *) looking right, and on nothing else. *)
let raylib_font_out = let raylib_font_out =
"valid yes\n\ "valid yes\n\
@ -3150,7 +3143,7 @@ let () =
(* Again at -O0. Everything above runs through mem2reg, which launders a (* Again at -O0. Everything above runs through mem2reg, which launders a
sloppy alloca; -O0 tests the IR actually emitted, so a disagreement sloppy alloca; -O0 tests the IR actually emitted, so a disagreement
between the two points at undefined behaviour rather than a typo. *) between the two points at undefined behaviour rather than a typo. *)
(* sand.flan's simulation, headless. This is the milestone-4 acceptance (* sand.fln's simulation, headless. This is the milestone-4 acceptance
case: N frames from a seeded PRNG, one hash. It imports the sim package case: N frames from a seeded PRNG, one hash. It imports the sim package
and not raylib, deliberately — a program that imports raylib links and not raylib, deliberately — a program that imports raylib links
libraylib on every target, and this one is the version meant to run on libraylib on every target, and this one is the version meant to run on
@ -3686,7 +3679,7 @@ let () =
outputs "an imported package nothing calls, -O0" ~opt:"-O0" outputs "an imported package nothing calls, -O0" ~opt:"-O0"
"programs/pkg-unused.flan" "ok\n"; "programs/pkg-unused.flan" "ok\n";
(* A package may import a package, and one reached along two routes is read (* A package may import a package, and one reached along two routes is read
once: pkg-shared imports sand.flan, which imports raylib, and imports once: pkg-shared imports sand.fln, which imports raylib, and imports
raylib itself. Loading it twice would declare every binding twice. *) raylib itself. Loading it twice would declare every binding twice. *)
if sand_checks then if sand_checks then
outputs "a package reached along two routes" "programs/pkg-shared.flan" outputs "a package reached along two routes" "programs/pkg-shared.flan"
@ -4022,7 +4015,7 @@ let () =
"programs/generic-map-reject.flan" "at $t = f64"; "programs/generic-map-reject.flan" "at $t = f64";
(* This one is refused either way, so the guard is about *which* refusal: (* This one is refused either way, so the guard is about *which* refusal:
with sand.flan out of date the file stops at the import and the needle with sand.fln out of date the file stops at the import and the needle
below would pass on the wrong error. *) below would pass on the wrong error. *)
if sand_checks then if sand_checks then
refuses "a package's main is not visible" "programs/pkg-hidden-main.flan" refuses "a package's main is not visible" "programs/pkg-hidden-main.flan"
@ -6291,7 +6284,7 @@ level "1"
of the program's three cross-kind cast *sites*, the third being the of the program's three cross-kind cast *sites*, the third being the
(f32 int-box) after the loop. That count is the whole reason the (f32 int-box) after the loop. That count is the whole reason the
runtime carries a table of sites at all: these casts live in frame runtime carries a table of sites at all: these casts live in frame
loops, sand.flan's at 120fps, and a flood is not a diagnostic. loops, sand.fln's at 120fps, and a flood is not a diagnostic.
[run] merges stderr into stdout, so the warnings and the numbers come [run] merges stderr into stdout, so the warnings and the numbers come
back in one string and the count is a count over it. *) back in one string and the count is a count over it. *)

View File

@ -670,13 +670,13 @@ let () =
either. The daemon derives the package from the path, so either. The daemon derives the package from the path, so
the file this is sent with is the one the import qualified. *) the file this is sent with is the one the import qualified. *)
let agent_file = let agent_file =
let p = "../vendor/agent/agent.flan" in let p = "../vendor/agent/agent.fln" in
try Unix.realpath p with Unix.Unix_error _ -> p try Unix.realpath p with Unix.Unix_error _ -> p
in in
let r = let r =
request c request c
(Printf.sprintf (Printf.sprintf
"(:op \"eval\" :code \"(defstruct Blob [id i32])\" :file %s)" "(:op \"eval\" :code \"struct Blob(id: i32)\" :file %s)"
(Wire.quote agent_file)) (Wire.quote agent_file))
in in
if status r <> "ok" then if status r <> "ok" then
@ -8924,7 +8924,7 @@ let () =
half of the same claim and lives where it always did. *) half of the same claim and lives where it always did. *)
(* ── A program that starts its agent late ────────────────────────── (* ── A program that starts its agent late ──────────────────────────
The shape a real one has: sand.flan opens a window and starts the The shape a real one has: sand.fln opens a window and starts the
agent afterwards, so the program's own [(agent/start ...)] is seconds agent afterwards, so the program's own [(agent/start ...)] is seconds
into the run. The merged session used to wait up to ten seconds for the into the run. The merged session used to wait up to ten seconds for the
socket *before* running its accept loop, which charged those seconds to socket *before* running its accept loop, which charged those seconds to

View File

@ -860,8 +860,9 @@ let () =
Printf.printf "FAIL %s does not parse: %s: %s\n" Printf.printf "FAIL %s does not parse: %s: %s\n"
path (Loc.to_string loc) msg) path (Loc.to_string loc) msg)
(* dune runs tests in _build/default/test/; the corpus is declared as a (* dune runs tests in _build/default/test/; the corpus is declared as a
dep in test/dune and lands at the build root. *) dep in test/dune and lands at the build root. sand is .fln now
[ "../calc-me.flan"; "../sand.flan" ]; (decision 130), so it is not here — this reads the paren syntax only. *)
[ "../calc-me.flan" ];
Test_support.report ~label:"parse" () Test_support.report ~label:"parse" ()

View File

@ -16,19 +16,15 @@ let () = Watchdog.arm ~seconds:600 "test_session"
let fail fmt = Test_support.fail fmt let fail fmt = Test_support.fail fmt
let has = Test_support.contains let has = Test_support.contains
(* sand.flan is the workspace's own program, edited by hand and not by this (* sand.fln is the workspace's own program, edited by hand and not by this
suite, and three cases below read it: it is the file with imports, and it is suite, and three cases below read it: it is the file with imports, and it is
the single-file package. While it calls the randomness functions by names the single-file package. It once called randomness functions by names the
the prelude does not have it does not check at all, and those three would be prelude did not have; it now calls `rand`, which the prelude does have, so
reporting that rather than anything about a session. The fixture decides, these cases run unconditionally — see test_acceptance.ml, which guards on
so there is nothing to undo once sand.flan is brought up to date. *) the same file for the same reason. *)
let sand_checks = let sand_checks =
match In_channel.with_open_bin "../sand.flan" In_channel.input_all with match In_channel.with_open_bin "../sand.fln" In_channel.input_all with
(* The open paren and not the bare name — see test_acceptance.ml, which | _ -> true
guards on the same file for the same reason: sand.flan names rand-f32 in
a comment as well, and matching that would leave these skipped for ever.
*)
| src -> not (has src "(rand-f32")
| exception _ -> false | exception _ -> false
(* Every rejection is asserted on its reason, not just on the failure: the (* Every rejection is asserted on its reason, not just on the failure: the
@ -760,23 +756,24 @@ let () =
keeps the *expanded* declarations, so the package's names are replaced in keeps the *expanded* declarations, so the package's names are replaced in
place rather than appended a second time and rejected as duplicates. *) place rather than appended a second time and rejected as duplicates. *)
if not sand_checks then if not sand_checks then
print_endline print_endline "session: skipping the cases that read sand.fln — it is not there"
"session: skipping the cases that read sand.flan — it calls rand-f32, \
which is not a name"
else begin else begin
let t, _ = Session.create ~file:"../sand.flan" () in let t, _ = Session.create ~file:"../sand.fln" () in
let src = In_channel.with_open_bin "../sand.flan" In_channel.input_all in let src = In_channel.with_open_bin "../sand.fln" In_channel.input_all in
(* [~origin] is the buffer's own path and both editor paths send it (* [~origin] is the buffer's own path and both editor paths send it
(flan.el's `:file (or buffer-file-name "<buffer>")`). Omitting it here (flan.el's `:file (or buffer-file-name "<buffer>")`). Omitting it here
was testing a request the editor never sends. It used to matter to this was testing a request the editor never sends. It used to matter to this
case for a second reason — sand.flan embedded brush.png, and an embedded case for a second reason — sand.fln embedded brush.png, and an embedded
path resolves relative to the file the form is written in, so the default path resolves relative to the file the form is written in, so the default
origin of "<eval>" found nothing. sand.flan has no embed any more; the origin of "<eval>" found nothing. sand.fln has no embed any more; the
first reason is the one that stands. *) first reason is the one that stands. *)
(match Session.eval ~origin:"../sand.flan" t src with (match
Source.with_code ~syntax:Source.Indented ~at:None (fun () ->
Session.eval ~origin:"../sand.fln" t src)
with
| c -> | c ->
if not (List.mem "game-draw" c.Session.fns) then if not (List.mem "game-draw" c.Session.fns) then
fail "reloading sand.flan did not include its own functions" fail "reloading sand.fln did not include its own functions"
| exception Loc.Error { Loc.dmsg = m; _ } -> | exception Loc.Error { Loc.dmsg = m; _ } ->
fail "reloading a file with imports failed: %s" m) fail "reloading a file with imports failed: %s" m)
end; end;
@ -1418,9 +1415,9 @@ let () =
that can decide it — which is why it is derived here and not sent by the that can decide it — which is why it is derived here and not sent by the
editor. *) editor. *)
if sand_checks then begin if sand_checks then begin
let t, _ = Session.create ~file:"../sand.flan" () in let t, _ = Session.create ~file:"../sand.fln" () in
(match (match
Session.eval ~origin:"../vendor/agent/agent.flan" t Session.eval ~origin:"../vendor/agent/agent.fln" t
"(defn poll [] i32 (poll-raw))" "(defn poll [] i32 (poll-raw))"
with with
| c -> | c ->
@ -1428,13 +1425,13 @@ let () =
fail "a form from a package file reported %s, wanted agent/poll" fail "a form from a package file reported %s, wanted agent/poll"
(String.concat " " c.Session.fns) (String.concat " " c.Session.fns)
| exception Loc.Error { Loc.dmsg = m; _ } -> fail "redefining agent/poll: %s" m); | exception Loc.Error { Loc.dmsg = m; _ } -> fail "redefining agent/poll: %s" m);
(* A package that is a single file, which is what sand.flan is to the (* A package that is a single file, which is what sand.fln is to the
headless driver. The file being edited *is* the package rather than a headless driver. The file being edited *is* the package rather than a
member of a directory, so matching on the directory alone would answer member of a directory, so matching on the directory alone would answer
"not a package" — and the failure is the silent one above: the form "not a package" — and the failure is the silent one above: the form
splices as a bare [step] and the running program keeps the one it had. *) splices as a bare [step] and the running program keeps the one it had. *)
let t2, _ = Session.create ~file:"programs/sand-headless.flan" () in let t2, _ = Session.create ~file:"programs/sand-headless.flan" () in
(match Session.eval ~origin:"../sand.flan" t2 "(defn step [] () (do))" with (match Session.eval ~origin:"../sand.fln" t2 "(defn step [] () (do))" with
| c -> | c ->
if c.Session.fns <> [ "sand/step" ] then if c.Session.fns <> [ "sand/step" ] then
fail "a form from a single-file package reported %s, wanted sand/step" fail "a form from a single-file package reported %s, wanted sand/step"
@ -1442,7 +1439,7 @@ let () =
| exception Loc.Error { Loc.dmsg = m; _ } -> fail "redefining sand/step: %s" m); | exception Loc.Error { Loc.dmsg = m; _ } -> fail "redefining sand/step: %s" m);
(* And a file that is not a package keeps its names as written. *) (* And a file that is not a package keeps its names as written. *)
(match Session.eval ~origin:"../sand.flan" t "(defn game-draw [] () (do))" with (match Session.eval ~origin:"../sand.fln" t "(defn game-draw [] () (do))" with
| c -> | c ->
if c.Session.fns <> [ "game-draw" ] then if c.Session.fns <> [ "game-draw" ] then
fail "a form from the program's own file reported %s" fail "a form from the program's own file reported %s"

View File

@ -252,7 +252,9 @@ let pair flan fln =
let () = let () =
pair "syntax/algorithms.flan" "syntax/algorithms.fln"; pair "syntax/algorithms.flan" "syntax/algorithms.fln";
pair "../sand.flan" "syntax/sand.fln"; (* sand itself is .fln now (decision 130), so there is no more paren
original to pair it against here; syntax/sand.fln is still checked
below. *)
pair "syntax/infer/main.flan" "syntax/infer/main.fln"; pair "syntax/infer/main.flan" "syntax/infer/main.fln";
(* Checked, never run: sand opens a window. *) (* Checked, never run: sand opens a window. *)
List.iter List.iter

View File

@ -174,7 +174,7 @@ let () =
end; end;
(* ── raylib in the browser ──────────────────────────────────────── (* ── raylib in the browser ────────────────────────────────────────
The claim docs/BUILT.md left open. core-basic-window.flan is built for the The claim docs/BUILT.md left open. core-basic-window.fln is built for the
web unchanged — no edit to its `until` loop, which is the whole point web unchanged — no edit to its `until` loop, which is the whole point
of choosing asyncify over emscripten_set_main_loop — and the module is of choosing asyncify over emscripten_set_main_loop — and the module is
then read for the two things that prove the claim rather than assert then read for the two things that prove the claim rather than assert
@ -203,7 +203,7 @@ let () =
fail "no GL imports in the module — raylib did not link"; fail "no GL imports in the module — raylib did not link";
cleanup out); cleanup out);
(* ── sand.flan, the flagship, for the browser ────────────────── (* ── sand.fln, the flagship, for the browser ──────────────────
The program the whole target was wanted for, and the last web build The program the whole target was wanted for, and the last web build
in this process on purpose — see [raylib_web] on why anything after in this process on purpose — see [raylib_web] on why anything after
it would pick the archive up silently. it would pick the archive up silently.
@ -233,8 +233,8 @@ let () =
paints. Only a human opening it can say that; docs/BUILT.md carries the paints. Only a human opening it can say that; docs/BUILT.md carries the
commands. *) commands. *)
let out = Filename.concat scratch "flan-web-sand.html" in let out = Filename.concat scratch "flan-web-sand.html" in
(match web_build "../sand.flan" out with (match web_build "../sand.fln" out with
| exception Failure m -> fail "sand.flan for the browser: %s" m | exception Failure m -> fail "sand.fln for the browser: %s" m
| () -> | () ->
let html, js, wasm = parts out in let html, js, wasm = parts out in
List.iter List.iter

View File

@ -29,7 +29,7 @@
* is no editor, no socket, and no session — `--dev` is refused by name on * is no editor, no socket, and no session — `--dev` is refused by name on
* every wasm target, so a web build has no cells to install a redefinition * every wasm target, so a web build has no cells to install a redefinition
* into even if one arrived. There is nothing to lose because there was never * into even if one arrived. There is nothing to lose because there was never
* anything there. sand.flan already says the same thing about a *native* * anything there. sand.fln already says the same thing about a *native*
* release build, at the call site: * release build, at the call site:
* *
* "Building without --dev is fine — nothing has cells to install into, so a * "Building without --dev is fine — nothing has cells to install into, so a
@ -44,9 +44,9 @@
* *
* It was the other candidate and it is ruled out by arithmetic, not taste. * It was the other candidate and it is ruled out by arithmetic, not taste.
* Flan has no conditional compilation, so a program cannot say "skip this on * Flan has no conditional compilation, so a program cannot say "skip this on
* web". sand.flan calls (agent/start ...) unconditionally, Reach cannot prune * web". sand.fln calls (agent/start ...) unconditionally, Reach cannot prune
* a package something reachable calls into, and a build-time refusal would * a package something reachable calls into, and a build-time refusal would
* therefore mean sand.flan does not build for the browser at all without being * therefore mean sand.fln does not build for the browser at all without being
* edited into a second program. Refusing is only honest when the caller has a * edited into a second program. Refusing is only honest when the caller has a
* way to not ask; here it has none. * way to not ask; here it has none.
* *

22
vendor/edn/edn.fln vendored
View File

@ -5,9 +5,9 @@
;;;; anything: every token's text is a `slice` of the input buffer, not a copy ;;;; anything: every token's text is a `slice` of the input buffer, not a copy
;;;; of it. ;;;; of it.
;;;; ;;;;
;;;; Two layers sit above it. `read.flan`, in this package, is the one that ;;;; Two layers sit above it. `read.fln`, in this package, is the one that
;;;; exists: `(edn/read bytes)` walks this cursor and answers a dynamic ;;;; exists: `edn/read(bytes)` walks this cursor and answers a dynamic
;;;; `Value`. The other, `(read-edn Enemy bytes)` emitting a parser from a ;;;; `Value`. The other, `read-edn(Enemy, bytes)` emitting a parser from a
;;;; compile-time walk over a struct's fields, belongs to the compiler and is ;;;; compile-time walk over a struct's fields, belongs to the compiler and is
;;;; not here; until it exists a caller writes the struct reader by hand ;;;; not here; until it exists a caller writes the struct reader by hand
;;;; against this cursor, and test/programs/edn.flan is a worked example of ;;;; against this cursor, and test/programs/edn.flan is a worked example of
@ -29,19 +29,19 @@
;;;; the kind of contract that otherwise gets discovered from a corrupted ;;;; the kind of contract that otherwise gets discovered from a corrupted
;;;; string three frames later. ;;;; string three frames later.
;;;; ;;;;
;;;; **`read.flan` does not follow this rule, deliberately.** The two layers of ;;;; **`read.fln` does not follow this rule, deliberately.** The two layers of
;;;; this package diverge on exactly this point: a Token is a view, and a Value ;;;; this package diverge on exactly this point: a Token is a view, and a Value
;;;; owns copies of every string in it. The reason is that a view is a fine ;;;; owns copies of every string in it. The reason is that a view is a fine
;;;; thing for a cursor a caller is driving inside the function that holds the ;;;; thing for a cursor a caller is driving inside the function that holds the
;;;; buffer, and a trap for a document handed back out of one. Said the other ;;;; buffer, and a trap for a document handed back out of one. Said the other
;;;; way: the contract above is a property of the *layer*, not of the package, ;;;; way: the contract above is a property of the *layer*, not of the package,
;;;; and a caller who mixes them — holding a Token out of an `(edn/read ...)` ;;;; and a caller who mixes them — holding a Token out of an `edn/read(...)`
;;;; that has returned — is on the tokenizer's terms and not the reader's. ;;;; that has returned — is on the tokenizer's terms and not the reader's.
;;;; ;;;;
;;;; ── What is refused, and why ──────────────────────────────────────── ;;;; ── What is refused, and why ────────────────────────────────────────
;;;; ;;;;
;;;; Every refusal below is a *named* one with a reason attached, reachable as ;;;; Every refusal below is a *named* one with a reason attached, reachable as
;;;; (edn/error-message code). A tokenizer that quietly skipped what it did not ;;;; edn/error-message(code). A tokenizer that quietly skipped what it did not
;;;; understand would hand a caller a value that is not the one in the file. ;;;; understand would hand a caller a value that is not the one in the file.
;;;; ;;;;
;;;; escaped strings "a\nb", "a\"b" — the important one. Unescaping needs ;;;; escaped strings "a\nb", "a\"b" — the important one. Unescaping needs
@ -81,9 +81,9 @@
;;;; ;;;;
;;;; On the cursor, not in the return type. `next` answers a Token whose kind ;;;; On the cursor, not in the return type. `next` answers a Token whose kind
;;;; is tok-error, and the cursor carries the code and the byte offset it was ;;;; is tok-error, and the cursor carries the code and the byte offset it was
;;;; found at; (edn/error-message code) turns the code into the sentence. The ;;;; found at; edn/error-message(code) turns the code into the sentence. The
;;;; offset is the point: an editor underlines a byte range, and an Option with ;;;; offset is the point: an editor underlines a byte range, and an Option with
;;;; no position could not tell it where. An (Option Token) was the alternative ;;;; no position could not tell it where. An Option(Token) was the alternative
;;;; and it loses exactly that — None says something went wrong, and a second ;;;; and it loses exactly that — None says something went wrong, and a second
;;;; out-parameter for the position is the same two fields with a worse shape. ;;;; out-parameter for the position is the same two fields with a worse shape.
;;;; ;;;;
@ -93,7 +93,7 @@
;; ── Token kinds ───────────────────────────────────────────────────── ;; ── Token kinds ─────────────────────────────────────────────────────
;; ;;
;; Plain i32 constants and not a `defenum`, which is the shape that wants ;; Plain i32 constants and not an `enum`, which is the shape that wants
;; explaining. An enum here is FFI-only: `=` on an Enum value fails in emit, ;; explaining. An enum here is FFI-only: `=` on an Enum value fails in emit,
;; and a keyword is not a pattern, so `match` cannot see one either. Both fixes ;; and a keyword is not a pattern, so `match` cannot see one either. Both fixes
;; live in check.ml and emit.ml, which this lane does not touch. An i32 loses ;; live in check.ml and emit.ml, which this lane does not touch. An i32 loses
@ -101,7 +101,7 @@
;; actually branch on, which is the whole job. ;; actually branch on, which is the whole job.
const tok-eof = 0 ; the input is exhausted; text is empty const tok-eof = 0 ; the input is exhausted; text is empty
const tok-error = 1 ; see (edn/error c) and (edn/error-message ...) const tok-error = 1 ; see edn/error(c) and edn/error-message(...)
const tok-nil = 2 ; nil const tok-nil = 2 ; nil
const tok-bool = 3 ; true / false — text is the word const tok-bool = 3 ; true / false — text is the word
const tok-int = 4 ; text parses as i64 const tok-int = 4 ; text parses as i64
@ -379,7 +379,7 @@ fn- read-string(c: Ptr(Cursor), lo: i32) -> Token
;; The one call a caller makes. Advances the cursor past the token it returns. ;; The one call a caller makes. Advances the cursor past the token it returns.
;; ;;
;; A cursor that has already failed keeps answering the same error token and ;; A cursor that has already failed keeps answering the same error token and
;; does not advance, so `(while (!= (.kind t) tok-eof) ...)` terminates on a ;; does not advance, so `while t.kind != tok-eof ...` terminates on a
;; malformed file instead of spinning. ;; malformed file instead of spinning.
fn next(c: Ptr(Cursor)) -> Token fn next(c: Ptr(Cursor)) -> Token
if not is-ok(c) if not is-ok(c)

113
vendor/edn/provide.fln vendored
View File

@ -1,13 +1,13 @@
;;;; defedn: a struct derived from a data file, at compile time. ;;;; defedn: a struct derived from a data file, at compile time.
;;;; ;;;;
;;;; F#'s type providers, with the part that makes them worth having and none ;;;; F#'s type providers, with the part that makes them worth having and none
;;;; of the part that needs a plugin protocol. `(edn/defedn Tileset "t.edn")` ;;;; of the part that needs a plugin protocol. `edn/defedn(Tileset, "t.edn")`
;;;; reads t.edn while the program is being compiled, works out what shape it ;;;; reads t.edn while the program is being compiled, works out what shape it
;;;; is, and emits the struct that shape implies together with a reader for it. ;;;; is, and emits the struct that shape implies together with a reader for it.
;;;; From then on `(.texture-path data)` is a field load off a struct: no Value, ;;;; From then on `(.texture-path data)` is a field load off a struct: no Value,
;;;; no match, no runtime tag, nothing to look up by name. ;;;; no match, no runtime tag, nothing to look up by name.
;;;; ;;;;
;;;; read.flan is the other half of the same choice, and both belong here. A ;;;; read.fln is the other half of the same choice, and both belong here. A
;;;; dynamic Value is what you want when the shape is the program's *input* — ;;;; dynamic Value is what you want when the shape is the program's *input* —
;;;; an editor opening a file it has never seen. A provider is what you want ;;;; an editor opening a file it has never seen. A provider is what you want
;;;; when the shape is part of the program and only the numbers change, which ;;;; when the shape is part of the program and only the numbers change, which
@ -20,25 +20,25 @@
;;;; able to run arbitrary code at expansion time. Three things it could not do ;;;; able to run arbitrary code at expansion time. Three things it could not do
;;;; are what this file rests on, and all three are general: ;;;; are what this file rests on, and all three are general:
;;;; ;;;;
;;;; - `(macro-slurp "t.edn")` reads a file at expansion time, resolved the ;;;; - `macro-slurp("t.edn")` reads a file at expansion time, resolved the
;;;; way `(embed "t.edn")` resolves a path — against the directory of the ;;;; way `embed("t.edn")` resolves a path — against the directory of the
;;;; source file the form is written in. ;;;; source file the form is written in.
;;;; - a package's macro may call the package's own functions, which is why ;;;; - a package's macro may call the package's own functions, which is why
;;;; the derivation below is ordinary Flan over the tokenizer next door ;;;; the derivation below is ordinary Flan over the tokenizer next door
;;;; rather than a second scanner inlined into a macro body. ;;;; rather than a second scanner inlined into a macro body.
;;;; - a macro may answer several declarations, as a top-level `(do ...)`, ;;;; - a macro may answer several declarations, as a top-level `do:`,
;;;; and may refuse with a sentence through `(compile-error "...")`. ;;;; and may refuse with a sentence through `compile-error("...")`.
;;;; ;;;;
;;;; ── The rules ──────────────────────────────────────────────────────── ;;;; ── The rules ────────────────────────────────────────────────────────
;;;; ;;;;
;;;; A map with keyword keys is a struct, one field per key, named for the ;;;; A map with keyword keys is a struct, one field per key, named for the
;;;; keyword. An integer is an i64, a float an f64, a boolean a bool, a string ;;;; keyword. An integer is an i64, a float an f64, a boolean a bool, a string
;;;; a `string` — copied, which is read.flan's contract and not the tokenizer's: ;;;; a `string` — copied, which is read.fln's contract and not the tokenizer's:
;;;; a Token's text points into the buffer, and a struct that outlives the ;;;; a Token's text points into the buffer, and a struct that outlives the
;;;; buffer cannot hold one. ;;;; buffer cannot hold one.
;;;; ;;;;
;;;; A vector of one repeated shape is a `(Vec T)`. A set is this repo's own ;;;; A vector of one repeated shape is a `Vec(T)`. A set is this repo's own
;;;; spelling of one, `(Map T bool)` — check.ml says exactly that where it ;;;; spelling of one, `Map(T, bool)` — check.ml says exactly that where it
;;;; refuses a map with a `()` value — and its elements are therefore read as ;;;; refuses a map with a `()` value — and its elements are therefore read as
;;;; map *keys*. That is why a vector inside a set derives to a fixed array ;;;; map *keys*. That is why a vector inside a set derives to a fixed array
;;;; `[n T]` rather than to a Vec: a Vec is not a map key and `[2 i64]` is. ;;;; `[n T]` rather than to a Vec: a Vec is not a map key and `[2 i64]` is.
@ -65,7 +65,7 @@ fn- joined(a: str, b: str) -> str
fn- joined3(a: str, b: str, c: str) -> str = joined(a, joined(b, c)) fn- joined3(a: str, b: str, c: str) -> str = joined(a, joined(b, c))
;; Copied out, and not `(str (i64->bytes n))`. The prelude's note over ;; Copied out, and not `str(i64->bytes(n))`. The prelude's note over
;; append-i64 is the reason: i64->bytes renders into one shared static buffer ;; append-i64 is the reason: i64->bytes renders into one shared static buffer
;; in the runtime, so two of its results cannot be held at once — and `where` ;; in the runtime, so two of its results cannot be held at once — and `where`
;; below holds a line and a column at the same time, which read as the same ;; below holds a line and a column at the same time, which read as the same
@ -145,7 +145,7 @@ fn need-bool(c: Ptr(Cursor)) -> bool
None -> false None -> false
;; Copied into the allocator, which is the whole difference between a field of ;; Copied into the allocator, which is the whole difference between a field of
;; a struct and a Token's text. The lifetime contract at the top of edn.flan is ;; a struct and a Token's text. The lifetime contract at the top of edn.fln is
;; the reason: `text` is a slice of the buffer, and a struct read out of a ;; the reason: `text` is a slice of the buffer, and a struct read out of a
;; buffer that is later freed would hold a dangling one. ;; buffer that is later freed would hold a dangling one.
fn need-string(c: Ptr(Cursor), a: Allocator) -> str fn need-string(c: Ptr(Cursor), a: Allocator) -> str
@ -210,7 +210,9 @@ struct ReadFailed(struct: str, code: i32, pos: i32)
fn- derive(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived fn- derive(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
let t = next(c) let t = next(c)
if not is-ok(c) if not is-ok(c)
return derived-bad(joined3("the data file could not be read at ", where(src, error-pos(c)), joined(": ", error-message(c.err)))) return derived-bad(joined3("the data file could not be read at ",
where(src, error-pos(c)),
joined(": ", error-message(c.err))))
if t.kind == tok-int if t.kind == tok-int
ok-derived(quasiquote(i64), form-nil(), quasiquote(need-int(c))) ok-derived(quasiquote(i64), form-nil(), quasiquote(need-int(c)))
elif t.kind == tok-float elif t.kind == tok-float
@ -226,9 +228,11 @@ fn- derive(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
elif t.kind == tok-set-open elif t.kind == tok-set-open
derive-set(c, name, t.pos, src) derive-set(c, name, t.pos, src)
elif t.kind == tok-nil elif t.kind == tok-nil
derived-bad(joined3("the nil at ", where(src, t.pos), " has no type to derive — a field that is sometimes absent is not something a struct can hold, so give it a value in the file or take the key out")) derived-bad(joined3("the nil at ", where(src, t.pos),
" has no type to derive — a field that is sometimes absent is not something a struct can hold, so give it a value in the file or take the key out"))
else else
derived-bad(joined3("the value at ", where(src, t.pos), " is not one defedn derives a type from — a map, a vector, a set, an integer, a float, a boolean or a string")) derived-bad(joined3("the value at ", where(src, t.pos),
" is not one defedn derives a type from — a map, a vector, a set, an integer, a float, a boolean or a string"))
;; A vector, whose elements must all come to the same type. The first element ;; A vector, whose elements must all come to the same type. The first element
;; decides; every one after it is compared against that decision and both ;; decides; every one after it is compared against that decision and both
@ -236,7 +240,8 @@ fn- derive(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
;; saying where sends someone to read the whole file. ;; saying where sends someone to read the whole file.
fn- derive-vec(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived fn- derive-vec(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
if is-at-byte(c, \]) if is-at-byte(c, \])
return derived-bad(joined3("the empty vector at ", where(src, at-pos), " has no element to derive an element type from — defedn reads the shape out of the data, and an empty collection carries none")) return derived-bad(joined3("the empty vector at ", where(src, at-pos),
" has no element to derive an element type from — defedn reads the shape out of the data, and an empty collection carries none"))
let head = derive(c, joined(name, "-item"), src) let head = derive(c, joined(name, "-item"), src)
if is-bad(head) if is-bad(head)
return head return head
@ -255,29 +260,34 @@ fn- derive-vec(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Deriv
cn = Form.Sym{.s joined(name, "-new")} cn = Form.Sym{.s joined(name, "-new")}
ok-derived(ty, ok-derived(ty,
with-decl(head.decls, quasiquote(defn(~cn, [a Allocator], ~ty, vec-new(a)))), with-decl(head.decls, quasiquote(defn(~cn, [a Allocator], ~ty, vec-new(a)))),
quasiquote(let([xs ~cn(a)], expect(c, tok-vec-open), while(is-ok(c) and not is-at-byte(c, \]), push(xs, ~read1)), expect(c, tok-vec-close), xs))) quasiquote(let([xs ~cn(a)],
expect(c, tok-vec-open),
while(is-ok(c) and not is-at-byte(c, \]), push(xs, ~read1)),
expect(c, tok-vec-close),
xs)))
;; Why every collection gets a one-line constructor of its own. ;; Why every collection gets a one-line constructor of its own.
;; ;;
;; `(vec-new)` and `(map-new)` each need to be told what they build, and the ;; `vec-new()` and `map-new()` each need to be told what they build, and the
;; way to tell them in argument position is to *name* a type: check.ml's ;; way to tell them in argument position is to *name* a type: check.ml's
;; vec_new_elem and map_new_types take an `Ast.Var` and nothing else. A type ;; vec_new_elem and map_new_types take an `Ast.Var` and nothing else. A type
;; this derives may have no name — `(Vec i64)` has none, and `[2 i64]`, which ;; this derives may have no name — `Vec(i64)` has none, and `[2 i64]`, which
;; is the key of the set in the file this was built for, has none either. ;; is the key of the set in the file this was built for, has none either.
;; ;;
;; Both fall back to what the context wants, and a function's return type is a ;; Both fall back to what the context wants, and a function's return type is a
;; type position where anything can be written. So the type is stated once, in ;; type position where anything can be written. So the type is stated once, in
;; a signature, and the bare call in the body gets it from `want`. It is also ;; a signature, and the bare call in the body gets it from `want`. It is also
;; the more readable expansion: the reader says `(cells-new a)` where it would ;; the more readable expansion: the reader says `cells-new(a)` where it would
;; otherwise carry a type nobody wrote. ;; otherwise carry a type nobody wrote.
fn- with-decl(decls: [Form], d: Form) -> [Form] fn- with-decl(decls: [Form], d: Form) -> [Form]
form-append(decls, form-cons(d, form-nil())) form-append(decls, form-cons(d, form-nil()))
;; A set becomes `(Map T bool)`, so its elements are map keys. `derive-key` is ;; A set becomes `Map(T, bool)`, so its elements are map keys. `derive-key` is
;; where that constraint is enforced and said. ;; where that constraint is enforced and said.
fn- derive-set(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived fn- derive-set(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
if is-at-byte(c, \}) if is-at-byte(c, \})
return derived-bad(joined3("the empty set at ", where(src, at-pos), " has no element to derive an element type from")) return derived-bad(joined3("the empty set at ", where(src, at-pos),
" has no element to derive an element type from"))
let head = derive-key(c, joined(name, "-key"), src) let head = derive-key(c, joined(name, "-key"), src)
if is-bad(head) if is-bad(head)
return head return head
@ -296,11 +306,15 @@ fn- derive-set(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Deriv
cn = Form.Sym{.s joined(name, "-new")} cn = Form.Sym{.s joined(name, "-new")}
ok-derived(ty, ok-derived(ty,
with-decl(head.decls, quasiquote(defn(~cn, [a Allocator], ~ty, map-new(a)))), with-decl(head.decls, quasiquote(defn(~cn, [a Allocator], ~ty, map-new(a)))),
quasiquote(let([tbl ~cn(a)], expect(c, tok-set-open), while(is-ok(c) and not is-at-byte(c, \}), put(tbl, ~read1, true)), expect(c, tok-map-close), tbl))) quasiquote(let([tbl ~cn(a)],
expect(c, tok-set-open),
while(is-ok(c) and not is-at-byte(c, \}), put(tbl, ~read1, true)),
expect(c, tok-map-close),
tbl)))
;; One element of a set. The scalars that are map keys pass; a vector becomes a ;; One element of a set. The scalars that are map keys pass; a vector becomes a
;; fixed array, which is one where a Vec is not; anything else is refused here ;; fixed array, which is one where a Vec is not; anything else is refused here
;; rather than at the `(Map ...)` the caller would build out of it, because a ;; rather than at the `Map(...)` the caller would build out of it, because a
;; map-key refusal names a type nobody wrote. ;; map-key refusal names a type nobody wrote.
fn- derive-key(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived fn- derive-key(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
if is-at-byte(c, \[) if is-at-byte(c, \[)
@ -309,7 +323,8 @@ fn- derive-key(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
if is-bad(d) if is-bad(d)
return d return d
if not is-key-type(d.ty) if not is-key-type(d.ty)
return derived-bad(joined3("a set of ", render(d.ty), " is not something this builds: a set becomes a (Map T bool), so its elements are map keys. Integers, booleans, strings and vectors of those are")) return derived-bad(joined3("a set of ", render(d.ty),
" is not something this builds: a set becomes a (Map T bool), so its elements are map keys. Integers, booleans, strings and vectors of those are"))
d d
;; A vector in key position. Its length is part of its type, so every element ;; A vector in key position. Its length is part of its type, so every element
@ -319,7 +334,8 @@ fn- derive-key(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
fn- derive-array(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived fn- derive-array(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
let open = next(c) let open = next(c)
if is-at-byte(c, \]) if is-at-byte(c, \])
return derived-bad(joined3("the empty vector at ", where(src, open.pos), " is inside a set, and an empty fixed array has no element type and no length")) return derived-bad(joined3("the empty vector at ", where(src, open.pos),
" is inside a set, and an empty fixed array has no element type and no length"))
let head = derive(c, joined(name, "-item"), src) let head = derive(c, joined(name, "-item"), src)
if is-bad(head) if is-bad(head)
return head return head
@ -333,16 +349,26 @@ fn- derive-array(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
n += 1 n += 1
expect(c, tok-vec-close) expect(c, tok-vec-close)
if not is-key-type(head.ty) if not is-key-type(head.ty)
return derived-bad(joined3("a set of vectors of ", render(head.ty), " is not something this builds: the vector becomes a fixed array, which is a map key only when its elements are compared bytewise")) return derived-bad(joined3("a set of vectors of ", render(head.ty),
" is not something this builds: the vector becomes a fixed array, which is a map key only when its elements are compared bytewise"))
let elem = head.ty let elem = head.ty
read1 = head.reader read1 = head.reader
count = Form.Int{.i n} count = Form.Int{.i n}
ok-derived(quasiquote([~count ~elem]), head.decls, ok-derived(quasiquote([~count ~elem]), head.decls,
quasiquote(let([arr array(~count, ~elem) i 0], expect(c, tok-vec-open), while(is-ok(c) and not is-at-byte(c, \]) and i < ~count, set(arr[i], ~read1), set(i, i + 1)), expect(c, tok-vec-close), arr))) quasiquote(let([arr array(~count, ~elem) i 0],
expect(c, tok-vec-open),
while(is-ok(c) and not is-at-byte(c, \]) and i < ~count,
set(arr[i], ~read1), set(i, i + 1)),
expect(c, tok-vec-close),
arr)))
fn- disagreement(what: str, src: [const u8], at-pos: i32, n: i64, first: Form, second: Form) -> str fn- disagreement(what: str, src: [const u8], at-pos: i32, n: i64, first: Form, second: Form) -> str
joined3(joined3("the ", what, " at "), where(src, at-pos), joined3(joined3("the ", what, " at "), where(src, at-pos),
joined3(joined3(" holds more than one shape: its first element is ", render(first), " and element "), i64->string(n), joined3(" is ", render(second), ". Every element has to be the same shape, because the type this becomes has one element type"))) joined3(joined3(" holds more than one shape: its first element is ", render(first),
" and element "),
i64->string(n),
joined3(" is ", render(second),
". Every element has to be the same shape, because the type this becomes has one element type")))
;; ── A map, which is a struct ──────────────────────────────────────── ;; ── A map, which is a struct ────────────────────────────────────────
;; ;;
@ -357,7 +383,8 @@ fn- disagreement(what: str, src: [const u8], at-pos: i32, n: i64, first: Form, s
;; Both signal SchemaDrift. See the note over that type. ;; Both signal SchemaDrift. See the note over that type.
fn- derive-map(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived fn- derive-map(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
if is-at-byte(c, \}) if is-at-byte(c, \})
return derived-bad(joined3("the empty map at ", where(src, at-pos), " has no keys to derive fields from — a struct with no fields is not a shape anything can be read into")) return derived-bad(joined3("the empty map at ", where(src, at-pos),
" has no keys to derive fields from — a struct with no fields is not a shape anything can be read into"))
let fields = vec-new(Form) ; the defstruct's [name type ...] vector let fields = vec-new(Form) ; the defstruct's [name type ...] vector
clauses = vec-new(Form) ; the reader's cond: test, body, test, body clauses = vec-new(Form) ; the reader's cond: test, body, test, body
missing = vec-new(Form) ; one per field, checked when the map closes missing = vec-new(Form) ; one per field, checked when the map closes
@ -366,9 +393,13 @@ fn- derive-map(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Deriv
while is-ok(c) and not is-at-byte(c, \}) while is-ok(c) and not is-at-byte(c, \})
let k = next(c) let k = next(c)
if not is-ok(c) if not is-ok(c)
return derived-bad(joined3("the data file could not be read at ", where(src, error-pos(c)), joined(": ", error-message(c.err)))) return derived-bad(joined3("the data file could not be read at ",
where(src, error-pos(c)),
joined(": ", error-message(c.err))))
if k.kind != tok-keyword if k.kind != tok-keyword
return derived-bad(joined3(joined3("the map at ", where(src, at-pos), " has a key at "), where(src, k.pos), " that is not a keyword. A struct's fields are named, so every key of a map defedn reads has to be one — :name, not \"name\" and not 1")) return derived-bad(joined3(joined3("the map at ", where(src, at-pos), " has a key at "),
where(src, k.pos),
" that is not a keyword. A struct's fields are named, so every key of a map defedn reads has to be one — :name, not \"name\" and not 1"))
let fname = copy-text(k.text) let fname = copy-text(k.text)
let d = derive(c, joined3(name, "-", fname), src) let d = derive(c, joined3(name, "-", fname), src)
if is-bad(d) if is-bad(d)
@ -388,7 +419,9 @@ fn- derive-map(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Deriv
;; bit is decided here, where the field is, so the two cannot fall ;; bit is decided here, where the field is, so the two cannot fall
;; out of step the way a parallel list of names would. ;; out of step the way a parallel list of names would.
push(missing, push(missing,
quasiquote(when seen && ~bit == 0 then signal(SchemaDrift{.field ~lit .struct ~(Form.Str{.s name}) .is-extra false .pos k.pos}))) quasiquote(when seen && ~bit == 0 then
signal(SchemaDrift{.field ~lit .struct ~(Form.Str{.s name})
.is-extra false .pos k.pos})))
idx += 1 idx += 1
expect(c, tok-map-close) expect(c, tok-map-close)
;; An unknown key. The hand-written reader skips one, which is right when a ;; An unknown key. The hand-written reader skips one, which is right when a
@ -398,7 +431,9 @@ fn- derive-map(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Deriv
;; handle the condition still reads the rest. ;; handle the condition still reads the rest.
push(clauses, quasiquote(:else)) push(clauses, quasiquote(:else))
push(clauses, push(clauses,
quasiquote(do(signal(SchemaDrift{.field copy-text(k.text) .struct ~(Form.Str{.s name}) .is-extra true .pos k.pos}), when not skip-value(c) then return out))) quasiquote(do(signal(SchemaDrift{.field copy-text(k.text) .struct ~(Form.Str{.s name})
.is-extra true .pos k.pos}),
when not skip-value(c) then return out)))
let sname = Form.Sym{.s name} let sname = Form.Sym{.s name}
rname = Form.Sym{.s joined("read-", name)} rname = Form.Sym{.s joined("read-", name)}
let (struct) = quasiquote(defstruct(~sname, ~(Form.Vec{.xs slice(fields)}))) let (struct) = quasiquote(defstruct(~sname, ~(Form.Vec{.xs slice(fields)})))
@ -428,7 +463,7 @@ fn- is-same-type(a: Form, b: Form) -> bool
is-bytes-equal(bytes-view(render(a)), bytes-view(render(b))) is-bytes-equal(bytes-view(render(a)), bytes-view(render(b)))
;; A type form as text, for the refusals. Only the shapes this file builds — a ;; A type form as text, for the refusals. Only the shapes this file builds — a
;; name, a number, `(Vec T)`, `(Map K V)` and `[n T]` — because nothing else ;; name, a number, `Vec(T)`, `Map(K, V)` and `[n T]` — because nothing else
;; ever reaches it. ;; ever reaches it.
fn render(f: Form) -> str fn render(f: Form) -> str
match f match f
@ -449,12 +484,13 @@ fn- render-items(xs: [Form]) -> str
;; itself, so there is no equality for a map to hash. ;; itself, so there is no equality for a map to hash.
fn- is-key-type(t: Form) -> bool fn- is-key-type(t: Form) -> bool
let s = bytes-view(render(t)) let s = bytes-view(render(t))
is-bytes-equal(s, bytes-view("i64")) or (is-bytes-equal(s, bytes-view("bool")) or is-bytes-equal(s, bytes-view("string"))) is-bytes-equal(s, bytes-view("i64")) or
(is-bytes-equal(s, bytes-view("bool")) or is-bytes-equal(s, bytes-view("string")))
;; ── The macro ─────────────────────────────────────────────────────── ;; ── The macro ───────────────────────────────────────────────────────
;; ;;
;; `(edn/defedn Tileset "assets/tileset.edn")`. The path is relative to the ;; `edn/defedn(Tileset, "assets/tileset.edn")`. The path is relative to the
;; file this is written in, exactly as `(embed "assets/tileset.edn")` is — see ;; file this is written in, exactly as `embed("assets/tileset.edn")` is — see
;; `macro-slurp` in the prelude, and check.ml's `embed_path`, which is the rule ;; `macro-slurp` in the prelude, and check.ml's `embed_path`, which is the rule
;; it copies. ;; it copies.
;; ;;
@ -474,7 +510,8 @@ macro defedn(& args)
match macro-slurp(path) match macro-slurp(path)
Some(src) -> provide(name, path, src) Some(src) -> provide(name, path, src)
None -> None ->
refuse(joined3("there is no file at ", path, ", read relative to the file this defedn is written in — the same place (embed \"...\") would look")) refuse(joined3("there is no file at ", path,
", read relative to the file this defedn is written in — the same place (embed \"...\") would look"))
_ -> _ ->
refuse("defedn's first argument is the name of the struct to declare, written as a name") refuse("defedn's first argument is the name of the struct to declare, written as a name")
_ -> _ ->

8
vendor/edn/read.fln vendored
View File

@ -1,9 +1,9 @@
;;;; The dynamic reader: an EDN document, and no type to read it into. ;;;; The dynamic reader: an EDN document, and no type to read it into.
;;;; ;;;;
;;;; `edn.flan` answers "what is the next token". This answers "what is in the ;;;; `edn.fln` answers "what is the next token". This answers "what is in the
;;;; file", for a caller that has no struct to hand — a config file whose keys ;;;; file", for a caller that has no struct to hand — a config file whose keys
;;;; are not known until it is read, a tileset, a save. `(edn/defedn T path)` ;;;; are not known until it is read, a tileset, a save. `edn/defedn(T, path)`
;;;; in provide.flan is the other direction: the shape known at compile time, ;;;; in provide.fln is the other direction: the shape known at compile time,
;;;; read into a struct, at no run-time cost. Together they are the two sides ;;;; read into a struct, at no run-time cost. Together they are the two sides
;;;; of one capability, and this is the dynamic one. ;;;; of one capability, and this is the dynamic one.
;;;; ;;;;
@ -59,7 +59,7 @@
;; ── Copying a token's text ────────────────────────────────────────── ;; ── Copying a token's text ──────────────────────────────────────────
;; Not the dyn reader's own — everything below boxes through the runtime, ;; Not the dyn reader's own — everything below boxes through the runtime,
;; which copies for itself — but provide.flan's generated readers build typed ;; which copies for itself — but provide.fln's generated readers build typed
;; strings out of token text and this is where that copy has always lived. ;; strings out of token text and this is where that copy has always lived.
;; The (Vec u8) is the copy; the string is a view of it, and the Vec header is ;; The (Vec u8) is the copy; the string is a view of it, and the Vec header is
;; dropped here on purpose. Nothing individually owns a block in a region — ;; dropped here on purpose. Nothing individually owns a block in a region —

18
vendor/json/json.fln vendored
View File

@ -3,7 +3,7 @@
;;;; The shape is vendor/edn's, deliberately: a Cursor over a caller's buffer, ;;;; The shape is vendor/edn's, deliberately: a Cursor over a caller's buffer,
;;;; one `next` that answers a Token, errors accumulated on the cursor with the ;;;; one `next` that answers a Token, errors accumulated on the cursor with the
;;;; byte offset they were found at, and every refusal named and given a reason ;;;; byte offset they were found at, and every refusal named and given a reason
;;;; reachable as (json/error-message code). Read vendor/edn/edn.flan first if ;;;; reachable as json/error-message(code). Read vendor/edn/edn.fln first if
;;;; you have not; everything that file argues for is argued for there and only ;;;; you have not; everything that file argues for is argued for there and only
;;;; the differences are argued for here. ;;;; the differences are argued for here.
;;;; ;;;;
@ -26,7 +26,7 @@
;;;; ;;;;
;;;; * a Token's `text` is a slice INTO the buffer, exactly as edn's is, and ;;;; * a Token's `text` is a slice INTO the buffer, exactly as edn's is, and
;;;; for a string token it is the RAW interior — backslashes and all; ;;;; for a string token it is the RAW interior — backslashes and all;
;;;; * (json/string-of t) is the one call that allocates. It answers a ;;;; * json/string-of(t) is the one call that allocates. It answers a
;;;; string whose bytes are a fresh copy in the context's allocator, with ;;;; string whose bytes are a fresh copy in the context's allocator, with
;;;; every escape resolved. ;;;; every escape resolved.
;;;; ;;;;
@ -40,7 +40,7 @@
;;;; survive the free-all in the cases that had no escapes and not in the ;;;; survive the free-all in the cases that had no escapes and not in the
;;;; others, which is the kind of contract nobody can hold in their head. ;;;; others, which is the kind of contract nobody can hold in their head.
;;;; ;;;;
;;;; Note what that buys against vendor/edn/read.flan's dyn reader, which ;;;; Note what that buys against vendor/edn/read.fln's dyn reader, which
;;;; settled the same question a different way: it takes no allocator at all, ;;;; settled the same question a different way: it takes no allocator at all,
;;;; because every string it produces is boxed onto the collector's heap on ;;;; because every string it produces is boxed onto the collector's heap on
;;;; the spot — read-value's "a document owns its strings" rule. This ;;;; the spot — read-value's "a document owns its strings" rule. This
@ -53,8 +53,8 @@
;;;; ── The allocator ─────────────────────────────────────────────────── ;;;; ── The allocator ───────────────────────────────────────────────────
;;;; ;;;;
;;;; string-of names no allocator and takes no parameter. spec-memory.md puts ;;;; string-of names no allocator and takes no parameter. spec-memory.md puts
;;;; the allocator in the calling convention, so the (vec-new u8) inside it ;;;; the allocator in the calling convention, so the vec-new(u8) inside it
;;;; takes the context's, and (with-allocator frame (json/string-of t)) is the ;;;; takes the context's, and with-allocator(frame, json/string-of(t)) is the
;;;; whole of "reading against an arena". An explicit allocator at the ;;;; whole of "reading against an arena". An explicit allocator at the
;;;; construction site would override that, which is how this would take one if ;;;; construction site would override that, which is how this would take one if
;;;; the idiom could not express it. It can. ;;;; the idiom could not express it. It can.
@ -75,7 +75,7 @@
;;;; single quotes 'x' — legal in JSON5. ;;;; single quotes 'x' — legal in JSON5.
;;;; +1 — a leading plus is JSON5's. ;;;; +1 — a leading plus is JSON5's.
;;;; .5 and 1. — JSON5 allows a bare leading or trailing ;;;; .5 and 1. — JSON5 allows a bare leading or trailing
;;;; decimal point. Worth contrasting with edn.flan, which ;;;; decimal point. Worth contrasting with edn.fln, which
;;;; goes the other way and reads `.5` as a float on ;;;; goes the other way and reads `.5` as a float on
;;;; purpose; here it is an error, and named. ;;;; purpose; here it is an error, and named.
;;;; 0x1f — hexadecimal is JSON5's. ;;;; 0x1f — hexadecimal is JSON5's.
@ -145,12 +145,12 @@
;; ── Token kinds ───────────────────────────────────────────────────── ;; ── Token kinds ─────────────────────────────────────────────────────
;; ;;
;; Plain i32 constants and not a `defenum`, for the reason vendor/edn gives: ;; Plain i32 constants and not an `enum`, for the reason vendor/edn gives:
;; `=` on an Enum value fails in emit and a keyword is not a pattern, so an ;; `=` on an Enum value fails in emit and a keyword is not a pattern, so an
;; enum here would be a token kind a caller could not branch on. ;; enum here would be a token kind a caller could not branch on.
const tok-eof = 0 ; the input is exhausted; text is empty const tok-eof = 0 ; the input is exhausted; text is empty
const tok-error = 1 ; see (json/error c) and (json/error-message ...) const tok-error = 1 ; see json/error(c) and json/error-message(...)
const tok-null = 2 ; null const tok-null = 2 ; null
const tok-bool = 3 ; true / false — text is the word const tok-bool = 3 ; true / false — text is the word
const tok-int = 4 ; a number with no fraction and no exponent const tok-int = 4 ; a number with no fraction and no exponent
@ -555,7 +555,7 @@ fn- read-string(c: Ptr(Cursor), lo: i32) -> Token
;; The one call a caller makes. Advances the cursor past the token it returns. ;; The one call a caller makes. Advances the cursor past the token it returns.
;; ;;
;; A cursor that has already failed keeps answering the same error token and ;; A cursor that has already failed keeps answering the same error token and
;; does not advance, so `(while (!= (.kind t) tok-eof) ...)` terminates on a ;; does not advance, so `while t.kind != tok-eof ...` terminates on a
;; malformed document instead of spinning. ;; malformed document instead of spinning.
fn next(c: Ptr(Cursor)) -> Token fn next(c: Ptr(Cursor)) -> Token
if not is-ok(c) if not is-ok(c)

View File

@ -1,8 +1,8 @@
;;;; defjson: a struct derived from a JSON file, at compile time. ;;;; defjson: a struct derived from a JSON file, at compile time.
;;;; ;;;;
;;;; `(json/defjson Config "config.json")` reads config.json while the program ;;;; `json/defjson(Config, "config.json")` reads config.json while the program
;;;; is being compiled, derives the struct its shape implies, and emits that ;;;; is being compiled, derives the struct its shape implies, and emits that
;;;; struct with a reader over the tokenizer next door. `(.port cfg)` is then a ;;;; struct with a reader over the tokenizer next door. `cfg.port` is then a
;;;; field load: no Value, no match, no runtime tag, nothing looked up by name. ;;;; field load: no Value, no match, no runtime tag, nothing looked up by name.
;;;; ;;;;
;;;; It is the same idea as vendor/edn's defedn and deliberately not the same ;;;; It is the same idea as vendor/edn's defedn and deliberately not the same
@ -16,7 +16,7 @@
;;;; ;;;;
;;;; ── What is different, and why ─────────────────────────────────────── ;;;; ── What is different, and why ───────────────────────────────────────
;;;; ;;;;
;;;; **Strings go through `string-of` and never through `.text`.** json.flan's ;;;; **Strings go through `string-of` and never through `.text`.** json.fln's
;;;; `.text` is the RAW interior of a string token, escapes undecoded, so a ;;;; `.text` is the RAW interior of a string token, escapes undecoded, so a
;;;; field read off it would hold a literal backslash-n where the file meant a ;;;; field read off it would hold a literal backslash-n where the file meant a
;;;; newline. `string-of` is the one call in that package that allocates, and ;;;; newline. `string-of` is the one call in that package that allocates, and
@ -31,7 +31,7 @@
;;;; field. ;;;; field.
;;;; ;;;;
;;;; **There are no sets**, so there is no map-key path and no fixed array: ;;;; **There are no sets**, so there is no map-key path and no fixed array:
;;;; every collection here is a `(Vec T)`. defjson is strictly the smaller of ;;;; every collection here is a `Vec(T)`. defjson is strictly the smaller of
;;;; the two. ;;;; the two.
;;;; ;;;;
;;;; JSON has no integer type of its own — the tokenizer draws the line at ;;;; JSON has no integer type of its own — the tokenizer draws the line at
@ -50,7 +50,7 @@ fn- joined(a: str, b: str) -> str
fn- joined3(a: str, b: str, c: str) -> str = joined(a, joined(b, c)) fn- joined3(a: str, b: str, c: str) -> str = joined(a, joined(b, c))
;; Copied out, and not `(str (i64->bytes n))`: the prelude's note over ;; Copied out, and not `str(i64->bytes(n))`: the prelude's note over
;; append-i64 is the reason — i64->bytes renders into one shared static buffer ;; append-i64 is the reason — i64->bytes renders into one shared static buffer
;; in the runtime, so two of its results cannot be held at once, and `where` ;; in the runtime, so two of its results cannot be held at once, and `where`
;; holds a line and a column at the same time. ;; holds a line and a column at the same time.
@ -138,7 +138,7 @@ fn is-at-byte(c: Ptr(Cursor), b: u8) -> bool
not is-at-end(c) and c.src[c.pos] == b not is-at-end(c) and c.src[c.pos] == b
;; The comma between two members, taken when there is one. A trailing comma is ;; The comma between two members, taken when there is one. A trailing comma is
;; json.flan's err-trailing-comma and is the tokenizer's to refuse, not this ;; json.fln's err-trailing-comma and is the tokenizer's to refuse, not this
;; loop's: taking it here and then meeting the closer is exactly the shape that ;; loop's: taking it here and then meeting the closer is exactly the shape that
;; refusal is written against. ;; refusal is written against.
fn comma(c: Ptr(Cursor)) -> () fn comma(c: Ptr(Cursor)) -> ()
@ -173,7 +173,9 @@ struct ReadFailed(struct: str, code: i32, pos: i32)
fn- derive(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived fn- derive(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
let t = next(c) let t = next(c)
if not is-ok(c) if not is-ok(c)
return derived-bad(joined3("the data file could not be read at ", where(src, error-pos(c)), joined(": ", error-message(c.err)))) return derived-bad(joined3("the data file could not be read at ",
where(src, error-pos(c)),
joined(": ", error-message(c.err))))
if t.kind == tok-int if t.kind == tok-int
ok-derived(quasiquote(i64), form-nil(), quasiquote(need-int(c))) ok-derived(quasiquote(i64), form-nil(), quasiquote(need-int(c)))
elif t.kind == tok-float elif t.kind == tok-float
@ -187,9 +189,11 @@ fn- derive(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
elif t.kind == tok-array-open elif t.kind == tok-array-open
derive-array(c, name, t.pos, src) derive-array(c, name, t.pos, src)
elif t.kind == tok-null elif t.kind == tok-null
derived-bad(joined3("the null at ", where(src, t.pos), " has no type to derive — a field that is sometimes absent is not something a struct can hold, so give it a value in the file or take the key out")) derived-bad(joined3("the null at ", where(src, t.pos),
" has no type to derive — a field that is sometimes absent is not something a struct can hold, so give it a value in the file or take the key out"))
else else
derived-bad(joined3("the value at ", where(src, t.pos), " is not one defjson derives a type from — an object, an array, a number, a boolean or a string")) derived-bad(joined3("the value at ", where(src, t.pos),
" is not one defjson derives a type from — an object, an array, a number, a boolean or a string"))
;; An array, whose elements must all come to the same type. The first decides; ;; An array, whose elements must all come to the same type. The first decides;
;; every one after it is compared against that, and both positions are named ;; every one after it is compared against that, and both positions are named
@ -197,7 +201,8 @@ fn- derive(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
;; whole file. ;; whole file.
fn- derive-array(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived fn- derive-array(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
if is-at-byte(c, \]) if is-at-byte(c, \])
return derived-bad(joined3("the empty array at ", where(src, at-pos), " has no element to derive an element type from — defjson reads the shape out of the data, and an empty collection carries none")) return derived-bad(joined3("the empty array at ", where(src, at-pos),
" has no element to derive an element type from — defjson reads the shape out of the data, and an empty collection carries none"))
let head = derive(c, joined(name, "-item"), src) let head = derive(c, joined(name, "-item"), src)
if is-bad(head) if is-bad(head)
return head return head
@ -208,7 +213,12 @@ fn- derive-array(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Der
if is-bad(item) if is-bad(item)
return item return item
if not is-same-type(head.ty, item.ty) if not is-same-type(head.ty, item.ty)
return derived-bad(joined3(joined3("the array at ", where(src, at-pos), " holds more than one shape: element 0 is "), render(head.ty), joined3(joined3(" and element ", i64->string(n), " is "), render(item.ty), ". Every element of an array has to be the same shape, because the (Vec T) it becomes has one element type"))) return derived-bad(joined3(joined3("the array at ", where(src, at-pos),
" holds more than one shape: element 0 is "),
render(head.ty),
joined3(joined3(" and element ", i64->string(n), " is "),
render(item.ty),
". Every element of an array has to be the same shape, because the (Vec T) it becomes has one element type")))
comma(c) comma(c)
n += 1 n += 1
expect(c, tok-array-close) expect(c, tok-array-close)
@ -216,20 +226,25 @@ fn- derive-array(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Der
read1 = head.reader read1 = head.reader
ty = quasiquote(Vec(~elem)) ty = quasiquote(Vec(~elem))
cn = Form.Sym{.s joined(name, "-new")} cn = Form.Sym{.s joined(name, "-new")}
;; The constructor states the type so the bare (vec-new a) in its body ;; The constructor states the type so the bare vec-new(a) in its body
;; can take it from `want`. (vec-new) has to be *told* what it builds by ;; can take it from `want`. vec-new() has to be *told* what it builds by
;; naming a type, and `(Vec i64)` has no name to be — but a signature is ;; naming a type, and `Vec(i64)` has no name to be — but a signature is
;; a type position where anything can be written. The reader reads ;; a type position where anything can be written. The reader reads
;; better for it too. ;; better for it too.
ok-derived(ty, ok-derived(ty,
with-decl(head.decls, quasiquote(defn(~cn, [a Allocator], ~ty, vec-new(a)))), with-decl(head.decls, quasiquote(defn(~cn, [a Allocator], ~ty, vec-new(a)))),
quasiquote(let([xs ~cn(a)], expect(c, tok-array-open), while(is-ok(c) and not is-at-byte(c, \]), push(xs, ~read1), comma(c)), expect(c, tok-array-close), xs))) quasiquote(let([xs ~cn(a)],
expect(c, tok-array-open),
while(is-ok(c) and not is-at-byte(c, \]), push(xs, ~read1), comma(c)),
expect(c, tok-array-close),
xs)))
;; ── An object, which is a struct ──────────────────────────────────── ;; ── An object, which is a struct ────────────────────────────────────
fn- derive-object(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived fn- derive-object(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
if is-at-byte(c, \}) if is-at-byte(c, \})
return derived-bad(joined3("the empty object at ", where(src, at-pos), " has no members to derive fields from — a struct with no fields is not a shape anything can be read into")) return derived-bad(joined3("the empty object at ", where(src, at-pos),
" has no members to derive fields from — a struct with no fields is not a shape anything can be read into"))
let fields = vec-new(Form) ; the defstruct's [name type ...] vector let fields = vec-new(Form) ; the defstruct's [name type ...] vector
clauses = vec-new(Form) ; the reader's cond: test, body, test, body clauses = vec-new(Form) ; the reader's cond: test, body, test, body
missing = vec-new(Form) ; one per field, checked when the object closes missing = vec-new(Form) ; one per field, checked when the object closes
@ -238,9 +253,13 @@ fn- derive-object(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> De
while is-ok(c) and not is-at-byte(c, \}) while is-ok(c) and not is-at-byte(c, \})
let k = next(c) let k = next(c)
if not is-ok(c) if not is-ok(c)
return derived-bad(joined3("the data file could not be read at ", where(src, error-pos(c)), joined(": ", error-message(c.err)))) return derived-bad(joined3("the data file could not be read at ",
where(src, error-pos(c)),
joined(": ", error-message(c.err))))
if k.kind != tok-string if k.kind != tok-string
return derived-bad(joined3("the object at ", joined3(where(src, at-pos), " has a member at ", where(src, k.pos)), " whose name is not a string, which JSON requires")) return derived-bad(joined3("the object at ",
joined3(where(src, at-pos), " has a member at ", where(src, k.pos)),
" whose name is not a string, which JSON requires"))
;; The comparison in the generated reader is against the token's RAW ;; The comparison in the generated reader is against the token's RAW
;; text, which costs no allocation per key. That is only the same ;; text, which costs no allocation per key. That is only the same
;; question as "is this the field" when the name has no escape in it — ;; question as "is this the field" when the name has no escape in it —
@ -249,9 +268,11 @@ fn- derive-object(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> De
;; the file where it would have mattered. ;; the file where it would have mattered.
let raw = k.text let raw = k.text
if has-escape(raw) if has-escape(raw)
return derived-bad(joined3("the member name at ", where(src, k.pos), " has an escape in it. A generated reader compares a key against the bytes as written, which costs nothing per key and is only the same question when the name is written plainly — so this one is refused rather than matched wrongly")) return derived-bad(joined3("the member name at ", where(src, k.pos),
" has an escape in it. A generated reader compares a key against the bytes as written, which costs nothing per key and is only the same question when the name is written plainly — so this one is refused rather than matched wrongly"))
if not is-name-like(raw) if not is-name-like(raw)
return derived-bad(joined3("the member name at ", where(src, k.pos), " is not a name a program could write, so there is no field it can become. A struct's fields are named; letters, digits, - and ? are what a name is made of")) return derived-bad(joined3("the member name at ", where(src, k.pos),
" is not a name a program could write, so there is no field it can become. A struct's fields are named; letters, digits, - and ? are what a name is made of"))
expect(c, tok-colon) expect(c, tok-colon)
let fname = copy-of(raw) let fname = copy-of(raw)
let d = derive(c, joined3(name, "-", fname), src) let d = derive(c, joined3(name, "-", fname), src)
@ -272,7 +293,9 @@ fn- derive-object(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> De
;; beside the field, so the two cannot fall out of step the way a ;; beside the field, so the two cannot fall out of step the way a
;; parallel list of names would. ;; parallel list of names would.
push(missing, push(missing,
quasiquote(when seen && ~bit == 0 then signal(SchemaDrift{.field ~lit .struct ~(Form.Str{.s name}) .is-extra false .pos close.pos}))) quasiquote(when seen && ~bit == 0 then
signal(SchemaDrift{.field ~lit .struct ~(Form.Str{.s name})
.is-extra false .pos close.pos})))
comma(c) comma(c)
idx += 1 idx += 1
expect(c, tok-object-close) expect(c, tok-object-close)
@ -281,7 +304,9 @@ fn- derive-object(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> De
;; program that declines to handle the condition still reads the rest. ;; program that declines to handle the condition still reads the rest.
push(clauses, quasiquote(:else)) push(clauses, quasiquote(:else))
push(clauses, push(clauses,
quasiquote(do(signal(SchemaDrift{.field match(string-of(k), Some(s), s, None, "") .struct ~(Form.Str{.s name}) .is-extra true .pos k.pos}), when not skip-value(c) then return out))) quasiquote(do(signal(SchemaDrift{.field match(string-of(k), Some(s), s, None, "")
.struct ~(Form.Str{.s name}) .is-extra true .pos k.pos}),
when not skip-value(c) then return out)))
let sname = Form.Sym{.s name} let sname = Form.Sym{.s name}
rname = Form.Sym{.s joined("read-", name)} rname = Form.Sym{.s joined("read-", name)}
let (struct) = quasiquote(defstruct(~sname, ~(Form.Vec{.xs slice(fields)}))) let (struct) = quasiquote(defstruct(~sname, ~(Form.Vec{.xs slice(fields)})))
@ -343,7 +368,7 @@ fn- is-same-type(a: Form, b: Form) -> bool
is-bytes-equal(bytes-view(render(a)), bytes-view(render(b))) is-bytes-equal(bytes-view(render(a)), bytes-view(render(b)))
;; A type form as text, for the refusals. Only the shapes this file builds — a ;; A type form as text, for the refusals. Only the shapes this file builds — a
;; name and `(Vec T)` — because nothing else ever reaches it. ;; name and `Vec(T)` — because nothing else ever reaches it.
fn render(f: Form) -> str fn render(f: Form) -> str
match f match f
Form.Sym(s) -> s Form.Sym(s) -> s
@ -360,8 +385,8 @@ fn- render-items(xs: [Form]) -> str
;; ── The macro ─────────────────────────────────────────────────────── ;; ── The macro ───────────────────────────────────────────────────────
;; ;;
;; `(json/defjson Config "config.json")`. The path is relative to the file this ;; `json/defjson(Config, "config.json")`. The path is relative to the file this
;; is written in, exactly as `(embed "config.json")` is — see `macro-slurp` in ;; is written in, exactly as `embed("config.json")` is — see `macro-slurp` in
;; the prelude, and check.ml's `embed_path`, which is the rule it copies. ;; the prelude, and check.ml's `embed_path`, which is the rule it copies.
;; ;;
;; It answers a `do`, which the top level splices: the nested structs innermost ;; It answers a `do`, which the top level splices: the nested structs innermost
@ -378,7 +403,8 @@ macro defjson(& args)
match macro-slurp(path) match macro-slurp(path)
Some(src) -> provide(name, path, src) Some(src) -> provide(name, path, src)
None -> None ->
refuse(joined3("there is no file at ", path, ", read relative to the file this defjson is written in — the same place (embed \"...\") would look")) refuse(joined3("there is no file at ", path,
", read relative to the file this defjson is written in — the same place (embed \"...\") would look"))
_ -> _ ->
refuse("defjson's first argument is the name of the struct to declare, written as a name") refuse("defjson's first argument is the name of the struct to declare, written as a name")
_ -> _ ->

View File

@ -17,7 +17,7 @@
# output the generator had already committed to. # output the generator had already committed to.
# #
# What does NOT belong here. Anything neither directive can express is a # What does NOT belong here. Anything neither directive can express is a
# hand-written `declare-c` in raylib.flan, which wins over the generated file # hand-written `declare-c` in raylib.fln, which wins over the generated file
# and is left alone by the importer. That is the escape hatch for a signature # and is left alone by the importer. That is the escape hatch for a signature
# the importer gets wrong and for a Flan face the header cannot describe — # the importer gets wrong and for a Flan face the header cannot describe —
# load-font-ex-raw and load-image-from-memory-raw are both that, each wrapped # load-font-ex-raw and load-image-from-memory-raw are both that, each wrapped
@ -40,9 +40,9 @@ exclude MemFree
# #
# A predicate keeps the kebab rule's name, `is-window-ready` for # A predicate keeps the kebab rule's name, `is-window-ready` for
# `IsWindowReady`: a Flan name carries no `?`, and the hand-written bindings in # `IsWindowReady`: a Flan name carries no `?`, and the hand-written bindings in
# raylib.flan follow the C name the same way. # raylib.fln follow the C name the same way.
# Hand-written in raylib.flan, so excluded here: a second declare-c for one C # Hand-written in raylib.fln, so excluded here: a second declare-c for one C
# symbol is refused for the whole program. These three are on a game's # symbol is refused for the whole program. These three are on a game's
# per-frame path (docs/PORTING.md), and a hand-written line is what the signature # per-frame path (docs/PORTING.md), and a hand-written line is what the signature
# check has to compare against -- generated output agrees with the header by # check has to compare against -- generated output agrees with the header by
@ -69,7 +69,7 @@ exclude IsWindowReady
# update-camera-pro is generated. (draw-cube-v was named here as the third # update-camera-pro is generated. (draw-cube-v was named here as the third
# such pair and no longer is — see the DrawCubeV line further down, which is # such pair and no longer is — see the DrawCubeV line further down, which is
# what happens to a split of this kind when an example starts calling the # what happens to a split of this kind when an example starts calling the
# other half.) Every other family in raylib.flan — the texture draws, the # other half.) Every other family in raylib.fln — the texture draws, the
# circle draws — sits together, so the split is worth naming: what is # circle draws — sits together, so the split is worth naming: what is
# hand-written is what the ported examples call, and hand-writing the variants # hand-written is what the ported examples call, and hand-writing the variants
# as well would widen the half that has to be maintained by hand for nothing # as well would widen the half that has to be maintained by hand for nothing
@ -99,7 +99,7 @@ exclude GetWorldToScreen
# of a closed set of names in fact, so their Flan face is a defenum and # of a closed set of names in fact, so their Flan face is a defenum and
# not the C signature — the same trade SetExitKey makes. # not the C signature — the same trade SetExitKey makes.
# - DrawCubeV, DrawSphere, DrawSphereWires and DrawRay are inside a frame, # - DrawCubeV, DrawSphere, DrawSphereWires and DrawRay are inside a frame,
# in examples/models-box-collisions.flan and examples/core-3d-picking.flan. # in examples/models-box-collisions.fln and examples/core-3d-picking.fln.
# draw-cube-v moving here is the one place this batch *narrows* the split # draw-cube-v moving here is the one place this batch *narrows* the split
# the paragraph above names: it was generated because nothing called it, # the paragraph above names: it was generated because nothing called it,
# and something calls it now. # and something calls it now.
@ -117,17 +117,17 @@ exclude GetScreenToWorldRay
# is a PixelFormat in fact — twenty-four codes of which exactly one is what a # is a PixelFormat in fact — twenty-four codes of which exactly one is what a
# texture upload needs — so its Flan face is the defenum and not the C # texture upload needs — so its Flan face is the defenum and not the C
# signature, the same trade SetTextureFilter makes one block up. # signature, the same trade SetTextureFilter makes one block up.
# examples/textures-image-processing.flan is what wanted it: the C's # examples/textures-image-processing.fln is what wanted it: the C's
# `PIXELFORMAT_UNCOMPRESSED_R8G8B8A8` is a name there and would have been a 7 # `PIXELFORMAT_UNCOMPRESSED_R8G8B8A8` is a name there and would have been a 7
# here. # here.
exclude ImageFormat exclude ImageFormat
# ── The idiomatic layer, which is what these last two blocks are for ── # ── The idiomatic layer, which is what these last two blocks are for ──
# #
# Three kinds of C signature get a Flan face in raylib.flan rather than the # Three kinds of C signature get a Flan face in raylib.fln rather than the
# generated one, and each kind is a directive here so that the generated file # generated one, and each kind is a directive here so that the generated file
# does not also define the name. See the "An idiomatic layer" section of # does not also define the name. See the "An idiomatic layer" section of
# raylib.flan for what each wrapper buys at the call site. # raylib.fln for what each wrapper buys at the call site.
# #
# 1. An `int` parameter the package already has a defenum for. These are # 1. An `int` parameter the package already has a defenum for. These are
# excluded and hand-written with the enum type, exactly as SetExitKey and # excluded and hand-written with the enum type, exactly as SetExitKey and
@ -156,7 +156,7 @@ name GetKeyPressed get-key-pressed-raw
# every raylib entry point that takes an array of vectors as pointer plus # every raylib entry point that takes an array of vectors as pointer plus
# count, and it is the whole family on purpose — a subset would put the # count, and it is the whole family on purpose — a subset would put the
# hole exactly where the next caller looks, which is the argument # hole exactly where the next caller looks, which is the argument
# raylib.flan makes about ConfigFlags. # raylib.fln makes about ConfigFlags.
name DrawLineStrip draw-line-strip-raw name DrawLineStrip draw-line-strip-raw
name DrawTriangleFan draw-triangle-fan-raw name DrawTriangleFan draw-triangle-fan-raw
name DrawTriangleStrip draw-triangle-strip-raw name DrawTriangleStrip draw-triangle-strip-raw
@ -231,7 +231,7 @@ constant Gesture/gesture-double-tap GESTURE_DOUBLETAP
constant PixelFormat/pixel-compressed-astc-4x4-rgba PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA constant PixelFormat/pixel-compressed-astc-4x4-rgba PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA
constant PixelFormat/pixel-compressed-astc-8x8-rgba PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA constant PixelFormat/pixel-compressed-astc-8x8-rgba PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA
# The 16 ConfigFlags bits. These are the values sand.flan and the ported # The 16 ConfigFlags bits. These are the values sand.fln and the ported
# window-flags example pass to set-config-flags, set-window-state and # window-flags example pass to set-config-flags, set-window-state and
# clear-window-state, and each is a single bit read off raylib.h by hand — # clear-window-state, and each is a single bit read off raylib.h by hand —
# exactly the transcription this check exists to second-guess. # exactly the transcription this check exists to second-guess.

View File

@ -6,7 +6,7 @@
;;;; beside it, which is read *while* these lines are made: `exclude` drops ;;;; beside it, which is read *while* these lines are made: `exclude` drops
;;;; a function, `name` gives one a Flan name the kebab rule would not. ;;;; a function, `name` gives one a Flan name the kebab rule would not.
;;;; Anything neither directive can express is a hand-written declare-c in ;;;; Anything neither directive can express is a hand-written declare-c in
;;;; the package's own .flan, which wins over this file and is left alone. ;;;; the package's own source, which wins over this file and is left alone.
;;;; ;;;;
;;;; Regenerating compares the package against the header first and ;;;; Regenerating compares the package against the header first and
;;;; refuses to write when they disagree, so this file and the ;;;; refuses to write when they disagree, so this file and the

View File

@ -15,7 +15,7 @@
# library they claim to bind. # library they claim to bind.
# #
# 2. every ordinary build. Every C symbol # 2. every ordinary build. Every C symbol
# is bound already — by hand in raylib.flan or by generation in # is bound already — by hand in raylib.fln or by generation in
# generated.fln — so the importer generates nothing and the header read # generated.fln — so the importer generates nothing and the header read
# is purely the check. It runs over every declaration in the package and # is purely the check. It runs over every declaration in the package and
# not only the hand-written ones, because the generated file is a package # not only the hand-written ones, because the generated file is a package
@ -27,7 +27,7 @@
# the time anybody checked them — which is what a census written into a # the time anybody checked them — which is what a census written into a
# comment beside a growing file always does. The generated half is # comment beside a growing file always does. The generated half is
# `grep -c '^(declare-c' generated.fln` and the hand-written half is the # `grep -c '^(declare-c' generated.fln` and the hand-written half is the
# same over the other .flan files here; neither number goes stale.) # same over the other .fln files here; neither number goes stale.)
# #
# Why it is no longer optional. It used to be `?${FLAN_RAYLIB_H}`, and the # Why it is no longer optional. It used to be `?${FLAN_RAYLIB_H}`, and the
# argument was that a build needs libraylib linkable and *not* raylib-devel # argument was that a build needs libraylib linkable and *not* raylib-devel
@ -56,7 +56,7 @@
# #
# To see what regeneration would produce without writing anything: # To see what regeneration would produce without writing anything:
# #
# flan import-c vendor/raylib/raylib-5.5.h vendor/raylib/raylib.flan # flan import-c vendor/raylib/raylib-5.5.h vendor/raylib/raylib.fln
# #
# What shapes the generated half — which functions are skipped, and what they # What shapes the generated half — which functions are skipped, and what they
# are called — is `bindings` beside this file. See its comments for why a # are called — is `bindings` beside this file. See its comments for why a

View File

@ -47,7 +47,7 @@
;;;; one place anybody writes it, which is a worse thing to hand somebody than ;;;; one place anybody writes it, which is a worse thing to hand somebody than
;;;; a macro that is honest about its extent. ;;;; a macro that is honest about its extent.
;;;; ;;;;
;;;; So the discipline sand.flan already writes down stays the discipline: ;;;; So the discipline sand.fln already writes down stays the discipline:
;;;; keep the restart boundary OUTSIDE the pair, so choosing `continue` for a ;;;; keep the restart boundary OUTSIDE the pair, so choosing `continue` for a
;;;; frame abandons the update and still reaches the drawing. spec-conditions ;;;; frame abandons the update and still reaches the drawing. spec-conditions
;;;; §5 is the rule behind that — a transfer runs the intervening frames' ;;;; §5 is the rule behind that — a transfer runs the intervening frames'
@ -55,17 +55,17 @@
;;;; ;;;;
;;;; ── Why this is a file of its own ─────────────────────────────────── ;;;; ── Why this is a file of its own ───────────────────────────────────
;;;; ;;;;
;;;; vector.flan's reasoning, unchanged: the split is on `declare-c`. ;;;; vector.fln's reasoning, unchanged: the split is on `declare-c`.
;;;; raylib.flan is the package's statement about C, it is the file the header ;;;; raylib.fln 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 ;;;; 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 ;;;; 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 ;;;; into names raylib.fln already declares — so there is nothing for the
;;;; header check to read and nothing raylib can make wrong. A package is a ;;;; 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 ;;;; directory, so this is another .fln beside the others and is qualified
;;;; `rl/` like the rest of it. ;;;; `rl/` like the rest of it.
;;;; ;;;;
;;;; The names come out qualified: the importer writes `(rl/with-drawing …)` ;;;; The names come out qualified: the importer writes `rl/with-drawing:`
;;;; and a bare `(with-drawing …)` is an unknown name there, the rule every ;;;; and a bare `with-drawing:` is an unknown name there, the rule every
;;;; declaration in this package follows. The expansions name this package's ;;;; declaration in this package follows. The expansions name this package's
;;;; own functions unqualified and the expander qualifies them on the way out ;;;; 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 ;;;; — see test/programs/pkgs/mac/mac.flan, which is that rule's worked
@ -105,7 +105,7 @@ macro with-drawing(& args)
;; The 2D camera. The argument is a Camera2D value, evaluated once where it ;; 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 ;; 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. ;; usable as an identity — raylib.fln says so beside the struct.
macro with-mode-2d(& args) macro with-mode-2d(& args)
if length(args) < 2 or (length(args) == 2 and is-form-empty-list(args[1])) if length(args) < 2 or (length(args) == 2 and is-form-empty-list(args[1]))
quote quote

View File

@ -71,7 +71,7 @@ struct Texture2D(id: u32, width: i32, height: i32, mipmaps: i32, format: i32)
struct Rectangle(x: f32, y: f32, width: f32, height: f32) struct Rectangle(x: f32, y: f32, width: f32, height: f32)
;; KeyboardKey, the subset sand.flan uses. A keyword at a call site resolves ;; KeyboardKey, the subset sand.fln uses. A keyword at a call site resolves
;; against these members at compile time and a typo is an error there. ;; against these members at compile time and a typo is an error there.
;; ;;
;; Every member carries the `key-` prefix, as every member of every other ;; Every member carries the `key-` prefix, as every member of every other
@ -185,7 +185,7 @@ declare-c(set-trace-log-level, [level TraceLogLevel], "SetTraceLogLevel")
;; A bitfield, like the gestures below and for the same reason: raylib wants ;; A bitfield, like the gestures below and for the same reason: raylib wants
;; the OR of several and a keyword can only ever name one member, so the ;; the OR of several and a keyword can only ever name one member, so the
;; parameter is a u32 and the members are defconsts rather than a defenum. ;; parameter is a u32 and the members are consts rather than a enum.
;; ;;
;; The part that is NOT obvious from the signature: set-config-flags has to ;; The part that is NOT obvious from the signature: set-config-flags has to
;; be called BEFORE init-window. raylib stores the flags and reads them while ;; be called BEFORE init-window. raylib stores the flags and reads them while
@ -193,7 +193,7 @@ declare-c(set-trace-log-level, [level TraceLogLevel], "SetTraceLogLevel")
;; INFO, and does nothing to the window that already exists — which looks ;; INFO, and does nothing to the window that already exists — which looks
;; exactly like a binding that did not work. Afterwards is what the generated ;; exactly like a binding that did not work. Afterwards is what the generated
;; set-window-state, clear-window-state and is-window-state are for, and they ;; set-window-state, clear-window-state and is-window-state are for, and they
;; take these same bits, which is why the four sand.flan sets grew into the ;; take these same bits, which is why the four sand.fln sets grew into the
;; whole of ConfigFlags: a subset has its hole exactly where the next caller ;; whole of ConfigFlags: a subset has its hole exactly where the next caller
;; looks. Every value below was read off raylib.h 5.5. ;; looks. Every value below was read off raylib.h 5.5.
;; ;;
@ -236,7 +236,7 @@ declare-c(is-key-released, [key Key], bool, "IsKeyReleased")
;; The other two halves of that family, hand-written for exactly the reason ;; The other two halves of that family, hand-written for exactly the reason
;; above and added late: they were generated, so they took an i32, so ;; above and added late: they were generated, so they took an i32, so
;; `(rl/is-key-up :key-space)` did not compile while `(rl/is-key-down :key-space)` did. ;; `rl/is-key-up(:key-space)` did not compile while `rl/is-key-down(:key-space)` did.
;; That is a hole in a family rather than a missing convenience — a caller ;; That is a hole in a family rather than a missing convenience — a caller
;; who has used is-key-down has no reason to expect the sibling to be spelled ;; who has used is-key-down has no reason to expect the sibling to be spelled
;; differently, and what they get instead of a keyword is a number nobody ;; differently, and what they get instead of a keyword is a number nobody
@ -263,8 +263,8 @@ declare-c(is-mouse-button-up, [button MouseButton], bool, "IsMouseButtonUp")
;; caller could otherwise have to think about — KEY_NULL for one, the NUL ;; caller could otherwise have to think about — KEY_NULL for one, the NUL
;; byte for the other. An Option says which of the two it is in the type, so ;; byte for the other. An Option says which of the two it is in the type, so
;; the loop that drains the queue cannot read the sentinel as a key or as a ;; the loop that drains the queue cannot read the sentinel as a key or as a
;; character: `while (> key 0)` is a comparison a reader has to know the ;; character: `while key > 0` is a comparison a reader has to know the
;; convention to trust, and `(while-some ...)` — or the `if-let` shape the ;; convention to trust, and `while-some(...)` — or the `if-let` shape the
;; examples use — is one a reader can check. ;; examples use — is one a reader can check.
;; ;;
;; The generated declarations are still what call C; only their names moved ;; The generated declarations are still what call C; only their names moved
@ -303,7 +303,7 @@ declare-c(get-mouse-wheel-move, [], f32, "GetMouseWheelMove")
;; EnableCursor — are in the generated half rather than here: they take no ;; EnableCursor — are in the generated half rather than here: they take no
;; arguments at all, so there is no Flan face for a hand-written line to ;; arguments at all, so there is no Flan face for a hand-written line to
;; improve, which is the same rule that leaves GetMouseX there. ;; improve, which is the same rule that leaves GetMouseX there.
;; examples/core-3d-picking.flan toggles them from the right mouse button. ;; examples/core-3d-picking.fln toggles them from the right mouse button.
declare-c(show-cursor, [], "ShowCursor") declare-c(show-cursor, [], "ShowCursor")
declare-c(hide-cursor, [], "HideCursor") declare-c(hide-cursor, [], "HideCursor")
@ -313,7 +313,7 @@ declare-c(is-cursor-hidden, [], bool, "IsCursorHidden")
;; The shape the pointer takes. raylib's SetMouseCursor says `int` and means ;; The shape the pointer takes. raylib's SetMouseCursor says `int` and means
;; one of these eleven, so the Flan face is the enum for the same reason ;; one of these eleven, so the Flan face is the enum for the same reason
;; set-exit-key takes a Key: the call is made every frame from a hover test, ;; set-exit-key takes a Key: the call is made every frame from a hover test,
;; and `(rl/set-mouse-cursor :cursor-ibeam)` is checked against the members ;; and `rl/set-mouse-cursor(:cursor-ibeam)` is checked against the members
;; where an i32 would take any number at all — including the one off-by-one ;; where an i32 would take any number at all — including the one off-by-one
;; that picks the arrow instead of the I-beam and looks like nothing at all ;; that picks the arrow instead of the I-beam and looks like nothing at all
;; went wrong. ;; went wrong.
@ -482,7 +482,7 @@ declare-c(get-world-to-screen-2d, [position Vector2 camera Camera2D], Vector2,
;; not a number. ;; not a number.
;; ;;
;; `projection` is `int` in the header and `CameraProjection` here, which is ;; `projection` is `int` in the header and `CameraProjection` here, which is
;; the same four bytes with a face on it: a Flan defenum lowers to int32_t in ;; the same four bytes with a face on it: a Flan enum lowers to int32_t in
;; a struct field exactly as it does in a parameter, and the layout check ;; a struct field exactly as it does in a parameter, and the layout check
;; knows that, so it accepts an enum where the header says `int` and still ;; knows that, so it accepts an enum where the header says `int` and still
;; refuses an `f64` where the header says `float`. What it buys is at the ;; refuses an `f64` where the header says `float`. What it buys is at the
@ -773,7 +773,7 @@ struct Image(data: Ptr(u8), width: i32, height: i32, mipmaps: i32, format: i32)
;; everywhere one is passed, and there is nothing in an `int` to say that 7 is ;; everywhere one is passed, and there is nothing in an `int` to say that 7 is
;; the one a texture upload requires — which is exactly the trade ;; the one a texture upload requires — which is exactly the trade
;; TextureFilter and MouseCursor already made, and the reason this is a ;; TextureFilter and MouseCursor already made, and the reason this is a
;; defenum rather than a row of defconsts. ;; enum rather than a row of consts.
;; ;;
;; The set is closed and is here whole, compressed members included, because a ;; The set is closed and is here whole, compressed members included, because a
;; subset would put the hole where the next caller looks: `format` is a field ;; subset would put the hole where the next caller looks: `format` is a field
@ -889,7 +889,7 @@ declare-c(image-crop, [image Ptr(Image) crop Rectangle], "ImageCrop")
;; duplicates it, which is what this was originally reached for — though ;; duplicates it, which is what this was originally reached for — though
;; image-copy in the generated half says that in one argument and is the ;; image-copy in the generated half says that in one argument and is the
;; better call for it. (An earlier comment here said 5.5 had no ImageCopy. It ;; better call for it. (An earlier comment here said 5.5 had no ImageCopy. It
;; does — raylib-5.5.h line 1348 — and generated.flan has bound it all along.) ;; does — raylib-5.5.h line 1348 — and generated.fln has bound it all along.)
;; ;;
;; The result owns its own buffer: unload-image it, like anything else that ;; The result owns its own buffer: unload-image it, like anything else that
;; allocated. ;; allocated.
@ -917,7 +917,7 @@ declare-c(load-texture-from-image, [image Image], Texture2D,
;; ;;
;; Immediate-mode drawing: each of these needs a GL context, so a window has ;; Immediate-mode drawing: each of these needs a GL context, so a window has
;; to be open and none of them can be in the acceptance table. They are ;; to be open and none of them can be in the acceptance table. They are
;; exercised by running sand.flan and looking at it, which is the honest ;; exercised by running sand.fln and looking at it, which is the honest
;; description — "it links" is not "it draws the right thing". ;; description — "it links" is not "it draws the right thing".
;; ;;
;; raylib's own naming is kept: a plain name fills, `-lines` outlines, and a ;; raylib's own naming is kept: a plain name fills, `-lines` outlines, and a
@ -954,7 +954,7 @@ declare-c(draw-circle-lines-v, [center Vector2 radius f32 color Color],
;; Two radii, horizontal then vertical. Equal radii is a circle, so a binding ;; Two radii, horizontal then vertical. Equal radii is a circle, so a binding
;; that exchanged them would be invisible unless they differ — which is why ;; that exchanged them would be invisible unless they differ — which is why
;; sand.flan's ellipse is deliberately wider than it is tall. ;; sand.fln's ellipse is deliberately wider than it is tall.
declare-c(draw-ellipse, [x i32 y i32 radius-h f32 radius-v f32 color Color], declare-c(draw-ellipse, [x i32 y i32 radius-h f32 radius-v f32 color Color],
"DrawEllipse") "DrawEllipse")
@ -1014,14 +1014,14 @@ declare-c(draw-rectangle-rounded-lines-ex,
;; ;;
;; Eleven entry points take an array of vectors as a pointer plus an `int` ;; Eleven entry points take an array of vectors as a pointer plus an `int`
;; count. A Flan slice already carries both, so every call site that does not ;; count. A Flan slice already carries both, so every call site that does not
;; go through a wrapper has to take the slice apart itself — `(addr (at pts ;; go through a wrapper has to take the slice apart itself — `addr(pts[0])`
;; 0))` and `(length pts)`, twice, in the right order — and the compiler cannot ;; and `length(pts)`, twice, in the right order — and the compiler cannot
;; check that the two halves came from the same slice. The wrapper is where ;; check that the two halves came from the same slice. The wrapper is where
;; that idiom lives, which is the rule check-collision-point-poly set. ;; that idiom lives, which is the rule check-collision-point-poly set.
;; ;;
;; It also guards the empty case, which is the part a hand-written call site ;; It also guards the empty case, which is the part a hand-written call site
;; gets wrong rather than merely writes out. raylib takes a count of 0 and ;; gets wrong rather than merely writes out. raylib takes a count of 0 and
;; draws nothing, but `(at pts 0)` on an empty slice is out of bounds before ;; draws nothing, but `pts[0]` on an empty slice is out of bounds before
;; raylib is ever reached: the safe call is "do not call at all", and it is ;; raylib is ever reached: the safe call is "do not call at all", and it is
;; written once here instead of at every use. ;; written once here instead of at every use.
;; ;;
@ -1146,7 +1146,7 @@ declare-c(get-screen-height, [], i32, "GetScreenHeight")
;; *at all* without a pad plugged in: with no gamepad, is-gamepad-available is ;; *at all* without a pad plugged in: with no gamepad, is-gamepad-available is
;; false, every button predicate is false and every axis reads 0.0, which is ;; false, every button predicate is false and every axis reads 0.0, which is
;; also exactly what a wrapper with its two int arguments exchanged would ;; also exactly what a wrapper with its two int arguments exchanged would
;; report. So these are bound, wired into sand.flan's HUD, and honestly ;; report. So these are bound, wired into sand.fln's HUD, and honestly
;; described as untested — the only check they get is that a pad moves the ;; described as untested — the only check they get is that a pad moves the
;; read-out. ;; read-out.
;; ;;
@ -1234,7 +1234,7 @@ declare-c(get-gamepad-axis-movement, [pad i32 axis GamepadAxis], f32,
;; silently accepted integers would give up what makes a keyword argument ;; silently accepted integers would give up what makes a keyword argument
;; checkable. The consequence is simply that an enum parameter cannot be ;; checkable. The consequence is simply that an enum parameter cannot be
;; indexed, and the caller spells the loop as a cond over the members it ;; indexed, and the caller spells the loop as a cond over the members it
;; knows — which is what examples/core-input-gamepad.flan does. ;; knows — which is what examples/core-input-gamepad.fln does.
;; SetGamepadVibration is NOT bound, and the reason is not the usual one. Two ;; SetGamepadVibration is NOT bound, and the reason is not the usual one. Two
;; things are wrong with it at once. Its arity changed — 5.1-dev takes three ;; things are wrong with it at once. Its arity changed — 5.1-dev takes three
@ -1249,7 +1249,7 @@ declare-c(get-gamepad-axis-movement, [pad i32 axis GamepadAxis], f32,
;; ;;
;; Touch is a superset of the mouse on a desktop: with no touchscreen, ;; Touch is a superset of the mouse on a desktop: with no touchscreen,
;; get-touch-point-count is 0 but get-touch-position 0 still tracks the mouse, ;; get-touch-point-count is 0 but get-touch-position 0 still tracks the mouse,
;; which is what makes it visible in sand.flan at all. ;; which is what makes it visible in sand.fln at all.
;; ;;
;; The gesture system is fed by raylib's own event polling, so a gesture is ;; The gesture system is fed by raylib's own event polling, so a gesture is
;; only ever detected inside a window's frame loop — nothing here is headless ;; only ever detected inside a window's frame loop — nothing here is headless
@ -1259,7 +1259,7 @@ declare-c(get-gamepad-axis-movement, [pad i32 axis GamepadAxis], f32,
;; several and is-gesture-detected tests against one. That is why the enabling ;; several and is-gesture-detected tests against one. That is why the enabling
;; call below takes a u32 and not a Gesture — a keyword can only ever name one ;; call below takes a u32 and not a Gesture — a keyword can only ever name one
;; member, and `gesture-all` is spelled out below so the common case still ;; member, and `gesture-all` is spelled out below so the common case still
;; reads. The defconst and the members now share the `gesture-` stem, which ;; reads. The const and the members now share the `gesture-` stem, which
;; is the prefix reading its way out of the enum and into the constant beside ;; is the prefix reading its way out of the enum and into the constant beside
;; it. ;; it.
enum Gesture enum Gesture
@ -1319,7 +1319,7 @@ declare-c(get-gesture-hold-duration, [], f32, "GetGestureHoldDuration")
;; The one thing the layout gets for free: RenderTexture2D is a u32 and two ;; The one thing the layout gets for free: RenderTexture2D is a u32 and two
;; Texture2Ds, and Texture2D is already pinned as far as anything headless can ;; Texture2Ds, and Texture2D is already pinned as far as anything headless can
;; pin it, so the only new claim here is the order of the three members. ;; pin it, so the only new claim here is the order of the three members.
;; sand.flan draws through one, which is where a wrong order shows up. ;; sand.fln draws through one, which is where a wrong order shows up.
struct RenderTexture2D(id: u32, texture: Texture2D, depth: Texture2D) struct RenderTexture2D(id: u32, texture: Texture2D, depth: Texture2D)
@ -1337,7 +1337,7 @@ declare-c(unload-render-texture, [target RenderTexture2D],
;; Everything drawn between these two lands in the target instead of the ;; Everything drawn between these two lands in the target instead of the
;; screen, and the target's texture comes out of the GPU upside down — raylib ;; screen, and the target's texture comes out of the GPU upside down — raylib
;; renders it bottom-up — so drawing it back with a NEGATIVE source height is ;; renders it bottom-up — so drawing it back with a NEGATIVE source height is
;; not a flourish, it is the correction. sand.flan does exactly that. ;; not a flourish, it is the correction. sand.fln does exactly that.
declare-c(begin-texture-mode, [target RenderTexture2D], "BeginTextureMode") declare-c(begin-texture-mode, [target RenderTexture2D], "BeginTextureMode")
declare-c(end-texture-mode, [], "EndTextureMode") declare-c(end-texture-mode, [], "EndTextureMode")
@ -1353,7 +1353,7 @@ declare-c(end-texture-mode, [], "EndTextureMode")
;; That makes Wave the audio equivalent of the Image family — raylib ;; That makes Wave the audio equivalent of the Image family — raylib
;; *computes* with it, headlessly — and it is the only part of this section ;; *computes* with it, headlessly — and it is the only part of this section
;; the acceptance table asserts. Everything from Sound down is exercised by ;; the acceptance table asserts. Everything from Sound down is exercised by
;; running sand.flan with a working sound server, and a machine without one ;; running sand.fln with a working sound server, and a machine without one
;; gets silence rather than a crash: init-audio-device logs a warning, every ;; gets silence rather than a crash: init-audio-device logs a warning, every
;; load answers a zeroed struct and every play is a no-op. ;; load answers a zeroed struct and every play is a no-op.
@ -1594,7 +1594,7 @@ declare-c(load-font, [path str], Font, "LoadFont")
;; raylib's own convention is that a NULL pointer with a count of 0 means the ;; raylib's own convention is that a NULL pointer with a count of 0 means the
;; default ASCII set, and the wrapper keeps it — but Flan has no null pointer ;; default ASCII set, and the wrapper keeps it — but Flan has no null pointer
;; literal, so the null comes from the one place the language does hand out ;; literal, so the null comes from the one place the language does hand out
;; zeroed bytes: a `defonce` with no initialiser is BSS (plan.org, zero ;; zeroed bytes: a `once` with no initialiser is BSS (plan.org, zero
;; values), and a zeroed (Ptr i32) is exactly a null one. It is never written ;; values), and a zeroed (Ptr i32) is exactly a null one. It is never written
;; to and never read through; raylib only ever compares it against NULL. ;; to and never read through; raylib only ever compares it against NULL.
once default-codepoints: Ptr(i32) once default-codepoints: Ptr(i32)
@ -1685,7 +1685,7 @@ declare-c(draw-text-codepoint,
;; which is also its answer for genuinely malformed UTF-8. Nothing in the ;; which is also its answer for genuinely malformed UTF-8. Nothing in the
;; result tells the two apart. docs/PORTING.md §A.1 is the whole story. ;; result tells the two apart. docs/PORTING.md §A.1 is the whole story.
;; ;;
;; So the declaration says `(Ptr u8)` and means it. That is a hand-written line ;; So the declaration says `Ptr(u8)` and means it. That is a hand-written line
;; over a `const char *` the importer would have rendered as `string`, which is ;; over a `const char *` the importer would have rendered as `string`, which is
;; exactly the case lib/cimport.ml's [ptr_agrees] exists for, and the generated ;; exactly the case lib/cimport.ml's [ptr_agrees] exists for, and the generated
;; half no longer carries the string-faced version at all — a binding that is ;; half no longer carries the string-faced version at all — a binding that is
@ -1713,7 +1713,7 @@ fn get-codepoint-previous(text: [const u8], offset: i32, codepoint-size: Ptr(i32
;; ;;
;; Layouts only, read off raylib.h 5.5 and checked against it on every build. ;; Layouts only, read off raylib.h 5.5 and checked against it on every build.
;; Describing them is what lets the importer bind the Load/Gen/Draw/Unload ;; Describing them is what lets the importer bind the Load/Gen/Draw/Unload
;; families over them in generated.flan; none of those calls is written here. ;; families over them in generated.fln; none of those calls is written here.
;; ;;
;; The field names are the header's through the kebab rule, which is how the ;; The field names are the header's through the kebab rule, which is how the
;; layout check pairs them, so Matrix's are m-0 to m-15 in raylib's order — a ;; layout check pairs them, so Matrix's are m-0 to m-15 in raylib's order — a

View File

@ -10,7 +10,7 @@
;;;; the arithmetic in Flan, or a small C file re-exporting them as symbols. ;;;; the arithmetic in Flan, or a small C file re-exporting them as symbols.
;;;; ;;;;
;;;; It is written in Flan, and the measurement that decided it was already ;;;; It is written in Flan, and the measurement that decided it was already
;;;; taken: examples/shapes-following-eyes.flan is an example whose every ;;;; taken: examples/shapes-following-eyes.fln is an example whose every
;;;; line is vector maths, ported without a vector library, and its own ;;;; line is vector maths, ported without a vector library, and its own
;;;; header reports that this cost nothing — the C does not use raymath there ;;;; header reports that this cost nothing — the C does not use raymath there
;;;; either. A C shim would buy identical arithmetic at the price of a ;;;; either. A C shim would buy identical arithmetic at the price of a
@ -25,7 +25,7 @@
;;;; ;;;;
;;;; ── Why this is a file of its own ─────────────────────────────────── ;;;; ── Why this is a file of its own ───────────────────────────────────
;;;; ;;;;
;;;; The split is on `declare-c`, not on "idiomatic". raylib.flan is the ;;;; The split is on `declare-c`, not on "idiomatic". raylib.fln is the
;;;; package's statement about C: every line in it is a declaration or a thin ;;;; package's statement about C: every line in it is a declaration or a thin
;;;; wrapper over one, it is the file the header check reads hand-written ;;;; wrapper over one, 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 ;;;; signatures out of, and a wrong line in it stops the build. There is not
@ -34,15 +34,15 @@
;;;; nothing here can be made wrong by raylib changing. A reader who wants to ;;;; nothing here can be made wrong by raylib changing. A reader who wants to
;;;; know what the package claims about C should not have to walk past four ;;;; know what the package claims about C should not have to walk past four
;;;; hundred lines of float arithmetic to find out, and 1300 lines of ;;;; hundred lines of float arithmetic to find out, and 1300 lines of
;;;; raylib.flan is already the argument against adding to it. ;;;; raylib.fln is already the argument against adding to it.
;;;; ;;;;
;;;; A package is a directory, so this is simply another .flan beside the ;;;; A package is a directory, so this is simply another .fln beside the
;;;; others and is qualified `rl/` like the rest of it. ;;;; others and is qualified `rl/` like the rest of it.
;;;; ;;;;
;;;; ── Names ─────────────────────────────────────────────────────────── ;;;; ── Names ───────────────────────────────────────────────────────────
;;;; ;;;;
;;;; `v2-` and `v3-`, not `vector2-`. These appear nested inside each other — ;;;; `v2-` and `v3-`, not `vector2-`. These appear nested inside each other —
;;;; `(rl/v2-add p (rl/v2-scale d t))` is the ordinary shape — and the longer ;;;; `rl/v2-add(p, rl/v2-scale(d, t))` is the ordinary shape — and the longer
;;;; spelling puts more characters between the reader and the arithmetic than ;;;; spelling puts more characters between the reader and the arithmetic than
;;;; it puts meaning. The prefix still says the type, which is the part a ;;;; it puts meaning. The prefix still says the type, which is the part a
;;;; language without generics needs it to say. ;;;; language without generics needs it to say.
@ -52,7 +52,7 @@
;;;; `clamp` and `lerp`. Both are already in the prelude — clamp as a macro ;;;; `clamp` and `lerp`. Both are already in the prelude — clamp as a macro
;;;; (prelude.ml, "clamp is a macro and not a function"), lerp as a function ;;;; (prelude.ml, "clamp is a macro and not a function"), lerp as a function
;;;; — and both are unqualified names every program already has; ;;;; — and both are unqualified names every program already has;
;;;; examples/textures-fog-of-war.flan calls the prelude's clamp today. A ;;;; examples/textures-fog-of-war.fln calls the prelude's clamp today. A
;;;; second `rl/lerp` would not even be the same function: the prelude writes ;;;; second `rl/lerp` would not even be the same function: the prelude writes
;;;; the weighted sum `(1-t)a + tb`, which returns b exactly at t = 1.0, ;;;; the weighted sum `(1-t)a + tb`, which returns b exactly at t = 1.0,
;;;; where raymath writes `a + t*(b - a)`, which does not once rounding is ;;;; where raymath writes `a + t*(b - a)`, which does not once rounding is
@ -77,8 +77,8 @@ fn inverse-lerp(value: f32, start: f32, end: f32) -> f32
;; for extrapolation — a caller who wants it bounded writes the prelude's ;; for extrapolation — a caller who wants it bounded writes the prelude's
;; clamp around it and can see that they did. ;; clamp around it and can see that they did.
;; ;;
;; Written as one expression rather than as (lerp out-start out-end ;; Written as one expression rather than as lerp(out-start, out-end,
;; (inverse-lerp ...)) because the prelude's lerp is the weighted-sum form ;; inverse-lerp(...)) because the prelude's lerp is the weighted-sum form
;; and raymath's Remap is the a + t*(b - a) form; composing them would be a ;; and raymath's Remap is the a + t*(b - a) form; composing them would be a
;; different function in the last bit. ;; different function in the last bit.
fn remap(value: f32, in-start: f32, in-end: f32, out-start: f32, out-end: f32) -> f32 fn remap(value: f32, in-start: f32, in-end: f32, out-start: f32, out-end: f32) -> f32

View File

@ -107,8 +107,8 @@ want "xfer channel" "$(printf '%s\n' "$ir" | grep 'define {} @"flan.main"')"
want "shim wrapper" "$("$FLAN" shim "$here/shimdemo.flan" | grep 'GetMousePosition();')" want "shim wrapper" "$("$FLAN" shim "$here/shimdemo.flan" | grep 'GetMousePosition();')"
# Lines quoted verbatim from repository files. # Lines quoted verbatim from repository files.
want "raylib binding" "$(grep -F 'unload-texture' "$root/vendor/raylib/raylib.flan")" want "raylib binding" "$(grep -F 'unload-texture' "$root/vendor/raylib/raylib.fln")"
want "agent declare" "$(grep -F 'flan_agent_poll' "$root/vendor/agent/agent.flan" | head -1)" want "agent declare" "$(grep -F 'flan_agent_poll' "$root/vendor/agent/agent.fln" | head -1)"
want "conditions.org" "$(grep -F 'Innermost frame offering the name wins' "$root/conditions.org")" want "conditions.org" "$(grep -F 'Innermost frame offering the name wins' "$root/conditions.org")"
# The value renderer, anchored in the test corpus rather than in NEXT.md (the # The value renderer, anchored in the test corpus rather than in NEXT.md (the
# rolling scratch document TODO.org replaced). It used to grep NEXT.md for this # rolling scratch document TODO.org replaced). It used to grep NEXT.md for this

View File

@ -1858,10 +1858,10 @@ directory may carry a <code>headers</code> file naming the library's own C heade
<code>flan generate-c &lt;package-dir&gt;</code> reads it with <code>flan generate-c &lt;package-dir&gt;</code> reads it with
<code>clang -Xclang -ast-dump=json</code>, turns every function it can represent into <code>clang -Xclang -ast-dump=json</code>, turns every function it can represent into
the same <code>declare-c</code> line a person would have written, and writes them to the same <code>declare-c</code> line a person would have written, and writes them to
<code>generated.flan</code> in the package — which is <em>committed</em>.</p> <code>generated.fln</code> in the package — which is <em>committed</em>.</p>
<pre><code class="sh">$ flan generate-c vendor/raylib <pre><code class="sh">$ flan generate-c vendor/raylib
wrote vendor/raylib/generated.flan: 268 declarations, 117 refused, of 581 functions wrote vendor/raylib/generated.fln: 318 declarations, 61 refused, of 581 functions
in vendor/raylib/raylib-5.5.h. in vendor/raylib/raylib-5.5.h.
Every defstruct, every hand-written declare-c and every mapped Every defstruct, every hand-written declare-c and every mapped
constant agrees with it.</code></pre> constant agrees with it.</code></pre>