summaryrefslogtreecommitdiffstats
path: root/src/util/expression-evaluator.h
blob: a45ad5a73cfa3e81907d6f5f0536516f0d90dcfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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