From e992491799eec0d1da1bffb952ee78c77e823988 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 15:00:34 +0700 Subject: [PATCH] The colon belongs to keys; the prose, the page and the sweep agree now web/index.html's Flan blocks convert and its output blocks do not, which is the same split render.ml makes: the printed form keeps the colon until the Emacs inspector that reads it moves too. Same in BUILT.md. plan.org, spec-conditions.md and spec-memory.md carried struct literals in the old spelling and now do not. NEXT.md decision 6 is struck, and batch item 2 with it, naming what to run at merge. BUILT.md says why the colon belongs to keys -- mostly that a map literal wants {:key value}, and two literals sharing one syntax would have left the reader asking the checker which it was looking at. The sweep was not idempotent and is now: {.k :hi} -- a field already converted, holding an enum member -- read as a destructuring pair on a second run and ate the member. A re-run over a lane's files would have corrupted them silently, which is exactly what the tool exists to do safely. --- BUILT.md | 45 +++++++++++++++++- NEXT.md | 34 ++++++------- plan.org | 4 +- spec-conditions.md | 2 +- spec-memory.md | 2 +- .../__pycache__/colon-to-dot.cpython-313.pyc | Bin 0 -> 11286 bytes tools/colon-to-dot.py | 14 ++++++ web/index.html | 22 ++++----- 8 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 tools/__pycache__/colon-to-dot.cpython-313.pyc diff --git a/BUILT.md b/BUILT.md index 5f882f8..60c69ec 100644 --- a/BUILT.md +++ b/BUILT.md @@ -1570,7 +1570,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`. @@ -1688,7 +1688,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 @@ -2042,3 +2042,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/`, and 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 5b0d339..1f82cee 100644 --- a/NEXT.md +++ b/NEXT.md @@ -277,23 +277,16 @@ 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, `vendor/` and 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. +**Two things it left behind.** `render.ml` still *prints* a struct with colons, deliberately: +`emacs/flan-inspect.el:165` parses that output and hard-codes the colon, so the printer has to move with its reader +and that belongs to the Emacs lane. `emacs/MANUAL.md` and `flan-mode.el`'s font-lock also still show the colon. **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 @@ -416,9 +409,10 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them. from this, and a red suite stops being a signal quickly. The handoff says to start by printing both sides of the comparison in `Dev.locals`. -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 @@ -854,7 +848,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/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/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/tools/__pycache__/colon-to-dot.cpython-313.pyc b/tools/__pycache__/colon-to-dot.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c381bfcaaf5e931178733dcb9ddb218c26e27ad GIT binary patch literal 11286 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 1 and t[0] == ':' else None + def converted(self): + """True if this atom is already a `.field` label.""" + t = self.text + return len(t) > 1 and t[0] == '.' and not t[1].isdigit() + class Seq: def __init__(self, open_char, start): @@ -52,6 +57,9 @@ class Seq: def label(self): return None + def converted(self): + return False + def lex_forms(src, i, end, stop=None): """Read forms from src[i:end] until `stop` (a closing char) or exhaustion. @@ -129,6 +137,12 @@ def collect(node, out): pass # a directive, not a field elif a_label is not None: out.append(a.tok) # {:field value} + elif a.converted(): + pass + # Already `{.field value}`. Without this the pair would fall + # to the rule below and an enum member in value position -- + # `{.k :hi}` -- would be read as a destructuring label and + # converted on a second run. Re-running must be a no-op. elif b_label is not None and b_label != 'keys': out.append(b.tok) # {pattern :field} k += 2 diff --git a/web/index.html b/web/index.html index dbb2f25..4068d17 100644 --- a/web/index.html +++ b/web/index.html @@ -381,13 +381,13 @@ heap is involved.

;; `set` takes a fixed list of forms, not an extensible setf. (defn main [] - (let [e (Enemy {:hp 10 :name "slime"}) + (let [e (Enemy {.hp 10 .name "slime"}) p (addr e)] (set spawned (+ spawned 1)) ; a local or a defvar (set (.hp e) 7) ; a struct field (set (.hp p) 8) ; through a (Ptr Enemy) — derefs one level (set (at room 2) 5) ; a fixed array or slice element - (set (deref p) (Enemy {:hp 3 :name "wisp"})) ; a whole-object store + (set (deref p) (Enemy {.hp 3 .name "wisp"})) ; a whole-object store (print (.hp e)) (println "") (println (.name e)) @@ -506,7 +506,7 @@ its fields, and omitted fields are zeroed.

(set (.pos c) (+ (.pos c) 1))) ; field access derefs one level (defn main [] - (let [c (Cursor {:src (bytes "hi")})] ; pos omitted, so pos is 0 + (let [c (Cursor {.src (bytes "hi")})] ; pos omitted, so pos is 0 (print (peek (addr c))) (println "") (advance (addr c)) (print (peek (addr c))) (println ""))) @@ -753,7 +753,7 @@ user-supplied printer to choose between.

(defn main [] (println 42) ; an i32, uncast (println 1.5) - (println (Enemy {:hp 3 :name "wisp" :key :left})) + (println (Enemy {.hp 3 .name "wisp" .key :left})) (println (look-up :space)) (println (look-up :left)) (print "no newline: ") (println true)) @@ -848,7 +848,7 @@ and no ceremony.

(defstruct V2 [x f32 y f32]) (defn add [a V2 b V2] V2 - (V2 {:x (+ (.x a) (.x b)) :y (+ (.y a) (.y b))})) + (V2 {.x (+ (.x a) (.x b)) .y (+ (.y a) (.y b))}))
;; 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)))