flan/test/programs/edn-read.flan
Joseph Ferano 9ce51ba94e One slice over everything with elements, and the warning at the push
as-slice was a warning, not an operation. The input type already decides
which of the two things happens — a Vec can only be borrowed, an array or a
string can only be viewed, and no call site picks between them — so the second
name expressed no choice a reader could make. And it warned at the moment the
view is taken, which is the one moment nothing is wrong; the danger arrives
later, at the push. slice now takes a Vec at all three arities and as-slice
is gone.

(slice v lo) was free, and is the arity the Vec never had: the runtime already
reads a hi of -1 as "to the end", so the tail form passes the caller's lo and
the same -1 — no slot, no length read, no second evaluation. The merge is
entirely in the checker; the Vec path builds the flan_vec_as_slice call it
always built and neither backend has a line about any of it.

A Vec a call returned is refused at every arity, and not for the array's
reason. (slice (mk)) over an array dangles. (slice (make-vec)) does not — the
storage outlives the expression — but the header is a temporary, so nothing
can ever free the block. The refusal says that and names the let.

The name's own refusal sits in ordinary_call after every table, so a program
that defines an as-slice still reaches its own. It reads for somebody who has
never heard of the old name and writes the call back out, spelling each
argument that is a name or a number.

The warning moved to where it bites: BUILT.md gains a section beside the Vec
table and the push row points at it, spec-memory.md's Borrowing says the same.
Investigated and deliberately not built — a diagnostic for a live view at the
push. (reserve v 100) then a slice, a push and a read is correct code under
the contract the spec chose, so any flag on it is a false positive by the
language's own semantics rather than by an approximation. FIX.org has the
finding and the syntactic sketch that does not work.
2026-09-21 09:51:35 +07:00

151 lines
6.8 KiB
Plaintext

;;;; (edn/read bytes) over the file it was built for, plus the properties that
;;;; are only claims until something runs them.
;;;;
;;;; assets/edn/tileset.edn is the real thing, copied out of the editor that
;;;; writes it: a map of :texture-path to a string and :selected-cells to a set
;;;; of 54 integer pairs. Nothing here declares a type for it — the point.
;;;;
;;;; It is in a subdirectory of assets/ rather than in it, because programs/
;;;; embed.flan holds (embed-dir "assets") in a [3 EmbedFile] and a fourth file
;;;; beside the other three is a type error in a program that has nothing to do
;;;; with this one. embed-dir does not descend, which is what makes a
;;;; subdirectory the answer rather than a second assets directory.
;;;;
;;;; The document is plain dyn now — maps, vecs, keywords, texts — where it
;;;; used to be the edn/Value tagged union. What the union's hand-written
;;;; equality bought, the runtime's structural equality answers:
;;;;
;;;; * a set holds each value once, by *structure*. #{[0 0] [0 0]} is one
;;;; element. A set reads as a dyn map from element to true, and the dedup
;;;; is put's own key-replace, so a set with a duplicate never exists.
;;;;
;;;; * the document owns its strings. The buffer a document was read from is
;;;; overwritten byte by byte afterwards, and the string read out of the
;;;; document still prints what was in the file — the box copied.
;;;;
;;;; * (edn/read-file path) frees its buffer inside the call, safe because
;;;; of the property above, and passes a FileError through untouched with
;;;; both restarts still armed.
;;;;
;;;; What dyn cannot say and the old (Option Value) could: `read` answers nil
;;;; both for malformed input and for the document `nil`. The distinction did
;;;; not vanish — it moved to the cursor, where the error position has always
;;;; lived, and `malformed?` below is the three lines it costs.
(import edn "vendor:edn")
;; The document, read at compile time. An embed is bytes in the binary, so
;; there is no file open here and no path to get wrong at run time.
(defconst tileset (embed "assets/edn/tileset.edn"))
(defn show-tileset [] ()
(let [doc (edn/read tileset)
cells (get doc :selected-cells)]
(println (get doc :texture-path))
(println (len cells))
;; Two cells that are in the file and one that is not. A reader that
;; flattened the pairs into 108 integers would still have the right count
;; of *something*, and would miss all of these; [3 4] answering yes where
;; [9 9] answers no is what says the pair compare is positional.
(println (has-key? cells [4 3]))
(println (has-key? cells [0 0]))
(println (has-key? cells [3 4]))
(println (has-key? cells [9 9]))))
;; The size of a set after the dedup, which is the whole of what the dedup can
;; be asked for.
(defn set-size [src string] ()
(println (len (edn/read (bytes-view src)))))
;; Malformed input, told apart from the document `nil` by the cursor — the
;; return value alone cannot say it, and this is the spelling that can.
(defn malformed? [src string] bool
(let [c (edn/cursor (bytes-view src))
t (edn/next (addr c))
v (edn/read-value (addr c) t)] ; the value is not the question here
(not (edn/ok? (addr c)))))
;; A document in a buffer this program owns and can write to. (bytes-view "literal")
;; is not that — a literal is constant data behind a writable-looking slice —
;; so the source is built with append and the write goes through slice.
(defn survives-its-buffer [] ()
(let [buf (vec-new u8)]
(append (addr buf) (bytes-view "{:name \"level-1\" :xs [1 2]}"))
(let [src (slice buf)
v (edn/read src)]
(dotimes [i (len src)]
(set (at src i) \x))
(println (get v :name)))))
;; The path-taking entry point over the same file the embed above holds. The
;; texture path printed here has to be the one show-tileset printed, which is
;; what says read-file read the file and not merely something — and that
;; freeing the buffer inside the call took none of the document with it. The
;; defonce is a dyn global, rooted once at startup, so the document lives past
;; the frame that read it.
(defonce game-data dyn)
(defn by-path [] ()
(set game-data (edn/read-file "programs/assets/edn/tileset.edn"))
(println (get game-data :texture-path)))
;; A missing file, answered from outside read-file. Nothing in the package
;; handles FileError, so the condition walks past it to here with both restarts
;; armed, and `use-value` names a path read-file then slurps instead — the read
;; resumes as if that file had been asked for all along, which is exactly what
;; slurp.flan asserts for slurp alone.
;;
;; The map check is the assertion: the answer is a real document rather than
;; the nil a swallowed error would have to become, so the restart was taken.
(defonce saw-file-error i64)
(defn by-missing-path [] ()
(handler-bind
[(FileError [c]
(set saw-file-error (+ saw-file-error 1))
(invoke-restart 'use-value "programs/assets/edn/tileset.edn"))]
(println (has-key? (edn/read-file "programs/assets/edn/not-here.edn")
:texture-path)))
(println saw-file-error))
(defn main [] i32
(show-tileset)
(println "")
;; Dedup, on each kind of element a set can hold. The nested pair is the
;; one a structural compare is needed for; the nested *set* is the one
;; that also needs the compare to ignore order, or #{1 2} and #{2 1}
;; would be two elements — as maps-to-true they are one map, compared by
;; lookup and not by position.
(set-size "#{}")
(set-size "#{1 1 2}")
(set-size "#{[0 0] [0 0] [0 1]}")
(set-size "#{\"a\" \"a\" :a :a}")
(set-size "#{#{1 2} #{2 1}}")
;; Three map cases and not one, because #{{:a 1} {:a 1} {:a 2}} answers 2
;; whether the map compare works or does nothing at all — two merge and one
;; does not, or none merge and there were only ever three. The pair below
;; isolates it: the first must be 1, and the second must be 2 on the
;; *keys*, which a size compare alone would get wrong.
(set-size "#{{:a 1} {:a 1}}")
(set-size "#{{:a 1} {:b 1}}")
(set-size "#{{:a 1} {:a 1} {:a 2}}")
;; One, and the old Value answered two. Dyn equality is the language's own,
;; and it says (= 1 1.0) the way the operators promote — so under a
;; maps-to-true set, 1 and 1.0 are one key. A narrowing against EDN's
;; letter, taken with open eyes: the alternative was a second equality kept
;; beside the runtime's, which is the duplication this rewrite retired.
(set-size "#{1 1.0}")
(set-size "#{true false true}")
(println "")
(survives-its-buffer)
;; And the refusal, told apart from the document that is literally nil.
(println (malformed? "#{1 2"))
(println (malformed? "nil"))
(println "")
(by-path)
(by-missing-path)
0)