ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
erf_inputs_detail Namespace Reference

Classes

struct  EntryInfo
 
struct  ScanState
 

Functions

std::string prefixed_name (const std::string &name)
 
std::vector< std::string > split_values (const std::string &rhs)
 
bool is_override (const EntryInfo &first, const std::vector< int > &includes)
 
void scan_inputs (const std::string &inputs_file, ScanState &state)
 

Function Documentation

◆ is_override()

bool erf_inputs_detail::is_override ( const EntryInfo &  first,
const std::vector< int > &  includes 
)
inline

Whether a key set again where includes are open overrides the setting first: it does when the first setting came from a file this one included earlier (the base + override idiom; ParmParse keeps the last value). A key set twice in one file, in two files included side by side, or in a file and then again in a file it includes afterwards is a duplicate.

97  {
98  if (first.includes.size() <= includes.size()) { return false; }
99  for (std::size_t n = 0; n < includes.size(); ++n) {
100  if (first.includes[n] != includes[n]) { return false; }
101  }
102  return true;
103  }

Referenced by scan_inputs().

Here is the caller graph for this function:

◆ prefixed_name()

std::string erf_inputs_detail::prefixed_name ( const std::string &  name)
inline

The name ParmParse opens for an inputs file: $AMREX_INPUTS_FILE_PREFIX, with a trailing '/' added when missing, in front of the name as written (AMReX_ParmParse.cpp, read_file).

55  {
56  const char* prefix = std::getenv("AMREX_INPUTS_FILE_PREFIX");
57  if (prefix == nullptr || prefix[0] == 0) { return name; }
58  std::string p(prefix);
59  if (p.back() != '/') { p += '/'; }
60  return p + name;
61  }
std::string name
Definition: ERF_Plotfile2DCatalog.cpp:105
@ p
Definition: ERF_WSM6.H:280

Referenced by scan_inputs().

Here is the caller graph for this function:

◆ scan_inputs()

void erf_inputs_detail::scan_inputs ( const std::string &  inputs_file,
ScanState &  state 
)
inline

Scan one inputs file, and every file it includes with FILE = <name>, the way ParmParse reads them: names are opened with $AMREX_INPUTS_FILE_PREFIX in front, FILE with more than one value is a parameter, UNSET = <keys> drops the keys, [name] starts a table whose keys are name.<key>, and if AMREX_SPACEDIM / #ifdef AMREX_USE_GPU regions that are off are skipped. Records each key with where it was set, and sets found_duplicates when a key is set twice (see is_override). An inputs file that cannot be opened, or that includes itself directly or through others, sets error and stops the scan.

116  {
117  const std::string name = prefixed_name(inputs_file);
118  std::ifstream ifs(name);
119 
120  if (!ifs.is_open()) {
121  state.error = "Could not open inputs file: " + name;
122  return;
123  }
124  std::error_code ec;
125  auto canonical = std::filesystem::weakly_canonical(name, ec);
126  if (ec) { canonical = std::filesystem::absolute(name); }
127  for (const auto& f : state.open_files) {
128  if (f == canonical) {
129  state.error = "The inputs file " + name + " includes itself (FILE = ...)";
130  return;
131  }
132  }
133  state.open_files.push_back(canonical);
134  state.includes.push_back(state.next_include++);
135 
136  // the preprocessor ParmParse applies before it reads the lines (AMReX_ParmParse.cpp, read_file)
137  static const std::regex if_regex(R"(^\s*#\s*if\s+\‍(?\s*AMREX_SPACEDIM\s*(>|<|==|>=|<=|!=)\s*([1-3])\s*\)?\s*$)");
138  static const std::regex elif_regex(R"(^\s*#\s*elif\s+\‍(?\s*AMREX_SPACEDIM\s*(>|<|==|>=|<=|!=)\s*([1-3])\s*\)?\s*$)");
139  static const std::regex else_regex(R"(^\s*#\s*else\s*$)");
140  static const std::regex endif_regex(R"(^\s*#\s*endif\s*$)");
141  static const std::regex if_gpu_regex(R"(^\s*#\s*ifdef\s+AMREX_USE_GPU\s*$)");
142  static const std::regex if_not_gpu_regex(R"(^\s*#\s*ifndef\s+AMREX_USE_GPU\s*$)");
143  static const std::regex table_regex(R"(^\[\s*([A-Za-z][A-Za-z0-9_.\-]*)\s*\]$)");
144 #ifdef AMREX_USE_GPU
145  constexpr bool use_gpu = true;
146 #else
147  constexpr bool use_gpu = false;
148 #endif
149  auto dim_true = [] (const std::smatch& sm) {
150  const std::string op = sm[1].str();
151  const int dim = std::stoi(sm[2].str());
152  return (op == "<" && AMREX_SPACEDIM < dim) || (op == ">" && AMREX_SPACEDIM > dim) ||
153  (op == "==" && AMREX_SPACEDIM == dim) || (op == "<=" && AMREX_SPACEDIM <= dim) ||
154  (op == ">=" && AMREX_SPACEDIM >= dim) || (op == "!=" && AMREX_SPACEDIM != dim);
155  };
156  std::vector<bool> region_on; // is each open #if region read?
157  std::vector<bool> region_had; // has a branch of each open #if region been read?
158 
159  std::string table; // the current [name] table, empty at the root
160  std::string line;
161  int line_number = 0;
162 
163  while (std::getline(ifs, line))
164  {
165  ++line_number;
166 
167  std::smatch sm;
168  if (std::regex_match(line, if_gpu_regex) || std::regex_match(line, if_not_gpu_regex)) {
169  const bool r = std::regex_match(line, if_gpu_regex) ? use_gpu : !use_gpu;
170  region_on.push_back(r); region_had.push_back(r);
171  continue;
172  } else if (std::regex_match(line, sm, if_regex)) {
173  const bool r = dim_true(sm);
174  region_on.push_back(r); region_had.push_back(r);
175  continue;
176  } else if (!region_on.empty() && std::regex_match(line, sm, elif_regex)) {
177  const bool r = !region_had.back() && dim_true(sm);
178  region_on.back() = r;
179  if (r) { region_had.back() = true; }
180  continue;
181  } else if (!region_on.empty() && std::regex_match(line, else_regex)) {
182  region_on.back() = !region_had.back();
183  region_had.back() = true;
184  continue;
185  } else if (!region_on.empty() && std::regex_match(line, endif_regex)) {
186  region_on.pop_back(); region_had.pop_back();
187  continue;
188  }
189  bool skip = false;
190  for (bool r : region_on) { if (!r) { skip = true; } }
191  if (skip) { continue; }
192 
193  // Remove comments
194  auto hash_pos = line.find('#');
195  if (hash_pos != std::string::npos) {
196  line = line.substr(0, hash_pos);
197  }
198 
199  line = trim_copy(line);
200 
201  // Skip blank lines
202  if (line.empty()) continue;
203 
204  // A [name] line starts a table: the keys after it are name.<key>
205  if (std::regex_match(line, sm, table_regex)) {
206  table = sm[1].str();
207  continue;
208  }
209 
210  // Skip lines that are not assignments
211  auto eq_pos = line.find('=');
212  if (eq_pos == std::string::npos) continue;
213 
214  const std::string def = trim_copy(line.substr(0, eq_pos));
215  const std::string value = trim_copy(line.substr(eq_pos + 1));
216  const auto vals = split_values(value);
217 
218  // FILE = <name> includes another inputs file, whose keys belong to the same deck.
219  // ParmParse reads it into a clean table scope.
220  if (def == amrex::ParmParse::FileKeyword && vals.size() == 1) {
221  scan_inputs(vals.front(), state);
222  // stop, closing this file's include on the way out
223  if (!state.error.empty()) { break; }
224  continue;
225  }
226 
227  // UNSET = <keys> drops the keys (full names, also inside a table), so they may be set again
228  if (def == amrex::ParmParse::UnsetKeyword) {
229  for (const auto& k : vals) { state.seen.erase(k); }
230  continue;
231  }
232 
233  const std::string key = table.empty() ? def : table + "." + def;
234  auto it = state.seen.find(key);
235 
236  if (it == state.seen.end())
237  {
238  state.seen.emplace(key, EntryInfo{name, line_number, value, state.includes});
239  }
240  else if (is_override(it->second, state.includes))
241  {
242  it->second = EntryInfo{name, line_number, value, state.includes};
243  }
244  else
245  {
246  state.found_duplicates = true;
247 
248  amrex::Print()
249  << "\nDuplicate input detected:\n"
250  << " Key : " << key << "\n"
251  << " First line : " << it->second.line_number << " of " << it->second.file << "\n"
252  << " Value : " << it->second.value << "\n"
253  << " Second line : " << line_number << " of " << name << "\n"
254  << " Value : " << value << "\n";
255  }
256  }
257 
258  state.includes.pop_back();
259  state.open_files.pop_back();
260  }
std::string prefixed_name(const std::string &name)
Definition: ERF_InputsName.H:54
bool is_override(const EntryInfo &first, const std::vector< int > &includes)
Definition: ERF_InputsName.H:96
std::vector< std::string > split_values(const std::string &rhs)
Definition: ERF_InputsName.H:68
void scan_inputs(const std::string &inputs_file, ScanState &state)
Definition: ERF_InputsName.H:115

Referenced by CheckForDuplicateInputs().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ split_values()

std::vector<std::string> erf_inputs_detail::split_values ( const std::string &  rhs)
inline

The values on the right of '=': whitespace separates them, and a double-quoted value is one value without its quotes.

69  {
70  std::vector<std::string> vals;
71  std::size_t i = 0;
72  while (i < rhs.size()) {
73  if (rhs[i] == ' ' || rhs[i] == '\t' || rhs[i] == '\r') { ++i; continue; }
74  if (rhs[i] == '"') {
75  auto e = rhs.find('"', i + 1);
76  if (e == std::string::npos) { e = rhs.size(); }
77  vals.push_back(rhs.substr(i + 1, e - i - 1));
78  i = e + 1;
79  } else {
80  auto e = rhs.find_first_of(" \t\r", i);
81  if (e == std::string::npos) { e = rhs.size(); }
82  vals.push_back(rhs.substr(i, e - i));
83  i = e;
84  }
85  }
86  return vals;
87  }

Referenced by scan_inputs().

Here is the caller graph for this function: