/* 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 . * Created: 2025-08-26 * Description: Stream implementation */ #include #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 : ""; memset(stream, 0, sizeof(*stream)); stream->type = STREAM_TYPE_STRING; stream->name = name; stream->string = contents; 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 : ""; 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: // Nothing to do, all dealt with outside of stream break; 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_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_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_FILE: return stream->position >= stream->pipe.cache.size; default: FAIL("Unreachable"); return 0; } } bool stream_chunk(stream_t *stream) { assert(stream); switch (stream->type) { case STREAM_TYPE_STRING: // vacuously true return true; case STREAM_TYPE_FILE: { if (feof(stream->pipe.file)) return false; vec_ensure_free(&stream->pipe.cache, STREAM_DEFAULT_CHUNK); int read = fread(vec_data(&stream->pipe.cache), 1, STREAM_DEFAULT_CHUNK, stream->pipe.file); stream->pipe.cache.size += read; return true; } 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) && stream_eos(stream)) return '\0'; switch (stream->type) { case STREAM_TYPE_STRING: return stream->string.data[stream->position]; case STREAM_TYPE_FILE: { // Cached already? We are done. if (stream->position < stream->pipe.cache.size) return ((char *)vec_data(&stream->pipe.cache))[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_eos(stream)) return false; switch (stream->type) { case STREAM_TYPE_STRING: { if (stream->position + offset < stream->string.size) { stream->position += offset; return true; } return false; } 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_eos(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_eos(stream)) return SV(NULL, 0); u64 current_position = stream->position; bool successful = stream_seek_forward(stream, size); // In case we did happen to move forward 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_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_FILE: { if (index + size < stream_size(stream)) return SV(vec_data(&stream->pipe.cache) + index, size); // stream_size(stream) <= index + size => 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); } }