ref: 15ad5bac3dd1a18be6e632afa73f5ac12b4a5ec1
dir: /lib/bio/bio.myr/
use std
use "types"
pkg bio =
/* creation */
const mk : (mode : mode, vt : vtab -> file#)
const close : (f : file# -> bool)
const free : (f : file# -> void)
/* basic i/o. Returns sub-buffer when applicable. */
const write : (f : file#, src : byte[:] -> std.result(std.size, err))
const read : (f : file#, dst : byte[:] -> std.result(byte[:], err))
const flush : (f : file# -> bool)
/* seeking */
const seek : (f : file#, off : std.off -> std.result(std.off, err))
/* single unit operations */
const putb : (f : file#, b : byte -> std.result(std.size, err))
const putc : (f : file#, c : char -> std.result(std.size, err))
const getb : (f : file# -> std.result(byte, err))
const getc : (f : file# -> std.result(char, err))
/* peeking */
const peekb : (f : file# -> std.result(byte, err))
const peekc : (f : file# -> std.result(char, err))
/* delimited read; returns freshly allocated buffer. */
const readln : (f : file# -> std.result(byte[:], err))
const readto : (f : file#, delim : byte[:] -> std.result(byte[:], err))
const readterm : (f : file#, delim : byte[:] -> std.result(byte[:], err))
const skipto : (f : file#, delim : byte[:] -> bool)
const skipspace : (f : file# -> bool)
/* formatted i/o */
const put : (f : file#, fmt : byte[:], args : ... -> std.result(std.size, err))
const putv : (f : file#, fmt : byte[:], ap : std.valist# -> std.result(std.size, err))
/* pkg funcs */
pkglocal const ensureread : (f : file#, n : std.size -> std.result(std.size, err))
pkglocal const ensurewrite : (f : file#, n : std.size -> std.result(std.size, err))
impl disposable file#
;;
const Bufsz = 16*std.KiB
const Small = 512
type skipmode = union
`Drop
`Read
`Keep
;;
impl disposable file# =
__dispose__ = {f
close(f)
}
;;
/* Creates a file from an fd, opened in the given mode. */
const mk = {mode, vt
var f
f = std.alloc()
f.mode = mode
f.read = std.fndup(vt.read)
f.write = std.fndup(vt.write)
f.seek = std.fndup(vt.seek)
f.close = std.fndup(vt.close)
f.lasterr = 0
if mode & Rd != 0
f.rbuf = std.slalloc(Bufsz)
f.rstart = 0
f.rend = 0
;;
if mode & Wr != 0
f.wbuf = std.slalloc(Bufsz)
f.wend = 0
;;
-> f
}
/* closes a file, flushing it to the output fd */
const close = {f
var ok
ok = flush(f)
ok = ok && f.close()
_free(f)
-> ok
}
const free = {f
flush(f)
_free(f)
}
const _free = {f
if f.mode & Rd != 0
std.slfree(f.rbuf)
;;
if f.mode & Wr != 0
std.slfree(f.wbuf)
;;
std.fnfree(f.read)
std.fnfree(f.write)
std.fnfree(f.seek)
std.fnfree(f.close)
std.free(f)
}
/*
writes to as much from `src` as possible to a file,
returning the number of bytes written.
*/
const write = {f, src
std.assert(f.mode & Wr != 0, "File is not in write mode")
/*
Tack small writes onto the buffer end. Big ones
flush the buffer and then go right to kernel.
*/
if src.len <= (f.wbuf.len - f.wend)
std.slcp(f.wbuf[f.wend:f.wend+src.len], src)
f.wend += src.len
-> `std.Ok src.len
else
flush(f)
-> writebuf(f, src)
;;
}
/*
reads as much into 'dst' as possible, up to the size of 'dst',
returning the number of bytes read.
*/
const read = {f, dst
var count, cap : std.size
var d : byte[:]
std.assert(f.mode & Rd != 0, "File is not in read mode")
/* Clear the error state so we can retry */
if f.lasterr != 0
-> `std.Err geterr(f)
;;
/*
a zero byte read always succeeds, reading 0 bytes; since
there are an infinite number of zero byte reads you can do
from anywhere in the file, including the end, this is not
an EOF condition.
*/
if dst.len == 0
-> `std.Ok dst
;;
/*
small reads should try to fill, so we don't have to make a
syscall for every read
*/
cap = f.rend - f.rstart
if dst.len < Small && cap < dst.len
fill(f, dst.len)
;;
/* Read as much as we can from the buffer */
count = std.min(dst.len, f.rend - f.rstart)
std.slcp(dst[:count], f.rbuf[f.rstart:f.rstart+count])
f.rstart += count
/* if we drained the buffer, reset it */
if f.rstart == f.rend
f.rstart = 0
f.rend = 0
;;
/* Read the rest directly from the fd */
d = dst[count:]
while d.len > 0
match f.read(d)
| `std.Ok 0:
break
| `std.Ok n:
count += n
d = d[n:]
| `std.Err err:
if count == 0
-> `std.Err errtype(err)
else
f.lasterr = err
;;
break
;;
;;
if count == 0
-> `std.Err `Eof
else
-> `std.Ok dst[:count]
;;
}
/* flushes f out to the backing fd */
const flush = {f
var ret
ret = true
if f.mode & Wr != 0
match writebuf(f, f.wbuf[:f.wend])
| `std.Ok n: ret = (n == f.wend)
| _: ret = false
;;
;;
f.wend = 0
-> ret
}
const seek = {f, off
flush(f)
f.rstart = f.rend = 0
match f.seek(off)
| `std.Ok ret: -> `std.Ok ret
| `std.Err e: -> `std.Err errtype(e)
;;
}
/* writes a single byte to the output stream */
const putb = {f, b
std.assert(f.mode & Wr != 0, "File is not in write mode")
match ensurewrite(f, 1)
| `std.Err e: -> `std.Err e
| `std.Ok n:
f.wbuf[f.wend++] = b
-> `std.Ok 1
;;
}
/* writes a single character to the output stream, encoded in utf8 */
const putc = {f, c
var sz
std.assert(f.mode & Wr != 0, "File is not in write mode")
sz = std.charlen(c)
match ensurewrite(f, sz)
| `std.Err e: -> `std.Err e
| `std.Ok n:
std.encode(f.wbuf[f.wend:], c)
f.wend += sz
-> `std.Ok sz
;;
}
/* reads a single byte from the input stream */
const getb = {f
std.assert(f.mode & Rd != 0, "File is not in read mode")
match ensureread(f, 1)
| `std.Err e: -> `std.Err e
| `std.Ok n:
-> `std.Ok f.rbuf[f.rstart++]
;;
}
/* reads a single character from the input stream, encoded in utf8 */
const getc = {f
var c
std.assert(f.mode & Rd != 0, "File is not in read mode")
match ensurecodepoint(f)
| `std.Err e: -> `std.Err e
| `std.Ok n:
c = std.decode(f.rbuf[f.rstart:f.rend])
f.rstart += std.charlen(c)
-> `std.Ok c
;;
}
/* ensures we have enough to read a single codepoint in the buffer */
const ensurecodepoint = {f
var b
var len
match ensureread(f, 1)
| `std.Err e: -> `std.Err e
| `std.Ok n:
b = f.rbuf[f.rstart]
if b & 0x80 == 0 /* 0b0xxx_xxxx */
len = 1
elif b & 0xe0 == 0xc0 /* 0b110x_xxxx */
len = 2
elif b & 0xf0 == 0xe0 /* 0b1110_xxxx */
len = 3
elif b & 0xf8 == 0xf0 /* 0b1111_0xxx */
len = 4
else
len = 1 /* invalid unicode char */
;;
-> ensureread(f, len)
;;
}
/* peeks a single byte from an input stream */
const peekb = {f
match ensureread(f, 1)
| `std.Err e: -> `std.Err e
| `std.Ok n:
-> `std.Ok f.rbuf[f.rstart]
;;
}
/* peeks a single character from a utf8 encoded input stream */
const peekc = {f
match ensurecodepoint(f)
| `std.Err e: -> `std.Err e
| `std.Ok n:
-> `std.Ok std.decode(f.rbuf[f.rstart:f.rend])
;;
}
/*
Reads up to a delimiter string. Drops the delimiter from
the input stream. EOF always counts as a delimiter.
Eg, with the input "foo,bar\n"
bio.readto(f, ',') -> "foo"
bio.readto(f, ',') -> "bar\n"
*/
const readto = {f, delim
-> readdelim(f, delim, `Read)
}
/* same as readto, but includes the delimiter if it was found */
const readterm = {f, delim
-> readdelim(f, delim, `Keep)
}
/* same as readto, but drops the read data. */
const skipto = {f, delim
match readdelim(f, delim, `Drop)
| `std.Ok _: -> true
| `std.Err _: -> false
;;
}
const skipspace = {f
while true
match bio.peekc(f)
| `std.Ok c:
if !std.isspace(c)
break
;;
bio.getc(f)
| `std.Err e: -> false
;;
;;
-> true
}
/* Same as delim, but with special handling for '\n', '\r', and '\r\n' */
const readln = {f
var ret, c
ret = [][:]
while true
/* get at least delimiter count of characters */
match ensureread(f, 1)
| `std.Err `Eof:
readinto(f, &ret, f.rend - f.rstart)
if ret.len > 0
-> `std.Ok ret
else
-> `std.Err `Eof
;;
| `std.Err e: -> `std.Err e
| `std.Ok _:
;;
/* scan for delimiter */
for var i = f.rstart; i < f.rend; i++
c = (f.rbuf[i] : char)
if c == '\r' || c == '\n'
readinto(f, &ret, i - f.rstart)
f.rstart++
/* if we have '\r', we can get '\r\n'. */
if c == '\r' && unwrapc(peekc(f), -1) == '\n'
f.rstart++
;;
-> `std.Ok ret
;;
:nextitergetln
;;
readinto(f, &ret, f.rend - f.rstart)
;;
std.die("unreachable")
}
const unwrapc = {cc, v
match cc
| `std.Ok c: -> c
| _: -> v
;;
}
const readdelim = {f, delim, mode
var ret, i, j
ret = [][:]
if delim.len == 0
-> `std.Ok ret
;;
while true
match ensureread(f, delim.len)
| `std.Err `Eof:
/*
* We tried to get a delimiter, but came short.
* Return the whole end of the buffer as the
* read value, as though there was a terminating
* delimiter at the end. If we're at 0 bytes even
* then, return an eof.
*/
match mode
| `Drop: f.rstart += f.rend - f.rstart
| `Read: readinto(f, &ret, f.rend - f.rstart)
| `Keep: readinto(f, &ret, f.rend - f.rstart)
;;
match ret.len
| 0: -> `std.Err `Eof
| _: -> `std.Ok ret
;;
| `std.Err e:
std.slfree(ret)
-> `std.Err e
| `std.Ok _: /* nothing: scan the buffer */
;;
for i = f.rstart; i <= f.rend - delim.len; i++
for j = 0; j < delim.len; j++
if f.rbuf[i + j] != delim[j]
goto nextiter
;;
;;
/* If we found it, return that information */
match mode
| `Drop:
f.rstart = i + delim.len
| `Read:
readinto(f, &ret, i - f.rstart)
f.rstart += delim.len
| `Keep:
readinto(f, &ret, i + delim.len - f.rstart)
;;
-> `std.Ok ret
:nextiter
;;
match mode
| `Drop: f.rstart = i
| `Read: readinto(f, &ret, i - f.rstart)
| `Keep: readinto(f, &ret, i - f.rstart)
;;
;;
std.die("unreachable")
}
/*
Same as std.put, but buffered. Returns the number of bytes written.
FIXME: depends on std.fmt() having a flush buffer API. Until then,
we're stuck with a small static buffer.
*/
const put = {f, fmt, args
var sl, ap, n
ap = std.vastart(&args)
sl = std.fmtv(fmt, &ap)
n = write(f, sl)
std.slfree(sl)
-> n
}
const putv = {f, fmt, ap
var sl, n
sl = std.fmtv(fmt, ap)
n = write(f, sl)
std.slfree(sl)
-> n
}
/*
reads n bytes from the read buffer onto the heap-allocated slice
provided.
*/
const readinto = {f, bufp, n
std.assert(f.rstart + n <= f.rend, "Reading too much from buffer")
std.sljoin(bufp, f.rbuf[f.rstart:f.rstart + n])
f.rstart += n
}
/* makes sure we can bufferedly write at least n bytes */
const ensurewrite = {f, n
std.assert(n < f.wbuf.len, "ensured write capacity > buffer size")
if n > f.wbuf.len - f.wend
match writebuf(f, f.wbuf[:f.wend])
| `std.Ok len:
f.wend = 0
-> `std.Ok len
| `std.Err e: -> `std.Err e
;;
;;
-> `std.Ok n
}
/*
makes sure we have at least n bytes buffered. returns true if we succeed
in buffering n bytes, false if we fail.
*/
const ensureread = {f, n
var held
std.assert(n < f.rbuf.len, "ensured read capacity > buffer size")
held = f.rend - f.rstart
if n > held
match fill(f, n)
| `std.Err e: -> `std.Err e
| `std.Ok len:
if held + len >= n
-> `std.Ok len
else
-> `std.Err `Eof
;;
;;
else
-> `std.Ok n
;;
}
/* blats a buffer to an fd */
const writebuf = {f, src
var count
count = 0
while src.len != 0
match f.write(src)
| `std.Ok 0:
-> `std.Err `Eof
| `std.Ok n:
count += n
src = src[n:]
| `std.Err e:
-> `std.Err errtype(e)
;;
;;
:writedone
-> `std.Ok count
}
/*
Reads as many bytes as possible from the file into
the read buffer.
*/
const fill = {f, min
var count, cap
count = 0
/* Clear the error state so we can retry */
if f.lasterr != 0
-> `std.Err geterr(f)
;;
/* if we need to shift the slice down to the start, do it */
cap = f.rend - f.rstart
if min > cap
std.slcp(f.rbuf[:cap], f.rbuf[f.rstart:f.rend])
f.rstart = 0
f.rend = cap
;;
while count < min
/*
If we've already read data, we don't want to
throw it away, so we report a successful short
read, and then error on the next read.
*/
match f.read(f.rbuf[f.rend:])
| `std.Ok 0:
break
| `std.Ok n:
count += n
f.rend += n
| `std.Err e:
if count > 0
f.lasterr = e
else
-> `std.Err errtype(e)
;;
break
;;
;;
if count == 0
-> `std.Err `Eof
else
-> `std.Ok count
;;
}
const geterr = {f
var e
e = f.lasterr
f.lasterr = 0
-> errtype(e)
}
const errtype = {e : std.errno -> err
var errno
errno = (e : std.errno)
if errno == std.Ebadf
-> `Ebadf
elif errno == std.Einval
-> `Ebadf
elif errno == std.Efault
-> `Eio
else
-> `Eio
;;
}