LCOV - code coverage report
Current view: top level - src/parser - Parser.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: fef103 Lines: 240 263 91.3 %
Date: 2025-09-03 20:01:23 Functions: 21 22 95.5 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.14