[glib/gsettings: 293/327] Add parser for text-format GVariants
- From: Ryan Lortie <ryanl src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [glib/gsettings: 293/327] Add parser for text-format GVariants
- Date: Thu, 27 Aug 2009 17:49:03 +0000 (UTC)
commit c0c5295ad9956f893c81526cdf804b8146e59e99
Author: Ryan Lortie <desrt desrt ca>
Date: Wed Jul 15 21:50:48 2009 -0400
Add parser for text-format GVariants
glib/Makefile.am | 1 +
glib/glib.symbols | 6 +
glib/gvariant-parser.c | 2091 ++++++++++++++++++++++++++++++++++++++++++++++++
glib/gvariant.h | 22 +-
4 files changed, 2119 insertions(+), 1 deletions(-)
---
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 3212d26..7c7de7d 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -166,6 +166,7 @@ libglib_2_0_la_SOURCES = \
gutils.c \
gvariant-private.h \
gvariant-core.c \
+ gvariant-parser.c \
gvariant-markup.c \
gvariant-serialiser.h \
gvariant-serialiser.c \
diff --git a/glib/glib.symbols b/glib/glib.symbols
index 2f7edf4..fe36f66 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -1706,6 +1706,12 @@ g_variant_builder_cancel
g_variant_lookup_value
#endif
+#if IN_FILE(_gvariant_parser_c_)
+g_variant_parse
+g_variant_parsef
+g_variant_parsef_va
+#endif
+
#if IN_FILE(_gvariant_markup_c_)
g_variant_markup_print
g_variant_markup_subparser_start
diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c
new file mode 100644
index 0000000..297871f
--- /dev/null
+++ b/glib/gvariant-parser.c
@@ -0,0 +1,2091 @@
+/*
+ * Copyright © 2009, Codethink Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: Ryan Lortie <desrt desrt ca>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+
+#include "galias.h"
+
+/*
+ * two-pass algorithm
+ * designed by ryan lortie and william hua
+ * designed in itb-229 and at ghazi's, 2009.
+ */
+
+typedef struct
+{
+ const gchar *stream;
+ gchar *this;
+} TokenStream;
+
+static void
+token_stream_prepare (TokenStream *stream)
+{
+ const gchar *end;
+
+ if (stream->this != NULL)
+ return;
+
+ while (g_ascii_isspace (*stream->stream))
+ stream->stream++;
+
+ switch (stream->stream[0])
+ {
+ case '\0':
+ return;
+
+ 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; g_ascii_isalnum (*end) ||
+ *end == '-' || *end == '+' || *end == '.'; end++);
+ 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; g_ascii_isalnum (*end); end++);
+ break;
+
+ case '@':
+ for (end = stream->stream + 1;
+ *end && strchr ("abdgimnoqrstuvxy(){}*?", *end); end++);
+ break;
+
+ case '%':
+ end = stream->stream + 1;
+ g_variant_format_string_scan (&end);
+ break;
+
+ case '\'': case '"':
+ for (end = stream->stream + 1;
+ *end && *end != stream->stream[0] && (*end != '\\' || *++end);
+ end++);
+
+ if (*end)
+ end++;
+
+ break;
+
+ default:
+ end = stream->stream + 1;
+ break;
+ }
+
+ stream->this = g_strndup (stream->stream, end - stream->stream);
+ stream->stream = end;
+}
+
+static void
+token_stream_next (TokenStream *stream)
+{
+ g_free (stream->this);
+ stream->this = NULL;
+}
+
+static gboolean
+token_stream_peek (TokenStream *stream,
+ gchar first_char)
+{
+ token_stream_prepare (stream);
+
+ return stream->this && stream->this[0] == first_char;
+}
+
+static gboolean
+token_stream_is_keyword (TokenStream *stream)
+{
+ token_stream_prepare (stream);
+
+ return stream->this &&
+ g_ascii_isalpha (stream->this[0]);
+}
+
+static gboolean
+token_stream_is_numeric (TokenStream *stream)
+{
+ token_stream_prepare (stream);
+
+ return stream->this &&
+ (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)
+{
+ token_stream_prepare (stream);
+
+ if (g_strcmp0 (stream->this, token) == 0)
+ {
+ token_stream_next (stream);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+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 = stream->this;
+ stream->this = NULL;
+
+ return result;
+}
+
+typedef enum
+{
+ NO_CONSTRAINTS,
+ CONSTRAINT_IS_NUMBER = 0x010000,
+ CONSTRAINT_IS_STRING = 0x020000,
+ CONSTRAINT_HAS_POINT = 0x080000,
+ CONSTRAINT_IS_BOOLEAN = 0x100000
+} Constraints;
+
+static char
+type_from_constraints (Constraints constraints)
+{
+ if (constraints & CONSTRAINT_IS_STRING)
+ {
+ if (constraints & (CONSTRAINT_IS_NUMBER | CONSTRAINT_IS_BOOLEAN))
+ return 0;
+
+ return 's';
+ }
+
+ if (constraints & CONSTRAINT_IS_BOOLEAN)
+ {
+ if (constraints & CONSTRAINT_IS_NUMBER)
+ return 0;
+
+ return 'b';
+ }
+
+ if (constraints & CONSTRAINT_HAS_POINT)
+ return 'd';
+
+ return 'i';
+}
+
+typedef struct
+{
+ gchar *type_string;
+
+ Constraints *constraints;
+ gint n_constraints;
+} Pattern;
+
+static void
+pattern_free (Pattern *pattern)
+{
+ g_free (pattern->constraints);
+ g_free (pattern->type_string);
+ g_slice_free (Pattern, pattern);
+}
+
+static Pattern *
+pattern_concat (Pattern **items,
+ gint n_items)
+{
+ Pattern *result;
+ gint i, j, c;
+
+ result = g_slice_new (Pattern);
+
+ result->n_constraints = 0;
+ for (i = 0; i < n_items; i++)
+ result->n_constraints += items[i]->n_constraints;
+
+ result->type_string = g_new (gchar, result->n_constraints + 1);
+ result->constraints = g_new (Constraints, result->n_constraints);
+
+ c = 0;
+ for (i = 0; i < n_items; i++)
+ {
+ const Pattern *item = items[i];
+
+ for (j = 0; j < item->n_constraints; j++, c++)
+ {
+ result->type_string[c] = item->type_string[j];
+ result->constraints[c] = item->constraints[j];
+ }
+
+ g_assert (item->type_string[j] == '\0');
+ }
+ g_assert (c == result->n_constraints);
+ result->type_string[c] = '\0';
+
+ return result;
+}
+
+static gboolean
+pattern_coalesce (Pattern *dest,
+ const Pattern *src)
+{
+ gint i;
+
+ if (dest->n_constraints != src->n_constraints)
+ return FALSE;
+
+ for (i = 0; i < dest->n_constraints; i++)
+ {
+ if (src->type_string[i] == dest->type_string[i])
+ dest->constraints[i] |= src->constraints[i];
+
+ else if (dest->type_string[i] == '?' &&
+ strchr ("bynqiuxtdsog", src->type_string[i]))
+ dest->type_string[i] = src->type_string[i];
+
+ else if (src->type_string[i] != '?' ||
+ !strchr ("bynqiuxtdsog", dest->type_string[i]))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static Pattern *
+pattern_from_class (GVariantTypeClass class)
+{
+ Pattern *pattern;
+
+ pattern = g_slice_new (Pattern);
+ pattern->n_constraints = 1;
+ pattern->type_string = g_new (gchar, 2);
+ pattern->type_string[0] = class;
+ pattern->type_string[1] = '\0';
+ pattern->constraints = g_new (Constraints, 1); /* value will not be used */
+
+ return pattern;
+}
+
+static Pattern *
+pattern_from_type (const GVariantType *type)
+{
+ Pattern *pattern;
+
+ pattern = g_slice_new (Pattern);
+ pattern->n_constraints = g_variant_type_get_string_length (type);
+ pattern->type_string = g_variant_type_dup_string (type);
+ pattern->constraints = g_new (Constraints, pattern->n_constraints);
+
+ return pattern;
+}
+
+static Pattern *
+pattern_from_constraints (Constraints constraints)
+{
+ Pattern *pattern;
+
+ pattern = g_slice_new (Pattern);
+ pattern->n_constraints = 1;
+ pattern->type_string = g_strdup ("?");
+ pattern->constraints = g_new (Constraints, 1);
+ pattern->constraints[0] = constraints;
+
+ return pattern;
+}
+
+typedef struct _AST AST;
+typedef Pattern * (*get_pattern_func) (AST *ast,
+ GError **error);
+typedef GVariant * (*get_value_func) (AST *ast,
+ const GVariantType *type,
+ GError **error);
+typedef GSList * (*get_examples_func) (AST *ast,
+ gint index,
+ Constraints *constraints);
+typedef void (*free_func) (AST *ast);
+
+typedef struct
+{
+ get_pattern_func get_pattern;
+ get_value_func get_value;
+ get_examples_func get_examples;
+ free_func free;
+} ASTClass;
+
+struct _AST
+{
+ const ASTClass *class;
+};
+
+static Pattern *
+ast_get_pattern (AST *ast,
+ GError **error)
+{
+ return ast->class->get_pattern (ast, error);
+}
+
+static gint
+ast_get_pattern_size (AST *ast)
+{
+ Pattern *pattern;
+ gint size;
+
+ pattern = ast_get_pattern (ast, NULL);
+ g_assert (pattern != NULL);
+
+ size = pattern->n_constraints;
+ pattern_free (pattern);
+
+ return size;
+}
+
+static GVariant *
+ast_get_value (AST *ast,
+ const GVariantType *type,
+ GError **error)
+{
+ return ast->class->get_value (ast, type, error);
+}
+
+static GSList *
+ast_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ return ast->class->get_examples (ast, index, constraints);
+}
+
+static GVariant *
+ast_resolve (AST *ast,
+ GError **error)
+{
+ Pattern *pattern;
+ GVariant *value;
+ gint i;
+
+ pattern = ast_get_pattern (ast, error);
+
+ if (pattern == NULL)
+ return NULL;
+
+ for (i = 0; i < pattern->n_constraints; i++)
+ if (pattern->type_string[i] == '?')
+ {
+ char new;
+
+ new = type_from_constraints (pattern->constraints[i]);
+
+ if (!new)
+ {
+ Constraints constraints = pattern->constraints[i];
+ GSList *examples;
+
+ examples = ast_get_examples (ast, i, &constraints);
+ g_assert_cmpint (g_slist_length (examples), ==, 2);
+
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_TYPE_MISMATCH,
+ "Unable to infer a type to contain both "
+ "'%s' and '%s'",
+ (gchar *) examples->data,
+ (gchar *) examples->next->data);
+
+ g_slist_free (examples);
+ pattern_free (pattern);
+
+ return NULL;
+ }
+
+ pattern->type_string[i] = new;
+ }
+ g_assert (pattern->type_string[i] == '\0');
+
+ value = ast_get_value (ast, G_VARIANT_TYPE (pattern->type_string), error);
+ pattern_free (pattern);
+
+ return value;
+}
+
+static void
+ast_free (AST *ast)
+{
+ ast->class->free (ast);
+}
+
+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);
+}
+
+typedef struct
+{
+ AST ast;
+
+ AST **children;
+ gint n_children;
+} Array;
+
+static Pattern *
+array_get_pattern (AST *ast,
+ GError **error)
+{
+ Array *array = (Array *) ast;
+ Pattern *result = NULL;
+ Pattern *child_type;
+ gint i;
+
+ if (array->n_children == 0)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_EMPTY_ARRAY,
+ "unable to infer the type of an empty array");
+ return NULL;
+ }
+
+ child_type = ast_get_pattern (array->children[0], error);
+
+ if (child_type == NULL)
+ return NULL;
+
+ for (i = 1; i < array->n_children; i++)
+ {
+ Pattern *other_child_type;
+
+ other_child_type = ast_get_pattern (array->children[i], error);
+
+ if (other_child_type == NULL)
+ break;
+
+ if (!pattern_coalesce (child_type, other_child_type))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_TYPE_MISMATCH,
+ "unable to coalesce types '%s' and '%s'",
+ child_type->type_string,
+ other_child_type->type_string);
+ pattern_free (other_child_type);
+
+ break;
+ }
+
+ pattern_free (other_child_type);
+ }
+
+ if (i == array->n_children)
+ {
+ Constraints undefined;
+ Pattern array_prefix = { "a", &undefined, 1 };
+ Pattern *parts[] = { &array_prefix, child_type };
+
+ result = pattern_concat (parts, 2);
+ }
+
+ pattern_free (child_type);
+
+ 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;
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE_CLASS_ARRAY, type);
+ childtype = g_variant_type_element (type);
+
+ for (i = 0; i < array->n_children; i++)
+ {
+ GVariant *child;
+
+ child = ast_get_value (array->children[i], childtype, error);
+
+ if (child == NULL)
+ {
+ g_variant_builder_cancel (builder);
+ return NULL;
+ }
+
+ g_variant_builder_add_value (builder, child);
+ }
+
+ return g_variant_builder_end (builder);
+}
+
+static GSList *
+array_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ Array *array = (Array *) ast;
+ GSList *result = NULL;
+ gint i;
+
+ /* remove the 'a' */
+ index--;
+
+ for (i = 0; i < array->n_children && *constraints; i++)
+ {
+ GSList *examples;
+
+ examples = ast_get_examples (array->children[i], index, constraints);
+ result = g_slist_concat (result, examples);
+ }
+
+ return result;
+}
+
+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,
+ array_get_value,
+ array_get_examples,
+ 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_consume (stream, ","))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "expecting ',' or ')' to follow tuple element");
+ 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 Pattern *
+tuple_get_pattern (AST *ast,
+ GError **error)
+{
+ Tuple *tuple = (Tuple *) ast;
+ Pattern *result = NULL;
+ Pattern **parts;
+ gint i;
+
+ parts = g_new (Pattern *, tuple->n_children + 2);
+ for (i = 0; i < tuple->n_children; i++)
+ {
+ Pattern *child;
+
+ child = ast_get_pattern (tuple->children[i], error);
+
+ if (child == NULL)
+ break;
+
+ parts[i + 1] = child;
+ }
+
+ if (i == tuple->n_children)
+ {
+ Constraints undefined;
+ Pattern open = { "(", &undefined, 1 };
+ Pattern close = { ")", &undefined, 1 };
+ parts[0] = &open;
+ parts[i + 1] = &close;
+
+ result = pattern_concat (parts, i + 2);
+ }
+
+ while (i)
+ pattern_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;
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE_CLASS_STRUCT, type);
+ childtype = g_variant_type_first (type);
+
+ for (i = 0; i < tuple->n_children; i++)
+ {
+ GVariant *child;
+
+ child = ast_get_value (tuple->children[i], childtype, error);
+
+ if (child == NULL)
+ {
+ g_variant_builder_cancel (builder);
+ return NULL;
+ }
+
+ g_variant_builder_add_value (builder, child);
+ childtype = g_variant_type_next (childtype);
+ }
+
+ return g_variant_builder_end (builder);
+}
+
+static GSList *
+tuple_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ Tuple *tuple = (Tuple *) ast;
+ gint i;
+
+ /* remove the '(' */
+ index--;
+
+ for (i = 0; i < tuple->n_children; i++)
+ {
+ gint size;
+
+ size = ast_get_pattern_size (tuple->children[i]);
+
+ if (index < size)
+ return ast_get_examples (tuple->children[i], index, constraints);
+
+ index -= size;
+ }
+
+ g_assert_not_reached ();
+}
+
+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,
+ tuple_get_value,
+ tuple_get_examples,
+ tuple_free
+ };
+ gboolean need_comma = FALSE;
+ 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_consume (stream, ","))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "expecting ',' or ')' to follow tuple element");
+ goto error;
+ }
+
+ child = parse (stream, app, error);
+
+ if (!child)
+ goto error;
+
+ ast_array_append (&tuple->children, &tuple->n_children, child);
+ 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 Pattern *
+variant_get_pattern (AST *ast,
+ GError **error)
+{
+ return pattern_from_class (G_VARIANT_TYPE_CLASS_VARIANT);
+}
+
+static GVariant *
+variant_get_value (AST *ast,
+ const GVariantType *type,
+ GError **error)
+{
+ Variant *variant = (Variant *) ast;
+
+ g_assert (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT));
+ return g_variant_new_variant (ast_resolve (variant->value, error));
+}
+
+static GSList *
+variant_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ g_assert_not_reached ();
+}
+
+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,
+ variant_get_value,
+ variant_get_examples,
+ variant_free
+ };
+ Variant *variant;
+ AST *value;
+
+ token_stream_assert (stream, "<");
+ value = parse (stream, app, error);
+
+ if (!value)
+ return NULL;
+
+ if (!token_stream_consume (stream, ">"))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "expecting '>' to follow variant value");
+ 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 Pattern *
+dictionary_get_pattern (AST *ast,
+ GError **error)
+{
+ Dictionary *dict = (Dictionary *) ast;
+ Pattern *result = NULL;
+ Pattern *value_pattern;
+ Pattern *key_pattern;
+ gint i;
+
+ if (dict->n_children == 0)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_EMPTY_ARRAY,
+ "unable to infer the type of an empty dictionary");
+ return NULL;
+ }
+
+ key_pattern = ast_get_pattern (dict->keys[0], error);
+
+ if (key_pattern == NULL)
+ return NULL;
+
+ value_pattern = ast_get_pattern (dict->values[0], error);
+
+ if (value_pattern == NULL)
+ {
+ pattern_free (key_pattern);
+ return NULL;
+ }
+
+ for (i = 1; i < dict->n_children; i++)
+ {
+ Pattern *other_pattern;
+
+ /* key */
+ other_pattern = ast_get_pattern (dict->keys[i], error);
+
+ if (other_pattern == NULL)
+ break;
+
+ if (!strchr ("bynqiuxtdsog?", other_pattern->type_string[0]))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_EMPTY_ARRAY,
+ "dictionary keys must have basic types");
+ pattern_free (other_pattern);
+ break;
+ }
+
+ if (!pattern_coalesce (key_pattern, other_pattern))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_TYPE_MISMATCH,
+ "unable to coalesce types '%s' and '%s'",
+ key_pattern->type_string, other_pattern->type_string);
+ pattern_free (other_pattern);
+
+ break;
+ }
+
+ pattern_free (other_pattern);
+
+ /* value */
+ other_pattern = ast_get_pattern (dict->values[i], error);
+
+ if (other_pattern == NULL)
+ break;
+
+ if (!pattern_coalesce (value_pattern, other_pattern))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_TYPE_MISMATCH,
+ "unable to coalesce types '%s' and '%s'",
+ key_pattern->type_string, other_pattern->type_string);
+ pattern_free (other_pattern);
+
+ break;
+ }
+
+ pattern_free (other_pattern);
+ }
+
+ if (i >= dict->n_children)
+ {
+ Constraints undefined;
+ Pattern a = { "a", &undefined, 1 };
+ Pattern open = { "{", &undefined, 1 };
+ Pattern close = { "}", &undefined, 1 };
+ Pattern *parts[] = { &a, &open, key_pattern, value_pattern, &close };
+
+ if (dict->n_children == -1)
+ result = pattern_concat (parts + 1, 4);
+ else
+ result = pattern_concat (parts, 5);
+ }
+
+ pattern_free (key_pattern);
+ pattern_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;
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE_CLASS_DICT_ENTRY, type);
+
+ subtype = g_variant_type_key (type);
+ if (!(subvalue = ast_get_value (dict->keys[0], subtype, error)))
+ {
+ g_variant_builder_cancel (builder);
+ return NULL;
+ }
+ 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_cancel (builder);
+ return NULL;
+ }
+ g_variant_builder_add_value (builder, subvalue);
+
+ return g_variant_builder_end (builder);
+ }
+ else
+ {
+ const GVariantType *entry, *key, *value;
+ GVariantBuilder *builder;
+ gint i;
+
+ entry = g_variant_type_element (type);
+ key = g_variant_type_key (entry);
+ value = g_variant_type_value (entry);
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE_CLASS_ARRAY, type);
+
+ for (i = 0; i < dict->n_children; i++)
+ {
+ GVariantBuilder *item;
+ GVariant *subvalue;
+
+ item = g_variant_builder_open (builder,
+ G_VARIANT_TYPE_CLASS_DICT_ENTRY,
+ entry);
+
+ if (!(subvalue = ast_get_value (dict->keys[i], key, error)))
+ {
+ g_variant_builder_cancel (builder);
+ return NULL;
+ }
+ g_variant_builder_add_value (item, subvalue);
+
+ if (!(subvalue = ast_get_value (dict->values[i], value, error)))
+ {
+ g_variant_builder_cancel (builder);
+ return NULL;
+ }
+ g_variant_builder_add_value (item, subvalue);
+ g_variant_builder_close (item);
+ }
+
+ return g_variant_builder_end (builder);
+ }
+}
+
+static GSList *
+dictionary_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ Dictionary *dict = (Dictionary *) ast;
+ GSList *result = NULL;
+ AST **list;
+ gint i;
+
+ if (dict->n_children == -1)
+ /* remove the '{' */
+ index--;
+ else
+ /* remove the 'a{' */
+ index -= 2;
+
+ if (index > 0)
+ {
+ /* we know keys have pattern size of 1,
+ * so if index > 0 then it must be inside the value.
+ * subtract one from the index and scan the values.
+ */
+ list = dict->values;
+ index--;
+ }
+ else
+ list = dict->keys;
+
+ for (i = 0; i < dict->n_children && *constraints; i++)
+ {
+ GSList *examples;
+
+ examples = ast_get_examples (list[i], index, constraints);
+ result = g_slist_concat (result, examples);
+ }
+
+ return result;
+}
+
+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,
+ dictionary_get_value,
+ dictionary_get_examples,
+ 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 ((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_consume (stream, ":"))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "expecting ',' or ':' to follow dictionary entry key");
+ 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_consume (stream, "}"))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "expecting '}' at end of dictionary entry");
+ 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_consume (stream, ","))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "expecting ',' or '}' to follow dictionary entry");
+ goto error;
+ }
+
+ child = parse (stream, app, error);
+
+ if (!child)
+ goto error;
+
+ ast_array_append (&dict->keys, &n_keys, child);
+
+ if (!token_stream_consume (stream, ":"))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "expecting ':' to follow dictionary entry key");
+ 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 *token;
+ gchar *string;
+} Terminal;
+
+static Constraints
+constraints_from_string (const gchar *token)
+{
+ if (token[0] == '\'' || token[0] == '\"')
+ return CONSTRAINT_IS_STRING;
+
+ if (strchr (token, '.') || strchr (token, 'e'))
+ return CONSTRAINT_HAS_POINT | CONSTRAINT_IS_NUMBER;
+
+ return CONSTRAINT_IS_NUMBER;
+}
+
+static Pattern *
+terminal_get_pattern (AST *ast,
+ GError **error)
+{
+ Terminal *terminal = (Terminal *) ast;
+ Constraints constraints;
+
+ constraints = constraints_from_string (terminal->token);
+
+ return pattern_from_constraints (constraints);
+}
+
+static gboolean
+parse_string (const gchar *token,
+ gchar **result,
+ GError **error)
+{
+ gint length;
+ gchar *tmp;
+
+ length = strlen (token);
+
+ if (length < 2 || token[0] != token[length - 1])
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Unterminated string constant");
+ return FALSE;
+ }
+
+ tmp = g_strndup (token + 1, length - 2);
+ *result = g_strcompress (tmp);
+ g_free (tmp);
+
+ return TRUE;
+}
+
+static gboolean
+parse_signed (const gchar *token,
+ gint64 min,
+ gint64 max,
+ gint64 *result,
+ GError **error)
+{
+ gint64 value;
+ gchar *end;
+
+ errno = 0;
+ value = g_ascii_strtoll (token, &end, 0);
+
+ if (errno == ERANGE || value < min || value > max)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Signed integer '%s' is too large for type", token);
+ return FALSE;
+ }
+
+ if (*end)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Unable to parse signed integer '%s'\n", token);
+ return FALSE;
+ }
+
+ *result = value;
+
+ return TRUE;
+}
+
+static gboolean
+parse_unsigned (const gchar *token,
+ guint64 max,
+ guint64 *result,
+ GError **error)
+{
+ guint64 value;
+ gchar *end;
+
+ /* g_ascii_strtoull handles '-' very poorly, so do it ourselves */
+ if (token[0] == '-')
+ goto error;
+
+ errno = 0;
+ value = g_ascii_strtoull (token, &end, 0);
+
+ if (errno == ERANGE || value > max)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Unsigned integer '%s' is too large for type", token);
+ return FALSE;
+ }
+
+ if (*end)
+ {
+ error:
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Unable to parse unsigned integer '%s'\n", token);
+ return FALSE;
+ }
+
+ *result = value;
+
+ return TRUE;
+}
+
+static gboolean
+parse_floating (const gchar *token,
+ gdouble *result,
+ GError **error)
+{
+ gdouble value;
+ gchar *end;
+
+ errno = 0;
+ value = g_ascii_strtod (token, &end);
+
+ if (errno == ERANGE)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Floating point value '%s' is too large", token);
+ return FALSE;
+ }
+
+ if (*end)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Unable to parse floating point value '%s'\n", token);
+ return FALSE;
+ }
+
+ *result = value;
+
+ return TRUE;
+}
+
+static GVariant *
+terminal_get_value (AST *ast,
+ const GVariantType *type,
+ GError **error)
+{
+ Terminal *terminal = (Terminal *) ast;
+
+ switch (g_variant_type_get_class (type))
+ {
+ case G_VARIANT_TYPE_CLASS_BOOLEAN:
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Unable to parse as boolean: %s", terminal->token);
+ return NULL;
+ }
+
+ case G_VARIANT_TYPE_CLASS_BYTE:
+ if (terminal->token[0] == '\'' || terminal->token[0] == '"')
+ {
+ gchar *string;
+ gchar value;
+
+ if (!parse_string (terminal->token, &string, error))
+ return NULL;
+
+ if (strlen (string) != 1)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Character constants must be one character");
+ g_free (string);
+
+ return NULL;
+ }
+
+ value = string[0];
+ g_free (string);
+
+ return g_variant_new_byte (value);
+ }
+ else
+ {
+ guint64 value;
+
+ if (!parse_unsigned (terminal->token, 255, &value, error))
+ return NULL;
+
+ return g_variant_new_byte (value);
+ }
+
+ case G_VARIANT_TYPE_CLASS_INT16:
+ {
+ gint64 value;
+
+ if (!parse_signed (terminal->token,
+ G_MININT16, G_MAXINT16,
+ &value, error))
+ return NULL;
+
+ return g_variant_new_int16 (value);
+ }
+
+ case G_VARIANT_TYPE_CLASS_UINT16:
+ {
+ guint64 value;
+
+ if (!parse_unsigned (terminal->token,
+ G_MAXUINT16,
+ &value, error))
+ return NULL;
+
+ return g_variant_new_uint16 (value);
+ }
+
+ case G_VARIANT_TYPE_CLASS_INT32:
+ {
+ gint64 value;
+
+ if (!parse_signed (terminal->token,
+ G_MININT32, G_MAXINT32,
+ &value, error))
+ return NULL;
+
+ return g_variant_new_int32 (value);
+ }
+
+ case G_VARIANT_TYPE_CLASS_UINT32:
+ {
+ guint64 value;
+
+ if (!parse_unsigned (terminal->token,
+ G_MAXUINT32,
+ &value, error))
+ return NULL;
+
+ return g_variant_new_uint32 (value);
+ }
+
+ case G_VARIANT_TYPE_CLASS_INT64:
+ {
+ gint64 value;
+
+ if (!parse_signed (terminal->token,
+ G_MININT64, G_MAXINT64,
+ &value, error))
+ return NULL;
+
+ return g_variant_new_int64 (value);
+ }
+
+ case G_VARIANT_TYPE_CLASS_UINT64:
+ {
+ guint64 value;
+
+ if (!parse_unsigned (terminal->token,
+ G_MAXUINT64,
+ &value, error))
+ return NULL;
+
+ return g_variant_new_uint64 (value);
+ }
+
+ case G_VARIANT_TYPE_CLASS_DOUBLE:
+ {
+ gdouble floating;
+
+ if (!parse_floating (terminal->token, &floating, error))
+ return NULL;
+
+ return g_variant_new_double (floating);
+ }
+
+ case G_VARIANT_TYPE_CLASS_STRING:
+ {
+ GVariant *value;
+
+ if (terminal->string == NULL)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Unable to parse as a string: %s",
+ terminal->token);
+ return NULL;
+ }
+
+ value = g_variant_new_string (terminal->string);
+
+ return value;
+ }
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static GSList *
+terminal_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ Terminal *terminal = (Terminal *) ast;
+ Constraints my_constraints;
+
+ g_assert_cmpint (index, ==, 0);
+
+ my_constraints = constraints_from_string (terminal->token);
+
+ if ((*constraints & my_constraints) == 0)
+ return NULL;
+
+ *constraints &= ~my_constraints;
+
+ return g_slist_prepend (NULL, terminal->token);
+}
+
+static void
+terminal_free (AST *ast)
+{
+ Terminal *terminal = (Terminal *) ast;
+
+ g_free (terminal->token);
+ g_free (terminal->string);
+ g_slice_free (Terminal, terminal);
+}
+
+static AST *
+terminal_parse (TokenStream *stream,
+ va_list *app,
+ GError **error)
+{
+ static const ASTClass terminal_class = {
+ terminal_get_pattern,
+ terminal_get_value,
+ terminal_get_examples,
+ terminal_free
+ };
+ Terminal *terminal;
+ gchar *string;
+ gchar *token;
+
+ token = token_stream_get (stream);
+
+ if (token == NULL)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "expecting token");
+ return NULL;
+ }
+
+ if (token[0] == '"' || token[0] == '\'')
+ {
+ if (!parse_string (token, &string, error))
+ {
+ g_free (token);
+ return NULL;
+ }
+ }
+ else
+ string = NULL;
+
+ terminal = g_slice_new (Terminal);
+ terminal->ast.class = &terminal_class;
+ terminal->token = token;
+ terminal->string = string;
+
+ return (AST *) terminal;
+}
+
+typedef struct
+{
+ AST ast;
+ gboolean value;
+} Boolean;
+
+static Pattern *
+boolean_get_pattern (AST *ast,
+ GError **error)
+{
+ /* do this as a constraint so that
+ * we get more consistent errors
+ */
+ return pattern_from_constraints (CONSTRAINT_IS_BOOLEAN);
+}
+
+static GVariant *
+boolean_get_value (AST *ast,
+ const GVariantType *type,
+ GError **error)
+{
+ Boolean *boolean = (Boolean *) ast;
+
+ return g_variant_new_boolean (boolean->value);
+}
+
+static GSList *
+boolean_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ Boolean *boolean = (Boolean *) ast;
+ GSList *examples = NULL;
+
+ g_assert_cmpint (index, ==, 0);
+
+ if (*constraints & CONSTRAINT_IS_BOOLEAN)
+ {
+ *constraints &= ~CONSTRAINT_IS_BOOLEAN;
+
+ examples = g_slist_prepend (examples,
+ g_strdup (boolean->value ?
+ "true" : "false"));
+ }
+
+ return examples;
+}
+
+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,
+ boolean_get_value,
+ boolean_get_examples,
+ 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 Pattern *
+positional_get_pattern (AST *ast,
+ GError **error)
+{
+ Positional *positional = (Positional *) ast;
+
+ return pattern_from_type (g_variant_get_type (positional->value));
+}
+
+static GVariant *
+positional_get_value (AST *ast,
+ const GVariantType *type,
+ GError **error)
+{
+ Positional *positional = (Positional *) ast;
+ GVariant *tmp;
+
+ g_assert (positional->value != NULL);
+ g_assert (g_variant_matches (positional->value, type));
+
+ /* XXX if _get is called more than once then
+ * things get messed up with respect to floating refs.
+ */
+ tmp = positional->value;
+ positional->value = NULL;
+
+ return tmp;
+}
+
+static GSList *
+positional_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ g_assert_not_reached ();
+}
+
+static void
+positional_free (AST *ast)
+{
+ Positional *positional = (Positional *) ast;
+
+ if (positional->value)
+ g_variant_unref (g_variant_ref_sink (positional->value));
+ 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,
+ positional_get_examples,
+ positional_free
+ };
+ Positional *positional;
+ const gchar *format;
+ gchar *token;
+
+ token = token_stream_get (stream);
+ g_assert (token[0] == '%');
+
+ format = token + 1;
+
+ positional = g_slice_new (Positional);
+ positional->ast.class = &positional_class;
+ positional->value = g_variant_new_va (NULL, &format, app);
+
+ if (*format)
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "invalid GVariant format string: %s", token + 1);
+
+ g_variant_unref (g_variant_ref_sink (positional->value));
+ g_slice_free (Positional, positional);
+ g_free (token);
+
+ return NULL;
+ }
+
+ g_free (token);
+
+ return (AST *) positional;
+}
+
+typedef struct
+{
+ AST ast;
+
+ GVariantType *type;
+ AST *child;
+} TypeDecl;
+
+static Pattern *
+typedecl_get_pattern (AST *ast,
+ GError **error)
+{
+ TypeDecl *decl = (TypeDecl *) ast;
+
+ return pattern_from_type (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 GSList *
+typedecl_get_examples (AST *ast,
+ gint index,
+ Constraints *constraints)
+{
+ g_assert_not_reached ();
+}
+
+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,
+ typedecl_get_examples,
+ 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))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Invalid type declaration '%s'", token);
+ g_free (token);
+
+ return NULL;
+ }
+
+ type = g_variant_type_new (token + 1);
+
+ if (!g_variant_type_is_concrete (type))
+ {
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Type declarations must be concrete: '%s'", token);
+ g_variant_type_free (type);
+ g_free (token);
+
+ return NULL;
+ }
+
+ 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, "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
+ {
+ gchar *token;
+
+ token = token_stream_get (stream);
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "unknown keyword: %s", token);
+ g_free (token);
+
+ 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)
+{
+ if (token_stream_peek (stream, '['))
+ return array_parse (stream, app, error);
+
+ if (token_stream_peek (stream, '('))
+ return tuple_parse (stream, app, error);
+
+ if (token_stream_peek (stream, '<'))
+ return variant_parse (stream, app, error);
+
+ if (token_stream_peek (stream, '{'))
+ return dictionary_parse (stream, app, error);
+
+ if (app && token_stream_peek (stream, '%'))
+ return positional_parse (stream, app, error);
+
+ if (token_stream_consume (stream, "true"))
+ return boolean_new (TRUE);
+
+ if (token_stream_consume (stream, "false"))
+ return boolean_new (FALSE);
+
+ if (token_stream_peek (stream, '@') || token_stream_is_keyword (stream))
+ return typedecl_parse (stream, app, error);
+
+ if (token_stream_is_numeric (stream) ||
+ token_stream_peek (stream, '\'') ||
+ token_stream_peek (stream, '"'))
+ return terminal_parse (stream, app, error);
+
+ {
+ gchar *token;
+
+ token = token_stream_get (stream);
+
+ if (token)
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Don't know what to do with token: %s\n", token);
+
+ else
+ g_set_error (error, G_VARIANT_PARSE_ERROR,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+ "Unexpected end of text");
+
+ g_free (token);
+
+ return NULL;
+ }
+}
+
+GVariant *
+g_variant_parsef_va (const gchar *format,
+ va_list *app)
+{
+ TokenStream stream = { format };
+ GError *error = NULL;
+ GVariant *result;
+ AST *ast;
+
+ g_return_val_if_fail (format != NULL, NULL);
+ g_return_val_if_fail (app != NULL, NULL);
+
+ if ((ast = parse (&stream, app, &error)) == NULL)
+ {
+ g_critical ("g_variant_parsef: %s", error->message);
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ result = ast_resolve (ast, &error);
+ ast_free (ast);
+
+ if (result == NULL)
+ {
+ g_critical ("g_variant_parsef: %s", error->message);
+ g_error_free (error);
+ }
+
+ return result;
+}
+
+GVariant *
+g_variant_parsef (const gchar *format,
+ ...)
+{
+ GVariant *result;
+ va_list ap;
+
+ va_start (ap, format);
+ result = g_variant_parsef_va (format, &ap);
+ va_end (ap);
+
+ return result;
+}
+
+GVariant *
+g_variant_parse (const gchar *text,
+ gint text_length,
+ const GVariantType *type,
+ GError **error)
+{
+ TokenStream stream = { text };
+ GVariant *result;
+ gchar *tmp;
+ AST *ast;
+
+ g_return_val_if_fail (text != NULL || text_length == 0, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (text_length != -1)
+ {
+ tmp = g_strndup (text, text_length);
+ stream.stream = tmp;
+ }
+ else
+ tmp = NULL;
+
+ if ((ast = parse (&stream, NULL, error)) == NULL)
+ return NULL;
+
+ result = ast_resolve (ast, error);
+ ast_free (ast);
+
+ g_free (tmp);
+
+ return result;
+}
+
+#define _gvariant_parser_c_
+#include "galiasdef.c"
diff --git a/glib/gvariant.h b/glib/gvariant.h
index d8d7cba..958da3c 100644
--- a/glib/gvariant.h
+++ b/glib/gvariant.h
@@ -145,6 +145,16 @@ GVariantBuilder *g_variant_builder_new (GVarian
GVariant *g_variant_builder_end (GVariantBuilder *builder);
void g_variant_builder_cancel (GVariantBuilder *builder);
+/* text parsing */
+GVariant * g_variant_parse (const gchar *text,
+ gint text_length,
+ const GVariantType *type,
+ GError **error);
+GVariant * g_variant_parsef (const gchar *format,
+ ...);
+GVariant * g_variant_parsef_va (const gchar *format,
+ va_list *app);
+
/* markup printing/parsing */
GString *g_variant_markup_print (GVariant *value,
GString *string,
@@ -167,7 +177,7 @@ GVariant *g_variant_markup_parse (const g
G_END_DECLS
#define G_VARIANT_BUILDER_ERROR \
- g_quark_from_static_string ("g-variant-builder-error-quark")
+ (g_quark_from_static_string ("g-variant-builder-error-quark"))
typedef enum
{
@@ -177,4 +187,14 @@ typedef enum
G_VARIANT_BUILDER_ERROR_TYPE
} GVariantBuilderError;
+#define G_VARIANT_PARSE_ERROR \
+ (g_quark_from_string ("GVariantParseError"))
+
+typedef enum
+{
+ G_VARIANT_PARSE_ERROR_EMPTY_ARRAY,
+ G_VARIANT_PARSE_ERROR_TYPE_MISMATCH,
+ G_VARIANT_PARSE_ERROR_SYNTAX,
+} GVariantParseError;
+
#endif /* _gvariant_h_ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]