xref: /llvm-project/flang/unittests/Runtime/Namelist.cpp (revision c91ba04328e1ded6f284469a7828d181324d4e30)
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-consts.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 
23 using namespace Fortran::runtime;
24 using namespace Fortran::runtime::io;
25 
26 struct NamelistTests : CrashHandlerFixture {};
27 
28 static void ClearDescriptorStorage(const Descriptor &descriptor) {
29   std::memset(descriptor.raw().base_addr, 0,
30       descriptor.Elements() * descriptor.ElementBytes());
31 }
32 
33 TEST(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(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(false);
54   logicals.push_back(true);
55   logicals.push_back(false);
56   std::vector<std::complex<float>> complexes;
57   complexes.push_back(std::complex<float>{123.0, -0.5});
58   std::vector<std::string> characters;
59   characters.emplace_back("aBcDeFgHiJkLmNoPqRsTuVwXyZ");
60   characters.emplace_back("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{"group1", items, 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 
129 TEST(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 
164 TEST(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 
192 TEST(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 
220 TEST(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 
249 TEST(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
278 TEST(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 // Tests REAL-looking input to integers
309 TEST(NamelistTests, RealValueForInt) {
310   OwningPtr<Descriptor> scDesc{
311       MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
312           std::vector<int>{}, std::vector<int>{{}})};
313   const NamelistGroup::Item items[]{{"j", *scDesc}};
314   const NamelistGroup group{"nml", 1, items};
315   static char t1[]{"&nml j=123.456/"};
316   StaticDescriptor<1, true> statDesc;
317   Descriptor &internalDesc{statDesc.descriptor()};
318   internalDesc.Establish(TypeCode{CFI_type_char},
319       /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
320   auto inCookie{IONAME(BeginInternalArrayListInput)(
321       internalDesc, nullptr, 0, __FILE__, __LINE__)};
322   ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
323   ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
324       << "namelist real input for integer";
325   char out[16];
326   internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
327       out, 0, nullptr, CFI_attribute_pointer);
328   auto outCookie{IONAME(BeginInternalArrayListOutput)(
329       internalDesc, nullptr, 0, __FILE__, __LINE__)};
330   ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
331   ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
332   std::string got{out, sizeof out};
333   static const std::string expect{" &NML J= 123/   "};
334   EXPECT_EQ(got, expect);
335 }
336 
337 // TODO: Internal NAMELIST error tests
338