LCOV - code coverage report
Current view: top level - frmts/vrt - vrtexpression_muparser.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 94 109 86.2 %
Date: 2025-10-25 23:36:32 Functions: 17 19 89.5 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Virtual GDAL Datasets
       4             :  * Purpose:  Implementation of GDALExpressionEvaluator.
       5             :  * Author:   Daniel Baston
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2024, ISciences LLC
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "vrtexpression.h"
      14             : #include "cpl_string.h"
      15             : 
      16             : #include <cmath>
      17             : #include <limits>
      18             : #include <map>
      19             : #include <optional>
      20             : #include "muparser_header.h"
      21             : 
      22             : namespace gdal
      23             : {
      24             : 
      25             : /*! @cond Doxygen_Suppress */
      26             : 
      27           1 : static mu::value_type fmod(mu::value_type x, mu::value_type y)
      28             : {
      29           1 :     return std::fmod(x, y);
      30             : }
      31             : 
      32           1 : static mu::value_type isnan(mu::value_type x)
      33             : {
      34           1 :     return std::isnan(x);
      35             : }
      36             : 
      37           0 : static mu::value_type isnodata(void *userdata, mu::value_type x)
      38             : {
      39           0 :     double noData = *static_cast<double *>(userdata);
      40           0 :     return x == noData || (std::isnan(x) && std::isnan(noData));
      41             : }
      42             : 
      43           0 : static mu::value_type always_false(mu::value_type)
      44             : {
      45           0 :     return 0;
      46             : }
      47             : 
      48             : // Only newer versions of muparser have the DefineFunUserData method that we
      49             : // need to register the isnodata() function above. Since it's not clear what
      50             : // version this was introduced or how to check the version, we test for the
      51             : // method directly.
      52             : namespace
      53             : {
      54             : 
      55             : template <typename, typename = void>
      56             : struct HasDefineFunUserData : std::false_type
      57             : {
      58             : };
      59             : 
      60             : template <typename Parser>
      61             : struct HasDefineFunUserData<
      62             :     Parser, std::void_t<decltype(std::declval<Parser>().DefineFunUserData(
      63             :                 _T("x"), isnodata, nullptr))>> : std::true_type
      64             : {
      65             : };
      66             : 
      67             : template <typename T> void DefineIsNoDataFunction(T &parser)
      68             : {
      69             :     const auto &varmap = parser.GetVar();
      70             :     if (auto it = varmap.find("NODATA"); it != varmap.end())
      71             :     {
      72             :         parser.DefineFunUserData(_T("isnodata"), isnodata, it->second);
      73             :     }
      74             :     else
      75             :     {
      76             :         // muparser doesn't allow userData to be null, so we bind isnodata
      77             :         // to a dummy function instead
      78             :         parser.DefineFun(_T("isnodata"), always_false);
      79             :     }
      80             : }
      81             : 
      82             : }  // namespace
      83             : 
      84        1755 : bool MuParserHasDefineFunUserData()
      85             : {
      86             :     if constexpr (HasDefineFunUserData<mu::Parser>::value)
      87             :     {
      88             :         return true;
      89             :     }
      90             :     else
      91             :     {
      92        1755 :         return false;
      93             :     }
      94             : }
      95             : 
      96         673 : static std::optional<std::string> Sanitize(const std::string &osVariable)
      97             : {
      98             :     // muparser does not allow characters '[' or ']' which we use to emulate
      99             :     // vectors. Replace these with a combination of underscores
     100         673 :     auto from = osVariable.find('[');
     101         673 :     if (from != std::string::npos)
     102             :     {
     103         237 :         auto to = osVariable.find(']');
     104         237 :         if (to != std::string::npos)
     105             :         {
     106         474 :             auto sanitized = std::string("__") + osVariable.substr(0, from) +
     107         474 :                              +"__" +
     108         711 :                              osVariable.substr(from + 1, to - from - 1) + "__";
     109         237 :             return sanitized;
     110             :         }
     111             :     }
     112             : 
     113         436 :     return std::nullopt;
     114             : }
     115             : 
     116         220 : static void ReplaceVariable(std::string &expression,
     117             :                             const std::string &variable,
     118             :                             const std::string &sanitized)
     119             : {
     120         220 :     std::string::size_type seekPos = 0;
     121         220 :     auto pos = expression.find(variable, seekPos);
     122         499 :     while (pos != std::string::npos)
     123             :     {
     124         279 :         auto end = pos + variable.size();
     125             : 
     126         483 :         if (pos == 0 ||
     127         204 :             (!std::isalnum(expression[pos - 1]) && expression[pos - 1] != '_'))
     128             :         {
     129             :             expression =
     130         236 :                 expression.substr(0, pos) + sanitized + expression.substr(end);
     131             :         }
     132             : 
     133         279 :         seekPos = end;
     134         279 :         pos = expression.find(variable, seekPos);
     135             :     }
     136         220 : }
     137             : 
     138             : class MuParserExpression::Impl
     139             : {
     140             :   public:
     141         356 :     explicit Impl(std::string_view osExpression)
     142         356 :         : m_osExpression(std::string(osExpression))
     143             :     {
     144         356 :     }
     145             : 
     146         644 :     void Register(std::string_view osVariable, double *pdfValue)
     147             :     {
     148             :         try
     149             :         {
     150         646 :             m_oParser.DefineVar(std::string(osVariable), pdfValue);
     151             :         }
     152           1 :         catch (const mu::Parser::exception_type &)
     153             :         {
     154           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid variable name: %s",
     155           2 :                      std::string(osVariable).c_str());
     156           1 :             m_bCompileFailed = true;
     157             :         }
     158         644 :     }
     159             : 
     160         362 :     CPLErr Compile()
     161             :     {
     162         362 :         if (m_bCompileFailed)
     163             :         {
     164           1 :             return CE_Failure;
     165             :         }
     166             : 
     167             :         // On some platforms muparser does not seem to parse "nan" as a floating
     168             :         // point literal.
     169             :         try
     170             :         {
     171         361 :             m_oParser.DefineConst("nan",
     172             :                                   std::numeric_limits<double>::quiet_NaN());
     173         361 :             m_oParser.DefineConst("NaN",
     174             :                                   std::numeric_limits<double>::quiet_NaN());
     175             :         }
     176           0 :         catch (const mu::Parser::exception_type &)
     177             :         {
     178             :         }
     179             : 
     180             :         try
     181             :         {
     182         361 :             m_oParser.DefineFun(_T("isnan"), isnan);
     183         361 :             m_oParser.DefineFun(_T("fmod"), fmod);
     184             : 
     185             :             // Check to see if a NODATA variable has been defined and, if so,
     186             :             // bind it to the isnodata() function
     187             :             if constexpr (HasDefineFunUserData<mu::Parser>::value)
     188             :             {
     189             :                 // gcc 9.4 still requires the code disabled by if constexpr to
     190             :                 // compile, so we hide it in a templated function
     191             :                 DefineIsNoDataFunction(m_oParser);
     192             :             }
     193             : 
     194             :             // Edit the expression to replace variable names such as X[1] with
     195             :             // their sanitized versions
     196         722 :             std::string tmpExpression(m_osExpression);
     197             : 
     198         581 :             for (const auto &[osFrom, osTo] : m_oSubstitutions)
     199             :             {
     200         220 :                 ReplaceVariable(tmpExpression, osFrom, osTo);
     201             :             }
     202             : 
     203         361 :             m_oParser.SetExpr(tmpExpression);
     204             :         }
     205           0 :         catch (const mu::Parser::exception_type &e)
     206             :         {
     207           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s", e.GetMsg().c_str());
     208           0 :             return CE_Failure;
     209             :         }
     210           0 :         catch (const std::exception &e)
     211             :         {
     212           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
     213           0 :             return CE_Failure;
     214             :         }
     215             : 
     216         361 :         return CE_None;
     217             :     }
     218             : 
     219    12991600 :     CPLErr Evaluate()
     220             :     {
     221    12991600 :         if (!m_bIsCompiled)
     222             :         {
     223         354 :             if (auto eErr = Compile(); eErr != CE_None)
     224             :             {
     225           1 :                 return eErr;
     226             :             }
     227             : 
     228         353 :             m_bIsCompiled = true;
     229             :         }
     230             : 
     231             :         try
     232             :         {
     233             :             int nResults;
     234    12991500 :             const double *dfResults = m_oParser.Eval(nResults);
     235    12991500 :             m_adfResults.resize(nResults);
     236    12991500 :             std::copy(dfResults, dfResults + nResults, m_adfResults.begin());
     237             :         }
     238           1 :         catch (const mu::Parser::exception_type &e)
     239             :         {
     240           1 :             CPLError(CE_Failure, CPLE_AppDefined, "%s", e.GetMsg().c_str());
     241           1 :             return CE_Failure;
     242             :         }
     243           0 :         catch (const std::exception &e)
     244             :         {
     245           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
     246           0 :             return CE_Failure;
     247             :         }
     248             : 
     249    12991500 :         return CE_None;
     250             :     }
     251             : 
     252             :     const CPLString m_osExpression;
     253             :     std::map<CPLString, CPLString> m_oSubstitutions{};
     254             :     mu::Parser m_oParser{};
     255             :     std::vector<double> m_adfResults{1};
     256             :     bool m_bIsCompiled = false;
     257             :     bool m_bCompileFailed = false;
     258             : };
     259             : 
     260         356 : MuParserExpression::MuParserExpression(std::string_view osExpression)
     261         356 :     : m_pImpl{std::make_unique<Impl>(osExpression)}
     262             : 
     263             : {
     264         356 : }
     265             : 
     266         712 : MuParserExpression::~MuParserExpression()
     267             : {
     268         712 : }
     269             : 
     270           8 : CPLErr MuParserExpression::Compile()
     271             : {
     272           8 :     return m_pImpl->Compile();
     273             : }
     274             : 
     275         644 : void MuParserExpression::RegisterVariable(std::string_view osVariable,
     276             :                                           double *pdfValue)
     277             : {
     278        1288 :     auto sanitized = Sanitize(std::string(osVariable));
     279         644 :     if (sanitized.has_value())
     280             :     {
     281         208 :         m_pImpl->m_oSubstitutions[std::string(osVariable)] = sanitized.value();
     282             :     }
     283         644 :     m_pImpl->Register(sanitized.value_or(std::string(osVariable)), pdfValue);
     284         644 : }
     285             : 
     286           7 : void MuParserExpression::RegisterVector(std::string_view osVariable,
     287             :                                         std::vector<double> *padfValues)
     288             : {
     289             :     // muparser does not support vector variables, so we simulate them
     290             :     // by creating a scalar variable for each element, and then replacing
     291             :     // the name of the vector by a list of its elements before compiling
     292             :     // the expression.
     293          14 :     CPLString osElementVarName;
     294          14 :     CPLString osElementsList;
     295           7 :     std::string osVectorVarName(osVariable);
     296             : 
     297             :     int nElementVarNameLength = static_cast<int>(
     298           7 :         4 + osVectorVarName.size() + std::log10(padfValues->size()));
     299           7 :     osElementsList.reserve(padfValues->size() *
     300           7 :                            (1 + nElementVarNameLength));  // +1 for commas
     301             : 
     302          36 :     for (std::size_t i = 0; i < padfValues->size(); i++)
     303             :     {
     304             :         osElementVarName.Printf("%s[%d]", osVectorVarName.c_str(),
     305          29 :                                 static_cast<int>(i));
     306          29 :         osElementVarName = Sanitize(osElementVarName).value();
     307          29 :         RegisterVariable(osElementVarName, padfValues->data() + i);
     308             : 
     309          29 :         if (i > 0)
     310             :         {
     311          22 :             osElementsList += ",";
     312             :         }
     313          29 :         osElementsList += osElementVarName;
     314             :     }
     315             : 
     316           7 :     m_pImpl->m_oSubstitutions[osVectorVarName] = std::move(osElementsList);
     317           7 : }
     318             : 
     319    12991600 : CPLErr MuParserExpression::Evaluate()
     320             : {
     321    12991600 :     return m_pImpl->Evaluate();
     322             : }
     323             : 
     324    12991600 : const std::vector<double> &MuParserExpression::Results() const
     325             : {
     326    12991600 :     return m_pImpl->m_adfResults;
     327             : }
     328             : 
     329             : /*! @endcond Doxygen_Suppress */
     330             : 
     331             : }  // namespace gdal

Generated by: LCOV version 1.14