glib/glib/gvariant-parser.c
Christian Hergert 18a33f72db introspection: use (nullable) or (optional) instead of (allow-none)
If we have an input parameter (or return value) we need to use (nullable).
However, if it is an (inout) or (out) parameter, (optional) is sufficient.

It looks like (nullable) could be used for everything according to the
Annotation documentation, but (optional) is more specific.
2016-11-22 14:14:37 -08:00

2803 lines
69 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, 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 "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
* 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.
**/
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)
{
return g_variant_parse_error_quark ();
}
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)
{
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);
}
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, code, format, ap);
va_end (ap);
}
typedef struct
{
const gchar *start;
const gchar *stream;
const gchar *end;
const gchar *this;
} TokenStream;
G_GNUC_PRINTF(5, 6)
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}.
* Also: ] and > are never in format strings.
*/
for (end = stream->stream + 1;
end != stream->end && *end != '\0' && *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;
}
static 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);
}
G_GNUC_PRINTF(5, 6)
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;
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)
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;
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;
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: (nullable): a #GVariantType, or %NULL
* @text: a string containing a GVariant in text form
* @limit: (nullable): a pointer to the end of @text, or %NULL
* @endptr: (nullable): a location to store the end pointer, or %NULL
* @error: (nullable): 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.
*
* 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. It is never floating, and must be freed with
* g_variant_unref().
*
* In case of any error, %NULL will be returned. If @error is non-%NULL
* 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 non-floating reference to a #GVariant, or %NULL
**/
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
*
* 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.
*
* 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,
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
*
* 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.
*
* 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].
*
* Consider this simple example:
* |[<!-- language="C" -->
* g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
* ]|
*
* 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
* |[<!-- 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
* 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 "%@".
*
* Returns: a new floating #GVariant instance
**/
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().
*
* 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:
*
* |[<!-- language="C" -->
* GVariant *
* make_pointless_dictionary (void)
* {
* GVariantBuilder builder;
* int i;
*
* 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);
* }
* ]|
*
* 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);
}
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;
*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);
}