34
36
namespace Inkscape {
39
GIMP_EEVL_TOKEN_NUM = 30000,
40
GIMP_EEVL_TOKEN_IDENTIFIER = 30001,
42
GIMP_EEVL_TOKEN_ANY = 40000,
44
GIMP_EEVL_TOKEN_END = 50000
47
typedef int GimpEevlTokenType;
52
GimpEevlTokenType type;
71
GimpEevlUnitResolverProc unit_resolver_proc;
74
GimpEevlToken current_token;
75
const gchar *start_of_current_token;
80
static bool unitresolverproc (const gchar* identifier, GimpEevlQuantity *result, Unit* unit)
84
result->dimension = 1;
86
}else if (!identifier) {
88
result->dimension = unit->isAbsolute() ? 1 : 0;
90
} else if (unit_table.hasUnit(identifier)) {
91
Unit identifier_unit = unit_table.getUnit(identifier);
93
// Catch the case of zero or negative unit factors (error!)
94
if (identifier_unit.factor < 0.0000001) {
98
result->value = unit->factor / identifier_unit.factor;
99
result->dimension = identifier_unit.isAbsolute() ? 1 : 0;
106
static void gimp_eevl_init (GimpEevl *eva,
108
GimpEevlUnitResolverProc unit_resolver_proc,
110
static GimpEevlQuantity gimp_eevl_complete (GimpEevl *eva);
111
static GimpEevlQuantity gimp_eevl_expression (GimpEevl *eva);
112
static GimpEevlQuantity gimp_eevl_term (GimpEevl *eva);
113
static GimpEevlQuantity gimp_eevl_signed_factor (GimpEevl *eva);
114
static GimpEevlQuantity gimp_eevl_factor (GimpEevl *eva);
115
static gboolean gimp_eevl_accept (GimpEevl *eva,
116
GimpEevlTokenType token_type,
117
GimpEevlToken *consumed_token);
118
static void gimp_eevl_lex (GimpEevl *eva);
119
static void gimp_eevl_lex_accept_count (GimpEevl *eva,
121
GimpEevlTokenType token_type);
122
static void gimp_eevl_lex_accept_to (GimpEevl *eva,
124
GimpEevlTokenType token_type);
125
static void gimp_eevl_move_past_whitespace (GimpEevl *eva);
126
static gboolean gimp_eevl_unit_identifier_start (gunichar c);
127
static gboolean gimp_eevl_unit_identifier_continue (gunichar c);
128
static gint gimp_eevl_unit_identifier_size (const gchar *s,
130
static void gimp_eevl_expect (GimpEevl *eva,
131
GimpEevlTokenType token_type,
132
GimpEevlToken *value);
133
static void gimp_eevl_error (GimpEevl *eva,
39
EvaluatorQuantity::EvaluatorQuantity(double value, unsigned int dimension) :
45
EvaluatorToken::EvaluatorToken()
51
ExpressionEvaluator::ExpressionEvaluator(const char *string, Unit *unit) :
55
current_token.type = TOKEN_END;
138
62
* Evaluates the given arithmetic expression, along with an optional dimension
139
63
* analysis, and basic unit conversions.
141
* @param string The NULL-terminated string to be evaluated.
142
* @param unit_resolver_proc Unit resolver callback.
144
65
* All units conversions factors are relative to some implicit
145
* base-unit (which in GIMP is inches). This is also the unit of the
66
* base-unit. This is also the unit of the returned value.
148
* Returns: A #GimpEevlQuantity with a value given in the base unit along with
149
* the order of the dimension (i.e. if the base unit is inches, a dimension
150
* order of two menas in^2).
68
* Returns: An EvaluatorQuantity with a value given in the base unit along with
69
* the order of the dimension (e.g. if the base unit is inches, a dimension
70
* order of two means in^2).
152
72
* @return Result of evaluation.
153
73
* @throws Inkscape::Util::EvaluatorException There was a parse error.
156
gimp_eevl_evaluate (const gchar* string, Unit* unit)
75
EvaluatorQuantity ExpressionEvaluator::evaluate()
158
if (! g_utf8_validate (string, -1, NULL)) {
77
if (!g_utf8_validate(string, -1, NULL)) {
159
78
throw EvaluatorException("Invalid UTF8 string", NULL);
163
gimp_eevl_init (&eva, string, unitresolverproc, unit);
165
return gimp_eevl_complete(&eva);
169
gimp_eevl_init (GimpEevl *eva,
171
GimpEevlUnitResolverProc unit_resolver_proc,
174
eva->string = string;
175
eva->unit_resolver_proc = unit_resolver_proc;
178
eva->current_token.type = GIMP_EEVL_TOKEN_END;
180
/* Preload symbol... */
184
static GimpEevlQuantity
185
gimp_eevl_complete (GimpEevl *eva)
187
GimpEevlQuantity result = {0, 0};
188
GimpEevlQuantity default_unit_factor;
190
/* Empty expression evaluates to 0 */
191
if (gimp_eevl_accept (eva, GIMP_EEVL_TOKEN_END, NULL))
194
result = gimp_eevl_expression (eva);
196
/* There should be nothing left to parse by now */
197
gimp_eevl_expect (eva, GIMP_EEVL_TOKEN_END, 0);
199
eva->unit_resolver_proc (NULL, &default_unit_factor, eva->unit);
201
/* Entire expression is dimensionless, apply default unit if
204
if (result.dimension == 0 && default_unit_factor.dimension != 0)
206
result.value /= default_unit_factor.value;
207
result.dimension = default_unit_factor.dimension;
212
static GimpEevlQuantity
213
gimp_eevl_expression (GimpEevl *eva)
216
GimpEevlQuantity evaluated_terms;
218
evaluated_terms = gimp_eevl_term (eva);
220
/* continue evaluating terms, chained with + or -. */
221
for (subtract = FALSE;
222
gimp_eevl_accept (eva, '+', NULL) ||
223
(subtract = gimp_eevl_accept (eva, '-', NULL));
226
GimpEevlQuantity new_term = gimp_eevl_term (eva);
228
/* If dimensions missmatch, attempt default unit assignent */
229
if (new_term.dimension != evaluated_terms.dimension)
231
GimpEevlQuantity default_unit_factor;
233
eva->unit_resolver_proc (NULL,
234
&default_unit_factor,
237
if (new_term.dimension == 0 &&
238
evaluated_terms.dimension == default_unit_factor.dimension)
240
new_term.value /= default_unit_factor.value;
241
new_term.dimension = default_unit_factor.dimension;
243
else if (evaluated_terms.dimension == 0 &&
244
new_term.dimension == default_unit_factor.dimension)
246
evaluated_terms.value /= default_unit_factor.value;
247
evaluated_terms.dimension = default_unit_factor.dimension;
251
gimp_eevl_error (eva, "Dimension missmatch during addition");
255
evaluated_terms.value += (subtract ? -new_term.value : new_term.value);
258
return evaluated_terms;
261
static GimpEevlQuantity
262
gimp_eevl_term (GimpEevl *eva)
265
GimpEevlQuantity evaluated_signed_factors;
267
evaluated_signed_factors = gimp_eevl_signed_factor (eva);
269
for (division = FALSE;
270
gimp_eevl_accept (eva, '*', NULL) ||
271
(division = gimp_eevl_accept (eva, '/', NULL));
274
GimpEevlQuantity new_signed_factor = gimp_eevl_signed_factor (eva);
278
evaluated_signed_factors.value /= new_signed_factor.value;
279
evaluated_signed_factors.dimension -= new_signed_factor.dimension;
284
evaluated_signed_factors.value *= new_signed_factor.value;
285
evaluated_signed_factors.dimension += new_signed_factor.dimension;
289
return evaluated_signed_factors;
292
static GimpEevlQuantity
293
gimp_eevl_signed_factor (GimpEevl *eva)
295
GimpEevlQuantity result;
296
gboolean negate = FALSE;
298
if (! gimp_eevl_accept (eva, '+', NULL))
299
negate = gimp_eevl_accept (eva, '-', NULL);
301
result = gimp_eevl_factor (eva);
303
if (negate) result.value = -result.value;
308
static GimpEevlQuantity
309
gimp_eevl_factor (GimpEevl *eva)
311
GimpEevlQuantity evaluated_factor = { 0, 0 };
312
GimpEevlToken consumed_token;
314
if (gimp_eevl_accept (eva,
318
evaluated_factor.value = consumed_token.value.fl;
320
else if (gimp_eevl_accept (eva, '(', NULL))
322
evaluated_factor = gimp_eevl_expression (eva);
323
gimp_eevl_expect (eva, ')', 0);
327
gimp_eevl_error (eva, "Expected number or '('");
330
if (eva->current_token.type == GIMP_EEVL_TOKEN_IDENTIFIER)
333
GimpEevlQuantity result;
335
gimp_eevl_accept (eva,
339
identifier = g_newa (gchar, consumed_token.value.size + 1);
341
strncpy (identifier, consumed_token.value.c, consumed_token.value.size);
342
identifier[consumed_token.value.size] = '\0';
344
if (eva->unit_resolver_proc (identifier,
348
evaluated_factor.value /= result.value;
349
evaluated_factor.dimension += result.dimension;
353
gimp_eevl_error (eva, "Unit was not resolved");
357
return evaluated_factor;
361
gimp_eevl_accept (GimpEevl *eva,
362
GimpEevlTokenType token_type,
363
GimpEevlToken *consumed_token)
365
gboolean existed = FALSE;
367
if (token_type == eva->current_token.type ||
368
token_type == GIMP_EEVL_TOKEN_ANY)
373
*consumed_token = eva->current_token;
375
/* Parse next token */
383
gimp_eevl_lex (GimpEevl *eva)
387
gimp_eevl_move_past_whitespace (eva);
389
eva->start_of_current_token = s;
391
if (! s || s[0] == '\0')
394
eva->current_token.type = GIMP_EEVL_TOKEN_END;
396
else if (s[0] == '+' || s[0] == '-')
398
/* Snatch these before the g_strtod() does, othewise they might
399
* be used in a numeric conversion.
401
gimp_eevl_lex_accept_count (eva, 1, s[0]);
406
/* Attempt to parse a numeric value */
407
gchar *endptr = NULL;
408
gdouble value = g_strtod (s, &endptr);
410
if (endptr && endptr != s)
412
/* A numeric could be parsed, use it */
413
eva->current_token.value.fl = value;
415
gimp_eevl_lex_accept_to (eva, endptr, GIMP_EEVL_TOKEN_NUM);
417
else if (gimp_eevl_unit_identifier_start (s[0]))
419
/* Unit identifier */
420
eva->current_token.value.c = s;
421
eva->current_token.value.size = gimp_eevl_unit_identifier_size (s, 0);
423
gimp_eevl_lex_accept_count (eva,
424
eva->current_token.value.size,
425
GIMP_EEVL_TOKEN_IDENTIFIER);
429
/* Everything else is a single character token */
430
gimp_eevl_lex_accept_count (eva, 1, s[0]);
436
gimp_eevl_lex_accept_count (GimpEevl *eva,
438
GimpEevlTokenType token_type)
440
eva->current_token.type = token_type;
441
eva->string += count;
445
gimp_eevl_lex_accept_to (GimpEevl *eva,
447
GimpEevlTokenType token_type)
449
eva->current_token.type = token_type;
454
gimp_eevl_move_past_whitespace (GimpEevl *eva)
459
while (g_ascii_isspace (*eva->string))
464
gimp_eevl_unit_identifier_start (gunichar c)
466
return (g_unichar_isalpha (c) ||
467
c == (gunichar) '%' ||
468
c == (gunichar) '\'');
472
gimp_eevl_unit_identifier_continue (gunichar c)
474
return (gimp_eevl_unit_identifier_start (c) ||
475
g_unichar_isdigit (c));
81
EvaluatorQuantity result = EvaluatorQuantity();
82
EvaluatorQuantity default_unit_factor;
84
// Empty expression evaluates to 0
85
if (acceptToken(TOKEN_END, NULL)) {
89
result = evaluateExpression();
91
// There should be nothing left to parse by now
92
isExpected(TOKEN_END, 0);
94
resolveUnit(NULL, &default_unit_factor, unit);
96
// Entire expression is dimensionless, apply default unit if applicable
97
if ( result.dimension == 0 && default_unit_factor.dimension != 0 ) {
98
result.value /= default_unit_factor.value;
99
result.dimension = default_unit_factor.dimension;
104
EvaluatorQuantity ExpressionEvaluator::evaluateExpression()
107
EvaluatorQuantity evaluated_terms;
109
evaluated_terms = evaluateTerm();
111
// Continue evaluating terms, chained with + or -.
112
for (subtract = FALSE;
113
acceptToken('+', NULL) || (subtract = acceptToken('-', NULL));
116
EvaluatorQuantity new_term = evaluateTerm();
118
// If dimensions mismatch, attempt default unit assignent
119
if ( new_term.dimension != evaluated_terms.dimension ) {
120
EvaluatorQuantity default_unit_factor;
122
resolveUnit(NULL, &default_unit_factor, unit);
124
if ( new_term.dimension == 0
125
&& evaluated_terms.dimension == default_unit_factor.dimension )
127
new_term.value /= default_unit_factor.value;
128
new_term.dimension = default_unit_factor.dimension;
129
} else if ( evaluated_terms.dimension == 0
130
&& new_term.dimension == default_unit_factor.dimension )
132
evaluated_terms.value /= default_unit_factor.value;
133
evaluated_terms.dimension = default_unit_factor.dimension;
135
throwError("Dimension mismatch during addition");
139
evaluated_terms.value += (subtract ? -new_term.value : new_term.value);
142
return evaluated_terms;
145
EvaluatorQuantity ExpressionEvaluator::evaluateTerm()
148
EvaluatorQuantity evaluated_exp_terms = evaluateExpTerm();
150
for ( division = false;
151
acceptToken('*', NULL) || (division = acceptToken('/', NULL));
154
EvaluatorQuantity new_exp_term = evaluateExpTerm();
157
evaluated_exp_terms.value /= new_exp_term.value;
158
evaluated_exp_terms.dimension -= new_exp_term.dimension;
160
evaluated_exp_terms.value *= new_exp_term.value;
161
evaluated_exp_terms.dimension += new_exp_term.dimension;
165
return evaluated_exp_terms;
168
EvaluatorQuantity ExpressionEvaluator::evaluateExpTerm()
170
EvaluatorQuantity evaluated_signed_factors = evaluateSignedFactor();
172
while(acceptToken('^', NULL)) {
173
EvaluatorQuantity new_signed_factor = evaluateSignedFactor();
175
if (new_signed_factor.dimension == 0) {
176
evaluated_signed_factors.value = pow(evaluated_signed_factors.value,
177
new_signed_factor.value);
178
evaluated_signed_factors.dimension *= new_signed_factor.value;
180
throwError("Unit in exponent");
184
return evaluated_signed_factors;
187
EvaluatorQuantity ExpressionEvaluator::evaluateSignedFactor()
189
EvaluatorQuantity result;
192
if (!acceptToken('+', NULL)) {
193
negate = acceptToken ('-', NULL);
196
result = evaluateFactor();
199
result.value = -result.value;
205
EvaluatorQuantity ExpressionEvaluator::evaluateFactor()
207
EvaluatorQuantity evaluated_factor = EvaluatorQuantity();
208
EvaluatorToken consumed_token = EvaluatorToken();
210
if (acceptToken(TOKEN_NUM, &consumed_token)) {
211
evaluated_factor.value = consumed_token.value.fl;
212
} else if (acceptToken('(', NULL)) {
213
evaluated_factor = evaluateExpression();
216
throwError("Expected number or '('");
219
if ( current_token.type == TOKEN_IDENTIFIER ) {
221
EvaluatorQuantity result;
223
acceptToken(TOKEN_ANY, &consumed_token);
225
identifier = g_newa(char, consumed_token.value.size + 1);
227
strncpy(identifier, consumed_token.value.c, consumed_token.value.size);
228
identifier[consumed_token.value.size] = '\0';
230
if (resolveUnit(identifier, &result, unit)) {
231
evaluated_factor.value /= result.value;
232
evaluated_factor.dimension += result.dimension;
234
throwError("Unit was not resolved");
238
return evaluated_factor;
241
bool ExpressionEvaluator::acceptToken(TokenType token_type,
242
EvaluatorToken *consumed_token)
244
bool existed = FALSE;
246
if ( token_type == current_token.type || token_type == TOKEN_ANY ) {
249
if (consumed_token) {
250
*consumed_token = current_token;
260
void ExpressionEvaluator::parseNextToken()
264
movePastWhiteSpace();
266
start_of_current_token = s;
268
if ( !s || s[0] == '\0' ) {
270
current_token.type = TOKEN_END;
271
} else if ( s[0] == '+' || s[0] == '-' ) {
272
// Snatch these before the g_strtod() does, othewise they might
273
// be used in a numeric conversion.
274
acceptTokenCount(1, s[0]);
276
// Attempt to parse a numeric value
278
gdouble value = g_strtod(s, &endptr);
280
if ( endptr && endptr != s ) {
281
// A numeric could be parsed, use it
282
current_token.value.fl = value;
284
current_token.type = TOKEN_NUM;
286
} else if (isUnitIdentifierStart(s[0])) {
288
current_token.value.c = s;
289
current_token.value.size = getIdentifierSize(s, 0);
291
acceptTokenCount(current_token.value.size, TOKEN_IDENTIFIER);
293
// Everything else is a single character token
294
acceptTokenCount(1, s[0]);
299
void ExpressionEvaluator::acceptTokenCount (int count, TokenType token_type)
301
current_token.type = token_type;
305
void ExpressionEvaluator::isExpected(TokenType token_type,
306
EvaluatorToken *value)
308
if (!acceptToken(token_type, value)) {
309
throwError("Unexpected token");
313
void ExpressionEvaluator::movePastWhiteSpace()
319
while (g_ascii_isspace(*string)) {
324
bool ExpressionEvaluator::isUnitIdentifierStart(gunichar c)
326
return (g_unichar_isalpha (c)
327
|| c == (gunichar) '%'
328
|| c == (gunichar) '\'');
479
* gimp_eevl_unit_identifier_size:
483
336
* Returns: Size of identifier in bytes (not including NULL
487
gimp_eevl_unit_identifier_size (const gchar *string,
339
int ExpressionEvaluator::getIdentifierSize(const char *string, int start_offset)
490
const gchar *start = g_utf8_offset_to_pointer (string, start_offset);
491
const gchar *s = start;
492
gunichar c = g_utf8_get_char (s);
495
if (gimp_eevl_unit_identifier_start (c))
497
s = g_utf8_next_char (s);
498
c = g_utf8_get_char (s);
501
while (gimp_eevl_unit_identifier_continue (c))
503
s = g_utf8_next_char (s);
504
c = g_utf8_get_char (s);
341
const char *start = g_utf8_offset_to_pointer(string, start_offset);
342
const char *s = start;
343
gunichar c = g_utf8_get_char(s);
346
if (isUnitIdentifierStart(c)) {
347
s = g_utf8_next_char (s);
348
c = g_utf8_get_char (s);
351
while ( isUnitIdentifierStart (c) || g_unichar_isdigit (c) ) {
352
s = g_utf8_next_char(s);
353
c = g_utf8_get_char(s);
509
return g_utf8_offset_to_pointer (start, length) - start;
513
gimp_eevl_expect (GimpEevl *eva,
514
GimpEevlTokenType token_type,
515
GimpEevlToken *value)
517
if (! gimp_eevl_accept (eva, token_type, value))
518
gimp_eevl_error (eva, "Unexpected token");
522
gimp_eevl_error (GimpEevl *eva,
525
throw EvaluatorException(msg, eva->start_of_current_token);
358
return g_utf8_offset_to_pointer(start, length) - start;
361
bool ExpressionEvaluator::resolveUnit (const char* identifier,
362
EvaluatorQuantity *result,
367
result->dimension = 1;
369
}else if (!identifier) {
371
result->dimension = unit->isAbsolute() ? 1 : 0;
373
} else if (unit_table.hasUnit(identifier)) {
374
Unit identifier_unit = unit_table.getUnit(identifier);
375
result->value = Quantity::convert(1, *unit, identifier_unit);
376
result->dimension = identifier_unit.isAbsolute() ? 1 : 0;
383
void ExpressionEvaluator::throwError(const char *msg)
385
throw EvaluatorException(msg, start_of_current_token);
528
388
} // namespace Util