* Author: Ryan Lortie <desrt@desrt.ca>
*/
+#include "config.h"
+
#include <stdlib.h>
#include <string.h>
#include <errno.h>
-#include <glib.h>
+#include "gerror.h"
+#include "gquark.h"
+#include "gstring.h"
+#include "gstrfuncs.h"
+#include "gtestutils.h"
+#include "gvariant.h"
+#include "gvarianttype.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.
**/
GQuark
g_variant_parser_get_error_quark (void)
parser_set_error_va (GError **error,
SourceRef *location,
SourceRef *other,
+ gint code,
const gchar *format,
va_list ap)
{
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);
}
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);
}
token_stream_set_error (TokenStream *stream,
GError **error,
gboolean this_token,
+ gint code,
const gchar *format,
...)
{
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++;
if (stream->stream == stream->end || *stream->stream == '\0')
{
stream->this = stream->stream;
- return;
+ return FALSE;
}
switch (stream->stream[0])
stream->this = stream->stream;
stream->stream = end;
+
+ return TRUE;
}
static void
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;
}
gchar first_char,
gchar second_char)
{
- token_stream_prepare (stream);
+ 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]) &&
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] == '-' ||
}
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
if (!token_stream_consume (stream, token))
{
token_stream_set_error (stream, error, FALSE,
+ G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN,
"expected `%s'%s", token, purpose);
return FALSE;
}
{
gchar *result;
- token_stream_prepare (stream);
+ if (!token_stream_prepare (stream))
+ return NULL;
result = g_strndup (stream->this, stream->stream - stream->this);
ast_set_error (AST *ast,
GError **error,
AST *other_ast,
+ gint code,
const gchar *format,
...)
{
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);
}
typestr = g_variant_type_dup_string (type);
ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_TYPE_ERROR,
"can not parse as value of type `%s'",
typestr);
g_free (typestr);
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;
merged = pattern_coalesce (pattern, tmp);
g_free (pattern);
pattern = merged;
- g_free (tmp);
if (merged == NULL)
/* set coalescence implies pairwise coalescence (i think).
* 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;
j++;
}
+
}
+
+ g_free (tmp);
}
return pattern;
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;
}
{
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);
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);
}
if (!strchr ("bynqiuxthdsogNS", key_char))
{
ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_BASIC_TYPE_EXPECTED,
"dictionary keys must have basic types");
return NULL;
}
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);
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);
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);
{
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;
}
{
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;
}
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;
}
{
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 '\\':
{
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;
if (!unicode_unescape (token, &i, str, &j, 8, &ref, error))
{
g_free (token);
+ g_free (str);
return NULL;
}
continue;
{
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':
parser_set_error (error, &ref, NULL,
+ G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
"unterminated string constant");
g_free (token);
return NULL;
Number *number = (Number *) ast;
if (strchr (number->token, '.') ||
- (!g_str_has_prefix (number->token, "0x") &&
- strchr (number->token, 'e')))
+ (!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");
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;
}
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;
}
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;
}
ref.end = ref.start + 1;
parser_set_error (error, &ref, NULL,
+ G_VARIANT_PARSE_ERROR_INVALID_CHARACTER,
"invalid character in number");
return NULL;
}
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;
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);
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);
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;
}
}
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);
token_stream_is_keyword (stream))
result = typedecl_parse (stream, app, error);
- else if (token_stream_is_numeric (stream))
- result = number_parse (stream, app, error);
-
else if (token_stream_peek (stream, '\'') ||
token_stream_peek (stream, '"'))
result = string_parse (stream, app, error);
else
{
- token_stream_set_error (stream, error, FALSE, "expected value");
+ token_stream_set_error (stream, error, FALSE,
+ G_VARIANT_PARSE_ERROR_VALUE_EXPECTED,
+ "expected value");
return NULL;
}
*
* A single #GVariant is parsed from the content of @text.
*
+ * The format is described <link linkend='gvariant-text'>here</link>.
+ *
* 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.
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);
*
* 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