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