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