xref: /llvm-project/flang/runtime/format-implementation.h (revision cd92c4255582299b9a55fa0dc485982b8f54c49a)
195696d56Speter klausler //===-- runtime/format-implementation.h -------------------------*- C++ -*-===//
295696d56Speter klausler //
395696d56Speter klausler // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
495696d56Speter klausler // See https://llvm.org/LICENSE.txt for license information.
595696d56Speter klausler // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
695696d56Speter klausler //
795696d56Speter klausler //===----------------------------------------------------------------------===//
895696d56Speter klausler 
995696d56Speter klausler // Implements out-of-line member functions of template class FormatControl
1095696d56Speter klausler 
1195696d56Speter klausler #ifndef FORTRAN_RUNTIME_FORMAT_IMPLEMENTATION_H_
1295696d56Speter klausler #define FORTRAN_RUNTIME_FORMAT_IMPLEMENTATION_H_
1395696d56Speter klausler 
14bad52055SPeter Klausler #include "emit-encoded.h"
1595696d56Speter klausler #include "format.h"
1695696d56Speter klausler #include "io-stmt.h"
17cc180f4cSPeter Klausler #include "memory.h"
1864ab3302SCarolineConcatto #include "flang/Common/format.h"
1964ab3302SCarolineConcatto #include "flang/Decimal/decimal.h"
20830c0b90SPeter Klausler #include "flang/Runtime/main.h"
21231fae90SIsuru Fernando #include <algorithm>
22cc180f4cSPeter Klausler #include <cstring>
2395696d56Speter klausler #include <limits>
2495696d56Speter klausler 
2595696d56Speter klausler namespace Fortran::runtime::io {
2695696d56Speter klausler 
2795696d56Speter klausler template <typename CONTEXT>
288ebf7411SSlava Zakharin RT_API_ATTRS FormatControl<CONTEXT>::FormatControl(const Terminator &terminator,
29cc180f4cSPeter Klausler     const CharType *format, std::size_t formatLength,
30cc180f4cSPeter Klausler     const Descriptor *formatDescriptor, int maxHeight)
3195696d56Speter klausler     : maxHeight_{static_cast<std::uint8_t>(maxHeight)}, format_{format},
3295696d56Speter klausler       formatLength_{static_cast<int>(formatLength)} {
333b635714Speter klausler   RUNTIME_CHECK(terminator, maxHeight == maxHeight_);
34cc180f4cSPeter Klausler   if (!format && formatDescriptor) {
35cc180f4cSPeter Klausler     // The format is a character array passed via a descriptor.
3627d666b9SV Donaldson     std::size_t elements{formatDescriptor->Elements()};
3727d666b9SV Donaldson     std::size_t elementBytes{formatDescriptor->ElementBytes()};
3827d666b9SV Donaldson     formatLength = elements * elementBytes / sizeof(CharType);
39cc180f4cSPeter Klausler     formatLength_ = static_cast<int>(formatLength);
40cc180f4cSPeter Klausler     if (formatDescriptor->IsContiguous()) {
41cc180f4cSPeter Klausler       // Treat the contiguous array as a single character value.
4227d666b9SV Donaldson       format_ = const_cast<const CharType *>(
43cc180f4cSPeter Klausler           reinterpret_cast<CharType *>(formatDescriptor->raw().base_addr));
44cc180f4cSPeter Klausler     } else {
45cc180f4cSPeter Klausler       // Concatenate its elements into a temporary array.
46cc180f4cSPeter Klausler       char *p{reinterpret_cast<char *>(
47cc180f4cSPeter Klausler           AllocateMemoryOrCrash(terminator, formatLength * sizeof(CharType)))};
4827d666b9SV Donaldson       format_ = p;
49cc180f4cSPeter Klausler       SubscriptValue at[maxRank];
50cc180f4cSPeter Klausler       formatDescriptor->GetLowerBounds(at);
5127d666b9SV Donaldson       for (std::size_t j{0}; j < elements; ++j) {
52cc180f4cSPeter Klausler         std::memcpy(p, formatDescriptor->Element<char>(at), elementBytes);
53cc180f4cSPeter Klausler         p += elementBytes;
54cc180f4cSPeter Klausler         formatDescriptor->IncrementSubscripts(at);
55cc180f4cSPeter Klausler       }
56cc180f4cSPeter Klausler       freeFormat_ = true;
57cc180f4cSPeter Klausler     }
58cc180f4cSPeter Klausler   }
593b635714Speter klausler   RUNTIME_CHECK(
603b635714Speter klausler       terminator, formatLength == static_cast<std::size_t>(formatLength_));
6195696d56Speter klausler   stack_[0].start = offset_;
6295696d56Speter klausler   stack_[0].remaining = Iteration::unlimited; // 13.4(8)
6395696d56Speter klausler }
6495696d56Speter klausler 
6595696d56Speter klausler template <typename CONTEXT>
668ebf7411SSlava Zakharin RT_API_ATTRS int FormatControl<CONTEXT>::GetIntField(
6779f6b812SPeter Klausler     IoErrorHandler &handler, CharType firstCh, bool *hadError) {
6895696d56Speter klausler   CharType ch{firstCh ? firstCh : PeekNext()};
6995696d56Speter klausler   bool negate{ch == '-'};
704fb679d3Speter klausler   if (negate || ch == '+') {
7104eb93b1SPeter Klausler     if (firstCh) {
7295696d56Speter klausler       firstCh = '\0';
7304eb93b1SPeter Klausler     } else {
7404eb93b1SPeter Klausler       ++offset_;
7504eb93b1SPeter Klausler     }
7695696d56Speter klausler     ch = PeekNext();
7795696d56Speter klausler   }
78fc71a49eSPeter Klausler   if (ch < '0' || ch > '9') {
79fc71a49eSPeter Klausler     handler.SignalError(IostatErrorInFormat,
80fc71a49eSPeter Klausler         "Invalid FORMAT: integer expected at '%c'", static_cast<char>(ch));
81fc71a49eSPeter Klausler     if (hadError) {
82fc71a49eSPeter Klausler       *hadError = true;
83fc71a49eSPeter Klausler     }
84fc71a49eSPeter Klausler     return 0;
85fc71a49eSPeter Klausler   }
86fc71a49eSPeter Klausler   int result{0};
8795696d56Speter klausler   while (ch >= '0' && ch <= '9') {
8832334b91SPeter Klausler     constexpr int tenth{std::numeric_limits<int>::max() / 10};
8932334b91SPeter Klausler     if (result > tenth ||
9032334b91SPeter Klausler         ch - '0' > std::numeric_limits<int>::max() - 10 * result) {
913b635714Speter klausler       handler.SignalError(
923b635714Speter klausler           IostatErrorInFormat, "FORMAT integer field out of range");
9379f6b812SPeter Klausler       if (hadError) {
9479f6b812SPeter Klausler         *hadError = true;
9579f6b812SPeter Klausler       }
963b635714Speter klausler       return result;
9795696d56Speter klausler     }
9895696d56Speter klausler     result = 10 * result + ch - '0';
9995696d56Speter klausler     if (firstCh) {
10095696d56Speter klausler       firstCh = '\0';
10195696d56Speter klausler     } else {
10295696d56Speter klausler       ++offset_;
10395696d56Speter klausler     }
10495696d56Speter klausler     ch = PeekNext();
10595696d56Speter klausler   }
10695696d56Speter klausler   if (negate && (result *= -1) > 0) {
1073b635714Speter klausler     handler.SignalError(
1083b635714Speter klausler         IostatErrorInFormat, "FORMAT integer field out of range");
10979f6b812SPeter Klausler     if (hadError) {
11079f6b812SPeter Klausler       *hadError = true;
11179f6b812SPeter Klausler     }
11295696d56Speter klausler   }
11395696d56Speter klausler   return result;
11495696d56Speter klausler }
11595696d56Speter klausler 
116c2a95ad2SPeter Klausler // Xn, TRn, TLn
117c2a95ad2SPeter Klausler template <typename CONTEXT>
118c2a95ad2SPeter Klausler static RT_API_ATTRS bool RelativeTabbing(CONTEXT &context, int n) {
119c2a95ad2SPeter Klausler   ConnectionState &connection{context.GetConnectionState()};
120c2a95ad2SPeter Klausler   if constexpr (std::is_same_v<CONTEXT,
121c2a95ad2SPeter Klausler                     ExternalFormattedIoStatementState<Direction::Input>> ||
122c2a95ad2SPeter Klausler       std::is_same_v<CONTEXT,
123c2a95ad2SPeter Klausler           ExternalFormattedIoStatementState<Direction::Output>>) {
124c2a95ad2SPeter Klausler     if (n != 0 && connection.isUTF8) {
125c2a95ad2SPeter Klausler       const char *p{};
126c2a95ad2SPeter Klausler       if (n > 0) { // Xn or TRn
127c2a95ad2SPeter Klausler         // Skip 'n' multi-byte characters.  If that's more than are in the
128c2a95ad2SPeter Klausler         // current record, that's valid -- the program can position past the
129c2a95ad2SPeter Klausler         // end and then reposition back with Tn or TLn.
130c2a95ad2SPeter Klausler         std::size_t bytesLeft{context.ViewBytesInRecord(p, true)};
131c2a95ad2SPeter Klausler         for (; n > 0 && bytesLeft && p; --n) {
132c2a95ad2SPeter Klausler           std::size_t byteCount{MeasureUTF8Bytes(*p)};
133c2a95ad2SPeter Klausler           if (byteCount > bytesLeft) {
134c2a95ad2SPeter Klausler             break;
135c2a95ad2SPeter Klausler           }
136c2a95ad2SPeter Klausler           context.HandleRelativePosition(byteCount);
137c2a95ad2SPeter Klausler           bytesLeft -= byteCount;
138c2a95ad2SPeter Klausler           // Don't call GotChar(byteCount), these don't count towards SIZE=
139c2a95ad2SPeter Klausler           p += byteCount;
140c2a95ad2SPeter Klausler         }
141c2a95ad2SPeter Klausler       } else { // n < 0: TLn
142c2a95ad2SPeter Klausler         n = -n;
143c2a95ad2SPeter Klausler         if (std::int64_t excess{connection.positionInRecord -
144c2a95ad2SPeter Klausler                 connection.recordLength.value_or(connection.positionInRecord)};
145c2a95ad2SPeter Klausler             excess > 0) {
146c2a95ad2SPeter Klausler           // Have tabbed past the end of the record
147c2a95ad2SPeter Klausler           if (excess >= n) {
148c2a95ad2SPeter Klausler             context.HandleRelativePosition(-n);
149c2a95ad2SPeter Klausler             return true;
150c2a95ad2SPeter Klausler           }
151c2a95ad2SPeter Klausler           context.HandleRelativePosition(-excess);
152c2a95ad2SPeter Klausler           n -= excess;
153c2a95ad2SPeter Klausler         }
154c2a95ad2SPeter Klausler         std::size_t bytesLeft{context.ViewBytesInRecord(p, false)};
155c2a95ad2SPeter Klausler         // Go back 'n' multi-byte characters.
156c2a95ad2SPeter Klausler         for (; n > 0 && bytesLeft && p; --n) {
157c2a95ad2SPeter Klausler           std::size_t byteCount{MeasurePreviousUTF8Bytes(p, bytesLeft)};
158c2a95ad2SPeter Klausler           context.HandleRelativePosition(-byteCount);
159c2a95ad2SPeter Klausler           bytesLeft -= byteCount;
160c2a95ad2SPeter Klausler           p -= byteCount;
161c2a95ad2SPeter Klausler         }
162c2a95ad2SPeter Klausler       }
163c2a95ad2SPeter Klausler     }
164c2a95ad2SPeter Klausler   }
165c2a95ad2SPeter Klausler   if (connection.internalIoCharKind > 1) {
166c2a95ad2SPeter Klausler     n *= connection.internalIoCharKind;
167c2a95ad2SPeter Klausler   }
168c2a95ad2SPeter Klausler   context.HandleRelativePosition(n);
169c2a95ad2SPeter Klausler   return true;
170c2a95ad2SPeter Klausler }
171c2a95ad2SPeter Klausler 
172c2a95ad2SPeter Klausler // Tn
173c2a95ad2SPeter Klausler template <typename CONTEXT>
174c2a95ad2SPeter Klausler static RT_API_ATTRS bool AbsoluteTabbing(CONTEXT &context, int n) {
175c2a95ad2SPeter Klausler   ConnectionState &connection{context.GetConnectionState()};
176c2a95ad2SPeter Klausler   n = n > 0 ? n - 1 : 0; // convert 1-based position to 0-based offset
177c2a95ad2SPeter Klausler   if constexpr (std::is_same_v<CONTEXT,
178c2a95ad2SPeter Klausler                     ExternalFormattedIoStatementState<Direction::Input>> ||
179c2a95ad2SPeter Klausler       std::is_same_v<CONTEXT,
180c2a95ad2SPeter Klausler           ExternalFormattedIoStatementState<Direction::Output>>) {
181c2a95ad2SPeter Klausler     if (connection.isUTF8) {
182c2a95ad2SPeter Klausler       // Reset to the beginning of the record, then TR(n-1)
183c2a95ad2SPeter Klausler       connection.HandleAbsolutePosition(0);
184c2a95ad2SPeter Klausler       return RelativeTabbing(context, n);
185c2a95ad2SPeter Klausler     }
186c2a95ad2SPeter Klausler   }
187c2a95ad2SPeter Klausler   if (connection.internalIoCharKind > 1) {
188c2a95ad2SPeter Klausler     n *= connection.internalIoCharKind;
189c2a95ad2SPeter Klausler   }
190c2a95ad2SPeter Klausler   context.HandleAbsolutePosition(n);
191c2a95ad2SPeter Klausler   return true;
192c2a95ad2SPeter Klausler }
193c2a95ad2SPeter Klausler 
19495696d56Speter klausler template <typename CONTEXT>
1958ebf7411SSlava Zakharin static RT_API_ATTRS void HandleControl(
1968ebf7411SSlava Zakharin     CONTEXT &context, char ch, char next, int n) {
19795696d56Speter klausler   MutableModes &modes{context.mutableModes()};
19895696d56Speter klausler   switch (ch) {
19995696d56Speter klausler   case 'B':
20095696d56Speter klausler     if (next == 'Z') {
20195696d56Speter klausler       modes.editingFlags |= blankZero;
20295696d56Speter klausler       return;
20395696d56Speter klausler     }
20495696d56Speter klausler     if (next == 'N') {
20595696d56Speter klausler       modes.editingFlags &= ~blankZero;
20695696d56Speter klausler       return;
20795696d56Speter klausler     }
20895696d56Speter klausler     break;
20995696d56Speter klausler   case 'D':
21095696d56Speter klausler     if (next == 'C') {
21195696d56Speter klausler       modes.editingFlags |= decimalComma;
21295696d56Speter klausler       return;
21395696d56Speter klausler     }
21495696d56Speter klausler     if (next == 'P') {
21595696d56Speter klausler       modes.editingFlags &= ~decimalComma;
21695696d56Speter klausler       return;
21795696d56Speter klausler     }
21895696d56Speter klausler     break;
21995696d56Speter klausler   case 'P':
22095696d56Speter klausler     if (!next) {
22195696d56Speter klausler       modes.scale = n; // kP - decimal scaling by 10**k
22295696d56Speter klausler       return;
22395696d56Speter klausler     }
22495696d56Speter klausler     break;
22595696d56Speter klausler   case 'R':
22695696d56Speter klausler     switch (next) {
2271f879005STim Keith     case 'N':
2281f879005STim Keith       modes.round = decimal::RoundNearest;
2291f879005STim Keith       return;
2301f879005STim Keith     case 'Z':
2311f879005STim Keith       modes.round = decimal::RoundToZero;
2321f879005STim Keith       return;
2331f879005STim Keith     case 'U':
2341f879005STim Keith       modes.round = decimal::RoundUp;
2351f879005STim Keith       return;
2361f879005STim Keith     case 'D':
2371f879005STim Keith       modes.round = decimal::RoundDown;
2381f879005STim Keith       return;
2391f879005STim Keith     case 'C':
2401f879005STim Keith       modes.round = decimal::RoundCompatible;
2411f879005STim Keith       return;
24295696d56Speter klausler     case 'P':
24395696d56Speter klausler       modes.round = executionEnvironment.defaultOutputRoundingMode;
24495696d56Speter klausler       return;
2451f879005STim Keith     default:
2461f879005STim Keith       break;
24795696d56Speter klausler     }
24895696d56Speter klausler     break;
24995696d56Speter klausler   case 'X':
250c2a95ad2SPeter Klausler     if (!next && RelativeTabbing(context, n)) {
25195696d56Speter klausler       return;
25295696d56Speter klausler     }
25395696d56Speter klausler     break;
25495696d56Speter klausler   case 'S':
25595696d56Speter klausler     if (next == 'P') {
25695696d56Speter klausler       modes.editingFlags |= signPlus;
25795696d56Speter klausler       return;
25895696d56Speter klausler     }
25995696d56Speter klausler     if (!next || next == 'S') {
26095696d56Speter klausler       modes.editingFlags &= ~signPlus;
26195696d56Speter klausler       return;
26295696d56Speter klausler     }
26395696d56Speter klausler     break;
26495696d56Speter klausler   case 'T': {
26595696d56Speter klausler     if (!next) { // Tn
266c2a95ad2SPeter Klausler       if (AbsoluteTabbing(context, n)) {
26795696d56Speter klausler         return;
26895696d56Speter klausler       }
269c2a95ad2SPeter Klausler     } else if (next == 'R' || next == 'L') { // TRn / TLn
270c2a95ad2SPeter Klausler       if (RelativeTabbing(context, next == 'L' ? -n : n)) {
27195696d56Speter klausler         return;
27295696d56Speter klausler       }
273c2a95ad2SPeter Klausler     }
27495696d56Speter klausler   } break;
2751f879005STim Keith   default:
2761f879005STim Keith     break;
27795696d56Speter klausler   }
27895696d56Speter klausler   if (next) {
2793b635714Speter klausler     context.SignalError(IostatErrorInFormat,
2803b635714Speter klausler         "Unknown '%c%c' edit descriptor in FORMAT", ch, next);
28195696d56Speter klausler   } else {
2823b635714Speter klausler     context.SignalError(
2833b635714Speter klausler         IostatErrorInFormat, "Unknown '%c' edit descriptor in FORMAT", ch);
28495696d56Speter klausler   }
28595696d56Speter klausler }
28695696d56Speter klausler 
28795696d56Speter klausler // Locates the next data edit descriptor in the format.
28895696d56Speter klausler // Handles all repetition counts and control edit descriptors.
28995696d56Speter klausler // Generally assumes that the format string has survived the common
29095696d56Speter klausler // format validator gauntlet.
29195696d56Speter klausler template <typename CONTEXT>
2928ebf7411SSlava Zakharin RT_API_ATTRS int FormatControl<CONTEXT>::CueUpNextDataEdit(
2938ebf7411SSlava Zakharin     Context &context, bool stop) {
294a1db3e62SPeter Klausler   bool hitUnlimitedLoopEnd{false};
29504eb93b1SPeter Klausler   // Do repetitions remain on an unparenthesized data edit?
29604eb93b1SPeter Klausler   while (height_ > 1 && format_[stack_[height_ - 1].start] != '(') {
29704eb93b1SPeter Klausler     offset_ = stack_[height_ - 1].start;
29804eb93b1SPeter Klausler     int repeat{stack_[height_ - 1].remaining};
29904eb93b1SPeter Klausler     --height_;
30004eb93b1SPeter Klausler     if (repeat > 0) {
30104eb93b1SPeter Klausler       return repeat;
30204eb93b1SPeter Klausler     }
30304eb93b1SPeter Klausler   }
30495696d56Speter klausler   while (true) {
30571e0261fSSlava Zakharin     Fortran::common::optional<int> repeat;
30695696d56Speter klausler     bool unlimited{false};
307c6cb726aSpeter klausler     auto maybeReversionPoint{offset_};
30827505565Speter klausler     CharType ch{GetNextChar(context)};
30995696d56Speter klausler     while (ch == ',' || ch == ':') {
31095696d56Speter klausler       // Skip commas, and don't complain if they're missing; the format
31195696d56Speter klausler       // validator does that.
31295696d56Speter klausler       if (stop && ch == ':') {
31395696d56Speter klausler         return 0;
31495696d56Speter klausler       }
31527505565Speter klausler       ch = GetNextChar(context);
31695696d56Speter klausler     }
31795696d56Speter klausler     if (ch == '-' || ch == '+' || (ch >= '0' && ch <= '9')) {
318fc71a49eSPeter Klausler       bool hadSign{ch == '-' || ch == '+'};
31995696d56Speter klausler       repeat = GetIntField(context, ch);
32095696d56Speter klausler       ch = GetNextChar(context);
321fc71a49eSPeter Klausler       if (hadSign && ch != 'p' && ch != 'P') {
322fc71a49eSPeter Klausler         ReportBadFormat(context,
323fc71a49eSPeter Klausler             "Invalid FORMAT: signed integer may appear only before 'P",
324fc71a49eSPeter Klausler             maybeReversionPoint);
325fc71a49eSPeter Klausler         return 0;
326fc71a49eSPeter Klausler       }
32795696d56Speter klausler     } else if (ch == '*') {
32895696d56Speter klausler       unlimited = true;
32995696d56Speter klausler       ch = GetNextChar(context);
33095696d56Speter klausler       if (ch != '(') {
33104eb93b1SPeter Klausler         ReportBadFormat(context,
33204eb93b1SPeter Klausler             "Invalid FORMAT: '*' may appear only before '('",
33304eb93b1SPeter Klausler             maybeReversionPoint);
3343b635714Speter klausler         return 0;
33595696d56Speter klausler       }
336fb9ec95cSPeter Klausler       if (height_ != 1) {
337fb9ec95cSPeter Klausler         ReportBadFormat(context,
338fb9ec95cSPeter Klausler             "Invalid FORMAT: '*' must be nested in exactly one set of "
339fb9ec95cSPeter Klausler             "parentheses",
340fb9ec95cSPeter Klausler             maybeReversionPoint);
341fb9ec95cSPeter Klausler         return 0;
342fb9ec95cSPeter Klausler       }
34395696d56Speter klausler     }
34427505565Speter klausler     ch = Capitalize(ch);
34595696d56Speter klausler     if (ch == '(') {
34695696d56Speter klausler       if (height_ >= maxHeight_) {
34704eb93b1SPeter Klausler         ReportBadFormat(context,
34804eb93b1SPeter Klausler             "FORMAT stack overflow: too many nested parentheses",
34904eb93b1SPeter Klausler             maybeReversionPoint);
3503b635714Speter klausler         return 0;
35195696d56Speter klausler       }
35295696d56Speter klausler       stack_[height_].start = offset_ - 1; // the '('
353c6cb726aSpeter klausler       RUNTIME_CHECK(context, format_[stack_[height_].start] == '(');
35495696d56Speter klausler       if (unlimited || height_ == 0) {
35595696d56Speter klausler         stack_[height_].remaining = Iteration::unlimited;
35695696d56Speter klausler       } else if (repeat) {
35795696d56Speter klausler         if (*repeat <= 0) {
35895696d56Speter klausler           *repeat = 1; // error recovery
35995696d56Speter klausler         }
36095696d56Speter klausler         stack_[height_].remaining = *repeat - 1;
36195696d56Speter klausler       } else {
36295696d56Speter klausler         stack_[height_].remaining = 0;
36395696d56Speter klausler       }
364ec4ba0f5SPeter Klausler       if (height_ == 1 && !hitEnd_) {
365ec4ba0f5SPeter Klausler         // Subtle point (F'2018 13.4 para 9): the last parenthesized group
366c6cb726aSpeter klausler         // at height 1 becomes the restart point after control reaches the
367c6cb726aSpeter klausler         // end of the format, including its repeat count.
36804eb93b1SPeter Klausler         stack_[0].start = maybeReversionPoint;
369c6cb726aSpeter klausler       }
37095696d56Speter klausler       ++height_;
37195696d56Speter klausler     } else if (height_ == 0) {
37204eb93b1SPeter Klausler       ReportBadFormat(context, "FORMAT lacks initial '('", maybeReversionPoint);
3733b635714Speter klausler       return 0;
37495696d56Speter klausler     } else if (ch == ')') {
37595696d56Speter klausler       if (height_ == 1) {
376fc71a49eSPeter Klausler         hitEnd_ = true;
37795696d56Speter klausler         if (stop) {
37895696d56Speter klausler           return 0; // end of FORMAT and no data items remain
37995696d56Speter klausler         }
38095696d56Speter klausler         context.AdvanceRecord(); // implied / before rightmost )
38195696d56Speter klausler       }
38204eb93b1SPeter Klausler       auto restart{stack_[height_ - 1].start};
38304eb93b1SPeter Klausler       if (format_[restart] == '(') {
38404eb93b1SPeter Klausler         ++restart;
38504eb93b1SPeter Klausler       }
38695696d56Speter klausler       if (stack_[height_ - 1].remaining == Iteration::unlimited) {
387fb9ec95cSPeter Klausler         if (height_ > 1 && GetNextChar(context) != ')') {
388fb9ec95cSPeter Klausler           ReportBadFormat(context,
389fb9ec95cSPeter Klausler               "Unlimited repetition in FORMAT may not be followed by more "
390fb9ec95cSPeter Klausler               "items",
391fb9ec95cSPeter Klausler               restart);
392fb9ec95cSPeter Klausler           return 0;
393fb9ec95cSPeter Klausler         }
394a1db3e62SPeter Klausler         if (hitUnlimitedLoopEnd) {
39504eb93b1SPeter Klausler           ReportBadFormat(context,
39604eb93b1SPeter Klausler               "Unlimited repetition in FORMAT lacks data edit descriptors",
39704eb93b1SPeter Klausler               restart);
398fb9ec95cSPeter Klausler           return 0;
39995696d56Speter klausler         }
400a1db3e62SPeter Klausler         hitUnlimitedLoopEnd = true;
401fb9ec95cSPeter Klausler         offset_ = restart;
40295696d56Speter klausler       } else if (stack_[height_ - 1].remaining-- > 0) {
403c6cb726aSpeter klausler         offset_ = restart;
40495696d56Speter klausler       } else {
40595696d56Speter klausler         --height_;
40695696d56Speter klausler       }
40795696d56Speter klausler     } else if (ch == '\'' || ch == '"') {
40895696d56Speter klausler       // Quoted 'character literal'
40995696d56Speter klausler       CharType quote{ch};
41095696d56Speter klausler       auto start{offset_};
41195696d56Speter klausler       while (offset_ < formatLength_ && format_[offset_] != quote) {
41295696d56Speter klausler         ++offset_;
41395696d56Speter klausler       }
41495696d56Speter klausler       if (offset_ >= formatLength_) {
41504eb93b1SPeter Klausler         ReportBadFormat(context,
41604eb93b1SPeter Klausler             "FORMAT missing closing quote on character literal",
41704eb93b1SPeter Klausler             maybeReversionPoint);
4183b635714Speter klausler         return 0;
41995696d56Speter klausler       }
42095696d56Speter klausler       ++offset_;
42195696d56Speter klausler       std::size_t chars{
42295696d56Speter klausler           static_cast<std::size_t>(&format_[offset_] - &format_[start])};
4231a3ac586SPeter Klausler       if (offset_ < formatLength_ && format_[offset_] == quote) {
42495696d56Speter klausler         // subtle: handle doubled quote character in a literal by including
42595696d56Speter klausler         // the first in the output, then treating the second as the start
42695696d56Speter klausler         // of another character literal.
42795696d56Speter klausler       } else {
42895696d56Speter klausler         --chars;
42995696d56Speter klausler       }
430bad52055SPeter Klausler       EmitAscii(context, format_ + start, chars);
43195696d56Speter klausler     } else if (ch == 'H') {
43295696d56Speter klausler       // 9HHOLLERITH
43395696d56Speter klausler       if (!repeat || *repeat < 1 || offset_ + *repeat > formatLength_) {
43404eb93b1SPeter Klausler         ReportBadFormat(context, "Invalid width on Hollerith in FORMAT",
43504eb93b1SPeter Klausler             maybeReversionPoint);
4363b635714Speter klausler         return 0;
43795696d56Speter klausler       }
438bad52055SPeter Klausler       EmitAscii(context, format_ + offset_, static_cast<std::size_t>(*repeat));
43995696d56Speter klausler       offset_ += *repeat;
44095696d56Speter klausler     } else if (ch >= 'A' && ch <= 'Z') {
44195696d56Speter klausler       int start{offset_ - 1};
442e5746415Speter klausler       CharType next{'\0'};
443e5746415Speter klausler       if (ch != 'P') { // 1PE5.2 - comma not required (C1302)
444e5746415Speter klausler         CharType peek{Capitalize(PeekNext())};
445e5746415Speter klausler         if (peek >= 'A' && peek <= 'Z') {
446*cd92c425SPeter Klausler           if ((ch == 'A' && peek == 'T' /* anticipate F'202X AT editing */) ||
447*cd92c425SPeter Klausler               ch == 'B' || ch == 'D' || ch == 'E' || ch == 'R' || ch == 'S' ||
448*cd92c425SPeter Klausler               ch == 'T') {
44944ff4df6SPeter Klausler             // Assume a two-letter edit descriptor
450e5746415Speter klausler             next = peek;
45195696d56Speter klausler             ++offset_;
45244ff4df6SPeter Klausler           } else {
45344ff4df6SPeter Klausler             // extension: assume a comma between 'ch' and 'peek'
45444ff4df6SPeter Klausler           }
455e5746415Speter klausler         }
45695696d56Speter klausler       }
45743fadefbSpeter klausler       if ((!next &&
45843fadefbSpeter klausler               (ch == 'A' || ch == 'I' || ch == 'B' || ch == 'E' || ch == 'D' ||
45943fadefbSpeter klausler                   ch == 'O' || ch == 'Z' || ch == 'F' || ch == 'G' ||
46043fadefbSpeter klausler                   ch == 'L')) ||
46143fadefbSpeter klausler           (ch == 'E' && (next == 'N' || next == 'S' || next == 'X')) ||
46243fadefbSpeter klausler           (ch == 'D' && next == 'T')) {
46395696d56Speter klausler         // Data edit descriptor found
46495696d56Speter klausler         offset_ = start;
46595696d56Speter klausler         return repeat && *repeat > 0 ? *repeat : 1;
46695696d56Speter klausler       } else {
46795696d56Speter klausler         // Control edit descriptor
46895696d56Speter klausler         if (ch == 'T') { // Tn, TLn, TRn
46995696d56Speter klausler           repeat = GetIntField(context);
47095696d56Speter klausler         }
47195696d56Speter klausler         HandleControl(context, static_cast<char>(ch), static_cast<char>(next),
47295696d56Speter klausler             repeat ? *repeat : 1);
47395696d56Speter klausler       }
47495696d56Speter klausler     } else if (ch == '/') {
47595696d56Speter klausler       context.AdvanceRecord(repeat && *repeat > 0 ? *repeat : 1);
476cd0a1226Speter klausler     } else if (ch == '$' || ch == '\\') {
477cd0a1226Speter klausler       context.mutableModes().nonAdvancing = true;
478fbb020fbSpeter klausler     } else if (ch == '\t' || ch == '\v') {
479fbb020fbSpeter klausler       // Tabs (extension)
480fbb020fbSpeter klausler       // TODO: any other raw characters?
481bad52055SPeter Klausler       EmitAscii(context, format_ + offset_ - 1, 1);
48295696d56Speter klausler     } else {
48304eb93b1SPeter Klausler       ReportBadFormat(
48404eb93b1SPeter Klausler           context, "Invalid character in FORMAT", maybeReversionPoint);
4853b635714Speter klausler       return 0;
48695696d56Speter klausler     }
48795696d56Speter klausler   }
48895696d56Speter klausler }
48995696d56Speter klausler 
49043fadefbSpeter klausler // Returns the next data edit descriptor
49195696d56Speter klausler template <typename CONTEXT>
4928ebf7411SSlava Zakharin RT_API_ATTRS Fortran::common::optional<DataEdit>
4938ebf7411SSlava Zakharin FormatControl<CONTEXT>::GetNextDataEdit(Context &context, int maxRepeat) {
49495696d56Speter klausler   int repeat{CueUpNextDataEdit(context)};
49595696d56Speter klausler   auto start{offset_};
49695696d56Speter klausler   DataEdit edit;
497c13f7e17SPeter Klausler   edit.modes = context.mutableModes();
498c13f7e17SPeter Klausler   // Handle repeated nonparenthesized edit descriptors
499c13f7e17SPeter Klausler   edit.repeat = std::min(repeat, maxRepeat); // 0 if maxRepeat==0
500c13f7e17SPeter Klausler   if (repeat > maxRepeat) {
501c13f7e17SPeter Klausler     stack_[height_].start = start; // after repeat count
502c13f7e17SPeter Klausler     stack_[height_].remaining = repeat - edit.repeat;
503c13f7e17SPeter Klausler     ++height_;
50443fadefbSpeter klausler   }
505c13f7e17SPeter Klausler   edit.descriptor = static_cast<char>(Capitalize(GetNextChar(context)));
506c13f7e17SPeter Klausler   if (edit.descriptor == 'D' && Capitalize(PeekNext()) == 'T') {
5077cf1608bSPeter Klausler     // DT['iotype'][(v_list)] defined I/O
50843fadefbSpeter klausler     edit.descriptor = DataEdit::DefinedDerivedType;
50943fadefbSpeter klausler     ++offset_;
51043fadefbSpeter klausler     if (auto quote{static_cast<char>(PeekNext())};
51143fadefbSpeter klausler         quote == '\'' || quote == '"') {
51243fadefbSpeter klausler       // Capture the quoted 'iotype'
51379f6b812SPeter Klausler       bool ok{false};
51443fadefbSpeter klausler       for (++offset_; offset_ < formatLength_;) {
51543fadefbSpeter klausler         auto ch{static_cast<char>(format_[offset_++])};
51643fadefbSpeter klausler         if (ch == quote &&
51743fadefbSpeter klausler             (offset_ == formatLength_ ||
51843fadefbSpeter klausler                 static_cast<char>(format_[offset_]) != quote)) {
51943fadefbSpeter klausler           ok = true;
52043fadefbSpeter klausler           break; // that was terminating quote
52179f6b812SPeter Klausler         }
52279f6b812SPeter Klausler         if (edit.ioTypeChars >= edit.maxIoTypeChars) {
52379f6b812SPeter Klausler           ReportBadFormat(context, "Excessive DT'iotype' in FORMAT", start);
52471e0261fSSlava Zakharin           return Fortran::common::nullopt;
52579f6b812SPeter Klausler         }
52643fadefbSpeter klausler         edit.ioType[edit.ioTypeChars++] = ch;
52743fadefbSpeter klausler         if (ch == quote) {
52895696d56Speter klausler           ++offset_;
52995696d56Speter klausler         }
53095696d56Speter klausler       }
53143fadefbSpeter klausler       if (!ok) {
53204eb93b1SPeter Klausler         ReportBadFormat(context, "Unclosed DT'iotype' in FORMAT", start);
53371e0261fSSlava Zakharin         return Fortran::common::nullopt;
53443fadefbSpeter klausler       }
53543fadefbSpeter klausler     }
53643fadefbSpeter klausler     if (PeekNext() == '(') {
53743fadefbSpeter klausler       // Capture the v_list arguments
53879f6b812SPeter Klausler       bool ok{false};
53943fadefbSpeter klausler       for (++offset_; offset_ < formatLength_;) {
54079f6b812SPeter Klausler         bool hadError{false};
54179f6b812SPeter Klausler         int n{GetIntField(context, '\0', &hadError)};
54279f6b812SPeter Klausler         if (hadError) {
54379f6b812SPeter Klausler           ok = false;
54479f6b812SPeter Klausler           break;
54543fadefbSpeter klausler         }
54679f6b812SPeter Klausler         if (edit.vListEntries >= edit.maxVListEntries) {
54779f6b812SPeter Klausler           ReportBadFormat(context, "Excessive DT(v_list) in FORMAT", start);
54871e0261fSSlava Zakharin           return Fortran::common::nullopt;
54979f6b812SPeter Klausler         }
55079f6b812SPeter Klausler         edit.vList[edit.vListEntries++] = n;
55143fadefbSpeter klausler         auto ch{static_cast<char>(GetNextChar(context))};
55243fadefbSpeter klausler         if (ch != ',') {
55343fadefbSpeter klausler           ok = ch == ')';
55443fadefbSpeter klausler           break;
55543fadefbSpeter klausler         }
55643fadefbSpeter klausler       }
55743fadefbSpeter klausler       if (!ok) {
55804eb93b1SPeter Klausler         ReportBadFormat(context, "Unclosed DT(v_list) in FORMAT", start);
55971e0261fSSlava Zakharin         return Fortran::common::nullopt;
56043fadefbSpeter klausler       }
56143fadefbSpeter klausler     }
562c13f7e17SPeter Klausler   } else { // not DT'iotype'
563c13f7e17SPeter Klausler     if (edit.descriptor == 'E') {
564c13f7e17SPeter Klausler       if (auto next{static_cast<char>(Capitalize(PeekNext()))};
565c13f7e17SPeter Klausler           next == 'N' || next == 'S' || next == 'X') {
566c13f7e17SPeter Klausler         edit.variation = next;
567c13f7e17SPeter Klausler         ++offset_;
56843fadefbSpeter klausler       }
569c13f7e17SPeter Klausler     }
570c13f7e17SPeter Klausler     // Width is optional for A[w] in the standard and optional
571c13f7e17SPeter Klausler     // for Lw in most compilers.
572c13f7e17SPeter Klausler     // Intel & (presumably, from bug report) Fujitsu allow
573c13f7e17SPeter Klausler     // a missing 'w' & 'd'/'m' for other edit descriptors -- but not
574c13f7e17SPeter Klausler     // 'd'/'m' with a missing 'w' -- and so interpret "(E)" as "(E0)".
575c13f7e17SPeter Klausler     if (CharType ch{PeekNext()}; (ch >= '0' && ch <= '9') || ch == '.') {
57695696d56Speter klausler       edit.width = GetIntField(context);
577aa77cf90SPeter Klausler       if constexpr (std::is_base_of_v<InputStatementState, CONTEXT>) {
578aa77cf90SPeter Klausler         if (edit.width.value_or(-1) == 0) {
579aa77cf90SPeter Klausler           ReportBadFormat(context, "Input field width is zero", start);
580aa77cf90SPeter Klausler         }
581aa77cf90SPeter Klausler       }
582c13f7e17SPeter Klausler       if (PeekNext() == '.') {
58395696d56Speter klausler         ++offset_;
58495696d56Speter klausler         edit.digits = GetIntField(context);
585c13f7e17SPeter Klausler         if (CharType ch{PeekNext()};
586c13f7e17SPeter Klausler             ch == 'e' || ch == 'E' || ch == 'd' || ch == 'D') {
58795696d56Speter klausler           ++offset_;
58895696d56Speter klausler           edit.expoDigits = GetIntField(context);
58995696d56Speter klausler         }
59095696d56Speter klausler       }
591c13f7e17SPeter Klausler     }
59295696d56Speter klausler   }
59395696d56Speter klausler   return edit;
59495696d56Speter klausler }
59595696d56Speter klausler 
59695696d56Speter klausler template <typename CONTEXT>
5978ebf7411SSlava Zakharin RT_API_ATTRS void FormatControl<CONTEXT>::Finish(Context &context) {
59895696d56Speter klausler   CueUpNextDataEdit(context, true /* stop at colon or end of FORMAT */);
599cc180f4cSPeter Klausler   if (freeFormat_) {
600cc180f4cSPeter Klausler     FreeMemory(const_cast<CharType *>(format_));
601cc180f4cSPeter Klausler   }
60295696d56Speter klausler }
6031f879005STim Keith } // namespace Fortran::runtime::io
60495696d56Speter klausler #endif // FORTRAN_RUNTIME_FORMAT_IMPLEMENTATION_H_
605