ref: cfa84e404def22f3bf7ccb8f0551b864ae08d926
dir: /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.option(file#)) const create : (path : byte[:], mode : mode, perm : int -> std.option(file#)) const close : (f : file# -> bool) /* basic i/o. Returns sub-buffer when applicable. */ const write : (f : file#, src : byte[:] -> std.size) const read : (f : file#, dst : byte[:] -> byte[:]) const flush : (f : file# -> bool) /* single unit operations */ const putb : (f : file#, b : byte -> std.size) const putc : (f : file#, c : char -> std.size) const getb : (f : file# -> byte) const getc : (f : file# -> char) /* typed binary reads */ generic putbe : (f : file#, v : @a::(tctest,tcnum,tcint) -> std.size) generic putle : (f : file#, v : @a::(tctest,tcnum,tcint) -> std.size) generic getbe : (f : file# -> @a::(tctest,tcnum,tcint)) generic getle : (f : file# -> @a::(tctest,tcnum,tcint)) /* peeking */ const peekb : (f : file# -> byte) const peekc : (f : file# -> char) /* delimited read; returns freshly allocated buffer. */ const readln : (f : file# -> byte[:]) const readto : (f : file#, delim : char -> byte[:]) const readtob : (f : file#, delim : byte -> byte[:]) /* formatted i/o */ const put : (f : file#, fmt : byte[:], args : ... -> std.size) const fill : (f : file#, sz : std.size -> std.size) ;; const Bufsz = 16*1024 /* 16k */ const Small = 512 const mkfile = {fd, mode var f f = std.alloc() f.fd = fd f.mode = mode if mode & Rd f.rbuf = std.slalloc(Bufsz) f.rstart = 0 f.rend = 0 ;; if mode & Wr f.wbuf = std.slalloc(Bufsz) f.wend = 0 ;; -> f } const open = {path, mode -> create(path, mode, 0o777) } const create = {path, mode, perm var openmode var fd if mode == Rd openmode = std.Ordonly elif mode == Wr openmode = std.Owronly elif mode == Rw openmode = std.Ordwr ;; openmode |= std.Ocreat fd = std.open(path, openmode, perm castto(int64)) if fd < 0 -> `std.None else -> `std.Some mkfile(fd, mode) ;; } const close = {f flush(f) if f.mode & Rd std.slfree(f.rbuf) ;; if f.mode & Wr std.slfree(f.wbuf) ;; -> std.close(f.fd) == 0 } 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) ;; } 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 -> dst[:count] } const flush = {f var ret ret = (writebuf(f.fd, f.wbuf[:f.wend]) == f.wend) f.wend = 0 -> ret } const putb = {f, b ensurewrite(f, 1) f.wbuf[f.wend++] = b -> 1 } const putc = {f, c var sz sz = std.charlen(c) ensurewrite(f, sz) std.encode(f.wbuf[f.wend:], c) f.wend += sz -> sz } const getb = {f if ensureread(f, 1) -> f.rbuf[f.rstart++] ;; -> -1 } const getc = {f var c if ensureread(f, std.Maxcharlen) c = std.decode(f.rbuf) f.rstart += std.charlen(c) -> c ;; -> -1 } generic putle = {f, v : @a::(tcnum,tcint,tctest) var i for i = 0; i < sizeof(@a); i++ putb(f, (v & 0xff) castto(byte)) v >>= 8 ;; -> sizeof(@a) } generic putbe = {f, v : @a::(tcnum,tcint,tctest) var i for i = sizeof(@a); i != 0; i-- putb(f, ((v >> (i - 1)*8) & 0xff) castto(byte)) ;; -> sizeof(@a) } generic getbe = {f -> @a::(tcnum,tcint,tctest) var ret var i ret = 0 for i = 0; i < sizeof(@a); i++ ret <<= 8 ret |= (getb(f) castto(@a::(tcnum,tcint,tctest))) ;; -> ret } generic getle = {f -> @a::(tcnum,tcint,tctest) var ret var i ret = 0 for i = 0; i < sizeof(@a); i++ ret = ret | ((getb(f) << 8*i) castto(@a::(tcnum,tcint,tctest))) ;; -> ret } const peekb = {f ensureread(f, 1) -> f.rbuf[f.rstart] } const peekc = {f ensureread(f, std.Maxcharlen) -> std.decode(f.rbuf) } const readto = {f, c var buf : byte[4] var srch var ret : byte[:] var i srch = buf[:std.encode(buf[:], c)] if srch.len == 0 -> [][:] ;; ret = [][:] while true if !ensureread(f, srch.len) -> readappend(f, ret, f.rend - f.rstart) ;; for i = f.rstart; i < f.rend; i++ if f.rbuf[i] == srch[0] && std.sleq(f.rbuf[i + 1:i + srch.len - 1], srch[1:]) -> readappend(f, ret, i - f.rstart) ;; ;; ret = readappend(f, ret, i - f.rstart) ;; -> ret } const readtob = {f, b var ret var i i = 0 ret = std.slalloc(0) while true fill(f, 1) if !ensureread(f, 1) -> readappend(f, ret, 0) ;; for i = f.rstart; i < f.rend; i++ if f.rbuf[i] == b ret = readappend(f, ret, i - f.rstart) /* skip past the recognized byte */ f.rstart += 1 -> ret ;; ;; ret = readappend(f, ret, i - f.rstart) ;; } const readln = {f -> readto(f, '\n') } const put = {f, fmt, args std.fatal(1, "Formatted put on BIO unimplemented") -> 0 } /* appends n bytes from the read buffer onto the heap-allocated slice provided */ const readappend = {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) ;; } /* 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.rbuf.len - f.rend 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 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 }