libostd/ostd/stream.hh

340 lines
7.9 KiB
C++

/* Generic stream implementation for libostd.
*
* This file is part of libostd. See COPYING.md for futher information.
*/
#ifndef OSTD_STREAM_HH
#define OSTD_STREAM_HH
#include <cerrno>
#include <cstdlib>
#include <type_traits>
#include <locale>
#include <optional>
#include <stdexcept>
#include <system_error>
#ifndef OSTD_PLATFORM_WIN32
#include <sys/types.h>
#endif
#include "ostd/platform.hh"
#include "ostd/range.hh"
#include "ostd/string.hh"
#include "ostd/format.hh"
namespace ostd {
#ifndef OSTD_PLATFORM_WIN32
using stream_off_t = off_t;
#else
using stream_off_t = __int64;
#endif
enum class stream_seek {
CUR = SEEK_CUR,
END = SEEK_END,
SET = SEEK_SET
};
template<typename T = char, bool = std::is_pod_v<T>>
struct stream_range;
struct stream_error: std::system_error {
using std::system_error::system_error;
};
template<typename T = char, typename TC = std::basic_string<T>>
struct stream_line_range;
struct stream {
using offset_type = stream_off_t;
virtual ~stream() {}
virtual void close() = 0;
virtual bool end() const = 0;
virtual offset_type size() {
offset_type p = tell();
seek(0, stream_seek::END);
offset_type e = tell();
if (p == e) {
return e;
}
seek(p, stream_seek::SET);
return e;
}
virtual void seek(offset_type, stream_seek = stream_seek::SET) {
throw stream_error{EINVAL, std::generic_category()};
}
virtual offset_type tell() const {
throw stream_error{EINVAL, std::generic_category()};
}
virtual void flush() {
throw stream_error{EINVAL, std::generic_category()};
}
virtual size_t read_bytes(void *, size_t) {
throw stream_error{EINVAL, std::generic_category()};
}
virtual void write_bytes(void const *, size_t) {
throw stream_error{EINVAL, std::generic_category()};
}
virtual int get_char() {
unsigned char c;
if (!read_bytes(&c, 1)) {
throw stream_error{EIO, std::generic_category()};
}
return c;
}
virtual void put_char(int c) {
unsigned char wc = static_cast<unsigned char>(c);
write_bytes(&wc, 1);
}
template<typename T = char, typename R>
void get_line(R &&writer, bool keep_nl = false) {
bool cr = false;
/* read one char, if it fails to read at all just propagate errors */
T c = get<T>();
bool gotc = false;
do {
if (cr) {
writer.put('\r');
cr = false;
}
if (c == '\r') {
cr = true;
continue;
}
writer.put(c);
gotc = safe_get<T>(c);
} while (gotc && (c != '\n'));
if (cr && (!gotc || keep_nl)) {
/* we had carriage return and either reached EOF
* or were told to keep separator, write the CR
*/
writer.put('\r');
}
if (gotc && keep_nl) {
writer.put('\n');
}
}
template<typename ...A>
void write(A const &...args);
template<typename ...A>
void writeln(A const &...args) {
write(args...);
put_char('\n');
}
template<typename ...A>
void writef(string_range fmt, A const &...args);
template<typename ...A>
void writefln(string_range fmt, A const &...args) {
writef(fmt, args...);
put_char('\n');
}
template<typename T = char>
stream_range<T> iter();
template<typename T = char, typename TC = std::basic_string<T>>
stream_line_range<T, TC> iter_lines(bool keep_nl = false);
template<typename T>
void put(T const *v, size_t count) {
write_bytes(v, count * sizeof(T));
}
template<typename T>
void put(T v) {
write_bytes(&v, sizeof(T));
}
template<typename T>
size_t get(T *v, size_t count) {
/* if eof was reached, at least return how many values succeeded */
return read_bytes(v, count * sizeof(T)) / sizeof(T);
}
template<typename T>
void get(T &v) {
if (read_bytes(&v, sizeof(T)) != sizeof(T)) {
throw stream_error{EIO, std::generic_category()};
}
}
template<typename T>
T get() {
T r;
get(r);
return r;
}
std::locale imbue(std::locale const &loc) {
std::locale ret{p_loc};
p_loc = loc;
return ret;
}
std::locale getloc() const {
return p_loc;
}
private:
/* helper for get_line, so we don't catch errors from output range put */
template<typename T>
bool safe_get(T &c) {
try {
c = get<T>();
return true;
} catch (stream_error const &) {
return false;
}
}
std::locale p_loc;
};
template<typename T>
struct stream_range<T, true>: input_range<stream_range<T>> {
using range_category = input_range_tag;
using value_type = T;
using reference = T;
using size_type = size_t;
using difference_type = stream_off_t;
stream_range() = delete;
stream_range(stream &s): p_stream(&s) {}
stream_range(stream_range const &r):
p_stream(r.p_stream), p_item(r.p_item)
{}
bool empty() const {
if (!p_item.has_value()) {
try {
p_item = p_stream->get<T>();
} catch (stream_error const &) {
return true;
}
}
return false;
}
void pop_front() {
if (p_item.has_value()) {
p_item = std::nullopt;
} else {
p_stream->get<T>();
}
}
T front() const {
if (p_item.has_value()) {
return p_item.value();
} else {
return (p_item = p_stream->get<T>()).value();
}
}
void put(T val) {
p_stream->put(val);
}
private:
stream *p_stream;
mutable std::optional<T> p_item;
};
template<typename T>
inline stream_range<T> stream::iter() {
return stream_range<T>{*this};
}
template<typename T, typename TC>
struct stream_line_range: input_range<stream_line_range<T, TC>> {
using range_category = input_range_tag;
using value_type = TC;
using reference = TC &;
using size_type = size_t;
using difference_type = stream_off_t;
stream_line_range() = delete;
stream_line_range(stream &s, bool keep_nl = false):
p_stream(&s), p_has_item(false), p_keep_nl(keep_nl)
{}
stream_line_range(stream_line_range const &r):
p_stream(r.p_stream), p_item(r.p_item),
p_has_item(r.p_has_item), p_keep_nl(r.p_keep_nl)
{}
bool empty() const {
if (!p_has_item) {
try {
p_item.clear();
p_stream->get_line(p_item, p_keep_nl);
p_has_item = true;
} catch (stream_error const &) {
return true;
}
}
return false;
}
void pop_front() {
if (p_has_item) {
p_item.clear();
p_has_item = false;
} else {
p_stream->get_line(noop_output_range<T>{});
}
}
reference front() const {
if (p_has_item) {
return p_item.get();
} else {
p_stream->get_line(p_item, p_keep_nl);
p_has_item = true;
return p_item.get();
}
}
private:
stream *p_stream;
mutable appender_range<TC> p_item;
mutable bool p_has_item;
bool p_keep_nl;
};
template<typename T, typename TC>
inline stream_line_range<T, TC> stream::iter_lines(bool keep_nl) {
return stream_line_range<T, TC>{*this, keep_nl};
}
template<typename ...A>
inline void stream::write(A const &...args) {
format_spec sp{'s', p_loc};
(sp.format_value(iter(), args), ...);
}
template<typename ...A>
inline void stream::writef(string_range fmt, A const &...args) {
format_spec{fmt, p_loc}.format(iter(), args...);
}
}
#endif