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 bool IONAME(OutputNamelist)(Cookie cookie, const NamelistGroup &group) { 24 IoStatementState &io{*cookie}; 25 io.CheckFormattedStmtType<Direction::Output>("OutputNamelist"); 26 ConnectionState &connection{io.GetConnectionState()}; 27 connection.modes.inNamelist = true; 28 // Internal functions to advance records and convert case 29 const auto EmitWithAdvance{[&](char ch) -> bool { 30 return (!connection.NeedAdvance(1) || io.AdvanceRecord()) && 31 io.Emit(&ch, 1); 32 }}; 33 const auto EmitUpperCase{[&](const char *str) -> bool { 34 if (connection.NeedAdvance(std::strlen(str)) && 35 !(io.AdvanceRecord() && io.Emit(" ", 1))) { 36 return false; 37 } 38 for (; *str; ++str) { 39 char up{*str >= 'a' && *str <= 'z' ? static_cast<char>(*str - 'a' + 'A') 40 : *str}; 41 if (!io.Emit(&up, 1)) { 42 return false; 43 } 44 } 45 return true; 46 }}; 47 // &GROUP 48 if (!(EmitWithAdvance('&') && EmitUpperCase(group.groupName))) { 49 return false; 50 } 51 for (std::size_t j{0}; j < group.items; ++j) { 52 // [,]ITEM=... 53 const NamelistGroup::Item &item{group.item[j]}; 54 if (!(EmitWithAdvance(j == 0 ? ' ' : ',') && EmitUpperCase(item.name) && 55 EmitWithAdvance('=') && 56 descr::DescriptorIO<Direction::Output>(io, item.descriptor))) { 57 return false; 58 } 59 } 60 // terminal / 61 return EmitWithAdvance('/'); 62 } 63 64 static constexpr bool IsLegalIdStart(char32_t ch) { 65 return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || 66 ch == '@' || ch == '$'; 67 } 68 69 static constexpr bool IsLegalIdChar(char32_t ch) { 70 return IsLegalIdStart(ch) || (ch >= '0' && ch <= '9'); 71 } 72 73 static constexpr char NormalizeIdChar(char32_t ch) { 74 return static_cast<char>(ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch); 75 } 76 77 static bool GetLowerCaseName( 78 IoStatementState &io, char buffer[], std::size_t maxLength) { 79 if (auto ch{io.GetNextNonBlank()}) { 80 if (IsLegalIdStart(*ch)) { 81 std::size_t j{0}; 82 do { 83 buffer[j] = NormalizeIdChar(*ch); 84 io.HandleRelativePosition(1); 85 ch = io.GetCurrentChar(); 86 } while (++j < maxLength && ch && IsLegalIdChar(*ch)); 87 buffer[j++] = '\0'; 88 if (j <= maxLength) { 89 return true; 90 } 91 io.GetIoErrorHandler().SignalError( 92 "Identifier '%s...' in NAMELIST input group is too long", buffer); 93 } 94 } 95 return false; 96 } 97 98 static std::optional<SubscriptValue> GetSubscriptValue(IoStatementState &io) { 99 std::optional<SubscriptValue> value; 100 std::optional<char32_t> ch{io.GetCurrentChar()}; 101 bool negate{ch && *ch == '-'}; 102 if (negate) { 103 io.HandleRelativePosition(1); 104 ch = io.GetCurrentChar(); 105 } 106 bool overflow{false}; 107 while (ch && *ch >= '0' && *ch <= '9') { 108 SubscriptValue was{value.value_or(0)}; 109 overflow |= was >= std::numeric_limits<SubscriptValue>::max() / 10; 110 value = 10 * was + *ch - '0'; 111 io.HandleRelativePosition(1); 112 ch = io.GetCurrentChar(); 113 } 114 if (overflow) { 115 io.GetIoErrorHandler().SignalError( 116 "NAMELIST input subscript value overflow"); 117 return std::nullopt; 118 } 119 if (negate) { 120 if (value) { 121 return -*value; 122 } else { 123 io.HandleRelativePosition(-1); // give back '-' with no digits 124 } 125 } 126 return value; 127 } 128 129 static bool HandleSubscripts(IoStatementState &io, Descriptor &desc, 130 const Descriptor &source, const char *name) { 131 IoErrorHandler &handler{io.GetIoErrorHandler()}; 132 io.HandleRelativePosition(1); // skip '(' 133 // Allow for blanks in subscripts; they're nonstandard, but not 134 // ambiguous within the parentheses. 135 SubscriptValue lower[maxRank], upper[maxRank], stride[maxRank]; 136 int j{0}; 137 std::size_t contiguousStride{source.ElementBytes()}; 138 bool ok{true}; 139 std::optional<char32_t> ch{io.GetNextNonBlank()}; 140 for (; ch && *ch != ')'; ++j) { 141 SubscriptValue dimLower{0}, dimUpper{0}, dimStride{0}; 142 if (j < maxRank && j < source.rank()) { 143 const Dimension &dim{source.GetDimension(j)}; 144 dimLower = dim.LowerBound(); 145 dimUpper = dim.UpperBound(); 146 dimStride = 147 dim.ByteStride() / std::max<SubscriptValue>(contiguousStride, 1); 148 contiguousStride *= dim.Extent(); 149 } else if (ok) { 150 handler.SignalError( 151 "Too many subscripts for rank-%d NAMELIST group item '%s'", 152 source.rank(), name); 153 ok = false; 154 } 155 if (auto low{GetSubscriptValue(io)}) { 156 if (*low < dimLower || (dimUpper >= dimLower && *low > dimUpper)) { 157 if (ok) { 158 handler.SignalError("Subscript %jd out of range %jd..%jd in NAMELIST " 159 "group item '%s' dimension %d", 160 static_cast<std::intmax_t>(*low), 161 static_cast<std::intmax_t>(dimLower), 162 static_cast<std::intmax_t>(dimUpper), name, j + 1); 163 ok = false; 164 } 165 } else { 166 dimLower = *low; 167 } 168 ch = io.GetNextNonBlank(); 169 } 170 if (ch && *ch == ':') { 171 io.HandleRelativePosition(1); 172 ch = io.GetNextNonBlank(); 173 if (auto high{GetSubscriptValue(io)}) { 174 if (*high > dimUpper) { 175 if (ok) { 176 handler.SignalError( 177 "Subscript triplet upper bound %jd out of range (>%jd) in " 178 "NAMELIST group item '%s' dimension %d", 179 static_cast<std::intmax_t>(*high), 180 static_cast<std::intmax_t>(dimUpper), name, j + 1); 181 ok = false; 182 } 183 } else { 184 dimUpper = *high; 185 } 186 ch = io.GetNextNonBlank(); 187 } 188 if (ch && *ch == ':') { 189 io.HandleRelativePosition(1); 190 ch = io.GetNextNonBlank(); 191 if (auto str{GetSubscriptValue(io)}) { 192 dimStride = *str; 193 ch = io.GetNextNonBlank(); 194 } 195 } 196 } else { // scalar 197 dimUpper = dimLower; 198 dimStride = 0; 199 } 200 if (ch && *ch == ',') { 201 io.HandleRelativePosition(1); 202 ch = io.GetNextNonBlank(); 203 } 204 if (ok) { 205 lower[j] = dimLower; 206 upper[j] = dimUpper; 207 stride[j] = dimStride; 208 } 209 } 210 if (ok) { 211 if (ch && *ch == ')') { 212 io.HandleRelativePosition(1); 213 if (desc.EstablishPointerSection(source, lower, upper, stride)) { 214 return true; 215 } else { 216 handler.SignalError( 217 "Bad subscripts for NAMELIST input group item '%s'", name); 218 } 219 } else { 220 handler.SignalError( 221 "Bad subscripts (missing ')') for NAMELIST input group item '%s'", 222 name); 223 } 224 } 225 return false; 226 } 227 228 static bool HandleComponent(IoStatementState &io, Descriptor &desc, 229 const Descriptor &source, const char *name) { 230 IoErrorHandler &handler{io.GetIoErrorHandler()}; 231 io.HandleRelativePosition(1); // skip '%' 232 char compName[nameBufferSize]; 233 if (GetLowerCaseName(io, compName, sizeof compName)) { 234 const DescriptorAddendum *addendum{source.Addendum()}; 235 if (const typeInfo::DerivedType * 236 type{addendum ? addendum->derivedType() : nullptr}) { 237 if (const typeInfo::Component * 238 comp{type->FindDataComponent(compName, std::strlen(compName))}) { 239 comp->CreatePointerDescriptor(desc, source, handler); 240 return true; 241 } else { 242 handler.SignalError( 243 "NAMELIST component reference '%%%s' of input group item %s is not " 244 "a component of its derived type", 245 compName, name); 246 } 247 } else if (source.type().IsDerived()) { 248 handler.Crash("Derived type object '%s' in NAMELIST is missing its " 249 "derived type information!", 250 name); 251 } else { 252 handler.SignalError("NAMELIST component reference '%%%s' of input group " 253 "item %s for non-derived type", 254 compName, name); 255 } 256 } else { 257 handler.SignalError("NAMELIST component reference of input group item %s " 258 "has no name after '%'", 259 name); 260 } 261 return false; 262 } 263 264 bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { 265 IoStatementState &io{*cookie}; 266 io.CheckFormattedStmtType<Direction::Input>("InputNamelist"); 267 ConnectionState &connection{io.GetConnectionState()}; 268 connection.modes.inNamelist = true; 269 IoErrorHandler &handler{io.GetIoErrorHandler()}; 270 auto *listInput{io.get_if<ListDirectedStatementState<Direction::Input>>()}; 271 RUNTIME_CHECK(handler, listInput != nullptr); 272 // Check the group header 273 io.BeginReadingRecord(); 274 std::optional<char32_t> next{io.GetNextNonBlank()}; 275 if (!next || *next != '&') { 276 handler.SignalError( 277 "NAMELIST input group does not begin with '&' (at '%lc')", *next); 278 return false; 279 } 280 io.HandleRelativePosition(1); 281 char name[nameBufferSize]; 282 if (!GetLowerCaseName(io, name, sizeof name)) { 283 handler.SignalError("NAMELIST input group has no name"); 284 return false; 285 } 286 RUNTIME_CHECK(handler, group.groupName != nullptr); 287 if (std::strcmp(group.groupName, name) != 0) { 288 handler.SignalError( 289 "NAMELIST input group name '%s' is not the expected '%s'", name, 290 group.groupName); 291 return false; 292 } 293 // Read the group's items 294 while (true) { 295 next = io.GetNextNonBlank(); 296 if (!next || *next == '/') { 297 break; 298 } 299 if (!GetLowerCaseName(io, name, sizeof name)) { 300 handler.SignalError( 301 "NAMELIST input group '%s' was not terminated", group.groupName); 302 return false; 303 } 304 std::size_t itemIndex{0}; 305 for (; itemIndex < group.items; ++itemIndex) { 306 if (std::strcmp(name, group.item[itemIndex].name) == 0) { 307 break; 308 } 309 } 310 if (itemIndex >= group.items) { 311 handler.SignalError( 312 "'%s' is not an item in NAMELIST group '%s'", name, group.groupName); 313 return false; 314 } 315 // Handle indexing and components, if any. No spaces are allowed. 316 // A copy of the descriptor is made if necessary. 317 const Descriptor &itemDescriptor{group.item[itemIndex].descriptor}; 318 const Descriptor *useDescriptor{&itemDescriptor}; 319 StaticDescriptor<maxRank, true, 16> staticDesc[2]; 320 int whichStaticDesc{0}; 321 next = io.GetCurrentChar(); 322 if (next && (*next == '(' || *next == '%')) { 323 do { 324 Descriptor &mutableDescriptor{staticDesc[whichStaticDesc].descriptor()}; 325 whichStaticDesc ^= 1; 326 if (*next == '(') { 327 if (!(HandleSubscripts( 328 io, mutableDescriptor, *useDescriptor, name))) { 329 return false; 330 } 331 } else { 332 if (!HandleComponent(io, mutableDescriptor, *useDescriptor, name)) { 333 return false; 334 } 335 } 336 useDescriptor = &mutableDescriptor; 337 next = io.GetCurrentChar(); 338 } while (next && (*next == '(' || *next == '%')); 339 } 340 // Skip the '=' 341 next = io.GetNextNonBlank(); 342 if (!next || *next != '=') { 343 handler.SignalError("No '=' found after item '%s' in NAMELIST group '%s'", 344 name, group.groupName); 345 return false; 346 } 347 io.HandleRelativePosition(1); 348 // Read the values into the descriptor. An array can be short. 349 listInput->ResetForNextNamelistItem(); 350 if (!descr::DescriptorIO<Direction::Input>(io, *useDescriptor)) { 351 return false; 352 } 353 next = io.GetNextNonBlank(); 354 if (next && *next == ',') { 355 io.HandleRelativePosition(1); 356 } 357 } 358 if (!next || *next != '/') { 359 handler.SignalError( 360 "No '/' found after NAMELIST group '%s'", group.groupName); 361 return false; 362 } 363 io.HandleRelativePosition(1); 364 return true; 365 } 366 367 bool IsNamelistName(IoStatementState &io) { 368 if (io.get_if<ListDirectedStatementState<Direction::Input>>()) { 369 ConnectionState &connection{io.GetConnectionState()}; 370 if (connection.modes.inNamelist) { 371 SavedPosition savedPosition{io}; 372 if (auto ch{io.GetNextNonBlank()}) { 373 if (IsLegalIdStart(*ch)) { 374 do { 375 io.HandleRelativePosition(1); 376 ch = io.GetCurrentChar(); 377 } while (ch && IsLegalIdChar(*ch)); 378 ch = io.GetNextNonBlank(); 379 // TODO: how to deal with NaN(...) ambiguity? 380 return ch && (*ch == '=' || *ch == '(' || *ch == '%'); 381 } 382 } 383 } 384 } 385 return false; 386 } 387 388 } // namespace Fortran::runtime::io 389