1 | //===-- runtime/io-stmt.cpp -----------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "io-stmt.h" |
10 | #include "connection.h" |
11 | #include "emit-encoded.h" |
12 | #include "format.h" |
13 | #include "tools.h" |
14 | #include "unit.h" |
15 | #include "utf.h" |
16 | #include "flang/Runtime/memory.h" |
17 | #include <algorithm> |
18 | #include <cstdio> |
19 | #include <cstring> |
20 | #include <limits> |
21 | #include <type_traits> |
22 | |
23 | namespace Fortran::runtime::io { |
24 | |
25 | bool IoStatementBase::Emit(const char *, std::size_t, std::size_t) { |
26 | return false; |
27 | } |
28 | |
29 | std::size_t IoStatementBase::GetNextInputBytes(const char *&p) { |
30 | p = nullptr; |
31 | return 0; |
32 | } |
33 | |
34 | bool IoStatementBase::AdvanceRecord(int) { return false; } |
35 | |
36 | void IoStatementBase::BackspaceRecord() {} |
37 | |
38 | bool IoStatementBase::Receive(char *, std::size_t, std::size_t) { |
39 | return false; |
40 | } |
41 | |
42 | std::optional<DataEdit> IoStatementBase::GetNextDataEdit( |
43 | IoStatementState &, int) { |
44 | return std::nullopt; |
45 | } |
46 | |
47 | ExternalFileUnit *IoStatementBase::GetExternalFileUnit() const { |
48 | return nullptr; |
49 | } |
50 | |
51 | bool IoStatementBase::BeginReadingRecord() { return true; } |
52 | |
53 | void IoStatementBase::FinishReadingRecord() {} |
54 | |
55 | void IoStatementBase::HandleAbsolutePosition(std::int64_t) {} |
56 | |
57 | void IoStatementBase::HandleRelativePosition(std::int64_t) {} |
58 | |
59 | bool IoStatementBase::Inquire(InquiryKeywordHash, char *, std::size_t) { |
60 | return false; |
61 | } |
62 | |
63 | bool IoStatementBase::Inquire(InquiryKeywordHash, bool &) { return false; } |
64 | |
65 | bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t, bool &) { |
66 | return false; |
67 | } |
68 | |
69 | bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t &) { |
70 | return false; |
71 | } |
72 | |
73 | std::int64_t IoStatementBase::InquirePos() { return 0; } |
74 | |
75 | void IoStatementBase::BadInquiryKeywordHashCrash(InquiryKeywordHash inquiry) { |
76 | char buffer[16]; |
77 | const char *decode{InquiryKeywordHashDecode(buffer, sizeof buffer, inquiry)}; |
78 | Crash("Bad InquiryKeywordHash 0x%x (%s)" , inquiry, |
79 | decode ? decode : "(cannot decode)" ); |
80 | } |
81 | |
82 | template <Direction DIR> |
83 | InternalIoStatementState<DIR>::InternalIoStatementState( |
84 | Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine) |
85 | : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length, 1} {} |
86 | |
87 | template <Direction DIR> |
88 | InternalIoStatementState<DIR>::InternalIoStatementState( |
89 | const Descriptor &d, const char *sourceFile, int sourceLine) |
90 | : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {} |
91 | |
92 | template <Direction DIR> |
93 | bool InternalIoStatementState<DIR>::Emit( |
94 | const char *data, std::size_t bytes, std::size_t /*elementBytes*/) { |
95 | if constexpr (DIR == Direction::Input) { |
96 | Crash("InternalIoStatementState<Direction::Input>::Emit() called" ); |
97 | return false; |
98 | } |
99 | return unit_.Emit(data, bytes, *this); |
100 | } |
101 | |
102 | template <Direction DIR> |
103 | std::size_t InternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) { |
104 | return unit_.GetNextInputBytes(p, *this); |
105 | } |
106 | |
107 | template <Direction DIR> |
108 | bool InternalIoStatementState<DIR>::AdvanceRecord(int n) { |
109 | while (n-- > 0) { |
110 | if (!unit_.AdvanceRecord(*this)) { |
111 | return false; |
112 | } |
113 | } |
114 | return true; |
115 | } |
116 | |
117 | template <Direction DIR> void InternalIoStatementState<DIR>::BackspaceRecord() { |
118 | unit_.BackspaceRecord(*this); |
119 | } |
120 | |
121 | template <Direction DIR> int InternalIoStatementState<DIR>::EndIoStatement() { |
122 | if constexpr (DIR == Direction::Output) { |
123 | unit_.EndIoStatement(); // fill |
124 | } |
125 | auto result{IoStatementBase::EndIoStatement()}; |
126 | if (free_) { |
127 | FreeMemory(this); |
128 | } |
129 | return result; |
130 | } |
131 | |
132 | template <Direction DIR> |
133 | void InternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) { |
134 | return unit_.HandleAbsolutePosition(n); |
135 | } |
136 | |
137 | template <Direction DIR> |
138 | void InternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) { |
139 | return unit_.HandleRelativePosition(n); |
140 | } |
141 | |
142 | template <Direction DIR> |
143 | std::int64_t InternalIoStatementState<DIR>::InquirePos() { |
144 | return unit_.InquirePos(); |
145 | } |
146 | |
147 | template <Direction DIR, typename CHAR> |
148 | InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState( |
149 | Buffer buffer, std::size_t length, const CharType *format, |
150 | std::size_t formatLength, const Descriptor *formatDescriptor, |
151 | const char *sourceFile, int sourceLine) |
152 | : InternalIoStatementState<DIR>{buffer, length, sourceFile, sourceLine}, |
153 | ioStatementState_{*this}, format_{*this, format, formatLength, |
154 | formatDescriptor} {} |
155 | |
156 | template <Direction DIR, typename CHAR> |
157 | InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState( |
158 | const Descriptor &d, const CharType *format, std::size_t formatLength, |
159 | const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine) |
160 | : InternalIoStatementState<DIR>{d, sourceFile, sourceLine}, |
161 | ioStatementState_{*this}, format_{*this, format, formatLength, |
162 | formatDescriptor} {} |
163 | |
164 | template <Direction DIR, typename CHAR> |
165 | void InternalFormattedIoStatementState<DIR, CHAR>::CompleteOperation() { |
166 | if (!this->completedOperation()) { |
167 | if constexpr (DIR == Direction::Output) { |
168 | format_.Finish(*this); // ignore any remaining input positioning actions |
169 | } |
170 | IoStatementBase::CompleteOperation(); |
171 | } |
172 | } |
173 | |
174 | template <Direction DIR, typename CHAR> |
175 | int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() { |
176 | CompleteOperation(); |
177 | return InternalIoStatementState<DIR>::EndIoStatement(); |
178 | } |
179 | |
180 | template <Direction DIR> |
181 | InternalListIoStatementState<DIR>::InternalListIoStatementState( |
182 | Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine) |
183 | : InternalIoStatementState<DIR>{buffer, length, sourceFile, sourceLine}, |
184 | ioStatementState_{*this} {} |
185 | |
186 | template <Direction DIR> |
187 | InternalListIoStatementState<DIR>::InternalListIoStatementState( |
188 | const Descriptor &d, const char *sourceFile, int sourceLine) |
189 | : InternalIoStatementState<DIR>{d, sourceFile, sourceLine}, |
190 | ioStatementState_{*this} {} |
191 | |
192 | template <Direction DIR> |
193 | int InternalListIoStatementState<DIR>::EndIoStatement() { |
194 | if constexpr (DIR == Direction::Input) { |
195 | if (int status{ListDirectedStatementState<DIR>::EndIoStatement()}; |
196 | status != IostatOk) { |
197 | return status; |
198 | } |
199 | } |
200 | return InternalIoStatementState<DIR>::EndIoStatement(); |
201 | } |
202 | |
203 | ExternalIoStatementBase::ExternalIoStatementBase( |
204 | ExternalFileUnit &unit, const char *sourceFile, int sourceLine) |
205 | : IoStatementBase{sourceFile, sourceLine}, unit_{unit} {} |
206 | |
207 | MutableModes &ExternalIoStatementBase::mutableModes() { |
208 | if (const ChildIo * child{unit_.GetChildIo()}) { |
209 | return child->parent().mutableModes(); |
210 | } |
211 | return unit_.modes; |
212 | } |
213 | |
214 | ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; } |
215 | |
216 | int ExternalIoStatementBase::EndIoStatement() { |
217 | CompleteOperation(); |
218 | auto result{IoStatementBase::EndIoStatement()}; |
219 | unit_.EndIoStatement(); // annihilates *this in unit_.u_ |
220 | return result; |
221 | } |
222 | |
223 | void ExternalIoStatementBase::SetAsynchronous() { |
224 | asynchronousID_ = unit().GetAsynchronousId(*this); |
225 | } |
226 | |
227 | std::int64_t ExternalIoStatementBase::InquirePos() { |
228 | return unit_.InquirePos(); |
229 | } |
230 | |
231 | void OpenStatementState::set_path(const char *path, std::size_t length) { |
232 | pathLength_ = TrimTrailingSpaces(path, length); |
233 | path_ = SaveDefaultCharacter(path, pathLength_, *this); |
234 | } |
235 | |
236 | void OpenStatementState::CompleteOperation() { |
237 | if (completedOperation()) { |
238 | return; |
239 | } |
240 | if (position_) { |
241 | if (access_ && *access_ == Access::Direct) { |
242 | SignalError("POSITION= may not be set with ACCESS='DIRECT'" ); |
243 | position_.reset(); |
244 | } |
245 | } |
246 | if (status_) { // 12.5.6.10 |
247 | if ((*status_ == OpenStatus::New || *status_ == OpenStatus::Replace) && |
248 | !path_.get()) { |
249 | SignalError("FILE= required on OPEN with STATUS='NEW' or 'REPLACE'" ); |
250 | } else if (*status_ == OpenStatus::Scratch && path_.get()) { |
251 | SignalError("FILE= may not appear on OPEN with STATUS='SCRATCH'" ); |
252 | } |
253 | } |
254 | // F'2023 12.5.6.13 - NEWUNIT= requires either FILE= or STATUS='SCRATCH' |
255 | if (isNewUnit_ && !path_.get() && |
256 | status_.value_or(OpenStatus::Unknown) != OpenStatus::Scratch) { |
257 | SignalError(IostatBadNewUnit); |
258 | status_ = OpenStatus::Scratch; // error recovery |
259 | } |
260 | if (path_.get() || wasExtant_ || |
261 | (status_ && *status_ == OpenStatus::Scratch)) { |
262 | if (unit().OpenUnit(status_, action_, position_.value_or(Position::AsIs), |
263 | std::move(path_), pathLength_, convert_, *this)) { |
264 | wasExtant_ = false; // existing unit was closed |
265 | } |
266 | } else { |
267 | unit().OpenAnonymousUnit( |
268 | status_, action_, position_.value_or(u: Position::AsIs), convert_, *this); |
269 | } |
270 | if (access_) { |
271 | if (*access_ != unit().access) { |
272 | if (wasExtant_) { |
273 | SignalError("ACCESS= may not be changed on an open unit" ); |
274 | access_.reset(); |
275 | } |
276 | } |
277 | if (access_) { |
278 | unit().access = *access_; |
279 | } |
280 | } |
281 | if (!unit().isUnformatted) { |
282 | unit().isUnformatted = isUnformatted_; |
283 | } |
284 | if (isUnformatted_ && *isUnformatted_ != *unit().isUnformatted) { |
285 | if (wasExtant_) { |
286 | SignalError("FORM= may not be changed on an open unit" ); |
287 | } |
288 | unit().isUnformatted = *isUnformatted_; |
289 | } |
290 | if (!unit().isUnformatted) { |
291 | // Set default format (C.7.4 point 2). |
292 | unit().isUnformatted = unit().access != Access::Sequential; |
293 | } |
294 | if (!wasExtant_ && InError()) { |
295 | // Release the new unit on failure |
296 | unit().CloseUnit(CloseStatus::Delete, *this); |
297 | unit().DestroyClosed(); |
298 | } |
299 | IoStatementBase::CompleteOperation(); |
300 | } |
301 | |
302 | int OpenStatementState::EndIoStatement() { |
303 | CompleteOperation(); |
304 | return ExternalIoStatementBase::EndIoStatement(); |
305 | } |
306 | |
307 | int CloseStatementState::EndIoStatement() { |
308 | CompleteOperation(); |
309 | int result{ExternalIoStatementBase::EndIoStatement()}; |
310 | unit().CloseUnit(status_, *this); |
311 | unit().DestroyClosed(); |
312 | return result; |
313 | } |
314 | |
315 | void NoUnitIoStatementState::CompleteOperation() { |
316 | SignalPendingError(); |
317 | IoStatementBase::CompleteOperation(); |
318 | } |
319 | |
320 | int NoUnitIoStatementState::EndIoStatement() { |
321 | CompleteOperation(); |
322 | auto result{IoStatementBase::EndIoStatement()}; |
323 | FreeMemory(this); |
324 | return result; |
325 | } |
326 | |
327 | template <Direction DIR> |
328 | ExternalIoStatementState<DIR>::ExternalIoStatementState( |
329 | ExternalFileUnit &unit, const char *sourceFile, int sourceLine) |
330 | : ExternalIoStatementBase{unit, sourceFile, sourceLine}, mutableModes_{ |
331 | unit.modes} { |
332 | if constexpr (DIR == Direction::Output) { |
333 | // If the last statement was a non-advancing IO input statement, the unit |
334 | // furthestPositionInRecord was not advanced, but the positionInRecord may |
335 | // have been advanced. Advance furthestPositionInRecord here to avoid |
336 | // overwriting the part of the record that has been read with blanks. |
337 | unit.furthestPositionInRecord = |
338 | std::max(a: unit.furthestPositionInRecord, b: unit.positionInRecord); |
339 | } |
340 | } |
341 | |
342 | template <Direction DIR> |
343 | void ExternalIoStatementState<DIR>::CompleteOperation() { |
344 | if (completedOperation()) { |
345 | return; |
346 | } |
347 | if constexpr (DIR == Direction::Input) { |
348 | BeginReadingRecord(); // in case there were no I/O items |
349 | if (mutableModes().nonAdvancing && !InError()) { |
350 | unit().leftTabLimit = unit().furthestPositionInRecord; |
351 | } else { |
352 | FinishReadingRecord(); |
353 | } |
354 | } else { // output |
355 | if (mutableModes().nonAdvancing) { |
356 | // Make effects of positioning past the last Emit() visible with blanks. |
357 | if (unit().positionInRecord > unit().furthestPositionInRecord) { |
358 | unit().Emit("" , 0, 1, *this); // Emit() will pad |
359 | } |
360 | unit().leftTabLimit = unit().positionInRecord; |
361 | } else { |
362 | unit().AdvanceRecord(*this); |
363 | } |
364 | unit().FlushIfTerminal(*this); |
365 | } |
366 | return IoStatementBase::CompleteOperation(); |
367 | } |
368 | |
369 | template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() { |
370 | CompleteOperation(); |
371 | return ExternalIoStatementBase::EndIoStatement(); |
372 | } |
373 | |
374 | template <Direction DIR> |
375 | bool ExternalIoStatementState<DIR>::Emit( |
376 | const char *data, std::size_t bytes, std::size_t elementBytes) { |
377 | if constexpr (DIR == Direction::Input) { |
378 | Crash("ExternalIoStatementState::Emit(char) called for input statement" ); |
379 | } |
380 | return unit().Emit(data, bytes, elementBytes, *this); |
381 | } |
382 | |
383 | template <Direction DIR> |
384 | std::size_t ExternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) { |
385 | return unit().GetNextInputBytes(p, *this); |
386 | } |
387 | |
388 | template <Direction DIR> |
389 | bool ExternalIoStatementState<DIR>::AdvanceRecord(int n) { |
390 | while (n-- > 0) { |
391 | if (!unit().AdvanceRecord(*this)) { |
392 | return false; |
393 | } |
394 | } |
395 | return true; |
396 | } |
397 | |
398 | template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() { |
399 | unit().BackspaceRecord(*this); |
400 | } |
401 | |
402 | template <Direction DIR> |
403 | void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) { |
404 | return unit().HandleAbsolutePosition(n); |
405 | } |
406 | |
407 | template <Direction DIR> |
408 | void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) { |
409 | return unit().HandleRelativePosition(n); |
410 | } |
411 | |
412 | template <Direction DIR> |
413 | bool ExternalIoStatementState<DIR>::BeginReadingRecord() { |
414 | if constexpr (DIR == Direction::Input) { |
415 | return unit().BeginReadingRecord(*this); |
416 | } else { |
417 | Crash("ExternalIoStatementState<Direction::Output>::BeginReadingRecord() " |
418 | "called" ); |
419 | return false; |
420 | } |
421 | } |
422 | |
423 | template <Direction DIR> |
424 | void ExternalIoStatementState<DIR>::FinishReadingRecord() { |
425 | if constexpr (DIR == Direction::Input) { |
426 | unit().FinishReadingRecord(*this); |
427 | } else { |
428 | Crash("ExternalIoStatementState<Direction::Output>::FinishReadingRecord() " |
429 | "called" ); |
430 | } |
431 | } |
432 | |
433 | template <Direction DIR, typename CHAR> |
434 | ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState( |
435 | ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength, |
436 | const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine) |
437 | : ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine}, |
438 | format_{*this, format, formatLength, formatDescriptor} {} |
439 | |
440 | template <Direction DIR, typename CHAR> |
441 | void ExternalFormattedIoStatementState<DIR, CHAR>::CompleteOperation() { |
442 | if (this->completedOperation()) { |
443 | return; |
444 | } |
445 | if constexpr (DIR == Direction::Input) { |
446 | this->BeginReadingRecord(); // in case there were no I/O items |
447 | } |
448 | format_.Finish(*this); |
449 | return ExternalIoStatementState<DIR>::CompleteOperation(); |
450 | } |
451 | |
452 | template <Direction DIR, typename CHAR> |
453 | int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() { |
454 | CompleteOperation(); |
455 | return ExternalIoStatementState<DIR>::EndIoStatement(); |
456 | } |
457 | |
458 | std::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) { |
459 | return common::visit( |
460 | [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_); |
461 | } |
462 | |
463 | bool IoStatementState::Emit( |
464 | const char *data, std::size_t bytes, std::size_t elementBytes) { |
465 | return common::visit( |
466 | [=](auto &x) { return x.get().Emit(data, bytes, elementBytes); }, u_); |
467 | } |
468 | |
469 | bool IoStatementState::Receive( |
470 | char *data, std::size_t n, std::size_t elementBytes) { |
471 | return common::visit( |
472 | [=](auto &x) { return x.get().Receive(data, n, elementBytes); }, u_); |
473 | } |
474 | |
475 | std::size_t IoStatementState::GetNextInputBytes(const char *&p) { |
476 | return common::visit( |
477 | [&](auto &x) { return x.get().GetNextInputBytes(p); }, u_); |
478 | } |
479 | |
480 | bool IoStatementState::AdvanceRecord(int n) { |
481 | return common::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_); |
482 | } |
483 | |
484 | void IoStatementState::BackspaceRecord() { |
485 | common::visit([](auto &x) { x.get().BackspaceRecord(); }, u_); |
486 | } |
487 | |
488 | void IoStatementState::HandleRelativePosition(std::int64_t n) { |
489 | common::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_); |
490 | } |
491 | |
492 | void IoStatementState::HandleAbsolutePosition(std::int64_t n) { |
493 | common::visit([=](auto &x) { x.get().HandleAbsolutePosition(n); }, u_); |
494 | } |
495 | |
496 | void IoStatementState::CompleteOperation() { |
497 | common::visit([](auto &x) { x.get().CompleteOperation(); }, u_); |
498 | } |
499 | |
500 | int IoStatementState::EndIoStatement() { |
501 | return common::visit([](auto &x) { return x.get().EndIoStatement(); }, u_); |
502 | } |
503 | |
504 | ConnectionState &IoStatementState::GetConnectionState() { |
505 | return common::visit( |
506 | [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); }, |
507 | u_); |
508 | } |
509 | |
510 | MutableModes &IoStatementState::mutableModes() { |
511 | return common::visit( |
512 | [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_); |
513 | } |
514 | |
515 | bool IoStatementState::BeginReadingRecord() { |
516 | return common::visit( |
517 | [](auto &x) { return x.get().BeginReadingRecord(); }, u_); |
518 | } |
519 | |
520 | IoErrorHandler &IoStatementState::GetIoErrorHandler() const { |
521 | return common::visit( |
522 | [](auto &x) -> IoErrorHandler & { |
523 | return static_cast<IoErrorHandler &>(x.get()); |
524 | }, |
525 | u_); |
526 | } |
527 | |
528 | ExternalFileUnit *IoStatementState::GetExternalFileUnit() const { |
529 | return common::visit( |
530 | [](auto &x) { return x.get().GetExternalFileUnit(); }, u_); |
531 | } |
532 | |
533 | std::optional<char32_t> IoStatementState::GetCurrentChar( |
534 | std::size_t &byteCount) { |
535 | const char *p{nullptr}; |
536 | std::size_t bytes{GetNextInputBytes(p)}; |
537 | if (bytes == 0) { |
538 | byteCount = 0; |
539 | return std::nullopt; |
540 | } else { |
541 | const ConnectionState &connection{GetConnectionState()}; |
542 | if (connection.isUTF8) { |
543 | std::size_t length{MeasureUTF8Bytes(first: *p)}; |
544 | if (length <= bytes) { |
545 | if (auto result{DecodeUTF8(p)}) { |
546 | byteCount = length; |
547 | return result; |
548 | } |
549 | } |
550 | GetIoErrorHandler().SignalError(IostatUTF8Decoding); |
551 | // Error recovery: return the next byte |
552 | } else if (connection.internalIoCharKind > 1) { |
553 | byteCount = connection.internalIoCharKind; |
554 | if (byteCount == 2) { |
555 | return *reinterpret_cast<const char16_t *>(p); |
556 | } else { |
557 | return *reinterpret_cast<const char32_t *>(p); |
558 | } |
559 | } |
560 | byteCount = 1; |
561 | return *p; |
562 | } |
563 | } |
564 | |
565 | std::optional<char32_t> IoStatementState::NextInField( |
566 | std::optional<int> &remaining, const DataEdit &edit) { |
567 | std::size_t byteCount{0}; |
568 | if (!remaining) { // Stream, list-directed, or NAMELIST |
569 | if (auto next{GetCurrentChar(byteCount)}) { |
570 | if (edit.IsListDirected()) { |
571 | // list-directed or NAMELIST: check for separators |
572 | switch (*next) { |
573 | case ' ': |
574 | case '\t': |
575 | case '/': |
576 | case '(': |
577 | case ')': |
578 | case '\'': |
579 | case '"': |
580 | case '*': |
581 | case '\n': // for stream access |
582 | return std::nullopt; |
583 | case '&': |
584 | case '$': |
585 | if (edit.IsNamelist()) { |
586 | return std::nullopt; |
587 | } |
588 | break; |
589 | case ',': |
590 | if (!(edit.modes.editingFlags & decimalComma)) { |
591 | return std::nullopt; |
592 | } |
593 | break; |
594 | case ';': |
595 | if (edit.modes.editingFlags & decimalComma) { |
596 | return std::nullopt; |
597 | } |
598 | break; |
599 | default: |
600 | break; |
601 | } |
602 | } |
603 | HandleRelativePosition(n: byteCount); |
604 | GotChar(byteCount); |
605 | return next; |
606 | } |
607 | } else if (*remaining > 0) { |
608 | if (auto next{GetCurrentChar(byteCount)}) { |
609 | if (byteCount > static_cast<std::size_t>(*remaining)) { |
610 | return std::nullopt; |
611 | } |
612 | *remaining -= byteCount; |
613 | HandleRelativePosition(n: byteCount); |
614 | GotChar(byteCount); |
615 | return next; |
616 | } |
617 | if (CheckForEndOfRecord(afterReading: 0)) { // do padding |
618 | --*remaining; |
619 | return std::optional<char32_t>{' '}; |
620 | } |
621 | } |
622 | return std::nullopt; |
623 | } |
624 | |
625 | bool IoStatementState::CheckForEndOfRecord(std::size_t afterReading) { |
626 | const ConnectionState &connection{GetConnectionState()}; |
627 | if (!connection.IsAtEOF()) { |
628 | if (auto length{connection.EffectiveRecordLength()}) { |
629 | if (connection.positionInRecord + |
630 | static_cast<std::int64_t>(afterReading) >= |
631 | *length) { |
632 | IoErrorHandler &handler{GetIoErrorHandler()}; |
633 | const auto &modes{mutableModes()}; |
634 | if (modes.nonAdvancing) { |
635 | if (connection.access == Access::Stream && |
636 | connection.unterminatedRecord) { |
637 | // Reading final unterminated record left by a |
638 | // non-advancing WRITE on a stream file prior to |
639 | // positioning or ENDFILE. |
640 | handler.SignalEnd(); |
641 | } else { |
642 | handler.SignalEor(); |
643 | } |
644 | } else if (!modes.pad) { |
645 | handler.SignalError(IostatRecordReadOverrun); |
646 | } |
647 | return modes.pad; // PAD='YES' |
648 | } |
649 | } |
650 | } |
651 | return false; |
652 | } |
653 | |
654 | bool IoStatementState::Inquire( |
655 | InquiryKeywordHash inquiry, char *out, std::size_t chars) { |
656 | return common::visit( |
657 | [&](auto &x) { return x.get().Inquire(inquiry, out, chars); }, u_); |
658 | } |
659 | |
660 | bool IoStatementState::Inquire(InquiryKeywordHash inquiry, bool &out) { |
661 | return common::visit( |
662 | [&](auto &x) { return x.get().Inquire(inquiry, out); }, u_); |
663 | } |
664 | |
665 | bool IoStatementState::Inquire( |
666 | InquiryKeywordHash inquiry, std::int64_t id, bool &out) { |
667 | return common::visit( |
668 | [&](auto &x) { return x.get().Inquire(inquiry, id, out); }, u_); |
669 | } |
670 | |
671 | bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) { |
672 | return common::visit( |
673 | [&](auto &x) { return x.get().Inquire(inquiry, n); }, u_); |
674 | } |
675 | |
676 | std::int64_t IoStatementState::InquirePos() { |
677 | return common::visit([&](auto &x) { return x.get().InquirePos(); }, u_); |
678 | } |
679 | |
680 | void IoStatementState::GotChar(int n) { |
681 | if (auto *formattedIn{ |
682 | get_if<FormattedIoStatementState<Direction::Input>>()}) { |
683 | formattedIn->GotChar(n); |
684 | } else { |
685 | GetIoErrorHandler().Crash("IoStatementState::GotChar() called for " |
686 | "statement that is not formatted input" ); |
687 | } |
688 | } |
689 | |
690 | std::size_t |
691 | FormattedIoStatementState<Direction::Input>::GetEditDescriptorChars() const { |
692 | return chars_; |
693 | } |
694 | |
695 | void FormattedIoStatementState<Direction::Input>::GotChar(int n) { |
696 | chars_ += n; |
697 | } |
698 | |
699 | bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance( |
700 | IoStatementState &io, std::size_t length, bool isCharacter) { |
701 | const ConnectionState &connection{io.GetConnectionState()}; |
702 | int space{connection.positionInRecord == 0 || |
703 | !(isCharacter && lastWasUndelimitedCharacter())}; |
704 | set_lastWasUndelimitedCharacter(false); |
705 | if (connection.NeedAdvance(space + length)) { |
706 | return io.AdvanceRecord(); |
707 | } |
708 | if (space) { |
709 | return EmitAscii(to&: io, data: " " , chars: 1); |
710 | } |
711 | return true; |
712 | } |
713 | |
714 | std::optional<DataEdit> |
715 | ListDirectedStatementState<Direction::Output>::GetNextDataEdit( |
716 | IoStatementState &io, int maxRepeat) { |
717 | DataEdit edit; |
718 | edit.descriptor = DataEdit::ListDirected; |
719 | edit.repeat = maxRepeat; |
720 | edit.modes = io.mutableModes(); |
721 | return edit; |
722 | } |
723 | |
724 | int ListDirectedStatementState<Direction::Input>::EndIoStatement() { |
725 | if (repeatPosition_) { |
726 | repeatPosition_->Cancel(); |
727 | } |
728 | return IostatOk; |
729 | } |
730 | |
731 | std::optional<DataEdit> |
732 | ListDirectedStatementState<Direction::Input>::GetNextDataEdit( |
733 | IoStatementState &io, int maxRepeat) { |
734 | // N.B. list-directed transfers cannot be nonadvancing (C1221) |
735 | ConnectionState &connection{io.GetConnectionState()}; |
736 | DataEdit edit; |
737 | edit.descriptor = DataEdit::ListDirected; |
738 | edit.repeat = 1; // may be overridden below |
739 | edit.modes = io.mutableModes(); |
740 | if (hitSlash_) { // everything after '/' is nullified |
741 | edit.descriptor = DataEdit::ListDirectedNullValue; |
742 | return edit; |
743 | } |
744 | char32_t comma{','}; |
745 | if (edit.modes.editingFlags & decimalComma) { |
746 | comma = ';'; |
747 | } |
748 | std::size_t byteCount{0}; |
749 | if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress |
750 | RUNTIME_CHECK(io.GetIoErrorHandler(), repeatPosition_.has_value()); |
751 | repeatPosition_.reset(); // restores the saved position |
752 | if (!imaginaryPart_) { |
753 | edit.repeat = std::min<int>(a: remaining_, b: maxRepeat); |
754 | auto ch{io.GetCurrentChar(byteCount)}; |
755 | if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { |
756 | // "r*" repeated null |
757 | edit.descriptor = DataEdit::ListDirectedNullValue; |
758 | } |
759 | } |
760 | remaining_ -= edit.repeat; |
761 | if (remaining_ > 0) { |
762 | repeatPosition_.emplace(io); |
763 | } |
764 | if (!imaginaryPart_) { |
765 | return edit; |
766 | } |
767 | } |
768 | // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018 |
769 | if (imaginaryPart_) { |
770 | imaginaryPart_ = false; |
771 | } else if (realPart_) { |
772 | realPart_ = false; |
773 | imaginaryPart_ = true; |
774 | edit.descriptor = DataEdit::ListDirectedImaginaryPart; |
775 | } |
776 | auto ch{io.GetNextNonBlank(byteCount)}; |
777 | if (ch && *ch == comma && eatComma_) { |
778 | // Consume comma & whitespace after previous item. |
779 | // This includes the comma between real and imaginary components |
780 | // in list-directed/NAMELIST complex input. |
781 | // (When DECIMAL='COMMA', the comma is actually a semicolon.) |
782 | io.HandleRelativePosition(n: byteCount); |
783 | ch = io.GetNextNonBlank(byteCount); |
784 | } |
785 | eatComma_ = true; |
786 | if (!ch) { |
787 | return std::nullopt; |
788 | } |
789 | if (*ch == '/') { |
790 | hitSlash_ = true; |
791 | edit.descriptor = DataEdit::ListDirectedNullValue; |
792 | return edit; |
793 | } |
794 | if (*ch == comma) { // separator: null value |
795 | edit.descriptor = DataEdit::ListDirectedNullValue; |
796 | return edit; |
797 | } |
798 | if (imaginaryPart_) { // can't repeat components |
799 | return edit; |
800 | } |
801 | if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count |
802 | auto start{connection.positionInRecord}; |
803 | int r{0}; |
804 | do { |
805 | static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10}; |
806 | if (r >= clamp) { |
807 | r = 0; |
808 | break; |
809 | } |
810 | r = 10 * r + (*ch - '0'); |
811 | io.HandleRelativePosition(n: byteCount); |
812 | ch = io.GetCurrentChar(byteCount); |
813 | } while (ch && *ch >= '0' && *ch <= '9'); |
814 | if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero |
815 | io.HandleRelativePosition(n: byteCount); |
816 | ch = io.GetCurrentChar(byteCount); |
817 | if (ch && *ch == '/') { // r*/ |
818 | hitSlash_ = true; |
819 | edit.descriptor = DataEdit::ListDirectedNullValue; |
820 | return edit; |
821 | } |
822 | if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { // "r*" null |
823 | edit.descriptor = DataEdit::ListDirectedNullValue; |
824 | } |
825 | edit.repeat = std::min<int>(a: r, b: maxRepeat); |
826 | remaining_ = r - edit.repeat; |
827 | if (remaining_ > 0) { |
828 | repeatPosition_.emplace(io); |
829 | } |
830 | } else { // not a repetition count, just an integer value; rewind |
831 | connection.positionInRecord = start; |
832 | } |
833 | } |
834 | if (!imaginaryPart_ && ch && *ch == '(') { |
835 | realPart_ = true; |
836 | io.HandleRelativePosition(n: byteCount); |
837 | edit.descriptor = DataEdit::ListDirectedRealPart; |
838 | } |
839 | return edit; |
840 | } |
841 | |
842 | template <Direction DIR> |
843 | int ExternalListIoStatementState<DIR>::EndIoStatement() { |
844 | if constexpr (DIR == Direction::Input) { |
845 | if (auto status{ListDirectedStatementState<DIR>::EndIoStatement()}; |
846 | status != IostatOk) { |
847 | return status; |
848 | } |
849 | } |
850 | return ExternalIoStatementState<DIR>::EndIoStatement(); |
851 | } |
852 | |
853 | template <Direction DIR> |
854 | bool ExternalUnformattedIoStatementState<DIR>::Receive( |
855 | char *data, std::size_t bytes, std::size_t elementBytes) { |
856 | if constexpr (DIR == Direction::Output) { |
857 | this->Crash("ExternalUnformattedIoStatementState::Receive() called for " |
858 | "output statement" ); |
859 | } |
860 | return this->unit().Receive(data, bytes, elementBytes, *this); |
861 | } |
862 | |
863 | template <Direction DIR> |
864 | ChildIoStatementState<DIR>::ChildIoStatementState( |
865 | ChildIo &child, const char *sourceFile, int sourceLine) |
866 | : IoStatementBase{sourceFile, sourceLine}, child_{child} {} |
867 | |
868 | template <Direction DIR> |
869 | MutableModes &ChildIoStatementState<DIR>::mutableModes() { |
870 | return child_.parent().mutableModes(); |
871 | } |
872 | |
873 | template <Direction DIR> |
874 | ConnectionState &ChildIoStatementState<DIR>::GetConnectionState() { |
875 | return child_.parent().GetConnectionState(); |
876 | } |
877 | |
878 | template <Direction DIR> |
879 | ExternalFileUnit *ChildIoStatementState<DIR>::GetExternalFileUnit() const { |
880 | return child_.parent().GetExternalFileUnit(); |
881 | } |
882 | |
883 | template <Direction DIR> int ChildIoStatementState<DIR>::EndIoStatement() { |
884 | CompleteOperation(); |
885 | auto result{IoStatementBase::EndIoStatement()}; |
886 | child_.EndIoStatement(); // annihilates *this in child_.u_ |
887 | return result; |
888 | } |
889 | |
890 | template <Direction DIR> |
891 | bool ChildIoStatementState<DIR>::Emit( |
892 | const char *data, std::size_t bytes, std::size_t elementBytes) { |
893 | return child_.parent().Emit(data, bytes, elementBytes); |
894 | } |
895 | |
896 | template <Direction DIR> |
897 | std::size_t ChildIoStatementState<DIR>::GetNextInputBytes(const char *&p) { |
898 | return child_.parent().GetNextInputBytes(p); |
899 | } |
900 | |
901 | template <Direction DIR> |
902 | void ChildIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) { |
903 | return child_.parent().HandleAbsolutePosition(n); |
904 | } |
905 | |
906 | template <Direction DIR> |
907 | void ChildIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) { |
908 | return child_.parent().HandleRelativePosition(n); |
909 | } |
910 | |
911 | template <Direction DIR, typename CHAR> |
912 | ChildFormattedIoStatementState<DIR, CHAR>::ChildFormattedIoStatementState( |
913 | ChildIo &child, const CHAR *format, std::size_t formatLength, |
914 | const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine) |
915 | : ChildIoStatementState<DIR>{child, sourceFile, sourceLine}, |
916 | mutableModes_{child.parent().mutableModes()}, format_{*this, format, |
917 | formatLength, |
918 | formatDescriptor} {} |
919 | |
920 | template <Direction DIR, typename CHAR> |
921 | void ChildFormattedIoStatementState<DIR, CHAR>::CompleteOperation() { |
922 | if (!this->completedOperation()) { |
923 | format_.Finish(*this); |
924 | ChildIoStatementState<DIR>::CompleteOperation(); |
925 | } |
926 | } |
927 | |
928 | template <Direction DIR, typename CHAR> |
929 | int ChildFormattedIoStatementState<DIR, CHAR>::EndIoStatement() { |
930 | CompleteOperation(); |
931 | return ChildIoStatementState<DIR>::EndIoStatement(); |
932 | } |
933 | |
934 | template <Direction DIR, typename CHAR> |
935 | bool ChildFormattedIoStatementState<DIR, CHAR>::AdvanceRecord(int n) { |
936 | return this->child().parent().AdvanceRecord(n); |
937 | } |
938 | |
939 | template <Direction DIR> |
940 | bool ChildUnformattedIoStatementState<DIR>::Receive( |
941 | char *data, std::size_t bytes, std::size_t elementBytes) { |
942 | return this->child().parent().Receive(data, bytes, elementBytes); |
943 | } |
944 | |
945 | template <Direction DIR> int ChildListIoStatementState<DIR>::EndIoStatement() { |
946 | if constexpr (DIR == Direction::Input) { |
947 | if (int status{ListDirectedStatementState<DIR>::EndIoStatement()}; |
948 | status != IostatOk) { |
949 | return status; |
950 | } |
951 | } |
952 | return ChildIoStatementState<DIR>::EndIoStatement(); |
953 | } |
954 | |
955 | template class InternalIoStatementState<Direction::Output>; |
956 | template class InternalIoStatementState<Direction::Input>; |
957 | template class InternalFormattedIoStatementState<Direction::Output>; |
958 | template class InternalFormattedIoStatementState<Direction::Input>; |
959 | template class InternalListIoStatementState<Direction::Output>; |
960 | template class InternalListIoStatementState<Direction::Input>; |
961 | template class ExternalIoStatementState<Direction::Output>; |
962 | template class ExternalIoStatementState<Direction::Input>; |
963 | template class ExternalFormattedIoStatementState<Direction::Output>; |
964 | template class ExternalFormattedIoStatementState<Direction::Input>; |
965 | template class ExternalListIoStatementState<Direction::Output>; |
966 | template class ExternalListIoStatementState<Direction::Input>; |
967 | template class ExternalUnformattedIoStatementState<Direction::Output>; |
968 | template class ExternalUnformattedIoStatementState<Direction::Input>; |
969 | template class ChildIoStatementState<Direction::Output>; |
970 | template class ChildIoStatementState<Direction::Input>; |
971 | template class ChildFormattedIoStatementState<Direction::Output>; |
972 | template class ChildFormattedIoStatementState<Direction::Input>; |
973 | template class ChildListIoStatementState<Direction::Output>; |
974 | template class ChildListIoStatementState<Direction::Input>; |
975 | template class ChildUnformattedIoStatementState<Direction::Output>; |
976 | template class ChildUnformattedIoStatementState<Direction::Input>; |
977 | |
978 | void ExternalMiscIoStatementState::CompleteOperation() { |
979 | if (completedOperation()) { |
980 | return; |
981 | } |
982 | ExternalFileUnit &ext{unit()}; |
983 | switch (which_) { |
984 | case Flush: |
985 | ext.FlushOutput(*this); |
986 | std::fflush(stream: nullptr); // flushes C stdio output streams (12.9(2)) |
987 | break; |
988 | case Backspace: |
989 | ext.BackspaceRecord(*this); |
990 | break; |
991 | case Endfile: |
992 | ext.Endfile(*this); |
993 | break; |
994 | case Rewind: |
995 | ext.Rewind(*this); |
996 | break; |
997 | case Wait: |
998 | break; // handled in io-api.cpp BeginWait |
999 | } |
1000 | return IoStatementBase::CompleteOperation(); |
1001 | } |
1002 | |
1003 | int ExternalMiscIoStatementState::EndIoStatement() { |
1004 | CompleteOperation(); |
1005 | return ExternalIoStatementBase::EndIoStatement(); |
1006 | } |
1007 | |
1008 | InquireUnitState::InquireUnitState( |
1009 | ExternalFileUnit &unit, const char *sourceFile, int sourceLine) |
1010 | : ExternalIoStatementBase{unit, sourceFile, sourceLine} {} |
1011 | |
1012 | bool InquireUnitState::Inquire( |
1013 | InquiryKeywordHash inquiry, char *result, std::size_t length) { |
1014 | if (unit().createdForInternalChildIo()) { |
1015 | SignalError(IostatInquireInternalUnit, |
1016 | "INQUIRE of unit created for defined derived type I/O of an internal " |
1017 | "unit" ); |
1018 | return false; |
1019 | } |
1020 | const char *str{nullptr}; |
1021 | switch (inquiry) { |
1022 | case HashInquiryKeyword("ACCESS" ): |
1023 | if (!unit().IsConnected()) { |
1024 | str = "UNDEFINED" ; |
1025 | } else { |
1026 | switch (unit().access) { |
1027 | case Access::Sequential: |
1028 | str = "SEQUENTIAL" ; |
1029 | break; |
1030 | case Access::Direct: |
1031 | str = "DIRECT" ; |
1032 | break; |
1033 | case Access::Stream: |
1034 | str = "STREAM" ; |
1035 | break; |
1036 | } |
1037 | } |
1038 | break; |
1039 | case HashInquiryKeyword("ACTION" ): |
1040 | str = !unit().IsConnected() ? "UNDEFINED" |
1041 | : unit().mayWrite() ? unit().mayRead() ? "READWRITE" : "WRITE" |
1042 | : "READ" ; |
1043 | break; |
1044 | case HashInquiryKeyword("ASYNCHRONOUS" ): |
1045 | str = !unit().IsConnected() ? "UNDEFINED" |
1046 | : unit().mayAsynchronous() ? "YES" |
1047 | : "NO" ; |
1048 | break; |
1049 | case HashInquiryKeyword("BLANK" ): |
1050 | str = !unit().IsConnected() || unit().isUnformatted.value_or(true) |
1051 | ? "UNDEFINED" |
1052 | : mutableModes().editingFlags & blankZero ? "ZERO" |
1053 | : "NULL" ; |
1054 | break; |
1055 | case HashInquiryKeyword("CARRIAGECONTROL" ): |
1056 | str = "LIST" ; |
1057 | break; |
1058 | case HashInquiryKeyword("CONVERT" ): |
1059 | str = unit().swapEndianness() ? "SWAP" : "NATIVE" ; |
1060 | break; |
1061 | case HashInquiryKeyword("DECIMAL" ): |
1062 | str = !unit().IsConnected() || unit().isUnformatted.value_or(true) |
1063 | ? "UNDEFINED" |
1064 | : mutableModes().editingFlags & decimalComma ? "COMMA" |
1065 | : "POINT" ; |
1066 | break; |
1067 | case HashInquiryKeyword("DELIM" ): |
1068 | if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) { |
1069 | str = "UNDEFINED" ; |
1070 | } else { |
1071 | switch (mutableModes().delim) { |
1072 | case '\'': |
1073 | str = "APOSTROPHE" ; |
1074 | break; |
1075 | case '"': |
1076 | str = "QUOTE" ; |
1077 | break; |
1078 | default: |
1079 | str = "NONE" ; |
1080 | break; |
1081 | } |
1082 | } |
1083 | break; |
1084 | case HashInquiryKeyword("DIRECT" ): |
1085 | str = !unit().IsConnected() ? "UNKNOWN" |
1086 | : unit().access == Access::Direct || |
1087 | (unit().mayPosition() && unit().openRecl) |
1088 | ? "YES" |
1089 | : "NO" ; |
1090 | break; |
1091 | case HashInquiryKeyword("ENCODING" ): |
1092 | str = !unit().IsConnected() ? "UNKNOWN" |
1093 | : unit().isUnformatted.value_or(true) ? "UNDEFINED" |
1094 | : unit().isUTF8 ? "UTF-8" |
1095 | : "ASCII" ; |
1096 | break; |
1097 | case HashInquiryKeyword("FORM" ): |
1098 | str = !unit().IsConnected() || !unit().isUnformatted ? "UNDEFINED" |
1099 | : *unit().isUnformatted ? "UNFORMATTED" |
1100 | : "FORMATTED" ; |
1101 | break; |
1102 | case HashInquiryKeyword("FORMATTED" ): |
1103 | str = !unit().IsConnected() ? "UNDEFINED" |
1104 | : !unit().isUnformatted ? "UNKNOWN" |
1105 | : *unit().isUnformatted ? "NO" |
1106 | : "YES" ; |
1107 | break; |
1108 | case HashInquiryKeyword("NAME" ): |
1109 | str = unit().path(); |
1110 | if (!str) { |
1111 | return true; // result is undefined |
1112 | } |
1113 | break; |
1114 | case HashInquiryKeyword("PAD" ): |
1115 | str = !unit().IsConnected() || unit().isUnformatted.value_or(true) |
1116 | ? "UNDEFINED" |
1117 | : mutableModes().pad ? "YES" |
1118 | : "NO" ; |
1119 | break; |
1120 | case HashInquiryKeyword("POSITION" ): |
1121 | if (!unit().IsConnected() || unit().access == Access::Direct) { |
1122 | str = "UNDEFINED" ; |
1123 | } else { |
1124 | switch (unit().InquirePosition()) { |
1125 | case Position::Rewind: |
1126 | str = "REWIND" ; |
1127 | break; |
1128 | case Position::Append: |
1129 | str = "APPEND" ; |
1130 | break; |
1131 | case Position::AsIs: |
1132 | str = "ASIS" ; |
1133 | break; |
1134 | } |
1135 | } |
1136 | break; |
1137 | case HashInquiryKeyword("READ" ): |
1138 | str = !unit().IsConnected() ? "UNDEFINED" : unit().mayRead() ? "YES" : "NO" ; |
1139 | break; |
1140 | case HashInquiryKeyword("READWRITE" ): |
1141 | str = !unit().IsConnected() ? "UNDEFINED" |
1142 | : unit().mayRead() && unit().mayWrite() ? "YES" |
1143 | : "NO" ; |
1144 | break; |
1145 | case HashInquiryKeyword("ROUND" ): |
1146 | if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) { |
1147 | str = "UNDEFINED" ; |
1148 | } else { |
1149 | switch (mutableModes().round) { |
1150 | case decimal::FortranRounding::RoundNearest: |
1151 | str = "NEAREST" ; |
1152 | break; |
1153 | case decimal::FortranRounding::RoundUp: |
1154 | str = "UP" ; |
1155 | break; |
1156 | case decimal::FortranRounding::RoundDown: |
1157 | str = "DOWN" ; |
1158 | break; |
1159 | case decimal::FortranRounding::RoundToZero: |
1160 | str = "ZERO" ; |
1161 | break; |
1162 | case decimal::FortranRounding::RoundCompatible: |
1163 | str = "COMPATIBLE" ; |
1164 | break; |
1165 | } |
1166 | } |
1167 | break; |
1168 | case HashInquiryKeyword("SEQUENTIAL" ): |
1169 | // "NO" for Direct, since Sequential would not work if |
1170 | // the unit were reopened without RECL=. |
1171 | str = !unit().IsConnected() ? "UNKNOWN" |
1172 | : unit().access == Access::Sequential ? "YES" |
1173 | : "NO" ; |
1174 | break; |
1175 | case HashInquiryKeyword("SIGN" ): |
1176 | str = !unit().IsConnected() || unit().isUnformatted.value_or(true) |
1177 | ? "UNDEFINED" |
1178 | : mutableModes().editingFlags & signPlus ? "PLUS" |
1179 | : "SUPPRESS" ; |
1180 | break; |
1181 | case HashInquiryKeyword("STREAM" ): |
1182 | str = !unit().IsConnected() ? "UNKNOWN" |
1183 | : unit().access == Access::Stream ? "YES" |
1184 | : "NO" ; |
1185 | break; |
1186 | case HashInquiryKeyword("UNFORMATTED" ): |
1187 | str = !unit().IsConnected() || !unit().isUnformatted ? "UNKNOWN" |
1188 | : *unit().isUnformatted ? "YES" |
1189 | : "NO" ; |
1190 | break; |
1191 | case HashInquiryKeyword("WRITE" ): |
1192 | str = !unit().IsConnected() ? "UNKNOWN" : unit().mayWrite() ? "YES" : "NO" ; |
1193 | break; |
1194 | } |
1195 | if (str) { |
1196 | ToFortranDefaultCharacter(to: result, toLength: length, from: str); |
1197 | return true; |
1198 | } else { |
1199 | BadInquiryKeywordHashCrash(inquiry); |
1200 | return false; |
1201 | } |
1202 | } |
1203 | |
1204 | bool InquireUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) { |
1205 | switch (inquiry) { |
1206 | case HashInquiryKeyword("EXIST" ): |
1207 | result = true; |
1208 | return true; |
1209 | case HashInquiryKeyword("NAMED" ): |
1210 | result = unit().path() != nullptr; |
1211 | return true; |
1212 | case HashInquiryKeyword("OPENED" ): |
1213 | result = unit().IsConnected(); |
1214 | return true; |
1215 | case HashInquiryKeyword("PENDING" ): |
1216 | result = false; // asynchronous I/O is not implemented |
1217 | return true; |
1218 | default: |
1219 | BadInquiryKeywordHashCrash(inquiry); |
1220 | return false; |
1221 | } |
1222 | } |
1223 | |
1224 | bool InquireUnitState::Inquire( |
1225 | InquiryKeywordHash inquiry, std::int64_t, bool &result) { |
1226 | switch (inquiry) { |
1227 | case HashInquiryKeyword("PENDING" ): |
1228 | result = false; // asynchronous I/O is not implemented |
1229 | return true; |
1230 | default: |
1231 | BadInquiryKeywordHashCrash(inquiry); |
1232 | return false; |
1233 | } |
1234 | } |
1235 | |
1236 | bool InquireUnitState::Inquire( |
1237 | InquiryKeywordHash inquiry, std::int64_t &result) { |
1238 | switch (inquiry) { |
1239 | case HashInquiryKeyword("NEXTREC" ): |
1240 | if (unit().access == Access::Direct) { |
1241 | result = unit().currentRecordNumber; |
1242 | } |
1243 | return true; |
1244 | case HashInquiryKeyword("NUMBER" ): |
1245 | result = unit().unitNumber(); |
1246 | return true; |
1247 | case HashInquiryKeyword("POS" ): |
1248 | result = unit().InquirePos(); |
1249 | return true; |
1250 | case HashInquiryKeyword("RECL" ): |
1251 | if (!unit().IsConnected()) { |
1252 | result = -1; |
1253 | } else if (unit().access == Access::Stream) { |
1254 | result = -2; |
1255 | } else if (unit().openRecl) { |
1256 | result = *unit().openRecl; |
1257 | } else { |
1258 | result = std::numeric_limits<std::int32_t>::max(); |
1259 | } |
1260 | return true; |
1261 | case HashInquiryKeyword("SIZE" ): |
1262 | result = -1; |
1263 | if (unit().IsConnected()) { |
1264 | unit().FlushOutput(*this); |
1265 | if (auto size{unit().knownSize()}) { |
1266 | result = *size; |
1267 | } |
1268 | } |
1269 | return true; |
1270 | default: |
1271 | BadInquiryKeywordHashCrash(inquiry); |
1272 | return false; |
1273 | } |
1274 | } |
1275 | |
1276 | InquireNoUnitState::InquireNoUnitState( |
1277 | const char *sourceFile, int sourceLine, int badUnitNumber) |
1278 | : NoUnitIoStatementState{*this, sourceFile, sourceLine, badUnitNumber} {} |
1279 | |
1280 | bool InquireNoUnitState::Inquire( |
1281 | InquiryKeywordHash inquiry, char *result, std::size_t length) { |
1282 | switch (inquiry) { |
1283 | case HashInquiryKeyword("ACCESS" ): |
1284 | case HashInquiryKeyword("ACTION" ): |
1285 | case HashInquiryKeyword("ASYNCHRONOUS" ): |
1286 | case HashInquiryKeyword("BLANK" ): |
1287 | case HashInquiryKeyword("CARRIAGECONTROL" ): |
1288 | case HashInquiryKeyword("CONVERT" ): |
1289 | case HashInquiryKeyword("DECIMAL" ): |
1290 | case HashInquiryKeyword("DELIM" ): |
1291 | case HashInquiryKeyword("FORM" ): |
1292 | case HashInquiryKeyword("NAME" ): |
1293 | case HashInquiryKeyword("PAD" ): |
1294 | case HashInquiryKeyword("POSITION" ): |
1295 | case HashInquiryKeyword("ROUND" ): |
1296 | case HashInquiryKeyword("SIGN" ): |
1297 | ToFortranDefaultCharacter(to: result, toLength: length, from: "UNDEFINED" ); |
1298 | return true; |
1299 | case HashInquiryKeyword("DIRECT" ): |
1300 | case HashInquiryKeyword("ENCODING" ): |
1301 | case HashInquiryKeyword("FORMATTED" ): |
1302 | case HashInquiryKeyword("READ" ): |
1303 | case HashInquiryKeyword("READWRITE" ): |
1304 | case HashInquiryKeyword("SEQUENTIAL" ): |
1305 | case HashInquiryKeyword("STREAM" ): |
1306 | case HashInquiryKeyword("WRITE" ): |
1307 | case HashInquiryKeyword("UNFORMATTED" ): |
1308 | ToFortranDefaultCharacter(to: result, toLength: length, from: "UNKNOWN" ); |
1309 | return true; |
1310 | default: |
1311 | BadInquiryKeywordHashCrash(inquiry); |
1312 | return false; |
1313 | } |
1314 | } |
1315 | |
1316 | bool InquireNoUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) { |
1317 | switch (inquiry) { |
1318 | case HashInquiryKeyword("EXIST" ): |
1319 | result = badUnitNumber() >= 0; |
1320 | return true; |
1321 | case HashInquiryKeyword("NAMED" ): |
1322 | case HashInquiryKeyword("OPENED" ): |
1323 | case HashInquiryKeyword("PENDING" ): |
1324 | result = false; |
1325 | return true; |
1326 | default: |
1327 | BadInquiryKeywordHashCrash(inquiry); |
1328 | return false; |
1329 | } |
1330 | } |
1331 | |
1332 | bool InquireNoUnitState::Inquire( |
1333 | InquiryKeywordHash inquiry, std::int64_t, bool &result) { |
1334 | switch (inquiry) { |
1335 | case HashInquiryKeyword("PENDING" ): |
1336 | result = false; |
1337 | return true; |
1338 | default: |
1339 | BadInquiryKeywordHashCrash(inquiry); |
1340 | return false; |
1341 | } |
1342 | } |
1343 | |
1344 | bool InquireNoUnitState::Inquire( |
1345 | InquiryKeywordHash inquiry, std::int64_t &result) { |
1346 | switch (inquiry) { |
1347 | case HashInquiryKeyword("NUMBER" ): |
1348 | result = badUnitNumber(); |
1349 | return true; |
1350 | case HashInquiryKeyword("NEXTREC" ): |
1351 | case HashInquiryKeyword("POS" ): |
1352 | case HashInquiryKeyword("RECL" ): |
1353 | case HashInquiryKeyword("SIZE" ): |
1354 | result = -1; |
1355 | return true; |
1356 | default: |
1357 | BadInquiryKeywordHashCrash(inquiry); |
1358 | return false; |
1359 | } |
1360 | } |
1361 | |
1362 | InquireUnconnectedFileState::InquireUnconnectedFileState( |
1363 | OwningPtr<char> &&path, const char *sourceFile, int sourceLine) |
1364 | : NoUnitIoStatementState{*this, sourceFile, sourceLine}, path_{std::move( |
1365 | path)} {} |
1366 | |
1367 | bool InquireUnconnectedFileState::Inquire( |
1368 | InquiryKeywordHash inquiry, char *result, std::size_t length) { |
1369 | const char *str{nullptr}; |
1370 | switch (inquiry) { |
1371 | case HashInquiryKeyword("ACCESS" ): |
1372 | case HashInquiryKeyword("ACTION" ): |
1373 | case HashInquiryKeyword("ASYNCHRONOUS" ): |
1374 | case HashInquiryKeyword("BLANK" ): |
1375 | case HashInquiryKeyword("CARRIAGECONTROL" ): |
1376 | case HashInquiryKeyword("CONVERT" ): |
1377 | case HashInquiryKeyword("DECIMAL" ): |
1378 | case HashInquiryKeyword("DELIM" ): |
1379 | case HashInquiryKeyword("FORM" ): |
1380 | case HashInquiryKeyword("PAD" ): |
1381 | case HashInquiryKeyword("POSITION" ): |
1382 | case HashInquiryKeyword("ROUND" ): |
1383 | case HashInquiryKeyword("SIGN" ): |
1384 | str = "UNDEFINED" ; |
1385 | break; |
1386 | case HashInquiryKeyword("DIRECT" ): |
1387 | case HashInquiryKeyword("ENCODING" ): |
1388 | case HashInquiryKeyword("FORMATTED" ): |
1389 | case HashInquiryKeyword("SEQUENTIAL" ): |
1390 | case HashInquiryKeyword("STREAM" ): |
1391 | case HashInquiryKeyword("UNFORMATTED" ): |
1392 | str = "UNKNOWN" ; |
1393 | break; |
1394 | case HashInquiryKeyword("READ" ): |
1395 | str = |
1396 | IsExtant(path_.get()) ? MayRead(path_.get()) ? "YES" : "NO" : "UNKNOWN" ; |
1397 | break; |
1398 | case HashInquiryKeyword("READWRITE" ): |
1399 | str = IsExtant(path_.get()) ? MayReadAndWrite(path_.get()) ? "YES" : "NO" |
1400 | : "UNKNOWN" ; |
1401 | break; |
1402 | case HashInquiryKeyword("WRITE" ): |
1403 | str = IsExtant(path_.get()) ? MayWrite(path_.get()) ? "YES" : "NO" |
1404 | : "UNKNOWN" ; |
1405 | break; |
1406 | case HashInquiryKeyword("NAME" ): |
1407 | str = path_.get(); |
1408 | if (!str) { |
1409 | return true; // result is undefined |
1410 | } |
1411 | break; |
1412 | } |
1413 | if (str) { |
1414 | ToFortranDefaultCharacter(to: result, toLength: length, from: str); |
1415 | return true; |
1416 | } else { |
1417 | BadInquiryKeywordHashCrash(inquiry); |
1418 | return false; |
1419 | } |
1420 | } |
1421 | |
1422 | bool InquireUnconnectedFileState::Inquire( |
1423 | InquiryKeywordHash inquiry, bool &result) { |
1424 | switch (inquiry) { |
1425 | case HashInquiryKeyword("EXIST" ): |
1426 | result = IsExtant(path_.get()); |
1427 | return true; |
1428 | case HashInquiryKeyword("NAMED" ): |
1429 | result = true; |
1430 | return true; |
1431 | case HashInquiryKeyword("OPENED" ): |
1432 | result = false; |
1433 | return true; |
1434 | case HashInquiryKeyword("PENDING" ): |
1435 | result = false; |
1436 | return true; |
1437 | default: |
1438 | BadInquiryKeywordHashCrash(inquiry); |
1439 | return false; |
1440 | } |
1441 | } |
1442 | |
1443 | bool InquireUnconnectedFileState::Inquire( |
1444 | InquiryKeywordHash inquiry, std::int64_t, bool &result) { |
1445 | switch (inquiry) { |
1446 | case HashInquiryKeyword("PENDING" ): |
1447 | result = false; |
1448 | return true; |
1449 | default: |
1450 | BadInquiryKeywordHashCrash(inquiry); |
1451 | return false; |
1452 | } |
1453 | } |
1454 | |
1455 | bool InquireUnconnectedFileState::Inquire( |
1456 | InquiryKeywordHash inquiry, std::int64_t &result) { |
1457 | switch (inquiry) { |
1458 | case HashInquiryKeyword("NEXTREC" ): |
1459 | case HashInquiryKeyword("NUMBER" ): |
1460 | case HashInquiryKeyword("POS" ): |
1461 | case HashInquiryKeyword("RECL" ): |
1462 | result = -1; |
1463 | return true; |
1464 | case HashInquiryKeyword("SIZE" ): |
1465 | result = SizeInBytes(path_.get()); |
1466 | return true; |
1467 | default: |
1468 | BadInquiryKeywordHashCrash(inquiry); |
1469 | return false; |
1470 | } |
1471 | } |
1472 | |
1473 | InquireIOLengthState::InquireIOLengthState( |
1474 | const char *sourceFile, int sourceLine) |
1475 | : NoUnitIoStatementState{*this, sourceFile, sourceLine} {} |
1476 | |
1477 | bool InquireIOLengthState::Emit(const char *, std::size_t bytes, std::size_t) { |
1478 | bytes_ += bytes; |
1479 | return true; |
1480 | } |
1481 | |
1482 | int ErroneousIoStatementState::EndIoStatement() { |
1483 | SignalPendingError(); |
1484 | if (unit_) { |
1485 | unit_->EndIoStatement(); |
1486 | } |
1487 | return IoStatementBase::EndIoStatement(); |
1488 | } |
1489 | |
1490 | } // namespace Fortran::runtime::io |
1491 | |