Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
InputStream.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. 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 else {
241 ELEMENT_TYPE ignored{};
242 Require (this->IsSeekable ());
243 SeekOffsetType saved = GetOffset ();
244 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&, this] () noexcept { this->Seek (saved); }); // @todo Seek() could throw/terminate
245 optional<span<ELEMENT_TYPE>> o = this->ReadNonBlocking (span{&ignored, 1});
246#if 0
247 // before Stroika 3.0d23, we had this WRONG code.
248 return not o.has_value ();
249#endif
250 if (o) {
251 // ReadNonBlocking returns nullopt ONLY if blockFlag = eNonBlocking AND there is NO data available without blocking.
252 // a return of NOT missing, but an empty span implies EOF.
253 return o->empty ();
254 }
255 else {
256 return nullopt; // we don't know if at EOF or just would block
258 }
259 }
260 template <typename ELEMENT_TYPE>
261 inline optional<size_t> InputStream::Ptr<ELEMENT_TYPE>::ReadNonBlocking () const
262 {
263 // DEPRECATED
264 return AvailableToRead ();
265 }
266 template <typename ELEMENT_TYPE>
267 inline optional<size_t> InputStream::Ptr<ELEMENT_TYPE>::ReadNonBlocking (ElementType* intoStart, ElementType* intoEnd) const
268 {
269 // DEPRECATED
270 try {
271 return this->Read (span{intoStart, intoEnd}, NoDataAvailableHandling::eDontBlock).size ();
272 }
273 catch (const EWouldBlock& e) {
274 return nullopt;
275 }
276 catch (...) {
278 }
280 template <typename ELEMENT_TYPE>
281 template <typename POD_TYPE>
282 inline POD_TYPE InputStream::Ptr<ELEMENT_TYPE>::ReadRaw () const
283 requires (same_as<ELEMENT_TYPE, byte> and is_standard_layout_v<POD_TYPE>)
284 {
285 POD_TYPE tmp; // intentionally don't zero-initialize
286 return this->ReadRaw (span{&tmp, 1})[0];
287 }
288 template <typename ELEMENT_TYPE>
289 template <typename POD_TYPE>
290 span<POD_TYPE> InputStream::Ptr<ELEMENT_TYPE>::ReadRaw (span<POD_TYPE> intoBuffer) const
291 requires (same_as<ELEMENT_TYPE, byte> and is_standard_layout_v<POD_TYPE>)
292 {
293 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
294 Require (IsOpen ());
295 Require (intoBuffer.size () == 1 or this->IsSeekable ());
296 auto byteSpan = as_writable_bytes (intoBuffer);
297 size_t n{Read (byteSpan)};
298 if (n < sizeof (POD_TYPE)) {
299 // must keep (blocking) reading til we have one POD_TYPE, and if we come up short, must throw EOF
300 while (n < sizeof (POD_TYPE)) {
301 auto ni = this->Read (byteSpan.subspan (n, sizeof (POD_TYPE) - n)).size ();
302 if (ni == 0) {
304 }
305 n += ni;
306 }
307 Assert (n == sizeof (POD_TYPE));
308 return intoBuffer.subspan (0, 1);
309 }
310 else {
311 // we win, just return the right span size
312 if (n % sizeof (POD_TYPE) != 0) {
313 // Read even number of objects and adjust seek pointer
314 this->Seek (eFromCurrent, -static_cast<SignedSeekOffsetType> (n % sizeof (POD_TYPE)));
315 }
316 // we win, just return the right span size
317 size_t nObjectsRead = n / sizeof (POD_TYPE);
318 Assert (1 <= nObjectsRead and nObjectsRead <= intoBuffer.size ());
319 return intoBuffer.subspan (0, nObjectsRead);
320 }
322 template <typename ELEMENT_TYPE>
324 requires (same_as<ELEMENT_TYPE, Character>)
325 {
327 Require (this->IsSeekable ());
328 StringBuilder result;
329 while (true) {
330 Character c = ReadBlocking ().value_or (Character{});
331 if (c.GetCharacterCode () == '\0') {
332 // EOF
333 return result.str ();
334 }
335 result.push_back (c);
336 if (c == '\n') {
337 return result.str ();
338 }
339 else if (c == '\r') {
340 c = this->ReadBlocking ().value_or (Character{});
341 // if CR is followed by LF, append that to result too before returning. Otherwise, put the character back
342 if (c == '\n') {
343 result.push_back (c);
344 }
345 else {
346 this->Seek (eFromCurrent, -1);
347 }
348 return result.str ();
349 }
350 }
351 }
352 template <typename ELEMENT_TYPE>
354 requires (same_as<ELEMENT_TYPE, Character>)
355 {
357 InputStream::Ptr<Character> copyOfStream = *this;
358 if (this->IsSeekable ()) [[likely]] {
359 return Traversal::CreateGenerator<String> ([copyOfStream] () -> optional<String> {
360 String line = copyOfStream.ReadLine ();
361 if (line.empty ()) {
362 return nullopt;
363 }
364 else {
365 return line;
366 }
367 });
368 }
369 else {
370 // We may need to 'read ahead' on an unseekable stream, so keep a little extra context to make it happen
371 auto readLine = [] (InputStream::Ptr<Character> s, optional<Character> firstChar) -> tuple<String, optional<Character>> {
372 StringBuilder result;
373 while (true) {
374 Character c = [&] () -> Character {
375 if (firstChar) {
376 return *firstChar;
377 }
378 return s.ReadBlocking ().value_or (Character{});
379 }();
380 firstChar = nullopt;
381 if (c.GetCharacterCode () == '\0') {
382 return make_tuple (result.str (), nullopt); // EOF
384 result.push_back (c);
385 if (c == '\n') {
386 return make_tuple (result.str (), nullopt);
387 }
388 else if (c == '\r') {
389 c = s.ReadBlocking ().value_or (Character{});
390 // if CR is followed by LF, append that to result too before returning. Otherwise, put the character back
391 if (c == '\n') {
392 result.push_back (c);
393 return make_tuple (result.str (), nullopt);
394 }
395 else {
396 return make_tuple (result.str (), c);
397 }
398 }
399 };
400 };
401 optional<Character> prefixLineChar; // magic so optional<Character> in mutable lambda
402 return Traversal::CreateGenerator<String> ([readLine, copyOfStream, prefixLineChar] () mutable -> optional<String> {
403 tuple<String, optional<Character>> lineEtc = readLine (copyOfStream, prefixLineChar);
404 if (get<String> (lineEtc).empty ()) {
405 return nullopt;
406 }
407 else {
408 prefixLineChar = get<optional<Character>> (lineEtc);
409 return get<String> (lineEtc);
410 }
411 });
412 }
413 }
414 DISABLE_COMPILER_MSC_WARNING_START (6262) // stack usage OK
415 template <typename ELEMENT_TYPE>
416 String InputStream::Ptr<ELEMENT_TYPE>::ReadAll (size_t upTo) const
417 requires (same_as<ELEMENT_TYPE, Character>)
419#if USE_NOISY_TRACE_IN_THIS_MODULE_
420 Debug::TraceContextBumper ctx{"InputStream::Ptr<Character>::ReadAll", "upTo: {}"_f, upTo};
421#endif
422 Require (upTo >= 1);
424 size_t nEltsLeft = upTo;
425 while (nEltsLeft > 0) {
426 Character buf[16 * 1024];
427 Character* s = std::begin (buf);
428 Character* e = std::end (buf);
429 if (nEltsLeft < std::size (buf)) {
430 e = s + nEltsLeft;
431 }
432 size_t n = ReadBlocking (span{s, e}).size ();
433 Assert (0 <= n and n <= nEltsLeft);
434 Assert (0 <= n and n <= std::size (buf));
435 if (n == 0) {
436 break;
437 }
438 else {
439 Assert (n <= nEltsLeft);
440 nEltsLeft -= n;
441 result.Append (span{s, n});
442 }
443 }
444#if USE_NOISY_TRACE_IN_THIS_MODULE_
445 DbgTrace (L"Returning {} characters"_f, result.size ());
446#endif
447 return result;
448 }
449 DISABLE_COMPILER_MSC_WARNING_END (6262)
450 template <typename ELEMENT_TYPE>
451 template <typename ELEMENT_TYPE2, size_t EXTENT2_T>
452 auto InputStream::Ptr<ELEMENT_TYPE>::ReadAll (span<ELEMENT_TYPE2, EXTENT2_T> intoBuffer) const -> span<ElementType>
453 requires (same_as<ELEMENT_TYPE, ELEMENT_TYPE2>)
454 {
455 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{this->_fThisAssertExternallySynchronized};
456 Require (not intoBuffer.empty ());
457 Require (IsOpen ());
458 /*
459 * Conceptually very simple - the same API as READ - so we can just forward. BUT - its not quite the same.
460 * Read is ALLOWED to return less than requested, without prejudice about whether more is available.
461 * This API is NOT.
462 *
463 * So keep reading will we know we got everything.
464 *
465 * 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).
466 */
467 size_t elementsRead{};
468 ElementType* intoEnd = intoBuffer.data () + intoBuffer.size ();
469 for (ElementType* readCursor = intoBuffer.data (); readCursor < intoEnd;) {
470 size_t eltsReadThisTime = ReadBlocking (span{readCursor, intoEnd}).size ();
471 if (eltsReadThisTime == 0) {
472 // irrevocable EOF
473 break;
474 }
475 elementsRead += eltsReadThisTime;
476 readCursor += eltsReadThisTime;
478 return intoBuffer.subspan (0, elementsRead);
479 }
480
481}
#define AssertNotImplemented()
Definition Assertions.h:402
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:317
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