src/encode.bend
bend-toon-codec@0.1.0.0 · 281 lines · 36 definitions
1import Base
2import bend-toon-codec@0.1.0.0/src/json.bend as J
3import bend-toon-codec@0.1.0.0/src/num.bend as N
4import bend-toon-codec@0.1.0.0/src/text.bend as T
5import bend-toon-codec@0.1.0.0/src/rep.bend as R
6import bend-toon-codec@0.1.0.0/src/fold.bend as F
7
8
9
10type Delim is Data:
11 Comma{}
12 Tab{}
13 Pipe{}
14
15
16
17
18type EOpts is Data:
19 EOpts{indent: U32, delim: Delim, fold: U32}
20
21def defaults() -> EOpts:
22 EOpts{2, Comma{}, 0}
23
24
25def code(+d: Delim) -> U32:
26 match d:
27 case Comma{}:
28 44
29 case Tab{}:
30 9
31 case Pipe{}:
32 124
33
34
35def sym(+d: Delim) -> String:
36 match d:
37 case Comma{}:
38 ""
39 case Tab{}:
40 "\t"
41 case Pipe{}:
42 "|"
43
44def delim.text(+d: Delim) -> String:
45 SCon{Chr{code(d)}, SNil{}}
46
47
48
49def bool.text(+b: Bool) -> String:
50 T.sel.s(b, "true", "false")
51
52def num.text(+m: Maybe<&2, String>) -> String:
53 match m:
54 case Some{t}:
55 t
56 case None{}:
57 "null"
58
59def 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
73def 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
87
88def count(+items: J.Json) -> U32:
89 match items:
90 case J.JCons{h, t}:
91 (1 + count(t) : U32)
92 case _:
93 0
94
95def 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
103def 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
112def 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
119def 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
126def 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
133def 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
140def 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
148def 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
155def 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
162def 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
170
171def 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
178def 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
186def 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
193def 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
200def tabular(+items: J.Json) -> Bool:
201 tabular.keys(first.keys(items), items)
202
203def 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
212def 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
221def 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
228def 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
235def head(+items: J.Json, +d: Delim) -> String:
236 "[" ++ U32.show(count(items)) ++ sym(d) ++ "]"
237
238def 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
241def 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
253
254
255def 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
259
260
261
262def 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
278def 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)))