[glib/gvariant] Add parser for text-format GVariants



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]