LCOV - code coverage report
Current view: top level - frmts/vrt - vrtexpression_muparser.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 91 106 85.8 %
Date: 2025-07-05 13:22:42 Functions: 16 18 88.9 %

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

Generated by: LCOV version 1.14