| //===-- runtime/edit-input.cpp --------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "edit-input.h" |
| #include "namelist.h" |
| #include "utf.h" |
| #include "flang/Common/optional.h" |
| #include "flang/Common/real.h" |
| #include "flang/Common/uint128.h" |
| #include "flang/Runtime/freestanding-tools.h" |
| #include <algorithm> |
| #include <cfenv> |
| |
| namespace Fortran::runtime::io { |
| RT_OFFLOAD_API_GROUP_BEGIN |
| |
| // Checks that a list-directed input value has been entirely consumed and |
| // doesn't contain unparsed characters before the next value separator. |
| static inline RT_API_ATTRS bool IsCharValueSeparator( |
| const DataEdit &edit, char32_t ch) { |
| char32_t comma{ |
| edit.modes.editingFlags & decimalComma ? char32_t{';'} : char32_t{','}}; |
| return ch == ' ' || ch == '\t' || ch == comma || ch == '/' || |
| (edit.IsNamelist() && (ch == '&' || ch == '$')); |
| } |
| |
| static RT_API_ATTRS bool CheckCompleteListDirectedField( |
| IoStatementState &io, const DataEdit &edit) { |
| if (edit.IsListDirected()) { |
| std::size_t byteCount; |
| if (auto ch{io.GetCurrentChar(byteCount)}) { |
| if (IsCharValueSeparator(edit, *ch)) { |
| return true; |
| } else { |
| const auto &connection{io.GetConnectionState()}; |
| io.GetIoErrorHandler().SignalError(IostatBadListDirectedInputSeparator, |
| "invalid character (0x%x) after list-directed input value, " |
| "at column %d in record %d", |
| static_cast<unsigned>(*ch), |
| static_cast<int>(connection.positionInRecord + 1), |
| static_cast<int>(connection.currentRecordNumber)); |
| return false; |
| } |
| } else { |
| return true; // end of record: ok |
| } |
| } else { |
| return true; |
| } |
| } |
| |
| static inline RT_API_ATTRS char32_t GetSeparatorChar(const DataEdit &edit) { |
| return edit.modes.editingFlags & decimalComma ? char32_t{';'} : char32_t{','}; |
| } |
| |
| template <int LOG2_BASE> |
| static RT_API_ATTRS bool EditBOZInput( |
| IoStatementState &io, const DataEdit &edit, void *n, std::size_t bytes) { |
| // Skip leading white space & zeroes |
| Fortran::common::optional<int> remaining{io.CueUpInput(edit)}; |
| auto start{io.GetConnectionState().positionInRecord}; |
| Fortran::common::optional<char32_t> next{io.NextInField(remaining, edit)}; |
| if (next.value_or('?') == '0') { |
| do { |
| start = io.GetConnectionState().positionInRecord; |
| next = io.NextInField(remaining, edit); |
| } while (next && *next == '0'); |
| } |
| // Count significant digits after any leading white space & zeroes |
| int digits{0}; |
| int significantBits{0}; |
| const char32_t comma{GetSeparatorChar(edit)}; |
| for (; next; next = io.NextInField(remaining, edit)) { |
| char32_t ch{*next}; |
| if (ch == ' ' || ch == '\t') { |
| if (edit.modes.editingFlags & blankZero) { |
| ch = '0'; // BZ mode - treat blank as if it were zero |
| } else { |
| continue; |
| } |
| } |
| if (ch >= '0' && ch <= '1') { |
| } else if (LOG2_BASE >= 3 && ch >= '2' && ch <= '7') { |
| } else if (LOG2_BASE >= 4 && ch >= '8' && ch <= '9') { |
| } else if (LOG2_BASE >= 4 && ch >= 'A' && ch <= 'F') { |
| } else if (LOG2_BASE >= 4 && ch >= 'a' && ch <= 'f') { |
| } else if (ch == comma) { |
| break; // end non-list-directed field early |
| } else { |
| io.GetIoErrorHandler().SignalError( |
| "Bad character '%lc' in B/O/Z input field", ch); |
| return false; |
| } |
| if (digits++ == 0) { |
| significantBits = 4; |
| if (ch >= '0' && ch <= '1') { |
| significantBits = 1; |
| } else if (ch >= '2' && ch <= '3') { |
| significantBits = 2; |
| } else if (ch >= '4' && ch <= '7') { |
| significantBits = 3; |
| } else { |
| significantBits = 4; |
| } |
| } else { |
| significantBits += LOG2_BASE; |
| } |
| } |
| auto significantBytes{static_cast<std::size_t>(significantBits + 7) / 8}; |
| if (significantBytes > bytes) { |
| io.GetIoErrorHandler().SignalError(IostatBOZInputOverflow, |
| "B/O/Z input of %d digits overflows %zd-byte variable", digits, bytes); |
| return false; |
| } |
| // Reset to start of significant digits |
| io.HandleAbsolutePosition(start); |
| remaining.reset(); |
| // Make a second pass now that the digit count is known |
| std::memset(n, 0, bytes); |
| int increment{isHostLittleEndian ? -1 : 1}; |
| auto *data{reinterpret_cast<unsigned char *>(n) + |
| (isHostLittleEndian ? significantBytes - 1 : bytes - significantBytes)}; |
| int shift{((digits - 1) * LOG2_BASE) & 7}; |
| while (digits > 0) { |
| char32_t ch{*io.NextInField(remaining, edit)}; |
| int digit{0}; |
| if (ch == ' ' || ch == '\t') { |
| if (edit.modes.editingFlags & blankZero) { |
| ch = '0'; // BZ mode - treat blank as if it were zero |
| } else { |
| continue; |
| } |
| } |
| --digits; |
| if (ch >= '0' && ch <= '9') { |
| digit = ch - '0'; |
| } else if (ch >= 'A' && ch <= 'F') { |
| digit = ch + 10 - 'A'; |
| } else if (ch >= 'a' && ch <= 'f') { |
| digit = ch + 10 - 'a'; |
| } else { |
| continue; |
| } |
| if (shift < 0) { |
| if (shift + LOG2_BASE > 0) { // misaligned octal |
| *data |= digit >> -shift; |
| } |
| shift += 8; |
| data += increment; |
| } |
| *data |= digit << shift; |
| shift -= LOG2_BASE; |
| } |
| return CheckCompleteListDirectedField(io, edit); |
| } |
| |
| static inline RT_API_ATTRS char32_t GetRadixPointChar(const DataEdit &edit) { |
| return edit.modes.editingFlags & decimalComma ? char32_t{','} : char32_t{'.'}; |
| } |
| |
| // Prepares input from a field, and returns the sign, if any, else '\0'. |
| static RT_API_ATTRS char ScanNumericPrefix(IoStatementState &io, |
| const DataEdit &edit, Fortran::common::optional<char32_t> &next, |
| Fortran::common::optional<int> &remaining) { |
| remaining = io.CueUpInput(edit); |
| next = io.NextInField(remaining, edit); |
| char sign{'\0'}; |
| if (next) { |
| if (*next == '-' || *next == '+') { |
| sign = *next; |
| if (!edit.IsListDirected()) { |
| io.SkipSpaces(remaining); |
| } |
| next = io.NextInField(remaining, edit); |
| } |
| } |
| return sign; |
| } |
| |
| RT_API_ATTRS bool EditIntegerInput( |
| IoStatementState &io, const DataEdit &edit, void *n, int kind) { |
| RUNTIME_CHECK(io.GetIoErrorHandler(), kind >= 1 && !(kind & (kind - 1))); |
| switch (edit.descriptor) { |
| case DataEdit::ListDirected: |
| if (IsNamelistNameOrSlash(io)) { |
| return false; |
| } |
| break; |
| case 'G': |
| case 'I': |
| break; |
| case 'B': |
| return EditBOZInput<1>(io, edit, n, kind); |
| case 'O': |
| return EditBOZInput<3>(io, edit, n, kind); |
| case 'Z': |
| return EditBOZInput<4>(io, edit, n, kind); |
| case 'A': // legacy extension |
| return EditCharacterInput(io, edit, reinterpret_cast<char *>(n), kind); |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInFormat, |
| "Data edit descriptor '%c' may not be used with an INTEGER data item", |
| edit.descriptor); |
| return false; |
| } |
| Fortran::common::optional<int> remaining; |
| Fortran::common::optional<char32_t> next; |
| char sign{ScanNumericPrefix(io, edit, next, remaining)}; |
| common::UnsignedInt128 value{0}; |
| bool any{!!sign}; |
| bool overflow{false}; |
| const char32_t comma{GetSeparatorChar(edit)}; |
| for (; next; next = io.NextInField(remaining, edit)) { |
| char32_t ch{*next}; |
| if (ch == ' ' || ch == '\t') { |
| if (edit.modes.editingFlags & blankZero) { |
| ch = '0'; // BZ mode - treat blank as if it were zero |
| } else { |
| continue; |
| } |
| } |
| int digit{0}; |
| if (ch >= '0' && ch <= '9') { |
| digit = ch - '0'; |
| } else if (ch == comma) { |
| break; // end non-list-directed field early |
| } else { |
| if (edit.modes.inNamelist && ch == GetRadixPointChar(edit)) { |
| // Ignore any fractional part that might appear in NAMELIST integer |
| // input, like a few other Fortran compilers do. |
| // TODO: also process exponents? Some compilers do, but they obviously |
| // can't just be ignored. |
| while ((next = io.NextInField(remaining, edit))) { |
| if (*next < '0' || *next > '9') { |
| break; |
| } |
| } |
| if (!next || *next == comma) { |
| break; |
| } |
| } |
| io.GetIoErrorHandler().SignalError( |
| "Bad character '%lc' in INTEGER input field", ch); |
| return false; |
| } |
| static constexpr auto maxu128{~common::UnsignedInt128{0}}; |
| static constexpr auto maxu128OverTen{maxu128 / 10}; |
| static constexpr int maxLastDigit{ |
| static_cast<int>(maxu128 - (maxu128OverTen * 10))}; |
| overflow |= value >= maxu128OverTen && |
| (value > maxu128OverTen || digit > maxLastDigit); |
| value *= 10; |
| value += digit; |
| any = true; |
| } |
| if (!any && !remaining) { |
| io.GetIoErrorHandler().SignalError( |
| "Integer value absent from NAMELIST or list-directed input"); |
| return false; |
| } |
| auto maxForKind{common::UnsignedInt128{1} << ((8 * kind) - 1)}; |
| overflow |= value >= maxForKind && (value > maxForKind || sign != '-'); |
| if (overflow) { |
| io.GetIoErrorHandler().SignalError(IostatIntegerInputOverflow, |
| "Decimal input overflows INTEGER(%d) variable", kind); |
| return false; |
| } |
| if (sign == '-') { |
| value = -value; |
| } |
| if (any || !io.GetIoErrorHandler().InError()) { |
| // The value is stored in the lower order bits on big endian platform. |
| // When memcpy, shift the value to the higher order bit. |
| auto shft{static_cast<int>(sizeof(value.low())) - kind}; |
| // For kind==8 (i.e. shft==0), the value is stored in low_ in big endian. |
| if (!isHostLittleEndian && shft >= 0) { |
| auto l{value.low() << (8 * shft)}; |
| std::memcpy(n, &l, kind); |
| } else { |
| std::memcpy(n, &value, kind); // a blank field means zero |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| // Parses a REAL input number from the input source as a normalized |
| // fraction into a supplied buffer -- there's an optional '-', a |
| // decimal point when the input is not hexadecimal, and at least one |
| // digit. Replaces blanks with zeroes where appropriate. |
| struct ScannedRealInput { |
| // Number of characters that (should) have been written to the |
| // buffer -- this can be larger than the buffer size, which |
| // indicates buffer overflow. Zero indicates an error. |
| int got{0}; |
| int exponent{0}; // adjusted as necessary; binary if isHexadecimal |
| bool isHexadecimal{false}; // 0X... |
| }; |
| static RT_API_ATTRS ScannedRealInput ScanRealInput( |
| char *buffer, int bufferSize, IoStatementState &io, const DataEdit &edit) { |
| Fortran::common::optional<int> remaining; |
| Fortran::common::optional<char32_t> next; |
| int got{0}; |
| Fortran::common::optional<int> radixPointOffset; |
| // The following lambda definition violates the conding style, |
| // but cuda-11.8 nvcc hits an internal error with the brace initialization. |
| auto Put = [&](char ch) -> void { |
| if (got < bufferSize) { |
| buffer[got] = ch; |
| } |
| ++got; |
| }; |
| char sign{ScanNumericPrefix(io, edit, next, remaining)}; |
| if (sign == '-') { |
| Put('-'); |
| } |
| bool bzMode{(edit.modes.editingFlags & blankZero) != 0}; |
| int exponent{0}; |
| if (!next || (!bzMode && *next == ' ') || |
| (!(edit.modes.editingFlags & decimalComma) && *next == ',')) { |
| if (!edit.IsListDirected() && !io.GetConnectionState().IsAtEOF()) { |
| // An empty/blank field means zero when not list-directed. |
| // A fixed-width field containing only a sign is also zero; |
| // this behavior isn't standard-conforming in F'2023 but it is |
| // required to pass FCVS. |
| Put('0'); |
| } |
| return {got, exponent, false}; |
| } |
| char32_t radixPointChar{GetRadixPointChar(edit)}; |
| char32_t first{*next >= 'a' && *next <= 'z' ? *next + 'A' - 'a' : *next}; |
| bool isHexadecimal{false}; |
| if (first == 'N' || first == 'I') { |
| // NaN or infinity - convert to upper case |
| // Subtle: a blank field of digits could be followed by 'E' or 'D', |
| for (; next && |
| ((*next >= 'a' && *next <= 'z') || (*next >= 'A' && *next <= 'Z')); |
| next = io.NextInField(remaining, edit)) { |
| if (*next >= 'a' && *next <= 'z') { |
| Put(*next - 'a' + 'A'); |
| } else { |
| Put(*next); |
| } |
| } |
| if (next && *next == '(') { // NaN(...) |
| Put('('); |
| int depth{1}; |
| while (true) { |
| next = io.NextInField(remaining, edit); |
| if (depth == 0) { |
| break; |
| } else if (!next) { |
| return {}; // error |
| } else if (*next == '(') { |
| ++depth; |
| } else if (*next == ')') { |
| --depth; |
| } |
| Put(*next); |
| } |
| } |
| } else if (first == radixPointChar || (first >= '0' && first <= '9') || |
| (bzMode && (first == ' ' || first == '\t')) || first == 'E' || |
| first == 'D' || first == 'Q') { |
| if (first == '0') { |
| next = io.NextInField(remaining, edit); |
| if (next && (*next == 'x' || *next == 'X')) { // 0X... |
| isHexadecimal = true; |
| next = io.NextInField(remaining, edit); |
| } else { |
| Put('0'); |
| } |
| } |
| // input field is normalized to a fraction |
| if (!isHexadecimal) { |
| Put('.'); |
| } |
| auto start{got}; |
| for (; next; next = io.NextInField(remaining, edit)) { |
| char32_t ch{*next}; |
| if (ch == ' ' || ch == '\t') { |
| if (isHexadecimal) { |
| return {}; // error |
| } else if (bzMode) { |
| ch = '0'; // BZ mode - treat blank as if it were zero |
| } else { |
| continue; // ignore blank in fixed field |
| } |
| } |
| if (ch == '0' && got == start && !radixPointOffset) { |
| // omit leading zeroes before the radix point |
| } else if (ch >= '0' && ch <= '9') { |
| Put(ch); |
| } else if (ch == radixPointChar && !radixPointOffset) { |
| // The radix point character is *not* copied to the buffer. |
| radixPointOffset = got - start; // # of digits before the radix point |
| } else if (isHexadecimal && ch >= 'A' && ch <= 'F') { |
| Put(ch); |
| } else if (isHexadecimal && ch >= 'a' && ch <= 'f') { |
| Put(ch - 'a' + 'A'); // normalize to capitals |
| } else { |
| break; |
| } |
| } |
| if (got == start) { |
| // Nothing but zeroes and maybe a radix point. F'2018 requires |
| // at least one digit, but F'77 did not, and a bare "." shows up in |
| // the FCVS suite. |
| Put('0'); // emit at least one digit |
| } |
| // In list-directed input, a bad exponent is not consumed. |
| auto nextBeforeExponent{next}; |
| auto startExponent{io.GetConnectionState().positionInRecord}; |
| bool hasGoodExponent{false}; |
| if (next) { |
| if (isHexadecimal) { |
| if (*next == 'p' || *next == 'P') { |
| next = io.NextInField(remaining, edit); |
| } else { |
| // The binary exponent is not optional in the standard. |
| return {}; // error |
| } |
| } else if (*next == 'e' || *next == 'E' || *next == 'd' || *next == 'D' || |
| *next == 'q' || *next == 'Q') { |
| // Optional exponent letter. Blanks are allowed between the |
| // optional exponent letter and the exponent value. |
| io.SkipSpaces(remaining); |
| next = io.NextInField(remaining, edit); |
| } |
| } |
| if (next && |
| (*next == '-' || *next == '+' || (*next >= '0' && *next <= '9') || |
| *next == ' ' || *next == '\t')) { |
| bool negExpo{*next == '-'}; |
| if (negExpo || *next == '+') { |
| next = io.NextInField(remaining, edit); |
| } |
| for (; next; next = io.NextInField(remaining, edit)) { |
| if (*next >= '0' && *next <= '9') { |
| hasGoodExponent = true; |
| if (exponent < 10000) { |
| exponent = 10 * exponent + *next - '0'; |
| } |
| } else if (*next == ' ' || *next == '\t') { |
| if (isHexadecimal) { |
| break; |
| } else if (bzMode) { |
| hasGoodExponent = true; |
| exponent = 10 * exponent; |
| } |
| } else { |
| break; |
| } |
| } |
| if (negExpo) { |
| exponent = -exponent; |
| } |
| } |
| if (!hasGoodExponent) { |
| if (isHexadecimal) { |
| return {}; // error |
| } |
| // There isn't a good exponent; do not consume it. |
| next = nextBeforeExponent; |
| io.HandleAbsolutePosition(startExponent); |
| // The default exponent is -kP, but the scale factor doesn't affect |
| // an explicit exponent. |
| exponent = -edit.modes.scale; |
| } |
| // Adjust exponent by number of digits before the radix point. |
| if (isHexadecimal) { |
| // Exponents for hexadecimal input are binary. |
| exponent += radixPointOffset.value_or(got - start) * 4; |
| } else if (radixPointOffset) { |
| exponent += *radixPointOffset; |
| } else { |
| // When no redix point (or comma) appears in the value, the 'd' |
| // part of the edit descriptor must be interpreted as the number of |
| // digits in the value to be interpreted as being to the *right* of |
| // the assumed radix point (13.7.2.3.2) |
| exponent += got - start - edit.digits.value_or(0); |
| } |
| } |
| // Consume the trailing ')' of a list-directed or NAMELIST complex |
| // input value. |
| if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) { |
| if (next && (*next == ' ' || *next == '\t')) { |
| io.SkipSpaces(remaining); |
| next = io.NextInField(remaining, edit); |
| } |
| if (!next) { // NextInField fails on separators like ')' |
| std::size_t byteCount{0}; |
| next = io.GetCurrentChar(byteCount); |
| if (next && *next == ')') { |
| io.HandleRelativePosition(byteCount); |
| } |
| } |
| } else if (remaining) { |
| while (next && (*next == ' ' || *next == '\t')) { |
| next = io.NextInField(remaining, edit); |
| } |
| if (next && (*next != ',' || (edit.modes.editingFlags & decimalComma))) { |
| return {}; // error: unused nonblank character in fixed-width field |
| } |
| } |
| return {got, exponent, isHexadecimal}; |
| } |
| |
| static RT_API_ATTRS void RaiseFPExceptions( |
| decimal::ConversionResultFlags flags) { |
| #undef RAISE |
| #if defined(RT_DEVICE_COMPILATION) |
| Terminator terminator(__FILE__, __LINE__); |
| #define RAISE(e) \ |
| terminator.Crash( \ |
| "not implemented yet: raising FP exception in device code: %s", #e); |
| #else // !defined(RT_DEVICE_COMPILATION) |
| #ifdef feraisexcept // a macro in some environments; omit std:: |
| #define RAISE feraiseexcept |
| #else |
| #define RAISE std::feraiseexcept |
| #endif |
| #endif // !defined(RT_DEVICE_COMPILATION) |
| |
| // Some environment (e.g. emscripten, musl) don't define FE_OVERFLOW as allowed |
| // by c99 (but not c++11) :-/ |
| #if defined(FE_OVERFLOW) || defined(RT_DEVICE_COMPILATION) |
| if (flags & decimal::ConversionResultFlags::Overflow) { |
| RAISE(FE_OVERFLOW); |
| } |
| #endif |
| #if defined(FE_UNDERFLOW) || defined(RT_DEVICE_COMPILATION) |
| if (flags & decimal::ConversionResultFlags::Underflow) { |
| RAISE(FE_UNDERFLOW); |
| } |
| #endif |
| #if defined(FE_INEXACT) || defined(RT_DEVICE_COMPILATION) |
| if (flags & decimal::ConversionResultFlags::Inexact) { |
| RAISE(FE_INEXACT); |
| } |
| #endif |
| #if defined(FE_INVALID) || defined(RT_DEVICE_COMPILATION) |
| if (flags & decimal::ConversionResultFlags::Invalid) { |
| RAISE(FE_INVALID); |
| } |
| #endif |
| #undef RAISE |
| } |
| |
| // If no special modes are in effect and the form of the input value |
| // that's present in the input stream is acceptable to the decimal->binary |
| // converter without modification, this fast path for real input |
| // saves time by avoiding memory copies and reformatting of the exponent. |
| template <int PRECISION> |
| static RT_API_ATTRS bool TryFastPathRealDecimalInput( |
| IoStatementState &io, const DataEdit &edit, void *n) { |
| if (edit.modes.editingFlags & (blankZero | decimalComma)) { |
| return false; |
| } |
| if (edit.modes.scale != 0) { |
| return false; |
| } |
| const ConnectionState &connection{io.GetConnectionState()}; |
| if (connection.internalIoCharKind > 1) { |
| return false; // reading non-default character |
| } |
| const char *str{nullptr}; |
| std::size_t got{io.GetNextInputBytes(str)}; |
| if (got == 0 || str == nullptr || !connection.recordLength.has_value()) { |
| return false; // could not access reliably-terminated input stream |
| } |
| const char *p{str}; |
| std::int64_t maxConsume{ |
| std::min<std::int64_t>(got, edit.width.value_or(got))}; |
| const char *limit{str + maxConsume}; |
| decimal::ConversionToBinaryResult<PRECISION> converted{ |
| decimal::ConvertToBinary<PRECISION>(p, edit.modes.round, limit)}; |
| if (converted.flags & (decimal::Invalid | decimal::Overflow)) { |
| return false; |
| } |
| if (edit.digits.value_or(0) != 0) { |
| // Edit descriptor is Fw.d (or other) with d != 0, which |
| // implies scaling |
| const char *q{str}; |
| for (; q < limit; ++q) { |
| if (*q == '.' || *q == 'n' || *q == 'N') { |
| break; |
| } |
| } |
| if (q == limit) { |
| // No explicit decimal point, and not NaN/Inf. |
| return false; |
| } |
| } |
| if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) { |
| // Need to consume a trailing ')', possibly with leading spaces |
| for (; p < limit && (*p == ' ' || *p == '\t'); ++p) { |
| } |
| if (p < limit && *p == ')') { |
| ++p; |
| } else { |
| return false; |
| } |
| } else if (edit.IsListDirected()) { |
| if (p < limit && !IsCharValueSeparator(edit, *p)) { |
| return false; |
| } |
| } else { |
| for (; p < limit && (*p == ' ' || *p == '\t'); ++p) { |
| } |
| if (edit.width && p < str + *edit.width) { |
| return false; // unconverted characters remain in fixed width field |
| } |
| } |
| // Success on the fast path! |
| *reinterpret_cast<decimal::BinaryFloatingPointNumber<PRECISION> *>(n) = |
| converted.binary; |
| io.HandleRelativePosition(p - str); |
| // Set FP exception flags |
| if (converted.flags != decimal::ConversionResultFlags::Exact) { |
| RaiseFPExceptions(converted.flags); |
| } |
| return true; |
| } |
| |
| template <int binaryPrecision> |
| RT_API_ATTRS decimal::ConversionToBinaryResult<binaryPrecision> |
| ConvertHexadecimal( |
| const char *&p, enum decimal::FortranRounding rounding, int expo) { |
| using RealType = decimal::BinaryFloatingPointNumber<binaryPrecision>; |
| using RawType = typename RealType::RawType; |
| bool isNegative{*p == '-'}; |
| constexpr RawType one{1}; |
| RawType signBit{0}; |
| if (isNegative) { |
| ++p; |
| signBit = one << (RealType::bits - 1); |
| } |
| RawType fraction{0}; |
| // Adjust the incoming binary P+/- exponent to shift the radix point |
| // to below the LSB and add in the bias. |
| expo += binaryPrecision - 1 + RealType::exponentBias; |
| // Input the fraction. |
| int roundingBit{0}; |
| int guardBit{0}; |
| for (; *p; ++p) { |
| fraction <<= 4; |
| expo -= 4; |
| if (*p >= '0' && *p <= '9') { |
| fraction |= *p - '0'; |
| } else if (*p >= 'A' && *p <= 'F') { |
| fraction |= *p - 'A' + 10; // data were normalized to capitals |
| } else { |
| break; |
| } |
| if (fraction >> binaryPrecision) { |
| while (fraction >> binaryPrecision) { |
| guardBit |= roundingBit; |
| roundingBit = (int)fraction & 1; |
| fraction >>= 1; |
| ++expo; |
| } |
| // Consume excess digits |
| while (*++p) { |
| if (*p == '0') { |
| } else if ((*p >= '1' && *p <= '9') || (*p >= 'A' && *p <= 'F')) { |
| guardBit = 1; |
| } else { |
| break; |
| } |
| } |
| break; |
| } |
| } |
| if (fraction) { |
| // Boost biased expo if too small |
| while (expo < 1) { |
| guardBit |= roundingBit; |
| roundingBit = (int)fraction & 1; |
| fraction >>= 1; |
| ++expo; |
| } |
| // Normalize |
| while (expo > 1 && !(fraction >> (binaryPrecision - 1))) { |
| fraction <<= 1; |
| --expo; |
| guardBit = roundingBit = 0; |
| } |
| } |
| // Rounding |
| bool increase{false}; |
| switch (rounding) { |
| case decimal::RoundNearest: // RN & RP |
| increase = roundingBit && (guardBit | ((int)fraction & 1)); |
| break; |
| case decimal::RoundUp: // RU |
| increase = !isNegative && (roundingBit | guardBit); |
| break; |
| case decimal::RoundDown: // RD |
| increase = isNegative && (roundingBit | guardBit); |
| break; |
| case decimal::RoundToZero: // RZ |
| break; |
| case decimal::RoundCompatible: // RC |
| increase = roundingBit != 0; |
| break; |
| } |
| if (increase) { |
| ++fraction; |
| if (fraction >> binaryPrecision) { |
| fraction >>= 1; |
| ++expo; |
| } |
| } |
| // Package & return result |
| constexpr RawType significandMask{(one << RealType::significandBits) - 1}; |
| int flags{(roundingBit | guardBit) ? decimal::Inexact : decimal::Exact}; |
| if (!fraction) { |
| expo = 0; |
| } else if (expo == 1 && !(fraction >> (binaryPrecision - 1))) { |
| expo = 0; // subnormal |
| flags |= decimal::Underflow; |
| } else if (expo >= RealType::maxExponent) { |
| if (rounding == decimal::RoundToZero || |
| (rounding == decimal::RoundDown && !isNegative) || |
| (rounding == decimal::RoundUp && isNegative)) { |
| expo = RealType::maxExponent - 1; // +/-HUGE() |
| fraction = significandMask; |
| } else { |
| expo = RealType::maxExponent; // +/-Inf |
| fraction = 0; |
| flags |= decimal::Overflow; |
| } |
| } else { |
| fraction &= significandMask; // remove explicit normalization unless x87 |
| } |
| return decimal::ConversionToBinaryResult<binaryPrecision>{ |
| RealType{static_cast<RawType>(signBit | |
| static_cast<RawType>(expo) << RealType::significandBits | fraction)}, |
| static_cast<decimal::ConversionResultFlags>(flags)}; |
| } |
| |
| template <int KIND> |
| RT_API_ATTRS bool EditCommonRealInput( |
| IoStatementState &io, const DataEdit &edit, void *n) { |
| constexpr int binaryPrecision{common::PrecisionOfRealKind(KIND)}; |
| if (TryFastPathRealDecimalInput<binaryPrecision>(io, edit, n)) { |
| return CheckCompleteListDirectedField(io, edit); |
| } |
| // Fast path wasn't available or didn't work; go the more general route |
| static constexpr int maxDigits{ |
| common::MaxDecimalConversionDigits(binaryPrecision)}; |
| static constexpr int bufferSize{maxDigits + 18}; |
| char buffer[bufferSize]; |
| auto scanned{ScanRealInput(buffer, maxDigits + 2, io, edit)}; |
| int got{scanned.got}; |
| if (got >= maxDigits + 2) { |
| io.GetIoErrorHandler().Crash("EditCommonRealInput: buffer was too small"); |
| return false; |
| } |
| if (got == 0) { |
| const auto &connection{io.GetConnectionState()}; |
| io.GetIoErrorHandler().SignalError(IostatBadRealInput, |
| "Bad real input data at column %d of record %d", |
| static_cast<int>(connection.positionInRecord + 1), |
| static_cast<int>(connection.currentRecordNumber)); |
| return false; |
| } |
| decimal::ConversionToBinaryResult<binaryPrecision> converted; |
| const char *p{buffer}; |
| if (scanned.isHexadecimal) { |
| buffer[got] = '\0'; |
| converted = ConvertHexadecimal<binaryPrecision>( |
| p, edit.modes.round, scanned.exponent); |
| } else { |
| bool hadExtra{got > maxDigits}; |
| int exponent{scanned.exponent}; |
| if (exponent != 0) { |
| buffer[got++] = 'e'; |
| if (exponent < 0) { |
| buffer[got++] = '-'; |
| exponent = -exponent; |
| } |
| if (exponent > 9999) { |
| exponent = 9999; // will convert to +/-Inf |
| } |
| if (exponent > 999) { |
| int dig{exponent / 1000}; |
| buffer[got++] = '0' + dig; |
| int rest{exponent - 1000 * dig}; |
| dig = rest / 100; |
| buffer[got++] = '0' + dig; |
| rest -= 100 * dig; |
| dig = rest / 10; |
| buffer[got++] = '0' + dig; |
| buffer[got++] = '0' + (rest - 10 * dig); |
| } else if (exponent > 99) { |
| int dig{exponent / 100}; |
| buffer[got++] = '0' + dig; |
| int rest{exponent - 100 * dig}; |
| dig = rest / 10; |
| buffer[got++] = '0' + dig; |
| buffer[got++] = '0' + (rest - 10 * dig); |
| } else if (exponent > 9) { |
| int dig{exponent / 10}; |
| buffer[got++] = '0' + dig; |
| buffer[got++] = '0' + (exponent - 10 * dig); |
| } else { |
| buffer[got++] = '0' + exponent; |
| } |
| } |
| buffer[got] = '\0'; |
| converted = decimal::ConvertToBinary<binaryPrecision>(p, edit.modes.round); |
| if (hadExtra) { |
| converted.flags = static_cast<enum decimal::ConversionResultFlags>( |
| converted.flags | decimal::Inexact); |
| } |
| } |
| if (*p) { // unprocessed junk after value |
| const auto &connection{io.GetConnectionState()}; |
| io.GetIoErrorHandler().SignalError(IostatBadRealInput, |
| "Trailing characters after real input data at column %d of record %d", |
| static_cast<int>(connection.positionInRecord + 1), |
| static_cast<int>(connection.currentRecordNumber)); |
| return false; |
| } |
| *reinterpret_cast<decimal::BinaryFloatingPointNumber<binaryPrecision> *>(n) = |
| converted.binary; |
| // Set FP exception flags |
| if (converted.flags != decimal::ConversionResultFlags::Exact) { |
| if (converted.flags & decimal::ConversionResultFlags::Overflow) { |
| io.GetIoErrorHandler().SignalError(IostatRealInputOverflow); |
| return false; |
| } |
| RaiseFPExceptions(converted.flags); |
| } |
| return CheckCompleteListDirectedField(io, edit); |
| } |
| |
| template <int KIND> |
| RT_API_ATTRS bool EditRealInput( |
| IoStatementState &io, const DataEdit &edit, void *n) { |
| switch (edit.descriptor) { |
| case DataEdit::ListDirected: |
| if (IsNamelistNameOrSlash(io)) { |
| return false; |
| } |
| return EditCommonRealInput<KIND>(io, edit, n); |
| case DataEdit::ListDirectedRealPart: |
| case DataEdit::ListDirectedImaginaryPart: |
| case 'F': |
| case 'E': // incl. EN, ES, & EX |
| case 'D': |
| case 'G': |
| return EditCommonRealInput<KIND>(io, edit, n); |
| case 'B': |
| return EditBOZInput<1>(io, edit, n, |
| common::BitsForBinaryPrecision(common::PrecisionOfRealKind(KIND)) >> 3); |
| case 'O': |
| return EditBOZInput<3>(io, edit, n, |
| common::BitsForBinaryPrecision(common::PrecisionOfRealKind(KIND)) >> 3); |
| case 'Z': |
| return EditBOZInput<4>(io, edit, n, |
| common::BitsForBinaryPrecision(common::PrecisionOfRealKind(KIND)) >> 3); |
| case 'A': // legacy extension |
| return EditCharacterInput(io, edit, reinterpret_cast<char *>(n), KIND); |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInFormat, |
| "Data edit descriptor '%c' may not be used for REAL input", |
| edit.descriptor); |
| return false; |
| } |
| } |
| |
| // 13.7.3 in Fortran 2018 |
| RT_API_ATTRS bool EditLogicalInput( |
| IoStatementState &io, const DataEdit &edit, bool &x) { |
| switch (edit.descriptor) { |
| case DataEdit::ListDirected: |
| if (IsNamelistNameOrSlash(io)) { |
| return false; |
| } |
| break; |
| case 'L': |
| case 'G': |
| break; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInFormat, |
| "Data edit descriptor '%c' may not be used for LOGICAL input", |
| edit.descriptor); |
| return false; |
| } |
| Fortran::common::optional<int> remaining{io.CueUpInput(edit)}; |
| Fortran::common::optional<char32_t> next{io.NextInField(remaining, edit)}; |
| if (next && *next == '.') { // skip optional period |
| next = io.NextInField(remaining, edit); |
| } |
| if (!next) { |
| io.GetIoErrorHandler().SignalError("Empty LOGICAL input field"); |
| return false; |
| } |
| switch (*next) { |
| case 'T': |
| case 't': |
| x = true; |
| break; |
| case 'F': |
| case 'f': |
| x = false; |
| break; |
| default: |
| io.GetIoErrorHandler().SignalError( |
| "Bad character '%lc' in LOGICAL input field", *next); |
| return false; |
| } |
| if (remaining) { // ignore the rest of a fixed-width field |
| io.HandleRelativePosition(*remaining); |
| } else if (edit.descriptor == DataEdit::ListDirected) { |
| while (io.NextInField(remaining, edit)) { // discard rest of field |
| } |
| } |
| return CheckCompleteListDirectedField(io, edit); |
| } |
| |
| // See 13.10.3.1 paragraphs 7-9 in Fortran 2018 |
| template <typename CHAR> |
| static RT_API_ATTRS bool EditDelimitedCharacterInput( |
| IoStatementState &io, CHAR *x, std::size_t length, char32_t delimiter) { |
| bool result{true}; |
| while (true) { |
| std::size_t byteCount{0}; |
| auto ch{io.GetCurrentChar(byteCount)}; |
| if (!ch) { |
| if (io.AdvanceRecord()) { |
| continue; |
| } else { |
| result = false; // EOF in character value |
| break; |
| } |
| } |
| io.HandleRelativePosition(byteCount); |
| if (*ch == delimiter) { |
| auto next{io.GetCurrentChar(byteCount)}; |
| if (next && *next == delimiter) { |
| // Repeated delimiter: use as character value |
| io.HandleRelativePosition(byteCount); |
| } else { |
| break; // closing delimiter |
| } |
| } |
| if (length > 0) { |
| *x++ = *ch; |
| --length; |
| } |
| } |
| Fortran::runtime::fill_n(x, length, ' '); |
| return result; |
| } |
| |
| template <typename CHAR> |
| static RT_API_ATTRS bool EditListDirectedCharacterInput( |
| IoStatementState &io, CHAR *x, std::size_t length, const DataEdit &edit) { |
| std::size_t byteCount{0}; |
| auto ch{io.GetCurrentChar(byteCount)}; |
| if (ch && (*ch == '\'' || *ch == '"')) { |
| io.HandleRelativePosition(byteCount); |
| return EditDelimitedCharacterInput(io, x, length, *ch); |
| } |
| if (IsNamelistNameOrSlash(io) || io.GetConnectionState().IsAtEOF()) { |
| return false; |
| } |
| // Undelimited list-directed character input: stop at a value separator |
| // or the end of the current record. Subtlety: the "remaining" count |
| // here is a dummy that's used to avoid the interpretation of separators |
| // in NextInField. |
| Fortran::common::optional<int> remaining{length > 0 ? maxUTF8Bytes : 0}; |
| while (Fortran::common::optional<char32_t> next{ |
| io.NextInField(remaining, edit)}) { |
| bool isSep{false}; |
| switch (*next) { |
| case ' ': |
| case '\t': |
| case '/': |
| isSep = true; |
| break; |
| case '&': |
| case '$': |
| isSep = edit.IsNamelist(); |
| break; |
| case ',': |
| isSep = !(edit.modes.editingFlags & decimalComma); |
| break; |
| case ';': |
| isSep = !!(edit.modes.editingFlags & decimalComma); |
| break; |
| default: |
| break; |
| } |
| if (isSep) { |
| remaining = 0; |
| } else { |
| *x++ = *next; |
| remaining = --length > 0 ? maxUTF8Bytes : 0; |
| } |
| } |
| Fortran::runtime::fill_n(x, length, ' '); |
| return true; |
| } |
| |
| template <typename CHAR> |
| RT_API_ATTRS bool EditCharacterInput(IoStatementState &io, const DataEdit &edit, |
| CHAR *x, std::size_t lengthChars) { |
| switch (edit.descriptor) { |
| case DataEdit::ListDirected: |
| return EditListDirectedCharacterInput(io, x, lengthChars, edit); |
| case 'A': |
| case 'G': |
| break; |
| case 'B': |
| return EditBOZInput<1>(io, edit, x, lengthChars * sizeof *x); |
| case 'O': |
| return EditBOZInput<3>(io, edit, x, lengthChars * sizeof *x); |
| case 'Z': |
| return EditBOZInput<4>(io, edit, x, lengthChars * sizeof *x); |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInFormat, |
| "Data edit descriptor '%c' may not be used with a CHARACTER data item", |
| edit.descriptor); |
| return false; |
| } |
| const ConnectionState &connection{io.GetConnectionState()}; |
| std::size_t remainingChars{lengthChars}; |
| // Skip leading characters. |
| // Their bytes don't count towards INQUIRE(IOLENGTH=). |
| std::size_t skipChars{0}; |
| if (edit.width && *edit.width > 0) { |
| remainingChars = *edit.width; |
| if (remainingChars > lengthChars) { |
| skipChars = remainingChars - lengthChars; |
| } |
| } |
| // When the field is wider than the variable, we drop the leading |
| // characters. When the variable is wider than the field, there can be |
| // trailing padding or an EOR condition. |
| const char *input{nullptr}; |
| std::size_t readyBytes{0}; |
| // Transfer payload bytes; these do count. |
| while (remainingChars > 0) { |
| if (readyBytes == 0) { |
| readyBytes = io.GetNextInputBytes(input); |
| if (readyBytes == 0 || |
| (readyBytes < remainingChars && edit.modes.nonAdvancing)) { |
| if (io.CheckForEndOfRecord(readyBytes)) { |
| if (readyBytes == 0) { |
| // PAD='YES' and no more data |
| Fortran::runtime::fill_n(x, lengthChars, ' '); |
| return !io.GetIoErrorHandler().InError(); |
| } else { |
| // Do partial read(s) then pad on last iteration |
| } |
| } else { |
| return !io.GetIoErrorHandler().InError(); |
| } |
| } |
| } |
| std::size_t chunkBytes; |
| std::size_t chunkChars{1}; |
| bool skipping{skipChars > 0}; |
| if (connection.isUTF8) { |
| chunkBytes = MeasureUTF8Bytes(*input); |
| if (skipping) { |
| --skipChars; |
| } else if (auto ucs{DecodeUTF8(input)}) { |
| if ((sizeof *x == 1 && *ucs > 0xff) || |
| (sizeof *x == 2 && *ucs > 0xffff)) { |
| *x++ = '?'; |
| } else { |
| *x++ = *ucs; |
| } |
| --lengthChars; |
| } else if (chunkBytes == 0) { |
| // error recovery: skip bad encoding |
| chunkBytes = 1; |
| } |
| } else if (connection.internalIoCharKind > 1) { |
| // Reading from non-default character internal unit |
| chunkBytes = connection.internalIoCharKind; |
| if (skipping) { |
| --skipChars; |
| } else { |
| char32_t buffer{0}; |
| std::memcpy(&buffer, input, chunkBytes); |
| if ((sizeof *x == 1 && buffer > 0xff) || |
| (sizeof *x == 2 && buffer > 0xffff)) { |
| *x++ = '?'; |
| } else { |
| *x++ = buffer; |
| } |
| --lengthChars; |
| } |
| } else if constexpr (sizeof *x > 1) { |
| // Read single byte with expansion into multi-byte CHARACTER |
| chunkBytes = 1; |
| if (skipping) { |
| --skipChars; |
| } else { |
| *x++ = static_cast<unsigned char>(*input); |
| --lengthChars; |
| } |
| } else { // single bytes -> default CHARACTER |
| if (skipping) { |
| chunkBytes = std::min<std::size_t>(skipChars, readyBytes); |
| chunkChars = chunkBytes; |
| skipChars -= chunkChars; |
| } else { |
| chunkBytes = std::min<std::size_t>(remainingChars, readyBytes); |
| chunkBytes = std::min<std::size_t>(lengthChars, chunkBytes); |
| chunkChars = chunkBytes; |
| std::memcpy(x, input, chunkBytes); |
| x += chunkBytes; |
| lengthChars -= chunkChars; |
| } |
| } |
| input += chunkBytes; |
| remainingChars -= chunkChars; |
| if (!skipping) { |
| io.GotChar(chunkBytes); |
| } |
| io.HandleRelativePosition(chunkBytes); |
| readyBytes -= chunkBytes; |
| } |
| // Pad the remainder of the input variable, if any. |
| Fortran::runtime::fill_n(x, lengthChars, ' '); |
| return CheckCompleteListDirectedField(io, edit); |
| } |
| |
| template RT_API_ATTRS bool EditRealInput<2>( |
| IoStatementState &, const DataEdit &, void *); |
| template RT_API_ATTRS bool EditRealInput<3>( |
| IoStatementState &, const DataEdit &, void *); |
| template RT_API_ATTRS bool EditRealInput<4>( |
| IoStatementState &, const DataEdit &, void *); |
| template RT_API_ATTRS bool EditRealInput<8>( |
| IoStatementState &, const DataEdit &, void *); |
| template RT_API_ATTRS bool EditRealInput<10>( |
| IoStatementState &, const DataEdit &, void *); |
| // TODO: double/double |
| template RT_API_ATTRS bool EditRealInput<16>( |
| IoStatementState &, const DataEdit &, void *); |
| |
| template RT_API_ATTRS bool EditCharacterInput( |
| IoStatementState &, const DataEdit &, char *, std::size_t); |
| template RT_API_ATTRS bool EditCharacterInput( |
| IoStatementState &, const DataEdit &, char16_t *, std::size_t); |
| template RT_API_ATTRS bool EditCharacterInput( |
| IoStatementState &, const DataEdit &, char32_t *, std::size_t); |
| |
| RT_OFFLOAD_API_GROUP_END |
| } // namespace Fortran::runtime::io |