;;;; (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)