ref: 08442dd9c3426e2dbe598ef1f6fb10f2f67638aa
dir: /lib/bio/bio.myr/
use std pkg bio = type mode = int const Rd : mode = 1 const Wr : mode = 2 const Rw : mode = 1 | 2 type file = struct /* backing fd */ fd : std.fd mode : mode /* read buffer */ rbuf : byte[:] rstart : std.size rend : std.size /* write buffer */ wbuf : byte[:] wend : std.size ;; /* creation */ const mkfile : (fd : std.fd, mode : mode -> file#) const open : (path : byte[:], mode : mode -> std.result(file#, byte[:])) const dial : (srv : byte[:], mode : mode -> std.result(file#, byte[:])) const create : (path : byte[:], mode : mode, perm : int -> std.result(file#, byte[:])) 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.size) const read : (f : file#, dst : byte[:] -> std.option(byte[:])) const flush : (f : file# -> bool) /* seeking */ const seek : (f : file#, std.off -> std.off) /* single unit operations */ const putb : (f : file#, b : byte -> std.size) const putc : (f : file#, c : char -> std.size) const getb : (f : file# -> std.option(byte)) const getc : (f : file# -> std.option(char)) /* peeking */ const peekb : (f : file# -> std.option(byte)) const peekc : (f : file# -> std.option(char)) /* delimited read; returns freshly allocated buffer. */ const readln : (f : file# -> std.option(byte[:])) const readto : (f : file#, delim : byte[:] -> std.option(byte[:])) const skipto : (f : file#, delim : byte[:] -> bool) const skipspace : (f : file# -> void) /* formatted i/o */ const put : (f : file#, fmt : byte[:], args : ... -> std.size) /* pkg funcs */ pkglocal const ensureread : (f : file#, n : std.size -> bool) pkglocal const ensurewrite : (f : file#, n : std.size -> bool) ;; const Bufsz = 16*std.KiB const Small = 512 /* Creates a file from an fd, opened in the given mode. */ const mkfile = {fd, mode var f f = std.alloc() f.fd = fd f.mode = mode 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 } /* Opens a file with mode provided. */ const open = {path, mode -> sysopen(path, mode, sysmode(mode), 0o777) } /* Creates a file for the provided path, with opened in the requested mode, with the requested permissions */ const create = {path, mode, perm -> sysopen(path, mode, sysmode(mode) | std.Ocreat | std.Otrunc, perm) } /* dial the server, and open a file using the returned fd */ const dial = {srv, mode match std.dial(srv) | `std.Ok sock: -> `std.Ok mkfile(sock, mode) | `std.Fail m: -> `std.Fail m ;; } /* map from the bio modes to the unix open modes */ const sysmode = {mode match mode | Rd: -> std.Ordonly | Wr: -> std.Owronly | Rw: -> std.Ordwr | _: std.fatal("bio: bad file mode") ;; -> 0 } /* open the file, and return it */ const sysopen = {path, mode, openmode, perm var fd fd = std.openmode(path, openmode, perm castto(int64)) if fd < 0 -> `std.Fail "could not open fd" else -> `std.Ok mkfile(fd, mode) ;; } /* closes a file, flushing it to the output fd */ const close = {f var fd fd = f.fd free(f) -> std.close(fd) == 0 } const free = {f flush(f) if f.mode & Rd != 0 std.slfree(f.rbuf) ;; if f.mode & Wr != 0 std.slfree(f.wbuf) ;; 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 -> src.len else flush(f) -> writebuf(f.fd, 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 n var d var count std.assert(f.mode & Rd != 0, "File is not in read mode") /* * small reads should try to fill, so we don't have to make a * syscall for every read */ if dst.len < Small fill(f, f.rbuf.len - f.rend) ;; /* 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 dst.len > 0 n = std.read(f.fd, d) if n <= 0 goto readdone ;; count += n d = d[n:] ;; :readdone if count > 0 -> `std.Some dst[:count] else -> `std.None ;; } /* flushes f out to the backing fd */ const flush = {f var ret ret = true if f.mode & Wr != 0 ret = (writebuf(f.fd, f.wbuf[:f.wend]) == f.wend) f.wend = 0 ;; -> ret } const seek = {f, off flush(f) -> std.seek(f.fd, off, std.Seekset) } /* writes a single byte to the output stream */ const putb = {f, b ensurewrite(f, 1) f.wbuf[f.wend++] = b -> 1 } /* writes a single character to the output stream, encoded in utf8 */ const putc = {f, c var sz sz = std.charlen(c) ensurewrite(f, sz) std.encode(f.wbuf[f.wend:], c) f.wend += sz -> sz } /* reads a single byte from the input stream */ const getb = {f if ensureread(f, 1) -> `std.Some f.rbuf[f.rstart++] ;; -> `std.None } /* reads a single character from the input stream, encoded in utf8 */ const getc = {f var c if ensurecodepoint(f) c = std.decode(f.rbuf[f.rstart:f.rend]) f.rstart += std.charlen(c) -> `std.Some c ;; -> `std.None } /* ensures we have enough to read a single codepoint in the buffer */ const ensurecodepoint = {f var b var len if !ensureread(f, 1) -> false ;; 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) } /* writes a single integer-like value to the output stream, in little endian format */ generic putle = {f, v : @a::(numeric,integral) for var i = 0; i < sizeof(@a); i++ putb(f, (v & 0xff) castto(byte)) v >>= 8 ;; -> sizeof(@a) } /* writes a single integer-like value to the output stream, in big endian format */ generic putbe = {f, v : @a::(numeric,integral) for var i = sizeof(@a); i != 0; i-- putb(f, ((v >> ((i-1)*8)) & 0xff) castto(byte)) ;; -> sizeof(@a) } /* peeks a single byte from an input stream */ const peekb = {f if !ensureread(f, 1) -> `std.None else -> `std.Some f.rbuf[f.rstart] ;; } /* peeks a single character from a utf8 encoded input stream */ const peekc = {f if !ensurecodepoint(f) -> `std.None else -> `std.Some std.decode(f.rbuf[f.rstart:f.rend]) ;; } /* reads up to a single character delimiter. 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, false) } /* same as readto, but drops the read data. */ const skipto = {f, delim match readdelim(f, delim, true) | `std.Some ret: -> true | `std.None: -> false ;; } const skipspace = {f while true match bio.peekc(f) | `std.Some c: if std.isspace(c) break ;; bio.getc(f) | `std.None: break ;; ;; } /* Same as readto, but the delimiter is always a '\n' */ const readln = {f -> readto(f, "\n") } const readdelim = {f, delim, drop var ret ret = [][:] while true if !ensureread(f, delim.len) if !drop ret = readinto(f, ret, f.rend - f.rstart) ;; if ret.len > 0 -> `std.Some ret else -> `std.None ;; ;; for var i = f.rstart; i < f.rend; i++ if f.rbuf[i] == delim[0] for var j = 0; j < delim.len; j++ if f.rbuf[i + j] != delim[j] goto nextiterread ;; ;; if !drop ret = readinto(f, ret, i - f.rstart) ;; f.rstart += delim.len -> `std.Some ret ;; :nextiterread ;; if !drop ret = readinto(f, ret, f.rend - 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 } /* reads n bytes from the read buffer onto the heap-allocated slice provided. */ const readinto = {f, buf, n var ret std.assert(f.rstart + n <= f.rend, "Reading too much from buffer") ret = std.sljoin(buf, f.rbuf[f.rstart:f.rstart + n]) f.rstart += n -> ret } /* 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 -> flush(f) ;; -> true } /* 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 var cap std.assert(n < f.rbuf.len, "ensured read capacity > buffer size") held = f.rend - f.rstart if n > held /* if we need to shift the slice down to the start, do it */ cap = f.rend - f.rstart if n > (cap + held) std.slcp(f.rbuf[:cap], f.rbuf[f.rstart:f.rend]) f.rstart = 0 f.rend = cap ;; -> fill(f, n) > n else -> true ;; } /* blats a buffer to an fd */ const writebuf = {fd, src var n var count count = 0 while src.len != 0 n = std.write(fd, src) if n <= 0 goto writedone ;; count += n src = src[n:] ;; :writedone -> count } /* Reads as many bytes as possible from the file into the read buffer. */ const fill = {f, min var n var count count = 0 while count < min n = std.read(f.fd, f.rbuf[f.rend:]) if n <= 0 goto filldone ;; count += n f.rend += n ;; :filldone -> count }