From cccbaf01be9c26dfdaa59e50d8c101216483f043 Mon Sep 17 00:00:00 2001 From: Peter Klausler Date: Thu, 17 Jul 2025 12:11:35 -0700 Subject: [PATCH] [flang][runtime] Handle spaces before ')' in alternative list-directed complex input List-directed reads of complex values that can't go through the usual fast path (as in this bug's test case, which uses DECIMAL='COMMA') didn't skip spaces before the closing right parenthesis correctly. Fixes https://github.com/llvm/llvm-project/issues/149164. --- flang-rt/lib/runtime/edit-input.cpp | 41 +++++-------------- .../unittests/Runtime/NumericalFormatTest.cpp | 31 ++++++++++++++ 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/flang-rt/lib/runtime/edit-input.cpp b/flang-rt/lib/runtime/edit-input.cpp index 0cc287aa3b47e..13557678f6057 100644 --- a/flang-rt/lib/runtime/edit-input.cpp +++ b/flang-rt/lib/runtime/edit-input.cpp @@ -19,16 +19,19 @@ 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. +// Handle DC or DECIMAL='COMMA' and determine the active separator character +static inline RT_API_ATTRS char32_t GetSeparatorChar(const DataEdit &edit) { + return edit.modes.editingFlags & decimalComma ? char32_t{';'} : char32_t{','}; +} + 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 == '/' || + return ch == ' ' || ch == '\t' || ch == '/' || ch == GetSeparatorChar(edit) || (edit.IsNamelist() && (ch == '&' || ch == '$')); } +// Checks that a list-directed input value has been entirely consumed and +// doesn't contain unparsed characters before the next value separator. static RT_API_ATTRS bool CheckCompleteListDirectedField( IoStatementState &io, const DataEdit &edit) { if (edit.IsListDirected()) { @@ -54,10 +57,6 @@ static RT_API_ATTRS bool CheckCompleteListDirectedField( } } -static inline RT_API_ATTRS char32_t GetSeparatorChar(const DataEdit &edit) { - return edit.modes.editingFlags & decimalComma ? char32_t{';'} : char32_t{','}; -} - template static RT_API_ATTRS bool EditBOZInput( IoStatementState &io, const DataEdit &edit, void *n, std::size_t bytes) { @@ -518,7 +517,7 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput( // Consume the trailing ')' of a list-directed or NAMELIST complex // input value. if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) { - if (next && (*next == ' ' || *next == '\t')) { + if (!next || *next == ' ' || *next == '\t') { io.SkipSpaces(remaining); next = io.NextInField(remaining, edit); } @@ -1006,27 +1005,7 @@ static RT_API_ATTRS bool EditListDirectedCharacterInput( // Undelimited list-directed character input: stop at a value separator // or the end of the current record. while (auto ch{io.GetCurrentChar(byteCount)}) { - bool isSep{false}; - switch (*ch) { - 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) { + if (IsCharValueSeparator(edit, *ch)) { break; } if (length > 0) { diff --git a/flang-rt/unittests/Runtime/NumericalFormatTest.cpp b/flang-rt/unittests/Runtime/NumericalFormatTest.cpp index f1492d0e39fec..73245dca13bc0 100644 --- a/flang-rt/unittests/Runtime/NumericalFormatTest.cpp +++ b/flang-rt/unittests/Runtime/NumericalFormatTest.cpp @@ -213,6 +213,37 @@ TEST(IOApiTests, ListInputTest) { << "', but got '" << output << "'"; } +TEST(IOApiTests, ListInputComplexRegressionTest) { + static const char input[]{"(1,;2, );(3,;4,)"}; + auto cookie{IONAME(BeginInternalListInput)(input, sizeof input - 1)}; + static constexpr int numRealValues{4}; + float z[numRealValues]; + ASSERT_TRUE(IONAME(SetDecimal)(cookie, "COMMA", 5)); + for (int j{0}; j < numRealValues; j += 2) { + ASSERT_TRUE(IONAME(InputComplex32)(cookie, &z[j])) + << "InputComplex32 failed with value " << z[j]; + } + auto status{IONAME(EndIoStatement)(cookie)}; + ASSERT_EQ(status, 0) << "Failed complex list-directed input, status " + << static_cast(status); + static constexpr int bufferSize{18}; + char output[bufferSize]; + output[bufferSize - 1] = '\0'; + cookie = IONAME(BeginInternalListOutput)(output, bufferSize - 1); + for (int j{0}; j < numRealValues; j += 2) { + ASSERT_TRUE(IONAME(OutputComplex32)(cookie, z[j], z[j + 1])) + << "OutputComplex32 failed when outputting value " << z[j] << ", " + << z[j + 1]; + } + status = IONAME(EndIoStatement)(cookie); + ASSERT_EQ(status, 0) << "Failed complex list-directed output, status " + << static_cast(status); + static const char expect[bufferSize]{" (1.,2.) (3.,4.) "}; + ASSERT_EQ(std::strncmp(output, expect, bufferSize), 0) + << "Failed complex list-directed output, expected '" << expect + << "', but got '" << output << "'"; +} + TEST(IOApiTests, DescriptorOutputTest) { static constexpr int bufferSize{10}; char buffer[bufferSize];