1-- A buffered writer gathers small writes in a caller's buffer and hands
2-- them to a writer in one call when the buffer is full or the program
3-- flushes it.
4
5import core/mem
6
7buffered_value: type (provider: type) = struct
8 host: provider
9 opened: file
10 buffer: []mut u8
11 used: usize
12end buffered_value
13
14--- Writes to one file, gathered in a buffer the caller supplies and
15--- written through a writer when the buffer fills or `flush` is called: a
16--- value the caller keeps and edits, passing it `inout`. There are no
17--- destructors, so bytes still in the buffer when the program ends, or when
18--- the writer is dropped, are never written: flush before every way out,
19--- with `defer io.flush(out) else ...` as the idiom, and flush again where
20--- the bytes must be out before something else happens, as before reading
21--- a reply. `pending` says how many are waiting.
22public buffered_writer: type (provider: type) = buffered_value(provider)
23
24--- A buffered writer over the caller's `buffer`, writing to `opened`
25--- through `host`. It holds a copy of the writer's handle; keep the
26--- handle's state and the buffer alive while it is used.
27public buffered_over: (provider: type is writer, host: provider,
28 opened: file, buffer: []mut u8)
29 -> (out: buffered_writer(provider) from buffer, host) =
30 out = (host: host, opened: opened, buffer: buffer, used: 0)
31end buffered_over
32
33--- Write the waiting bytes to the file. A failure reports the writer's
34--- `io_failed` and keeps every byte the writer did not take at the front
35--- of the buffer, so a later flush writes each byte exactly once.
36---
37--- Flushing goes through `write_some`, whose failure transfers nothing,
38--- so what the writer took is known exactly; a writer that makes no
39--- progress on nonempty bytes breaks the writer contract and is reported
40--- as failing rather than asked forever.
41public flush: (provider: type is writer,
42 inout out: buffered_writer(provider)) -> none ! io_failed =
43 mut done: usize = 0
44 while done < out.used do
45 wrote: usize = provider.write_some(out.host, out.opened,
46 out.buffer[done..<out.used])
47 else (problem)
48 try kept(out, done)
49 fail problem
50 end
51 if wrote == 0 then
52 try kept(out, done)
53 fail io_failed
54 end if
55 done += wrote
56 end while
57 out.used = 0
58end flush
59
60-- Keep the bytes after `done` at the front of the buffer, the copy moving
61-- them down over the ones already written.
62kept: (provider: type, inout out: buffered_writer(provider), done: usize)
63 -> none ! io_failed =
64 remaining: usize = out.used - done
65 mem.copy(out.buffer[0..<remaining], out.buffer[done..<out.used])
66 else (problem)
67 _ = problem
68 fail io_failed
69 end
70 out.used = remaining
71end kept
72
73--- Write `bytes` to the file through the buffer. Bytes that fit wait in
74--- it; otherwise the waiting ones are flushed first, and then bytes fewer
75--- than the buffer holds wait in it and any more go straight to the writer
76--- in one `write`. A failure is the writer's `io_failed`: a failed flush
77--- keeps its bytes, as `flush` does, and a failed direct write may follow
78--- partial output, as `io.write` may.
79public put: (provider: type is writer, inout out: buffered_writer(provider),
80 bytes: []u8) -> none ! io_failed =
81 count: usize = lenof bytes
82 return when count == 0
83 room := out.buffer
84 capacity: usize = lenof room
85 if count > capacity - out.used then
86 try flush(out)
87 if count >= capacity then
88 try provider.write(out.host, out.opened, bytes)
89 return
90 end if
91 end if
92 ends: usize = out.used + count
93 mem.copy(out.buffer[out.used..<ends], bytes) else (problem)
94 _ = problem
95 fail io_failed
96 end
97 out.used = ends
98end put
99
100--- How many bytes are waiting in the buffer for the next flush.
101public pending: (provider: type, out: buffered_writer(provider))
102 -> (count: usize) =
103 count = out.used
104end pending