Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
expression-evaluator.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-3.0-or-later
5/* LIBGIMP - The GIMP Library
6 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
7 *
8 * Original file from libgimpwidgets: gimpeevl.c
9 * Copyright (C) 2008 Fredrik Alstromer <roe@excu.se>
10 * Copyright (C) 2008 Martin Nordholts <martinn@svn.gnome.org>
11 * Modified for Inkscape by Johan Engelen
12 * Copyright (C) 2011 Johan Engelen
13 * Copyright (C) 2013 Matthew Petroff
14 *
15 * This library is free software: you can redistribute it and/or
16 * modify it under the terms of the GNU Lesser General Public
17 * License as published by the Free Software Foundation; either
18 * version 3 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public
26 * License along with this library. If not, see
27 * <http://www.gnu.org/licenses/>.
28 */
29
31
32#include <cmath>
33#include <cstring>
34
35namespace Inkscape {
36namespace Util {
37
38EvaluatorQuantity::EvaluatorQuantity(double value, unsigned int dimension) :
39 value(value),
40 dimension(dimension)
41{
42}
43
45{
46 type = 0;
47 value.fl = 0;
48}
49
50ExpressionEvaluator::ExpressionEvaluator(const char *string, Unit const *unit) :
51 string(string),
52 unit(unit)
53{
55
56 // Preload symbol
58}
59
75{
76 if (!g_utf8_validate(string, -1, nullptr)) {
77 throw EvaluatorException("Invalid UTF8 string", nullptr);
78 }
79
81 EvaluatorQuantity default_unit_factor;
82
83 // Empty expression evaluates to 0
84 if (acceptToken(TOKEN_END, nullptr)) {
85 return result;
86 }
87
89
90 // There should be nothing left to parse by now
91 isExpected(TOKEN_END, nullptr);
92
93 resolveUnit(nullptr, &default_unit_factor, unit);
94
95 // Entire expression is dimensionless, apply default unit if applicable
96 if ( result.dimension == 0 && default_unit_factor.dimension != 0 ) {
97 result.value /= default_unit_factor.value;
98 result.dimension = default_unit_factor.dimension;
99 }
100 return result;
101}
102
104{
105 bool subtract;
106 EvaluatorQuantity evaluated_terms;
107
108 evaluated_terms = evaluateTerm();
109
110 // Continue evaluating terms, chained with + or -.
111 for (subtract = FALSE;
112 acceptToken('+', nullptr) || (subtract = acceptToken('-', nullptr));
113 subtract = FALSE)
114 {
115 EvaluatorQuantity new_term = evaluateTerm();
116
117 // If dimensions mismatch, attempt default unit assignment
118 if ( new_term.dimension != evaluated_terms.dimension ) {
119 EvaluatorQuantity default_unit_factor;
120
121 resolveUnit(nullptr, &default_unit_factor, unit);
122
123 if ( new_term.dimension == 0
124 && evaluated_terms.dimension == default_unit_factor.dimension )
125 {
126 new_term.value /= default_unit_factor.value;
127 new_term.dimension = default_unit_factor.dimension;
128 } else if ( evaluated_terms.dimension == 0
129 && new_term.dimension == default_unit_factor.dimension )
130 {
131 evaluated_terms.value /= default_unit_factor.value;
132 evaluated_terms.dimension = default_unit_factor.dimension;
133 } else {
134 throwError("Dimension mismatch during addition");
135 }
136 }
137
138 evaluated_terms.value += (subtract ? -new_term.value : new_term.value);
139 }
140
141 return evaluated_terms;
142}
143
145{
146 bool division;
147 EvaluatorQuantity evaluated_exp_terms = evaluateExpTerm();
148
149 for ( division = false;
150 acceptToken('*', nullptr) ||
151 (division = acceptToken('/', nullptr)) ||
152 (division = acceptToken(':', nullptr)); // accept ':' too (for ratio)
153 division = false )
154 {
155 EvaluatorQuantity new_exp_term = evaluateExpTerm();
156
157 if (division) {
158 evaluated_exp_terms.value /= new_exp_term.value;
159 evaluated_exp_terms.dimension -= new_exp_term.dimension;
160 } else {
161 evaluated_exp_terms.value *= new_exp_term.value;
162 evaluated_exp_terms.dimension += new_exp_term.dimension;
163 }
164 }
165
166 return evaluated_exp_terms;
167}
168
170{
171 EvaluatorQuantity evaluated_signed_factors = evaluateSignedFactor();
172
173 while(acceptToken('^', nullptr)) {
174 EvaluatorQuantity new_signed_factor = evaluateSignedFactor();
175
176 if (new_signed_factor.dimension == 0) {
177 evaluated_signed_factors.value = pow(evaluated_signed_factors.value,
178 new_signed_factor.value);
179 evaluated_signed_factors.dimension *= new_signed_factor.value;
180 } else {
181 throwError("Unit in exponent");
182 }
183 }
184
185 return evaluated_signed_factors;
186}
187
189{
191 bool negate = FALSE;
192
193 if (!acceptToken('+', nullptr)) {
194 negate = acceptToken ('-', nullptr);
195 }
196
198
199 if (negate) {
200 result.value = -result.value;
201 }
202
203 return result;
204}
205
207{
208 EvaluatorQuantity evaluated_factor = EvaluatorQuantity();
209 EvaluatorToken consumed_token = EvaluatorToken();
210
211 if (acceptToken(TOKEN_END, &consumed_token)) {
212 return evaluated_factor;
213 }
214 else if (acceptToken(TOKEN_NUM, &consumed_token)) {
215 evaluated_factor.value = consumed_token.value.fl;
216 } else if (acceptToken('(', nullptr)) {
217 evaluated_factor = evaluateExpression();
218 isExpected(')', nullptr);
219 } else {
220 throwError("Expected number or '('");
221 }
222
224 char *identifier;
226
227 acceptToken(TOKEN_ANY, &consumed_token);
228
229 identifier = g_newa(char, consumed_token.value.size + 1);
230
231 strncpy(identifier, consumed_token.value.c, consumed_token.value.size);
232 identifier[consumed_token.value.size] = '\0';
233
234 if (resolveUnit(identifier, &result, unit)) {
235 evaluated_factor.value /= result.value;
236 evaluated_factor.dimension += result.dimension;
237 } else {
238 throwError("Unit was not resolved");
239 }
240 }
241
242 return evaluated_factor;
243}
244
246 EvaluatorToken *consumed_token)
247{
248 bool existed = FALSE;
249
250 if ( token_type == current_token.type || token_type == TOKEN_ANY ) {
251 existed = TRUE;
252
253 if (consumed_token) {
254 *consumed_token = current_token;
255 }
256
257 // Parse next token
259 }
260
261 return existed;
262}
263
265{
266 const char *s;
267
269 s = string;
271
272 if ( !s || s[0] == '\0' ) {
273 // We're all done
275 } else if ( s[0] == '+' || s[0] == '-' ) {
276 // Snatch these before the g_strtod() does, otherwise they might
277 // be used in a numeric conversion.
278 acceptTokenCount(1, s[0]);
279 } else {
280 // Attempt to parse a numeric value
281 char *endptr = nullptr;
282 gdouble value = g_strtod(s, &endptr);
283
284 if ( endptr && endptr != s ) {
285 // A numeric could be parsed, use it
286 current_token.value.fl = value;
287
289 string = endptr;
290 } else if (isUnitIdentifierStart(s[0])) {
291 // Unit identifier
294
296 } else {
297 // Everything else is a single character token
298 acceptTokenCount(1, s[0]);
299 }
300 }
301}
302
304{
305 current_token.type = token_type;
306 string += count;
307}
308
310 EvaluatorToken *value)
311{
312 if (!acceptToken(token_type, value)) {
313 throwError("Unexpected token");
314 }
315}
316
318{
319 if (!string) {
320 return;
321 }
322
323 while (g_ascii_isspace(*string)) {
324 string++;
325 }
326}
327
329{
330 return (g_unichar_isalpha (c)
331 || c == (gunichar) '%'
332 || c == (gunichar) '\'');
333}
334
343int ExpressionEvaluator::getIdentifierSize(const char *string, int start_offset)
344{
345 const char *start = g_utf8_offset_to_pointer(string, start_offset);
346 const char *s = start;
347 gunichar c = g_utf8_get_char(s);
348 int length = 0;
349
351 s = g_utf8_next_char (s);
352 c = g_utf8_get_char (s);
353 length++;
354
355 while ( isUnitIdentifierStart (c) || g_unichar_isdigit (c) ) {
356 s = g_utf8_next_char(s);
357 c = g_utf8_get_char(s);
358 length++;
359 }
360 }
361
362 return g_utf8_offset_to_pointer(start, length) - start;
363}
364
365bool ExpressionEvaluator::resolveUnit (const char* identifier,
367 Unit const* unit)
368{
369 auto const &unit_table = UnitTable::get();
370 if (!unit) {
371 result->value = 1;
372 result->dimension = 1;
373 return true;
374 }else if (!identifier) {
375 result->value = 1;
376 result->dimension = unit->isAbsolute() ? 1 : 0;
377 return true;
378 } else if (unit_table.hasUnit(identifier)) {
379 Unit const *identifier_unit = unit_table.getUnit(identifier);
380 result->value = Quantity::convert(1, unit, identifier_unit);
381 result->dimension = identifier_unit->isAbsolute() ? 1 : 0;
382 return true;
383 } else {
384 return false;
385 }
386}
387
392
393} // namespace Util
394} // namespace Inkscape
Special exception class for the expression evaluator.
EvaluatorQuantity(double value=0, unsigned int dimension=0)
union Inkscape::Util::EvaluatorToken::@62 value
void isExpected(TokenType token_type, EvaluatorToken *value)
void acceptTokenCount(int count, TokenType token_type)
EvaluatorQuantity evaluate()
Evaluates the given arithmetic expression, along with an optional dimension analysis,...
static int getIdentifierSize(const char *s, int start)
getIdentifierSize: @s: @start:
static bool resolveUnit(const char *identifier, EvaluatorQuantity *result, Unit const *unit)
bool acceptToken(TokenType token_type, EvaluatorToken *consumed_token)
ExpressionEvaluator(const char *string, Unit const *unit=nullptr)
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:588
static UnitTable & get()
Definition units.cpp:441
bool isAbsolute() const
Definition units.h:64
Css & result
Glib::ustring msg
double c[8][4]
TODO: insert short description here.
Geom::Point start
Miscellaneous supporting code.
Definition document.h:93
Helper class to stream background task notifications as a series of messages.
unsigned int gunichar