GCC Code Coverage Report


Directory: ./
File: util/ExpressionProcessor.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 173 183 94.5%
Functions: 31 31 100.0%
Branches: 167 278 60.1%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14 #include <vector>
15 #include <cstring>
16 #include <cctype>
17 #include <algorithm>
18
19 #include <list>
20 #include <map>
21
22 #include "../log.h"
23 #include "ExpressionProcessor.h"
24
25 #include "tinyexpr.h"
26
27 // tinyexpr's own tokenizer (next_token() in tinyexpr.c) only starts an
28 // identifier on a lowercase a-z (not '_', not uppercase - see the
29 // `s->next[0] >= 'a' && s->next[0] <= 'z'` check), then allows a-z/0-9/_.
30 // Not true of names like "mqtt-some/topic" (hyphens, slashes), nor of an
31 // uppercase-led User Variable name. The
32 // "%%name%%" substitution form below is a plain map lookup and never cares
33 // about any of this; only the tinyexpr ("=..."/"==...==") math forms do.
34 93339 static bool isValidExprIdentifier(const std::string& name) {
35
6/8
✓ Branch 0 taken 93339 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 89143 times.
✓ Branch 3 taken 4196 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 89143 times.
✓ Branch 6 taken 4196 times.
✓ Branch 7 taken 89143 times.
93339 if (name.empty() || name[0] < 'a' || name[0] > 'z') {
36 4196 return false;
37 }
38
2/2
✓ Branch 0 taken 1490580 times.
✓ Branch 1 taken 88637 times.
1579217 for (char c : name) {
39
8/10
✓ Branch 0 taken 1315384 times.
✓ Branch 1 taken 175196 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1315384 times.
✓ Branch 4 taken 174694 times.
✓ Branch 5 taken 502 times.
✓ Branch 6 taken 174694 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 506 times.
✓ Branch 9 taken 174690 times.
1490580 if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_')) {
40 506 return false;
41 }
42 }
43 88637 return true;
44 }
45
46 // A character that continues a tinyexpr token - a name or a number - so a
47 // variable name with one of these next to it is only part of a token.
48 8 static bool isExprWordChar(char c) {
49
5/16
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 8 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 8 times.
8 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '.';
50 }
51
52 // A variable named "2" or "1.5" can't be told apart from the number it
53 // spells, so aliasing it would hijack that literal in every expression. Such a
54 // variable is still reachable as "%%2%%".
55 4702 static bool isNumericLiteral(const std::string& name) {
56 // Digit- or point-led only: strtod() also takes "Inf", "NaN" and leading
57 // blanks, and those names still need their alias.
58
7/10
✓ Branch 0 taken 4702 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4702 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4701 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 4701 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4701 times.
✓ Branch 9 taken 1 times.
4702 if (name.empty() || !((name[0] >= '0' && name[0] <= '9') || name[0] == '.')) {
59 4701 return false;
60 }
61 1 const char* start = name.c_str();
62 1 char* end = nullptr;
63 1 strtod(start, &end);
64
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 return end != start && *end == 0;
65 }
66
67 93393 ExpressionProcessor::ExpressionVariable::ExpressionVariable(const std::string& n) :
68 93393 name(n) {
69 93393 }
70 93393 ExpressionProcessor::ExpressionVariable::~ExpressionVariable() {
71 93393 }
72
73 93393 void ExpressionProcessor::ExpressionVariable::setValue(const std::string& s) {
74 93393 sValue = s;
75 try {
76
2/2
✓ Branch 0 taken 63101 times.
✓ Branch 1 taken 30292 times.
93393 dValue = std::stod(s);
77 30292 } catch (...) {
78 30292 dValue = 0;
79 30292 }
80 93393 }
81
82 class EvalStep {
83 public:
84 3247 EvalStep() {}
85 6494 virtual ~EvalStep() {}
86
87 virtual std::string eval() = 0;
88 };
89
90 class TinyExprEvalStep : public EvalStep {
91 public:
92 // tinyexpr recurses once per nesting level when it parses ("((((1))))",
93 // "abs abs abs 1"), and again per tree level when it evaluates and frees
94 // - so even a flat "1+1+1+..." becomes a left-deep tree as deep as it is
95 // long. With no limit, a ~60KB expression in a Set Variable or If command
96 // ran the calling thread out of stack. Every level costs at least one
97 // character, so capping the length caps all three; real expressions are
98 // a few dozen characters.
99 static constexpr size_t MAX_EXPRESSION_LENGTH = 4096;
100
101 1283 TinyExprEvalStep(const std::string& s, std::vector<te_variable>& exprVars) {
102
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1283 times.
1283 if (s.size() > MAX_EXPRESSION_LENGTH) {
103 ✗ LogWarn(VB_GENERAL, "Expression is %d characters long, the limit is %d - not evaluating it\n",
104 (int)s.size(), (int)MAX_EXPRESSION_LENGTH);
105 ✗ return;
106 }
107 1283 int err = 0;
108
1/2
✓ Branch 0 taken 1283 times.
✗ Branch 1 not taken.
1283 expr = te_compile(s.c_str(), exprVars.data(), exprVars.size(), &err);
109 ✗ }
110 5132 ~TinyExprEvalStep() {
111
2/2
✓ Branch 0 taken 1263 times.
✓ Branch 1 taken 20 times.
2566 if (expr) {
112 2526 te_free(expr);
113 }
114 5132 }
115
116 1283 bool ok() const { return expr != nullptr; }
117
118 1272 virtual std::string eval() override {
119
2/2
✓ Branch 0 taken 1260 times.
✓ Branch 1 taken 12 times.
1272 if (expr) {
120
1/2
✓ Branch 0 taken 1260 times.
✗ Branch 1 not taken.
1260 double d = te_eval(expr);
121 // "%lf" of a large value is long - 1e30 prints 31 digits before
122 // the point, and DBL_MAX over 300 - so a fixed buffer truncated
123 // it, and the zero-stripping below then returned a number with
124 // the wrong magnitude. Size the buffer to what snprintf needs.
125 1260 int need = snprintf(nullptr, 0, "%lf", d);
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1260 times.
1260 if (need <= 0) {
127 ✗ return "0";
128 }
129
1/2
✓ Branch 0 taken 1260 times.
✗ Branch 1 not taken.
1260 std::string buf(need + 1, '\0');
130 1260 snprintf(buf.data(), buf.size(), "%lf", d);
131
1/2
✓ Branch 0 taken 1260 times.
✗ Branch 1 not taken.
1260 buf.resize(need);
132
1/2
✓ Branch 0 taken 8795 times.
✗ Branch 1 not taken.
8795 for (int x = need - 1; x >= 0; x--) {
133
5/6
✓ Branch 0 taken 7540 times.
✓ Branch 1 taken 1255 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7540 times.
✓ Branch 4 taken 1255 times.
✓ Branch 5 taken 7540 times.
8795 if (buf[x] == '.' || buf[x] == ',') {
134
1/2
✓ Branch 0 taken 1255 times.
✗ Branch 1 not taken.
1255 buf.resize(x);
135 1255 return buf;
136 }
137
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 7535 times.
7540 if (buf[x] != '0') {
138 5 return buf;
139 }
140
1/2
✓ Branch 0 taken 7535 times.
✗ Branch 1 not taken.
7535 buf.resize(x);
141 }
142 // if it gets here, everything is 0
143 ✗ return "0";
144 1260 }
145
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
24 return "";
146 }
147
148 te_expr* expr = nullptr;
149 };
150 class TextEvalStep : public EvalStep {
151 public:
152 1957 TextEvalStep(const std::string& s) :
153
1/2
✓ Branch 0 taken 1957 times.
✗ Branch 1 not taken.
1957 str(s) {}
154 7828 ~TextEvalStep() {}
155
156 1949 virtual std::string eval() override {
157 1949 return str;
158 }
159
160 std::string str;
161 };
162 class VariableEvalStep : public EvalStep {
163 public:
164 7 VariableEvalStep(ExpressionProcessor::ExpressionVariable* v) :
165 7 var(v) {}
166 28 ~VariableEvalStep() {}
167
168 4 virtual std::string eval() override {
169
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (var) {
170 2 return var->getValue();
171 }
172
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 return "";
173 }
174
175 ExpressionProcessor::ExpressionVariable* var;
176 };
177
178 class ExpressionProcessorData {
179 public:
180 3237 ExpressionProcessorData() {}
181 3237 ~ExpressionProcessorData() {
182 3237 clear();
183 3237 }
184
185 // Steps first: a compiled tinyexpr tree only holds the variables'
186 // addresses, never the names, but nothing should outlive what it was
187 // compiled against.
188 6474 void clear() {
189
2/2
✓ Branch 0 taken 3247 times.
✓ Branch 1 taken 6474 times.
9721 for (auto a : steps) {
190
1/2
✓ Branch 0 taken 3247 times.
✗ Branch 1 not taken.
3247 delete a;
191 }
192 6474 steps.clear();
193
2/2
✓ Branch 0 taken 93339 times.
✓ Branch 1 taken 6474 times.
99813 for (auto& a : exprVars) {
194 93339 free((void*)a.name);
195 }
196 6474 exprVars.clear();
197 6474 }
198
199 93393 void bind(ExpressionProcessor::ExpressionVariable* var) {
200 93393 variables[var->getName()] = var;
201 93393 }
202 3237 bool compile(const std::string& s) {
203 // A second compile() replaces the first rather than appending to
204 // its steps and leaking its strdup'd names.
205 3237 clear();
206
207 // The "==...=="/"%%...%%" scan below re-copies the rest of the
208 // string at every marker, so it is quadratic: a 1MB input took over
209 // a second before the per-expression cap in TinyExprEvalStep was
210 // ever reached. Nothing legitimate is this long.
211
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3235 times.
3237 if (s.size() > TinyExprEvalStep::MAX_EXPRESSION_LENGTH) {
212
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogWarn(VB_GENERAL, "Expression is %d characters long, the limit is %d - not evaluating it\n",
213 (int)s.size(), (int)TinyExprEvalStep::MAX_EXPRESSION_LENGTH);
214 2 return false;
215 }
216
217
1/2
✓ Branch 0 taken 3235 times.
✗ Branch 1 not taken.
3235 exprVars.resize(variables.size());
218 // Names that aren't valid tinyexpr identifiers get a safe alias for
219 // tinyexpr's benefit only - the real name is still what "%%name%%"
220 // and bind()/variables map use.
221 3235 std::map<std::string, std::string> aliasForName;
222 3235 int cur = 0;
223
2/2
✓ Branch 0 taken 93339 times.
✓ Branch 1 taken 3235 times.
96574 for (auto& a : variables) {
224 93339 const std::string& name = a.second->getName();
225
1/2
✓ Branch 0 taken 93339 times.
✗ Branch 1 not taken.
93339 std::string boundName = name;
226
6/6
✓ Branch 0 taken 4702 times.
✓ Branch 1 taken 88637 times.
✓ Branch 2 taken 4701 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 4701 times.
✓ Branch 5 taken 88638 times.
93339 if (!isValidExprIdentifier(name) && !isNumericLiteral(name)) {
227 // Must itself satisfy isValidExprIdentifier() - lowercase-led.
228
1/2
✓ Branch 0 taken 4701 times.
✗ Branch 1 not taken.
4701 boundName = "ev" + std::to_string(cur);
229
2/4
✓ Branch 0 taken 4701 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4701 times.
✗ Branch 3 not taken.
4701 aliasForName[name] = boundName;
230 }
231 93339 exprVars[cur].name = strdup(boundName.c_str());
232 93339 exprVars[cur].address = &a.second->dValue;
233 93339 exprVars[cur].type = TE_VARIABLE;
234 93339 cur++;
235 93339 }
236
237 // Longest-name-first so aliasing one name can't be clobbered by a
238 // shorter name that happens to be a substring of it.
239 3235 std::vector<std::string> aliasedNames;
240
1/2
✓ Branch 0 taken 3235 times.
✗ Branch 1 not taken.
3235 aliasedNames.reserve(aliasForName.size());
241
2/2
✓ Branch 0 taken 4701 times.
✓ Branch 1 taken 3235 times.
7936 for (auto& kv : aliasForName) {
242
1/2
✓ Branch 0 taken 4701 times.
✗ Branch 1 not taken.
4701 aliasedNames.push_back(kv.first);
243 }
244
1/2
✓ Branch 0 taken 3235 times.
✗ Branch 1 not taken.
3235 std::sort(aliasedNames.begin(), aliasedNames.end(),
245 6810 [](const std::string& a, const std::string& b) { return a.size() > b.size(); });
246 // Only a whole token is replaced: a match with a word character
247 // right before or after it is part of a longer name or number. A
248 // plain find() turned "12" into "1ev0" for a variable named "2", and
249 // could rewrite the digits of an alias already substituted.
250 1283 auto aliasExpr = [&](std::string expr) {
251
2/2
✓ Branch 0 taken 2031 times.
✓ Branch 1 taken 1283 times.
3314 for (auto& name : aliasedNames) {
252
1/2
✓ Branch 0 taken 2031 times.
✗ Branch 1 not taken.
2031 const std::string& alias = aliasForName[name];
253 2031 std::size_t pos = 0;
254
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2031 times.
2038 while ((pos = expr.find(name, pos)) != std::string::npos) {
255 7 std::size_t end = pos + name.size();
256
5/8
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 7 times.
21 if ((pos > 0 && isExprWordChar(expr[pos - 1])) ||
257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
14 (end < expr.size() && isExprWordChar(expr[end]))) {
258 ✗ pos++;
259 ✗ continue;
260 }
261
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 expr.replace(pos, name.size(), alias);
262 7 pos += alias.size();
263 }
264 }
265 1283 return expr;
266 3235 };
267
268 3235 bool success = true;
269
270
8/8
✓ Branch 0 taken 1327 times.
✓ Branch 1 taken 1908 times.
✓ Branch 2 taken 1280 times.
✓ Branch 3 taken 47 times.
✓ Branch 4 taken 1279 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1279 times.
✓ Branch 7 taken 1956 times.
3235 if (s.size() > 1 && s[0] == '=' && s[1] != '=') {
271 // simple math expression
272
5/12
✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1279 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1279 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1279 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 1279 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
1279 auto step = new TinyExprEvalStep(aliasExpr(s.substr(1)), exprVars);
273 1279 success = step->ok();
274
1/2
✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
1279 steps.push_back(step);
275 } else {
276
1/2
✓ Branch 0 taken 1956 times.
✗ Branch 1 not taken.
1956 std::string cur = s;
277
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 1956 times.
2196 for (int x = 1; x < cur.size(); x++) {
278
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 209 times.
240 if (cur[x - 1] == cur[x]) {
279
6/6
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 20 times.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 20 times.
31 if (cur[x] == '=' || cur[x] == '%') {
280
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 11 times.
78 for (int y = x + 2; y < cur.size(); y++) {
281
5/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 56 times.
67 if (cur[y - 1] == cur[y] && cur[y] == cur[x]) {
282 11 int key = cur[x];
283
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 std::string start = cur.substr(0, x - 1);
284
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4 times.
11 if (start.size() > 0) {
285
4/10
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
7 steps.push_back(new TextEvalStep(start));
286 }
287
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 std::string expr = cur.substr(x + 1, y - x - 2);
288
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4 times.
11 if (key == '%') {
289 // find(), not operator[]: that inserted a
290 // null variable, which the next compile()
291 // dereferenced when binding.
292
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 auto it = variables.find(expr);
293
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
7 if (it == variables.end()) {
294 4 success = false;
295 }
296
5/10
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
7 steps.push_back(new VariableEvalStep(it == variables.end() ? nullptr : it->second));
297
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 } else if (key == '=') {
298
5/12
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 4 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
4 auto step = new TinyExprEvalStep(aliasExpr(expr), exprVars);
299
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!step->ok()) {
300 ✗ success = false;
301 }
302
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 steps.push_back(step);
303 } else {
304 ✗ steps.push_back(new TextEvalStep(expr));
305 }
306
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 cur = cur.substr(y + 1);
307 11 x = 0;
308 11 y = cur.size();
309 11 }
310 }
311 }
312 }
313 }
314
2/2
✓ Branch 0 taken 1950 times.
✓ Branch 1 taken 6 times.
1956 if (cur.size()) {
315
4/10
✓ Branch 0 taken 1950 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1950 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1950 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1950 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
1950 steps.push_back(new TextEvalStep(cur));
316 }
317 1956 }
318 3235 return success;
319 3235 }
320
321 3219 std::string evaluate(const std::string& type) {
322 3219 std::string s;
323
2/2
✓ Branch 0 taken 3225 times.
✓ Branch 1 taken 3219 times.
6444 for (auto step : steps) {
324
2/4
✓ Branch 0 taken 3225 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3225 times.
✗ Branch 3 not taken.
3225 s += step->eval();
325 }
326 3219 return s;
327 ✗ }
328
329 std::map<std::string, ExpressionProcessor::ExpressionVariable*> variables;
330 std::vector<te_variable> exprVars;
331 std::list<EvalStep*> steps;
332 };
333
334 3237 ExpressionProcessor::ExpressionProcessor() {
335
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3237 times.
3237 data = new ExpressionProcessorData();
336 3237 }
337 3237 ExpressionProcessor::~ExpressionProcessor() {
338
1/2
✓ Branch 0 taken 3237 times.
✗ Branch 1 not taken.
3237 if (data) {
339
1/2
✓ Branch 0 taken 3237 times.
✗ Branch 1 not taken.
3237 delete data;
340 }
341 3237 }
342
343 93393 void ExpressionProcessor::bindVariable(ExpressionVariable* var) {
344 93393 data->bind(var);
345 93393 }
346
347 3237 bool ExpressionProcessor::compile(const std::string& s) {
348 3237 return data->compile(s);
349 }
350
351 3219 std::string ExpressionProcessor::evaluate(const std::string& type) {
352 3219 return data->evaluate(type);
353 }
354