[gtk/wip/otte/listview: 1/10] Add GtkExpression



commit 56562ba2d489b27a95641fc3e7b49fc0aa7ef698
Author: Benjamin Otte <otte redhat com>
Date:   Thu Oct 17 05:21:31 2019 +0200

    Add GtkExpression
    
    This is the new replacement for bindings, though I'm not entirely sure
    what I'm doing.

 demos/gtk-demo/fishbowl.ui |   17 +-
 gtk/gtkbuilder.c           |   62 +-
 gtk/gtkbuilderparser.c     |  136 +++-
 gtk/gtkbuilderprivate.h    |    9 +-
 gtk/gtkexpression.c        | 1699 ++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkexpressionprivate.h |   54 ++
 gtk/meson.build            |    1 +
 7 files changed, 1936 insertions(+), 42 deletions(-)
---
diff --git a/demos/gtk-demo/fishbowl.ui b/demos/gtk-demo/fishbowl.ui
index 5d09b3477a..ab8201f6d7 100644
--- a/demos/gtk-demo/fishbowl.ui
+++ b/demos/gtk-demo/fishbowl.ui
@@ -28,22 +28,7 @@
         </child>
         <child type="end">
           <object class="GtkLabel">
-            <property name="label">fps</property>
-          </object>
-        </child>
-        <child type="end">
-          <object class="GtkLabel">
-            <property name="label" bind-source="bowl" bind-property="framerate-string"/>
-          </object>
-        </child>
-        <child type="end">
-          <object class="GtkLabel">
-            <property name="label">Icons, </property>
-          </object>
-        </child>
-        <child type="end">
-          <object class="GtkLabel">
-            <property name="label" bind-source="bowl" bind-property="count"/>
+            <binding name="label">bowl.count + " Icons, " + bowl.framerate + "fps"</binding>
           </object>
         </child>
         <child type="end">
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index b3f27ade31..cbdf7b9267 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -209,6 +209,7 @@
 #include "gtkbuildable.h"
 #include "gtkbuilderprivate.h"
 #include "gtkdebug.h"
+#include "gtkexpressionprivate.h"
 #include "gtkmain.h"
 #include "gtkintl.h"
 #include "gtkprivate.h"
@@ -992,19 +993,9 @@ gtk_builder_apply_delayed_properties (GtkBuilder *builder)
   g_slist_free (props);
 }
 
-static inline void
-free_binding_info (gpointer data,
-                   gpointer user)
-{
-  BindingInfo *info = data;
-
-  g_free (info->source);
-  g_free (info->source_property);
-  g_slice_free (BindingInfo, data);
-}
-
-static inline void
-gtk_builder_create_bindings (GtkBuilder *builder)
+static inline gboolean
+gtk_builder_create_bindings (GtkBuilder  *builder,
+                             GError     **error)
 {
   GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
   GSList *l;
@@ -1014,24 +1005,51 @@ gtk_builder_create_bindings (GtkBuilder *builder)
       BindingInfo *info = l->data;
       GObject *source;
 
-      source = _gtk_builder_lookup_object (builder, info->source, info->line, info->col);
-      if (source)
-        g_object_bind_property (source, info->source_property,
-                                info->target, info->target_pspec->name,
-                                info->flags);
+      if (info->tag_type == TAG_BINDING)
+        {
+          source = _gtk_builder_lookup_object (builder, info->source, info->line, info->col);
+          if (source)
+            g_object_bind_property (source, info->source_property,
+                                    info->target, info->target_pspec->name,
+                                    info->flags);
+        }
+      else
+        {
+          GtkExpression *expression, *assign;
+          GValue value = G_VALUE_INIT;
+
+          expression = gtk_expression_parse (builder, info->source, error);
+          if (expression == NULL)
+            {
+              g_prefix_error (error, "%s:%d:%d: ", priv->filename, info->line, info->col);
+              goto fail;
+            }
+          assign = gtk_expression_new_assign (info->target, info->target_pspec->name, expression);
+          if (gtk_expression_evaluate (assign, &value))
+            g_value_unset (&value);
+          gtk_expression_unref (assign);
+        }
+
+      _free_binding_info (info, NULL);
+    }
 
-      free_binding_info (info, NULL);
+fail:
+  for (; l; l = l->next)
+    {
+      _free_binding_info (l->data, NULL);
     }
 
   g_slist_free (priv->bindings);
   priv->bindings = NULL;
+  return TRUE;
 }
 
-void
-_gtk_builder_finish (GtkBuilder *builder)
+gboolean
+_gtk_builder_finish (GtkBuilder  *builder,
+                     GError     **error)
 {
   gtk_builder_apply_delayed_properties (builder);
-  gtk_builder_create_bindings (builder);
+  return gtk_builder_create_bindings (builder, error);
 }
 
 /**
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index 954a06b772..20e3ab245e 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -928,7 +928,8 @@ parse_property (ParserData   *data,
     {
       BindingInfo *binfo;
 
-      binfo = g_slice_new (BindingInfo);
+      binfo = g_slice_new0 (BindingInfo);
+      binfo->tag_type = TAG_BINDING;
       binfo->target = NULL;
       binfo->target_pspec = pspec;
       binfo->source = g_strdup (bind_source);
@@ -960,6 +961,87 @@ parse_property (ParserData   *data,
   state_push (data, info);
 }
 
+static void
+parse_binding (ParserData   *data,
+               const gchar  *element_name,
+               const gchar **names,
+               const gchar **values,
+               GError      **error)
+{
+  BindingInfo *info;
+  const gchar *name = NULL;
+  const gchar *context = NULL;
+  gboolean translatable = FALSE;
+  ObjectInfo *object_info;
+  GParamSpec *pspec = NULL;
+  gint line, col;
+
+  object_info = state_peek_info (data, ObjectInfo);
+  if (!object_info ||
+      !(object_info->tag_type == TAG_OBJECT ||
+        object_info->tag_type == TAG_TEMPLATE))
+    {
+      error_invalid_tag (data, element_name, NULL, error);
+      return;
+    }
+
+  if (!g_markup_collect_attributes (element_name, names, values, error,
+                                    G_MARKUP_COLLECT_STRING, "name", &name,
+                                    G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable", 
&translatable,
+                                    G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context", &context,
+                                    G_MARKUP_COLLECT_INVALID))
+    {
+      _gtk_builder_prefix_error (data->builder, &data->ctx, error);
+      return;
+    }
+
+  pspec = g_object_class_find_property (object_info->oclass, name);
+
+  if (!pspec)
+    {
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_INVALID_PROPERTY,
+                   "Invalid property: %s.%s",
+                   g_type_name (object_info->type), name);
+      _gtk_builder_prefix_error (data->builder, &data->ctx, error);
+      return;
+    }
+  else if (pspec->flags & G_PARAM_CONSTRUCT_ONLY)
+    {
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_INVALID_PROPERTY,
+                   "%s.%s is a construct-only property",
+                   g_type_name (object_info->type), name);
+      _gtk_builder_prefix_error (data->builder, &data->ctx, error);
+      return;
+    }
+  else if (!(pspec->flags & G_PARAM_WRITABLE))
+    {
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_INVALID_PROPERTY,
+                   "%s.%s is a non-writable property",
+                   g_type_name (object_info->type), name);
+      _gtk_builder_prefix_error (data->builder, &data->ctx, error);
+      return;
+    }
+
+  gtk_buildable_parse_context_get_position (&data->ctx, &line, &col);
+
+  info = g_slice_new0 (BindingInfo);
+  info->tag_type = TAG_EXPRESSION;
+  info->target = NULL;
+  info->target_pspec = pspec;
+  info->source = NULL;
+  info->flags = 0;
+  info->line = line;
+  info->col = col;
+
+  state_push (data, info);
+}
+
 static void
 free_property_info (PropertyInfo *info)
 {
@@ -1052,6 +1134,16 @@ _free_signal_info (SignalInfo *info,
   g_slice_free (SignalInfo, info);
 }
 
+void
+_free_binding_info (BindingInfo *info,
+                    gpointer     user)
+{
+  g_free (info->source);
+  g_free (info->source_property);
+  g_slice_free (BindingInfo, info);
+}
+
+
 static void
 free_requires_info (RequiresInfo *info,
                     gpointer      user_data)
@@ -1292,6 +1384,8 @@ start_element (GtkBuildableParseContext  *context,
     }
   else if (strcmp (element_name, "property") == 0)
     parse_property (data, element_name, names, values, error);
+  else if (strcmp (element_name, "binding") == 0)
+    parse_binding (data, element_name, names, values, error);
   else if (strcmp (element_name, "child") == 0)
     parse_child (data, element_name, names, values, error);
   else if (strcmp (element_name, "signal") == 0)
@@ -1377,6 +1471,23 @@ end_element (GtkBuildableParseContext  *context,
       else
         g_assert_not_reached ();
     }
+  else if (strcmp (element_name, "binding") == 0)
+    {
+      BindingInfo *binfo = state_pop_info (data, BindingInfo);
+      CommonInfo *info = state_peek_info (data, CommonInfo);
+
+      g_assert (info != NULL);
+
+      /* Normal properties */
+      if (info->tag_type == TAG_OBJECT ||
+          info->tag_type == TAG_TEMPLATE)
+        {
+          ObjectInfo *object_info = (ObjectInfo*)info;
+          object_info->bindings = g_slist_prepend (object_info->bindings, binfo);
+        }
+      else
+        g_assert_not_reached ();
+    }
   else if (strcmp (element_name, "object") == 0 ||
            strcmp (element_name, "template") == 0)
     {
@@ -1517,6 +1628,22 @@ text (GtkBuildableParseContext  *context,
 
       g_string_append_len (prop_info->text, text, text_len);
     }
+  else if (strcmp (gtk_buildable_parse_context_get_element (context), "binding") == 0)
+    {
+      BindingInfo *binfo = (BindingInfo *) info;
+
+      if (binfo->source == NULL)
+        {
+          binfo->source = g_strndup (text, text_len);
+        }
+      else
+        {
+          char *s;
+          s = g_strdup_printf ("%s%*s", binfo->source, (guint) text_len, text);
+          g_free (binfo->source);
+          binfo->source = s;
+        }
+    }
 }
 
 static void
@@ -1531,6 +1658,10 @@ free_info (CommonInfo *info)
       case TAG_CHILD:
         free_child_info ((ChildInfo *)info);
         break;
+      case TAG_BINDING:
+      case TAG_EXPRESSION:
+        _free_binding_info ((BindingInfo *)info, NULL);
+        break;
       case TAG_PROPERTY:
         free_property_info ((PropertyInfo *)info);
         break;
@@ -1594,7 +1725,8 @@ _gtk_builder_parser_parse_buffer (GtkBuilder   *builder,
   if (!gtk_buildable_parse_context_parse (&data.ctx, buffer, length, error))
     goto out;
 
-  _gtk_builder_finish (builder);
+  if (!_gtk_builder_finish (builder, error))
+    goto out;
   if (_gtk_builder_lookup_failed (builder, error))
     goto out;
 
diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h
index 0846efa378..b9ed6d58d6 100644
--- a/gtk/gtkbuilderprivate.h
+++ b/gtk/gtkbuilderprivate.h
@@ -24,7 +24,8 @@
 
 enum {
   TAG_PROPERTY,
-  TAG_MENU,
+  TAG_BINDING,
+  TAG_EXPRESSION,
   TAG_REQUIRES,
   TAG_OBJECT,
   TAG_CHILD,
@@ -84,6 +85,7 @@ typedef struct {
 
 typedef struct
 {
+  guint tag_type;
   GObject *target;
   GParamSpec *target_pspec;
   gchar *source;
@@ -175,9 +177,12 @@ void      _gtk_builder_add (GtkBuilder *builder,
                             ChildInfo *child_info);
 void      _gtk_builder_add_signals (GtkBuilder *builder,
                                    GSList     *signals);
-void      _gtk_builder_finish (GtkBuilder *builder);
+gboolean  _gtk_builder_finish (GtkBuilder  *builder,
+                               GError     **error);
 void _free_signal_info (SignalInfo *info,
                         gpointer user_data);
+void _free_binding_info (BindingInfo *info,
+                         gpointer user_data);
 
 /* Internal API which might be made public at some point */
 gboolean _gtk_builder_boolean_from_string (const gchar  *string,
diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c
new file mode 100644
index 0000000000..4a5b483aa3
--- /dev/null
+++ b/gtk/gtkexpression.c
@@ -0,0 +1,1699 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * 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.1 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
+ * Lesser 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkexpressionprivate.h"
+
+#include <gtk/css/gtkcss.h>
+#include <gtk/css/gtkcssparserprivate.h>
+#include "gtk/gtkbuildable.h"
+
+typedef struct _GtkExpressionClass GtkExpressionClass;
+
+struct _GtkExpression
+{
+  const GtkExpressionClass *expression_class;
+  GType value_type;
+
+  GtkExpression *owner;
+};
+
+struct _GtkExpressionClass
+{
+  gsize struct_size;
+  const char *type_name;
+
+  void                  (* finalize)            (GtkExpression          *expr);
+  void                  (* print)               (GtkExpression          *expr,
+                                                 GString                *string);
+  void                  (* notify)              (GtkExpression          *expr,
+                                                 GtkExpression          *source);
+  gboolean              (* evaluate)            (GtkExpression          *expr,
+                                                 GValue                 *value);
+};
+
+/**
+ * GtkExpression: (ref-func gtk_expression_ref) (unref-func gtk_expression_unref)
+ *
+ * The `GtkExpression` structure contains only private data.
+ */
+
+/*< private >
+ * gtk_expression_alloc:
+ * @expression_class: class structure for this expression
+ * @value_type: the type of the value returned by this expression
+ *
+ * Returns: (transfer full): the newly created #GtkExpression
+ */
+static gpointer
+gtk_expression_alloc (const GtkExpressionClass *expression_class,
+                      GType                     value_type)
+{
+  GtkExpression *self;
+
+  g_return_val_if_fail (expression_class != NULL, NULL);
+
+  self = g_atomic_rc_box_alloc0 (expression_class->struct_size);
+
+  self->expression_class = expression_class;
+  self->value_type = value_type;
+
+  return self;
+}
+
+static void
+gtk_expression_notify (GtkExpression *self)
+{
+  if (self->owner == NULL)
+    return;
+
+  self->owner->expression_class->notify (self->owner, self);
+}
+
+static void
+gtk_expression_set_owner (GtkExpression *self,
+                          GtkExpression *owner)
+{
+  g_assert (self->owner == NULL);
+  self->owner = owner;
+}
+
+/*** LITERAL ***/
+
+typedef struct _GtkLiteralExpression GtkLiteralExpression;
+
+struct _GtkLiteralExpression
+{
+  GtkExpression parent;
+
+  GValue value;
+};
+
+static void
+gtk_literal_expression_finalize (GtkExpression *expr)
+{
+  GtkLiteralExpression *self = (GtkLiteralExpression *) expr;
+
+  g_value_unset (&self->value);
+}
+
+static void
+gtk_literal_expression_print (GtkExpression *expr,
+                              GString       *string)
+{
+  GtkLiteralExpression *self = (GtkLiteralExpression *) expr;
+  char *s;
+
+  s = g_strdup_value_contents (&self->value);
+  g_string_append (string, s);
+  g_free (s);
+}
+
+static gboolean
+gtk_literal_expression_evaluate (GtkExpression *expr,
+                                 GValue        *value)
+{
+  GtkLiteralExpression *self = (GtkLiteralExpression *) expr;
+
+  g_value_init (value, G_VALUE_TYPE (&self->value));
+  g_value_copy (&self->value, value);
+  return TRUE;
+}
+
+static const GtkExpressionClass GTK_LITERAL_EXPRESSION_CLASS =
+{
+  sizeof (GtkLiteralExpression),
+  "GtkLiteralExpression",
+  gtk_literal_expression_finalize,
+  gtk_literal_expression_print,
+  NULL,
+  gtk_literal_expression_evaluate,
+};
+
+/*** OBJECT ***/
+
+typedef struct _GtkObjectExpression GtkObjectExpression;
+
+struct _GtkObjectExpression
+{
+  GtkExpression parent;
+
+  GObject *object;
+};
+
+static void
+gtk_object_expression_weak_cb (gpointer expr,
+                               GObject *unused)
+{
+  GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+  self->object = NULL;
+  gtk_expression_notify (expr);
+}
+
+static void
+gtk_object_expression_finalize (GtkExpression *expr)
+{
+  GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+  if (self->object)
+    g_object_weak_unref (self->object, gtk_object_expression_weak_cb, self);
+}
+
+static void
+print_object (GObject *object,
+              GString *string)
+{
+  if (GTK_IS_BUILDABLE (object) && gtk_buildable_get_name (GTK_BUILDABLE (object)))
+    {
+      g_string_append (string, gtk_buildable_get_name (GTK_BUILDABLE (object)));
+    }
+  else if (object == NULL)
+    {
+      g_string_append (string, "NULL");
+    }
+  else
+    {
+      g_string_append_printf (string, "0x%p:%s", object, G_OBJECT_TYPE_NAME (object));
+    }
+}
+
+static void
+gtk_object_expression_print (GtkExpression *expr,
+                             GString       *string)
+{
+  GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+  print_object (self->object, string);
+}
+
+static gboolean
+gtk_object_expression_evaluate (GtkExpression *expr,
+                                GValue        *value)
+{
+  GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+  if (self->object == NULL)
+    return FALSE;
+
+  g_value_init (value, G_OBJECT_TYPE (self->object));
+  g_value_set_object (value, self->object);
+
+  return TRUE;
+}
+
+static const GtkExpressionClass GTK_OBJECT_EXPRESSION_CLASS =
+{
+  sizeof (GtkObjectExpression),
+  "GtkObjectExpression",
+  gtk_object_expression_finalize,
+  gtk_object_expression_print,
+  NULL,
+  gtk_object_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_object_expression_new (GObject *object)
+{
+  GtkObjectExpression *result;
+
+  result = gtk_expression_alloc (&GTK_OBJECT_EXPRESSION_CLASS, G_OBJECT_TYPE (object));
+  result->object = object;
+  g_object_weak_ref (result->object, gtk_object_expression_weak_cb, result);
+
+  return (GtkExpression *) result;
+}
+
+/*** PROPERTY ***/
+
+typedef struct _GtkPropertyExpression GtkPropertyExpression;
+
+struct _GtkPropertyExpression
+{
+  GtkExpression parent;
+
+  GtkExpression *expr;
+  GParamSpec *pspec;
+
+  GClosure *closure;
+};
+
+static void
+gtk_property_expression_finalize (GtkExpression *expr)
+{
+  GtkPropertyExpression *self = (GtkPropertyExpression *) expr;
+
+  gtk_expression_unref (self->expr);
+  if (self->closure)
+    {
+      g_closure_invalidate (self->closure);
+      g_closure_unref (self->closure);
+    }
+}
+
+static void
+gtk_property_expression_print (GtkExpression *expr,
+                              GString       *string)
+{
+  GtkPropertyExpression *self = (GtkPropertyExpression *) expr;
+
+  gtk_expression_print (self->expr, string);
+  g_string_append (string, ".");
+  g_string_append (string, self->pspec->name);
+}
+
+static void
+gtk_property_expression_notify_cb (GObject       *object,
+                                   GParamSpec    *pspec,
+                                   GtkExpression *expr)
+{
+  gtk_expression_notify (expr);
+}
+
+static void
+gtk_property_expression_connect (GtkPropertyExpression *self)
+{
+  GValue value = G_VALUE_INIT;
+
+  if (gtk_expression_evaluate (self->expr, &value))
+    {
+      GObject *object = g_value_get_object (&value);
+      if (object)
+        {
+          self->closure = g_cclosure_new (G_CALLBACK (gtk_property_expression_notify_cb), self, NULL);
+          if (!g_signal_connect_closure_by_id (object,
+                                               g_signal_lookup ("notify", G_OBJECT_TYPE (object)),
+                                               g_quark_from_string (self->pspec->name),
+                                               g_closure_ref (self->closure),
+                                               FALSE))
+            {
+              g_assert_not_reached ();
+            }
+        }
+      g_value_unset (&value);
+    }
+}
+
+static void
+gtk_property_expression_notify (GtkExpression *expr,
+                                GtkExpression *source)
+{
+  GtkPropertyExpression *self = (GtkPropertyExpression *) expr;
+
+  if (self->closure)
+    {
+      g_closure_invalidate (self->closure);
+      g_clear_pointer (&self->closure, g_closure_unref);
+    }
+
+  gtk_property_expression_connect (self);
+
+  gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_property_expression_evaluate (GtkExpression *expr,
+                                  GValue        *value)
+{
+  GtkPropertyExpression *self = (GtkPropertyExpression *) expr;
+  GValue object_value = G_VALUE_INIT;
+  GObject *object;
+
+  if (!gtk_expression_evaluate (self->expr, &object_value))
+    return FALSE;
+
+  object = g_value_get_object (&object_value);
+  if (object == NULL)
+    {
+      g_value_unset (&object_value);
+      return FALSE;
+    }
+
+  g_object_get_property (object, self->pspec->name, value);
+  g_value_unset (&object_value);
+  return TRUE;
+}
+
+static const GtkExpressionClass GTK_PROPERTY_EXPRESSION_CLASS =
+{
+  sizeof (GtkPropertyExpression),
+  "GtkPropertyExpression",
+  gtk_property_expression_finalize,
+  gtk_property_expression_print,
+  gtk_property_expression_notify,
+  gtk_property_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_property (GtkExpression *expr,
+                             GParamSpec    *pspec)
+{
+  GtkPropertyExpression *result;
+
+  g_assert (g_type_is_a (gtk_expression_get_value_type (expr), pspec->owner_type));
+
+  result = gtk_expression_alloc (&GTK_PROPERTY_EXPRESSION_CLASS, pspec->value_type);
+
+  gtk_expression_set_owner (expr, (GtkExpression *) result);
+  result->expr = expr;
+  result->pspec = pspec;
+
+  gtk_property_expression_connect (result);
+
+  return (GtkExpression *) result;
+}
+
+/*** CAST ***/
+
+typedef struct _GtkCastExpression GtkCastExpression;
+
+typedef gboolean (* GtkCastFunc) (GValue       *to,
+                                  const GValue *from);
+
+struct _GtkCastExpression
+{
+  GtkExpression parent;
+
+  GtkExpression *expr;
+  GtkCastFunc cast_func;
+};
+
+static gboolean
+gtk_cast_copy (GValue       *to,
+               const GValue *from)
+{
+  g_value_copy (from, to);
+  return TRUE;
+}
+
+static gboolean
+gtk_cast_transform (GValue       *to,
+                    const GValue *from)
+{
+  g_value_transform (from, to);
+  return TRUE;
+}
+
+static gboolean
+gtk_cast_upcast (GValue       *to,
+                 const GValue *from)
+{
+  GObject *o = g_value_get_object (from);
+
+  if (o && !g_type_is_a (G_OBJECT_TYPE (o), G_VALUE_TYPE (to)))
+    {
+      g_value_unset (to);
+      return FALSE;
+    }
+
+  g_value_set_object (to, o);
+  return TRUE;
+}
+
+static gboolean
+gtk_cast_impossible (GValue       *to,
+                     const GValue *from)
+{
+  g_value_unset (to);
+  return FALSE;
+}
+
+static GtkCastFunc
+gtk_cast_expression_find_cast_func (GType from,
+                                    GType to)
+{
+  if (g_value_type_compatible (from, to))
+    return gtk_cast_copy;
+  else if (g_type_is_a (from, G_TYPE_OBJECT) && g_type_is_a (to, G_TYPE_OBJECT))
+    return gtk_cast_upcast;
+  else if (g_value_type_transformable (from, to))
+    return gtk_cast_transform;
+  else
+    return gtk_cast_impossible;
+}
+
+static void
+gtk_cast_expression_finalize (GtkExpression *expr)
+{
+  GtkCastExpression *self = (GtkCastExpression *) expr;
+
+  gtk_expression_unref (self->expr);
+}
+
+static void
+gtk_cast_expression_print (GtkExpression *expr,
+                           GString       *string)
+{
+  GtkCastExpression *self = (GtkCastExpression *) expr;
+
+  gtk_expression_print (self->expr, string);
+  g_string_append (string, ":");
+  g_string_append (string, g_type_name (gtk_expression_get_value_type (expr)));
+}
+
+static void
+gtk_cast_expression_notify (GtkExpression *expr,
+                            GtkExpression *source)
+{
+  gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_cast_expression_evaluate (GtkExpression *expr,
+                              GValue        *value)
+{
+  GtkCastExpression *self = (GtkCastExpression *) expr;
+  GValue expr_value = G_VALUE_INIT;
+  gboolean result;
+
+  if (!gtk_expression_evaluate (self->expr, &expr_value))
+    return FALSE;
+
+  g_value_init (value, gtk_expression_get_value_type (expr));
+  result = self->cast_func (value, &expr_value);
+  g_value_unset (&expr_value);
+
+  return result;
+}
+
+static const GtkExpressionClass GTK_CAST_EXPRESSION_CLASS =
+{
+  sizeof (GtkCastExpression),
+  "GtkCastExpression",
+  gtk_cast_expression_finalize,
+  gtk_cast_expression_print,
+  gtk_cast_expression_notify,
+  gtk_cast_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_cast (GtkExpression *expr,
+                         GType          type)
+{
+  GtkCastExpression *result;
+
+  result = gtk_expression_alloc (&GTK_CAST_EXPRESSION_CLASS, type);
+
+  gtk_expression_set_owner (expr, (GtkExpression *) result);
+  result->expr = expr;
+  result->cast_func = gtk_cast_expression_find_cast_func (gtk_expression_get_value_type (expr),
+                                                          type);
+
+  return (GtkExpression *) result;
+}
+
+/*** ASSIGN ***/
+
+typedef struct _GtkAssignExpression GtkAssignExpression;
+
+struct _GtkAssignExpression
+{
+  GtkExpression parent;
+
+  GObject *object;
+  char *property;
+  GtkExpression *expr;
+};
+
+static void
+gtk_assign_expression_weak_cb (gpointer expr,
+                               GObject *unused)
+{
+  GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+  self->object = NULL;
+  gtk_expression_unref (expr);
+}
+
+static void
+gtk_assign_expression_finalize (GtkExpression *expr)
+{
+  GtkAssignExpression *self = (GtkAssignExpression *) expr;
+
+  g_assert (self->object == NULL);
+  gtk_expression_unref (self->expr);
+  g_free (self->property);
+}
+
+static void
+gtk_assign_expression_print (GtkExpression *expr,
+                             GString       *string)
+{
+  GtkAssignExpression *self = (GtkAssignExpression *) expr;
+
+  print_object (self->object, string);
+  g_string_append (string, ".");
+  g_string_append (string, self->property);
+  g_string_append (string, " = ");
+  gtk_expression_print (self->expr, string);
+}
+
+static void
+gtk_assign_expression_notify (GtkExpression *expr,
+                              GtkExpression *source)
+{
+  GtkAssignExpression *self = (GtkAssignExpression *) expr;
+  GValue value = G_VALUE_INIT;
+
+  if (self->object)
+    {
+      if (gtk_expression_evaluate (self->expr, &value))
+        {
+          g_object_set_property (self->object, self->property, &value);
+          g_value_unset (&value);
+        }
+      else
+        {
+          /* XXX: init default value here or just not do anything? */
+          /* g_warn_if_reached (); */
+        }
+    }
+
+  gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_assign_expression_evaluate (GtkExpression *expr,
+                                GValue        *value)
+{
+  GtkAssignExpression *self = (GtkAssignExpression *) expr;
+
+  if (!gtk_expression_evaluate (self->expr, value))
+    return FALSE;
+
+  g_object_set_property (self->object, self->property, value);
+  return TRUE;
+}
+
+static const GtkExpressionClass GTK_ASSIGN_EXPRESSION_CLASS =
+{
+  sizeof (GtkAssignExpression),
+  "GtkAssignExpression",
+  gtk_assign_expression_finalize,
+  gtk_assign_expression_print,
+  gtk_assign_expression_notify,
+  gtk_assign_expression_evaluate,
+};
+
+GtkExpression *
+gtk_expression_new_assign (GObject       *object,
+                           const char    *property,
+                           GtkExpression *expr)
+{
+  GtkAssignExpression *result;
+
+  result = gtk_expression_alloc (&GTK_ASSIGN_EXPRESSION_CLASS,
+                                 gtk_expression_get_value_type (expr));
+
+  gtk_expression_set_owner (expr, (GtkExpression *) result);
+  result->object = object;
+  result->expr = expr;
+  result->property = g_strdup (property);
+  if (result->object)
+    {
+      g_object_weak_ref (result->object, gtk_assign_expression_weak_cb, result);
+      gtk_expression_ref ((GtkExpression *) result);
+    }
+
+  return (GtkExpression *) result;
+}
+
+/*** NEGATE ***/
+
+typedef struct _GtkNegateExpression GtkNegateExpression;
+
+struct _GtkNegateExpression
+{
+  GtkExpression parent;
+
+  GtkExpression *expr;
+};
+
+static void
+gtk_negate_expression_finalize (GtkExpression *expr)
+{
+  GtkNegateExpression *self = (GtkNegateExpression *) expr;
+
+  gtk_expression_unref (self->expr);
+}
+
+static void
+gtk_negate_expression_print (GtkExpression *expr,
+                             GString       *string)
+{
+  GtkNegateExpression *self = (GtkNegateExpression *) expr;
+
+  g_string_append (string, "!");
+  gtk_expression_print (self->expr, string);
+}
+
+static void
+gtk_negate_expression_notify (GtkExpression *expr,
+                              GtkExpression *source)
+{
+  gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_negate_expression_evaluate (GtkExpression *expr,
+                                GValue        *value)
+{
+  GtkNegateExpression *self = (GtkNegateExpression *) expr;
+  GValue expr_value = G_VALUE_INIT;
+
+  if (!gtk_expression_evaluate (self->expr, &expr_value))
+    return FALSE;
+
+  g_value_init (value, G_TYPE_BOOLEAN);
+  g_value_set_boolean (value, !gtk_expression_value_to_boolean (&expr_value));
+  g_value_unset (&expr_value);
+  return TRUE;
+}
+
+static const GtkExpressionClass GTK_NEGATE_EXPRESSION_CLASS =
+{
+  sizeof (GtkNegateExpression),
+  "GtkNegateExpression",
+  gtk_negate_expression_finalize,
+  gtk_negate_expression_print,
+  gtk_negate_expression_notify,
+  gtk_negate_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_negate (GtkExpression *expr)
+{
+  GtkNegateExpression *result;
+
+  result = gtk_expression_alloc (&GTK_NEGATE_EXPRESSION_CLASS,
+                                 G_TYPE_BOOLEAN);
+
+  gtk_expression_set_owner (expr, (GtkExpression *) result);
+  result->expr = expr;
+
+  return (GtkExpression *) result;
+}
+
+/*** SUM ***/
+
+typedef struct _GtkSumExpression GtkSumExpression;
+
+struct _GtkSumExpression
+{
+  GtkExpression parent;
+
+  GtkExpression *left;
+  GtkExpression *right;
+};
+
+static void
+gtk_sum_expression_finalize (GtkExpression *expr)
+{
+  GtkSumExpression *self = (GtkSumExpression *) expr;
+
+  gtk_expression_unref (self->left);
+  gtk_expression_unref (self->right);
+}
+
+static void
+gtk_sum_expression_print (GtkExpression *expr,
+                          GString       *string)
+{
+  GtkSumExpression *self = (GtkSumExpression *) expr;
+
+  gtk_expression_print (self->left, string);
+  g_string_append (string, " + ");
+  gtk_expression_print (self->right, string);
+}
+
+static void
+gtk_sum_expression_notify (GtkExpression *expr,
+                           GtkExpression *source)
+{
+  gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_sum_expression_evaluate (GtkExpression *expr,
+                             GValue        *value)
+{
+  GtkSumExpression *self = (GtkSumExpression *) expr;
+  GValue lvalue = G_VALUE_INIT;
+  GValue rvalue = G_VALUE_INIT;
+  char *lstr, *rstr;
+
+  if (!gtk_expression_evaluate (self->left, &lvalue))
+    return FALSE;
+  if (!gtk_expression_evaluate (self->right, &rvalue))
+    {
+      g_value_unset (&lvalue);
+      return FALSE;
+    }
+
+  lstr = gtk_expression_value_to_string (&lvalue);
+  rstr = gtk_expression_value_to_string (&rvalue);
+  g_value_init (value, G_TYPE_STRING);
+  g_value_take_string (value, g_strconcat (lstr, rstr, NULL));
+  g_free (lstr);
+  g_free (rstr);
+
+  g_value_unset (&rvalue);
+  g_value_unset (&lvalue);
+  return TRUE;
+}
+
+static const GtkExpressionClass GTK_SUM_EXPRESSION_CLASS =
+{
+  sizeof (GtkSumExpression),
+  "GtkSumExpression",
+  gtk_sum_expression_finalize,
+  gtk_sum_expression_print,
+  gtk_sum_expression_notify,
+  gtk_sum_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_sum (GtkExpression *left,
+                        GtkExpression *right)
+{
+  GtkSumExpression *result;
+
+  result = gtk_expression_alloc (&GTK_SUM_EXPRESSION_CLASS,
+                                 G_TYPE_STRING);
+
+  gtk_expression_set_owner (left, (GtkExpression *) result);
+  gtk_expression_set_owner (right, (GtkExpression *) result);
+  result->left = left;
+  result->right = right;
+
+  return (GtkExpression *) result;
+}
+
+/*** DEBUG ***/
+
+typedef struct _GtkDebugExpression GtkDebugExpression;
+
+struct _GtkDebugExpression
+{
+  GtkExpression parent;
+
+  GtkExpression *expr;
+};
+
+static void
+gtk_debug_expression_finalize (GtkExpression *expr)
+{
+  GtkDebugExpression *self = (GtkDebugExpression *) expr;
+
+  gtk_expression_unref (self->expr);
+}
+
+static void
+gtk_debug_expression_print (GtkExpression *expr,
+                            GString       *string)
+{
+  GtkDebugExpression *self = (GtkDebugExpression *) expr;
+
+  g_string_append (string, "debug(");
+  gtk_expression_print (self->expr, string);
+  g_string_append (string, ")");
+}
+
+static void
+gtk_debug_expression_notify (GtkExpression *expr,
+                             GtkExpression *source)
+{
+  gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_debug_expression_evaluate (GtkExpression *expr,
+                               GValue        *value)
+{
+  GtkDebugExpression *self = (GtkDebugExpression *) expr;
+  char *message, *expr_string;
+  gboolean result;
+
+  result = gtk_expression_evaluate (self->expr, value);
+
+  expr_string = gtk_expression_to_string (self->expr);
+  if (result)
+    message = gtk_expression_value_to_string (value);
+  else
+    message = g_strdup ("%s: **** failed to evaluate ***");
+
+  g_printerr ("%s: %s\n", expr_string, message);
+  g_free (message);
+  g_free (expr_string);
+
+  return result;
+}
+
+static const GtkExpressionClass GTK_DEBUG_EXPRESSION_CLASS =
+{
+  sizeof (GtkDebugExpression),
+  "GtkDebugExpression",
+  gtk_debug_expression_finalize,
+  gtk_debug_expression_print,
+  gtk_debug_expression_notify,
+  gtk_debug_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_debug (GtkExpression *expr)
+{
+  GtkDebugExpression *result;
+
+  result = gtk_expression_alloc (&GTK_DEBUG_EXPRESSION_CLASS,
+                                 gtk_expression_get_value_type (expr));
+
+  gtk_expression_set_owner (expr, (GtkExpression *) result);
+  result->expr = expr;
+
+  return (GtkExpression *) result;
+}
+
+/*** FILE INFO ***/
+
+typedef struct _GtkFileInfoExpression GtkFileInfoExpression;
+
+struct _GtkFileInfoExpression
+{
+  GtkExpression parent;
+
+  GtkExpression *expr;
+  char *attribute;
+  GFileAttributeType type;
+};
+
+const char *file_attribute_type_names[] = {
+  [G_FILE_ATTRIBUTE_TYPE_STRING] = "string",
+  [G_FILE_ATTRIBUTE_TYPE_BOOLEAN] = "boolean",
+  [G_FILE_ATTRIBUTE_TYPE_UINT32] = "uint32",
+  [G_FILE_ATTRIBUTE_TYPE_INT32] = "int32",
+  [G_FILE_ATTRIBUTE_TYPE_UINT64] = "uint64",
+  [G_FILE_ATTRIBUTE_TYPE_INT64] = "int64",
+  [G_FILE_ATTRIBUTE_TYPE_OBJECT] = "object",
+};
+
+static GType
+gtk_file_attribute_type_get_gtype (GFileAttributeType type)
+{
+  switch (type)
+  {
+    case G_FILE_ATTRIBUTE_TYPE_STRING:
+      return G_TYPE_STRING;
+    case G_FILE_ATTRIBUTE_TYPE_BOOLEAN:
+      return G_TYPE_BOOLEAN;
+    case G_FILE_ATTRIBUTE_TYPE_UINT32:
+      return G_TYPE_UINT;
+    case G_FILE_ATTRIBUTE_TYPE_INT32:
+      return G_TYPE_INT;
+    case G_FILE_ATTRIBUTE_TYPE_UINT64:
+      return G_TYPE_UINT64;
+    case G_FILE_ATTRIBUTE_TYPE_INT64:
+      return G_TYPE_INT64;
+    case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+      return G_TYPE_OBJECT;
+    case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING:
+    case G_FILE_ATTRIBUTE_TYPE_STRINGV:
+    case G_FILE_ATTRIBUTE_TYPE_INVALID:
+    default:
+      /* not supported */
+      g_return_val_if_reached (G_TYPE_INVALID);
+  }
+}
+
+static void
+gtk_file_info_expression_finalize (GtkExpression *expr)
+{
+  GtkFileInfoExpression *self = (GtkFileInfoExpression *) expr;
+
+  gtk_expression_unref (self->expr);
+  g_free (self->attribute);
+}
+
+static void
+gtk_file_info_expression_print (GtkExpression *expr,
+                                GString       *string)
+{
+  GtkFileInfoExpression *self = (GtkFileInfoExpression *) expr;
+
+  g_string_append (string, "file-info(");
+  gtk_expression_print (self->expr, string);
+  g_string_append_printf (string, ", %s, %s)",
+                          self->attribute,
+                          file_attribute_type_names [self->type]);
+}
+
+static void
+gtk_file_info_expression_notify (GtkExpression *expr,
+                                 GtkExpression *source)
+{
+  gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_file_info_expression_evaluate (GtkExpression *expr,
+                                   GValue        *value)
+{
+  GtkFileInfoExpression *self = (GtkFileInfoExpression *) expr;
+  GValue expr_value = G_VALUE_INIT;
+  GFileInfo *info;
+
+  if (!gtk_expression_evaluate (self->expr, &expr_value))
+    return FALSE;
+
+  info = g_value_get_object (&expr_value);
+  if (info == NULL)
+    return FALSE;
+  if (g_file_info_get_attribute_type (info, self->attribute) != self->type)
+    return FALSE;
+
+  switch (self->type)
+    {
+    case G_FILE_ATTRIBUTE_TYPE_STRING:
+      g_value_init (value, G_TYPE_STRING);
+      g_value_set_string (value, g_file_info_get_attribute_string (info, self->attribute));
+      break;
+
+    case G_FILE_ATTRIBUTE_TYPE_BOOLEAN:
+      g_value_init (value, G_TYPE_BOOLEAN);
+      g_value_set_boolean (value, g_file_info_get_attribute_boolean (info, self->attribute));
+      break;
+
+    case G_FILE_ATTRIBUTE_TYPE_UINT32:
+      g_value_init (value, G_TYPE_UINT);
+      g_value_set_uint (value, g_file_info_get_attribute_uint32 (info, self->attribute));
+      break;
+
+    case G_FILE_ATTRIBUTE_TYPE_INT32:
+      g_value_init (value, G_TYPE_INT);
+      g_value_set_int (value, g_file_info_get_attribute_int32 (info, self->attribute));
+      break;
+
+    case G_FILE_ATTRIBUTE_TYPE_UINT64:
+      g_value_init (value, G_TYPE_UINT64);
+      g_value_set_uint64 (value, g_file_info_get_attribute_uint64 (info, self->attribute));
+      break;
+
+    case G_FILE_ATTRIBUTE_TYPE_INT64:
+      g_value_init (value, G_TYPE_INT64);
+      g_value_set_int64 (value, g_file_info_get_attribute_int64 (info, self->attribute));
+      break;
+
+    case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+      g_value_init (value, G_TYPE_OBJECT);
+      g_value_set_object (value, g_file_info_get_attribute_object (info, self->attribute));
+      break;
+    case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING:
+    case G_FILE_ATTRIBUTE_TYPE_STRINGV:
+    case G_FILE_ATTRIBUTE_TYPE_INVALID:
+    default:
+      g_assert_not_reached ();
+    }
+
+  g_value_unset (&expr_value);
+  return TRUE;
+}
+
+static const GtkExpressionClass GTK_FILE_INFO_EXPRESSION_CLASS =
+{
+  sizeof (GtkFileInfoExpression),
+  "GtkFileInfoExpression",
+  gtk_file_info_expression_finalize,
+  gtk_file_info_expression_print,
+  gtk_file_info_expression_notify,
+  gtk_file_info_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_file_info (GtkExpression      *expr,
+                              char               *attribute,
+                              GFileAttributeType  type)
+{
+  GtkFileInfoExpression *result;
+
+  g_return_val_if_fail (g_type_is_a (gtk_expression_get_value_type (expr), G_TYPE_FILE_INFO), NULL);
+
+  result = gtk_expression_alloc (&GTK_FILE_INFO_EXPRESSION_CLASS,
+                                 gtk_file_attribute_type_get_gtype (type));
+
+  gtk_expression_set_owner (expr, (GtkExpression *) result);
+  result->expr = expr;
+  result->attribute = g_strdup (attribute);
+  result->type = type;
+
+  return (GtkExpression *) result;
+}
+
+/*** PUBLIC API ***/
+
+static void
+gtk_expression_finalize (GtkExpression *self)
+{
+  self->expression_class->finalize (self);
+}
+
+/**
+ * gtk_expression_ref:
+ * @self: (allow-none): a #GtkExpression
+ *
+ * Acquires a reference on the given #GtkExpression.
+ *
+ * Returns: (transfer none): the #GtkExpression with an additional reference
+ */
+GtkExpression *
+gtk_expression_ref (GtkExpression *self)
+{
+  return g_atomic_rc_box_acquire (self);
+}
+
+/**
+ * gtk_expression_unref:
+ * @self: (allow-none): a #GtkExpression
+ *
+ * Releases a reference on the given #GtkExpression.
+ *
+ * If the reference was the last, the resources associated to the @self are
+ * freed.
+ */
+void
+gtk_expression_unref (GtkExpression *self)
+{
+  g_atomic_rc_box_release_full (self, (GDestroyNotify) gtk_expression_finalize);
+}
+
+/**
+ * gtk_expression_print:
+ * @self: a #GtkExpression
+ * @string: The string to print into
+ *
+ * Converts @self into a human-readable string representation suitable
+ * for printing that can be parsed with gtk_expression_parse().
+ **/
+void
+gtk_expression_print (GtkExpression *self,
+                      GString       *string)
+{
+  g_return_if_fail (GTK_IS_EXPRESSION (self));
+  g_return_if_fail (string != NULL);
+
+  self->expression_class->print (self, string);
+}
+
+/**
+ * gtk_expression_to_string:
+ * @self: a #GtkExpression
+ *
+ * Converts an expression into a string that is suitable for
+ * printing and can later be parsed with gtk_expression_parse().
+ *
+ * This is a wrapper around gtk_expression_print(), see that function
+ * for details.
+ *
+ * Returns: A new string for @self
+ **/
+char *
+gtk_expression_to_string (GtkExpression *self)
+{
+  GString *string;
+
+  string = g_string_new ("");
+
+  gtk_expression_print (self, string);
+
+  return g_string_free (string, FALSE);
+}
+
+/**
+ * gtk_expression_get_value_type:
+ * @self: a #GtkExpression
+ *
+ * Gets the #GType that this expression evaluates to. This type
+ * is constant and will not change over the lifetime of this expression.
+ *
+ * Returns: The type returned from gtk_expression_evaluate()
+ **/
+GType
+gtk_expression_get_value_type (GtkExpression *self)
+{
+  g_return_val_if_fail (GTK_IS_EXPRESSION (self), G_TYPE_INVALID);
+
+  return self->value_type;
+}
+
+/**
+ * gtk_expression_evaluate:
+ * @self: a #GtkExpression
+ * @value: an empty #GValue
+ *
+ * Evaluates the given expression and on success stores the result
+ * in @value. The #GType of @value will be the type given by
+ * gtk_expression_get_value_type().
+ *
+ * It is possible that expressions cannot be evaluated - for example
+ * when the expression references objects that have been destroyed or
+ * set to %NULL. In that case @value will remain empty and %FALSE
+ * will be returned.
+ *
+ * Returns: %TRUE if the expression could be evaluated
+ **/
+gboolean
+gtk_expression_evaluate (GtkExpression *self,
+                         GValue        *value)
+{
+  g_return_val_if_fail (GTK_IS_EXPRESSION (self), FALSE);
+  g_return_val_if_fail (value != NULL, FALSE);
+
+  return self->expression_class->evaluate (self, value);
+}
+
+/* forward declaration */
+static GtkExpression *
+gtk_expression_parser_parse (GtkBuilder   *scope,
+                             GtkCssParser *parser);
+
+typedef struct {
+  GtkBuilder *scope;
+
+  GtkExpression *expr;
+  char *attribute;
+  GFileAttributeType type;
+} FileInfoData;
+
+static guint
+gtk_expression_parser_parse_file_info (GtkCssParser *parser,
+                                       guint         n,
+                                       gpointer      _data)
+{
+  FileInfoData *data = _data;
+  guint i;
+
+  switch (n)
+  {
+    case 0:
+      data->expr = gtk_expression_parser_parse (data->scope, parser);
+      if (data->expr == NULL)
+        return 0;
+      if (!g_type_is_a (gtk_expression_get_value_type (data->expr), G_TYPE_FILE_INFO))
+        {
+          gtk_css_parser_error_value (parser, "Expression does not evaluate a GFileInfo");
+          return 0;
+        }
+      return 1;
+
+    case 1:
+      data->attribute = gtk_css_parser_consume_string (parser);
+      if (data->attribute == NULL)
+        return 0;
+      return 1;
+
+    case 2:
+      for (i = 0; i < G_N_ELEMENTS (file_attribute_type_names); i++)
+        {
+          if (file_attribute_type_names[i] == NULL)
+            continue;
+
+          if (gtk_css_parser_try_ident (parser, file_attribute_type_names[i]))
+            {
+              data->type = i;
+              return 1;
+            }
+        }
+      return 0;
+
+    default:
+      g_assert_not_reached ();
+      return 0;
+  }
+}
+
+typedef struct {
+  GtkBuilder *scope;
+  GtkExpression *expr;
+} DebugData;
+
+static guint
+gtk_expression_parser_parse_debug (GtkCssParser *parser,
+                                   guint         n,
+                                   gpointer      _data)
+{
+  DebugData *data = _data;
+
+  data->expr = gtk_expression_parser_parse (data->scope, parser);
+  if (data->expr == NULL)
+    return 0;
+
+  return 1;
+}
+
+static GtkExpression *
+gtk_expression_parser_parse_primary (GtkBuilder   *scope,
+                                     GtkCssParser *parser)
+{
+  if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_INTEGER))
+    {
+      GtkLiteralExpression *literal = gtk_expression_alloc (&GTK_LITERAL_EXPRESSION_CLASS, G_TYPE_INT);
+      int i;
+
+      g_value_init (&literal->value, G_TYPE_INT);
+      gtk_css_parser_consume_integer (parser, &i);
+      g_value_set_int (&literal->value, i);
+
+      return (GtkExpression *) literal;
+    }
+  else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+    {
+      GtkLiteralExpression *literal = gtk_expression_alloc (&GTK_LITERAL_EXPRESSION_CLASS, G_TYPE_UINT);
+      int i;
+
+      g_value_init (&literal->value, G_TYPE_UINT);
+      gtk_css_parser_consume_integer (parser, &i);
+      g_value_set_uint (&literal->value, i);
+
+      return (GtkExpression *) literal;
+    }
+  else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
+           gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER))
+    {
+      GtkLiteralExpression *literal = gtk_expression_alloc (&GTK_LITERAL_EXPRESSION_CLASS, G_TYPE_DOUBLE);
+      double d;
+
+      g_value_init (&literal->value, G_TYPE_DOUBLE);
+      gtk_css_parser_consume_number (parser, &d);
+      g_value_set_double (&literal->value, d);
+
+      return (GtkExpression *) literal;
+    }
+  else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_STRING))
+    {
+      GtkLiteralExpression *literal = gtk_expression_alloc (&GTK_LITERAL_EXPRESSION_CLASS, G_TYPE_STRING);
+
+      g_value_init (&literal->value, G_TYPE_STRING);
+      g_value_take_string (&literal->value, gtk_css_parser_consume_string (parser));
+
+      return (GtkExpression *) literal;
+    }
+  else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_IDENT))
+    {
+      char *ident = gtk_css_parser_consume_ident (parser);
+      GObject *object = gtk_builder_get_object (scope, ident);
+
+      if (object == NULL)
+        {
+          gtk_css_parser_error_value (parser, "No variable named \"%s\"", ident);
+          g_free (ident);
+          return NULL;
+        }
+
+      g_free (ident);
+      return gtk_object_expression_new (object);
+    }
+  else if (gtk_css_parser_has_function (parser, "file-info"))
+    {
+      FileInfoData data = { scope, };
+
+      if (!gtk_css_parser_consume_function (parser, 3, 3, gtk_expression_parser_parse_file_info, &data))
+        {
+          g_clear_pointer (&data.expr, gtk_expression_unref);
+          g_free (data.attribute);
+          return FALSE;
+        }
+
+      return gtk_expression_new_file_info (data.expr, data.attribute, data.type);
+    }
+  else if (gtk_css_parser_has_function (parser, "debug"))
+    {
+      DebugData data = { scope, };
+
+      if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_expression_parser_parse_debug, &data))
+        return FALSE;
+
+      return gtk_expression_new_debug (data.expr);
+    }
+  else
+    {
+      gtk_css_parser_error_syntax (parser, "Unexpected syntax");
+      return NULL;
+    }
+}
+
+static GtkExpression *
+gtk_expression_parser_parse_postfix (GtkBuilder   *scope,
+                                     GtkCssParser *parser)
+{
+  const GtkCssToken *token;
+  GtkExpression *expr;
+  
+  expr = gtk_expression_parser_parse_primary (scope, parser);
+  if (expr == NULL)
+    return NULL;
+
+  while (TRUE)
+    {
+      token = gtk_css_parser_peek_token (parser);
+      if (gtk_css_token_is_delim (token, '.'))
+        {
+          GType type = gtk_expression_get_value_type (expr);
+          GParamSpec *pspec;
+
+          gtk_css_parser_consume_token (parser);
+          token = gtk_css_parser_peek_token (parser);
+          if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+            {
+              gtk_css_parser_error_syntax (parser, "Expected field member after '.'");
+              gtk_expression_unref (expr);
+              return NULL;
+            }
+          if (g_type_is_a (type, G_TYPE_OBJECT))
+            {
+              pspec = g_object_class_find_property (g_type_class_peek (type), token->string.string);
+            }
+          else if (g_type_is_a (type, G_TYPE_INTERFACE))
+            {
+              pspec = g_object_interface_find_property (g_type_default_interface_peek (type),
+                                                        token->string.string);
+            }
+          else
+            {
+              gtk_css_parser_error_value (parser, "Values of type \"%s\" cannot have members",
+                                          g_type_name (type));
+              gtk_expression_unref (expr);
+              return NULL;
+            }
+          if (pspec == NULL)
+            {
+              gtk_css_parser_error_value (parser, "\"%s\" has no property named \"%s\"",
+                                          g_type_name (type), token->string.string);
+              gtk_expression_unref (expr);
+              return NULL;
+            }
+
+          expr = gtk_expression_new_property (expr, pspec);
+          gtk_css_parser_consume_token (parser);
+        }
+      else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COLON))
+        {
+          GType type;
+
+          gtk_css_parser_consume_token (parser);
+          token = gtk_css_parser_peek_token (parser);
+          if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+            {
+              gtk_css_parser_error_syntax (parser, "Expected type name after ':'");
+              gtk_expression_unref (expr);
+              return NULL;
+            }
+          type = gtk_builder_get_type_from_name (scope, token->string.string);
+          if (type == G_TYPE_INVALID)
+            {
+              gtk_css_parser_error_value (parser, "Cannot cast to unknown type \"%s\"", 
token->string.string);
+              gtk_expression_unref (expr);
+              return NULL;
+            }
+          expr = gtk_expression_new_cast (expr, type);
+          gtk_css_parser_consume_token (parser);
+        }
+      else
+        {
+          break;
+        }
+    }
+  return expr;
+}
+
+static GtkExpression *
+gtk_expression_parser_parse_unary (GtkBuilder   *scope,
+                                   GtkCssParser *parser)
+{
+  if (gtk_css_parser_try_delim (parser, '!'))
+    {
+      GtkExpression *expr;
+
+      expr = gtk_expression_parser_parse_postfix (scope, parser);
+      if (expr == NULL)
+        return NULL;
+
+      return gtk_expression_new_negate (expr);
+    }
+  else
+    {
+      return gtk_expression_parser_parse_postfix (scope, parser);
+    }
+}
+
+static GtkExpression *
+gtk_expression_parser_parse_additive (GtkBuilder   *scope,
+                                      GtkCssParser *parser)
+{
+  GtkExpression *expr;
+
+  expr = gtk_expression_parser_parse_unary (scope, parser);
+  if (expr == NULL)
+    return NULL;
+
+  while (TRUE)
+    {
+      if (gtk_css_parser_try_delim (parser, '+'))
+        {
+          GtkExpression *right;
+
+          right = gtk_expression_parser_parse_unary (scope, parser);
+          if (right == NULL)
+            {
+              gtk_expression_unref (expr);
+              return NULL;
+            }
+          expr = gtk_expression_new_sum (expr, right);
+        }
+      else
+        {
+          return expr;
+        }
+    }
+}
+
+static GtkExpression *
+gtk_expression_parser_parse (GtkBuilder   *scope,
+                             GtkCssParser *parser)
+{
+  return gtk_expression_parser_parse_additive (scope, parser);
+}
+
+static void
+gtk_expression_builder_error_forward (GtkCssParser         *parser,
+                                      const GtkCssLocation *start,
+                                      const GtkCssLocation *end,
+                                      const GError         *error,
+                                      gpointer              user_data)
+{
+  GError **forward_error = (GError **) user_data;
+
+  if (forward_error && *forward_error == NULL)
+    *forward_error = g_error_copy (error);
+}
+
+/**
+ * gtk_expression_parse:
+ * @scope: a #GtkBuilder object to lookup variables in
+ * @string: the string to parse
+ * @error: Return location to store error or %NULL to ignore
+ *
+ * Parses the given @string into an expression and returns it.
+ * Strings printed via gtk_expression_to_string()
+ * can be read in again successfully using this function.
+ *
+ * If @string does not describe a valid expression, %NULL is
+ * returned.
+ *
+ * Returns: A new expression
+ **/
+GtkExpression *
+gtk_expression_parse (GtkBuilder  *scope,
+                      const char  *string,
+                      GError     **error)
+{
+  GtkCssParser *parser;
+  GBytes *bytes;
+  GtkExpression *result;
+
+  g_return_val_if_fail (GTK_IS_BUILDER (scope), NULL);
+  g_return_val_if_fail (string != NULL, NULL);
+
+  bytes = g_bytes_new_static (string, strlen (string));
+  parser = gtk_css_parser_new_for_bytes (bytes,
+                                         NULL,
+                                         NULL,
+                                         gtk_expression_builder_error_forward,
+                                         error,
+                                         NULL);
+
+  result = gtk_expression_parser_parse (scope, parser);
+  if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
+    {
+      gtk_css_parser_error_syntax (parser, "Unexpected junk at end of value");
+      g_clear_pointer (&result, gtk_expression_unref);
+    }
+
+  gtk_css_parser_unref (parser);
+  g_bytes_unref (bytes);
+
+  return result; 
+}
+
+/**
+ * gtk_expression_value_to_string:
+ * @value: a #GValue
+ *
+ * Converts a given #GValue to its string representation.
+ * This operation never fails, but the returned strings may
+ * not be useful.
+ *
+ * Returns: a new string, free with g_free().
+ **/
+char *
+gtk_expression_value_to_string (const GValue *value)
+{
+  g_return_val_if_fail (G_IS_VALUE (value), NULL);
+
+  switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value)))
+    {
+    case G_TYPE_INVALID:
+      return g_strdup ("[invalid]");
+    case G_TYPE_NONE:
+      return g_strdup ("[none]");
+    case G_TYPE_INTERFACE:
+    case G_TYPE_CHAR:
+    case G_TYPE_UCHAR:
+      g_return_val_if_reached (g_strdup ("FIXME"));
+    case G_TYPE_BOOLEAN:
+      return g_strdup (g_value_get_boolean (value) ? "true" : "false");
+    case G_TYPE_INT:
+      return g_strdup_printf ("%d", g_value_get_int (value));
+    case G_TYPE_UINT:
+      return g_strdup_printf ("%u", g_value_get_uint (value));
+    case G_TYPE_LONG:
+      return g_strdup_printf ("%ld", g_value_get_long (value));
+    case G_TYPE_ULONG:
+      return g_strdup_printf ("%lu", g_value_get_ulong (value));
+    case G_TYPE_INT64:
+      return g_strdup_printf ("%" G_GINT64_FORMAT , g_value_get_int64 (value));
+    case G_TYPE_UINT64:
+      return g_strdup_printf ("%" G_GUINT64_FORMAT , g_value_get_uint64 (value));
+    case G_TYPE_ENUM:
+    case G_TYPE_FLAGS:
+      g_return_val_if_reached (g_strdup ("FIXME"));
+    case G_TYPE_FLOAT:
+      return g_ascii_dtostr (g_malloc (G_ASCII_DTOSTR_BUF_SIZE),
+                             G_ASCII_DTOSTR_BUF_SIZE,
+                             g_value_get_float (value));
+    case G_TYPE_DOUBLE:
+      return g_ascii_dtostr (g_malloc (G_ASCII_DTOSTR_BUF_SIZE),
+                             G_ASCII_DTOSTR_BUF_SIZE,
+                             g_value_get_double (value));
+    case G_TYPE_STRING:
+      return g_value_dup_string (value);
+    case G_TYPE_POINTER:
+      return g_strdup_printf ("[0x%p]", g_value_get_pointer (value));
+    case G_TYPE_BOXED:
+      return g_strdup_printf ("[%s|%p]", G_VALUE_TYPE_NAME (value), g_value_get_boxed (value));
+    case G_TYPE_PARAM:
+      return g_strdup_printf ("[%s|%p]", G_VALUE_TYPE_NAME (value), g_value_get_param (value));
+    case G_TYPE_OBJECT:
+      return g_strdup_printf ("[%s|%p]", G_VALUE_TYPE_NAME (value), g_value_get_object (value));
+    case G_TYPE_VARIANT:
+      return g_variant_print (g_value_get_variant (value), TRUE);
+    default:
+      return g_strconcat ("[", g_type_name (G_VALUE_TYPE (value)), "]", NULL);
+    }
+}
+
+/**
+ * gtk_expression_value_to_boolean:
+ * @value: a #GValue
+ *
+ * Converts a given #GValue to its boolean value.
+ * Every value has a boolean representation.
+ *
+ * Number types are %TRUE when their value is different
+ * from 0, pointer types are %TRUE when their value
+ * is different from %NULL and unknown and invalid
+ * types are always %FALSE.
+ *
+ * In particular, this means that the empty string "" is %TRUE.
+ *
+ * Returns: the boolean representation of @value.
+ **/
+gboolean
+gtk_expression_value_to_boolean (const GValue *value)
+{
+  g_return_val_if_fail (G_IS_VALUE (value), FALSE);
+
+  switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value)))
+    {
+    case G_TYPE_INVALID:
+    case G_TYPE_NONE:
+      return FALSE;
+    case G_TYPE_INTERFACE:
+      g_return_val_if_reached (FALSE);
+    case G_TYPE_CHAR:
+      return g_value_get_char (value) != 0;
+    case G_TYPE_UCHAR:
+      return g_value_get_uchar (value) != 0;
+    case G_TYPE_BOOLEAN:
+      return g_value_get_boolean (value);
+    case G_TYPE_INT:
+      return g_value_get_int (value) != 0;
+    case G_TYPE_UINT:
+      return g_value_get_uint (value) != 0;
+    case G_TYPE_LONG:
+      return g_value_get_long (value) != 0;
+    case G_TYPE_ULONG:
+      return g_value_get_ulong (value) != 0;
+    case G_TYPE_INT64:
+      return g_value_get_int64 (value) != 0;
+    case G_TYPE_UINT64:
+      return g_value_get_uint64 (value) != 0;
+    case G_TYPE_ENUM:
+      return g_value_get_enum (value) != 0;
+    case G_TYPE_FLAGS:
+      return g_value_get_flags (value) != 0;
+    case G_TYPE_FLOAT:
+      return g_value_get_float (value) != 0;
+    case G_TYPE_DOUBLE:
+      return g_value_get_double (value) != 0;
+    case G_TYPE_STRING:
+      return g_value_get_string (value) != NULL;
+    case G_TYPE_POINTER:
+      return g_value_get_pointer (value) != NULL;
+    case G_TYPE_BOXED:
+      return g_value_get_boxed (value) != NULL;
+    case G_TYPE_PARAM:
+      return g_value_get_param (value) != NULL;
+    case G_TYPE_OBJECT:
+      return g_value_get_object (value) != NULL;
+    case G_TYPE_VARIANT:
+      return g_value_get_variant (value) != NULL;
+    default:
+      return FALSE;
+    }
+}
+
diff --git a/gtk/gtkexpressionprivate.h b/gtk/gtkexpressionprivate.h
new file mode 100644
index 0000000000..6af23807c5
--- /dev/null
+++ b/gtk/gtkexpressionprivate.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * 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.1 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
+ * Lesser 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_EXPRESSION_PRIVATE_H__
+#define __GTK_EXPRESSION_PRIVATE_H__
+
+#include <gtk/gtktypes.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkExpression GtkExpression;
+
+#define GTK_IS_EXPRESSION(expr) ((expr) != NULL)
+
+GtkExpression *         gtk_expression_new_assign               (GObject                        *object,
+                                                                 const char                     *property,
+                                                                 GtkExpression                  *expr);
+
+GtkExpression *         gtk_expression_ref                      (GtkExpression                  *self);
+void                    gtk_expression_unref                    (GtkExpression                  *self);
+
+void                    gtk_expression_print                    (GtkExpression                  *self,
+                                                                 GString                        *string);
+char *                  gtk_expression_to_string                (GtkExpression                  *self);
+GtkExpression *         gtk_expression_parse                    (GtkBuilder                     *scope,
+                                                                 const char                     *string,
+                                                                 GError                        **error);
+
+GType                   gtk_expression_get_value_type           (GtkExpression                  *self);
+gboolean                gtk_expression_evaluate                 (GtkExpression                  *self,
+                                                                 GValue                         *value);
+
+char *                  gtk_expression_value_to_string          (const GValue                   *value);
+gboolean                gtk_expression_value_to_boolean         (const GValue                   *value);
+G_END_DECLS
+
+#endif /* __GTK_EXPRESSION_PRIVATE_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 5dadaaa5af..fac94d2748 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -230,6 +230,7 @@ gtk_public_sources = files([
   'gtkeventcontrollermotion.c',
   'gtkeventcontrollerscroll.c',
   'gtkexpander.c',
+  'gtkexpression.c',
   'gtkfilechooser.c',
   'gtkfilechooserbutton.c',
   'gtkfilechooserdialog.c',


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