mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-25 18:06:05 +00:00

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.
309 lines
14 KiB
C++
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
|