ref: 8f6bfd8f7bf8c5ca162bdf49a94570e21e03ba3a
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 : byte[:] -> byte[:]) /* formatted i/o */ const put : (f : file#, fmt : byte[:], args : ... -> std.size) /* FIXME: internal decls leaking out for generics. */ const fill const ensureread ;; 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 f.rbuf = std.slalloc(Bufsz) f.rstart = 0 f.rend = 0 ;; if mode & Wr 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, perm) } /* 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(1, "bio: bad file mode") ;; -> 0 } /* open the file, and return it */ const sysopen = {path, mode, openmode, perm var fd fd = std.open(path, openmode, perm castto(int64)) if fd < 0 -> `std.None else -> `std.Some mkfile(fd, mode) ;; } /* closes a file, flushing it to the output fd */ 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 } /* 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 -> dst[:count] } /* flushes f out to the backing fd */ const flush = {f var ret ret = (writebuf(f.fd, f.wbuf[:f.wend]) == f.wend) f.wend = 0 -> ret } /* 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) -> f.rbuf[f.rstart++] ;; -> -1 } /* 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) -> c ;; -> -1 } /* ensures we have enough to read a single codepoint in the buffer */ const ensurecodepoint = {f var b var len b = peekb(f) 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 -> false ;; -> ensureread(f, len) } /* writes a single integer-like value to the output stream, in little endian format */ 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) } /* writes a single integer-like value to the output stream, in big endian format */ 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) } /* reads a single integer-like value to the output stream, in big endian format */ generic getbe = {f -> @a::(tcnum,tcint,tctest) var ret var i ret = 0 ensureread(f, sizeof(@a)) for i = 0; i < sizeof(@a); i++ ret <<= 8 ret |= (getb(f) castto(@a::(tcnum,tcint,tctest))) ;; -> ret } /* reads a single integer-like value to the output stream, in little endian format */ generic getle = {f -> @a::(tcnum,tcint,tctest) var ret var b var i ret = 0 ensureread(f, sizeof(@a)) for i = 0; i < sizeof(@a); i++ b = getb(f) castto(@a::(tcnum,tcint,tctest)) ret = ret | (b << (8*i)) ;; -> ret } /* peeks a single byte from an input stream */ const peekb = {f ensureread(f, 1) -> f.rbuf[f.rstart] } /* peeks a single character from a utf8 encoded input stream */ const peekc = {f if !ensurecodepoint(f) -> -1 ;; -> 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 var ret : byte[:] var i, j ret = [][:] while true if !ensureread(f, delim.len) -> readinto(f, ret, f.rend - f.rstart) ;; for i = f.rstart; i < f.rend; i++ if f.rbuf[i] == delim[0] for j = 0; j < delim.len; j++ if f.rbuf[i + j] != delim[j] goto nextiter ;; ;; ret = readinto(f, ret, i - f.rstart) f.rstart += delim.len -> ret ;; :nextiter ;; ret = readinto(f, ret, i - f.rstart) ;; -> ret } /* Same as readto, but the delimiter is always a '\n' */ const readln = {f -> readto(f, "\n") } /* 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 buf : byte[2048] var n n = std.bfmt(buf[:], fmt, std.vastart(&args)) -> write(f, buf[: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) ;; } /* 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 }