Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
InputStream.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
8#include "Stroika/Foundation/Math/Common.h"
11
12namespace Stroika::Foundation::Streams::InputStream {
13
14 /*
15 ********************************************************************************
16 *********************** InputStream::IRep<ELEMENT_TYPE> ************************
17 ********************************************************************************
18 */
19 template <typename ELEMENT_TYPE>
21 {
22 Require (this->IsSeekable ()); // subclassers must override if not seekable
23 SeekOffsetType offset = GetReadOffset ();
24 // note this impl assumes seek won't fail - perhaps should catch internally rather than std::terminate?
25 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&, this] () noexcept { this->SeekRead (eFromStart, offset); });
26 ElementType elts[1]; // typically not useful to know if more than one available, and typically more costly to read extras we will toss out
27 try {
28 optional<span<ELEMENT_TYPE>> o = this->Read (span{elts}, NoDataAvailableHandling::eDontBlock);
29 return o ? o->size () : optional<size_t>{};
30 }
31 catch (const EWouldBlock&) {
32 return nullopt;
33 }
34 catch (...) {
36 }
37 }
38 template <typename ELEMENT_TYPE>
40 {
41 return nullopt;
42 }
43 template <typename ELEMENT_TYPE>
44 auto InputStream::IRep<ELEMENT_TYPE>::SeekRead ([[maybe_unused]] Whence whence, [[maybe_unused]] SignedSeekOffsetType offset) -> SeekOffsetType
45 {
46 AssertNotImplemented (); // never call if not seekable and must override if IsSeekable
47 return 0;
48 }
49
50 /*
51 ********************************************************************************
52 *********************** InputStream::Ptr<ELEMENT_TYPE> *************************
53 ********************************************************************************
54 */
55 template <typename ELEMENT_TYPE>
56 inline InputStream::Ptr<ELEMENT_TYPE>::Ptr (const shared_ptr<IRep<ELEMENT_TYPE>>& rep)
57 : inherited{rep}
58 {
59 }
60 template <typename ELEMENT_TYPE>
62 : inherited{nullptr}
63 {
64 }
65#if !qCompilerAndStdLib_template_requires_doesnt_work_with_specialization_Buggy
66 template <typename ELEMENT_TYPE>
67 template <typename ASSTREAMABLE>
68 inline InputStream::Ptr<ELEMENT_TYPE>::Ptr (ASSTREAMABLE&& src)
69 requires requires (ASSTREAMABLE) { src.template As<Ptr<ELEMENT_TYPE>> (); }
70 : inherited{src.template As<Ptr<ELEMENT_TYPE>> ()}
71 {
72 }
73#endif
74 template <typename ELEMENT_TYPE>
76 {
77 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
78 GetRepRWRef ().CloseRead ();
79 Ensure (not IsOpen ());
80 }
81 template <typename ELEMENT_TYPE>
82 inline void InputStream::Ptr<ELEMENT_TYPE>::Close (bool reset)
83 {
84 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{this->_fThisAssertExternallySynchronized};
85 GetRepRWRef ().CloseRead ();
86 if (reset) {
87 this->reset ();
88 }
89 }
90 template <typename ELEMENT_TYPE>
92 {
93 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
94 return GetRepConstRef ().IsOpenRead ();
95 }
96 template <typename ELEMENT_TYPE>
97 inline auto InputStream::Ptr<ELEMENT_TYPE>::GetSharedRep () const -> shared_ptr<IRep<ELEMENT_TYPE>>
98 {
99 return Debug::UncheckedDynamicPointerCast<IRep<ELEMENT_TYPE>> (inherited::GetSharedRep ());
100 }
101 template <typename ELEMENT_TYPE>
102 inline auto InputStream::Ptr<ELEMENT_TYPE>::GetRepConstRef () const -> const IRep<ELEMENT_TYPE>&
103 {
104 return Debug::UncheckedDynamicCast<const IRep<ELEMENT_TYPE>&> (inherited::GetRepConstRef ());
105 }
106 template <typename ELEMENT_TYPE>
107 inline auto InputStream::Ptr<ELEMENT_TYPE>::GetRepRWRef () const -> IRep<ELEMENT_TYPE>&
108 {
109 return Debug::UncheckedDynamicCast<IRep<ELEMENT_TYPE>&> (inherited::GetRepRWRef ());
110 }
111 template <typename ELEMENT_TYPE>
113 {
114 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
115 Require (IsOpen ());
116 return GetRepConstRef ().GetReadOffset ();
117 }
118 template <typename ELEMENT_TYPE>
120 {
121 Require (offset < static_cast<SeekOffsetType> (numeric_limits<SignedSeekOffsetType>::max ()));
122 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
123 Require (IsOpen ());
124 Require (this->IsSeekable ());
125 return GetRepRWRef ().SeekRead (eFromStart, static_cast<SignedSeekOffsetType> (offset));
126 }
127 template <typename ELEMENT_TYPE>
129 {
130 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
131 Require (IsOpen ());
132 Require (this->IsSeekable ());
133 return GetRepRWRef ().SeekRead (whence, offset);
134 }
135 template <typename ELEMENT_TYPE>
136 inline auto InputStream::Ptr<ELEMENT_TYPE>::AvailableToRead () const -> optional<size_t>
137 {
138 return GetRepRWRef ().AvailableToRead ();
139 }
140 template <typename ELEMENT_TYPE>
141 optional<Memory::InlineBuffer<ELEMENT_TYPE>> InputStream::Ptr<ELEMENT_TYPE>::ReadAllAvailable () const
142 {
143 if (auto o = AvailableToRead ()) {
144 if (*o == 0) [[unlikely]] {
145 return Memory::InlineBuffer<ELEMENT_TYPE>{0}; // this means EOF
146 }
147 else {
148 // AvailableToRead often returns 1 if it doesn't know really how much is available. But always safe todo blocking read for larger number
149 // and will just return smaller span, which we can resize down to...
150 constexpr size_t kRoundUpTo_ = max<size_t> (1, 4 * 1024 / sizeof (ELEMENT_TYPE));
151 Memory::InlineBuffer<ELEMENT_TYPE> buf{Memory::eUninitialized, Math::RoundUpTo (*o, kRoundUpTo_)};
152 span<ELEMENT_TYPE> r = this->ReadBlocking (span<ELEMENT_TYPE>{buf}); // since available to read- this CANNOT BLOCK (but may return fewer elts)
153 Assert (r.data () == buf.data ());
154 Assert (r.size () <= buf.size ());
155 buf.ShrinkTo (r.size ());
156 return buf;
157 }
158 }
159 else {
160 return nullopt;
161 }
162 }
163 template <typename ELEMENT_TYPE>
165 {
166 return GetRepRWRef ().RemainingLength ();
167 }
168 template <typename ELEMENT_TYPE>
169 inline auto InputStream::Ptr<ELEMENT_TYPE>::Read (span<ElementType> intoBuffer, NoDataAvailableHandling blockFlag) const
170 -> optional<span<ElementType>>
171 {
172 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
173 Require (IsOpen ()); // note - its OK for Write() side of input stream to be closed
174 Require (not intoBuffer.empty ());
175 return GetRepRWRef ().Read (intoBuffer, blockFlag);
176 }
177 template <typename ELEMENT_TYPE>
178 inline auto InputStream::Ptr<ELEMENT_TYPE>::ReadOrThrow (span<ElementType> intoBuffer, NoDataAvailableHandling blockFlag) const -> span<ElementType>
179 {
180 if (auto o = Read (intoBuffer, blockFlag)) [[likely]] {
181 return *o;
182 }
183 Execution::Throw (EWouldBlock::kThe);
184 }
185 template <typename ELEMENT_TYPE>
186 inline auto InputStream::Ptr<ELEMENT_TYPE>::ReadBlocking () const -> optional<ElementType>
187 {
188 ElementType e{};
189 span<ElementType> r = ReadBlocking (span{&e, 1});
190 return r.empty () ? optional<ElementType>{} : e;
191 }
192 template <typename ELEMENT_TYPE>
193 inline auto InputStream::Ptr<ELEMENT_TYPE>::ReadBlocking (span<ElementType> intoBuffer) const -> span<ElementType>
194 {
195 return Memory::ValueOf (Read (intoBuffer, NoDataAvailableHandling::eBlockIfNoDataAvailable));
196 }
197 template <typename ELEMENT_TYPE>
198 auto InputStream::Ptr<ELEMENT_TYPE>::ReadBlocking (Memory::InlineBuffer<ElementType>* intoBuffer, ElementType upToSentinel) const
199 -> span<ElementType>
200 {
201 Require (intoBuffer->size () == 0);
202 while (auto oe = ReadBlocking ()) {
203 intoBuffer->push_back (*oe); // include the sentinel
204 if (*oe == upToSentinel) {
205 return span{intoBuffer->data (), intoBuffer->size () - 1}; // dont include the sentinel
206 }
207 }
208 return span{intoBuffer->data (), intoBuffer->size ()};
209 }
210 template <typename ELEMENT_TYPE>
211 inline auto InputStream::Ptr<ELEMENT_TYPE>::ReadNonBlocking (span<ElementType> intoBuffer) const -> optional<span<ElementType>>
212 {
213 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
214 Require (IsOpen ()); // note - its OK for Write() side of input stream to be closed
215 Require (not intoBuffer.empty ());
216 return GetRepRWRef ().Read (intoBuffer, NoDataAvailableHandling::eDontBlock);
217 }
218 template <typename ELEMENT_TYPE>
219 inline auto InputStream::Ptr<ELEMENT_TYPE>::PeekBlocking () const -> optional<ElementType>
220 {
221 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
222 Require (this->IsSeekable ());
223 Require (IsOpen ());
224 SeekOffsetType saved = GetOffset ();
225 auto result = this->ReadBlocking ();
226 this->Seek (saved);
227 return result;
228 }
229 template <typename ELEMENT_TYPE>
231 {
232 return not PeekBlocking ().has_value ();
233 }
234 template <typename ELEMENT_TYPE>
236 {
237 if (blockFlag == eBlockIfNoDataAvailable) {
238 return IsAtEOF ();
239 }
240 ELEMENT_TYPE ignored{};
241 Require (this->IsSeekable ());
242 SeekOffsetType saved = GetOffset ();
243 auto o = this->ReadNonBlocking (span{&ignored, 1});
244 this->Seek (saved);
245 return not o.has_value ();
246 }
247 template <typename ELEMENT_TYPE>
248 inline optional<size_t> InputStream::Ptr<ELEMENT_TYPE>::ReadNonBlocking () const
249 {
250 // DEPRECATED
251 return AvailableToRead ();
252 }
253 template <typename ELEMENT_TYPE>
254 inline optional<size_t> InputStream::Ptr<ELEMENT_TYPE>::ReadNonBlocking (ElementType* intoStart, ElementType* intoEnd) const
255 {
256 // DEPRECATED
257 try {
258 return this->Read (span{intoStart, intoEnd}, NoDataAvailableHandling::eDontBlock).size ();
259 }
260 catch (const EWouldBlock& e) {
261 return nullopt;
262 }
263 catch (...) {
265 }
266 }
267 template <typename ELEMENT_TYPE>
268 template <typename POD_TYPE>
269 inline POD_TYPE InputStream::Ptr<ELEMENT_TYPE>::ReadRaw () const
270 requires (same_as<ELEMENT_TYPE, byte> and is_standard_layout_v<POD_TYPE>)
271 {
272 POD_TYPE tmp; // intentionally don't zero-initialize
273 return this->ReadRaw (span{&tmp, 1})[0];
274 }
275 template <typename ELEMENT_TYPE>
276 template <typename POD_TYPE>
277 span<POD_TYPE> InputStream::Ptr<ELEMENT_TYPE>::ReadRaw (span<POD_TYPE> intoBuffer) const
278 requires (same_as<ELEMENT_TYPE, byte> and is_standard_layout_v<POD_TYPE>)
280 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
281 Require (IsOpen ());
282 Require (intoBuffer.size () == 1 or this->IsSeekable ());
283 auto byteSpan = as_writable_bytes (intoBuffer);
284 size_t n{Read (byteSpan)};
285 if (n < sizeof (POD_TYPE)) {
286 // must keep (blocking) reading til we have one POD_TYPE, and if we come up short, must throw EOF
287 while (n < sizeof (POD_TYPE)) {
288 auto ni = this->Read (byteSpan.subspan (n, sizeof (POD_TYPE) - n)).size ();
289 if (ni == 0) {
291 }
292 n += ni;
293 }
294 Assert (n == sizeof (POD_TYPE));
295 return intoBuffer.subspan (0, 1);
296 }
297 else {
298 // we win, just return the right span size
299 if (n % sizeof (POD_TYPE) != 0) {
300 // Read even number of objects and adjust seek pointer
301 this->Seek (eFromCurrent, -static_cast<SignedSeekOffsetType> (n % sizeof (POD_TYPE)));
302 }
303 // we win, just return the right span size
304 size_t nObjectsRead = n / sizeof (POD_TYPE);
305 Assert (1 <= nObjectsRead and nObjectsRead <= intoBuffer.size ());
306 return intoBuffer.subspan (0, nObjectsRead);
307 }
308 }
309 template <typename ELEMENT_TYPE>
311 requires (same_as<ELEMENT_TYPE, Character>)
312 {
314 Require (this->IsSeekable ());
315 StringBuilder result;
316 while (true) {
317 Character c = ReadBlocking ().value_or (Character{});
318 if (c.GetCharacterCode () == '\0') {
319 // EOF
320 return result.str ();
322 result.push_back (c);
323 if (c == '\n') {
324 return result.str ();
325 }
326 else if (c == '\r') {
327 c = this->ReadBlocking ().value_or (Character{});
328 // if CR is followed by LF, append that to result too before returning. Otherwise, put the character back
329 if (c == '\n') {
330 result.push_back (c);
331 }
332 else {
333 this->Seek (eFromCurrent, -1);
334 }
335 return result.str ();
337 }
338 }
339 template <typename ELEMENT_TYPE>
341 requires (same_as<ELEMENT_TYPE, Character>)
342 {
344 InputStream::Ptr<Character> copyOfStream = *this;
345 if (this->IsSeekable ()) [[likely]] {
346 return Traversal::CreateGenerator<String> ([copyOfStream] () -> optional<String> {
347 String line = copyOfStream.ReadLine ();
348 if (line.empty ()) {
349 return nullopt;
350 }
351 else {
352 return line;
353 }
354 });
355 }
356 else {
357 // We may need to 'read ahead' on an unseekable stream, so keep a little extra context to make it happen
358 auto readLine = [] (InputStream::Ptr<Character> s, optional<Character> firstChar) -> tuple<String, optional<Character>> {
359 StringBuilder result;
360 while (true) {
361 Character c = [&] () -> Character {
362 if (firstChar) {
363 return *firstChar;
364 }
365 return s.ReadBlocking ().value_or (Character{});
366 }();
367 firstChar = nullopt;
368 if (c.GetCharacterCode () == '\0') {
369 return make_tuple (result.str (), nullopt); // EOF
370 }
371 result.push_back (c);
372 if (c == '\n') {
373 return make_tuple (result.str (), nullopt);
374 }
375 else if (c == '\r') {
376 c = s.ReadBlocking ().value_or (Character{});
377 // if CR is followed by LF, append that to result too before returning. Otherwise, put the character back
378 if (c == '\n') {
379 result.push_back (c);
380 return make_tuple (result.str (), nullopt);
381 }
382 else {
383 return make_tuple (result.str (), c);
384 }
385 }
386 };
387 };
388 optional<Character> prefixLineChar; // magic so optional<Character> in mutable lambda
389 return Traversal::CreateGenerator<String> ([readLine, copyOfStream, prefixLineChar] () mutable -> optional<String> {
390 tuple<String, optional<Character>> lineEtc = readLine (copyOfStream, prefixLineChar);
391 if (get<String> (lineEtc).empty ()) {
392 return nullopt;
393 }
394 else {
395 prefixLineChar = get<optional<Character>> (lineEtc);
396 return get<String> (lineEtc);
397 }
398 });
399 }
400 }
401 DISABLE_COMPILER_MSC_WARNING_START (6262) // stack usage OK
402 template <typename ELEMENT_TYPE>
403 String InputStream::Ptr<ELEMENT_TYPE>::ReadAll (size_t upTo) const
404 requires (same_as<ELEMENT_TYPE, Character>)
405 {
406#if USE_NOISY_TRACE_IN_THIS_MODULE_
407 Debug::TraceContextBumper ctx{"InputStream::Ptr<Character>::ReadAll", "upTo: {}"_f, upTo};
408#endif
409 Require (upTo >= 1);
411 size_t nEltsLeft = upTo;
412 while (nEltsLeft > 0) {
413 Character buf[16 * 1024];
414 Character* s = std::begin (buf);
415 Character* e = std::end (buf);
416 if (nEltsLeft < Memory::NEltsOf (buf)) {
417 e = s + nEltsLeft;
419 size_t n = ReadBlocking (span{s, e}).size ();
420 Assert (0 <= n and n <= nEltsLeft);
421 Assert (0 <= n and n <= Memory::NEltsOf (buf));
422 if (n == 0) {
423 break;
424 }
425 else {
426 Assert (n <= nEltsLeft);
427 nEltsLeft -= n;
428 result.Append (span{s, n});
429 }
430 }
431#if USE_NOISY_TRACE_IN_THIS_MODULE_
432 DbgTrace (L"Returning {} characters"_f, result.size ());
433#endif
434 return result;
435 }
436 DISABLE_COMPILER_MSC_WARNING_END (6262)
437 template <typename ELEMENT_TYPE>
438 template <typename ELEMENT_TYPE2, size_t EXTENT2_T>
439 auto InputStream::Ptr<ELEMENT_TYPE>::ReadAll (span<ELEMENT_TYPE2, EXTENT2_T> intoBuffer) const -> span<ElementType>
440 requires (same_as<ELEMENT_TYPE, ELEMENT_TYPE2>)
441 {
442 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
443 Require (not intoBuffer.empty ());
444 Require (IsOpen ());
445 /*
446 * Conceptually very simple - the same API as READ - so we can just forward. BUT - its not quite the same.
447 * Read is ALLOWED to return less than requested, without prejudice about whether more is available.
448 * This API is NOT.
449 *
450 * So keep reading will we know we got everything.
451 *
452 * Note - its because of this, and to avoid potentially needing to unread, that this API doesn't support non-blocking ReadAll (could do with seek).
453 */
454 size_t elementsRead{};
455 ElementType* intoEnd = intoBuffer.data () + intoBuffer.size ();
456 for (ElementType* readCursor = intoBuffer.data (); readCursor < intoEnd;) {
457 size_t eltsReadThisTime = ReadBlocking (span{readCursor, intoEnd}).size ();
458 if (eltsReadThisTime == 0) {
459 // irrevocable EOF
460 break;
461 }
462 elementsRead += eltsReadThisTime;
463 readCursor += eltsReadThisTime;
464 }
465 return intoBuffer.subspan (0, elementsRead);
466 }
467
468}
#define AssertNotImplemented()
Definition Assertions.h:401
NoDataAvailableHandling
If eDontBlock passed to most Stream APIs, then when the code would do a blocking read,...
Definition Stream.h:90
#define DbgTrace
Definition Trace.h:309
constexpr char32_t GetCharacterCode() const noexcept
Return the char32_t UNICODE code-point associated with this character.
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual size_t size() const noexcept
nonvirtual void Append(span< const CHAR_T > s)
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
the stream ended prematurely, so that the requested operation could not be completed.
a read (typically) or write operation would have blocked, and the XXX flag was passed to a Stream rea...
Definition EWouldBlock.h:24
virtual optional< SeekOffsetType > RemainingLength()
returns nullopt if not known (typical, and the default) - but sometimes it is known,...
virtual optional< size_t > AvailableToRead()
returns nullopt if nothing known available, zero if known EOF, and any other number of elements (typi...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual optional< ElementType > ReadBlocking() const
ReadBlocking () reads either a single element, or fills in argument intoBuffer - but never blocks (no...
nonvirtual optional< size_t > AvailableToRead() const
returns nullopt if nothing known available, zero if known EOF, and any other number of elements (typi...
nonvirtual optional< Memory::InlineBuffer< ELEMENT_TYPE > > ReadAllAvailable() const
nonvirtual SeekOffsetType Seek(SeekOffsetType offset) const
nonvirtual bool IsAtEOF() const
check if the stream is currently at EOF
nonvirtual Iterable< String > ReadLines() const
nonvirtual SeekOffsetType GetOffset() const
nonvirtual optional< span< ElementType > > Read(span< ElementType > intoBuffer, NoDataAvailableHandling blockFlag) const
Read into data referenced by span argument - and using argument blocking strategy (default blocking)
nonvirtual POD_TYPE ReadRaw() const
Read a single (or span of) POD_TYPE objects, like with Read () - except always blocking,...
nonvirtual optional< ElementType > PeekBlocking() const
block until one item available, read it and return it (seeking back before returning) - and returns n...
nonvirtual shared_ptr< IRep< ELEMENT_TYPE > > GetSharedRep() const
access to underlying stream smart pointer
nonvirtual optional< SeekOffsetType > RemainingLength() const
returns nullopt if not known (typical, and the default) - but sometimes it is known,...
nonvirtual span< ElementType > ReadOrThrow(span< ElementType > intoBuffer, NoDataAvailableHandling blockFlag) const
Read (either one or into argument span) and taking NoDataAvailableHandling blockFlag option arg),...
nonvirtual const IRep< ELEMENT_TYPE > & GetRepConstRef() const
nonvirtual IRep< ELEMENT_TYPE > & GetRepRWRef() const
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31