From 56fb675d865d4331037bd95f57ce6f381564fe27 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Sun, 22 Dec 2013 11:41:19 -0500 Subject: [PATCH] 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(+) diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 48f41156b..84b2f4b55 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 g_variant_parse_error_quark diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c index 44bec579e..4aa838956 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 7baff8950..e214f5eba 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);