glib/glib/gvariant-parser.c
Ryan Lortie 23f684454f Drop dead code in GVariant parser
There is no chance that an unsigned integer value will be negative after
we do the bounds check that enforces its non-negativity.

Caught by Matthias running Coverity.
2011-06-14 22:45:06 -04:00

2548 lines
62 KiB
C

/*
* Copyright © 2009, 2010 Codethink Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the licence, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <errno.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
* designed by ryan lortie and william hua
* designed in itb-229 and at ghazi's, 2009.
*/
/**
* G_VARIANT_PARSE_ERROR:
*
* Error domain for GVariant text format parsing. Specific error codes
* are not currently defined for this domain. See #GError for
* information on error domains.
**/
/**
* GVariantParseError:
* @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.
**/
GQuark
g_variant_parser_get_error_quark (void)
{
static GQuark the_quark;
if (the_quark == 0)
the_quark = g_quark_from_static_string ("g-variant-parse-error-quark");
return the_quark;
}
typedef struct
{
gint start, end;
} SourceRef;
static void
parser_set_error_va (GError **error,
SourceRef *location,
SourceRef *other,
gint code,
const gchar *format,
va_list ap)
{
GString *msg = g_string_new (NULL);
if (location->start == location->end)
g_string_append_printf (msg, "%d", location->start);
else
g_string_append_printf (msg, "%d-%d", location->start, location->end);
if (other != NULL)
{
g_assert (other->start != other->end);
g_string_append_printf (msg, ",%d-%d", other->start, other->end);
}
g_string_append_c (msg, ':');
g_string_append_vprintf (msg, format, ap);
g_set_error_literal (error, G_VARIANT_PARSE_ERROR, code, msg->str);
g_string_free (msg, TRUE);
}
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, code, format, ap);
va_end (ap);
}
typedef struct
{
const gchar *start;
const gchar *stream;
const gchar *end;
const gchar *this;
} TokenStream;
static void
token_stream_set_error (TokenStream *stream,
GError **error,
gboolean this_token,
gint code,
const gchar *format,
...)
{
SourceRef ref;
va_list ap;
ref.start = stream->this - stream->start;
if (this_token)
ref.end = stream->stream - stream->start;
else
ref.end = ref.start;
va_start (ap, format);
parser_set_error_va (error, &ref, NULL, code, format, ap);
va_end (ap);
}
static gboolean
token_stream_prepare (TokenStream *stream)
{
gint brackets = 0;
const gchar *end;
if (stream->this != NULL)
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 FALSE;
}
switch (stream->stream[0])
{
case '-': case '+': case '.': case '0': case '1': case '2':
case '3': case '4': case '5': case '6': case '7': case '8':
case '9':
for (end = stream->stream; end != stream->end; end++)
if (!g_ascii_isalnum (*end) &&
*end != '-' && *end != '+' && *end != '.')
break;
break;
case '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':
case 'y': case 'z':
for (end = stream->stream; end != stream->end; end++)
if (!g_ascii_isalnum (*end))
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}.
*/
for (end = stream->stream + 1;
end != stream->end && *end != ',' &&
*end != ':' && *end != '>' && !g_ascii_isspace (*end);
end++)
if (*end == '(' || *end == '{')
brackets++;
else if ((*end == ')' || *end == '}') && !brackets--)
break;
break;
default:
end = stream->stream + 1;
break;
}
stream->this = stream->stream;
stream->stream = end;
return TRUE;
}
static void
token_stream_next (TokenStream *stream)
{
stream->this = NULL;
}
static gboolean
token_stream_peek (TokenStream *stream,
gchar first_char)
{
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)
{
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)
{
if (!token_stream_prepare (stream))
return FALSE;
return (g_ascii_isdigit (stream->this[0]) ||
stream->this[0] == '-' ||
stream->this[0] == '+' ||
stream->this[0] == '.');
}
static gboolean
token_stream_peek_string (TokenStream *stream,
const gchar *token)
{
gint length = strlen (token);
return token_stream_prepare (stream) &&
stream->stream - stream->this == length &&
memcmp (stream->this, token, length) == 0;
}
static gboolean
token_stream_consume (TokenStream *stream,
const gchar *token)
{
if (!token_stream_peek_string (stream, token))
return FALSE;
token_stream_next (stream);
return TRUE;
}
static gboolean
token_stream_require (TokenStream *stream,
const gchar *token,
const gchar *purpose,
GError **error)
{
if (!token_stream_consume (stream, token))
{
token_stream_set_error (stream, error, FALSE,
G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN,
"expected `%s'%s", token, purpose);
return FALSE;
}
return TRUE;
}
static void
token_stream_assert (TokenStream *stream,
const gchar *token)
{
gboolean correct_token;
correct_token = token_stream_consume (stream, token);
g_assert (correct_token);
}
static gchar *
token_stream_get (TokenStream *stream)
{
gchar *result;
if (!token_stream_prepare (stream))
return NULL;
result = g_strndup (stream->this, stream->stream - stream->this);
return result;
}
static void
token_stream_start_ref (TokenStream *stream,
SourceRef *ref)
{
token_stream_prepare (stream);
ref->start = stream->this - stream->start;
}
static void
token_stream_end_ref (TokenStream *stream,
SourceRef *ref)
{
ref->end = stream->stream - stream->start;
}
void
pattern_copy (gchar **out,
const gchar **in)
{
gint brackets = 0;
while (**in == 'a' || **in == 'm' || **in == 'M')
*(*out)++ = *(*in)++;
do
{
if (**in == '(' || **in == '{')
brackets++;
else if (**in == ')' || **in == '}')
brackets--;
*(*out)++ = *(*in)++;
}
while (brackets);
}
static gchar *
pattern_coalesce (const gchar *left,
const gchar *right)
{
gchar *result;
gchar *out;
/* the length of the output is loosely bound by the sum of the input
* lengths, not simply the greater of the two lengths.
*
* (*(iii)) + ((iii)*) ((iii)(iii))
*
* 8 + 8 = 12
*/
out = result = g_malloc (strlen (left) + strlen (right));
while (*left && *right)
{
if (*left == *right)
{
*out++ = *left++;
right++;
}
else
{
const gchar **one = &left, **the_other = &right;
again:
if (**one == '*' && **the_other != ')')
{
pattern_copy (&out, the_other);
(*one)++;
}
else if (**one == 'M' && **the_other == 'm')
{
*out++ = *(*the_other)++;
}
else if (**one == 'M' && **the_other != 'm')
{
(*one)++;
}
else if (**one == 'N' && strchr ("ynqiuxthd", **the_other))
{
*out++ = *(*the_other)++;
(*one)++;
}
else if (**one == 'S' && strchr ("sog", **the_other))
{
*out++ = *(*the_other)++;
(*one)++;
}
else if (one == &left)
{
one = &right, the_other = &left;
goto again;
}
else
break;
}
}
if (*left || *right)
{
g_free (result);
result = NULL;
}
else
*out++ = '\0';
return result;
}
typedef struct _AST AST;
typedef gchar * (*get_pattern_func) (AST *ast,
GError **error);
typedef GVariant * (*get_value_func) (AST *ast,
const GVariantType *type,
GError **error);
typedef GVariant * (*get_base_value_func) (AST *ast,
const GVariantType *type,
GError **error);
typedef void (*free_func) (AST *ast);
typedef struct
{
gchar * (* get_pattern) (AST *ast,
GError **error);
GVariant * (* get_value) (AST *ast,
const GVariantType *type,
GError **error);
GVariant * (* get_base_value) (AST *ast,
const GVariantType *type,
GError **error);
void (* free) (AST *ast);
} ASTClass;
struct _AST
{
const ASTClass *class;
SourceRef source_ref;
};
static gchar *
ast_get_pattern (AST *ast,
GError **error)
{
return ast->class->get_pattern (ast, error);
}
static GVariant *
ast_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
return ast->class->get_value (ast, type, error);
}
static void
ast_free (AST *ast)
{
ast->class->free (ast);
}
static void
ast_set_error (AST *ast,
GError **error,
AST *other_ast,
gint code,
const gchar *format,
...)
{
va_list ap;
va_start (ap, format);
parser_set_error_va (error, &ast->source_ref,
other_ast ? & other_ast->source_ref : NULL,
code,
format, ap);
va_end (ap);
}
static GVariant *
ast_type_error (AST *ast,
const GVariantType *type,
GError **error)
{
gchar *typestr;
typestr = g_variant_type_dup_string (type);
ast_set_error (ast, error, NULL,
G_VARIANT_PARSE_ERROR_TYPE_ERROR,
"can not parse as value of type `%s'",
typestr);
g_free (typestr);
return NULL;
}
static GVariant *
ast_resolve (AST *ast,
GError **error)
{
GVariant *value;
gchar *pattern;
gint i, j = 0;
pattern = ast_get_pattern (ast, error);
if (pattern == NULL)
return NULL;
/* choose reasonable defaults
*
* 1) favour non-maybe values where possible
* 2) default type for strings is 's'
* 3) default type for integers is 'i'
*/
for (i = 0; pattern[i]; i++)
switch (pattern[i])
{
case '*':
ast_set_error (ast, error, NULL,
G_VARIANT_PARSE_ERROR_CANNOT_INFER_TYPE,
"unable to infer type");
g_free (pattern);
return NULL;
case 'M':
break;
case 'S':
pattern[j++] = 's';
break;
case 'N':
pattern[j++] = 'i';
break;
default:
pattern[j++] = pattern[i];
break;
}
pattern[j++] = '\0';
value = ast_get_value (ast, G_VARIANT_TYPE (pattern), error);
g_free (pattern);
return value;
}
static AST *parse (TokenStream *stream,
va_list *app,
GError **error);
static void
ast_array_append (AST ***array,
gint *n_items,
AST *ast)
{
if ((*n_items & (*n_items - 1)) == 0)
*array = g_renew (AST *, *array, *n_items ? 2 ** n_items : 1);
(*array)[(*n_items)++] = ast;
}
static void
ast_array_free (AST **array,
gint n_items)
{
gint i;
for (i = 0; i < n_items; i++)
ast_free (array[i]);
g_free (array);
}
static gchar *
ast_array_get_pattern (AST **array,
gint n_items,
GError **error)
{
gchar *pattern;
gint i;
pattern = ast_get_pattern (array[0], error);
if (pattern == NULL)
return NULL;
for (i = 1; i < n_items; i++)
{
gchar *tmp, *merged;
tmp = ast_get_pattern (array[i], error);
if (tmp == NULL)
{
g_free (pattern);
return NULL;
}
merged = pattern_coalesce (pattern, tmp);
g_free (pattern);
pattern = merged;
if (merged == NULL)
/* set coalescence implies pairwise coalescence (i think).
* we should therefore be able to trace the failure to a single
* pair of values.
*/
{
int j = 0;
while (TRUE)
{
gchar *tmp2;
gchar *m;
/* if 'j' reaches 'i' then we failed to find the pair */
g_assert (j < i);
tmp2 = ast_get_pattern (array[j], NULL);
g_assert (tmp2 != NULL);
m = pattern_coalesce (tmp, tmp2);
g_free (tmp2);
g_free (m);
if (m == NULL)
{
/* we found a conflict between 'i' and 'j'.
*
* report the error. note: 'j' is first.
*/
ast_set_error (array[j], error, array[i],
G_VARIANT_PARSE_ERROR_NO_COMMON_TYPE,
"unable to find a common type");
g_free (tmp);
return NULL;
}
j++;
}
}
g_free (tmp);
}
return pattern;
}
typedef struct
{
AST ast;
AST *child;
} Maybe;
static gchar *
maybe_get_pattern (AST *ast,
GError **error)
{
Maybe *maybe = (Maybe *) ast;
if (maybe->child != NULL)
{
gchar *child_pattern;
gchar *pattern;
child_pattern = ast_get_pattern (maybe->child, error);
if (child_pattern == NULL)
return NULL;
pattern = g_strdup_printf ("m%s", child_pattern);
g_free (child_pattern);
return pattern;
}
return g_strdup ("m*");
}
static GVariant *
maybe_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
Maybe *maybe = (Maybe *) ast;
GVariant *value;
if (!g_variant_type_is_maybe (type))
return ast_type_error (ast, type, error);
type = g_variant_type_element (type);
if (maybe->child)
{
value = ast_get_value (maybe->child, type, error);
if (value == NULL)
return NULL;
}
else
value = NULL;
return g_variant_new_maybe (type, value);
}
static void
maybe_free (AST *ast)
{
Maybe *maybe = (Maybe *) ast;
if (maybe->child != NULL)
ast_free (maybe->child);
g_slice_free (Maybe, maybe);
}
static AST *
maybe_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass maybe_class = {
maybe_get_pattern,
maybe_get_value, NULL,
maybe_free
};
AST *child = NULL;
Maybe *maybe;
if (token_stream_consume (stream, "just"))
{
child = parse (stream, app, error);
if (child == NULL)
return NULL;
}
else if (!token_stream_consume (stream, "nothing"))
{
token_stream_set_error (stream, error, TRUE,
G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD,
"unknown keyword");
return NULL;
}
maybe = g_slice_new (Maybe);
maybe->ast.class = &maybe_class;
maybe->child = child;
return (AST *) maybe;
}
static GVariant *
maybe_wrapper (AST *ast,
const GVariantType *type,
GError **error)
{
const GVariantType *t;
GVariant *value;
int depth;
for (depth = 0, t = type;
g_variant_type_is_maybe (t);
depth++, t = g_variant_type_element (t));
value = ast->class->get_base_value (ast, t, error);
if (value == NULL)
return NULL;
while (depth--)
value = g_variant_new_maybe (NULL, value);
return value;
}
typedef struct
{
AST ast;
AST **children;
gint n_children;
} Array;
static gchar *
array_get_pattern (AST *ast,
GError **error)
{
Array *array = (Array *) ast;
gchar *pattern;
gchar *result;
if (array->n_children == 0)
return g_strdup ("Ma*");
pattern = ast_array_get_pattern (array->children, array->n_children, error);
if (pattern == NULL)
return NULL;
result = g_strdup_printf ("Ma%s", pattern);
g_free (pattern);
return result;
}
static GVariant *
array_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
Array *array = (Array *) ast;
const GVariantType *childtype;
GVariantBuilder builder;
gint i;
if (!g_variant_type_is_array (type))
return ast_type_error (ast, type, error);
g_variant_builder_init (&builder, type);
childtype = g_variant_type_element (type);
for (i = 0; i < array->n_children; i++)
{
GVariant *child;
if (!(child = ast_get_value (array->children[i], childtype, error)))
{
g_variant_builder_clear (&builder);
return NULL;
}
g_variant_builder_add_value (&builder, child);
}
return g_variant_builder_end (&builder);
}
static void
array_free (AST *ast)
{
Array *array = (Array *) ast;
ast_array_free (array->children, array->n_children);
g_slice_free (Array, array);
}
static AST *
array_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass array_class = {
array_get_pattern,
maybe_wrapper, array_get_value,
array_free
};
gboolean need_comma = FALSE;
Array *array;
array = g_slice_new (Array);
array->ast.class = &array_class;
array->children = NULL;
array->n_children = 0;
token_stream_assert (stream, "[");
while (!token_stream_consume (stream, "]"))
{
AST *child;
if (need_comma &&
!token_stream_require (stream, ",",
" or `]' to follow array element",
error))
goto error;
child = parse (stream, app, error);
if (!child)
goto error;
ast_array_append (&array->children, &array->n_children, child);
need_comma = TRUE;
}
return (AST *) array;
error:
ast_array_free (array->children, array->n_children);
g_slice_free (Array, array);
return NULL;
}
typedef struct
{
AST ast;
AST **children;
gint n_children;
} Tuple;
static gchar *
tuple_get_pattern (AST *ast,
GError **error)
{
Tuple *tuple = (Tuple *) ast;
gchar *result = NULL;
gchar **parts;
gint i;
parts = g_new (gchar *, tuple->n_children + 4);
parts[tuple->n_children + 1] = (gchar *) ")";
parts[tuple->n_children + 2] = NULL;
parts[0] = (gchar *) "M(";
for (i = 0; i < tuple->n_children; i++)
if (!(parts[i + 1] = ast_get_pattern (tuple->children[i], error)))
break;
if (i == tuple->n_children)
result = g_strjoinv ("", parts);
/* parts[0] should not be freed */
while (i)
g_free (parts[i--]);
g_free (parts);
return result;
}
static GVariant *
tuple_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
Tuple *tuple = (Tuple *) ast;
const GVariantType *childtype;
GVariantBuilder builder;
gint i;
if (!g_variant_type_is_tuple (type))
return ast_type_error (ast, type, error);
g_variant_builder_init (&builder, type);
childtype = g_variant_type_first (type);
for (i = 0; i < tuple->n_children; i++)
{
GVariant *child;
if (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);
return FALSE;
}
g_variant_builder_add_value (&builder, child);
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);
}
static void
tuple_free (AST *ast)
{
Tuple *tuple = (Tuple *) ast;
ast_array_free (tuple->children, tuple->n_children);
g_slice_free (Tuple, tuple);
}
static AST *
tuple_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass tuple_class = {
tuple_get_pattern,
maybe_wrapper, tuple_get_value,
tuple_free
};
gboolean need_comma = FALSE;
gboolean first = TRUE;
Tuple *tuple;
tuple = g_slice_new (Tuple);
tuple->ast.class = &tuple_class;
tuple->children = NULL;
tuple->n_children = 0;
token_stream_assert (stream, "(");
while (!token_stream_consume (stream, ")"))
{
AST *child;
if (need_comma &&
!token_stream_require (stream, ",",
" or `)' to follow tuple element",
error))
goto error;
child = parse (stream, app, error);
if (!child)
goto error;
ast_array_append (&tuple->children, &tuple->n_children, child);
/* the first time, we absolutely require a comma, so grab it here
* and leave need_comma = FALSE so that the code above doesn't
* require a second comma.
*
* the second and remaining times, we set need_comma = TRUE.
*/
if (first)
{
if (!token_stream_require (stream, ",",
" after first tuple element", error))
goto error;
first = FALSE;
}
else
need_comma = TRUE;
}
return (AST *) tuple;
error:
ast_array_free (tuple->children, tuple->n_children);
g_slice_free (Tuple, tuple);
return NULL;
}
typedef struct
{
AST ast;
AST *value;
} Variant;
static gchar *
variant_get_pattern (AST *ast,
GError **error)
{
return g_strdup ("Mv");
}
static GVariant *
variant_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
Variant *variant = (Variant *) ast;
GVariant *child;
g_assert (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT));
child = ast_resolve (variant->value, error);
if (child == NULL)
return NULL;
return g_variant_new_variant (child);
}
static void
variant_free (AST *ast)
{
Variant *variant = (Variant *) ast;
ast_free (variant->value);
g_slice_free (Variant, variant);
}
static AST *
variant_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass variant_class = {
variant_get_pattern,
maybe_wrapper, variant_get_value,
variant_free
};
Variant *variant;
AST *value;
token_stream_assert (stream, "<");
value = parse (stream, app, error);
if (!value)
return NULL;
if (!token_stream_require (stream, ">", " to follow variant value", error))
{
ast_free (value);
return NULL;
}
variant = g_slice_new (Variant);
variant->ast.class = &variant_class;
variant->value = value;
return (AST *) variant;
}
typedef struct
{
AST ast;
AST **keys;
AST **values;
gint n_children;
} Dictionary;
static gchar *
dictionary_get_pattern (AST *ast,
GError **error)
{
Dictionary *dict = (Dictionary *) ast;
gchar *value_pattern;
gchar *key_pattern;
gchar key_char;
gchar *result;
if (dict->n_children == 0)
return g_strdup ("Ma{**}");
key_pattern = ast_array_get_pattern (dict->keys,
abs (dict->n_children),
error);
if (key_pattern == NULL)
return NULL;
/* we can not have maybe keys */
if (key_pattern[0] == 'M')
key_char = key_pattern[1];
else
key_char = key_pattern[0];
g_free (key_pattern);
/* the basic types,
* plus undetermined number type and undetermined string type.
*/
if (!strchr ("bynqiuxthdsogNS", key_char))
{
ast_set_error (ast, error, NULL,
G_VARIANT_PARSE_ERROR_BASIC_TYPE_EXPECTED,
"dictionary keys must have basic types");
return NULL;
}
value_pattern = ast_get_pattern (dict->values[0], error);
if (value_pattern == NULL)
return NULL;
result = g_strdup_printf ("M%s{%c%s}",
dict->n_children > 0 ? "a" : "",
key_char, value_pattern);
g_free (value_pattern);
return result;
}
static GVariant *
dictionary_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
Dictionary *dict = (Dictionary *) ast;
if (dict->n_children == -1)
{
const GVariantType *subtype;
GVariantBuilder builder;
GVariant *subvalue;
if (!g_variant_type_is_dict_entry (type))
return ast_type_error (ast, type, error);
g_variant_builder_init (&builder, type);
subtype = g_variant_type_key (type);
if (!(subvalue = ast_get_value (dict->keys[0], subtype, error)))
{
g_variant_builder_clear (&builder);
return 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_clear (&builder);
return NULL;
}
g_variant_builder_add_value (&builder, subvalue);
return g_variant_builder_end (&builder);
}
else
{
const GVariantType *entry, *key, *val;
GVariantBuilder builder;
gint i;
if (!g_variant_type_is_subtype_of (type, G_VARIANT_TYPE_DICTIONARY))
return ast_type_error (ast, type, error);
entry = g_variant_type_element (type);
key = g_variant_type_key (entry);
val = g_variant_type_value (entry);
g_variant_builder_init (&builder, type);
for (i = 0; i < dict->n_children; i++)
{
GVariant *subvalue;
g_variant_builder_open (&builder, entry);
if (!(subvalue = ast_get_value (dict->keys[i], key, error)))
{
g_variant_builder_clear (&builder);
return NULL;
}
g_variant_builder_add_value (&builder, subvalue);
if (!(subvalue = ast_get_value (dict->values[i], val, error)))
{
g_variant_builder_clear (&builder);
return NULL;
}
g_variant_builder_add_value (&builder, subvalue);
g_variant_builder_close (&builder);
}
return g_variant_builder_end (&builder);
}
}
static void
dictionary_free (AST *ast)
{
Dictionary *dict = (Dictionary *) ast;
gint n_children;
if (dict->n_children > -1)
n_children = dict->n_children;
else
n_children = 1;
ast_array_free (dict->keys, n_children);
ast_array_free (dict->values, n_children);
g_slice_free (Dictionary, dict);
}
static AST *
dictionary_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass dictionary_class = {
dictionary_get_pattern,
maybe_wrapper, dictionary_get_value,
dictionary_free
};
gint n_keys, n_values;
gboolean only_one;
Dictionary *dict;
AST *first;
dict = g_slice_new (Dictionary);
dict->ast.class = &dictionary_class;
dict->keys = NULL;
dict->values = NULL;
n_keys = n_values = 0;
token_stream_assert (stream, "{");
if (token_stream_consume (stream, "}"))
{
dict->n_children = 0;
return (AST *) dict;
}
if ((first = parse (stream, app, error)) == NULL)
goto error;
ast_array_append (&dict->keys, &n_keys, first);
only_one = token_stream_consume (stream, ",");
if (!only_one &&
!token_stream_require (stream, ":",
" or `,' to follow dictionary entry key",
error))
goto error;
if ((first = parse (stream, app, error)) == NULL)
goto error;
ast_array_append (&dict->values, &n_values, first);
if (only_one)
{
if (!token_stream_require (stream, "}", " at end of dictionary entry",
error))
goto error;
g_assert (n_keys == 1 && n_values == 1);
dict->n_children = -1;
return (AST *) dict;
}
while (!token_stream_consume (stream, "}"))
{
AST *child;
if (!token_stream_require (stream, ",",
" or `}' to follow dictionary entry", error))
goto error;
child = parse (stream, app, error);
if (!child)
goto error;
ast_array_append (&dict->keys, &n_keys, child);
if (!token_stream_require (stream, ":",
" to follow dictionary entry key", error))
goto error;
child = parse (stream, app, error);
if (!child)
goto error;
ast_array_append (&dict->values, &n_values, child);
}
g_assert (n_keys == n_values);
dict->n_children = n_keys;
return (AST *) dict;
error:
ast_array_free (dict->keys, n_keys);
ast_array_free (dict->values, n_values);
g_slice_free (Dictionary, dict);
return NULL;
}
typedef struct
{
AST ast;
gchar *string;
} String;
static gchar *
string_get_pattern (AST *ast,
GError **error)
{
return g_strdup ("MS");
}
static GVariant *
string_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
String *string = (String *) ast;
if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
return g_variant_new_string (string->string);
else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH))
{
if (!g_variant_is_object_path (string->string))
{
ast_set_error (ast, error, NULL,
G_VARIANT_PARSE_ERROR_INVALID_OBJECT_PATH,
"not a valid object path");
return NULL;
}
return g_variant_new_object_path (string->string);
}
else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
{
if (!g_variant_is_signature (string->string))
{
ast_set_error (ast, error, NULL,
G_VARIANT_PARSE_ERROR_INVALID_SIGNATURE,
"not a valid signature");
return NULL;
}
return g_variant_new_signature (string->string);
}
else
return ast_type_error (ast, type, error);
}
static void
string_free (AST *ast)
{
String *string = (String *) ast;
g_free (string->string);
g_slice_free (String, string);
}
static gboolean
unicode_unescape (const gchar *src,
gint *src_ofs,
gchar *dest,
gint *dest_ofs,
gint length,
SourceRef *ref,
GError **error)
{
gchar buffer[9];
guint64 value;
gchar *end;
(*src_ofs)++;
g_assert (length < sizeof (buffer));
strncpy (buffer, src + *src_ofs, length);
buffer[length] = '\0';
value = g_ascii_strtoull (buffer, &end, 0x10);
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;
}
g_assert (value <= G_MAXUINT32);
*dest_ofs += g_unichar_to_utf8 (value, dest + *dest_ofs);
*src_ofs += length;
return TRUE;
}
static AST *
string_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass string_class = {
string_get_pattern,
maybe_wrapper, string_get_value,
string_free
};
String *string;
SourceRef ref;
gchar *token;
gsize length;
gchar quote;
gchar *str;
gint i, j;
token_stream_start_ref (stream, &ref);
token = token_stream_get (stream);
token_stream_end_ref (stream, &ref);
length = strlen (token);
quote = token[0];
str = g_malloc (length);
g_assert (quote == '"' || quote == '\'');
j = 0;
i = 1;
while (token[i] != quote)
switch (token[i])
{
case '\0':
parser_set_error (error, &ref, NULL,
G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
"unterminated string constant");
g_free (token);
g_free (str);
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);
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;
case 'U':
if (!unicode_unescape (token, &i, str, &j, 8, &ref, error))
{
g_free (token);
g_free (str);
return NULL;
}
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 (String);
string->ast.class = &string_class;
string->string = str;
token_stream_next (stream);
return (AST *) string;
}
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;
g_assert (g_variant_type_equal (type, G_VARIANT_TYPE_BYTESTRING));
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;
static gchar *
number_get_pattern (AST *ast,
GError **error)
{
Number *number = (Number *) ast;
if (strchr (number->token, '.') ||
(!g_str_has_prefix (number->token, "0x") && strchr (number->token, 'e')) ||
strstr (number->token, "inf") ||
strstr (number->token, "nan"))
return g_strdup ("Md");
return g_strdup ("MN");
}
static GVariant *
number_overflow (AST *ast,
const GVariantType *type,
GError **error)
{
ast_set_error (ast, error, NULL,
G_VARIANT_PARSE_ERROR_NUMBER_OUT_OF_RANGE,
"number out of range for type `%c'",
g_variant_type_peek_string (type)[0]);
return NULL;
}
static GVariant *
number_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
Number *number = (Number *) ast;
const gchar *token;
gboolean negative;
gboolean floating;
guint64 abs_val;
gdouble dbl_val;
gchar *end;
token = number->token;
if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
{
floating = TRUE;
errno = 0;
dbl_val = g_ascii_strtod (token, &end);
if (dbl_val != 0.0 && errno == ERANGE)
{
ast_set_error (ast, error, NULL,
G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG,
"number too big for any type");
return NULL;
}
/* silence uninitialised warnings... */
negative = FALSE;
abs_val = 0;
}
else
{
floating = FALSE;
negative = token[0] == '-';
if (token[0] == '-')
token++;
errno = 0;
abs_val = g_ascii_strtoull (token, &end, 0);
if (abs_val == G_MAXUINT64 && errno == ERANGE)
{
ast_set_error (ast, error, NULL,
G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG,
"integer too big for any type");
return NULL;
}
if (abs_val == 0)
negative = FALSE;
/* silence uninitialised warning... */
dbl_val = 0.0;
}
if (*end != '\0')
{
SourceRef ref;
ref = ast->source_ref;
ref.start += end - number->token;
ref.end = ref.start + 1;
parser_set_error (error, &ref, NULL,
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))
{
case 'y':
if (negative || abs_val > G_MAXUINT8)
return number_overflow (ast, type, error);
return g_variant_new_byte (abs_val);
case 'n':
if (abs_val - negative > G_MAXINT16)
return number_overflow (ast, type, error);
return g_variant_new_int16 (negative ? -abs_val : abs_val);
case 'q':
if (negative || abs_val > G_MAXUINT16)
return number_overflow (ast, type, error);
return g_variant_new_uint16 (abs_val);
case 'i':
if (abs_val - negative > G_MAXINT32)
return number_overflow (ast, type, error);
return g_variant_new_int32 (negative ? -abs_val : abs_val);
case 'u':
if (negative || abs_val > G_MAXUINT32)
return number_overflow (ast, type, error);
return g_variant_new_uint32 (abs_val);
case 'x':
if (abs_val - negative > G_MAXINT64)
return number_overflow (ast, type, error);
return g_variant_new_int64 (negative ? -abs_val : abs_val);
case 't':
if (negative)
return number_overflow (ast, type, error);
return g_variant_new_uint64 (abs_val);
case 'h':
if (abs_val - negative > G_MAXINT32)
return number_overflow (ast, type, error);
return g_variant_new_handle (negative ? -abs_val : abs_val);
default:
return ast_type_error (ast, type, error);
}
}
static void
number_free (AST *ast)
{
Number *number = (Number *) ast;
g_free (number->token);
g_slice_free (Number, number);
}
static AST *
number_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass number_class = {
number_get_pattern,
maybe_wrapper, number_get_value,
number_free
};
Number *number;
number = g_slice_new (Number);
number->ast.class = &number_class;
number->token = token_stream_get (stream);
token_stream_next (stream);
return (AST *) number;
}
typedef struct
{
AST ast;
gboolean value;
} Boolean;
static gchar *
boolean_get_pattern (AST *ast,
GError **error)
{
return g_strdup ("Mb");
}
static GVariant *
boolean_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
Boolean *boolean = (Boolean *) ast;
if (!g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
return ast_type_error (ast, type, error);
return g_variant_new_boolean (boolean->value);
}
static void
boolean_free (AST *ast)
{
Boolean *boolean = (Boolean *) ast;
g_slice_free (Boolean, boolean);
}
static AST *
boolean_new (gboolean value)
{
static const ASTClass boolean_class = {
boolean_get_pattern,
maybe_wrapper, boolean_get_value,
boolean_free
};
Boolean *boolean;
boolean = g_slice_new (Boolean);
boolean->ast.class = &boolean_class;
boolean->value = value;
return (AST *) boolean;
}
typedef struct
{
AST ast;
GVariant *value;
} Positional;
static gchar *
positional_get_pattern (AST *ast,
GError **error)
{
Positional *positional = (Positional *) ast;
return g_strdup (g_variant_get_type_string (positional->value));
}
static GVariant *
positional_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
Positional *positional = (Positional *) ast;
GVariant *value;
g_assert (positional->value != NULL);
if G_UNLIKELY (!g_variant_is_of_type (positional->value, type))
return ast_type_error (ast, type, error);
/* NOTE: if _get is called more than once then
* things get messed up with respect to floating refs.
*
* fortunately, this function should only ever get called once.
*/
g_assert (positional->value != NULL);
value = positional->value;
positional->value = NULL;
return value;
}
static void
positional_free (AST *ast)
{
Positional *positional = (Positional *) ast;
/* if positional->value is set, just leave it.
* memory management doesn't matter in case of programmer error.
*/
g_slice_free (Positional, positional);
}
static AST *
positional_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass positional_class = {
positional_get_pattern,
positional_get_value, NULL,
positional_free
};
Positional *positional;
const gchar *endptr;
gchar *token;
token = token_stream_get (stream);
g_assert (token[0] == '%');
positional = g_slice_new (Positional);
positional->ast.class = &positional_class;
positional->value = g_variant_new_va (token + 1, &endptr, app);
if (*endptr || positional->value == NULL)
{
token_stream_set_error (stream, error, TRUE,
G_VARIANT_PARSE_ERROR_INVALID_FORMAT_STRING,
"invalid GVariant format string");
/* memory management doesn't matter in case of programmer error. */
return NULL;
}
token_stream_next (stream);
g_free (token);
return (AST *) positional;
}
typedef struct
{
AST ast;
GVariantType *type;
AST *child;
} TypeDecl;
static gchar *
typedecl_get_pattern (AST *ast,
GError **error)
{
TypeDecl *decl = (TypeDecl *) ast;
return g_variant_type_dup_string (decl->type);
}
static GVariant *
typedecl_get_value (AST *ast,
const GVariantType *type,
GError **error)
{
TypeDecl *decl = (TypeDecl *) ast;
return ast_get_value (decl->child, type, error);
}
static void
typedecl_free (AST *ast)
{
TypeDecl *decl = (TypeDecl *) ast;
ast_free (decl->child);
g_variant_type_free (decl->type);
g_slice_free (TypeDecl, decl);
}
static AST *
typedecl_parse (TokenStream *stream,
va_list *app,
GError **error)
{
static const ASTClass typedecl_class = {
typedecl_get_pattern,
typedecl_get_value, NULL,
typedecl_free
};
GVariantType *type;
TypeDecl *decl;
AST *child;
if (token_stream_peek (stream, '@'))
{
gchar *token;
token = token_stream_get (stream);
if (!g_variant_type_string_is_valid (token + 1))
{
token_stream_set_error (stream, error, TRUE,
G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
"invalid type declaration");
g_free (token);
return NULL;
}
type = g_variant_type_new (token + 1);
if (!g_variant_type_is_definite (type))
{
token_stream_set_error (stream, error, TRUE,
G_VARIANT_PARSE_ERROR_DEFINITE_TYPE_EXPECTED,
"type declarations must be definite");
g_variant_type_free (type);
g_free (token);
return NULL;
}
token_stream_next (stream);
g_free (token);
}
else
{
if (token_stream_consume (stream, "boolean"))
type = g_variant_type_copy (G_VARIANT_TYPE_BOOLEAN);
else if (token_stream_consume (stream, "byte"))
type = g_variant_type_copy (G_VARIANT_TYPE_BYTE);
else if (token_stream_consume (stream, "int16"))
type = g_variant_type_copy (G_VARIANT_TYPE_INT16);
else if (token_stream_consume (stream, "uint16"))
type = g_variant_type_copy (G_VARIANT_TYPE_UINT16);
else if (token_stream_consume (stream, "int32"))
type = g_variant_type_copy (G_VARIANT_TYPE_INT32);
else if (token_stream_consume (stream, "handle"))
type = g_variant_type_copy (G_VARIANT_TYPE_HANDLE);
else if (token_stream_consume (stream, "uint32"))
type = g_variant_type_copy (G_VARIANT_TYPE_UINT32);
else if (token_stream_consume (stream, "int64"))
type = g_variant_type_copy (G_VARIANT_TYPE_INT64);
else if (token_stream_consume (stream, "uint64"))
type = g_variant_type_copy (G_VARIANT_TYPE_UINT64);
else if (token_stream_consume (stream, "double"))
type = g_variant_type_copy (G_VARIANT_TYPE_DOUBLE);
else if (token_stream_consume (stream, "string"))
type = g_variant_type_copy (G_VARIANT_TYPE_STRING);
else if (token_stream_consume (stream, "objectpath"))
type = g_variant_type_copy (G_VARIANT_TYPE_OBJECT_PATH);
else if (token_stream_consume (stream, "signature"))
type = g_variant_type_copy (G_VARIANT_TYPE_SIGNATURE);
else
{
token_stream_set_error (stream, error, TRUE,
G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD,
"unknown keyword");
return NULL;
}
}
if ((child = parse (stream, app, error)) == NULL)
{
g_variant_type_free (type);
return NULL;
}
decl = g_slice_new (TypeDecl);
decl->ast.class = &typedecl_class;
decl->type = type;
decl->child = child;
return (AST *) decl;
}
static AST *
parse (TokenStream *stream,
va_list *app,
GError **error)
{
SourceRef source_ref;
AST *result;
token_stream_prepare (stream);
token_stream_start_ref (stream, &source_ref);
if (token_stream_peek (stream, '['))
result = array_parse (stream, app, error);
else if (token_stream_peek (stream, '('))
result = tuple_parse (stream, app, error);
else if (token_stream_peek (stream, '<'))
result = variant_parse (stream, app, error);
else if (token_stream_peek (stream, '{'))
result = dictionary_parse (stream, app, error);
else if (app && token_stream_peek (stream, '%'))
result = positional_parse (stream, app, error);
else if (token_stream_consume (stream, "true"))
result = boolean_new (TRUE);
else if (token_stream_consume (stream, "false"))
result = boolean_new (FALSE);
else if (token_stream_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);
else if (token_stream_peek (stream, '@') ||
token_stream_is_keyword (stream))
result = typedecl_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,
G_VARIANT_PARSE_ERROR_VALUE_EXPECTED,
"expected value");
return NULL;
}
if (result != NULL)
{
token_stream_end_ref (stream, &source_ref);
result->source_ref = source_ref;
}
return result;
}
/**
* g_variant_parse:
* @type: a #GVariantType, or %NULL
* @text: a string containing a GVariant in text form
* @limit: a pointer to the end of @text, or %NULL
* @endptr: a location to store the end pointer, or %NULL
* @error: a pointer to a %NULL #GError pointer, or %NULL
* @Returns: a reference to a #GVariant, or %NULL
*
* Parses a #GVariant from a text representation.
*
* A single #GVariant is parsed from the content of @text.
*
* The 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.
*
* If @endptr is non-%NULL then @text is permitted to contain data
* following the value that this function parses and @endptr will be
* updated to point to the first character past the end of the text
* parsed by this function. If @endptr is %NULL and there is extra data
* then an error is returned.
*
* If @type is non-%NULL then the value will be parsed to have that
* type. This may result in additional parse errors (in the case that
* the parsed value doesn't fit the type) but may also result in fewer
* errors (in the case that the type would have been ambiguous, such as
* with empty arrays).
*
* In the event that the parsing is successful, the resulting #GVariant
* is returned.
*
* In case of any error, %NULL will be returned. If @error is non-%NULL
* then it will be set to reflect the error that occured.
*
* Officially, the language understood by the parser is "any string
* produced by g_variant_print()".
**/
GVariant *
g_variant_parse (const GVariantType *type,
const gchar *text,
const gchar *limit,
const gchar **endptr,
GError **error)
{
TokenStream stream = { 0, };
GVariant *result = NULL;
AST *ast;
g_return_val_if_fail (text != NULL, NULL);
g_return_val_if_fail (text == limit || text != NULL, NULL);
stream.start = text;
stream.stream = text;
stream.end = limit;
if ((ast = parse (&stream, NULL, error)))
{
if (type == NULL)
result = ast_resolve (ast, error);
else
result = ast_get_value (ast, type, error);
if (result != NULL)
{
g_variant_ref_sink (result);
if (endptr == NULL)
{
while (stream.stream != limit &&
g_ascii_isspace (*stream.stream))
stream.stream++;
if (stream.stream != limit && *stream.stream != '\0')
{
SourceRef ref = { stream.stream - text,
stream.stream - text };
parser_set_error (error, &ref, NULL,
G_VARIANT_PARSE_ERROR_INPUT_NOT_AT_END,
"expected end of input");
g_variant_unref (result);
result = NULL;
}
}
else
*endptr = stream.stream;
}
ast_free (ast);
}
return result;
}
/**
* g_variant_new_parsed_va:
* @format: a text format #GVariant
* @app: a pointer to a #va_list
* @returns: a new, usually floating, #GVariant
*
* Parses @format and returns the result.
*
* This is the version of g_variant_new_parsed() intended to be used
* from libraries.
*
* The return value will be floating if it was a newly created GVariant
* instance. In the case that @format simply specified the collection
* of a #GVariant pointer (eg: @format was "%*") then the collected
* #GVariant pointer will be returned unmodified, without adding any
* additional references.
*
* 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.
**/
GVariant *
g_variant_new_parsed_va (const gchar *format,
va_list *app)
{
TokenStream stream = { 0, };
GVariant *result = NULL;
GError *error = NULL;
AST *ast;
g_return_val_if_fail (format != NULL, NULL);
g_return_val_if_fail (app != NULL, NULL);
stream.start = format;
stream.stream = format;
stream.end = NULL;
if ((ast = parse (&stream, app, &error)))
{
result = ast_resolve (ast, &error);
ast_free (ast);
}
if (result == NULL)
g_error ("g_variant_new_parsed: %s", error->message);
if (*stream.stream)
g_error ("g_variant_new_parsed: trailing text after value");
return result;
}
/**
* g_variant_new_parsed:
* @format: a text format #GVariant
* @...: arguments as per @format
* @returns: a new floating #GVariant instance
*
* Parses @format and returns the result.
*
* @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:
*
* <informalexample><programlisting>
* 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>.
*
* This function is intended only to be used with @format as a string
* literal. Any parse error is fatal to the calling process. If you
* want to parse data from untrusted sources, use g_variant_parse().
*
* You may not use this function to return, unmodified, a single
* #GVariant pointer from the argument list. ie: @format may not solely
* be anything along the lines of "%*", "%?", "%r", or anything starting
* with "%@".
**/
GVariant *
g_variant_new_parsed (const gchar *format,
...)
{
GVariant *result;
va_list ap;
va_start (ap, format);
result = g_variant_new_parsed_va (format, &ap);
va_end (ap);
return result;
}
/**
* g_variant_builder_add_parsed:
* @builder: a #GVariantBuilder
* @format: a text format #GVariant
* @...: arguments as per @format
*
* Adds to a #GVariantBuilder.
*
* This call is a convenience wrapper that is exactly equivalent to
* calling g_variant_new_parsed() followed by
* g_variant_builder_add_value().
*
* This function might be used as follows:
*
* <programlisting>
* GVariant *
* make_pointless_dictionary (void)
* {
* 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);
* }
* </programlisting>
*
* Since: 2.26
**/
void
g_variant_builder_add_parsed (GVariantBuilder *builder,
const gchar *format,
...)
{
va_list ap;
va_start (ap, format);
g_variant_builder_add_value (builder, g_variant_new_parsed_va (format, &ap));
va_end (ap);
}