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