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