1 import Base 2 import ./json.bend as J 3 import ./num.bend as N 4 import ./text.bend as T 5 import ./rep.bend as R 6 import ./fold.bend as F 7 8 # Encoder (spec sections 2, 3, 6 to 12), without key folding. 9 10 type Delim is Data: 11 Comma{} 12 Tab{} 13 Pipe{} 14 15 # Encoder options: spaces per indent level, the document delimiter, and key 16 # folding (section 13.4): 0 is off, n of 2 or more folds chains of single-key 17 # objects into dotted keys of at most n segments, 4294967295 folds whole chains. 18 type EOpts is Data: 19 EOpts{indent: U32, delim: Delim, fold: U32} 20 21 def defaults() -> EOpts: 22 EOpts{2, Comma{}, 0} 23 24 # The delimiter as a code point. 25 def code(+d: Delim) -> U32: 26 match d: 27 case Comma{}: 28 44 29 case Tab{}: 30 9 31 case Pipe{}: 32 124 33 34 # What a header writes inside the brackets for this delimiter. 35 def sym(+d: Delim) -> String: 36 match d: 37 case Comma{}: 38 "" 39 case Tab{}: 40 "\t" 41 case Pipe{}: 42 "|" 43 44 def delim.text(+d: Delim) -> String: 45 SCon{Chr{code(d)}, SNil{}} 46 47 # ---- primitives ------------------------------------------------------------------ 48 49 def bool.text(+b: Bool) -> String: 50 T.sel.s(b, "true", "false") 51 52 def num.text(+m: Maybe<&2, String>) -> String: 53 match m: 54 case Some{t}: 55 t 56 case None{}: 57 "null" 58 59 def is_prim(+j: J.Json) -> Bool: 60 match j: 61 case J.JNull{}: 62 True{} 63 case J.JBool{b}: 64 True{} 65 case J.JNum{t}: 66 True{} 67 case J.JStr{s}: 68 True{} 69 case _: 70 False{} 71 72 # The text of a primitive, quoted as `d` (a code point) requires. 73 def prim.text(+j: J.Json, +d: U32) -> String: 74 match j: 75 case J.JNull{}: 76 "null" 77 case J.JBool{b}: 78 bool.text(b) 79 case J.JNum{t}: 80 num.text(N.canon(t)) 81 case J.JStr{s}: 82 T.enc.str(s, d) 83 case _: 84 "" 85 86 # ---- array shape ----------------------------------------------------------------- 87 88 def count(+items: J.Json) -> U32: 89 match items: 90 case J.JCons{h, t}: 91 (1 + count(t) : U32) 92 case _: 93 0 94 95 def all_prim(+items: J.Json) -> Bool: 96 match items: 97 case J.JCons{h, t}: 98 is_prim(h) && all_prim(t) 99 case _: 100 True{} 101 102 # Primitives joined by the delimiter. 103 def join.prims(+items: J.Json, +d: Delim) -> String: 104 match items: 105 case J.JCons{h, J.JNil{}}: 106 prim.text(h, code(d)) 107 case J.JCons{h, t}: 108 prim.text(h, code(d)) ++ delim.text(d) ++ join.prims(t, d) 109 case _: 110 "" 111 112 def fields.len(+fs: J.Json) -> U32: 113 match fs: 114 case J.JField{k, v, t}: 115 (1 + fields.len(t) : U32) 116 case _: 117 0 118 119 def fields.prim(+fs: J.Json) -> Bool: 120 match fs: 121 case J.JField{k, v, t}: 122 is_prim(v) && fields.prim(t) 123 case _: 124 True{} 125 126 def field.pick(+hit: Bool, +v: J.Json, +rest: J.Json) -> J.Json: 127 match hit: 128 case True{}: 129 v 130 case False{}: 131 rest 132 133 def field.get(+k: String, +fs: J.Json) -> J.Json: 134 match fs: 135 case J.JField{k2, v, t}: 136 field.pick(String.eq(k, k2), v, field.get(k, t)) 137 case _: 138 J.JNull{} 139 140 def has_key(+k: String, +fs: J.Json) -> Bool: 141 match fs: 142 case J.JField{k2, v, t}: 143 String.eq(k, k2) || has_key(k, t) 144 case _: 145 False{} 146 147 # The keys of an object. 148 def keys.of(+fs: J.Json) -> List<&2, String>: 149 match fs: 150 case J.JField{k, v, t}: 151 Con{k, keys.of(t)} 152 case _: 153 Nil{} 154 155 def has_all(+ks: List<&2, String>, +fs: J.Json) -> Bool: 156 match ks: 157 case Nil{}: 158 True{} 159 case Con{k, t}: 160 has_key(k, fs) && has_all(t, fs) 161 162 def list.len(+ks: List<&2, String>) -> U32: 163 match ks: 164 case Nil{}: 165 0 166 case Con{k, t}: 167 (1 + list.len(t) : U32) 168 169 # One element fits the header's keys: an object of primitives with exactly 170 # those keys. 171 def row.fits(+ks: List<&2, String>, +e: J.Json) -> Bool: 172 match e: 173 case J.JObj{fs}: 174 fields.prim(fs) && U32.is_eq(fields.len(fs), list.len(ks)) && has_all(ks, fs) 175 case _: 176 False{} 177 178 def tab.ok(+ks: List<&2, String>, +items: J.Json) -> Bool: 179 match items: 180 case J.JCons{h, t}: 181 row.fits(ks, h) && tab.ok(ks, t) 182 case _: 183 True{} 184 185 # Keys of the first element when it is a non-empty object, else none. 186 def first.keys(+items: J.Json) -> List<&2, String>: 187 match items: 188 case J.JCons{J.JObj{fs}, t}: 189 keys.of(fs) 190 case _: 191 Nil{} 192 193 def tabular.keys(+ks: List<&2, String>, +items: J.Json) -> Bool: 194 match ks: 195 case Nil{}: 196 False{} 197 case Con{k, t}: 198 tab.ok(Con{k, t}, items) 199 200 def tabular(+items: J.Json) -> Bool: 201 tabular.keys(first.keys(items), items) 202 203 def fields.text(+ks: List<&2, String>, +d: Delim) -> String: 204 match ks: 205 case Nil{}: 206 "" 207 case Con{k, Nil{}}: 208 T.enc.key(k) 209 case Con{k, t}: 210 T.enc.key(k) ++ delim.text(d) ++ fields.text(t, d) 211 212 def row.cells(+ks: List<&2, String>, +fs: J.Json, +d: Delim) -> String: 213 match ks: 214 case Nil{}: 215 "" 216 case Con{k, Nil{}}: 217 prim.text(field.get(k, fs), code(d)) 218 case Con{k, t}: 219 prim.text(field.get(k, fs), code(d)) ++ delim.text(d) ++ row.cells(t, fs, d) 220 221 def row.of(+ks: List<&2, String>, +e: J.Json, +d: Delim) -> String: 222 match e: 223 case J.JObj{fs}: 224 row.cells(ks, fs, d) 225 case _: 226 "" 227 228 def rows(+ks: List<&2, String>, +items: J.Json, +d: Delim) -> List<&2, R.Ln>: 229 match items: 230 case J.JCons{h, t}: 231 Con{R.Ln{1, row.of(ks, h, d)}, rows(ks, t, d)} 232 case _: 233 Nil{} 234 235 def head(+items: J.Json, +d: Delim) -> String: 236 "[" ++ U32.show(count(items)) ++ sym(d) ++ "]" 237 238 def tab.rep(+items: J.Json, +d: Delim) -> R.Rep: 239 R.RArr{head(items, d) ++ "{" ++ fields.text(first.keys(items), d) ++ "}:", rows(first.keys(items), items, d)} 240 241 def arr.pick(+empty: Bool, +prims: Bool, +tab: Bool, +items: J.Json, +d: Delim, +ls: R.Rep) -> R.Rep: 242 match empty prims tab: 243 case True{} _ _: 244 R.REmpty{} 245 case False{} True{} _: 246 R.RArr{head(items, d) ++ ": " ++ join.prims(items, d), Nil{}} 247 case False{} False{} True{}: 248 tab.rep(items, d) 249 case False{} False{} False{}: 250 R.RArr{head(items, d) ++ ":", R.shift(R.lines.of(ls))} 251 252 # An array's rep. `elem` is true for an element of another array, where the 253 # tabular form is not available (section 9.4); `ls` is the encoding of the 254 # elements as list items. 255 def arr.rep(+elem: Bool, +items: J.Json, +d: Delim, +ls: R.Rep) -> R.Rep: 256 arr.pick(U32.is_zero(count(items)), all_prim(items), Bool.not(elem) && tabular(items), items, d, ls) 257 258 # ---- the encoder ----------------------------------------------------------------- 259 260 # One def for values, object fields and list items. `elem` says the value is an 261 # array element (no tabular form). `j` shrinks on every call. 262 def enc(j: J.Json, +elem: Bool, +d: Delim) -> R.Rep: 263 match j: 264 case J.JArr{+items}: 265 arr.rep(elem, items, d, enc(items, True{}, d)) 266 case J.JObj{fields}: 267 enc(fields, False{}, d) 268 case J.JNil{}: 269 R.RLines{Nil{}} 270 case J.JCons{h, t}: 271 R.RLines{R.cat(R.item(enc(h, True{}, d)), R.lines.of(enc(t, True{}, d)))} 272 case J.JField{k, v, t}: 273 R.RLines{R.cat(R.field(T.enc.key(k), enc(v, False{}, d)), R.lines.of(enc(t, False{}, d)))} 274 case _: 275 R.RPrim{prim.text(j, code(d))} 276 277 # Encode a value as TOON text. 278 def encode(j: J.Json, o: EOpts) -> String: 279 match o: 280 case EOpts{indent, d, fold}: 281 R.render(indent, R.root(enc(F.fold(j, fold), False{}, d)))