shithub: mc

ref: d0ffb958c7247028223c7e53253249d52a47a324
dir: /doc/api/libbio/index.txt/

View raw version
{
        title:  libbio
        description:    BIO library description
}

Myrddin's BIO library is used for buffered input and output. It is a fairly
simple library that handles reading and writing from file descriptors.

The concepts in libbio should be familiar to anyone that has used a buffered
IO library in most languages. The usual concepts are supported: Files,
formatted output, binary and ascii reads, and so on. There are also some
utility functions to deal with reading integers in a known endianness.

One thing to keep in mind with libbio that it does not attempt to flush
buffers on program exit, which means that any unwritten, unflushed data
will be lost. Closing the bio files will prevent this issue.

Summary
-------

    pkg bio =
            type mode
            const Rd	: mode
            const Wr	: mode
            const Rw	: mode

            type file = struct
            ;;

	    type lineiter

            type status(@a) = union
                    `Eof
                    `Ok @a
                    `Err ioerr
            ;;

            type ioerr = union
                    `Ebadfile
                    `Ebadbuf
                    `Ebadfd
                    `Eioerr
            ;;

	    impl iterable lineiter

            /* 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[:]	-> status(std.size))
            const read	: (f : file#, dst : byte[:]	-> status(byte[:]))
            const flush	: (f : file# -> bool)

            /* seeking */
            const seek	: (f : file#, std.off -> std.result(std.off, ioerr))

            /* single unit operations */
            const putb	: (f : file#, b : byte	-> status(std.size))
            const putc	: (f : file#, c : char	-> status(std.size))
            const getb	: (f : file# -> status(byte))
            const getc	: (f : file# -> status(char))

            /* peeking */
            const peekb	: (f : file# -> status(byte))
            const peekc	: (f : file# -> status(char))

            /* delimited read; returns freshly allocated buffer. */
            const readln	: (f : file#	-> status(byte[:]))
            const readto	: (f : file#, delim : byte[:]	-> status(byte[:]))
            const skipto	: (f : file#, delim : byte[:]	-> bool)
            const skipspace	: (f : file# -> bool)

            /* iterators */
            const lineiter	: (f : file# -> lineiter)

            /* formatted i/o */
            const put	: (f : file#, fmt : byte[:], args : ... -> status(std.size))

            /* unsigned big endian reads */
            generic getbe8	: (f : file# -> status(@a::(numeric,integral)))
            generic getbe16	: (f : file# -> status(@a::(numeric,integral)))
            generic getbe32	: (f : file# -> status(@a::(numeric,integral)))
            generic getbe64	: (f : file# -> status(@a::(numeric,integral)))

            /* signed big endian reads */
            generic getle8	: (f : file# -> status(@a::(numeric,integral)))
            generic getle16	: (f : file# -> status(@a::(numeric,integral)))
            generic getle32	: (f : file# -> status(@a::(numeric,integral)))
            generic getle64	: (f : file# -> status(@a::(numeric,integral)))

            /* unsigned big endian */
            generic putbe8	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
            generic putbe16	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
            generic putbe32	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
            generic putbe64	: (f : file#, v : @a::(numeric,integral) -> status(std.size))

            /* unsigned little endian */
            generic putle8	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
            generic putle16	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
            generic putle32	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
            generic putle64	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
    ;;

The Data Structures
-------------------

    type file = struct
            fd  : std.fd
    ;;

The `bio.file` type contains the state required to implement buffered files.
All of the state is internal, except for the file descriptor, which is exposed
for the purposes of poll() and friends. Reading from it directly may lead to
inconsistent buffer state, and is strongly not recommended.

    type ioerr = union
            `Ebadfile
            `Ebadbuf
            `Ebadfd
            `Eioerr
    ;;

    type status(@a) = union
            `Eof
            `Ok @a
            `Err ioerr
    ;;

The `bio.status(@a)` union returns the result of the read operation, which
is going to either be a result of type '@a', an error-free end of file, or
an error of type `ioerr`. All of these conditions are transient, and are
retried on subsequent calls to the IO operations. For example, if reading
a file returns `Eof, but data is appended to a file, then a subsequent read
will return data again.

In general, errors will be queued up, so if there is a buffered read or
write that partly succeeds but is interrupted halfway, the error will be
reported on the next operation. This way, partial operations are not lost.

File Creation and Opening
-------------------------

    const Rd	: mode
    const Wr	: mode
    const Rw	: mode

When opening a file, one of the above flags must be passed to set the mode
of the file. `Rd` indicates that the bio file should be read only, `Wr`
indicates that it should be write only, and `Rw` indicates that it should be
both readable and writable.

Bio file creation
-------------

    const mkfile	: (fd : std.fd, mode : mode	-> file#)

This function creates a bio file from a file descriptor and mode, returning a
`bio.file#` which will buffer reads and/or writes from the fd. This function
assumes that you are passing it a correctly initialized fd, and will always
succeed. If the FD is incorrectly configured, uses of it will error out.

Returns: A buffered file wrapping the file descriptor `fd`

    const open	: (path : byte[:], mode : mode	-> std.result(file#, byte[:]))

This function attempts to open the path passed in with the mode, returning
a result which is either a file, or a string representing the error that
prevented the file from being opened.

Returns: A buffered file representing `path` opened with the requested
permissions.

    const dial	: (srv	: byte[:], mode : mode	-> std.result(file#, byte[:]))

This function is similar to open, however, this function will open a
connection via a dialstring (as in `std.dial`), and buffer the fd returned
from that.

Returns: A buffered file representing `dialstr` opened with the requested
permissions.

    const create	: (path : byte[:], mode : mode, perm : int	-> std.result(file#, byte[:]))

This function is similar to open, however, this function will attempt to
atomically create and open the file descriptor.

Returns: A buffered file representing `path` opened with the requested
permissions.

    const close	: (f : file# -> bool)

Closes the file descriptor that the bio file is wrapping, and frees any
resources used for buffering it. Any data that has not yet been written is
flushed.

Returns: `true` if flushing the file succeeded, `false` if it failed.

    const free	: (f : file# -> void)

Frees any resources used for the file descriptor, but leaves it open. This is
useful if the file descriptor has been 'stolen' using bio.mkfile, and is not
owned by the bio file.

    const flush	: (f : file# -> bool)

Clears any data that has not been sent to the backing file descriptor.

Returns: `true` if flushing the file succeeded, `false` if it failed.

Binary I/O
-----------

    const write	: (f : file#, src : byte[:]	-> result(std.size, byte[:]))

Writes bytes from the buffer `src` to a bio file, returning the number of
bytes written in the case of success. This number may be smaller than the size
of the buffer, but will never be larger.

    const read	: (f : file#, dst : byte[:]	-> result(byte[:]))

Reads from a bio file, into the buffer 'dst', returning the section of the
buffer that was read in the result.

	const seek	: (f : file#, std.off -> std.result(std.off, ioerr))

Seeks the bio file to the given absolute offset.

    const flush	: (f : file# -> bool)

Flush attempts to clear the buffers within `f`, writing everything within
the buffers and discarding them. If writing fails, `false` is returned.


Single Unit Operations
----------------------

        /* single unit operations */
    const putb	: (f : file#, b : byte	-> status(std.size))

Writes a single byte out to the file 'f', returning a status. If
it is successful, the return value represents the number of bytes written.
For single byte writes, this value is unsurprisingly always 1.

    const putc	: (f : file#, c : char	-> status(std.size))

Writes a single unicode character out to the file 'f', returning a status.
This character is encoded in utf-8. If it is successful, the return value
represents the number of bytes written, varying between 1 and 4.

    const getb	: (f : file# -> status(byte))

Reads a single byte from the file `f`, returning a status. If this read
succeeds, the next byte in the file is returned.

    const getc	: (f : file# -> status(char))

Reads a unicode character from the file `f`, returning a status. If this read
was successful, the next character is returned from the file. The file is
assumed to be encoded in utf-8.

    /* peeking */
    const peekb	: (f : file# -> status(byte))
    const peekc	: (f : file# -> status(char))

Both peekb and peekc are similar to getb and getc respectively, although
they return the value without advancing the position within the buffer.


Delimited Operations
--------------------

    /* delimited read; returns freshly allocated buffer. */
    const readln	: (f : file#	-> status(byte[:]))

Readln reads a single line of input from the file 'f', returning the line
with the line ending characters removed. '\n', '\r', and '\r\n' are all
accepted as valid line endings, and are treated interchangably when reading.

The buffer is heap allocated, and must be freed with std.slfree

    const readto	: (f : file#, delim : byte[:]	-> status(byte[:]))

Readto is similar to readln, but instead of reading to a line ending, it will
read up to the point where it finds the requested delimiter. The delimiter is
an arbitrary sequence of bytes. If an end of file is reached, then the buffer
up to the Eof is returned.

The buffer is heap allocated, and must be freed with std.slfree

    const skipto	: (f : file#, delim : byte[:]	-> bool)

Skipto is identical to readto, but instead of allocating a buffer and reading
into it, skipto will ignore the values and drop them. It returns true for
any successful reads, and false if an error was encountered.

    const skipspace	: (f : file# -> bool)

Skipspace will consume any space tokens from the start of the input stream, It
returns true for any successful reads, and false if an error was encountered.

Iteration
---------

    const lineiter	: (f : file# -> lineiter)

Lineiter will return an interable type that will iterate through each line
in a file. These lines are allocated on the heap, and are automatically freed
at the end of the iteration.

The returned iterator object is a value type, and does not need to be freed.

Formatted IO
-------------

    const put	: (f : file#, fmt : byte[:], args : ... -> status(std.size))

This formats the output using the std.fmt api, and writes it to the file `f`.
All custom formatters installed for `std.fmt` are used for this formatting.
This call returns the number of bytes written on success.

Endian Aware Reads
------------------

    generic getbe8	: (f : file# -> status(@a::(numeric,integral)))
    generic getbe16	: (f : file# -> status(@a::(numeric,integral)))
    generic getbe32	: (f : file# -> status(@a::(numeric,integral)))
    generic getbe64	: (f : file# -> status(@a::(numeric,integral)))

    generic getle8	: (f : file# -> status(@a::(numeric,integral)))
    generic getle16	: (f : file# -> status(@a::(numeric,integral)))
    generic getle32	: (f : file# -> status(@a::(numeric,integral)))
    generic getle64	: (f : file# -> status(@a::(numeric,integral)))

The functions above all read from a bio file, and return the value read on
success. The 'be' variants read big endian values, and the `le' variants
read little endian values. They final result is converted to whatever
numeric, integral type is desired. If the value does not fit within the bounds
of the type, it is truncated.

The number of bytes read is determined by the number at the end of the
function call. For eample, `getbe8()` will read 8 bits, or one byte from the
stream. `getle64` will get a 64 bit, or 8 byte, value. The number of bytes
consumed is independent of the size of the type being assigned to.

    generic putbe8	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
    generic putbe16	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
    generic putbe32	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
    generic putbe64	: (f : file#, v : @a::(numeric,integral) -> status(std.size))

    generic putle8	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
    generic putle16	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
    generic putle32	: (f : file#, v : @a::(numeric,integral) -> status(std.size))
    generic putle64	: (f : file#, v : @a::(numeric,integral) -> status(std.size))

The functions above all write to a bio file, and return the number of bytes
written on success. The number of bytes written will always be the size of the
type. The 'be' variants write the value in a big endian representation, and
the 'le' variants write it in a little endian representation.

The number of bytes written is determined by the number at the end of the
function call. For eample, `putbe8()` will write 8 bits, or one byte.
`putbe64` will write 64 bits, or 8 bytes. The number of
bytes consumed is independent of the size of the type being assigned to.

Examples
---------

The example below is the simplest program that creates and opens a file, and
writes to it. It creates it with 0o644 permissions (ie, rw-r--r--), and then
immediately closes it. The result of this program should be a file called
`create-example` containing the words.

    use std
    use bio

    const main = {
            var f

            match bio.create("create-example", bio.Wr, 0o644)
            | `std.Some bio:    f = bio
            | `std.None:        std.fatal(1, "Failed to open file\n")
            ;;
            bio.write(f, "Hello user\n")
            bio.close(f)
    }

The next example shows reading from a file called "lines", line by line. It
should echo the lines, numbering them as it prints them:

    use std
    use bio

    const main = {
            var f
            var i

            match bio.open("lines", bio.Rd)
            | `std.Some bio:        f = bio
            | `std.None:    std.fatal(1, "Unable to open data file\n")
            ;;

            while true
                    match bio.readlin(f)
                    | `std.Some ln:
                            std.put("line %i: %s\n", i, ln)
                    | `std.None:
                            break;
                    ;;
            ;;
    }