/* 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 #include #include #include #include #include #include #ifndef OSTD_PLATFORM_WIN32 #include #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> struct stream_range; struct stream_error: std::system_error { using std::system_error::system_error; }; template> 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(c); write_bytes(&wc, 1); } template 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(); bool gotc = false; do { if (cr) { writer.put('\r'); cr = false; } if (c == '\r') { cr = true; continue; } writer.put(c); gotc = safe_get(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 void write(A const &...args); template void writeln(A const &...args) { write(args...); put_char('\n'); } template void writef(string_range fmt, A const &...args); template void writefln(string_range fmt, A const &...args) { writef(fmt, args...); put_char('\n'); } template stream_range iter(); template> stream_line_range iter_lines(bool keep_nl = false); template void put(T const *v, size_t count) { write_bytes(v, count * sizeof(T)); } template void put(T v) { write_bytes(&v, sizeof(T)); } template 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 void get(T &v) { if (read_bytes(&v, sizeof(T)) != sizeof(T)) { throw stream_error{EIO, std::generic_category()}; } } template 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 bool safe_get(T &c) { try { c = get(); return true; } catch (stream_error const &) { return false; } } std::locale p_loc; }; template struct stream_range: input_range> { 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(); } 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 front() const { if (p_item.has_value()) { return p_item.value(); } else { return (p_item = p_stream->get()).value(); } } void put(T val) { p_stream->put(val); } private: stream *p_stream; mutable std::optional p_item; }; template inline stream_range stream::iter() { return stream_range{*this}; } template struct stream_line_range: input_range> { 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{}); } } 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 p_item; mutable bool p_has_item; bool p_keep_nl; }; template inline stream_line_range stream::iter_lines(bool keep_nl) { return stream_line_range{*this, keep_nl}; } template inline void stream::write(A const &...args) { format_spec sp{'s', p_loc}; (sp.format_value(iter(), args), ...); } template inline void stream::writef(string_range fmt, A const &...args) { format_spec{fmt, p_loc}.format(iter(), args...); } } #endif