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