| 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 |