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 <limits>
17 : #include <map>
18 : #include <optional>
19 : #include "muparser_header.h"
20 :
21 : namespace gdal
22 : {
23 :
24 : /*! @cond Doxygen_Suppress */
25 :
26 177 : static std::optional<std::string> Sanitize(const std::string &osVariable)
27 : {
28 : // muparser does not allow characters '[' or ']' which we use to emulate
29 : // vectors. Replace these with a combination of underscores
30 177 : auto from = osVariable.find('[');
31 177 : if (from != std::string::npos)
32 : {
33 80 : auto to = osVariable.find(']');
34 80 : if (to != std::string::npos)
35 : {
36 160 : auto sanitized = std::string("__") + osVariable.substr(0, from) +
37 160 : +"__" +
38 240 : osVariable.substr(from + 1, to - from - 1) + "__";
39 80 : return sanitized;
40 : }
41 : }
42 :
43 97 : return std::nullopt;
44 : }
45 :
46 64 : static void ReplaceVariable(std::string &expression,
47 : const std::string &variable,
48 : const std::string &sanitized)
49 : {
50 64 : std::string::size_type seekPos = 0;
51 64 : auto pos = expression.find(variable, seekPos);
52 183 : while (pos != std::string::npos)
53 : {
54 119 : auto end = pos + variable.size();
55 :
56 219 : if (pos == 0 ||
57 100 : (!std::isalnum(expression[pos - 1]) && expression[pos - 1] != '_'))
58 : {
59 : expression =
60 76 : expression.substr(0, pos) + sanitized + expression.substr(end);
61 : }
62 :
63 119 : seekPos = end;
64 119 : pos = expression.find(variable, seekPos);
65 : }
66 64 : }
67 :
68 : class MuParserExpression::Impl
69 : {
70 : public:
71 53 : explicit Impl(std::string_view osExpression)
72 53 : : m_osExpression(std::string(osExpression))
73 : {
74 53 : }
75 :
76 148 : void Register(std::string_view osVariable, double *pdfValue)
77 : {
78 : try
79 : {
80 164 : m_oParser.DefineVar(std::string(osVariable), pdfValue);
81 : }
82 8 : catch (const mu::Parser::exception_type &)
83 : {
84 8 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid variable name: %s",
85 16 : std::string(osVariable).c_str());
86 8 : m_bCompileFailed = true;
87 : }
88 148 : }
89 :
90 61 : CPLErr Compile()
91 : {
92 61 : if (m_bCompileFailed)
93 : {
94 8 : return CE_Failure;
95 : }
96 :
97 : // On some platforms muparser does not seem to parse "nan" as a floating
98 : // point literal.
99 : try
100 : {
101 53 : m_oParser.DefineConst("nan",
102 : std::numeric_limits<double>::quiet_NaN());
103 53 : m_oParser.DefineConst("NaN",
104 : std::numeric_limits<double>::quiet_NaN());
105 : }
106 0 : catch (const mu::Parser::exception_type &)
107 : {
108 : }
109 :
110 : try
111 : {
112 106 : std::string tmpExpression(m_osExpression);
113 :
114 117 : for (const auto &[osFrom, osTo] : m_oSubstitutions)
115 : {
116 64 : ReplaceVariable(tmpExpression, osFrom, osTo);
117 : }
118 :
119 53 : m_oParser.SetExpr(tmpExpression);
120 : }
121 0 : catch (const mu::Parser::exception_type &e)
122 : {
123 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", e.GetMsg().c_str());
124 0 : return CE_Failure;
125 : }
126 0 : catch (const std::exception &e)
127 : {
128 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
129 0 : return CE_Failure;
130 : }
131 :
132 53 : return CE_None;
133 : }
134 :
135 18771 : CPLErr Evaluate()
136 : {
137 18771 : if (!m_bIsCompiled)
138 : {
139 53 : if (auto eErr = Compile(); eErr != CE_None)
140 : {
141 8 : return eErr;
142 : }
143 :
144 45 : m_bIsCompiled = true;
145 : }
146 :
147 : try
148 : {
149 : int nResults;
150 18763 : const double *dfResults = m_oParser.Eval(nResults);
151 18762 : m_adfResults.resize(nResults);
152 18762 : std::copy(dfResults, dfResults + nResults, m_adfResults.begin());
153 : }
154 1 : catch (const mu::Parser::exception_type &e)
155 : {
156 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s", e.GetMsg().c_str());
157 1 : return CE_Failure;
158 : }
159 0 : catch (const std::exception &e)
160 : {
161 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
162 0 : return CE_Failure;
163 : }
164 :
165 18762 : return CE_None;
166 : }
167 :
168 : const CPLString m_osExpression;
169 : std::map<CPLString, CPLString> m_oSubstitutions{};
170 : mu::Parser m_oParser{};
171 : std::vector<double> m_adfResults{1};
172 : bool m_bIsCompiled = false;
173 : bool m_bCompileFailed = false;
174 : };
175 :
176 53 : MuParserExpression::MuParserExpression(std::string_view osExpression)
177 53 : : m_pImpl{std::make_unique<Impl>(osExpression)}
178 :
179 : {
180 53 : }
181 :
182 106 : MuParserExpression::~MuParserExpression()
183 : {
184 106 : }
185 :
186 8 : CPLErr MuParserExpression::Compile()
187 : {
188 8 : return m_pImpl->Compile();
189 : }
190 :
191 148 : void MuParserExpression::RegisterVariable(std::string_view osVariable,
192 : double *pdfValue)
193 : {
194 296 : auto sanitized = Sanitize(std::string(osVariable));
195 148 : if (sanitized.has_value())
196 : {
197 51 : m_pImpl->m_oSubstitutions[std::string(osVariable)] = sanitized.value();
198 : }
199 148 : m_pImpl->Register(sanitized.value_or(std::string(osVariable)), pdfValue);
200 148 : }
201 :
202 7 : void MuParserExpression::RegisterVector(std::string_view osVariable,
203 : std::vector<double> *padfValues)
204 : {
205 : // muparser does not support vector variables, so we simulate them
206 : // by creating a scalar variable for each element, and then replacing
207 : // the name of the vector by a list of its elements before compiling
208 : // the expression.
209 14 : CPLString osElementVarName;
210 14 : CPLString osElementsList;
211 7 : std::string osVectorVarName(osVariable);
212 :
213 : int nElementVarNameLength = static_cast<int>(
214 7 : 4 + osVectorVarName.size() + std::log10(padfValues->size()));
215 7 : osElementsList.reserve(padfValues->size() *
216 7 : (1 + nElementVarNameLength)); // +1 for commas
217 :
218 36 : for (std::size_t i = 0; i < padfValues->size(); i++)
219 : {
220 : osElementVarName.Printf("%s[%d]", osVectorVarName.c_str(),
221 29 : static_cast<int>(i));
222 29 : osElementVarName = Sanitize(osElementVarName).value();
223 29 : RegisterVariable(osElementVarName, padfValues->data() + i);
224 :
225 29 : if (i > 0)
226 : {
227 22 : osElementsList += ",";
228 : }
229 29 : osElementsList += osElementVarName;
230 : }
231 :
232 7 : m_pImpl->m_oSubstitutions[osVectorVarName] = std::move(osElementsList);
233 7 : }
234 :
235 18771 : CPLErr MuParserExpression::Evaluate()
236 : {
237 18771 : return m_pImpl->Evaluate();
238 : }
239 :
240 18777 : const std::vector<double> &MuParserExpression::Results() const
241 : {
242 18777 : return m_pImpl->m_adfResults;
243 : }
244 :
245 : /*! @endcond Doxygen_Suppress */
246 :
247 : } // namespace gdal
|