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 "flang/Common/format.h" 17 #include "flang/Decimal/decimal.h" 18 #include "flang/Runtime/main.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 || ch == '+') { 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 auto maybeReversionPoint{offset_}; 229 CharType ch{GetNextChar(context)}; 230 while (ch == ',' || ch == ':') { 231 // Skip commas, and don't complain if they're missing; the format 232 // validator does that. 233 if (stop && ch == ':') { 234 return 0; 235 } 236 ch = GetNextChar(context); 237 } 238 if (ch == '-' || ch == '+' || (ch >= '0' && ch <= '9')) { 239 repeat = GetIntField(context, ch); 240 ch = GetNextChar(context); 241 } else if (ch == '*') { 242 unlimited = true; 243 ch = GetNextChar(context); 244 if (ch != '(') { 245 context.SignalError(IostatErrorInFormat, 246 "Invalid FORMAT: '*' may appear only before '('"); 247 return 0; 248 } 249 } 250 ch = Capitalize(ch); 251 if (ch == '(') { 252 if (height_ >= maxHeight_) { 253 context.SignalError(IostatErrorInFormat, 254 "FORMAT stack overflow: too many nested parentheses"); 255 return 0; 256 } 257 stack_[height_].start = offset_ - 1; // the '(' 258 RUNTIME_CHECK(context, format_[stack_[height_].start] == '('); 259 if (unlimited || height_ == 0) { 260 stack_[height_].remaining = Iteration::unlimited; 261 unlimitedLoopCheck = offset_ - 1; 262 } else if (repeat) { 263 if (*repeat <= 0) { 264 *repeat = 1; // error recovery 265 } 266 stack_[height_].remaining = *repeat - 1; 267 } else { 268 stack_[height_].remaining = 0; 269 } 270 if (height_ == 1) { 271 // Subtle point (F'2018 13.4 para 9): tha last parenthesized group 272 // at height 1 becomes the restart point after control reaches the 273 // end of the format, including its repeat count. 274 stack_[0].start = maybeReversionPoint - 1; 275 } 276 ++height_; 277 } else if (height_ == 0) { 278 context.SignalError(IostatErrorInFormat, "FORMAT lacks initial '('"); 279 return 0; 280 } else if (ch == ')') { 281 if (height_ == 1) { 282 if (stop) { 283 return 0; // end of FORMAT and no data items remain 284 } 285 context.AdvanceRecord(); // implied / before rightmost ) 286 } 287 auto restart{stack_[height_ - 1].start + 1}; 288 if (stack_[height_ - 1].remaining == Iteration::unlimited) { 289 offset_ = restart; 290 if (offset_ == unlimitedLoopCheck) { 291 context.SignalError(IostatErrorInFormat, 292 "Unlimited repetition in FORMAT lacks data edit descriptors"); 293 } 294 } else if (stack_[height_ - 1].remaining-- > 0) { 295 offset_ = restart; 296 } else { 297 --height_; 298 } 299 } else if (ch == '\'' || ch == '"') { 300 // Quoted 'character literal' 301 CharType quote{ch}; 302 auto start{offset_}; 303 while (offset_ < formatLength_ && format_[offset_] != quote) { 304 ++offset_; 305 } 306 if (offset_ >= formatLength_) { 307 context.SignalError(IostatErrorInFormat, 308 "FORMAT missing closing quote on character literal"); 309 return 0; 310 } 311 ++offset_; 312 std::size_t chars{ 313 static_cast<std::size_t>(&format_[offset_] - &format_[start])}; 314 if (PeekNext() == quote) { 315 // subtle: handle doubled quote character in a literal by including 316 // the first in the output, then treating the second as the start 317 // of another character literal. 318 } else { 319 --chars; 320 } 321 context.Emit(format_ + start, chars); 322 } else if (ch == 'H') { 323 // 9HHOLLERITH 324 if (!repeat || *repeat < 1 || offset_ + *repeat > formatLength_) { 325 context.SignalError( 326 IostatErrorInFormat, "Invalid width on Hollerith in FORMAT"); 327 return 0; 328 } 329 context.Emit(format_ + offset_, static_cast<std::size_t>(*repeat)); 330 offset_ += *repeat; 331 } else if (ch >= 'A' && ch <= 'Z') { 332 int start{offset_ - 1}; 333 CharType next{'\0'}; 334 if (ch != 'P') { // 1PE5.2 - comma not required (C1302) 335 CharType peek{Capitalize(PeekNext())}; 336 if (peek >= 'A' && peek <= 'Z') { 337 next = peek; 338 ++offset_; 339 } 340 } 341 if ((!next && 342 (ch == 'A' || ch == 'I' || ch == 'B' || ch == 'E' || ch == 'D' || 343 ch == 'O' || ch == 'Z' || ch == 'F' || ch == 'G' || 344 ch == 'L')) || 345 (ch == 'E' && (next == 'N' || next == 'S' || next == 'X')) || 346 (ch == 'D' && next == 'T')) { 347 // Data edit descriptor found 348 offset_ = start; 349 return repeat && *repeat > 0 ? *repeat : 1; 350 } else { 351 // Control edit descriptor 352 if (ch == 'T') { // Tn, TLn, TRn 353 repeat = GetIntField(context); 354 } 355 HandleControl(context, static_cast<char>(ch), static_cast<char>(next), 356 repeat ? *repeat : 1); 357 } 358 } else if (ch == '/') { 359 context.AdvanceRecord(repeat && *repeat > 0 ? *repeat : 1); 360 } else if (ch == '$' || ch == '\\') { 361 context.mutableModes().nonAdvancing = true; 362 } else if (ch == '\t' || ch == '\v') { 363 // Tabs (extension) 364 // TODO: any other raw characters? 365 context.Emit(format_ + offset_ - 1, 1); 366 } else { 367 context.SignalError(IostatErrorInFormat, 368 "Invalid character '%c' in FORMAT", static_cast<char>(ch)); 369 return 0; 370 } 371 } 372 } 373 374 // Returns the next data edit descriptor 375 template <typename CONTEXT> 376 DataEdit FormatControl<CONTEXT>::GetNextDataEdit( 377 Context &context, int maxRepeat) { 378 int repeat{CueUpNextDataEdit(context)}; 379 auto start{offset_}; 380 DataEdit edit; 381 edit.descriptor = static_cast<char>(Capitalize(GetNextChar(context))); 382 if (edit.descriptor == 'E') { 383 if (auto next{static_cast<char>(Capitalize(PeekNext()))}; 384 next == 'N' || next == 'S' || next == 'X') { 385 edit.variation = next; 386 ++offset_; 387 } 388 } else if (edit.descriptor == 'D' && Capitalize(PeekNext()) == 'T') { 389 // DT'iotype'(v_list) user-defined derived type I/O 390 edit.descriptor = DataEdit::DefinedDerivedType; 391 ++offset_; 392 if (auto quote{static_cast<char>(PeekNext())}; 393 quote == '\'' || quote == '"') { 394 // Capture the quoted 'iotype' 395 bool ok{false}, tooLong{false}; 396 for (++offset_; offset_ < formatLength_;) { 397 auto ch{static_cast<char>(format_[offset_++])}; 398 if (ch == quote && 399 (offset_ == formatLength_ || 400 static_cast<char>(format_[offset_]) != quote)) { 401 ok = true; 402 break; // that was terminating quote 403 } else if (edit.ioTypeChars >= edit.maxIoTypeChars) { 404 tooLong = true; 405 } else { 406 edit.ioType[edit.ioTypeChars++] = ch; 407 if (ch == quote) { 408 ++offset_; 409 } 410 } 411 } 412 if (!ok) { 413 context.SignalError( 414 IostatErrorInFormat, "Unclosed DT'iotype' in FORMAT"); 415 } else if (tooLong) { 416 context.SignalError( 417 IostatErrorInFormat, "Excessive DT'iotype' in FORMAT"); 418 } 419 } 420 if (PeekNext() == '(') { 421 // Capture the v_list arguments 422 bool ok{false}, tooLong{false}; 423 for (++offset_; offset_ < formatLength_;) { 424 int n{GetIntField(context)}; 425 if (edit.vListEntries >= edit.maxVListEntries) { 426 tooLong = true; 427 } else { 428 edit.vList[edit.vListEntries++] = n; 429 } 430 auto ch{static_cast<char>(GetNextChar(context))}; 431 if (ch != ',') { 432 ok = ch == ')'; 433 break; 434 } 435 } 436 if (!ok) { 437 context.SignalError( 438 IostatErrorInFormat, "Unclosed DT(v_list) in FORMAT"); 439 } else if (tooLong) { 440 context.SignalError( 441 IostatErrorInFormat, "Excessive DT(v_list) in FORMAT"); 442 } 443 } 444 } 445 if (edit.descriptor == 'A') { // width is optional for A[w] 446 auto ch{PeekNext()}; 447 if (ch >= '0' && ch <= '9') { 448 edit.width = GetIntField(context); 449 } 450 } else if (edit.descriptor != DataEdit::DefinedDerivedType) { 451 edit.width = GetIntField(context); 452 } 453 if (edit.descriptor != DataEdit::DefinedDerivedType && PeekNext() == '.') { 454 ++offset_; 455 edit.digits = GetIntField(context); 456 CharType ch{PeekNext()}; 457 if (ch == 'e' || ch == 'E' || ch == 'd' || ch == 'D') { 458 ++offset_; 459 edit.expoDigits = GetIntField(context); 460 } 461 } 462 edit.modes = context.mutableModes(); 463 464 // Handle repeated nonparenthesized edit descriptors 465 if (repeat > maxRepeat) { 466 stack_[height_].start = start; // after repeat count 467 stack_[height_].remaining = repeat; // full count 468 ++height_; 469 } 470 edit.repeat = std::min(1, maxRepeat); // 0 if maxRepeat==0 471 if (height_ > 1) { // Subtle: stack_[0].start doesn't necessarily point to '(' 472 int start{stack_[height_ - 1].start}; 473 if (format_[start] != '(') { 474 if (stack_[height_ - 1].remaining > maxRepeat) { 475 edit.repeat = maxRepeat; 476 stack_[height_ - 1].remaining -= maxRepeat; 477 offset_ = start; // repeat same edit descriptor next time 478 } else { 479 edit.repeat = stack_[height_ - 1].remaining; 480 --height_; 481 } 482 } 483 } 484 return edit; 485 } 486 487 template <typename CONTEXT> 488 void FormatControl<CONTEXT>::Finish(Context &context) { 489 CueUpNextDataEdit(context, true /* stop at colon or end of FORMAT */); 490 } 491 } // namespace Fortran::runtime::io 492 #endif // FORTRAN_RUNTIME_FORMAT_IMPLEMENTATION_H_ 493