1 //===-- runtime/namelist.cpp ----------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
10 #include "descriptor-io.h"
11 #include "emit-encoded.h"
13 #include "flang/Runtime/io-api.h"
18 namespace Fortran::runtime::io
{
21 // Max size of a group, symbol or component identifier that can appear in
22 // NAMELIST input, plus a byte for NUL termination.
23 static constexpr RT_CONST_VAR_ATTRS
std::size_t nameBufferSize
{201};
26 RT_OFFLOAD_API_GROUP_BEGIN
28 static inline RT_API_ATTRS char32_t
GetComma(IoStatementState
&io
) {
29 return io
.mutableModes().editingFlags
& decimalComma
? char32_t
{';'}
33 bool IODEF(OutputNamelist
)(Cookie cookie
, const NamelistGroup
&group
) {
34 IoStatementState
&io
{*cookie
};
35 io
.CheckFormattedStmtType
<Direction::Output
>("OutputNamelist");
36 io
.mutableModes().inNamelist
= true;
37 ConnectionState
&connection
{io
.GetConnectionState()};
38 // The following lambda definition violates the conding style,
39 // but cuda-11.8 nvcc hits an internal error with the brace initialization.
41 // Internal function to advance records and convert case
42 const auto EmitUpperCase
= [&](const char *prefix
, std::size_t prefixLen
,
43 const char *str
, char suffix
) -> bool {
44 if ((connection
.NeedAdvance(prefixLen
) &&
45 !(io
.AdvanceRecord() && EmitAscii(io
, " ", 1))) ||
46 !EmitAscii(io
, prefix
, prefixLen
) ||
47 (connection
.NeedAdvance(
48 Fortran::runtime::strlen(str
) + (suffix
!= ' ')) &&
49 !(io
.AdvanceRecord() && EmitAscii(io
, " ", 1)))) {
53 char up
{*str
>= 'a' && *str
<= 'z' ? static_cast<char>(*str
- 'a' + 'A')
55 if (!EmitAscii(io
, &up
, 1)) {
59 return suffix
== ' ' || EmitAscii(io
, &suffix
, 1);
62 if (!EmitUpperCase(" &", 2, group
.groupName
, ' ')) {
65 auto *listOutput
{io
.get_if
<ListDirectedStatementState
<Direction::Output
>>()};
66 char comma
{static_cast<char>(GetComma(io
))};
68 for (std::size_t j
{0}; j
< group
.items
; ++j
) {
70 const NamelistGroup::Item
&item
{group
.item
[j
]};
72 listOutput
->set_lastWasUndelimitedCharacter(false);
74 if (!EmitUpperCase(&prefix
, 1, item
.name
, '=')) {
78 if (const auto *addendum
{item
.descriptor
.Addendum()};
79 addendum
&& addendum
->derivedType()) {
80 const NonTbpDefinedIoTable
*table
{group
.nonTbpDefinedIo
};
81 if (!IONAME(OutputDerivedType
)(cookie
, item
.descriptor
, table
)) {
84 } else if (!descr::DescriptorIO
<Direction::Output
>(io
, item
.descriptor
)) {
89 return EmitUpperCase("/", 1, "", ' ');
92 static constexpr RT_API_ATTRS
bool IsLegalIdStart(char32_t ch
) {
93 return (ch
>= 'A' && ch
<= 'Z') || (ch
>= 'a' && ch
<= 'z') || ch
== '_' ||
97 static constexpr RT_API_ATTRS
bool IsLegalIdChar(char32_t ch
) {
98 return IsLegalIdStart(ch
) || (ch
>= '0' && ch
<= '9');
101 static constexpr RT_API_ATTRS
char NormalizeIdChar(char32_t ch
) {
102 return static_cast<char>(ch
>= 'A' && ch
<= 'Z' ? ch
- 'A' + 'a' : ch
);
105 static RT_API_ATTRS
bool GetLowerCaseName(
106 IoStatementState
&io
, char buffer
[], std::size_t maxLength
) {
107 std::size_t byteLength
{0};
108 if (auto ch
{io
.GetNextNonBlank(byteLength
)}) {
109 if (IsLegalIdStart(*ch
)) {
112 buffer
[j
] = NormalizeIdChar(*ch
);
113 io
.HandleRelativePosition(byteLength
);
114 ch
= io
.GetCurrentChar(byteLength
);
115 } while (++j
< maxLength
&& ch
&& IsLegalIdChar(*ch
));
117 if (j
<= maxLength
) {
120 io
.GetIoErrorHandler().SignalError(
121 "Identifier '%s...' in NAMELIST input group is too long", buffer
);
127 static RT_API_ATTRS
Fortran::common::optional
<SubscriptValue
> GetSubscriptValue(
128 IoStatementState
&io
) {
129 Fortran::common::optional
<SubscriptValue
> value
;
130 std::size_t byteCount
{0};
131 Fortran::common::optional
<char32_t
> ch
{io
.GetCurrentChar(byteCount
)};
132 bool negate
{ch
&& *ch
== '-'};
133 if ((ch
&& *ch
== '+') || negate
) {
134 io
.HandleRelativePosition(byteCount
);
135 ch
= io
.GetCurrentChar(byteCount
);
137 bool overflow
{false};
138 while (ch
&& *ch
>= '0' && *ch
<= '9') {
139 SubscriptValue was
{value
.value_or(0)};
140 overflow
|= was
>= std::numeric_limits
<SubscriptValue
>::max() / 10;
141 value
= 10 * was
+ *ch
- '0';
142 io
.HandleRelativePosition(byteCount
);
143 ch
= io
.GetCurrentChar(byteCount
);
146 io
.GetIoErrorHandler().SignalError(
147 "NAMELIST input subscript value overflow");
148 return Fortran::common::nullopt
;
154 io
.HandleRelativePosition(-byteCount
); // give back '-' with no digits
160 static RT_API_ATTRS
bool HandleSubscripts(IoStatementState
&io
,
161 Descriptor
&desc
, const Descriptor
&source
, const char *name
) {
162 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
163 // Allow for blanks in subscripts; they're nonstandard, but not
164 // ambiguous within the parentheses.
165 SubscriptValue lower
[maxRank
], upper
[maxRank
], stride
[maxRank
];
167 std::size_t contiguousStride
{source
.ElementBytes()};
169 std::size_t byteCount
{0};
170 Fortran::common::optional
<char32_t
> ch
{io
.GetNextNonBlank(byteCount
)};
171 char32_t comma
{GetComma(io
)};
172 for (; ch
&& *ch
!= ')'; ++j
) {
173 SubscriptValue dimLower
{0}, dimUpper
{0}, dimStride
{0};
174 if (j
< maxRank
&& j
< source
.rank()) {
175 const Dimension
&dim
{source
.GetDimension(j
)};
176 dimLower
= dim
.LowerBound();
177 dimUpper
= dim
.UpperBound();
179 dim
.ByteStride() / std::max
<SubscriptValue
>(contiguousStride
, 1);
180 contiguousStride
*= dim
.Extent();
183 "Too many subscripts for rank-%d NAMELIST group item '%s'",
184 source
.rank(), name
);
187 if (auto low
{GetSubscriptValue(io
)}) {
188 if (*low
< dimLower
|| (dimUpper
>= dimLower
&& *low
> dimUpper
)) {
190 handler
.SignalError("Subscript %jd out of range %jd..%jd in NAMELIST "
191 "group item '%s' dimension %d",
192 static_cast<std::intmax_t>(*low
),
193 static_cast<std::intmax_t>(dimLower
),
194 static_cast<std::intmax_t>(dimUpper
), name
, j
+ 1);
200 ch
= io
.GetNextNonBlank(byteCount
);
202 if (ch
&& *ch
== ':') {
203 io
.HandleRelativePosition(byteCount
);
204 ch
= io
.GetNextNonBlank(byteCount
);
205 if (auto high
{GetSubscriptValue(io
)}) {
206 if (*high
> dimUpper
) {
209 "Subscript triplet upper bound %jd out of range (>%jd) in "
210 "NAMELIST group item '%s' dimension %d",
211 static_cast<std::intmax_t>(*high
),
212 static_cast<std::intmax_t>(dimUpper
), name
, j
+ 1);
218 ch
= io
.GetNextNonBlank(byteCount
);
220 if (ch
&& *ch
== ':') {
221 io
.HandleRelativePosition(byteCount
);
222 ch
= io
.GetNextNonBlank(byteCount
);
223 if (auto str
{GetSubscriptValue(io
)}) {
225 ch
= io
.GetNextNonBlank(byteCount
);
232 if (ch
&& *ch
== comma
) {
233 io
.HandleRelativePosition(byteCount
);
234 ch
= io
.GetNextNonBlank(byteCount
);
239 stride
[j
] = dimStride
;
243 if (ch
&& *ch
== ')') {
244 io
.HandleRelativePosition(byteCount
);
245 if (desc
.EstablishPointerSection(source
, lower
, upper
, stride
)) {
249 "Bad subscripts for NAMELIST input group item '%s'", name
);
253 "Bad subscripts (missing ')') for NAMELIST input group item '%s'",
260 static RT_API_ATTRS
void StorageSequenceExtension(
261 Descriptor
&desc
, const Descriptor
&source
) {
262 // Support the near-universal extension of NAMELIST input into a
263 // designatable storage sequence identified by its initial scalar array
264 // element. For example, treat "A(1) = 1. 2. 3." as if it had been
265 // "A(1:) = 1. 2. 3.".
266 if (desc
.rank() == 0 && (source
.rank() == 1 || source
.IsContiguous())) {
267 if (auto stride
{source
.rank() == 1
268 ? source
.GetDimension(0).ByteStride()
269 : static_cast<SubscriptValue
>(source
.ElementBytes())};
271 desc
.raw().attribute
= CFI_attribute_pointer
;
276 ((source
.OffsetElement() - desc
.OffsetElement()) / stride
))
277 .SetByteStride(stride
);
282 static RT_API_ATTRS
bool HandleSubstring(
283 IoStatementState
&io
, Descriptor
&desc
, const char *name
) {
284 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
285 auto pair
{desc
.type().GetCategoryAndKind()};
286 if (!pair
|| pair
->first
!= TypeCategory::Character
) {
287 handler
.SignalError("Substring reference to non-character item '%s'", name
);
290 int kind
{pair
->second
};
291 SubscriptValue chars
{static_cast<SubscriptValue
>(desc
.ElementBytes()) / kind
};
292 // Allow for blanks in substring bounds; they're nonstandard, but not
293 // ambiguous within the parentheses.
294 Fortran::common::optional
<SubscriptValue
> lower
, upper
;
295 std::size_t byteCount
{0};
296 Fortran::common::optional
<char32_t
> ch
{io
.GetNextNonBlank(byteCount
)};
301 lower
= GetSubscriptValue(io
);
302 ch
= io
.GetNextNonBlank(byteCount
);
305 if (ch
&& *ch
== ':') {
306 io
.HandleRelativePosition(byteCount
);
307 ch
= io
.GetNextNonBlank(byteCount
);
312 upper
= GetSubscriptValue(io
);
313 ch
= io
.GetNextNonBlank(byteCount
);
317 if (ch
&& *ch
== ')') {
318 io
.HandleRelativePosition(byteCount
);
319 if (lower
&& upper
) {
320 if (*lower
> *upper
) {
321 // An empty substring, whatever the values are
322 desc
.raw().elem_len
= 0;
325 if (*lower
>= 1 && *upper
<= chars
) {
326 // Offset the base address & adjust the element byte length
327 desc
.raw().elem_len
= (*upper
- *lower
+ 1) * kind
;
328 desc
.set_base_addr(reinterpret_cast<void *>(
329 reinterpret_cast<char *>(desc
.raw().base_addr
) +
330 kind
* (*lower
- 1)));
335 "Bad substring bounds for NAMELIST input group item '%s'", name
);
338 "Bad substring (missing ')') for NAMELIST input group item '%s'", name
);
343 static RT_API_ATTRS
bool HandleComponent(IoStatementState
&io
, Descriptor
&desc
,
344 const Descriptor
&source
, const char *name
) {
345 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
346 char compName
[nameBufferSize
];
347 if (GetLowerCaseName(io
, compName
, sizeof compName
)) {
348 const DescriptorAddendum
*addendum
{source
.Addendum()};
349 if (const typeInfo::DerivedType
*
350 type
{addendum
? addendum
->derivedType() : nullptr}) {
351 if (const typeInfo::Component
*
352 comp
{type
->FindDataComponent(
353 compName
, Fortran::runtime::strlen(compName
))}) {
354 bool createdDesc
{false};
355 if (comp
->rank() > 0 && source
.rank() > 0) {
356 // If base and component are both arrays, the component name
357 // must be followed by subscripts; process them now.
358 std::size_t byteCount
{0};
359 if (Fortran::common::optional
<char32_t
> next
{
360 io
.GetNextNonBlank(byteCount
)};
361 next
&& *next
== '(') {
362 io
.HandleRelativePosition(byteCount
); // skip over '('
363 StaticDescriptor
<maxRank
, true, 16> staticDesc
;
364 Descriptor
&tmpDesc
{staticDesc
.descriptor()};
365 comp
->CreatePointerDescriptor(tmpDesc
, source
, handler
);
366 if (!HandleSubscripts(io
, desc
, tmpDesc
, compName
)) {
373 comp
->CreatePointerDescriptor(desc
, source
, handler
);
375 if (source
.rank() > 0) {
376 if (desc
.rank() > 0) {
378 "NAMELIST component reference '%%%s' of input group "
379 "item %s cannot be an array when its base is not scalar",
383 desc
.raw().rank
= source
.rank();
384 for (int j
{0}; j
< source
.rank(); ++j
) {
385 const auto &srcDim
{source
.GetDimension(j
)};
387 .SetBounds(1, srcDim
.UpperBound())
388 .SetByteStride(srcDim
.ByteStride());
394 "NAMELIST component reference '%%%s' of input group item %s is not "
395 "a component of its derived type",
398 } else if (source
.type().IsDerived()) {
399 handler
.Crash("Derived type object '%s' in NAMELIST is missing its "
400 "derived type information!",
403 handler
.SignalError("NAMELIST component reference '%%%s' of input group "
404 "item %s for non-derived type",
408 handler
.SignalError("NAMELIST component reference of input group item %s "
409 "has no name after '%%'",
415 // Advance to the terminal '/' of a namelist group or leading '&'/'$'
417 static RT_API_ATTRS
void SkipNamelistGroup(IoStatementState
&io
) {
418 std::size_t byteCount
{0};
419 while (auto ch
{io
.GetNextNonBlank(byteCount
)}) {
420 io
.HandleRelativePosition(byteCount
);
421 if (*ch
== '/' || *ch
== '&' || *ch
== '$') {
423 } else if (*ch
== '\'' || *ch
== '"') {
424 // Skip quoted character literal
427 if ((ch
= io
.GetCurrentChar(byteCount
))) {
428 io
.HandleRelativePosition(byteCount
);
432 } else if (!io
.AdvanceRecord()) {
440 bool IODEF(InputNamelist
)(Cookie cookie
, const NamelistGroup
&group
) {
441 IoStatementState
&io
{*cookie
};
442 io
.CheckFormattedStmtType
<Direction::Input
>("InputNamelist");
443 io
.mutableModes().inNamelist
= true;
444 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
445 auto *listInput
{io
.get_if
<ListDirectedStatementState
<Direction::Input
>>()};
446 RUNTIME_CHECK(handler
, listInput
!= nullptr);
447 // Find this namelist group's header in the input
448 io
.BeginReadingRecord();
449 Fortran::common::optional
<char32_t
> next
;
450 char name
[nameBufferSize
];
451 RUNTIME_CHECK(handler
, group
.groupName
!= nullptr);
452 char32_t comma
{GetComma(io
)};
453 std::size_t byteCount
{0};
455 next
= io
.GetNextNonBlank(byteCount
);
456 while (next
&& *next
!= '&' && *next
!= '$') {
457 // Extension: comment lines without ! before namelist groups
458 if (!io
.AdvanceRecord()) {
461 next
= io
.GetNextNonBlank(byteCount
);
468 if (*next
!= '&' && *next
!= '$') {
470 "NAMELIST input group does not begin with '&' or '$' (at '%lc')",
474 io
.HandleRelativePosition(byteCount
);
475 if (!GetLowerCaseName(io
, name
, sizeof name
)) {
476 handler
.SignalError("NAMELIST input group has no name");
479 if (Fortran::runtime::strcmp(group
.groupName
, name
) == 0) {
482 SkipNamelistGroup(io
);
484 // Read the group's items
486 next
= io
.GetNextNonBlank(byteCount
);
487 if (!next
|| *next
== '/' || *next
== '&' || *next
== '$') {
490 if (!GetLowerCaseName(io
, name
, sizeof name
)) {
492 "NAMELIST input group '%s' was not terminated at '%c'",
493 group
.groupName
, static_cast<char>(*next
));
496 std::size_t itemIndex
{0};
497 for (; itemIndex
< group
.items
; ++itemIndex
) {
498 if (Fortran::runtime::strcmp(name
, group
.item
[itemIndex
].name
) == 0) {
502 if (itemIndex
>= group
.items
) {
504 "'%s' is not an item in NAMELIST group '%s'", name
, group
.groupName
);
507 // Handle indexing and components, if any. No spaces are allowed.
508 // A copy of the descriptor is made if necessary.
509 const Descriptor
&itemDescriptor
{group
.item
[itemIndex
].descriptor
};
510 const Descriptor
*useDescriptor
{&itemDescriptor
};
511 StaticDescriptor
<maxRank
, true, 16> staticDesc
[2];
512 int whichStaticDesc
{0};
513 next
= io
.GetCurrentChar(byteCount
);
514 bool hadSubscripts
{false};
515 bool hadSubstring
{false};
516 if (next
&& (*next
== '(' || *next
== '%')) {
517 const Descriptor
*lastSubscriptBase
{nullptr};
518 Descriptor
*lastSubscriptDescriptor
{nullptr};
520 Descriptor
&mutableDescriptor
{staticDesc
[whichStaticDesc
].descriptor()};
521 whichStaticDesc
^= 1;
522 io
.HandleRelativePosition(byteCount
); // skip over '(' or '%'
523 lastSubscriptDescriptor
= nullptr;
524 lastSubscriptBase
= nullptr;
526 if (!hadSubstring
&& (hadSubscripts
|| useDescriptor
->rank() == 0)) {
527 mutableDescriptor
= *useDescriptor
;
528 mutableDescriptor
.raw().attribute
= CFI_attribute_pointer
;
529 if (!HandleSubstring(io
, mutableDescriptor
, name
)) {
533 } else if (hadSubscripts
) {
534 handler
.SignalError("Multiple sets of subscripts for item '%s' in "
535 "NAMELIST group '%s'",
536 name
, group
.groupName
);
538 } else if (HandleSubscripts(
539 io
, mutableDescriptor
, *useDescriptor
, name
)) {
540 lastSubscriptBase
= useDescriptor
;
541 lastSubscriptDescriptor
= &mutableDescriptor
;
545 hadSubscripts
= true;
547 if (!HandleComponent(io
, mutableDescriptor
, *useDescriptor
, name
)) {
550 hadSubscripts
= false;
551 hadSubstring
= false;
553 useDescriptor
= &mutableDescriptor
;
554 next
= io
.GetCurrentChar(byteCount
);
555 } while (next
&& (*next
== '(' || *next
== '%'));
556 if (lastSubscriptDescriptor
) {
557 StorageSequenceExtension(*lastSubscriptDescriptor
, *lastSubscriptBase
);
561 next
= io
.GetNextNonBlank(byteCount
);
562 if (!next
|| *next
!= '=') {
563 handler
.SignalError("No '=' found after item '%s' in NAMELIST group '%s'",
564 name
, group
.groupName
);
567 io
.HandleRelativePosition(byteCount
);
568 // Read the values into the descriptor. An array can be short.
569 if (const auto *addendum
{useDescriptor
->Addendum()};
570 addendum
&& addendum
->derivedType()) {
571 const NonTbpDefinedIoTable
*table
{group
.nonTbpDefinedIo
};
572 listInput
->ResetForNextNamelistItem(/*inNamelistSequence=*/true);
573 if (!IONAME(InputDerivedType
)(cookie
, *useDescriptor
, table
)) {
577 listInput
->ResetForNextNamelistItem(useDescriptor
->rank() > 0);
578 if (!descr::DescriptorIO
<Direction::Input
>(io
, *useDescriptor
)) {
582 next
= io
.GetNextNonBlank(byteCount
);
583 if (next
&& *next
== comma
) {
584 io
.HandleRelativePosition(byteCount
);
587 if (next
&& *next
== '/') {
588 io
.HandleRelativePosition(byteCount
);
589 } else if (*next
&& (*next
== '&' || *next
== '$')) {
590 // stop at beginning of next group
593 "No '/' found after NAMELIST group '%s'", group
.groupName
);
599 RT_API_ATTRS
bool IsNamelistNameOrSlash(IoStatementState
&io
) {
601 io
.get_if
<ListDirectedStatementState
<Direction::Input
>>()}) {
602 if (listInput
->inNamelistSequence()) {
603 SavedPosition savedPosition
{io
};
604 std::size_t byteCount
{0};
605 if (auto ch
{io
.GetNextNonBlank(byteCount
)}) {
606 if (IsLegalIdStart(*ch
)) {
608 io
.HandleRelativePosition(byteCount
);
609 ch
= io
.GetCurrentChar(byteCount
);
610 } while (ch
&& IsLegalIdChar(*ch
));
611 ch
= io
.GetNextNonBlank(byteCount
);
612 // TODO: how to deal with NaN(...) ambiguity?
613 return ch
&& (*ch
== '=' || *ch
== '(' || *ch
== '%');
615 return *ch
== '/' || *ch
== '&' || *ch
== '$';
623 RT_OFFLOAD_API_GROUP_END
625 } // namespace Fortran::runtime::io