core: add b_stream interface
b_stream represents a character-based I/O stream. it is an abstract interface that can be implemented by any other object, allowing the object to be interacted with via the b_stream api.
This commit is contained in:
553
core/stream.c
Normal file
553
core/stream.c
Normal file
@@ -0,0 +1,553 @@
|
||||
#include "printf.h"
|
||||
|
||||
#include <blue/core/stream.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define IDX_STDIN 0
|
||||
#define IDX_STDOUT 1
|
||||
#define IDX_STDERR 2
|
||||
|
||||
static b_status stdio_read(
|
||||
struct b_stream *stream, unsigned char *out, size_t max, size_t *nr_read)
|
||||
{
|
||||
FILE *fp = stream->s_ptr;
|
||||
size_t count = 0;
|
||||
enum b_status status = B_SUCCESS;
|
||||
|
||||
for (size_t i = 0; i < max; i++) {
|
||||
int c = fgetc(fp);
|
||||
if (ferror(fp)) {
|
||||
status = B_ERR_IO_FAILURE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
out[count++] = c;
|
||||
}
|
||||
|
||||
*nr_read = count;
|
||||
return status;
|
||||
}
|
||||
|
||||
static b_status stdio_write(
|
||||
struct b_stream *stream, const unsigned char *data, size_t count,
|
||||
size_t *nr_written)
|
||||
{
|
||||
FILE *fp = stream->s_ptr;
|
||||
enum b_status status = B_SUCCESS;
|
||||
size_t w = 0;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
fputc(data[i], fp);
|
||||
|
||||
if (ferror(fp)) {
|
||||
status = B_ERR_IO_FAILURE;
|
||||
break;
|
||||
}
|
||||
|
||||
w++;
|
||||
}
|
||||
|
||||
*nr_written = w;
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct b_stream stdio[] = {
|
||||
[IDX_STDIN] = {
|
||||
.s_mode = B_STREAM_READ,
|
||||
.s_read = stdio_read,
|
||||
.s_ptr = NULL, /* set to stdin (stdio.h) at runtime */
|
||||
},
|
||||
[IDX_STDOUT] = {
|
||||
.s_mode = B_STREAM_WRITE,
|
||||
.s_write = stdio_write,
|
||||
.s_ptr = NULL, /* set to stdout (stdio.h) at runtime */
|
||||
},
|
||||
[IDX_STDERR] = {
|
||||
.s_mode = B_STREAM_WRITE,
|
||||
.s_write = stdio_write,
|
||||
.s_ptr = NULL, /* set to stderr (stdio.h) at runtime */
|
||||
},
|
||||
};
|
||||
|
||||
struct b_stream *z__b_stream_get_stdin(void)
|
||||
{
|
||||
stdio[IDX_STDIN].s_ptr = stdin;
|
||||
return &stdio[IDX_STDIN];
|
||||
}
|
||||
|
||||
struct b_stream *z__b_stream_get_stdout(void)
|
||||
{
|
||||
stdio[IDX_STDOUT].s_ptr = stdout;
|
||||
return &stdio[IDX_STDOUT];
|
||||
}
|
||||
|
||||
struct b_stream *z__b_stream_get_stderr(void)
|
||||
{
|
||||
stdio[IDX_STDERR].s_ptr = stderr;
|
||||
return &stdio[IDX_STDERR];
|
||||
}
|
||||
|
||||
enum b_status b_stream_pipeline_create(
|
||||
size_t buffer_size, struct b_stream_pipeline **out)
|
||||
{
|
||||
struct b_stream_pipeline *pipeline = malloc(sizeof *pipeline);
|
||||
if (!pipeline) {
|
||||
return B_ERR_NO_MEMORY;
|
||||
}
|
||||
|
||||
memset(pipeline, 0x0, sizeof *pipeline);
|
||||
|
||||
pipeline->p_buf = malloc(buffer_size);
|
||||
if (!pipeline->p_buf) {
|
||||
free(pipeline);
|
||||
return B_ERR_NO_MEMORY;
|
||||
}
|
||||
|
||||
pipeline->p_buf_len = buffer_size;
|
||||
pipeline->p_flags
|
||||
= B_STREAM_PIPELINE_F_DYNAMIC | B_STREAM_PIPELINE_F_BUF_DYNAMIC;
|
||||
|
||||
*out = pipeline;
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_pipeline_init(
|
||||
void *p, size_t len, struct b_stream_pipeline *out)
|
||||
{
|
||||
memset(out, 0x0, sizeof *out);
|
||||
|
||||
out->p_buf = p;
|
||||
out->p_buf_len = len;
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_pipeline_destroy(struct b_stream_pipeline *pipeline)
|
||||
{
|
||||
if (pipeline->p_flags & B_STREAM_PIPELINE_F_BUF_DYNAMIC) {
|
||||
free(pipeline->p_buf);
|
||||
}
|
||||
|
||||
if (pipeline->p_flags & B_STREAM_PIPELINE_F_DYNAMIC) {
|
||||
free(pipeline);
|
||||
}
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_close(b_stream *stream)
|
||||
{
|
||||
if (stream->s_istack) {
|
||||
free(stream->s_istack);
|
||||
}
|
||||
|
||||
if (stream->s_close) {
|
||||
stream->s_close(stream);
|
||||
}
|
||||
|
||||
if (!(stream->s_mode & Z__B_STREAM_STATIC)) {
|
||||
free(stream);
|
||||
}
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_reserve(b_stream *stream, size_t len)
|
||||
{
|
||||
if (!stream->s_reserve) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return stream->s_reserve(stream, len);
|
||||
}
|
||||
|
||||
enum b_status b_stream_seek(
|
||||
b_stream *stream, long long offset, b_stream_seek_origin origin)
|
||||
{
|
||||
if (!stream->s_seek) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return stream->s_seek(stream, offset, origin);
|
||||
}
|
||||
|
||||
size_t b_stream_cursor(const b_stream *stream)
|
||||
{
|
||||
return stream->s_cursor;
|
||||
}
|
||||
|
||||
enum b_status b_stream_push_indent(b_stream *stream, int indent)
|
||||
{
|
||||
if (!(stream->s_mode & B_STREAM_WRITE)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (stream->s_mode & B_STREAM_BINARY) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (!stream->s_istack) {
|
||||
stream->s_istack = calloc(4, sizeof(int));
|
||||
stream->s_istack_size = 4;
|
||||
stream->s_istack_ptr = 0;
|
||||
}
|
||||
|
||||
if (stream->s_istack_ptr + 1 > stream->s_istack_size) {
|
||||
int *buf = realloc(
|
||||
stream->s_istack,
|
||||
(stream->s_istack_size + 4) * sizeof(int));
|
||||
if (!buf) {
|
||||
return B_ERR_NO_MEMORY;
|
||||
}
|
||||
|
||||
stream->s_istack = buf;
|
||||
stream->s_istack_size += 4;
|
||||
}
|
||||
|
||||
int cur_indent = stream->s_istack[stream->s_istack_ptr];
|
||||
stream->s_istack[++stream->s_istack_ptr] = cur_indent + indent;
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_pop_indent(b_stream *stream)
|
||||
{
|
||||
if (!(stream->s_mode & B_STREAM_WRITE)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (stream->s_mode & B_STREAM_BINARY) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (!stream->s_istack || !stream->s_istack_size || !stream->s_istack_ptr) {
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
stream->s_istack_ptr--;
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_read_char(struct b_stream *stream, int *c)
|
||||
{
|
||||
if (!(stream->s_mode & B_STREAM_READ)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum b_status status = B_ERR_NOT_SUPPORTED;
|
||||
|
||||
if (stream->s_getc) {
|
||||
status = stream->s_getc(stream, c);
|
||||
} else if (stream->s_read) {
|
||||
size_t r;
|
||||
unsigned char v = 0;
|
||||
status = stream->s_read(stream, &v, 1, &r);
|
||||
|
||||
*c = v;
|
||||
|
||||
if (status == B_SUCCESS && r < 1) {
|
||||
status = B_ERR_NO_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
enum b_status b_stream_read_bytes(
|
||||
struct b_stream *stream, void *buf, size_t count, size_t *nr_read)
|
||||
{
|
||||
if (!(stream->s_mode & B_STREAM_READ)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum b_status status = B_ERR_NOT_SUPPORTED;
|
||||
|
||||
if (!stream->s_read) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return stream->s_read(stream, buf, count, nr_read);
|
||||
}
|
||||
|
||||
enum b_status b_stream_read_line(struct b_stream *stream, char *s, size_t max)
|
||||
{
|
||||
if (!(stream->s_mode & B_STREAM_READ)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum b_status status = B_SUCCESS;
|
||||
|
||||
size_t i = 0;
|
||||
int c = 0;
|
||||
|
||||
while (1) {
|
||||
if (i >= max) {
|
||||
break;
|
||||
}
|
||||
|
||||
status = b_stream_read_char(stream, &c);
|
||||
if (status != B_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
|
||||
s[i++] = c;
|
||||
s[i] = '\0';
|
||||
}
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_read_line_s(struct b_stream *src, b_stream *dest)
|
||||
{
|
||||
if (!(src->s_mode & B_STREAM_READ)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (!(dest->s_mode & B_STREAM_WRITE)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum b_status status = B_SUCCESS;
|
||||
|
||||
size_t i = 0;
|
||||
int c = 0;
|
||||
|
||||
while (1) {
|
||||
status = b_stream_read_char(src, &c);
|
||||
if (status != B_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
|
||||
b_stream_write_char(dest, c);
|
||||
}
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_read_all_bytes(
|
||||
struct b_stream *stream, void *p, size_t max, size_t *out_nr_read)
|
||||
{
|
||||
if (!(stream->s_mode & B_STREAM_READ)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum b_status status = B_SUCCESS;
|
||||
size_t nr_read = 0;
|
||||
unsigned char *s = p;
|
||||
|
||||
while (nr_read < max) {
|
||||
int c;
|
||||
status = b_stream_read_char(stream, &c);
|
||||
if (status != B_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
s[nr_read++] = c;
|
||||
}
|
||||
|
||||
if (status == B_ERR_NO_DATA && nr_read > 0) {
|
||||
status = B_SUCCESS;
|
||||
}
|
||||
|
||||
*out_nr_read = nr_read;
|
||||
return status;
|
||||
}
|
||||
|
||||
enum b_status b_stream_read_all_bytes_s(
|
||||
struct b_stream *src, struct b_stream *dest,
|
||||
struct b_stream_pipeline *pipeline, size_t *out_nr_read)
|
||||
{
|
||||
if (!(src->s_mode & B_STREAM_READ)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (!(dest->s_mode & B_STREAM_WRITE)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (!pipeline) {
|
||||
return B_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (src->s_seek && dest->s_reserve) {
|
||||
size_t offset = b_stream_cursor(src);
|
||||
b_stream_seek(src, 0, B_STREAM_SEEK_END);
|
||||
size_t length = b_stream_cursor(src);
|
||||
b_stream_seek(src, offset, B_STREAM_SEEK_START);
|
||||
|
||||
b_stream_reserve(dest, length);
|
||||
}
|
||||
|
||||
enum b_status status = B_SUCCESS;
|
||||
size_t nr_read = 0;
|
||||
|
||||
while (1) {
|
||||
size_t r = 0, w = 0;
|
||||
status = b_stream_read_bytes(
|
||||
src, pipeline->p_buf, pipeline->p_buf_len, &r);
|
||||
if (status != B_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
status = b_stream_write_bytes(dest, pipeline->p_buf, r, &w);
|
||||
nr_read += w;
|
||||
|
||||
if (status != B_SUCCESS || w != pipeline->p_buf_len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == B_ERR_NO_DATA && nr_read > 0) {
|
||||
status = B_SUCCESS;
|
||||
}
|
||||
|
||||
if (out_nr_read) {
|
||||
*out_nr_read = nr_read;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static enum b_status __write_char(struct b_stream *stream, char c)
|
||||
{
|
||||
size_t w;
|
||||
enum b_status status = stream->s_write(stream, (unsigned char *)&c, 1, &w);
|
||||
|
||||
if (status == B_SUCCESS && w < 1) {
|
||||
status = B_ERR_IO_FAILURE;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int current_indent(struct b_stream *stream)
|
||||
{
|
||||
if (!stream->s_istack || !stream->s_istack_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return stream->s_istack[stream->s_istack_ptr];
|
||||
}
|
||||
|
||||
enum b_status b_stream_write_char(struct b_stream *stream, char c)
|
||||
{
|
||||
if (!(stream->s_mode & B_STREAM_WRITE)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum b_status status = B_ERR_NOT_SUPPORTED;
|
||||
|
||||
if (!stream->s_write) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (c == '\n') {
|
||||
stream->s_add_indent = 1;
|
||||
}
|
||||
|
||||
if (!stream->s_istack_size) {
|
||||
return __write_char(stream, c);
|
||||
}
|
||||
|
||||
if (stream->s_add_indent && c != '\n') {
|
||||
int indent = current_indent(stream);
|
||||
for (int i = 0; i < indent; i++) {
|
||||
__write_char(stream, ' ');
|
||||
__write_char(stream, ' ');
|
||||
}
|
||||
|
||||
stream->s_add_indent = 0;
|
||||
}
|
||||
|
||||
__write_char(stream, c);
|
||||
|
||||
if (c == '\n') {
|
||||
stream->s_add_indent = 1;
|
||||
}
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_write_string(
|
||||
b_stream *stream, const char *s, size_t *nr_written)
|
||||
{
|
||||
size_t i;
|
||||
enum b_status status = B_SUCCESS;
|
||||
|
||||
for (i = 0; s[i]; i++) {
|
||||
status = b_stream_write_char(stream, s[i]);
|
||||
|
||||
if (!B_OK(status)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nr_written) {
|
||||
*nr_written = i;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
enum b_status b_stream_write_bytes(
|
||||
struct b_stream *stream, void *buf, size_t count, size_t *nr_written)
|
||||
{
|
||||
if (!(stream->s_mode & B_STREAM_WRITE)) {
|
||||
return B_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum b_status status = B_ERR_NOT_SUPPORTED;
|
||||
|
||||
if (!stream->s_write) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return stream->s_write(stream, buf, count, nr_written);
|
||||
}
|
||||
|
||||
static void fctprintf_callback(char c, void *p)
|
||||
{
|
||||
struct b_stream *stream = p;
|
||||
b_stream_write_char(stream, c);
|
||||
}
|
||||
|
||||
enum b_status b_stream_write_fmt(
|
||||
b_stream *stream, size_t *nr_written, const char *format, ...)
|
||||
{
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
int w = z__b_fctprintf(fctprintf_callback, stream, format, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (nr_written) {
|
||||
*nr_written = w;
|
||||
}
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
|
||||
enum b_status b_stream_write_vfmt(
|
||||
b_stream *stream, size_t *nr_written, const char *format, va_list arg)
|
||||
{
|
||||
int w = z__b_fctprintf(fctprintf_callback, stream, format, arg);
|
||||
|
||||
if (nr_written) {
|
||||
*nr_written = w;
|
||||
}
|
||||
|
||||
return B_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user