694 lines · cpp
1//===-- lib/runtime/namelist.cpp --------------------------------*- C++ -*-===//2//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "flang-rt/runtime/namelist.h"10#include "descriptor-io.h"11#include "flang-rt/runtime/emit-encoded.h"12#include "flang-rt/runtime/io-stmt.h"13#include "flang-rt/runtime/type-info.h"14#include "flang/Runtime/io-api.h"15#include <algorithm>16#include <cstring>17#include <limits>18 19namespace Fortran::runtime::io {20 21RT_VAR_GROUP_BEGIN22// Max size of a group, symbol or component identifier that can appear in23// NAMELIST input, plus a byte for NUL termination.24static constexpr RT_CONST_VAR_ATTRS std::size_t nameBufferSize{201};25RT_VAR_GROUP_END26 27RT_OFFLOAD_API_GROUP_BEGIN28 29static inline RT_API_ATTRS char32_t GetComma(IoStatementState &io) {30 return io.mutableModes().GetSeparatorChar();31}32 33bool 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.40 41 // Internal function to advance records and convert case42 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(runtime::strlen(str) + (suffix != ' ')) &&48 !(io.AdvanceRecord() && EmitAscii(io, " ", 1)))) {49 return false;50 }51 for (; *str; ++str) {52 char up{*str >= 'a' && *str <= 'z' ? static_cast<char>(*str - 'a' + 'A')53 : *str};54 if (!EmitAscii(io, &up, 1)) {55 return false;56 }57 }58 return suffix == ' ' || EmitAscii(io, &suffix, 1);59 };60 // &GROUP61 if (!EmitUpperCase(" &", 2, group.groupName, ' ')) {62 return false;63 }64 auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()};65 char comma{static_cast<char>(GetComma(io))};66 char prefix{' '};67 for (std::size_t j{0}; j < group.items; ++j) {68 // [,]ITEM=...69 const NamelistGroup::Item &item{group.item[j]};70 if (listOutput) {71 listOutput->set_lastWasUndelimitedCharacter(false);72 }73 if (!EmitUpperCase(&prefix, 1, item.name, '=')) {74 return false;75 }76 prefix = comma;77 if (const auto *addendum{item.descriptor.Addendum()};78 addendum && addendum->derivedType()) {79 const NonTbpDefinedIoTable *table{group.nonTbpDefinedIo};80 if (!IONAME(OutputDerivedType)(cookie, item.descriptor, table)) {81 return false;82 }83 } else if (!descr::DescriptorIO<Direction::Output>(io, item.descriptor)) {84 return false;85 }86 }87 // terminal /88 return EmitUpperCase("/", 1, "", ' ');89}90 91static constexpr RT_API_ATTRS bool IsLegalIdStart(char32_t ch) {92 return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' ||93 ch == '@';94}95 96static constexpr RT_API_ATTRS bool IsLegalIdChar(char32_t ch) {97 return IsLegalIdStart(ch) || (ch >= '0' && ch <= '9');98}99 100static constexpr RT_API_ATTRS char NormalizeIdChar(char32_t ch) {101 return static_cast<char>(ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch);102}103 104static RT_API_ATTRS bool GetLowerCaseName(IoStatementState &io, char buffer[],105 std::size_t maxLength, bool crashIfTooLong = true) {106 std::size_t byteLength{0};107 if (auto ch{io.GetNextNonBlank(byteLength)}) {108 if (IsLegalIdStart(*ch)) {109 std::size_t j{0};110 do {111 buffer[j] = NormalizeIdChar(*ch);112 io.HandleRelativePosition(byteLength);113 ch = io.GetCurrentChar(byteLength);114 } while (++j < maxLength && ch && IsLegalIdChar(*ch));115 buffer[j++] = '\0';116 if (j <= maxLength) {117 return true;118 }119 if (crashIfTooLong) {120 io.GetIoErrorHandler().SignalError(121 "Identifier '%s...' in NAMELIST input group is too long", buffer);122 }123 }124 }125 return false;126}127 128static RT_API_ATTRS common::optional<SubscriptValue> GetSubscriptValue(129 IoStatementState &io) {130 common::optional<SubscriptValue> value;131 std::size_t byteCount{0};132 common::optional<char32_t> ch{io.GetCurrentChar(byteCount)};133 bool negate{ch && *ch == '-'};134 if ((ch && *ch == '+') || negate) {135 io.HandleRelativePosition(byteCount);136 ch = io.GetCurrentChar(byteCount);137 }138 bool overflow{false};139 while (ch && *ch >= '0' && *ch <= '9') {140 SubscriptValue was{value.value_or(0)};141 overflow |= was >= std::numeric_limits<SubscriptValue>::max() / 10;142 value = 10 * was + *ch - '0';143 io.HandleRelativePosition(byteCount);144 ch = io.GetCurrentChar(byteCount);145 }146 if (overflow) {147 io.GetIoErrorHandler().SignalError(148 "NAMELIST input subscript value overflow");149 return common::nullopt;150 }151 if (negate) {152 if (value) {153 return -*value;154 } else {155 io.HandleRelativePosition(-byteCount); // give back '-' with no digits156 }157 }158 return value;159}160 161static RT_API_ATTRS bool HandleSubscripts(IoStatementState &io,162 Descriptor &desc, const Descriptor &source, const char *name) {163 IoErrorHandler &handler{io.GetIoErrorHandler()};164 // Allow for blanks in subscripts; they're nonstandard, but not165 // ambiguous within the parentheses.166 SubscriptValue lower[maxRank], upper[maxRank], stride[maxRank];167 int j{0};168 std::size_t contiguousStride{source.ElementBytes()};169 bool ok{true};170 std::size_t byteCount{0};171 common::optional<char32_t> ch{io.GetNextNonBlank(byteCount)};172 char32_t comma{GetComma(io)};173 for (; ch && *ch != ')'; ++j) {174 SubscriptValue dimLower{0}, dimUpper{0}, dimStride{0};175 if (j < maxRank && j < source.rank()) {176 const Dimension &dim{source.GetDimension(j)};177 dimLower = dim.LowerBound();178 dimUpper = dim.UpperBound();179 dimStride =180 dim.ByteStride() / std::max<SubscriptValue>(contiguousStride, 1);181 contiguousStride *= dim.Extent();182 } else if (ok) {183 handler.SignalError(184 "Too many subscripts for rank-%d NAMELIST group item '%s'",185 source.rank(), name);186 ok = false;187 }188 if (auto low{GetSubscriptValue(io)}) {189 if (*low < dimLower || (dimUpper >= dimLower && *low > dimUpper)) {190 if (ok) {191 handler.SignalError("Subscript %jd out of range %jd..%jd in NAMELIST "192 "group item '%s' dimension %d",193 static_cast<std::intmax_t>(*low),194 static_cast<std::intmax_t>(dimLower),195 static_cast<std::intmax_t>(dimUpper), name, j + 1);196 ok = false;197 }198 } else {199 dimLower = *low;200 }201 ch = io.GetNextNonBlank(byteCount);202 }203 if (ch && *ch == ':') {204 io.HandleRelativePosition(byteCount);205 ch = io.GetNextNonBlank(byteCount);206 if (auto high{GetSubscriptValue(io)}) {207 if (*high > dimUpper) {208 if (ok) {209 handler.SignalError(210 "Subscript triplet upper bound %jd out of range (>%jd) in "211 "NAMELIST group item '%s' dimension %d",212 static_cast<std::intmax_t>(*high),213 static_cast<std::intmax_t>(dimUpper), name, j + 1);214 ok = false;215 }216 } else {217 dimUpper = *high;218 }219 ch = io.GetNextNonBlank(byteCount);220 }221 if (ch && *ch == ':') {222 io.HandleRelativePosition(byteCount);223 ch = io.GetNextNonBlank(byteCount);224 if (auto str{GetSubscriptValue(io)}) {225 dimStride = *str;226 ch = io.GetNextNonBlank(byteCount);227 }228 }229 } else { // scalar230 dimUpper = dimLower;231 dimStride = 0;232 }233 if (ch && *ch == comma) {234 io.HandleRelativePosition(byteCount);235 ch = io.GetNextNonBlank(byteCount);236 }237 if (ok) {238 lower[j] = dimLower;239 upper[j] = dimUpper;240 stride[j] = dimStride;241 }242 }243 if (ok) {244 if (ch && *ch == ')') {245 io.HandleRelativePosition(byteCount);246 if (desc.EstablishPointerSection(source, lower, upper, stride)) {247 return true;248 } else {249 handler.SignalError(250 "Bad subscripts for NAMELIST input group item '%s'", name);251 }252 } else {253 handler.SignalError(254 "Bad subscripts (missing ')') for NAMELIST input group item '%s'",255 name);256 }257 }258 return false;259}260 261static RT_API_ATTRS bool HasDefinedIoSubroutine(common::DefinedIo definedIo,262 typeInfo::SpecialBinding::Which specialBinding,263 const typeInfo::DerivedType *derivedType,264 const NonTbpDefinedIoTable *table) {265 for (; derivedType; derivedType = derivedType->GetParentType()) {266 if ((table && table->Find(*derivedType, definedIo) != nullptr) ||267 derivedType->FindSpecialBinding(specialBinding)) {268 return true;269 }270 }271 return false;272}273 274static RT_API_ATTRS bool HasDefinedIoSubroutine(common::DefinedIo definedIo,275 typeInfo::SpecialBinding::Which specialBinding,276 const Descriptor &descriptor, const NonTbpDefinedIoTable *table) {277 const DescriptorAddendum *addendum{descriptor.Addendum()};278 return addendum &&279 HasDefinedIoSubroutine(280 definedIo, specialBinding, addendum->derivedType(), table);281}282 283static RT_API_ATTRS void StorageSequenceExtension(Descriptor &desc,284 const Descriptor &source, const io::NonTbpDefinedIoTable *table) {285 // Support the near-universal extension of NAMELIST input into a286 // designatable storage sequence identified by its initial scalar array287 // element. For example, treat "A(1) = 1. 2. 3." as if it had been288 // "A(1:) = 1. 2. 3.".289 // (But don't do this for derived types with defined formatted READs,290 // since they might do non-list-directed input that won't stop at the291 // next namelist input item name.)292 if (desc.rank() == 0 && (source.rank() == 1 || source.IsContiguous()) &&293 !HasDefinedIoSubroutine(common::DefinedIo::ReadFormatted,294 typeInfo::SpecialBinding::Which::ReadFormatted, desc, table)) {295 if (auto stride{source.rank() == 1296 ? source.GetDimension(0).ByteStride()297 : static_cast<SubscriptValue>(source.ElementBytes())};298 stride != 0) {299 common::optional<DescriptorAddendum> savedAddendum;300 if (const DescriptorAddendum *addendum{desc.Addendum()}) {301 // Preserve a copy of the addendum, if any, before clobbering it302 savedAddendum.emplace(*addendum);303 }304 desc.raw().attribute = CFI_attribute_pointer;305 desc.raw().rank = 1;306 desc.GetDimension(0)307 .SetBounds(1,308 source.Elements() -309 ((source.OffsetElement() - desc.OffsetElement()) / stride))310 .SetByteStride(stride);311 if (savedAddendum) {312 *desc.Addendum() = *savedAddendum;313 }314 }315 }316}317 318static RT_API_ATTRS bool HandleSubstring(319 IoStatementState &io, Descriptor &desc, const char *name) {320 IoErrorHandler &handler{io.GetIoErrorHandler()};321 auto pair{desc.type().GetCategoryAndKind()};322 if (!pair || pair->first != TypeCategory::Character) {323 handler.SignalError("Substring reference to non-character item '%s'", name);324 return false;325 }326 int kind{pair->second};327 SubscriptValue chars{static_cast<SubscriptValue>(desc.ElementBytes()) / kind};328 // Allow for blanks in substring bounds; they're nonstandard, but not329 // ambiguous within the parentheses.330 common::optional<SubscriptValue> lower, upper;331 std::size_t byteCount{0};332 common::optional<char32_t> ch{io.GetNextNonBlank(byteCount)};333 if (ch) {334 if (*ch == ':') {335 lower = 1;336 } else {337 lower = GetSubscriptValue(io);338 ch = io.GetNextNonBlank(byteCount);339 }340 }341 if (ch && *ch == ':') {342 io.HandleRelativePosition(byteCount);343 ch = io.GetNextNonBlank(byteCount);344 if (ch) {345 if (*ch == ')') {346 upper = chars;347 } else {348 upper = GetSubscriptValue(io);349 ch = io.GetNextNonBlank(byteCount);350 }351 }352 }353 if (ch && *ch == ')') {354 io.HandleRelativePosition(byteCount);355 if (lower && upper) {356 if (*lower > *upper) {357 // An empty substring, whatever the values are358 desc.raw().elem_len = 0;359 return true;360 }361 if (*lower >= 1 && *upper <= chars) {362 // Offset the base address & adjust the element byte length363 desc.raw().elem_len = (*upper - *lower + 1) * kind;364 desc.set_base_addr(reinterpret_cast<void *>(365 reinterpret_cast<char *>(desc.raw().base_addr) +366 kind * (*lower - 1)));367 return true;368 }369 }370 handler.SignalError(371 "Bad substring bounds for NAMELIST input group item '%s'", name);372 } else {373 handler.SignalError(374 "Bad substring (missing ')') for NAMELIST input group item '%s'", name);375 }376 return false;377}378 379static RT_API_ATTRS bool HandleComponent(IoStatementState &io, Descriptor &desc,380 const Descriptor &source, const char *name) {381 IoErrorHandler &handler{io.GetIoErrorHandler()};382 char compName[nameBufferSize];383 if (GetLowerCaseName(io, compName, sizeof compName)) {384 const DescriptorAddendum *addendum{source.Addendum()};385 if (const typeInfo::DerivedType *386 type{addendum ? addendum->derivedType() : nullptr}) {387 if (const typeInfo::Component *comp{388 type->FindDataComponent(compName, runtime::strlen(compName))}) {389 bool createdDesc{false};390 if (comp->rank() > 0 && source.rank() > 0) {391 // If base and component are both arrays, the component name392 // must be followed by subscripts; process them now.393 std::size_t byteCount{0};394 if (common::optional<char32_t> next{io.GetNextNonBlank(byteCount)};395 next && *next == '(') {396 io.HandleRelativePosition(byteCount); // skip over '('397 StaticDescriptor<maxRank, true, 16> staticDesc;398 Descriptor &tmpDesc{staticDesc.descriptor()};399 comp->CreatePointerDescriptor(tmpDesc, source, handler);400 if (!HandleSubscripts(io, desc, tmpDesc, compName)) {401 return false;402 }403 createdDesc = true;404 }405 }406 if (!createdDesc) {407 comp->CreatePointerDescriptor(desc, source, handler);408 }409 if (source.rank() > 0) {410 if (desc.rank() > 0) {411 handler.SignalError(412 "NAMELIST component reference '%%%s' of input group "413 "item %s cannot be an array when its base is not scalar",414 compName, name);415 return false;416 }417 desc.raw().rank = source.rank();418 for (int j{0}; j < source.rank(); ++j) {419 const auto &srcDim{source.GetDimension(j)};420 desc.GetDimension(j)421 .SetBounds(1, srcDim.UpperBound())422 .SetByteStride(srcDim.ByteStride());423 }424 }425 return true;426 } else {427 handler.SignalError(428 "NAMELIST component reference '%%%s' of input group item %s is not "429 "a component of its derived type",430 compName, name);431 }432 } else if (source.type().IsDerived()) {433 handler.Crash("Derived type object '%s' in NAMELIST is missing its "434 "derived type information!",435 name);436 } else {437 handler.SignalError("NAMELIST component reference '%%%s' of input group "438 "item %s for non-derived type",439 compName, name);440 }441 } else {442 handler.SignalError("NAMELIST component reference of input group item %s "443 "has no name after '%%'",444 name);445 }446 return false;447}448 449// Advance to the terminal '/' of a namelist group or leading '&'/'$'450// of the next.451static RT_API_ATTRS void SkipNamelistGroup(IoStatementState &io) {452 std::size_t byteCount{0};453 while (auto ch{io.GetNextNonBlank(byteCount)}) {454 io.HandleRelativePosition(byteCount);455 if (*ch == '/' || *ch == '&' || *ch == '$') {456 break;457 } else if (*ch == '\'' || *ch == '"') {458 // Skip quoted character literal459 char32_t quote{*ch};460 while (true) {461 if ((ch = io.GetCurrentChar(byteCount))) {462 io.HandleRelativePosition(byteCount);463 if (*ch == quote) {464 break;465 }466 } else if (!io.AdvanceRecord()) {467 return;468 }469 }470 }471 }472}473 474bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {475 IoStatementState &io{*cookie};476 io.CheckFormattedStmtType<Direction::Input>("InputNamelist");477 io.mutableModes().inNamelist = true;478 IoErrorHandler &handler{io.GetIoErrorHandler()};479 auto *listInput{io.get_if<ListDirectedStatementState<Direction::Input>>()};480 RUNTIME_CHECK(handler, listInput != nullptr);481 // Find this namelist group's header in the input482 io.BeginReadingRecord();483 common::optional<char32_t> next;484 char name[nameBufferSize];485 RUNTIME_CHECK(handler, group.groupName != nullptr);486 char32_t comma{GetComma(io)};487 std::size_t byteCount{0};488 while (true) {489 next = io.GetNextNonBlank(byteCount);490 while (next && *next != '&' && *next != '$') {491 // Extension: comment lines without ! before namelist groups492 if (!io.AdvanceRecord()) {493 next.reset();494 } else {495 next = io.GetNextNonBlank(byteCount);496 }497 }498 if (!next) {499 handler.SignalEnd();500 return false;501 }502 if (*next != '&' && *next != '$') {503 handler.SignalError(504 "NAMELIST input group does not begin with '&' or '$' (at '%lc')",505 *next);506 return false;507 }508 io.HandleRelativePosition(byteCount);509 if (!GetLowerCaseName(io, name, sizeof name)) {510 handler.SignalError("NAMELIST input group has no name");511 return false;512 }513 if (runtime::strcmp(group.groupName, name) == 0) {514 break; // found it515 }516 SkipNamelistGroup(io);517 }518 // Read the group's items519 while (true) {520 next = io.GetNextNonBlank(byteCount);521 if (!next || *next == '/' || *next == '&' || *next == '$') {522 break;523 }524 if (!GetLowerCaseName(io, name, sizeof name)) {525 handler.SignalError(526 "NAMELIST input group '%s' was not terminated at '%c'",527 group.groupName, static_cast<char>(*next));528 return false;529 }530 std::size_t itemIndex{0};531 for (; itemIndex < group.items; ++itemIndex) {532 if (runtime::strcmp(name, group.item[itemIndex].name) == 0) {533 break;534 }535 }536 if (itemIndex >= group.items) {537 handler.SignalError(538 "'%s' is not an item in NAMELIST group '%s'", name, group.groupName);539 return false;540 }541 // Handle indexing and components, if any. No spaces are allowed.542 // A copy of the descriptor is made if necessary.543 const Descriptor &itemDescriptor{group.item[itemIndex].descriptor};544 const Descriptor *useDescriptor{&itemDescriptor};545 StaticDescriptor<maxRank, true, 16> staticDesc[2];546 int whichStaticDesc{0};547 next = io.GetCurrentChar(byteCount);548 bool hadSubscripts{false};549 bool hadSubstring{false};550 if (next && (*next == '(' || *next == '%')) {551 const Descriptor *lastSubscriptBase{nullptr};552 Descriptor *lastSubscriptDescriptor{nullptr};553 do {554 Descriptor &mutableDescriptor{staticDesc[whichStaticDesc].descriptor()};555 whichStaticDesc ^= 1;556 io.HandleRelativePosition(byteCount); // skip over '(' or '%'557 lastSubscriptDescriptor = nullptr;558 lastSubscriptBase = nullptr;559 if (*next == '(') {560 if (!hadSubstring && (hadSubscripts || useDescriptor->rank() == 0)) {561 mutableDescriptor = *useDescriptor;562 mutableDescriptor.raw().attribute = CFI_attribute_pointer;563 if (!HandleSubstring(io, mutableDescriptor, name)) {564 return false;565 }566 hadSubstring = true;567 } else if (hadSubscripts) {568 handler.SignalError("Multiple sets of subscripts for item '%s' in "569 "NAMELIST group '%s'",570 name, group.groupName);571 return false;572 } else if (HandleSubscripts(573 io, mutableDescriptor, *useDescriptor, name)) {574 lastSubscriptBase = useDescriptor;575 lastSubscriptDescriptor = &mutableDescriptor;576 } else {577 return false;578 }579 hadSubscripts = true;580 } else {581 if (!HandleComponent(io, mutableDescriptor, *useDescriptor, name)) {582 return false;583 }584 hadSubscripts = false;585 hadSubstring = false;586 }587 useDescriptor = &mutableDescriptor;588 next = io.GetCurrentChar(byteCount);589 } while (next && (*next == '(' || *next == '%'));590 if (lastSubscriptDescriptor) {591 StorageSequenceExtension(*lastSubscriptDescriptor, *lastSubscriptBase,592 group.nonTbpDefinedIo);593 }594 }595 // Skip the '='596 next = io.GetNextNonBlank(byteCount);597 if (!next || *next != '=') {598 handler.SignalError("No '=' found after item '%s' in NAMELIST group '%s'",599 name, group.groupName);600 return false;601 }602 io.HandleRelativePosition(byteCount);603 // Read the values into the descriptor. An array can be short.604 if (const auto *addendum{useDescriptor->Addendum()};605 addendum && addendum->derivedType()) {606 const NonTbpDefinedIoTable *table{group.nonTbpDefinedIo};607 listInput->ResetForNextNamelistItem(&group);608 if (!IONAME(InputDerivedType)(cookie, *useDescriptor, table) &&609 handler.InError()) {610 return false;611 }612 } else {613 listInput->ResetForNextNamelistItem(614 useDescriptor->rank() > 0 ? &group : nullptr);615 if (!descr::DescriptorIO<Direction::Input>(io, *useDescriptor) &&616 handler.InError()) {617 return false;618 }619 }620 next = io.GetNextNonBlank(byteCount);621 if (next && *next == comma) {622 io.HandleRelativePosition(byteCount);623 }624 }625 if (next && *next == '/') {626 io.HandleRelativePosition(byteCount);627 if (auto *listInput{628 io.get_if<ListDirectedStatementState<Direction::Input>>()}) {629 // Don't let the namelist's terminal '/' mess up a parent I/O's630 // list-directed input.631 listInput->set_hitSlash(false);632 }633 } else if (*next && (*next == '&' || *next == '$')) {634 // stop at beginning of next group635 } else {636 handler.SignalError(637 "No '/' found after NAMELIST group '%s'", group.groupName);638 return false;639 }640 return true;641}642 643RT_API_ATTRS bool IsNamelistNameOrSlash(IoStatementState &io) {644 auto *listInput{io.get_if<ListDirectedStatementState<Direction::Input>>()};645 if (!listInput || !listInput->namelistGroup()) {646 return false; // not namelist647 }648 SavedPosition savedPosition{io};649 std::size_t byteCount{0};650 auto ch{io.GetNextNonBlank(byteCount)};651 if (!ch) {652 return false;653 } else if (!IsLegalIdStart(*ch)) {654 return *ch == '/' || *ch == '&' || *ch == '$';655 }656 char id[nameBufferSize];657 if (!GetLowerCaseName(io, id, sizeof id, /*crashIfTooLong=*/false)) {658 return true; // long name659 }660 // It looks like a name, but might be "inf" or "nan". Check what661 // follows.662 ch = io.GetNextNonBlank(byteCount);663 if (!ch) {664 return false;665 } else if (*ch == '=' || *ch == '%') {666 return true;667 } else if (*ch != '(') {668 return false;669 } else if (runtime::strcmp(id, "nan") != 0) {670 return true;671 }672 // "nan(" ambiguity673 int depth{1};674 while (true) {675 io.HandleRelativePosition(byteCount);676 ch = io.GetNextNonBlank(byteCount);677 if (depth == 0) {678 // nan(...) followed by '=', '%', or '('?679 break;680 } else if (!ch) {681 return true; // not a valid NaN(...)682 } else if (*ch == '(') {683 ++depth;684 } else if (*ch == ')') {685 --depth;686 }687 }688 return ch && (*ch == '=' || *ch == '%' || *ch == '(');689}690 691RT_OFFLOAD_API_GROUP_END692 693} // namespace Fortran::runtime::io694