[gtk/wip/ebassi/constraint-layout] Add VFL parser for constraints



commit 1e725b66a5d1031f87f687c689724a8ab920a1b0
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Sat Jun 29 18:03:42 2019 +0100

    Add VFL parser for constraints
    
    Constraints can be expressed with a compact syntax, called VFL (visual
    format language).

 gtk/gtkconstraintvflparser.c        | 1226 +++++++++++++++++++++++++++++++++++
 gtk/gtkconstraintvflparserprivate.h |   89 +++
 gtk/meson.build                     |    1 +
 3 files changed, 1316 insertions(+)
---
diff --git a/gtk/gtkconstraintvflparser.c b/gtk/gtkconstraintvflparser.c
new file mode 100644
index 0000000000..0d46784991
--- /dev/null
+++ b/gtk/gtkconstraintvflparser.c
@@ -0,0 +1,1226 @@
+/* gtkconstraintvflparser.c: VFL constraint definition parser
+ *
+ * Copyright 2017  Endless
+ * Copyright 2019  GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * 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/>.
+ */
+
+#include "config.h"
+
+#include "gtkconstraintvflparserprivate.h"
+
+#include <string.h>
+
+typedef enum {
+  VFL_HORIZONTAL,
+  VFL_VERTICAL
+} VflOrientation;
+
+typedef struct {
+  GtkConstraintRelation relation;
+
+  double constant;
+  double multiplier;
+  const char *subject;
+  char *object;
+  const char *attr;
+
+  double priority;
+} VflPredicate;
+
+typedef enum {
+  SPACING_SET = 1 << 0,
+  SPACING_DEFAULT = 1 << 1,
+  SPACING_PREDICATE = 1 << 2
+} VflSpacingFlags;
+
+typedef struct {
+  double size;
+  VflSpacingFlags flags;
+  VflPredicate predicate;
+} VflSpacing;
+
+typedef struct _VflView VflView;
+
+struct _VflView
+{
+  char *name;
+
+  /* Decides which attributes are admissible */
+  VflOrientation orientation;
+
+  /* A set of predicates, which will be used to
+   * set up constraints
+   */
+  GArray *predicates;
+
+  VflSpacing spacing;
+
+  VflView *prev_view;
+  VflView *next_view;
+};
+
+struct _GtkConstraintVflParser
+{
+  char *buffer;
+  gsize buffer_len;
+
+  int error_offset;
+  int error_range;
+
+  int default_spacing[2];
+
+  /* Set<name, double> */
+  GHashTable *metrics_set;
+  /* Set<name, widget> */
+  GHashTable *views_set;
+
+  const char *cursor;
+
+  /* Decides which attributes are admissible */
+  VflOrientation orientation;
+
+  VflView *leading_super;
+  VflView *trailing_super;
+
+  VflView *current_view;
+  VflView *views;
+};
+
+GQuark
+gtk_constraint_vfl_parser_error_quark (void)
+{
+  return g_quark_from_static_string ("gtk-constraint-vfl-parser-error-quark");
+}
+
+GtkConstraintVflParser *
+gtk_constraint_vfl_parser_new (void)
+{
+  GtkConstraintVflParser *res = g_new0 (GtkConstraintVflParser, 1);
+
+  res->default_spacing[VFL_HORIZONTAL] = 8;
+  res->default_spacing[VFL_VERTICAL] = 8;
+
+  res->orientation = VFL_HORIZONTAL;
+
+  return res;
+}
+
+void
+gtk_constraint_vfl_parser_set_default_spacing (GtkConstraintVflParser *parser,
+                                               int hspacing,
+                                               int vspacing)
+{
+  parser->default_spacing[VFL_HORIZONTAL] = hspacing < 0 ? 8 : hspacing;
+  parser->default_spacing[VFL_VERTICAL] = vspacing < 0 ? 8 : vspacing;
+}
+
+void
+gtk_constraint_vfl_parser_set_metrics (GtkConstraintVflParser *parser,
+                                       GHashTable *metrics)
+{
+  parser->metrics_set = metrics;
+}
+
+void
+gtk_constraint_vfl_parser_set_views (GtkConstraintVflParser *parser,
+                                     GHashTable *views)
+{
+  parser->views_set = views;
+}
+
+static int
+get_default_spacing (GtkConstraintVflParser *parser)
+{
+  return parser->default_spacing[parser->orientation];
+}
+
+/* Default attributes, if unnamed, depending on the orientation */
+static const char *default_attribute[2] = {
+  [VFL_HORIZONTAL] = "width",
+  [VFL_VERTICAL] = "height",
+};
+
+static gboolean 
+parse_relation (const char *str,
+                GtkConstraintRelation *relation,
+                char **endptr,
+                GError **error)
+{
+  const char *cur = str;
+
+  if (*cur == '=')
+    {
+      cur += 1;
+
+      if (*cur == '=')
+        {
+          *relation = GTK_CONSTRAINT_RELATION_EQ;
+          *endptr = (char *) cur + 1;
+          return TRUE;
+        }
+
+      goto out;
+    }
+  else if (*cur == '>')
+    {
+      cur += 1;
+
+      if (*cur == '=')
+        {
+          *relation = GTK_CONSTRAINT_RELATION_GE;
+          *endptr = (char *) cur + 1;
+          return TRUE;
+        }
+
+      goto out;
+    }
+  else if (*cur == '<')
+    {
+      cur += 1;
+
+      if (*cur == '=')
+        {
+          *relation = GTK_CONSTRAINT_RELATION_LE;
+          *endptr = (char *) cur + 1;
+          return TRUE;
+        }
+
+      goto out;
+    }
+
+out:
+  g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+               GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_RELATION,
+               "Unknown relation; must be one of '==', '>=', or '<='");
+  return FALSE;
+}
+
+static gboolean 
+has_metric (GtkConstraintVflParser *parser,
+            const char *name)
+{
+  if (parser->metrics_set == NULL)
+    return FALSE;
+
+  return g_hash_table_contains (parser->metrics_set, name);
+}
+
+static gboolean
+has_view (GtkConstraintVflParser *parser,
+          const char *name)
+{
+  if (parser->views_set == NULL)
+    return FALSE;
+
+  if (!g_hash_table_contains (parser->views_set, name))
+    return FALSE;
+
+  return g_hash_table_lookup (parser->views_set, name) != NULL;
+}
+
+/* Valid attributes */
+static const struct {
+  int len;
+  const char *name;
+} valid_attributes[] = {
+  { 5, "width" },
+  { 6, "height" },
+  { 7, "centerX" },
+  { 7, "centerY" },
+  { 3, "top" },
+  { 6, "bottom" },
+  { 4, "left" },
+  { 5, "right" },
+  { 5, "start" },
+  { 3, "end" },
+  { 8, "baseline" }
+};
+
+static char *
+get_offset_to (const char *str,
+               const char *tokens)
+{
+  char *offset = NULL;
+  int n_tokens = strlen (tokens);
+
+  for (int i = 0; i < n_tokens; i++)
+    {
+      if ((offset = strchr (str, tokens[i])) != NULL)
+        break;
+    }
+
+  return offset;
+}
+
+static gboolean 
+parse_predicate (GtkConstraintVflParser *parser,
+                 const char *cursor,
+                 VflPredicate *predicate,
+                 char **endptr,
+                 GError **error)
+{
+  VflOrientation orientation = parser->orientation;
+  const char *end = cursor;
+
+  predicate->object = NULL;
+  predicate->multiplier = 1.0;
+
+  /*         <predicate> = (<relation>)? (<objectOfPredicate>) ('.'<attribute>)? (<operator>)? 
('@'<priority>)?
+   *          <relation> = '==' | '<=' | '>='
+   * <objectOfPredicate> = <constant> | <viewName>
+   *          <constant> = <number> | <metricName>
+   *          <viewName> = [A-Za-z_]([A-Za-z0-9_]*)
+   *        <metricName> = [A-Za-z_]([A-Za-z0-9_]*)
+   *          <operator> = (['*'|'/']<positiveNumber>)? (['+'|'-']<positiveNumber>)?
+   *          <priority> = <positiveNumber> | 'weak' | 'medium' | 'strong' | 'required'
+   */
+
+  /* Parse relation */
+  if (*end == '=' || *end == '>' || *end == '<')
+    {
+      GtkConstraintRelation relation;
+      char *tmp;
+
+      if (!parse_relation (end, &relation, &tmp, error))
+        {
+          parser->error_offset = end - parser->cursor;
+          parser->error_range = 0;
+          return FALSE;
+        }
+
+      predicate->relation = relation;
+
+      end = tmp;
+    }
+  else
+    predicate->relation = GTK_CONSTRAINT_RELATION_EQ;
+
+  /* Parse object of predicate */
+  if (g_ascii_isdigit (*end))
+    {
+      char *tmp;
+
+      /* <constant> */
+      predicate->object = NULL;
+      predicate->attr = default_attribute[orientation];
+      predicate->constant = g_ascii_strtod (end, &tmp);
+
+      end = tmp;
+    }
+  else if (g_ascii_isalpha (*end) || *end == '_')
+    {
+      const char *name_start = end;
+
+      while (g_ascii_isalnum (*end) || *end == '_')
+        end += 1;
+
+      char *name = g_strndup (name_start, end - name_start);
+
+      /* We only accept view names if the subject of the predicate
+       * is a view, i.e. we do not allow view names inside a spacing
+       * predicate
+       */
+      if (predicate->subject == NULL)
+        {
+          if (parser->metrics_set == NULL || !has_metric (parser, name))
+            {
+              parser->error_offset = name_start - parser->cursor;
+              parser->error_range = end - name_start;
+              g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                           GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_METRIC,
+                           "Unable to find metric with name '%s'", name);
+              g_free (name);
+              return FALSE;
+            }
+
+          double *val = g_hash_table_lookup (parser->metrics_set, name);
+
+          predicate->object = NULL;
+          predicate->attr = default_attribute[orientation];
+          predicate->constant = *val;
+
+          g_free (name);
+
+          goto parse_operators;
+        }
+
+      if (has_metric (parser, name))
+        {
+          double *val = g_hash_table_lookup (parser->metrics_set, name);
+
+          predicate->object = NULL;
+          predicate->attr = default_attribute[orientation];
+          predicate->constant = *val;
+
+          g_free (name);
+
+          goto parse_operators;
+        }
+
+      if (has_view (parser, name))
+        {
+          /* Transfer name's ownership to the predicate */
+          predicate->object = name;
+          predicate->attr = default_attribute[orientation];
+          predicate->constant = 0;
+
+          goto parse_attribute;
+        }
+
+      parser->error_offset = name_start - parser->cursor;
+      parser->error_range = end - name_start;
+      g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                   GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+                   "Unable to find view with name '%s'", name);
+      g_free (name);
+      return FALSE;
+    }
+  else
+    {
+      parser->error_offset = end - parser->cursor;
+      parser->error_range = 0;
+      g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                   GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                   "Expected constant, view name, or metric");
+      return FALSE;
+    }
+
+parse_attribute:
+  if (*end == '.')
+    {
+      end += 1;
+      predicate->attr = NULL;
+
+      for (int i = 0; i < G_N_ELEMENTS (valid_attributes); i++)
+        {
+          if (g_ascii_strncasecmp (valid_attributes[i].name, end, valid_attributes[i].len) == 0)
+            {
+              predicate->attr = valid_attributes[i].name;
+              end += valid_attributes[i].len;
+            }
+        }
+
+      if (predicate->attr == NULL)
+        {
+          char *range_end = get_offset_to (end, "*/+-@,)]");
+
+          if (range_end != NULL)
+            parser->error_range = range_end - end - 1;
+          else
+            parser->error_range = 0;
+
+          g_free (predicate->object);
+
+          parser->error_offset = end - parser->cursor;
+          g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                       GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_ATTRIBUTE,
+                       "Attribute must be on one of 'width', 'height', "
+                       "'centerX', 'centerY', 'top', 'bottom', "
+                       "'left', 'right', 'start', 'end', 'baseline'");
+          return FALSE;
+        }
+    }
+
+parse_operators:
+  /* Parse multiplier operator */
+  while (g_ascii_isspace (*end))
+    end += 1;
+
+  if ((*end == '*') || (*end == '/'))
+    {
+      double multiplier;
+      const char *operator;
+
+      operator = end;
+      end += 1;
+
+      while (g_ascii_isspace (*end))
+        end += 1;
+
+      if (g_ascii_isdigit (*end))
+        {
+          char *tmp;
+
+          multiplier = g_ascii_strtod (end, &tmp);
+          end = tmp;
+        }
+      else
+        {
+          g_free (predicate->object);
+
+          parser->error_offset = end - parser->cursor;
+          parser->error_range = 0;
+          g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                       GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                       "Expected a positive number as a multiplier");
+          return FALSE;
+        }
+
+      if (predicate->object != NULL)
+        {
+          if (*operator == '*')
+            predicate->multiplier = multiplier;
+          else
+            predicate->multiplier = 1.0 / multiplier;
+        }
+      else
+        {
+          /* If the subject is a constant then apply multiplier directly */
+          if (*operator == '*')
+            predicate->constant *= multiplier;
+          else
+            predicate->constant *= 1.0 / multiplier;
+        }
+    }
+
+  /* Parse constant operator */
+  while (g_ascii_isspace (*end))
+    end += 1;
+
+  if ((*end == '+') || (*end == '-'))
+    {
+      double constant;
+      const char *operator;
+
+      operator = end;
+      end += 1;
+
+      while (g_ascii_isspace (*end))
+        end += 1;
+
+      if (g_ascii_isdigit (*end))
+        {
+          char *tmp;
+
+          constant = g_ascii_strtod (end, &tmp);
+          end = tmp;
+        }
+      else
+        {
+          g_free (predicate->object);
+
+          parser->error_offset = end - parser->cursor;
+          parser->error_range = 0;
+          g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                       GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                       "Expected positive number as a constant");
+          return FALSE;
+        }
+
+      if (*operator == '+')
+        predicate->constant += constant;
+      else
+        predicate->constant += -1.0 * constant;
+    }
+
+  /* Parse priority */
+  if (*end == '@')
+    {
+      double priority;
+      end += 1;
+
+      if (g_ascii_isdigit (*end))
+        {
+          char *tmp;
+
+          priority = g_ascii_strtod (end, &tmp);
+          end = tmp;
+        }
+      else if (strncmp (end, "weak", 4) == 0)
+        {
+          priority = GTK_CONSTRAINT_STRENGTH_WEAK;
+          end += 4;
+        }
+      else if (strncmp (end, "medium", 6) == 0)
+        {
+          priority = GTK_CONSTRAINT_STRENGTH_MEDIUM;
+          end += 6;
+        }
+      else if (strncmp (end, "strong", 6) == 0)
+        {
+          priority = GTK_CONSTRAINT_STRENGTH_STRONG;
+          end += 6;
+        }
+      else if (strncmp (end, "required", 8) == 0)
+        {
+          priority = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+          end += 8;
+        }
+      else
+        {
+          char *range_end = get_offset_to (end, ",)]");
+
+          g_free (predicate->object);
+
+          if (range_end != NULL)
+            parser->error_range = range_end - end - 1;
+          else
+            parser->error_range = 0;
+
+          parser->error_offset = end - parser->cursor;
+          g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                       GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_PRIORITY,
+                       "Priority must be a positive number or one of "
+                       "'weak', 'medium', 'strong', and 'required'");
+          return FALSE;
+        }
+
+      predicate->priority = priority;
+    }
+  else
+    predicate->priority = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+
+  if (endptr != NULL)
+    *endptr = (char *) end;
+
+  return TRUE;
+}
+
+static gboolean
+parse_view (GtkConstraintVflParser *parser,
+            const char *cursor,
+            VflView *view,
+            char **endptr,
+            GError **error)
+{
+  const char *end = cursor;
+
+  /*     <view> = '[' <viewName> (<predicateListWithParens>)? ']'
+   * <viewName> = [A-Za-z_]([A-Za-z0-9_]+)
+   */
+
+  g_assert (*end == '[');
+
+  /* Skip '[' */
+  end += 1;
+
+  if (!(g_ascii_isalpha (*end) || *end == '_'))
+    {
+      parser->error_offset = end - parser->cursor;
+      parser->error_range = 0;
+      g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                   GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+                   "View identifiers must be valid C identifiers");
+      return FALSE;
+    }
+
+  while (g_ascii_isalnum (*end))
+    end += 1;
+
+  if (*end == '\0')
+    {
+      parser->error_offset = end - parser->cursor;
+      g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                   GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                   "A view must end with ']'");
+      return FALSE;
+    }
+
+  char *name = g_strndup (cursor + 1, end - cursor - 1);
+  if (!has_view (parser, name))
+    {
+      parser->error_offset = (cursor + 1) - parser->cursor;
+      parser->error_range = end - cursor - 1;
+      g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                   GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+                   "Unable to find view with name '%s'", name);
+      g_free (name);
+      return FALSE;
+    }
+
+  view->name = name;
+  view->predicates = g_array_new (FALSE, FALSE, sizeof (VflPredicate));
+
+  if (*end == ']')
+    {
+      if (endptr != NULL)
+        *endptr = (char *) end + 1;
+
+      return TRUE;
+    }
+
+  /* <predicateListWithParens> = '(' <predicate> (',' <predicate>)* ')' */
+  if (*end != '(')
+    {
+      parser->error_offset = end - parser->cursor;
+      g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                   GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                   "A predicate must follow a view name");
+      return FALSE;
+    }
+
+  end += 1;
+
+  while (*end != '\0')
+    {
+      VflPredicate cur_predicate;
+      char *tmp;
+
+      if (*end == ']' || *end == '\0')
+        {
+          parser->error_offset = end - parser->cursor;
+          g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                       GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                       "A predicate on a view must end with ')'");
+          return FALSE;
+        }
+
+      memset (&cur_predicate, 0, sizeof (VflPredicate));
+
+      cur_predicate.subject = view->name;
+      if (!parse_predicate (parser, end, &cur_predicate, &tmp, error))
+        return FALSE;
+
+      end = tmp;
+
+#ifdef G_ENABLE_DEBUG
+      g_debug ("*** Found predicate: %s.%s %s %g %s (%g)\n",
+               cur_predicate.object != NULL ? cur_predicate.object : view->name,
+               cur_predicate.attr,
+               cur_predicate.relation == GTK_CONSTRAINT_RELATION_EQ ? "==" :
+               cur_predicate.relation == GTK_CONSTRAINT_RELATION_LE ? "<=" :
+               cur_predicate.relation == GTK_CONSTRAINT_RELATION_GE ? ">=" :
+               "unknown relation",
+               cur_predicate.constant,
+               cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_WEAK ? "weak" :
+               cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_MEDIUM ? "medium" :
+               cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_STRONG ? "strong" :
+               cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_REQUIRED ? "required" :
+               "explicit strength",
+               cur_predicate.priority);
+#endif
+
+      g_array_append_val (view->predicates, cur_predicate);
+
+      /* If the predicate is a list, iterate again */
+      if (*end == ',')
+        {
+          end += 1;
+          continue;
+        }
+
+      /* We reached the end of the predicate */
+      if (*end == ')')
+        {
+          end += 1;
+          break;
+        }
+
+      parser->error_offset = end - parser->cursor;
+      g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                   GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                   "Expected ')' at the end of a predicate, not '%c'", *end);
+      return FALSE;
+    }
+
+  if (*end != ']')
+    {
+      parser->error_offset = end - parser->cursor;
+      g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                   GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                   "Expected ']' at the end of a view, not '%c'", *end);
+      return FALSE;
+    }
+
+  if (endptr != NULL)
+    *endptr = (char *) end + 1;
+
+  return TRUE;
+}
+
+static void
+vfl_view_free (VflView *view)
+{
+  if (view == NULL)
+    return;
+
+  g_free (view->name);
+
+  if (view->predicates != NULL)
+    {
+      for (int i = 0; i < view->predicates->len; i++)
+        {
+          VflPredicate *p = &g_array_index (view->predicates, VflPredicate, i);
+
+          g_free (p->object);
+        }
+
+      g_array_free (view->predicates, TRUE);
+      view->predicates = NULL;
+    }
+
+  view->prev_view = NULL;
+  view->next_view = NULL;
+
+  g_slice_free (VflView, view);
+}
+
+static void
+gtk_constraint_vfl_parser_clear (GtkConstraintVflParser *parser)
+{
+  parser->error_offset = 0;
+  parser->error_range = 0;
+
+  VflView *iter = parser->views; 
+  while (iter != NULL)
+    {
+      VflView *next = iter->next_view;
+
+      vfl_view_free (iter);
+
+      iter = next;
+    }
+
+  parser->views = NULL;
+  parser->current_view = NULL;
+  parser->leading_super = NULL;
+  parser->trailing_super = NULL;
+
+  parser->cursor = NULL;
+
+  g_free (parser->buffer);
+  parser->buffer_len = 0;
+}
+
+void
+gtk_constraint_vfl_parser_free (GtkConstraintVflParser *parser)
+{
+  if (parser == NULL)
+    return;
+
+  gtk_constraint_vfl_parser_clear (parser);
+
+  g_free (parser);
+}
+
+gboolean
+gtk_constraint_vfl_parser_parse_line (GtkConstraintVflParser *parser,
+                                      const char *buffer,
+                                      gssize len,
+                                      GError **error)
+{
+  gtk_constraint_vfl_parser_clear (parser);
+
+  if (len > 0)
+    {
+      parser->buffer = g_strndup (buffer, len);
+      parser->buffer_len = len;
+    }
+  else
+    {
+      parser->buffer = g_strdup (buffer);
+      parser->buffer_len = strlen (buffer);
+    }
+
+  parser->cursor = parser->buffer;
+
+  const char *cur = parser->cursor;
+
+  /* Skip leading whitespace */
+  while (g_ascii_isspace (*cur))
+    cur += 1;
+
+  /* Check orientation; if none is specified, then we assume horizontal */
+  parser->orientation = VFL_HORIZONTAL;
+  if (*cur == 'H')
+    {
+      cur += 1;
+
+      if (*cur != ':')
+        {
+          parser->error_offset = cur - parser->cursor;
+          g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                       GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                       "Expected ':' after horizontal orientation");
+          return FALSE;
+        }
+
+      parser->orientation = VFL_HORIZONTAL;
+      cur += 1;
+    }
+  else if (*cur == 'V')
+    {
+      cur += 1;
+
+      if (*cur != ':')
+        {
+          parser->error_offset = cur - parser->cursor;
+          g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                       GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                       "Expected ':' after vertical orientation");
+          return FALSE;
+        }
+
+      parser->orientation = VFL_VERTICAL;
+      cur += 1;
+    }
+
+  while (*cur != '\0')
+    {
+      /* Super-view */
+      if (*cur == '|')
+        {
+          if (parser->views == NULL && parser->leading_super == NULL)
+            {
+              parser->leading_super = g_slice_new0 (VflView);
+
+              parser->leading_super->name = g_strdup ("super");
+              parser->leading_super->orientation = parser->orientation;
+
+              parser->current_view = parser->leading_super;
+              parser->views = parser->leading_super;
+            }
+          else if (parser->trailing_super == NULL)
+            {
+              parser->trailing_super = g_slice_new0 (VflView);
+
+              parser->trailing_super->name = g_strdup ("super");
+              parser->trailing_super->orientation = parser->orientation;
+
+              parser->current_view->next_view = parser->trailing_super;
+              parser->trailing_super->prev_view = parser->current_view;
+
+              parser->current_view = parser->trailing_super;
+
+              /* If we reached the second '|' then we completed a line
+               * of layout, and we can stop
+               */
+              break;
+            }
+          else
+            {
+              parser->error_offset = cur - parser->cursor;
+              g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                           GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                           "Super views can only appear at the beginning "
+                           "and end of the layout, and not multiple times");
+              return FALSE;
+            }
+
+          cur += 1;
+
+          continue;
+        }
+
+      /* Spacing */
+      if (*cur == '-')
+        {
+          if (*(cur + 1) == '\0')
+            {
+              parser->error_offset = cur - parser->cursor;
+              g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                           GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                           "Unterminated spacing");
+              return FALSE;
+            }
+
+          if (parser->current_view == NULL)
+            {
+              parser->error_offset = cur - parser->cursor;
+              g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                           GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                           "Spacing cannot be set without a view");
+              return FALSE;
+            }
+
+          if (*(cur + 1) == '|' || *(cur + 1) == '[')
+            {
+              VflSpacing *spacing = &(parser->current_view->spacing);
+
+              /* Default spacer */
+              spacing->flags = SPACING_SET | SPACING_DEFAULT;
+              spacing->size = 0;
+
+              cur += 1;
+
+              continue;
+            }
+          else if (*(cur + 1) == '(')
+            {
+              VflPredicate *predicate;
+              VflSpacing *spacing;
+              char *tmp;
+
+              /* Predicate */
+              cur += 1;
+
+              spacing = &(parser->current_view->spacing);
+              spacing->flags = SPACING_SET | SPACING_PREDICATE;
+              spacing->size = 0;
+
+              /* Spacing predicates have no subject */
+              predicate = &(spacing->predicate);
+              predicate->subject = NULL;
+
+              cur += 1;
+              if (!parse_predicate (parser, cur, predicate, &tmp, error))
+                return FALSE;
+
+              if (*tmp != ')')
+                {
+                  g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                               GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                               "Expected ')' at the end of a predicate, not '%c'", *tmp);
+                  return FALSE;
+                }
+
+              cur = tmp + 1;
+              if (*cur != '-')
+                {
+                  g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                               GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                               "Explicit spacing must be followed by '-'");
+                  return FALSE;
+                }
+
+              cur += 1;
+
+              continue;
+            }
+          else if (g_ascii_isdigit (*(cur + 1)))
+            {
+              VflSpacing *spacing;
+              char *tmp;
+
+              /* Explicit spacing */
+              cur += 1;
+
+              spacing = &(parser->current_view->spacing);
+              spacing->flags = SPACING_SET;
+              spacing->size = g_ascii_strtod (cur, &tmp);
+
+              if (tmp == cur)
+                {
+                  parser->error_offset = cur - parser->cursor;
+                  g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                               GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                               "Spacing must be a number");
+                  return FALSE;
+                }
+
+              if (*tmp != '-')
+                {
+                  parser->error_offset = cur - parser->cursor;
+                  parser->error_range = tmp - cur;
+                  g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                               GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                               "Explicit spacing must be followed by '-'");
+                  return FALSE;
+                }
+
+              cur = tmp + 1;
+
+              continue;
+            }
+          else
+            {
+              parser->error_offset = cur - parser->cursor;
+              g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+                           GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+                           "Spacing can either be '-' or a number");
+              return FALSE;
+            }
+        }
+
+      if (*cur == '[')
+        {
+          VflView *view = g_slice_new0 (VflView);
+          char *tmp;
+
+          view->orientation = parser->orientation;
+
+          if (!parse_view (parser, cur, view, &tmp, error))
+            {
+              vfl_view_free (view);
+              return FALSE;
+            }
+
+          cur = tmp;
+
+          if (parser->views == NULL)
+            parser->views = view;
+
+          view->prev_view = parser->current_view;
+
+          if (parser->current_view != NULL)
+            parser->current_view->next_view = view;
+
+          parser->current_view = view;
+
+          continue;
+        }
+
+      cur += 1;
+    }
+
+  return TRUE;
+}
+
+GtkConstraintVfl *
+gtk_constraint_vfl_parser_get_constraints (GtkConstraintVflParser *parser,
+                                           int *n_constraints)
+{
+  GArray *constraints;
+  VflView *iter;
+
+  constraints = g_array_new (FALSE, FALSE, sizeof (GtkConstraintVfl));
+
+  iter = parser->views;
+  while (iter != NULL)
+    {
+      GtkConstraintVfl c;
+
+      if (iter->predicates != NULL)
+        {
+          for (int i = 0; i < iter->predicates->len; i++)
+            {
+              const VflPredicate *p = &g_array_index (iter->predicates, VflPredicate, i);
+
+              c.view1 = iter->name;
+              c.attr1 = iter->orientation == VFL_HORIZONTAL ? "width" : "height";
+              if (p->object != NULL)
+                {
+                  c.view2 = p->object;
+                  c.attr2 = p->attr;
+                }
+              else
+                {
+                  c.view2 = NULL;
+                  c.attr2 = NULL;
+                }
+              c.relation = p->relation;
+              c.constant = p->constant;
+              c.multiplier = p->multiplier;
+              c.strength = p->priority;
+
+              g_array_append_val (constraints, c);
+            }
+        }
+
+      if ((iter->spacing.flags & SPACING_SET) != 0)
+        {
+          c.view1 = iter->name;
+
+          /* If this is the first view, we need to anchor the leading edge */
+          if (iter == parser->leading_super)
+            c.attr1 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+          else
+            c.attr1 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+
+          c.view2 = iter->next_view != NULL ? iter->next_view->name : "super";
+
+          if (iter == parser->trailing_super || iter->next_view == parser->trailing_super)
+            c.attr2 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+          else
+            c.attr2 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+
+          if ((iter->spacing.flags & SPACING_PREDICATE) != 0)
+            {
+              const VflPredicate *p = &(iter->spacing.predicate);
+
+              c.constant = p->constant * -1.0;
+              c.relation = p->relation;
+              c.strength = p->priority;
+            }
+          else if ((iter->spacing.flags & SPACING_DEFAULT) != 0)
+            {
+              c.constant = get_default_spacing (parser) * -1.0;
+              c.relation = GTK_CONSTRAINT_RELATION_EQ;
+              c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+            }
+          else
+            {
+              c.constant = iter->spacing.size * -1.0;
+              c.relation = GTK_CONSTRAINT_RELATION_EQ;
+              c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+            }
+
+          c.multiplier = 1.0;
+
+          g_array_append_val (constraints, c);
+        }
+      else if (iter->next_view != NULL)
+        {
+          c.view1 = iter->name;
+
+          /* If this is the first view, we need to anchor the leading edge */
+          if (iter == parser->leading_super)
+            c.attr1 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+          else
+            c.attr1 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+
+          c.relation = GTK_CONSTRAINT_RELATION_EQ;
+
+          c.view2 = iter->next_view->name;
+
+          if (iter->next_view == parser->trailing_super)
+            c.attr2 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+          else
+            c.attr2 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+
+          c.constant = 0.0;
+          c.multiplier = 1.0;
+          c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+
+          g_array_append_val (constraints, c);
+        }
+
+      iter = iter->next_view;
+    }
+
+  if (n_constraints != NULL)
+    *n_constraints = constraints->len;
+
+#ifdef G_ENABLE_DEBUG
+  for (int i = 0; i < constraints->len; i++)
+    {
+      const GtkConstraintVfl *c = &g_array_index (constraints, GtkConstraintVfl, i);
+
+      g_debug ("{\n"
+               "  .view1: '%s',\n"
+               "  .attr1: '%s',\n"
+               "  .relation: '%d',\n"
+               "  .view2: '%s',\n"
+               "  .attr2: '%s',\n"
+               "  .constant: %g,\n"
+               "  .multiplier: %g,\n"
+               "  .strength: %g\n"
+               "}\n",
+               c->view1, c->attr1,
+               c->relation,
+               c->view2 != NULL ? c->view2 : "none", c->attr2 != NULL ? c->attr2 : "none",
+               c->constant,
+               c->multiplier,
+               c->strength);
+    }
+#endif
+
+  return (GtkConstraintVfl *) g_array_free (constraints, FALSE);
+}
+
+int
+gtk_constraint_vfl_parser_get_error_offset (GtkConstraintVflParser *parser)
+{
+  return parser->error_offset;
+}
+
+int
+gtk_constraint_vfl_parser_get_error_range (GtkConstraintVflParser *parser)
+{
+  return parser->error_range;
+}
diff --git a/gtk/gtkconstraintvflparserprivate.h b/gtk/gtkconstraintvflparserprivate.h
new file mode 100644
index 0000000000..a89b474893
--- /dev/null
+++ b/gtk/gtkconstraintvflparserprivate.h
@@ -0,0 +1,89 @@
+/* gtkconstraintvflparserprivate.h: VFL constraint definition parser 
+ *
+ * Copyright 2017  Endless
+ * Copyright 2019  GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include "gtkconstrainttypesprivate.h"
+
+G_BEGIN_DECLS
+
+#define GTK_CONSTRAINT_VFL_PARSER_ERROR (gtk_constraint_vfl_parser_error_quark ())
+
+typedef enum {
+  GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+  GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_ATTRIBUTE,
+  GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+  GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_METRIC,
+  GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_PRIORITY,
+  GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_RELATION
+} VflError;
+
+typedef struct _GtkConstraintVflParser       GtkConstraintVflParser;
+
+typedef struct {
+  const char *view1;
+  const char *attr1;
+  GtkConstraintRelation relation;
+  const char *view2;
+  const char *attr2;
+  double constant;
+  double multiplier;
+  double strength;
+} GtkConstraintVfl;
+
+GQuark gtk_constraint_vfl_parser_error_quark (void);
+
+GtkConstraintVflParser *
+gtk_constraint_vfl_parser_new (void);
+
+void
+gtk_constraint_vfl_parser_free (GtkConstraintVflParser *parser);
+
+void
+gtk_constraint_vfl_parser_set_default_spacing (GtkConstraintVflParser *parser,
+                                               int hspacing,
+                                               int vspacing);
+
+void
+gtk_constraint_vfl_parser_set_metrics (GtkConstraintVflParser *parser,
+                                       GHashTable *metrics);
+
+void
+gtk_constraint_vfl_parser_set_views (GtkConstraintVflParser *parser,
+                                     GHashTable *views);
+
+gboolean
+gtk_constraint_vfl_parser_parse_line (GtkConstraintVflParser *parser,
+                                      const char *line,
+                                      gssize len,
+                                      GError **error);
+
+int
+gtk_constraint_vfl_parser_get_error_offset (GtkConstraintVflParser *parser);
+
+int
+gtk_constraint_vfl_parser_get_error_range (GtkConstraintVflParser *parser);
+
+GtkConstraintVfl *
+gtk_constraint_vfl_parser_get_constraints (GtkConstraintVflParser *parser,
+                                           int *n_constraints);
+
+G_END_DECLS
diff --git a/gtk/meson.build b/gtk/meson.build
index a79e3e7ecc..0b60ce22e4 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -39,6 +39,7 @@ gtk_private_sources = files([
   'gtkcolorswatch.c',
   'gtkconstraintexpression.c',
   'gtkconstraintsolver.c',
+  'gtkconstraintvflparser.c',
   'gtkcssanimatedstyle.c',
   'gtkcssanimation.c',
   'gtkcssarrayvalue.c',



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