Landin library reference source

core/io/buffered.ldn

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