diff --git a/BUILT.md b/BUILT.md
index eb18060..7723cd4 100644
--- a/BUILT.md
+++ b/BUILT.md
@@ -1594,7 +1594,7 @@ And a `Vec` does not cross to C: handing a header that owns storage to C hands o
### `StorageExhausted` went in *with* `Vec`, not after it
No allocating operation returns an error and none can fail silently. When the allocator cannot satisfy a request the
-operation signals `(StorageExhausted {:bytes n :align a :allocator id})` with `error` — whose type is `Never` — inside a
+operation signals `(StorageExhausted {.bytes n .align a .allocator id})` with `error` — whose type is `Never` — inside a
`restart-case` offering `retry`. One rule over every allocating operation, which is what keeps `push` and `reserve` at
`Unit`, `clone` at the container, and no signature anywhere growing a `Result`.
@@ -1712,7 +1712,7 @@ rule without an exception: *no allocating operation returns an error*. There is
no error code — `slurp`'s type is `(Vec u8)` and `barf`'s is `Unit`.
**Two failures, two conditions, and the guards nest rather than merge.** Allocation failure is `StorageExhausted` under
-`retry`, unchanged and reused. File failure is `FileError {:path :op :reason}` under `retry` and `use-value`. They stay
+`retry`, unchanged and reused. File failure is `FileError {.path .op .reason}` under `retry` and `use-value`. They stay
apart because they ask two different answerable questions: the handler that grows an arena is not the handler that
supplies another path, and collapsing them would make one handler guess which it was looking at. `file_guard` in
check.ml is `alloc_guard`'s shape built from the same nodes — a `while`, a `restart-case` and an `error` — so the
@@ -2066,3 +2066,44 @@ on `window is not defined` — which says the module is live and says nothing ab
correct for a page, which is torn down by the tab closing, and it is worth knowing before reading anything into it.
- **Canvas size against `screen-width`/`screen-height`.** The shell is a string in `Build` and its canvas is not sized
from the program, so 900x600 may be letterboxed or cropped.
+
+## The colon belongs to keys, so a field label is a dot
+
+`{.x 1.0 .y 2.0}` is how a struct is constructed, and `{inner .field}` is how a pattern names one. The colon is gone
+from both, and what is left of it is one job: keys — map keys and enum members.
+
+**What was wrong with the colon.** Nothing, taken alone. The problem was that it had two jobs and the dot had one.
+`(.x v)` already read a field; `{:x 1.0}` also named a field, while `:space` named an enum member. So the dot meant
+"field" and the colon meant "field, or member, depending". Moving the label to the dot leaves each mark with one
+meaning, and it costs nothing to read because **the delimiter already disambiguates**: `(.x v)` is a list and
+therefore a call and therefore an access; `{.x 1.0}` is a brace form and therefore a construction. There is no
+position where the two could be confused, which is why the same spelling can serve both.
+
+**Why it had to land before `Map`, and this is the real reason.** A map literal wants to be `{:key value}`. While
+struct construction owned that exact spelling, a map literal and a struct literal were *the same syntax*, and the
+only thing that could tell them apart was what the checker expected at that position. That is a context-sensitive
+grammar for no gain. Reserving the colon for keys keeps the two visibly distinct at the reader, before any type is
+known. Doing it after `Map` landed would have meant changing both; doing it first meant changing one.
+
+**`:keys` kept its colon, and that is the point rather than an inconsistency.** `{:keys [x y]}` is the one thing in a
+brace that is not a field name — it is an instruction to the compiler that happens to sit there, and it takes a
+vector rather than a value. Giving it a dot would have made the dot mean "a field, or the word keys". Leaving it a
+colon lets the dot mean exactly one thing, *this names a field*, which is the whole reason the colon was given up.
+Everything else Clojure puts in that position — `:as`, `:or`, `:strs`, `:syms` — is still refused by its own name.
+
+**The old spelling is refused, not accepted quietly**, and the refusal names the new one: `a field label is written
+.x, not :x — the colon is for keys`. Two accepted spellings is how two spellings become permanent, and the standing
+rule here is that what is not supported is rejected explicitly with the reason. Both refusals are tested by their
+reason, on the construction side and on the destructuring side, which is what stops the colon drifting back.
+
+**The sweep is a tool, not a one-off.** `tools/colon-to-dot.py` converted 681 labels across 45 `.flan` files,
+`vendor/` included, and 94 more in the Flan embedded in `lib/prelude.ml` and the tests. It works on *forms*, not on text: a keyword
+becomes a dot only where it sits in a field-label position inside a brace, so an enum member in value position
+(`{.k :hi}`), a genuine EDN map inside a string (`test/programs/edn.flan`), and a type-position `{K V}` are all left
+alone. It was kept in the tree because several lanes branched before it and their Flan needs the same pass at merge.
+
+**What it deliberately did not change: the printed form.** `render.ml` still prints `(V {:x 1.5 :y 0})`. That string
+is a wire format — `emacs/flan-inspect.el` parses it back and hard-codes the colon when it reads a field out — so
+moving the printer alone would break struct inspection in the dev loop without breaking any test that says so. The
+printer moves when its reader does, in the Emacs lane. It is the one place the old spelling is still correct, and
+the reason is worth keeping: **a format with two ends only changes at both.**
diff --git a/NEXT.md b/NEXT.md
index cf1e669..0956926 100644
--- a/NEXT.md
+++ b/NEXT.md
@@ -277,23 +277,18 @@ length is not known until the file is read and therefore cannot exist before an
## Decided later the same day, and queued
-**6. A field label is written with a dot, not a colon, and the colon is reserved for keys.** `{.x 1.0 .y 2.0}`
-replaces `{:x 1.0 :y 2.0}` in struct construction, and the same change applies where destructuring names a field.
-The delimiter is what makes it unambiguous, which is the author's argument and it holds: `(.x v)` is a call and
-therefore an access, `{.x 1.0}` is a brace form and therefore a construction. Today the dot already means "read a
-field" and the colon means both "field label" and "enum member", which is the ambiguity the change removes.
+~~**6. A field label is written with a dot, not a colon, and the colon is reserved for keys.**~~ **Done.**
+`{.x 1.0 .y 2.0}` is struct construction and `{inner .field}` is destructuring; the old spelling is refused, and the
+refusal names the new one. `:keys` kept its colon — it names no field, so leaving it alone is what lets the dot mean
+exactly one thing. 681 labels across 45 `.flan` files including `vendor/`, plus 94 more in the Flan embedded in
+`lib/prelude.ml` and the tests. `Map` is now free to take `{:key value}` without colliding with struct literals. See
+BUILT.md, "The colon belongs to keys".
-**The reason to do it before `Map`, not after.** A map literal will want to be `{:key value}`. If struct
-construction owns that exact spelling, map literals and struct literals are the same syntax and the checker has to
-tell them apart from context. Reserving the colon for keys — map keys and enum members — keeps the two visibly
-distinct. Doing this after `Map` lands means changing both; doing it now means changing one.
-
-Cost: ~284 sites across 45 `.flan` files, mechanical. **Queued rather than started** only because it touches nearly
-every Flan file in the repo, including ones a running lane held. Run it when the tree is quiet, before step 4.
-
-One thing to decide with it: destructuring uses the colon two ways — `{inner :field}` names a field, which should
-become a dot like any other field, and `{:keys [x y]}` where `:keys` is an instruction to the compiler rather than a
-field name, which arguably stays a colon. Settle both in the same pass rather than leaving the rule half-applied.
+**What it left for the Emacs lane, both verified.** `render.ml` still *prints* a struct with colons, deliberately:
+`emacs/flan-inspect.el:165` parses that output and hard-codes the colon when it reads a field out, so the printer
+has to move in the same commit as its reader. And `flan-mode.el:61` font-locks `:name` as a constant with nothing
+matching `.name`, so a field label is now unfontified where it used to be coloured. Neither is urgent; both belong
+with whoever next opens `emacs/`.
**7. `Map` follows Odin's implementation.** Read `base/runtime/dynamic_map_internal.odin` before writing any of it;
the checkout is at `~/Repositories/Odin`. Three properties are the ones worth copying, and they are stated in its own
@@ -418,9 +413,10 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
no comparison. The mechanism was sound and stayed: it hashes slot *names* as well as types, so it does see a
rename. See BUILT.md, "Locals of a stopped frame".
-2. **The colon-to-dot change.** Cheap, mechanical, ~284 sites across 45 files — and it must land **before** `Map`, or
- map literals and struct literals collide and both have to change instead of one. It gets more expensive every day
- more Flan is written. See the decision above for the destructuring wrinkle to settle in the same pass.
+2. ~~**The colon-to-dot change.**~~ **Done**, and `Map` is unblocked: `{:key value}` is free. The sweep is
+ `tools/colon-to-dot.py`, kept rather than thrown away, because the lanes that branched before it wrote Flan in the
+ old spelling and their files want the same pass at merge — `python3 tools/colon-to-dot.py .` over the tree, and
+ `--in-strings` for a `test/*.ml` that embeds Flan.
3. **`Map`, and the `defer` relaxation.** `Map` is step 4 of the container build order and finishes what `Vec` started.
The `defer` change — permitting it in a `let` whose extent is the function body — is small, independent, and is the
@@ -882,7 +878,7 @@ expander last, on 6's unions.
with it — `rt_die` is the non-dev path too, where there is no listener and nothing to deadlock against, so whether it
should be `_exit` unconditionally or only under `--dev` is a decision rather than a typo.
-- **`(A {:x 1})` on a union variant says "unknown struct A"** rather than the union refusal `check_struct` plainly
+- **`(A {.x 1})` on a union variant says "unknown struct A"** rather than the union refusal `check_struct` plainly
intends — `env` has no table of variant names. A diagnostics bug, not a backend death.
### Test blind spots, from a mutation pass
diff --git a/calc-me.flan b/calc-me.flan
index d86ebd2..bd099b7 100644
--- a/calc-me.flan
+++ b/calc-me.flan
@@ -105,7 +105,7 @@
;; ── Whole input, or nothing. Trailing junk is an error, not ignored. ──
(defn evaluate [src [u8]] (Option f64)
- (let [c (Cursor {:src src})] ; pos omitted: zeroed
+ (let [c (Cursor {.src src})] ; pos omitted: zeroed
(let [v (some (parse-expr (addr c) 1))]
(skip-spaces (addr c))
(if (= (peek (addr c)) 0)
diff --git a/conditions-play.flan b/conditions-play.flan
index 662b27d..291598a 100644
--- a/conditions-play.flan
+++ b/conditions-play.flan
@@ -15,7 +15,7 @@
;;; Two frames under the restart-case, so a transfer has something to cross.
(defn probe [n i32] i32
- (signal (AssetMissing {:id n}))
+ (signal (AssetMissing {.id n}))
100)
(defn middle [n i32] i32
diff --git a/emacs/test-flan-dev.el b/emacs/test-flan-dev.el
index 7a89d58..4dbb798 100644
--- a/emacs/test-flan-dev.el
+++ b/emacs/test-flan-dev.el
@@ -412,7 +412,7 @@ is written instead — the real `message' call the real command makes."
;; that errors stops it on its own game thread, in a frame of its own — not
;; inside anything this client asked for. Nothing tells Emacs.
(flan-dev--eval
- "(defn step [] i64 (restart-case (do (error (Missing {:id 7})) 0) (use-placeholder [] -1)))"
+ "(defn step [] i64 (restart-case (do (error (Missing {.id 7})) 0) (use-placeholder [] -1)))"
"form")
(let ((deadline (+ (float-time) 20)))
(while (and (not flan-dev--stopped) (< (float-time) deadline))
diff --git a/examples/core-delta-time.flan b/examples/core-delta-time.flan
index 5f6ed65..dc24721 100644
--- a/examples/core-delta-time.flan
+++ b/examples/core-delta-time.flan
@@ -41,9 +41,9 @@
(defer (rl/close-window))
(set current-fps 60)
- (set delta-circle (rl/Vector2 {:x 0.0 :y (/ (f32 screen-height) 3.0)}))
- (set frame-circle (rl/Vector2 {:x 0.0
- :y (* (f32 screen-height) (/ 2.0 3.0))}))
+ (set delta-circle (rl/Vector2 {.x 0.0 .y (/ (f32 screen-height) 3.0)}))
+ (set frame-circle (rl/Vector2 {.x 0.0
+ .y (* (f32 screen-height) (/ 2.0 3.0))}))
(rl/set-target-fps current-fps)
diff --git a/examples/core-input-gamepad.flan b/examples/core-input-gamepad.flan
index 167362c..6e4b574 100644
--- a/examples/core-input-gamepad.flan
+++ b/examples/core-input-gamepad.flan
@@ -92,7 +92,7 @@
(defn draw-pad-background []
(rl/draw-rectangle-rounded
- (rl/Rectangle {:x 175.0 :y 110.0 :width 460.0 :height 220.0})
+ (rl/Rectangle {.x 175.0 .y 110.0 .width 460.0 .height 220.0})
0.3 16 rl/darkgray)
;; The three middle buttons and the four face buttons, as outlines. The
@@ -113,10 +113,10 @@
(rl/draw-rectangle 217 176 84 25 rl/black)
(rl/draw-rectangle-rounded
- (rl/Rectangle {:x 215.0 :y 98.0 :width 100.0 :height 10.0})
+ (rl/Rectangle {.x 215.0 .y 98.0 .width 100.0 .height 10.0})
0.5 16 rl/darkgray)
(rl/draw-rectangle-rounded
- (rl/Rectangle {:x 495.0 :y 98.0 :width 100.0 :height 10.0})
+ (rl/Rectangle {.x 495.0 .y 98.0 .width 100.0 .height 10.0})
0.5 16 rl/darkgray))
(defn draw-pad-buttons []
@@ -146,11 +146,11 @@
(when (rl/gamepad-button-down? gamepad :left-trigger-1)
(rl/draw-rectangle-rounded
- (rl/Rectangle {:x 215.0 :y 98.0 :width 100.0 :height 10.0})
+ (rl/Rectangle {.x 215.0 .y 98.0 .width 100.0 .height 10.0})
0.5 16 rl/red))
(when (rl/gamepad-button-down? gamepad :right-trigger-1)
(rl/draw-rectangle-rounded
- (rl/Rectangle {:x 495.0 :y 98.0 :width 100.0 :height 10.0})
+ (rl/Rectangle {.x 495.0 .y 98.0 .width 100.0 .height 10.0})
0.5 16 rl/red)))
(defn draw-stick [cx i32 cy i32 ax f32 ay f32 thumb-down bool]
@@ -178,9 +178,9 @@
(when (rl/key-pressed? :right) (set gamepad (+ gamepad 1)))
(let [axis-count (min 6 (rl/get-gamepad-axis-count gamepad))
- vibrate-rect (rl/Rectangle {:x 10.0
- :y (+ 90.0 (* 20.0 (f32 axis-count)))
- :width 75.0 :height 24.0})]
+ vibrate-rect (rl/Rectangle {.x 10.0
+ .y (+ 90.0 (* 20.0 (f32 axis-count)))
+ .width 75.0 .height 24.0})]
;; Draw
(rl/begin-drawing)
diff --git a/examples/core-input-gestures-testbed.flan b/examples/core-input-gestures-testbed.flan
index b21f746..833968c 100644
--- a/examples/core-input-gestures-testbed.flan
+++ b/examples/core-input-gestures-testbed.flan
@@ -179,7 +179,7 @@
(rl/draw-circle (+ last-x 80) (+ last-y 16) 10.0
(if (= last-gesture :tap) rl/blue rl/lightgray))
;; segments 0 lets raylib pick the count from the radius.
- (rl/draw-ring (rl/Vector2 {:x (f32 (+ last-x 103)) :y (f32 (+ last-y 16))})
+ (rl/draw-ring (rl/Vector2 {.x (f32 (+ last-x 103)) .y (f32 (+ last-y 16))})
6.0 11.0 0.0 360.0 0
(if (= last-gesture :drag) rl/lime rl/lightgray))
(rl/draw-circle (+ last-x 80) (+ last-y 43) 10.0
@@ -191,21 +191,21 @@
;; for pinch-in. Counter-clockwise winding, or raylib culls them.
(let [out-c (if (= last-gesture :pinch-out) rl/orange rl/lightgray)
in-c (if (= last-gesture :pinch-in) rl/violet rl/lightgray)]
- (rl/draw-triangle (rl/Vector2 {:x (f32 (+ last-x 122)) :y (f32 (+ last-y 16))})
- (rl/Vector2 {:x (f32 (+ last-x 137)) :y (f32 (+ last-y 26))})
- (rl/Vector2 {:x (f32 (+ last-x 137)) :y (f32 (+ last-y 6))})
+ (rl/draw-triangle (rl/Vector2 {.x (f32 (+ last-x 122)) .y (f32 (+ last-y 16))})
+ (rl/Vector2 {.x (f32 (+ last-x 137)) .y (f32 (+ last-y 26))})
+ (rl/Vector2 {.x (f32 (+ last-x 137)) .y (f32 (+ last-y 6))})
out-c)
- (rl/draw-triangle (rl/Vector2 {:x (f32 (+ last-x 147)) :y (f32 (+ last-y 6))})
- (rl/Vector2 {:x (f32 (+ last-x 147)) :y (f32 (+ last-y 26))})
- (rl/Vector2 {:x (f32 (+ last-x 162)) :y (f32 (+ last-y 16))})
+ (rl/draw-triangle (rl/Vector2 {.x (f32 (+ last-x 147)) .y (f32 (+ last-y 6))})
+ (rl/Vector2 {.x (f32 (+ last-x 147)) .y (f32 (+ last-y 26))})
+ (rl/Vector2 {.x (f32 (+ last-x 162)) .y (f32 (+ last-y 16))})
out-c)
- (rl/draw-triangle (rl/Vector2 {:x (f32 (+ last-x 125)) :y (f32 (+ last-y 33))})
- (rl/Vector2 {:x (f32 (+ last-x 125)) :y (f32 (+ last-y 53))})
- (rl/Vector2 {:x (f32 (+ last-x 140)) :y (f32 (+ last-y 43))})
+ (rl/draw-triangle (rl/Vector2 {.x (f32 (+ last-x 125)) .y (f32 (+ last-y 33))})
+ (rl/Vector2 {.x (f32 (+ last-x 125)) .y (f32 (+ last-y 53))})
+ (rl/Vector2 {.x (f32 (+ last-x 140)) .y (f32 (+ last-y 43))})
in-c)
- (rl/draw-triangle (rl/Vector2 {:x (f32 (+ last-x 144)) :y (f32 (+ last-y 43))})
- (rl/Vector2 {:x (f32 (+ last-x 159)) :y (f32 (+ last-y 53))})
- (rl/Vector2 {:x (f32 (+ last-x 159)) :y (f32 (+ last-y 33))})
+ (rl/draw-triangle (rl/Vector2 {.x (f32 (+ last-x 144)) .y (f32 (+ last-y 43))})
+ (rl/Vector2 {.x (f32 (+ last-x 159)) .y (f32 (+ last-y 53))})
+ (rl/Vector2 {.x (f32 (+ last-x 159)) .y (f32 (+ last-y 33))})
in-c))
;; Four pips, one per simultaneous touch raylib reports.
@@ -223,8 +223,8 @@
(if (= i 0) gesture-color rl/lightgray))))
;; The two mode buttons. Maroon means the mode that button controls is on.
- (let [b1 (rl/Rectangle {:x 53.0 :y 7.0 :width 48.0 :height 26.0})
- b2 (rl/Rectangle {:x 108.0 :y 7.0 :width 36.0 :height 26.0})]
+ (let [b1 (rl/Rectangle {.x 53.0 .y 7.0 .width 48.0 .height 26.0})
+ b2 (rl/Rectangle {.x 108.0 .y 7.0 .width 36.0 .height 26.0})]
(rl/draw-rectangle-rec b1 (if (or (= log-mode 1) (= log-mode 3))
rl/maroon rl/gray))
(rl/draw-text "Hide" 60 10 10 rl/white)
@@ -242,16 +242,16 @@
(d/draw-f32 current-angle 2 (+ (i32 prot-x) 55) (+ (i32 prot-y) 92) 20
gesture-color)
- (rl/draw-circle-v (rl/Vector2 {:x prot-x :y prot-y}) 80.0 rl/white)
- (rl/draw-line-ex (rl/Vector2 {:x (- prot-x 90.0) :y prot-y})
- (rl/Vector2 {:x (+ prot-x 90.0) :y prot-y}) 3.0 rl/lightgray)
- (rl/draw-line-ex (rl/Vector2 {:x prot-x :y (- prot-y 90.0)})
- (rl/Vector2 {:x prot-x :y (+ prot-y 90.0)}) 3.0 rl/lightgray)
- (rl/draw-line-ex (rl/Vector2 {:x (- prot-x 80.0) :y (- prot-y 45.0)})
- (rl/Vector2 {:x (+ prot-x 80.0) :y (+ prot-y 45.0)}) 3.0
+ (rl/draw-circle-v (rl/Vector2 {.x prot-x .y prot-y}) 80.0 rl/white)
+ (rl/draw-line-ex (rl/Vector2 {.x (- prot-x 90.0) .y prot-y})
+ (rl/Vector2 {.x (+ prot-x 90.0) .y prot-y}) 3.0 rl/lightgray)
+ (rl/draw-line-ex (rl/Vector2 {.x prot-x .y (- prot-y 90.0)})
+ (rl/Vector2 {.x prot-x .y (+ prot-y 90.0)}) 3.0 rl/lightgray)
+ (rl/draw-line-ex (rl/Vector2 {.x (- prot-x 80.0) .y (- prot-y 45.0)})
+ (rl/Vector2 {.x (+ prot-x 80.0) .y (+ prot-y 45.0)}) 3.0
rl/green)
- (rl/draw-line-ex (rl/Vector2 {:x (- prot-x 80.0) :y (+ prot-y 45.0)})
- (rl/Vector2 {:x (+ prot-x 80.0) :y (- prot-y 45.0)}) 3.0
+ (rl/draw-line-ex (rl/Vector2 {.x (- prot-x 80.0) .y (+ prot-y 45.0)})
+ (rl/Vector2 {.x (+ prot-x 80.0) .y (- prot-y 45.0)}) 3.0
rl/green)
(rl/draw-text "0" (+ (i32 prot-x) 96) (- (i32 prot-y) 9) 20 rl/black)
@@ -267,9 +267,9 @@
;; against cos feeding y is what rotates it the way the dial is labelled.
(unless (= current-angle 0.0)
(let [rad (/ (* (+ current-angle 90.0) pi) 180.0)]
- (rl/draw-line-ex (rl/Vector2 {:x prot-x :y prot-y})
- (rl/Vector2 {:x (+ (* angle-length (sin-f32 rad)) prot-x)
- :y (+ (* angle-length (cos-f32 rad)) prot-y)})
+ (rl/draw-line-ex (rl/Vector2 {.x prot-x .y prot-y})
+ (rl/Vector2 {.x (+ (* angle-length (sin-f32 rad)) prot-x)
+ .y (+ (* angle-length (cos-f32 rad)) prot-y)})
3.0 gesture-color))))
(defn main []
@@ -285,8 +285,8 @@
(rl/set-target-fps 60)
- (let [b1 (rl/Rectangle {:x 53.0 :y 7.0 :width 48.0 :height 26.0})
- b2 (rl/Rectangle {:x 108.0 :y 7.0 :width 36.0 :height 26.0})]
+ (let [b1 (rl/Rectangle {.x 53.0 .y 7.0 .width 48.0 .height 26.0})
+ b2 (rl/Rectangle {.x 108.0 .y 7.0 .width 36.0 .height 26.0})]
(until (rl/window-should-close?)
;; Update
(let [g (rl/get-gesture-detected)
diff --git a/examples/core-input-gestures.flan b/examples/core-input-gestures.flan
index 364c9e5..3c97431 100644
--- a/examples/core-input-gestures.flan
+++ b/examples/core-input-gestures.flan
@@ -59,9 +59,9 @@
(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
(set last-gesture current-gesture)
diff --git a/examples/core-input-keys.flan b/examples/core-input-keys.flan
index 9f8c95d..6eabef1 100644
--- a/examples/core-input-keys.flan
+++ b/examples/core-input-keys.flan
@@ -26,8 +26,8 @@
"raylib [core] example - input keys")
(defer (rl/close-window))
- (set ball (rl/Vector2 {:x (f32 (/ screen-width 2))
- :y (f32 (/ screen-height 2))}))
+ (set ball (rl/Vector2 {.x (f32 (/ screen-width 2))
+ .y (f32 (/ screen-height 2))}))
(rl/set-target-fps 60)
diff --git a/examples/core-input-multitouch.flan b/examples/core-input-multitouch.flan
index 44be21b..5345054 100644
--- a/examples/core-input-multitouch.flan
+++ b/examples/core-input-multitouch.flan
@@ -13,7 +13,7 @@
;;;; It is a top-level `defvar` 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.
;;;;
diff --git a/examples/core-input-virtual-controls.flan b/examples/core-input-virtual-controls.flan
index df80dd3..aba327b 100644
--- a/examples/core-input-virtual-controls.flan
+++ b/examples/core-input-virtual-controls.flan
@@ -58,10 +58,10 @@
;; 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.
(defconst button-positions [button-max rl/Vector2]
- [(rl/Vector2 {:x 100.0 :y 305.0}) ; up
- (rl/Vector2 {:x 55.0 :y 350.0}) ; left
- (rl/Vector2 {:x 145.0 :y 350.0}) ; right
- (rl/Vector2 {:x 100.0 :y 395.0})]) ; down
+ [(rl/Vector2 {.x 100.0 .y 305.0}) ; up
+ (rl/Vector2 {.x 55.0 .y 350.0}) ; left
+ (rl/Vector2 {.x 145.0 .y 350.0}) ; right
+ (rl/Vector2 {.x 100.0 .y 395.0})]) ; down
(defconst player-speed f32 75.0)
@@ -70,8 +70,8 @@
(defvar player rl/Vector2)
(defn reset-player []
- (set player (rl/Vector2 {:x (/ (f32 screen-width) 2.0)
- :y (/ (f32 screen-height) 2.0)})))
+ (set player (rl/Vector2 {.x (/ (f32 screen-width) 2.0)
+ .y (/ (f32 screen-height) 2.0)})))
(defn abs-f32 [v f32] f32
(max v (- 0.0 v)))
@@ -130,24 +130,24 @@
;; [4 [3 rl/Vector2]]: a fixed array of fixed arrays of a struct, which is the
;; deepest shape any of these ten examples asks for and which works.
(defconst arrow-tris [button-max [3 rl/Vector2]]
- [[(rl/Vector2 {:x 100.0 :y 293.0})
- (rl/Vector2 {:x 91.0 :y 314.0})
- (rl/Vector2 {:x 109.0 :y 314.0})]
- [(rl/Vector2 {:x 64.0 :y 341.0})
- (rl/Vector2 {:x 43.0 :y 350.0})
- (rl/Vector2 {:x 64.0 :y 359.0})]
- [(rl/Vector2 {:x 157.0 :y 350.0})
- (rl/Vector2 {:x 136.0 :y 341.0})
- (rl/Vector2 {:x 136.0 :y 359.0})]
- [(rl/Vector2 {:x 91.0 :y 386.0})
- (rl/Vector2 {:x 100.0 :y 407.0})
- (rl/Vector2 {:x 109.0 :y 386.0})]])
+ [[(rl/Vector2 {.x 100.0 .y 293.0})
+ (rl/Vector2 {.x 91.0 .y 314.0})
+ (rl/Vector2 {.x 109.0 .y 314.0})]
+ [(rl/Vector2 {.x 64.0 .y 341.0})
+ (rl/Vector2 {.x 43.0 .y 350.0})
+ (rl/Vector2 {.x 64.0 .y 359.0})]
+ [(rl/Vector2 {.x 157.0 .y 350.0})
+ (rl/Vector2 {.x 136.0 .y 341.0})
+ (rl/Vector2 {.x 136.0 .y 359.0})]
+ [(rl/Vector2 {.x 91.0 .y 386.0})
+ (rl/Vector2 {.x 100.0 .y 407.0})
+ (rl/Vector2 {.x 109.0 .y 386.0})]])
(defconst label-colors [button-max rl/Color]
- [(rl/Color {:r 253 :g 249 :b 0 :a 255}) ; yellow, up
- (rl/Color {:r 0 :g 121 :b 241 :a 255}) ; blue, left
- (rl/Color {:r 230 :g 41 :b 55 :a 255}) ; red, right
- (rl/Color {:r 0 :g 228 :b 48 :a 255})]) ; green, down
+ [(rl/Color {.r 253 .g 249 .b 0 .a 255}) ; yellow, up
+ (rl/Color {.r 0 .g 121 .b 241 .a 255}) ; blue, left
+ (rl/Color {.r 230 .g 41 .b 55 .a 255}) ; red, right
+ (rl/Color {.r 0 .g 228 .b 48 .a 255})]) ; green, down
(defn main []
(rl/init-window screen-width screen-height
diff --git a/lib/ast.ml b/lib/ast.ml
index c37147e..9485081 100644
--- a/lib/ast.ml
+++ b/lib/ast.ml
@@ -46,7 +46,7 @@ and expr_kind =
| Field of expr * string (* (.pos c) — auto-derefs one level *)
| Call of expr * expr list
| Match of expr * arm list
- | Struct of string * (string * expr) list (* (Cursor {:src s}) *)
+ | Struct of string * (string * expr) list (* (Cursor {.src s}) *)
| Arr of expr list (* [0xE6B800FF ...] — a fixed array value *)
(* These bind names or alter control flow, so none of them can be a call. *)
| Fn of string list * expr list (* (fn [x y] ...) — non-escaping *)
diff --git a/lib/check.ml b/lib/check.ml
index 91be36e..f2773a5 100644
--- a/lib/check.ml
+++ b/lib/check.ml
@@ -1384,7 +1384,7 @@ and fold_left_prim ctx ~want loc name p ok what args =
No allocating operation returns an error and none can fail silently. When
the allocator cannot satisfy a request the operation signals
- (StorageExhausted {:bytes n :align a :allocator id})
+ (StorageExhausted {.bytes n .align a .allocator id})
with [error] — whose type is Never — inside a [restart-case] offering
[retry]. That is one rule over every allocating operation, which is what
@@ -2309,7 +2309,7 @@ and named_call ctx ~want loc name args =
1. It does NOT check UTF-8, because `string` does not claim UTF-8. The
prelude settles this: valid-utf8? is an ordinary function you call when
you care, decode-rune/rune-at/rune-count all take [u8] rather than
- string, and decode-rune answers {:ok false :width 1} on a malformed
+ string, and decode-rune answers {.ok false .width 1} on a malformed
byte rather than assuming its input is well-formed. The one place the
runtime treats a string differently from a byte slice is
flan_escape_bytes, for a string nested in a printed structure, and that
@@ -2486,7 +2486,7 @@ and named_call ctx ~want loc name args =
if Hashtbl.mem ctx.env.structs name || Hashtbl.mem ctx.env.unions name
then
fail loc
- "%s is a type — a struct value is written (%s {:field value ...})"
+ "%s is a type — a struct value is written (%s {.field value ...})"
name name
else if String.contains name '/' then
unimplemented loc
diff --git a/lib/form.ml b/lib/form.ml
index 6c81f11..8ee4239 100644
--- a/lib/form.ml
+++ b/lib/form.ml
@@ -17,7 +17,8 @@ and value =
| Byte of int (* \space \0 \( (0..255) *)
| List of t list (* (f x) *)
| Vec of t list (* [1 2 3] and every binding/type bracket *)
- | Map of t list (* {:key v} in value position, {K V} in type position *)
+ | Map of t list (* {.field v} a struct value, {K V} a type. The
+ colon spelling is left for map literals. *)
let make v loc = { v; loc }
diff --git a/lib/parse.ml b/lib/parse.ml
index 229dd93..a9347c6 100644
--- a/lib/parse.ml
+++ b/lib/parse.ml
@@ -111,7 +111,7 @@ let rec expr (f : Form.t) : Ast.expr =
(* In value position brackets are a fixed-array literal; in type position
they are a slice or array type. Position disambiguates, as with {}. *)
| Vec items -> mk (Ast.Arr (List.map expr items))
- | Map _ -> fail f "a bare map is not an expression; write (Type {:field v})"
+ | Map _ -> fail f "a bare map is not an expression; write (Type {.field v})"
| List [] -> fail f "() is not an expression"
| List (head :: args) -> form f mk head args
@@ -343,11 +343,11 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
| [ target ] -> mk (Ast.Field (expr target, field))
| _ -> fail f "field access is (.%s value)" field)
- (* ── struct literal: (Cursor {:src s :pos 0}) ───────────────────── *)
+ (* ── struct literal: (Cursor {.src s .pos 0}) ───────────────────── *)
| Sym name when args <> [] && is_map (List.hd args) ->
(match args with
| [ { v = Map kvs; _ } ] -> mk (Ast.Struct (name, struct_fields f kvs))
- | _ -> fail f "a struct literal is (%s {:field value ...})" name)
+ | _ -> fail f "a struct literal is (%s {.field value ...})" name)
(* ── anything else is a call ────────────────────────────────────── *)
| _ -> mk (Ast.Call (expr head, List.map expr args))
@@ -410,11 +410,18 @@ and destructure (p : Form.t) (v : Ast.expr) : Ast.binding list =
{:keys [x y]} over a struct or [a b] over a fixed array"
(Form.to_string p)
-(* {:keys [x y]} and {inner :field}, over a struct. Clojure's map destructuring
+(* {:keys [x y]} and {inner .field}, over a struct. Clojure's map destructuring
with Flan's structs standing in for its maps: [:keys] is the common case and
the pair form is what nests, since a [:keys] entry is a name and never a
pattern. Everything else Clojure puts in this position — [:as], [:or],
- [:strs], [:syms] — is refused by name where it is written. *)
+ [:strs], [:syms] — is refused by name where it is written.
+
+ [:keys] keeps its colon while [.field] takes the dot, and the split is the
+ point rather than an inconsistency: [.field] names a field of the struct,
+ [:keys] names no field at all — it is an instruction to the compiler that
+ happens to sit in the same brace. Keeping them apart leaves the dot meaning
+ exactly one thing, "this names a field", which is the whole reason the
+ colon was given up here. *)
and dmap (p : Form.t) (t : Ast.expr) (items : Form.t list) : Ast.binding list =
let ex loc e : Ast.expr = { Ast.e; loc } in
let field loc name = ex loc (Ast.Field (t, name)) in
@@ -438,7 +445,7 @@ and dmap (p : Form.t) (t : Ast.expr) (items : Form.t list) : Ast.binding list =
| _ ->
Loc.fail n.loc
":keys binds field names, and %s is not one — a nested pattern \
- is written {%s :field}"
+ is written {%s .field}"
(Form.to_string n) (Form.to_string n)
in
{ Ast.bname = name; bty = None; bval = field n.loc name; bloc = n.loc }
@@ -449,15 +456,22 @@ and dmap (p : Form.t) (t : Ast.expr) (items : Form.t list) : Ast.binding list =
ignore rest;
Loc.fail bad.loc
":%s is not implemented in a destructuring pattern — a struct pattern \
- is {:keys [x y]} or {name :field}, and nothing else" k
- | pat :: ({ v = Kw fld; _ } as fform) :: rest ->
- destructure pat (field fform.loc fld) @ go rest
+ is {:keys [x y]} or {name .field}, and nothing else" k
+ | pat :: ({ v = Sym s; _ } as fform) :: rest
+ when String.length s > 1 && s.[0] = '.' ->
+ destructure pat (field fform.loc (String.sub s 1 (String.length s - 1)))
+ @ go rest
+ | pat :: ({ v = Kw fld; _ } as bad) :: _ ->
+ ignore pat;
+ Loc.fail bad.loc
+ "a field label is written .%s, not :%s — the colon is for keys, and a \
+ struct pattern binds {name .%s}" fld fld fld
| pat :: other :: _ ->
Loc.fail other.loc
- "expected :field after %s, found %s — a struct pattern binds \
- {name :field}" (Form.to_string pat) (Form.to_string other)
+ "expected .field after %s, found %s — a struct pattern binds \
+ {name .field}" (Form.to_string pat) (Form.to_string other)
| [ odd ] ->
- Loc.fail odd.loc "%s has no :field — a struct pattern comes in pairs"
+ Loc.fail odd.loc "%s has no .field — a struct pattern comes in pairs"
(Form.to_string odd)
in
if items = [] then
@@ -543,12 +557,22 @@ and no_duplicates (p : Form.t) (bs : Ast.binding list) =
in
ignore p; go [] bs
+(* A field label is a dot, never a colon. The delimiter is what disambiguates:
+ [(.x v)] is a call and therefore an access, [{.x 1.0}] is a brace form and
+ therefore a construction. The colon is left for keys — map keys and enum
+ members — so the two never share a spelling. *)
and struct_fields f (items : Form.t list) : (string * Ast.expr) list =
let rec go = function
| [] -> []
- | { v = Kw k; _ } :: value :: rest -> (k, expr value) :: go rest
+ | { v = Sym s; _ } :: value :: rest
+ when String.length s > 1 && s.[0] = '.' ->
+ (String.sub s 1 (String.length s - 1), expr value) :: go rest
+ | ({ v = Kw k; _ } as bad) :: _ :: _ ->
+ Loc.fail bad.loc
+ "a field label is written .%s, not :%s — the colon is for keys, and a \
+ struct value is (Type {.%s value ...})" k k k
| other :: _ :: _ ->
- Loc.fail other.loc "expected :field, found %s" (Form.to_string other)
+ Loc.fail other.loc "expected .field, found %s" (Form.to_string other)
| [ odd ] -> Loc.fail odd.loc "field %s has no value" (Form.to_string odd)
in
ignore f; go items
diff --git a/lib/prelude.ml b/lib/prelude.ml
index 5ce9bd5..f6d32c0 100644
--- a/lib/prelude.ml
+++ b/lib/prelude.ml
@@ -526,10 +526,10 @@ let source = {flan|
(defn decode-rune [s [u8]] Rune
(when (= (len s) 0)
- (return (Rune {:code 0 :width 0 :ok false})))
+ (return (Rune {.code 0 .width 0 .ok false})))
(let [b0 (at s 0)]
(when (< b0 0x80)
- (return (Rune {:code (i32 b0) :width 1 :ok true})))
+ (return (Rune {.code (i32 b0) .width 1 .ok true})))
;; size 0 means "this byte cannot lead"; lo/hi are the *second* byte's
;; accepted range, which is the only place the overlong and surrogate
;; rules live. Bytes three and four are always 0x80..0xbf.
@@ -548,34 +548,34 @@ let source = {flan|
(= b0 0xf4) (do (set size 4) (set hi (u8 0x8f)))
:else (set size 0))
(when (= size 0)
- (return (Rune {:code 0 :width 1 :ok false})))
+ (return (Rune {.code 0 .width 1 .ok false})))
;; A sequence cut off by the end of the slice. Width 1, so a caller
;; scanning a buffer boundary makes progress instead of stalling.
(when (> size (len s))
- (return (Rune {:code 0 :width 1 :ok false})))
+ (return (Rune {.code 0 .width 1 .ok false})))
(let [b1 (at s 1)]
(when (or (< b1 lo) (> b1 hi))
- (return (Rune {:code 0 :width 1 :ok false})))
+ (return (Rune {.code 0 .width 1 .ok false})))
(when (= size 2)
- (return (Rune {:code (bit-or (<< (i32 (bit-and b0 0x1f)) 6)
+ (return (Rune {.code (bit-or (<< (i32 (bit-and b0 0x1f)) 6)
(i32 (bit-and b1 0x3f)))
- :width 2 :ok true})))
+ .width 2 .ok true})))
(let [b2 (at s 2)]
(when (or (< b2 0x80) (> b2 0xbf))
- (return (Rune {:code 0 :width 1 :ok false})))
+ (return (Rune {.code 0 .width 1 .ok false})))
(when (= size 3)
- (return (Rune {:code (bit-or (bit-or (<< (i32 (bit-and b0 0x0f)) 12)
+ (return (Rune {.code (bit-or (bit-or (<< (i32 (bit-and b0 0x0f)) 12)
(<< (i32 (bit-and b1 0x3f)) 6))
(i32 (bit-and b2 0x3f)))
- :width 3 :ok true})))
+ .width 3 .ok true})))
(let [b3 (at s 3)]
(when (or (< b3 0x80) (> b3 0xbf))
- (return (Rune {:code 0 :width 1 :ok false})))
- (Rune {:code (bit-or (bit-or (<< (i32 (bit-and b0 0x07)) 18)
+ (return (Rune {.code 0 .width 1 .ok false})))
+ (Rune {.code (bit-or (bit-or (<< (i32 (bit-and b0 0x07)) 18)
(bit-or (<< (i32 (bit-and b1 0x3f)) 12)
(<< (i32 (bit-and b2 0x3f)) 6)))
(i32 (bit-and b3 0x3f)))
- :width 4 :ok true})))))))
+ .width 4 .ok true})))))))
;; Decode at a byte offset. None when the offset is not on a rune boundary or
;; the bytes there are malformed, which is stricter than Odin's rune_at — that
@@ -683,7 +683,7 @@ let source = {flan|
(defstruct Split [rest [u8] sep u8 more bool])
(defn split-on-byte [s [u8] sep u8] Split
- (Split {:rest s :sep sep :more true}))
+ (Split {.rest s .sep sep .more true}))
(defn split-next! [it (Ptr Split)] (Option [u8])
(when (not (.more it))
diff --git a/lib/render.ml b/lib/render.ml
index ec3fe2a..1346521 100644
--- a/lib/render.ml
+++ b/lib/render.ml
@@ -147,6 +147,10 @@ let rec render c depth (e : Tast.expr) : Tast.expr list =
(fun i (f : Tast.field) ->
let v = { Tast.e = Tast.Field (e, i); ty = f.Tast.fty; loc } in
(if i = 0 then [] else [ lit " " ])
+ (* Still a colon, deliberately. This printed form is a wire
+ format: emacs/flan-inspect.el parses it back, and it
+ hard-codes the colon. Moving the printer to the dot has
+ to land with that reader, in the Emacs lane. *)
@ [ lit (":" ^ f.Tast.fname ^ " ") ]
@ render c (depth + 1) v)
shown)
diff --git a/plan.org b/plan.org
index 2cd59a6..1a6bf92 100644
--- a/plan.org
+++ b/plan.org
@@ -117,9 +117,9 @@ world.
~(clone x)~. Value structs are the snapshot / undo / replay story; they need no
separate type.
- Literals live in read-only memory.
-- Struct literals name fields: ~(Cursor {:src src :pos 0})~. *Omitted fields are
+- Struct literals name fields: ~(Cursor {.src src .pos 0})~. *Omitted fields are
zeroed*, as in Odin — the same rule as a declaration with no initialiser, so
- ~(Cursor {:src src})~ is complete and means ~pos~ is 0.
+ ~(Cursor {.src src})~ is complete and means ~pos~ is 0.
- *Zero is initialisation (ZII), with an opt-out.* No initialiser means
all-bytes-zero. ~(defvar buf [65536 u8] uninit)~ skips it, exactly as Odin's
~---~ does, for a large buffer that is about to be overwritten. ~uninit~ is
diff --git a/sand.flan b/sand.flan
index bd3fbce..f20da9f 100644
--- a/sand.flan
+++ b/sand.flan
@@ -101,7 +101,7 @@
;; over a mutable scan position, which is what a while loop is.
(defn settle [row i32 col i32]
(let [vel (+ gravity (at velocity row col))
- some-point (rl/Vector2 {:x 15.0 :y 12})
+ some-point (rl/Vector2 {.x 15.0 .y 12})
y (min (- rows 1) (+ row (i32 vel)))]
(while (> y row)
(when (empty-at? y col)
@@ -217,18 +217,18 @@
(let [m (rl/get-mouse-position)
frame (f32 (if (rl/mouse-button-down? :left) 8.0 0.0))]
(rl/draw-texture-rec brush
- (rl/Rectangle {:x frame :y 0.0 :width 8.0 :height 8.0})
- (rl/Vector2 {:x (- (.x m) 4.0) :y (- (.y m) 4.0)})
+ (rl/Rectangle {.x frame .y 0.0 .width 8.0 .height 8.0})
+ (rl/Vector2 {.x (- (.x m) 4.0) .y (- (.y m) 4.0)})
rl/white)
(rl/draw-texture brush 20 50 rl/white)
- (rl/draw-texture-v brush (rl/Vector2 {:x 44.0 :y 50.0})
+ (rl/draw-texture-v brush (rl/Vector2 {.x 44.0 .y 50.0})
(rl/get-color (at colors current-color)))
- (rl/draw-texture-ex brush (rl/Vector2 {:x 72.0 :y 46.0}) 0.0 2.0 rl/white)))
+ (rl/draw-texture-ex brush (rl/Vector2 {.x 72.0 .y 46.0}) 0.0 2.0 rl/white)))
;; The mirrored one beside them, scaled up so the flip is visible rather
;; than eight pixels wide. If the two badges look the same, either the flip
;; did nothing or the upload took the unedited buffer.
(when brush-mirrored-ok
- (rl/draw-texture-ex brush-mirrored (rl/Vector2 {:x 110.0 :y 46.0})
+ (rl/draw-texture-ex brush-mirrored (rl/Vector2 {.x 110.0 .y 46.0})
0.0 2.0 rl/white)))
;; ── Sound ──────────────────────────────────────────────────────────
@@ -281,9 +281,9 @@
(unless audio-ok
(println "sand: no audio device — the grains are silent"))
(build-tone 40)
- (let [w (rl/Wave {:frame-count (u32 tone-frames) :sample-rate (u32 tone-rate)
- :sample-size 16 :channels 1
- :data (addr (at tone-pcm 0))})]
+ (let [w (rl/Wave {.frame-count (u32 tone-frames) .sample-rate (u32 tone-rate)
+ .sample-size 16 .channels 1
+ .data (addr (at tone-pcm 0))})]
(when audio-ok
(set tone (rl/load-sound-from-wave w))
(set tone-ok (rl/sound-valid? tone))
@@ -376,16 +376,16 @@
;; A fresh (Camera2D {}) has a zoom of 0, which is singular: both conversions
;; hand back NaN and nothing draws. 1.0 is the identity.
(defn reset-view []
- (set view (rl/Camera2D {:offset (rl/Vector2 {:x 0.0 :y 0.0})
- :target (rl/Vector2 {:x 0.0 :y 0.0})
- :rotation 0.0
- :zoom 1.0})))
+ (set view (rl/Camera2D {.offset (rl/Vector2 {.x 0.0 .y 0.0})
+ .target (rl/Vector2 {.x 0.0 .y 0.0})
+ .rotation 0.0
+ .zoom 1.0})))
(defn set-view [target-x f32 target-y f32 zoom f32]
- (set view (rl/Camera2D {:offset (.offset view)
- :target (rl/Vector2 {:x target-x :y target-y})
- :rotation (.rotation view)
- :zoom zoom})))
+ (set view (rl/Camera2D {.offset (.offset view)
+ .target (rl/Vector2 {.x target-x .y target-y})
+ .rotation (.rotation view)
+ .zoom zoom})))
;; Panning is world units per second and zooming is a factor per second, so
;; neither changes with the frame rate. That is the whole of what
@@ -482,12 +482,12 @@
(rl/draw-circle-lines-v p (+ r (f32 6.0)) tint)
;; A crosshair: two thin lines and one thick one, which is three separate
;; raylib calls with three different shapes of argument.
- (rl/draw-line-v (rl/Vector2 {:x (- x r) :y y})
- (rl/Vector2 {:x (+ x r) :y y}) tint)
- (rl/draw-line-v (rl/Vector2 {:x x :y (- y r)})
- (rl/Vector2 {:x x :y (+ y r)}) tint)
- (rl/draw-line-ex (rl/Vector2 {:x (- x (f32 4.0)) :y y})
- (rl/Vector2 {:x (+ x (f32 4.0)) :y y}) (f32 3.0) rl/white)
+ (rl/draw-line-v (rl/Vector2 {.x (- x r) .y y})
+ (rl/Vector2 {.x (+ x r) .y y}) tint)
+ (rl/draw-line-v (rl/Vector2 {.x x .y (- y r)})
+ (rl/Vector2 {.x x .y (+ y r)}) tint)
+ (rl/draw-line-ex (rl/Vector2 {.x (- x (f32 4.0)) .y y})
+ (rl/Vector2 {.x (+ x (f32 4.0)) .y y}) (f32 3.0) rl/white)
;; The exact world point: a filled dot, and one pixel of white on top of
;; it. Both are the Vector2 forms, so they land where the ring's centre
;; is and not somewhere an integer cast put them.
@@ -496,16 +496,16 @@
;; A pointer above the cursor. Counter-clockwise, because raylib culls the
;; other winding and draws nothing — which looks exactly like a broken
;; binding and is why the outline is drawn over it as a control.
- (let [tip (rl/Vector2 {:x x :y (- y (+ r (f32 26.0)))})
- left (rl/Vector2 {:x (- x (f32 12.0)) :y (- y (+ r (f32 6.0)))})
- rght (rl/Vector2 {:x (+ x (f32 12.0)) :y (- y (+ r (f32 6.0)))})]
+ (let [tip (rl/Vector2 {.x x .y (- y (+ r (f32 26.0)))})
+ left (rl/Vector2 {.x (- x (f32 12.0)) .y (- y (+ r (f32 6.0)))})
+ rght (rl/Vector2 {.x (+ x (f32 12.0)) .y (- y (+ r (f32 6.0)))})]
(rl/draw-triangle tip left rght tint)
(rl/draw-triangle-lines tip left rght rl/white))
;; And the world's own edge, so panning has something to pan against.
(rl/draw-rectangle-lines-ex
- (rl/Rectangle {:x 0.0 :y 0.0
- :width (f32 screen-width)
- :height (f32 screen-height)})
+ (rl/Rectangle {.x 0.0 .y 0.0
+ .width (f32 screen-width)
+ .height (f32 screen-height)})
(f32 2.0) (rl/get-color 0x303030FF))))
;; Drawn outside the camera, in screen pixels, so it stays put while the world
@@ -532,8 +532,8 @@
h 72
x 24
y (- (rl/get-screen-height) (+ h 24))
- panel (rl/Rectangle {:x (f32 (- x 12)) :y (f32 (- y 12))
- :width (f32 (+ w 24)) :height (f32 (+ h 24))})]
+ panel (rl/Rectangle {.x (f32 (- x 12)) .y (f32 (- y 12))
+ .width (f32 (+ w 24)) .height (f32 (+ h 24))})]
(rl/draw-rectangle-rounded panel (f32 0.2) 8 (rl/get-color 0x101018E0))
;; Both outline forms, one inside the other: the plain one has no
;; thickness in raylib 5.5 and the -ex one is where thickness went.
@@ -541,7 +541,7 @@
(rl/draw-rectangle-rounded-lines-ex panel (f32 0.2) 8 (f32 2.0)
(rl/get-color 0x6060A0FF))
(if hud-font-ok
- (rl/draw-text-ex hud-font title (rl/Vector2 {:x (f32 x) :y (f32 y)})
+ (rl/draw-text-ex hud-font title (rl/Vector2 {.x (f32 x) .y (f32 y)})
30.0 4.0 rl/white)
(rl/draw-text title x y 30 rl/white))
(rl/draw-text keys x (+ y 40) 20 (rl/get-color 0xA0A0B0FF))
@@ -565,8 +565,8 @@
(let [cp (+ 48 current-color)]
(when (> (rl/get-glyph-index hud-font cp) 0)
(rl/draw-text-codepoint hud-font cp
- (rl/Vector2 {:x (f32 (- sw 24))
- :y (f32 (- sh 96))})
+ (rl/Vector2 {.x (f32 (- sw 24))
+ .y (f32 (- sh 96))})
30.0 rl/white))))
;; A zoom read-out with no number in it, because there is no string
@@ -577,11 +577,11 @@
(let [bx (f32 (- sw 220))
by (f32 (- sh 60))
fill (* (f32 200.0) (min (f32 1.0) (/ (.zoom view) (f32 8.0))))]
- (rl/draw-rectangle-rec (rl/Rectangle {:x bx :y by :width (f32 200.0)
- :height (f32 10.0)})
+ (rl/draw-rectangle-rec (rl/Rectangle {.x bx .y by .width (f32 200.0)
+ .height (f32 10.0)})
(rl/get-color 0x202028FF))
- (rl/draw-rectangle-v (rl/Vector2 {:x bx :y by})
- (rl/Vector2 {:x fill :y (f32 10.0)})
+ (rl/draw-rectangle-v (rl/Vector2 {.x bx .y by})
+ (rl/Vector2 {.x fill .y (f32 10.0)})
(rl/get-color 0x6060A0FF))
(rl/draw-rectangle-lines (- sw 220) (- sh 60) 200 10
(rl/get-color 0x8080C0FF))
@@ -600,7 +600,7 @@
(rl/draw-ellipse ex ey (f32 26.0) (f32 12.0) (rl/get-color 0x303040FF))
(rl/draw-ellipse-lines ex ey (f32 26.0) (f32 12.0)
(rl/get-color 0x8080C0FF))
- (rl/draw-ring-lines (rl/Vector2 {:x (f32 ex) :y (f32 ey)})
+ (rl/draw-ring-lines (rl/Vector2 {.x (f32 ex) .y (f32 ey)})
(f32 30.0) (f32 34.0) spin (+ spin (f32 270.0)) 32
rl/white)))))
@@ -628,19 +628,19 @@
;; separates "centred stick" from "no pad" — both of which are a dot in
;; the middle otherwise.
(rl/draw-circle-lines (i32 ox) (i32 oy) r (rl/get-color 0x6060A0FF))
- (let [p (rl/Vector2 {:x (+ ox (* (rl/get-gamepad-axis-movement 0 :left-x) r))
- :y (+ oy (* (rl/get-gamepad-axis-movement 0 :left-y) r))})]
+ (let [p (rl/Vector2 {.x (+ ox (* (rl/get-gamepad-axis-movement 0 :left-x) r))
+ .y (+ oy (* (rl/get-gamepad-axis-movement 0 :left-y) r))})]
(rl/draw-circle-v p (f32 5.0) rl/white))
;; The two triggers as bars of different lengths, so exchanging them is
;; visible. They rest at -1 and not at 0, which raylib does not
;; normalise and neither does this — hence the +1.
(let [lt (+ (f32 1.0) (rl/get-gamepad-axis-movement 0 :left-trigger))
rt (+ (f32 1.0) (rl/get-gamepad-axis-movement 0 :right-trigger))]
- (rl/draw-rectangle-v (rl/Vector2 {:x (+ ox (f32 46.0)) :y (- oy (f32 12.0))})
- (rl/Vector2 {:x (* lt (f32 30.0)) :y (f32 8.0)})
+ (rl/draw-rectangle-v (rl/Vector2 {.x (+ ox (f32 46.0)) .y (- oy (f32 12.0))})
+ (rl/Vector2 {.x (* lt (f32 30.0)) .y (f32 8.0)})
(rl/get-color 0x8080C0FF))
- (rl/draw-rectangle-v (rl/Vector2 {:x (+ ox (f32 46.0)) :y (+ oy (f32 4.0))})
- (rl/Vector2 {:x (* rt (f32 50.0)) :y (f32 8.0)})
+ (rl/draw-rectangle-v (rl/Vector2 {.x (+ ox (f32 46.0)) .y (+ oy (f32 4.0))})
+ (rl/Vector2 {.x (* rt (f32 50.0)) .y (f32 8.0)})
(rl/get-color 0x8080C0FF)))
;; One pip per axis the pad reports, and one per face button that is
;; NOT up — gamepad-button-up? rather than -down? so the negative form
@@ -661,8 +661,8 @@
(rl/draw-circle-lines-v (rl/get-touch-position i) (f32 18.0)
(rl/get-color 0xA0A0FFFF))
(rl/draw-pixel (rl/get-touch-x) (rl/get-touch-y) rl/white)
- (rl/draw-pixel-v (rl/Vector2 {:x (f32 (rl/get-touch-point-id i))
- :y (f32 4.0)})
+ (rl/draw-pixel-v (rl/Vector2 {.x (f32 (rl/get-touch-point-id i))
+ .y (f32 4.0)})
rl/white))
;; And the gesture, if any: a bar as long as the hold has lasted, and the
@@ -673,14 +673,14 @@
8 rl/white)
(when (rl/gesture-detected? :drag)
(let [d (rl/get-gesture-drag-vector)]
- (rl/draw-line-v (rl/Vector2 {:x ox :y oy})
- (rl/Vector2 {:x (+ ox (* (.x d) (f32 200.0)))
- :y (+ oy (* (.y d) (f32 200.0)))})
+ (rl/draw-line-v (rl/Vector2 {.x ox .y oy})
+ (rl/Vector2 {.x (+ ox (* (.x d) (f32 200.0)))
+ .y (+ oy (* (.y d) (f32 200.0)))})
(rl/get-color 0xFFC000FF))))
(when (rl/gesture-detected? :pinch-in)
(let [q (rl/get-gesture-pinch-vector)]
- (rl/draw-circle-v (rl/Vector2 {:x (+ ox (* (.x q) (f32 200.0)))
- :y (+ oy (* (.y q) (f32 200.0)))})
+ (rl/draw-circle-v (rl/Vector2 {.x (+ ox (* (.x q) (f32 200.0)))
+ .y (+ oy (* (.y q) (f32 200.0)))})
(f32 4.0) rl/white))))))
(defn draw-world []
@@ -706,10 +706,10 @@
;; screen and nothing else changes.
(if scene-ok
(rl/draw-texture-rec (.texture scene)
- (rl/Rectangle {:x 0.0 :y 0.0
- :width (f32 screen-width)
- :height (f32 (- 0 screen-height))})
- (rl/Vector2 {:x 0.0 :y 0.0})
+ (rl/Rectangle {.x 0.0 .y 0.0
+ .width (f32 screen-width)
+ .height (f32 (- 0 screen-height))})
+ (rl/Vector2 {.x 0.0 .y 0.0})
rl/white)
(draw-world))
;; And everything after it is in screen pixels again.
diff --git a/spec-conditions.md b/spec-conditions.md
index 5aedd48..1391c8e 100644
--- a/spec-conditions.md
+++ b/spec-conditions.md
@@ -25,7 +25,7 @@ must produce its type on the *fall-through* path too:
(if (file-exists? path)
(rl/load-texture path)
(restart-case
- (do (signal (AssetMissing {:path path}))
+ (do (signal (AssetMissing {.path path}))
(abort "unhandled AssetMissing")) ; fall-through must not return
(use-placeholder [] placeholder-texture)
(retry [] (load-texture path)))))
diff --git a/spec-memory.md b/spec-memory.md
index deecd60..72f3f66 100644
--- a/spec-memory.md
+++ b/spec-memory.md
@@ -385,7 +385,7 @@ fixed here; nothing is built that needs it yet.
the allocator cannot satisfy a request, the operation signals
```
-(StorageExhausted {:bytes n :align a :allocator id})
+(StorageExhausted {.bytes n .align a .allocator id})
```
with `error`, whose type is `Never` (spec-conditions.md §2), inside a
diff --git a/syntax-sketch.flan b/syntax-sketch.flan
index 3afe994..def2402 100644
--- a/syntax-sketch.flan
+++ b/syntax-sketch.flan
@@ -20,8 +20,10 @@
;; {string i32} owning hashmap — move-only, shorthand for (Map string i32)
;;
;; Braces are read by position: in a TYPE position {K V} is a map type; in a
-;; VALUE position {:field v ...} is a struct or condition literal. There is no
-;; map literal — a map is built with make-map and an allocator.
+;; VALUE position {.field v ...} is a struct or condition literal — a field
+;; label is a dot, and the colon is left for keys. There is no map literal yet;
+;; a map is built with make-map and an allocator, and when a literal arrives it
+;; takes {:key value}, which is why the dot is what struct construction uses.
;; (Ptr World) pointer
;; (Fn [f32] bool) function pointer, no captured environment
;; (Option a) union from the stdlib
@@ -100,8 +102,8 @@
(let [text (try (read-file path))
table (try (parse-toml text))
port (try (ok-or (get table "port")
- (MissingKey {:key "port"})))]
- (Ok (Config {:port port}))))
+ (MissingKey {.key "port"})))]
+ (Ok (Config {.port port}))))
;; errdefer runs only on the Result failure path — NOT on a restart transfer
;; (spec-conditions.md §5). Pairs with explicit allocation.
@@ -109,7 +111,7 @@
(let [buf (alloc-image context/allocator)]
(errdefer (free buf))
(try (decode-png path buf))
- (Ok (Atlas {:image buf}))))
+ (Ok (Atlas {.image buf}))))
;; ── Conditions: handlers run on the signalling frame, nothing unwinds ─
;; load-texture cannot know the right recovery — an editor wants a placeholder,
@@ -125,7 +127,7 @@
(if (file-exists? path)
(rl/load-texture path)
(restart-case
- (do (signal (AssetMissing {:path path}))
+ (do (signal (AssetMissing {.path path}))
(abort "unhandled AssetMissing"))
(use-placeholder [] placeholder-texture)
(retry [] (load-texture path)))))
diff --git a/test/programs/agent-longname.flan b/test/programs/agent-longname.flan
index c1a7d2f..4e7d86e 100644
--- a/test/programs/agent-longname.flan
+++ b/test/programs/agent-longname.flan
@@ -11,5 +11,5 @@
(defn main [] i32
;; The path is overridden by FLAN_AGENT_SOCKET; a program has to name one.
(agent/start "/tmp/flan-longname.sock")
- (error (MissingYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY {:id 1}))
+ (error (MissingYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY {.id 1}))
0)
diff --git a/test/programs/break.flan b/test/programs/break.flan
index 64a8d5b..fe37c9b 100644
--- a/test/programs/break.flan
+++ b/test/programs/break.flan
@@ -10,7 +10,7 @@
(defn fetch [n i32] i32
(restart-case
- (do (error (Missing {:id n})) 0)
+ (do (error (Missing {.id n})) 0)
(use-placeholder [] -1)
(retry [] 7)))
@@ -21,7 +21,7 @@
;;; file that by-name lookup cannot produce.
(defn shadowed [n i32] i32
(restart-case
- (+ (restart-case (do (error (Missing {:id n})) 0)
+ (+ (restart-case (do (error (Missing {.id n})) 0)
(retry [] 5))
100)
(retry [] 900)))
diff --git a/test/programs/cleanup.flan b/test/programs/cleanup.flan
index 19b3a4a..4fa80d2 100644
--- a/test/programs/cleanup.flan
+++ b/test/programs/cleanup.flan
@@ -26,7 +26,7 @@
;;; and (6) a handler-bind with two clauses has to pop both, innermost-first.
;;; If either leaks, the stack keeps a frame pointing into a function that has
;;; gone, and the next signal calls into it.
-(defn deep [] i32 (signal (Missing {:id 1})) 0)
+(defn deep [] i32 (signal (Missing {.id 1})) 0)
(defn leaky [] i32
(restart-case
diff --git a/test/programs/conditions.flan b/test/programs/conditions.flan
index c578016..b2b24b8 100644
--- a/test/programs/conditions.flan
+++ b/test/programs/conditions.flan
@@ -13,9 +13,9 @@
;;; Signals twice and keeps going both times — that is the whole of §1.
(defn load-all []
- (signal (AssetMissing {:id 1}))
- (signal (AssetMissing {:id 2}))
- (signal (Corrupt {:id 3})))
+ (signal (AssetMissing {.id 1}))
+ (signal (AssetMissing {.id 2}))
+ (signal (Corrupt {.id 3})))
(defn main [] i32
;; No handler: a no-op, not an abort and not a message (§2).
@@ -36,7 +36,7 @@
;; Nesting: the inner frame does not displace the outer one, so both run.
(handler-bind [(Corrupt [c] (set other (+ other 100)))]
(handler-bind [(Corrupt [c] (set other (+ other 1000)))]
- (signal (Corrupt {:id 0}))))
+ (signal (Corrupt {.id 0}))))
(print other) (println "") ; 3 + 1000 + 100 = 1103
;; And the stack is back to what it was: no handler, no effect.
diff --git a/test/programs/debug-permuted.flan b/test/programs/debug-permuted.flan
index a6a6259..0b5c66b 100644
--- a/test/programs/debug-permuted.flan
+++ b/test/programs/debug-permuted.flan
@@ -16,7 +16,7 @@
bump))
(defn main [] i32
- (let [c (Cell {:alive true :heat 3.25 :id 7 :name "grain"})]
+ (let [c (Cell {.alive true .heat 3.25 .id 7 .name "grain"})]
(let [r (tick (addr c) 41)]
(print r) (println "")
(print (.heat c)) (println "")
diff --git a/test/programs/debug.flan b/test/programs/debug.flan
index fed2282..9e29a60 100644
--- a/test/programs/debug.flan
+++ b/test/programs/debug.flan
@@ -21,7 +21,7 @@
bump))
(defn main [] i32
- (let [c (Cell {:alive true :heat 3.25 :id 7 :name "grain"})]
+ (let [c (Cell {.alive true .heat 3.25 .id 7 .name "grain"})]
(let [r (tick (addr c) 41)]
(print r) (println "")
(print (.heat c)) (println "")
diff --git a/test/programs/destructure.flan b/test/programs/destructure.flan
index 031c74e..94ecd29 100644
--- a/test/programs/destructure.flan
+++ b/test/programs/destructure.flan
@@ -20,7 +20,7 @@
(defn make-point [] Point
(set calls (+ calls 1))
- (Point {:x 3 :y 4}))
+ (Point {.x 3 .y 4}))
(defn show2 [label string a i32 b i32]
(print label)
@@ -32,24 +32,24 @@
(defn main [] i32
;; :keys, the common case: one name per field, spelled as the field is.
- (let [{:keys [x y]} (Point {:x 1 :y 2})]
+ (let [{:keys [x y]} (Point {.x 1 .y 2})]
(show2 "keys" x y))
;; The pair form, which is what renames and what nests — a :keys entry is a
;; field name and never a pattern.
- (let [{a :x b :y} (Point {:x 10 :y 20})]
+ (let [{a .x b .y} (Point {.x 10 .y 20})]
(show2 "pairs" a b))
- (let [l (Line {:a (Point {:x 5 :y 6}) :b (Point {:x 7 :y 8})})]
- (let [{{:keys [x y]} :b} l]
+ (let [l (Line {.a (Point {.x 5 .y 6}) .b (Point {.x 7 .y 8})})]
+ (let [{{:keys [x y]} .b} l]
(show2 "nested" x y))
;; A pattern may shadow the very name it destructures, because the value is
;; read into a temporary before any of the names are bound.
- (let [{l :a} l]
+ (let [{l .a} l]
(show2 "shadow" (.x l) (.y l))))
;; A later binding sees an earlier pattern's names, as in any let.
- (let [{:keys [x]} (Point {:x 100 :y 0})
+ (let [{:keys [x]} (Point {.x 100 .y 0})
doubled (* x 2)]
(show2 "sequential" x doubled))
@@ -81,15 +81,15 @@
(print (len rest)) (println ""))
;; Patterns nest through each other: a struct inside an array.
- (let [ps [(Point {:x 1 :y 2}) (Point {:x 3 :y 4})]
- [{:keys [x]} {y :y}] ps]
+ (let [ps [(Point {.x 1 .y 2}) (Point {.x 3 .y 4})]
+ [{:keys [x]} {y .y}] ps]
(show2 "nested-in-array" x y))
;; A tail of something wider than a machine word. The corpus slices arrays of
;; i32, u8 and f32 and nothing else, so this is the one place the desugared
;; (slice xs n (len xs)) has to get a struct's stride right rather than a
;; scalar's.
- (let [ps [(Point {:x 1 :y 2}) (Point {:x 3 :y 4}) (Point {:x 5 :y 6})]
+ (let [ps [(Point {.x 1 .y 2}) (Point {.x 3 .y 4}) (Point {.x 5 .y 6})]
[first & others] ps]
(print "struct-tail ")
(print (.x first)) (print " ")
@@ -100,7 +100,7 @@
;; Evaluate-once. Two patterns, two calls, four names — one call per pattern.
;; Without the temporary each of the four names would call it again: 4, not 2.
(let [{:keys [x y]} (make-point)
- {a :x b :y} (make-point)]
+ {a .x b .y} (make-point)]
(print "calls ")
(print calls) (print " ")
(print (+ x (+ y (+ a b))))
diff --git a/test/programs/dev-break.flan b/test/programs/dev-break.flan
index 9df48b2..b8a60b6 100644
--- a/test/programs/dev-break.flan
+++ b/test/programs/dev-break.flan
@@ -14,7 +14,7 @@
(defn fetch [n i32] i32
(restart-case
- (do (error (Missing {:id n})) 0)
+ (do (error (Missing {.id n})) 0)
(use-placeholder [] -1)
(retry [] 7)))
diff --git a/test/programs/dev-locals.flan b/test/programs/dev-locals.flan
index da27e79..7a7ef4a 100644
--- a/test/programs/dev-locals.flan
+++ b/test/programs/dev-locals.flan
@@ -12,11 +12,11 @@
(defstruct Boom [why i32])
(defn look [n i64 label string] i64
- (let [p (Point {:x 1.5 :y 2.5})
+ (let [p (Point {.x 1.5 .y 2.5})
xs [10 20 30]
flag (> n 0)]
(restart-case
- (do (error (Boom {:why 7}))
+ (do (error (Boom {.why 7}))
;; Never reached before the break, so [after] is a slot with nothing
;; in it: the frame records a null for it and this is what "not bound
;; yet" has to mean.
diff --git a/test/programs/dev-loop.flan b/test/programs/dev-loop.flan
index 60287b9..d92cec1 100644
--- a/test/programs/dev-loop.flan
+++ b/test/programs/dev-loop.flan
@@ -12,7 +12,7 @@
(defstruct Missing [id i32])
(defn probe [] i64
- (signal (Missing {:id 1}))
+ (signal (Missing {.id 1}))
0)
(defn step [] i64
diff --git a/test/programs/edn.flan b/test/programs/edn.flan
index c506a64..000cfcc 100644
--- a/test/programs/edn.flan
+++ b/test/programs/edn.flan
@@ -82,7 +82,7 @@
;; rather than being returned, which is why this can be a straight line of
;; assignments with one test at the end.
(defn read-enemy [c (Ptr edn/Cursor)] Enemy
- (let [e (Enemy {:hp 0 :speed 0.0 :boss? false})]
+ (let [e (Enemy {.hp 0 .speed 0.0 .boss? false})]
(edn/expect c edn/tok-map-open)
(while (edn/ok? c)
(let [k (edn/next c)]
diff --git a/test/programs/enum-compare.flan b/test/programs/enum-compare.flan
index 45ae7f1..b737b70 100644
--- a/test/programs/enum-compare.flan
+++ b/test/programs/enum-compare.flan
@@ -22,6 +22,6 @@
(println (if (below? :hi) "hi below mid" "hi not below mid"))
;; And through a struct field, which is a different path to the same compare.
- (let [s (S {:k :hi})]
+ (let [s (S {.k :hi})]
(println (if (= (.k s) :hi) "field eq yes" "field eq no")))
0)
diff --git a/test/programs/error.flan b/test/programs/error.flan
index 9148117..2dd96f9 100644
--- a/test/programs/error.flan
+++ b/test/programs/error.flan
@@ -10,5 +10,5 @@
;; A handler that returns normally. It runs — signal's lookup is the same —
;; and it still does not answer the error.
(handler-bind [(AssetMissing [c] (println "handler ran"))]
- (error (AssetMissing {:id 1})))
+ (error (AssetMissing {.id 1})))
0)
diff --git a/test/programs/machine.flan b/test/programs/machine.flan
index 2fddcc0..59027b7 100644
--- a/test/programs/machine.flan
+++ b/test/programs/machine.flan
@@ -31,10 +31,10 @@
(set total (sum-grid))
(print total) (println "") ; 12
(print (at pal 2)) (println "") ; 30
- (let [p (P {:x 1 :y 2})] ; :y omitted is zeroed
+ (let [p (P {.x 1 .y 2})] ; :y omitted is zeroed
(bump (addr p))
(print (.x p)) (println "") ; 2
- (let [l (Line {:a p})]
+ (let [l (Line {.a p})]
(print (.y (.a l))) (println ""))) ; 2
(print (/ 7 2)) (println "") ; 3 integer divide
(print (/ (f64 7) 2.0)) (println "") ; 3.5 float divide
diff --git a/test/programs/pkg-return.flan b/test/programs/pkg-return.flan
index 48b497d..2b42e09 100644
--- a/test/programs/pkg-return.flan
+++ b/test/programs/pkg-return.flan
@@ -17,7 +17,7 @@
;;; And the one that must keep working: a lowercase qualified name in the same
;;; position is an expression, not a type.
-(defn local [] Local (Local {:n 5}))
+(defn local [] Local (Local {.n 5}))
(defn main [] i32
(let [c (fresh (bytes "[1 2]"))
diff --git a/test/programs/raylib-audio.flan b/test/programs/raylib-audio.flan
index dee76ba..dc29ff1 100644
--- a/test/programs/raylib-audio.flan
+++ b/test/programs/raylib-audio.flan
@@ -133,8 +133,8 @@
;; Nothing raylib made: four integers Flan chose and a buffer Flan owns. So
;; every number below is raylib reading THIS struct, and there is no
;; raylib-produced struct anywhere for a permutation to hide inside.
- (let [src (rl/Wave {:frame-count 8 :sample-rate 8000 :sample-size 16
- :channels 1 :data (addr (at pcm 0))})]
+ (let [src (rl/Wave {.frame-count 8 .sample-rate 8000 .sample-size 16
+ .channels 1 .data (addr (at pcm 0))})]
(show-bool "valid" (rl/wave-valid? src))
(show-wave "source" src)
diff --git a/test/programs/raylib-ffi.flan b/test/programs/raylib-ffi.flan
index c802b27..391d47b 100644
--- a/test/programs/raylib-ffi.flan
+++ b/test/programs/raylib-ffi.flan
@@ -37,10 +37,10 @@
;; The camera below is chosen so that no field is silently unpinned — offset
;; and target differ, zoom is 2.0 and not the identity 1.0, and every
;; component is a distinct dyadic value that prints exactly.
-(defconst cam (rl/Camera2D {:offset (rl/Vector2 {:x 100.0 :y 50.0})
- :target (rl/Vector2 {:x 8.0 :y 4.0})
- :rotation 0.0
- :zoom 2.0}))
+(defconst cam (rl/Camera2D {.offset (rl/Vector2 {.x 100.0 .y 50.0})
+ .target (rl/Vector2 {.x 8.0 .y 4.0})
+ .rotation 0.0
+ .zoom 2.0}))
;; Absolute values, not a round trip: screen (140,90) is world (28,24) because
;; ((140-100)/2)+8 = 28 and ((90-50)/2)+4 = 24. Swap offset and target in the
@@ -90,17 +90,17 @@
;; Rectangle, pinned completely. The intersection of (0,0,10,4) and
;; (6,1,10,10) is (6,1,4,3) — four different numbers, each derived from a
;; different pair of fields, so swapping any two fields changes the answer.
- (show-rect (rl/get-collision-rec (rl/Rectangle {:x 0.0 :y 0.0 :width 10.0 :height 4.0})
- (rl/Rectangle {:x 6.0 :y 1.0 :width 10.0 :height 10.0})))
+ (show-rect (rl/get-collision-rec (rl/Rectangle {.x 0.0 .y 0.0 .width 10.0 .height 4.0})
+ (rl/Rectangle {.x 6.0 .y 1.0 .width 10.0 .height 10.0})))
;; Texture2D, as far as a machine with no GPU can go. raylib keeps the
;; shapes texture without touching GL, and substitutes a default when
;; `texture.id`, `source.width` or `source.height` is zero — that guard is
;; the only asymmetry a headless test gets.
- (let [rect (rl/Rectangle {:x 3.5 :y 7.25 :width 11.5 :height 13.75})]
+ (let [rect (rl/Rectangle {.x 3.5 .y 7.25 .width 11.5 .height 13.75})]
;; (A) Valid, five distinct values: they come back, so the struct crosses
;; intact in both directions and raylib stored it rather than defaulting.
- (rl/set-shapes-texture (rl/Texture2D {:id 7 :width 13 :height 17 :mipmaps 2 :format 4}) rect)
+ (rl/set-shapes-texture (rl/Texture2D {.id 7 .width 13 .height 17 .mipmaps 2 .format 4}) rect)
(show-texture (rl/get-shapes-texture))
(show-rect (rl/get-shapes-texture-rectangle))
@@ -108,7 +108,7 @@
;; back. The 7 is the only distinct field in it, so this pins `format` as
;; the last field, and the substitution happening at all pins `id` as the
;; field the guard reads.
- (rl/set-shapes-texture (rl/Texture2D {:id 0 :width 13 :height 17 :mipmaps 2 :format 4}) rect)
+ (rl/set-shapes-texture (rl/Texture2D {.id 0 .width 13 .height 17 .mipmaps 2 .format 4}) rect)
(show-texture (rl/get-shapes-texture))
;; (C) width zero, id positive: still stored, because the guard does not
@@ -120,24 +120,24 @@
;; without a GL context reads width, height or mipmaps, so their order
;; among themselves is not pinned by this test. A swap there shows up as a
;; visibly wrong sprite in the interactive run, and nowhere else.
- (rl/set-shapes-texture (rl/Texture2D {:id 7 :width 0 :height 17 :mipmaps 2 :format 4}) rect)
+ (rl/set-shapes-texture (rl/Texture2D {.id 7 .width 0 .height 17 .mipmaps 2 .format 4}) rect)
(show-texture (rl/get-shapes-texture)))
;; Camera2D, each direction on its own. See the note above show-v for why
;; this is not a round trip.
- (show-v (rl/get-screen-to-world-2d (rl/Vector2 {:x 140.0 :y 90.0}) cam))
- (show-v (rl/get-world-to-screen-2d (rl/Vector2 {:x 28.0 :y 24.0}) cam))
+ (show-v (rl/get-screen-to-world-2d (rl/Vector2 {.x 140.0 .y 90.0}) cam))
+ (show-v (rl/get-world-to-screen-2d (rl/Vector2 {.x 28.0 .y 24.0}) cam))
;; And the rotated camera, which is what pins Vector2's own two fields.
- (let [spun (rl/Camera2D {:offset (rl/Vector2 {:x 100.0 :y 50.0})
- :target (rl/Vector2 {:x 8.0 :y 4.0})
- :rotation 90.0
- :zoom 2.0})]
+ (let [spun (rl/Camera2D {.offset (rl/Vector2 {.x 100.0 .y 50.0})
+ .target (rl/Vector2 {.x 8.0 .y 4.0})
+ .rotation 90.0
+ .zoom 2.0})]
(show-near "rotated screen-to-world"
- (rl/get-screen-to-world-2d (rl/Vector2 {:x 140.0 :y 90.0}) spun)
+ (rl/get-screen-to-world-2d (rl/Vector2 {.x 140.0 .y 90.0}) spun)
28.0 -16.0)
(show-near "rotated world-to-screen"
- (rl/get-world-to-screen-2d (rl/Vector2 {:x 28.0 :y 24.0}) spun)
+ (rl/get-world-to-screen-2d (rl/Vector2 {.x 28.0 .y 24.0}) spun)
60.0 90.0))
;; ── Collision, which is the best material a headless test gets ──────
@@ -146,25 +146,25 @@
;; so a wrong field order gives a wrong answer rather than the same struct
;; back. Each case below is paired with one that must come out the other
;; way, because a predicate that always said yes would pass a single case.
- (let [r (rl/Rectangle {:x 0.0 :y 0.0 :width 10.0 :height 4.0})]
+ (let [r (rl/Rectangle {.x 0.0 .y 0.0 .width 10.0 .height 4.0})]
;; Inside on both axes, then outside on y only. Swap width and height and
;; both of these flip, which is what makes the pair worth more than either.
- (show-bool "point in rect" (rl/collision-point-rec? (rl/Vector2 {:x 5.0 :y 3.0}) r))
- (show-bool "point below rect" (rl/collision-point-rec? (rl/Vector2 {:x 5.0 :y 5.0}) r))
+ (show-bool "point in rect" (rl/collision-point-rec? (rl/Vector2 {.x 5.0 .y 3.0}) r))
+ (show-bool "point below rect" (rl/collision-point-rec? (rl/Vector2 {.x 5.0 .y 5.0}) r))
;; Overlapping by one unit, then clear of it. Pins x against width.
(show-bool "rects overlap"
- (rl/collision-recs? r (rl/Rectangle {:x 9.0 :y 1.0 :width 10.0 :height 10.0})))
+ (rl/collision-recs? r (rl/Rectangle {.x 9.0 .y 1.0 .width 10.0 .height 10.0})))
(show-bool "rects apart"
- (rl/collision-recs? r (rl/Rectangle {:x 11.0 :y 1.0 :width 10.0 :height 10.0}))))
+ (rl/collision-recs? r (rl/Rectangle {.x 11.0 .y 1.0 .width 10.0 .height 10.0}))))
;; Centres five apart with radii summing to six, then seven apart. The radius
;; is a scalar beside two Vector2s, so this pins it against their fields.
(show-bool "circles touch"
- (rl/collision-circles? (rl/Vector2 {:x 0.0 :y 0.0}) 3.0
- (rl/Vector2 {:x 5.0 :y 0.0}) 3.0))
+ (rl/collision-circles? (rl/Vector2 {.x 0.0 .y 0.0}) 3.0
+ (rl/Vector2 {.x 5.0 .y 0.0}) 3.0))
(show-bool "circles clear"
- (rl/collision-circles? (rl/Vector2 {:x 0.0 :y 0.0}) 3.0
- (rl/Vector2 {:x 7.0 :y 0.0}) 3.0))
+ (rl/collision-circles? (rl/Vector2 {.x 0.0 .y 0.0}) 3.0
+ (rl/Vector2 {.x 7.0 .y 0.0}) 3.0))
;; The one that answers with a number rather than a yes: a horizontal segment
;; at y = 7 crossed by a vertical one at x = 3, so the answer is (3 7).
@@ -187,86 +187,86 @@
;;
;; What this section *does* pin is Rectangle, completely — swapping width and
;; height turns three of the four predicates below the wrong way.
- (match (rl/collision-lines (rl/Vector2 {:x 0.0 :y 7.0}) (rl/Vector2 {:x 10.0 :y 7.0})
- (rl/Vector2 {:x 3.0 :y 0.0}) (rl/Vector2 {:x 3.0 :y 10.0}))
+ (match (rl/collision-lines (rl/Vector2 {.x 0.0 .y 7.0}) (rl/Vector2 {.x 10.0 .y 7.0})
+ (rl/Vector2 {.x 3.0 .y 0.0}) (rl/Vector2 {.x 3.0 .y 10.0}))
(Some p) (show-v p)
None (println "no crossing"))
;; The rest of the collision family, each with the case that must come out
;; the other way. Bound and linking is not the same as working: a wrapper
;; whose arguments are in the wrong order links perfectly and answers
;; nonsense, and until something calls it nothing says so.
- (let [r (rl/Rectangle {:x 0.0 :y 0.0 :width 10.0 :height 4.0})]
+ (let [r (rl/Rectangle {.x 0.0 .y 0.0 .width 10.0 .height 4.0})]
;; Circle against rect: just touching at the right edge, then clear of it.
(show-bool "circle meets rect"
- (rl/collision-circle-rec? (rl/Vector2 {:x 12.0 :y 2.0}) 3.0 r))
+ (rl/collision-circle-rec? (rl/Vector2 {.x 12.0 .y 2.0}) 3.0 r))
(show-bool "circle clears rect"
- (rl/collision-circle-rec? (rl/Vector2 {:x 14.0 :y 2.0}) 3.0 r)))
+ (rl/collision-circle-rec? (rl/Vector2 {.x 14.0 .y 2.0}) 3.0 r)))
;; Circle against a segment, which is the one that pins the radius against
;; the two endpoints rather than against a single centre.
(show-bool "circle meets line"
- (rl/collision-circle-line? (rl/Vector2 {:x 5.0 :y 2.0}) 3.0
- (rl/Vector2 {:x 0.0 :y 0.0})
- (rl/Vector2 {:x 10.0 :y 0.0})))
+ (rl/collision-circle-line? (rl/Vector2 {.x 5.0 .y 2.0}) 3.0
+ (rl/Vector2 {.x 0.0 .y 0.0})
+ (rl/Vector2 {.x 10.0 .y 0.0})))
(show-bool "circle clears line"
- (rl/collision-circle-line? (rl/Vector2 {:x 5.0 :y 4.0}) 3.0
- (rl/Vector2 {:x 0.0 :y 0.0})
- (rl/Vector2 {:x 10.0 :y 0.0})))
+ (rl/collision-circle-line? (rl/Vector2 {.x 5.0 .y 4.0}) 3.0
+ (rl/Vector2 {.x 0.0 .y 0.0})
+ (rl/Vector2 {.x 10.0 .y 0.0})))
(show-bool "point in circle"
- (rl/collision-point-circle? (rl/Vector2 {:x 2.0 :y 0.0})
- (rl/Vector2 {:x 0.0 :y 0.0}) 3.0))
+ (rl/collision-point-circle? (rl/Vector2 {.x 2.0 .y 0.0})
+ (rl/Vector2 {.x 0.0 .y 0.0}) 3.0))
(show-bool "point outside circle"
- (rl/collision-point-circle? (rl/Vector2 {:x 4.0 :y 0.0})
- (rl/Vector2 {:x 0.0 :y 0.0}) 3.0))
+ (rl/collision-point-circle? (rl/Vector2 {.x 4.0 .y 0.0})
+ (rl/Vector2 {.x 0.0 .y 0.0}) 3.0))
;; A right triangle with the square corner at the origin. The inside point is
;; inside for one vertex order and not the other, so this is one of the few
;; here that notices which vertex is which.
(show-bool "point in triangle"
- (rl/collision-point-triangle? (rl/Vector2 {:x 1.0 :y 1.0})
- (rl/Vector2 {:x 0.0 :y 0.0})
- (rl/Vector2 {:x 8.0 :y 0.0})
- (rl/Vector2 {:x 0.0 :y 6.0})))
+ (rl/collision-point-triangle? (rl/Vector2 {.x 1.0 .y 1.0})
+ (rl/Vector2 {.x 0.0 .y 0.0})
+ (rl/Vector2 {.x 8.0 .y 0.0})
+ (rl/Vector2 {.x 0.0 .y 6.0})))
(show-bool "point outside triangle"
- (rl/collision-point-triangle? (rl/Vector2 {:x 7.0 :y 5.0})
- (rl/Vector2 {:x 0.0 :y 0.0})
- (rl/Vector2 {:x 8.0 :y 0.0})
- (rl/Vector2 {:x 0.0 :y 6.0})))
+ (rl/collision-point-triangle? (rl/Vector2 {.x 7.0 .y 5.0})
+ (rl/Vector2 {.x 0.0 .y 0.0})
+ (rl/Vector2 {.x 8.0 .y 0.0})
+ (rl/Vector2 {.x 0.0 .y 6.0})))
;; On the segment, then beside it. The threshold is the last argument, so a
;; wrapper that lost it among the four coordinates answers with whatever was
;; in that register.
(show-bool "point on line"
- (rl/collision-point-line? (rl/Vector2 {:x 5.0 :y 0.0})
- (rl/Vector2 {:x 0.0 :y 0.0})
- (rl/Vector2 {:x 10.0 :y 0.0}) 1))
+ (rl/collision-point-line? (rl/Vector2 {.x 5.0 .y 0.0})
+ (rl/Vector2 {.x 0.0 .y 0.0})
+ (rl/Vector2 {.x 10.0 .y 0.0}) 1))
(show-bool "point off line"
- (rl/collision-point-line? (rl/Vector2 {:x 5.0 :y 4.0})
- (rl/Vector2 {:x 0.0 :y 0.0})
- (rl/Vector2 {:x 10.0 :y 0.0}) 1))
+ (rl/collision-point-line? (rl/Vector2 {.x 5.0 .y 4.0})
+ (rl/Vector2 {.x 0.0 .y 0.0})
+ (rl/Vector2 {.x 10.0 .y 0.0}) 1))
;; The only one that crosses a *slice*, so it is the only one where ptr+len
;; has to arrive as raylib's pointer-and-count. A wrong length reads past the
;; array or stops short, and either way the square stops being a square.
- (let [square [(rl/Vector2 {:x 0.0 :y 0.0}) (rl/Vector2 {:x 8.0 :y 0.0})
- (rl/Vector2 {:x 8.0 :y 8.0}) (rl/Vector2 {:x 0.0 :y 8.0})]]
+ (let [square [(rl/Vector2 {.x 0.0 .y 0.0}) (rl/Vector2 {.x 8.0 .y 0.0})
+ (rl/Vector2 {.x 8.0 .y 8.0}) (rl/Vector2 {.x 0.0 .y 8.0})]]
(show-bool "point in poly"
- (rl/collision-point-poly? (rl/Vector2 {:x 4.0 :y 4.0}) (slice square 0 4)))
+ (rl/collision-point-poly? (rl/Vector2 {.x 4.0 .y 4.0}) (slice square 0 4)))
(show-bool "point outside poly"
- (rl/collision-point-poly? (rl/Vector2 {:x 12.0 :y 4.0}) (slice square 0 4)))
+ (rl/collision-point-poly? (rl/Vector2 {.x 12.0 .y 4.0}) (slice square 0 4)))
;; The same point against the same array, three corners instead of four:
;; inside the square, outside the triangle the first three make. This is
;; the case that proves the *length* crosses — everything above would pass
;; with a hardcoded count, or with the pointer alone.
(show-bool "in square, four corners"
- (rl/collision-point-poly? (rl/Vector2 {:x 2.0 :y 6.0}) (slice square 0 4)))
+ (rl/collision-point-poly? (rl/Vector2 {.x 2.0 .y 6.0}) (slice square 0 4)))
(show-bool "out of triangle, three"
- (rl/collision-point-poly? (rl/Vector2 {:x 2.0 :y 6.0}) (slice square 0 3))))
+ (rl/collision-point-poly? (rl/Vector2 {.x 2.0 .y 6.0}) (slice square 0 3))))
;; Parallel, so they never meet: None rather than a point nobody wrote.
- (match (rl/collision-lines (rl/Vector2 {:x 0.0 :y 0.0}) (rl/Vector2 {:x 1.0 :y 2.0})
- (rl/Vector2 {:x 5.0 :y 0.0}) (rl/Vector2 {:x 6.0 :y 2.0}))
+ (match (rl/collision-lines (rl/Vector2 {.x 0.0 .y 0.0}) (rl/Vector2 {.x 1.0 .y 2.0})
+ (rl/Vector2 {.x 5.0 .y 0.0}) (rl/Vector2 {.x 6.0 .y 2.0}))
(Some p) (show-v p)
None (println "no crossing"))
diff --git a/test/programs/raylib-font.flan b/test/programs/raylib-font.flan
index 7e75d7c..1f81afa 100644
--- a/test/programs/raylib-font.flan
+++ b/test/programs/raylib-font.flan
@@ -68,17 +68,17 @@
(defvar glyphs [3 rl/GlyphInfo])
(defn build-glyphs []
- (set (at glyph-recs 0) (rl/Rectangle {:x 0.0 :y 0.0 :width 5.0 :height 10.0}))
- (set (at glyph-recs 1) (rl/Rectangle {:x 5.0 :y 0.0 :width 7.0 :height 10.0}))
- (set (at glyph-recs 2) (rl/Rectangle {:x 12.0 :y 0.0 :width 9.0 :height 10.0}))
+ (set (at glyph-recs 0) (rl/Rectangle {.x 0.0 .y 0.0 .width 5.0 .height 10.0}))
+ (set (at glyph-recs 1) (rl/Rectangle {.x 5.0 .y 0.0 .width 7.0 .height 10.0}))
+ (set (at glyph-recs 2) (rl/Rectangle {.x 12.0 .y 0.0 .width 9.0 .height 10.0}))
;; 65 66 67 are A B C. Advances 11 and 13 for the first two; C's advance is
;; 0 on purpose, which is what sends raylib down the other branch.
- (set (at glyphs 0) (rl/GlyphInfo {:value 65 :offset-x 1 :offset-y 0
- :advance-x 11 :image (rl/Image {})}))
- (set (at glyphs 1) (rl/GlyphInfo {:value 66 :offset-x 2 :offset-y 0
- :advance-x 13 :image (rl/Image {})}))
- (set (at glyphs 2) (rl/GlyphInfo {:value 67 :offset-x 3 :offset-y 0
- :advance-x 0 :image (rl/Image {})})))
+ (set (at glyphs 0) (rl/GlyphInfo {.value 65 .offset-x 1 .offset-y 0
+ .advance-x 11 .image (rl/Image {})}))
+ (set (at glyphs 1) (rl/GlyphInfo {.value 66 .offset-x 2 .offset-y 0
+ .advance-x 13 .image (rl/Image {})}))
+ (set (at glyphs 2) (rl/GlyphInfo {.value 67 .offset-x 3 .offset-y 0
+ .advance-x 0 .image (rl/Image {})})))
(defn show-bool [name string b bool]
(print name) (print " ")
@@ -116,11 +116,11 @@
;; therefore parses rl/Font as an expression and fails with "unknown name".
;; That is a parser limit and not something this file wanted; it applies to
;; rl/Vector2 just as much as to rl/Font.
- (let [f (rl/Font {:base-size 10 :glyph-count 3 :glyph-padding 2
- :texture (rl/Texture2D {:id 1 :width 32 :height 16
- :mipmaps 1 :format 7})
- :recs (addr (at glyph-recs 0))
- :glyphs (addr (at glyphs 0))})]
+ (let [f (rl/Font {.base-size 10 .glyph-count 3 .glyph-padding 2
+ .texture (rl/Texture2D {.id 1 .width 32 .height 16
+ .mipmaps 1 .format 7})
+ .recs (addr (at glyph-recs 0))
+ .glyphs (addr (at glyphs 0))})]
;; font-valid? reads the texture id and both array pointers, so it is a
;; null check on the three things everything below dereferences.
(show-bool "valid" (rl/font-valid? f))
diff --git a/test/programs/raylib-image.flan b/test/programs/raylib-image.flan
index 873144a..f59d914 100644
--- a/test/programs/raylib-image.flan
+++ b/test/programs/raylib-image.flan
@@ -26,9 +26,9 @@
;; transposed read pass, and that is exactly the trap the collision cases fell
;; into.
-(defconst bg (rl/Color {:r 10 :g 20 :b 30 :a 255}))
-(defconst mark-a (rl/Color {:r 200 :g 0 :b 0 :a 255}))
-(defconst mark-b (rl/Color {:r 0 :g 200 :b 0 :a 255}))
+(defconst bg (rl/Color {.r 10 .g 20 .b 30 .a 255}))
+(defconst mark-a (rl/Color {.r 200 .g 0 .b 0 .a 255}))
+(defconst mark-b (rl/Color {.r 0 .g 200 .b 0 .a 255}))
;; Where the export goes and comes back from. The two optimisation levels
;; write identical bytes, so sharing one path between runs is harmless.
@@ -151,7 +151,7 @@
;; the Rectangle and the result is 1 x 2 with the mark gone.
(let [img (rl/gen-image-color 6 3 bg)]
(rl/image-draw-pixel (addr img) 5 0 mark-a)
- (rl/image-crop (addr img) (rl/Rectangle {:x 4.0 :y 0.0 :width 2.0 :height 1.0}))
+ (rl/image-crop (addr img) (rl/Rectangle {.x 4.0 .y 0.0 .width 2.0 .height 1.0}))
(show-image "cropped" img)
(show-pixel "cropped at 1,0" img 1 0)
(show-pixel "cropped at 0,0" img 0 0)
diff --git a/test/programs/reach-walk.flan b/test/programs/reach-walk.flan
index a82c111..0a35f50 100644
--- a/test/programs/reach-walk.flan
+++ b/test/programs/reach-walk.flan
@@ -29,7 +29,7 @@
;;; Signals with nothing to return, so the clause body is the only way past.
(defn missing [] i32
- (error (Nope {:id 1})))
+ (error (Nope {.id 1})))
(defn pick [] i32
(restart-case (missing)
diff --git a/test/programs/restarts.flan b/test/programs/restarts.flan
index 0b6b6ba..7765f44 100644
--- a/test/programs/restarts.flan
+++ b/test/programs/restarts.flan
@@ -16,7 +16,7 @@
;;; The signalling end. Two frames below the restart-case, so the transfer has
;;; something to cross.
(defn load [n i32] i32
- (signal (AssetMissing {:id n}))
+ (signal (AssetMissing {.id n}))
100)
;;; §5: this defer runs whether the call below returns or transfers, and it
@@ -46,7 +46,7 @@
;;; signalling version needs does not exist here.
(defn strict [n i32] i32
(restart-case
- (do (error (AssetMissing {:id n}))
+ (do (error (AssetMissing {.id n}))
;; unreachable — error is Never, so nothing after it runs
0)
(use-placeholder [] -2)))
diff --git a/test/programs/values.flan b/test/programs/values.flan
index 1a711fc..0118047 100644
--- a/test/programs/values.flan
+++ b/test/programs/values.flan
@@ -5,7 +5,7 @@
(defvar arr [3 i32])
(defn main [] i32
- (let [a (P {:x 1})]
+ (let [a (P {.x 1})]
(let [b a] ; a copy, not an alias
(set (.x a) 99)
(print (.x b)) (println ""))) ; 1
diff --git a/test/programs/vec.flan b/test/programs/vec.flan
index 4a8811e..bc7193c 100644
--- a/test/programs/vec.flan
+++ b/test/programs/vec.flan
@@ -76,8 +76,8 @@
;; A second element type over the same runtime, and a struct element, so
;; that size_of and align_of are doing work rather than both being 4.
(let [ps (vec-new Point)]
- (push ps (Point {:x 1 :y 2}))
- (push ps (Point {:x 3 :y 4}))
+ (push ps (Point {.x 1 .y 2}))
+ (push ps (Point {.x 3 .y 4}))
(println (len ps)) ; 2
(println (.y (at ps 1))) ; 4
(free ps))
diff --git a/test/programs/virtual-controls-headless.flan b/test/programs/virtual-controls-headless.flan
index cb39acd..c4a0762 100644
--- a/test/programs/virtual-controls-headless.flan
+++ b/test/programs/virtual-controls-headless.flan
@@ -48,7 +48,7 @@
(while (<= y sweep-y1)
(let [x sweep-x0]
(while (<= x sweep-x1)
- (let [p (rl/Vector2 {:x (f32 x) :y (f32 y)})]
+ (let [p (rl/Vector2 {.x (f32 x) .y (f32 y)})]
(vc/move-player (vc/nearest-button p) dt))
(set x (+ x sweep-step))))
(set y (+ y sweep-step))))
diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml
index 0b9f47f..04efbf4 100644
--- a/test/test_acceptance.ml
+++ b/test/test_acceptance.ml
@@ -1749,7 +1749,7 @@ ERR@7 unexpected token: not the kind the caller was reading
in
let cell = "(defstruct Cell [alive bool heat f64 id i32 name string])\n" in
let cell' = "(defstruct Cell [name string id i32 alive bool heat f64])\n" in
- let body = "(defn main [] i32 (let [c (Cell {:id 1})] (i32 (.id c))))\n" in
+ let body = "(defn main [] i32 (let [c (Cell {.id 1})] (i32 (.id c))))\n" in
layout_case "DWARF offsets agree with LLVM: a mixed struct" (cell ^ body)
"Cell" [ "alive"; "heat"; "id"; "name" ];
(* The same struct, permuted. If the offsets came from anywhere but the
@@ -1759,7 +1759,7 @@ ERR@7 unexpected token: not the kind the caller was reading
layout_case "DWARF offsets agree with LLVM: nesting and fixed arrays"
("(defstruct P [x i32 y i32])\n\
(defstruct Board [tag u8 cells [4 P] here P edge (Ptr P) seen (Option i64)])\n\
- (defn main [] i32 (let [b (Board {:tag 1})] (i32 (.tag b))))\n")
+ (defn main [] i32 (let [b (Board {.tag 1})] (i32 (.tag b))))\n")
"Board" [ "tag"; "cells"; "here"; "edge"; "seen" ];
(* Permuting the fields must actually move them. Asserting that the two
@@ -1903,7 +1903,7 @@ ERR@7 unexpected token: not the kind the caller was reading
(defn pick [xs [i32] i i32] i32 (at xs i))\n\
(defn fetch [n i32] i32\n\
\ (restart-case\n\
- \ (do (error (Missing {:id n})) 0)\n\
+ \ (do (error (Missing {.id n})) 0)\n\
\ (use-value [v i32 s string] (do (print s) v))\n\
\ (use-placeholder [] -1)))\n\
(defn run [] i32\n\
diff --git a/test/test_dev.ml b/test/test_dev.ml
index 264ba63..05a55d6 100644
--- a/test/test_dev.ml
+++ b/test/test_dev.ml
@@ -650,7 +650,7 @@ let () =
its loop, so a body that errors stops it — and take the exit. *)
(match
ask
- "(:op \"eval\" :code \"(defn step [] i64 (restart-case (do (error (Missing {:id 9})) 0) (use-placeholder [] -1)))\" :file \"/tmp/buf.flan\")"
+ "(:op \"eval\" :code \"(defn step [] i64 (restart-case (do (error (Missing {.id 9})) 0) (use-placeholder [] -1)))\" :file \"/tmp/buf.flan\")"
with
| r when status r <> "ok" ->
fail "installing a body that errors: %s"
@@ -833,7 +833,7 @@ let () =
holding [p]'s value and nothing would say so. *)
let r =
ask
- "(:op \"eval\" :code \"(defn look [n i64 label string] i64 (let [q (Point {:x 9.0 :y 9.0}) ys [1 2 3] mark (< n 0)] (restart-case (do (error (Boom {:why 7})) (let [after (i64 99)] after)) (carry-on [] 5))))\" :file \"/tmp/buf.flan\")"
+ "(:op \"eval\" :code \"(defn look [n i64 label string] i64 (let [q (Point {.x 9.0 .y 9.0}) ys [1 2 3] mark (< n 0)] (restart-case (do (error (Boom {.why 7})) (let [after (i64 99)] after)) (carry-on [] 5))))\" :file \"/tmp/buf.flan\")"
in
if status r <> "ok" then
fail "installing a renamed body while stopped: %s"
@@ -1065,7 +1065,7 @@ let () =
in
let r =
request c
- "(:op \"eval\" :code \"(defn step [] i64 (error (Missing {:id 3})))\" :file \"/tmp/disasm.flan\")"
+ "(:op \"eval\" :code \"(defn step [] i64 (error (Missing {.id 3})))\" :file \"/tmp/disasm.flan\")"
in
if status r <> "ok" then
fail "installing a body that errors: %s"
diff --git a/test/test_flan.ml b/test/test_flan.ml
index 8e1b1dc..a4e501b 100644
--- a/test/test_flan.ml
+++ b/test/test_flan.ml
@@ -113,11 +113,11 @@ let () =
(* ── Sequences ─────────────────────────────────────────────────── *)
reads "list" "(+ 1 2)" "(+ 1 2)";
reads "vector" "[1 2 3]" "[1 2 3]";
- reads "map literal" "{:src src :pos 0}" "{:src src :pos 0}";
+ reads "map literal" "{.src src .pos 0}" "{.src src .pos 0}";
reads "type notation" "[4 f32]" "[4 f32]";
reads "nested type" "[rows [cols u32]]" "[rows [cols u32]]";
reads "commas as space" "[1, 2, 3]" "[1 2 3]";
- reads "nested" "(a (b [c {:d e}]))" "(a (b [c {:d e}]))";
+ reads "nested" "(a (b [c {.d e}]))" "(a (b [c {.d e}]))";
(* ── Trivia ────────────────────────────────────────────────────── *)
reads "line comment" "; nope\n42" "42";
@@ -173,8 +173,8 @@ let () =
| _ -> []
in
let corpus =
- "(invoke-restart 'skip-form) (a 'b [c 'd] {:e 'f}) '(g 'h) \
- `(i ~j ~@k) `(l `(m ~n)) [`o ~p] {:q `r} (f a~b x`y)"
+ "(invoke-restart 'skip-form) (a 'b [c 'd] {.e 'f}) '(g 'h) \
+ `(i ~j ~@k) `(l `(m ~n)) [`o ~p] {.q `r} (f a~b x`y)"
in
check "no sigils leak into names"
(bad_names (Form.make (Form.List (read corpus))
@@ -314,7 +314,7 @@ let () =
(match (parse1 "(.pos c)").e with
| Field ({ e = Var "c"; _ }, "pos") -> ()
| _ -> check "field access" false);
- (match (parse1 "(Cursor {:src s :pos 0})").e with
+ (match (parse1 "(Cursor {.src s .pos 0})").e with
| Struct ("Cursor", [ ("src", _); ("pos", _) ]) -> ()
| _ -> check "struct literal" false);
(match (parse1 "[1 2 3]").e with
@@ -611,13 +611,19 @@ let () =
(* ── Structs, fields and auto-deref ────────────────────────────── *)
let cursor = "(defstruct Cursor [src [u8] pos i32]) " in
accepts "struct literal, omitted field zeroed"
- (cursor ^ "(defn f [s [u8]] Cursor (Cursor {:src s}))");
+ (cursor ^ "(defn f [s [u8]] Cursor (Cursor {.src s}))");
rejects_check "unknown field"
- (cursor ^ "(defn f [s [u8]] Cursor (Cursor {:nope s}))")
+ (cursor ^ "(defn f [s [u8]] Cursor (Cursor {.nope s}))")
~needle:"has no field nope";
rejects_check "field given twice"
- (cursor ^ "(defn f [s [u8]] Cursor (Cursor {:pos 0 :pos 1}))")
+ (cursor ^ "(defn f [s [u8]] Cursor (Cursor {.pos 0 .pos 1}))")
~needle:"given twice";
+ (* The old spelling is refused rather than quietly accepted, and the refusal
+ names the new one. Two accepted spellings is how two spellings become
+ permanent, and the colon is wanted for keys. *)
+ rejects_check "a field label written with a colon"
+ (cursor ^ "(defn f [s [u8]] Cursor (Cursor {:src s}))")
+ ~needle:"a field label is written .src, not :src";
accepts "field through a pointer auto-derefs"
(cursor ^ "(defn f [c (Ptr Cursor)] i32 (.pos c))");
accepts "set through a pointer"
@@ -634,7 +640,7 @@ let () =
"(defconst k 1) (defn f [] (set k 2))" ~needle:"is a constant";
accepts "addr of a local gives a pointer"
(cursor ^ "(defn g [c (Ptr Cursor)] i32 (.pos c)) \
- (defn f [s [u8]] i32 (let [c (Cursor {:src s})] (g (addr c))))");
+ (defn f [s [u8]] i32 (let [c (Cursor {.src s})] (g (addr c))))");
rejects_check "addr of a non-place"
"(defn f [] (addr (+ 1 2)))" ~needle:"addr takes the address of a place";
@@ -818,7 +824,7 @@ let () =
accepts "handler-bind over a struct condition"
"(defstruct C [id i32]) (defvar n i64)\n\
- (defn f [] (handler-bind [(C [c] (set n 1))] (signal (C {:id 2}))))";
+ (defn f [] (handler-bind [(C [c] (set n 1))] (signal (C {.id 2}))))";
(* Matching is by type and there is no hierarchy, so a condition has to be a
struct — an integer would have nothing to match against. *)
rejects_check "signalling a non-struct"
@@ -830,18 +836,18 @@ let () =
usable as a restart-case body's fall-through. *)
accepts "error in value position"
"(defstruct C [id i32])\n\
- (defn f [] i32 (error (C {:id 1})))";
+ (defn f [] i32 (error (C {.id 1})))";
(* And signal is not: it is Unit, whatever it finds. *)
rejects_check "signal in value position"
"(defstruct C [id i32])\n\
- (defn f [] i32 (signal (C {:id 1})))" ~needle:"expected i32";
+ (defn f [] i32 (signal (C {.id 1})))" ~needle:"expected i32";
(* A handler is lifted into a function of its own, so the establishing
function's locals are not there. Capturing them is a closure, which is
milestone 5 — until then it is refused for the reason it is refused for
rather than as an unknown name. *)
rejects_check "a handler capturing a local"
"(defstruct C [id i32])\n\
- (defn f [] (let [n 0] (handler-bind [(C [c] (set n 1))] (signal (C {:id 2})))))"
+ (defn f [] (let [n 0] (handler-bind [(C [c] (set n 1))] (signal (C {.id 2})))))"
~needle:"a handler cannot see n";
(* The frames are popped on the way out of the body, so an early exit would
leave them on the stack pointing into a function that has gone. *)
@@ -860,7 +866,7 @@ let () =
accepts "restart-case with a clause that transfers into it"
"(defstruct C [id i32])\n\
- (defn g [] i32 (signal (C {:id 1})) 0)\n\
+ (defn g [] i32 (signal (C {.id 1})) 0)\n\
(defn f [] i32 (restart-case (g) (skip [] 7)))\n\
(defn h [] i32 (handler-bind [(C [c] (invoke-restart 'skip))] (f)))";
(* §3: the body and every clause yield the whole form, so they have to agree
@@ -926,17 +932,17 @@ let () =
accepts "struct pattern with :keys"
(pt ^ "(defn f [p Point] i32 (let [{:keys [x y]} p] (+ x y)))");
accepts "struct pattern with a name/:field pair"
- (pt ^ "(defn f [p Point] i32 (let [{a :x b :y} p] (+ a b)))");
+ (pt ^ "(defn f [p Point] i32 (let [{a .x b .y} p] (+ a b)))");
accepts "a nested struct pattern"
- (line ^ "(defn f [l Line] i32 (let [{{:keys [x y]} :a} l] (+ x y)))");
+ (line ^ "(defn f [l Line] i32 (let [{{:keys [x y]} .a} l] (+ x y)))");
(* A later binding sees an earlier pattern's names, as in any let. *)
accepts "a binding after a pattern sees its names"
(pt ^ "(defn f [p Point] i32 (let [{:keys [x]} p y (+ x 1)] y))");
(* Shadowing works because the value goes into a temporary first. *)
accepts "a pattern may shadow the name it destructures"
- (line ^ "(defn f [a Line] i32 (let [{a :a} a] (.x a)))");
+ (line ^ "(defn f [a Line] i32 (let [{a .a} a] (.x a)))");
accepts "a pattern over a call"
- (pt ^ "(defn mk [] Point (Point {:x 1 :y 2}))\n\
+ (pt ^ "(defn mk [] Point (Point {.x 1 .y 2}))\n\
(defn f [] i32 (let [{:keys [x y]} (mk)] (+ x y)))");
rejects_check "a field the struct does not have"
@@ -952,11 +958,19 @@ let () =
(pt ^ "(defn f [p Point] i32 (let [{} p] 0))")
~needle:"an empty struct pattern {} binds nothing";
rejects_check "a field name with no pattern before it"
- (pt ^ "(defn f [p Point] i32 (let [{:x} p] 0))")
- ~needle:"has no :field";
+ (pt ^ "(defn f [p Point] i32 (let [{.x} p] 0))")
+ ~needle:"has no .field";
rejects_check "a pattern with no field name after it"
(pt ^ "(defn f [p Point] i32 (let [{a b} p] 0))")
- ~needle:"expected :field after a";
+ ~needle:"expected .field after a";
+ (* The same refusal on the destructuring side: a field is a field wherever it
+ is named, so the rule is not half-applied. :keys is the one that keeps its
+ colon, and the test below it says so. *)
+ rejects_check "a destructured field written with a colon"
+ (pt ^ "(defn f [p Point] i32 (let [{a :x} p] a))")
+ ~needle:"a field label is written .x, not :x";
+ accepts ":keys keeps its colon, naming no field"
+ (pt ^ "(defn f [p Point] i32 (let [{:keys [x y]} p] (+ x y)))");
(* Clojure's other map-destructuring keys. Each is refused by its own name:
"unexpected form" would leave the author guessing which of the four they
@@ -981,7 +995,7 @@ let () =
"(defn f [] i32 (let [xs [1 2] [a b & r] xs] (+ a (+ b (len r)))))";
accepts "a struct pattern nested in an array pattern"
(pt ^ "(defn f [ps [2 Point]] i32 \
- (let [[{:keys [x]} {y :y}] ps] (+ x y)))");
+ (let [[{:keys [x]} {y .y}] ps] (+ x y)))");
rejects_check "an array pattern that names too few elements"
"(defn f [] i32 (let [xs [1 2 3] [a b] xs] (+ a b)))"
diff --git a/tools/__pycache__/colon-to-dot.cpython-313.pyc b/tools/__pycache__/colon-to-dot.cpython-313.pyc
new file mode 100644
index 0000000..5c381bf
Binary files /dev/null and b/tools/__pycache__/colon-to-dot.cpython-313.pyc differ
diff --git a/tools/colon-to-dot.py b/tools/colon-to-dot.py
new file mode 100755
index 0000000..d680f0b
--- /dev/null
+++ b/tools/colon-to-dot.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python3
+"""Rewrite struct field labels from the colon spelling to the dot spelling.
+
+`{:x 1.0 :y 2.0}` becomes `{.x 1.0 .y 2.0}`, and the destructuring pair
+`{inner :field}` becomes `{inner .field}`. `:keys` is left alone: it names no
+field, it is an instruction to the compiler, so the dot keeps exactly one
+meaning -- "this names a field".
+
+Only keywords that sit in a *field-label* position inside a brace form are
+touched. Enum members, map keys and every keyword inside a string literal or a
+comment are left as they are. The lexing rules here mirror lib/reader.ml.
+
+Re-runnable: converting an already-converted file is a no-op, so this can be
+run again over files a parallel branch wrote in the old spelling.
+
+ tools/colon-to-dot.py
;; geom/len.flan — a second file in the same directory shares one top-level
;; scope: it does not import vec.flan, and the order of the two does not matter.
@@ -860,8 +860,8 @@ and no ceremony.
(import g "geom")
(defn main []
- (let [v (g/add (g/V2 {:x 3.0 :y 0.0})
- (g/V2 {:x 0.0 :y 4.0}))]
+ (let [v (g/add (g/V2 {.x 3.0 .y 0.0})
+ (g/V2 {.x 0.0 .y 4.0}))]
(print (g/length v))
(println "")))
@@ -934,8 +934,8 @@ normally leaves the signaller to carry on — the accumulation case:
(defvar seen i64)
(defn load-all []
- (signal (AssetMissing {:id 1})) ; Unit — the caller carries on
- (signal (AssetMissing {:id 2})))
+ (signal (AssetMissing {.id 1})) ; Unit — the caller carries on
+ (signal (AssetMissing {.id 2})))
(defn main []
(load-all) ; no handler: a no-op
@@ -959,7 +959,7 @@ first, before the clause body starts.
(defvar cleanups i64)
(defn load [n i32] i32
- (signal (AssetMissing {:id n}))
+ (signal (AssetMissing {.id n}))
100)
(defn middle [n i32] i32
@@ -1055,7 +1055,7 @@ and an exit status of 134:
(defn load [n i32] i32
(restart-case
- (do (error (Missing {:id n})) ; Never — only a transfer gets past
+ (do (error (Missing {.id n})) ; Never — only a transfer gets past
0)
(use-placeholder [] -1)
(retry [] 7)))