flan/test/programs/edn-read.flan
Joseph Ferano 51df451e7b The map dedup is asserted by the case that can only pass if it works
#{{:a 1} {:a 1} {:a 2}} answers 2 whether tables=? compares anything or
compares nothing, so it was proving the count and not the compare. The
pair beside it isolates both halves: one map twice must collapse to 1,
and two maps of one entry each with different keys must stay 2, which is
what a size-only compare would get wrong.

And read's comment stops implying a property it does not have: empty
input answers (Some Value.Nil), indistinguishable from the document that
is nil. Empty is not malformed and the reader is not the thing that gets
to decide it is.
2026-09-18 23:03:56 +07:00

133 lines
5.7 KiB
Plaintext

;;;; (edn/read bytes) over the file it was built for, plus the two 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 hand-written struct reader in programs/edn.flan is the other route and
;;;; needs a defstruct per file; this one needs nothing and reads a file whose
;;;; keys it has never heard of.
;;;;
;;;; The two properties:
;;;;
;;;; * a set holds each value once, by *structure*. #{[0 0] [0 0]} is one
;;;; element, and a dedup written with `=` would make it two — two Vec
;;;; headers over two blocks are never the same header.
;;;;
;;;; * a Value owns its strings. The last case reads a document out of a
;;;; buffer and then overwrites every byte of that buffer in place. A
;;;; reader holding views prints the overwriting bytes; one holding copies
;;;; prints what was in the file. The buffer is written rather than freed
;;;; because a freed buffer is a read of released memory, which can pass by
;;;; luck; overwriting it cannot.
(import edn "vendor:edn")
(defvar frame Allocator)
;; 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"))
;; [a b] as a Value, so a membership test can be written against a pair this
;; program made rather than one it found. Building a Value from outside the
;; package is the same two forms as building one inside it.
(defn pair [a i64 b i64] edn/Value
(let [items (vec-new edn/Value)]
(push items (edn/Value.Int {.n a}))
(push items (edn/Value.Int {.n b}))
(edn/Value.List {.items items})))
(defn field [v edn/Value k string] edn/Value
(match v
(Table entries) (match (get entries k) (Some x) x None edn/Value.Nil)
_ edn/Value.Nil))
(defn show-tileset [] ()
(match (edn/read tileset)
(Some doc)
(do
(match (field doc "texture-path")
(Text s) (println s)
_ (println "no texture path"))
(match (field doc "selected-cells")
(Set cells)
(do
(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 both of these.
(println (edn/member? cells (pair (i64 4) (i64 3))))
(println (edn/member? cells (pair (i64 0) (i64 0))))
(println (edn/member? cells (pair (i64 3) (i64 4))))
(println (edn/member? cells (pair (i64 9) (i64 9)))))
_ (println "no cells")))
None (println "malformed")))
;; 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] ()
(match (edn/read (bytes src))
(Some v) (match v (Set items) (println (len items)) _ (println "not a set"))
None (println "malformed")))
;; A document in a buffer this program owns and can write to. (bytes "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 as-slice.
(defn survives-its-buffer [] ()
(let [buf (vec-new u8)]
(append! (addr buf) (bytes "{:name \"level-1\" :xs [1 2]}"))
(let [src (as-slice buf)
v (edn/read src)]
(dotimes [i (len src)]
(set (at src i) \x))
(match v
(Some doc)
(match (field doc "name")
(Text s) (println s)
_ (println "no name"))
None (println "malformed")))))
(defn main [] i32
(set frame (arena-new 262144))
(with-allocator frame
(do
(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.
(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 tables=? 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}}")
(set-size "#{1 1.0}") ; an int and a float are two values
(set-size "#{true false true}")
(println "")
(survives-its-buffer)
;; And the refusal, which has to be distinguishable from the document
;; that is literally nil.
(match (edn/read (bytes "#{1 2")) (Some _v) (println "read") None (println "malformed"))
(match (edn/read (bytes "nil")) (Some _v) (println "read") None (println "malformed"))))
(free-all frame)
(arena-destroy frame)
0)