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 6896 : FuncParseEvaler::eval(hit::Field * n, const std::list<std::string> & args, hit::BraceExpander & exp)
30 : {
31 6896 : std::string func_text;
32 22456 : for (auto & s : args)
33 15560 : func_text += s;
34 6896 : auto n_errs = exp.errors.size();
35 :
36 6896 : FunctionParser fp;
37 13792 : fp.AddConstant("pi", libMesh::pi);
38 6896 : fp.AddConstant("e", std::exp(Real(1)));
39 6896 : std::vector<std::string> var_names;
40 6896 : auto ret = fp.ParseAndDeduceVariables(func_text, var_names);
41 6896 : 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 6896 : std::vector<double> var_vals;
49 14200 : for (auto & var : var_names)
50 : {
51 : // recursively check all parent scopes for the needed variables
52 7304 : hit::Node * curr = n;
53 20050 : while ((curr = curr->parent()))
54 : {
55 20048 : auto src = curr->find(var);
56 20048 : if (src && src != n && src->type() == hit::NodeType::Field)
57 : {
58 29208 : exp.used.push_back(hit::pathJoin({curr->fullpath(), var}));
59 7302 : var_vals.push_back(curr->param<double>(var));
60 7302 : break;
61 : }
62 : }
63 :
64 7304 : if (curr == nullptr)
65 6 : exp.errors.emplace_back("no variable '" + var +
66 4 : "' found for use in function parser expression in '" +
67 8 : n->fullpath() + "'",
68 : n);
69 : }
70 :
71 6896 : if (exp.errors.size() != n_errs)
72 2 : return n->val();
73 :
74 6894 : std::stringstream ss;
75 6894 : ss << std::setprecision(17) << fp.Eval(var_vals.data());
76 :
77 : // change kind only (not val)
78 6894 : n->setVal(n->val(), hit::Field::Kind::Float);
79 6894 : return ss.str();
80 14198 : }
81 :
82 : std::string
83 68 : UnitsConversionEvaler::eval(hit::Field * n,
84 : const std::list<std::string> & args,
85 : hit::BraceExpander & exp)
86 : {
87 68 : std::vector<std::string> argv;
88 68 : 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 68 : if (argv.size() == 2)
92 : {
93 44 : n->setVal(n->val(), hit::Field::Kind::Float);
94 44 : return argv[0];
95 : }
96 :
97 : // conversion
98 24 : 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 24 : auto from_unit = MooseUnits(argv[1]);
109 24 : auto to_unit = MooseUnits(argv[3]);
110 24 : 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 24 : Real num = MooseUtils::convert<Real>(argv[0]);
121 :
122 : // convert units
123 24 : std::stringstream ss;
124 24 : 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 24 : n->setVal(n->val(), hit::Field::Kind::Float);
140 24 : return ss.str();
141 68 : }
142 :
143 67044 : Parser::Parser(const std::vector<std::string> & input_filenames,
144 67044 : const std::optional<std::vector<std::string>> & input_text /* = {} */)
145 67044 : : _root(nullptr),
146 67044 : _input_filenames(input_filenames),
147 67044 : _input_text(input_text ? *input_text : std::vector<std::string>()),
148 67044 : _cli_root(nullptr),
149 67044 : _throw_on_error(false)
150 : {
151 67044 : if (input_text && _input_filenames.size() != input_text->size())
152 0 : mooseError("Parser: Input text not the same length as input filenames");
153 67044 : }
154 :
155 12123 : Parser::Parser(const std::string & input_filename,
156 12123 : const std::optional<std::string> & input_text /* = {} */)
157 48492 : : Parser(std::vector<std::string>{input_filename},
158 24324 : input_text ? std::optional<std::vector<std::string>>({*input_text})
159 12123 : : std::optional<std::vector<std::string>>())
160 : {
161 24272 : }
162 :
163 : void
164 4929730 : DupParamWalker::walk(const std::string & fullpath, const std::string & /*nodepath*/, hit::Node * n)
165 : {
166 4929730 : const auto it = _have.try_emplace(fullpath, n);
167 4929730 : if (!it.second)
168 : {
169 16 : const std::string type = n->type() == hit::NodeType::Field ? "parameter" : "section";
170 16 : 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 16 : const auto existing = it.first->second;
174 16 : if (std::find_if(errors.begin(),
175 : errors.end(),
176 16 : [&existing](const auto & err)
177 48 : { return err.node == existing; }) == errors.end())
178 16 : errors.emplace_back(error, existing);
179 :
180 16 : errors.emplace_back(error, n);
181 16 : }
182 4929730 : }
183 :
184 : void
185 2423125 : CompileParamWalker::walk(const std::string & fullpath,
186 : const std::string & /*nodepath*/,
187 : hit::Node * n)
188 : {
189 2423125 : if (n->type() == hit::NodeType::Field)
190 2423125 : _map[fullpath] = n;
191 2423125 : }
192 :
193 : void
194 1136 : OverrideParamWalker::walk(const std::string & fullpath,
195 : const std::string & /*nodepath*/,
196 : hit::Node * n)
197 : {
198 1136 : const auto it = _map.find(fullpath);
199 1136 : if (it != _map.end())
200 21 : warnings.push_back(hit::errormsg(n,
201 : " Parameter '",
202 : fullpath,
203 : "' overrides the same parameter in ",
204 21 : it->second->filename(),
205 : ":",
206 21 : it->second->line()));
207 1136 : }
208 :
209 : void
210 2315845 : BadActiveWalker ::walk(const std::string & fullpath,
211 : const std::string & /*nodepath*/,
212 : hit::Node * section)
213 : {
214 4631690 : auto actives = section->find("active");
215 4631690 : auto inactives = section->find("inactive");
216 :
217 26084 : if (actives && inactives && actives->type() == hit::NodeType::Field &&
218 2341929 : inactives->type() == hit::NodeType::Field && actives->parent() == inactives->parent())
219 : {
220 3 : errors.emplace_back(
221 6 : "'active' and 'inactive' parameters both provided in section '" + fullpath + "'", section);
222 3 : return;
223 : }
224 :
225 : // ensures we don't recheck deeper nesting levels
226 2315842 : if (actives && actives->type() == hit::NodeType::Field && actives->parent() == section)
227 : {
228 78069 : auto vars = section->param<std::vector<std::string>>("active");
229 26023 : std::string msg = "";
230 67670 : for (auto & var : vars)
231 : {
232 41647 : if (!section->find(var))
233 8 : msg += var + ", ";
234 : }
235 26023 : if (msg.size() > 0)
236 : {
237 8 : msg = msg.substr(0, msg.size() - 2);
238 24 : errors.emplace_back("variables listed as active (" + msg + ") in section '" +
239 32 : section->fullpath() + "' not found in input",
240 : section);
241 : }
242 26023 : }
243 : // ensures we don't recheck deeper nesting levels
244 2315842 : if (inactives && inactives->type() == hit::NodeType::Field && inactives->parent() == section)
245 : {
246 9855 : auto vars = section->param<std::vector<std::string>>("inactive");
247 3285 : std::string msg = "";
248 6936 : for (auto & var : vars)
249 : {
250 3651 : if (!section->find(var))
251 6 : msg += var + ", ";
252 : }
253 3285 : if (msg.size() > 0)
254 : {
255 6 : msg = msg.substr(0, msg.size() - 2);
256 18 : errors.emplace_back("variables listed as inactive (" + msg + ") in section '" +
257 24 : section->fullpath() + "' not found in input",
258 : section);
259 : }
260 3285 : }
261 : }
262 :
263 : class FindAppWalker : public hit::Walker
264 : {
265 : public:
266 : void
267 2419560 : walk(const std::string & /*fullpath*/, const std::string & /*nodepath*/, hit::Node * n) override
268 : {
269 2419560 : if (n && n->type() == hit::NodeType::Field && n->fullpath() == "Application/type")
270 63 : _app_type = n->param<std::string>();
271 2419560 : }
272 67040 : const std::optional<std::string> & getApp() { return _app_type; };
273 :
274 : private:
275 : std::optional<std::string> _app_type;
276 : };
277 :
278 : void
279 67002 : Parser::setCommandLineParams(const std::vector<std::string> & params)
280 : {
281 : mooseAssert(!_command_line_params, "Already set");
282 67002 : _command_line_params = params;
283 67002 : }
284 :
285 : const std::string &
286 41612 : Parser::getLastInputFileName() const
287 : {
288 41612 : if (_input_filenames.empty())
289 0 : mooseError("Parser::getLastInputFileName(): No inputs are set");
290 41612 : return _input_filenames.back();
291 : }
292 :
293 28 : Parser::Error::Error(const std::vector<hit::ErrorMessage> & error_messages)
294 28 : : hit::Error(error_messages)
295 : {
296 28 : }
297 :
298 : void
299 67044 : Parser::parse()
300 : {
301 : mooseAssert(!_root && !_cli_root, "Has already parsed");
302 :
303 67044 : if (getInputFileNames().size() > 1)
304 740 : 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 67044 : std::getenv("MOOSE_RELATIVE_FILEPATHS") ? std::getenv("MOOSE_RELATIVE_FILEPATHS") : "false";
309 67044 : const auto use_real_paths = use_rel_paths_str == "0" || use_rel_paths_str == "false";
310 67044 : std::vector<std::string> filenames;
311 133487 : for (const auto & filename : getInputFileNames())
312 66443 : filenames.push_back(use_real_paths ? MooseUtils::realpath(filename) : filename);
313 :
314 : // Load each input file if text was not provided
315 67044 : if (_input_text.empty())
316 133403 : for (const auto & filename : filenames)
317 : {
318 66401 : MooseUtils::checkFileReadable(filename, true);
319 66395 : std::ifstream f(filename);
320 132790 : _input_text.push_back(
321 132790 : std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()));
322 66395 : }
323 :
324 67038 : CompileParamWalker::ParamMap override_map;
325 67038 : CompileParamWalker cpw(override_map);
326 67038 : OverrideParamWalker opw(override_map);
327 :
328 : // Errors from the duplicate param walker, ran within each input
329 : // independently first
330 67038 : std::vector<hit::ErrorMessage> dw_errors;
331 :
332 133461 : for (const auto i : index_range(getInputFileNames()))
333 : {
334 66437 : const auto & filename = filenames[i];
335 66437 : 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 66437 : std::vector<hit::ErrorMessage> syntax_errors;
342 66437 : std::unique_ptr<hit::Node> root(hit::parse(filename, input, &syntax_errors));
343 :
344 66437 : DupParamWalker dw;
345 66437 : root->walk(&dw, hit::NodeType::Field);
346 66437 : appendErrorMessages(dw_errors, dw.errors);
347 :
348 66437 : if (!queryRoot())
349 66226 : _root = std::move(root);
350 : else
351 : {
352 211 : root->walk(&opw, hit::NodeType::Field);
353 211 : hit::merge(root.get(), &getRoot());
354 : }
355 :
356 66437 : if (!syntax_errors.empty())
357 14 : throw Parser::Error(syntax_errors);
358 :
359 66423 : getRoot().walk(&cpw, hit::NodeType::Field);
360 66465 : }
361 14 : catch (hit::Error & err)
362 : {
363 18 : parseError(err.error_messages);
364 4 : }
365 : }
366 :
367 : // warn about overridden parameters in multiple inputs
368 67024 : if (!opw.warnings.empty())
369 84 : 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 67024 : if (!queryRoot())
374 4060 : _root.reset(hit::parse("EMPTY", ""));
375 :
376 : {
377 67024 : BadActiveWalker bw;
378 67024 : getRoot().walk(&bw, hit::NodeType::Section);
379 67024 : if (bw.errors.size())
380 7 : parseError(bw.errors);
381 67021 : }
382 :
383 : {
384 67019 : FindAppWalker fw;
385 67019 : getRoot().walk(&fw, hit::NodeType::Field);
386 67019 : if (fw.getApp())
387 21 : setAppType(*fw.getApp());
388 67019 : }
389 :
390 : // Duplicate parameter errors (within each input file)
391 67019 : if (dw_errors.size())
392 10 : parseError(dw_errors);
393 :
394 : // Merge in command line HIT arguments
395 : const auto joined_params =
396 134064 : _command_line_params ? MooseUtils::stringJoin(*_command_line_params) : "";
397 : try
398 : {
399 134022 : _cli_root.reset(hit::parse("CLI_ARGS", joined_params));
400 67011 : hit::merge(&getCommandLineRoot(), &getRoot());
401 : }
402 0 : catch (hit::Error & err)
403 : {
404 0 : parseError(err.error_messages);
405 0 : }
406 :
407 67011 : 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 67011 : hit::RawEvaler raw;
414 67011 : hit::EnvEvaler env;
415 67011 : hit::ReplaceEvaler repl;
416 67011 : FuncParseEvaler fparse_ev;
417 67011 : UnitsConversionEvaler units_ev;
418 67011 : hit::BraceExpander exw;
419 134022 : exw.registerEvaler("raw", raw);
420 134022 : exw.registerEvaler("env", env);
421 134022 : exw.registerEvaler("fparse", fparse_ev);
422 134022 : exw.registerEvaler("replace", repl);
423 67011 : exw.registerEvaler("units", units_ev);
424 67011 : getRoot().walk(&exw);
425 95354 : for (auto & var : exw.used)
426 28343 : _extracted_vars.insert(var);
427 67011 : Parser::appendErrorMessages(errors, exw.errors);
428 67011 : }
429 :
430 : // Collect duplicate parameters now that we've merged inputs
431 : {
432 67011 : DupParamWalker dw;
433 67011 : getRoot().walk(&dw, hit::NodeType::Field);
434 67011 : Parser::appendErrorMessages(errors, dw.errors);
435 67011 : }
436 :
437 : // Check bad active now that we've merged inputs
438 : {
439 67011 : BadActiveWalker bw;
440 67011 : getRoot().walk(&bw, hit::NodeType::Section);
441 67011 : Parser::appendErrorMessages(errors, bw.errors);
442 67011 : }
443 :
444 67011 : if (errors.size())
445 13 : parseError(errors);
446 67064 : }
447 :
448 : hit::Node &
449 2092726 : Parser::getRoot()
450 : {
451 2092726 : if (!queryRoot())
452 0 : mooseError("Parser::getRoot(): root is not set");
453 2092726 : return *queryRoot();
454 : }
455 :
456 : const hit::Node &
457 129433 : Parser::getCommandLineRoot() const
458 : {
459 129433 : if (!queryCommandLineRoot())
460 0 : mooseError("Parser::getCommandLineRoot(): command line root is not set");
461 129433 : return *queryCommandLineRoot();
462 : }
463 :
464 : hit::Node &
465 129348 : Parser::getCommandLineRoot()
466 : {
467 129348 : return const_cast<hit::Node &>(std::as_const(*this).getCommandLineRoot());
468 : }
469 :
470 : void
471 392146 : Parser::appendErrorMessages(std::vector<hit::ErrorMessage> & to,
472 : const std::vector<hit::ErrorMessage> & from)
473 : {
474 392146 : to.insert(to.end(), from.begin(), from.end());
475 392146 : }
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 171 : Parser::joinErrorMessages(const std::vector<hit::ErrorMessage> & error_messages)
485 : {
486 171 : std::vector<std::string> values;
487 396 : for (const auto & em : error_messages)
488 225 : values.push_back(em.prefixed_message);
489 342 : return MooseUtils::stringJoin(values, "\n");
490 171 : }
491 :
492 : void
493 111 : 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 295 : for (auto & em : messages)
511 184 : if (em.node && queryCommandLineRoot())
512 85 : if (getCommandLineRoot().find(em.node->fullpath()))
513 126 : em = hit::ErrorMessage(em.message, "CLI_ARGS");
514 :
515 111 : if (_throw_on_error)
516 14 : throw Parser::Error(messages);
517 : else
518 97 : mooseError(joinErrorMessages(messages));
519 : }
|