summaryrefslogtreecommitdiff
path: root/doc/api/libbio/index.txt
blob: c0099b4300e371987f6152bb1ebb4c874c6f4876 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
{
        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;
                    ;;
            ;;
    }