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, nullptr, 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 { 248 handler.SignalError("NAMELIST component reference '%%%s' of input group " 249 "item %s for non-derived type", 250 compName, name); 251 } 252 } else { 253 handler.SignalError("NAMELIST component reference of input group item %s " 254 "has no name after '%'", 255 name); 256 } 257 return false; 258 } 259 260 bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { 261 IoStatementState &io{*cookie}; 262 io.CheckFormattedStmtType<Direction::Input>("InputNamelist"); 263 ConnectionState &connection{io.GetConnectionState()}; 264 connection.modes.inNamelist = true; 265 IoErrorHandler &handler{io.GetIoErrorHandler()}; 266 auto *listInput{io.get_if<ListDirectedStatementState<Direction::Input>>()}; 267 RUNTIME_CHECK(handler, listInput != nullptr); 268 // Check the group header 269 io.BeginReadingRecord(); 270 std::optional<char32_t> next{io.GetNextNonBlank()}; 271 if (!next || *next != '&') { 272 handler.SignalError( 273 "NAMELIST input group does not begin with '&' (at '%lc')", *next); 274 return false; 275 } 276 io.HandleRelativePosition(1); 277 char name[nameBufferSize]; 278 if (!GetLowerCaseName(io, name, sizeof name)) { 279 handler.SignalError("NAMELIST input group has no name"); 280 return false; 281 } 282 RUNTIME_CHECK(handler, group.groupName != nullptr); 283 if (std::strcmp(group.groupName, name) != 0) { 284 handler.SignalError( 285 "NAMELIST input group name '%s' is not the expected '%s'", name, 286 group.groupName); 287 return false; 288 } 289 // Read the group's items 290 while (true) { 291 next = io.GetNextNonBlank(); 292 if (!next || *next == '/') { 293 break; 294 } 295 if (!GetLowerCaseName(io, name, sizeof name)) { 296 handler.SignalError( 297 "NAMELIST input group '%s' was not terminated", group.groupName); 298 return false; 299 } 300 std::size_t itemIndex{0}; 301 for (; itemIndex < group.items; ++itemIndex) { 302 if (std::strcmp(name, group.item[itemIndex].name) == 0) { 303 break; 304 } 305 } 306 if (itemIndex >= group.items) { 307 handler.SignalError( 308 "'%s' is not an item in NAMELIST group '%s'", name, group.groupName); 309 return false; 310 } 311 // Handle indexing and components, if any. No spaces are allowed. 312 // A copy of the descriptor is made if necessary. 313 const Descriptor &itemDescriptor{group.item[itemIndex].descriptor}; 314 const Descriptor *useDescriptor{&itemDescriptor}; 315 StaticDescriptor<maxRank, true, 16> staticDesc[2]; 316 int whichStaticDesc{0}; 317 next = io.GetCurrentChar(); 318 if (next && (*next == '(' || *next == '%')) { 319 do { 320 Descriptor &mutableDescriptor{staticDesc[whichStaticDesc].descriptor()}; 321 whichStaticDesc ^= 1; 322 if (*next == '(') { 323 HandleSubscripts(io, mutableDescriptor, *useDescriptor, name); 324 } else { 325 HandleComponent(io, mutableDescriptor, *useDescriptor, name); 326 } 327 useDescriptor = &mutableDescriptor; 328 next = io.GetCurrentChar(); 329 } while (next && (*next == '(' || *next == '%')); 330 } 331 // Skip the '=' 332 next = io.GetNextNonBlank(); 333 if (!next || *next != '=') { 334 handler.SignalError("No '=' found after item '%s' in NAMELIST group '%s'", 335 name, group.groupName); 336 return false; 337 } 338 io.HandleRelativePosition(1); 339 // Read the values into the descriptor. An array can be short. 340 listInput->ResetForNextNamelistItem(); 341 if (!descr::DescriptorIO<Direction::Input>(io, *useDescriptor)) { 342 return false; 343 } 344 next = io.GetNextNonBlank(); 345 if (next && *next == ',') { 346 io.HandleRelativePosition(1); 347 } 348 } 349 if (!next || *next != '/') { 350 handler.SignalError( 351 "No '/' found after NAMELIST group '%s'", group.groupName); 352 return false; 353 } 354 io.HandleRelativePosition(1); 355 return true; 356 } 357 358 bool IsNamelistName(IoStatementState &io) { 359 if (io.get_if<ListDirectedStatementState<Direction::Input>>()) { 360 ConnectionState &connection{io.GetConnectionState()}; 361 if (connection.modes.inNamelist) { 362 SavedPosition savedPosition{connection}; 363 if (auto ch{io.GetNextNonBlank()}) { 364 if (IsLegalIdStart(*ch)) { 365 do { 366 io.HandleRelativePosition(1); 367 ch = io.GetCurrentChar(); 368 } while (ch && IsLegalIdChar(*ch)); 369 ch = io.GetNextNonBlank(); 370 // TODO: how to deal with NaN(...) ambiguity? 371 return ch && (ch == '=' || ch == '(' || ch == '%'); 372 } 373 } 374 } 375 } 376 return false; 377 } 378 379 } // namespace Fortran::runtime::io 380