Line data Source code
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 5052 : FuncParseEvaler::eval(hit::Field * n, const std::list<std::string> & args, hit::BraceExpander & exp)
30 : {
31 5052 : std::string func_text;
32 12834 : for (auto & s : args)
33 7782 : func_text += s;
34 5052 : auto n_errs = exp.errors.size();
35 :
36 5052 : FunctionParser fp;
37 10104 : fp.AddConstant("pi", libMesh::pi);
38 5052 : fp.AddConstant("e", std::exp(Real(1)));
39 5052 : std::vector<std::string> var_names;
40 5052 : auto ret = fp.ParseAndDeduceVariables(func_text, var_names);
41 5052 : if (ret != -1)
42 : {
43 0 : exp.errors.emplace_back(
44 0 : "fparse error: " + std::string(fp.ErrorMsg()) + " in '" + n->fullpath() + "'", n);
45 0 : return n->val();
46 : }
47 :
48 5052 : std::vector<double> var_vals;
49 12143 : for (auto & var : var_names)
50 : {
51 : // recursively check all parent scopes for the needed variables
52 7091 : hit::Node * curr = n;
53 18156 : while ((curr = curr->parent()))
54 : {
55 18155 : auto src = curr->find(var);
56 18155 : if (src && src != n && src->type() == hit::NodeType::Field)
57 : {
58 28360 : exp.used.push_back(hit::pathJoin({curr->fullpath(), var}));
59 7090 : var_vals.push_back(curr->param<double>(var));
60 7090 : break;
61 : }
62 : }
63 :
64 7091 : if (curr == nullptr)
65 3 : exp.errors.emplace_back("no variable '" + var +
66 2 : "' found for use in function parser expression in '" +
67 4 : n->fullpath() + "'",
68 : n);
69 : }
70 :
71 5052 : if (exp.errors.size() != n_errs)
72 1 : return n->val();
73 :
74 5051 : std::stringstream ss;
75 5051 : ss << std::setprecision(17) << fp.Eval(var_vals.data());
76 :
77 : // change kind only (not val)
78 5051 : n->setVal(n->val(), hit::Field::Kind::Float);
79 5051 : return ss.str();
80 12142 : }
81 :
82 : std::string
83 55 : UnitsConversionEvaler::eval(hit::Field * n,
84 : const std::list<std::string> & args,
85 : hit::BraceExpander & exp)
86 : {
87 55 : std::vector<std::string> argv;
88 55 : 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 55 : if (argv.size() == 2)
92 : {
93 29 : n->setVal(n->val(), hit::Field::Kind::Float);
94 29 : return argv[0];
95 : }
96 :
97 : // conversion
98 26 : if (argv.size() != 4 || (argv.size() >= 3 && argv[2] != "->"))
99 : {
100 0 : exp.errors.emplace_back("units error: Expected 4 arguments ${units number from_unit -> "
101 0 : "to_unit} or 2 arguments ${units number unit} in '" +
102 0 : n->fullpath() + "'",
103 : n);
104 0 : return n->val();
105 : }
106 :
107 : // get and check units
108 26 : auto from_unit = MooseUnits(argv[1]);
109 26 : auto to_unit = MooseUnits(argv[3]);
110 26 : if (!from_unit.conformsTo(to_unit))
111 : {
112 0 : std::ostringstream err;
113 0 : err << "units error: " << argv[1] << " (" << from_unit << ") does not convert to " << argv[3]
114 0 : << " (" << to_unit << ") in '" << n->fullpath() << "'";
115 0 : exp.errors.emplace_back(err.str(), n);
116 0 : return n->val();
117 0 : }
118 :
119 : // parse number
120 26 : Real num = MooseUtils::convert<Real>(argv[0]);
121 :
122 : // convert units
123 26 : std::stringstream ss;
124 26 : 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 26 : n->setVal(n->val(), hit::Field::Kind::Float);
140 26 : return ss.str();
141 55 : }
142 :
143 68521 : Parser::Parser(const std::vector<std::string> & input_filenames,
144 68521 : const std::optional<std::vector<std::string>> & input_text /* = {} */)
145 68521 : : _root(nullptr),
146 68521 : _input_filenames(input_filenames),
147 68521 : _input_text(input_text ? *input_text : std::vector<std::string>()),
148 68521 : _cli_root(nullptr),
149 68521 : _throw_on_error(false)
150 : {
151 68521 : if (input_text && _input_filenames.size() != input_text->size())
152 0 : mooseError("Parser: Input text not the same length as input filenames");
153 68521 : }
154 :
155 12427 : Parser::Parser(const std::string & input_filename,
156 12427 : const std::optional<std::string> & input_text /* = {} */)
157 49708 : : Parser(std::vector<std::string>{input_filename},
158 24875 : input_text ? std::optional<std::vector<std::string>>({*input_text})
159 12427 : : std::optional<std::vector<std::string>>())
160 : {
161 24861 : }
162 :
163 : void
164 5121156 : DupParamWalker::walk(const std::string & fullpath, const std::string & /*nodepath*/, hit::Node * n)
165 : {
166 5121156 : const auto it = _have.try_emplace(fullpath, n);
167 5121156 : if (!it.second)
168 : {
169 18 : const std::string type = n->type() == hit::NodeType::Field ? "parameter" : "section";
170 18 : 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 18 : const auto existing = it.first->second;
174 18 : if (std::find_if(errors.begin(),
175 : errors.end(),
176 18 : [&existing](const auto & err)
177 54 : { return err.node == existing; }) == errors.end())
178 18 : errors.emplace_back(error, existing);
179 :
180 18 : errors.emplace_back(error, n);
181 18 : }
182 5121156 : }
183 :
184 : void
185 2518056 : CompileParamWalker::walk(const std::string & fullpath,
186 : const std::string & /*nodepath*/,
187 : hit::Node * n)
188 : {
189 2518056 : if (n->type() == hit::NodeType::Field)
190 2518056 : _map[fullpath] = n;
191 2518056 : }
192 :
193 : void
194 1273 : OverrideParamWalker::walk(const std::string & fullpath,
195 : const std::string & /*nodepath*/,
196 : hit::Node * n)
197 : {
198 1273 : const auto it = _map.find(fullpath);
199 1273 : if (it != _map.end())
200 23 : warnings.push_back(hit::errormsg(n,
201 : " Parameter '",
202 : fullpath,
203 : "' overrides the same parameter in ",
204 23 : it->second->filename(),
205 : ":",
206 23 : it->second->line()));
207 1273 : }
208 :
209 : void
210 2389194 : BadActiveWalker ::walk(const std::string & fullpath,
211 : const std::string & /*nodepath*/,
212 : hit::Node * section)
213 : {
214 4778388 : auto actives = section->find("active");
215 4778388 : auto inactives = section->find("inactive");
216 :
217 27876 : if (actives && inactives && actives->type() == hit::NodeType::Field &&
218 2417070 : inactives->type() == hit::NodeType::Field && actives->parent() == inactives->parent())
219 : {
220 4 : errors.emplace_back(
221 8 : "'active' and 'inactive' parameters both provided in section '" + fullpath + "'", section);
222 4 : return;
223 : }
224 :
225 : // ensures we don't recheck deeper nesting levels
226 2389190 : if (actives && actives->type() == hit::NodeType::Field && actives->parent() == section)
227 : {
228 83538 : auto vars = section->param<std::vector<std::string>>("active");
229 27846 : std::string msg = "";
230 72560 : for (auto & var : vars)
231 : {
232 44714 : if (!section->find(var))
233 9 : msg += var + ", ";
234 : }
235 27846 : if (msg.size() > 0)
236 : {
237 9 : msg = msg.substr(0, msg.size() - 2);
238 27 : errors.emplace_back("variables listed as active (" + msg + ") in section '" +
239 36 : section->fullpath() + "' not found in input",
240 : section);
241 : }
242 27846 : }
243 : // ensures we don't recheck deeper nesting levels
244 2389190 : if (inactives && inactives->type() == hit::NodeType::Field && inactives->parent() == section)
245 : {
246 9765 : auto vars = section->param<std::vector<std::string>>("inactive");
247 3255 : std::string msg = "";
248 7231 : for (auto & var : vars)
249 : {
250 3976 : if (!section->find(var))
251 8 : msg += var + ", ";
252 : }
253 3255 : if (msg.size() > 0)
254 : {
255 8 : msg = msg.substr(0, msg.size() - 2);
256 24 : errors.emplace_back("variables listed as inactive (" + msg + ") in section '" +
257 32 : section->fullpath() + "' not found in input",
258 : section);
259 : }
260 3255 : }
261 : }
262 :
263 : class FindAppWalker : public hit::Walker
264 : {
265 : public:
266 : void
267 2514127 : walk(const std::string & /*fullpath*/, const std::string & /*nodepath*/, hit::Node * n) override
268 : {
269 2514127 : if (n && n->type() == hit::NodeType::Field && n->fullpath() == "Application/type")
270 60 : _app_type = n->param<std::string>();
271 2514127 : }
272 68512 : const std::optional<std::string> & getApp() { return _app_type; };
273 :
274 : private:
275 : std::optional<std::string> _app_type;
276 : };
277 :
278 : void
279 68518 : Parser::setCommandLineParams(const std::vector<std::string> & params)
280 : {
281 : mooseAssert(!_command_line_params, "Already set");
282 68518 : _command_line_params = params;
283 68518 : }
284 :
285 : const std::string &
286 42606 : Parser::getLastInputFileName() const
287 : {
288 42606 : if (_input_filenames.empty())
289 0 : mooseError("Parser::getLastInputFileName(): No inputs are set");
290 42606 : return _input_filenames.back();
291 : }
292 :
293 20 : Parser::Error::Error(const std::vector<hit::ErrorMessage> & error_messages)
294 20 : : hit::Error(error_messages)
295 : {
296 20 : }
297 :
298 : void
299 68521 : Parser::parse()
300 : {
301 : mooseAssert(!_root && !_cli_root, "Has already parsed");
302 :
303 68521 : if (getInputFileNames().size() > 1)
304 812 : 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 68521 : std::getenv("MOOSE_RELATIVE_FILEPATHS") ? std::getenv("MOOSE_RELATIVE_FILEPATHS") : "false";
309 68521 : const auto use_real_paths = use_rel_paths_str == "0" || use_rel_paths_str == "false";
310 68521 : std::vector<std::string> filenames;
311 136716 : for (const auto & filename : getInputFileNames())
312 68195 : filenames.push_back(use_real_paths ? MooseUtils::realpath(filename) : filename);
313 :
314 : // Load each input file if text was not provided
315 68521 : if (_input_text.empty())
316 136681 : for (const auto & filename : filenames)
317 : {
318 68180 : MooseUtils::checkFileReadable(filename, true);
319 68172 : std::ifstream f(filename);
320 136344 : _input_text.push_back(
321 136344 : std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()));
322 68172 : }
323 :
324 68513 : CompileParamWalker::ParamMap override_map;
325 68513 : CompileParamWalker cpw(override_map);
326 68513 : OverrideParamWalker opw(override_map);
327 :
328 : // Errors from the duplicate param walker, ran within each input
329 : // independently first
330 68513 : std::vector<hit::ErrorMessage> dw_errors;
331 :
332 136684 : for (const auto i : index_range(getInputFileNames()))
333 : {
334 68187 : const auto & filename = filenames[i];
335 68187 : 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 68187 : std::vector<hit::ErrorMessage> syntax_errors;
342 68187 : std::unique_ptr<hit::Node> root(hit::parse(filename, input, &syntax_errors));
343 :
344 68187 : DupParamWalker dw;
345 68187 : root->walk(&dw, hit::NodeType::Field);
346 68187 : appendErrorMessages(dw_errors, dw.errors);
347 :
348 68187 : if (!queryRoot())
349 67956 : _root = std::move(root);
350 : else
351 : {
352 231 : root->walk(&opw, hit::NodeType::Field);
353 231 : hit::merge(root.get(), &getRoot());
354 : }
355 :
356 68187 : if (!syntax_errors.empty())
357 16 : throw Parser::Error(syntax_errors);
358 :
359 68171 : getRoot().walk(&cpw, hit::NodeType::Field);
360 68219 : }
361 16 : catch (hit::Error & err)
362 : {
363 17 : parseError(err.error_messages);
364 1 : }
365 : }
366 :
367 : // warn about overridden parameters in multiple inputs
368 68497 : if (!opw.warnings.empty())
369 92 : 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 68497 : if (!queryRoot())
374 2785 : _root.reset(hit::parse("EMPTY", ""));
375 :
376 : {
377 68497 : BadActiveWalker bw;
378 68497 : getRoot().walk(&bw, hit::NodeType::Section);
379 68497 : if (bw.errors.size())
380 6 : parseError(bw.errors);
381 68493 : }
382 :
383 : {
384 68492 : FindAppWalker fw;
385 68492 : getRoot().walk(&fw, hit::NodeType::Field);
386 68492 : if (fw.getApp())
387 20 : setAppType(*fw.getApp());
388 68492 : }
389 :
390 : // Duplicate parameter errors (within each input file)
391 68492 : if (dw_errors.size())
392 10 : parseError(dw_errors);
393 :
394 : // Merge in command line HIT arguments
395 : const auto joined_params =
396 136969 : _command_line_params ? MooseUtils::stringJoin(*_command_line_params) : "";
397 : try
398 : {
399 136966 : _cli_root.reset(hit::parse("CLI_ARGS", joined_params));
400 68483 : hit::merge(&getCommandLineRoot(), &getRoot());
401 : }
402 0 : catch (hit::Error & err)
403 : {
404 0 : parseError(err.error_messages);
405 0 : }
406 :
407 68483 : 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 68483 : hit::RawEvaler raw;
414 68483 : hit::EnvEvaler env;
415 68483 : hit::ReplaceEvaler repl;
416 68483 : FuncParseEvaler fparse_ev;
417 68483 : UnitsConversionEvaler units_ev;
418 68483 : hit::BraceExpander exw;
419 136966 : exw.registerEvaler("raw", raw);
420 136966 : exw.registerEvaler("env", env);
421 136966 : exw.registerEvaler("fparse", fparse_ev);
422 136966 : exw.registerEvaler("replace", repl);
423 68483 : exw.registerEvaler("units", units_ev);
424 68483 : getRoot().walk(&exw);
425 96394 : for (auto & var : exw.used)
426 27911 : _extracted_vars.insert(var);
427 68483 : Parser::appendErrorMessages(errors, exw.errors);
428 68483 : }
429 :
430 : // Collect duplicate parameters now that we've merged inputs
431 : {
432 68483 : DupParamWalker dw;
433 68483 : getRoot().walk(&dw, hit::NodeType::Field);
434 68483 : Parser::appendErrorMessages(errors, dw.errors);
435 68483 : }
436 :
437 : // Check bad active now that we've merged inputs
438 : {
439 68483 : BadActiveWalker bw;
440 68483 : getRoot().walk(&bw, hit::NodeType::Section);
441 68483 : Parser::appendErrorMessages(errors, bw.errors);
442 68483 : }
443 :
444 68483 : if (errors.size())
445 14 : parseError(errors);
446 68496 : }
447 :
448 : hit::Node &
449 2070533 : Parser::getRoot()
450 : {
451 2070533 : if (!queryRoot())
452 0 : mooseError("Parser::getRoot(): root is not set");
453 2070533 : return *queryRoot();
454 : }
455 :
456 : const hit::Node &
457 131392 : Parser::getCommandLineRoot() const
458 : {
459 131392 : if (!queryCommandLineRoot())
460 0 : mooseError("Parser::getCommandLineRoot(): command line root is not set");
461 131392 : return *queryCommandLineRoot();
462 : }
463 :
464 : hit::Node &
465 131323 : Parser::getCommandLineRoot()
466 : {
467 131323 : return const_cast<hit::Node &>(std::as_const(*this).getCommandLineRoot());
468 : }
469 :
470 : void
471 399316 : Parser::appendErrorMessages(std::vector<hit::ErrorMessage> & to,
472 : const std::vector<hit::ErrorMessage> & from)
473 : {
474 399316 : to.insert(to.end(), from.begin(), from.end());
475 399316 : }
476 :
477 : void
478 0 : Parser::appendErrorMessages(std::vector<hit::ErrorMessage> & to, const hit::Error & error)
479 : {
480 0 : appendErrorMessages(to, error.error_messages);
481 0 : }
482 :
483 : std::string
484 181 : Parser::joinErrorMessages(const std::vector<hit::ErrorMessage> & error_messages)
485 : {
486 181 : std::vector<std::string> values;
487 435 : for (const auto & em : error_messages)
488 254 : values.push_back(em.prefixed_message);
489 362 : return MooseUtils::stringJoin(values, "\n");
490 181 : }
491 :
492 : void
493 95 : 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 229 : for (auto & em : messages)
511 134 : if (em.node && queryCommandLineRoot())
512 69 : if (getCommandLineRoot().find(em.node->fullpath()))
513 88 : em = hit::ErrorMessage(em.message, "CLI_ARGS");
514 :
515 95 : if (_throw_on_error)
516 4 : throw Parser::Error(messages);
517 : else
518 91 : mooseError(joinErrorMessages(messages));
519 : }
|