src/frame.bend

bend-toon-codec@0.1.0.0 · 241 lines · 32 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/head.bend as Hd
5
6# The decoder's explicit stack of open containers (an object, a list array or
7# a tabular array), with the operations that grow, close and unwind it.
8#
9# `depth` is the depth of a frame's child lines. `att` says how the finished
10# value attaches to the frame beneath it. Children are kept in reverse order
11# and reversed when the frame closes.
12
13type Att is Data:
14  AKey{k: String}
15  AItem{}
16  ARoot{}
17
18type Fr is Data:
19  FObj{depth: U32, att: Att, fs: J.Json}
20  FLst{depth: U32, att: Att, n: U32, c: U32, xs: J.Json}
21  FTab{depth: U32, att: Att, n: U32, c: U32, d: U32, ks: List<&2, String>, xs: J.Json}
22
23# The stack (top first), the finished root once there is one, and the shallowest
24# array that had a blank line inside it since the last content line.
25type St is Data:
26  St{stk: List<&2, Fr>, root: Maybe<&2, J.Json>, bmin: U32}
27
28def stk.of(+st: St) -> List<&2, Fr>:
29  match st:
30    case St{stk, root, bmin}:
31      stk
32
33def bmin.of(+st: St) -> U32:
34  match st:
35    case St{stk, root, bmin}:
36      bmin
37
38def fr.depth(+f: Fr) -> U32:
39  match f:
40    case FObj{d, a, fs}:
41      d
42    case FLst{d, a, n, c, xs}:
43      d
44    case FTab{d, a, n, c, dl, ks, xs}:
45      d
46
47def fr.att(+f: Fr) -> Att:
48  match f:
49    case FObj{d, a, fs}:
50      a
51    case FLst{d, a, n, c, xs}:
52      a
53    case FTab{d, a, n, c, dl, ks, xs}:
54      a
55
56# ---- chains --------------------------------------------------------------------------
57
58def rev.items(+xs: J.Json, +acc: J.Json) -> J.Json:
59  match xs:
60    case J.JCons{h, t}:
61      rev.items(t, J.JCons{h, acc})
62    case _:
63      acc
64
65def rev.fields(+fs: J.Json, +acc: J.Json) -> J.Json:
66  match fs:
67    case J.JField{k, v, t}:
68      rev.fields(t, J.JField{k, v, acc})
69    case _:
70      acc
71
72def has_key(+k: String, +fs: J.Json) -> Bool:
73  match fs:
74    case J.JField{k2, v, t}:
75      Hd.keq(k, k2) || has_key(k, t)
76    case _:
77      False{}
78
79def set.pick(+hit: Bool, +k: String, +v: J.Json, +old: J.Json, +rest: J.Json, +t: J.Json) -> J.Json:
80  match hit:
81    case True{}:
82      J.JField{k, v, t}
83    case False{}:
84      J.JField{k, old, rest}
85
86# Replace the value under `k`, keeping its place.
87def set(+k: String, +v: J.Json, +fs: J.Json) -> J.Json:
88  match fs:
89    case J.JField{k2, old, t}:
90      set.pick(Hd.keq(k, k2), k2, v, old, set(k, v, t), t)
91    case _:
92      fs
93
94def add.if(+dup: Bool, +strict: Bool, +k: String, +v: J.Json, +fs: J.Json) -> Result<&2, &2, String, J.Json>:
95  match dup strict:
96    case False{} _:
97      Done{J.JField{k, v, fs}}
98    case True{} True{}:
99      Fail{"duplicate key"}
100    case True{} False{}:
101      Done{set(k, v, fs)}
102
103# Add a field to a reversed field chain. A repeated key is an error in strict
104# mode; otherwise the later value replaces the earlier (section 14.4).
105def add_field(+strict: Bool, +fs: J.Json, +k: String, +v: J.Json) -> Result<&2, &2, String, J.Json>:
106  add.if(has_key(k, fs), strict, k, v, fs)
107
108# ---- closing -------------------------------------------------------------------------
109
110def count.ok(+strict: Bool, +n: U32, +c: U32) -> Bool:
111  Bool.not(strict) || U32.is_eq(n, c)
112
113def fin.arr(+ok: Bool, +xs: J.Json) -> Result<&2, &2, String, J.Json>:
114  match ok:
115    case True{}:
116      Done{J.JArr{rev.items(xs, J.JNil{})}}
117    case False{}:
118      Fail{"array length does not match its header"}
119
120def finish(+f: Fr, +strict: Bool) -> Result<&2, &2, String, J.Json>:
121  match f:
122    case FObj{d, a, fs}:
123      Done{J.JObj{rev.fields(fs, J.JNil{})}}
124    case FLst{d, a, n, c, xs}:
125      fin.arr(count.ok(strict, n, c), xs)
126    case FTab{d, a, n, c, dl, ks, xs}:
127      fin.arr(count.ok(strict, n, c), xs)
128
129def put.field.done(+r: Result<&2, &2, String, J.Json>, +d: U32, +a: Att, +rest: List<&2, Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, St>:
130  match r:
131    case Fail{e}:
132      Fail{e}
133    case Done{fs2}:
134      Done{St{Con{FObj{d, a, fs2}, rest}, root, bmin}}
135
136def put.field(+strict: Bool, +d: U32, +a: Att, +fs: J.Json, +k: String, +v: J.Json, +rest: List<&2, Fr>, +root: Maybe<&2, J.Json>, +bmin: U32) -> Result<&2, &2, String, St>:
137  put.field.done(add_field(strict, fs, k, v), d, a, rest, root, bmin)
138
139# Attach a finished value to the frame beneath, or make it the root.
140def attach.top(+top: Fr, +att: Att, +v: J.Json, +rest: List<&2, Fr>, +root: Maybe<&2, J.Json>, +strict: Bool, +bmin: U32) -> Result<&2, &2, String, St>:
141  match top att:
142    case FObj{d, a, fs} AKey{k}:
143      put.field(strict, d, a, fs, k, v, rest, root, bmin)
144    case FLst{d, a, n, c, xs} AItem{}:
145      Done{St{Con{FLst{d, a, n, c, J.JCons{v, xs}}, rest}, root, bmin}}
146    case _ _:
147      Fail{"misplaced value"}
148
149def attach(+att: Att, +v: J.Json, +stk: List<&2, Fr>, +root: Maybe<&2, J.Json>, +strict: Bool, +bmin: U32) -> Result<&2, &2, String, St>:
150  match stk:
151    case Nil{}:
152      Done{St{Nil{}, Some{v}, bmin}}
153    case Con{top, rest}:
154      attach.top(top, att, v, rest, root, strict, bmin)
155
156def close.go(+r: Result<&2, &2, String, J.Json>, +att: Att, +rest: List<&2, Fr>, +root: Maybe<&2, J.Json>, +strict: Bool, +bmin: U32) -> Result<&2, &2, String, St>:
157  match r:
158    case Fail{e}:
159      Fail{e}
160    case Done{v}:
161      attach(att, v, rest, root, strict, bmin)
162
163# Close the top frame into the one beneath it.
164def close(+st: St, +strict: Bool) -> Result<&2, &2, String, St>:
165  match st:
166    case St{Con{top, rest}, root, bmin}:
167      close.go(finish(top, strict), fr.att(top), rest, root, strict, bmin)
168    case _:
169      Done{st}
170
171def close.if(+b: Bool, +st: St, +strict: Bool) -> Result<&2, &2, String, St>:
172  match b:
173    case True{}:
174      close(st, strict)
175    case False{}:
176      Done{st}
177
178# Is the top frame at or below depth `t`?
179def should_pop(+stk: List<&2, Fr>, +t: U32) -> Bool:
180  match stk:
181    case Con{top, rest}:
182      U32.is_ge(fr.depth(top), t)
183    case Nil{}:
184      False{}
185
186# Close every frame whose child depth is at least `t`. `fuel` is the stack
187# height, enough for every frame to close.
188def pop(+fuel: Nat, +sr: Result<&2, &2, String, St>, +t: U32, +strict: Bool) -> Result<&2, &2, String, St>:
189  match fuel sr:
190    case 0n _:
191      sr
192    case 1n+p Fail{e}:
193      Fail{e}
194    case 1n+p Done{st}:
195      pop(p, close.if(should_pop(stk.of(st), t), st, strict), t, strict)
196
197# ---- blank lines ---------------------------------------------------------------------
198
199def arr.depth.if(+empty: Bool, +d: U32) -> U32:
200  match empty:
201    case True{}:
202      S.none()
203    case False{}:
204      d
205
206def arr.depth(+f: Fr) -> U32:
207  match f:
208    case FLst{d, a, n, c, xs}:
209      arr.depth.if(U32.is_zero(c), d)
210    case FTab{d, a, n, c, dl, ks, xs}:
211      arr.depth.if(U32.is_zero(c), d)
212    case _:
213      S.none()
214
215# Shallowest array frame that already has an item or row.
216def arr.min(+stk: List<&2, Fr>) -> U32:
217  match stk:
218    case Nil{}:
219      S.none()
220    case Con{top, rest}:
221      U32.min(arr.depth(top), arr.min(rest))
222
223# A blank line: remember the shallowest array it falls inside.
224def blank.note(+st: St) -> St:
225  match st:
226    case St{stk, root, bmin}:
227      St{stk, root, U32.min(bmin, arr.min(stk))}
228
229def blank.if(+bad: Bool, +st: St) -> Result<&2, &2, String, St>:
230  match bad:
231    case True{}:
232      Fail{"blank line inside an array"}
233    case False{}:
234      match st:
235        case St{stk, root, bmin}:
236          Done{St{stk, root, S.none()}}
237
238# A content line at depth `d` after a blank: it is inside the array when the
239# array's child depth is at most `d`.
240def blank.check(+st: St, +d: U32, +strict: Bool) -> Result<&2, &2, String, St>:
241  blank.if(strict && U32.is_le(bmin.of(st), d), st)