Index: libgimpmath/gimpmathtypes.h
===================================================================
--- libgimpmath/gimpmathtypes.h	(revision 25470)
+++ libgimpmath/gimpmathtypes.h	(working copy)
@@ -32,6 +32,8 @@ typedef struct _GimpVector2 GimpVector2;
 typedef struct _GimpVector3 GimpVector3;
 typedef struct _GimpVector4 GimpVector4;
 
+typedef struct _GimpEevlQuantity GimpEevlQuantity;
+
 G_END_DECLS
 
 #endif /* __GIMP_MATH_TYPES_H__ */
Index: libgimpmath/Makefile.am
===================================================================
--- libgimpmath/Makefile.am	(revision 25470)
+++ libgimpmath/Makefile.am	(working copy)
@@ -68,14 +68,17 @@ libgimpmath_2_0_la_SOURCES = \
 	gimpmd5.c	\
 	gimpmd5.h	\
 	gimpvector.c	\
-	gimpvector.h
+	gimpvector.h	\
+	gimpeevl.c	\
+	gimpeevl.h
 
 libgimpmathinclude_HEADERS = \
 	gimpmath.h	\
 	gimpmathtypes.h	\
 	gimpmatrix.h	\
 	gimpmd5.h	\
-	gimpvector.h
+	gimpvector.h	\
+	gimpeevl.h
 
 libgimpmath_2_0_la_LDFLAGS = \
 	-version-info $(LT_VERSION_INFO)	\
@@ -86,6 +89,31 @@ libgimpmath_2_0_la_DEPENDENCIES = $(gimp
 
 libgimpmath_2_0_la_LIBADD = $(GLIB_LIBS) $(libm)
 
+test_eevl_DEPENDENCIES = \
+	$(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la
+
+test_eevl_LDADD = \
+	$(GLIB_LIBS) 		\
+	$(test_eevl_DEPENDENCIES)
+
+#
+# test programs, not to be built by default and never installed
+#
+
+TESTS = test-eevl$(EXEEXT)
+
+EXTRA_PROGRAMS = test-eevl
+
+test_eevl_DEPENDENCIES = \
+	$(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la
+
+test_eevl_LDADD = \
+	$(GLIB_LIBS) 		\
+	$(test_eevl_DEPENDENCIES)
+
+
+CLEANFILES = $(EXTRA_PROGRAMS)
+
 
 install-data-local: install-ms-lib install-libtool-import-lib
 
Index: libgimpmath/gimpeevl.c
===================================================================
--- libgimpmath/gimpeevl.c	(revision 0)
+++ libgimpmath/gimpeevl.c	(revision 0)
@@ -0,0 +1,655 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpeevl.c
+ * Copyright (C) 2008 Fredrik Alstromer <roe@excu.se>
+ *
+ * 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 2 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
+ * Library 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* Introducing eevl eva, the 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, I figured with the given use case, that's enough
+ * to combat any rounding errors (as opposed to optimizing the evalutation).
+ *
+ * Variables can be supplied through a callback, providing a quantity (value
+ * and dimensionality) for a specified identifier, which can be useful for
+ * allowing the user to specify a height in terms of the width or similar.
+ *
+ * It must be supplied with a pxpu (pixels per unit) for unit conversions, as
+ * pixels are the base unit, and will be the unit of the value returned. A
+ * callback can be provided for arbitrary units as an x-per-unit conversion
+ * factor. If using the internal units, the unit is inches, and pxpu is
+ * effectively dpi (dots per inch).
+ *
+ * It does basic unit conversions, and an elementary dimensionality constraint
+ * check (e.g. 2mm+3px*4in is an error, as L + L^2 is a missmatch).
+ *
+ * It uses setjmp/longjmp for try/catch like pattern on error, it uses strtod
+ * (stdlib.h) for numeric conversions it's non-destructive in terms of the
+ * paramters, and it's thread safe.
+ *
+ * EBNF:
+ *
+ * expression := term { ( '+' | '-' ) term }*
+ * 	|  <empty string>
+ *
+ * term := sfactor { ( '*' | '/' ) sfactor }*
+ *
+ * sfactor := ( '+' | '-' )? factor
+ *
+ * factor := NUMERIC ( unit )?
+ * 	|  IDENTIFIER
+ * 	|  '(' expression ')'
+ *
+ * unit := ( 'px' | '"' | '\'' )
+ * 	|  IDENTIFIER
+ * 	|  ( 'mm' | 'cm' | 'dm' | 'm' | 'ft' | 'in' )  < if defined >
+ *
+ * The code matches the EBNF rather closely (except for the non-terminal unit,
+ * 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.
+ *
+ * The identifiers are parsed as [A-Za-z_][A-Za-z0-9_]*, as usual. Numerics are
+ * parsed as [0-9.] followed by whatever strtod wants to consume. Units are
+ * case sensitive for the time being, and I suppose that's rather common to not
+ * mix up milli and Mega (not that mega is currently supported). Whitespace is
+ * treated as [ \t\v\r\n] and is discarded.
+ */
+
+#include "config.h"
+
+#include <setjmp.h>   /* for setjmp/longjmp */
+
+#include <glib-object.h>
+
+#include "gimpmath.h"
+
+#include "libgimp/libgimp-intl.h"  /* for error messages */
+
+// tokens out of ascii-range
+enum Tokens
+{
+  tok_num   = 301,
+  tok_ident = 302,
+
+  tok_px    = 400,
+
+#ifdef USE_EEVL_UNITS
+  /* -- these may be handled by an external unit system -- */
+  tok_m     = 401,
+  tok_cm    = 402,
+  tok_mm    = 403,
+  tok_ft    = 404,
+  tok_in    = 405,
+#endif /* USE_EEVL_UNITS */
+
+  tok_end   = 500
+};
+
+struct Symbol
+  {
+    gint token;
+    union
+      {
+        gdouble fl;
+        struct
+          {
+            const gchar *c;
+            gint len;
+          };
+      } value;
+  };
+
+typedef GimpEevlQuantity quant;
+
+struct Eevl
+{
+  const gchar *data;
+  struct Symbol sym;
+  GimpEevlQuantity defunit;
+
+  const gchar *error;
+  jmp_buf catcher;
+
+  gdouble pxpu;
+  GimpEevlVariableProc vars;
+  GimpEevlUnitProc units;
+  gpointer proc_data;
+};
+
+static const GimpEevlQuantity invalid_quantity = { 0./0., 0 };
+
+static void error (struct Eevl *eva,
+                   gchar* msg);
+
+#define INIDENT(x) (((x)>='a'&&(x)<='z')||((x)>='A'&&(x)<='Z')||((x)>='0'&&(x)<='9')||(x)=='_')
+#define ISSPACE(x) ((x)==' '||(x)=='\t'||(x)=='\n'||(x)=='\r'||(x)=='\v')
+
+/* token scanner */
+
+static gint
+identlen (const gchar *s,
+          gint         start)
+{
+  while (INIDENT (s[start])) start++;
+  return start;
+}
+
+static void
+lexaccept2 (struct Eevl *eva,
+            gchar       *to,
+            gint         token)
+{
+  eva->sym.token = token;
+  eva->data = to;
+}
+
+static void
+lexaccept (struct Eevl *eva, gint count, gint token)
+{
+  eva->sym.token = token;
+  eva->data += count;
+}
+
+static void
+lex (struct Eevl *eva)
+{
+  const gchar *s;
+
+  if (eva->data) while (ISSPACE (*eva->data)) eva->data++;
+
+  if (!eva->data || !*eva->data)
+    {
+      eva->sym.token = tok_end; // all done.
+      return;
+    }
+
+  s = eva->data;
+
+# ifdef USE_EEVL_UNITS
+  if (s[0] == 'c')
+    {
+      if (s[1] == 'm' && !INIDENT (s[2]))
+        {
+          lexaccept (eva, 2, tok_cm);
+        }
+      else
+        {
+          eva->sym.value.c = s;
+          lexaccept (eva, eva->sym.value.len = identlen (s, 1), tok_ident);
+        }
+    }
+  else if (s[0] == 'd')
+    {
+      if (s[1] == 'm' && !INIDENT (s[2]))
+        {
+          lexaccept (eva, 2, tok_cm);
+        }
+      else
+        {
+          eva->sym.value.c = s;
+          lexaccept (eva, eva->sym.value.len = identlen (s, 1), tok_ident);
+        }
+    }
+  else if (s[0] == 'f')
+    {
+      if (s[1] == 't' && !INIDENT (s[2]))
+        {
+          lexaccept (eva, 2, tok_ft);
+        }
+      else
+        {
+          eva->sym.value.c = s;
+          lexaccept (eva, eva->sym.value.len = identlen (s, 1), tok_ident);
+        }
+    }
+  else if (s[0] == 'i')
+    {
+      if (s[1] == 'n' && !INIDENT (s[2]))
+        {
+          lexaccept (eva, 2, tok_in);
+        }
+      else
+        {
+          eva->sym.value.c = s;
+          lexaccept (eva, eva->sym.value.len = identlen (s, 1), tok_ident);
+        }
+    }
+  else if (s[0] == 'm')
+    {
+      if (s[1] == 'm' && !INIDENT (s[2]))
+        {
+          lexaccept (eva, 2, tok_mm);
+        }
+      else if (!INIDENT (s[1]))
+        {
+          lexaccept (eva, 1, tok_m);
+        }
+      else
+        {
+          eva->sym.value.c = s;
+          lexaccept (eva, eva->sym.value.len = identlen (s, 1), tok_ident);
+        }
+    }
+  else
+# endif /* USE_EEVL_UNITS */
+
+    if (s[0] == 'p')
+      {
+        if (s[1] == 'x' && !INIDENT (s[2]))
+          {
+            lexaccept (eva, 2, tok_px);
+          }
+        else
+          {
+            eva->sym.value.c = s;
+            lexaccept (eva, eva->sym.value.len = identlen (s, 1), tok_ident);
+          }
+      }
+    else if (s[0] == '+' || s[0] == '-')
+      {
+        /* snatch these before the strtod does, othewise it might be used
+         * in a numeric conversion. */
+        lexaccept (eva, 1, s[0]);
+      }
+    else
+      {
+        /* attempt to parse a numeric value, according to locale. */
+        gchar *endptr = 0;
+        gdouble v = g_strtod (s, &endptr);
+        if (endptr != s)
+          {
+            /* a numeric could be parsed, use it. */
+            eva->sym.value.fl = v;
+            lexaccept2 (eva, endptr, tok_num);
+          }
+        else if (INIDENT (s[0]))
+          {
+            eva->sym.value.c = s;
+            lexaccept (eva, eva->sym.value.len = identlen (s, 0), tok_ident);
+          }
+        else
+          {
+            /* everything else is a single character token. */
+            lexaccept (eva, 1, s[0]);
+          }
+      }
+}
+
+static struct Eevl
+create (const gchar *data,
+        quant defunit,
+        gdouble pxpu,
+        GimpEevlVariableProc vars,
+        GimpEevlUnitProc units,
+        gpointer proc_data)
+  {
+    struct Eevl eva;
+
+    eva.data = data;
+    eva.defunit = defunit;
+
+    eva.error = 0;
+
+    eva.pxpu = pxpu;
+    eva.vars = vars;
+    eva.units = units;
+    eva.proc_data = proc_data;
+
+    lex (&eva); // preload symbol..
+    return eva;
+  }
+
+static gboolean
+shift (struct Eevl   *eva,
+       gint           token,
+       struct Symbol *value)
+{
+  if (token == eva->sym.token || !token)
+    {
+      if (value) *value = eva->sym;
+      lex (eva);
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static gint
+lookahead (struct Eevl *eva)
+{
+  return eva->sym.token;
+}
+
+/* This is the error reporting function, pretty much the 'throw'-statement
+ * equivalent if you wish. This function never returns.
+ */
+static void
+error (struct Eevl *eva,
+      gchar       *msg)
+{
+  eva->error = msg;
+  longjmp (eva->catcher, 1);
+}
+
+/* Short-hand function, only returns if the expected token is in the pipe. */
+static void
+expect (struct Eevl   *eva,
+       gint           token,
+       struct Symbol *value)
+{
+  if (!shift (eva, token, value))
+    {
+      error (eva, N_("Unexpected token"));
+    }
+}
+
+static quant expression (struct Eevl *eva);
+
+static quant
+factor (struct Eevl *eva)
+{
+  quant v = {0, 0};
+  struct Symbol tok;
+
+  if (shift (eva, tok_num, &tok))
+    {
+      v.val = tok.value.fl;
+      switch (lookahead (eva))
+        {
+        case tok_px:
+          v.space = 1;
+          v.val /= eva->pxpu;
+          shift (eva, 0, 0);
+          break;
+#ifdef USE_EEVL_UNITS
+        case tok_m:
+          v.val *= 10; /* fallthrough */
+        case tok_cm:
+          v.val *= 10;
+        case tok_mm:
+          v.val /= 25.4; // 25.4 mm/inch
+          v.space = 1;
+          shift (eva, 0, 0);
+          break;
+        case tok_ft:
+        case '\'':
+          v.val *= 12;
+        case '"' :
+        case tok_in:
+          v.space = 1;
+          shift (eva, 0, 0);
+          break;
+          /* if these are not handled internally, pass them on
+          * to the external unit system */
+#endif /* USE_EEVL_UNITS  */
+        case tok_ident:
+        {
+          quant d = gimp_eevl_quantity_invalid ();
+          shift (eva, 0, &tok);
+          if (eva->units)
+            {
+              d = eva->units (tok.value.c, tok.value.len, eva->proc_data);
+            }
+          if (d.val != d.val)
+            {
+              error (eva, N_("Unsupported unit specifier"));
+            }
+          v.val /= d.val;
+          v.space = d.space;
+
+          break;
+        }
+        case '(': /* these are no unit symbols */
+        case ')':
+        case '+':
+        case '-':
+        case '*':
+        case '/':
+          break;
+	default:
+          if (lookahead (eva) < 256)
+            {
+              quant d = gimp_eevl_quantity_invalid ();
+              gchar char_string[2] = { 0, 0 };
+              shift (eva, 0, &tok);
+              char_string[0] = (gchar) tok.token;
+              if (eva->units)
+                {
+                  d = eva->units (char_string, 1, eva->proc_data);
+                }
+              if (d.val != d.val)
+                {
+                  error (eva, N_("Unsupported unit symbol"));
+                }
+              v.val /= d.val;
+              v.space = d.space;
+            }
+        }
+    }
+  else if (shift (eva, tok_ident, &tok))
+    {
+      if (eva->vars)
+        {
+          v = eva->vars (tok.value.c, tok.value.len, eva->proc_data);
+          if (v.val != v.val) error (eva, N_("Unknown variable"));
+        }
+      else
+        {
+          error (eva, N_("There are no variables available"));
+        }
+    }
+  else if (shift (eva, '(', 0))
+    {
+      v = expression (eva);
+      expect (eva, ')', 0);
+    }
+  else
+    {
+      error (eva, N_("Expected identifier, numeric or '('"));
+    }
+
+  return v;
+}
+
+static quant
+sfactor (struct Eevl *eva)
+{
+  quant v;
+  gboolean neg = FALSE;
+
+  if (!shift (eva, '+', 0))
+    neg = shift (eva, '-', 0);
+
+  v = factor (eva);
+
+  if (neg) v.val = -v.val;
+  return v;
+}
+
+static quant
+term (struct Eevl *eva)
+{
+  gboolean div;
+  quant v;
+
+  v = sfactor (eva);
+  for (div = FALSE; shift (eva, '*', 0) || ((div = shift (eva, '/', 0))); div = FALSE)
+    {
+      quant f = sfactor (eva);
+      if (div)
+        {
+          v.val /= f.val;
+          v.space -= f.space;
+
+        }
+      else
+        {
+          v.val *= f.val;
+          v.space += f.space;
+        }
+    }
+
+  return v;
+}
+
+static quant
+expression (struct Eevl *eva)
+{
+  gboolean neg;
+  quant v;
+
+  v = term (eva);
+  for (neg = FALSE; shift (eva, '+', 0) || ((neg = shift (eva, '-', 0))); neg = FALSE)
+    {
+      quant t = term (eva);
+      /* Note:
+       * if t.space != v.space, and either is 0, the other will never
+       * match the default unit if the default unit is dimensionless,
+       * hence, to disable this behavior, just pass a dimensionless
+       * default unit.
+       */
+      if (t.space != v.space)
+        {
+          /* dimension missmatch, attempt default unit assignent*/
+          if (t.space == 0 && v.space == eva->defunit.space)
+            {
+              t.val /= eva->defunit.val;
+              t.space = eva->defunit.space;
+            }
+          else if (v.space == 0 && t.space == eva->defunit.space)
+            {
+              v.val /= eva->defunit.val;
+              v.space = eva->defunit.space;
+            }
+          else
+            {
+              error (eva, N_("Dimension missmatch"));
+            }
+        }
+      if (neg)
+        {
+          v.val -= t.val;
+        }
+      else
+        {
+          v.val += t.val;
+        }
+    }
+  return v;
+}
+
+static quant
+complete (struct Eevl *eva)
+{
+  quant v = {0,0};
+  if (shift (eva, tok_end, 0))
+    {
+      return v; // empty expression is 0.
+    }
+  v = expression (eva);
+  expect (eva, tok_end, 0);
+
+  /* entire expression is dimensionless, apply default unit if applicable. */
+  if (v.space == 0 && eva->defunit.space != 0)
+    {
+      v.val /= eva->defunit.val;
+      v.space = eva->defunit.space;
+    }
+  return v;
+}
+
+/**
+ * gimp_eevl_quantity_invalid:
+ *
+ * Returns: A #GimpEevlQuantity denoting an invalid value.
+ **/
+GimpEevlQuantity
+gimp_eevl_quantity_invalid (void)
+{
+  return invalid_quantity;
+}
+
+/**
+ * gimp_eevl_evaluate:
+ * @string:         The zero-terminated string to be evaluated.
+ * @default_unit:   The default unit conversion factor to be applied to
+ *                  dimensionless terms. A dimensionless default unit will
+ *                  not be applied. Note that this does not affect the unit
+ *                  of the returned value, which will always be the implicit
+ *                  base unit.
+ * @pxpu:           Pixels-per-unit, if the unit is inches, this is the dpi.
+ * @variable_proc:  Callback to resolve variables in the expression.
+ * @unit_proc:      Callback to resolve units in the expression.
+ * @data:           The data pointer used in the callbacks.
+ * @error_ptr:      Will point to the poisiton within the string, before
+ *                  which the parse / evaluation error occured. Will be set
+ *                  to null of no error occured.
+ * @error_message:  Will point to a static string with a semi-descriptive
+ *                  error message if parsing / evaluation failed.
+ *
+ * Evaluates the given arithmetic expression, along with an optional dimension
+ * analysis, and basic unit conversions.
+ *
+ * All units conversions factors (be it the pxpu parameter, the default_unit
+ * parameter, or a return value from the unit resolver) are relative to some
+ * implicit base-unit. This will also be the unit of the returned value.
+ *
+ * All callbacks should return the invalid quantity from
+ * gimp_eevl_quantity_invalid if the corresponding identifier could not be
+ * resolved. If no unit should be applied to dimensionless terms (causing
+ * dimension error if they are mixed with terms of other dimensions), this
+ * should also be specified as the invalid quantity.
+ * 
+ * Returns: A #GimpEevlQuantity with a value given in the base unit along with
+ * the order of the dimension (i.e. if the base unit is inches, a dimension
+ * order of two menas in^2).
+ **/
+GimpEevlQuantity gimp_eevl_evaluate (const gchar           *string,
+                                     GimpEevlQuantity       default_unit,
+                                     gdouble                pxpu,
+                                     GimpEevlVariableProc   variable_proc,
+                                     GimpEevlUnitProc       unit_proc,
+                                     gpointer               data,
+                                     const gchar          **error_ptr,
+                                     const gchar          **error_message)
+{
+  struct Eevl eva = create (string,
+                            default_unit, pxpu,
+                            variable_proc, unit_proc,
+                            data);
+
+  *error_ptr = 0;
+  *error_message = 0;
+
+  if (!setjmp (eva.catcher))  // try...
+    {
+
+      return complete (&eva);
+
+    }
+  else   // catch..
+    {
+      *error_ptr = eva.data;
+      *error_message = eva.error;
+      return invalid_quantity;
+    }
+}
Index: libgimpmath/test-eevl.c
===================================================================
--- libgimpmath/test-eevl.c	(revision 0)
+++ libgimpmath/test-eevl.c	(revision 0)
@@ -0,0 +1,148 @@
+/* A small regression test case for the evaluator */
+#include "config.h"
+
+#include <string.h>
+
+#include <glib-object.h>
+
+#include "gimpmath.h"
+
+static const GimpEevlQuantity  width = {210./25.4, 1};
+
+typedef struct
+{
+  const gchar      *string;
+  GimpEevlQuantity  result;
+  gboolean          should_fail;
+} TestCase;
+
+static TestCase cases[] =
+{
+  { "1 +2in",          { 1/25.4+2, 1 },               FALSE },
+  { "(1+2in)*width+3", { 0,-1 },                      TRUE  },
+  { "1+2/3",           { (1+2./3.)/25.4, 1 },         FALSE },
+  { "(1+2in)*width",   { (1/25.4+2)*(210./25.4), 2 }, FALSE },
+  { 0, {0, 0}, FALSE }
+};
+
+static GimpEevlQuantity
+test_vars(const gchar *ident,
+          gint         len,
+          gpointer     data)
+{
+  GimpEevlQuantity x = {0./0., 0};
+  if (len == 5 && 0 == strncmp (ident, "width", len))
+    {
+      x = width;
+    }
+  return x;
+}
+static GimpEevlQuantity
+test_units(const gchar *ident,
+           int          len,
+           gpointer     data)
+{
+  GimpEevlQuantity q = {0./0., 0};
+#ifndef USE_EEVL_UNITS
+  if (len == 2)
+    {
+      if (0 == strncmp ("in", ident, 2))
+        {
+          q.space = 1;
+          q.val = 1.;
+        }
+      else if (0 == strncmp ("mm", ident, 2))
+        {
+          q.space = 1;
+          q.val = 25.4;
+        }
+    }
+#endif /* not USE_EEVL_UNITS */
+  return q;
+}
+
+
+int
+main(void)
+{
+  gint             i;
+  gint             failed = 0;
+  gint             succeeded = 0;
+  GimpEevlQuantity defunit = test_units ("mm", 2, NULL);
+
+  g_print ("Testing Eevl Eva, the Evaluator\n\n");
+
+  for (i = 0; cases[i].string; i++)
+    {
+      const gchar      *test        = cases[i].string;
+      GimpEevlQuantity  should      = cases[i].result;
+      GimpEevlQuantity  result      = { 0, -1 };
+      gboolean          should_fail = cases[i].should_fail;
+      const gchar      *errstr      = 0;
+      const gchar      *errptr      = 0;
+
+      result = gimp_eevl_evaluate (test, defunit, 72,
+                                  test_vars, test_units, NULL,
+                                  &errptr ,&errstr);
+
+      g_print ("%s = %lg (%d): ", test, result.val, result.space);
+      if (errstr || errptr)
+        {
+          if (!should_fail)
+            {
+              failed++;
+              g_print ("evaluation failed ");
+              if (errstr)
+                {
+                  g_print ("with: %s, ", errstr);
+                }
+              else
+                {
+                  g_print ("without reason, ");
+                }
+              if (errptr)
+                {
+                  if (*errptr) g_print ("before '%s'.", errptr);
+                  else g_print ("at end of input.");
+                }
+              else
+                {
+                  g_print ("but didn't say where.");
+                }
+              g_print ("\n");
+            }
+          else
+            {
+              g_print ("OK (failure test case)\n");
+              succeeded++;
+            }
+        }
+      else if (should_fail)
+        {
+          g_print ("evaluation should've failed, but didn't.\n");
+          failed++;
+        }
+      else if (should.val != result.val || should.space != result.space)
+        {
+          g_print ("results don't match, should be: %lg (%d)\n",
+                    should.val, should.space);
+          failed++;
+        }
+      else
+        {
+          g_print ("OK\n");
+          succeeded++;
+        }
+    }
+
+  g_print ("\n");
+  if (!failed)
+    g_print ("All OK. ");
+  else
+    g_print ("Test failed! ");
+
+  g_print ("(%d/%d) %lg%%\n\n", succeeded, succeeded+failed,
+            100*succeeded/(gdouble)(succeeded+failed));
+
+  return failed;
+}
Index: libgimpmath/gimpmath.h
===================================================================
--- libgimpmath/gimpmath.h	(revision 25470)
+++ libgimpmath/gimpmath.h	(working copy)
@@ -37,6 +37,7 @@
 #include <libgimpmath/gimpmatrix.h>
 #include <libgimpmath/gimpmd5.h>
 #include <libgimpmath/gimpvector.h>
+#include <libgimpmath/gimpeevl.h>
 
 G_BEGIN_DECLS
 
Index: libgimpmath/gimpeevl.h
===================================================================
--- libgimpmath/gimpeevl.h	(revision 0)
+++ libgimpmath/gimpeevl.h	(revision 0)
@@ -0,0 +1,54 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpeevl.h
+ * Copyright (C) 2008 Fredrik Alstromer <roe@excu.se>
+ *
+ * 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 2 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
+ * Library 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GIMP_EEVL_H__
+#define __GIMP_EEVL_H__
+
+G_BEGIN_DECLS
+
+struct _GimpEevlQuantity
+{
+  gdouble val;
+  gint space;
+};
+
+typedef GimpEevlQuantity (* GimpEevlVariableProc) (const gchar *ident,
+                                                   gint         len_ident,
+                                                   gpointer     data);
+typedef GimpEevlQuantity (* GimpEevlUnitProc)     (const gchar *ident,
+                                                   gint         len_ident,
+                                                   gpointer     data);
+
+GimpEevlQuantity gimp_eevl_quantity_invalid (void);
+
+GimpEevlQuantity gimp_eevl_evaluate (const gchar          *string,
+                                     GimpEevlQuantity      default_unit,
+                                     gdouble               pxpu,
+                                     GimpEevlVariableProc  variable_proc,
+                                     GimpEevlUnitProc      unit_proc,
+                                     gpointer              data,
+                                     const gchar         **error_ptr,
+                                     const gchar         **error_message);
+
+G_END_DECLS
+
+#endif /* __GIMP_EEVL_H__ */
Index: po/POTFILES.skip
===================================================================
--- po/POTFILES.skip	(revision 25470)
+++ po/POTFILES.skip	(working copy)
@@ -41,6 +41,8 @@ libgimpconfig/gimpconfigwriter.c
 libgimpconfig/gimpconfigwriter.c
 libgimpconfig/gimpscanner.c
 libgimpconfig/gimpscanner.c
+libgimpmath/gimpeevl.c
+libgimpmath/gimpeevl.c
 libgimpmodule/gimpmodule.c
 libgimpmodule/gimpmodule.c
 libgimpthumb/gimpthumb-utils.c
Index: po-libgimp/POTFILES.in
===================================================================
--- po-libgimp/POTFILES.in	(revision 25470)
+++ po-libgimp/POTFILES.in	(working copy)
@@ -30,6 +30,8 @@ libgimpconfig/gimpconfig-path.c
 libgimpconfig/gimpconfigwriter.c
 libgimpconfig/gimpscanner.c
 
+libgimpmath/gimpeevl.c
+
 libgimpmodule/gimpmodule.c
 
 libgimpthumb/gimpthumb-utils.c
Index: libgimpwidgets/gimpsizeentry.c
===================================================================
--- libgimpwidgets/gimpsizeentry.c	(revision 25470)
+++ libgimpwidgets/gimpsizeentry.c	(working copy)
@@ -25,7 +25,10 @@
 
 #include <gtk/gtk.h>
 
+#include <string.h>
+
 #include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
 
 #include "gimpwidgets.h"
 
@@ -86,6 +89,18 @@ static void   gimp_size_entry_update_uni
 static void   gimp_size_entry_unit_callback   (GtkWidget          *widget,
                                                GimpSizeEntry      *sizeentry);
 
+/* input validation / evaluation */
+static gint   gimp_size_entry_input_callback  (GtkSpinButton      *spinner,
+                                               gdouble            *return_val,
+                                               GimpSizeEntryField *gsef);
+static GimpEevlQuantity
+              gimp_size_entry_unit_resolver   (const gchar        *ident,
+                                               gint                len_ident,
+                                               gpointer            data);
+static GimpEevlQuantity
+              gimp_size_entry_var_resolver    (const gchar        *ident,
+                                               gint                len_ident,
+                                               gpointer            data);
 
 G_DEFINE_TYPE (GimpSizeEntry, gimp_size_entry, GTK_TYPE_TABLE)
 
@@ -288,6 +303,13 @@ gimp_size_entry_new (gint               
                                                      1.0, 10.0, 0.0,
                                                      1.0, digits);
 
+      /* non-numeric input allows expression evaluation */
+      gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (gsef->value_spinbutton),
+                                   FALSE);
+      g_signal_connect (gsef->value_spinbutton, "input",
+                        G_CALLBACK (gimp_size_entry_input_callback),
+                        gsef);
+
       if (spinbutton_width > 0)
         {
           if (spinbutton_width < 17)
@@ -404,6 +426,15 @@ gimp_size_entry_add_field  (GimpSizeEntr
                     G_CALLBACK (gimp_size_entry_value_callback),
                     gsef);
 
+  /* non-numeric input allows expression evaluation */
+  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (gsef->value_spinbutton),
+                               FALSE);
+  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (gsef->value_spinbutton),
+                                     GTK_UPDATE_IF_VALID);
+  g_signal_connect (gsef->value_spinbutton, "input",
+                    G_CALLBACK (gimp_size_entry_input_callback),
+                    gsef);
+
   if (gse->show_refval)
     {
       gsef->refval_adjustment =
@@ -1269,3 +1300,134 @@ gimp_size_entry_get_help_widget (GimpSiz
   return (gsef->refval_spinbutton ?
           gsef->refval_spinbutton : gsef->value_spinbutton);
 }
+
+static GimpEevlQuantity
+gimp_size_entry_var_resolver (const gchar *ident,
+                               gint         len_ident,
+                               gpointer     data)
+{
+  return gimp_eevl_quantity_invalid ();
+}
+
+static GimpEevlQuantity
+gimp_size_entry_unit_resolver (const gchar *ident,
+                               gint         len_ident,
+                               gpointer     data)
+{
+  GimpEevlQuantity    invalid_unit;
+  GimpSizeEntryField *gsef;
+  GimpSizeEntry      *gse;
+  GimpUnit            unit;
+  gint                units;
+  
+  gsef = (GimpSizeEntryField*) data;
+
+  invalid_unit = gimp_eevl_quantity_invalid ();
+
+  g_return_val_if_fail (gsef, invalid_unit);
+  
+  gse = gsef->gse;
+
+  g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gse), invalid_unit);
+
+  units = gimp_unit_get_number_of_units ();
+
+  for (unit = 0; unit <= units; unit++)
+    {
+      const gchar *symbol;
+      const gchar *abbrev;
+      gint         len_symbol;
+      gint         len_abbrev;
+      
+      /* a shortcut to handling percent within the loop */
+      if (unit == units)
+        unit = GIMP_UNIT_PERCENT;
+
+      symbol = gimp_unit_get_symbol (unit);
+      abbrev = gimp_unit_get_abbreviation (unit);
+      len_symbol = strlen (symbol);
+      len_abbrev = strlen (abbrev);
+
+      if (   (len_symbol == len_ident
+              && 0  ==  strncmp (symbol, ident, len_ident))
+          || (len_abbrev == len_ident
+              && 0 == strncmp (abbrev, ident, len_ident)))
+        {
+          /* either the abbreviation or symbol matched. */
+          GimpEevlQuantity eevl_unit = {1, 1};
+          switch (unit)
+            {
+            case GIMP_UNIT_PIXEL:
+              eevl_unit.val = gsef->resolution;
+              break;
+            case GIMP_UNIT_PERCENT:
+              /* percent is a unitless factor of 1/100, i.e 2in*50 % = 1in */
+              eevl_unit.val = 100;
+              eevl_unit.space = 0;
+              break;
+            default:
+              eevl_unit.val = gimp_unit_get_factor (unit);
+            }
+            return eevl_unit;
+        }
+    }
+
+  return invalid_unit;
+}
+
+static gint
+gimp_size_entry_input_callback (GtkSpinButton      *spinner,
+                                gdouble            *return_val,
+                                GimpSizeEntryField *gsef)
+{
+  GimpSizeEntry    *gse;
+  GimpEevlQuantity  result;
+  GimpEevlQuantity  ui_unit = {1, 1};
+
+  const gchar      *errptr = 0;
+  const gchar      *errstr = 0;
+
+  g_return_val_if_fail (gsef, FALSE);
+
+  gse = gsef->gse;
+
+  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spinner), FALSE);
+  g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gse), FALSE);
+
+  switch (gse->unit)
+    {
+    case GIMP_UNIT_PIXEL:
+      ui_unit.val = gsef->resolution;
+      break;
+    case GIMP_UNIT_PERCENT:
+      ui_unit.space = 0;
+      break;
+    default:
+      ui_unit.val = gimp_unit_get_factor(gse->unit);
+    }
+
+  result = gimp_eevl_evaluate (gtk_entry_get_text (GTK_ENTRY (spinner)),
+                               ui_unit,
+                               gsef->resolution,
+                               gimp_size_entry_var_resolver,
+                               gimp_size_entry_unit_resolver,
+                               gsef,
+                               &errptr, &errstr);
+  if (errptr)
+    {
+      g_print ("ERROR: %s before '%s'\n", errstr,
+               *errptr ? errptr : "<End of input>");
+      return GTK_INPUT_ERROR;
+    }
+
+  if (result.space != 1)
+    {
+      g_print ("ERROR: result has wrong dimension (%d)\n", result.space);
+      return GTK_INPUT_ERROR;
+    }
+
+  /* transform back to UI-unit */
+  *return_val = result.val * ui_unit.val;
+  return TRUE;
+
+}
