1 //===-- runtime/edit-output.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 "edit-output.h" 10 #include "utf.h" 11 #include "flang/Common/uint128.h" 12 #include <algorithm> 13 14 namespace Fortran::runtime::io { 15 16 template <int KIND> 17 bool EditIntegerOutput(IoStatementState &io, const DataEdit &edit, 18 common::HostSignedIntType<8 * KIND> n) { 19 char buffer[130], *end{&buffer[sizeof buffer]}, *p{end}; 20 bool isNegative{n < 0}; 21 using Unsigned = common::HostUnsignedIntType<8 * KIND>; 22 Unsigned un{static_cast<Unsigned>(n)}; 23 int signChars{0}; 24 switch (edit.descriptor) { 25 case DataEdit::ListDirected: 26 case 'G': 27 case 'I': 28 if (isNegative) { 29 un = -un; 30 } 31 if (isNegative || (edit.modes.editingFlags & signPlus)) { 32 signChars = 1; // '-' or '+' 33 } 34 while (un > 0) { 35 auto quotient{un / 10u}; 36 *--p = '0' + static_cast<int>(un - Unsigned{10} * quotient); 37 un = quotient; 38 } 39 break; 40 case 'B': 41 for (; un > 0; un >>= 1) { 42 *--p = '0' + (static_cast<int>(un) & 1); 43 } 44 break; 45 case 'O': 46 for (; un > 0; un >>= 3) { 47 *--p = '0' + (static_cast<int>(un) & 7); 48 } 49 break; 50 case 'Z': 51 for (; un > 0; un >>= 4) { 52 int digit = static_cast<int>(un) & 0xf; 53 *--p = digit >= 10 ? 'A' + (digit - 10) : '0' + digit; 54 } 55 break; 56 case 'A': // legacy extension 57 return EditCharacterOutput( 58 io, edit, reinterpret_cast<char *>(&n), sizeof n); 59 default: 60 io.GetIoErrorHandler().SignalError(IostatErrorInFormat, 61 "Data edit descriptor '%c' may not be used with an INTEGER data item", 62 edit.descriptor); 63 return false; 64 } 65 66 int digits = end - p; 67 int leadingZeroes{0}; 68 int editWidth{edit.width.value_or(0)}; 69 if (edit.digits && digits <= *edit.digits) { // Iw.m 70 if (*edit.digits == 0 && n == 0) { 71 // Iw.0 with zero value: output field must be blank. For I0.0 72 // and a zero value, emit one blank character. 73 signChars = 0; // in case of SP 74 editWidth = std::max(1, editWidth); 75 } else { 76 leadingZeroes = *edit.digits - digits; 77 } 78 } else if (n == 0) { 79 leadingZeroes = 1; 80 } 81 int subTotal{signChars + leadingZeroes + digits}; 82 int leadingSpaces{std::max(0, editWidth - subTotal)}; 83 if (editWidth > 0 && leadingSpaces + subTotal > editWidth) { 84 return io.EmitRepeated('*', editWidth); 85 } 86 if (edit.IsListDirected()) { 87 int total{std::max(leadingSpaces, 1) + subTotal}; 88 if (io.GetConnectionState().NeedAdvance(static_cast<std::size_t>(total)) && 89 !io.AdvanceRecord()) { 90 return false; 91 } 92 leadingSpaces = 1; 93 } 94 return io.EmitRepeated(' ', leadingSpaces) && 95 io.Emit(n < 0 ? "-" : "+", signChars) && 96 io.EmitRepeated('0', leadingZeroes) && io.Emit(p, digits); 97 } 98 99 // Formats the exponent (see table 13.1 for all the cases) 100 const char *RealOutputEditingBase::FormatExponent( 101 int expo, const DataEdit &edit, int &length) { 102 char *eEnd{&exponent_[sizeof exponent_]}; 103 char *exponent{eEnd}; 104 for (unsigned e{static_cast<unsigned>(std::abs(expo))}; e > 0;) { 105 unsigned quotient{e / 10u}; 106 *--exponent = '0' + e - 10 * quotient; 107 e = quotient; 108 } 109 if (edit.expoDigits) { 110 if (int ed{*edit.expoDigits}) { // Ew.dEe with e > 0 111 while (exponent > exponent_ + 2 /*E+*/ && exponent + ed > eEnd) { 112 *--exponent = '0'; 113 } 114 } else if (exponent == eEnd) { 115 *--exponent = '0'; // Ew.dE0 with zero-valued exponent 116 } 117 } else { // ensure at least two exponent digits 118 while (exponent + 2 > eEnd) { 119 *--exponent = '0'; 120 } 121 } 122 *--exponent = expo < 0 ? '-' : '+'; 123 if (edit.expoDigits || edit.IsListDirected() || exponent + 3 == eEnd) { 124 *--exponent = edit.descriptor == 'D' ? 'D' : 'E'; // not 'G' 125 } 126 length = eEnd - exponent; 127 return exponent; 128 } 129 130 bool RealOutputEditingBase::EmitPrefix( 131 const DataEdit &edit, std::size_t length, std::size_t width) { 132 if (edit.IsListDirected()) { 133 int prefixLength{edit.descriptor == DataEdit::ListDirectedRealPart ? 2 134 : edit.descriptor == DataEdit::ListDirectedImaginaryPart ? 0 135 : 1}; 136 int suffixLength{edit.descriptor == DataEdit::ListDirectedRealPart || 137 edit.descriptor == DataEdit::ListDirectedImaginaryPart 138 ? 1 139 : 0}; 140 length += prefixLength + suffixLength; 141 ConnectionState &connection{io_.GetConnectionState()}; 142 return (!connection.NeedAdvance(length) || io_.AdvanceRecord()) && 143 io_.Emit(" (", prefixLength); 144 } else if (width > length) { 145 return io_.EmitRepeated(' ', width - length); 146 } else { 147 return true; 148 } 149 } 150 151 bool RealOutputEditingBase::EmitSuffix(const DataEdit &edit) { 152 if (edit.descriptor == DataEdit::ListDirectedRealPart) { 153 return io_.Emit(edit.modes.editingFlags & decimalComma ? ";" : ",", 1); 154 } else if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) { 155 return io_.Emit(")", 1); 156 } else { 157 return true; 158 } 159 } 160 161 template <int binaryPrecision> 162 decimal::ConversionToDecimalResult RealOutputEditing<binaryPrecision>::Convert( 163 int significantDigits, enum decimal::FortranRounding rounding, int flags) { 164 auto converted{decimal::ConvertToDecimal<binaryPrecision>(buffer_, 165 sizeof buffer_, static_cast<enum decimal::DecimalConversionFlags>(flags), 166 significantDigits, rounding, x_)}; 167 if (!converted.str) { // overflow 168 io_.GetIoErrorHandler().Crash( 169 "RealOutputEditing::Convert : buffer size %zd was insufficient", 170 sizeof buffer_); 171 } 172 return converted; 173 } 174 175 // 13.7.2.3.3 in F'2018 176 template <int binaryPrecision> 177 bool RealOutputEditing<binaryPrecision>::EditEorDOutput(const DataEdit &edit) { 178 int editDigits{edit.digits.value_or(0)}; // 'd' field 179 int editWidth{edit.width.value_or(0)}; // 'w' field 180 int significantDigits{editDigits}; 181 int flags{0}; 182 if (edit.modes.editingFlags & signPlus) { 183 flags |= decimal::AlwaysSign; 184 } 185 if (editWidth == 0) { // "the processor selects the field width" 186 if (edit.digits.has_value()) { // E0.d 187 editWidth = editDigits + 6; // -.666E+ee 188 } else { // E0 189 flags |= decimal::Minimize; 190 significantDigits = 191 sizeof buffer_ - 5; // sign, NUL, + 3 extra for EN scaling 192 } 193 } 194 bool isEN{edit.variation == 'N'}; 195 bool isES{edit.variation == 'S'}; 196 int scale{isEN || isES ? 1 : edit.modes.scale}; // 'kP' value 197 int zeroesAfterPoint{0}; 198 if (scale < 0) { 199 if (scale <= -editDigits) { 200 io_.GetIoErrorHandler().SignalError(IostatBadScaleFactor, 201 "Scale factor (kP) %d cannot be less than -d (%d)", scale, 202 -editDigits); 203 return false; 204 } 205 zeroesAfterPoint = -scale; 206 significantDigits = std::max(0, significantDigits - zeroesAfterPoint); 207 } else if (scale > 0) { 208 if (scale >= editDigits + 2) { 209 io_.GetIoErrorHandler().SignalError(IostatBadScaleFactor, 210 "Scale factor (kP) %d cannot be greater than d+2 (%d)", scale, 211 editDigits + 2); 212 return false; 213 } 214 ++significantDigits; 215 scale = std::min(scale, significantDigits + 1); 216 } 217 // In EN editing, multiple attempts may be necessary, so it's in a loop. 218 while (true) { 219 decimal::ConversionToDecimalResult converted{ 220 Convert(significantDigits, edit.modes.round, flags)}; 221 if (IsInfOrNaN(converted)) { 222 return EmitPrefix(edit, converted.length, editWidth) && 223 io_.Emit(converted.str, converted.length) && EmitSuffix(edit); 224 } 225 if (!IsZero()) { 226 converted.decimalExponent -= scale; 227 } 228 if (isEN && scale < 3 && (converted.decimalExponent % 3) != 0) { 229 // EN mode: boost the scale and significant digits, try again; need 230 // an effective exponent field that's a multiple of three. 231 ++scale; 232 ++significantDigits; 233 continue; 234 } 235 // Format the exponent (see table 13.1 for all the cases) 236 int expoLength{0}; 237 const char *exponent{ 238 FormatExponent(converted.decimalExponent, edit, expoLength)}; 239 int signLength{*converted.str == '-' || *converted.str == '+' ? 1 : 0}; 240 int convertedDigits{static_cast<int>(converted.length) - signLength}; 241 int zeroesBeforePoint{std::max(0, scale - convertedDigits)}; 242 int digitsBeforePoint{std::max(0, scale - zeroesBeforePoint)}; 243 int digitsAfterPoint{convertedDigits - digitsBeforePoint}; 244 int trailingZeroes{flags & decimal::Minimize 245 ? 0 246 : std::max(0, 247 significantDigits - (convertedDigits + zeroesBeforePoint))}; 248 int totalLength{signLength + digitsBeforePoint + zeroesBeforePoint + 249 1 /*'.'*/ + zeroesAfterPoint + digitsAfterPoint + trailingZeroes + 250 expoLength}; 251 int width{editWidth > 0 ? editWidth : totalLength}; 252 if (totalLength > width) { 253 return io_.EmitRepeated('*', width); 254 } 255 if (totalLength < width && digitsBeforePoint == 0 && 256 zeroesBeforePoint == 0) { 257 zeroesBeforePoint = 1; 258 ++totalLength; 259 } 260 return EmitPrefix(edit, totalLength, width) && 261 io_.Emit(converted.str, signLength + digitsBeforePoint) && 262 io_.EmitRepeated('0', zeroesBeforePoint) && 263 io_.Emit(edit.modes.editingFlags & decimalComma ? "," : ".", 1) && 264 io_.EmitRepeated('0', zeroesAfterPoint) && 265 io_.Emit( 266 converted.str + signLength + digitsBeforePoint, digitsAfterPoint) && 267 io_.EmitRepeated('0', trailingZeroes) && 268 io_.Emit(exponent, expoLength) && EmitSuffix(edit); 269 } 270 } 271 272 // 13.7.2.3.2 in F'2018 273 template <int binaryPrecision> 274 bool RealOutputEditing<binaryPrecision>::EditFOutput(const DataEdit &edit) { 275 int fracDigits{edit.digits.value_or(0)}; // 'd' field 276 const int editWidth{edit.width.value_or(0)}; // 'w' field 277 enum decimal::FortranRounding rounding{edit.modes.round}; 278 int flags{0}; 279 if (edit.modes.editingFlags & signPlus) { 280 flags |= decimal::AlwaysSign; 281 } 282 if (editWidth == 0) { // "the processor selects the field width" 283 if (!edit.digits.has_value()) { // F0 284 flags |= decimal::Minimize; 285 fracDigits = sizeof buffer_ - 2; // sign & NUL 286 } 287 } 288 // Multiple conversions may be needed to get the right number of 289 // effective rounded fractional digits. 290 int extraDigits{0}; 291 bool canIncrease{true}; 292 while (true) { 293 decimal::ConversionToDecimalResult converted{ 294 Convert(extraDigits + fracDigits, rounding, flags)}; 295 if (IsInfOrNaN(converted)) { 296 return EmitPrefix(edit, converted.length, editWidth) && 297 io_.Emit(converted.str, converted.length) && EmitSuffix(edit); 298 } 299 int expo{converted.decimalExponent + edit.modes.scale /*kP*/}; 300 int signLength{*converted.str == '-' || *converted.str == '+' ? 1 : 0}; 301 int convertedDigits{static_cast<int>(converted.length) - signLength}; 302 if (IsZero()) { // don't treat converted "0" as significant digit 303 expo = 0; 304 convertedDigits = 0; 305 } 306 int trailingOnes{0}; 307 if (expo > extraDigits && extraDigits >= 0 && canIncrease) { 308 extraDigits = expo; 309 if (!edit.digits.has_value()) { // F0 310 fracDigits = sizeof buffer_ - extraDigits - 2; // sign & NUL 311 } 312 canIncrease = false; // only once 313 continue; 314 } else if (expo == -fracDigits && convertedDigits > 0) { 315 if (rounding != decimal::FortranRounding::RoundToZero) { 316 // Convert again without rounding so that we can round here 317 rounding = decimal::FortranRounding::RoundToZero; 318 continue; 319 } else if (converted.str[signLength] >= '5') { 320 // Value rounds up to a scaled 1 (e.g., 0.06 for F5.1 -> 0.1) 321 ++expo; 322 convertedDigits = 0; 323 trailingOnes = 1; 324 } else { 325 // Value rounds down to zero 326 expo = 0; 327 convertedDigits = 0; 328 } 329 } else if (expo < extraDigits && extraDigits > -fracDigits) { 330 extraDigits = std::max(expo, -fracDigits); 331 continue; 332 } 333 int digitsBeforePoint{std::max(0, std::min(expo, convertedDigits))}; 334 int zeroesBeforePoint{std::max(0, expo - digitsBeforePoint)}; 335 int zeroesAfterPoint{std::min(fracDigits, std::max(0, -expo))}; 336 int digitsAfterPoint{convertedDigits - digitsBeforePoint}; 337 int trailingZeroes{flags & decimal::Minimize 338 ? 0 339 : std::max(0, 340 fracDigits - 341 (zeroesAfterPoint + digitsAfterPoint + trailingOnes))}; 342 if (digitsBeforePoint + zeroesBeforePoint + zeroesAfterPoint + 343 digitsAfterPoint + trailingOnes + trailingZeroes == 344 0) { 345 zeroesBeforePoint = 1; // "." -> "0." 346 } 347 int totalLength{signLength + digitsBeforePoint + zeroesBeforePoint + 348 1 /*'.'*/ + zeroesAfterPoint + digitsAfterPoint + trailingOnes + 349 trailingZeroes}; 350 int width{editWidth > 0 ? editWidth : totalLength}; 351 if (totalLength > width) { 352 return io_.EmitRepeated('*', width); 353 } 354 if (totalLength < width && digitsBeforePoint + zeroesBeforePoint == 0) { 355 zeroesBeforePoint = 1; 356 ++totalLength; 357 } 358 return EmitPrefix(edit, totalLength, width) && 359 io_.Emit(converted.str, signLength + digitsBeforePoint) && 360 io_.EmitRepeated('0', zeroesBeforePoint) && 361 io_.Emit(edit.modes.editingFlags & decimalComma ? "," : ".", 1) && 362 io_.EmitRepeated('0', zeroesAfterPoint) && 363 io_.Emit( 364 converted.str + signLength + digitsBeforePoint, digitsAfterPoint) && 365 io_.EmitRepeated('1', trailingOnes) && 366 io_.EmitRepeated('0', trailingZeroes) && 367 io_.EmitRepeated(' ', trailingBlanks_) && EmitSuffix(edit); 368 } 369 } 370 371 // 13.7.5.2.3 in F'2018 372 template <int binaryPrecision> 373 DataEdit RealOutputEditing<binaryPrecision>::EditForGOutput(DataEdit edit) { 374 edit.descriptor = 'E'; 375 int significantDigits{ 376 edit.digits.value_or(BinaryFloatingPoint::decimalPrecision)}; // 'd' 377 if (!edit.width.has_value() || (*edit.width > 0 && significantDigits == 0)) { 378 return edit; // Gw.0 -> Ew.0 for w > 0 379 } 380 int flags{0}; 381 if (edit.modes.editingFlags & signPlus) { 382 flags |= decimal::AlwaysSign; 383 } 384 decimal::ConversionToDecimalResult converted{ 385 Convert(significantDigits, edit.modes.round, flags)}; 386 if (IsInfOrNaN(converted)) { 387 return edit; 388 } 389 int expo{IsZero() ? 1 : converted.decimalExponent}; // 's' 390 if (expo < 0 || expo > significantDigits) { 391 return edit; // Ew.d 392 } 393 edit.descriptor = 'F'; 394 edit.modes.scale = 0; // kP is ignored for G when no exponent field 395 trailingBlanks_ = 0; 396 int editWidth{edit.width.value_or(0)}; 397 if (editWidth > 0) { 398 int expoDigits{edit.expoDigits.value_or(0)}; 399 trailingBlanks_ = expoDigits > 0 ? expoDigits + 2 : 4; // 'n' 400 *edit.width = std::max(0, editWidth - trailingBlanks_); 401 } 402 if (edit.digits.has_value()) { 403 *edit.digits = std::max(0, *edit.digits - expo); 404 } 405 return edit; 406 } 407 408 // 13.10.4 in F'2018 409 template <int binaryPrecision> 410 bool RealOutputEditing<binaryPrecision>::EditListDirectedOutput( 411 const DataEdit &edit) { 412 decimal::ConversionToDecimalResult converted{Convert(1, edit.modes.round)}; 413 if (IsInfOrNaN(converted)) { 414 return EditEorDOutput(edit); 415 } 416 int expo{converted.decimalExponent}; 417 if (expo < 0 || expo > BinaryFloatingPoint::decimalPrecision) { 418 DataEdit copy{edit}; 419 copy.modes.scale = 1; // 1P 420 return EditEorDOutput(copy); 421 } 422 return EditFOutput(edit); 423 } 424 425 // 13.7.5.2.6 in F'2018 426 template <int binaryPrecision> 427 bool RealOutputEditing<binaryPrecision>::EditEXOutput(const DataEdit &) { 428 io_.GetIoErrorHandler().Crash( 429 "not yet implemented: EX output editing"); // TODO 430 } 431 432 template <int KIND> bool RealOutputEditing<KIND>::Edit(const DataEdit &edit) { 433 switch (edit.descriptor) { 434 case 'D': 435 return EditEorDOutput(edit); 436 case 'E': 437 if (edit.variation == 'X') { 438 return EditEXOutput(edit); 439 } else { 440 return EditEorDOutput(edit); 441 } 442 case 'F': 443 return EditFOutput(edit); 444 case 'B': 445 case 'O': 446 case 'Z': 447 return EditIntegerOutput<KIND>(io_, edit, 448 static_cast<common::HostSignedIntType<8 * KIND>>( 449 decimal::BinaryFloatingPointNumber<binaryPrecision>{x_}.raw())); 450 case 'G': 451 return Edit(EditForGOutput(edit)); 452 case 'A': // legacy extension 453 return EditCharacterOutput( 454 io_, edit, reinterpret_cast<char *>(&x_), sizeof x_); 455 default: 456 if (edit.IsListDirected()) { 457 return EditListDirectedOutput(edit); 458 } 459 io_.GetIoErrorHandler().SignalError(IostatErrorInFormat, 460 "Data edit descriptor '%c' may not be used with a REAL data item", 461 edit.descriptor); 462 return false; 463 } 464 return false; 465 } 466 467 bool ListDirectedLogicalOutput(IoStatementState &io, 468 ListDirectedStatementState<Direction::Output> &list, bool truth) { 469 return list.EmitLeadingSpaceOrAdvance(io) && io.Emit(truth ? "T" : "F", 1); 470 } 471 472 bool EditLogicalOutput(IoStatementState &io, const DataEdit &edit, bool truth) { 473 switch (edit.descriptor) { 474 case 'L': 475 case 'G': 476 return io.EmitRepeated(' ', std::max(0, edit.width.value_or(1) - 1)) && 477 io.Emit(truth ? "T" : "F", 1); 478 default: 479 io.GetIoErrorHandler().SignalError(IostatErrorInFormat, 480 "Data edit descriptor '%c' may not be used with a LOGICAL data item", 481 edit.descriptor); 482 return false; 483 } 484 } 485 486 template <typename CHAR> 487 bool ListDirectedCharacterOutput(IoStatementState &io, 488 ListDirectedStatementState<Direction::Output> &list, const CHAR *x, 489 std::size_t length) { 490 bool ok{true}; 491 MutableModes &modes{io.mutableModes()}; 492 ConnectionState &connection{io.GetConnectionState()}; 493 if (modes.delim) { 494 ok = ok && list.EmitLeadingSpaceOrAdvance(io); 495 // Value is delimited with ' or " marks, and interior 496 // instances of that character are doubled. 497 auto EmitOne{[&](CHAR ch) { 498 if (connection.NeedAdvance(1)) { 499 ok = ok && io.AdvanceRecord(); 500 } 501 ok = ok && io.EmitEncoded(&ch, 1); 502 }}; 503 EmitOne(modes.delim); 504 for (std::size_t j{0}; j < length; ++j) { 505 // Doubled delimiters must be put on the same record 506 // in order to be acceptable as list-directed or NAMELIST 507 // input; however, this requirement is not always possible 508 // when the records have a fixed length, as is the case with 509 // internal output. The standard is silent on what should 510 // happen, and no two extant Fortran implementations do 511 // the same thing when tested with this case. 512 // This runtime splits the doubled delimiters across 513 // two records for lack of a better alternative. 514 if (x[j] == static_cast<CHAR>(modes.delim)) { 515 EmitOne(x[j]); 516 } 517 EmitOne(x[j]); 518 } 519 EmitOne(modes.delim); 520 } else { 521 // Undelimited list-directed output 522 ok = ok && list.EmitLeadingSpaceOrAdvance(io, length > 0 ? 1 : 0, true); 523 std::size_t put{0}; 524 std::size_t oneIfUTF8{connection.useUTF8<CHAR>() ? 1 : length}; 525 while (ok && put < length) { 526 if (std::size_t chunk{std::min<std::size_t>( 527 std::min<std::size_t>(length - put, oneIfUTF8), 528 connection.RemainingSpaceInRecord())}) { 529 ok = io.EmitEncoded(x + put, chunk); 530 put += chunk; 531 } else { 532 ok = io.AdvanceRecord() && io.Emit(" ", 1); 533 } 534 } 535 list.set_lastWasUndelimitedCharacter(true); 536 } 537 return ok; 538 } 539 540 template <typename CHAR> 541 bool EditCharacterOutput(IoStatementState &io, const DataEdit &edit, 542 const CHAR *x, std::size_t length) { 543 switch (edit.descriptor) { 544 case 'A': 545 case 'G': 546 break; 547 default: 548 io.GetIoErrorHandler().SignalError(IostatErrorInFormat, 549 "Data edit descriptor '%c' may not be used with a CHARACTER data item", 550 edit.descriptor); 551 return false; 552 } 553 int len{static_cast<int>(length)}; 554 int width{edit.width.value_or(len)}; 555 return io.EmitRepeated(' ', std::max(0, width - len)) && 556 io.EmitEncoded(x, std::min(width, len)); 557 } 558 559 template bool EditIntegerOutput<1>( 560 IoStatementState &, const DataEdit &, std::int8_t); 561 template bool EditIntegerOutput<2>( 562 IoStatementState &, const DataEdit &, std::int16_t); 563 template bool EditIntegerOutput<4>( 564 IoStatementState &, const DataEdit &, std::int32_t); 565 template bool EditIntegerOutput<8>( 566 IoStatementState &, const DataEdit &, std::int64_t); 567 template bool EditIntegerOutput<16>( 568 IoStatementState &, const DataEdit &, common::int128_t); 569 570 template class RealOutputEditing<2>; 571 template class RealOutputEditing<3>; 572 template class RealOutputEditing<4>; 573 template class RealOutputEditing<8>; 574 template class RealOutputEditing<10>; 575 // TODO: double/double 576 template class RealOutputEditing<16>; 577 578 template bool ListDirectedCharacterOutput(IoStatementState &, 579 ListDirectedStatementState<Direction::Output> &, const char *, 580 std::size_t chars); 581 template bool ListDirectedCharacterOutput(IoStatementState &, 582 ListDirectedStatementState<Direction::Output> &, const char16_t *, 583 std::size_t chars); 584 template bool ListDirectedCharacterOutput(IoStatementState &, 585 ListDirectedStatementState<Direction::Output> &, const char32_t *, 586 std::size_t chars); 587 588 template bool EditCharacterOutput( 589 IoStatementState &, const DataEdit &, const char *, std::size_t chars); 590 template bool EditCharacterOutput( 591 IoStatementState &, const DataEdit &, const char16_t *, std::size_t chars); 592 template bool EditCharacterOutput( 593 IoStatementState &, const DataEdit &, const char32_t *, std::size_t chars); 594 595 } // namespace Fortran::runtime::io 596