#pragma once typedef struct ios_loc ios_loc;typedef struct ios ios; // this flag controls when data actually moves out to the underlying I/O// channel. in-memory io is a special case of this where the data// never moves out.typedef enum { IOS_BUF_NONE, IOS_BUF_LINE, IOS_BUF_BLOCK, IOS_BUF_MEM,}sl_packed ios_buf_mode; typedef enum { IOS_STATE_NONE, IOS_STATE_RD, IOS_STATE_WR, IOS_STATE_CLOSED,}sl_packed ios_state; typedef enum { IOS_CREATE = 1<<0, IOS_READ = 1<<1, IOS_WRITE = 1<<2, IOS_TRUNC = 1<<3, IOS_APPEND = 1<<4, IOS_EXCL = 1<<5, IOS_DIR = 1<<6, IOS_RCLOSE = 1<<7, IOS_RDWR = IOS_READ|IOS_WRITE,}sl_enum_flags sl_packed ios_flags; typedef enum { IOS_FL_RDONLY = 1<<0, IOS_FL_WRONLY = 1<<1, IOS_FL_OWNBUF = 1<<2, IOS_FL_OWNFD = 1<<3, IOS_FL_EOF = 1<<4, // this means you can read, seek back, then read the same data // again any number of times. usually only true for files and in-memory buffers. IOS_FL_REREADABLE = 1<<5,}sl_enum_flags sl_packed ios_state_flags; enum { IOS_INLSIZE = 128+2, // on Windows XP read with 32K buffer might return ERROR_NOT_ENOUGH_MEMORY IOS_BUFSIZE = 16384,}; struct ios_loc { u32int lineno; u32int colno;}; struct ios { u8int *buf; // start of buffer usize maxsize; // space allocated to buffer usize size; // length of valid data in buf, >=ndirty usize bpos; // current position in buffer usize ndirty; // # bytes at &buf[0] that need to be written char *filename; sl_v data; int fd; ios_loc loc; // the state only indicates where the underlying file position is relative // to the buffer. reading: at the end. writing: at the beginning. // in general, you can do any operation in any state. ios_state state; ios_state_flags flags; ios_buf_mode bm; u8int colnowait; union { u8int local[IOS_INLSIZE]; struct { u64int tmp; usize start; usize end; }; };}; void *sl_memrchr(const void *s, int c, usize n) sl_rd(1, 3) sl_purefn; /* low-level interface functions */usize ios_read(ios *s, void *dest, usize n) sl_nonnull(1, 2) sl_wr(2, 3);usize ios_peek(ios *s, void *dest, usize n) sl_nonnull(1, 2) sl_wr(2, 3);usize ios_write(ios *s, const void *data, usize n) sl_nonnull(1, 2) sl_rd(2, 3);soffset ios_seek(ios *s, soffset pos) sl_nonnull(1); // absolute seeksoffset ios_seek_end(ios *s) sl_nonnull(1);soffset ios_skip(ios *s, soffset offs, bool end) sl_nonnull(1); // relative seekint ios_trunc(ios *s, soffset size) sl_nonnull(1);int ios_flush(ios *s) sl_nonnull(1);void ios_close(ios *s) sl_nonnull(1);void ios_free(ios *s) sl_nonnull(1);usize ios_readprep(ios *s, usize n) sl_nonnull(1);u8int *ios_takebuf(ios *s, usize *psize) sl_nonnull(1, 2); // null-terminate and release buffer to caller// set buffer space to useint ios_setbuf(ios *s, u8int *buf, usize size, bool own) sl_nonnull(1, 2);int ios_bufmode(ios *s, ios_buf_mode mode) sl_nonnull(1);void ios_set_readonly(ios *s) sl_nonnull(1);usize ios_copy(ios *to, ios *from, usize nbytes) sl_nonnull(1, 2);usize ios_copyall(ios *to, ios *from) sl_nonnull(1, 2);usize ios_copyuntil(ios *to, ios *from, Rune delim) sl_nonnull(1, 2); /* io creation */ios *ios_file(ios *s, const char *fname, ios_flags flags, usize mode) sl_nonnull(1, 2) sl_cstr(2);ios *ios_mem(ios *s, usize initsize) sl_nonnull(1) sl_returns_nonnull;ios *ios_pipe(ios *s, ios_flags flags, int *otherside) sl_nonnull(1, 3) sl_returns_nonnull;ios *ios_static_buffer(ios *s, const u8int *buf, usize sz) sl_nonnull(1, 2) sl_returns_nonnull sl_rd(2, 3);ios *ios_fd(ios *s, int fd, const char *name, bool isfile, bool own) sl_nonnull(1, 3) sl_returns_nonnull sl_cstr(3) sl_fd(2); extern ios *ios_stdin;extern ios *ios_stdout;extern ios *ios_stderr; void ios_init_std(void); /* high-level functions - output */int ios_putrune(ios *s, Rune r) sl_nonnull(1);int ios_printf(ios *s, const char *format, ...) sl_printfmt(2, 3) sl_nonnull(1, 2);int ios_vprintf(ios *s, const char *format, va_list args) sl_printfmt(2, 0) sl_nonnull(1, 2); /* high-level io functions - input */int ios_peekrune(ios *s, Rune *r) sl_nonnull(1, 2);int ios_getrune(ios *s, Rune *r) sl_nonnull(1, 2); // discard data buffered for readingvoid ios_purge(ios *s) sl_nonnull(1); /* stdio-style functions */#define IOS_EOF (-1)int ios_putc(ios *s, int c) sl_nonnull(1);int ios_getc(ios *s) sl_nonnull(1);int ios_peekc(ios *s) sl_nonnull(1);#define ios_puts(s, str) ios_write(s, str, strlen(str)) /* With in-memory io, mixed reads and writes are equivalent to performing sequences of *p++, as either an lvalue or rvalue. File ios behave similarly, but other ios might not support this. Using unbuffered mode makes this more predictable. Note on "unget" functions: There are two kinds of functions here: those that operate on sized blocks of bytes and those that operate on logical units like "character" or "integer". The "unget" functions only work on logical units. There is no "unget n bytes". You can only do an unget after a matching get. However, data pushed back by an unget is available to all read operations. The reason for this is that unget is defined in terms of its effect on the underlying buffer (namely, it rebuffers data as if it had been buffered but not read yet). IOS reserves the right to perform large block operations directly, bypassing the buffer. In such a case data was never buffered, so "rebuffering" has no meaning (i.e. there is no correspondence between the buffer and the physical io). Single-bit I/O is able to write partial bytes ONLY IF the io supports seeking. Also, line buffering is not well-defined in the context of single-bit I/O, so it might not do what you expect. implementation notes: in order to know where we are in a file, we must ensure the buffer is only populated from the underlying io starting with p==buf. to switch from writing to reading: flush, set p=buf, cnt=0 to switch from reading to writing: seek backwards cnt bytes, p=buf, cnt=0 when writing: buf starts at curr. physical io pos, p - buf is how many bytes we've written logically. cnt==0 dirty == (bitpos>0 && state==iost_wr), EXCEPT right after switching from reading to writing, where we might be in the middle of a byte without having changed it. to write a bit: if !dirty, read up to maxsize-(p-buf) into buffer, then seek back by the same amount (undo it). write onto those bits. now set the dirty bit. in this state, we can bit-read up to the end of the byte, then formally switch to the read state using flush. design points: - data-source independence, including in-memory io - expose buffer to user, allow user-owned buffers - allow direct I/O, don't always go through buffer - buffer-internal seeking. makes seeking back 1-2 bytes very fast, and makes it possible for sockets where it otherwise wouldn't be - tries to allow switching between reading and writing - support 64-bit and large files - efficient, low-latency buffering - special support for utf8 - type-aware functions with byte-order swapping service - position counter for meaningful data offsets with sockets theory of operation: the buffer is a view of part of a file/stream. you can seek, read, and write around in it as much as you like, as if it were just a string. we keep track of the part of the buffer that's invalid (written to). we remember whether the position of the underlying io is aligned with the end of the buffer (reading mode) or the beginning (writing mode). based on this info, we might have to seek back before doing a flush. as optimizations, we do no writing if the buffer isn't "dirty", and we do no reading if the data will only be overwritten.*/
#pragma once
typedef struct ios_loc ios_loc;
typedef struct ios ios;
// this flag controls when data actually moves out to the underlying I/O
// channel. in-memory io is a special case of this where the data
// never moves out.
typedef enum {
IOS_BUF_NONE,
IOS_BUF_LINE,
IOS_BUF_BLOCK,
IOS_BUF_MEM,
}sl_packed ios_buf_mode;
typedef enum {
IOS_STATE_NONE,
IOS_STATE_RD,
IOS_STATE_WR,
IOS_STATE_CLOSED,
}sl_packed ios_state;
typedef enum {
IOS_CREATE = 1<<0,
IOS_READ = 1<<1,
IOS_WRITE = 1<<2,
IOS_TRUNC = 1<<3,
IOS_APPEND = 1<<4,
IOS_EXCL = 1<<5,
IOS_DIR = 1<<6,
IOS_RCLOSE = 1<<7,
IOS_RDWR = IOS_READ|IOS_WRITE,
}sl_enum_flags sl_packed ios_flags;
typedef enum {
IOS_FL_RDONLY = 1<<0,
IOS_FL_WRONLY = 1<<1,
IOS_FL_OWNBUF = 1<<2,
IOS_FL_OWNFD = 1<<3,
IOS_FL_EOF = 1<<4,
// this means you can read, seek back, then read the same data
// again any number of times. usually only true for files and in-memory buffers.
IOS_FL_REREADABLE = 1<<5,
}sl_enum_flags sl_packed ios_state_flags;
enum {
IOS_INLSIZE = 128+2,
// on Windows XP read with 32K buffer might return ERROR_NOT_ENOUGH_MEMORY
IOS_BUFSIZE = 16384,
};
struct ios_loc {
u32int lineno;
u32int colno;
};
struct ios {
u8int *buf; // start of buffer
usize maxsize; // space allocated to buffer
usize size; // length of valid data in buf, >=ndirty
usize bpos; // current position in buffer
usize ndirty; // # bytes at &buf[0] that need to be written
char *filename;
sl_v data;
int fd;
ios_loc loc;
// the state only indicates where the underlying file position is relative
// to the buffer. reading: at the end. writing: at the beginning.
// in general, you can do any operation in any state.
ios_state state;
ios_state_flags flags;
ios_buf_mode bm;
u8int colnowait;
union {
u8int local[IOS_INLSIZE];
struct {
u64int tmp;
usize start;
usize end;
};
};
};
void *sl_memrchr(const void *s, int c, usize n) sl_rd(1, 3) sl_purefn;
/* low-level interface functions */
usize ios_read(ios *s, void *dest, usize n) sl_nonnull(1, 2) sl_wr(2, 3);
usize ios_peek(ios *s, void *dest, usize n) sl_nonnull(1, 2) sl_wr(2, 3);
usize ios_write(ios *s, const void *data, usize n) sl_nonnull(1, 2) sl_rd(2, 3);
soffset ios_seek(ios *s, soffset pos) sl_nonnull(1); // absolute seek
soffset ios_seek_end(ios *s) sl_nonnull(1);
soffset ios_skip(ios *s, soffset offs, bool end) sl_nonnull(1); // relative seek
int ios_trunc(ios *s, soffset size) sl_nonnull(1);
int ios_flush(ios *s) sl_nonnull(1);
void ios_close(ios *s) sl_nonnull(1);
void ios_free(ios *s) sl_nonnull(1);
usize ios_readprep(ios *s, usize n) sl_nonnull(1);
u8int *ios_takebuf(ios *s, usize *psize) sl_nonnull(1, 2); // null-terminate and release buffer to caller
// set buffer space to use
int ios_setbuf(ios *s, u8int *buf, usize size, bool own) sl_nonnull(1, 2);
int ios_bufmode(ios *s, ios_buf_mode mode) sl_nonnull(1);
void ios_set_readonly(ios *s) sl_nonnull(1);
usize ios_copy(ios *to, ios *from, usize nbytes) sl_nonnull(1, 2);
usize ios_copyall(ios *to, ios *from) sl_nonnull(1, 2);
usize ios_copyuntil(ios *to, ios *from, Rune delim) sl_nonnull(1, 2);
/* io creation */
ios *ios_file(ios *s, const char *fname, ios_flags flags, usize mode) sl_nonnull(1, 2) sl_cstr(2);
ios *ios_mem(ios *s, usize initsize) sl_nonnull(1) sl_returns_nonnull;
ios *ios_pipe(ios *s, ios_flags flags, int *otherside) sl_nonnull(1, 3) sl_returns_nonnull;
ios *ios_static_buffer(ios *s, const u8int *buf, usize sz) sl_nonnull(1, 2) sl_returns_nonnull sl_rd(2, 3);
ios *ios_fd(ios *s, int fd, const char *name, bool isfile, bool own) sl_nonnull(1, 3) sl_returns_nonnull sl_cstr(3) sl_fd(2);
extern ios *ios_stdin;
extern ios *ios_stdout;
extern ios *ios_stderr;
void ios_init_std(void);
/* high-level functions - output */
int ios_putrune(ios *s, Rune r) sl_nonnull(1);
int ios_printf(ios *s, const char *format, ...) sl_printfmt(2, 3) sl_nonnull(1, 2);
int ios_vprintf(ios *s, const char *format, va_list args) sl_printfmt(2, 0) sl_nonnull(1, 2);
/* high-level io functions - input */
int ios_peekrune(ios *s, Rune *r) sl_nonnull(1, 2);
int ios_getrune(ios *s, Rune *r) sl_nonnull(1, 2);
// discard data buffered for reading
void ios_purge(ios *s) sl_nonnull(1);
/* stdio-style functions */
#define IOS_EOF (-1)
int ios_putc(ios *s, int c) sl_nonnull(1);
int ios_getc(ios *s) sl_nonnull(1);
int ios_peekc(ios *s) sl_nonnull(1);
#define ios_puts(s, str) ios_write(s, str, strlen(str))
/*
With in-memory io, mixed reads and writes are equivalent to performing
sequences of *p++, as either an lvalue or rvalue. File ios behave
similarly, but other ios might not support this. Using unbuffered
mode makes this more predictable.
Note on "unget" functions:
There are two kinds of functions here: those that operate on sized
blocks of bytes and those that operate on logical units like "character"
or "integer". The "unget" functions only work on logical units. There
is no "unget n bytes". You can only do an unget after a matching get.
However, data pushed back by an unget is available to all read operations.
The reason for this is that unget is defined in terms of its effect on
the underlying buffer (namely, it rebuffers data as if it had been
buffered but not read yet). IOS reserves the right to perform large block
operations directly, bypassing the buffer. In such a case data was
never buffered, so "rebuffering" has no meaning (i.e. there is no
correspondence between the buffer and the physical io).
Single-bit I/O is able to write partial bytes ONLY IF the io supports
seeking. Also, line buffering is not well-defined in the context of
single-bit I/O, so it might not do what you expect.
implementation notes:
in order to know where we are in a file, we must ensure the buffer
is only populated from the underlying io starting with p==buf.
to switch from writing to reading: flush, set p=buf, cnt=0
to switch from reading to writing: seek backwards cnt bytes, p=buf, cnt=0
when writing: buf starts at curr. physical io pos, p - buf is how
many bytes we've written logically. cnt==0
dirty == (bitpos>0 && state==iost_wr), EXCEPT right after switching from
reading to writing, where we might be in the middle of a byte without
having changed it.
to write a bit: if !dirty, read up to maxsize-(p-buf) into buffer, then
seek back by the same amount (undo it). write onto those bits. now set
the dirty bit. in this state, we can bit-read up to the end of the byte,
then formally switch to the read state using flush.
design points:
- data-source independence, including in-memory io
- expose buffer to user, allow user-owned buffers
- allow direct I/O, don't always go through buffer
- buffer-internal seeking. makes seeking back 1-2 bytes very fast,
and makes it possible for sockets where it otherwise wouldn't be
- tries to allow switching between reading and writing
- support 64-bit and large files
- efficient, low-latency buffering
- special support for utf8
- type-aware functions with byte-order swapping service
- position counter for meaningful data offsets with sockets
theory of operation:
the buffer is a view of part of a file/stream. you can seek, read, and
write around in it as much as you like, as if it were just a string.
we keep track of the part of the buffer that's invalid (written to).
we remember whether the position of the underlying io is aligned
with the end of the buffer (reading mode) or the beginning (writing mode).
based on this info, we might have to seek back before doing a flush.
as optimizations, we do no writing if the buffer isn't "dirty", and we
do no reading if the data will only be overwritten.
*/