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