src/step.bend
bend-toon-codec@0.1.0.0 · 267 lines · 35 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/split.bend as S
4import bend-toon-codec@0.1.0.0/src/prim.bend as P
5import bend-toon-codec@0.1.0.0/src/hdr.bend as H
6import bend-toon-codec@0.1.0.0/src/entry.bend as E
7import bend-toon-codec@0.1.0.0/src/dline.bend as D
8import bend-toon-codec@0.1.0.0/src/frame.bend as F
9import bend-toon-codec@0.1.0.0/src/head.bend as Hd
10
11
12
13type Start is Data:
14 SVal{v: J.Json}
15 SOpen{fr: F.Fr}
16
17
18type Item is Data:
19 IBad{}
20 IEmpty{}
21 IText{t: String}
22
23def count(+xs: J.Json) -> U32:
24 match xs:
25 case J.JCons{h, t}:
26 (1 + count(t) : U32)
27 case _:
28 0
29
30def list.len(+ks: List<&2, String>) -> U32:
31 match ks:
32 case Nil{}:
33 0
34 case Con{k, t}:
35 (1 + list.len(t) : U32)
36
37
38
39def inline.ok(+ok: Bool, +vs: J.Json) -> Result<&2, &2, String, Start>:
40 match ok:
41 case True{}:
42 Done{SVal{J.JArr{vs}}}
43 case False{}:
44 Fail{"array length does not match its header"}
45
46def inline.done(+r: Result<&2, &2, String, J.Json>, +n: U32, +strict: Bool) -> Result<&2, &2, String, Start>:
47 match r:
48 case Fail{e}:
49 Fail{e}
50 case Done{vs}:
51 inline.ok(Bool.not(strict) || U32.is_eq(count(vs), n), vs)
52
53def inline.go(+fields: List<&2, String>, +n: U32, +d: U32, +rest: String, +strict: Bool) -> Result<&2, &2, String, Start>:
54 match fields:
55 case Nil{}:
56 inline.done(P.values(S.split(rest, d)), n, strict)
57 case Con{f, t}:
58 Fail{"values after a field list"}
59
60def block(+zero: Bool, +fields: List<&2, String>, +n: U32, +d: U32, +att: F.Att, +depth: U32) -> Result<&2, &2, String, Start>:
61 match zero fields:
62 case True{} _:
63 Done{SVal{J.JArr{J.JNil{}}}}
64 case False{} Nil{}:
65 Done{SOpen{F.FLst{depth, att, n, 0, J.JNil{}}}}
66 case False{} Con{f, t}:
67 Done{SOpen{F.FTab{depth, att, n, 0, d, Con{f, t}, J.JNil{}}}}
68
69def start.pick(+noinline: Bool, +n: U32, +d: U32, +fields: List<&2, String>, +rest: String, +att: F.Att, +depth: U32, +strict: Bool) -> Result<&2, &2, String, Start>:
70 match noinline:
71 case True{}:
72 block(U32.is_zero(n), fields, n, d, att, depth)
73 case False{}:
74 inline.go(fields, n, d, rest, strict)
75
76
77
78def arr_start(+att: F.Att, +depth: U32, +h: H.Hdr, +strict: Bool) -> Result<&2, &2, String, Start>:
79 match h:
80 case H.Hdr{n, d, fields, rest}:
81 start.pick(String.is_empty(String.trim(rest)), n, d, fields, rest, att, depth, strict)
82
83
84
85def obj.arr(+r: Result<&2, &2, String, Start>, +k: String, +dp: U32, +a: F.Att, +fs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32, +strict: Bool) -> Result<&2, &2, String, F.St>:
86 match r:
87 case Fail{e}:
88 Fail{e}
89 case Done{SVal{v}}:
90 F.put.field(strict, dp, a, fs, k, v, rest, root, bmin)
91 case Done{SOpen{fr}}:
92 Done{F.St{Con{fr, Con{F.FObj{dp, a, fs}, rest}}, root, bmin}}
93
94
95def obj.ent(+ent: E.Ent, +dp: U32, +a: F.Att, +fs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32, +strict: Bool) -> Result<&2, &2, String, F.St>:
96 match ent:
97 case E.ENot{}:
98 Fail{"missing colon after key"}
99 case E.EVal{k, v}:
100 F.put.field(strict, dp, a, fs, k, v, rest, root, bmin)
101 case E.EOpen{k}:
102 Done{F.St{Con{F.FObj{(dp + 1 : U32), F.AKey{k}, J.JNil{}}, Con{F.FObj{dp, a, fs}, rest}}, root, bmin}}
103 case E.EArr{Some{k}, h}:
104 obj.arr(arr_start(F.AKey{k}, (dp + 1 : U32), h, strict), k, dp, a, fs, rest, root, bmin, strict)
105 case E.EArr{None{}, h}:
106 Fail{"array header without a key"}
107
108def obj.line(+r: Result<&2, &2, String, E.Ent>, +dp: U32, +a: F.Att, +fs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32, +strict: Bool) -> Result<&2, &2, String, F.St>:
109 match r:
110 case Fail{e}:
111 Fail{e}
112 case Done{ent}:
113 obj.ent(ent, dp, a, fs, rest, root, bmin, strict)
114
115
116
117def hyphen.pick(+bare: Bool, +spaced: Bool, +text: String) -> Item:
118 match bare spaced:
119 case True{} _:
120 IEmpty{}
121 case False{} True{}:
122 IText{String.trim(Hd.drop(Hd.drop(text)))}
123 case False{} False{}:
124 IBad{}
125
126def hyphen(+text: String) -> Item:
127 hyphen.pick(String.eq(text, "-"), Hd.lead2(text, 45, 32), text)
128
129def lst.push(+v: J.Json, +dp: U32, +a: F.Att, +n: U32, +c: U32, +xs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, F.St>:
130 Done{F.St{Con{F.FLst{dp, a, n, (c + 1 : U32), J.JCons{v, xs}}, rest}, root, bmin}}
131
132def item.prim(+r: Result<&2, &2, String, J.Json>, +dp: U32, +a: F.Att, +n: U32, +c: U32, +xs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, F.St>:
133 match r:
134 case Fail{e}:
135 Fail{e}
136 case Done{v}:
137 lst.push(v, dp, a, n, c, xs, rest, root, bmin)
138
139def item.arr(+r: Result<&2, &2, String, Start>, +dp: U32, +a: F.Att, +n: U32, +c: U32, +xs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, F.St>:
140 match r:
141 case Fail{e}:
142 Fail{e}
143 case Done{SVal{v}}:
144 lst.push(v, dp, a, n, c, xs, rest, root, bmin)
145 case Done{SOpen{fr}}:
146 Done{F.St{Con{fr, Con{F.FLst{dp, a, n, (c + 1 : U32), xs}, rest}}, root, bmin}}
147
148
149def item.ent(+r: Result<&2, &2, String, E.Ent>, +t: String, +strict: Bool, +dp: U32, +a: F.Att, +n: U32, +c: U32, +xs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, F.St>:
150 match r:
151 case Fail{e}:
152 Fail{e}
153 case Done{E.ENot{}}:
154 item.prim(P.parse(t), dp, a, n, c, xs, rest, root, bmin)
155 case Done{E.EArr{None{}, h}}:
156 item.arr(arr_start(F.AItem{}, (dp + 1 : U32), h, strict), dp, a, n, c, xs, rest, root, bmin)
157 case Done{ent}:
158 obj.ent(ent, (dp + 1 : U32), F.AItem{}, J.JNil{}, Con{F.FLst{dp, a, n, (c + 1 : U32), xs}, rest}, root, bmin, strict)
159
160def lst.item(+it: Item, +strict: Bool, +dp: U32, +a: F.Att, +n: U32, +c: U32, +xs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, F.St>:
161 match it:
162 case IBad{}:
163 Fail{"expected a list item"}
164 case IEmpty{}:
165 lst.push(J.JObj{J.JNil{}}, dp, a, n, c, xs, rest, root, bmin)
166 case IText{t}:
167 item.ent(E.parse(strict, t), t, strict, dp, a, n, c, xs, rest, root, bmin)
168
169
170
171def row.like.go(+di: U32, +ci: U32) -> Bool:
172 U32.is_eq(ci, S.none()) || (Bool.not(U32.is_eq(di, S.none())) && U32.is_lt(di, ci))
173
174
175
176def row.like(+text: String, +d: U32) -> Bool:
177 row.like.go(S.find_unq(text, d, 0, 0), S.find_unq(text, 58, 0, 0))
178
179def zip(+ks: List<&2, String>, +vs: J.Json) -> J.Json:
180 match ks vs:
181 case Con{k, kt} J.JCons{v, vt}:
182 J.JField{k, v, zip(kt, vt)}
183 case _ _:
184 J.JNil{}
185
186def row.push(+ok: Bool, +vs: J.Json, +dp: U32, +a: F.Att, +n: U32, +c: U32, +d: U32, +ks: List<&2, String>, +xs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, F.St>:
187 match ok:
188 case False{}:
189 Fail{"row width does not match the field count"}
190 case True{}:
191 Done{F.St{Con{F.FTab{dp, a, n, (c + 1 : U32), d, ks, J.JCons{J.JObj{zip(ks, vs)}, xs}}, rest}, root, bmin}}
192
193def row.add(+r: Result<&2, &2, String, J.Json>, +strict: Bool, +dp: U32, +a: F.Att, +n: U32, +c: U32, +d: U32, +ks: List<&2, String>, +xs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, F.St>:
194 match r:
195 case Fail{e}:
196 Fail{e}
197 case Done{vs}:
198 row.push(Bool.not(strict) || U32.is_eq(count(vs), list.len(ks)), vs, dp, a, n, c, d, ks, xs, rest, root, bmin)
199
200def tab.line(+isrow: Bool, +text: String, +strict: Bool, +dp: U32, +a: F.Att, +n: U32, +c: U32, +d: U32, +ks: List<&2, String>, +xs: J.Json, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, F.St>:
201 match isrow:
202 case False{}:
203 Fail{"key-value line among tabular rows"}
204 case True{}:
205 row.add(P.values(S.split(text, d)), strict, dp, a, n, c, d, ks, xs, rest, root, bmin)
206
207
208
209def top.go(+text: String, +top: F.Fr, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32, +strict: Bool) -> Result<&2, &2, String, F.St>:
210 match top:
211 case F.FObj{dp, a, fs}:
212 obj.line(E.parse(strict, text), dp, a, fs, rest, root, bmin, strict)
213 case F.FLst{dp, a, n, c, xs}:
214 lst.item(hyphen(text), strict, dp, a, n, c, xs, rest, root, bmin)
215 case F.FTab{dp, a, n, c, d, ks, xs}:
216 tab.line(row.like(text, d), text, strict, dp, a, n, c, d, ks, xs, rest, root, bmin)
217
218def top.line(+deep: Bool, +text: String, +top: F.Fr, +rest: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32, +strict: Bool) -> Result<&2, &2, String, F.St>:
219 match deep:
220 case True{}:
221 Fail{"unexpected indentation"}
222 case False{}:
223 top.go(text, top, rest, root, bmin, strict)
224
225def dispatch.stk(+depth: U32, +text: String, +stk: List<&2, F.Fr>, +root: Maybe<&2, J.Json>, +bmin: U32, +strict: Bool) -> Result<&2, &2, String, F.St>:
226 match stk:
227 case Nil{}:
228 Fail{"unexpected content after the end of the document"}
229 case Con{top, rest}:
230 top.line(strict && U32.is_gt(depth, F.fr.depth(top)), text, top, rest, root, bmin, strict)
231
232def dispatch(+depth: U32, +text: String, +r: Result<&2, &2, String, F.St>, +strict: Bool) -> Result<&2, &2, String, F.St>:
233 match r:
234 case Fail{e}:
235 Fail{e}
236 case Done{F.St{stk, root, bmin}}:
237 dispatch.stk(depth, text, stk, root, bmin, strict)
238
239def wrap(+no: U32, +r: Result<&2, &2, String, F.St>) -> Result<&2, &2, String, F.St>:
240 match r:
241 case Fail{e}:
242 Fail{D.at(no, e)}
243 case Done{st}:
244 Done{st}
245
246def note.blank(+strict: Bool, +st: F.St) -> F.St:
247 match strict:
248 case True{}:
249 F.blank.note(st)
250 case False{}:
251 st
252
253def content(+no: U32, +depth: U32, +text: String, +st: F.St, +strict: Bool) -> Result<&2, &2, String, F.St>:
254 wrap(no, dispatch(depth, text, F.pop(List.length(&2, F.Fr, F.stk.of(st)), F.blank.check(st, depth, strict), (depth + 1 : U32), strict), strict))
255
256def step.go(+blank: Bool, +no: U32, +depth: U32, +text: String, +st: F.St, +strict: Bool) -> Result<&2, &2, String, F.St>:
257 match blank:
258 case True{}:
259 Done{note.blank(strict, st)}
260 case False{}:
261 content(no, depth, text, st, strict)
262
263
264def step(+l: D.DL, +st: F.St, +strict: Bool) -> Result<&2, &2, String, F.St>:
265 match l:
266 case D.DL{no, depth, text, blank}:
267 step.go(blank, no, depth, text, st, strict)