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.
151 lines
6.8 KiB
Plaintext
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)
|