MT Core (C++)
Core library for replacing C++ standard in project usage
Loading...
Searching...
No Matches
io/reader.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_READER_HPP
20#define MTCORE_READER_HPP
21
25#include "mtcore/traits.hpp"
26
31
33namespace mtcore::io {
34
41 template<ReaderImpl Impl>
42 struct Reader {
43 using ReadElem = typename Impl::ReadElem;
44 using ErrType = typename Impl::ErrType;
45
48
50
60
69 auto res = read(buff);
70 if (res.is_success() && res.value().size() < buff.size()) {
71 return error(ErrType::END_OF_FILE);
72 }
73 return res;
74 }
75
84 auto res = read(buff);
85 if (res.is_error()) {
86 return res.error();
87 }
88 auto resSlice = res.value();
89 if (resSlice.size() < buff.size()) {
90 return resSlice;
91 }
92
93 // If there's another element not read, then we didn't read everything
94 if (auto ro = read_one(); ro.is_success() || ro.error().code != ErrType::END_OF_FILE) {
95 return error(ErrType::SIZE_EXCEEDED);
96 }
97 return resSlice;
98 }
99
113 Result<BuffSlice, ErrType> read_all(Allocator &alloc, size_t maxSize = std::numeric_limits<size_t>::max()) {
114 ensure(maxSize > 0, "BAD MAX SIZE");
115 // If we have no limit or a really high limit (> 100MB), we're going to allocate in chunks
116 if (maxSize == std::numeric_limits<size_t>::max() || maxSize >= 100000000) {
117 // A segmented list will be use to track the chunks
118 // We'll store the actual chunks here rather than copy the individual items
120 if (auto res = fullBuff.init(alloc, 10); res.is_error()) {
121 return error(ErrType::ALLOCATION_FAILED);
122 }
123 // We will do a full copy before a successful return
124 // So we can safely set it up to always clean up the full buff
125 mtdefer {
126 BuffSlice curSlice;
127 auto iter = fullBuff.iter();
128 while (iter.next().copy_if_present(curSlice)) {
129 alloc.destroy_many(curSlice);
130 }
131 fullBuff.deinit(alloc);
132 };
133
134 size_t count = 0;
135
136 bool atEnd = false;
137 do {
138 bool added = false;
139 // We do chunks of around 8,000 items
140 auto curRes = alloc.create_many<std::remove_const_t<ReadElem>>(8000);
141 if (curRes.is_error()) {
142 return error(ErrType::ALLOCATION_FAILED);
143 }
144
145 auto cur = curRes.value();
146 mtdefer {
147 if (!added) {
148 alloc.destroy_many(cur);
149 }
150 };
151
152 auto readRes = read(cur);
153 if (readRes.is_error()) {
154 return error(ErrType::ALLOCATION_FAILED);
155 }
156
157 auto readData = readRes.value();
158 if (readData.empty()) {
159 atEnd = true;
160 }
161 else {
162 auto toPush = cur.sub(0, readData.size());
163 if (auto pushRes = fullBuff.push(alloc, toPush); pushRes.is_error()) {
164 return error(ErrType::ALLOCATION_FAILED);
165 }
166
167 added = true;
168 count += readData.size();
169
170 if (readData.size() < cur.size()) {
171 atEnd = true;
172 }
173 }
174 } while (!atEnd);
175
176 if (count == 0) {
177 return BuffSlice{nullptr, 0};
178 }
179
180 // We have everything chunked in memory, but our contract is to return a contiguous set of bytes
181 // To do this, we need to do one final copy into contiguous memory
182 auto finalRes = alloc.create_many<std::remove_const_t<ReadElem>>(count);
183 if (finalRes.is_error()) {
184 return error(ErrType::ALLOCATION_FAILED);
185 }
186
187 auto finalSlice = finalRes.value();
188 // We'll use a Slice writer to make things a little simpler
189 auto finalWriter = slice_writer(finalSlice);
190 // Since we stored the chunks, we can just iterate each chunk and write the entire chunk at once
191 auto iter = fullBuff.iter();
192 BuffSlice curBuff;
193 while (iter.next().copy_if_present(curBuff)) {
194 if (auto r = finalWriter.write_all(curBuff); r.is_error()) {
195 alloc.destroy_many(finalSlice);
196 return error(ErrType::ALLOCATION_FAILED);
197 }
198 }
199
200 // Return our Result
201 return finalSlice;
202 }
203 else {
204 // If we have a smal enough max size, just read it all into one chunk of memory
205 auto outRes = alloc.create_many<std::remove_const_t<ReadElem>>(maxSize);
206 if (outRes.is_error()) {
207 return error(ErrType::ALLOCATION_FAILED);
208 }
209 auto out = outRes.value();
210 auto res = read_all(out);
211 if (res.is_error()) {
212 alloc.destroy_many(out);
213 return res.error();
214 }
215 auto finalRes = res.value();
216
217 // Check to see if we can save memory if we shrink things down
218 // This is more CPU time, but less memory pressure
219 if (finalRes.size() + alignof(ReadElem) < out.size()) {
220 if (finalRes.size() == 0) {
221 alloc.destroy_many(out);
222 return BuffSlice{nullptr, 0};
223 }
224 // Try to create a sub optimal buffer
225 auto smallerRes = alloc.create_many<std::remove_const_t<ReadElem>>(finalRes.size());
226 if (smallerRes.is_error()) {
227 // we have it already in memory, it's just suboptimal. Return it anyway
228 return out.sub(0, finalRes.size());
229 }
230 auto smaller = smallerRes.value();
231 auto w = slice_writer(smaller);
232 ensure(w.write_all(finalRes).is_success(), "SOMETHING IS WRONG WITH SLICE WRITER!");
233 alloc.destroy_many(out);
234 return success(smaller);
235 }
236 return success(out.sub(0, finalRes.size()));
237 }
238 }
239
246 std::remove_const_t<ReadElem> elem;
247 auto s = Slice<std::remove_const_t<ReadElem>>{&elem, 1};
248 auto readRes = read(s);
249 if (readRes.is_error()) {
250 return readRes.error();
251 }
252 auto res = readRes.value();
253 if (res.empty()) {
254 return error(ErrType::END_OF_FILE);
255 }
256 return elem;
257 }
258
266 for (size_t i = 0; i < n; ++i) {
267 auto r = read_one();
268 if (r.is_error()) {
269 return r.error();
270 }
271 }
272 return success();
273 }
274 };
275
282 namespace readers {
283 template<ReaderImpl RI>
295 template<typename T>
298 const std::remove_const_t<typename Reader<T>::ReadElem> &delim) {
299 using ErrType = typename Reader<T>::ErrType;
300 for (size_t i = 0; i < buff.size(); ++i) {
301 auto chRes = self.read_one();
302 if (chRes.is_error()) {
303 if (chRes.error().code == ErrType::END_OF_FILE) {
304 return buff.sub(0, i).to_const();
305 }
306 return chRes.error();
307 }
308 auto ch = chRes.value();
309 if (ch == delim) {
310 return buff.sub(0, i).to_const();
311 }
312 buff[i] = ch;
313 }
314 auto nxt = self.read_one();
315 if (nxt.is_error()) {
316 if (nxt.error().code == ErrType::END_OF_FILE) {
317 return buff.to_const();
318 }
319 return nxt.error();
320 }
321 if (nxt.value() != delim) {
322 return error(ErrType::SIZE_EXCEEDED);
323 }
324 return buff.to_const();
325 }
326
340 template<typename T>
342 read_until_or_eof(Reader<T> &self, Allocator &alloc, const std::remove_const_t<typename Reader<T>::ReadElem> &delim,
343 size_t maxSize = std::numeric_limits<size_t>::max()) {
344 using ErrType = typename Reader<T>::ErrType;
345 using BuffSlice = typename Reader<T>::BuffSlice;
346 using ReadElem = typename Reader<T>::ReadElem;
347 ensure(maxSize > 0, "BAD MAX SIZE");
348 // If we have no limit or a really high limit (> 100MB), we're going to allocate in chunks
349 if (maxSize == std::numeric_limits<size_t>::max() || maxSize >= 100000000) {
350 // A segmented list will be use to track the data
352 if (auto res = fullBuff.init(alloc, 4000); res.is_error()) {
353 return error(ErrType::ALLOCATION_FAILED);
354 }
355 // We will do a full copy before a successful return
356 // So we can safely set it up to always clean up the full buff
357 mtdefer { fullBuff.deinit(alloc); };
358
359 size_t count = 0;
360
361 while (true) {
362 auto readRes = self.read_one();
363 if (readRes.is_error()) {
364 if (readRes.error().code == ErrType::END_OF_FILE) {
365 break;
366 }
367 return error(ErrType::ALLOCATION_FAILED);
368 }
369
370 auto cur = readRes.value();
371 if (cur == delim) {
372 break;
373 }
374 if (auto pushRes = fullBuff.push(alloc, cur); pushRes.is_error()) {
375 return error(ErrType::ALLOCATION_FAILED);
376 }
377 ++count;
378 }
379
380 if (count == 0) {
381 return BuffSlice{nullptr, 0};
382 }
383
384 // We have everything chunked in memory, but our contract is to return a contiguous set of bytes
385 // To do this, we need to do one final copy into contiguous memory
386 auto finalRes = alloc.create_many<std::remove_const_t<ReadElem>>(count);
387 if (finalRes.is_error()) {
388 return error(ErrType::ALLOCATION_FAILED);
389 }
390
391 auto finalSlice = finalRes.value();
392 // We'll use a Slice writer to make things a little simpler
393 auto finalWriter = slice_writer(finalSlice);
394 // Since we stored the chunks, we can just iterate each chunk and write the entire chunk at once
395 auto iter = fullBuff.iter();
396 std::remove_const_t<ReadElem> curElem;
397 while (iter.next().copy_if_present(curElem)) {
398 if (auto r = finalWriter.write(curElem); r.is_error()) {
399 alloc.destroy_many(finalSlice);
400 return error(ErrType::ALLOCATION_FAILED);
401 }
402 }
403
404 // Return our Result
405 return finalSlice;
406 }
407 else {
408 // If we have a smal enough max size, just read it all into one chunk of memory
409 auto outRes = alloc.create_many<std::remove_const_t<ReadElem>>(maxSize);
410 if (outRes.is_error()) {
411 return error(ErrType::ALLOCATION_FAILED);
412 }
413 auto out = outRes.value();
414 auto res = read_until_or_eof(self, out, delim);
415 if (res.is_error()) {
416 alloc.destroy_many(out);
417 return res.error();
418 }
419 auto finalRes = res.value();
420
421 // Check to see if we can save memory if we shrink things down
422 // This is more CPU time, but less memory pressure
423 if (finalRes.size() + alignof(ReadElem) < out.size()) {
424 if (finalRes.size() == 0) {
425 alloc.destroy_many(out);
426 return BuffSlice{nullptr, 0};
427 }
428 // Try to create a sub optimal buffer
429 auto smallerRes = alloc.create_many<std::remove_const_t<ReadElem>>(finalRes.size());
430 if (smallerRes.is_error()) {
431 // we have it already in memory, it's just suboptimal. Return it anyway
432 return out.sub(0, finalRes.size());
433 }
434 auto smaller = smallerRes.value();
435 auto w = slice_writer(smaller);
436 ensure(w.write_all(finalRes).is_success(), "SOMETHING IS WRONG WITH SLICE WRITER!");
437 alloc.destroy_many(out);
438 return success(smaller);
439 }
440 return success(out.sub(0, finalRes.size()));
441 }
442 }
443
455 template<typename T>
458 const std::remove_const_t<typename Reader<T>::ReadElem> &delim) {
459 using ErrType = typename Reader<T>::ErrType;
460 for (size_t i = 0; i < buff.size(); ++i) {
461 auto chRes = self.read_one();
462 if (chRes.is_error()) {
463 if (chRes.error().code == ErrType::END_OF_FILE) {
464 return error(ErrType::END_OF_FILE);
465 }
466 return chRes.error();
467 }
468 auto ch = chRes.value();
469 if (ch == delim) {
470 return buff.sub(0, i).to_const();
471 }
472 buff[i] = ch;
473 }
474 auto nxt = self.read_one();
475 if (nxt.is_error()) {
476 return nxt.error();
477 }
478 if (nxt.value() != delim) {
479 return error(ErrType::SIZE_EXCEEDED);
480 }
481 return buff.to_const();
482 }
483
498 template<typename T>
500 read_until_no_eof(Reader<T> &self, Allocator &alloc, const std::remove_const_t<typename Reader<T>::ReadElem> &delim,
501 size_t maxSize = std::numeric_limits<size_t>::max()) {
502 using ErrType = typename Reader<T>::ErrType;
503 using BuffSlice = typename Reader<T>::BuffSlice;
504 using ReadElem = typename Reader<T>::ReadElem;
505 ensure(maxSize > 0, "BAD MAX SIZE");
506 // If we have no limit or a really high limit (> 100MB), we're going to allocate in chunks
507 if (maxSize == std::numeric_limits<size_t>::max() || maxSize >= 100000000) {
508 // A segmented list will be use to track the data
510 if (auto res = fullBuff.init(alloc, 4000); res.is_error()) {
511 return error(ErrType::ALLOCATION_FAILED);
512 }
513 // We will do a full copy before a successful return
514 // So we can safely set it up to always clean up the full buff
515 mtdefer { fullBuff.deinit(alloc); };
516
517 size_t count = 0;
518
519 while (true) {
520 auto readRes = self.read_one();
521 if (readRes.is_error()) {
522 if (readRes.error().code == ErrType::END_OF_FILE) {
523 return error(ErrType::END_OF_FILE);
524 }
525 return error(ErrType::ALLOCATION_FAILED);
526 }
527
528 auto cur = readRes.value();
529 if (cur == delim) {
530 break;
531 }
532 if (auto pushRes = fullBuff.push(alloc, cur); pushRes.is_error()) {
533 return error(ErrType::ALLOCATION_FAILED);
534 }
535 ++count;
536 }
537
538 if (count == 0) {
539 return BuffSlice{nullptr, 0};
540 }
541
542 // We have everything chunked in memory, but our contract is to return a contiguous set of bytes
543 // To do this, we need to do one final copy into contiguous memory
544 auto finalRes = alloc.create_many<std::remove_const_t<ReadElem>>(count);
545 if (finalRes.is_error()) {
546 return error(ErrType::ALLOCATION_FAILED);
547 }
548
549 auto finalSlice = finalRes.value();
550 // We'll use a Slice writer to make things a little simpler
551 auto finalWriter = slice_writer(finalSlice);
552 // Since we stored the chunks, we can just iterate each chunk and write the entire chunk at once
553 auto iter = fullBuff.iter();
554 std::remove_const_t<ReadElem> curElem;
555 while (iter.next().copy_if_present(curElem)) {
556 if (auto r = finalWriter.write(curElem); r.is_error()) {
557 alloc.destroy_many(finalSlice);
558 return error(ErrType::ALLOCATION_FAILED);
559 }
560 }
561
562 // Return our Result
563 return finalSlice;
564 }
565 else {
566 // If we have a smal enough max size, just read it all into one chunk of memory
567 auto outRes = alloc.create_many<std::remove_const_t<ReadElem>>(maxSize);
568 if (outRes.is_error()) {
569 return error(ErrType::ALLOCATION_FAILED);
570 }
571 auto out = outRes.value();
572 auto res = read_until_no_eof(self, out, delim);
573 if (res.is_error()) {
574 alloc.destroy_many(out);
575 return res.error();
576 }
577 auto finalRes = res.value();
578
579 // Check to see if we can save memory if we shrink things down
580 // This is more CPU time, but less memory pressure
581 if (finalRes.size() + alignof(ReadElem) < out.size()) {
582 if (finalRes.size() == 0) {
583 alloc.destroy_many(out);
584 return BuffSlice{nullptr, 0};
585 }
586 // Try to create a sub optimal buffer
587 auto smallerRes = alloc.create_many<std::remove_const_t<ReadElem>>(finalRes.size());
588 if (smallerRes.is_error()) {
589 // we have it already in memory, it's just suboptimal. Return it anyway
590 return out.sub(0, finalRes.size());
591 }
592 auto smaller = smallerRes.value();
593 auto w = slice_writer(smaller);
594 ensure(w.write_all(finalRes).is_success(), "SOMETHING IS WRONG WITH SLICE WRITER!");
595 alloc.destroy_many(out);
596 return success(smaller);
597 }
598 return success(out.sub(0, finalRes.size()));
599 }
600 }
601 } // namespace readers
602
609
610 namespace impl {
611 template<typename T>
612 struct SliceReaderImpl {
613 Slice<T> buff;
614 size_t curReadIndex = 0;
615 bool first = true;
616
617 using ReadElem = T;
618 using ErrType = SliceReaderError;
619
620 using BuffSlice = Slice<std::remove_const_t<ReadElem>>;
621 using ResSlice = Slice<std::add_const_t<ReadElem>>;
622
623 Result<ResSlice, ErrType> read(BuffSlice out) {
624 // Return at least an empty Slice when the Slice is empty
625 if (!first && curReadIndex >= buff.size()) {
626 return error(ErrType::END_OF_FILE);
627 }
628 first = false;
629 size_t count = 0;
630 for (; curReadIndex < buff.size() && count < out.size(); ++curReadIndex, ++count) {
631 out[count] = buff[curReadIndex];
632 }
633 return success(out.sub(0, count).to_const());
634 }
635 };
636 } // namespace impl
637
645 template<typename T>
647 return Reader<impl::SliceReaderImpl<T>>{.underlying = impl::SliceReaderImpl<T>{.buff = buff}};
648 }
649} // namespace mtcore::io
650
651#endif // MTCORE_READER_HPP
SliceReaderError
Errors that can happen from the Slice Reader Note that these are all required by the Reader class as ...
#define ensure(check,...)
Ensures that a check holds true, aborts the program if not true Will print error if the condition is ...
#define mtdefer
Defer statement that will mtdefer execution until the scope is left, at which point the code will run...
Result< typename Reader< T >::ResSlice, typename Reader< T >::ErrType > read_until_or_eof(Reader< T > &self, typename Reader< T >::BuffSlice buff, const std::remove_const_t< typename Reader< T >::ReadElem > &delim)
Reads data from a reader into a Slice up until there is either a delimiter or end of file If the buff...
Result< typename Reader< T >::ResSlice, typename Reader< T >::ErrType > read_until_no_eof(Reader< T > &self, typename Reader< T >::BuffSlice buff, const std::remove_const_t< typename Reader< T >::ReadElem > &delim)
Reads data from a reader into a Slice up until there is either a delimiter If the buffer is not big e...
Reader< impl::SliceReaderImpl< T > > slice_reader(Slice< T > buff)
Creates a reader to read the contents of a Slice.
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
io::Writer< impl::SliceWriterImpl< T > > slice_writer(Slice< T > out)
Creates a writer to write to a Slice.
Additional algorithms that can be done on a reader without having to extend the reader type We're enc...
Reader< RI > Reader
Generic iterator defaults built on common contracts Does not guarantee performance of iterators Actua...
Definition iter.hpp:91
Represents a memory allocator Exact behavior depends on the underlying VTable used Should use the a_*...
void destroy_many(Slice< T > s)
Destroys objects allocated by this allocator by calling the destructors and freeing memory.
Result< Slice< T >, AllocationError > create_many(size_t count=1)
Allocates some block of memory with an allocator with a known type Will call default constructor on t...
Represents a Result that may have an error (error code) or a success value A type of "void" means the...
Definition result.hpp:170
Segmented list where each segment contains multiple nodes Allows growing the list without having to i...
Result< void, CollectionAddNoAllocationError > push(const T &elem) noexcept
Pushes a new element.
void deinit(Allocator &alloc)
Cleans up memory tied to segmented list.
Result< void, AllocationError > init(Allocator &alloc, size_t initCapacity)
Initializes a segmented list.
ConstIter iter() const noexcept
Const iterator that gives element references.
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.
constexpr Slice< std::add_const_t< T > > to_const() const noexcept
Converts to a const Slice.
A reader that reads data from some sort of stream or buffer Note: the data elements read should be tr...
Definition io/reader.hpp:42
Result< ResSlice, ErrType > read_no_eof(BuffSlice buff)
Tries to read data from a reader into a buffer It will stop once the buffer is full,...
Definition io/reader.hpp:68
Slice< std::remove_const_t< ReadElem > > BuffSlice
Definition io/reader.hpp:46
Result< ResSlice, ErrType > read(BuffSlice buff)
Tries to read data from a reader into a buffer It will stop once the buffer is full,...
Definition io/reader.hpp:59
Result< ResSlice, ErrType > read_all(BuffSlice buff)
Tries to read all data from a reader into a buffer It will stop once the EOF is reached If the buffer...
Definition io/reader.hpp:83
typename Impl::ReadElem ReadElem
Definition io/reader.hpp:43
typename Impl::ErrType ErrType
Definition io/reader.hpp:44
Slice< std::add_const_t< ReadElem > > ResSlice
Definition io/reader.hpp:47
Result< void, ErrType > skip(size_t n)
Will skip n items (basically reads and discards them) If there are not n items left to skip (aka it h...
Result< ReadElem, ErrType > read_one()
Will try to read a single item from the reader If there is no item to read (aka at EOF),...
Result< BuffSlice, ErrType > read_all(Allocator &alloc, size_t maxSize=std::numeric_limits< size_t >::max())
Tries to read all data from a reader into an internally allocated buffer Will try to shrink the buffe...