[kdbus] sync with kdbus (kdbus.h - commit: 5ae1ecac44cb)
[platform/upstream/glib.git] / glib / gvariant-parser.c
index 93a5dd3..218f526 100644 (file)
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  *
  * Author: Ryan Lortie <desrt@desrt.ca>
  */
 
+#include "config.h"
+
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
-#include <glib.h>
 
-#include "galias.h"
+#include "gerror.h"
+#include "gquark.h"
+#include "gstring.h"
+#include "gstrfuncs.h"
+#include "gtestutils.h"
+#include "gvariant.h"
+#include "gvarianttype.h"
+#include "gslice.h"
+#include "gthread.h"
 
 /*
  * two-pass algorithm
  **/
 /**
  * GVariantParseError:
- * @G_VARIANT_PARSE_ERROR_FAILED: generic error
+ * @G_VARIANT_PARSE_ERROR_FAILED: generic error (unused)
+ * @G_VARIANT_PARSE_ERROR_BASIC_TYPE_EXPECTED: a non-basic #GVariantType was given where a basic type was expected
+ * @G_VARIANT_PARSE_ERROR_CANNOT_INFER_TYPE: cannot infer the #GVariantType
+ * @G_VARIANT_PARSE_ERROR_DEFINITE_TYPE_EXPECTED: an indefinite #GVariantType was given where a definite type was expected
+ * @G_VARIANT_PARSE_ERROR_INPUT_NOT_AT_END: extra data after parsing finished
+ * @G_VARIANT_PARSE_ERROR_INVALID_CHARACTER: invalid character in number or unicode escape
+ * @G_VARIANT_PARSE_ERROR_INVALID_FORMAT_STRING: not a valid #GVariant format string
+ * @G_VARIANT_PARSE_ERROR_INVALID_OBJECT_PATH: not a valid object path
+ * @G_VARIANT_PARSE_ERROR_INVALID_SIGNATURE: not a valid type signature
+ * @G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING: not a valid #GVariant type string
+ * @G_VARIANT_PARSE_ERROR_NO_COMMON_TYPE: could not find a common type for array entries
+ * @G_VARIANT_PARSE_ERROR_NUMBER_OUT_OF_RANGE: the numerical value is out of range of the given type
+ * @G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG: the numerical value is out of range for any type
+ * @G_VARIANT_PARSE_ERROR_TYPE_ERROR: cannot parse as variant of the specified type
+ * @G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN: an unexpected token was encountered
+ * @G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD: an unknown keyword was encountered
+ * @G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT: unterminated string constant
+ * @G_VARIANT_PARSE_ERROR_VALUE_EXPECTED: no value given
  *
- * Error codes returned by parsing text-format GVariants.  Currently the
- * parser makes no distinction between different types of error.
+ * Error codes returned by parsing text-format GVariants.
  **/
+G_DEFINE_QUARK (g-variant-parse-error-quark, g_variant_parse_error)
+
+/**
+ * g_variant_parser_get_error_quark:
+ *
+ * Same as g_variant_error_quark().
+ *
+ * Deprecated: Use g_variant_parse_error_quark() instead.
+ */
 GQuark
 g_variant_parser_get_error_quark (void)
 {
-  static GQuark the_quark;
-
-  if (the_quark == 0)
-    the_quark = g_quark_from_static_string ("g-variant-parse-error-quark");
-
-  return the_quark;
+  return g_variant_parse_error_quark ();
 }
 
 typedef struct
@@ -62,10 +89,12 @@ typedef struct
   gint start, end;
 } SourceRef;
 
+G_GNUC_PRINTF(5, 0)
 static void
 parser_set_error_va (GError      **error,
                      SourceRef    *location,
                      SourceRef    *other,
+                     gint          code,
                      const gchar  *format,
                      va_list       ap)
 {
@@ -84,21 +113,23 @@ parser_set_error_va (GError      **error,
   g_string_append_c (msg, ':');
 
   g_string_append_vprintf (msg, format, ap);
-  g_set_error_literal (error, G_VARIANT_PARSE_ERROR, 0, msg->str);
+  g_set_error_literal (error, G_VARIANT_PARSE_ERROR, code, msg->str);
   g_string_free (msg, TRUE);
 }
 
+G_GNUC_PRINTF(5, 6)
 static void
 parser_set_error (GError      **error,
                   SourceRef    *location,
                   SourceRef    *other,
+                  gint          code,
                   const gchar  *format,
                   ...)
 {
   va_list ap;
 
   va_start (ap, format);
-  parser_set_error_va (error, location, other, format, ap);
+  parser_set_error_va (error, location, other, code, format, ap);
   va_end (ap);
 }
 
@@ -112,10 +143,12 @@ typedef struct
 } TokenStream;
 
 
+G_GNUC_PRINTF(5, 6)
 static void
 token_stream_set_error (TokenStream  *stream,
                         GError      **error,
                         gboolean      this_token,
+                        gint          code,
                         const gchar  *format,
                         ...)
 {
@@ -130,18 +163,18 @@ token_stream_set_error (TokenStream  *stream,
     ref.end = ref.start;
 
   va_start (ap, format);
-  parser_set_error_va (error, &ref, NULL, format, ap);
+  parser_set_error_va (error, &ref, NULL, code, format, ap);
   va_end (ap);
 }
 
-static void
+static gboolean
 token_stream_prepare (TokenStream *stream)
 {
   gint brackets = 0;
   const gchar *end;
 
   if (stream->this != NULL)
-    return;
+    return TRUE;
 
   while (stream->stream != stream->end && g_ascii_isspace (*stream->stream))
     stream->stream++;
@@ -149,7 +182,7 @@ token_stream_prepare (TokenStream *stream)
   if (stream->stream == stream->end || *stream->stream == '\0')
     {
       stream->this = stream->stream;
-      return;
+      return FALSE;
     }
 
   switch (stream->stream[0])
@@ -163,7 +196,22 @@ token_stream_prepare (TokenStream *stream)
           break;
       break;
 
-    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+    case 'b':
+      if (stream->stream[1] == '\'' || stream->stream[1] == '"')
+        {
+          for (end = stream->stream + 2; end != stream->end; end++)
+            if (*end == stream->stream[1] || *end == '\0' ||
+                (*end == '\\' && (++end == stream->end || *end == '\0')))
+              break;
+
+          if (end != stream->end && *end)
+            end++;
+          break;
+        }
+
+      else    /* ↓↓↓ */;
+
+    case 'a': /* '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':
@@ -173,13 +221,24 @@ token_stream_prepare (TokenStream *stream)
           break;
       break;
 
+    case '\'': case '"':
+      for (end = stream->stream + 1; end != stream->end; end++)
+        if (*end == stream->stream[0] || *end == '\0' ||
+            (*end == '\\' && (++end == stream->end || *end == '\0')))
+          break;
+
+      if (end != stream->end && *end)
+        end++;
+      break;
+
     case '@': case '%':
       /* stop at the first space, comma, colon or unmatched bracket.
        * deals nicely with cases like (%i, %i) or {%i: %i}.
+       * Also: ] and > are never in format strings.
        */
       for (end = stream->stream + 1;
            end != stream->end && *end != ',' &&
-           *end != ':' && !g_ascii_isspace (*end);
+           *end != ':' && *end != '>' && *end != ']' && !g_ascii_isspace (*end);
            end++)
 
         if (*end == '(' || *end == '{')
@@ -190,16 +249,6 @@ token_stream_prepare (TokenStream *stream)
 
       break;
 
-    case '\'': case '"':
-      for (end = stream->stream + 1; end != stream->end; end++)
-        if (*end == stream->stream[0] || *end == '\0' ||
-            (*end == '\\' && (++end == stream->end || *end == '\0')))
-          break;
-
-      if (end != stream->end && *end)
-        end++;
-      break;
-
     default:
       end = stream->stream + 1;
       break;
@@ -207,6 +256,8 @@ token_stream_prepare (TokenStream *stream)
 
   stream->this = stream->stream;
   stream->stream = end;
+
+  return TRUE;
 }
 
 static void
@@ -219,23 +270,39 @@ static gboolean
 token_stream_peek (TokenStream *stream,
                    gchar        first_char)
 {
-  token_stream_prepare (stream);
+  if (!token_stream_prepare (stream))
+    return FALSE;
 
   return stream->this[0] == first_char;
 }
 
 static gboolean
+token_stream_peek2 (TokenStream *stream,
+                    gchar        first_char,
+                    gchar        second_char)
+{
+  if (!token_stream_prepare (stream))
+    return FALSE;
+
+  return stream->this[0] == first_char &&
+         stream->this[1] == second_char;
+}
+
+static gboolean
 token_stream_is_keyword (TokenStream *stream)
 {
-  token_stream_prepare (stream);
+  if (!token_stream_prepare (stream))
+    return FALSE;
 
-  return g_ascii_isalpha (stream->this[0]);
+  return g_ascii_isalpha (stream->this[0]) &&
+         g_ascii_isalpha (stream->this[1]);
 }
 
 static gboolean
 token_stream_is_numeric (TokenStream *stream)
 {
-  token_stream_prepare (stream);
+  if (!token_stream_prepare (stream))
+    return FALSE;
 
   return (g_ascii_isdigit (stream->this[0]) ||
           stream->this[0] == '-' ||
@@ -244,21 +311,25 @@ token_stream_is_numeric (TokenStream *stream)
 }
 
 static gboolean
-token_stream_consume (TokenStream *stream,
-                      const gchar *token)
+token_stream_peek_string (TokenStream *stream,
+                          const gchar *token)
 {
   gint length = strlen (token);
 
-  token_stream_prepare (stream);
+  return token_stream_prepare (stream) &&
+         stream->stream - stream->this == length &&
+         memcmp (stream->this, token, length) == 0;
+}
 
-  if (stream->stream - stream->this == length &&
-      memcmp (stream->this, token, length) == 0)
-    {
-      token_stream_next (stream);
-      return TRUE;
-    }
+static gboolean
+token_stream_consume (TokenStream *stream,
+                      const gchar *token)
+{
+  if (!token_stream_peek_string (stream, token))
+    return FALSE;
 
-  return FALSE;
+  token_stream_next (stream);
+  return TRUE;
 }
 
 static gboolean
@@ -271,7 +342,8 @@ token_stream_require (TokenStream  *stream,
   if (!token_stream_consume (stream, token))
     {
       token_stream_set_error (stream, error, FALSE,
-                              "expected `%s'%s", token, purpose);
+                              G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN,
+                              "expected '%s'%s", token, purpose);
       return FALSE;
     }
 
@@ -293,7 +365,8 @@ token_stream_get (TokenStream *stream)
 {
   gchar *result;
 
-  token_stream_prepare (stream);
+  if (!token_stream_prepare (stream))
+    return NULL;
 
   result = g_strndup (stream->this, stream->stream - stream->this);
 
@@ -315,7 +388,7 @@ token_stream_end_ref (TokenStream *stream,
   ref->end = stream->stream - stream->start;
 }
 
-void
+static void
 pattern_copy (gchar       **out,
               const gchar **in)
 {
@@ -382,7 +455,13 @@ pattern_coalesce (const gchar *left,
               (*one)++;
             }
 
-          else if (**one == 'N' && strchr ("ynqiuxthd", **the_other))
+          else if (**one == 'N' && strchr ("ynqiuxthfd", **the_other))
+            {
+              *out++ = *(*the_other)++;
+              (*one)++;
+            }
+
+          else if (**one == 'D' && (**the_other == 'f' || **the_other == 'd'))
             {
               *out++ = *(*the_other)++;
               (*one)++;
@@ -467,10 +546,12 @@ ast_free (AST *ast)
   ast->class->free (ast);
 }
 
+G_GNUC_PRINTF(5, 6)
 static void
 ast_set_error (AST          *ast,
                GError      **error,
                AST          *other_ast,
+               gint          code,
                const gchar  *format,
                ...)
 {
@@ -479,6 +560,7 @@ ast_set_error (AST          *ast,
   va_start (ap, format);
   parser_set_error_va (error, &ast->source_ref,
                        other_ast ? & other_ast->source_ref : NULL,
+                       code,
                        format, ap);
   va_end (ap);
 }
@@ -492,7 +574,8 @@ ast_type_error (AST                 *ast,
 
   typestr = g_variant_type_dup_string (type);
   ast_set_error (ast, error, NULL,
-                 "can not parse as value of type `%s'",
+                 G_VARIANT_PARSE_ERROR_TYPE_ERROR,
+                 "can not parse as value of type '%s'",
                  typestr);
   g_free (typestr);
 
@@ -522,7 +605,9 @@ ast_resolve (AST     *ast,
     switch (pattern[i])
       {
       case '*':
-        ast_set_error (ast, error, NULL, "unable to infer type");
+        ast_set_error (ast, error, NULL,
+                       G_VARIANT_PARSE_ERROR_CANNOT_INFER_TYPE,
+                       "unable to infer type");
         g_free (pattern);
         return NULL;
 
@@ -537,6 +622,10 @@ ast_resolve (AST     *ast,
         pattern[j++] = 'i';
         break;
 
+      case 'D':
+        pattern[j++] = 'd';
+        break;
+
       default:
         pattern[j++] = pattern[i];
         break;
@@ -635,6 +724,7 @@ ast_array_get_pattern (AST    **array,
                    * report the error.  note: 'j' is first.
                    */
                   ast_set_error (array[j], error, array[i],
+                                 G_VARIANT_PARSE_ERROR_NO_COMMON_TYPE,
                                  "unable to find a common type");
                   g_free (tmp);
                   return NULL;
@@ -642,7 +732,10 @@ ast_array_get_pattern (AST    **array,
 
               j++;
             }
+
         }
+
+      g_free (tmp);
     }
 
   return pattern;
@@ -739,7 +832,9 @@ maybe_parse (TokenStream  *stream,
 
   else if (!token_stream_consume (stream, "nothing"))
     {
-      token_stream_set_error (stream, error, TRUE, "unknown keyword");
+      token_stream_set_error (stream, error, TRUE,
+                              G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD,
+                              "unknown keyword");
       return NULL;
     }
 
@@ -870,7 +965,7 @@ array_parse (TokenStream  *stream,
 
       if (need_comma &&
           !token_stream_require (stream, ",",
-                                 " or `]' to follow array element",
+                                 " or ']' to follow array element",
                                  error))
         goto error;
 
@@ -949,6 +1044,12 @@ tuple_get_value (AST                 *ast,
     {
       GVariant *child;
 
+      if (childtype == NULL)
+        {
+          g_variant_builder_clear (&builder);
+          return ast_type_error (ast, type, error);
+        }
+
       if (!(child = ast_get_value (tuple->children[i], childtype, error)))
         {
           g_variant_builder_clear (&builder);
@@ -959,6 +1060,12 @@ tuple_get_value (AST                 *ast,
       childtype = g_variant_type_next (childtype);
     }
 
+  if (childtype != NULL)
+    {
+      g_variant_builder_clear (&builder);
+      return ast_type_error (ast, type, error);
+    }
+
   return g_variant_builder_end (&builder);
 }
 
@@ -997,7 +1104,7 @@ tuple_parse (TokenStream  *stream,
 
       if (need_comma &&
           !token_stream_require (stream, ",",
-                                 " or `)' to follow tuple element",
+                                 " or ')' to follow tuple element",
                                  error))
         goto error;
 
@@ -1057,7 +1164,9 @@ variant_get_value (AST                 *ast,
   Variant *variant = (Variant *) ast;
   GVariant *child;
 
-  g_assert (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT));
+  if (!g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT))
+    return ast_type_error (ast, type, error);
+
   child = ast_resolve (variant->value, error);
 
   if (child == NULL)
@@ -1147,9 +1256,10 @@ dictionary_get_pattern (AST     *ast,
   /* the basic types,
    * plus undetermined number type and undetermined string type.
    */
-  if (!strchr ("bynqiuxthdsogNS", key_char))
+  if (!strchr ("bynqiuxthfdsogNDS", key_char))
     {
       ast_set_error (ast, error, NULL,
+                     G_VARIANT_PARSE_ERROR_BASIC_TYPE_EXPECTED,
                      "dictionary keys must have basic types");
       return NULL;
     }
@@ -1189,7 +1299,7 @@ dictionary_get_value (AST                 *ast,
       if (!(subvalue = ast_get_value (dict->keys[0], subtype, error)))
         {
           g_variant_builder_clear (&builder);
-          return FALSE;
+          return NULL;
         }
       g_variant_builder_add_value (&builder, subvalue);
 
@@ -1197,7 +1307,7 @@ dictionary_get_value (AST                 *ast,
       if (!(subvalue = ast_get_value (dict->values[0], subtype, error)))
         {
           g_variant_builder_clear (&builder);
-          return FALSE;
+          return NULL;
         }
       g_variant_builder_add_value (&builder, subvalue);
 
@@ -1227,14 +1337,14 @@ dictionary_get_value (AST                 *ast,
           if (!(subvalue = ast_get_value (dict->keys[i], key, error)))
             {
               g_variant_builder_clear (&builder);
-              return FALSE;
+              return NULL;
             }
           g_variant_builder_add_value (&builder, subvalue);
 
           if (!(subvalue = ast_get_value (dict->values[i], val, error)))
             {
               g_variant_builder_clear (&builder);
-              return FALSE;
+              return NULL;
             }
           g_variant_builder_add_value (&builder, subvalue);
           g_variant_builder_close (&builder);
@@ -1297,7 +1407,7 @@ dictionary_parse (TokenStream  *stream,
   only_one = token_stream_consume (stream, ",");
   if (!only_one &&
       !token_stream_require (stream, ":",
-                             " or `,' to follow dictionary entry key",
+                             " or ',' to follow dictionary entry key",
                              error))
     goto error;
 
@@ -1323,7 +1433,7 @@ dictionary_parse (TokenStream  *stream,
       AST *child;
 
       if (!token_stream_require (stream, ",",
-                                 " or `}' to follow dictionary entry", error))
+                                 " or '}' to follow dictionary entry", error))
         goto error;
 
       child = parse (stream, app, error);
@@ -1385,7 +1495,9 @@ string_get_value (AST                 *ast,
     {
       if (!g_variant_is_object_path (string->string))
         {
-          ast_set_error (ast, error, NULL, "not a valid object path");
+          ast_set_error (ast, error, NULL,
+                         G_VARIANT_PARSE_ERROR_INVALID_OBJECT_PATH,
+                         "not a valid object path");
           return NULL;
         }
 
@@ -1396,7 +1508,9 @@ string_get_value (AST                 *ast,
     {
       if (!g_variant_is_signature (string->string))
         {
-          ast_set_error (ast, error, NULL, "not a valid signature");
+          ast_set_error (ast, error, NULL,
+                         G_VARIANT_PARSE_ERROR_INVALID_SIGNATURE,
+                         "not a valid signature");
           return NULL;
         }
 
@@ -1440,6 +1554,7 @@ unicode_unescape (const gchar  *src,
   if (value == 0 || end != buffer + length)
     {
       parser_set_error (error, ref, NULL,
+                        G_VARIANT_PARSE_ERROR_INVALID_CHARACTER,
                         "invalid %d-character unicode escape", length);
       return FALSE;
     }
@@ -1485,8 +1600,10 @@ string_parse (TokenStream  *stream,
       {
       case '\0':
         parser_set_error (error, &ref, NULL,
+                          G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
                           "unterminated string constant");
         g_free (token);
+        g_free (str);
         return NULL;
 
       case '\\':
@@ -1494,14 +1611,17 @@ string_parse (TokenStream  *stream,
           {
           case '\0':
             parser_set_error (error, &ref, NULL,
+                              G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
                               "unterminated string constant");
             g_free (token);
+            g_free (str);
             return NULL;
 
           case 'u':
             if (!unicode_unescape (token, &i, str, &j, 4, &ref, error))
               {
                 g_free (token);
+                g_free (str);
                 return NULL;
               }
             continue;
@@ -1510,6 +1630,7 @@ string_parse (TokenStream  *stream,
             if (!unicode_unescape (token, &i, str, &j, 8, &ref, error))
               {
                 g_free (token);
+                g_free (str);
                 return NULL;
               }
             continue;
@@ -1542,6 +1663,131 @@ string_parse (TokenStream  *stream,
 typedef struct
 {
   AST ast;
+  gchar *string;
+} ByteString;
+
+static gchar *
+bytestring_get_pattern (AST     *ast,
+                        GError **error)
+{
+  return g_strdup ("May");
+}
+
+static GVariant *
+bytestring_get_value (AST                 *ast,
+                      const GVariantType  *type,
+                      GError             **error)
+{
+  ByteString *string = (ByteString *) ast;
+
+  if (!g_variant_type_equal (type, G_VARIANT_TYPE_BYTESTRING))
+    return ast_type_error (ast, type, error);
+
+  return g_variant_new_bytestring (string->string);
+}
+
+static void
+bytestring_free (AST *ast)
+{
+  ByteString *string = (ByteString *) ast;
+
+  g_free (string->string);
+  g_slice_free (ByteString, string);
+}
+
+static AST *
+bytestring_parse (TokenStream  *stream,
+                  va_list      *app,
+                  GError      **error)
+{
+  static const ASTClass bytestring_class = {
+    bytestring_get_pattern,
+    maybe_wrapper, bytestring_get_value,
+    bytestring_free
+  };
+  ByteString *string;
+  SourceRef ref;
+  gchar *token;
+  gsize length;
+  gchar quote;
+  gchar *str;
+  gint i, j;
+
+  token_stream_start_ref (stream, &ref);
+  token = token_stream_get (stream);
+  token_stream_end_ref (stream, &ref);
+  g_assert (token[0] == 'b');
+  length = strlen (token);
+  quote = token[1];
+
+  str = g_malloc (length);
+  g_assert (quote == '"' || quote == '\'');
+  j = 0;
+  i = 2;
+  while (token[i] != quote)
+    switch (token[i])
+      {
+      case '\0':
+        parser_set_error (error, &ref, NULL,
+                          G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
+                          "unterminated string constant");
+        g_free (token);
+        return NULL;
+
+      case '\\':
+        switch (token[++i])
+          {
+          case '\0':
+            parser_set_error (error, &ref, NULL,
+                              G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
+                              "unterminated string constant");
+            g_free (token);
+            return NULL;
+
+          case '0': case '1': case '2': case '3':
+          case '4': case '5': case '6': case '7':
+            {
+              /* up to 3 characters */
+              guchar val = token[i++] - '0';
+
+              if ('0' <= token[i] && token[i] < '8')
+                val = (val << 3) | (token[i++] - '0');
+
+              if ('0' <= token[i] && token[i] < '8')
+                val = (val << 3) | (token[i++] - '0');
+
+              str[j++] = val;
+            }
+            continue;
+
+          case 'a': str[j++] = '\a'; i++; continue;
+          case 'b': str[j++] = '\b'; i++; continue;
+          case 'f': str[j++] = '\f'; i++; continue;
+          case 'n': str[j++] = '\n'; i++; continue;
+          case 'r': str[j++] = '\r'; i++; continue;
+          case 't': str[j++] = '\t'; i++; continue;
+          case 'v': str[j++] = '\v'; i++; continue;
+          case '\n': i++; continue;
+          }
+
+      default:
+        str[j++] = token[i++];
+      }
+  str[j++] = '\0';
+  g_free (token);
+
+  string = g_slice_new (ByteString);
+  string->ast.class = &bytestring_class;
+  string->string = str;
+
+  token_stream_next (stream);
+
+  return (AST *) string;
+}
+
+typedef struct
+{
+  AST ast;
 
   gchar *token;
 } Number;
@@ -1553,9 +1799,10 @@ number_get_pattern (AST     *ast,
   Number *number = (Number *) ast;
 
   if (strchr (number->token, '.') ||
-      (!g_str_has_prefix (number->token, "0x") &&
-       strchr (number->token, 'e')))
-    return g_strdup ("Md");
+      (!g_str_has_prefix (number->token, "0x") && strchr (number->token, 'e')) ||
+      strstr (number->token, "inf") ||
+      strstr (number->token, "nan"))
+    return g_strdup ("MD");
 
   return g_strdup ("MN");
 }
@@ -1565,7 +1812,9 @@ number_overflow (AST                 *ast,
                  const GVariantType  *type,
                  GError             **error)
 {
-  ast_set_error (ast, error, NULL, "number out of range for type `%c'",
+  ast_set_error (ast, error, NULL,
+                 G_VARIANT_PARSE_ERROR_NUMBER_OUT_OF_RANGE,
+                 "number out of range for type '%c'",
                  g_variant_type_peek_string (type)[0]);
   return NULL;
 }
@@ -1578,22 +1827,23 @@ number_get_value (AST                 *ast,
   Number *number = (Number *) ast;
   const gchar *token;
   gboolean negative;
-  gboolean floating;
   guint64 abs_val;
   gdouble dbl_val;
+  gchar typechar;
   gchar *end;
 
+  typechar = *g_variant_type_peek_string (type);
   token = number->token;
 
-  if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+  if (typechar == 'f' || typechar == 'd')
     {
-      floating = TRUE;
-
       errno = 0;
       dbl_val = g_ascii_strtod (token, &end);
       if (dbl_val != 0.0 && errno == ERANGE)
         {
-          ast_set_error (ast, error, NULL, "number too big for any type");
+          ast_set_error (ast, error, NULL,
+                         G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG,
+                         "number too big for any type");
           return NULL;
         }
 
@@ -1603,7 +1853,6 @@ number_get_value (AST                 *ast,
     }
   else
     {
-      floating = FALSE;
       negative = token[0] == '-';
       if (token[0] == '-')
         token++;
@@ -1612,7 +1861,9 @@ number_get_value (AST                 *ast,
       abs_val = g_ascii_strtoull (token, &end, 0);
       if (abs_val == G_MAXUINT64 && errno == ERANGE)
         {
-          ast_set_error (ast, error, NULL, "integer too big for any type");
+          ast_set_error (ast, error, NULL,
+                         G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG,
+                         "integer too big for any type");
           return NULL;
         }
 
@@ -1632,14 +1883,12 @@ number_get_value (AST                 *ast,
       ref.end = ref.start + 1;
 
       parser_set_error (error, &ref, NULL,
+                        G_VARIANT_PARSE_ERROR_INVALID_CHARACTER,
                         "invalid character in number");
       return NULL;
      }
 
-  if (floating)
-    return g_variant_new_double (dbl_val);
-
-  switch (*g_variant_type_peek_string (type))
+  switch (typechar)
     {
     case 'y':
       if (negative || abs_val > G_MAXUINT8)
@@ -1654,7 +1903,7 @@ number_get_value (AST                 *ast,
     case 'q':
       if (negative || abs_val > G_MAXUINT16)
         return number_overflow (ast, type, error);
-      return g_variant_new_uint16 (negative ? -abs_val : abs_val);
+      return g_variant_new_uint16 (abs_val);
 
     case 'i':
       if (abs_val - negative > G_MAXINT32)
@@ -1664,7 +1913,7 @@ number_get_value (AST                 *ast,
     case 'u':
       if (negative || abs_val > G_MAXUINT32)
         return number_overflow (ast, type, error);
-      return g_variant_new_uint32 (negative ? -abs_val : abs_val);
+      return g_variant_new_uint32 (abs_val);
 
     case 'x':
       if (abs_val - negative > G_MAXINT64)
@@ -1674,13 +1923,19 @@ number_get_value (AST                 *ast,
     case 't':
       if (negative)
         return number_overflow (ast, type, error);
-      return g_variant_new_uint64 (negative ? -abs_val : abs_val);
+      return g_variant_new_uint64 (abs_val);
 
     case 'h':
       if (abs_val - negative > G_MAXINT32)
         return number_overflow (ast, type, error);
       return g_variant_new_handle (negative ? -abs_val : abs_val);
 
+    case 'f':
+      return g_variant_new_float (dbl_val);
+
+    case 'd':
+      return g_variant_new_double (dbl_val);
+
     default:
       return ast_type_error (ast, type, error);
     }
@@ -1842,6 +2097,7 @@ positional_parse (TokenStream  *stream,
   if (*endptr || positional->value == NULL)
     {
       token_stream_set_error (stream, error, TRUE,
+                              G_VARIANT_PARSE_ERROR_INVALID_FORMAT_STRING,
                               "invalid GVariant format string");
       /* memory management doesn't matter in case of programmer error. */
       return NULL;
@@ -1913,6 +2169,7 @@ typedecl_parse (TokenStream  *stream,
       if (!g_variant_type_string_is_valid (token + 1))
         {
           token_stream_set_error (stream, error, TRUE,
+                                  G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
                                   "invalid type declaration");
           g_free (token);
 
@@ -1924,6 +2181,7 @@ typedecl_parse (TokenStream  *stream,
       if (!g_variant_type_is_definite (type))
         {
           token_stream_set_error (stream, error, TRUE,
+                                  G_VARIANT_PARSE_ERROR_DEFINITE_TYPE_EXPECTED,
                                   "type declarations must be definite");
           g_variant_type_free (type);
           g_free (token);
@@ -1963,6 +2221,9 @@ typedecl_parse (TokenStream  *stream,
       else if (token_stream_consume (stream, "uint64"))
         type = g_variant_type_copy (G_VARIANT_TYPE_UINT64);
 
+      else if (token_stream_consume (stream, "float"))
+        type = g_variant_type_copy (G_VARIANT_TYPE_FLOAT);
+
       else if (token_stream_consume (stream, "double"))
         type = g_variant_type_copy (G_VARIANT_TYPE_DOUBLE);
 
@@ -1977,7 +2238,9 @@ typedecl_parse (TokenStream  *stream,
 
       else
         {
-          token_stream_set_error (stream, error, TRUE, "unknown keyword");
+          token_stream_set_error (stream, error, TRUE,
+                                  G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD,
+                                  "unknown keyword");
           return NULL;
         }
     }
@@ -2028,6 +2291,11 @@ parse (TokenStream  *stream,
   else if (token_stream_consume (stream, "false"))
     result = boolean_new (FALSE);
 
+  else if (token_stream_is_numeric (stream) ||
+           token_stream_peek_string (stream, "inf") ||
+           token_stream_peek_string (stream, "nan"))
+    result = number_parse (stream, app, error);
+
   else if (token_stream_peek (stream, 'n') ||
            token_stream_peek (stream, 'j'))
     result = maybe_parse (stream, app, error);
@@ -2036,16 +2304,19 @@ parse (TokenStream  *stream,
            token_stream_is_keyword (stream))
     result = typedecl_parse (stream, app, error);
 
-  else if (token_stream_is_numeric (stream))
-    result = number_parse (stream, app, error);
-
   else if (token_stream_peek (stream, '\'') ||
            token_stream_peek (stream, '"'))
     result = string_parse (stream, app, error);
 
+  else if (token_stream_peek2 (stream, 'b', '\'') ||
+           token_stream_peek2 (stream, 'b', '"'))
+    result = bytestring_parse (stream, app, error);
+
   else
     {
-      token_stream_set_error (stream, error, FALSE, "expected value");
+      token_stream_set_error (stream, error, FALSE,
+                              G_VARIANT_PARSE_ERROR_VALUE_EXPECTED,
+                              "expected value");
       return NULL;
     }
 
@@ -2060,17 +2331,18 @@ parse (TokenStream  *stream,
 
 /**
  * g_variant_parse:
- * @type: a #GVariantType, or %NULL
+ * @type: (allow-none): a #GVariantType, or %NULL
  * @text: a string containing a GVariant in text form
- * @limit: a pointer to the end of @text, or %NULL
- * @endptr: a location to store the end pointer, or %NULL
- * @error: a pointer to a %NULL #GError pointer, or %NULL
- * @Returns: a reference to a #GVariant, or %NULL
+ * @limit: (allow-none): a pointer to the end of @text, or %NULL
+ * @endptr: (allow-none): a location to store the end pointer, or %NULL
+ * @error: (allow-none): a pointer to a %NULL #GError pointer, or %NULL
  *
  * Parses a #GVariant from a text representation.
  *
  * A single #GVariant is parsed from the content of @text.
  *
+ * The format is described [here][gvariant-text].
+ *
  * The memory at @limit will never be accessed and the parser behaves as
  * if the character at @limit is the nul terminator.  This has the
  * effect of bounding @text.
@@ -2091,10 +2363,12 @@ parse (TokenStream  *stream,
  * is returned.
  *
  * In case of any error, %NULL will be returned.  If @error is non-%NULL
- * then it will be set to reflect the error that occured.
+ * then it will be set to reflect the error that occurred.
  *
  * Officially, the language understood by the parser is "any string
  * produced by g_variant_print()".
+ *
+ * Returns: a reference to a #GVariant, or %NULL
  **/
 GVariant *
 g_variant_parse (const GVariantType  *type,
@@ -2137,6 +2411,7 @@ g_variant_parse (const GVariantType  *type,
                                     stream.stream - text };
 
                   parser_set_error (error, &ref, NULL,
+                                    G_VARIANT_PARSE_ERROR_INPUT_NOT_AT_END,
                                     "expected end of input");
                   g_variant_unref (result);
 
@@ -2157,7 +2432,6 @@ g_variant_parse (const GVariantType  *type,
  * g_variant_new_parsed_va:
  * @format: a text format #GVariant
  * @app: a pointer to a #va_list
- * @returns: a new, usually floating, #GVariant
  *
  * Parses @format and returns the result.
  *
@@ -2170,12 +2444,18 @@ g_variant_parse (const GVariantType  *type,
  * #GVariant pointer will be returned unmodified, without adding any
  * additional references.
  *
+ * Note that the arguments in @app must be of the correct width for their types
+ * specified in @format when collected into the #va_list. See
+ * the [GVariant varargs documentation][gvariant-varargs].
+ *
  * In order to behave correctly in all cases it is necessary for the
  * calling function to g_variant_ref_sink() the return result before
  * returning control to the user that originally provided the pointer.
  * At this point, the caller will have their own full reference to the
  * result.  This can also be done by adding the result to a container,
  * or by passing it to another g_variant_new() call.
+ *
+ * Returns: a new, usually floating, #GVariant
  **/
 GVariant *
 g_variant_new_parsed_va (const gchar *format,
@@ -2212,25 +2492,30 @@ g_variant_new_parsed_va (const gchar *format,
  * g_variant_new_parsed:
  * @format: a text format #GVariant
  * @...: arguments as per @format
- * @returns: a new floating #GVariant instance
  *
  * Parses @format and returns the result.
  *
- * @format must be a text format #GVariant with one extention: at any
+ * @format must be a text format #GVariant with one extension: at any
  * point that a value may appear in the text, a '%' character followed
  * by a GVariant format string (as per g_variant_new()) may appear.  In
  * that case, the same arguments are collected from the argument list as
  * g_variant_new() would have collected.
  *
- * Consider this simple example:
+ * Note that the arguments must be of the correct width for their types
+ * specified in @format. This can be achieved by casting them. See
+ * the [GVariant varargs documentation][gvariant-varargs].
  *
- * <informalexample><programlisting>
+ * Consider this simple example:
+ * |[<!-- language="C" --> 
  *  g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
- * </programlisting></informalexample>
+ * ]|
  *
  * In the example, the variable argument parameters are collected and
  * filled in as if they were part of the original string to produce the
- * result of <code>[('one', 1), ('two', 2), ('three', 3)]</code>.
+ * result of
+ * |[<!-- language="C" --> 
+ * [('one', 1), ('two', 2), ('three', 3)]
+ * ]|
  *
  * This function is intended only to be used with @format as a string
  * literal.  Any parse error is fatal to the calling process.  If you
@@ -2238,8 +2523,10 @@ g_variant_new_parsed_va (const gchar *format,
  *
  * You may not use this function to return, unmodified, a single
  * #GVariant pointer from the argument list.  ie: @format may not solely
- * be anything along the lines of "%*", "%?", "%r", or anything starting
+ * be anything along the lines of "%*", "%?", "\%r", or anything starting
  * with "%@".
+ *
+ * Returns: a new floating #GVariant instance
  **/
 GVariant *
 g_variant_new_parsed (const gchar *format,
@@ -2267,25 +2554,29 @@ g_variant_new_parsed (const gchar *format,
  * calling g_variant_new_parsed() followed by
  * g_variant_builder_add_value().
  *
+ * Note that the arguments must be of the correct width for their types
+ * specified in @format_string. This can be achieved by casting them. See
+ * the [GVariant varargs documentation][gvariant-varargs].
+ *
  * This function might be used as follows:
  *
- * <programlisting>
+ * |[<!-- language="C" --> 
  * GVariant *
  * make_pointless_dictionary (void)
  * {
- *   GVariantBuilder *builder;
+ *   GVariantBuilder builder;
  *   int i;
  *
- *   builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
- *   g_variant_builder_add_parsed (builder, "{'width', <%i>}", 600);
- *   g_variant_builder_add_parsed (builder, "{'title', <%s>}", "foo");
- *   g_variant_builder_add_parsed (builder, "{'transparency', <0.5>}");
- *   return g_variant_builder_end (builder);
+ *   g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ *   g_variant_builder_add_parsed (&builder, "{'width', <%i>}", 600);
+ *   g_variant_builder_add_parsed (&builder, "{'title', <%s>}", "foo");
+ *   g_variant_builder_add_parsed (&builder, "{'transparency', <0.5>}");
+ *   return g_variant_builder_end (&builder);
  * }
- * </programlisting>
+ * ]|
  *
  * Since: 2.26
- **/
+ */
 void
 g_variant_builder_add_parsed (GVariantBuilder *builder,
                               const gchar     *format,
@@ -2298,6 +2589,224 @@ g_variant_builder_add_parsed (GVariantBuilder *builder,
   va_end (ap);
 }
 
+static gboolean
+parse_num (const gchar *num,
+           const gchar *limit,
+           gint        *result)
+{
+  gchar *endptr;
+  gint64 bignum;
+
+  bignum = g_ascii_strtoll (num, &endptr, 10);
+
+  if (endptr != limit)
+    return FALSE;
+
+  if (bignum < 0 || bignum > G_MAXINT)
+    return FALSE;
 
-#define __G_VARIANT_PARSER_C__
-#include "galiasdef.c"
+  *result = bignum;
+
+  return TRUE;
+}
+
+static void
+add_last_line (GString     *err,
+               const gchar *str)
+{
+  const gchar *last_nl;
+  gchar *chomped;
+  gint i;
+
+  /* This is an error at the end of input.  If we have a file
+   * with newlines, that's probably the empty string after the
+   * last newline, which is not the most useful thing to show.
+   *
+   * Instead, show the last line of non-whitespace that we have
+   * and put the pointer at the end of it.
+   */
+  chomped = g_strchomp (g_strdup (str));
+  last_nl = strrchr (chomped, '\n');
+  if (last_nl == NULL)
+    last_nl = chomped;
+  else
+    last_nl++;
+
+  /* Print the last line like so:
+   *
+   *   [1, 2, 3,
+   *            ^
+   */
+  g_string_append (err, "  ");
+  if (last_nl[0])
+    g_string_append (err, last_nl);
+  else
+    g_string_append (err, "(empty input)");
+  g_string_append (err, "\n  ");
+  for (i = 0; last_nl[i]; i++)
+    g_string_append_c (err, ' ');
+  g_string_append (err, "^\n");
+  g_free (chomped);
+}
+
+static void
+add_lines_from_range (GString     *err,
+                      const gchar *str,
+                      const gchar *start1,
+                      const gchar *end1,
+                      const gchar *start2,
+                      const gchar *end2)
+{
+  while (str < end1 || str < end2)
+    {
+      const gchar *nl;
+
+      nl = str + strcspn (str, "\n");
+
+      if ((start1 < nl && str < end1) || (start2 < nl && str < end2))
+        {
+          const gchar *s;
+
+          /* We're going to print this line */
+          g_string_append (err, "  ");
+          g_string_append_len (err, str, nl - str);
+          g_string_append (err, "\n  ");
+
+          /* And add underlines... */
+          for (s = str; s < nl; s++)
+            {
+              if ((start1 <= s && s < end1) || (start2 <= s && s < end2))
+                g_string_append_c (err, '^');
+              else
+                g_string_append_c (err, ' ');
+            }
+          g_string_append_c (err, '\n');
+        }
+
+      if (!*nl)
+        break;
+
+      str = nl + 1;
+    }
+}
+
+/**
+ * g_variant_parse_error_print_context:
+ * @error: a #GError from the #GVariantParseError domain
+ * @source_str: the string that was given to the parser
+ *
+ * Pretty-prints a message showing the context of a #GVariant parse
+ * error within the string for which parsing was attempted.
+ *
+ * The resulting string is suitable for output to the console or other
+ * monospace media where newlines are treated in the usual way.
+ *
+ * The message will typically look something like one of the following:
+ *
+ * |[
+ * unterminated string constant:
+ *   (1, 2, 3, 'abc
+ *             ^^^^
+ * ]|
+ *
+ * or
+ *
+ * |[
+ * unable to find a common type:
+ *   [1, 2, 3, 'str']
+ *    ^        ^^^^^
+ * ]|
+ *
+ * The format of the message may change in a future version.
+ *
+ * @error must have come from a failed attempt to g_variant_parse() and
+ * @source_str must be exactly the same string that caused the error.
+ * If @source_str was not nul-terminated when you passed it to
+ * g_variant_parse() then you must add nul termination before using this
+ * function.
+ *
+ * Returns: (transfer full): the printed message
+ *
+ * Since: 2.40
+ **/
+gchar *
+g_variant_parse_error_print_context (GError      *error,
+                                     const gchar *source_str)
+{
+  const gchar *colon, *dash, *comma;
+  gboolean success = FALSE;
+  GString *err;
+
+  g_return_val_if_fail (error->domain == G_VARIANT_PARSE_ERROR, FALSE);
+
+  /* We can only have a limited number of possible types of ranges
+   * emitted from the parser:
+   *
+   *  - a:          -- usually errors from the tokeniser (eof, invalid char, etc.)
+   *  - a-b:        -- usually errors from handling one single token
+   *  - a-b,c-d:    -- errors involving two tokens (ie: type inferencing)
+   *
+   * We never see, for example "a,c".
+   */
+
+  colon = strchr (error->message, ':');
+  dash = strchr (error->message, '-');
+  comma = strchr (error->message, ',');
+
+  if (!colon)
+    return NULL;
+
+  err = g_string_new (colon + 1);
+  g_string_append (err, ":\n");
+
+  if (dash == NULL || colon < dash)
+    {
+      gint point;
+
+      /* we have a single point */
+      if (!parse_num (error->message, colon, &point))
+        goto out;
+
+      if (point >= strlen (source_str))
+        /* the error is at the end of the input */
+        add_last_line (err, source_str);
+      else
+        /* otherwise just treat it as a error at a thin range */
+        add_lines_from_range (err, source_str, source_str + point, source_str + point + 1, NULL, NULL);
+    }
+  else
+    {
+      /* We have one or two ranges... */
+      if (comma && comma < colon)
+        {
+          gint start1, end1, start2, end2;
+          const gchar *dash2;
+
+          /* Two ranges */
+          dash2 = strchr (comma, '-');
+
+          if (!parse_num (error->message, dash, &start1) || !parse_num (dash + 1, comma, &end1) ||
+              !parse_num (comma + 1, dash2, &start2) || !parse_num (dash2 + 1, colon, &end2))
+            goto out;
+
+          add_lines_from_range (err, source_str,
+                                source_str + start1, source_str + end1,
+                                source_str + start2, source_str + end2);
+        }
+      else
+        {
+          gint start, end;
+
+          /* One range */
+          if (!parse_num (error->message, dash, &start) || !parse_num (dash + 1, colon, &end))
+            goto out;
+
+          add_lines_from_range (err, source_str, source_str + start, source_str + end, NULL, NULL);
+        }
+    }
+
+  success = TRUE;
+
+out:
+  return g_string_free (err, !success);
+}