summaryrefslogtreecommitdiffstats
path: root/src/util/expression-evaluator.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/expression-evaluator.h')
-rw-r--r--src/util/expression-evaluator.h196
1 files changed, 196 insertions, 0 deletions
diff --git a/src/util/expression-evaluator.h b/src/util/expression-evaluator.h
new file mode 100644
index 0000000..a45ad5a
--- /dev/null
+++ b/src/util/expression-evaluator.h
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+/** @file
+ * TODO: insert short description here
+ */
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * Original file from libgimpwidgets: gimpeevl.h
+ * Copyright (C) 2008-2009 Fredrik Alstromer <roe@excu.se>
+ * Copyright (C) 2008-2009 Martin Nordholts <martinn@svn.gnome.org>
+ * Modified for Inkscape by Johan Engelen
+ * Copyright (C) 2011 Johan Engelen
+ * Copyright (C) 2013 Matthew Petroff
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef INKSCAPE_UTIL_EXPRESSION_EVALUATOR_H
+#define INKSCAPE_UTIL_EXPRESSION_EVALUATOR_H
+
+#include "util/units.h"
+
+#include <exception>
+#include <sstream>
+#include <string>
+
+/**
+ * @file
+ * Expression evaluator: A straightforward recursive
+ * descent parser, no fuss, no new dependencies. The lexer is hand
+ * coded, tedious, not extremely fast but works. It evaluates the
+ * expression as it goes along, and does not create a parse tree or
+ * anything, and will not optimize anything. It uses doubles for
+ * precision, with the given use case, that's enough to combat any
+ * rounding errors (as opposed to optimizing the evaluation).
+ *
+ * It relies on external unit resolving through a callback and does
+ * elementary dimensionality constraint check (e.g. "2 mm + 3 px * 4
+ * in" is an error, as L + L^2 is a mismatch). It uses g_strtod() for numeric
+ * conversions and it's non-destructive in terms of the parameters, and
+ * it's reentrant.
+ *
+ * EBNF:
+ *
+ * expression ::= term { ('+' | '-') term }* |
+ * <empty string> ;
+ *
+ * term ::= exponent { ( '*' | '/' ) exponent }* ;
+ *
+ * exponent ::= signed factor { '^' signed factor }* ;
+ *
+ * signed factor ::= ( '+' | '-' )? factor ;
+ *
+ * unit factor ::= factor unit? ;
+ *
+ * factor ::= number | '(' expression ')' ;
+ *
+ * number ::= ? what g_strtod() consumes ? ;
+ *
+ * unit ::= ? what not g_strtod() consumes and not whitespace ? ;
+ *
+ * The code should match the EBNF rather closely (except for the
+ * non-terminal unit factor, which is inlined into factor) for
+ * maintainability reasons.
+ *
+ * It will allow 1++1 and 1+-1 (resulting in 2 and 0, respectively),
+ * but I figured one might want that, and I don't think it's going to
+ * throw anyone off.
+ */
+
+namespace Inkscape {
+namespace Util {
+
+class Unit;
+
+/**
+ * EvaluatorQuantity:
+ * @param value In reference units.
+ * @param dimension mm has a dimension of 1, mm^2 has a dimension of 2, etc.
+ */
+class EvaluatorQuantity
+{
+public:
+ EvaluatorQuantity(double value = 0, unsigned int dimension = 0);
+
+ double value;
+ unsigned int dimension;
+};
+
+/**
+ * TokenType
+ */
+enum {
+ TOKEN_NUM = 30000,
+ TOKEN_IDENTIFIER = 30001,
+ TOKEN_ANY = 40000,
+ TOKEN_END = 50000
+};
+typedef int TokenType;
+
+/**
+ * EvaluatorToken
+ */
+class EvaluatorToken
+{
+public:
+ EvaluatorToken();
+
+ TokenType type;
+
+ union {
+ double fl;
+ struct {
+ const char *c;
+ int size;
+ };
+ } value;
+};
+
+/**
+ * ExpressionEvaluator
+ * @param string NULL terminated input string to evaluate
+ * @param unit Unit output should be in
+ */
+class ExpressionEvaluator
+{
+public:
+ ExpressionEvaluator(const char *string, Unit const *unit = nullptr);
+
+ EvaluatorQuantity evaluate();
+
+private:
+ const char *string;
+ Unit const *unit;
+
+ EvaluatorToken current_token;
+ const char *start_of_current_token;
+
+ EvaluatorQuantity evaluateExpression();
+ EvaluatorQuantity evaluateTerm();
+ EvaluatorQuantity evaluateExpTerm();
+ EvaluatorQuantity evaluateSignedFactor();
+ EvaluatorQuantity evaluateFactor();
+
+ bool acceptToken(TokenType token_type, EvaluatorToken *consumed_token);
+ void parseNextToken();
+ void acceptTokenCount(int count, TokenType token_type);
+ void isExpected(TokenType token_type, EvaluatorToken *value);
+
+ void movePastWhiteSpace();
+
+ static bool isUnitIdentifierStart(gunichar c);
+ static int getIdentifierSize(const char *s, int start);
+
+ static bool resolveUnit(const char *identifier, EvaluatorQuantity *result, Unit const *unit);
+
+ void throwError(const char *msg);
+};
+
+/**
+ * Special exception class for the expression evaluator.
+ */
+class EvaluatorException : public std::exception {
+public:
+ EvaluatorException(const char *message, const char *at_position) {
+ std::ostringstream os;
+ const char *token = at_position ? at_position : "<End of input>";
+ os << "Expression evaluator error: " << message << " at '" << token << "'";
+ msgstr = os.str();
+ }
+
+ ~EvaluatorException() noexcept override = default; // necessary to destroy the string object!!!
+
+ const char *what() const noexcept override {
+ return msgstr.c_str();
+ }
+protected:
+ std::string msgstr;
+};
+
+}
+}
+
+#endif // INKSCAPE_UTIL_EXPRESSION_EVALUATOR_H