Program Listing for File BufWriter.hpp

Return to documentation for file (src/rdf4cpp/writer/BufWriter.hpp)

#ifndef RDF4CPP_BUFWRITER_HPP
#define RDF4CPP_BUFWRITER_HPP

#include <array>
#include <cstdio>
#include <cstring>
#include <ostream>
#include <string>
#include <string_view>
#include <system_error>

namespace rdf4cpp::writer {

template<typename W>
concept BufWriter = requires (W &bw, size_t additional_cap) {
    typename W::Buffer;

    { bw.buffer() } -> std::same_as<typename W::Buffer &>;

    { bw.write_area() } -> std::same_as<char *&>;

    { bw.write_area_size() } -> std::same_as<size_t &>;

    { bw.finalize() } -> std::convertible_to<bool>;

    W::flush(&bw.buffer(), &bw.write_area(), &bw.write_area_size(), additional_cap);
};

using FlushFunc = void (*)(void *buffer, char **write_area, size_t *write_area_size, size_t additional_cap);

struct BufWriterParts {
    void *buffer; //< pointer to arbitrary buffer structure
    char **write_area; //< pointer to free space
    size_t *write_area_size; //< size of free space
    FlushFunc flush; //< function to flush the contents of buffer and update cursor to point to the new free space

    BufWriterParts() = delete;
    BufWriterParts(BufWriterParts const &) noexcept = default;
    BufWriterParts(BufWriterParts &&) noexcept = default;
    BufWriterParts &operator=(BufWriterParts const &) noexcept = default;
    BufWriterParts &operator=(BufWriterParts &&) noexcept = default;
    ~BufWriterParts() noexcept = default;

    template<BufWriter W>
    BufWriterParts(W &w) noexcept : buffer{&w.buffer()},
                                    write_area{&w.write_area()},
                                    write_area_size{&w.write_area_size()},
                                    flush{&W::flush} {
    }

    BufWriterParts(void *buffer, char **cursor, size_t *remaining_size, FlushFunc flush) noexcept : buffer{buffer},
                                                                                                    write_area{cursor},
                                                                                                    write_area_size{remaining_size},
                                                                                                    flush{flush} {
    }
};

inline bool write_str(std::string_view str, BufWriterParts const writer) noexcept {
    if (str.empty()) [[unlikely]] {
        // An empty std::string_view may have str.data() == nullptr
        // Calling memcpy() with a nullptr is undefined behavior **even if size == 0**
        return true;
    }

    while (true) {
        auto const max_write = std::min(str.size(), *writer.write_area_size);
        memcpy(*writer.write_area, str.data(), max_write);
        *writer.write_area += max_write;
        *writer.write_area_size -= max_write;

        if (max_write == str.size()) [[likely]] {
            break;
        }

        str.remove_prefix(max_write);

        if (writer.flush(writer.buffer, writer.write_area, writer.write_area_size, str.size()); *writer.write_area_size == 0) [[unlikely]] {
            return false;
        }
    }
    return true;
}

template<typename CRTP, typename Buffer>
struct BufWriterBase {
private:
    Buffer buffer_;
    char *write_area_ = nullptr;
    size_t write_area_size_ = 0;

public:
    template<typename ...BufferArgs>
    explicit constexpr BufWriterBase(BufferArgs &&...buffer_args) : buffer_{std::forward<BufferArgs>(buffer_args)...} {
    }

    [[nodiscard]] constexpr char *&write_area() noexcept { return write_area_; }
    [[nodiscard]] constexpr size_t &write_area_size() noexcept { return write_area_size_; }
    [[nodiscard]] constexpr Buffer &buffer() noexcept { return buffer_; }

    [[nodiscard]] constexpr char *write_area() const noexcept { return write_area_; }
    [[nodiscard]] constexpr size_t write_area_size() const noexcept { return write_area_size_; }
    [[nodiscard]] constexpr Buffer const &buffer() const noexcept { return buffer_; }

    static void flush(void *buffer, char **write_area, size_t *write_area_size, size_t additional_cap) noexcept {
        CRTP::flush_impl(*static_cast<typename CRTP::Buffer *>(buffer), *write_area, *write_area_size, additional_cap);
    }
};

struct StringBuffer {
    std::string *buffer_;

    explicit StringBuffer(std::string &buffer) noexcept : buffer_{&buffer} {
    }
};

struct StringWriter : BufWriterBase<StringWriter, StringBuffer> {
    using Buffer = StringBuffer;

    constexpr StringWriter(std::string &buf) noexcept : BufWriterBase<StringWriter, StringBuffer>{buf} {
        clear();
    }

    bool finalize() noexcept {
        buffer().buffer_->resize(static_cast<size_t>(write_area() - buffer().buffer_->data()));
        return true;
    }

    [[nodiscard]] std::string_view view() const noexcept {
        return std::string_view{buffer().buffer_->data(), static_cast<size_t>(write_area() - buffer().buffer_->data())};
    }

    constexpr void clear() noexcept {
        write_area() = buffer().buffer_->data();
        write_area_size() = buffer().buffer_->size();
    }

    static void flush_impl(Buffer &buffer, char *&write_area, size_t &write_area_size, size_t additional_cap) noexcept {
        auto const bytes_filled = write_area - buffer.buffer_->data();

        buffer.buffer_->resize(std::bit_ceil(buffer.buffer_->size() + additional_cap));

        write_area = buffer.buffer_->data() + bytes_filled;
        write_area_size = buffer.buffer_->size() - bytes_filled;
    }

    template<typename F>
    static std::string oneshot(F &&f) requires std::is_invocable_r_v<bool, decltype(std::forward<F>(f)), StringWriter &> {
        std::string s;
        s.resize(32);
        StringWriter w{s};

        if (!std::invoke(std::forward<F>(f), w)) {
            throw std::runtime_error{"rdf4cpp::writer::StringWriter::oneshot failed"};
        }

        w.finalize(); // cannot fail
        return s;
    }
};

struct CFileBuffer {
    FILE *file_;
    std::array<char, BUFSIZ> buffer_;

    explicit constexpr CFileBuffer(FILE *file) noexcept : file_{file} {}
};

struct BufCFileWriter : BufWriterBase<BufCFileWriter, CFileBuffer> {
    using Buffer = CFileBuffer;

    explicit constexpr BufCFileWriter(FILE *file) noexcept : BufWriterBase<BufCFileWriter, CFileBuffer>{file} {
        write_area() = buffer().buffer_.data();
        write_area_size() = buffer().buffer_.size();
    }

    bool finalize() {
        auto const to_write = static_cast<size_t>(write_area() - buffer().buffer_.data());
        return fwrite(buffer().buffer_.data(), 1, to_write, buffer().file_) == to_write;
    }

    static void flush_impl(Buffer &buffer, char *&write_area, size_t &write_area_size, [[maybe_unused]] size_t additional_cap) noexcept {
        auto const bytes_flushed = fwrite(buffer.buffer_.data(), 1, static_cast<size_t>(write_area - buffer.buffer_.data()), buffer.file_);
        write_area -= bytes_flushed;
        write_area_size += bytes_flushed;
    }
};

struct OStreamBuffer {
    std::ostream *os_;
    std::array<char, BUFSIZ> buffer_;

    explicit constexpr OStreamBuffer(std::ostream &os) noexcept : os_{&os} {}
};

struct BufOStreamWriter : BufWriterBase<BufOStreamWriter, OStreamBuffer> {
    using Buffer = OStreamBuffer;

    explicit constexpr BufOStreamWriter(std::ostream &os) noexcept : BufWriterBase<BufOStreamWriter, OStreamBuffer>{os} {
        write_area() = buffer().buffer_.data();
        write_area_size() = buffer().buffer_.size();
    }

    bool finalize() {
        return static_cast<bool>(buffer().os_->write(buffer().buffer_.data(), static_cast<std::streamsize>(write_area() - buffer().buffer_.data())));
    }

    static void flush_impl(Buffer &buffer, char *&write_area, size_t &write_area_size, [[maybe_unused]] size_t additional_cap) noexcept {
        if (!buffer.os_->write(buffer.buffer_.data(), static_cast<std::streamsize>(write_area - buffer.buffer_.data()))) {
            return;
        }

        write_area = buffer.buffer_.data();
        write_area_size = buffer.buffer_.size();
    }
};

template<std::output_iterator<char> OutIter>
struct OutputIteratorBuffer {
    OutIter iter;
    std::array<char, BUFSIZ> buffer_{};

    explicit constexpr OutputIteratorBuffer(OutIter i) noexcept
        : iter(i) {
    }

    void write_out(char const *end) {
        char const *b = buffer_.data();
        RDF4CPP_ASSERT(b <= end && end <= buffer_.end());
        while (b != end) {
            *iter = *b;
            ++iter;
            ++b;
        }
    }
};

template<std::output_iterator<char> OutIter>
struct BufOutputIteratorWriter : BufWriterBase<BufOutputIteratorWriter<OutIter>, OutputIteratorBuffer<OutIter>> {
    using Buffer = OutputIteratorBuffer<OutIter>;

    explicit constexpr BufOutputIteratorWriter(OutIter i) noexcept
        : BufWriterBase<BufOutputIteratorWriter<OutIter>, OutputIteratorBuffer<OutIter>>{i} {

        this->write_area() = this->buffer().buffer_.data();
        this->write_area_size() = this->buffer().buffer_.size();
    }

    bool finalize() {
        this->buffer().write_out(this->write_area());
        return true;
    }

    static void flush_impl(Buffer &buffer, char *&write_area, size_t &write_area_size, [[maybe_unused]] size_t additional_cap) noexcept {
        buffer.write_out(write_area);
        write_area = buffer.buffer_.data();
        write_area_size = buffer.buffer_.size();
    }
};
} // namespace rdf4cpp::writer

#endif  // RDF4CPP_BUFWRITER_HPP