journal.bend
bend-kit-journal@0.3.1.0 · 399 lines · 65 definitions
1
2import Base
3import bend-kit-bytes@0.3.3.0/bytes.bend as Bytes
4import bend-kit-hash@0.3.0.2/hash.bend as Hash
5
6
7
8
9
10
11
12
13type Tail is Data:
14 Clean{}
15 Torn{}
16 Corrupt{why: String}
17
18
19type Step<-P: Type, -S: Type> is Type:
20 End{s: S}
21 Short{s: S}
22 Bad{why: String, s: S}
23 Got{p: P, s: S}
24
25
26type Recovery<a, -P: Kind(a), -S: Type> is Type:
27 Recovery{records: List<a, P>, tail: Tail, rest: S}
28
29
30type Replay<-A: Type, -S: Type> is Type:
31 Replay{acc: A, tail: Tail, rest: S}
32
33
34
35
36
37def next.verdict(~P: Type, ~S: Type, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ok: Bool, +len: U32, s: S, p: P) -> Step<P, S>:
38 match ok:
39 case True{}:
40 Got{p, skip(s, 12, len)}
41 case False{}:
42 Bad{"checksum mismatch", s}
43
44def next.summed(~P: Type, ~S: Type, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, +want: U32, +len: U32, s: S, r: P & U32) -> Step<P, S>:
45 (p, +got) = r
46 next.verdict(~P, ~S, ~skip, U32.is_eq(got, want), len, s, p)
47
48def next.cut(~P: Type, ~S: Type, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, +want: U32, +len: U32, r: S & P) -> Step<P, S>:
49 (s, p) = r
50 next.summed(~P, ~S, ~skip, want, len, s, sum(p))
51
52def next.want(~P: Type, ~S: Type, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, +len: U32, r: S & U32) -> Step<P, S>:
53 (s, +want) = r
54 next.cut(~P, ~S, ~skip, ~sum, want, len, cut(s, 12, len))
55
56def next.body(~P: Type, ~S: Type, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, +len: U32, r: S & Bool) -> Step<P, S>:
57 (s, whole) = r
58 match whole:
59 case True{}:
60 next.want(~P, ~S, ~cut, ~skip, ~sum, len, u32(s, 8))
61 case False{}:
62 Short{s}
63
64def next.size(~P: Type, ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, over: Bool, +len: U32, s: S) -> Step<P, S>:
65 match over:
66 case True{}:
67 Bad{"length over max", s}
68 case False{}:
69 next.body(~P, ~S, ~u32, ~cut, ~skip, ~sum, len, fits(s, 12, len))
70
71def next.head(~P: Type, ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, ok: Bool, +max: U32, +len: U32, s: S) -> Step<P, S>:
72 match ok:
73 case True{}:
74 next.size(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, U32.is_gt(len, max), len, s)
75 case False{}:
76 Bad{"length check mismatch", s}
77
78def next.lenx(~P: Type, ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, +max: U32, +len: U32, r: S & U32) -> Step<P, S>:
79 (s, +lenx) = r
80 next.head(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, U32.is_eq(lenx, U32.not(len)), max, len, s)
81
82def next.len(~P: Type, ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, +max: U32, r: S & U32) -> Step<P, S>:
83 (s, +len) = r
84 next.lenx(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, max, len, u32(s, 4))
85
86
87
88def next.start(~P: Type, ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, +max: U32, r: S & Bool) -> Step<P, S>:
89 (s, both) = r
90 match both:
91 case True{}:
92 next.len(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, max, u32(s, 0))
93 case False{}:
94 Short{s}
95
96def next.any(~P: Type, ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, +max: U32, r: S & Bool) -> Step<P, S>:
97 (s, some) = r
98 match some:
99 case True{}:
100 next.start(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, max, fits(s, 0, 8))
101 case False{}:
102 End{s}
103
104
105def next(~P: Type, ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, +max: U32, s: S) -> Step<P, S>:
106 next.any(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, max, fits(s, 0, 1))
107
108def recover.con(~a: Quant, ~P: Kind(a), ~S: Type, p: P, r: Recovery<a, P, S>) -> Recovery<a, P, S>:
109 Recovery{rs, t, s} = r
110 Recovery{Con{p, rs}, t, s}
111
112
113
114def recover.go(~a: Quant, ~P: Kind(a), ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, n: Nat, +max: U32, st: Step<P, S>) -> Recovery<a, P, S>:
115 match n st:
116 case 0n End{s}:
117 Recovery{Nil{}, Clean{}, s}
118 case 0n Short{s}:
119 Recovery{Nil{}, Torn{}, s}
120 case 0n Bad{why, s}:
121 Recovery{Nil{}, Corrupt{why}, s}
122 case 0n Got{p, s}:
123 Recovery{Con{p, Nil{}}, Corrupt{"frame budget exhausted"}, s}
124 case 1n+m End{s}:
125 Recovery{Nil{}, Clean{}, s}
126 case 1n+m Short{s}:
127 Recovery{Nil{}, Torn{}, s}
128 case 1n+m Bad{why, s}:
129 Recovery{Nil{}, Corrupt{why}, s}
130 case 1n+m Got{p, s}:
131 recover.con(~a, ~P, ~S, p, recover.go(~a, ~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, m, max, next(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, max, s)))
132
133
134def recover(~a: Quant, ~P: Kind(a), ~S: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, n: Nat, +max: U32, s: S) -> Recovery<a, P, S>:
135 recover.go(~a, ~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, n, max, next(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, max, s))
136
137
138def replay.go(~P: Type, ~S: Type, ~A: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, ~step: A -> P -> A, n: Nat, +max: U32, st: Step<P, S>, acc: A) -> Replay<A, S>:
139 match n st:
140 case 0n End{s}:
141 Replay{acc, Clean{}, s}
142 case 0n Short{s}:
143 Replay{acc, Torn{}, s}
144 case 0n Bad{why, s}:
145 Replay{acc, Corrupt{why}, s}
146 case 0n Got{p, s}:
147 Replay{step(acc, p), Corrupt{"frame budget exhausted"}, s}
148 case 1n+m End{s}:
149 Replay{acc, Clean{}, s}
150 case 1n+m Short{s}:
151 Replay{acc, Torn{}, s}
152 case 1n+m Bad{why, s}:
153 Replay{acc, Corrupt{why}, s}
154 case 1n+m Got{p, s}:
155 replay.go(~P, ~S, ~A, ~fits, ~u32, ~cut, ~skip, ~sum, ~step, m, max, next(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, max, s), step(acc, p))
156
157def replay(~P: Type, ~S: Type, ~A: Type, ~fits: S -> @+at:U32 -> @+n:U32 -> S & Bool, ~u32: S -> @+at:U32 -> S & U32, ~cut: S -> @+hs:U32 -> @+len:U32 -> S & P, ~skip: S -> @+hs:U32 -> @+len:U32 -> S, ~sum: P -> P & U32, ~step: A -> P -> A, n: Nat, +max: U32, s: S, acc: A) -> Replay<A, S>:
158 replay.go(~P, ~S, ~A, ~fits, ~u32, ~cut, ~skip, ~sum, ~step, n, max, next(~P, ~S, ~fits, ~u32, ~cut, ~skip, ~sum, max, s), acc)
159
160
161
162
163
164def list.fits(xs: List<&2, U32>, +at: U32, +n: U32) -> List<&2, U32> & Bool:
165 +ys = xs
166 (ys, Nat.is_le(Nat.add(U32.to_nat(at), U32.to_nat(n)), List.length(&2, U32, ys)))
167
168def list.field(xs: List<&2, U32>) -> U32:
169 match xs:
170 case Con{x, t}:
171 x
172 case Nil{}:
173 0
174
175def list.u32(xs: List<&2, U32>, +at: U32) -> List<&2, U32> & U32:
176 +ys = xs
177 (ys, list.field(List.drop(&2, U32, ys, U32.to_nat(at))))
178
179def list.cut(xs: List<&2, U32>, +hs: U32, +len: U32) -> List<&2, U32> & List<&2, U32>:
180 +ys = xs
181 (ys, List.take(&2, U32, List.drop(&2, U32, ys, U32.to_nat(hs)), U32.to_nat(len)))
182
183def list.skip(xs: List<&2, U32>, +hs: U32, +len: U32) -> List<&2, U32>:
184 List.drop(&2, U32, xs, Nat.add(U32.to_nat(hs), U32.to_nat(len)))
185
186def crc.bit(n: Nat, +c: U32) -> U32:
187 match n:
188 case 0n:
189 c
190 case 1n+m:
191 crc.bit(m, Bool.pick(U32, U32.is_eq((c .&. 1 : U32), 1), ((c >> 1n) .^. 3988292384 : U32), (c >> 1n : U32)))
192
193def crc.go(xs: List<&2, U32>, +c: U32) -> U32:
194 match xs:
195 case Nil{}:
196 c
197 case Con{+x, t}:
198 crc.go(t, crc.bit(8n, (c .^. (x .&. 255 : U32) : U32)))
199
200
201def list.crc(xs: List<&2, U32>) -> U32:
202 U32.not(crc.go(xs, 4294967295))
203
204def list.sum(xs: List<&2, U32>) -> List<&2, U32> & U32:
205 +ys = xs
206 (ys, list.crc(ys))
207
208def list.next(+max: U32, xs: List<&2, U32>) -> Step<List<&2, U32>, List<&2, U32>>:
209 next(~List<&2, U32>, ~List<&2, U32>, ~list.fits, ~list.u32, ~list.cut, ~list.skip, ~list.sum, max, xs)
210
211def list.recover(n: Nat, +max: U32, xs: List<&2, U32>) -> Recovery<&2, List<&2, U32>, List<&2, U32>>:
212 recover(~&2, ~List<&2, U32>, ~List<&2, U32>, ~list.fits, ~list.u32, ~list.cut, ~list.skip, ~list.sum, n, max, xs)
213
214def list.replay(~A: Type, ~step: A -> List<&2, U32> -> A, n: Nat, +max: U32, xs: List<&2, U32>, acc: A) -> Replay<A, List<&2, U32>>:
215 replay(~List<&2, U32>, ~List<&2, U32>, ~A, ~list.fits, ~list.u32, ~list.cut, ~list.skip, ~list.sum, ~step, n, max, xs, acc)
216
217def list.head(+len: U32, +sum: U32) -> List<&2, U32>:
218 [len, 0, 0, 0, U32.not(len), 0, 0, 0, sum, 0, 0, 0]
219
220def list.frame(+p: List<&2, U32>) -> List<&2, U32>:
221 List.append(&2, U32, list.head(U32.from_nat(List.length(&2, U32, p)), list.crc(p)), p)
222
223
224def list.log(rs: List<&2, List<&2, U32>>) -> List<&2, U32>:
225 match rs:
226 case Nil{}:
227 Nil{}
228 case Con{p, t}:
229 List.append(&2, U32, list.frame(p), list.log(t))
230
231
232type Cur is Type:
233 Cur{b: Bytes.Bytes, pos: U32}
234
235def cur.fits(c: Cur, +at: U32, +n: U32) -> Cur & Bool:
236 Cur{Bytes.Bytes{+len, buf}, +pos} = c
237 (Cur{Bytes.Bytes{len, buf}, pos}, Bytes.fits((len - pos : U32), at, n))
238
239def cur.field(m: Maybe<&2, U32>) -> U32:
240 match m:
241 case Some{x}:
242 x
243 case None{}:
244 0
245
246def cur.u32.fin(+pos: U32, r: Bytes.Bytes & Maybe<&2, U32>) -> Cur & U32:
247 (b, m) = r
248 (Cur{b, pos}, cur.field(m))
249
250def cur.u32(c: Cur, +at: U32) -> Cur & U32:
251 Cur{b, +pos} = c
252 cur.u32.fin(pos, Bytes.get.u32le(b, (pos + at : U32)))
253
254def cur.cut.fin(+pos: U32, r: Bytes.Bytes & Bytes.Bytes) -> Cur & Bytes.Bytes:
255 (b, p) = r
256 (Cur{b, pos}, p)
257
258def cur.cut(c: Cur, +hs: U32, +len: U32) -> Cur & Bytes.Bytes:
259 Cur{b, +pos} = c
260 cur.cut.fin(pos, Bytes.slice(b, (pos + hs : U32), len))
261
262def cur.skip(c: Cur, +hs: U32, +len: U32) -> Cur:
263 Cur{b, +pos} = c
264 Cur{b, (pos + hs + len : U32)}
265
266
267def cur.sum(p: Bytes.Bytes) -> Bytes.Bytes & U32:
268 Hash.crc32(p)
269
270
271def fuel(+len: U32) -> Nat:
272 U32.to_nat((len / 12 + 1 : U32))
273
274
275def replay.bytes(~A: Type, ~step: A -> Bytes.Bytes -> A, +max: U32, b: Bytes.Bytes, acc: A) -> Replay<A, Cur>:
276 Bytes.Bytes{+len, buf} = b
277 replay(~Bytes.Bytes, ~Cur, ~A, ~cur.fits, ~cur.u32, ~cur.cut, ~cur.skip, ~cur.sum, ~step, fuel(len), max, Cur{Bytes.Bytes{len, buf}, 0}, acc)
278
279def frame.of(+len: U32, r: Bytes.Bytes & U32) -> Bytes.Bytes:
280 (p, +sum) = r
281 Bytes.append(Bytes.set.u32le(Bytes.set.u32le(Bytes.set.u32le(Bytes.new(12), 0, len), 4, U32.not(len)), 8, sum), p)
282
283
284def frame(p: Bytes.Bytes) -> Bytes.Bytes:
285 Bytes.Bytes{+len, buf} = p
286 frame.of(len, Hash.crc32(Bytes.Bytes{len, buf}))
287
288
289
290
291
292def open.words(path: String, limit: U32) -> IO(Result<&1, &1, U32 & String, File & U32 & Array<U32>>):
293 import "./effs/journal.c"
294 import "./effs/journal.js"
295
296
297def append.words(file: File, len: U32, words: Array<U32>) -> IO(File & Result<&1, &1, U32 & String, Unit>):
298 import "./effs/journal.c"
299 import "./effs/journal.js"
300
301
302def truncate(file: File, len: U32) -> IO(File & Result<&1, &1, U32 & String, Unit>):
303 import "./effs/journal.c"
304 import "./effs/journal.js"
305
306
307
308type Journal is Type:
309 Journal{file: File, max: U32, end: U32, ok: Bool}
310
311def open.cut(r: File & Result<&1, &1, U32 & String, Unit>, +max: U32, +end: U32, -A: Type, acc: A) -> IO(Result<&1, &1, U32 & String, Journal & A>):
312 match r:
313 case (file, Fail{e}):
314 do IO<Result<&1, &1, U32 & String, Journal & A>>:
315 File.close(file)
316 return Fail{e}
317 case (file, Done{u}):
318 do IO<Result<&1, &1, U32 & String, Journal & A>>:
319 return Done{(Journal{file, max, end, True{}}, acc)}
320
321def open.tail(t: Tail, file: File, +max: U32, +pos: U32, -A: Type, acc: A) -> IO(Result<&1, &1, U32 & String, Journal & A>):
322 match t:
323 case Clean{}:
324 do IO<Result<&1, &1, U32 & String, Journal & A>>:
325 return Done{(Journal{file, max, pos, True{}}, acc)}
326 case Torn{}:
327 do IO<Result<&1, &1, U32 & String, Journal & A>>:
328 r : File & Result<&1, &1, U32 & String, Unit> <- truncate(file, pos)
329 open.cut(r, max, pos, A, acc)
330 case Corrupt{why}:
331 do IO<Result<&1, &1, U32 & String, Journal & A>>:
332 File.close(file)
333 return Fail{(22, "journal: corrupt frame at offset " ++ U32.show(pos) ++ ": " ++ why)}
334
335def open.replayed(file: File, +max: U32, -A: Type, r: Replay<A, Cur>) -> IO(Result<&1, &1, U32 & String, Journal & A>):
336 Replay{acc, t, rest} = r
337 Cur{b, +pos} = rest
338 open.tail(t, file, max, pos, A, acc)
339
340def open.read(~A: Type, ~step: A -> Bytes.Bytes -> A, r: Result<&1, &1, U32 & String, File & U32 & Array<U32>>, +max: U32, acc: A) -> IO(Result<&1, &1, U32 & String, Journal & A>):
341 match r:
342 case Fail{e}:
343 do IO<Result<&1, &1, U32 & String, Journal & A>>:
344 return Fail{e}
345 case Done{(file, (len, words))}:
346 open.replayed(file, max, A, replay.bytes(~A, ~step, max, Bytes.Bytes{len, words}, acc))
347
348
349
350
351def open(~A: Type, ~step: A -> Bytes.Bytes -> A, path: String, +max: U32, limit: U32, acc: A) -> IO(Result<&1, &1, U32 & String, Journal & A>):
352 do IO<Result<&1, &1, U32 & String, Journal & A>>:
353 r : Result<&1, &1, U32 & String, File & U32 & Array<U32>> <- open.words(path, limit)
354 open.read(~A, ~step, r, max, acc)
355
356def append.back(+max: U32, +end: U32, +len: U32, r: File & Result<&1, &1, U32 & String, Unit>) -> Journal & Result<&1, &1, U32 & String, Unit>:
357 match r:
358 case (file, Done{u}):
359 (Journal{file, max, (end + len : U32), True{}}, Done{u})
360 case (file, Fail{e}):
361 (Journal{file, max, end, False{}}, Fail{e})
362
363def append.write(file: File, +max: U32, +end: U32, f: Bytes.Bytes) -> IO(Journal & Result<&1, &1, U32 & String, Unit>):
364 Bytes.Bytes{+len, words} = f
365 do IO<Journal & Result<&1, &1, U32 & String, Unit>>:
366 r : File & Result<&1, &1, U32 & String, Unit> <- append.words(file, len, words)
367 return append.back(max, end, len, r)
368
369def append.refuse(file: File, +max: U32, +end: U32, +ok: Bool, e: U32 & String) -> IO(Journal & Result<&1, &1, U32 & String, Unit>):
370 do IO<Journal & Result<&1, &1, U32 & String, Unit>>:
371 return (Journal{file, max, end, ok}, Fail{e})
372
373
374def append.size(over: Bool, big: Bool, file: File, +max: U32, +end: U32, p: Bytes.Bytes) -> IO(Journal & Result<&1, &1, U32 & String, Unit>):
375 match over big:
376 case True{} _:
377 append.refuse(file, max, end, True{}, (22, "journal: record over max"))
378 case False{} True{}:
379 append.refuse(file, max, end, True{}, (27, "journal: file would reach 4 GiB"))
380 case False{} False{}:
381 append.write(file, max, end, frame(p))
382
383def append.ok(ok: Bool, file: File, +max: U32, +end: U32, p: Bytes.Bytes) -> IO(Journal & Result<&1, &1, U32 & String, Unit>):
384 match ok:
385 case False{}:
386 append.refuse(file, max, end, False{}, (5, "journal: an append failed; reopen the journal"))
387 case True{}:
388 Bytes.Bytes{+len, buf} = p
389 append.size(U32.is_gt(len, max), Bool.or(U32.is_gt(end, 4294967283), U32.is_lt((4294967283 - end : U32), len)), file, max, end, Bytes.Bytes{len, buf})
390
391
392
393def append(j: Journal, p: Bytes.Bytes) -> IO(Journal & Result<&1, &1, U32 & String, Unit>):
394 Journal{file, +max, +end, ok} = j
395 append.ok(ok, file, max, end, p)
396
397def close(j: Journal) -> IO(Unit):
398 Journal{file, max, end, ok} = j
399 File.close(file)