[glib] GVariant: add way to print context of parse errors



commit 56fb675d865d4331037bd95f57ce6f381564fe27
Author: Ryan Lortie <desrt desrt ca>
Date:   Sun Dec 22 11:41:19 2013 -0500

    GVariant: add way to print context of parse errors
    
    This was a feature intended from the very beginning that somehow never
    got written.  It's a way to replace these sort of error messages out of
    the GVariant parser:
    
      1-2,10-15:unable to find a common type
    
    with something in the style of the Vala compiler:
    
      unable to find a common type:
        [1, 2, 3, 'str']
         ^        ^^^^^
    
    https://bugzilla.gnome.org/show_bug.cgi?id=715028

 docs/reference/glib/glib-sections.txt |    1 +
 glib/gvariant-parser.c                |  222 +++++++++++++++++++++++++++++++++
 glib/gvariant.h                       |    4 +
 3 files changed, 227 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 48f4115..84b2f4b 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -3229,6 +3229,7 @@ G_VARIANT_PARSE_ERROR
 g_variant_parse
 g_variant_new_parsed_va
 g_variant_new_parsed
+g_variant_parse_error_print_context
 
 <SUBSECTION Private>
 g_variant_parse_error_quark
diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c
index 44bec57..4aa8389 100644
--- a/glib/gvariant-parser.c
+++ b/glib/gvariant-parser.c
@@ -2558,3 +2558,225 @@ g_variant_builder_add_parsed (GVariantBuilder *builder,
   g_variant_builder_add_value (builder, g_variant_new_parsed_va (format, &ap));
   va_end (ap);
 }
+
+static gboolean
+parse_num (const gchar *num,
+           const gchar *limit,
+           gint        *result)
+{
+  gchar *endptr;
+  gint64 bignum;
+
+  bignum = g_ascii_strtoll (num, &endptr, 10);
+
+  if (endptr != limit)
+    return FALSE;
+
+  if (bignum < 0 || bignum > G_MAXINT)
+    return FALSE;
+
+  *result = bignum;
+
+  return TRUE;
+}
+
+static void
+add_last_line (GString     *err,
+               const gchar *str)
+{
+  const gchar *last_nl;
+  gchar *chomped;
+  gint i;
+
+  /* This is an error at the end of input.  If we have a file
+   * with newlines, that's probably the empty string after the
+   * last newline, which is not the most useful thing to show.
+   *
+   * Instead, show the last line of non-whitespace that we have
+   * and put the pointer at the end of it.
+   */
+  chomped = g_strchomp (g_strdup (str));
+  last_nl = strrchr (chomped, '\n');
+  if (last_nl == NULL)
+    last_nl = chomped;
+  else
+    last_nl++;
+
+  /* Print the last line like so:
+   *
+   *   [1, 2, 3,
+   *            ^
+   */
+  g_string_append (err, "  ");
+  if (last_nl[0])
+    g_string_append (err, last_nl);
+  else
+    g_string_append (err, "(empty input)");
+  g_string_append (err, "\n  ");
+  for (i = 0; last_nl[i]; i++)
+    g_string_append_c (err, ' ');
+  g_string_append (err, "^\n");
+  g_free (chomped);
+}
+
+static void
+add_lines_from_range (GString     *err,
+                      const gchar *str,
+                      const gchar *start1,
+                      const gchar *end1,
+                      const gchar *start2,
+                      const gchar *end2)
+{
+  while (str < end1 || str < end2)
+    {
+      const gchar *nl;
+
+      nl = str + strcspn (str, "\n");
+
+      if ((start1 < nl && str < end1) || (start2 < nl && str < end2))
+        {
+          const gchar *s;
+
+          /* We're going to print this line */
+          g_string_append (err, "  ");
+          g_string_append_len (err, str, nl - str);
+          g_string_append (err, "\n  ");
+
+          /* And add underlines... */
+          for (s = str; s < nl; s++)
+            {
+              if ((start1 <= s && s < end1) || (start2 <= s && s < end2))
+                g_string_append_c (err, '^');
+              else
+                g_string_append_c (err, ' ');
+            }
+          g_string_append_c (err, '\n');
+        }
+
+      if (!*nl)
+        break;
+
+      str = nl + 1;
+    }
+}
+
+/**
+ * g_variant_parse_error_print_context:
+ * @error: a #GError from the #GVariantParseError domain
+ * @source_str: the string that was given to the parser
+ *
+ * Pretty-prints a message showing the context of a #GVariant parse
+ * error within the string for which parsing was attempted.
+ *
+ * The resulting string is suitable for output to the console or other
+ * monospace media where newlines are treated in the usual way.
+ *
+ * The message will typically look something like one of the following:
+ *
+ * |[
+ * unterminated string constant:
+ *   (1, 2, 3, 'abc
+ *             ^^^^
+ * ]|
+ *
+ * or
+ *
+ * |[
+ * unable to find a common type:
+ *   [1, 2, 3, 'str']
+ *    ^        ^^^^^
+ * ]|
+ *
+ * The format of the message may change in a future version.
+ *
+ * @error must have come from a failed attempt to g_variant_parse() and
+ * @source_str must be exactly the same string that caused the error.
+ * If @source_str was not nul-terminated when you passed it to
+ * g_variant_parse() then you must add nul termination before using this
+ * function.
+ *
+ * Returns: (transfer full): the printed message
+ *
+ * Since: 2.40
+ **/
+gchar *
+g_variant_parse_error_print_context (GError      *error,
+                                     const gchar *source_str)
+{
+  const gchar *colon, *dash, *comma;
+  gboolean success = FALSE;
+  GString *err;
+
+  g_return_val_if_fail (error->domain == G_VARIANT_PARSE_ERROR, FALSE);
+
+  /* We can only have a limited number of possible types of ranges
+   * emitted from the parser:
+   *
+   *  - a:          -- usually errors from the tokeniser (eof, invalid char, etc.)
+   *  - a-b:        -- usually errors from handling one single token
+   *  - a-b,c-d:    -- errors involving two tokens (ie: type inferencing)
+   *
+   * We never see, for example "a,c".
+   */
+
+  colon = strchr (error->message, ':');
+  dash = strchr (error->message, '-');
+  comma = strchr (error->message, ',');
+
+  if (!colon)
+    return NULL;
+
+  err = g_string_new (colon + 1);
+  g_string_append (err, ":\n");
+
+  if (dash == NULL || colon < dash)
+    {
+      gint point;
+
+      /* we have a single point */
+      if (!parse_num (error->message, colon, &point))
+        goto out;
+
+      if (point >= strlen (source_str))
+        /* the error is at the end if the input */
+        add_last_line (err, source_str);
+      else
+        /* otherwise just treat it as a error at a thin range */
+        add_lines_from_range (err, source_str, source_str + point, source_str + point + 1, NULL, NULL);
+    }
+  else
+    {
+      /* We have one or two ranges... */
+      if (comma && comma < colon)
+        {
+          gint start1, end1, start2, end2;
+          const gchar *dash2;
+
+          /* Two ranges */
+          dash2 = strchr (comma, '-');
+
+          if (!parse_num (error->message, dash, &start1) || !parse_num (dash + 1, comma, &end1) ||
+              !parse_num (comma + 1, dash2, &start2) || !parse_num (dash2 + 1, colon, &end2))
+            goto out;
+
+          add_lines_from_range (err, source_str,
+                                source_str + start1, source_str + end1,
+                                source_str + start2, source_str + end2);
+        }
+      else
+        {
+          gint start, end;
+
+          /* One range */
+          if (!parse_num (error->message, dash, &start) || !parse_num (dash + 1, colon, &end))
+            goto out;
+
+          add_lines_from_range (err, source_str, source_str + start, source_str + end, NULL, NULL);
+        }
+    }
+
+  success = TRUE;
+
+out:
+  return g_string_free (err, !success);
+}
diff --git a/glib/gvariant.h b/glib/gvariant.h
index 7baff89..e214f5e 100644
--- a/glib/gvariant.h
+++ b/glib/gvariant.h
@@ -395,6 +395,10 @@ GLIB_AVAILABLE_IN_ALL
 GVariant *                      g_variant_new_parsed_va                 (const gchar          *format,
                                                                          va_list              *app);
 
+GLIB_AVAILABLE_IN_2_40
+gchar *                         g_variant_parse_error_print_context     (GError               *error,
+                                                                         const gchar          *source_str);
+
 GLIB_AVAILABLE_IN_ALL
 gint                            g_variant_compare                       (gconstpointer one,
                                                                          gconstpointer two);


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