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:
```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
@ -77,7 +77,7 @@ you want to use the shorter `flan` commands shown below.
Start a long-lived development session:
```sh
flan dev sand.flan
flan dev sand.fln
```
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
;;;; 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.
;;;; 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
;;;; 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,
;;;; 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.
;;;;
;;;; 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
;;;; 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
;;;; two ifs. It is the same arithmetic; the C spells it out because C has no
;;;; clamp.
@ -38,7 +38,7 @@ fn main() -> ()
rl/init-window(screen-width, screen-height,
"raylib [core] example - 2d camera")
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)
build-colors = array(max-buildings, rl/Color)
;; 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)
let w = f32(rl/get-random-value(50, 200))
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)
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
;; 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.
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)
until rl/window-should-close()
;; Update
@ -61,16 +61,14 @@ fn main() -> ()
;; right, which is the else-if and not an accident.
if rl/is-key-down(:key-right)
player.x += 2.0
else
if rl/is-key-down(:key-left)
player.x -= 2.0
elif rl/is-key-down(:key-left)
player.x -= 2.0
;; The camera follows the player's centre.
camera.target = rl/Vector2{.x player.x + 20.0, .y player.y + 20.0}
if rl/is-key-down(:key-a)
camera.rotation -= 1.0
else
if rl/is-key-down(:key-s)
camera.rotation += 1.0
elif rl/is-key-down(:key-s)
camera.rotation += 1.0
camera.rotation = clamp(camera.rotation, -40.0, 40.0)
camera.zoom += rl/get-mouse-wheel-move() * 0.05
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
;;;; 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
;;;; 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
@ -13,7 +13,7 @@
;;;; something, which is what a RayCollision is. Two structs the package could
;;;; 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.
;;;; - `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
;;;; bytes between them that a permutation destroys. BoundingBox's two
;;;; 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-world-to-screen and the cube draws for the reasons `bindings`
;;;; 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
;; 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.
;;
;; 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
;; 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
;; v3-sub and v3-add are Flan and not C.
fn box-around(centre: rl/Vector3, size: rl/Vector3) -> rl/BoundingBox
@ -69,14 +69,14 @@ fn main() -> ()
rl/init-window(screen-width, screen-height,
"raylib [core] example - 3d picking")
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-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
;; degenerate line at the origin and a zero RayCollision has hit false, so
;; 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}})
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}})
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}}
rl/set-target-fps(60)
until rl/window-should-close()
;; 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
;;;; `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`
;;;; 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
;;;; 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.
import rl "vendor:raylib"

View File

@ -21,7 +21,7 @@
;;;; 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
;;;; 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
;;;; 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
@ -51,7 +51,7 @@ fn deadzone(v: f32, limit: f32) -> f32
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 —
;; 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.
fn trigger-deadzoned(v: f32) -> f32
if v < trigger-deadzone then -1.0 else v
@ -162,7 +162,7 @@ fn main() -> ()
if rl/is-key-pressed(:key-right)
gamepad += 1
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
rl/with-drawing:
rl/clear-background(rl/raywhite)
@ -225,7 +225,7 @@ fn main() -> ()
;; has type i32 while its sibling has type () and the `if` will not
;; typecheck — "expected i32, found ()". Both arms end in 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()
if b >= 0
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
;;;; 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
;;;; 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
;;;; 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
;;;; magic number the keyword reads better than.
;;;;
@ -48,9 +48,9 @@
;;;;
;;;; **No local fixed arrays.** The C declares `char gestureLog[20][12]` and
;;;; `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
;;;; `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
;;;; literals, so a slot holds a `string` and there is no TextCopy and no

View File

@ -18,7 +18,7 @@
;;;; has nothing to catch.
;;;;
;;;; 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
;;;; the C's `{ "" }` initialiser arriving by a different route, and it is why
;;;; resetting the log below only has to reset the counter.
@ -67,7 +67,7 @@ fn main() -> ()
"raylib [core] example - input gestures")
defer rl/close-window()
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()
;; Update
last-gesture = current-gesture

View File

@ -5,13 +5,13 @@
;;;;
;;;; 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
;;;; (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
;;;; 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
;;;; 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.
import rl "vendor:raylib"

View File

@ -9,7 +9,7 @@
;;;; 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.
;;;;
;;;; 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.
import rl "vendor:raylib"

View File

@ -1,19 +1,19 @@
;;;; raylib [core] example - input multitouch
;;;;
;;;; 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
;;;; 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
;;;; 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
;;;; 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
;;;; 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
;;;; 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.
;;;;
;;;; 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-
;;;; 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
@ -18,7 +18,7 @@
;;;;
;;;; 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
;;;; 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
@ -26,14 +26,14 @@
;;;; 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
;;;; value for floats, and the comment there says why for integers. `(max v
;;;; (- 0.0 v))` is it, and unlike the integer case it has no edge: there is no
;;;; float whose negation is itself except -0.0, whose absolute value is 0.0
;;;; either way.
;;;; value for floats, and the comment there says why for integers.
;;;; `max(v, 0.0 - v)` is it, and unlike the integer case it has no edge: there
;;;; is no float whose negation is itself except -0.0, whose absolute value is
;;;; 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
;;;; 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"
@ -41,7 +41,7 @@ const screen-width = 800
const screen-height = 450
;; 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
;; ("i32 converts a number, found ..."), so it would have to be turned back
;; 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
;; 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
; left
; right

View File

@ -1,7 +1,7 @@
;;;; raylib [core] example - scissor test
;;;;
;;;; 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
;;;; 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

View File

@ -1,14 +1,14 @@
;;;; raylib [core] example - window flags
;;;;
;;;; examples/core/core_window_flags.c. The reason to port this one is the
;;;; flags themselves: raylib.flan carried four of ConfigFlags' sixteen
;;;; members — the four sand.flan sets before the window exists — and this
;;;; flags themselves: raylib.fln carried four of ConfigFlags' sixteen
;;;; 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
;;;; 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,
;;;; 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
;;;; keyword can only ever name one member.
;;;;
@ -16,7 +16,7 @@
;;;; minimize-window, maximize-window and restore-window all come from the
;;;; 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
;;;; 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
;;;; 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
@ -71,7 +71,7 @@ fn main() -> ()
rl/init-window(screen-width, screen-height,
"raylib [core] example - window flags")
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}
;; 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

View File

@ -1,22 +1,22 @@
;;;; raylib [core] example - window should close
;;;;
;;;; 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
;;;; the generated half because the Flan face differs — raylib declares
;;;; `void SetExitKey(int key)` and the generated line therefore takes an i32,
;;;; where this one takes a Key, so `(rl/set-exit-key :key-null)` is checked
;;;; where this one takes a Key, so `rl/set-exit-key(:key-null)` is checked
;;;; against the enum and `:nul` is a compile error instead of a 0.
;;;;
;;;; The whole example is about what window-should-close means. It is not a
;;;; 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.
;;;; 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
;;;; 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
;;;; window-should-close reports.
@ -47,9 +47,8 @@ fn main() -> ()
if exit-requested
if rl/is-key-pressed(:key-y)
exiting = true
else
if rl/is-key-pressed(:key-n)
exit-requested = false
elif rl/is-key-pressed(:key-n)
exit-requested = false
;; Draw
rl/with-drawing:
rl/clear-background(rl/raywhite)

View File

@ -8,7 +8,7 @@
;;;; none of them, and the generated half grew the cubes, spheres, cylinders
;;;; 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
;;;; 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
;; rather than a 0. raylib's field is `int` and the layout is unchanged —
;; 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}
;; The mouse becomes the camera's, not a pointer's.
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
;;;; 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
;;;; `(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.
;;;;
;;;; 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
;;;; 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
@ -25,8 +25,8 @@
;;;; name anyway.
;;;;
;;;; `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
;;;; calls `(free-temp)` once per frame, after drawing, and clones any text it
;;;; 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
;;;; keeps longer.
;;;;
;;;; 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 —
;;;; 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
;;;; GetRayCollisionBox), and that is the cheapest widening of the 3D surface
;;;; 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
;;;; 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
;; 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.
fn box-around(centre: rl/Vector3, size: rl/Vector3) -> rl/BoundingBox
let half = rl/v3-scale(size, 0.5)
@ -68,9 +68,9 @@ fn main() -> ()
"raylib [models] example - box collisions")
defer rl/close-window()
;; 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.
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-size = rl/Vector3{.x 1.0 .y 2.0 .z 1.0}
player-color = rl/green

View File

@ -7,17 +7,17 @@
;;;; 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
;;;; 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.
;;;;
;;;; 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
;;;; 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
;;;; 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
;;;; 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`
;;;; 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,
"raylib [shapes] example - collision area")
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-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
;; drawn before the first collision sets it.
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
if box-b.x + box-b.width >= f32(rl/get-screen-width())
box-b.x = f32(rl/get-screen-width()) - box-b.width
else
if box-b.x <= 0.0
box-b.x = 0.0
elif box-b.x <= 0.0
box-b.x = 0.0
if box-b.y + box-b.height >= f32(rl/get-screen-height())
box-b.y = f32(rl/get-screen-height()) - box-b.height
else
if box-b.y <= f32(screen-upper-limit)
box-b.y = f32(screen-upper-limit)
elif box-b.y <= f32(screen-upper-limit)
box-b.y = f32(screen-upper-limit)
collision = rl/check-collision-recs(box-a, box-b)
;; 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

View File

@ -18,7 +18,7 @@
;;;; file.
;;;;
;;;; 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
;;;; price of a compilation unit and a second place raylib's semantics live.
;;;; 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
let d = rl/v2-sub(mouse, centre)
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() -> ()
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
;; is open rather than off the constants, and so does this: on a high-DPI
;; 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-right = 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}
iris-left = sclera-left
iris-right = sclera-right
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.
;;;; Nothing in the
;;;; 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
;;;; opinion for this. The count below is a count of codepoints because raylib
;;;; decoded them, not because Flan did.
@ -37,7 +37,7 @@
;;;; 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
;;;; 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
;;;; been promising. `rl/get-codepoint-previous` is that binding and
;;;; `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`.
;;
;; `(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
;; `[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
@ -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
;; handed. `rl/get-codepoint-previous` takes the bytes and an offset instead
;; 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.
;;
;; 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
;; 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
;; question whose answer cannot change while the program runs is exactly what
;; the per-frame rule in docs/PORTING.md is about.

View File

@ -13,7 +13,7 @@
;;;; the second character of a fast pair.
;;;;
;;;; 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
;;;; 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
@ -21,9 +21,9 @@
;;;; 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.
;;;;
;;;; 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
;;;; 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
;;;; `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
@ -66,7 +66,7 @@ fn main() -> ()
defer rl/close-window()
name = array(max-input-chars, u8)
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
frames-counter = 0
rl/set-target-fps(60)

View File

@ -5,9 +5,9 @@
;;;; form was built rather than an application found for it afterwards.
;;;;
;;;; 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
;;;; `(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
;;;; readable surface of a two-hundred-glyph array.
;;;;
@ -35,7 +35,7 @@
;;;; wrap, which is the thing the example is about.
;;;;
;;;; - `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:
;;;; 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
@ -158,7 +158,7 @@ fn main() -> ()
let text = bytes-view(message)
is-resizing = false
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 min-width = f32(60.0)
min-height = f32(60.0)
@ -185,9 +185,8 @@ fn main() -> ()
let h = container.height + (mouse.y - last-mouse.y)
container.width = clamp(w, min-width, max-width)
container.height = clamp(h, min-height, max-height)
else
if rl/is-mouse-button-down(:mouse-left) and rl/check-collision-point-rec(mouse, resizer)
is-resizing = true
elif rl/is-mouse-button-down(:mouse-left) and rl/check-collision-point-rec(mouse, resizer)
is-resizing = true
resizer.x = container.x + container.width - 17.0
resizer.y = container.y + container.height - 17.0
last-mouse = mouse

View File

@ -6,14 +6,14 @@
;;;; DrawText(TextSubtext(message, 0, framesCounter/10), ...)
;;;;
;;;; 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
;;;; memory the caller does not own. There is no missing line to add — the
;;;; binding would be wrong at any signature.
;;;;
;;;; 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
;;;; bytes as a string at no cost, and `(string (slice message 0 n))` is
;;;; `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
;;;; 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
;;;; *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 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
;;;; `(string …)` and costs nothing either. The embedded newline is written as
;;;; or a slice; `bytes-view("…")` is the bridge in the other direction from
;;;; `string(…)` and costs nothing either. The embedded newline is written as
;;;; an escape, and raylib's draw-text breaks the line on it.
import rl "vendor:raylib"

View File

@ -8,8 +8,8 @@
;;;; bilinear sampler doing the interpolation. With the default
;;;; :filter-point the same program draws 32-pixel squares.
;;;;
;;;; That is what needed adding: `TextureFilter`, a new defenum in
;;;; vendor/raylib/raylib.flan, with set-texture-filter moved from the
;;;; That is what needed adding: `TextureFilter`, a new enum in
;;;; vendor/raylib/raylib.fln, with set-texture-filter moved from the
;;;; 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`
;;;; 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.
;; 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.
let y-2 = player-tile-y - player-tile-visibility
while y-2 < player-tile-y + player-tile-visibility
let vis-y = player-tile-y - player-tile-visibility
while vis-y < player-tile-y + player-tile-visibility
let 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
tile-fog[y-2 * tiles-x + x] = 1
if x >= 0 and x < tiles-x and vis-y >= 0 and vis-y < tiles-y
tile-fog[vis-y * tiles-x + x] = 1
x += 1
y-2 += 1
vis-y += 1
;; Draw the fog into its own little target first, at one pixel per tile.
;; blank is alpha 0, so a tile that is neither unseen nor remembered
;; leaves nothing behind and the map below shows through unmodified.

View File

@ -8,7 +8,7 @@
;;;; of its pixels.
;;;;
;;;; 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
;;;; 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

View File

@ -9,16 +9,16 @@
;;;; 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
;;;; 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
;;;; take the image by pointer and change `data` under the caller —
;;;; 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
;;;; kept deliberately so a caller can see which calls change what they are
;;;; 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
;;;; and mapped in `bindings` so its twenty-four members are checked against
;;;; raylib.h. The C's line is
@ -67,9 +67,9 @@ const screen-height = 450
const num-processes = 9
;; The C's ImageProcess enum. Flan's defenum lowers to an i32 for C's benefit
;; and these never cross to C, so they are defconsts — the same choice
;; examples/textures-image-generation.flan made for its texture index.
;; 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 consts — the same choice
;; examples/textures-image-generation.fln made for its texture index.
const proc-none = 0
const proc-color-grayscale = 1
const proc-color-tint = 2
@ -109,7 +109,7 @@ fn make-source-image() -> rl/Image
img
;; 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.
;;
;; Every arm takes the image by pointer and every arm rewrites the buffer the
@ -188,14 +188,14 @@ fn main() -> ()
mouse-hover-rec = -1
toggle-recs = array(num-processes, rl/Rectangle)
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)
until rl/window-should-close()
;; Update
;;
;; 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
;; 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 —
;; the nine rectangles do not overlap, so there is no first-hit-wins rule
;; to preserve.

View File

@ -7,14 +7,14 @@
;;;; load-image-from-texture → image-flip-vertical → export-image
;;;;
;;;; 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
;;;; 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
;;;; adding for this file.
;;;;
;;;; 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
;;;; 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
@ -92,7 +92,7 @@ fn main() -> ()
;; 30 wide with a 2-pixel gap, starting 10 from the left.
colors-recs = array(max-colors-count, rl/Rectangle)
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-prev = 0
color-mouse-hover = 0
@ -117,9 +117,8 @@ fn main() -> ()
let mouse-pos = rl/get-mouse-position()
if rl/is-key-pressed(:key-right)
color-selected += 1
else
if rl/is-key-pressed(:key-left)
color-selected -= 1
elif rl/is-key-pressed(:key-left)
color-selected -= 1
color-selected = clamp(color-selected, 0, max-colors-count - 1)
;; 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.
@ -156,10 +155,9 @@ fn main() -> ()
if mouse-pos.y > 50.0
rl/draw-circle(i32(mouse-pos.x), i32(mouse-pos.y), brush-size,
colors[0])
else
if rl/is-mouse-button-released(:mouse-right) and mouse-was-pressed
color-selected = color-selected-prev
mouse-was-pressed = false
elif rl/is-mouse-button-released(:mouse-right) and mouse-was-pressed
color-selected = color-selected-prev
mouse-was-pressed = false
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
;; 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
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
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
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

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] —
which is not running, because it is further up this very call
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
then stopped inside [flan_write_stdout], in an [fwrite] that
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
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
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
*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
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;
(* Every [defmacro] the imported packages declare, qualified under the alias
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
(* 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
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

View File

@ -106,7 +106,7 @@ let source = {flan|
;; what it buys is a *different element*, silently.
;;
;; 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
;; 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
@ -985,7 +985,7 @@ let source = {flan|
;;
;; 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
;; 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
;; nobody's name on it. One copy with the caveat written down beats a copy per
;; file with none.
@ -1217,7 +1217,7 @@ let source = {flan|
;; **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
;; 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
;; 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
@ -2469,7 +2469,7 @@ let source = {flan|
;; 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"
;; 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.
(defn is-form-empty-list [f Form] bool
(match f

View File

@ -398,7 +398,7 @@ let package_decls t =
(* 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
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

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
* 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.
*
* 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.
*
* 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]
* 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

View File

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

View File

@ -1,11 +1,11 @@
;;;; 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
;;;; 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.
(import sand "../../sand.flan")
(import sand "../../sand.fln")
(defn main [] i32
(sand/main)

View File

@ -1,11 +1,11 @@
;;;; 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
;;;; collision, so a directory is read once and keyed by its real path. Nothing
;;;; calls into raylib, so nothing links it either.
(import sand "../../sand.flan")
(import sand "../../sand.fln")
(import rl "vendor:raylib")
(defn main [] i32

View File

@ -36,7 +36,7 @@
;;;;
;;;; 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")
(defconst max-walk 128)

View File

@ -37,7 +37,7 @@
;;;; at all, so there is nothing in them for an implementation to have an
;;;; opinion about.
(import ip "../../examples/textures-image-processing.flan")
(import ip "../../examples/textures-image-processing.fln")
(import rl "vendor:raylib")
;; 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
;;;; 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")
;; A sixtieth of a second, fixed. The real program uses get-frame-time, which

View File

@ -278,28 +278,21 @@ module Pool = struct
done
end
(* sand.flan 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
functions by names the prelude does not have, so those three cannot check
until it is brought up to date — and the fixture, rather than a comment, is
what says so: the moment sand.flan calls (rand) every case below runs again
with nothing to undo. The one thing that is not automatic is the sand hash,
which is stale whatever happens because the generator now answers 64 bits
per draw; its row says so where the number is. *)
(* sand.fln is the workspace's own program, edited by hand and not by this
suite, and several cases here compile it. It once called randomness
functions by names the prelude did not have, and was skipped until it
called real ones; it now calls `rand`, which the prelude does have, so
these cases run unconditionally. The one thing that is not automatic is
the sand hash, which is stale whatever happens because the generator now
answers 64 bits per draw; its row says so where the number is. *)
let sand_checks =
match In_channel.with_open_bin "../sand.flan" In_channel.input_all with
(* The open paren is the test and not the bare name: sand.flan explains
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")
match In_channel.with_open_bin "../sand.fln" In_channel.input_all with
| _ -> true
| exception _ -> false
let () =
if not sand_checks then
print_endline
"acceptance: skipping the cases that compile sand.flan — it calls \
rand-f32, which is not a name";
print_endline "acceptance: skipping the cases that compile sand.fln — it is not there";
match Sys.command "command -v clang > /dev/null 2>&1" with
| 0 ->
let exe = compile "../calc-me.flan" in
@ -2778,9 +2771,9 @@ let () =
This used to be skipped without FLAN_RAYLIB_H, because the generated
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
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
would say so on an ordinary machine rather than only on one with a
header exported. *)
@ -3126,7 +3119,7 @@ let () =
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
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. *)
let raylib_font_out =
"valid yes\n\
@ -3150,7 +3143,7 @@ let () =
(* Again at -O0. Everything above runs through mem2reg, which launders a
sloppy alloca; -O0 tests the IR actually emitted, so a disagreement
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
and not raylib, deliberately — a program that imports raylib links
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"
"programs/pkg-unused.flan" "ok\n";
(* 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. *)
if sand_checks then
outputs "a package reached along two routes" "programs/pkg-shared.flan"
@ -4022,7 +4015,7 @@ let () =
"programs/generic-map-reject.flan" "at $t = f64";
(* 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. *)
if sand_checks then
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
(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
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
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
the file this is sent with is the one the import qualified. *)
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
in
let r =
request c
(Printf.sprintf
"(:op \"eval\" :code \"(defstruct Blob [id i32])\" :file %s)"
"(:op \"eval\" :code \"struct Blob(id: i32)\" :file %s)"
(Wire.quote agent_file))
in
if status r <> "ok" then
@ -8924,7 +8924,7 @@ let () =
half of the same claim and lives where it always did. *)
(* ── 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
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

View File

@ -860,8 +860,9 @@ let () =
Printf.printf "FAIL %s does not parse: %s: %s\n"
path (Loc.to_string loc) msg)
(* dune runs tests in _build/default/test/; the corpus is declared as a
dep in test/dune and lands at the build root. *)
[ "../calc-me.flan"; "../sand.flan" ];
dep in test/dune and lands at the build root. sand is .fln now
(decision 130), so it is not here — this reads the paren syntax only. *)
[ "../calc-me.flan" ];
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 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
the single-file package. While it calls the randomness functions by names
the prelude does not have it does not check at all, and those three would be
reporting that rather than anything about a session. The fixture decides,
so there is nothing to undo once sand.flan is brought up to date. *)
the single-file package. It once called randomness functions by names the
prelude did not have; it now calls `rand`, which the prelude does have, so
these cases run unconditionally — see test_acceptance.ml, which guards on
the same file for the same reason. *)
let sand_checks =
match In_channel.with_open_bin "../sand.flan" In_channel.input_all with
(* The open paren and not the bare name — see test_acceptance.ml, which
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")
match In_channel.with_open_bin "../sand.fln" In_channel.input_all with
| _ -> true
| exception _ -> false
(* 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
place rather than appended a second time and rejected as duplicates. *)
if not sand_checks then
print_endline
"session: skipping the cases that read sand.flan — it calls rand-f32, \
which is not a name"
print_endline "session: skipping the cases that read sand.fln — it is not there"
else begin
let t, _ = Session.create ~file:"../sand.flan" () in
let src = In_channel.with_open_bin "../sand.flan" In_channel.input_all in
let t, _ = Session.create ~file:"../sand.fln" () 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
(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
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
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. *)
(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 ->
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; _ } ->
fail "reloading a file with imports failed: %s" m)
end;
@ -1418,9 +1415,9 @@ let () =
that can decide it — which is why it is derived here and not sent by the
editor. *)
if sand_checks then begin
let t, _ = Session.create ~file:"../sand.flan" () in
let t, _ = Session.create ~file:"../sand.fln" () in
(match
Session.eval ~origin:"../vendor/agent/agent.flan" t
Session.eval ~origin:"../vendor/agent/agent.fln" t
"(defn poll [] i32 (poll-raw))"
with
| c ->
@ -1428,13 +1425,13 @@ let () =
fail "a form from a package file reported %s, wanted agent/poll"
(String.concat " " c.Session.fns)
| 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
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
splices as a bare [step] and the running program keeps the one it had. *)
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 ->
if c.Session.fns <> [ "sand/step" ] then
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);
(* 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 ->
if c.Session.fns <> [ "game-draw" ] then
fail "a form from the program's own file reported %s"

View File

@ -252,7 +252,9 @@ let pair flan fln =
let () =
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";
(* Checked, never run: sand opens a window. *)
List.iter

View File

@ -174,7 +174,7 @@ let () =
end;
(* ── 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
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
@ -203,7 +203,7 @@ let () =
fail "no GL imports in the module — raylib did not link";
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
in this process on purpose — see [raylib_web] on why anything after
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
commands. *)
let out = Filename.concat scratch "flan-web-sand.html" in
(match web_build "../sand.flan" out with
| exception Failure m -> fail "sand.flan for the browser: %s" m
(match web_build "../sand.fln" out with
| exception Failure m -> fail "sand.fln for the browser: %s" m
| () ->
let html, js, wasm = parts out in
List.iter

View File

@ -29,7 +29,7 @@
* 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
* 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:
*
* "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.
* 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
* 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
* 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
;;;; of it.
;;;;
;;;; Two layers sit above it. `read.flan`, in this package, is the one that
;;;; exists: `(edn/read bytes)` walks this cursor and answers a dynamic
;;;; `Value`. The other, `(read-edn Enemy bytes)` emitting a parser from a
;;;; 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
;;;; `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
;;;; 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
@ -29,19 +29,19 @@
;;;; the kind of contract that otherwise gets discovered from a corrupted
;;;; 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
;;;; 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
;;;; 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,
;;;; 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.
;;;;
;;;; ── What is refused, and why ────────────────────────────────────────
;;;;
;;;; 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.
;;;;
;;;; 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
;;;; 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
;;;; 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
;;;; out-parameter for the position is the same two fields with a worse shape.
;;;;
@ -93,7 +93,7 @@
;; ── 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,
;; 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
@ -101,7 +101,7 @@
;; actually branch on, which is the whole job.
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-bool = 3 ; true / false — text is the word
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.
;;
;; 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.
fn next(c: Ptr(Cursor)) -> Token
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.
;;;;
;;;; 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
;;;; 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,
;;;; 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* —
;;;; 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
@ -20,25 +20,25 @@
;;;; 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:
;;;;
;;;; - `(macro-slurp "t.edn")` reads a file at expansion time, resolved the
;;;; way `(embed "t.edn")` resolves a path — against the directory of 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
;;;; source file the form is written in.
;;;; - 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
;;;; rather than a second scanner inlined into a macro body.
;;;; - a macro may answer several declarations, as a top-level `(do ...)`,
;;;; and may refuse with a sentence through `(compile-error "...")`.
;;;; - a macro may answer several declarations, as a top-level `do:`,
;;;; and may refuse with a sentence through `compile-error("...")`.
;;;;
;;;; ── The rules ────────────────────────────────────────────────────────
;;;;
;;;; 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
;;;; 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
;;;; buffer cannot hold one.
;;;;
;;;; 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
;;;; 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
;;;; 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
;;;; `[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))
;; 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
;; 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
@ -145,7 +145,7 @@ fn need-bool(c: Ptr(Cursor)) -> bool
None -> false
;; 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
;; buffer that is later freed would hold a dangling one.
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
let t = next(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
ok-derived(quasiquote(i64), form-nil(), quasiquote(need-int(c)))
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
derive-set(c, name, t.pos, src)
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
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
;; 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.
fn- derive-vec(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
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)
if is-bad(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")}
ok-derived(ty,
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.
;;
;; `(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
;; 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.
;;
;; 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
;; 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.
fn- with-decl(decls: [Form], d: Form) -> [Form]
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.
fn- derive-set(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
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)
if is-bad(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")}
ok-derived(ty,
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
;; 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.
fn- derive-key(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
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)
return d
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
;; 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
let open = next(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)
if is-bad(head)
return head
@ -333,16 +349,26 @@ fn- derive-array(c: Ptr(Cursor), name: str, src: [const u8]) -> Derived
n += 1
expect(c, tok-vec-close)
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
read1 = head.reader
count = Form.Int{.i n}
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
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 ────────────────────────────────────────
;;
@ -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.
fn- derive-map(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
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
clauses = vec-new(Form) ; the reader's cond: test, body, test, body
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, \})
let k = next(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
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 d = derive(c, joined3(name, "-", fname), src)
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
;; out of step the way a parallel list of names would.
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
expect(c, tok-map-close)
;; 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.
push(clauses, quasiquote(:else))
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}
rname = Form.Sym{.s joined("read-", name)}
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)))
;; 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.
fn render(f: Form) -> str
match f
@ -449,12 +484,13 @@ fn- render-items(xs: [Form]) -> str
;; itself, so there is no equality for a map to hash.
fn- is-key-type(t: Form) -> bool
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 ───────────────────────────────────────────────────────
;;
;; `(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
;; `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
;; `macro-slurp` in the prelude, and check.ml's `embed_path`, which is the rule
;; it copies.
;;
@ -474,7 +510,8 @@ macro defedn(& args)
match macro-slurp(path)
Some(src) -> provide(name, path, src)
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")
_ ->

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.
;;;;
;;;; `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
;;;; 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,
;;;; are not known until it is read, a tileset, a save. `edn/defedn(T, path)`
;;;; 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
;;;; of one capability, and this is the dynamic one.
;;;;
@ -59,7 +59,7 @@
;; ── Copying a token's text ──────────────────────────────────────────
;; 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.
;; 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 —

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,
;;;; 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
;;;; 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
;;;; 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
;;;; 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
;;;; every escape resolved.
;;;;
@ -40,7 +40,7 @@
;;;; 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.
;;;;
;;;; 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,
;;;; 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
@ -53,8 +53,8 @@
;;;; ── The allocator ───────────────────────────────────────────────────
;;;;
;;;; 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
;;;; takes the context's, and (with-allocator frame (json/string-of t)) is the
;;;; 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
;;;; whole of "reading against an arena". An explicit allocator at the
;;;; construction site would override that, which is how this would take one if
;;;; the idiom could not express it. It can.
@ -75,7 +75,7 @@
;;;; single quotes 'x' — legal in JSON5.
;;;; +1 — a leading plus is JSON5's.
;;;; .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
;;;; purpose; here it is an error, and named.
;;;; 0x1f — hexadecimal is JSON5's.
@ -145,12 +145,12 @@
;; ── 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
;; 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-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-bool = 3 ; true / false — text is the word
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.
;;
;; 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.
fn next(c: Ptr(Cursor)) -> Token
if not is-ok(c)

View File

@ -1,8 +1,8 @@
;;;; 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
;;;; 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.
;;;;
;;;; It is the same idea as vendor/edn's defedn and deliberately not the same
@ -16,7 +16,7 @@
;;;;
;;;; ── 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
;;;; 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
@ -31,7 +31,7 @@
;;;; field.
;;;;
;;;; **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.
;;;;
;;;; 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))
;; 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
;; 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.
@ -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
;; 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
;; refusal is written against.
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
let t = next(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
ok-derived(quasiquote(i64), form-nil(), quasiquote(need-int(c)))
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
derive-array(c, name, t.pos, src)
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
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;
;; 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.
fn- derive-array(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
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)
if is-bad(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)
return item
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)
n += 1
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
ty = quasiquote(Vec(~elem))
cn = Form.Sym{.s joined(name, "-new")}
;; 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
;; naming a type, and `(Vec i64)` has no name to be — but a signature is
;; 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
;; 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
;; better for it too.
ok-derived(ty,
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 ────────────────────────────────────
fn- derive-object(c: Ptr(Cursor), name: str, at-pos: i32, src: [const u8]) -> Derived
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
clauses = vec-new(Form) ; the reader's cond: test, body, test, body
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, \})
let k = next(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
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
;; 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 —
@ -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.
let raw = k.text
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)
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)
let fname = copy-of(raw)
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
;; parallel list of names would.
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)
idx += 1
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.
push(clauses, quasiquote(:else))
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}
rname = Form.Sym{.s joined("read-", name)}
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)))
;; 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
match f
Form.Sym(s) -> s
@ -360,8 +385,8 @@ fn- render-items(xs: [Form]) -> str
;; ── The macro ───────────────────────────────────────────────────────
;;
;; `(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
;; `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
;; 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
@ -378,7 +403,8 @@ macro defjson(& args)
match macro-slurp(path)
Some(src) -> provide(name, path, src)
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")
_ ->

View File

@ -17,7 +17,7 @@
# output the generator had already committed to.
#
# 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
# 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
@ -40,9 +40,9 @@ exclude MemFree
#
# A predicate keeps the kebab rule's name, `is-window-ready` for
# `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
# 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
@ -69,7 +69,7 @@ exclude IsWindowReady
# 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
# 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
# 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
@ -99,7 +99,7 @@ exclude GetWorldToScreen
# 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.
# - 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
# the paragraph above names: it was generated because nothing called it,
# 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
# texture upload needs — so its Flan face is the defenum and not the C
# 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
# here.
exclude ImageFormat
# ── 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
# 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
# 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
# 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
# raylib.flan makes about ConfigFlags.
# raylib.fln makes about ConfigFlags.
name DrawLineStrip draw-line-strip-raw
name DrawTriangleFan draw-triangle-fan-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-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
# clear-window-state, and each is a single bit read off raylib.h by hand —
# 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
;;;; 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
;;;; 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
;;;; refuses to write when they disagree, so this file and the

View File

@ -15,7 +15,7 @@
# library they claim to bind.
#
# 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
# 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
@ -27,7 +27,7 @@
# the time anybody checked them — which is what a census written into a
# comment beside a growing file always does. The generated half is
# `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
# 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:
#
# 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
# 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
;;;; 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
;;;; frame abandons the update and still reaches the drawing. spec-conditions
;;;; §5 is the rule behind that — a transfer runs the intervening frames'
@ -55,17 +55,17 @@
;;;;
;;;; ── Why this is a file of its own ───────────────────────────────────
;;;;
;;;; vector.flan's reasoning, unchanged: the split is on `declare-c`.
;;;; raylib.flan is the package's statement about C, it is the file the header
;;;; vector.fln's reasoning, unchanged: the split is on `declare-c`.
;;;; 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
;;;; 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
;;;; 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.
;;;;
;;;; The names come out qualified: the importer writes `(rl/with-drawing …)`
;;;; and a bare `(with-drawing …)` is an unknown name there, the rule every
;;;; The names come out qualified: the importer writes `rl/with-drawing:`
;;;; and a bare `with-drawing:` is an unknown name there, the rule every
;;;; declaration in this package follows. The expansions name this package's
;;;; own functions unqualified and the expander qualifies them on the way out
;;;; — see test/programs/pkgs/mac/mac.flan, which is that rule's worked
@ -105,7 +105,7 @@ macro with-drawing(& args)
;; The 2D camera. The argument is a Camera2D value, evaluated once where it
;; always was. Remember that a fresh (Camera2D {}) has zoom 0.0 and is not
;; usable as an identity — raylib.flan says so beside the struct.
;; usable as an identity — raylib.fln says so beside the struct.
macro with-mode-2d(& args)
if length(args) < 2 or (length(args) == 2 and is-form-empty-list(args[1]))
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)
;; 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.
;;
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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
@ -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
;; 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
;; 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
;; 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
;; examples use — is one a reader can check.
;;
;; 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
;; 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.
;; 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(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
;; 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,
;; 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
;; that picks the arrow instead of the I-beam and looks like nothing at all
;; went wrong.
@ -482,7 +482,7 @@ declare-c(get-world-to-screen-2d, [position Vector2 camera Camera2D], Vector2,
;; not a number.
;;
;; `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
;; 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
@ -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
;; the one a texture upload requires — which is exactly the trade
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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".
;;
;; 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
;; 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],
"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`
;; 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
;; 0))` and `(length pts)`, twice, in the right order — and the compiler cannot
;; go through a wrapper has to take the slice apart itself — `addr(pts[0])`
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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
;; 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,
;; 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
;; 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
;; 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
;; 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
;; it.
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
;; 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.
;; 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)
@ -1337,7 +1337,7 @@ declare-c(unload-render-texture, [target RenderTexture2D],
;; 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
;; 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(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
;; *computes* with it, headlessly — and it is the only part of this section
;; 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
;; 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
;; 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
;; 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
;; to and never read through; raylib only ever compares it against NULL.
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
;; 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
;; 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
@ -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.
;; 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
;; 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.
;;;;
;;;; 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
;;;; 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
@ -25,7 +25,7 @@
;;;;
;;;; ── 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
;;;; 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
@ -34,15 +34,15 @@
;;;; 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
;;;; 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.
;;;;
;;;; ── Names ───────────────────────────────────────────────────────────
;;;;
;;;; `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
;;;; it puts meaning. The prefix still says the type, which is the part a
;;;; language without generics needs it to say.
@ -52,7 +52,7 @@
;;;; `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
;;;; — 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
;;;; 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
@ -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
;; clamp around it and can see that they did.
;;
;; Written as one expression rather than as (lerp out-start out-end
;; (inverse-lerp ...)) because the prelude's lerp is the weighted-sum form
;; Written as one expression rather than as lerp(out-start, out-end,
;; 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
;; different function in the last bit.
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();')"
# Lines quoted verbatim from repository files.
want "raylib binding" "$(grep -F 'unload-texture' "$root/vendor/raylib/raylib.flan")"
want "agent declare" "$(grep -F 'flan_agent_poll' "$root/vendor/agent/agent.flan" | head -1)"
want "raylib binding" "$(grep -F 'unload-texture' "$root/vendor/raylib/raylib.fln")"
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")"
# 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

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>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
<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
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.
Every defstruct, every hand-written declare-c and every mapped
constant agrees with it.</code></pre>