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
{
20 // Max size of a group, symbol or component identifier that can appear in
21 // NAMELIST input, plus a byte for NUL termination.
22 static constexpr std::size_t nameBufferSize
{201};
24 static inline char32_t
GetComma(IoStatementState
&io
) {
25 return io
.mutableModes().editingFlags
& decimalComma
? char32_t
{';'}
29 bool IONAME(OutputNamelist
)(Cookie cookie
, const NamelistGroup
&group
) {
30 IoStatementState
&io
{*cookie
};
31 io
.CheckFormattedStmtType
<Direction::Output
>("OutputNamelist");
32 io
.mutableModes().inNamelist
= true;
33 char comma
{static_cast<char>(GetComma(io
))};
34 ConnectionState
&connection
{io
.GetConnectionState()};
35 // Internal functions to advance records and convert case
36 const auto EmitWithAdvance
{[&](char ch
) -> bool {
37 return (!connection
.NeedAdvance(1) || io
.AdvanceRecord()) &&
38 EmitAscii(io
, &ch
, 1);
40 const auto EmitUpperCase
{[&](const char *str
) -> bool {
41 if (connection
.NeedAdvance(std::strlen(str
)) &&
42 !(io
.AdvanceRecord() && EmitAscii(io
, " ", 1))) {
46 char up
{*str
>= 'a' && *str
<= 'z' ? static_cast<char>(*str
- 'a' + 'A')
48 if (!EmitAscii(io
, &up
, 1)) {
55 if (!(EmitWithAdvance('&') && EmitUpperCase(group
.groupName
))) {
58 auto *listOutput
{io
.get_if
<ListDirectedStatementState
<Direction::Output
>>()};
59 for (std::size_t j
{0}; j
< group
.items
; ++j
) {
61 const NamelistGroup::Item
&item
{group
.item
[j
]};
63 listOutput
->set_lastWasUndelimitedCharacter(false);
65 if (!EmitWithAdvance(j
== 0 ? ' ' : comma
) || !EmitUpperCase(item
.name
) ||
66 !EmitWithAdvance('=')) {
69 if (const auto *addendum
{item
.descriptor
.Addendum()};
70 addendum
&& addendum
->derivedType()) {
71 const NonTbpDefinedIoTable
*table
{group
.nonTbpDefinedIo
};
72 if (!IONAME(OutputDerivedType
)(cookie
, item
.descriptor
, table
)) {
75 } else if (!descr::DescriptorIO
<Direction::Output
>(io
, item
.descriptor
)) {
80 return EmitWithAdvance('/');
83 static constexpr bool IsLegalIdStart(char32_t ch
) {
84 return (ch
>= 'A' && ch
<= 'Z') || (ch
>= 'a' && ch
<= 'z') || ch
== '_' ||
85 ch
== '@' || ch
== '$';
88 static constexpr bool IsLegalIdChar(char32_t ch
) {
89 return IsLegalIdStart(ch
) || (ch
>= '0' && ch
<= '9');
92 static constexpr char NormalizeIdChar(char32_t ch
) {
93 return static_cast<char>(ch
>= 'A' && ch
<= 'Z' ? ch
- 'A' + 'a' : ch
);
96 static bool GetLowerCaseName(
97 IoStatementState
&io
, char buffer
[], std::size_t maxLength
) {
98 std::size_t byteLength
{0};
99 if (auto ch
{io
.GetNextNonBlank(byteLength
)}) {
100 if (IsLegalIdStart(*ch
)) {
103 buffer
[j
] = NormalizeIdChar(*ch
);
104 io
.HandleRelativePosition(byteLength
);
105 ch
= io
.GetCurrentChar(byteLength
);
106 } while (++j
< maxLength
&& ch
&& IsLegalIdChar(*ch
));
108 if (j
<= maxLength
) {
111 io
.GetIoErrorHandler().SignalError(
112 "Identifier '%s...' in NAMELIST input group is too long", buffer
);
118 static std::optional
<SubscriptValue
> GetSubscriptValue(IoStatementState
&io
) {
119 std::optional
<SubscriptValue
> value
;
120 std::size_t byteCount
{0};
121 std::optional
<char32_t
> ch
{io
.GetCurrentChar(byteCount
)};
122 bool negate
{ch
&& *ch
== '-'};
123 if ((ch
&& *ch
== '+') || negate
) {
124 io
.HandleRelativePosition(byteCount
);
125 ch
= io
.GetCurrentChar(byteCount
);
127 bool overflow
{false};
128 while (ch
&& *ch
>= '0' && *ch
<= '9') {
129 SubscriptValue was
{value
.value_or(0)};
130 overflow
|= was
>= std::numeric_limits
<SubscriptValue
>::max() / 10;
131 value
= 10 * was
+ *ch
- '0';
132 io
.HandleRelativePosition(byteCount
);
133 ch
= io
.GetCurrentChar(byteCount
);
136 io
.GetIoErrorHandler().SignalError(
137 "NAMELIST input subscript value overflow");
144 io
.HandleRelativePosition(-byteCount
); // give back '-' with no digits
150 static bool HandleSubscripts(IoStatementState
&io
, Descriptor
&desc
,
151 const Descriptor
&source
, const char *name
) {
152 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
153 // Allow for blanks in subscripts; they're nonstandard, but not
154 // ambiguous within the parentheses.
155 SubscriptValue lower
[maxRank
], upper
[maxRank
], stride
[maxRank
];
157 std::size_t contiguousStride
{source
.ElementBytes()};
159 std::size_t byteCount
{0};
160 std::optional
<char32_t
> ch
{io
.GetNextNonBlank(byteCount
)};
161 char32_t comma
{GetComma(io
)};
162 for (; ch
&& *ch
!= ')'; ++j
) {
163 SubscriptValue dimLower
{0}, dimUpper
{0}, dimStride
{0};
164 if (j
< maxRank
&& j
< source
.rank()) {
165 const Dimension
&dim
{source
.GetDimension(j
)};
166 dimLower
= dim
.LowerBound();
167 dimUpper
= dim
.UpperBound();
169 dim
.ByteStride() / std::max
<SubscriptValue
>(contiguousStride
, 1);
170 contiguousStride
*= dim
.Extent();
173 "Too many subscripts for rank-%d NAMELIST group item '%s'",
174 source
.rank(), name
);
177 if (auto low
{GetSubscriptValue(io
)}) {
178 if (*low
< dimLower
|| (dimUpper
>= dimLower
&& *low
> dimUpper
)) {
180 handler
.SignalError("Subscript %jd out of range %jd..%jd in NAMELIST "
181 "group item '%s' dimension %d",
182 static_cast<std::intmax_t>(*low
),
183 static_cast<std::intmax_t>(dimLower
),
184 static_cast<std::intmax_t>(dimUpper
), name
, j
+ 1);
190 ch
= io
.GetNextNonBlank(byteCount
);
192 if (ch
&& *ch
== ':') {
193 io
.HandleRelativePosition(byteCount
);
194 ch
= io
.GetNextNonBlank(byteCount
);
195 if (auto high
{GetSubscriptValue(io
)}) {
196 if (*high
> dimUpper
) {
199 "Subscript triplet upper bound %jd out of range (>%jd) in "
200 "NAMELIST group item '%s' dimension %d",
201 static_cast<std::intmax_t>(*high
),
202 static_cast<std::intmax_t>(dimUpper
), name
, j
+ 1);
208 ch
= io
.GetNextNonBlank(byteCount
);
210 if (ch
&& *ch
== ':') {
211 io
.HandleRelativePosition(byteCount
);
212 ch
= io
.GetNextNonBlank(byteCount
);
213 if (auto str
{GetSubscriptValue(io
)}) {
215 ch
= io
.GetNextNonBlank(byteCount
);
222 if (ch
&& *ch
== comma
) {
223 io
.HandleRelativePosition(byteCount
);
224 ch
= io
.GetNextNonBlank(byteCount
);
229 stride
[j
] = dimStride
;
233 if (ch
&& *ch
== ')') {
234 io
.HandleRelativePosition(byteCount
);
235 if (desc
.EstablishPointerSection(source
, lower
, upper
, stride
)) {
239 "Bad subscripts for NAMELIST input group item '%s'", name
);
243 "Bad subscripts (missing ')') for NAMELIST input group item '%s'",
250 static bool HandleSubstring(
251 IoStatementState
&io
, Descriptor
&desc
, const char *name
) {
252 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
253 auto pair
{desc
.type().GetCategoryAndKind()};
254 if (!pair
|| pair
->first
!= TypeCategory::Character
) {
255 handler
.SignalError("Substring reference to non-character item '%s'", name
);
258 int kind
{pair
->second
};
259 SubscriptValue chars
{static_cast<SubscriptValue
>(desc
.ElementBytes()) / kind
};
260 // Allow for blanks in substring bounds; they're nonstandard, but not
261 // ambiguous within the parentheses.
262 std::optional
<SubscriptValue
> lower
, upper
;
263 std::size_t byteCount
{0};
264 std::optional
<char32_t
> ch
{io
.GetNextNonBlank(byteCount
)};
269 lower
= GetSubscriptValue(io
);
270 ch
= io
.GetNextNonBlank(byteCount
);
273 if (ch
&& ch
== ':') {
274 io
.HandleRelativePosition(byteCount
);
275 ch
= io
.GetNextNonBlank(byteCount
);
280 upper
= GetSubscriptValue(io
);
281 ch
= io
.GetNextNonBlank(byteCount
);
285 if (ch
&& *ch
== ')') {
286 io
.HandleRelativePosition(byteCount
);
287 if (lower
&& upper
) {
288 if (*lower
> *upper
) {
289 // An empty substring, whatever the values are
290 desc
.raw().elem_len
= 0;
293 if (*lower
>= 1 || *upper
<= chars
) {
294 // Offset the base address & adjust the element byte length
295 desc
.raw().elem_len
= (*upper
- *lower
+ 1) * kind
;
296 desc
.set_base_addr(reinterpret_cast<void *>(
297 reinterpret_cast<char *>(desc
.raw().base_addr
) +
298 kind
* (*lower
- 1)));
303 "Bad substring bounds for NAMELIST input group item '%s'", name
);
306 "Bad substring (missing ')') for NAMELIST input group item '%s'", name
);
311 static bool HandleComponent(IoStatementState
&io
, Descriptor
&desc
,
312 const Descriptor
&source
, const char *name
) {
313 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
314 char compName
[nameBufferSize
];
315 if (GetLowerCaseName(io
, compName
, sizeof compName
)) {
316 const DescriptorAddendum
*addendum
{source
.Addendum()};
317 if (const typeInfo::DerivedType
*
318 type
{addendum
? addendum
->derivedType() : nullptr}) {
319 if (const typeInfo::Component
*
320 comp
{type
->FindDataComponent(compName
, std::strlen(compName
))}) {
321 bool createdDesc
{false};
322 if (comp
->rank() > 0 && source
.rank() > 0) {
323 // If base and component are both arrays, the component name
324 // must be followed by subscripts; process them now.
325 std::size_t byteCount
{0};
326 if (std::optional
<char32_t
> next
{io
.GetNextNonBlank(byteCount
)};
327 next
&& *next
== '(') {
328 io
.HandleRelativePosition(byteCount
); // skip over '('
329 StaticDescriptor
<maxRank
, true, 16> staticDesc
;
330 Descriptor
&tmpDesc
{staticDesc
.descriptor()};
331 comp
->CreatePointerDescriptor(tmpDesc
, source
, handler
);
332 if (!HandleSubscripts(io
, desc
, tmpDesc
, compName
)) {
339 comp
->CreatePointerDescriptor(desc
, source
, handler
);
341 if (source
.rank() > 0) {
342 if (desc
.rank() > 0) {
344 "NAMELIST component reference '%%%s' of input group "
345 "item %s cannot be an array when its base is not scalar",
349 desc
.raw().rank
= source
.rank();
350 for (int j
{0}; j
< source
.rank(); ++j
) {
351 const auto &srcDim
{source
.GetDimension(j
)};
353 .SetBounds(1, srcDim
.UpperBound())
354 .SetByteStride(srcDim
.ByteStride());
360 "NAMELIST component reference '%%%s' of input group item %s is not "
361 "a component of its derived type",
364 } else if (source
.type().IsDerived()) {
365 handler
.Crash("Derived type object '%s' in NAMELIST is missing its "
366 "derived type information!",
369 handler
.SignalError("NAMELIST component reference '%%%s' of input group "
370 "item %s for non-derived type",
374 handler
.SignalError("NAMELIST component reference of input group item %s "
375 "has no name after '%%'",
381 // Advance to the terminal '/' of a namelist group.
382 static void SkipNamelistGroup(IoStatementState
&io
) {
383 std::size_t byteCount
{0};
384 while (auto ch
{io
.GetNextNonBlank(byteCount
)}) {
385 io
.HandleRelativePosition(byteCount
);
388 } else if (*ch
== '\'' || *ch
== '"') {
389 // Skip quoted character literal
392 if ((ch
= io
.GetCurrentChar(byteCount
))) {
393 io
.HandleRelativePosition(byteCount
);
397 } else if (!io
.AdvanceRecord()) {
405 bool IONAME(InputNamelist
)(Cookie cookie
, const NamelistGroup
&group
) {
406 IoStatementState
&io
{*cookie
};
407 io
.CheckFormattedStmtType
<Direction::Input
>("InputNamelist");
408 io
.mutableModes().inNamelist
= true;
409 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
410 auto *listInput
{io
.get_if
<ListDirectedStatementState
<Direction::Input
>>()};
411 RUNTIME_CHECK(handler
, listInput
!= nullptr);
412 // Find this namelist group's header in the input
413 io
.BeginReadingRecord();
414 std::optional
<char32_t
> next
;
415 char name
[nameBufferSize
];
416 RUNTIME_CHECK(handler
, group
.groupName
!= nullptr);
417 char32_t comma
{GetComma(io
)};
418 std::size_t byteCount
{0};
420 next
= io
.GetNextNonBlank(byteCount
);
421 while (next
&& *next
!= '&') {
422 // Extension: comment lines without ! before namelist groups
423 if (!io
.AdvanceRecord()) {
426 next
= io
.GetNextNonBlank(byteCount
);
435 "NAMELIST input group does not begin with '&' (at '%lc')", *next
);
438 io
.HandleRelativePosition(byteCount
);
439 if (!GetLowerCaseName(io
, name
, sizeof name
)) {
440 handler
.SignalError("NAMELIST input group has no name");
443 if (std::strcmp(group
.groupName
, name
) == 0) {
446 SkipNamelistGroup(io
);
448 // Read the group's items
450 next
= io
.GetNextNonBlank(byteCount
);
451 if (!next
|| *next
== '/') {
454 if (!GetLowerCaseName(io
, name
, sizeof name
)) {
456 "NAMELIST input group '%s' was not terminated at '%c'",
457 group
.groupName
, static_cast<char>(*next
));
460 std::size_t itemIndex
{0};
461 for (; itemIndex
< group
.items
; ++itemIndex
) {
462 if (std::strcmp(name
, group
.item
[itemIndex
].name
) == 0) {
466 if (itemIndex
>= group
.items
) {
468 "'%s' is not an item in NAMELIST group '%s'", name
, group
.groupName
);
471 // Handle indexing and components, if any. No spaces are allowed.
472 // A copy of the descriptor is made if necessary.
473 const Descriptor
&itemDescriptor
{group
.item
[itemIndex
].descriptor
};
474 const Descriptor
*useDescriptor
{&itemDescriptor
};
475 StaticDescriptor
<maxRank
, true, 16> staticDesc
[2];
476 int whichStaticDesc
{0};
477 next
= io
.GetCurrentChar(byteCount
);
478 bool hadSubscripts
{false};
479 bool hadSubstring
{false};
480 if (next
&& (*next
== '(' || *next
== '%')) {
482 Descriptor
&mutableDescriptor
{staticDesc
[whichStaticDesc
].descriptor()};
483 whichStaticDesc
^= 1;
484 io
.HandleRelativePosition(byteCount
); // skip over '(' or '%'
486 if (!hadSubstring
&& (hadSubscripts
|| useDescriptor
->rank() == 0)) {
487 mutableDescriptor
= *useDescriptor
;
488 mutableDescriptor
.raw().attribute
= CFI_attribute_pointer
;
489 if (!HandleSubstring(io
, mutableDescriptor
, name
)) {
493 } else if (hadSubscripts
) {
494 handler
.SignalError("Multiple sets of subscripts for item '%s' in "
495 "NAMELIST group '%s'",
496 name
, group
.groupName
);
499 if (!HandleSubscripts(
500 io
, mutableDescriptor
, *useDescriptor
, name
)) {
504 hadSubscripts
= true;
506 if (!HandleComponent(io
, mutableDescriptor
, *useDescriptor
, name
)) {
509 hadSubscripts
= false;
510 hadSubstring
= false;
512 useDescriptor
= &mutableDescriptor
;
513 next
= io
.GetCurrentChar(byteCount
);
514 } while (next
&& (*next
== '(' || *next
== '%'));
517 next
= io
.GetNextNonBlank(byteCount
);
518 if (!next
|| *next
!= '=') {
519 handler
.SignalError("No '=' found after item '%s' in NAMELIST group '%s'",
520 name
, group
.groupName
);
523 io
.HandleRelativePosition(byteCount
);
524 // Read the values into the descriptor. An array can be short.
525 if (const auto *addendum
{useDescriptor
->Addendum()};
526 addendum
&& addendum
->derivedType()) {
527 const NonTbpDefinedIoTable
*table
{group
.nonTbpDefinedIo
};
528 listInput
->ResetForNextNamelistItem(/*inNamelistSequence=*/true);
529 if (!IONAME(InputDerivedType
)(cookie
, *useDescriptor
, table
)) {
533 listInput
->ResetForNextNamelistItem(useDescriptor
->rank() > 0);
534 if (!descr::DescriptorIO
<Direction::Input
>(io
, *useDescriptor
)) {
538 next
= io
.GetNextNonBlank(byteCount
);
539 if (next
&& *next
== comma
) {
540 io
.HandleRelativePosition(byteCount
);
543 if (!next
|| *next
!= '/') {
545 "No '/' found after NAMELIST group '%s'", group
.groupName
);
548 io
.HandleRelativePosition(byteCount
);
552 bool IsNamelistNameOrSlash(IoStatementState
&io
) {
554 io
.get_if
<ListDirectedStatementState
<Direction::Input
>>()}) {
555 if (listInput
->inNamelistSequence()) {
556 SavedPosition savedPosition
{io
};
557 std::size_t byteCount
{0};
558 if (auto ch
{io
.GetNextNonBlank(byteCount
)}) {
559 if (IsLegalIdStart(*ch
)) {
561 io
.HandleRelativePosition(byteCount
);
562 ch
= io
.GetCurrentChar(byteCount
);
563 } while (ch
&& IsLegalIdChar(*ch
));
564 ch
= io
.GetNextNonBlank(byteCount
);
565 // TODO: how to deal with NaN(...) ambiguity?
566 return ch
&& (*ch
== '=' || *ch
== '(' || *ch
== '%');
576 } // namespace Fortran::runtime::io