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