1 //===-- runtime/namelist.cpp ----------------------------------------------===// 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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "namelist.h" 10 #include "descriptor-io.h" 11 #include "io-stmt.h" 12 #include "flang/Runtime/io-api.h" 13 #include <algorithm> 14 #include <cstring> 15 #include <limits> 16 17 namespace Fortran::runtime::io { 18 19 // Max size of a group, symbol or component identifier that can appear in 20 // NAMELIST input, plus a byte for NUL termination. 21 static constexpr std::size_t nameBufferSize{201}; 22 23 static inline char32_t GetComma(IoStatementState &io) { 24 return io.mutableModes().editingFlags & decimalComma ? char32_t{';'} 25 : char32_t{','}; 26 } 27 28 bool IONAME(OutputNamelist)(Cookie cookie, const NamelistGroup &group) { 29 IoStatementState &io{*cookie}; 30 io.CheckFormattedStmtType<Direction::Output>("OutputNamelist"); 31 io.mutableModes().inNamelist = true; 32 char comma{static_cast<char>(GetComma(io))}; 33 ConnectionState &connection{io.GetConnectionState()}; 34 // Internal functions to advance records and convert case 35 const auto EmitWithAdvance{[&](char ch) -> bool { 36 return (!connection.NeedAdvance(1) || io.AdvanceRecord()) && 37 io.Emit(&ch, 1); 38 }}; 39 const auto EmitUpperCase{[&](const char *str) -> bool { 40 if (connection.NeedAdvance(std::strlen(str)) && 41 !(io.AdvanceRecord() && io.Emit(" ", 1))) { 42 return false; 43 } 44 for (; *str; ++str) { 45 char up{*str >= 'a' && *str <= 'z' ? static_cast<char>(*str - 'a' + 'A') 46 : *str}; 47 if (!io.Emit(&up, 1)) { 48 return false; 49 } 50 } 51 return true; 52 }}; 53 // &GROUP 54 if (!(EmitWithAdvance('&') && EmitUpperCase(group.groupName))) { 55 return false; 56 } 57 auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()}; 58 for (std::size_t j{0}; j < group.items; ++j) { 59 // [,]ITEM=... 60 const NamelistGroup::Item &item{group.item[j]}; 61 if (listOutput) { 62 listOutput->set_lastWasUndelimitedCharacter(false); 63 } 64 if (!(EmitWithAdvance(j == 0 ? ' ' : comma) && EmitUpperCase(item.name) && 65 EmitWithAdvance('=') && 66 descr::DescriptorIO<Direction::Output>(io, item.descriptor))) { 67 return false; 68 } 69 } 70 // terminal / 71 return EmitWithAdvance('/'); 72 } 73 74 static constexpr bool IsLegalIdStart(char32_t ch) { 75 return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || 76 ch == '@' || ch == '$'; 77 } 78 79 static constexpr bool IsLegalIdChar(char32_t ch) { 80 return IsLegalIdStart(ch) || (ch >= '0' && ch <= '9'); 81 } 82 83 static constexpr char NormalizeIdChar(char32_t ch) { 84 return static_cast<char>(ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch); 85 } 86 87 static bool GetLowerCaseName( 88 IoStatementState &io, char buffer[], std::size_t maxLength) { 89 if (auto ch{io.GetNextNonBlank()}) { 90 if (IsLegalIdStart(*ch)) { 91 std::size_t j{0}; 92 do { 93 buffer[j] = NormalizeIdChar(*ch); 94 io.HandleRelativePosition(1); 95 ch = io.GetCurrentChar(); 96 } while (++j < maxLength && ch && IsLegalIdChar(*ch)); 97 buffer[j++] = '\0'; 98 if (j <= maxLength) { 99 return true; 100 } 101 io.GetIoErrorHandler().SignalError( 102 "Identifier '%s...' in NAMELIST input group is too long", buffer); 103 } 104 } 105 return false; 106 } 107 108 static std::optional<SubscriptValue> GetSubscriptValue(IoStatementState &io) { 109 std::optional<SubscriptValue> value; 110 std::optional<char32_t> ch{io.GetCurrentChar()}; 111 bool negate{ch && *ch == '-'}; 112 if ((ch && *ch == '+') || negate) { 113 io.HandleRelativePosition(1); 114 ch = io.GetCurrentChar(); 115 } 116 bool overflow{false}; 117 while (ch && *ch >= '0' && *ch <= '9') { 118 SubscriptValue was{value.value_or(0)}; 119 overflow |= was >= std::numeric_limits<SubscriptValue>::max() / 10; 120 value = 10 * was + *ch - '0'; 121 io.HandleRelativePosition(1); 122 ch = io.GetCurrentChar(); 123 } 124 if (overflow) { 125 io.GetIoErrorHandler().SignalError( 126 "NAMELIST input subscript value overflow"); 127 return std::nullopt; 128 } 129 if (negate) { 130 if (value) { 131 return -*value; 132 } else { 133 io.HandleRelativePosition(-1); // give back '-' with no digits 134 } 135 } 136 return value; 137 } 138 139 static bool HandleSubscripts(IoStatementState &io, Descriptor &desc, 140 const Descriptor &source, const char *name) { 141 IoErrorHandler &handler{io.GetIoErrorHandler()}; 142 io.HandleRelativePosition(1); // skip '(' 143 // Allow for blanks in subscripts; they're nonstandard, but not 144 // ambiguous within the parentheses. 145 SubscriptValue lower[maxRank], upper[maxRank], stride[maxRank]; 146 int j{0}; 147 std::size_t contiguousStride{source.ElementBytes()}; 148 bool ok{true}; 149 std::optional<char32_t> ch{io.GetNextNonBlank()}; 150 char32_t comma{GetComma(io)}; 151 for (; ch && *ch != ')'; ++j) { 152 SubscriptValue dimLower{0}, dimUpper{0}, dimStride{0}; 153 if (j < maxRank && j < source.rank()) { 154 const Dimension &dim{source.GetDimension(j)}; 155 dimLower = dim.LowerBound(); 156 dimUpper = dim.UpperBound(); 157 dimStride = 158 dim.ByteStride() / std::max<SubscriptValue>(contiguousStride, 1); 159 contiguousStride *= dim.Extent(); 160 } else if (ok) { 161 handler.SignalError( 162 "Too many subscripts for rank-%d NAMELIST group item '%s'", 163 source.rank(), name); 164 ok = false; 165 } 166 if (auto low{GetSubscriptValue(io)}) { 167 if (*low < dimLower || (dimUpper >= dimLower && *low > dimUpper)) { 168 if (ok) { 169 handler.SignalError("Subscript %jd out of range %jd..%jd in NAMELIST " 170 "group item '%s' dimension %d", 171 static_cast<std::intmax_t>(*low), 172 static_cast<std::intmax_t>(dimLower), 173 static_cast<std::intmax_t>(dimUpper), name, j + 1); 174 ok = false; 175 } 176 } else { 177 dimLower = *low; 178 } 179 ch = io.GetNextNonBlank(); 180 } 181 if (ch && *ch == ':') { 182 io.HandleRelativePosition(1); 183 ch = io.GetNextNonBlank(); 184 if (auto high{GetSubscriptValue(io)}) { 185 if (*high > dimUpper) { 186 if (ok) { 187 handler.SignalError( 188 "Subscript triplet upper bound %jd out of range (>%jd) in " 189 "NAMELIST group item '%s' dimension %d", 190 static_cast<std::intmax_t>(*high), 191 static_cast<std::intmax_t>(dimUpper), name, j + 1); 192 ok = false; 193 } 194 } else { 195 dimUpper = *high; 196 } 197 ch = io.GetNextNonBlank(); 198 } 199 if (ch && *ch == ':') { 200 io.HandleRelativePosition(1); 201 ch = io.GetNextNonBlank(); 202 if (auto str{GetSubscriptValue(io)}) { 203 dimStride = *str; 204 ch = io.GetNextNonBlank(); 205 } 206 } 207 } else { // scalar 208 dimUpper = dimLower; 209 dimStride = 0; 210 } 211 if (ch && *ch == comma) { 212 io.HandleRelativePosition(1); 213 ch = io.GetNextNonBlank(); 214 } 215 if (ok) { 216 lower[j] = dimLower; 217 upper[j] = dimUpper; 218 stride[j] = dimStride; 219 } 220 } 221 if (ok) { 222 if (ch && *ch == ')') { 223 io.HandleRelativePosition(1); 224 if (desc.EstablishPointerSection(source, lower, upper, stride)) { 225 return true; 226 } else { 227 handler.SignalError( 228 "Bad subscripts for NAMELIST input group item '%s'", name); 229 } 230 } else { 231 handler.SignalError( 232 "Bad subscripts (missing ')') for NAMELIST input group item '%s'", 233 name); 234 } 235 } 236 return false; 237 } 238 239 static bool HandleSubstring( 240 IoStatementState &io, Descriptor &desc, const char *name) { 241 IoErrorHandler &handler{io.GetIoErrorHandler()}; 242 auto pair{desc.type().GetCategoryAndKind()}; 243 if (!pair || pair->first != TypeCategory::Character) { 244 handler.SignalError("Substring reference to non-character item '%s'", name); 245 return false; 246 } 247 int kind{pair->second}; 248 SubscriptValue chars{static_cast<SubscriptValue>(desc.ElementBytes()) / kind}; 249 // Allow for blanks in substring bounds; they're nonstandard, but not 250 // ambiguous within the parentheses. 251 io.HandleRelativePosition(1); // skip '(' 252 std::optional<SubscriptValue> lower, upper; 253 std::optional<char32_t> ch{io.GetNextNonBlank()}; 254 if (ch) { 255 if (*ch == ':') { 256 lower = 1; 257 } else { 258 lower = GetSubscriptValue(io); 259 ch = io.GetNextNonBlank(); 260 } 261 } 262 if (ch && ch == ':') { 263 io.HandleRelativePosition(1); 264 ch = io.GetNextNonBlank(); 265 if (ch) { 266 if (*ch == ')') { 267 upper = chars; 268 } else { 269 upper = GetSubscriptValue(io); 270 ch = io.GetNextNonBlank(); 271 } 272 } 273 } 274 if (ch && *ch == ')') { 275 io.HandleRelativePosition(1); 276 if (lower && upper) { 277 if (*lower > *upper) { 278 // An empty substring, whatever the values are 279 desc.raw().elem_len = 0; 280 return true; 281 } 282 if (*lower >= 1 || *upper <= chars) { 283 // Offset the base address & adjust the element byte length 284 desc.raw().elem_len = (*upper - *lower + 1) * kind; 285 desc.set_base_addr(reinterpret_cast<void *>( 286 reinterpret_cast<char *>(desc.raw().base_addr) + 287 kind * (*lower - 1))); 288 return true; 289 } 290 } 291 handler.SignalError( 292 "Bad substring bounds for NAMELIST input group item '%s'", name); 293 } else { 294 handler.SignalError( 295 "Bad substring (missing ')') for NAMELIST input group item '%s'", name); 296 } 297 return false; 298 } 299 300 static bool HandleComponent(IoStatementState &io, Descriptor &desc, 301 const Descriptor &source, const char *name) { 302 IoErrorHandler &handler{io.GetIoErrorHandler()}; 303 io.HandleRelativePosition(1); // skip '%' 304 char compName[nameBufferSize]; 305 if (GetLowerCaseName(io, compName, sizeof compName)) { 306 const DescriptorAddendum *addendum{source.Addendum()}; 307 if (const typeInfo::DerivedType * 308 type{addendum ? addendum->derivedType() : nullptr}) { 309 if (const typeInfo::Component * 310 comp{type->FindDataComponent(compName, std::strlen(compName))}) { 311 comp->CreatePointerDescriptor(desc, source, handler); 312 return true; 313 } else { 314 handler.SignalError( 315 "NAMELIST component reference '%%%s' of input group item %s is not " 316 "a component of its derived type", 317 compName, name); 318 } 319 } else if (source.type().IsDerived()) { 320 handler.Crash("Derived type object '%s' in NAMELIST is missing its " 321 "derived type information!", 322 name); 323 } else { 324 handler.SignalError("NAMELIST component reference '%%%s' of input group " 325 "item %s for non-derived type", 326 compName, name); 327 } 328 } else { 329 handler.SignalError("NAMELIST component reference of input group item %s " 330 "has no name after '%'", 331 name); 332 } 333 return false; 334 } 335 336 // Advance to the terminal '/' of a namelist group. 337 static void SkipNamelistGroup(IoStatementState &io) { 338 while (auto ch{io.GetNextNonBlank()}) { 339 io.HandleRelativePosition(1); 340 if (*ch == '/') { 341 break; 342 } else if (*ch == '\'' || *ch == '"') { 343 // Skip quoted character literal 344 char32_t quote{*ch}; 345 while (true) { 346 if ((ch = io.GetCurrentChar())) { 347 io.HandleRelativePosition(1); 348 if (*ch == quote) { 349 break; 350 } 351 } else if (!io.AdvanceRecord()) { 352 return; 353 } 354 } 355 } 356 } 357 } 358 359 bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { 360 IoStatementState &io{*cookie}; 361 io.CheckFormattedStmtType<Direction::Input>("InputNamelist"); 362 io.mutableModes().inNamelist = true; 363 IoErrorHandler &handler{io.GetIoErrorHandler()}; 364 auto *listInput{io.get_if<ListDirectedStatementState<Direction::Input>>()}; 365 RUNTIME_CHECK(handler, listInput != nullptr); 366 // Find this namelist group's header in the input 367 io.BeginReadingRecord(); 368 std::optional<char32_t> next; 369 char name[nameBufferSize]; 370 RUNTIME_CHECK(handler, group.groupName != nullptr); 371 char32_t comma{GetComma(io)}; 372 while (true) { 373 next = io.GetNextNonBlank(); 374 while (next && *next != '&') { 375 // Extension: comment lines without ! before namelist groups 376 if (!io.AdvanceRecord()) { 377 next.reset(); 378 } else { 379 next = io.GetNextNonBlank(); 380 } 381 } 382 if (!next || *next != '&') { 383 handler.SignalError( 384 "NAMELIST input group does not begin with '&' (at '%lc')", *next); 385 return false; 386 } 387 io.HandleRelativePosition(1); 388 if (!GetLowerCaseName(io, name, sizeof name)) { 389 handler.SignalError("NAMELIST input group has no name"); 390 return false; 391 } 392 if (std::strcmp(group.groupName, name) == 0) { 393 break; // found it 394 } 395 SkipNamelistGroup(io); 396 } 397 // Read the group's items 398 while (true) { 399 next = io.GetNextNonBlank(); 400 if (!next || *next == '/') { 401 break; 402 } 403 if (!GetLowerCaseName(io, name, sizeof name)) { 404 handler.SignalError( 405 "NAMELIST input group '%s' was not terminated at '%c'", 406 group.groupName, static_cast<char>(*next)); 407 return false; 408 } 409 std::size_t itemIndex{0}; 410 for (; itemIndex < group.items; ++itemIndex) { 411 if (std::strcmp(name, group.item[itemIndex].name) == 0) { 412 break; 413 } 414 } 415 if (itemIndex >= group.items) { 416 handler.SignalError( 417 "'%s' is not an item in NAMELIST group '%s'", name, group.groupName); 418 return false; 419 } 420 // Handle indexing and components, if any. No spaces are allowed. 421 // A copy of the descriptor is made if necessary. 422 const Descriptor &itemDescriptor{group.item[itemIndex].descriptor}; 423 const Descriptor *useDescriptor{&itemDescriptor}; 424 StaticDescriptor<maxRank, true, 16> staticDesc[2]; 425 int whichStaticDesc{0}; 426 next = io.GetCurrentChar(); 427 bool hadSubscripts{false}; 428 bool hadSubstring{false}; 429 if (next && (*next == '(' || *next == '%')) { 430 do { 431 Descriptor &mutableDescriptor{staticDesc[whichStaticDesc].descriptor()}; 432 whichStaticDesc ^= 1; 433 if (*next == '(') { 434 if (!hadSubstring && (hadSubscripts || useDescriptor->rank() == 0)) { 435 mutableDescriptor = *useDescriptor; 436 mutableDescriptor.raw().attribute = CFI_attribute_pointer; 437 if (!HandleSubstring(io, mutableDescriptor, name)) { 438 return false; 439 } 440 hadSubstring = true; 441 } else if (hadSubscripts) { 442 handler.SignalError("Multiple sets of subscripts for item '%s' in " 443 "NAMELIST group '%s'", 444 name, group.groupName); 445 return false; 446 } else if (!HandleSubscripts( 447 io, mutableDescriptor, *useDescriptor, name)) { 448 return false; 449 } 450 hadSubscripts = true; 451 } else { 452 if (!HandleComponent(io, mutableDescriptor, *useDescriptor, name)) { 453 return false; 454 } 455 hadSubscripts = false; 456 hadSubstring = false; 457 } 458 useDescriptor = &mutableDescriptor; 459 next = io.GetCurrentChar(); 460 } while (next && (*next == '(' || *next == '%')); 461 } 462 // Skip the '=' 463 next = io.GetNextNonBlank(); 464 if (!next || *next != '=') { 465 handler.SignalError("No '=' found after item '%s' in NAMELIST group '%s'", 466 name, group.groupName); 467 return false; 468 } 469 io.HandleRelativePosition(1); 470 // Read the values into the descriptor. An array can be short. 471 listInput->ResetForNextNamelistItem(); 472 if (!descr::DescriptorIO<Direction::Input>(io, *useDescriptor)) { 473 return false; 474 } 475 next = io.GetNextNonBlank(); 476 if (next && *next == comma) { 477 io.HandleRelativePosition(1); 478 } 479 } 480 if (!next || *next != '/') { 481 handler.SignalError( 482 "No '/' found after NAMELIST group '%s'", group.groupName); 483 return false; 484 } 485 io.HandleRelativePosition(1); 486 return true; 487 } 488 489 bool IsNamelistName(IoStatementState &io) { 490 if (io.get_if<ListDirectedStatementState<Direction::Input>>()) { 491 if (io.mutableModes().inNamelist) { 492 SavedPosition savedPosition{io}; 493 if (auto ch{io.GetNextNonBlank()}) { 494 if (IsLegalIdStart(*ch)) { 495 do { 496 io.HandleRelativePosition(1); 497 ch = io.GetCurrentChar(); 498 } while (ch && IsLegalIdChar(*ch)); 499 ch = io.GetNextNonBlank(); 500 // TODO: how to deal with NaN(...) ambiguity? 501 return ch && (*ch == '=' || *ch == '(' || *ch == '%'); 502 } 503 } 504 } 505 } 506 return false; 507 } 508 509 } // namespace Fortran::runtime::io 510