export module coral.io; import coral; export namespace coral { /** * Multiplexing byte-based ring buffer of `capacity` size that may be used for memory-backed * I/O operations and lightweight data construction. */ template struct fixed_buffer : public writer, public reader { fixed_buffer(coral::u8 fill_value) : data{fill_value} {} /** * Returns a mutable [slice] ranging from the head to the last-filled element. * * *Note*: The lifetime and validity of the returned slice is only guaranteed for as long * as the source [fixed_buffer] is not mutated or out-of-scope. */ slice as_slice() const { return {0, this->filled}; } /** * Returns the base pointer of the buffer data. */ u8 * begin() { return this->data; } /** * Returns the base pointer of the buffer data. */ u8 const * begin() const { return this->data; } /** * Returns the tail pointer of the buffer data. */ u8 * end() { return this->data + this->cursor; } /** * Returns the tail pointer of the buffer data. */ u8 const * end() const { return this->data + this->cursor; } /** * Returns `true` if the buffer has been completely filled with data. */ bool is_full() const { return this->filled == capacity; } /** * Reads whatever data is in the buffer into `data`, returning the number of bytes read * from the buffer. */ expected read(slice const & data) override { slice const readable_data{this->data, min(this->filled, data.length)}; this->filled -= readable_data.length; for (usize index = 0; index < readable_data.length; index += 1) { data[index] = this->data[this->read_index]; this->read_index = (this->read_index + 1) % capacity; } return readable_data.length; } /** * Attempts to write `data` to the buffer, returning the number of bytes written or * [io_error::unavailable] if it has been completely filled and no more bytes can be * written. */ expected write(slice const & data) override { if (this->is_full()) return io_error::unavailable; slice const writable_data{data.sliced(0, min(data.length, this->filled))}; this->filled += writable_data.length; for (usize index = 0; index < writable_data.length; index += 1) { this->data[this->write_index] = data[index]; this->write_index = (this->write_index + 1) % capacity; } return writable_data.length; } private: usize filled = 0; usize read_index = 0; usize write_index = 0; u8 data[capacity]; }; /** * Streams the data from `input` to `output`, using `buffer` as temporary transfer space. * * The returned [expected] can be used to introspect if `input` or `output` encountered any * issues during streaming, otherwise it will contain the number of bytes streamed. * * *Note*: if `buffer` has a length of `0`, no data will be streamed as there is nowhere to * temporarily place data during streaming. */ expected stream(writer & output, reader & input, slice const & buffer) { usize total_bytes_written = 0; expected bytes_read = input.read(buffer); if (!bytes_read.is_ok()) return bytes_read.error(); usize read = bytes_read.value(); while (read != 0) { expected const bytes_written = output.write(buffer.sliced(0, read)); if (!bytes_written.is_ok()) return bytes_read.error(); total_bytes_written += bytes_written.value(); bytes_read = input.read(buffer); if (!bytes_read.is_ok()) return bytes_read.error(); read = bytes_read.value(); } return total_bytes_written; } /** * Returns a reference to a shared [allocator] which will always return `nullptr` on calls to * [allocator::reallocate]. */ allocator & null_allocator() { static struct : public allocator { u8 * reallocate(u8 * maybe_allocation, usize requested_size) override { if (maybe_allocation != nullptr) unreachable(); return nullptr; } void deallocate(void * allocation) override { if (allocation != nullptr) unreachable(); } } a; return a; } /** * Attempts to format and print `value` as an unsigned integer out to `output`. * * The returned [expected] can be used to introspect if `output` encountered any issues during * printing, otherwise it will contain the number of characters used to print `value` as text. */ expected print_unsigned(writer & output, u64 value) { if (value == 0) return output.write(slice{"0"}.as_bytes()); u8 buffer[20]{0}; usize buffer_count{0}; while (value != 0) { constexpr usize radix{10}; buffer[buffer_count] = static_cast((value % radix) + '0'); value = (value / radix); buffer_count += 1; } usize const half_buffer_count{buffer_count / 2}; for (usize i = 0; i < half_buffer_count; i += 1) swap(buffer[i], buffer[buffer_count - i - 1]); return output.write({buffer, buffer_count}); } }