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
|