[gimp/wip/gum-lang: 58/59] app: add parser/evaluator for the GUM language



commit e75e52140b3b5bd2400365dc27219cbccee79382
Author: Ell <ell_se yahoo com>
Date:   Sun Apr 30 15:49:29 2017 -0400

    app: add parser/evaluator for the GUM language
    
    This is a small DSL, used in some property keys of GEGL operations
    to dynamically control UI attributes.  This commit only adds a
    parser/evaluator for the language; see the next commit for the
    actual handling of the specific keys.
    
    See the comment at the top of gimppropgui-eval.c for a description
    of the language.

 app/widgets/Makefile.am        |    2 +
 app/widgets/gimppropgui-eval.c | 1022 ++++++++++++++++++++++++++++++++++++++++
 app/widgets/gimppropgui-eval.h |   36 ++
 3 files changed, 1060 insertions(+), 0 deletions(-)
---
diff --git a/app/widgets/Makefile.am b/app/widgets/Makefile.am
index f96da4f..a74dc94 100644
--- a/app/widgets/Makefile.am
+++ b/app/widgets/Makefile.am
@@ -303,6 +303,8 @@ libappwidgets_a_sources = \
        gimppropgui.h                   \
        gimppropgui-constructors.c      \
        gimppropgui-constructors.h      \
+       gimppropgui-eval.c              \
+       gimppropgui-eval.h              \
        gimppropwidgets.c               \
        gimppropwidgets.h               \
        gimpradioaction.c               \
diff --git a/app/widgets/gimppropgui-eval.c b/app/widgets/gimppropgui-eval.c
new file mode 100644
index 0000000..d8050f3
--- /dev/null
+++ b/app/widgets/gimppropgui-eval.c
@@ -0,0 +1,1022 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimppropgui-eval.c
+ * Copyright (C) 2017 Ell
+ *A qute
+ * This program 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 program 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 Less General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This is a simple parser/evaluator for the GUM language (the GEGL UI Meta
+ * language), used in ceratin property keys of GEGL operations.  What follows
+ * is a hand-wavy summary of the syntax and semantics (no BNF for you!)
+ *
+ * There are currently two types of expressions:
+ *
+ * Boolean expressions
+ * -------------------
+ *
+ * There are three types of simple boolean expressions:
+ *
+ *   - Literal:  Either `0` or `1`, evaluating to FALSE and TRUE, respectively.
+ *
+ *   - Reference:  Has the form `$key` or `$property.key`.  Evaluates to the
+ *     value of key `key`, which should itself be a boolean expression.  In
+ *     the first form, `key` refers to a key of the same property to which the
+ *     currently-evaluated key belongs.  In the second form, `key` refers to a
+ *     key of `property`.
+ *
+ *   - Dependency:  Dependencies begin with the name of a property, on which
+ *     the result depends.  Currently supported property types are:
+ *
+ *       - Boolean:  The expression consists of the property name alone, and
+ *         its value is the value of the property.
+ *
+ *       - Enum:  The property name shall be followed by a brace-enclosed,
+ *         comma-separated list of enum values, given as nicks.  The expression
+ *         evaluates to TRUE iff the property matches any of the values.
+ *
+ * Complex boolean expressions can be formed using `!` (negation), `&`
+ * (conjunction), `|` (disjunction), and parentheses (grouping), following the
+ * usual precedence rules.
+ *
+ * String expressions
+ * ------------------
+ *
+ * There are three types of simple string expressions:
+ *
+ *   - Literal:  A string literal, surrounded by single quotes (`'`).  Special
+ *     characters (in particular, single quotes) can be escaped using a
+ *     backslash (`\`).
+ *
+ *   - Reference:  Same as a boolean reference, but should refer to a key
+ *     containing a string expression.
+ *
+ *   - Deferred literal:  Names a key, in the same fashion as a reference, but
+ *     without the leading `$`.  The value of this key is taken literally as
+ *     the value of the expression.  Deferred literals should usually be
+ *     favored over in-place string literals, because they can be translated
+ *     independently of the expression.
+ *
+ * Currently, the only complex string expression is string selection:  It has
+ * the form of a bracket-enclosed, comma-separated list of expressions of the
+ * form `<condition> : <value>`, where `<condition>` is a boolean expression,
+ * and `<value>` is a string expression.  The result of the expression is the
+ * associated value of the first condition that evaluates to TRUE.  If no
+ * condition is met, the result is NULL.
+ *
+ *
+ * Whitespace between subexpressions is insignificant in both types of
+ * expression.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gegl-paramspecs.h>
+#include <gtk/gtk.h>
+
+#include "widgets-types.h"
+
+#include "gimppropgui-eval.h"
+
+
+typedef enum
+{
+  GIMP_PROP_EVAL_FAILED  /* generic error condition */
+} GimpPropEvalErrorCode;
+
+
+static gboolean   gimp_prop_eval_boolean_impl     (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar    *key,
+                                                   gint            default_value,
+                                                   GError        **error,
+                                                   gint            depth);
+static gboolean   gimp_prop_eval_boolean_or       (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error,
+                                                   gint            depth);
+static gboolean   gimp_prop_eval_boolean_and      (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error,
+                                                   gint            depth);
+static gboolean   gimp_prop_eval_boolean_not      (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error,
+                                                   gint            depth);
+static gboolean   gimp_prop_eval_boolean_group    (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error,
+                                                   gint            depth);
+static gboolean   gimp_prop_eval_boolean_simple   (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error,
+                                                   gint            depth);
+
+static gchar    * gimp_prop_eval_string_impl      (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar    *key,
+                                                   const gchar    *default_value,
+                                                   GError        **error,
+                                                   gint            depth);
+static gchar    * gimp_prop_eval_string_selection (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error,
+                                                   gint            depth);
+static gchar    * gimp_prop_eval_string_simple    (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error,
+                                                   gint            depth);
+
+static gboolean   gimp_prop_eval_parse_reference  (GObject        *config,
+                                                   GParamSpec     *pspec,
+                                                   const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error,
+                                                   GParamSpec    **ref_pspec,
+                                                   gchar         **ref_key);
+
+static gboolean   gimp_prop_eval_depth_test       (gint            depth,
+                                                   GError        **error);
+
+static gchar    * gimp_prop_eval_read_token       (const gchar   **expr,
+                                                   gchar         **t,
+                                                   GError        **error);
+static gboolean   gimp_prop_eval_is_name          (const gchar    *token);
+
+#define GIMP_PROP_EVAL_ERROR (gimp_prop_eval_error_quark ())
+
+static GQuark     gimp_prop_eval_error_quark      (void);
+
+
+/*  public functions  */
+
+gboolean
+gimp_prop_eval_boolean (GObject     *config,
+                        GParamSpec  *pspec,
+                        const gchar *key,
+                        gboolean     default_value)
+{
+  GError   *error = NULL;
+  gboolean  result;
+
+  result = gimp_prop_eval_boolean_impl (config, pspec,
+                                        key, default_value, &error, 0);
+
+  if (error)
+    {
+      g_warning ("in object of type '%s': %s",
+                 G_OBJECT_TYPE_NAME (config),
+                 error->message);
+
+      g_error_free (error);
+
+      return default_value;
+    }
+
+  return result;
+}
+
+gchar *
+gimp_prop_eval_string (GObject     *config,
+                       GParamSpec  *pspec,
+                       const gchar *key,
+                       const gchar *default_value)
+{
+  GError *error = NULL;
+  gchar  *result;
+
+  result = gimp_prop_eval_string_impl (config, pspec,
+                                       key, default_value, &error, 0);
+
+  if (error)
+    {
+      g_warning ("in object of type '%s': %s",
+                 G_OBJECT_TYPE_NAME (config),
+                 error->message);
+
+      g_error_free (error);
+
+      return g_strdup (default_value);
+    }
+
+  return result;
+}
+
+
+/*  private functions  */
+
+static gboolean
+gimp_prop_eval_boolean_impl (GObject      *config,
+                             GParamSpec   *pspec,
+                             const gchar  *key,
+                             gint          default_value,
+                             GError      **error,
+                             gint          depth)
+{
+  const gchar *expr;
+  gchar       *t      = NULL;
+  gboolean     result = FALSE;
+
+  if (! gimp_prop_eval_depth_test (depth, error))
+    return FALSE;
+
+  expr = gegl_param_spec_get_property_key (pspec, key);
+
+  if (! expr)
+    {
+      /* we use `default_value < 0` to specify that the key must exist */
+      if (default_value < 0)
+        {
+          g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                       "key '%s' of property '%s' not found",
+                       key,
+                       g_param_spec_get_name (pspec));
+
+          return FALSE;
+        }
+
+      return default_value;
+    }
+
+  gimp_prop_eval_read_token (&expr, &t, error);
+
+  if (! *error)
+    {
+      result = gimp_prop_eval_boolean_or (config, pspec,
+                                          &expr, &t, error, depth);
+    }
+
+  /* check for trailing tokens at the end of the expression */
+  if (! *error && t)
+    {
+      g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                   "invalid expression");
+    }
+
+  g_free (t);
+
+  if (*error)
+    {
+      g_prefix_error (error,
+                      "in key '%s' of property '%s': ",
+                      key,
+                      g_param_spec_get_name (pspec));
+
+      return FALSE;
+    }
+
+  return result;
+}
+
+static gboolean
+gimp_prop_eval_boolean_or (GObject      *config,
+                           GParamSpec   *pspec,
+                           const gchar **expr,
+                           gchar       **t,
+                           GError      **error,
+                           gint          depth)
+{
+  gboolean result;
+
+  if (! gimp_prop_eval_depth_test (depth, error))
+    return FALSE;
+
+  result = gimp_prop_eval_boolean_and (config, pspec,
+                                       expr, t, error, depth);
+
+  while (! *error && ! g_strcmp0 (*t, "|"))
+    {
+      gimp_prop_eval_read_token (expr, t, error);
+
+      if (*error)
+        return FALSE;
+
+      /* keep evaluating even if `result` is TRUE, because we still need to
+       * parse the rest of the subexpression.
+       */
+      result |= gimp_prop_eval_boolean_and (config, pspec,
+                                            expr, t, error, depth);
+    }
+
+  return result;
+}
+
+static gboolean
+gimp_prop_eval_boolean_and (GObject      *config,
+                            GParamSpec   *pspec,
+                            const gchar **expr,
+                            gchar       **t,
+                            GError      **error,
+                            gint          depth)
+{
+  gboolean result;
+
+  if (! gimp_prop_eval_depth_test (depth, error))
+    return FALSE;
+
+  result = gimp_prop_eval_boolean_not (config, pspec,
+                                       expr, t, error, depth);
+
+  while (! *error && ! g_strcmp0 (*t, "&"))
+    {
+      gimp_prop_eval_read_token (expr, t, error);
+
+      if (*error)
+        return FALSE;
+
+      /* keep evaluating even if `result` is FALSE, because we still need to
+       * parse the rest of the subexpression.
+       */
+      result &= gimp_prop_eval_boolean_not (config, pspec,
+                                            expr, t, error, depth);
+    }
+
+  return result;
+}
+
+static gboolean
+gimp_prop_eval_boolean_not (GObject      *config,
+                            GParamSpec   *pspec,
+                            const gchar **expr,
+                            gchar       **t,
+                            GError      **error,
+                            gint          depth)
+{
+  if (! gimp_prop_eval_depth_test (depth, error))
+    return FALSE;
+
+  if (! g_strcmp0 (*t, "!"))
+    {
+      gimp_prop_eval_read_token (expr, t, error);
+
+      if (*error)
+        return FALSE;
+
+      return ! gimp_prop_eval_boolean_not (config, pspec,
+                                           expr, t, error, depth + 1);
+    }
+
+  return gimp_prop_eval_boolean_group (config, pspec,
+                                       expr, t, error, depth);
+}
+
+static gboolean
+gimp_prop_eval_boolean_group (GObject      *config,
+                              GParamSpec   *pspec,
+                              const gchar **expr,
+                              gchar       **t,
+                              GError      **error,
+                              gint          depth)
+{
+  if (! gimp_prop_eval_depth_test (depth, error))
+    return FALSE;
+
+  if (! g_strcmp0 (*t, "("))
+    {
+      gboolean result;
+
+      gimp_prop_eval_read_token (expr, t, error);
+
+      if (*error)
+        return FALSE;
+
+      result = gimp_prop_eval_boolean_or (config, pspec,
+                                          expr, t, error, depth + 1);
+
+      if (! *error && g_strcmp0 (*t, ")"))
+        {
+          g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                       "unterminated group");
+
+          return FALSE;
+        }
+
+      gimp_prop_eval_read_token (expr, t, error);
+
+      return result;
+    }
+
+  return gimp_prop_eval_boolean_simple (config, pspec,
+                                        expr, t, error, depth);
+}
+
+static gboolean
+gimp_prop_eval_boolean_simple (GObject      *config,
+                               GParamSpec   *pspec,
+                               const gchar **expr,
+                               gchar       **t,
+                               GError      **error,
+                               gint          depth)
+{
+  gboolean result;
+
+  if (! gimp_prop_eval_depth_test (depth, error))
+    return FALSE;
+
+  if (! *t)
+    {
+      g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                   "invalid expression");
+
+      return FALSE;
+    }
+
+  /* literal */
+  if (! strcmp (*t, "0"))
+    {
+      result = FALSE;
+    }
+  else if (! strcmp (*t, "1"))
+    {
+      result = TRUE;
+    }
+  /* reference */
+  else if (! strcmp (*t, "$"))
+    {
+      gchar *key;
+
+      gimp_prop_eval_read_token (expr, t, error);
+
+      if (*error)
+        return FALSE;
+
+      if (! gimp_prop_eval_parse_reference (config, pspec,
+                                            expr, t, error, &pspec, &key))
+        return FALSE;
+
+      result = gimp_prop_eval_boolean_impl (config, pspec,
+                                            key, -1, error, depth + 1);
+
+      g_free (key);
+    }
+  /* dependency */
+  else if (gimp_prop_eval_is_name (*t))
+    {
+      const gchar *property_name;
+      GParamSpec  *pspec;
+      GType        type;
+
+      property_name = *t;
+
+      pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
+                                            property_name);
+
+      if (! pspec)
+        {
+          g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                       "property '%s' not found",
+                       property_name);
+
+          return TRUE;
+        }
+
+      property_name = g_param_spec_get_name (pspec);
+      type          = G_PARAM_SPEC_VALUE_TYPE (pspec);
+
+      if (g_type_is_a (type, G_TYPE_BOOLEAN))
+        {
+          g_object_get (config, property_name, &result, NULL);
+        }
+      else if (g_type_is_a (type, G_TYPE_ENUM))
+        {
+          GEnumClass *enum_class;
+          gint        value;
+
+          gimp_prop_eval_read_token (expr, t, error);
+
+          if (*error)
+            return FALSE;
+
+          if (g_strcmp0 (*t , "{"))
+            {
+              g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                           "missing enum value set "
+                           "for property '%s'",
+                           property_name);
+
+              return FALSE;
+            }
+
+          enum_class = g_type_class_peek (type);
+
+          g_object_get (config, property_name, &value, NULL);
+
+          result = FALSE;
+
+          while (gimp_prop_eval_read_token (expr, t, error) &&
+                 gimp_prop_eval_is_name (*t))
+            {
+              const gchar *nick;
+              GEnumValue  *enum_value;
+
+              nick       = *t;
+              enum_value = g_enum_get_value_by_nick (enum_class, nick);
+
+              if (! enum_value)
+                {
+                  g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                               "invalid enum value '%s' "
+                               "for property '%s'",
+                               nick,
+                               property_name);
+
+                  return FALSE;
+                }
+
+              if (value == enum_value->value)
+                result = TRUE;
+
+              gimp_prop_eval_read_token (expr, t, error);
+
+              if (*error)
+                return FALSE;
+
+              if (! g_strcmp0 (*t, ","))
+                {
+                  continue;
+                }
+              else if (! g_strcmp0 (*t, "}"))
+                {
+                  break;
+                }
+              else
+                {
+                  g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                               "invalid enum value set "
+                               "for property '%s'",
+                               property_name);
+
+                  return FALSE;
+                }
+            }
+
+          if (*error)
+            return FALSE;
+
+          if (g_strcmp0 (*t, "}"))
+            {
+              g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                           "unterminated enum value set "
+                           "for property '%s'",
+                           property_name);
+
+              return FALSE;
+            }
+        }
+      else
+        {
+          g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                       "invalid type "
+                       "for property '%s'",
+                       property_name);
+
+          return FALSE;
+        }
+    }
+  else
+    {
+      g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                   "invalid expression");
+
+      return FALSE;
+    }
+
+  gimp_prop_eval_read_token (expr, t, error);
+
+  return result;
+}
+
+static gchar *
+gimp_prop_eval_string_impl (GObject      *config,
+                            GParamSpec   *pspec,
+                            const gchar  *key,
+                            const gchar  *default_value,
+                            GError      **error,
+                            gint          depth)
+{
+  const gchar *expr;
+  gchar       *t      = NULL;
+  gchar       *result = NULL;
+
+  if (! gimp_prop_eval_depth_test (depth, error))
+    return NULL;
+
+  expr = gegl_param_spec_get_property_key (pspec, key);
+
+  if (! expr)
+    return g_strdup (default_value);
+
+  gimp_prop_eval_read_token (&expr, &t, error);
+
+  if (! *error)
+    {
+      result = gimp_prop_eval_string_selection (config, pspec,
+                                                &expr, &t, error, depth);
+    }
+
+  /* check for trailing tokens at the end of the expression */
+  if (! *error && t)
+    {
+      g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                   "invalid expression");
+    }
+
+  g_free (t);
+
+  if (*error)
+    {
+      g_prefix_error (error,
+                      "in key '%s' of property '%s': ",
+                      key,
+                      g_param_spec_get_name (pspec));
+
+      return NULL;
+    }
+
+  if (result)
+    return result;
+  else
+    return g_strdup (default_value);
+}
+
+static gchar *
+gimp_prop_eval_string_selection (GObject      *config,
+                                 GParamSpec   *pspec,
+                                 const gchar **expr,
+                                 gchar       **t,
+                                 GError      **error,
+                                 gint          depth)
+{
+  if (! gimp_prop_eval_depth_test (depth, error) || ! t)
+    return NULL;
+
+  if (! g_strcmp0 (*t, "["))
+    {
+      gboolean  match  = FALSE;
+      gchar    *result = NULL;
+
+      if (! g_strcmp0 (gimp_prop_eval_read_token (expr, t, error), "]"))
+        return NULL;
+
+      while (! *error)
+        {
+          gboolean  cond;
+          gchar    *value;
+
+          cond = gimp_prop_eval_boolean_or (config, pspec,
+                                            expr, t, error, depth + 1);
+
+          if (*error)
+            break;
+
+          if (g_strcmp0 (*t, ":"))
+            {
+              g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                           "missing string selection value");
+
+              break;
+            }
+
+          gimp_prop_eval_read_token (expr, t, error);
+
+          if (*error)
+            break;
+
+          value = gimp_prop_eval_string_selection (config, pspec,
+                                                   expr, t, error, depth + 1);
+
+          if (*error)
+            break;
+
+          if (! match && cond)
+            {
+              match  = TRUE;
+              result = value;
+            }
+
+          if (! g_strcmp0 (*t, ","))
+            {
+              gimp_prop_eval_read_token (expr, t, error);
+
+              continue;
+            }
+          else if (! g_strcmp0 (*t, "]"))
+            {
+              gimp_prop_eval_read_token (expr, t, error);
+
+              break;
+            }
+          else
+            {
+              if (*t)
+                {
+                  g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                               "invalid string selection");
+                }
+              else
+                {
+                  g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                               "unterminated string selection");
+                }
+
+              break;
+            }
+        }
+
+      if (*error)
+        {
+          g_free (result);
+
+          return NULL;
+        }
+
+      return result;
+    }
+
+  return gimp_prop_eval_string_simple (config,
+                                       pspec, expr, t, error, depth);
+}
+
+static gchar *
+gimp_prop_eval_string_simple (GObject      *config,
+                              GParamSpec   *pspec,
+                              const gchar **expr,
+                              gchar       **t,
+                              GError      **error,
+                              gint          depth)
+{
+  gchar *result = NULL;
+
+  if (! gimp_prop_eval_depth_test (depth, error))
+    return NULL;
+
+  /* literal */
+  if (*t && **t == '\'')
+    {
+      gchar *escaped;
+
+      escaped = g_strndup (*t + 1, strlen (*t + 1) - 1);
+
+      result = g_strcompress (escaped);
+
+      g_free (escaped);
+
+      gimp_prop_eval_read_token (expr, t, error);
+
+      if (*error)
+        {
+          g_free (result);
+
+          return NULL;
+        }
+    }
+  /* reference */
+  else if (! g_strcmp0 (*t, "$"))
+    {
+      gchar *key;
+
+      gimp_prop_eval_read_token (expr, t, error);
+
+      if (*error)
+        return NULL;
+
+      if (! gimp_prop_eval_parse_reference (config, pspec,
+                                            expr, t, error, &pspec, &key))
+        return NULL;
+
+      result = gimp_prop_eval_string_impl (config, pspec,
+                                           key, NULL, error, depth + 1);
+
+      g_free (key);
+    }
+  /* deferred literal */
+  else if (gimp_prop_eval_is_name (*t))
+    {
+      GParamSpec  *str_pspec;
+      gchar       *str_key;
+      const gchar *str;
+
+      if (! gimp_prop_eval_parse_reference (config, pspec,
+                                            expr, t, error,
+                                            &str_pspec, &str_key))
+        return NULL;
+
+      str = gegl_param_spec_get_property_key (str_pspec, str_key);
+
+      if (! str)
+        {
+          g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                       "key '%s' of property '%s' not found",
+                       str_key,
+                       g_param_spec_get_name (str_pspec));
+
+          g_free (str_key);
+
+          return NULL;
+        }
+
+      g_free (str_key);
+
+      result = g_strdup (str);
+    }
+  else
+    {
+      g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                   "invalid expression");
+
+      return NULL;
+    }
+
+  return result;
+}
+
+static gboolean
+gimp_prop_eval_parse_reference (GObject      *config,
+                                GParamSpec   *pspec,
+                                const gchar **expr,
+                                gchar       **t,
+                                GError      **error,
+                                GParamSpec  **ref_pspec,
+                                gchar       **ref_key)
+{
+  if (! gimp_prop_eval_is_name (*t))
+    {
+      g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                   "invalid reference");
+
+      return FALSE;
+    }
+
+  *ref_pspec = pspec;
+  *ref_key   = g_strdup (*t);
+
+  gimp_prop_eval_read_token (expr, t, error);
+
+  if (*error)
+    {
+      g_free (ref_key);
+
+      return FALSE;
+    }
+
+  if (! g_strcmp0 (*t, "."))
+    {
+      gchar *property_name;
+
+      property_name = *ref_key;
+
+      if (! gimp_prop_eval_read_token (expr, t, error) ||
+          ! gimp_prop_eval_is_name (*t))
+        {
+          if (! *error)
+            {
+              g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                           "invalid reference");
+            }
+
+          g_free (property_name);
+
+          return FALSE;
+        }
+
+      *ref_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
+                                                 property_name);
+
+      if (! *ref_pspec)
+        {
+          g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                       "property '%s' not found",
+                       property_name);
+
+          g_free (property_name);
+
+          return FALSE;
+        }
+
+      g_free (property_name);
+
+      *ref_key = g_strdup (*t);
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gimp_prop_eval_depth_test (gint     depth,
+                           GError **error)
+{
+  /* make sure we don't recurse too deep.  in particular, guard against
+   * circular references.
+   */
+  if (depth == 100)
+    {
+      g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                   "maximal nesting level exceeded");
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gchar *
+gimp_prop_eval_read_token (const gchar **expr,
+                           gchar       **t,
+                           GError      **error)
+{
+  const gchar *token;
+
+  g_free (*t);
+  *t = NULL;
+
+  /* skip whitespace */
+  while (g_ascii_isspace (**expr))
+    ++*expr;
+
+  token = *expr;
+
+  if (*token == '\0')
+    return NULL;
+
+  /* name */
+  if (gimp_prop_eval_is_name (token))
+    {
+      do { ++*expr; } while (g_ascii_isalnum (**expr) ||
+                             **expr == '_'            ||
+                             **expr == '-');
+    }
+  /* string literal */
+  else if (token[0] == '\'')
+    {
+      for (++*expr; **expr != '\0' && **expr != '\''; ++*expr)
+        {
+          if (**expr == '\\')
+            {
+              ++*expr;
+
+              if (**expr == '\0')
+                break;
+            }
+        }
+
+      if (**expr == '\0')
+        {
+          g_set_error (error, GIMP_PROP_EVAL_ERROR, GIMP_PROP_EVAL_FAILED,
+                       "unterminated string literal");
+
+          return NULL;
+        }
+
+      ++*expr;
+    }
+  /* punctuation */
+  else
+    {
+      ++*expr;
+    }
+
+  *t = g_strndup (token, *expr - token);
+
+  return *t;
+}
+
+static gboolean
+gimp_prop_eval_is_name (const gchar *token)
+{
+  return token && (g_ascii_isalpha (*token) || *token == '_');
+}
+
+static GQuark
+gimp_prop_eval_error_quark (void)
+{
+  return g_quark_from_static_string ("gimp-prop-eval-error-quark");
+}
diff --git a/app/widgets/gimppropgui-eval.h b/app/widgets/gimppropgui-eval.h
new file mode 100644
index 0000000..6462197
--- /dev/null
+++ b/app/widgets/gimppropgui-eval.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimppropgui.h
+ * Copyright (C) 2017 Ell
+ *
+ * This program 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 program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PROP_GUI_EVAL_H__
+#define __GIMP_PROP_GUI_EVAL_H__
+
+
+gboolean   gimp_prop_eval_boolean (GObject     *config,
+                                   GParamSpec  *pspec,
+                                   const gchar *key,
+                                   gboolean     default_value);
+
+gchar    * gimp_prop_eval_string  (GObject     *config,
+                                   GParamSpec  *pspec,
+                                   const gchar *key,
+                                   const gchar *default_value);
+
+
+#endif /* __GIMP_PROP_GUI_EVAL_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]