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