std1.0.0-beta-7 SafeC docs

Source io_buf.h

View in the repository

#pragma once
// SafeC Standard Library — buffered streams over any Reader / Writer.
//
// BufReader<R> and BufWriter<W> borrow a stream (io_traits.h's Reader /
// Writer: File, Pipe, TcpStream, ...) and put an IO_BUF_SIZE-byte buffer in
// front of it: many small reads or writes become one call on the stream.
// Each is a Reader / Writer itself, so read_exact, write_all, copy_stream
// and any generic<R: Reader> code take it like the stream it wraps.
//
//   struct TcpStream s = TcpStream::from_fd(fd);
//   struct BufReader<TcpStream> in = BufReader::over(&s);
//   struct String head = String::new();
//   if (in.read_until(&head, "\r\n\r\n")) { ... }   // the header block
//   read_exact(&in, body[0..len]);                   // bytes after it
//
//   struct BufWriter<TcpStream> out = BufWriter::over(&s);
//   write_all(&out, hdr[0..6]);                      // buffered
//   write_all(&out, payload.as_bytes());
//   if (out.flush() != 0) { ... }                    // one write to s
//
// A BufWriter holds what was written until flush() (or until its buffer
// fills): call flush() before the stream is used directly or closed.
#include <std/io_traits.h>
#include <std/mem.h>
#include <std/collections/string.h>

#define IO_BUF_SIZE 4096

namespace std {

// ── BufReader ───────────────────────────────────────────────────────────────

generic<R: Reader>
struct BufReader {
    &R            inner;    // the stream read from (borrowed)
    unsigned long pos;      // next unread byte in buf
    unsigned long len;      // bytes in buf
    unsigned char buf[IO_BUF_SIZE];

    static struct BufReader<R> over(&R inner);

    // Reader: at most n bytes, from the buffer first. A read of at least
    // IO_BUF_SIZE bytes with nothing buffered goes straight to the stream.
    // The count read, 0 at the stream's end, -1 on error.
    long long     read([]unsigned char out);

    // Appends bytes to 'out' up to and including the first occurrence of
    // 'term' (a non-empty string; "\n" reads a line, "\r\n\r\n" an HTTP
    // header block). 1 when 'term' arrived; 0 when the stream ended or
    // failed first ('out' then holds what did arrive). Bytes after 'term'
    // stay buffered for the next read.
    int           read_until(&String out, const &char term);

    // Bytes already read from the stream and not yet returned: a read of
    // at most this many doesn't touch the stream.
    unsigned long buffered() const;
};

generic<R: Reader>
struct BufReader<R> BufReader::over(&R inner) {
    struct BufReader<R> b;
    b.inner = inner;
    b.pos = 0UL;
    b.len = 0UL;
    return b;
}

generic<R: Reader>
long long BufReader::read([]unsigned char out_s) {
    unsigned long n = out_s.len;
    if (n == 0UL) { return 0LL; }
    &unsigned char out;
    unsafe { out = (&unsigned char)out_s.ptr; }
    if (self.pos == self.len) {
        if (n >= (unsigned long)IO_BUF_SIZE) { return self.inner.read(out_s); }
        long long k = self.inner.read(self.buf[0..(unsigned long)IO_BUF_SIZE]);
        if (k <= 0LL) { return k; }
        self.pos = 0UL;
        self.len = (unsigned long)k;
    }
    unsigned long m = self.len - self.pos;
    if (m > n) { m = n; }
    unsafe { safe_memcpy_raw_((unsigned char*)out, (const unsigned char*)&self.buf[self.pos], m); }
    self.pos = self.pos + m;
    return (long long)m;
}

// After 'matched' bytes of 'term' and then byte c: how many bytes of 'term'
// the input now ends with (the longest prefix of 'term' that is a suffix of
// term[0..matched) + c).
static unsigned long io_term_step_(const char* term, unsigned long matched, unsigned char c) {
    unsafe {
        if ((unsigned char)term[matched] == c) { return matched + 1UL; }
        unsigned long k = matched;
        while (k > 0UL) {
            // candidate: term[0..k) == term[matched-k+1..matched) + c
            if ((unsigned char)term[k - 1UL] == c) {
                unsigned long j = 0UL;
                while (j + 1UL < k && term[j] == term[matched - k + 1UL + j]) { j = j + 1UL; }
                if (j + 1UL == k) { return k; }
            }
            k = k - 1UL;
        }
    }
    return 0UL;
}

generic<R: Reader>
int BufReader::read_until(&String out, const &char term) {
    const char* t = term;
    unsigned long tl = 0UL;
    unsafe { while (t[tl] != (char)0) { tl = tl + 1UL; } }
    if (tl == 0UL) { return 1; }
    unsigned long matched = 0UL;
    while (1) {
        if (self.pos == self.len) {
            long long k = self.inner.read(self.buf[0..(unsigned long)IO_BUF_SIZE]);
            if (k <= 0LL) { return 0; }
            self.pos = 0UL;
            self.len = (unsigned long)k;
        }
        unsigned long i = self.pos;
        while (i < self.len) {
            matched = io_term_step_(t, matched, self.buf[i]);
            i = i + 1UL;
            if (matched == tl) {
                unsafe { out.push_n_((const &char)(const char*)&self.buf[self.pos], i - self.pos); }
                self.pos = i;
                return 1;
            }
        }
        unsafe { out.push_n_((const &char)(const char*)&self.buf[self.pos], self.len - self.pos); }
        self.pos = self.len;
    }
    return 0;
}

generic<R: Reader>
unsigned long BufReader::buffered() const { return self.len - self.pos; }

// ── BufWriter ───────────────────────────────────────────────────────────────

generic<W: Writer>
struct BufWriter {
    &W            inner;    // the stream written to (borrowed)
    unsigned long len;      // bytes waiting in buf
    unsigned char buf[IO_BUF_SIZE];

    static struct BufWriter<W> over(&W inner);

    // Writer: takes all of data (into the buffer, or, for a write that
    // doesn't fit, after flushing it, straight to the stream when it is at
    // least IO_BUF_SIZE). data.len, or -1 if the stream failed.
    long long     write([]const unsigned char data);

    // Flusher: writes out everything buffered. 0 on success, -1 if the
    // stream failed (the buffered bytes are dropped either way).
    int           flush();
};

generic<W: Writer>
struct BufWriter<W> BufWriter::over(&W inner) {
    struct BufWriter<W> b;
    b.inner = inner;
    b.len = 0UL;
    return b;
}

generic<W: Writer>
long long BufWriter::write([]const unsigned char data_s) {
    unsigned long n = data_s.len;
    if (n == 0UL) { return 0LL; }
    const &unsigned char data;
    unsafe { data = (const &unsigned char)data_s.ptr; }
    if (n > (unsigned long)IO_BUF_SIZE - self.len) {
        if (self.flush() != 0) { return -1LL; }
        if (n >= (unsigned long)IO_BUF_SIZE) {
            return write_all(self.inner, data_s) ? (long long)n : -1LL;
        }
    }
    unsafe { safe_memcpy_raw_((unsigned char*)&self.buf[self.len], (const unsigned char*)data, n); }
    self.len = self.len + n;
    return (long long)n;
}

generic<W: Writer>
int BufWriter::flush() {
    if (self.len == 0UL) { return 0; }
    int ok = write_all(self.inner, self.buf[0..self.len]);
    self.len = 0UL;
    return ok ? 0 : -1;
}

} // namespace std