MT Core (C++)
Core library for replacing C++ standard in project usage
Loading...
Searching...
No Matches
io/writer.hpp
Go to the documentation of this file.
1/*
2
3Copyright 2025 Matthew Tolman
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17*/
18
19#ifndef MTCORE_WRITER_HPP
20#define MTCORE_WRITER_HPP
21
25#include "mtcore/traits.hpp"
26#include <functional>
27#include <ostream>
28
33
35namespace mtcore::io {
36 template<typename T>
37 struct Formatter;
38
39 struct FormatOptions;
40
41 template<WriterImpl Impl>
42 struct Writer;
43
50 template<WriterImpl Impl>
51 struct Writer {
52 using WriteElem = typename Impl::WriteElem;
53 using ErrType = typename Impl::ErrType;
54
56
62 Result<size_t, ErrType> write(const Slice<std::remove_const_t<WriteElem>> &s) { return write(s.to_const()); }
63
69 Result<size_t, ErrType> write(const Slice<std::add_const_t<WriteElem>> &s) { return underlying.write(s); }
70
77 Result<size_t, ErrType> write(const std::remove_const_t<WriteElem> &one) {
78 auto s = Slice<std::add_const_t<WriteElem>>{&one, 1};
79 return write_all(s);
80 }
81
87 if constexpr (is_flushable<Impl>) {
88 return underlying.flush();
89 }
90 return success();
91 }
92
98 void deinit(Allocator &allocator)
99 requires(AllocatorDeinit<Impl>)
100 {
101 underlying.deinit(allocator);
102 }
103
108 void deinit()
109 requires(DefaultDeinit<Impl>)
110 {
111 underlying.deinit();
112 }
113
118 [[nodiscard]] size_t bytes_written() const { return underlying.bytes_written(); }
119
127 Result<size_t, ErrType> write_all(const Slice<std::remove_const_t<WriteElem>> &s) {
128 return write_all(s.to_const());
129 }
130
134 [[nodiscard]] size_t bytes_written() const
135 requires(WriterImplBytes<Impl>)
136 {
137 return underlying.bytes_written();
138 }
139
140 [[nodiscard]] auto written() const
141 requires(WriterImplWritten<Impl>)
142 {
143 return underlying.written();
144 }
145
146 void reset()
148 {
149 underlying.reset();
150 }
151
159 Result<size_t, ErrType> write_all(const Slice<std::add_const_t<WriteElem>> &s) {
160 auto r = underlying.write(s);
161 if (r.is_error()) {
162 return r.error();
163 }
164 // if we're dealing with a write-through writer, we have to rely on the final check in the stack
165 // as there will be transformations, so we can't check for out of room here
166 if constexpr (!is_write_through<Impl>) {
167 if (r.value() != s.size()) {
168 return error(ErrType::OUT_OF_ROOM);
169 }
170 }
171 return success(r.value());
172 }
173
182 Result<void, ErrType> write_n_times(const Slice<std::remove_const_t<WriteElem>> &s, size_t n) {
183 return write_n_times(s.to_const(), n);
184 }
185
194 Result<size_t, ErrType> write_n_times(const Slice<std::add_const_t<WriteElem>> &s, size_t n) {
195 for (size_t i = 0; i < n; ++i) {
196 auto r = write_all(s);
197 if (r.is_error()) {
198 return r.error();
199 }
200 }
201 return success(n);
202 }
203
212 Result<size_t, ErrType> write_n_times(const std::remove_const_t<WriteElem> &one, size_t n) {
213 auto s = Slice<std::add_const_t<WriteElem>>{&one, 1};
214 for (size_t i = 0; i < n; ++i) {
215 auto r = write_all(s);
216 if (r.is_error()) {
217 return r.error();
218 }
219 }
220 return success(n);
221 }
222 };
223
228 enum class SliceWriteError {
229 OUT_OF_ROOM, // necessary for writers
230 };
231
236 enum class VoidWriteError {
238 };
239
244 enum class OstreamWriteErr {
246 };
247
248 namespace impl {
249 template<typename T>
250 struct SliceWriterImpl {
251 static_assert(!std::is_const_v<T>, "Cannot write to const pointer!");
252 Slice<T> out;
253 size_t curWriteIndex = 0;
254
255 using WriteElem = T;
256 using ErrType = SliceWriteError;
257
258 Result<size_t, ErrType> write(Slice<std::add_const_t<T>> bytes) {
259 size_t i = 0;
260 for (; i < bytes.size() && curWriteIndex < out.size(); ++i, ++curWriteIndex) {
261 out[curWriteIndex] = bytes[i];
262 }
263 return success(i);
264 }
265
266 [[nodiscard]] size_t bytes_written() const { return curWriteIndex; }
267
268 [[nodiscard]] Slice<T> written() const { return out.sub(0, bytes_written()); }
269
270 void reset() {
271 curWriteIndex = 0;
272#ifndef NDEBUG
273 if constexpr (std::is_same_v<T, char>) {
274 memset(out.head, 0, out.len * sizeof(char));
275 }
276#endif
277 }
278 };
279
280 template<typename T>
281 struct VoidWriterImpl {
282 using WriteElem = T;
283 using ErrType = VoidWriteError;
284 Result<size_t, ErrType> write(Slice<std::add_const_t<T>> elems) { return success(elems.size()); }
285 };
286
287 template<WriterImpl WI>
288 struct EnsureWriteable : std::true_type {};
289 static_assert(EnsureWriteable<VoidWriterImpl<char>>::value);
290
291 template<typename T>
292 struct OstreamWriterImpl {
293 using WriteElem = T;
294 using ErrType = OstreamWriteErr;
295 std::ostream &os;
296
297 Result<size_t, OstreamWriteErr> write(Slice<std::add_const_t<T>> elems) {
298 for (size_t i = 0; i < elems.size(); ++i) {
299 os << elems[i];
300 }
301 return success(elems.size());
302 }
303 };
304
305 template<typename InType, WriterImpl WI, typename Err = typename io::Writer<WI>::ErrType,
306 typename FuncType = std::function<Result<size_t, Err>(io::Writer<WI> &, Slice<std::add_const_t<InType>>)>>
307 struct WriteTransformer {
308 static constexpr bool IsWriteThrough = true;
309 using WriteElem = InType;
310 using ErrType = Err;
311 io::Writer<WI> &underlying;
312 FuncType mapper;
313
319 void deinit(Allocator &allocator)
320 requires(AllocatorDeinit<WI>)
321 {
322 underlying.deinit(allocator);
323 }
324
329 void deinit()
330 requires(DefaultDeinit<WI>)
331 {
332 underlying.deinit();
333 }
334
338 [[nodiscard]] size_t bytes_written() const
339 requires(WriterImplBytes<WI>)
340 {
341 return underlying.bytes_written();
342 }
343
347 [[nodiscard]] Slice<typename WI::WriteElem> written() const
348 requires(WriterImplWritten<WI>)
349 {
350 return underlying.written();
351 }
352
356 [[nodiscard]] size_t reset() const
357 requires(WriterImplResettable<WI>)
358 {
359 return underlying.reset();
360 }
361
366 Result<size_t, ErrType> write(Slice<std::add_const_t<InType>> elems) { return mapper(underlying, elems); }
367 };
368 } // namespace impl
369
382 template<typename T, WriterImpl WI, typename Err = typename io::Writer<WI>::ErrType,
383 typename FuncType = std::function<Result<size_t, Err>(io::Writer<WI> &, Slice<std::add_const_t<T>>)>>
387
395 template<typename T>
397 return io::Writer<impl::SliceWriterImpl<T>>{.underlying = impl::SliceWriterImpl<T>{.out = out}};
398 }
399
407 template<typename T>
411
419 template<Formattable T>
420 auto ostream_writer(std::ostream &os) {
421 return io::Writer<impl::OstreamWriterImpl<T>>{.underlying = impl::OstreamWriterImpl<T>{os}};
422 }
423} // namespace mtcore::io
424
425#include "../colls/result.hpp"
426#include "format.hpp"
427
428namespace mtcore::io {
443 template<WriterImpl WI, typename Arg>
445 return Formatter<Arg>::fmt(writer, opts, arg);
446 }
447
481 template<WriterImpl WI, typename... Args>
482 Result<size_t, typename Writer<WI>::ErrType> print(Writer<WI> &writer, const char *fmt, const Args &...args) {
483 return print(writer, slice_from(fmt), args...);
484 }
485
486 namespace impl {
487 template<WriterImpl WI, typename A, typename... Args>
488 Result<size_t, typename Writer<WI>::ErrType> print(size_t written, Writer<WI> &writer, Slice<const char> fmt,
489 const A &curArg, const Args &...args);
490
491 template<WriterImpl WI>
492 Result<size_t, typename Writer<WI>::ErrType> print(size_t written, Writer<WI> &writer, Slice<const char> fmt);
493 } // namespace impl
494
528 template<WriterImpl WI, typename... Args>
530 const Args &...args) {
531 return impl::print(0, writer, fmt, args...);
532 }
533
534 namespace impl {
535 struct NextEscape {
536 enum class EscapeType { CHAR, FORMAT };
537 EscapeType type;
538 size_t startIndex;
539 size_t endIndex;
540 Slice<const char> escaped;
541 };
542
543 inline Optional<NextEscape> next_escape(Slice<const char> fmt) {
544 static constexpr auto printStartChars = "{";
545 static constexpr auto printStart = slice_from(printStartChars);
546 static constexpr auto printEndChars = "}";
547 static constexpr auto printEnd = slice_from(printEndChars);
548 static constexpr auto esc = '\\';
549
550 for (size_t i = 0; i < fmt.size(); ++i) {
551 if (fmt[i] == esc) {
552 ensure(i + 1 < fmt.size(), "INVALID FORMAT STRING! EXPECTED CHARACTER AFTER '\\'");
553 return NextEscape{NextEscape::EscapeType::CHAR, i, i + 1, fmt.sub(i + 1, 1)};
554 }
555 else if (slices::starts_with(printStart, fmt.sub(i))) {
556 auto endOpt = slices::first_index_not_proceeded_by('\\', printEnd, fmt.sub(i + printStart.size()));
557 ensure(endOpt.has_value(), "INVALID FORMAT STRING! UNTERMINATED FORMATTING SPECIFIER! EXPECTED '}'");
558 auto escaped = fmt.sub(i + printStart.size(), endOpt.value());
559 auto end = endOpt.value() + i + printStart.size();
560 return NextEscape{NextEscape::EscapeType::FORMAT, i, end, escaped};
561 }
562 }
563 return nullopt;
564 }
565
566 template<WriterImpl WI>
567 Result<size_t, typename Writer<WI>::ErrType> print(size_t written, Writer<WI> &writer, Slice<const char> fmt) {
568 if (fmt.empty()) {
569 return written;
570 }
571
572 using WriteAllRes = Result<size_t, typename Writer<WI>::ErrType>;
573 for (size_t i = 0; !fmt.empty() && i < 1000; ++i) {
574 auto s = next_escape(fmt);
575 if (!s.has_value()) {
576 written += fmt.size();
577 writer.write_all(fmt);
578 break;
579 }
580
581 NextEscape ne = s.value();
582 auto prefix = fmt.sub(0, ne.startIndex);
583 if (!prefix.empty()) {
584 WriteAllRes pRes = writer.write_all(prefix);
585 if (pRes.is_error()) {
586 return pRes.error();
587 }
588 written += prefix.size();
589 }
590
591 ensure(ne.type == NextEscape::EscapeType::CHAR, "Missing argument for format specifier");
592 WriteAllRes escRes = writer.write_all(ne.escaped);
593 if (escRes.is_error()) {
594 return escRes.error();
595 }
596 written += ne.escaped.size();
597 fmt = fmt.sub(ne.endIndex + 1);
598 }
599 return written;
600 }
601
602 template<WriterImpl WI, typename A, typename... Args>
603 Result<size_t, typename Writer<WI>::ErrType> print(size_t written, Writer<WI> &writer, Slice<const char> fmt,
604 const A &curArg, const Args &...args) {
605 using PrintRes = Result<size_t, typename Writer<WI>::ErrType>;
606 using WriteAllRes = Result<size_t, typename Writer<WI>::ErrType>;
607 for (size_t i = 0; !fmt.empty() && i < 1000; ++i) {
608 auto s = next_escape(fmt);
609 ensure(s.has_value(), "Missing format specifier for argument");
610
611 NextEscape ne = s.value();
612 auto prefix = fmt.sub(0, ne.startIndex);
613 if (!prefix.empty()) {
614 WriteAllRes pRes = writer.write_all(prefix);
615 if (pRes.is_error()) {
616 return pRes.error();
617 }
618 written += prefix.size();
619 }
620
621 if (ne.type == NextEscape::EscapeType::CHAR) {
622 WriteAllRes escRes = writer.write_all(ne.escaped);
623 if (escRes.is_error()) {
624 return escRes.error();
625 }
626 written += ne.escaped.size();
627 fmt = fmt.sub(ne.endIndex + 1);
628 }
629 else {
630 PrintRes fRes = format(writer, FormatOptions{ne.escaped}, curArg);
631 if (fRes.is_error()) {
632 return fRes.error();
633 }
634 written += fRes.value();
635 return print(written, writer, fmt.sub(ne.endIndex + 1), args...);
636 }
637 }
638 unreachable("Missing Format Specifier for Argument");
639 }
640 } // namespace impl
641} // namespace mtcore::io
642
643#endif // MTCORE_WRITER_HPP
Represents a type that has a deinit which takes an allocator (usually does memory cleanup)
Represents a type that has a default (no allocator) deinit method.
Represents an implementation of a writer that we can get how many bytes were written.
Represents an implementation of a writer that we can reset.
Represents an implementation of a writer that we can get the output.
Represents an implementation of a writer (basically checks if the type can be wrapped with Writer<>)
bool starts_with(const Slice< T > &needle, const Slice< T > &haystack)
Checks whether a slice (the haystack) starts with elements in another slice in the same order (the ne...
constexpr auto nullopt
Placeholder value for an empty Optional.
Definition optional.hpp:409
mtcore::Optional< size_t > first_index_not_proceeded_by(const std::remove_const_t< T > &prefix, const std::remove_const_t< T > &needle, const Slice< T > &haystack)
Gets the first index that a needle appears in the haystack, or nullopt if the needle does not appear ...
constexpr Slice< const char32_t > slice_from(char32_t *cstr)
Creates a slice from a utf32 string in the form of a c string.
io::Writer< csv::impl::Writer< WI > > writer(io::Writer< WI > &underlying, Options opts={})
Creates a CSV writer which will encode the data before writing it out.
SliceWriteError
Errors when writing to a slice.
OstreamWriteErr
Errors when writing to a slice.
VoidWriteError
Errors when writing to a slice.
Result< size_t, typename Writer< WI >::ErrType > print(Writer< WI > &writer, const char *fmt, const Args &...args)
Prints arguments using a format string Element insert points are designated by a pair of curly braces...
#define ensure(check,...)
Ensures that a check holds true, aborts the program if not true Will print error if the condition is ...
#define unreachable(...)
Marks code as unreachable.
Success< void > success()
Creates a successful void Result object.
Definition result.hpp:398
Error< Underlying > error(Underlying err)
Creates an error.
Definition result.hpp:425
constexpr bool is_flushable
Boolean check for if something has the trait Flushable.
io::Writer< impl::WriteTransformer< T, WI, Err, FuncType > > write_transformer(io::Writer< WI > &writer, FuncType mapper)
A specialized writer that will transform input data before passing it through to another writer Usefu...
Result< size_t, typename Writer< WI >::ErrType > format(Writer< WI > &writer, const FormatOptions &opts, Arg arg)
Generic format function which takes a bunch of arguments (of the same type) and formatting options an...
io::Writer< impl::VoidWriterImpl< T > > void_writer()
Creates a writer which discards what's written (think of it as writer to /dev/null) Useful for doing ...
auto ostream_writer(std::ostream &os)
Creates a writer which passes its output to an ostream Useful for compatibility with the C++ standard...
io::Writer< impl::SliceWriterImpl< T > > slice_writer(Slice< T > out)
Creates a writer to write to a Slice.
constexpr bool is_write_through
Checks if a writer type is a write-through writer.
auto ne(const L &left, const R &right)
Inequality comparison (used by macros)
Definition mttest.hpp:304
Represents a memory allocator Exact behavior depends on the underlying VTable used Should use the a_*...
Represents a Result that may have an error (error code) or a success value A type of "void" means the...
Definition result.hpp:170
A Slice which is just a pointer + length Accessing elements through the array operator will do bounds...
constexpr Slice sub(size_t start) const noexcept
Gets a sub Slice from start.
constexpr size_t size() const noexcept
Gets the size of a Slice.
Options for specifying formatting.
Definition format.hpp:36
Struct to override to specify how a type should be formatted.
Definition format.hpp:45
A writer that writes data to some sort of stream or buffer Note: the data elements written should be ...
Definition io/writer.hpp:51
size_t bytes_written() const
Gets the number of bytes written.
Result< size_t, ErrType > write(const std::remove_const_t< WriteElem > &one)
Will write a single element If there is no room, will return the error OUT_OF_ROOM.
Definition io/writer.hpp:77
Result< size_t, ErrType > write_n_times(const std::remove_const_t< WriteElem > &one, size_t n)
Will write a single element N times If there is no room, will return the error OUT_OF_ROOM Note: May ...
typename Impl::WriteElem WriteElem
Definition io/writer.hpp:52
size_t bytes_written() const
Result< size_t, ErrType > write(const Slice< std::add_const_t< WriteElem > > &s)
Will write as much of a Slice as possible.
Definition io/writer.hpp:69
Result< void, ErrType > write_n_times(const Slice< std::remove_const_t< WriteElem > > &s, size_t n)
Will write all the elements in a Slice N times If there is no room, will return the error OUT_OF_ROOM...
void deinit(Allocator &allocator)
Cleans up any memory stuff Only usable if the underlying writer has a deinit that takes an allocator.
Definition io/writer.hpp:98
typename Impl::ErrType ErrType
Definition io/writer.hpp:53
void deinit()
Cleans up writer Only usable if the underlying writer has a deinit that takes an allocator.
Result< size_t, ErrType > write_n_times(const Slice< std::add_const_t< WriteElem > > &s, size_t n)
Will write all the elements in a Slice N times If there is no room, will return the error OUT_OF_ROOM...
Result< void, ErrType > flush()
Flushes a writer (i.e.
Definition io/writer.hpp:86
auto written() const
Result< size_t, ErrType > write_all(const Slice< std::add_const_t< WriteElem > > &s)
Will write all the elements in a Slice If there is no room, will return the error OUT_OF_ROOM Note: M...
Result< size_t, ErrType > write(const Slice< std::remove_const_t< WriteElem > > &s)
Will write as much of a Slice as possible.
Definition io/writer.hpp:62
Result< size_t, ErrType > write_all(const Slice< std::remove_const_t< WriteElem > > &s)
Will write all the elements in a Slice If there is no room, will return the error OUT_OF_ROOM Note: M...