xref: /llvm-project/flang/runtime/format.cpp (revision 352d347aa5f5f1ba9b17aedd90daa5c110b8a50e)
1 //===-- runtime/format.cpp --------------------------------------*- C++ -*-===//
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 "format.h"
10 #include "io-stmt.h"
11 #include "flang/common/format.h"
12 #include "flang/decimal/decimal.h"
13 #include <limits>
14 
15 namespace Fortran::runtime::io {
16 
17 // Default FormatContext virtual member functions
18 void FormatContext::Emit(const char *, std::size_t) {
19   Crash("Cannot emit data from this FORMAT string");
20 }
21 void FormatContext::Emit(const char16_t *, std::size_t) {
22   Crash("Cannot emit data from this FORMAT string");
23 }
24 void FormatContext::Emit(const char32_t *, std::size_t) {
25   Crash("Cannot emit data from this FORMAT string");
26 }
27 void FormatContext::HandleSlash(int) {
28   Crash("A / control edit descriptor may not appear in this FORMAT string");
29 }
30 void FormatContext::HandleAbsolutePosition(int) {
31   Crash("A Tn control edit descriptor may not appear in this FORMAT string");
32 }
33 void FormatContext::HandleRelativePosition(int) {
34   Crash("An nX, TLn, or TRn control edit descriptor may not appear in this "
35         "FORMAT string");
36 }
37 
38 template<typename CHAR>
39 FormatControl<CHAR>::FormatControl(Terminator &terminator, const CHAR *format,
40     std::size_t formatLength, int maxHeight)
41   : maxHeight_{static_cast<std::uint8_t>(maxHeight)}, format_{format},
42     formatLength_{static_cast<int>(formatLength)} {
43   // The additional two items are for the whole string and a
44   // repeated non-parenthesized edit descriptor.
45   if (maxHeight > std::numeric_limits<std::int8_t>::max()) {
46     terminator.Crash("internal Fortran runtime error: maxHeight %d", maxHeight);
47   }
48   stack_[0].start = offset_;
49   stack_[0].remaining = Iteration::unlimited;  // 13.4(8)
50 }
51 
52 template<typename CHAR>
53 int FormatControl<CHAR>::GetMaxParenthesisNesting(
54     Terminator &terminator, const CHAR *format, std::size_t formatLength) {
55   using Validator = common::FormatValidator<CHAR>;
56   typename Validator::Reporter reporter{
57       [&](const common::FormatMessage &message) {
58         terminator.Crash(message.text, message.arg);
59         return false;  // crashes on error above
60       }};
61   Validator validator{format, formatLength, reporter};
62   validator.Check();
63   return validator.maxNesting();
64 }
65 
66 template<typename CHAR>
67 int FormatControl<CHAR>::GetIntField(Terminator &terminator, CHAR firstCh) {
68   CHAR ch{firstCh ? firstCh : PeekNext()};
69   if (ch != '-' && ch != '+' && (ch < '0' || ch > '9')) {
70     terminator.Crash(
71         "Invalid FORMAT: integer expected at '%c'", static_cast<char>(ch));
72   }
73   int result{0};
74   bool negate{ch == '-'};
75   if (negate) {
76     firstCh = '\0';
77     ch = PeekNext();
78   }
79   while (ch >= '0' && ch <= '9') {
80     if (result >
81         std::numeric_limits<int>::max() / 10 - (static_cast<int>(ch) - '0')) {
82       terminator.Crash("FORMAT integer field out of range");
83     }
84     result = 10 * result + ch - '0';
85     if (firstCh) {
86       firstCh = '\0';
87     } else {
88       ++offset_;
89     }
90     ch = PeekNext();
91   }
92   if (negate && (result *= -1) > 0) {
93     terminator.Crash("FORMAT integer field out of range");
94   }
95   return result;
96 }
97 
98 static void HandleControl(
99     FormatContext &context, std::uint16_t &scale, char ch, char next, int n) {
100   MutableModes &modes{context.mutableModes()};
101   switch (ch) {
102   case 'B':
103     if (next == 'Z') {
104       modes.editingFlags |= blankZero;
105       return;
106     }
107     if (next == 'N') {
108       modes.editingFlags &= ~blankZero;
109       return;
110     }
111     break;
112   case 'D':
113     if (next == 'C') {
114       modes.editingFlags |= decimalComma;
115       return;
116     }
117     if (next == 'P') {
118       modes.editingFlags &= ~decimalComma;
119       return;
120     }
121     break;
122   case 'P':
123     if (!next) {
124       scale = n;  // kP - decimal scaling by 10**k (TODO)
125       return;
126     }
127     break;
128   case 'R':
129     switch (next) {
130     case 'N': modes.roundingMode = common::RoundingMode::TiesToEven; return;
131     case 'Z': modes.roundingMode = common::RoundingMode::ToZero; return;
132     case 'U': modes.roundingMode = common::RoundingMode::Up; return;
133     case 'D': modes.roundingMode = common::RoundingMode::Down; return;
134     case 'C':
135       modes.roundingMode = common::RoundingMode::TiesAwayFromZero;
136       return;
137     default: break;
138     }
139     break;
140   case 'X':
141     if (!next) {
142       context.HandleRelativePosition(n);
143       return;
144     }
145     break;
146   case 'S':
147     if (next == 'P') {
148       modes.editingFlags |= signPlus;
149       return;
150     }
151     if (!next || next == 'S') {
152       modes.editingFlags &= ~signPlus;
153       return;
154     }
155     break;
156   case 'T': {
157     if (!next) {  // Tn
158       context.HandleAbsolutePosition(n);
159       return;
160     }
161     if (next == 'L' || next == 'R') {  // TLn & TRn
162       context.HandleRelativePosition(next == 'L' ? -n : n);
163       return;
164     }
165   } break;
166   default: break;
167   }
168   if (next) {
169     context.Crash("Unknown '%c%c' edit descriptor in FORMAT", ch, next);
170   } else {
171     context.Crash("Unknown '%c' edit descriptor in FORMAT", ch);
172   }
173 }
174 
175 // Locates the next data edit descriptor in the format.
176 // Handles all repetition counts and control edit descriptors.
177 // Generally assumes that the format string has survived the common
178 // format validator gauntlet.
179 template<typename CHAR>
180 int FormatControl<CHAR>::CueUpNextDataEdit(FormatContext &context, bool stop) {
181   int unlimitedLoopCheck{-1};
182   while (true) {
183     std::optional<int> repeat;
184     bool unlimited{false};
185     CHAR ch{Capitalize(GetNextChar(context))};
186     while (ch == ',' || ch == ':') {
187       // Skip commas, and don't complain if they're missing; the format
188       // validator does that.
189       if (stop && ch == ':') {
190         return 0;
191       }
192       ch = Capitalize(GetNextChar(context));
193     }
194     if (ch == '-' || ch == '+' || (ch >= '0' && ch <= '9')) {
195       repeat = GetIntField(context, ch);
196       ch = GetNextChar(context);
197     } else if (ch == '*') {
198       unlimited = true;
199       ch = GetNextChar(context);
200       if (ch != '(') {
201         context.Crash("Invalid FORMAT: '*' may appear only before '('");
202       }
203     }
204     if (ch == '(') {
205       if (height_ >= maxHeight_) {
206         context.Crash("FORMAT stack overflow: too many nested parentheses");
207       }
208       stack_[height_].start = offset_ - 1;  // the '('
209       if (unlimited || height_ == 0) {
210         stack_[height_].remaining = Iteration::unlimited;
211         unlimitedLoopCheck = offset_ - 1;
212       } else if (repeat) {
213         if (*repeat <= 0) {
214           *repeat = 1;  // error recovery
215         }
216         stack_[height_].remaining = *repeat - 1;
217       } else {
218         stack_[height_].remaining = 0;
219       }
220       ++height_;
221     } else if (height_ == 0) {
222       context.Crash("FORMAT lacks initial '('");
223     } else if (ch == ')') {
224       if (height_ == 1) {
225         if (stop) {
226           return 0;  // end of FORMAT and no data items remain
227         }
228         context.HandleSlash();  // implied / before rightmost )
229       }
230       if (stack_[height_ - 1].remaining == Iteration::unlimited) {
231         offset_ = stack_[height_ - 1].start + 1;
232         if (offset_ == unlimitedLoopCheck) {
233           context.Crash(
234               "Unlimited repetition in FORMAT lacks data edit descriptors");
235         }
236       } else if (stack_[height_ - 1].remaining-- > 0) {
237         offset_ = stack_[height_ - 1].start + 1;
238       } else {
239         --height_;
240       }
241     } else if (ch == '\'' || ch == '"') {
242       // Quoted 'character literal'
243       CHAR quote{ch};
244       auto start{offset_};
245       while (offset_ < formatLength_ && format_[offset_] != quote) {
246         ++offset_;
247       }
248       if (offset_ >= formatLength_) {
249         context.Crash("FORMAT missing closing quote on character literal");
250       }
251       ++offset_;
252       std::size_t chars{
253           static_cast<std::size_t>(&format_[offset_] - &format_[start])};
254       if (PeekNext() == quote) {
255         // subtle: handle doubled quote character in a literal by including
256         // the first in the output, then treating the second as the start
257         // of another character literal.
258       } else {
259         --chars;
260       }
261       context.Emit(format_ + start, chars);
262     } else if (ch == 'H') {
263       // 9HHOLLERITH
264       if (!repeat || *repeat < 1 || offset_ + *repeat > formatLength_) {
265         context.Crash("Invalid width on Hollerith in FORMAT");
266       }
267       context.Emit(format_ + offset_, static_cast<std::size_t>(*repeat));
268       offset_ += *repeat;
269     } else if (ch >= 'A' && ch <= 'Z') {
270       int start{offset_ - 1};
271       CHAR next{Capitalize(PeekNext())};
272       if (next < 'A' || next > 'Z') {
273         next = '\0';
274       }
275       if (ch == 'E' ||
276           (!next &&
277               (ch == 'A' || ch == 'I' || ch == 'B' || ch == 'O' || ch == 'Z' ||
278                   ch == 'F' || ch == 'D' || ch == 'G'))) {
279         // Data edit descriptor found
280         offset_ = start;
281         return repeat && *repeat > 0 ? *repeat : 1;
282       } else {
283         // Control edit descriptor
284         if (ch == 'T') {  // Tn, TLn, TRn
285           repeat = GetIntField(context);
286         }
287         HandleControl(context, scale_, static_cast<char>(ch),
288             static_cast<char>(next), repeat && *repeat > 0 ? *repeat : 1);
289       }
290     } else if (ch == '/') {
291       context.HandleSlash(repeat && *repeat > 0 ? *repeat : 1);
292     } else {
293       context.Crash("Invalid character '%c' in FORMAT", static_cast<char>(ch));
294     }
295   }
296 }
297 
298 template<typename CHAR>
299 void FormatControl<CHAR>::GetNext(
300     FormatContext &context, DataEdit &edit, int maxRepeat) {
301 
302   // TODO: DT editing
303 
304   // Return the next data edit descriptor
305   int repeat{CueUpNextDataEdit(context)};
306   auto start{offset_};
307   edit.descriptor = static_cast<char>(Capitalize(GetNextChar(context)));
308   if (edit.descriptor == 'E') {
309     edit.variation = static_cast<char>(Capitalize(PeekNext()));
310     if (edit.variation >= 'A' && edit.variation <= 'Z') {
311       ++offset_;
312     } else {
313       edit.variation = '\0';
314     }
315   } else {
316     edit.variation = '\0';
317   }
318 
319   edit.width = GetIntField(context);
320   edit.modes = context.mutableModes();
321   if (PeekNext() == '.') {
322     ++offset_;
323     edit.digits = GetIntField(context);
324     CHAR ch{PeekNext()};
325     if (ch == 'e' || ch == 'E' || ch == 'd' || ch == 'D') {
326       ++offset_;
327       edit.expoDigits = GetIntField(context);
328     } else {
329       edit.expoDigits.reset();
330     }
331   } else {
332     edit.digits.reset();
333     edit.expoDigits.reset();
334   }
335 
336   // Handle repeated nonparenthesized edit descriptors
337   if (repeat > 1) {
338     stack_[height_].start = start;  // after repeat count
339     stack_[height_].remaining = repeat;  // full count
340     ++height_;
341   }
342   edit.repeat = 1;
343   if (height_ > 1) {
344     int start{stack_[height_ - 1].start};
345     if (format_[start] != '(') {
346       if (stack_[height_ - 1].remaining > maxRepeat) {
347         edit.repeat = maxRepeat;
348         stack_[height_ - 1].remaining -= maxRepeat;
349         offset_ = start;  // repeat same edit descriptor next time
350       } else {
351         edit.repeat = stack_[height_ - 1].remaining;
352         --height_;
353       }
354     }
355   }
356 }
357 
358 template<typename CHAR>
359 void FormatControl<CHAR>::FinishOutput(FormatContext &context) {
360   CueUpNextDataEdit(context, true /* stop at colon or end of FORMAT */);
361 }
362 
363 template class FormatControl<char>;
364 template class FormatControl<char16_t>;
365 template class FormatControl<char32_t>;
366 }
367