1//===-- flang/unittests/Runtime/Namelist.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 "../../runtime/namelist.h"
10#include "CrashHandlerFixture.h"
11#include "tools.h"
12#include "flang/Runtime/descriptor.h"
13#include "flang/Runtime/io-api.h"
14#include <algorithm>
15#include <cinttypes>
16#include <complex>
17#include <cstring>
18#include <gtest/gtest.h>
19#include <limits>
20#include <string>
21#include <vector>
22
23using namespace Fortran::runtime;
24using namespace Fortran::runtime::io;
25
26struct NamelistTests : CrashHandlerFixture {};
27
28static void ClearDescriptorStorage(const Descriptor &descriptor) {
29 std::memset(s: descriptor.raw().base_addr, c: 0,
30 n: descriptor.Elements() * descriptor.ElementBytes());
31}
32
33TEST(NamelistTests, BasicSanity) {
34 static constexpr int numLines{12};
35 static constexpr int lineLength{32};
36 static char buffer[numLines][lineLength];
37 StaticDescriptor<1, true> statDescs[1];
38 Descriptor &internalDesc{statDescs[0].descriptor()};
39 SubscriptValue extent[]{numLines};
40 internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength,
41 &buffer, 1, extent, CFI_attribute_pointer);
42 // Set up data arrays
43 std::vector<int> ints;
44 for (int j{0}; j < 20; ++j) {
45 ints.push_back(x: j % 2 == 0 ? (1 << j) : -(1 << j));
46 }
47 std::vector<double> reals{0.0, -0.0, std::numeric_limits<double>::infinity(),
48 -std::numeric_limits<double>::infinity(),
49 std::numeric_limits<double>::quiet_NaN(),
50 std::numeric_limits<double>::max(), std::numeric_limits<double>::lowest(),
51 std::numeric_limits<double>::epsilon()};
52 std::vector<std::uint8_t> logicals;
53 logicals.push_back(x: false);
54 logicals.push_back(x: true);
55 logicals.push_back(x: false);
56 std::vector<std::complex<float>> complexes;
57 complexes.push_back(x: std::complex<float>{123.0, -0.5});
58 std::vector<std::string> characters;
59 characters.emplace_back(args: "aBcDeFgHiJkLmNoPqRsTuVwXyZ");
60 characters.emplace_back(args: "0123456789'\"..............");
61 // Copy the data into new descriptors
62 OwningPtr<Descriptor> intDesc{
63 MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
64 std::vector<int>{5, 4}, std::move(ints))};
65 OwningPtr<Descriptor> realDesc{
66 MakeArray<TypeCategory::Real, static_cast<int>(sizeof(double))>(
67 std::vector<int>{4, 2}, std::move(reals))};
68 OwningPtr<Descriptor> logicalDesc{
69 MakeArray<TypeCategory::Logical, static_cast<int>(sizeof(std::uint8_t))>(
70 std::vector<int>{3}, std::move(logicals))};
71 OwningPtr<Descriptor> complexDesc{
72 MakeArray<TypeCategory::Complex, static_cast<int>(sizeof(float))>(
73 std::vector<int>{}, std::move(complexes))};
74 OwningPtr<Descriptor> characterDesc{MakeArray<TypeCategory::Character, 1>(
75 std::vector<int>{2}, std::move(characters), characters[0].size())};
76 // Create a NAMELIST group
77 static constexpr int items{5};
78 const NamelistGroup::Item itemArray[items]{{"ints", *intDesc},
79 {"reals", *realDesc}, {"logicals", *logicalDesc},
80 {"complexes", *complexDesc}, {"characters", *characterDesc}};
81 const NamelistGroup group{.groupName: "group1", .items: items, .item: itemArray};
82 // Do an internal NAMELIST write and check results
83 auto outCookie1{IONAME(BeginInternalArrayListOutput)(
84 internalDesc, nullptr, 0, __FILE__, __LINE__)};
85 ASSERT_TRUE(IONAME(SetDelim)(outCookie1, "APOSTROPHE", 10));
86 ASSERT_TRUE(IONAME(OutputNamelist)(outCookie1, group));
87 auto outStatus1{IONAME(EndIoStatement)(outCookie1)};
88 ASSERT_EQ(outStatus1, 0) << "Failed namelist output sanity, status "
89 << static_cast<int>(outStatus1);
90
91 static const std::string expect{" &GROUP1 INTS= 1 -2 4 -8 16 -32 "
92 " 64 -128 256 -512 1024 -2048 "
93 " 4096 -8192 16384 -32768 65536 "
94 " -131072 262144 -524288,REALS= "
95 " 0. -0. Inf -Inf NaN "
96 " 1.7976931348623157E+308 "
97 " -1.7976931348623157E+308 "
98 " 2.220446049250313E-16,LOGICALS="
99 "F T F,COMPLEXES= (123.,-.5), "
100 " CHARACTERS= 'aBcDeFgHiJkLmNoPqR"
101 "sTuVwXyZ' '0123456789''\"........"
102 "......'/ "};
103 std::string got{buffer[0], sizeof buffer};
104 EXPECT_EQ(got, expect);
105
106 // Clear the arrays, read them back, write out again, and compare
107 ClearDescriptorStorage(*intDesc);
108 ClearDescriptorStorage(*realDesc);
109 ClearDescriptorStorage(*logicalDesc);
110 ClearDescriptorStorage(*complexDesc);
111 ClearDescriptorStorage(*characterDesc);
112 auto inCookie{IONAME(BeginInternalArrayListInput)(
113 internalDesc, nullptr, 0, __FILE__, __LINE__)};
114 ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
115 auto inStatus{IONAME(EndIoStatement)(inCookie)};
116 ASSERT_EQ(inStatus, 0) << "Failed namelist input sanity, status "
117 << static_cast<int>(inStatus);
118 auto outCookie2{IONAME(BeginInternalArrayListOutput)(
119 internalDesc, nullptr, 0, __FILE__, __LINE__)};
120 ASSERT_TRUE(IONAME(SetDelim)(outCookie2, "APOSTROPHE", 10));
121 ASSERT_TRUE(IONAME(OutputNamelist)(outCookie2, group));
122 auto outStatus2{IONAME(EndIoStatement)(outCookie2)};
123 ASSERT_EQ(outStatus2, 0) << "Failed namelist output sanity rewrite, status "
124 << static_cast<int>(outStatus2);
125 std::string got2{buffer[0], sizeof buffer};
126 EXPECT_EQ(got2, expect);
127}
128
129TEST(NamelistTests, Subscripts) {
130 // INTEGER :: A(-1:0, -1:1)
131 OwningPtr<Descriptor> aDesc{
132 MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
133 std::vector<int>{2, 3}, std::vector<int>(6, 0))};
134 aDesc->GetDimension(0).SetBounds(-1, 0);
135 aDesc->GetDimension(1).SetBounds(-1, 1);
136 const NamelistGroup::Item items[]{{"a", *aDesc}};
137 const NamelistGroup group{"justa", 1, items};
138 static char t1[]{"&justa A(0,+1:-1:-2)=1 2/"};
139 StaticDescriptor<1, true> statDesc;
140 Descriptor &internalDesc{statDesc.descriptor()};
141 internalDesc.Establish(TypeCode{CFI_type_char},
142 /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
143 auto inCookie{IONAME(BeginInternalArrayListInput)(
144 internalDesc, nullptr, 0, __FILE__, __LINE__)};
145 ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
146 auto inStatus{IONAME(EndIoStatement)(inCookie)};
147 ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status "
148 << static_cast<int>(inStatus);
149 char out[40];
150 internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
151 out, 0, nullptr, CFI_attribute_pointer);
152 auto outCookie{IONAME(BeginInternalArrayListOutput)(
153 internalDesc, nullptr, 0, __FILE__, __LINE__)};
154 ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
155 auto outStatus{IONAME(EndIoStatement)(outCookie)};
156 ASSERT_EQ(outStatus, 0)
157 << "Failed namelist output subscripts rewrite, status "
158 << static_cast<int>(outStatus);
159 std::string got{out, sizeof out};
160 static const std::string expect{" &JUSTA A= 0 2 0 0 0 1/ "};
161 EXPECT_EQ(got, expect);
162}
163
164TEST(NamelistTests, ShortArrayInput) {
165 OwningPtr<Descriptor> aDesc{
166 MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
167 std::vector<int>{2}, std::vector<int>(2, -1))};
168 OwningPtr<Descriptor> bDesc{
169 MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
170 std::vector<int>{2}, std::vector<int>(2, -2))};
171 const NamelistGroup::Item items[]{{"a", *aDesc}, {"b", *bDesc}};
172 const NamelistGroup group{"nl", 2, items};
173 // Two 12-character lines of internal input
174 static char t1[]{"&nl a = 1 b "
175 " = 2 / "};
176 StaticDescriptor<1, true> statDesc;
177 Descriptor &internalDesc{statDesc.descriptor()};
178 SubscriptValue shape{2};
179 internalDesc.Establish(1, 12, t1, 1, &shape, CFI_attribute_pointer);
180 auto inCookie{IONAME(BeginInternalArrayListInput)(
181 internalDesc, nullptr, 0, __FILE__, __LINE__)};
182 ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
183 auto inStatus{IONAME(EndIoStatement)(inCookie)};
184 ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status "
185 << static_cast<int>(inStatus);
186 EXPECT_EQ(*aDesc->ZeroBasedIndexedElement<int>(0), 1);
187 EXPECT_EQ(*aDesc->ZeroBasedIndexedElement<int>(1), -1);
188 EXPECT_EQ(*bDesc->ZeroBasedIndexedElement<int>(0), 2);
189 EXPECT_EQ(*bDesc->ZeroBasedIndexedElement<int>(1), -2);
190}
191
192TEST(NamelistTests, ScalarSubstring) {
193 OwningPtr<Descriptor> scDesc{MakeArray<TypeCategory::Character, 1>(
194 std::vector<int>{}, std::vector<std::string>{"abcdefgh"}, 8)};
195 const NamelistGroup::Item items[]{{"a", *scDesc}};
196 const NamelistGroup group{"justa", 1, items};
197 static char t1[]{"&justa A(2:5)='BCDE'/"};
198 StaticDescriptor<1, true> statDesc;
199 Descriptor &internalDesc{statDesc.descriptor()};
200 internalDesc.Establish(TypeCode{CFI_type_char},
201 /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
202 auto inCookie{IONAME(BeginInternalArrayListInput)(
203 internalDesc, nullptr, 0, __FILE__, __LINE__)};
204 ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
205 ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
206 << "namelist scalar substring input";
207 char out[32];
208 internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
209 out, 0, nullptr, CFI_attribute_pointer);
210 auto outCookie{IONAME(BeginInternalArrayListOutput)(
211 internalDesc, nullptr, 0, __FILE__, __LINE__)};
212 ASSERT_TRUE(IONAME(SetDelim)(outCookie, "apostrophe", 10));
213 ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
214 ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
215 std::string got{out, sizeof out};
216 static const std::string expect{" &JUSTA A= 'aBCDEfgh'/ "};
217 EXPECT_EQ(got, expect);
218}
219
220TEST(NamelistTests, ArraySubstring) {
221 OwningPtr<Descriptor> scDesc{
222 MakeArray<TypeCategory::Character, 1>(std::vector<int>{2},
223 std::vector<std::string>{"abcdefgh", "ijklmnop"}, 8)};
224 const NamelistGroup::Item items[]{{"a", *scDesc}};
225 const NamelistGroup group{"justa", 1, items};
226 static char t1[]{"&justa A(:)(2:+5)='BCDE' 'JKLM'/"};
227 StaticDescriptor<1, true> statDesc;
228 Descriptor &internalDesc{statDesc.descriptor()};
229 internalDesc.Establish(TypeCode{CFI_type_char},
230 /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
231 auto inCookie{IONAME(BeginInternalArrayListInput)(
232 internalDesc, nullptr, 0, __FILE__, __LINE__)};
233 ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
234 ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
235 << "namelist scalar substring input";
236 char out[40];
237 internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
238 out, 0, nullptr, CFI_attribute_pointer);
239 auto outCookie{IONAME(BeginInternalArrayListOutput)(
240 internalDesc, nullptr, 0, __FILE__, __LINE__)};
241 ASSERT_TRUE(IONAME(SetDelim)(outCookie, "apostrophe", 10));
242 ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
243 ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
244 std::string got{out, sizeof out};
245 static const std::string expect{" &JUSTA A= 'aBCDEfgh' 'iJKLMnop'/ "};
246 EXPECT_EQ(got, expect);
247}
248
249TEST(NamelistTests, Skip) {
250 OwningPtr<Descriptor> scDesc{
251 MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
252 std::vector<int>{}, std::vector<int>{-1})};
253 const NamelistGroup::Item items[]{{"j", *scDesc}};
254 const NamelistGroup group{"nml", 1, items};
255 static char t1[]{"&skip a='str''ing'/&nml j=123/"};
256 StaticDescriptor<1, true> statDesc;
257 Descriptor &internalDesc{statDesc.descriptor()};
258 internalDesc.Establish(TypeCode{CFI_type_char},
259 /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
260 auto inCookie{IONAME(BeginInternalArrayListInput)(
261 internalDesc, nullptr, 0, __FILE__, __LINE__)};
262 ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
263 ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
264 << "namelist input with skipping";
265 char out[20];
266 internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
267 out, 0, nullptr, CFI_attribute_pointer);
268 auto outCookie{IONAME(BeginInternalArrayListOutput)(
269 internalDesc, nullptr, 0, __FILE__, __LINE__)};
270 ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
271 ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
272 std::string got{out, sizeof out};
273 static const std::string expect{" &NML J= 123/ "};
274 EXPECT_EQ(got, expect);
275}
276
277// Tests DECIMAL=COMMA mode
278TEST(NamelistTests, Comma) {
279 OwningPtr<Descriptor> scDesc{
280 MakeArray<TypeCategory::Complex, static_cast<int>(sizeof(float))>(
281 std::vector<int>{2}, std::vector<std::complex<float>>{{}, {}})};
282 const NamelistGroup::Item items[]{{"z", *scDesc}};
283 const NamelistGroup group{"nml", 1, items};
284 static char t1[]{"&nml z=(-1,0;2,0);(-3,0;0,5)/"};
285 StaticDescriptor<1, true> statDesc;
286 Descriptor &internalDesc{statDesc.descriptor()};
287 internalDesc.Establish(TypeCode{CFI_type_char},
288 /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
289 auto inCookie{IONAME(BeginInternalArrayListInput)(
290 internalDesc, nullptr, 0, __FILE__, __LINE__)};
291 ASSERT_TRUE(IONAME(SetDecimal)(inCookie, "COMMA", 5));
292 ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
293 ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
294 << "namelist input with skipping";
295 char out[30];
296 internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
297 out, 0, nullptr, CFI_attribute_pointer);
298 auto outCookie{IONAME(BeginInternalArrayListOutput)(
299 internalDesc, nullptr, 0, __FILE__, __LINE__)};
300 ASSERT_TRUE(IONAME(SetDecimal)(outCookie, "COMMA", 5));
301 ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
302 ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
303 std::string got{out, sizeof out};
304 static const std::string expect{" &NML Z= (-1,;2,) (-3,;,5)/ "};
305 EXPECT_EQ(got, expect);
306}
307
308// TODO: Internal NAMELIST error tests
309

source code of flang/unittests/Runtime/Namelist.cpp