Skip to content

Commit 35cabd6

Browse files
authored
[flang] Support fixed-width input field truncation for LOGICAL (#151203)
As a common extension, we support the truncation of fixed-width fields for non-list-directed input editing when a separator character (',' or ';' depending on DECIMAL='POINT' or 'COMMA' resp.) appears in the field. This isn't working for L input editing; fix. (The bug reports a failure with DC mode, but it doesn't work with a comma either.) Fixes #151178.
1 parent 13b2fc1 commit 35cabd6

File tree

7 files changed

+134
-28
lines changed

7 files changed

+134
-28
lines changed

flang-rt/include/flang-rt/runtime/format.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ enum EditingFlags {
3636
};
3737

3838
struct MutableModes {
39+
// Handle DC or DECIMAL='COMMA' and determine the active separator character
40+
constexpr RT_API_ATTRS char32_t GetSeparatorChar() const {
41+
return editingFlags & decimalComma ? char32_t{';'} : char32_t{','};
42+
}
43+
constexpr RT_API_ATTRS char32_t GetRadixPointChar() const {
44+
return editingFlags & decimalComma ? char32_t{','} : char32_t{'.'};
45+
}
46+
3947
std::uint8_t editingFlags{0}; // BN, DP, SS
4048
enum decimal::FortranRounding round{
4149
executionEnvironment

flang-rt/lib/runtime/edit-input.cpp

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,10 @@
1919
namespace Fortran::runtime::io {
2020
RT_OFFLOAD_API_GROUP_BEGIN
2121

22-
// Handle DC or DECIMAL='COMMA' and determine the active separator character
23-
static inline RT_API_ATTRS char32_t GetSeparatorChar(const DataEdit &edit) {
24-
return edit.modes.editingFlags & decimalComma ? char32_t{';'} : char32_t{','};
25-
}
26-
2722
static inline RT_API_ATTRS bool IsCharValueSeparator(
2823
const DataEdit &edit, char32_t ch) {
29-
return ch == ' ' || ch == '\t' || ch == '/' || ch == GetSeparatorChar(edit) ||
24+
return ch == ' ' || ch == '\t' || ch == '/' ||
25+
ch == edit.modes.GetSeparatorChar() ||
3026
(edit.IsNamelist() && (ch == '&' || ch == '$'));
3127
}
3228

@@ -68,7 +64,7 @@ static RT_API_ATTRS bool EditBOZInput(
6864
// Count significant digits after any leading white space & zeroes
6965
int digits{0};
7066
int significantBits{0};
71-
const char32_t comma{GetSeparatorChar(edit)};
67+
char32_t comma{edit.modes.GetSeparatorChar()};
7268
for (; next; next = io.NextInField(remaining, edit)) {
7369
char32_t ch{*next};
7470
if (ch == ' ' || ch == '\t') {
@@ -156,10 +152,6 @@ static RT_API_ATTRS bool EditBOZInput(
156152
return CheckCompleteListDirectedField(io, edit);
157153
}
158154

159-
static inline RT_API_ATTRS char32_t GetRadixPointChar(const DataEdit &edit) {
160-
return edit.modes.editingFlags & decimalComma ? char32_t{','} : char32_t{'.'};
161-
}
162-
163155
// Prepares input from a field, and returns the sign, if any, else '\0'.
164156
static RT_API_ATTRS char ScanNumericPrefix(IoStatementState &io,
165157
const DataEdit &edit, Fortran::common::optional<char32_t> &next,
@@ -221,7 +213,7 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
221213
common::uint128_t value{0};
222214
bool any{!!sign};
223215
bool overflow{false};
224-
const char32_t comma{GetSeparatorChar(edit)};
216+
char32_t comma{edit.modes.GetSeparatorChar()};
225217
static constexpr auto maxu128{~common::uint128_t{0}};
226218
for (; next; next = io.NextInField(remaining, edit, &fastField)) {
227219
char32_t ch{*next};
@@ -238,7 +230,7 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
238230
} else if (ch == comma) {
239231
break; // end non-list-directed field early
240232
} else {
241-
if (edit.modes.inNamelist && ch == GetRadixPointChar(edit)) {
233+
if (edit.modes.inNamelist && ch == edit.modes.GetRadixPointChar()) {
242234
// Ignore any fractional part that might appear in NAMELIST integer
243235
// input, like a few other Fortran compilers do.
244236
// TODO: also process exponents? Some compilers do, but they obviously
@@ -344,7 +336,7 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
344336
}
345337
bool bzMode{(edit.modes.editingFlags & blankZero) != 0};
346338
int exponent{0};
347-
const char32_t comma{GetSeparatorChar(edit)};
339+
char32_t comma{edit.modes.GetSeparatorChar()};
348340
if (!next || (!bzMode && *next == ' ') || *next == comma) {
349341
if (!edit.IsListDirected() && !io.GetConnectionState().IsAtEOF()) {
350342
// An empty/blank field means zero when not list-directed.
@@ -355,7 +347,7 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
355347
}
356348
return {got, exponent, false};
357349
}
358-
char32_t radixPointChar{GetRadixPointChar(edit)};
350+
char32_t radixPointChar{edit.modes.GetRadixPointChar()};
359351
char32_t first{*next >= 'a' && *next <= 'z' ? *next + 'A' - 'a' : *next};
360352
bool isHexadecimal{false};
361353
if (first == 'N' || first == 'I') {
@@ -518,7 +510,7 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
518510
} else if (radixPointOffset) {
519511
exponent += *radixPointOffset;
520512
} else {
521-
// When no redix point (or comma) appears in the value, the 'd'
513+
// When no radix point (or comma) appears in the value, the 'd'
522514
// part of the edit descriptor must be interpreted as the number of
523515
// digits in the value to be interpreted as being to the *right* of
524516
// the assumed radix point (13.7.2.3.2)
@@ -959,10 +951,12 @@ RT_API_ATTRS bool EditLogicalInput(
959951
"Bad character '%lc' in LOGICAL input field", *next);
960952
return false;
961953
}
962-
if (remaining) { // ignore the rest of a fixed-width field
963-
io.HandleRelativePosition(*remaining);
964-
} else if (edit.descriptor == DataEdit::ListDirected) {
965-
while (io.NextInField(remaining, edit)) { // discard rest of field
954+
if (remaining || edit.descriptor == DataEdit::ListDirected) {
955+
// Ignore the rest of the input field; stop after separator when
956+
// not list-directed.
957+
char32_t comma{edit.modes.GetSeparatorChar()};
958+
while (next && *next != comma) {
959+
next = io.NextInField(remaining, edit);
966960
}
967961
}
968962
return CheckCompleteListDirectedField(io, edit);

flang-rt/lib/runtime/io-stmt.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -839,10 +839,7 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
839839
edit.descriptor = DataEdit::ListDirectedNullValue;
840840
return edit;
841841
}
842-
char32_t comma{','};
843-
if (edit.modes.editingFlags & decimalComma) {
844-
comma = ';';
845-
}
842+
const char32_t comma{edit.modes.GetSeparatorChar()};
846843
std::size_t byteCount{0};
847844
if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress
848845
RUNTIME_CHECK(io.GetIoErrorHandler(), repeatPosition_.has_value());

flang-rt/lib/runtime/namelist.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ RT_VAR_GROUP_END
2727
RT_OFFLOAD_API_GROUP_BEGIN
2828

2929
static inline RT_API_ATTRS char32_t GetComma(IoStatementState &io) {
30-
return io.mutableModes().editingFlags & decimalComma ? char32_t{';'}
31-
: char32_t{','};
30+
return io.mutableModes().GetSeparatorChar();
3231
}
3332

3433
bool IODEF(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {

flang-rt/unittests/Runtime/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_flangrt_unittest(RuntimeTests
1919
Derived.cpp
2020
ExternalIOTest.cpp
2121
Format.cpp
22+
InputExtensions.cpp
2223
Inquiry.cpp
2324
ListInputTest.cpp
2425
LogicalFormatTest.cpp
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//===-- unittests/Runtime/InputExtensions.cpp -------------------*- 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+
#include "CrashHandlerFixture.h"
10+
#include "flang-rt/runtime/descriptor.h"
11+
#include "flang/Runtime/io-api.h"
12+
#include <algorithm>
13+
#include <array>
14+
#include <cstring>
15+
#include <gtest/gtest.h>
16+
#include <tuple>
17+
18+
using namespace Fortran::runtime;
19+
using namespace Fortran::runtime::io;
20+
21+
struct InputExtensionTests : CrashHandlerFixture {};
22+
23+
TEST(InputExtensionTests, SeparatorInField_F) {
24+
static const struct {
25+
int get;
26+
const char *format, *data;
27+
double expect[3];
28+
} test[] = {
29+
{2, "(2F6)", "1.25,3.75,", {1.25, 3.75}},
30+
{2, "(2F6)", "1.25 ,3.75 ,", {1.25, 3.75}},
31+
{2, "(DC,2F6)", "1,25;3,75;", {1.25, 3.75}},
32+
{2, "(DC,2F6)", "1,25 ;3,75 ;", {1.25, 3.75}},
33+
};
34+
for (std::size_t j{0}; j < sizeof test / sizeof *test; ++j) {
35+
auto cookie{IONAME(BeginInternalFormattedInput)(test[j].data,
36+
std::strlen(test[j].data), test[j].format,
37+
std::strlen(test[j].format))};
38+
for (int k{0}; k < test[j].get; ++k) {
39+
float got;
40+
IONAME(InputReal32)(cookie, got);
41+
ASSERT_EQ(got, test[j].expect[k])
42+
<< "expected " << test[j].expect[k] << ", got " << got;
43+
}
44+
auto status{IONAME(EndIoStatement)(cookie)};
45+
ASSERT_EQ(status, 0) << "error status " << status << " on F test case "
46+
<< j;
47+
}
48+
}
49+
50+
TEST(InputExtensionTests, SeparatorInField_I) {
51+
static const struct {
52+
int get;
53+
const char *format, *data;
54+
std::int64_t expect[3];
55+
} test[] = {
56+
{2, "(2I4)", "12,34,", {12, 34}},
57+
{2, "(2I4)", "12 ,34 ,", {12, 34}},
58+
{2, "(DC,2I4)", "12;34;", {12, 34}},
59+
{2, "(DC,2I4)", "12 ;34 ;", {12, 34}},
60+
};
61+
for (std::size_t j{0}; j < sizeof test / sizeof *test; ++j) {
62+
auto cookie{IONAME(BeginInternalFormattedInput)(test[j].data,
63+
std::strlen(test[j].data), test[j].format,
64+
std::strlen(test[j].format))};
65+
for (int k{0}; k < test[j].get; ++k) {
66+
std::int64_t got;
67+
IONAME(InputInteger)(cookie, got);
68+
ASSERT_EQ(got, test[j].expect[k])
69+
<< "expected " << test[j].expect[k] << ", got " << got;
70+
}
71+
auto status{IONAME(EndIoStatement)(cookie)};
72+
ASSERT_EQ(status, 0) << "error status " << status << " on I test case "
73+
<< j;
74+
}
75+
}
76+
77+
TEST(InputExtensionTests, SeparatorInField_L) {
78+
static const struct {
79+
int get;
80+
const char *format, *data;
81+
bool expect[3];
82+
} test[] = {
83+
{2, "(2L4)", ".T,F,", {true, false}},
84+
{2, "(2L4)", ".F,T,", {false, true}},
85+
{2, "(2L4)", ".T.,F,", {true, false}},
86+
{2, "(2L4)", ".F.,T,", {false, true}},
87+
{2, "(DC,2L4)", ".T;F,", {true, false}},
88+
{2, "(DC,2L4)", ".F;T,", {false, true}},
89+
{2, "(DC,2L4)", ".T.;F,", {true, false}},
90+
{2, "(DC,2L4)", ".F.;T,", {false, true}},
91+
};
92+
for (std::size_t j{0}; j < sizeof test / sizeof *test; ++j) {
93+
auto cookie{IONAME(BeginInternalFormattedInput)(test[j].data,
94+
std::strlen(test[j].data), test[j].format,
95+
std::strlen(test[j].format))};
96+
for (int k{0}; k < test[j].get; ++k) {
97+
bool got;
98+
IONAME(InputLogical)(cookie, got);
99+
ASSERT_EQ(got, test[j].expect[k])
100+
<< "expected " << test[j].expect[k] << ", got " << got;
101+
}
102+
auto status{IONAME(EndIoStatement)(cookie)};
103+
ASSERT_EQ(status, 0) << "error status " << status << " on L test case "
104+
<< j;
105+
}
106+
}

flang/docs/Extensions.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,9 @@ end
420420
* A `NAMELIST` input group may omit its trailing `/` character if
421421
it is followed by another `NAMELIST` input group.
422422
* A `NAMELIST` input group may begin with either `&` or `$`.
423-
* A comma in a fixed-width numeric input field terminates the
424-
field rather than signaling an invalid character error.
423+
* A comma (or semicolon in `DECIMAL='COMMA'` or `DC` mode) in a
424+
fixed-width numeric input field terminates the field rather than
425+
signaling an invalid character error.
425426
* Arguments to the intrinsic functions `MAX` and `MIN` are converted
426427
when necessary to the type of the result.
427428
An `OPTIONAL`, `POINTER`, or `ALLOCATABLE` argument after

0 commit comments

Comments
 (0)