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
|