https://mooseframework.inl.gov
Parser.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 // MOOSE includes
11 #include "MooseUtils.h"
12 #include "MooseInit.h"
13 #include "MooseTypes.h"
14 #include "CommandLine.h"
15 #include "SystemInfo.h"
16 #include "Parser.h"
17 #include "Units.h"
18 
19 #include "libmesh/parallel.h"
20 #include "libmesh/fparser.hh"
21 
22 // C++ includes
23 #include <map>
24 #include <fstream>
25 #include <algorithm>
26 #include <cstdlib>
27 
28 std::string
29 FuncParseEvaler::eval(hit::Field * n, const std::list<std::string> & args, hit::BraceExpander & exp)
30 {
31  std::string func_text;
32  for (auto & s : args)
33  func_text += s;
34  auto n_errs = exp.errors.size();
35 
36  FunctionParser fp;
37  fp.AddConstant("pi", libMesh::pi);
38  fp.AddConstant("e", std::exp(Real(1)));
39  std::vector<std::string> var_names;
40  auto ret = fp.ParseAndDeduceVariables(func_text, var_names);
41  if (ret != -1)
42  {
43  exp.errors.emplace_back(
44  "fparse error: " + std::string(fp.ErrorMsg()) + " in '" + n->fullpath() + "'", n);
45  return n->val();
46  }
47 
48  std::vector<double> var_vals;
49  for (auto & var : var_names)
50  {
51  // recursively check all parent scopes for the needed variables
52  hit::Node * curr = n;
53  while ((curr = curr->parent()))
54  {
55  auto src = curr->find(var);
56  if (src && src != n && src->type() == hit::NodeType::Field)
57  {
58  exp.used.push_back(hit::pathJoin({curr->fullpath(), var}));
59  var_vals.push_back(curr->param<double>(var));
60  break;
61  }
62  }
63 
64  if (curr == nullptr)
65  exp.errors.emplace_back("no variable '" + var +
66  "' found for use in function parser expression in '" +
67  n->fullpath() + "'",
68  n);
69  }
70 
71  if (exp.errors.size() != n_errs)
72  return n->val();
73 
74  std::stringstream ss;
75  ss << std::setprecision(17) << fp.Eval(var_vals.data());
76 
77  // change kind only (not val)
78  n->setVal(n->val(), hit::Field::Kind::Float);
79  return ss.str();
80 }
81 
82 std::string
84  const std::list<std::string> & args,
85  hit::BraceExpander & exp)
86 {
87  std::vector<std::string> argv;
88  argv.insert(argv.begin(), args.begin(), args.end());
89 
90  // no conversion, the expression currently only documents the units and passes through the value
91  if (argv.size() == 2)
92  {
93  n->setVal(n->val(), hit::Field::Kind::Float);
94  return argv[0];
95  }
96 
97  // conversion
98  if (argv.size() != 4 || (argv.size() >= 3 && argv[2] != "->"))
99  {
100  exp.errors.emplace_back("units error: Expected 4 arguments ${units number from_unit -> "
101  "to_unit} or 2 arguments ${units number unit} in '" +
102  n->fullpath() + "'",
103  n);
104  return n->val();
105  }
106 
107  // get and check units
108  auto from_unit = MooseUnits(argv[1]);
109  auto to_unit = MooseUnits(argv[3]);
110  if (!from_unit.conformsTo(to_unit))
111  {
112  std::ostringstream err;
113  err << "units error: " << argv[1] << " (" << from_unit << ") does not convert to " << argv[3]
114  << " (" << to_unit << ") in '" << n->fullpath() << "'";
115  exp.errors.emplace_back(err.str(), n);
116  return n->val();
117  }
118 
119  // parse number
120  Real num = MooseUtils::convert<Real>(argv[0]);
121 
122  // convert units
123  std::stringstream ss;
124  ss << std::setprecision(17) << to_unit.convert(num, from_unit);
125 
126 #ifndef NDEBUG
127  mooseInfoRepeated(n->filename() + ':' + Moose::stringify(n->line()) + ':' +
128  Moose::stringify(n->column()) + ": Unit conversion ",
129  num,
130  ' ',
131  argv[1],
132  " -> ",
133  ss.str(),
134  ' ',
135  argv[3]);
136 #endif
137 
138  // change kind only (not val)
139  n->setVal(n->val(), hit::Field::Kind::Float);
140  return ss.str();
141 }
142 
143 Parser::Parser(const std::vector<std::string> & input_filenames,
144  const std::optional<std::vector<std::string>> & input_text /* = {} */)
145  : _root(nullptr),
146  _input_filenames(input_filenames),
147  _input_text(input_text ? *input_text : std::vector<std::string>()),
148  _cli_root(nullptr),
149  _throw_on_error(false)
150 {
151  if (input_text && _input_filenames.size() != input_text->size())
152  mooseError("Parser: Input text not the same length as input filenames");
153 }
154 
155 Parser::Parser(const std::string & input_filename,
156  const std::optional<std::string> & input_text /* = {} */)
157  : Parser(std::vector<std::string>{input_filename},
158  input_text ? std::optional<std::vector<std::string>>({*input_text})
159  : std::optional<std::vector<std::string>>())
160 {
161 }
162 
163 void
164 DupParamWalker::walk(const std::string & fullpath, const std::string & /*nodepath*/, hit::Node * n)
165 {
166  const auto it = _have.try_emplace(fullpath, n);
167  if (!it.second)
168  {
169  const std::string type = n->type() == hit::NodeType::Field ? "parameter" : "section";
170  const std::string error = type + " '" + fullpath + "' supplied multiple times";
171 
172  // Don't warn multiple times (will happen if we find it three+ times)
173  const auto existing = it.first->second;
174  if (std::find_if(errors.begin(),
175  errors.end(),
176  [&existing](const auto & err)
177  { return err.node == existing; }) == errors.end())
178  errors.emplace_back(error, existing);
179 
180  errors.emplace_back(error, n);
181  }
182 }
183 
184 void
185 CompileParamWalker::walk(const std::string & fullpath,
186  const std::string & /*nodepath*/,
187  hit::Node * n)
188 {
189  if (n->type() == hit::NodeType::Field)
190  _map[fullpath] = n;
191 }
192 
193 void
194 OverrideParamWalker::walk(const std::string & fullpath,
195  const std::string & /*nodepath*/,
196  hit::Node * n)
197 {
198  const auto it = _map.find(fullpath);
199  if (it != _map.end())
200  warnings.push_back(hit::errormsg(n,
201  " Parameter '",
202  fullpath,
203  "' overrides the same parameter in ",
204  it->second->filename(),
205  ":",
206  it->second->line()));
207 }
208 
209 void
210 BadActiveWalker ::walk(const std::string & fullpath,
211  const std::string & /*nodepath*/,
212  hit::Node * section)
213 {
214  auto actives = section->find("active");
215  auto inactives = section->find("inactive");
216 
217  if (actives && inactives && actives->type() == hit::NodeType::Field &&
218  inactives->type() == hit::NodeType::Field && actives->parent() == inactives->parent())
219  {
220  errors.emplace_back(
221  "'active' and 'inactive' parameters both provided in section '" + fullpath + "'", section);
222  return;
223  }
224 
225  // ensures we don't recheck deeper nesting levels
226  if (actives && actives->type() == hit::NodeType::Field && actives->parent() == section)
227  {
228  auto vars = section->param<std::vector<std::string>>("active");
229  std::string msg = "";
230  for (auto & var : vars)
231  {
232  if (!section->find(var))
233  msg += var + ", ";
234  }
235  if (msg.size() > 0)
236  {
237  msg = msg.substr(0, msg.size() - 2);
238  errors.emplace_back("variables listed as active (" + msg + ") in section '" +
239  section->fullpath() + "' not found in input",
240  section);
241  }
242  }
243  // ensures we don't recheck deeper nesting levels
244  if (inactives && inactives->type() == hit::NodeType::Field && inactives->parent() == section)
245  {
246  auto vars = section->param<std::vector<std::string>>("inactive");
247  std::string msg = "";
248  for (auto & var : vars)
249  {
250  if (!section->find(var))
251  msg += var + ", ";
252  }
253  if (msg.size() > 0)
254  {
255  msg = msg.substr(0, msg.size() - 2);
256  errors.emplace_back("variables listed as inactive (" + msg + ") in section '" +
257  section->fullpath() + "' not found in input",
258  section);
259  }
260  }
261 }
262 
263 class FindAppWalker : public hit::Walker
264 {
265 public:
266  void
267  walk(const std::string & /*fullpath*/, const std::string & /*nodepath*/, hit::Node * n) override
268  {
269  if (n && n->type() == hit::NodeType::Field && n->fullpath() == "Application/type")
270  _app_type = n->param<std::string>();
271  }
272  const std::optional<std::string> & getApp() { return _app_type; };
273 
274 private:
275  std::optional<std::string> _app_type;
276 };
277 
278 void
279 Parser::setCommandLineParams(const std::vector<std::string> & params)
280 {
281  mooseAssert(!_command_line_params, "Already set");
282  _command_line_params = params;
283 }
284 
285 const std::string &
287 {
288  if (_input_filenames.empty())
289  mooseError("Parser::getLastInputFileName(): No inputs are set");
290  return _input_filenames.back();
291 }
292 
293 Parser::Error::Error(const std::vector<hit::ErrorMessage> & error_messages)
294  : hit::Error(error_messages)
295 {
296 }
297 
298 void
300 {
301  mooseAssert(!_root && !_cli_root, "Has already parsed");
302 
303  if (getInputFileNames().size() > 1)
304  mooseInfo("Merging inputs ", Moose::stringify(getInputFileNames()));
305 
306  // Correct filenames (default is to use real path)
307  const std::string use_rel_paths_str =
308  std::getenv("MOOSE_RELATIVE_FILEPATHS") ? std::getenv("MOOSE_RELATIVE_FILEPATHS") : "false";
309  const auto use_real_paths = use_rel_paths_str == "0" || use_rel_paths_str == "false";
310  std::vector<std::string> filenames;
311  for (const auto & filename : getInputFileNames())
312  filenames.push_back(use_real_paths ? MooseUtils::realpath(filename) : filename);
313 
314  // Load each input file if text was not provided
315  if (_input_text.empty())
316  for (const auto & filename : filenames)
317  {
318  MooseUtils::checkFileReadable(filename, true);
319  std::ifstream f(filename);
320  _input_text.push_back(
321  std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()));
322  }
323 
324  CompileParamWalker::ParamMap override_map;
325  CompileParamWalker cpw(override_map);
326  OverrideParamWalker opw(override_map);
327 
328  // Errors from the duplicate param walker, ran within each input
329  // independently first
330  std::vector<hit::ErrorMessage> dw_errors;
331 
332  for (const auto i : index_range(getInputFileNames()))
333  {
334  const auto & filename = filenames[i];
335  const auto & input = getInputText()[i];
336 
337  try
338  {
339  // provide stream to hit parse function to capture any syntax errors,
340  // set parser root node, then throw those errors if any were captured
341  std::vector<hit::ErrorMessage> syntax_errors;
342  std::unique_ptr<hit::Node> root(hit::parse(filename, input, &syntax_errors));
343 
344  DupParamWalker dw;
345  root->walk(&dw, hit::NodeType::Field);
346  appendErrorMessages(dw_errors, dw.errors);
347 
348  if (!queryRoot())
349  _root = std::move(root);
350  else
351  {
352  root->walk(&opw, hit::NodeType::Field);
353  hit::merge(root.get(), &getRoot());
354  }
355 
356  if (!syntax_errors.empty())
357  throw Parser::Error(syntax_errors);
358 
359  getRoot().walk(&cpw, hit::NodeType::Field);
360  }
361  catch (hit::Error & err)
362  {
363  parseError(err.error_messages);
364  }
365  }
366 
367  // warn about overridden parameters in multiple inputs
368  if (!opw.warnings.empty())
369  mooseInfo(Moose::stringify(opw.warnings), "\n");
370 
371  // If we don't have a root (allow no input files),
372  // create an empty one
373  if (!queryRoot())
374  _root.reset(hit::parse("EMPTY", ""));
375 
376  {
377  BadActiveWalker bw;
378  getRoot().walk(&bw, hit::NodeType::Section);
379  if (bw.errors.size())
380  parseError(bw.errors);
381  }
382 
383  {
384  FindAppWalker fw;
385  getRoot().walk(&fw, hit::NodeType::Field);
386  if (fw.getApp())
387  setAppType(*fw.getApp());
388  }
389 
390  // Duplicate parameter errors (within each input file)
391  if (dw_errors.size())
392  parseError(dw_errors);
393 
394  // Merge in command line HIT arguments
395  const auto joined_params =
397  try
398  {
399  _cli_root.reset(hit::parse("CLI_ARGS", joined_params));
401  }
402  catch (hit::Error & err)
403  {
404  parseError(err.error_messages);
405  }
406 
407  std::vector<hit::ErrorMessage> errors;
408 
409  // expand ${bla} parameter values and mark/include variables
410  // used in expansion as "used" (obtained later by the Builder
411  // with getExtractedVars())
412  {
413  hit::RawEvaler raw;
414  hit::EnvEvaler env;
415  hit::ReplaceEvaler repl;
416  FuncParseEvaler fparse_ev;
417  UnitsConversionEvaler units_ev;
418  hit::BraceExpander exw;
419  exw.registerEvaler("raw", raw);
420  exw.registerEvaler("env", env);
421  exw.registerEvaler("fparse", fparse_ev);
422  exw.registerEvaler("replace", repl);
423  exw.registerEvaler("units", units_ev);
424  getRoot().walk(&exw);
425  for (auto & var : exw.used)
426  _extracted_vars.insert(var);
427  Parser::appendErrorMessages(errors, exw.errors);
428  }
429 
430  // Collect duplicate parameters now that we've merged inputs
431  {
432  DupParamWalker dw;
433  getRoot().walk(&dw, hit::NodeType::Field);
435  }
436 
437  // Check bad active now that we've merged inputs
438  {
439  BadActiveWalker bw;
440  getRoot().walk(&bw, hit::NodeType::Section);
442  }
443 
444  if (errors.size())
445  parseError(errors);
446 }
447 
448 hit::Node &
450 {
451  if (!queryRoot())
452  mooseError("Parser::getRoot(): root is not set");
453  return *queryRoot();
454 }
455 
456 const hit::Node &
458 {
459  if (!queryCommandLineRoot())
460  mooseError("Parser::getCommandLineRoot(): command line root is not set");
461  return *queryCommandLineRoot();
462 }
463 
464 hit::Node &
466 {
467  return const_cast<hit::Node &>(std::as_const(*this).getCommandLineRoot());
468 }
469 
470 void
471 Parser::appendErrorMessages(std::vector<hit::ErrorMessage> & to,
472  const std::vector<hit::ErrorMessage> & from)
473 {
474  to.insert(to.end(), from.begin(), from.end());
475 }
476 
477 void
478 Parser::appendErrorMessages(std::vector<hit::ErrorMessage> & to, const hit::Error & error)
479 {
480  appendErrorMessages(to, error.error_messages);
481 }
482 
483 std::string
484 Parser::joinErrorMessages(const std::vector<hit::ErrorMessage> & error_messages)
485 {
486  std::vector<std::string> values;
487  for (const auto & em : error_messages)
488  values.push_back(em.prefixed_message);
489  return MooseUtils::stringJoin(values, "\n");
490 }
491 
492 void
493 Parser::parseError(std::vector<hit::ErrorMessage> messages) const
494 {
495  // Few things about command line arguments...
496  // 1. We don't care to add line and column context for CLI args, because
497  // it doesn't make sense. We go from the full CLI args and pull out
498  // the HIT parameters so "line" 1 might not even be command line
499  // argument 1. So, remove line/column context from all CLI args.
500  // 2. Whenever we have a parameter in input that then gets overridden
501  // by a command line argument, under the hood we're merging two
502  // different HIT trees. However, WASP doesn't currently update the
503  // "filename" context for the updated parameter. Which means that
504  // a param that is in input and then overridden by CLI will have
505  // its location as in input. Which isn't true. So we get around this
506  // by searching the independent CLI args tree for params that we have
507  // errors for. If the associated path is also in CLI args, we manually
508  // set its error to come from CLI args. This should be fixed in
509  // the future with a WASP update.
510  for (auto & em : messages)
511  if (em.node && queryCommandLineRoot())
512  if (getCommandLineRoot().find(em.node->fullpath()))
513  em = hit::ErrorMessage(em.message, "CLI_ARGS");
514 
515  if (_throw_on_error)
516  throw Parser::Error(messages);
517  else
518  mooseError(joinErrorMessages(messages));
519 }
const std::vector< std::string > _input_filenames
The input file names.
Definition: Parser.h:234
OStreamProxy err
void setAppType(const std::string &app_type)
Definition: Parser.h:170
const hit::Node * queryCommandLineRoot() const
Definition: Parser.h:138
void parseError(std::vector< hit::ErrorMessage > messages) const
Helper for throwing an error with the given messages.
Definition: Parser.C:493
void parse()
Parses the inputs.
Definition: Parser.C:299
auto exp(const T &)
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:333
ParamMap & _map
Definition: Parser.h:67
void setCommandLineParams(const std::vector< std::string > &params)
Sets the HIT parameters from the command line.
Definition: Parser.C:279
char ** vars
virtual std::string eval(hit::Field *n, const std::list< std::string > &args, hit::BraceExpander &exp)
Definition: Parser.C:83
void walk(const std::string &, const std::string &, hit::Node *n) override
Definition: Parser.C:267
const std::string & getLastInputFileName() const
Definition: Parser.C:286
hit::Node & getRoot()
Definition: Parser.C:449
void mooseInfoRepeated(Args &&... args)
Emit an informational message with the given stringified, concatenated args.
Definition: MooseError.h:408
const std::vector< std::string > & getInputFileNames() const
Definition: Parser.h:155
std::vector< std::string > warnings
Definition: Parser.h:76
std::string realpath(const std::string &path)
Wrapper around PetscGetRealPath, which is a cross-platform replacement for realpath.
Definition: MooseUtils.C:1231
const hit::Node * queryRoot() const
Definition: Parser.h:121
std::vector< hit::ErrorMessage > errors
Definition: Parser.h:42
static void appendErrorMessages(std::vector< hit::ErrorMessage > &to, const std::vector< hit::ErrorMessage > &from)
Helper for accumulating errors from a walker into an accumulation of errors.
Definition: Parser.C:471
virtual void walk(const std::string &fullpath, const std::string &, hit::Node *n) override
Definition: Parser.C:185
void mooseInfo(Args &&... args)
Emit an informational message with the given stringified, concatenated args.
Definition: MooseError.h:400
std::unique_ptr< hit::Node > _cli_root
The root node for command line hit arguments.
Definition: Parser.h:240
static std::string joinErrorMessages(const std::vector< hit::ErrorMessage > &error_messages)
Helper for combining error messages into a single, newline separated message.
Definition: Parser.C:484
Parser(const std::vector< std::string > &input_filenames, const std::optional< std::vector< std::string >> &input_text={})
Constructor given a list of input files, given in input_filenames.
Definition: Parser.C:143
std::map< std::string, hit::Node * > _have
Definition: Parser.h:45
constexpr auto merge(std::index_sequence< first... >, std::index_sequence< second... >)
Merge two index sequences into one.
bool checkFileReadable(const std::string &filename, bool check_line_endings=false, bool throw_on_unreadable=true, bool check_for_git_lfs_pointer=true)
Checks to see if a file is readable (exists and permissions)
Definition: MooseUtils.C:250
bool _throw_on_error
Whether or not to throw on error.
Definition: Parser.h:246
std::vector< std::string > _input_text
The input text (may be filled during parse())
Definition: Parser.h:237
std::string stringify(const T &t)
conversion to string
Definition: Conversion.h:64
std::map< std::string, hit::Node * > ParamMap
Definition: Parser.h:60
Definition: Moose.h:42
std::set< std::string > _extracted_vars
Variables that have been extracted during brace expansion.
Definition: Parser.h:252
Physical unit management class with runtime unit string parsing, unit checking, unit conversion...
Definition: Units.h:32
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const std::optional< std::string > & getApp()
Definition: Parser.C:272
virtual void walk(const std::string &fullpath, const std::string &, hit::Node *n) override
Definition: Parser.C:164
std::unique_ptr< hit::Node > _root
The root node, which owns the whole tree.
Definition: Parser.h:231
void walk(const std::string &fullpath, const std::string &, hit::Node *n) override
Definition: Parser.C:194
const hit::Node & getCommandLineRoot() const
Definition: Parser.C:457
std::optional< std::string > _app_type
Definition: Parser.C:272
Error()=delete
const std::vector< std::string > & getInputText() const
Definition: Parser.h:160
std::vector< hit::ErrorMessage > errors
Definition: Parser.h:54
std::optional< std::vector< std::string > > _command_line_params
The command line HIT parameters (if any)
Definition: Parser.h:249
bool _throw_on_error
Variable to turn on exceptions during mooseError(), should only be used within MOOSE unit tests or wh...
Definition: Moose.C:780
std::string stringJoin(const std::vector< std::string > &values, const std::string &separator=" ")
Concatenates value into a single string separated by separator.
Definition: MooseUtils.C:1050
virtual std::string eval(hit::Field *n, const std::list< std::string > &args, hit::BraceExpander &exp)
Definition: Parser.C:29
auto index_range(const T &sizable)
Class for parsing input files.
Definition: Parser.h:87
virtual void walk(const std::string &, const std::string &, hit::Node *section) override
Definition: Parser.C:210
const CompileParamWalker::ParamMap & _map
Definition: Parser.h:79
const Real pi