From bf4dbdbf0e1a3ac4349980942b9e91056ce7e448 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Sun, 21 Mar 2010 12:31:46 -0500 Subject: [PATCH] merge GVariant parser --- docs/reference/glib/glib-sections.txt | 7 + glib/Makefile.am | 1 + glib/glib.symbols | 7 + glib/gvariant-parser.c | 2185 +++++++++++++++++++++++++++++++++ glib/gvariant.c | 28 +- glib/gvariant.h | 13 + glib/tests/gvariant.c | 249 +++- 7 files changed, 2473 insertions(+), 17 deletions(-) create mode 100644 glib/gvariant-parser.c diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 05b06cd..860e8b8 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -2894,7 +2894,14 @@ g_variant_builder_end g_variant_builder_open g_variant_builder_close + +G_VARIANT_PARSE_ERROR +g_variant_parse +g_variant_new_parsed_va +g_variant_new_parsed + +g_variant_parser_get_error g_variant_type_checked_ diff --git a/glib/Makefile.am b/glib/Makefile.am index 0654503..9428f62 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -179,6 +179,7 @@ libglib_2_0_la_SOURCES = \ gvariant-core.h \ gvariant-core.c \ gvariant-internal.h \ + gvariant-parser.c \ gvariant-serialiser.h \ gvariant-serialiser.c \ gvarianttypeinfo.h \ diff --git a/glib/glib.symbols b/glib/glib.symbols index ee9da31..f9a0d8d 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -1794,6 +1794,13 @@ g_variant_new_from_data g_variant_get_normal_form g_variant_byteswap #endif + +#if IN_FILE(__G_VARIANT_PARSER_C__) +g_variant_new_parsed +g_variant_new_parsed_va +g_variant_parse +g_variant_parser_get_error_quark +#endif #endif #if IN_HEADER(__G_VARIANT_TYPE_INFO_H__) diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c new file mode 100644 index 0000000..2e35d05 --- /dev/null +++ b/glib/gvariant-parser.c @@ -0,0 +1,2185 @@ +/* + * Copyright © 2009, 2010 Codethink Limited + * + * 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 of the licence, 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#include +#include +#include +#include + +#include "galias.h" + +/* + * two-pass algorithm + * designed by ryan lortie and william hua + * designed in itb-229 and at ghazi's, 2009. + */ + +GQuark +g_variant_parser_get_error_quark (void) +{ + static GQuark the_quark; + + if (the_quark == 0) + the_quark = g_quark_from_static_string ("g-variant-parse-error-quark"); + + return the_quark; +} + +typedef struct +{ + gint start, end; +} SourceRef; + +static void +parser_set_error_va (GError **error, + SourceRef *location, + SourceRef *other, + const gchar *format, + va_list ap) +{ + GString *msg = g_string_new (NULL); + + if (location->start == location->end) + g_string_append_printf (msg, "%d", location->start); + else + g_string_append_printf (msg, "%d-%d", location->start, location->end); + + if (other != NULL) + { + g_assert (other->start != other->end); + g_string_append_printf (msg, ",%d-%d", other->start, other->end); + } + g_string_append_c (msg, ':'); + + g_string_append_vprintf (msg, format, ap); + g_set_error_literal (error, G_VARIANT_PARSE_ERROR, 0, msg->str); + g_string_free (msg, TRUE); +} + +static void +parser_set_error (GError **error, + SourceRef *location, + SourceRef *other, + const gchar *format, + ...) +{ + va_list ap; + + va_start (ap, format); + parser_set_error_va (error, location, other, format, ap); + va_end (ap); +} + +typedef struct +{ + const gchar *start; + const gchar *stream; + const gchar *end; + + const gchar *this; +} TokenStream; + + +static void +token_stream_set_error (TokenStream *stream, + GError **error, + gboolean this_token, + const gchar *format, + ...) +{ + SourceRef ref; + va_list ap; + + ref.start = stream->this - stream->start; + + if (this_token) + ref.end = stream->stream - stream->start; + else + ref.end = ref.start; + + va_start (ap, format); + parser_set_error_va (error, &ref, NULL, format, ap); + va_end (ap); +} + +static void +token_stream_prepare (TokenStream *stream) +{ + gint brackets = 0; + const gchar *end; + + if (stream->this != NULL) + return; + + while (stream->stream != stream->end && g_ascii_isspace (*stream->stream)) + stream->stream++; + + if (stream->stream == stream->end || *stream->stream == '\0') + { + stream->this = stream->stream; + return; + } + + switch (stream->stream[0]) + { + case '-': case '+': case '.': case '0': case '1': case '2': + case '3': case '4': case '5': case '6': case '7': case '8': + case '9': + for (end = stream->stream; end != stream->end; end++) + if (!g_ascii_isalnum (*end) && + *end != '-' && *end != '+' && *end != '.') + break; + break; + + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + for (end = stream->stream; end != stream->end; end++) + if (!g_ascii_isalnum (*end)) + break; + break; + + case '@': case '%': + /* stop at the first space, comma or unmatched bracket. + * deals nicely with cases like (%i, %i). + */ + for (end = stream->stream + 1; + end != stream->end && *end != ',' && !g_ascii_isspace (*end); + end++) + + if (*end == '(' || *end == '{') + brackets++; + + else if ((*end == ')' || *end == '}') && !brackets--) + break; + + break; + + case '\'': case '"': + for (end = stream->stream + 1; end != stream->end; end++) + if (*end == stream->stream[0] || *end == '\0' || + (*end == '\\' && (++end == stream->end || *end == '\0'))) + break; + + if (end != stream->end && *end) + end++; + break; + + default: + end = stream->stream + 1; + break; + } + + stream->this = stream->stream; + stream->stream = end; +} + +static void +token_stream_next (TokenStream *stream) +{ + stream->this = NULL; +} + +static gboolean +token_stream_peek (TokenStream *stream, + gchar first_char) +{ + token_stream_prepare (stream); + + return stream->this[0] == first_char; +} + +static gboolean +token_stream_is_keyword (TokenStream *stream) +{ + token_stream_prepare (stream); + + return g_ascii_isalpha (stream->this[0]); +} + +static gboolean +token_stream_is_numeric (TokenStream *stream) +{ + token_stream_prepare (stream); + + return (g_ascii_isdigit (stream->this[0]) || + stream->this[0] == '-' || + stream->this[0] == '+' || + stream->this[0] == '.'); +} + +static gboolean +token_stream_consume (TokenStream *stream, + const gchar *token) +{ + gint length = strlen (token); + + token_stream_prepare (stream); + + if (stream->stream - stream->this == length && + memcmp (stream->this, token, length) == 0) + { + token_stream_next (stream); + return TRUE; + } + + return FALSE; +} + +static gboolean +token_stream_require (TokenStream *stream, + const gchar *token, + const gchar *purpose, + GError **error) +{ + + if (!token_stream_consume (stream, token)) + { + token_stream_set_error (stream, error, FALSE, + "expected `%s'%s", token, purpose); + return FALSE; + } + + return TRUE; +} + +static void +token_stream_assert (TokenStream *stream, + const gchar *token) +{ + gboolean correct_token; + + correct_token = token_stream_consume (stream, token); + g_assert (correct_token); +} + +static gchar * +token_stream_get (TokenStream *stream) +{ + gchar *result; + + token_stream_prepare (stream); + + result = g_strndup (stream->this, stream->stream - stream->this); + + return result; +} + +static void +token_stream_start_ref (TokenStream *stream, + SourceRef *ref) +{ + token_stream_prepare (stream); + ref->start = stream->this - stream->start; +} + +static void +token_stream_end_ref (TokenStream *stream, + SourceRef *ref) +{ + ref->end = stream->stream - stream->start; +} + +void +pattern_copy (gchar **out, + const gchar **in) +{ + gint brackets = 0; + + while (**in == 'a' || **in == 'm' || **in == 'M') + *(*out)++ = *(*in)++; + + do + { + if (**in == '(' || **in == '{') + brackets++; + + else if (**in == ')' || **in == '}') + brackets--; + + *(*out)++ = *(*in)++; + } + while (brackets); +} + +static gchar * +pattern_coalesce (const gchar *left, + const gchar *right) +{ + gchar *result; + gchar *out; + + /* the length of the output is loosely bound by the sum of the input + * lengths, not simply the greater of the two lengths. + * + * (*(iii)) + ((iii)*) ((iii)(iii)) + * + * 8 + 8 = 12 + */ + out = result = g_malloc (strlen (left) + strlen (right)); + + while (*left && *right) + { + if (*left == *right) + { + *out++ = *left++; + right++; + } + + else + { + const gchar **one = &left, **the_other = &right; + + again: + if (**one == '*' && **the_other != ')') + { + pattern_copy (&out, the_other); + (*one)++; + } + + else if (**one == 'M' && **the_other == 'm') + { + *out++ = *(*the_other)++; + } + + else if (**one == 'M' && **the_other != 'm') + { + (*one)++; + } + + else if (**one == 'N' && strchr ("ynqiuxthd", **the_other)) + { + *out++ = *(*the_other)++; + (*one)++; + } + + else if (**one == 'S' && strchr ("sog", **the_other)) + { + *out++ = *(*the_other)++; + (*one)++; + } + + else if (one == &left) + { + one = &right, the_other = &left; + goto again; + } + + else + break; + } + } + + if (*left || *right) + { + g_free (result); + result = NULL; + } + else + *out++ = '\0'; + + return result; +} + +typedef struct _AST AST; +typedef gchar * (*get_pattern_func) (AST *ast, + GError **error); +typedef GVariant * (*get_value_func) (AST *ast, + const GVariantType *type, + GError **error); +typedef GVariant * (*get_base_value_func) (AST *ast, + const GVariantType *type, + GError **error); +typedef void (*free_func) (AST *ast); + +typedef struct +{ + gchar * (* get_pattern) (AST *ast, + GError **error); + GVariant * (* get_value) (AST *ast, + const GVariantType *type, + GError **error); + GVariant * (* get_base_value) (AST *ast, + const GVariantType *type, + GError **error); + void (* free) (AST *ast); +} ASTClass; + +struct _AST +{ + const ASTClass *class; + SourceRef source_ref; +}; + +static gchar * +ast_get_pattern (AST *ast, + GError **error) +{ + return ast->class->get_pattern (ast, error); +} + +static GVariant * +ast_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + return ast->class->get_value (ast, type, error); +} + +static void +ast_free (AST *ast) +{ + ast->class->free (ast); +} + +static void +ast_set_error (AST *ast, + GError **error, + AST *other_ast, + const gchar *format, + ...) +{ + va_list ap; + + va_start (ap, format); + parser_set_error_va (error, &ast->source_ref, + other_ast ? & other_ast->source_ref : NULL, + format, ap); + va_end (ap); +} + +static GVariant * +ast_type_error (AST *ast, + const GVariantType *type, + GError **error) +{ + gchar *typestr; + + typestr = g_variant_type_dup_string (type); + ast_set_error (ast, error, NULL, + "can not parse as value of type `%s'", + typestr); + g_free (typestr); + + return NULL; +} + +static GVariant * +ast_resolve (AST *ast, + GError **error) +{ + GVariant *value; + gchar *pattern; + gint i, j = 0; + + pattern = ast_get_pattern (ast, error); + + if (pattern == NULL) + return NULL; + + /* choose reasonable defaults + * + * 1) favour non-maybe values where possible + * 2) default type for strings is 's' + * 3) default type for integers is 'i' + */ + for (i = 0; pattern[i]; i++) + switch (pattern[i]) + { + case '*': + ast_set_error (ast, error, NULL, "unable to infer type"); + g_free (pattern); + return NULL; + + case 'M': + break; + + case 'S': + pattern[j++] = 's'; + break; + + case 'N': + pattern[j++] = 'i'; + break; + + default: + pattern[j++] = pattern[i]; + break; + } + pattern[j++] = '\0'; + + value = ast_get_value (ast, G_VARIANT_TYPE (pattern), error); + g_free (pattern); + + return value; +} + + +static AST *parse (TokenStream *stream, + va_list *app, + GError **error); + +static void +ast_array_append (AST ***array, + gint *n_items, + AST *ast) +{ + if ((*n_items & (*n_items - 1)) == 0) + *array = g_renew (AST *, *array, *n_items ? 2 ** n_items : 1); + + (*array)[(*n_items)++] = ast; +} + +static void +ast_array_free (AST **array, + gint n_items) +{ + gint i; + + for (i = 0; i < n_items; i++) + ast_free (array[i]); + g_free (array); +} + +static gchar * +ast_array_get_pattern (AST **array, + gint n_items, + GError **error) +{ + gchar *pattern; + gint i; + + pattern = ast_get_pattern (array[0], error); + + if (pattern == NULL) + return NULL; + + for (i = 1; i < n_items; i++) + { + gchar *tmp, *merged; + + tmp = ast_get_pattern (array[i], error); + + if (tmp == NULL) + { + g_free (pattern); + return NULL; + } + + merged = pattern_coalesce (pattern, tmp); + g_free (pattern); + pattern = merged; + + if (merged == NULL) + /* set coalescence implies pairwise coalescence (i think). + * we should therefore be able to trace the failure to a single + * pair of values. + */ + { + int j = 0; + + while (TRUE) + { + gchar *tmp2; + gchar *m; + + /* if 'j' reaches 'i' then we failed to find the pair */ + g_assert (j < i); + + tmp2 = ast_get_pattern (array[j], NULL); + g_assert (tmp2 != NULL); + + m = pattern_coalesce (tmp, tmp2); + g_free (tmp2); + g_free (m); + + if (m == NULL) + { + /* we found a conflict between 'i' and 'j'. + * + * report the error. note: 'j' is first. + */ + ast_set_error (array[j], error, array[i], + "unable to find a common type"); + g_free (tmp); + return NULL; + } + + j++; + } + } + } + + return pattern; +} + +typedef struct +{ + AST ast; + + AST *child; +} Maybe; + +static gchar * +maybe_get_pattern (AST *ast, + GError **error) +{ + Maybe *maybe = (Maybe *) ast; + + if (maybe->child != NULL) + { + gchar *child_pattern; + gchar *pattern; + + child_pattern = ast_get_pattern (maybe->child, error); + + if (child_pattern == NULL) + return NULL; + + pattern = g_strdup_printf ("m%s", child_pattern); + g_free (child_pattern); + + return pattern; + } + + return g_strdup ("m*"); +} + +static GVariant * +maybe_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + Maybe *maybe = (Maybe *) ast; + GVariant *value; + + if (!g_variant_type_is_maybe (type)) + return ast_type_error (ast, type, error); + + type = g_variant_type_element (type); + + if (maybe->child) + { + value = ast_get_value (maybe->child, type, error); + + if (value == NULL) + return NULL; + } + else + value = NULL; + + return g_variant_new_maybe (type, value); +} + +static void +maybe_free (AST *ast) +{ + Maybe *maybe = (Maybe *) ast; + + if (maybe->child != NULL) + ast_free (maybe->child); + + g_slice_free (Maybe, maybe); +} + +static AST * +maybe_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass maybe_class = { + maybe_get_pattern, + maybe_get_value, NULL, + maybe_free + }; + AST *child = NULL; + Maybe *maybe; + + if (token_stream_consume (stream, "just")) + { + child = parse (stream, app, error); + if (child == NULL) + return NULL; + } + + else if (!token_stream_consume (stream, "nothing")) + { + token_stream_set_error (stream, error, TRUE, "unknown keyword"); + return NULL; + } + + maybe = g_slice_new (Maybe); + maybe->ast.class = &maybe_class; + maybe->child = child; + + return (AST *) maybe; +} + +static GVariant * +maybe_wrapper (AST *ast, + const GVariantType *type, + GError **error) +{ + const GVariantType *t; + GVariant *value; + int depth; + + for (depth = 0, t = type; + g_variant_type_is_maybe (t); + depth++, t = g_variant_type_element (t)); + + value = ast->class->get_base_value (ast, t, error); + + if (value == NULL) + return NULL; + + while (depth--) + value = g_variant_new_maybe (NULL, value); + + return value; +} + +typedef struct +{ + AST ast; + + AST **children; + gint n_children; +} Array; + +static gchar * +array_get_pattern (AST *ast, + GError **error) +{ + Array *array = (Array *) ast; + gchar *pattern; + gchar *result; + + if (array->n_children == 0) + return g_strdup ("Ma*"); + + pattern = ast_array_get_pattern (array->children, array->n_children, error); + + if (pattern == NULL) + return NULL; + + result = g_strdup_printf ("Ma%s", pattern); + g_free (pattern); + + return result; +} + +static GVariant * +array_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + Array *array = (Array *) ast; + const GVariantType *childtype; + GVariantBuilder builder; + gint i; + + if (!g_variant_type_is_array (type)) + return ast_type_error (ast, type, error); + + g_variant_builder_init (&builder, type); + childtype = g_variant_type_element (type); + + for (i = 0; i < array->n_children; i++) + { + GVariant *child; + + if (!(child = ast_get_value (array->children[i], childtype, error))) + { + g_variant_builder_clear (&builder); + return NULL; + } + + g_variant_builder_add_value (&builder, child); + } + + return g_variant_builder_end (&builder); +} + +static void +array_free (AST *ast) +{ + Array *array = (Array *) ast; + + ast_array_free (array->children, array->n_children); + g_slice_free (Array, array); +} + +static AST * +array_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass array_class = { + array_get_pattern, + maybe_wrapper, array_get_value, + array_free + }; + gboolean need_comma = FALSE; + Array *array; + + array = g_slice_new (Array); + array->ast.class = &array_class; + array->children = NULL; + array->n_children = 0; + + token_stream_assert (stream, "["); + while (!token_stream_consume (stream, "]")) + { + AST *child; + + if (need_comma && + !token_stream_require (stream, ",", + " or `]' to follow array element", + error)) + goto error; + + child = parse (stream, app, error); + + if (!child) + goto error; + + ast_array_append (&array->children, &array->n_children, child); + need_comma = TRUE; + } + + return (AST *) array; + + error: + ast_array_free (array->children, array->n_children); + g_slice_free (Array, array); + + return NULL; +} + +typedef struct +{ + AST ast; + + AST **children; + gint n_children; +} Tuple; + +static gchar * +tuple_get_pattern (AST *ast, + GError **error) +{ + Tuple *tuple = (Tuple *) ast; + gchar *result = NULL; + gchar **parts; + gint i; + + parts = g_new (gchar *, tuple->n_children + 4); + parts[tuple->n_children + 1] = (gchar *) ")"; + parts[tuple->n_children + 2] = NULL; + parts[0] = (gchar *) "M("; + + for (i = 0; i < tuple->n_children; i++) + if (!(parts[i + 1] = ast_get_pattern (tuple->children[i], error))) + break; + + if (i == tuple->n_children) + result = g_strjoinv ("", parts); + + /* parts[0] should not be freed */ + while (i) + g_free (parts[i--]); + g_free (parts); + + return result; +} + +static GVariant * +tuple_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + Tuple *tuple = (Tuple *) ast; + const GVariantType *childtype; + GVariantBuilder builder; + gint i; + + if (!g_variant_type_is_tuple (type)) + return ast_type_error (ast, type, error); + + g_variant_builder_init (&builder, type); + childtype = g_variant_type_first (type); + + for (i = 0; i < tuple->n_children; i++) + { + GVariant *child; + + if (!(child = ast_get_value (tuple->children[i], childtype, error))) + { + g_variant_builder_clear (&builder); + return FALSE; + } + + g_variant_builder_add_value (&builder, child); + childtype = g_variant_type_next (childtype); + } + + return g_variant_builder_end (&builder); +} + +static void +tuple_free (AST *ast) +{ + Tuple *tuple = (Tuple *) ast; + + ast_array_free (tuple->children, tuple->n_children); + g_slice_free (Tuple, tuple); +} + +static AST * +tuple_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass tuple_class = { + tuple_get_pattern, + maybe_wrapper, tuple_get_value, + tuple_free + }; + gboolean need_comma = FALSE; + gboolean first = TRUE; + Tuple *tuple; + + tuple = g_slice_new (Tuple); + tuple->ast.class = &tuple_class; + tuple->children = NULL; + tuple->n_children = 0; + + token_stream_assert (stream, "("); + while (!token_stream_consume (stream, ")")) + { + AST *child; + + if (need_comma && + !token_stream_require (stream, ",", + " or `)' to follow tuple element", + error)) + goto error; + + child = parse (stream, app, error); + + if (!child) + goto error; + + ast_array_append (&tuple->children, &tuple->n_children, child); + + /* the first time, we absolutely require a comma, so grab it here + * and leave need_comma = FALSE so that the code above doesn't + * require a second comma. + * + * the second and remaining times, we set need_comma = TRUE. + */ + if (first) + { + if (!token_stream_require (stream, ",", + " after first tuple element", error)) + goto error; + + first = FALSE; + } + else + need_comma = TRUE; + } + + return (AST *) tuple; + + error: + ast_array_free (tuple->children, tuple->n_children); + g_slice_free (Tuple, tuple); + + return NULL; +} + +typedef struct +{ + AST ast; + + AST *value; +} Variant; + +static gchar * +variant_get_pattern (AST *ast, + GError **error) +{ + return g_strdup ("Mv"); +} + +static GVariant * +variant_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + Variant *variant = (Variant *) ast; + GVariant *child; + + g_assert (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT)); + child = ast_resolve (variant->value, error); + + if (child == NULL) + return NULL; + + return g_variant_new_variant (child); +} + +static void +variant_free (AST *ast) +{ + Variant *variant = (Variant *) ast; + + ast_free (variant->value); + g_slice_free (Variant, variant); +} + +static AST * +variant_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass variant_class = { + variant_get_pattern, + maybe_wrapper, variant_get_value, + variant_free + }; + Variant *variant; + AST *value; + + token_stream_assert (stream, "<"); + value = parse (stream, app, error); + + if (!value) + return NULL; + + if (!token_stream_require (stream, ">", " to follow variant value", error)) + { + ast_free (value); + return NULL; + } + + variant = g_slice_new (Variant); + variant->ast.class = &variant_class; + variant->value = value; + + return (AST *) variant; +} + +typedef struct +{ + AST ast; + + AST **keys; + AST **values; + gint n_children; +} Dictionary; + +static gchar * +dictionary_get_pattern (AST *ast, + GError **error) +{ + Dictionary *dict = (Dictionary *) ast; + gchar *value_pattern; + gchar *key_pattern; + gchar key_char; + gchar *result; + + if (dict->n_children == 0) + return g_strdup ("Ma{**}"); + + key_pattern = ast_array_get_pattern (dict->keys, + abs (dict->n_children), + error); + + if (key_pattern == NULL) + return NULL; + + /* we can not have maybe keys */ + if (key_pattern[0] == 'M') + key_char = key_pattern[1]; + else + key_char = key_pattern[0]; + + g_free (key_pattern); + + /* the basic types, + * plus undetermined number type and undetermined string type. + */ + if (!strchr ("bynqiuxthdsogNS", key_char)) + { + ast_set_error (ast, error, NULL, + "dictionary keys must have basic types"); + return NULL; + } + + value_pattern = ast_get_pattern (dict->values[0], error); + + if (value_pattern == NULL) + return NULL; + + result = g_strdup_printf ("M%s{%c%s}", + dict->n_children > 0 ? "a" : "", + key_char, value_pattern); + g_free (value_pattern); + + return result; +} + +static GVariant * +dictionary_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + Dictionary *dict = (Dictionary *) ast; + + if (dict->n_children == -1) + { + const GVariantType *subtype; + GVariantBuilder builder; + GVariant *subvalue; + + if (!g_variant_type_is_dict_entry (type)) + return ast_type_error (ast, type, error); + + g_variant_builder_init (&builder, type); + + subtype = g_variant_type_key (type); + if (!(subvalue = ast_get_value (dict->keys[0], subtype, error))) + { + g_variant_builder_clear (&builder); + return FALSE; + } + g_variant_builder_add_value (&builder, subvalue); + + subtype = g_variant_type_value (type); + if (!(subvalue = ast_get_value (dict->values[0], subtype, error))) + { + g_variant_builder_clear (&builder); + return FALSE; + } + g_variant_builder_add_value (&builder, subvalue); + + return g_variant_builder_end (&builder); + } + else + { + const GVariantType *entry, *key, *val; + GVariantBuilder builder; + gint i; + + if (!g_variant_type_is_subtype_of (type, G_VARIANT_TYPE_DICTIONARY)) + return ast_type_error (ast, type, error); + + entry = g_variant_type_element (type); + key = g_variant_type_key (entry); + val = g_variant_type_value (entry); + + g_variant_builder_init (&builder, type); + + for (i = 0; i < dict->n_children; i++) + { + GVariant *subvalue; + + g_variant_builder_open (&builder, entry); + + if (!(subvalue = ast_get_value (dict->keys[i], key, error))) + { + g_variant_builder_clear (&builder); + return FALSE; + } + g_variant_builder_add_value (&builder, subvalue); + + if (!(subvalue = ast_get_value (dict->values[i], val, error))) + { + g_variant_builder_clear (&builder); + return FALSE; + } + g_variant_builder_add_value (&builder, subvalue); + g_variant_builder_close (&builder); + } + + return g_variant_builder_end (&builder); + } +} + +static void +dictionary_free (AST *ast) +{ + Dictionary *dict = (Dictionary *) ast; + gint n_children; + + if (dict->n_children > -1) + n_children = dict->n_children; + else + n_children = 1; + + ast_array_free (dict->keys, n_children); + ast_array_free (dict->values, n_children); + g_slice_free (Dictionary, dict); +} + +static AST * +dictionary_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass dictionary_class = { + dictionary_get_pattern, + maybe_wrapper, dictionary_get_value, + dictionary_free + }; + gint n_keys, n_values; + gboolean only_one; + Dictionary *dict; + AST *first; + + dict = g_slice_new (Dictionary); + dict->ast.class = &dictionary_class; + dict->keys = NULL; + dict->values = NULL; + n_keys = n_values = 0; + + token_stream_assert (stream, "{"); + + if (token_stream_consume (stream, "}")) + { + dict->n_children = 0; + return (AST *) dict; + } + + if ((first = parse (stream, app, error)) == NULL) + goto error; + + ast_array_append (&dict->keys, &n_keys, first); + + only_one = token_stream_consume (stream, ","); + if (!only_one && + !token_stream_require (stream, ":", + " or `,' to follow dictionary entry key", + error)) + goto error; + + if ((first = parse (stream, app, error)) == NULL) + goto error; + + ast_array_append (&dict->values, &n_values, first); + + if (only_one) + { + if (!token_stream_require (stream, "}", " at end of dictionary entry", + error)) + goto error; + + g_assert (n_keys == 1 && n_values == 1); + dict->n_children = -1; + + return (AST *) dict; + } + + while (!token_stream_consume (stream, "}")) + { + AST *child; + + if (!token_stream_require (stream, ",", + " or `}' to follow dictionary entry", error)) + goto error; + + child = parse (stream, app, error); + + if (!child) + goto error; + + ast_array_append (&dict->keys, &n_keys, child); + + if (!token_stream_require (stream, ":", + " to follow dictionary entry key", error)) + goto error; + + child = parse (stream, app, error); + + if (!child) + goto error; + + ast_array_append (&dict->values, &n_values, child); + } + + g_assert (n_keys == n_values); + dict->n_children = n_keys; + + return (AST *) dict; + + error: + ast_array_free (dict->keys, n_keys); + ast_array_free (dict->values, n_values); + g_slice_free (Dictionary, dict); + + return NULL; +} + +typedef struct +{ + AST ast; + gchar *string; +} String; + +static gchar * +string_get_pattern (AST *ast, + GError **error) +{ + return g_strdup ("MS"); +} + +static GVariant * +string_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + String *string = (String *) ast; + + if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) + return g_variant_new_string (string->string); + + else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH)) + { + if (!g_variant_is_object_path (string->string)) + { + ast_set_error (ast, error, NULL, "not a valid object path"); + return NULL; + } + + return g_variant_new_object_path (string->string); + } + + else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE)) + { + if (!g_variant_is_signature (string->string)) + { + ast_set_error (ast, error, NULL, "not a valid signature"); + return NULL; + } + + return g_variant_new_signature (string->string); + } + + else + return ast_type_error (ast, type, error); +} + +static void +string_free (AST *ast) +{ + String *string = (String *) ast; + + g_free (string->string); + g_slice_free (String, string); +} + +static AST * +string_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass string_class = { + string_get_pattern, + maybe_wrapper, string_get_value, + string_free + }; + String *string; + SourceRef ref; + gchar *token; + gsize length; + gchar quote; + gchar *str; + gint i, j; + + token_stream_start_ref (stream, &ref); + token = token_stream_get (stream); + token_stream_end_ref (stream, &ref); + length = strlen (token); + quote = token[0]; + + str = g_malloc (length); + g_assert (quote == '"' || quote == '\''); + j = 0; + i = 1; + while (token[i] != quote) + switch (token[i]) + { + case '\0': + parser_set_error (error, &ref, NULL, + "unterminated string constant"); + g_free (token); + return NULL; + + case '\\': + switch (token[++i]) + { + case '\0': + parser_set_error (error, &ref, NULL, + "unterminated string constant"); + g_free (token); + return NULL; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + /* up to 3 characters */ + guchar val = token[i++] - '0'; + + if ('0' <= token[i] && token[i] < '8') + val = (val << 3) | (token[i++] - '0'); + + if ('0' <= token[i] && token[i] < '8') + val = (val << 3) | (token[i++] - '0'); + + str[j++] = val; + } + continue; + + case 'b': str[j++] = '\b'; i++; continue; + case 'f': str[j++] = '\f'; i++; continue; + case 'n': str[j++] = '\n'; i++; continue; + case 'r': str[j++] = '\r'; i++; continue; + case 't': str[j++] = '\t'; i++; continue; + case '\n': i++; continue; + } + + default: + str[j++] = token[i++]; + } + str[j++] = '\0'; + g_free (token); + + string = g_slice_new (String); + string->ast.class = &string_class; + string->string = str; + + token_stream_next (stream); + + return (AST *) string; +} + +typedef struct +{ + AST ast; + + gchar *token; +} Number; + +static gchar * +number_get_pattern (AST *ast, + GError **error) +{ + Number *number = (Number *) ast; + + if (strchr (number->token, '.') || + (!g_str_has_prefix (number->token, "0x") && + strchr (number->token, 'e'))) + return g_strdup ("Md"); + + return g_strdup ("MN"); +} + +static GVariant * +number_overflow (AST *ast, + const GVariantType *type, + GError **error) +{ + ast_set_error (ast, error, NULL, "number out of range for type `%c'", + g_variant_type_peek_string (type)[0]); + return NULL; +} + +static GVariant * +number_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + Number *number = (Number *) ast; + const gchar *token; + gboolean negative; + gboolean floating; + guint64 abs_val; + gdouble dbl_val; + gchar *end; + + token = number->token; + + if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE)) + { + floating = TRUE; + + errno = 0; + dbl_val = g_ascii_strtod (token, &end); + if (dbl_val != 0.0 && errno == ERANGE) + { + ast_set_error (ast, error, NULL, "number too big for any type"); + return NULL; + } + } + else + { + floating = FALSE; + negative = token[0] == '-'; + if (token[0] == '-') + token++; + + errno = 0; + abs_val = g_ascii_strtoull (token, &end, 0); + if (abs_val == G_MAXUINT64 && errno == ERANGE) + { + ast_set_error (ast, error, NULL, "integer too big for any type"); + return NULL; + } + + if (abs_val == 0) + negative = FALSE; + } + + if (*end != '\0') + { + SourceRef ref; + + ref = ast->source_ref; + ref.start += end - number->token; + ref.end = ref.start + 1; + + parser_set_error (error, &ref, NULL, + "invalid character in number"); + return NULL; + } + + if (floating) + return g_variant_new_double (dbl_val); + + switch (*g_variant_type_peek_string (type)) + { + case 'y': + if (negative || abs_val > G_MAXUINT8) + return number_overflow (ast, type, error); + return g_variant_new_byte (abs_val); + + case 'n': + if (abs_val - negative > G_MAXINT16) + return number_overflow (ast, type, error); + return g_variant_new_int16 (negative ? -abs_val : abs_val); + + case 'q': + if (negative || abs_val > G_MAXUINT16) + return number_overflow (ast, type, error); + return g_variant_new_uint16 (negative ? -abs_val : abs_val); + + case 'i': + if (abs_val - negative > G_MAXINT32) + return number_overflow (ast, type, error); + return g_variant_new_int32 (negative ? -abs_val : abs_val); + + case 'u': + if (negative || abs_val > G_MAXUINT32) + return number_overflow (ast, type, error); + return g_variant_new_uint32 (negative ? -abs_val : abs_val); + + case 'x': + if (abs_val - negative > G_MAXINT64) + return number_overflow (ast, type, error); + return g_variant_new_int64 (negative ? -abs_val : abs_val); + + case 't': + if (negative) + return number_overflow (ast, type, error); + return g_variant_new_uint64 (negative ? -abs_val : abs_val); + + case 'h': + if (abs_val - negative > G_MAXINT32) + return number_overflow (ast, type, error); + return g_variant_new_handle (negative ? -abs_val : abs_val); + + default: + return ast_type_error (ast, type, error); + } +} + +static void +number_free (AST *ast) +{ + Number *number = (Number *) ast; + + g_free (number->token); + g_slice_free (Number, number); +} + +static AST * +number_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass number_class = { + number_get_pattern, + maybe_wrapper, number_get_value, + number_free + }; + Number *number; + + number = g_slice_new (Number); + number->ast.class = &number_class; + number->token = token_stream_get (stream); + token_stream_next (stream); + + return (AST *) number; +} + +typedef struct +{ + AST ast; + gboolean value; +} Boolean; + +static gchar * +boolean_get_pattern (AST *ast, + GError **error) +{ + return g_strdup ("Mb"); +} + +static GVariant * +boolean_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + Boolean *boolean = (Boolean *) ast; + + if (!g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) + return ast_type_error (ast, type, error); + + return g_variant_new_boolean (boolean->value); +} + +static void +boolean_free (AST *ast) +{ + Boolean *boolean = (Boolean *) ast; + + g_slice_free (Boolean, boolean); +} + +static AST * +boolean_new (gboolean value) +{ + static const ASTClass boolean_class = { + boolean_get_pattern, + maybe_wrapper, boolean_get_value, + boolean_free + }; + Boolean *boolean; + + boolean = g_slice_new (Boolean); + boolean->ast.class = &boolean_class; + boolean->value = value; + + return (AST *) boolean; +} + +typedef struct +{ + AST ast; + + GVariant *value; +} Positional; + +static gchar * +positional_get_pattern (AST *ast, + GError **error) +{ + Positional *positional = (Positional *) ast; + + return g_strdup (g_variant_get_type_string (positional->value)); +} + +static GVariant * +positional_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + Positional *positional = (Positional *) ast; + GVariant *value; + + g_assert (positional->value != NULL); + + if G_UNLIKELY (!g_variant_is_of_type (positional->value, type)) + return ast_type_error (ast, type, error); + + /* NOTE: if _get is called more than once then + * things get messed up with respect to floating refs. + * + * fortunately, this function should only ever get called once. + */ + g_assert (positional->value != NULL); + value = positional->value; + positional->value = NULL; + + return value; +} + +static void +positional_free (AST *ast) +{ + Positional *positional = (Positional *) ast; + + /* if positional->value is set, just leave it. + * memory management doesn't matter in case of programmer error. + */ + g_slice_free (Positional, positional); +} + +static AST * +positional_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass positional_class = { + positional_get_pattern, + positional_get_value, NULL, + positional_free + }; + Positional *positional; + const gchar *endptr; + gchar *token; + + token = token_stream_get (stream); + g_assert (token[0] == '%'); + + positional = g_slice_new (Positional); + positional->ast.class = &positional_class; + positional->value = g_variant_new_va (token + 1, &endptr, app); + + if (*endptr || positional->value == NULL) + { + token_stream_set_error (stream, error, TRUE, + "invalid GVariant format string"); + /* memory management doesn't matter in case of programmer error. */ + return NULL; + } + + token_stream_next (stream); + g_free (token); + + return (AST *) positional; +} + +typedef struct +{ + AST ast; + + GVariantType *type; + AST *child; +} TypeDecl; + +static gchar * +typedecl_get_pattern (AST *ast, + GError **error) +{ + TypeDecl *decl = (TypeDecl *) ast; + + return g_variant_type_dup_string (decl->type); +} + +static GVariant * +typedecl_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + TypeDecl *decl = (TypeDecl *) ast; + + return ast_get_value (decl->child, type, error); +} + +static void +typedecl_free (AST *ast) +{ + TypeDecl *decl = (TypeDecl *) ast; + + ast_free (decl->child); + g_variant_type_free (decl->type); + g_slice_free (TypeDecl, decl); +} + +static AST * +typedecl_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass typedecl_class = { + typedecl_get_pattern, + typedecl_get_value, NULL, + typedecl_free + }; + GVariantType *type; + TypeDecl *decl; + AST *child; + + if (token_stream_peek (stream, '@')) + { + gchar *token; + + token = token_stream_get (stream); + + if (!g_variant_type_string_is_valid (token + 1)) + { + token_stream_set_error (stream, error, TRUE, + "invalid type declaration"); + g_free (token); + + return NULL; + } + + type = g_variant_type_new (token + 1); + + if (!g_variant_type_is_definite (type)) + { + token_stream_set_error (stream, error, TRUE, + "type declarations must be definite"); + g_variant_type_free (type); + g_free (token); + + return NULL; + } + + token_stream_next (stream); + g_free (token); + } + else + { + if (token_stream_consume (stream, "boolean")) + type = g_variant_type_copy (G_VARIANT_TYPE_BOOLEAN); + + else if (token_stream_consume (stream, "byte")) + type = g_variant_type_copy (G_VARIANT_TYPE_BYTE); + + else if (token_stream_consume (stream, "int16")) + type = g_variant_type_copy (G_VARIANT_TYPE_INT16); + + else if (token_stream_consume (stream, "uint16")) + type = g_variant_type_copy (G_VARIANT_TYPE_UINT16); + + else if (token_stream_consume (stream, "int32")) + type = g_variant_type_copy (G_VARIANT_TYPE_INT32); + + else if (token_stream_consume (stream, "handle")) + type = g_variant_type_copy (G_VARIANT_TYPE_HANDLE); + + else if (token_stream_consume (stream, "uint32")) + type = g_variant_type_copy (G_VARIANT_TYPE_UINT32); + + else if (token_stream_consume (stream, "int64")) + type = g_variant_type_copy (G_VARIANT_TYPE_INT64); + + else if (token_stream_consume (stream, "uint64")) + type = g_variant_type_copy (G_VARIANT_TYPE_UINT64); + + else if (token_stream_consume (stream, "double")) + type = g_variant_type_copy (G_VARIANT_TYPE_DOUBLE); + + else if (token_stream_consume (stream, "string")) + type = g_variant_type_copy (G_VARIANT_TYPE_STRING); + + else if (token_stream_consume (stream, "objectpath")) + type = g_variant_type_copy (G_VARIANT_TYPE_OBJECT_PATH); + + else if (token_stream_consume (stream, "signature")) + type = g_variant_type_copy (G_VARIANT_TYPE_SIGNATURE); + + else + { + token_stream_set_error (stream, error, TRUE, "unknown keyword"); + return NULL; + } + } + + if ((child = parse (stream, app, error)) == NULL) + { + g_variant_type_free (type); + return NULL; + } + + decl = g_slice_new (TypeDecl); + decl->ast.class = &typedecl_class; + decl->type = type; + decl->child = child; + + return (AST *) decl; +} + +static AST * +parse (TokenStream *stream, + va_list *app, + GError **error) +{ + SourceRef source_ref; + AST *result; + + token_stream_prepare (stream); + token_stream_start_ref (stream, &source_ref); + + if (token_stream_peek (stream, '[')) + result = array_parse (stream, app, error); + + else if (token_stream_peek (stream, '(')) + result = tuple_parse (stream, app, error); + + else if (token_stream_peek (stream, '<')) + result = variant_parse (stream, app, error); + + else if (token_stream_peek (stream, '{')) + result = dictionary_parse (stream, app, error); + + else if (app && token_stream_peek (stream, '%')) + result = positional_parse (stream, app, error); + + else if (token_stream_consume (stream, "true")) + result = boolean_new (TRUE); + + else if (token_stream_consume (stream, "false")) + result = boolean_new (FALSE); + + else if (token_stream_peek (stream, 'n') || + token_stream_peek (stream, 'j')) + result = maybe_parse (stream, app, error); + + else if (token_stream_peek (stream, '@') || + token_stream_is_keyword (stream)) + result = typedecl_parse (stream, app, error); + + else if (token_stream_is_numeric (stream)) + result = number_parse (stream, app, error); + + else if (token_stream_peek (stream, '\'') || + token_stream_peek (stream, '"')) + result = string_parse (stream, app, error); + + else + { + token_stream_set_error (stream, error, FALSE, "expected value"); + return NULL; + } + + if (result != NULL) + { + token_stream_end_ref (stream, &source_ref); + result->source_ref = source_ref; + } + + return result; +} + +/** + * g_variant_parse: + * @type: a #GVariantType, or %NULL + * @text: a string containing a GVariant in text form + * @limit: a pointer to the end of @text, or %NULL + * @endptr: a location to store the end pointer, or %NULL + * @error: a pointer to a %NULL #GError pointer, or %NULL + * @Returns: a reference to a #GVariant, or %NULL + * + * Parses a #GVariant from a text representation. + * + * A single #GVariant is parsed from the content of @text. + * + * The memory at @limit will never be accessed and the parser behaves as + * if the character at @limit is the nul terminator. This has the + * effect of bounding @text. + * + * If @endptr is non-%NULL then @text is permitted to contain data + * following the value that this function parses and @endptr will be + * updated to point to the first character past the end of the text + * parsed by this function. If @endptr is %NULL and there is extra data + * then an error is returned. + * + * If @type is non-%NULL then the value will be parsed to have that + * type. This may result in additional parse errors (in the case that + * the parsed value doesn't fit the type) but may also result in fewer + * errors (in the case that the type would have been ambiguous, such as + * with empty arrays). + * + * In the event that the parsing is successful, the resulting #GVariant + * is returned. + * + * In case of any error, %NULL will be returned. If @error is non-%NULL + * then it will be set to reflect the error that occured. + * + * Officially, the language understood by the parser is "any string + * produced by g_variant_print()". + **/ +GVariant * +g_variant_parse (const GVariantType *type, + const gchar *text, + const gchar *limit, + const gchar **endptr, + GError **error) +{ + TokenStream stream = { }; + GVariant *result = NULL; + AST *ast; + + g_return_val_if_fail (text != NULL, NULL); + g_return_val_if_fail (text == limit || text != NULL, NULL); + + stream.start = text; + stream.stream = text; + stream.end = limit; + + if ((ast = parse (&stream, NULL, error))) + { + if (type == NULL) + result = ast_resolve (ast, error); + else + result = ast_get_value (ast, type, error); + + if (result != NULL) + { + g_variant_ref_sink (result); + + if (endptr == NULL) + { + while (stream.stream != limit && + g_ascii_isspace (*stream.stream)) + stream.stream++; + + if (stream.stream != limit && *stream.stream != '\0') + { + SourceRef ref = { stream.stream - text, + stream.stream - text }; + + parser_set_error (error, &ref, NULL, + "expected end of input"); + g_variant_unref (result); + + result = NULL; + } + } + else + *endptr = stream.stream; + } + + ast_free (ast); + } + + return result; +} + +/** + * g_variant_new_parsed_va: + * @format: a text format #GVariant + * @app: a pointer to a #va_list + * @returns: a new floating #GVariant instance + * + * Parses @format and returns the result. + * + * This is the version of g_variant_new_parsed() intended to be used + * from libraries. + **/ +GVariant * +g_variant_new_parsed_va (const gchar *format, + va_list *app) +{ + TokenStream stream = { }; + GVariant *result = NULL; + GError *error = NULL; + AST *ast; + + g_return_val_if_fail (format != NULL, NULL); + g_return_val_if_fail (app != NULL, NULL); + + stream.start = format; + stream.stream = format; + stream.end = NULL; + + if ((ast = parse (&stream, app, &error))) + { + result = ast_resolve (ast, &error); + ast_free (ast); + } + + if (result == NULL) + g_error ("g_variant_new_parsed: %s", error->message); + + if (*stream.stream) + g_error ("g_variant_new_parsed: trailing text after value"); + + return result; +} + +/** + * g_variant_new_parsed: + * @format: a text format #GVariant + * @...: arguments as per @format + * + * Parses @format and returns the result. + * + * @format must be a text format #GVariant with one extention: at any + * point that a value may appear in the text, a '%' character followed + * by a GVariant format string (as per g_variant_new()) may appear. In + * that case, the same arguments are collected from the argument list as + * g_variant_new() would have collected. + * + * Consider this simple example: + * + * + * g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three"); + * + * + * In the example, the variable argument parameters are collected and + * filled in as if they were part of the original string to produce the + * result of [('one', 1), ('two', 2), ('three', 3)]. + * + * This function is intended only to be used with @format as a string + * literal. Any parse error is fatal to the calling process. If you + * want to parse data from untrusted sources, use g_variant_parse(). + * + * You may not use this function to return, unmodified, a single + * #GVariant pointer from the argument list. ie: @format may not solely + * be anything along the lines of "%*", "%?", "%r", or anything starting + * with "%@". + **/ +GVariant * +g_variant_new_parsed (const gchar *format, + ...) +{ + GVariant *result; + va_list ap; + + va_start (ap, format); + result = g_variant_new_parsed_va (format, &ap); + va_end (ap); + + return result; +} + +#define __G_VARIANT_PARSER_C__ +#include "galiasdef.c" diff --git a/glib/gvariant.c b/glib/gvariant.c index 154b0ba..e3e7cdc 100644 --- a/glib/gvariant.c +++ b/glib/gvariant.c @@ -1454,31 +1454,31 @@ g_variant_print_string (GVariant *value, /* Nested maybes: * * Consider the case of the type "mmi". In this case we could - * write "Just Just 4", but "4" alone is totally unambiguous, - * so we try to drop "Just" where possible. + * write "just just 4", but "4" alone is totally unambiguous, + * so we try to drop "just" where possible. * - * We have to be careful not to always drop "Just", though, - * since "Nothing" needs to be distinguishable from "Just - * Nothing". The case where we need to ensure we keep the - * "Just" is actually exactly the case where we have a nested + * We have to be careful not to always drop "just", though, + * since "nothing" needs to be distinguishable from "just + * nothing". The case where we need to ensure we keep the + * "just" is actually exactly the case where we have a nested * Nothing. * * Instead of searching for that nested Nothing, we just print * the contained value into a separate string and see if we - * end up with "Nothing" at the end of it. If so, we need to - * add "Just" at our level. + * end up with "nothing" at the end of it. If so, we need to + * add "just" at our level. */ element = g_variant_get_child_value (value, 0); printed_child = g_variant_print (element, FALSE); g_variant_unref (element); - if (g_str_has_suffix (printed_child, "Nothing")) - g_string_append (string, "Just "); + if (g_str_has_suffix (printed_child, "nothing")) + g_string_append (string, "just "); g_string_append (string, printed_child); g_free (printed_child); } else - g_string_append (string, "Nothing"); + g_string_append (string, "nothing"); break; @@ -1633,7 +1633,11 @@ g_variant_print_string (GVariant *value, const gchar *str = g_variant_get_string (value, NULL); gchar *escaped = g_strescape (str, NULL); - g_string_append_printf (string, "\'%s\'", escaped); + /* use double quotes only if a ' is in the string */ + if (strchr (str, '\'')) + g_string_append_printf (string, "\"%s\"", escaped); + else + g_string_append_printf (string, "'%s'", escaped); g_free (escaped); } diff --git a/glib/gvariant.h b/glib/gvariant.h index 19c57c6..f51f793 100644 --- a/glib/gvariant.h +++ b/glib/gvariant.h @@ -176,6 +176,9 @@ struct _GVariantBuilder { gsize x[16]; }; +#define G_VARIANT_PARSE_ERROR (g_variant_parser_get_error_quark ()) +GQuark g_variant_parser_get_error_quark (void); + GVariantBuilder * g_variant_builder_new (const GVariantType *type); void g_variant_builder_unref (GVariantBuilder *builder); GVariantBuilder * g_variant_builder_ref (GVariantBuilder *builder); @@ -206,6 +209,16 @@ void g_variant_get_va (GVarian va_list *app); +GVariant * g_variant_parse (const GVariantType *type, + const gchar *text, + const gchar *limit, + const gchar **endptr, + GError **error); +GVariant * g_variant_new_parsed (const gchar *format, + ...); +GVariant * g_variant_new_parsed_va (const gchar *format, + va_list *app); + G_END_DECLS #endif /* __G_VARIANT_H__ */ diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c index 610fb65..f756d13 100644 --- a/glib/tests/gvariant.c +++ b/glib/tests/gvariant.c @@ -2742,7 +2742,7 @@ do_failed_test (const gchar *pattern) static void test_invalid_varargs (void) { - if (do_failed_test ("*not a valid GVariant format string*")) + if (do_failed_test ("*GVariant format string*")) { g_variant_new ("z"); abort (); @@ -2790,7 +2790,7 @@ test_varargs (void) g_variant_new_double (37.5)))); check_and_free (g_variant_new ("(ma{sv}m(a{sv})ma{sv}ii)", NULL, FALSE, NULL, &array, 7777, 8888), - "(Nothing, Nothing, {'size': <(800, 600)>, " + "(nothing, nothing, {'size': <(800, 600)>, " "'title': <'Test case'>, " "'temperature': <37.5>}, " "7777, 8888)"); @@ -2802,7 +2802,7 @@ test_varargs (void) FALSE, TRUE, 321, TRUE, FALSE, 321, TRUE, TRUE, 123), - "(123, Nothing, 123, Nothing, Just Nothing, 123)"); + "(123, nothing, 123, nothing, just nothing, 123)"); check_and_free (g_variant_new ("(ybnixd)", 'a', 1, 22, 33, (guint64) 44, 5.5), @@ -3047,7 +3047,7 @@ test_varargs (void) gdouble dval; gint32 hval; - /* test all 'Nothing' */ + /* test all 'nothing' */ value = g_variant_new ("(mymbmnmqmimumxmtmhmdmv)", FALSE, 'a', FALSE, TRUE, @@ -3144,7 +3144,7 @@ test_varargs (void) g_variant_unref (value); - /* test all 'Just' */ + /* test all 'just' */ value = g_variant_new ("(mymbmnmqmimumxmtmhmdmv)", TRUE, 'a', TRUE, TRUE, @@ -3455,6 +3455,242 @@ test_gv_byteswap () g_free (string); } +static void +test_parser (void) +{ + TreeInstance *tree; + GVariant *parsed; + GVariant *value; + gchar *pt, *p; + gchar *res; + + tree = tree_instance_new (NULL, 3); + value = tree_instance_get_gvariant (tree); + tree_instance_free (tree); + + pt = g_variant_print (value, TRUE); + p = g_variant_print (value, FALSE); + + parsed = g_variant_parse (NULL, pt, NULL, NULL, NULL); + res = g_variant_print (parsed, FALSE); + g_assert_cmpstr (p, ==, res); + g_variant_unref (parsed); + g_free (res); + + parsed = g_variant_parse (g_variant_get_type (value), p, + NULL, NULL, NULL); + res = g_variant_print (parsed, TRUE); + g_assert_cmpstr (pt, ==, res); + g_variant_unref (parsed); + g_free (res); + + g_variant_unref (value); + g_free (pt); + g_free (p); +} + +static void +test_parses (void) +{ + gint i; + + for (i = 0; i < 100; i++) + { + test_parser (); + } + + /* mini test */ + { + gchar str[256]; + GVariant *val; + gchar *p, *p2; + + for (i = 0; i < 256; i++) + str[i] = i + 1; + + val = g_variant_new_string (str); + p = g_variant_print (val, FALSE); + g_variant_unref (val); + + val = g_variant_parse (NULL, p, NULL, NULL, NULL); + p2 = g_variant_print (val, FALSE); + + g_assert_cmpstr (str, ==, g_variant_get_string (val, NULL)); + g_assert_cmpstr (p, ==, p2); + + g_variant_unref (val); + g_free (p2); + g_free (p); + } + + /* another mini test */ + { + const gchar *end; + GVariant *value; + + value = g_variant_parse (G_VARIANT_TYPE_INT32, "1 2 3", NULL, &end, NULL); + g_assert_cmpint (g_variant_get_int32 (value), ==, 1); + /* make sure endptr returning works */ + g_assert_cmpstr (end, ==, " 2 3"); + g_variant_unref (value); + } + + g_variant_type_info_assert_no_infos (); +} + +static void +test_parse_failures (void) +{ + const gchar *test[] = { + "[1, 2,", "6:", "expected value", + "", "0:", "expected value", + "(1, 2,", "6:", "expected value", + "<1", "2:", "expected `>'", + "[]", "0-2:", "unable to infer", + "(,", "1:", "expected value", + "[4,'']", "1-2,3-5:", "common type", + "[4, '', 5]", "1-2,4-6:", "common type", + "['', 4, 5]", "1-3,5-6:", "common type", + "[4, 5, '']", "1-2,7-9:", "common type", + "[[4], [], ['']]", "1-4,10-14:", "common type", + "[[], [4], ['']]", "5-8,10-14:", "common type", + "just", "4:", "expected value", + "nothing", "0-7:", "unable to infer", + "just [4, '']", "6-7,9-11:", "common type", + "[[4,'']]", "2-3,4-6:", "common type", + "([4,''],)", "2-3,4-6:", "common type", + "(4)", "2:", "`,'", + "{}", "0-2:", "unable to infer", + "{[1,2],[3,4]}", "0-13:", "basic types", + "{[1,2]:[3,4]}", "0-13:", "basic types", + "justt", "0-5:", "unknown keyword", + "nothng", "0-6:", "unknown keyword", + "uint33", "0-6:", "unknown keyword", + "@mi just ''", "9-11:", "can not parse as", + "@ai ['']", "5-7:", "can not parse as", + "@(i) ('',)", "6-8:", "can not parse as", + "[[], 5]", "1-3,5-6:", "common type", + "[[5], 5]", "1-4,6-7:", "common type", + "5 5", "2:", "expected end of input", + "[5, [5, '']]", "5-6,8-10:", "common type", + "@i just 5", "3-9:", "can not parse as", + "@i nothing", "3-10:", "can not parse as", + "@i []", "3-5:", "can not parse as", + "@i ()", "3-5:", "can not parse as", + "@ai (4,)", "4-8:", "can not parse as", + "@(i) []", "5-7:", "can not parse as", + "(5 5)", "3:", "expected `,'", + "[5 5]", "3:", "expected `,' or `]'", + "(5, 5 5)", "6:", "expected `,' or `)'", + "[5, 5 5]", "6:", "expected `,' or `]'", + "<@i []>", "4-6:", "can not parse as", + "<[5 5]>", "4:", "expected `,' or `]'", + "{[4,''],5}", "2-3,4-6:", "common type", + "{5,[4,'']}", "4-5,6-8:", "common type", + "@i {1,2}", "3-8:", "can not parse as", + "{@i '', 5}", "4-6:", "can not parse as", + "{5, @i ''}", "7-9:", "can not parse as", + "@ai {}", "4-6:", "can not parse as", + "{@i '': 5}", "4-6:", "can not parse as", + "{5: @i ''}", "7-9:", "can not parse as", + "{<4,5}", "3:", "expected `>'", + "{4,<5}", "5:", "expected `>'", + "{4,5,6}", "4:", "expected `}'", + "{5 5}", "3:", "expected `:' or `,'", + "{4: 5: 6}", "5:", "expected `,' or `}'", + "{4:5,<6:7}", "7:", "expected `>'", + "{4:5,6:<7}", "9:", "expected `>'", + "{4:5,6 7}", "7:", "expected `:'", + "@o 'foo'", "3-8:", "object path", + "@g 'zzz'", "3-8:", "signature", + "@i true", "3-7:", "can not parse as", + "@z 4", "0-2:", "invalid type", + "@a* []", "0-3:", "definite", + "@ai [3 3]", "7:", "expected `,' or `]'", + "18446744073709551616", "0-20:", "too big for any type", + "-18446744073709551616", "0-21:", "too big for any type", + "byte 256", "5-8:", "out of range for type", + "byte -1", "5-7:", "out of range for type", + "int16 32768", "6-11:", "out of range for type", + "int16 -32769", "6-12:", "out of range for type", + "uint16 -1", "7-9:", "out of range for type", + "uint16 65536", "7-12:", "out of range for type", + "2147483648", "0-10:", "out of range for type", + "-2147483649", "0-11:", "out of range for type", + "uint32 -1", "7-9:", "out of range for type", + "uint32 4294967296", "7-17:", "out of range for type", + "@x 9223372036854775808", "3-22:", "out of range for type", + "@x -9223372036854775809", "3-23:", "out of range for type", + "@t -1", "3-5:", "out of range for type", + "@t 18446744073709551616", "3-23:", "too big for any type", + "handle 2147483648", "7-17:", "out of range for type", + "handle -2147483649", "7-18:", "out of range for type", + "1.798e308", "0-9:", "too big for any type", + "37.5a488", "4-5:", "invalid character", + "0x7ffgf", "5-6:", "invalid character", + "07758", "4-5:", "invalid character", + "123a5", "3-4:", "invalid character", + "@ai 123", "4-7:", "can not parse as", + "'\"\\'", "0-4:", "unterminated string", + "'\"\\'\\", "0-5:", "unterminated string", + "boolean 4", "8-9:", "can not parse as", + "int32 true", "6-10:", "can not parse as", + "[double 5, int32 5]", "1-9,11-18:", "common type", + "string 4", "7-8:", "can not parse as" + }; + gint i; + + for (i = 0; i < G_N_ELEMENTS (test); i += 3) + { + GError *error = NULL; + GVariant *value; + + value = g_variant_parse (NULL, test[i], NULL, NULL, &error); + g_assert (value == NULL); + + if (!strstr (error->message, test[i+2])) + g_error ("test %d: Can't find `%s' in `%s'", i / 3, + test[i+2], error->message); + + if (!g_str_has_prefix (error->message, test[i+1])) + g_error ("test %d: Expected location `%s' in `%s'", i / 3, + test[i+1], error->message); + + g_error_free (error); + } +} + +static void +test_parse_positional (void) +{ + GVariant *value; + check_and_free (g_variant_new_parsed ("[('one', 1), (%s, 2)," + " ('three', %i)]", "two", 3), + "[('one', 1), ('two', 2), ('three', 3)]"); + value = g_variant_new_parsed ("[('one', 1), (%s, 2)," + " ('three', %u)]", "two", 3); + g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("a(su)"))); + check_and_free (value, "[('one', 1), ('two', 2), ('three', 3)]"); + + if (do_failed_test ("*GVariant format string*")) + { + g_variant_new_parsed ("%z"); + abort (); + } + + if (do_failed_test ("*can not parse as*")) + { + g_variant_new_parsed ("uint32 %i", 2); + abort (); + } + + if (do_failed_test ("*expected GVariant of type `i'*")) + { + g_variant_new_parsed ("%@i", g_variant_new_uint32 (2)); + abort (); + } +} + int main (int argc, char **argv) { @@ -3489,6 +3725,9 @@ main (int argc, char **argv) g_test_add_func ("/gvariant/builder-memory", test_builder_memory); g_test_add_func ("/gvariant/hashing", test_hashing); g_test_add_func ("/gvariant/byteswap", test_gv_byteswap); + g_test_add_func ("/gvariant/parser", test_parses); + g_test_add_func ("/gvariant/parse-failures", test_parse_failures); + g_test_add_func ("/gvariant/parse-positional", test_parse_positional); return g_test_run (); } -- 2.7.4