Peter Klausler f08b55d1a6
[flang][runtime] Emit leading spaces in NAMELIST output (#76846)
As NAMELIST output is a variant of list-directed output, its editing
must produce leading spaces on (most) output records to effect carriage
control. These spaces are required by the language standard and
implemented by nearly all other Fortran compilers (except GNU).

Fixes https://github.com/llvm/llvm-project/issues/76798.
2024-01-15 10:02:52 -08:00

309 lines
14 KiB
C++

//===-- flang/unittests/Runtime/Namelist.cpp --------------------*- C++ -*-===//
//
// 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 "../../runtime/namelist.h"
#include "CrashHandlerFixture.h"
#include "tools.h"
#include "flang/Runtime/descriptor.h"
#include "flang/Runtime/io-api.h"
#include <algorithm>
#include <cinttypes>
#include <complex>
#include <cstring>
#include <gtest/gtest.h>
#include <limits>
#include <string>
#include <vector>
using namespace Fortran::runtime;
using namespace Fortran::runtime::io;
struct NamelistTests : CrashHandlerFixture {};
static void ClearDescriptorStorage(const Descriptor &descriptor) {
std::memset(descriptor.raw().base_addr, 0,
descriptor.Elements() * descriptor.ElementBytes());
}
TEST(NamelistTests, BasicSanity) {
static constexpr int numLines{12};
static constexpr int lineLength{32};
static char buffer[numLines][lineLength];
StaticDescriptor<1, true> statDescs[1];
Descriptor &internalDesc{statDescs[0].descriptor()};
SubscriptValue extent[]{numLines};
internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength,
&buffer, 1, extent, CFI_attribute_pointer);
// Set up data arrays
std::vector<int> ints;
for (int j{0}; j < 20; ++j) {
ints.push_back(j % 2 == 0 ? (1 << j) : -(1 << j));
}
std::vector<double> reals{0.0, -0.0, std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::max(), std::numeric_limits<double>::lowest(),
std::numeric_limits<double>::epsilon()};
std::vector<std::uint8_t> logicals;
logicals.push_back(false);
logicals.push_back(true);
logicals.push_back(false);
std::vector<std::complex<float>> complexes;
complexes.push_back(std::complex<float>{123.0, -0.5});
std::vector<std::string> characters;
characters.emplace_back("aBcDeFgHiJkLmNoPqRsTuVwXyZ");
characters.emplace_back("0123456789'\"..............");
// Copy the data into new descriptors
OwningPtr<Descriptor> intDesc{
MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
std::vector<int>{5, 4}, std::move(ints))};
OwningPtr<Descriptor> realDesc{
MakeArray<TypeCategory::Real, static_cast<int>(sizeof(double))>(
std::vector<int>{4, 2}, std::move(reals))};
OwningPtr<Descriptor> logicalDesc{
MakeArray<TypeCategory::Logical, static_cast<int>(sizeof(std::uint8_t))>(
std::vector<int>{3}, std::move(logicals))};
OwningPtr<Descriptor> complexDesc{
MakeArray<TypeCategory::Complex, static_cast<int>(sizeof(float))>(
std::vector<int>{}, std::move(complexes))};
OwningPtr<Descriptor> characterDesc{MakeArray<TypeCategory::Character, 1>(
std::vector<int>{2}, std::move(characters), characters[0].size())};
// Create a NAMELIST group
static constexpr int items{5};
const NamelistGroup::Item itemArray[items]{{"ints", *intDesc},
{"reals", *realDesc}, {"logicals", *logicalDesc},
{"complexes", *complexDesc}, {"characters", *characterDesc}};
const NamelistGroup group{"group1", items, itemArray};
// Do an internal NAMELIST write and check results
auto outCookie1{IONAME(BeginInternalArrayListOutput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(SetDelim)(outCookie1, "APOSTROPHE", 10));
ASSERT_TRUE(IONAME(OutputNamelist)(outCookie1, group));
auto outStatus1{IONAME(EndIoStatement)(outCookie1)};
ASSERT_EQ(outStatus1, 0) << "Failed namelist output sanity, status "
<< static_cast<int>(outStatus1);
static const std::string expect{" &GROUP1 INTS= 1 -2 4 -8 16 -32 "
" 64 -128 256 -512 1024 -2048 "
" 4096 -8192 16384 -32768 65536 "
" -131072 262144 -524288,REALS= "
" 0. -0. Inf -Inf NaN "
" 1.7976931348623157E+308 "
" -1.7976931348623157E+308 "
" 2.220446049250313E-16,LOGICALS="
"F T F,COMPLEXES= (123.,-.5), "
" CHARACTERS= 'aBcDeFgHiJkLmNoPqR"
"sTuVwXyZ' '0123456789''\"........"
"......'/ "};
std::string got{buffer[0], sizeof buffer};
EXPECT_EQ(got, expect);
// Clear the arrays, read them back, write out again, and compare
ClearDescriptorStorage(*intDesc);
ClearDescriptorStorage(*realDesc);
ClearDescriptorStorage(*logicalDesc);
ClearDescriptorStorage(*complexDesc);
ClearDescriptorStorage(*characterDesc);
auto inCookie{IONAME(BeginInternalArrayListInput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
auto inStatus{IONAME(EndIoStatement)(inCookie)};
ASSERT_EQ(inStatus, 0) << "Failed namelist input sanity, status "
<< static_cast<int>(inStatus);
auto outCookie2{IONAME(BeginInternalArrayListOutput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(SetDelim)(outCookie2, "APOSTROPHE", 10));
ASSERT_TRUE(IONAME(OutputNamelist)(outCookie2, group));
auto outStatus2{IONAME(EndIoStatement)(outCookie2)};
ASSERT_EQ(outStatus2, 0) << "Failed namelist output sanity rewrite, status "
<< static_cast<int>(outStatus2);
std::string got2{buffer[0], sizeof buffer};
EXPECT_EQ(got2, expect);
}
TEST(NamelistTests, Subscripts) {
// INTEGER :: A(-1:0, -1:1)
OwningPtr<Descriptor> aDesc{
MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
std::vector<int>{2, 3}, std::vector<int>(6, 0))};
aDesc->GetDimension(0).SetBounds(-1, 0);
aDesc->GetDimension(1).SetBounds(-1, 1);
const NamelistGroup::Item items[]{{"a", *aDesc}};
const NamelistGroup group{"justa", 1, items};
static char t1[]{"&justa A(0,+1:-1:-2)=1 2/"};
StaticDescriptor<1, true> statDesc;
Descriptor &internalDesc{statDesc.descriptor()};
internalDesc.Establish(TypeCode{CFI_type_char},
/*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
auto inCookie{IONAME(BeginInternalArrayListInput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
auto inStatus{IONAME(EndIoStatement)(inCookie)};
ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status "
<< static_cast<int>(inStatus);
char out[40];
internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
out, 0, nullptr, CFI_attribute_pointer);
auto outCookie{IONAME(BeginInternalArrayListOutput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
auto outStatus{IONAME(EndIoStatement)(outCookie)};
ASSERT_EQ(outStatus, 0)
<< "Failed namelist output subscripts rewrite, status "
<< static_cast<int>(outStatus);
std::string got{out, sizeof out};
static const std::string expect{" &JUSTA A= 0 2 0 0 0 1/ "};
EXPECT_EQ(got, expect);
}
TEST(NamelistTests, ShortArrayInput) {
OwningPtr<Descriptor> aDesc{
MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
std::vector<int>{2}, std::vector<int>(2, -1))};
OwningPtr<Descriptor> bDesc{
MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
std::vector<int>{2}, std::vector<int>(2, -2))};
const NamelistGroup::Item items[]{{"a", *aDesc}, {"b", *bDesc}};
const NamelistGroup group{"nl", 2, items};
// Two 12-character lines of internal input
static char t1[]{"&nl a = 1 b "
" = 2 / "};
StaticDescriptor<1, true> statDesc;
Descriptor &internalDesc{statDesc.descriptor()};
SubscriptValue shape{2};
internalDesc.Establish(1, 12, t1, 1, &shape, CFI_attribute_pointer);
auto inCookie{IONAME(BeginInternalArrayListInput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
auto inStatus{IONAME(EndIoStatement)(inCookie)};
ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status "
<< static_cast<int>(inStatus);
EXPECT_EQ(*aDesc->ZeroBasedIndexedElement<int>(0), 1);
EXPECT_EQ(*aDesc->ZeroBasedIndexedElement<int>(1), -1);
EXPECT_EQ(*bDesc->ZeroBasedIndexedElement<int>(0), 2);
EXPECT_EQ(*bDesc->ZeroBasedIndexedElement<int>(1), -2);
}
TEST(NamelistTests, ScalarSubstring) {
OwningPtr<Descriptor> scDesc{MakeArray<TypeCategory::Character, 1>(
std::vector<int>{}, std::vector<std::string>{"abcdefgh"}, 8)};
const NamelistGroup::Item items[]{{"a", *scDesc}};
const NamelistGroup group{"justa", 1, items};
static char t1[]{"&justa A(2:5)='BCDE'/"};
StaticDescriptor<1, true> statDesc;
Descriptor &internalDesc{statDesc.descriptor()};
internalDesc.Establish(TypeCode{CFI_type_char},
/*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
auto inCookie{IONAME(BeginInternalArrayListInput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
<< "namelist scalar substring input";
char out[32];
internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
out, 0, nullptr, CFI_attribute_pointer);
auto outCookie{IONAME(BeginInternalArrayListOutput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(SetDelim)(outCookie, "apostrophe", 10));
ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
std::string got{out, sizeof out};
static const std::string expect{" &JUSTA A= 'aBCDEfgh'/ "};
EXPECT_EQ(got, expect);
}
TEST(NamelistTests, ArraySubstring) {
OwningPtr<Descriptor> scDesc{
MakeArray<TypeCategory::Character, 1>(std::vector<int>{2},
std::vector<std::string>{"abcdefgh", "ijklmnop"}, 8)};
const NamelistGroup::Item items[]{{"a", *scDesc}};
const NamelistGroup group{"justa", 1, items};
static char t1[]{"&justa A(:)(2:+5)='BCDE' 'JKLM'/"};
StaticDescriptor<1, true> statDesc;
Descriptor &internalDesc{statDesc.descriptor()};
internalDesc.Establish(TypeCode{CFI_type_char},
/*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
auto inCookie{IONAME(BeginInternalArrayListInput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
<< "namelist scalar substring input";
char out[40];
internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
out, 0, nullptr, CFI_attribute_pointer);
auto outCookie{IONAME(BeginInternalArrayListOutput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(SetDelim)(outCookie, "apostrophe", 10));
ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
std::string got{out, sizeof out};
static const std::string expect{" &JUSTA A= 'aBCDEfgh' 'iJKLMnop'/ "};
EXPECT_EQ(got, expect);
}
TEST(NamelistTests, Skip) {
OwningPtr<Descriptor> scDesc{
MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
std::vector<int>{}, std::vector<int>{-1})};
const NamelistGroup::Item items[]{{"j", *scDesc}};
const NamelistGroup group{"nml", 1, items};
static char t1[]{"&skip a='str''ing'/&nml j=123/"};
StaticDescriptor<1, true> statDesc;
Descriptor &internalDesc{statDesc.descriptor()};
internalDesc.Establish(TypeCode{CFI_type_char},
/*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
auto inCookie{IONAME(BeginInternalArrayListInput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
<< "namelist input with skipping";
char out[20];
internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
out, 0, nullptr, CFI_attribute_pointer);
auto outCookie{IONAME(BeginInternalArrayListOutput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
std::string got{out, sizeof out};
static const std::string expect{" &NML J= 123/ "};
EXPECT_EQ(got, expect);
}
// Tests DECIMAL=COMMA mode
TEST(NamelistTests, Comma) {
OwningPtr<Descriptor> scDesc{
MakeArray<TypeCategory::Complex, static_cast<int>(sizeof(float))>(
std::vector<int>{2}, std::vector<std::complex<float>>{{}, {}})};
const NamelistGroup::Item items[]{{"z", *scDesc}};
const NamelistGroup group{"nml", 1, items};
static char t1[]{"&nml z=(-1,0;2,0);(-3,0;0,5)/"};
StaticDescriptor<1, true> statDesc;
Descriptor &internalDesc{statDesc.descriptor()};
internalDesc.Establish(TypeCode{CFI_type_char},
/*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
auto inCookie{IONAME(BeginInternalArrayListInput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(SetDecimal)(inCookie, "COMMA", 5));
ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
<< "namelist input with skipping";
char out[30];
internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
out, 0, nullptr, CFI_attribute_pointer);
auto outCookie{IONAME(BeginInternalArrayListOutput)(
internalDesc, nullptr, 0, __FILE__, __LINE__)};
ASSERT_TRUE(IONAME(SetDecimal)(outCookie, "COMMA", 5));
ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
std::string got{out, sizeof out};
static const std::string expect{" &NML Z= (-1,;2,) (-3,;,5)/ "};
EXPECT_EQ(got, expect);
}
// TODO: Internal NAMELIST error tests