flan/test/programs/json-provide.flan
Joseph Ferano dc34c62ce0 defjson, which shares the design and not a line of the code
vendor:json depends on vendor:edn for nothing, and borrowing a shape walk
across that line would be a dependency for the sake of a resemblance. What
carries over is the shape of the answer — one walk giving a type, the
declarations that type needs and the expression that reads one; a refusal
carried in a field rather than raised; a typed one-line constructor per
collection; and a compile-error wrapped in a defn nothing calls.

What is genuinely different is four things. Strings go through string-of and
never through .text: .text is the raw interior with escapes undecoded, so a
field read off it would hold a backslash and an n where the file meant a
newline — which is the first two lines of the acceptance output and the reason
they are two. Commas and colons are tokens rather than whitespace. An object's
keys are strings, so a key has to be refused when it is not a name a program
could write, and refused again when it carries an escape: the generated reader
compares against the bytes as written, which costs no allocation per key and is
only the same question when the name is written plainly. And there are no sets,
so there is no map-key path and no fixed array — every collection is a (Vec T)
and defjson is the smaller of the two by half.

JSON has no integer type; the tokenizer draws the line at whether a number has
a fraction or an exponent, which is the only line there is, so 1 derives i64
and 1.0 derives f64. That is the file's own distinction and the honest one to
take.

@x86 matches on it and @sanitize is clean.
2026-09-19 06:41:57 +07:00

54 lines
2.2 KiB
Plaintext

;;;; defjson over a config file, and the escape that says it is not defedn.
;;;;
;;;; The same idea as edn-provide.flan and a different package: json depends on
;;;; edn for nothing and this program imports only json.
;;;;
;;;; "name" is the line that matters. Its value in the file is written
;;;; "tileset\nrunner", and JSON's `.text` is the RAW interior — escapes
;;;; undecoded — so a reader built on `.text` prints one line with a backslash
;;;; and an n in it. This prints two lines, which is `string-of` having been
;;;; used where a field is filled.
(import json "vendor:json")
(json/defjson Config "assets/json/config.json")
(defconst config (embed "assets/json/config.json"))
(defn main [] i32
(let [a (heap-allocator)
cfg (Config-of-bytes config a)]
;; Two lines, not one with a backslash in it.
(println (.name cfg))
(println (.port cfg))
(println (.scale cfg))
(println (if (.debug cfg) "yes" "no"))
(println (len (.layers cfg)))
;; 3 + 1 + 4 + 1 + 5. A length alone would pass on a Vec that was allocated
;; and never filled, so the sum is the claim.
(let [total (i64 0)]
(dotimes [i (len (.layers cfg))]
(set total (+ total (at (.layers cfg) i))))
(println total))
;; The nested structs, by the names the paths give them: Config-window and
;; Config-window-origin. Ordinary field loads, two deep.
(println (.w (.window cfg)))
(println (.h (.window cfg)))
(println (.x (.origin (.window cfg))))
(println (.y (.origin (.window cfg))))
(println "")
;; Drift, the same contract defedn's reader carries: the struct says "port"
;; and these bytes do not, and they carry a "host" it has never heard of.
(handler-bind
[(json/SchemaDrift [d]
(do (print (if (.extra? d) "extra " "missing "))
(print (.field d))
(print " in ")
(println (.struct d))))]
(let [c2 (Config-of-bytes
(bytes "{\"name\":\"x\",\"host\":\"h\",\"scale\":2.0,\"debug\":false,\"layers\":[1],\"window\":{\"w\":1,\"h\":1,\"origin\":{\"x\":0,\"y\":0}}}")
a)]
(println (.name c2))
(println (.port c2)))))
0)