//===-- runtime/namelist.cpp ----------------------------------------------===//
//
// 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 "namelist.h"
#include "descriptor-io.h"
#include "emit-encoded.h"
#include "io-stmt.h"
#include "flang/Runtime/io-api.h"
#include <algorithm>
#include <cstring>
#include <limits>
namespace Fortran::runtime::io {
RT_VAR_GROUP_BEGIN
// Max size of a group, symbol or component identifier that can appear in
// NAMELIST input, plus a byte for NUL termination.
static constexpr RT_CONST_VAR_ATTRS std::size_t nameBufferSize{201};
RT_VAR_GROUP_END
RT_OFFLOAD_API_GROUP_BEGIN
static inline RT_API_ATTRS char32_t GetComma(IoStatementState &io) {
return io.mutableModes().editingFlags & decimalComma ? char32_t{';'}
: char32_t{','};
}
bool IODEF(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
IoStatementState &io{*cookie};
io.CheckFormattedStmtType<Direction::Output>("OutputNamelist");
io.mutableModes().inNamelist = true;
ConnectionState &connection{io.GetConnectionState()};
// The following lambda definition violates the conding style,
// but cuda-11.8 nvcc hits an internal error with the brace initialization.
// Internal function to advance records and convert case
const auto EmitUpperCase = [&](const char *prefix, std::size_t prefixLen,
const char *str, char suffix) -> bool {
if ((connection.NeedAdvance(prefixLen) &&
!(io.AdvanceRecord() && EmitAscii(io, " ", 1))) ||
!EmitAscii(io, prefix, prefixLen) ||
(connection.NeedAdvance(
Fortran::runtime::strlen(str) + (suffix != ' ')) &&
!(io.AdvanceRecord() && EmitAscii(io, " ", 1)))) {
return false;
}
for (; *str; ++str) {
char up{*str >= 'a' && *str <= 'z' ? static_cast<char>(*str - 'a' + 'A')
: *str};
if (!EmitAscii(io, &up, 1)) {
return false;
}
}
return suffix == ' ' || EmitAscii(io, &suffix, 1);
};
// &GROUP
if (!EmitUpperCase(" &", 2, group.groupName, ' ')) {
return false;
}
auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()};
char comma{static_cast<char>(GetComma(io))};
char prefix{' '};
for (std::size_t j{0}; j < group.items; ++j) {
// [,]ITEM=...
const NamelistGroup::Item &item{group.item[j]};
if (listOutput) {
listOutput->set_lastWasUndelimitedCharacter(false);
}
if (!EmitUpperCase(&prefix, 1, item.name, '=')) {
return false;
}
prefix = comma;
if (const auto *addendum{item.descriptor.Addendum()};
addendum && addendum->derivedType()) {
const NonTbpDefinedIoTable *table{group.nonTbpDefinedIo};
if (!IONAME(OutputDerivedType)(cookie, item.descriptor, table)) {
return false;
}
} else if (!descr::DescriptorIO<Direction::Output>(io, item.descriptor)) {
return false;
}
}
// terminal /
return EmitUpperCase("/", 1, "", ' ');
}
static constexpr RT_API_ATTRS bool IsLegalIdStart(char32_t ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' ||
ch == '@';
}
static constexpr RT_API_ATTRS bool IsLegalIdChar(char32_t ch) {
return IsLegalIdStart(ch) || (ch >= '0' && ch <= '9');
}
static constexpr RT_API_ATTRS char NormalizeIdChar(char32_t ch) {
return static_cast<char>(ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch);
}
static RT_API_ATTRS bool GetLowerCaseName(
IoStatementState &io, char buffer[], std::size_t maxLength) {
std::size_t byteLength{0};
if (auto ch{io.GetNextNonBlank(byteLength)}) {
if (IsLegalIdStart(*ch)) {
std::size_t j{0};
do {
buffer[j] = NormalizeIdChar(*ch);
io.HandleRelativePosition(byteLength);
ch = io.GetCurrentChar(byteLength);
} while (++j < maxLength && ch && IsLegalIdChar(*ch));
buffer[j++] = '\0';
if (j <= maxLength) {
return true;
}
io.GetIoErrorHandler().SignalError(
"Identifier '%s...' in NAMELIST input group is too long", buffer);
}
}
return false;
}
static RT_API_ATTRS Fortran::common::optional<SubscriptValue> GetSubscriptValue(
IoStatementState &io) {
Fortran::common::optional<SubscriptValue> value;
std::size_t byteCount{0};
Fortran::common::optional<char32_t> ch{io.GetCurrentChar(byteCount)};
bool negate{ch && *ch == '-'};
if ((ch && *ch == '+') || negate) {
io.HandleRelativePosition(byteCount);
ch = io.GetCurrentChar(byteCount);
}
bool overflow{false};
while (ch && *ch >= '0' && *ch <= '9') {
SubscriptValue was{value.value_or(0)};
overflow |= was >= std::numeric_limits<SubscriptValue>::max() / 10;
value = 10 * was + *ch - '0';
io.HandleRelativePosition(byteCount);
ch = io.GetCurrentChar(byteCount);
}
if (overflow) {
io.GetIoErrorHandler().SignalError(
"NAMELIST input subscript value overflow");
return Fortran::common::nullopt;
}
if (negate) {
if (value) {
return -*value;
} else {
io.HandleRelativePosition(-byteCount); // give back '-' with no digits
}
}
return value;
}
static RT_API_ATTRS bool HandleSubscripts(IoStatementState &io,
Descriptor &desc, const Descriptor &source, const char *name) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
// Allow for blanks in subscripts; they're nonstandard, but not
// ambiguous within the parentheses.
SubscriptValue lower[maxRank], upper[maxRank], stride[maxRank];
int j{0};
std::size_t contiguousStride{source.ElementBytes()};
bool ok{true};
std::size_t byteCount{0};
Fortran::common::optional<char32_t> ch{io.GetNextNonBlank(byteCount)};
char32_t comma{GetComma(io)};
for (; ch && *ch != ')'; ++j) {
SubscriptValue dimLower{0}, dimUpper{0}, dimStride{0};
if (j < maxRank && j < source.rank()) {
const Dimension &dim{source.GetDimension(j)};
dimLower = dim.LowerBound();
dimUpper = dim.UpperBound();
dimStride =
dim.ByteStride() / std::max<SubscriptValue>(contiguousStride, 1);
contiguousStride *= dim.Extent();
} else if (ok) {
handler.SignalError(
"Too many subscripts for rank-%d NAMELIST group item '%s'",
source.rank(), name);
ok = false;
}
if (auto low{GetSubscriptValue(io)}) {
if (*low < dimLower || (dimUpper >= dimLower && *low > dimUpper)) {
if (ok) {
handler.SignalError("Subscript %jd out of range %jd..%jd in NAMELIST "
"group item '%s' dimension %d",
static_cast<std::intmax_t>(*low),
static_cast<std::intmax_t>(dimLower),
static_cast<std::intmax_t>(dimUpper), name, j + 1);
ok = false;
}
} else {
dimLower = *low;
}
ch = io.GetNextNonBlank(byteCount);
}
if (ch && *ch == ':') {
io.HandleRelativePosition(byteCount);
ch = io.GetNextNonBlank(byteCount);
if (auto high{GetSubscriptValue(io)}) {
if (*high > dimUpper) {
if (ok) {
handler.SignalError(
"Subscript triplet upper bound %jd out of range (>%jd) in "
"NAMELIST group item '%s' dimension %d",
static_cast<std::intmax_t>(*high),
static_cast<std::intmax_t>(dimUpper), name, j + 1);
ok = false;
}
} else {
dimUpper = *high;
}
ch = io.GetNextNonBlank(byteCount);
}
if (ch && *ch == ':') {
io.HandleRelativePosition(byteCount);
ch = io.GetNextNonBlank(byteCount);
if (auto str{GetSubscriptValue(io)}) {
dimStride = *str;
ch = io.GetNextNonBlank(byteCount);
}
}
} else { // scalar
dimUpper = dimLower;
dimStride = 0;
}
if (ch && *ch == comma) {
io.HandleRelativePosition(byteCount);
ch = io.GetNextNonBlank(byteCount);
}
if (ok) {
lower[j] = dimLower;
upper[j] = dimUpper;
stride[j] = dimStride;
}
}
if (ok) {
if (ch && *ch == ')') {
io.HandleRelativePosition(byteCount);
if (desc.EstablishPointerSection(source, lower, upper, stride)) {
return true;
} else {
handler.SignalError(
"Bad subscripts for NAMELIST input group item '%s'", name);
}
} else {
handler.SignalError(
"Bad subscripts (missing ')') for NAMELIST input group item '%s'",
name);
}
}
return false;
}
static RT_API_ATTRS void StorageSequenceExtension(
Descriptor &desc, const Descriptor &source) {
// Support the near-universal extension of NAMELIST input into a
// designatable storage sequence identified by its initial scalar array
// element. For example, treat "A(1) = 1. 2. 3." as if it had been
// "A(1:) = 1. 2. 3.".
if (desc.rank() == 0 && (source.rank() == 1 || source.IsContiguous())) {
if (auto stride{source.rank() == 1
? source.GetDimension(0).ByteStride()
: static_cast<SubscriptValue>(source.ElementBytes())};
stride != 0) {
desc.raw().attribute = CFI_attribute_pointer;
desc.raw().rank = 1;
desc.GetDimension(0)
.SetBounds(1,
source.Elements() -
((source.OffsetElement() - desc.OffsetElement()) / stride))
.SetByteStride(stride);
}
}
}
static RT_API_ATTRS bool HandleSubstring(
IoStatementState &io, Descriptor &desc, const char *name) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
auto pair{desc.type().GetCategoryAndKind()};
if (!pair || pair->first != TypeCategory::Character) {
handler.SignalError("Substring reference to non-character item '%s'", name);
return false;
}
int kind{pair->second};
SubscriptValue chars{static_cast<SubscriptValue>(desc.ElementBytes()) / kind};
// Allow for blanks in substring bounds; they're nonstandard, but not
// ambiguous within the parentheses.
Fortran::common::optional<SubscriptValue> lower, upper;
std::size_t byteCount{0};
Fortran::common::optional<char32_t> ch{io.GetNextNonBlank(byteCount)};
if (ch) {
if (*ch == ':') {
lower = 1;
} else {
lower = GetSubscriptValue(io);
ch = io.GetNextNonBlank(byteCount);
}
}
if (ch && *ch == ':') {
io.HandleRelativePosition(byteCount);
ch = io.GetNextNonBlank(byteCount);
if (ch) {
if (*ch == ')') {
upper = chars;
} else {
upper = GetSubscriptValue(io);
ch = io.GetNextNonBlank(byteCount);
}
}
}
if (ch && *ch == ')') {
io.HandleRelativePosition(byteCount);
if (lower && upper) {
if (*lower > *upper) {
// An empty substring, whatever the values are
desc.raw().elem_len = 0;
return true;
}
if (*lower >= 1 && *upper <= chars) {
// Offset the base address & adjust the element byte length
desc.raw().elem_len = (*upper - *lower + 1) * kind;
desc.set_base_addr(reinterpret_cast<void *>(
reinterpret_cast<char *>(desc.raw().base_addr) +
kind * (*lower - 1)));
return true;
}
}
handler.SignalError(
"Bad substring bounds for NAMELIST input group item '%s'", name);
} else {
handler.SignalError(
"Bad substring (missing ')') for NAMELIST input group item '%s'", name);
}
return false;
}
static RT_API_ATTRS bool HandleComponent(IoStatementState &io, Descriptor &desc,
const Descriptor &source, const char *name) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
char compName[nameBufferSize];
if (GetLowerCaseName(io, compName, sizeof compName)) {
const DescriptorAddendum *addendum{source.Addendum()};
if (const typeInfo::DerivedType *
type{addendum ? addendum->derivedType() : nullptr}) {
if (const typeInfo::Component *
comp{type->FindDataComponent(
compName, Fortran::runtime::strlen(compName))}) {
bool createdDesc{false};
if (comp->rank() > 0 && source.rank() > 0) {
// If base and component are both arrays, the component name
// must be followed by subscripts; process them now.
std::size_t byteCount{0};
if (Fortran::common::optional<char32_t> next{
io.GetNextNonBlank(byteCount)};
next && *next == '(') {
io.HandleRelativePosition(byteCount); // skip over '('
StaticDescriptor<maxRank, true, 16> staticDesc;
Descriptor &tmpDesc{staticDesc.descriptor()};
comp->CreatePointerDescriptor(tmpDesc, source, handler);
if (!HandleSubscripts(io, desc, tmpDesc, compName)) {
return false;
}
createdDesc = true;
}
}
if (!createdDesc) {
comp->CreatePointerDescriptor(desc, source, handler);
}
if (source.rank() > 0) {
if (desc.rank() > 0) {
handler.SignalError(
"NAMELIST component reference '%%%s' of input group "
"item %s cannot be an array when its base is not scalar",
compName, name);
return false;
}
desc.raw().rank = source.rank();
for (int j{0}; j < source.rank(); ++j) {
const auto &srcDim{source.GetDimension(j)};
desc.GetDimension(j)
.SetBounds(1, srcDim.UpperBound())
.SetByteStride(srcDim.ByteStride());
}
}
return true;
} else {
handler.SignalError(
"NAMELIST component reference '%%%s' of input group item %s is not "
"a component of its derived type",
compName, name);
}
} else if (source.type().IsDerived()) {
handler.Crash("Derived type object '%s' in NAMELIST is missing its "
"derived type information!",
name);
} else {
handler.SignalError("NAMELIST component reference '%%%s' of input group "
"item %s for non-derived type",
compName, name);
}
} else {
handler.SignalError("NAMELIST component reference of input group item %s "
"has no name after '%%'",
name);
}
return false;
}
// Advance to the terminal '/' of a namelist group or leading '&'/'$'
// of the next.
static RT_API_ATTRS void SkipNamelistGroup(IoStatementState &io) {
std::size_t byteCount{0};
while (auto ch{io.GetNextNonBlank(byteCount)}) {
io.HandleRelativePosition(byteCount);
if (*ch == '/' || *ch == '&' || *ch == '$') {
break;
} else if (*ch == '\'' || *ch == '"') {
// Skip quoted character literal
char32_t quote{*ch};
while (true) {
if ((ch = io.GetCurrentChar(byteCount))) {
io.HandleRelativePosition(byteCount);
if (*ch == quote) {
break;
}
} else if (!io.AdvanceRecord()) {
return;
}
}
}
}
}
bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
IoStatementState &io{*cookie};
io.CheckFormattedStmtType<Direction::Input>("InputNamelist");
io.mutableModes().inNamelist = true;
IoErrorHandler &handler{io.GetIoErrorHandler()};
auto *listInput{io.get_if<ListDirectedStatementState<Direction::Input>>()};
RUNTIME_CHECK(handler, listInput != nullptr);
// Find this namelist group's header in the input
io.BeginReadingRecord();
Fortran::common::optional<char32_t> next;
char name[nameBufferSize];
RUNTIME_CHECK(handler, group.groupName != nullptr);
char32_t comma{GetComma(io)};
std::size_t byteCount{0};
while (true) {
next = io.GetNextNonBlank(byteCount);
while (next && *next != '&' && *next != '$') {
// Extension: comment lines without ! before namelist groups
if (!io.AdvanceRecord()) {
next.reset();
} else {
next = io.GetNextNonBlank(byteCount);
}
}
if (!next) {
handler.SignalEnd();
return false;
}
if (*next != '&' && *next != '$') {
handler.SignalError(
"NAMELIST input group does not begin with '&' or '$' (at '%lc')",
*next);
return false;
}
io.HandleRelativePosition(byteCount);
if (!GetLowerCaseName(io, name, sizeof name)) {
handler.SignalError("NAMELIST input group has no name");
return false;
}
if (Fortran::runtime::strcmp(group.groupName, name) == 0) {
break; // found it
}
SkipNamelistGroup(io);
}
// Read the group's items
while (true) {
next = io.GetNextNonBlank(byteCount);
if (!next || *next == '/' || *next == '&' || *next == '$') {
break;
}
if (!GetLowerCaseName(io, name, sizeof name)) {
handler.SignalError(
"NAMELIST input group '%s' was not terminated at '%c'",
group.groupName, static_cast<char>(*next));
return false;
}
std::size_t itemIndex{0};
for (; itemIndex < group.items; ++itemIndex) {
if (Fortran::runtime::strcmp(name, group.item[itemIndex].name) == 0) {
break;
}
}
if (itemIndex >= group.items) {
handler.SignalError(
"'%s' is not an item in NAMELIST group '%s'", name, group.groupName);
return false;
}
// Handle indexing and components, if any. No spaces are allowed.
// A copy of the descriptor is made if necessary.
const Descriptor &itemDescriptor{group.item[itemIndex].descriptor};
const Descriptor *useDescriptor{&itemDescriptor};
StaticDescriptor<maxRank, true, 16> staticDesc[2];
int whichStaticDesc{0};
next = io.GetCurrentChar(byteCount);
bool hadSubscripts{false};
bool hadSubstring{false};
if (next && (*next == '(' || *next == '%')) {
const Descriptor *lastSubscriptBase{nullptr};
Descriptor *lastSubscriptDescriptor{nullptr};
do {
Descriptor &mutableDescriptor{staticDesc[whichStaticDesc].descriptor()};
whichStaticDesc ^= 1;
io.HandleRelativePosition(byteCount); // skip over '(' or '%'
lastSubscriptDescriptor = nullptr;
lastSubscriptBase = nullptr;
if (*next == '(') {
if (!hadSubstring && (hadSubscripts || useDescriptor->rank() == 0)) {
mutableDescriptor = *useDescriptor;
mutableDescriptor.raw().attribute = CFI_attribute_pointer;
if (!HandleSubstring(io, mutableDescriptor, name)) {
return false;
}
hadSubstring = true;
} else if (hadSubscripts) {
handler.SignalError("Multiple sets of subscripts for item '%s' in "
"NAMELIST group '%s'",
name, group.groupName);
return false;
} else if (HandleSubscripts(
io, mutableDescriptor, *useDescriptor, name)) {
lastSubscriptBase = useDescriptor;
lastSubscriptDescriptor = &mutableDescriptor;
} else {
return false;
}
hadSubscripts = true;
} else {
if (!HandleComponent(io, mutableDescriptor, *useDescriptor, name)) {
return false;
}
hadSubscripts = false;
hadSubstring = false;
}
useDescriptor = &mutableDescriptor;
next = io.GetCurrentChar(byteCount);
} while (next && (*next == '(' || *next == '%'));
if (lastSubscriptDescriptor) {
StorageSequenceExtension(*lastSubscriptDescriptor, *lastSubscriptBase);
}
}
// Skip the '='
next = io.GetNextNonBlank(byteCount);
if (!next || *next != '=') {
handler.SignalError("No '=' found after item '%s' in NAMELIST group '%s'",
name, group.groupName);
return false;
}
io.HandleRelativePosition(byteCount);
// Read the values into the descriptor. An array can be short.
if (const auto *addendum{useDescriptor->Addendum()};
addendum && addendum->derivedType()) {
const NonTbpDefinedIoTable *table{group.nonTbpDefinedIo};
listInput->ResetForNextNamelistItem(/*inNamelistSequence=*/true);
if (!IONAME(InputDerivedType)(cookie, *useDescriptor, table)) {
return false;
}
} else {
listInput->ResetForNextNamelistItem(useDescriptor->rank() > 0);
if (!descr::DescriptorIO<Direction::Input>(io, *useDescriptor)) {
return false;
}
}
next = io.GetNextNonBlank(byteCount);
if (next && *next == comma) {
io.HandleRelativePosition(byteCount);
}
}
if (next && *next == '/') {
io.HandleRelativePosition(byteCount);
} else if (*next && (*next == '&' || *next == '$')) {
// stop at beginning of next group
} else {
handler.SignalError(
"No '/' found after NAMELIST group '%s'", group.groupName);
return false;
}
return true;
}
RT_API_ATTRS bool IsNamelistNameOrSlash(IoStatementState &io) {
if (auto *listInput{
io.get_if<ListDirectedStatementState<Direction::Input>>()}) {
if (listInput->inNamelistSequence()) {
SavedPosition savedPosition{io};
std::size_t byteCount{0};
if (auto ch{io.GetNextNonBlank(byteCount)}) {
if (IsLegalIdStart(*ch)) {
do {
io.HandleRelativePosition(byteCount);
ch = io.GetCurrentChar(byteCount);
} while (ch && IsLegalIdChar(*ch));
ch = io.GetNextNonBlank(byteCount);
// TODO: how to deal with NaN(...) ambiguity?
return ch && (*ch == '=' || *ch == '(' || *ch == '%');
} else {
return *ch == '/' || *ch == '&' || *ch == '$';
}
}
}
}
return false;
}
RT_OFFLOAD_API_GROUP_END
} // namespace Fortran::runtime::io