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:
parent
e219e90446
commit
c1018d87c5
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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.
|
||||
;;;;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
4
sand.fln
4
sand.fln
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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. *)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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" ()
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
6
vendor/agent/flan_agent.web.c
vendored
6
vendor/agent/flan_agent.web.c
vendored
@ -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
22
vendor/edn/edn.fln
vendored
@ -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
113
vendor/edn/provide.fln
vendored
@ -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
8
vendor/edn/read.fln
vendored
@ -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
18
vendor/json/json.fln
vendored
@ -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)
|
||||
|
||||
78
vendor/json/provide.fln
vendored
78
vendor/json/provide.fln
vendored
@ -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")
|
||||
_ ->
|
||||
|
||||
20
vendor/raylib/bindings
vendored
20
vendor/raylib/bindings
vendored
@ -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.
|
||||
|
||||
2
vendor/raylib/generated.fln
vendored
2
vendor/raylib/generated.fln
vendored
@ -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
|
||||
|
||||
6
vendor/raylib/headers
vendored
6
vendor/raylib/headers
vendored
@ -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
|
||||
|
||||
16
vendor/raylib/modes.fln
vendored
16
vendor/raylib/modes.fln
vendored
@ -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
|
||||
|
||||
52
vendor/raylib/raylib.fln
vendored
52
vendor/raylib/raylib.fln
vendored
@ -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
|
||||
|
||||
16
vendor/raylib/vector.fln
vendored
16
vendor/raylib/vector.fln
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1858,10 +1858,10 @@ directory may carry a <code>headers</code> file naming the library's own C heade
|
||||
<code>flan generate-c <package-dir></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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user