Branch data Line data Source code
1 : : #pragma once
2 : :
3 : : /*
4 : : MIT License
5 : :
6 : : Copyright (c) 2014-2024 Stephane Cuillerdier (aka aiekick)
7 : :
8 : : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : : of this software and associated documentation files (the "Software"), to deal
10 : : in the Software without restriction, including without limitation the rights
11 : : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : : copies of the Software, and to permit persons to whom the Software is
13 : : furnished to do so, subject to the following conditions:
14 : :
15 : : The above copyright notice and this permission notice shall be included in all
16 : : copies or substantial portions of the Software.
17 : :
18 : : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : : SOFTWARE.
25 : : */
26 : :
27 : : // ezExpr is part of the ezLibs project : https://github.com/aiekick/ezLibs.git
28 : :
29 : : #ifndef _USE_MATH_DEFINES
30 : : #define _USE_MATH_DEFINES
31 : : #endif // _USE_MATH_DEFINES
32 : :
33 : : #include <array>
34 : : #include <math.h>
35 : : #include <string>
36 : : #include <vector>
37 : : #include <limits>
38 : : #include <memory>
39 : : #include <chrono>
40 : : #include <cstring>
41 : : #include <sstream>
42 : : #include <iostream>
43 : : #include <functional>
44 : : #include <unordered_map>
45 : :
46 : : #ifndef DONT_DEFINE_DEFAULT_BUILTINS
47 : : #define DEFINE_DEFAULT_BUILTINS
48 : : #endif // DONT_DEFINE_DEFAULT_BUILTINS
49 : :
50 : : #ifndef DONT_USE_PERFO_MEASURING
51 : : #define USE_PERFO_MEASURING
52 : : #endif // DONT_USE_PERFO_MEASURING
53 : :
54 : : namespace ez {
55 : :
56 : : // Class that encapsulates a string
57 : : class String {
58 : : private:
59 : : char* m_Data; // Pointer to the character string
60 : : size_t m_Length; // Length of the string
61 : :
62 : : public:
63 : : // Default constructor: initializes members to nullptr and length to 0
64 : 997 : String() : m_Data(nullptr), m_Length(0) {}
65 : :
66 : : // Constructor from a std::string
67 : 24 : String(const ::std::string& str) {
68 [ + - ]: 24 : if (!str.empty()) {
69 : 24 : m_Length = str.size();
70 : 24 : m_Data = new char[m_Length + 1];
71 : 24 : ::std::memcpy(m_Data, str.data(), m_Length + 1);
72 : 24 : } else {
73 : 0 : m_Data = nullptr;
74 : 0 : m_Length = 0;
75 : 0 : }
76 : 24 : }
77 : :
78 : : // Constructor from a C-style string (const char*)
79 : 26010311 : String(const char* str) {
80 [ + - ]: 26010311 : if (str) {
81 : 26010311 : m_Length = ::std::strlen(str);
82 : 26010311 : m_Data = new char[m_Length + 1];
83 : 26010311 : ::std::memcpy(m_Data, str, m_Length + 1);
84 : 26010311 : } else {
85 : 0 : m_Data = nullptr;
86 : 0 : m_Length = 0;
87 : 0 : }
88 : 26010311 : }
89 : :
90 : : // Constructor from a single character
91 : 815 : String(char ch) {
92 : 815 : m_Length = 1;
93 : 815 : m_Data = new char[2];
94 : 815 : m_Data[0] = ch;
95 : 815 : m_Data[1] = '\0';
96 : 815 : }
97 : :
98 : : // Copy constructor
99 : 9848 : String(const String& other) {
100 [ + + ]: 9848 : if (other.m_Data) {
101 : 9768 : m_Length = other.m_Length;
102 : 9768 : m_Data = new char[m_Length + 1];
103 : 9768 : ::std::memcpy(m_Data, other.m_Data, m_Length + 1);
104 : 9768 : } else {
105 : 80 : m_Data = nullptr;
106 : 80 : m_Length = 0;
107 : 80 : }
108 : 9848 : }
109 : :
110 : : // Move constructor
111 : 5392 : String(String&& other) noexcept : m_Data(other.m_Data), m_Length(other.m_Length) {
112 : 5392 : other.m_Data = nullptr;
113 : 5392 : other.m_Length = 0;
114 : 5392 : }
115 : :
116 : : // Copy assignment operator
117 : 84000079 : char operator[](const size_t idx) {
118 : 84000079 : return m_Data[idx]; // no verification
119 : 84000079 : }
120 : :
121 : : // Copy assignment operator
122 : 558 : String& operator=(const String& other) {
123 [ + - ]: 558 : if (this != &other) { // Avoid self-assignment
124 : 558 : delete[] m_Data; // Free existing memory
125 [ + - ]: 558 : if (other.m_Data) {
126 : 558 : m_Length = other.m_Length;
127 : 558 : m_Data = new char[m_Length + 1];
128 : 558 : ::std::memcpy(m_Data, other.m_Data, m_Length + 1);
129 : 558 : } else {
130 : 0 : m_Data = nullptr;
131 : 0 : m_Length = 0;
132 : 0 : }
133 : 558 : }
134 : 558 : return *this;
135 : 558 : }
136 : :
137 : : // Move assignment operator
138 : 677 : String& operator=(String&& other) noexcept {
139 [ + - ]: 677 : if (this != &other) { // Avoid self-assignment
140 : 677 : delete[] m_Data; // Free existing memory
141 : 677 : m_Data = other.m_Data;
142 : 677 : m_Length = other.m_Length;
143 : 677 : other.m_Data = nullptr; // Reset moved object
144 : 677 : other.m_Length = 0;
145 : 677 : }
146 : 677 : return *this;
147 : 677 : }
148 : :
149 : : // Destructor: releases allocated memory
150 : 26027387 : ~String() { delete[] m_Data; }
151 : :
152 : : // Static method to create a String from a double
153 : 284 : static String fromDouble(double value) {
154 : 284 : char buffer[32];
155 : 284 : ::std::snprintf(buffer, sizeof(buffer), "%.16g", value); // Convert double to string
156 : 284 : return String(buffer);
157 : 284 : }
158 : :
159 : : // Method to create a std::string from a String
160 : 0 : ::std::string to_string() const {
161 : 0 : if (m_Data == nullptr) {
162 : 0 : return {};
163 : 0 : }
164 : 0 : return std::string(m_Data, m_Length);
165 : 0 : }
166 : :
167 : : // Equality operator
168 : 88005411 : bool operator==(const String& other) const {
169 [ + + ]: 88005411 : if (m_Length != other.m_Length) {
170 : 3881 : return false; // If lengths are different, strings are different
171 : 3881 : }
172 : 88001530 : return ::std::memcmp(m_Data, other.m_Data, m_Length) == 0; // Compare data
173 : 88005411 : }
174 : :
175 : : // Inequality operator
176 : 0 : bool operator!=(const String& other) const { return !(*this == other); }
177 : :
178 : : // Equality operator with a C-style string
179 : 558 : bool operator==(const char* str) const { return ::std::strcmp(m_Data, str) == 0; }
180 : :
181 : : // Inequality operator with a C-style string
182 : 0 : bool operator!=(const char* str) const { return ::std::strcmp(m_Data, str) != 0; }
183 : :
184 : : // Returns the encapsulated C-style string
185 [ + - ]: 88028110 : const char* c_str() const { return m_Data ? m_Data : ""; }
186 : :
187 : : // Returns the length of the string
188 : 232134615 : size_t length() const { return m_Length; }
189 : :
190 : : // Concatenation operator with another String
191 : 484 : String operator+(const String& other) const {
192 : 484 : size_t newLength = m_Length + other.m_Length; // Calculate new length
193 : 484 : char* newData = new char[newLength + 1]; // Allocate memory
194 : 484 : ::std::memcpy(newData, m_Data, m_Length); // Copy first string
195 : 484 : ::std::memcpy(newData + m_Length, other.m_Data, other.m_Length + 1); // Copy second string
196 : 484 : return String(newData); // Return new String
197 : 484 : }
198 : :
199 : : // Concatenation operator with a C-style string
200 : 145 : String operator+(const char* str) const {
201 : 145 : size_t strLength = ::std::strlen(str); // Calculate length of str
202 : 145 : size_t newLength = m_Length + strLength; // Calculate new length
203 : 145 : char* newData = new char[newLength + 1]; // Allocate memory
204 : 145 : ::std::memcpy(newData, m_Data, m_Length); // Copy first string
205 : 145 : ::std::memcpy(newData + m_Length, str, strLength + 1); // Copy str
206 : 145 : return String(newData); // Return new String
207 : 145 : }
208 : :
209 : : // Stream output operator overload for displaying the string
210 : 0 : friend ::std::ostream& operator<<(::std::ostream& os, const String& str) {
211 : 0 : os << str.c_str(); // Write the string to the stream
212 : 0 : return os;
213 : 0 : }
214 : : };
215 : :
216 : : } // namespace ez
217 : :
218 : : namespace std {
219 : :
220 : : // Specialization of std::hash to use ez::String as a key in containers
221 : : template <>
222 : : struct hash<ez::String> {
223 : 88027340 : size_t operator()(const ez::String& s) const noexcept {
224 : 88027340 : size_t hash = 0;
225 : 88027340 : const char* data = s.c_str();
226 [ + + ]: 232134557 : for (size_t i = 0; i < s.length(); ++i) {
227 : 144107217 : hash = hash * 31 + static_cast<size_t>(data[i]); // Simple hash calculation
228 : 144107217 : }
229 : 88027340 : return hash;
230 : 88027340 : }
231 : : };
232 : :
233 : : } // namespace std
234 : :
235 : : namespace ez {
236 : :
237 : : // Overload of the + operator to allow concatenation of const char* and ez::String
238 : 29 : static inline String operator+(const char* lhs, const String& rhs) {
239 : 29 : size_t lhsLength = ::std::strlen(lhs); // Calculate length of lhs
240 : 29 : size_t newLength = lhsLength + rhs.length(); // Calculate new length
241 : 29 : char* newData = new char[newLength + 1]; // Allocate memory
242 : 29 : ::std::memcpy(newData, lhs, lhsLength); // Copy lhs
243 : 29 : ::std::memcpy(newData + lhsLength, rhs.c_str(), rhs.length() + 1); // Copy rhs
244 : 29 : return String(newData); // Return new String
245 : 29 : }
246 : :
247 : : // Definition of aliases for functions with different numbers of arguments
248 : : typedef ::std::function<double(double)> UnaryFunctor;
249 : : typedef ::std::function<double(double, double)> BinaryFunctor;
250 : : typedef ::std::function<double(double, double, double)> TernaryFunctor;
251 : : typedef ::std::unordered_map<String, double> VarContainer;
252 : : typedef ::std::unordered_map<String, double> ConstantContainer;
253 : :
254 : : // Structure representing a function with its number of arguments and behavior
255 : : struct Function {
256 : : UnaryFunctor unaryFunctor = nullptr;
257 : : BinaryFunctor binaryFunctor = nullptr;
258 : : TernaryFunctor ternaryFunctor = nullptr;
259 : : size_t argCount = 0U;
260 : : };
261 : :
262 : : // Container to store available functions
263 : : typedef ::std::unordered_map<String, Function> FunctionContainer;
264 : :
265 : : // Definition of possible token types
266 : : enum class TokenType { NUMBER = 0, LPAREN, RPAREN, VARIABLE, OPERATOR, FUNCTION, SEPARATOR, Count };
267 : :
268 : : // Structure representing a token (lexical unit)
269 : : struct Token {
270 : : TokenType type;
271 : : String value;
272 : : };
273 : :
274 : : // Definition of possible error codes
275 : : enum class ErrorCode {
276 : : NONE = 0,
277 : : PARSE_ERROR,
278 : : EVALUATION_NAN,
279 : : EVALUATION_INF,
280 : : DIVISION_BY_ZERO,
281 : : UNKNOWN_NODE_TYPE,
282 : : EMPTY_PARENTHESIS,
283 : : VARIABLE_NOT_FOUND,
284 : : FUNCTION_NOT_FOUND,
285 : : OPERATOR_NOT_FOUND,
286 : : UNMATCHED_PARENTHESIS,
287 : : FUNCTION_WRONG_ARGUMENTS_COUNT,
288 : : Count
289 : : };
290 : :
291 : : // Definition of possible node types in a syntax tree
292 : : enum class NodeType { NUMBER = 0, VARIABLE, OPERATOR, FUNCTION, Count };
293 : :
294 : : // Structure representing a node in the syntax tree
295 : : struct Node {
296 : : NodeType type = NodeType::NUMBER;
297 : : String name;
298 : : double value = 0.0;
299 : : ::std::array<::std::shared_ptr<Node>, 3> childs{}; // Array of pointers to child nodes
300 : : ::std::array<double, 3> tmp{}; // for avoid allocation during evaluation
301 : : size_t childCount = 0;
302 : : };
303 : :
304 : : // Class to manage exceptions specific to expression evaluation
305 : : class ExprException : public ::std::exception {
306 : : public:
307 : 56 : ExprException(ErrorCode code, const String& message) : m_Code(code), m_Message(message) {}
308 : :
309 : 56 : ErrorCode getCode() const { return m_Code; }
310 : :
311 : 56 : const char* what() const noexcept override {
312 : 56 : return m_Message.c_str(); // Return the error message
313 : 56 : }
314 : :
315 : : private:
316 : : ErrorCode m_Code;
317 : : String m_Message;
318 : : };
319 : :
320 : : // Main class for evaluating mathematical expressions
321 : : class Expr {
322 : : private:
323 : : String m_Expr; // Expression to evaluate
324 : : Node m_RootExpr; // Root of the syntax tree
325 : : VarContainer m_ParsedVariables; // Container for variables found during parsing
326 : : VarContainer m_DefinedVariables; // Container for variables defined after parsing
327 : : ConstantContainer m_Constant; // Container for constants
328 : : FunctionContainer m_Functions; // Container for functions
329 : : double m_EvalResult = 0.0; // Evaluation result
330 : : bool m_Verbose = false; // Verbose mode
331 : : ::std::stringstream m_printExpr; // Stream to print the expression and result
332 : : ::std::chrono::duration<double, ::std::milli> m_Elapsed; // Evaluation time
333 : : ::std::chrono::steady_clock::time_point m_StartTime;
334 : : ::std::chrono::steady_clock::time_point m_EndTime;
335 : :
336 : : public:
337 : 202 : Expr() {
338 : 202 : #ifdef DEFINE_DEFAULT_BUILTINS
339 : 202 : defineDefaultBuiltins();
340 : 202 : #endif // DEFINE_DEFAULT_BUILTINS
341 : 202 : }
342 : :
343 : 202 : void defineDefaultBuiltins() {
344 : : // Initialization of common constants
345 : 202 : addConstant("pi", M_PI); // PI
346 : 202 : addConstant("e", M_E); // e
347 : :
348 : : // Initialization of common unary functions
349 : 202 : addFunction("-", [](double a) { return -a; });
350 : :
351 : 2000003 : addFunction("abs", [](double a) { return ::std::abs(a); });
352 : 202 : addFunction("floor", [](double a) { return ::std::floor(a); });
353 : 202 : addFunction("ceil", [](double a) { return ::std::ceil(a); });
354 : 202 : addFunction("round", [](double a) { return ::std::round(a); });
355 : :
356 : 202 : addFunction("fract", [](double a) { return a - ::std::floor(a); });
357 : 202 : addFunction("sign", [this](double a) { return m_Sign(a); });
358 : :
359 : 4000003 : addFunction("sin", [](double a) { return ::std::sin(a); });
360 : 2000003 : addFunction("cos", [](double a) { return ::std::cos(a); });
361 : 202 : addFunction("tan", [](double a) { return ::std::tan(a); });
362 : :
363 : 202 : addFunction("asin", [](double a) { return ::std::asin(a); });
364 : 202 : addFunction("acos", [](double a) { return ::std::acos(a); });
365 : 202 : addFunction("atan", [](double a) { return ::std::atan(a); });
366 : :
367 : 202 : addFunction("sinh", [](double a) { return ::std::sinh(a); });
368 : 202 : addFunction("cosh", [](double a) { return ::std::cosh(a); });
369 : 202 : addFunction("tanh", [](double a) { return ::std::tanh(a); });
370 : :
371 : 202 : addFunction("asinh", [](double a) { return ::std::asinh(a); });
372 : 202 : addFunction("acosh", [](double a) { return ::std::acosh(a); });
373 : 202 : addFunction("atanh", [](double a) { return ::std::atanh(a); });
374 : :
375 : 202 : addFunction("ln", [](double a) { return ::std::log(a); });
376 : 202 : addFunction("log", [](double a) { return ::std::log(a); });
377 : 202 : addFunction("log1p", [](double a) { return ::std::log1p(a); });
378 : 202 : addFunction("logb", [](double a) { return ::std::logb(a); });
379 : 202 : addFunction("log2", [](double a) { return ::std::log2(a); });
380 : 202 : addFunction("log10", [](double a) { return ::std::log10(a); });
381 : :
382 : 4000004 : addFunction("sqrt", [](double a) { return ::std::sqrt(a); });
383 : 202 : addFunction("exp", [](double a) { return ::std::exp(a); });
384 : :
385 : 202 : addFunction("fact", [this](double a) { return m_Factorial(a); });
386 : :
387 : 202 : addFunction("saturate", [this](double a) { return m_Clamp(a, 0.0, 1.0); });
388 : :
389 : : // Initialization of common binary functions
390 : 202 : addFunction("mod", [](double a, double b) { return ::std::fmod(a, b); });
391 : 4000000 : addFunction("pow", [](double a, double b) {
392 [ - + ]: 4000000 : if (a < 0.0) {
393 : 0 : throw ExprException(ErrorCode::EVALUATION_NAN, "Pow base value is negative");
394 : 0 : }
395 : 4000000 : return ::std::pow(a, b);
396 : 4000000 : });
397 : 202 : addFunction("atan2", [](double a, double b) { return ::std::atan2(a, b); });
398 : 202 : addFunction("min", [this](double a, double b) { return m_Min(a, b); });
399 : 202 : addFunction("max", [this](double a, double b) { return m_Max(a, b); });
400 : 202 : addFunction("step", [this](double a, double b) { return m_Step(a, b); });
401 : 202 : addFunction("hypot", [](double a, double b) { return ::std::hypot(a, b); });
402 : 202 : addFunction("smoothabs", [this](double a, double b) { return m_SmoothAbs(a, b); });
403 : :
404 : : // Initialization of common ternary functions
405 : 202 : addFunction("clamp", [this](double a, double b, double c) { return m_Clamp(a, b, c); });
406 : 202 : addFunction("lerp", [this](double a, double b, double c) { return m_Mix(a, b, c); });
407 : 202 : addFunction("mix", [this](double a, double b, double c) { return m_Mix(a, b, c); });
408 : 202 : addFunction("smoothstep", [this](double a, double b, double c) { return m_SmoothStep(a, b, c); });
409 : 202 : }
410 : :
411 : : // Method to parse an expression
412 : 202 : Expr& parse(const String& vExpr) {
413 : 202 : m_Expr = vExpr; // Store the expression
414 : 202 : m_ParsedVariables.clear(); // clearing discoverd vairable during parsing
415 : 202 : auto tokens = m_tokenize(m_Expr); // Tokenize the expression
416 : 202 : size_t pos = 0;
417 : 202 : m_RootExpr = m_parseExpression(tokens, pos, 0); // Parse the expression to create the syntax tree
418 : :
419 : : // Check for remaining tokens after parsing
420 [ + + ]: 202 : if (pos < tokens.size()) {
421 : : // If the next token is an unmatched closing parenthesis
422 [ + + ]: 11 : if (tokens[pos].value == ")") {
423 : 3 : throw ExprException(ErrorCode::UNMATCHED_PARENTHESIS, "Unmatched parenthesis found at the end of the expression");
424 : 3 : }
425 : : // If other tokens remain, which is also abnormal
426 : 8 : throw ExprException(ErrorCode::PARSE_ERROR, "Unexpected token found after complete parsing.");
427 : 11 : }
428 : :
429 : 191 : return *this;
430 : 202 : }
431 : :
432 : : // Method to set the value of a variable
433 : 26000000 : Expr& set(const String& vName, const double vValue, const bool vIfNotExist = false) {
434 [ - + ]: 26000000 : if (vIfNotExist) {
435 [ # # ]: 0 : if (m_DefinedVariables.find(vName) != m_DefinedVariables.end()) { // If exists
436 : 0 : return *this;
437 : 0 : }
438 : 0 : }
439 : 26000000 : m_DefinedVariables[vName] = vValue;
440 : 26000000 : return *this;
441 : 26000000 : }
442 : :
443 : : // Method to evaluate the expression
444 : 24000136 : Expr& eval() {
445 : 24000136 : m_evalNode(m_RootExpr, m_EvalResult); // Evaluate the root of the syntax tree
446 : 24000136 : return *this;
447 : 24000136 : }
448 : :
449 : 0 : Expr& startTime() {
450 : 0 : m_StartTime = ::std::chrono::steady_clock::now(); // Start time
451 : 0 : return *this;
452 : 0 : }
453 : :
454 : 0 : Expr& endTime() {
455 : 0 : m_EndTime = ::std::chrono::steady_clock::now(); // End time
456 : 0 : return *this;
457 : 0 : }
458 : :
459 : : // Returns the evaluation time in milliseconds
460 : 0 : double getEvalTime() {
461 : 0 : m_Elapsed = m_EndTime - m_StartTime;
462 : 0 : return m_Elapsed.count();
463 : 0 : }
464 : :
465 : : // Method to add a constant
466 : 404 : Expr& addConstant(const String& vName, const double vValue) {
467 : 404 : m_Constant[vName] = vValue; // Add the constant
468 : 404 : return *this;
469 : 404 : }
470 : :
471 : : // Method to add a unary function
472 : 5858 : Expr& addFunction(const String& vName, const UnaryFunctor& functor) {
473 : 5858 : Function fun;
474 : 5858 : fun.unaryFunctor = functor;
475 : 5858 : fun.argCount = 1;
476 : 5858 : m_Functions[vName] = fun;
477 : 5858 : return *this;
478 : 5858 : }
479 : :
480 : : // Method to add a binary function
481 : 1616 : Expr& addFunction(const String& vName, const BinaryFunctor& functor) {
482 : 1616 : Function fun;
483 : 1616 : fun.binaryFunctor = functor;
484 : 1616 : fun.argCount = 2;
485 : 1616 : m_Functions[vName] = fun;
486 : 1616 : return *this;
487 : 1616 : }
488 : :
489 : : // Method to add a ternary function
490 : 808 : Expr& addFunction(const String& vName, const TernaryFunctor& functor) {
491 : 808 : Function fun;
492 : 808 : fun.ternaryFunctor = functor;
493 : 808 : fun.argCount = 3;
494 : 808 : m_Functions[vName] = fun;
495 : 808 : return *this;
496 : 808 : }
497 : :
498 : : // Method to enable or disable verbose mode
499 : 40 : Expr& setVerbose(bool vVerbose) {
500 : 40 : m_Verbose = vVerbose;
501 : 40 : return *this;
502 : 40 : }
503 : :
504 : : // Returns the evaluation result
505 : 24000000 : double getResult() { return m_EvalResult; }
506 : :
507 : : // Prints the expression and its result
508 : 0 : Expr& print() {
509 : 0 : m_printExpr = {};
510 : 0 : m_printExpr << "Expr \"" << m_Expr << "\"";
511 : 0 : for (const auto& it : m_DefinedVariables) {
512 : 0 : m_printExpr << ", " << it.first << " = " << it.second;
513 : 0 : }
514 : 0 : m_printExpr << " => " << m_EvalResult;
515 : 0 : ::std::cout << m_printExpr.str() << " (" << getEvalTime() << " ms)" << ::std::endl;
516 : 0 : return *this;
517 : 0 : }
518 : :
519 : : // Checks if the evaluation result is close to a given value
520 : 122 : bool check(const double vValue) { return (::std::abs(m_EvalResult - vValue) < 0.001); }
521 : :
522 : : // test if a variable found during parsing exist
523 : 0 : bool isParsedVariableExist(const String& vName) { return (m_ParsedVariables.find(vName) != m_ParsedVariables.end()); }
524 : :
525 : : // Returns variables as a constant reference
526 : 0 : const VarContainer& getParsedVars() { return m_ParsedVariables; }
527 : :
528 : : // Returns variables as a modifiable reference
529 : 0 : VarContainer& getParsedVarsRef() { return m_ParsedVariables; }
530 : :
531 : : // test if a defined variable exist
532 : 0 : bool isDefinedVariableExist(const String& vName) { return (m_DefinedVariables.find(vName) != m_ParsedVariables.end()); }
533 : :
534 : : // Returns defined variables as a constant reference
535 : 0 : const VarContainer& getDefinedVars() { return m_DefinedVariables; }
536 : :
537 : : // Returns defined variables as a modifiable reference
538 : 0 : VarContainer& getDefinedVarsRef() { return m_DefinedVariables; }
539 : :
540 : : protected:
541 : : // Tokenize the expression
542 : 202 : ::std::vector<Token> m_tokenize(const String& expr) {
543 : 202 : ::std::vector<Token> tokens;
544 : 202 : ::std::istringstream stream(expr.c_str());
545 : 202 : char ch = 0;
546 [ + + ]: 1206 : while (stream >> ch) {
547 [ + + ][ - + ]: 1004 : if (::std::isdigit(ch) || ch == '.') {
548 : 282 : stream.putback(ch);
549 : 282 : double number;
550 : 282 : stream >> number;
551 : 282 : tokens.push_back({TokenType::NUMBER, String::fromDouble(number)});
552 [ + + ]: 722 : } else if (::std::isalpha(ch)) {
553 : 201 : String identifier(ch);
554 [ + + ][ + + ]: 681 : while (stream.get(ch) && ::std::isalnum(ch)) {
555 : 480 : identifier = identifier + String(ch);
556 : 480 : }
557 : 201 : stream.putback(ch);
558 : 201 : tokens.push_back({TokenType::VARIABLE, identifier}); // Traitement g�n�rique comme identifiant
559 [ + + ]: 521 : } else if (ch == '(') {
560 : 165 : tokens.push_back({TokenType::LPAREN, String("(")});
561 [ + + ]: 356 : } else if (ch == ')') {
562 : 165 : tokens.push_back({TokenType::RPAREN, String(")")});
563 [ + + ]: 191 : } else if (ch == ',') {
564 : 61 : tokens.push_back({TokenType::SEPARATOR, String(",")});
565 : 130 : } else {
566 : 130 : String op(ch);
567 [ + + ][ + + ]: 134 : while (stream.get(ch) && !::std::isalnum(ch) && ch != ' ' && ch != '(' && ch != ')' && ch != ',') {
[ + + ][ + + ]
[ + - ][ + - ]
568 : 4 : op = op + String(ch);
569 : 4 : }
570 : 130 : stream.putback(ch);
571 : 130 : tokens.push_back({TokenType::OPERATOR, op});
572 : 130 : }
573 : 1004 : }
574 [ + + ]: 202 : if (m_Verbose) {
575 : 40 : m_log("Tokens: ");
576 [ + + ]: 143 : for (const auto& token : tokens) {
577 : 143 : m_log(token.value + " ");
578 : 143 : }
579 : 40 : m_log("\n");
580 : 40 : }
581 : 202 : return tokens;
582 : 202 : }
583 : :
584 : : // Logs messages if verbose mode is enabled
585 : 225 : void m_log(const String& message) {
586 [ + - ]: 225 : if (m_Verbose) {
587 : 225 : ::std::cout << message.c_str();
588 : 225 : }
589 : 225 : }
590 : :
591 : : // Evaluate a node in the syntax tree
592 : 128000369 : void m_evalNode(Node& node, double& vOutResult) {
593 : 128000369 : switch (node.type) {
594 [ + + ]: 32000197 : case NodeType::NUMBER: {
595 : 32000197 : vOutResult = node.value;
596 : 32000197 : } break;
597 [ + + ]: 42000020 : case NodeType::OPERATOR: {
598 : 42000020 : m_evalNode(*node.childs[0], node.tmp[0]); // left
599 : 42000020 : m_evalNode(*node.childs[1], node.tmp[1]); // right
600 [ + + ]: 42000020 : if (node.name[0] == '+') {
601 : 24000000 : vOutResult = node.tmp[0] + node.tmp[1];
602 [ - + ]: 24000000 : } else if (node.name[0] == '-') {
603 : 0 : vOutResult = node.tmp[0] - node.tmp[1];
604 [ + + ]: 18000020 : } else if (node.name[0] == '*') {
605 : 12000000 : vOutResult = node.tmp[0] * node.tmp[1];
606 [ + + ]: 12000000 : } else if (node.name[0] == '/') {
607 [ + + ]: 6000008 : if (node.tmp[1] == 0.0) {
608 : 5 : throw ExprException(ErrorCode::DIVISION_BY_ZERO, "Division by zero");
609 : 5 : }
610 : 6000003 : vOutResult = node.tmp[0] / node.tmp[1];
611 [ + + ]: 6000003 : } else if (node.name[0] == '^') {
612 [ + + ]: 4 : if (node.tmp[0] < 0.0) {
613 : 1 : throw ExprException(ErrorCode::EVALUATION_NAN, "Pow base value is negative");
614 : 1 : }
615 : 3 : vOutResult = ::std::pow(node.tmp[0], node.tmp[1]);
616 [ + + ]: 8 : } else if (node.name[0] == '%') {
617 [ + + ]: 4 : if (node.tmp[1] == 0.0) {
618 : 1 : throw ExprException(ErrorCode::DIVISION_BY_ZERO, "Division by zero");
619 : 1 : }
620 : 3 : vOutResult = ::std::fmod(node.tmp[0], node.tmp[1]);
621 : 3 : }
622 : 42000020 : } break;
623 [ + + ]: 42000013 : case NodeType::VARIABLE: {
624 : 38000003 : auto it = m_DefinedVariables.find(node.name);
625 [ + + ]: 38000003 : if (it == m_DefinedVariables.end()) {
626 : 3 : throw ExprException(ErrorCode::VARIABLE_NOT_FOUND, "Variable not found: " + node.name);
627 : 3 : }
628 : 38000000 : vOutResult = it->second;
629 : 38000000 : } break;
630 [ + + ]: 16000149 : case NodeType::FUNCTION: {
631 : 16000149 : auto it = m_Functions.find(node.name);
632 [ + + ]: 16000149 : if (it == m_Functions.end()) {
633 : : // Special case handling for the "!" operator (factorial)
634 [ + - ]: 3 : if (node.name[0] == '!') {
635 : 3 : m_evalNode(*node.childs[0], node.tmp[0]);
636 : 3 : vOutResult = m_Factorial(node.tmp[0]); // Call the factorial function
637 : 3 : } else {
638 : 0 : throw ExprException(ErrorCode::FUNCTION_NOT_FOUND, "Function not found: " + node.name);
639 : 0 : }
640 : 16000146 : } else {
641 [ + + ]: 16000146 : if (it->second.argCount == 1) {
642 : 12000110 : m_evalNode(*node.childs[0], node.tmp[0]);
643 : 12000110 : vOutResult = it->second.unaryFunctor(node.tmp[0]); // first
644 [ + + ]: 12000110 : } else if (it->second.argCount == 2) {
645 : 4000024 : m_evalNode(*node.childs[0], node.tmp[0]); // first
646 : 4000024 : m_evalNode(*node.childs[1], node.tmp[1]); // second
647 : 4000024 : vOutResult = it->second.binaryFunctor(node.tmp[0], node.tmp[1]);
648 [ + - ]: 4000024 : } else if (it->second.argCount == 3) {
649 : 12 : m_evalNode(*node.childs[0], node.tmp[0]); // first
650 : 12 : m_evalNode(*node.childs[1], node.tmp[1]); // second
651 : 12 : m_evalNode(*node.childs[2], node.tmp[2]); // third
652 : 12 : vOutResult = it->second.ternaryFunctor(node.tmp[0], node.tmp[1], node.tmp[2]);
653 : 12 : }
654 : 16000146 : }
655 : 16000149 : } break;
656 [ - + ]: 16000149 : default: throw ExprException(ErrorCode::UNKNOWN_NODE_TYPE, "Unknown node type");
657 : 128000369 : }
658 : :
659 : : // V�rification du r�sultat pour NaN ou Inf
660 [ + + ]: 128000355 : if (::std::isnan(vOutResult)) {
661 : 2 : throw ExprException(ErrorCode::EVALUATION_NAN, "Result is NaN");
662 [ + + ]: 128000353 : } else if (::std::isinf(vOutResult)) {
663 : 2 : throw ExprException(ErrorCode::EVALUATION_INF, "Result is Inf");
664 : 2 : }
665 : :
666 [ + + ]: 128000351 : if (m_Verbose) {
667 : 2 : m_log("Evaluating Node: " + String::fromDouble(vOutResult) + "\n");
668 : 2 : }
669 : 128000351 : }
670 : :
671 : : // Parse an expression to create a syntax tree
672 : 490 : Node m_parseExpression(::std::vector<Token>& tokens, size_t& pos, int precedence) {
673 [ + + ]: 490 : if (pos >= tokens.size()) {
674 : 1 : throw ExprException(ErrorCode::PARSE_ERROR, "Unexpected end of expression");
675 : 1 : }
676 : 489 : Node node = m_parseFactor(tokens, pos);
677 [ + + ]: 563 : while (pos < tokens.size()) {
678 : 337 : String op = tokens[pos].value;
679 : 337 : int opPrecedence = 0;
680 [ + + ]: 337 : if (tokens[pos].type == TokenType::OPERATOR) {
681 [ + + ][ - + ]: 93 : if (op == "+" || op == "-" || op == "*" || op == "/" || op == "^" || op == "%" || op == "!") {
[ + + ][ + + ]
[ + + ][ + + ]
[ - + ]
682 [ + + ][ - + ]: 89 : opPrecedence = (op == "+" || op == "-") ? 1 : (op == "*" || op == "/" || op == "%") ? 2 : 3;
[ + + ][ + + ]
[ + + ]
683 : 89 : } else {
684 : 4 : throw ExprException(ErrorCode::OPERATOR_NOT_FOUND, "Operator not found: " + op);
685 : 4 : }
686 : 93 : }
687 [ + + ]: 333 : if (opPrecedence <= precedence) {
688 : 256 : break;
689 : 256 : }
690 : 77 : ++pos;
691 [ + + ]: 77 : if (pos >= tokens.size()) {
692 : 3 : throw ExprException(ErrorCode::PARSE_ERROR, "Incomplete expression after operator: " + op);
693 : 3 : }
694 : 74 : Node rightNode = m_parseExpression(tokens, pos, opPrecedence);
695 : 74 : Node opNode;
696 : 74 : opNode.type = NodeType::OPERATOR;
697 : 74 : opNode.name = op;
698 : 74 : opNode.childCount = 2;
699 : 74 : opNode.childs[0] = ::std::make_shared<Node>(node);
700 : 74 : opNode.childs[1] = ::std::make_shared<Node>(rightNode);
701 : 74 : node = opNode;
702 : 74 : }
703 : 482 : return node;
704 : 489 : }
705 : :
706 : : // Parse a factor in an expression (number, variable, parenthesis, function)
707 : 519 : Node m_parseFactor(::std::vector<Token>& tokens, size_t& pos) {
708 : 519 : Node node;
709 [ - + ]: 519 : if (pos >= tokens.size()) {
710 : 0 : throw ExprException(ErrorCode::PARSE_ERROR, "Unexpected end of expression");
711 : 0 : }
712 : :
713 [ + + ]: 519 : if (tokens[pos].type == TokenType::NUMBER) {
714 : 258 : node.type = NodeType::NUMBER;
715 : 258 : node.value = ::std::stod(tokens[pos].value.c_str());
716 : 258 : ++pos;
717 : :
718 : : // Gestion de l'op�rateur postfix� "!" pour les nombres
719 [ + + ][ + + ]: 258 : if (pos < tokens.size() && tokens[pos].type == TokenType::OPERATOR && tokens[pos].value == "!") {
[ + + ]
720 : 5 : Node opNode;
721 : 5 : opNode.type = NodeType::FUNCTION;
722 : 5 : opNode.name = "!";
723 : 5 : opNode.childs[0] = ::std::make_shared<Node>(node);
724 : 5 : opNode.childCount = 1;
725 : 5 : node = opNode; // Remplacer le n�ud par l'op�rateur postfix�
726 : 5 : ++pos;
727 : 5 : }
728 [ + + ]: 261 : } else if (tokens[pos].type == TokenType::VARIABLE) {
729 : 199 : String identifier = tokens[pos].value;
730 : 199 : ++pos;
731 : :
732 [ + + ][ + + ]: 199 : if (pos < tokens.size() && tokens[pos].type == TokenType::LPAREN) {
733 : 143 : auto it = m_Functions.find(identifier);
734 [ + + ]: 143 : if (it == m_Functions.end()) {
735 : 3 : throw ExprException(ErrorCode::FUNCTION_NOT_FOUND, "Function not found: " + identifier);
736 : 3 : }
737 : :
738 : 140 : ++pos; // Passer la parenth�se ouvrante
739 : :
740 [ + - ][ + + ]: 140 : if (pos < tokens.size() && tokens[pos].type == TokenType::RPAREN) {
741 : 1 : throw ExprException(ErrorCode::FUNCTION_WRONG_ARGUMENTS_COUNT, "Function called with incorrect number of arguments: " + identifier);
742 : 1 : }
743 : :
744 : 139 : node.type = NodeType::FUNCTION;
745 : 139 : node.name = identifier;
746 : 139 : node.childs[0] = ::std::make_shared<Node>(m_parseExpression(tokens, pos, 0));
747 : 139 : node.childCount = 1;
748 : :
749 [ + + ][ + + ]: 195 : while (pos < tokens.size() && tokens[pos].type == TokenType::SEPARATOR) {
750 : 56 : ++pos;
751 [ - + ]: 56 : if (node.childCount >= 3) {
752 : 0 : throw ExprException(ErrorCode::PARSE_ERROR, "Too many arguments provided for function: " + identifier);
753 : 0 : }
754 : 56 : node.childs[node.childCount++] = ::std::make_shared<Node>(m_parseExpression(tokens, pos, 0));
755 : 56 : }
756 : :
757 [ + + ][ - + ]: 139 : if (pos >= tokens.size() || tokens[pos].type != TokenType::RPAREN) {
758 : 0 : throw ExprException(ErrorCode::UNMATCHED_PARENTHESIS, "Unmatched parenthesis");
759 : 0 : }
760 : 139 : ++pos;
761 : :
762 [ + + ]: 139 : if (node.childCount != it->second.argCount) {
763 : 3 : throw ExprException(ErrorCode::FUNCTION_WRONG_ARGUMENTS_COUNT, "Incorrect number of arguments for function: " + identifier);
764 : 3 : }
765 : :
766 : 139 : } else {
767 : : // Gestion des variables (non-fonctions et non-constantes)
768 : 56 : auto it = m_Constant.find(identifier);
769 [ + + ]: 56 : if (it != m_Constant.end()) {
770 : 12 : node.type = NodeType::NUMBER;
771 : 12 : node.value = it->second;
772 : 44 : } else {
773 : 44 : node.type = NodeType::VARIABLE;
774 : 44 : node.name = identifier;
775 : 44 : m_ParsedVariables[identifier] = 0.0; // Ajout de la variable trouv�e avec une valeur par d�faut de 0.0
776 : 44 : }
777 : :
778 : : // Gestion de l'op�rateur postfix� "!" pour les variables
779 [ + + ][ + + ]: 56 : if (pos < tokens.size() && tokens[pos].type == TokenType::OPERATOR && tokens[pos].value == "!") {
[ - + ]
780 : 0 : Node opNode;
781 : 0 : opNode.type = NodeType::FUNCTION;
782 : 0 : opNode.name = "!";
783 : 0 : opNode.childs[0] = ::std::make_shared<Node>(node);
784 : 0 : opNode.childCount = 1;
785 : 0 : node = opNode; // Remplacer le n�ud par l'op�rateur postfix�
786 : 0 : ++pos;
787 : 0 : }
788 : 56 : }
789 [ + + ]: 199 : } else if (tokens[pos].type == TokenType::LPAREN) {
790 : 20 : ++pos;
791 [ + - ][ + + ]: 20 : if (pos < tokens.size() && tokens[pos].type == TokenType::RPAREN) {
792 : 1 : throw ExprException(ErrorCode::EMPTY_PARENTHESIS, "Empty parenthesis found");
793 : 1 : }
794 : 19 : node = m_parseExpression(tokens, pos, 0);
795 [ + + ][ - + ]: 19 : if (pos >= tokens.size() || tokens[pos].type != TokenType::RPAREN) {
796 : 3 : throw ExprException(ErrorCode::UNMATCHED_PARENTHESIS, "Unmatched parenthesis");
797 : 3 : }
798 : 16 : ++pos;
799 [ + + ]: 42 : } else if (tokens[pos].type == TokenType::OPERATOR) {
800 : 38 : String op = tokens[pos].value;
801 [ + + ]: 38 : if (op == "-") {
802 : 30 : node.type = NodeType::FUNCTION;
803 : 30 : node.name = tokens[pos].value;
804 : 30 : ++pos;
805 : 30 : node.childs[0] = ::std::make_shared<Node>(m_parseFactor(tokens, pos));
806 : 30 : node.childCount = 1;
807 [ + + ]: 30 : } else if (op == "!") {
808 : 2 : throw ExprException(ErrorCode::PARSE_ERROR, "Factorial operator '!' cannot be used alone or in prefix position.");
809 : 6 : } else {
810 : 6 : throw ExprException(ErrorCode::PARSE_ERROR, "Unexpected operator: " + tokens[pos].value);
811 : 6 : }
812 : 38 : } else {
813 : 4 : throw ExprException(ErrorCode::PARSE_ERROR, "Unexpected token: " + tokens[pos].value);
814 : 4 : }
815 : :
816 : 496 : return node;
817 : 519 : }
818 : :
819 : : /////////////////////////////////////////
820 : : //// BUILTINS FUNCTIONS /////////////////
821 : : /////////////////////////////////////////
822 : :
823 : : // Calculate a factorial
824 : 3 : double m_Factorial(double vValue) {
825 [ - + ][ - + ]: 3 : if (vValue < 0 || ::std::floor(vValue) != vValue) {
826 : 0 : throw ExprException(ErrorCode::PARSE_ERROR, "Factorial is not defined for negative or non-integer values.");
827 : 0 : }
828 : 3 : double result = 1;
829 : 3 : const auto count = static_cast<int>(vValue);
830 [ + + ]: 11 : for (int i = 1; i <= count; ++i) {
831 : 8 : result *= i;
832 : 8 : }
833 : 3 : return result;
834 : 3 : }
835 : :
836 : : // https://registry.khronos.org/OpenGL-Refpages/gl4/html/min.xhtml
837 : 12 : double m_Min(double vX, double vY) {
838 [ + + ]: 12 : if (vX < vY) {
839 : 8 : return vX;
840 : 8 : }
841 : 4 : return vY;
842 : 12 : }
843 : :
844 : : // https://registry.khronos.org/OpenGL-Refpages/gl4/html/max.xhtml
845 : 12 : double m_Max(double vX, double vY) {
846 [ + + ]: 12 : if (vX > vY) {
847 : 9 : return vX;
848 : 9 : }
849 : 3 : return vY;
850 : 12 : }
851 : :
852 : : // https://www.shadertoy.com/???? : sqrt(v*v+k) with k >= 0
853 : 3 : double m_SmoothAbs(double vV, double vK) { return ::std::sqrt(vV * vV + ::std::abs(vK)); }
854 : :
855 : : // https://registry.khronos.org/OpenGL-Refpages/gl4/html/clamp.xhtml
856 : : // Clamp (glsl), Saturate (hlsl)
857 : 9 : double m_Clamp(double vX, double vMinVal, double vMaxVal) { return m_Min(m_Max(vX, vMinVal), vMaxVal); }
858 : :
859 : : // https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml
860 : 3 : double m_SmoothStep(double vEdge0, double vEdge1, double vX) {
861 : 3 : double t = m_Clamp((vX - vEdge0) / (vEdge1 - vEdge0), 0.0, 1.0);
862 : 3 : return t * t * (3.0 - 2.0 * t);
863 : 3 : }
864 : :
865 : : // https://registry.khronos.org/OpenGL-Refpages/gl4/html/mix.xhtml
866 : : // Mix (glsl), lerp (hlsl)
867 : 6 : double m_Mix(double vX, double vY, double vA) { return vX * (1.0 - vA) + vY * vA; }
868 : :
869 : : // https://registry.khronos.org/OpenGL-Refpages/gl4/html/step.xhtml
870 [ + + ]: 3 : double m_Step(double vEdge, double vX) { return vX < vEdge ? 0.0 : 1.0; }
871 : :
872 : : // https://registry.khronos.org/OpenGL-Refpages/gl4/html/sign.xhtml
873 : 3 : double m_Sign(double vX) {
874 [ + + ]: 3 : if (vX < 0.0) {
875 : 1 : return -1.0;
876 [ + + ]: 2 : } else if (vX > 0.0) {
877 : 1 : return 1.0;
878 : 1 : }
879 : 1 : return 0.0;
880 : 3 : }
881 : : };
882 : :
883 : : } // namespace ez
|