342 lines
7.8 KiB
C
342 lines
7.8 KiB
C
/* Copyright (C) 2025 Aryadev Chavali
|
|
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the Unlicense for details.
|
|
|
|
* You may distribute and modify this code under the terms of the Unlicense,
|
|
* which you should have received a copy of along with this program. If not,
|
|
* please go to <https://unlicense.org/>.
|
|
|
|
* Created: 2025-08-26
|
|
* Description: Stream implementation
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "./alisp.h"
|
|
|
|
stream_err_t stream_init_string(stream_t *stream, char *name, sv_t contents)
|
|
{
|
|
if (!stream)
|
|
return STREAM_ERR_INVALID_PTR;
|
|
name = name ? name : "<stream>";
|
|
memset(stream, 0, sizeof(*stream));
|
|
|
|
stream->type = STREAM_TYPE_STRING;
|
|
stream->name = name;
|
|
stream->string = sv_copy(contents);
|
|
|
|
return STREAM_ERR_OK;
|
|
}
|
|
|
|
stream_err_t stream_init_pipe(stream_t *stream, char *name, FILE *pipe)
|
|
{
|
|
if (!stream || !pipe)
|
|
return STREAM_ERR_INVALID_PTR;
|
|
name = name ? name : "<stream>";
|
|
memset(stream, 0, sizeof(*stream));
|
|
|
|
stream->type = STREAM_TYPE_PIPE;
|
|
stream->name = name;
|
|
stream->pipe.file = pipe;
|
|
|
|
vec_init(&stream->pipe.cache, STREAM_DEFAULT_CHUNK);
|
|
|
|
return STREAM_ERR_OK;
|
|
}
|
|
|
|
stream_err_t stream_init_file(stream_t *stream, char *name, FILE *pipe)
|
|
{
|
|
if (!stream || !pipe)
|
|
return STREAM_ERR_INVALID_PTR;
|
|
name = name ? name : "<stream>";
|
|
memset(stream, 0, sizeof(*stream));
|
|
|
|
stream->type = STREAM_TYPE_FILE;
|
|
stream->name = name;
|
|
stream->pipe.file = pipe;
|
|
|
|
vec_init(&stream->pipe.cache, STREAM_DEFAULT_CHUNK);
|
|
|
|
return STREAM_ERR_OK;
|
|
}
|
|
|
|
void stream_stop(stream_t *stream)
|
|
{
|
|
if (!stream)
|
|
return;
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
free(stream->string.data);
|
|
break;
|
|
case STREAM_TYPE_PIPE:
|
|
case STREAM_TYPE_FILE:
|
|
// Must cleanup vector
|
|
vec_free(&stream->pipe.cache);
|
|
break;
|
|
}
|
|
memset(stream, 0, sizeof(*stream));
|
|
}
|
|
|
|
u64 stream_size(stream_t *stream)
|
|
{
|
|
assert(stream);
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
return stream->string.size;
|
|
case STREAM_TYPE_PIPE:
|
|
case STREAM_TYPE_FILE:
|
|
return stream->pipe.cache.size;
|
|
default:
|
|
FAIL("Unreachable");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool stream_eos(stream_t *stream)
|
|
{
|
|
assert(stream);
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
return stream->position >= stream->string.size;
|
|
case STREAM_TYPE_PIPE:
|
|
case STREAM_TYPE_FILE:
|
|
return feof(stream->pipe.file);
|
|
default:
|
|
FAIL("Unreachable");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool stream_eoc(stream_t *stream)
|
|
{
|
|
assert(stream);
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
return stream->position >= stream->string.size;
|
|
case STREAM_TYPE_PIPE:
|
|
case STREAM_TYPE_FILE:
|
|
return feof(stream->pipe.file) &&
|
|
stream->position >= stream->pipe.cache.size;
|
|
default:
|
|
FAIL("Unreachable");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool stream_chunk(stream_t *stream)
|
|
{
|
|
assert(stream);
|
|
u64 to_read = STREAM_DEFAULT_CHUNK;
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
// vacuously true
|
|
return true;
|
|
case STREAM_TYPE_PIPE:
|
|
to_read = 1;
|
|
// fallthrough
|
|
case STREAM_TYPE_FILE:
|
|
{
|
|
if (feof(stream->pipe.file))
|
|
// We can't read anymore. End of the line
|
|
return false;
|
|
vec_ensure_free(&stream->pipe.cache, to_read);
|
|
int read = fread(vec_data(&stream->pipe.cache) + stream->pipe.cache.size, 1,
|
|
to_read, stream->pipe.file);
|
|
|
|
// If we read something it's a good thing
|
|
if (read > 0)
|
|
{
|
|
stream->pipe.cache.size += read;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
default:
|
|
FAIL("Unreachable");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
char stream_next(stream_t *stream)
|
|
{
|
|
char c = stream_peek(stream);
|
|
if (c != '\0')
|
|
++stream->position;
|
|
return c;
|
|
}
|
|
|
|
char stream_peek(stream_t *stream)
|
|
{
|
|
// If we've reached end of stream, and end of content, there's really nothing
|
|
// to check here.
|
|
if (stream_eoc(stream))
|
|
return '\0';
|
|
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
return stream->string.data[stream->position];
|
|
case STREAM_TYPE_PIPE:
|
|
case STREAM_TYPE_FILE:
|
|
{
|
|
// Cached already? We are done.
|
|
if (stream->position < stream->pipe.cache.size)
|
|
{
|
|
const char *const str = vec_data(&stream->pipe.cache);
|
|
return str[stream->position];
|
|
}
|
|
|
|
// Try to read chunks in till we've reached it or we're at the end of the
|
|
// file.
|
|
for (bool read_chunk = stream_chunk(stream);
|
|
read_chunk && stream->position >= stream->pipe.cache.size;
|
|
read_chunk = stream_chunk(stream))
|
|
continue;
|
|
|
|
// Same principle as the stream_eos(stream) check.
|
|
if (stream->position >= stream->pipe.cache.size)
|
|
return '\0';
|
|
return ((char *)vec_data(&stream->pipe.cache))[stream->position];
|
|
}
|
|
default:
|
|
FAIL("Unreachable");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool stream_seek(stream_t *stream, i64 offset)
|
|
{
|
|
if (offset < 0)
|
|
return stream_seek_backward(stream, offset * -1);
|
|
else if (offset > 0)
|
|
return stream_seek_forward(stream, offset);
|
|
else
|
|
// vacuously successful
|
|
return true;
|
|
}
|
|
|
|
bool stream_seek_forward(stream_t *stream, u64 offset)
|
|
{
|
|
if (stream_eoc(stream))
|
|
return false;
|
|
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
{
|
|
if (stream->position + offset >= stream->string.size)
|
|
return false;
|
|
|
|
stream->position += offset;
|
|
return true;
|
|
}
|
|
case STREAM_TYPE_PIPE:
|
|
case STREAM_TYPE_FILE:
|
|
{
|
|
// Similar principle as stream_peek really...
|
|
|
|
// Cached already? We are done.
|
|
if (stream->position + offset < stream->pipe.cache.size)
|
|
{
|
|
stream->position += offset;
|
|
return true;
|
|
}
|
|
|
|
// Try to read chunks in till we've reached it or we're at the end of the
|
|
// file.
|
|
for (bool read_chunk = stream_chunk(stream);
|
|
read_chunk && stream->position + offset >= stream->pipe.cache.size;
|
|
read_chunk = stream_chunk(stream))
|
|
continue;
|
|
|
|
// Same principle as the stream_eoc(stream) check.
|
|
if (stream->position + offset > stream->pipe.cache.size)
|
|
return false;
|
|
stream->position += offset;
|
|
return true;
|
|
}
|
|
default:
|
|
FAIL("Unreachable");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool stream_seek_backward(stream_t *stream, u64 offset)
|
|
{
|
|
assert(stream);
|
|
if (stream->position < offset)
|
|
return false;
|
|
stream->position -= offset;
|
|
return true;
|
|
}
|
|
|
|
sv_t stream_substr(stream_t *stream, u64 size)
|
|
{
|
|
if (stream_eoc(stream))
|
|
return SV(NULL, 0);
|
|
|
|
// See if I can go forward enough to make this substring
|
|
u64 current_position = stream->position;
|
|
bool successful = stream_seek_forward(stream, size);
|
|
// Reset the position in either situation
|
|
stream->position = current_position;
|
|
|
|
if (!successful)
|
|
return SV(NULL, 0);
|
|
|
|
char *ptr = NULL;
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
ptr = stream->string.data;
|
|
break;
|
|
case STREAM_TYPE_PIPE:
|
|
case STREAM_TYPE_FILE:
|
|
ptr = vec_data(&stream->pipe.cache);
|
|
break;
|
|
default:
|
|
FAIL("Unreachable");
|
|
return SV(NULL, 0);
|
|
}
|
|
|
|
return SV(ptr + stream->position, size);
|
|
}
|
|
|
|
sv_t stream_substr_abs(stream_t *stream, u64 index, u64 size)
|
|
{
|
|
switch (stream->type)
|
|
{
|
|
case STREAM_TYPE_STRING:
|
|
if (index + size <= stream_size(stream))
|
|
return SV(stream->string.data + index, size);
|
|
return SV(NULL, 0);
|
|
case STREAM_TYPE_PIPE:
|
|
case STREAM_TYPE_FILE:
|
|
{
|
|
if (index + size <= stream_size(stream))
|
|
return SV(vec_data(&stream->pipe.cache) + index, size);
|
|
// (index + size > stream_size(stream)) => try reading chunks
|
|
for (bool read_chunk = stream_chunk(stream);
|
|
read_chunk && index + size >= stream->pipe.cache.size;
|
|
read_chunk = stream_chunk(stream))
|
|
continue;
|
|
|
|
if (index + size > stream_size(stream))
|
|
return SV(NULL, 0);
|
|
return SV(vec_data(&stream->pipe.cache) + index, size);
|
|
}
|
|
default:
|
|
assert("Unreachable");
|
|
return SV(NULL, 0);
|
|
}
|
|
}
|