~engelmarkus/inkscape/cppify

« back to all changes in this revision

Viewing changes to src/util/expression-evaluator.cpp

  • Committer: Markus Engel
  • Date: 2013-09-24 22:17:24 UTC
  • mfrom: (11608.7.303 inkscape)
  • Revision ID: markus.engel@tum.de-20130924221724-mts1vaw7r25jqana
Merged from trunk (r12588).

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
 * Copyright (C) 2008 Martin Nordholts <martinn@svn.gnome.org>
7
7
 * Modified for Inkscape by Johan Engelen
8
8
 * Copyright (C) 2011 Johan Engelen
 
9
 * Copyright (C) 2013 Matthew Petroff
9
10
 *
10
11
 * This library is free software: you can redistribute it and/or
11
12
 * modify it under the terms of the GNU Lesser General Public
27
28
#include "util/expression-evaluator.h"
28
29
#include "util/units.h"
29
30
 
 
31
#include <math.h>
30
32
#include <string.h>
31
33
 
32
34
using Inkscape::Util::unit_table;
34
36
namespace Inkscape {
35
37
namespace Util {
36
38
 
37
 
enum
38
 
{
39
 
  GIMP_EEVL_TOKEN_NUM        = 30000,
40
 
  GIMP_EEVL_TOKEN_IDENTIFIER = 30001,
41
 
 
42
 
  GIMP_EEVL_TOKEN_ANY        = 40000,
43
 
 
44
 
  GIMP_EEVL_TOKEN_END        = 50000
45
 
};
46
 
 
47
 
typedef int GimpEevlTokenType;
48
 
 
49
 
 
50
 
typedef struct
51
 
{
52
 
  GimpEevlTokenType type;
53
 
 
54
 
  union
55
 
  {
56
 
    gdouble fl;
57
 
 
58
 
    struct
59
 
    {
60
 
      const gchar *c;
61
 
      gint         size;
62
 
    };
63
 
 
64
 
  } value;
65
 
 
66
 
} GimpEevlToken;
67
 
 
68
 
typedef struct
69
 
{
70
 
  const gchar             *string;
71
 
  GimpEevlUnitResolverProc unit_resolver_proc;
72
 
  Unit                    *unit;
73
 
 
74
 
  GimpEevlToken            current_token;
75
 
  const gchar             *start_of_current_token;
76
 
} GimpEevl;
77
 
 
78
 
/** Unit Resolver...
79
 
 */
80
 
static bool unitresolverproc  (const gchar* identifier, GimpEevlQuantity *result, Unit* unit)
81
 
{
82
 
    if (!unit) {
83
 
        result->value = 1;
84
 
        result->dimension = 1;
85
 
        return true;
86
 
    }else if (!identifier) {
87
 
        result->value = 1;
88
 
        result->dimension = unit->isAbsolute() ? 1 : 0;
89
 
        return true;
90
 
    } else if (unit_table.hasUnit(identifier)) {
91
 
        Unit identifier_unit = unit_table.getUnit(identifier);
92
 
 
93
 
        // Catch the case of zero or negative unit factors (error!)
94
 
        if (identifier_unit.factor < 0.0000001) {
95
 
            return false;
96
 
        }
97
 
 
98
 
        result->value = unit->factor / identifier_unit.factor;
99
 
        result->dimension = identifier_unit.isAbsolute() ? 1 : 0;
100
 
        return true;
101
 
    } else {
102
 
        return false;
103
 
    }
104
 
}
105
 
 
106
 
static void             gimp_eevl_init                     (GimpEevl                 *eva,
107
 
                                                            const gchar              *string,
108
 
                                                            GimpEevlUnitResolverProc  unit_resolver_proc,
109
 
                                                            Unit                     *unit);
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,
120
 
                                                            gint                      count,
121
 
                                                            GimpEevlTokenType         token_type);
122
 
static void             gimp_eevl_lex_accept_to             (GimpEevl                 *eva,
123
 
                                                            gchar                    *to,
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,
129
 
                                                            gint                      start);
130
 
static void             gimp_eevl_expect                   (GimpEevl                 *eva,
131
 
                                                            GimpEevlTokenType         token_type,
132
 
                                                            GimpEevlToken            *value);
133
 
static void             gimp_eevl_error                    (GimpEevl                 *eva,
134
 
                                                            const char               *msg);
135
 
 
 
39
EvaluatorQuantity::EvaluatorQuantity(double value, unsigned int dimension) :
 
40
    value(value),
 
41
    dimension(dimension)
 
42
{
 
43
}
 
44
 
 
45
EvaluatorToken::EvaluatorToken()
 
46
{
 
47
    type = 0;
 
48
    value.fl = 0;
 
49
}
 
50
 
 
51
ExpressionEvaluator::ExpressionEvaluator(const char *string, Unit *unit) :
 
52
    string(string),
 
53
    unit(unit)
 
54
{
 
55
    current_token.type  = TOKEN_END;
 
56
    
 
57
    // Preload symbol
 
58
    parseNextToken();
 
59
}
136
60
 
137
61
/**
138
62
 * Evaluates the given arithmetic expression, along with an optional dimension
139
63
 * analysis, and basic unit conversions.
140
64
 *
141
 
 * @param string              The NULL-terminated string to be evaluated.
142
 
 * @param unit_resolver_proc  Unit resolver callback.
143
 
 *
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
146
 
 * returned value.
 
66
 * base-unit. This is also the unit of the returned value.
147
67
 *
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).
151
71
 *
152
72
 * @return Result of evaluation.
153
73
 * @throws Inkscape::Util::EvaluatorException There was a parse error.
154
74
 **/
155
 
GimpEevlQuantity
156
 
gimp_eevl_evaluate (const gchar* string, Unit* unit)
 
75
EvaluatorQuantity ExpressionEvaluator::evaluate()
157
76
{
158
 
    if (! g_utf8_validate (string, -1, NULL)) {
 
77
    if (!g_utf8_validate(string, -1, NULL)) {
159
78
        throw EvaluatorException("Invalid UTF8 string", NULL);
160
79
    }
161
 
 
162
 
    GimpEevl eva;
163
 
    gimp_eevl_init (&eva, string, unitresolverproc, unit);
164
 
 
165
 
    return gimp_eevl_complete(&eva);
166
 
}
167
 
 
168
 
static void
169
 
gimp_eevl_init (GimpEevl                  *eva,
170
 
                const gchar               *string,
171
 
                GimpEevlUnitResolverProc   unit_resolver_proc,
172
 
                Unit                      *unit)
173
 
{
174
 
  eva->string              = string;
175
 
  eva->unit_resolver_proc  = unit_resolver_proc;
176
 
  eva->unit                = unit;
177
 
 
178
 
  eva->current_token.type  = GIMP_EEVL_TOKEN_END;
179
 
 
180
 
  /* Preload symbol... */
181
 
  gimp_eevl_lex (eva);
182
 
}
183
 
 
184
 
static GimpEevlQuantity
185
 
gimp_eevl_complete (GimpEevl *eva)
186
 
{
187
 
  GimpEevlQuantity result = {0, 0};
188
 
  GimpEevlQuantity default_unit_factor;
189
 
 
190
 
  /* Empty expression evaluates to 0 */
191
 
  if (gimp_eevl_accept (eva, GIMP_EEVL_TOKEN_END, NULL))
192
 
    return result;
193
 
 
194
 
  result = gimp_eevl_expression (eva);
195
 
 
196
 
  /* There should be nothing left to parse by now */
197
 
  gimp_eevl_expect (eva, GIMP_EEVL_TOKEN_END, 0);
198
 
 
199
 
  eva->unit_resolver_proc (NULL, &default_unit_factor, eva->unit);
200
 
 
201
 
  /* Entire expression is dimensionless, apply default unit if
202
 
   * applicable
203
 
   */
204
 
  if (result.dimension == 0 && default_unit_factor.dimension != 0)
205
 
    {
206
 
      result.value     /= default_unit_factor.value;
207
 
      result.dimension  = default_unit_factor.dimension;
208
 
    }
209
 
  return result;
210
 
}
211
 
 
212
 
static GimpEevlQuantity
213
 
gimp_eevl_expression (GimpEevl *eva)
214
 
{
215
 
  gboolean         subtract;
216
 
  GimpEevlQuantity evaluated_terms;
217
 
 
218
 
  evaluated_terms = gimp_eevl_term (eva);
219
 
 
220
 
  /* continue evaluating terms, chained with + or -. */
221
 
  for (subtract = FALSE;
222
 
       gimp_eevl_accept (eva, '+', NULL) ||
223
 
       (subtract = gimp_eevl_accept (eva, '-', NULL));
224
 
       subtract = FALSE)
225
 
    {
226
 
      GimpEevlQuantity new_term = gimp_eevl_term (eva);
227
 
 
228
 
      /* If dimensions missmatch, attempt default unit assignent */
229
 
      if (new_term.dimension != evaluated_terms.dimension)
230
 
        {
231
 
          GimpEevlQuantity default_unit_factor;
232
 
 
233
 
          eva->unit_resolver_proc (NULL,
234
 
                                   &default_unit_factor,
235
 
                                   eva->unit);
236
 
 
237
 
          if (new_term.dimension == 0 &&
238
 
              evaluated_terms.dimension == default_unit_factor.dimension)
239
 
            {
240
 
              new_term.value     /= default_unit_factor.value;
241
 
              new_term.dimension  = default_unit_factor.dimension;
242
 
            }
243
 
          else if (evaluated_terms.dimension == 0 &&
244
 
                   new_term.dimension == default_unit_factor.dimension)
245
 
            {
246
 
              evaluated_terms.value     /= default_unit_factor.value;
247
 
              evaluated_terms.dimension  = default_unit_factor.dimension;
248
 
            }
249
 
          else
250
 
            {
251
 
              gimp_eevl_error (eva, "Dimension missmatch during addition");
252
 
            }
253
 
        }
254
 
 
255
 
      evaluated_terms.value += (subtract ? -new_term.value : new_term.value);
256
 
    }
257
 
 
258
 
  return evaluated_terms;
259
 
}
260
 
 
261
 
static GimpEevlQuantity
262
 
gimp_eevl_term (GimpEevl *eva)
263
 
{
264
 
  gboolean         division;
265
 
  GimpEevlQuantity evaluated_signed_factors;
266
 
 
267
 
  evaluated_signed_factors = gimp_eevl_signed_factor (eva);
268
 
 
269
 
  for (division = FALSE;
270
 
       gimp_eevl_accept (eva, '*', NULL) ||
271
 
       (division = gimp_eevl_accept (eva, '/', NULL));
272
 
       division = FALSE)
273
 
    {
274
 
      GimpEevlQuantity new_signed_factor = gimp_eevl_signed_factor (eva);
275
 
 
276
 
      if (division)
277
 
        {
278
 
          evaluated_signed_factors.value     /= new_signed_factor.value;
279
 
          evaluated_signed_factors.dimension -= new_signed_factor.dimension;
280
 
 
281
 
        }
282
 
      else
283
 
        {
284
 
          evaluated_signed_factors.value     *= new_signed_factor.value;
285
 
          evaluated_signed_factors.dimension += new_signed_factor.dimension;
286
 
        }
287
 
    }
288
 
 
289
 
  return evaluated_signed_factors;
290
 
}
291
 
 
292
 
static GimpEevlQuantity
293
 
gimp_eevl_signed_factor (GimpEevl *eva)
294
 
{
295
 
  GimpEevlQuantity result;
296
 
  gboolean         negate = FALSE;
297
 
 
298
 
  if (! gimp_eevl_accept (eva, '+', NULL))
299
 
    negate = gimp_eevl_accept (eva, '-', NULL);
300
 
 
301
 
  result = gimp_eevl_factor (eva);
302
 
 
303
 
  if (negate) result.value = -result.value;
304
 
 
305
 
  return result;
306
 
}
307
 
 
308
 
static GimpEevlQuantity
309
 
gimp_eevl_factor (GimpEevl *eva)
310
 
{
311
 
  GimpEevlQuantity evaluated_factor = { 0, 0 };
312
 
  GimpEevlToken    consumed_token;
313
 
 
314
 
  if (gimp_eevl_accept (eva,
315
 
                        GIMP_EEVL_TOKEN_NUM,
316
 
                        &consumed_token))
317
 
    {
318
 
      evaluated_factor.value = consumed_token.value.fl;
319
 
    }
320
 
  else if (gimp_eevl_accept (eva, '(', NULL))
321
 
    {
322
 
      evaluated_factor = gimp_eevl_expression (eva);
323
 
      gimp_eevl_expect (eva, ')', 0);
324
 
    }
325
 
  else
326
 
    {
327
 
      gimp_eevl_error (eva, "Expected number or '('");
328
 
    }
329
 
 
330
 
  if (eva->current_token.type == GIMP_EEVL_TOKEN_IDENTIFIER)
331
 
    {
332
 
      gchar            *identifier;
333
 
      GimpEevlQuantity  result;
334
 
 
335
 
      gimp_eevl_accept (eva,
336
 
                        GIMP_EEVL_TOKEN_ANY,
337
 
                        &consumed_token);
338
 
 
339
 
      identifier = g_newa (gchar, consumed_token.value.size + 1);
340
 
 
341
 
      strncpy (identifier, consumed_token.value.c, consumed_token.value.size);
342
 
      identifier[consumed_token.value.size] = '\0';
343
 
 
344
 
      if (eva->unit_resolver_proc (identifier,
345
 
                                   &result,
346
 
                                   eva->unit))
347
 
        {
348
 
          evaluated_factor.value      /= result.value;
349
 
          evaluated_factor.dimension  += result.dimension;
350
 
        }
351
 
      else
352
 
        {
353
 
          gimp_eevl_error (eva, "Unit was not resolved");
354
 
        }
355
 
    }
356
 
 
357
 
  return evaluated_factor;
358
 
}
359
 
 
360
 
static gboolean
361
 
gimp_eevl_accept (GimpEevl          *eva,
362
 
                  GimpEevlTokenType  token_type,
363
 
                  GimpEevlToken     *consumed_token)
364
 
{
365
 
  gboolean existed = FALSE;
366
 
 
367
 
  if (token_type == eva->current_token.type ||
368
 
      token_type == GIMP_EEVL_TOKEN_ANY)
369
 
    {
370
 
      existed = TRUE;
371
 
 
372
 
      if (consumed_token)
373
 
        *consumed_token = eva->current_token;
374
 
 
375
 
      /* Parse next token */
376
 
      gimp_eevl_lex (eva);
377
 
    }
378
 
 
379
 
  return existed;
380
 
}
381
 
 
382
 
static void
383
 
gimp_eevl_lex (GimpEevl *eva)
384
 
{
385
 
  const gchar *s;
386
 
 
387
 
  gimp_eevl_move_past_whitespace (eva);
388
 
  s = eva->string;
389
 
  eva->start_of_current_token = s;
390
 
 
391
 
  if (! s || s[0] == '\0')
392
 
    {
393
 
      /* We're all done */
394
 
      eva->current_token.type = GIMP_EEVL_TOKEN_END;
395
 
    }
396
 
  else if (s[0] == '+' || s[0] == '-')
397
 
    {
398
 
      /* Snatch these before the g_strtod() does, othewise they might
399
 
       * be used in a numeric conversion.
400
 
       */
401
 
      gimp_eevl_lex_accept_count (eva, 1, s[0]);
402
 
    }
403
 
  else
404
 
    
405
 
    {
406
 
      /* Attempt to parse a numeric value */
407
 
      gchar  *endptr = NULL;
408
 
      gdouble value      = g_strtod (s, &endptr);
409
 
 
410
 
      if (endptr && endptr != s)
411
 
        {
412
 
          /* A numeric could be parsed, use it */
413
 
          eva->current_token.value.fl = value;
414
 
 
415
 
          gimp_eevl_lex_accept_to (eva, endptr, GIMP_EEVL_TOKEN_NUM);
416
 
        }
417
 
      else if (gimp_eevl_unit_identifier_start (s[0]))
418
 
        {
419
 
          /* Unit identifier */
420
 
          eva->current_token.value.c    = s;
421
 
          eva->current_token.value.size = gimp_eevl_unit_identifier_size (s, 0);
422
 
 
423
 
          gimp_eevl_lex_accept_count (eva,
424
 
                                      eva->current_token.value.size,
425
 
                                      GIMP_EEVL_TOKEN_IDENTIFIER);
426
 
        }
427
 
      else
428
 
        {
429
 
          /* Everything else is a single character token */
430
 
          gimp_eevl_lex_accept_count (eva, 1, s[0]);
431
 
        }
432
 
    }
433
 
}
434
 
 
435
 
static void
436
 
gimp_eevl_lex_accept_count (GimpEevl          *eva,
437
 
                            gint               count,
438
 
                            GimpEevlTokenType  token_type)
439
 
{
440
 
  eva->current_token.type  = token_type;
441
 
  eva->string             += count;
442
 
}
443
 
 
444
 
static void
445
 
gimp_eevl_lex_accept_to (GimpEevl          *eva,
446
 
                         gchar             *to,
447
 
                         GimpEevlTokenType  token_type)
448
 
{
449
 
  eva->current_token.type = token_type;
450
 
  eva->string             = to;
451
 
}
452
 
 
453
 
static void
454
 
gimp_eevl_move_past_whitespace (GimpEevl *eva)
455
 
{
456
 
  if (! eva->string)
457
 
    return;
458
 
 
459
 
  while (g_ascii_isspace (*eva->string))
460
 
    eva->string++;
461
 
}
462
 
 
463
 
static gboolean
464
 
gimp_eevl_unit_identifier_start (gunichar c)
465
 
{
466
 
  return (g_unichar_isalpha (c) ||
467
 
          c == (gunichar) '%'   ||
468
 
          c == (gunichar) '\'');
469
 
}
470
 
 
471
 
static gboolean
472
 
gimp_eevl_unit_identifier_continue (gunichar c)
473
 
{
474
 
  return (gimp_eevl_unit_identifier_start (c) ||
475
 
          g_unichar_isdigit (c));
 
80
    
 
81
    EvaluatorQuantity result = EvaluatorQuantity();
 
82
    EvaluatorQuantity default_unit_factor;
 
83
    
 
84
    // Empty expression evaluates to 0
 
85
    if (acceptToken(TOKEN_END, NULL)) {
 
86
        return result;
 
87
    }
 
88
    
 
89
    result = evaluateExpression();
 
90
    
 
91
    // There should be nothing left to parse by now
 
92
    isExpected(TOKEN_END, 0);
 
93
    
 
94
    resolveUnit(NULL, &default_unit_factor, unit);
 
95
    
 
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;
 
100
    }
 
101
    return result;
 
102
}
 
103
 
 
104
EvaluatorQuantity ExpressionEvaluator::evaluateExpression()
 
105
{
 
106
    bool subtract;
 
107
    EvaluatorQuantity evaluated_terms;
 
108
    
 
109
    evaluated_terms = evaluateTerm();
 
110
    
 
111
    // Continue evaluating terms, chained with + or -.
 
112
    for (subtract = FALSE;
 
113
        acceptToken('+', NULL) || (subtract = acceptToken('-', NULL));
 
114
        subtract = FALSE)
 
115
    {
 
116
        EvaluatorQuantity new_term = evaluateTerm();
 
117
        
 
118
        // If dimensions mismatch, attempt default unit assignent
 
119
        if ( new_term.dimension != evaluated_terms.dimension ) {
 
120
            EvaluatorQuantity default_unit_factor;
 
121
            
 
122
            resolveUnit(NULL, &default_unit_factor, unit);
 
123
            
 
124
            if ( new_term.dimension == 0
 
125
                && evaluated_terms.dimension == default_unit_factor.dimension )
 
126
            {
 
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 )
 
131
            {
 
132
                evaluated_terms.value     /= default_unit_factor.value;
 
133
                evaluated_terms.dimension  = default_unit_factor.dimension;
 
134
            } else {
 
135
                throwError("Dimension mismatch during addition");
 
136
            }
 
137
        }
 
138
        
 
139
        evaluated_terms.value += (subtract ? -new_term.value : new_term.value);
 
140
    }
 
141
    
 
142
    return evaluated_terms;
 
143
}
 
144
 
 
145
EvaluatorQuantity ExpressionEvaluator::evaluateTerm()
 
146
{
 
147
    bool division;
 
148
    EvaluatorQuantity evaluated_exp_terms = evaluateExpTerm();
 
149
    
 
150
    for ( division = false;
 
151
        acceptToken('*', NULL) || (division = acceptToken('/', NULL));
 
152
        division = false )
 
153
    {
 
154
        EvaluatorQuantity new_exp_term = evaluateExpTerm();
 
155
        
 
156
        if (division) {
 
157
            evaluated_exp_terms.value     /= new_exp_term.value;
 
158
            evaluated_exp_terms.dimension -= new_exp_term.dimension;
 
159
        } else {
 
160
            evaluated_exp_terms.value     *= new_exp_term.value;
 
161
            evaluated_exp_terms.dimension += new_exp_term.dimension;
 
162
        }
 
163
    }
 
164
    
 
165
    return evaluated_exp_terms;
 
166
}
 
167
 
 
168
EvaluatorQuantity ExpressionEvaluator::evaluateExpTerm()
 
169
{
 
170
    EvaluatorQuantity evaluated_signed_factors = evaluateSignedFactor();
 
171
    
 
172
    while(acceptToken('^', NULL)) {
 
173
        EvaluatorQuantity new_signed_factor = evaluateSignedFactor();
 
174
        
 
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;
 
179
        } else {
 
180
            throwError("Unit in exponent");
 
181
        }
 
182
    }
 
183
    
 
184
    return evaluated_signed_factors;
 
185
}
 
186
 
 
187
EvaluatorQuantity ExpressionEvaluator::evaluateSignedFactor()
 
188
{
 
189
    EvaluatorQuantity result;
 
190
    bool negate = FALSE;
 
191
    
 
192
    if (!acceptToken('+', NULL)) {
 
193
        negate = acceptToken ('-', NULL);
 
194
    }
 
195
    
 
196
    result = evaluateFactor();
 
197
    
 
198
    if (negate) {
 
199
        result.value = -result.value;
 
200
    }
 
201
    
 
202
    return result;
 
203
}
 
204
 
 
205
EvaluatorQuantity ExpressionEvaluator::evaluateFactor()
 
206
{
 
207
    EvaluatorQuantity evaluated_factor = EvaluatorQuantity();
 
208
    EvaluatorToken consumed_token = EvaluatorToken();
 
209
    
 
210
    if (acceptToken(TOKEN_NUM, &consumed_token)) {
 
211
        evaluated_factor.value = consumed_token.value.fl;
 
212
    } else if (acceptToken('(', NULL)) {
 
213
        evaluated_factor = evaluateExpression();
 
214
        isExpected(')', 0);
 
215
    } else {
 
216
        throwError("Expected number or '('");
 
217
    }
 
218
    
 
219
    if ( current_token.type == TOKEN_IDENTIFIER ) {
 
220
        char *identifier;
 
221
        EvaluatorQuantity result;
 
222
        
 
223
        acceptToken(TOKEN_ANY, &consumed_token);
 
224
        
 
225
        identifier = g_newa(char, consumed_token.value.size + 1);
 
226
        
 
227
        strncpy(identifier, consumed_token.value.c, consumed_token.value.size);
 
228
        identifier[consumed_token.value.size] = '\0';
 
229
        
 
230
        if (resolveUnit(identifier, &result, unit)) {
 
231
            evaluated_factor.value      /= result.value;
 
232
            evaluated_factor.dimension  += result.dimension;
 
233
        } else {
 
234
            throwError("Unit was not resolved");
 
235
        }
 
236
    }
 
237
    
 
238
    return evaluated_factor;
 
239
}
 
240
 
 
241
bool ExpressionEvaluator::acceptToken(TokenType token_type,
 
242
                                      EvaluatorToken *consumed_token)
 
243
{
 
244
    bool existed = FALSE;
 
245
    
 
246
    if ( token_type == current_token.type || token_type == TOKEN_ANY ) {
 
247
        existed = TRUE;
 
248
        
 
249
        if (consumed_token) {
 
250
            *consumed_token = current_token;
 
251
        }
 
252
        
 
253
        // Parse next token
 
254
        parseNextToken();
 
255
    }
 
256
    
 
257
    return existed;
 
258
}
 
259
 
 
260
void ExpressionEvaluator::parseNextToken()
 
261
{
 
262
    const char *s;
 
263
    
 
264
    movePastWhiteSpace();
 
265
    s = string;
 
266
    start_of_current_token = s;
 
267
    
 
268
    if ( !s || s[0] == '\0' ) {
 
269
        // We're all done
 
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]);
 
275
    } else {
 
276
        // Attempt to parse a numeric value
 
277
        char *endptr = NULL;
 
278
        gdouble value = g_strtod(s, &endptr);
 
279
        
 
280
        if ( endptr && endptr != s ) {
 
281
            // A numeric could be parsed, use it
 
282
            current_token.value.fl = value;
 
283
            
 
284
            current_token.type = TOKEN_NUM;
 
285
            string             = endptr;
 
286
        } else if (isUnitIdentifierStart(s[0])) {
 
287
            // Unit identifier
 
288
            current_token.value.c = s;
 
289
            current_token.value.size = getIdentifierSize(s, 0);
 
290
            
 
291
            acceptTokenCount(current_token.value.size, TOKEN_IDENTIFIER);
 
292
        } else {
 
293
            // Everything else is a single character token
 
294
            acceptTokenCount(1, s[0]);
 
295
        }
 
296
    }
 
297
}
 
298
 
 
299
void ExpressionEvaluator::acceptTokenCount (int count, TokenType token_type)
 
300
{
 
301
    current_token.type  = token_type;
 
302
    string             += count;
 
303
}
 
304
 
 
305
void ExpressionEvaluator::isExpected(TokenType token_type,
 
306
                                     EvaluatorToken *value)
 
307
{
 
308
    if (!acceptToken(token_type, value)) {
 
309
        throwError("Unexpected token");
 
310
    }
 
311
}
 
312
 
 
313
void ExpressionEvaluator::movePastWhiteSpace()
 
314
{
 
315
    if (!string) {
 
316
        return;
 
317
    }
 
318
    
 
319
    while (g_ascii_isspace(*string)) {
 
320
        string++;
 
321
    }
 
322
}
 
323
 
 
324
bool ExpressionEvaluator::isUnitIdentifierStart(gunichar c)
 
325
{
 
326
    return (g_unichar_isalpha (c)
 
327
        || c == (gunichar) '%'
 
328
        || c == (gunichar) '\'');
476
329
}
477
330
 
478
331
/**
479
 
 * gimp_eevl_unit_identifier_size:
 
332
 * getIdentifierSize:
480
333
 * @s:
481
334
 * @start:
482
335
 *
483
336
 * Returns: Size of identifier in bytes (not including NULL
484
337
 * terminator).
485
338
 **/
486
 
static gint
487
 
gimp_eevl_unit_identifier_size (const gchar *string,
488
 
                                gint         start_offset)
 
339
int ExpressionEvaluator::getIdentifierSize(const char *string, int start_offset)
489
340
{
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);
493
 
  gint         length = 0;
494
 
 
495
 
  if (gimp_eevl_unit_identifier_start (c))
496
 
    {
497
 
      s = g_utf8_next_char (s);
498
 
      c = g_utf8_get_char (s);
499
 
      length++;
500
 
 
501
 
      while (gimp_eevl_unit_identifier_continue (c))
502
 
        {
503
 
          s = g_utf8_next_char (s);
504
 
          c = g_utf8_get_char (s);
505
 
          length++;
 
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);
 
344
    int         length = 0;
 
345
    
 
346
    if (isUnitIdentifierStart(c)) {
 
347
        s = g_utf8_next_char (s);
 
348
        c = g_utf8_get_char (s);
 
349
        length++;
 
350
        
 
351
        while ( isUnitIdentifierStart (c) || g_unichar_isdigit (c) ) {
 
352
            s = g_utf8_next_char(s);
 
353
            c = g_utf8_get_char(s);
 
354
            length++;
506
355
        }
507
356
    }
508
 
  
509
 
  return g_utf8_offset_to_pointer (start, length) - start;
510
 
}
511
 
 
512
 
static void
513
 
gimp_eevl_expect (GimpEevl          *eva,
514
 
                  GimpEevlTokenType  token_type,
515
 
                  GimpEevlToken     *value)
516
 
{
517
 
  if (! gimp_eevl_accept (eva, token_type, value))
518
 
    gimp_eevl_error (eva, "Unexpected token");
519
 
}
520
 
 
521
 
static void
522
 
gimp_eevl_error (GimpEevl *eva,
523
 
                 const char    *msg)
524
 
{
525
 
  throw EvaluatorException(msg, eva->start_of_current_token);
 
357
    
 
358
    return g_utf8_offset_to_pointer(start, length) - start;
 
359
}
 
360
 
 
361
bool ExpressionEvaluator::resolveUnit (const char* identifier,
 
362
                                       EvaluatorQuantity *result,
 
363
                                       Unit* unit)
 
364
{
 
365
    if (!unit) {
 
366
        result->value = 1;
 
367
        result->dimension = 1;
 
368
        return true;
 
369
    }else if (!identifier) {
 
370
        result->value = 1;
 
371
        result->dimension = unit->isAbsolute() ? 1 : 0;
 
372
        return true;
 
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;
 
377
        return true;
 
378
    } else {
 
379
        return false;
 
380
    }
 
381
}
 
382
 
 
383
void ExpressionEvaluator::throwError(const char *msg)
 
384
{
 
385
    throw EvaluatorException(msg, start_of_current_token);
526
386
}
527
387
 
528
388
} // namespace Util