* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
+#include "config.h"
+
#include <stdlib.h>
#include <string.h>
#include <errno.h>
-#include <glib.h>
-#include "galias.h"
+#include "gerror.h"
+#include "gquark.h"
+#include "gstring.h"
+#include "gstrfuncs.h"
+#include "gtestutils.h"
+#include "gvariant.h"
+#include "gvarianttype.h"
+#include "gslice.h"
+#include "gthread.h"
/*
* two-pass algorithm
* 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:
+ *
+ * Deprecated: Use g_variant_parse_error_quark() instead.
+ */
GQuark
g_variant_parser_get_error_quark (void)
{
- static GQuark the_quark;
-
- if (the_quark == 0)
- the_quark = g_quark_from_static_string ("g-variant-parse-error-quark");
-
- return the_quark;
+ return g_variant_parse_error_quark ();
}
typedef struct
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)
{
g_string_append_c (msg, ':');
g_string_append_vprintf (msg, format, ap);
- g_set_error_literal (error, G_VARIANT_PARSE_ERROR, 0, msg->str);
+ g_set_error_literal (error, G_VARIANT_PARSE_ERROR, code, msg->str);
g_string_free (msg, TRUE);
}
+G_GNUC_PRINTF(5, 6)
static void
parser_set_error (GError **error,
SourceRef *location,
SourceRef *other,
+ gint code,
const gchar *format,
...)
{
va_list ap;
va_start (ap, format);
- parser_set_error_va (error, location, other, format, ap);
+ parser_set_error_va (error, location, other, code, format, ap);
va_end (ap);
}
} TokenStream;
+G_GNUC_PRINTF(5, 6)
static void
token_stream_set_error (TokenStream *stream,
GError **error,
gboolean this_token,
+ gint code,
const gchar *format,
...)
{
ref.end = ref.start;
va_start (ap, format);
- parser_set_error_va (error, &ref, NULL, format, ap);
+ parser_set_error_va (error, &ref, NULL, code, format, ap);
va_end (ap);
}
-static void
+static gboolean
token_stream_prepare (TokenStream *stream)
{
gint brackets = 0;
const gchar *end;
if (stream->this != NULL)
- return;
+ return TRUE;
while (stream->stream != stream->end && g_ascii_isspace (*stream->stream))
stream->stream++;
if (stream->stream == stream->end || *stream->stream == '\0')
{
stream->this = stream->stream;
- return;
+ return FALSE;
}
switch (stream->stream[0])
break;
break;
- case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'b':
+ if (stream->stream[1] == '\'' || stream->stream[1] == '"')
+ {
+ for (end = stream->stream + 2; end != stream->end; end++)
+ if (*end == stream->stream[1] || *end == '\0' ||
+ (*end == '\\' && (++end == stream->end || *end == '\0')))
+ break;
+
+ if (end != stream->end && *end)
+ end++;
+ break;
+ }
+
+ else /* ↓↓↓ */;
+
+ case 'a': /* 'b' */ case 'c': case 'd': case 'e': case 'f':
case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
case 's': case 't': case 'u': case 'v': case 'w': case 'x':
break;
break;
+ case '\'': case '"':
+ for (end = stream->stream + 1; end != stream->end; end++)
+ if (*end == stream->stream[0] || *end == '\0' ||
+ (*end == '\\' && (++end == stream->end || *end == '\0')))
+ break;
+
+ if (end != stream->end && *end)
+ end++;
+ break;
+
case '@': case '%':
/* stop at the first space, comma, colon or unmatched bracket.
* deals nicely with cases like (%i, %i) or {%i: %i}.
+ * Also: ] and > are never in format strings.
*/
for (end = stream->stream + 1;
end != stream->end && *end != ',' &&
- *end != ':' && !g_ascii_isspace (*end);
+ *end != ':' && *end != '>' && *end != ']' && !g_ascii_isspace (*end);
end++)
if (*end == '(' || *end == '{')
break;
- case '\'': case '"':
- for (end = stream->stream + 1; end != stream->end; end++)
- if (*end == stream->stream[0] || *end == '\0' ||
- (*end == '\\' && (++end == stream->end || *end == '\0')))
- break;
-
- if (end != stream->end && *end)
- end++;
- break;
-
default:
end = stream->stream + 1;
break;
stream->this = stream->stream;
stream->stream = end;
+
+ return TRUE;
}
static void
token_stream_peek (TokenStream *stream,
gchar first_char)
{
- token_stream_prepare (stream);
+ if (!token_stream_prepare (stream))
+ return FALSE;
return stream->this[0] == first_char;
}
static gboolean
+token_stream_peek2 (TokenStream *stream,
+ gchar first_char,
+ gchar second_char)
+{
+ if (!token_stream_prepare (stream))
+ return FALSE;
+
+ return stream->this[0] == first_char &&
+ stream->this[1] == second_char;
+}
+
+static gboolean
token_stream_is_keyword (TokenStream *stream)
{
- token_stream_prepare (stream);
+ if (!token_stream_prepare (stream))
+ return FALSE;
- return g_ascii_isalpha (stream->this[0]);
+ return g_ascii_isalpha (stream->this[0]) &&
+ g_ascii_isalpha (stream->this[1]);
}
static gboolean
token_stream_is_numeric (TokenStream *stream)
{
- token_stream_prepare (stream);
+ if (!token_stream_prepare (stream))
+ return FALSE;
return (g_ascii_isdigit (stream->this[0]) ||
stream->this[0] == '-' ||
}
static gboolean
-token_stream_consume (TokenStream *stream,
- const gchar *token)
+token_stream_peek_string (TokenStream *stream,
+ const gchar *token)
{
gint length = strlen (token);
- token_stream_prepare (stream);
+ return token_stream_prepare (stream) &&
+ stream->stream - stream->this == length &&
+ memcmp (stream->this, token, length) == 0;
+}
- if (stream->stream - stream->this == length &&
- memcmp (stream->this, token, length) == 0)
- {
- token_stream_next (stream);
- return TRUE;
- }
+static gboolean
+token_stream_consume (TokenStream *stream,
+ const gchar *token)
+{
+ if (!token_stream_peek_string (stream, token))
+ return FALSE;
- return FALSE;
+ token_stream_next (stream);
+ return TRUE;
}
static gboolean
if (!token_stream_consume (stream, token))
{
token_stream_set_error (stream, error, FALSE,
- "expected `%s'%s", token, purpose);
+ G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN,
+ "expected '%s'%s", token, purpose);
return FALSE;
}
{
gchar *result;
- token_stream_prepare (stream);
+ if (!token_stream_prepare (stream))
+ return NULL;
result = g_strndup (stream->this, stream->stream - stream->this);
ref->end = stream->stream - stream->start;
}
-void
+static void
pattern_copy (gchar **out,
const gchar **in)
{
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_start (ap, format);
parser_set_error_va (error, &ast->source_ref,
other_ast ? & other_ast->source_ref : NULL,
+ code,
format, ap);
va_end (ap);
}
typestr = g_variant_type_dup_string (type);
ast_set_error (ast, error, NULL,
- "can not parse as value of type `%s'",
+ G_VARIANT_PARSE_ERROR_TYPE_ERROR,
+ "can not parse as value of type '%s'",
typestr);
g_free (typestr);
switch (pattern[i])
{
case '*':
- ast_set_error (ast, error, NULL, "unable to infer type");
+ ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_CANNOT_INFER_TYPE,
+ "unable to infer type");
g_free (pattern);
return NULL;
* report the error. note: 'j' is first.
*/
ast_set_error (array[j], error, array[i],
+ G_VARIANT_PARSE_ERROR_NO_COMMON_TYPE,
"unable to find a common type");
g_free (tmp);
return NULL;
j++;
}
+
}
+
+ g_free (tmp);
}
return pattern;
else if (!token_stream_consume (stream, "nothing"))
{
- token_stream_set_error (stream, error, TRUE, "unknown keyword");
+ token_stream_set_error (stream, error, TRUE,
+ G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD,
+ "unknown keyword");
return NULL;
}
if (need_comma &&
!token_stream_require (stream, ",",
- " or `]' to follow array element",
+ " or ']' to follow array element",
error))
goto error;
{
GVariant *child;
+ if (childtype == NULL)
+ {
+ g_variant_builder_clear (&builder);
+ return ast_type_error (ast, type, error);
+ }
+
if (!(child = ast_get_value (tuple->children[i], childtype, error)))
{
g_variant_builder_clear (&builder);
childtype = g_variant_type_next (childtype);
}
+ if (childtype != NULL)
+ {
+ g_variant_builder_clear (&builder);
+ return ast_type_error (ast, type, error);
+ }
+
return g_variant_builder_end (&builder);
}
if (need_comma &&
!token_stream_require (stream, ",",
- " or `)' to follow tuple element",
+ " or ')' to follow tuple element",
error))
goto error;
Variant *variant = (Variant *) ast;
GVariant *child;
- g_assert (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT));
+ if (!g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT))
+ return ast_type_error (ast, type, error);
+
child = ast_resolve (variant->value, error);
if (child == NULL)
if (!strchr ("bynqiuxthdsogNS", key_char))
{
ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_BASIC_TYPE_EXPECTED,
"dictionary keys must have basic types");
return NULL;
}
if (!(subvalue = ast_get_value (dict->keys[0], subtype, error)))
{
g_variant_builder_clear (&builder);
- return FALSE;
+ return NULL;
}
g_variant_builder_add_value (&builder, subvalue);
if (!(subvalue = ast_get_value (dict->values[0], subtype, error)))
{
g_variant_builder_clear (&builder);
- return FALSE;
+ return NULL;
}
g_variant_builder_add_value (&builder, subvalue);
if (!(subvalue = ast_get_value (dict->keys[i], key, error)))
{
g_variant_builder_clear (&builder);
- return FALSE;
+ return NULL;
}
g_variant_builder_add_value (&builder, subvalue);
if (!(subvalue = ast_get_value (dict->values[i], val, error)))
{
g_variant_builder_clear (&builder);
- return FALSE;
+ return NULL;
}
g_variant_builder_add_value (&builder, subvalue);
g_variant_builder_close (&builder);
only_one = token_stream_consume (stream, ",");
if (!only_one &&
!token_stream_require (stream, ":",
- " or `,' to follow dictionary entry key",
+ " or ',' to follow dictionary entry key",
error))
goto error;
AST *child;
if (!token_stream_require (stream, ",",
- " or `}' to follow dictionary entry", error))
+ " or '}' to follow dictionary entry", error))
goto error;
child = parse (stream, app, error);
{
if (!g_variant_is_object_path (string->string))
{
- ast_set_error (ast, error, NULL, "not a valid object path");
+ ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_INVALID_OBJECT_PATH,
+ "not a valid object path");
return NULL;
}
{
if (!g_variant_is_signature (string->string))
{
- ast_set_error (ast, error, NULL, "not a valid signature");
+ ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_INVALID_SIGNATURE,
+ "not a valid signature");
return NULL;
}
if (value == 0 || end != buffer + length)
{
parser_set_error (error, ref, NULL,
+ G_VARIANT_PARSE_ERROR_INVALID_CHARACTER,
"invalid %d-character unicode escape", length);
return FALSE;
}
{
case '\0':
parser_set_error (error, &ref, NULL,
+ G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
"unterminated string constant");
g_free (token);
+ g_free (str);
return NULL;
case '\\':
{
case '\0':
parser_set_error (error, &ref, NULL,
+ G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
"unterminated string constant");
g_free (token);
+ g_free (str);
return NULL;
case 'u':
if (!unicode_unescape (token, &i, str, &j, 4, &ref, error))
{
g_free (token);
+ g_free (str);
return NULL;
}
continue;
if (!unicode_unescape (token, &i, str, &j, 8, &ref, error))
{
g_free (token);
+ g_free (str);
return NULL;
}
continue;
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;
Number *number = (Number *) ast;
if (strchr (number->token, '.') ||
- (!g_str_has_prefix (number->token, "0x") &&
- strchr (number->token, 'e')))
+ (!g_str_has_prefix (number->token, "0x") && strchr (number->token, 'e')) ||
+ strstr (number->token, "inf") ||
+ strstr (number->token, "nan"))
return g_strdup ("Md");
return g_strdup ("MN");
const GVariantType *type,
GError **error)
{
- ast_set_error (ast, error, NULL, "number out of range for type `%c'",
+ ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_NUMBER_OUT_OF_RANGE,
+ "number out of range for type '%c'",
g_variant_type_peek_string (type)[0]);
return NULL;
}
dbl_val = g_ascii_strtod (token, &end);
if (dbl_val != 0.0 && errno == ERANGE)
{
- ast_set_error (ast, error, NULL, "number too big for any type");
+ ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG,
+ "number too big for any type");
return NULL;
}
abs_val = g_ascii_strtoull (token, &end, 0);
if (abs_val == G_MAXUINT64 && errno == ERANGE)
{
- ast_set_error (ast, error, NULL, "integer too big for any type");
+ ast_set_error (ast, error, NULL,
+ G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG,
+ "integer too big for any type");
return NULL;
}
ref.end = ref.start + 1;
parser_set_error (error, &ref, NULL,
+ G_VARIANT_PARSE_ERROR_INVALID_CHARACTER,
"invalid character in number");
return NULL;
}
case 'q':
if (negative || abs_val > G_MAXUINT16)
return number_overflow (ast, type, error);
- return g_variant_new_uint16 (negative ? -abs_val : abs_val);
+ return g_variant_new_uint16 (abs_val);
case 'i':
if (abs_val - negative > G_MAXINT32)
case 'u':
if (negative || abs_val > G_MAXUINT32)
return number_overflow (ast, type, error);
- return g_variant_new_uint32 (negative ? -abs_val : abs_val);
+ return g_variant_new_uint32 (abs_val);
case 'x':
if (abs_val - negative > G_MAXINT64)
case 't':
if (negative)
return number_overflow (ast, type, error);
- return g_variant_new_uint64 (negative ? -abs_val : abs_val);
+ return g_variant_new_uint64 (abs_val);
case 'h':
if (abs_val - negative > G_MAXINT32)
if (*endptr || positional->value == NULL)
{
token_stream_set_error (stream, error, TRUE,
+ G_VARIANT_PARSE_ERROR_INVALID_FORMAT_STRING,
"invalid GVariant format string");
/* memory management doesn't matter in case of programmer error. */
return NULL;
if (!g_variant_type_string_is_valid (token + 1))
{
token_stream_set_error (stream, error, TRUE,
+ G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
"invalid type declaration");
g_free (token);
if (!g_variant_type_is_definite (type))
{
token_stream_set_error (stream, error, TRUE,
+ G_VARIANT_PARSE_ERROR_DEFINITE_TYPE_EXPECTED,
"type declarations must be definite");
g_variant_type_free (type);
g_free (token);
else
{
- token_stream_set_error (stream, error, TRUE, "unknown keyword");
+ token_stream_set_error (stream, error, TRUE,
+ G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD,
+ "unknown keyword");
return NULL;
}
}
else if (token_stream_consume (stream, "false"))
result = boolean_new (FALSE);
+ else if (token_stream_is_numeric (stream) ||
+ token_stream_peek_string (stream, "inf") ||
+ token_stream_peek_string (stream, "nan"))
+ result = number_parse (stream, app, error);
+
else if (token_stream_peek (stream, 'n') ||
token_stream_peek (stream, 'j'))
result = maybe_parse (stream, app, error);
token_stream_is_keyword (stream))
result = typedecl_parse (stream, app, error);
- else if (token_stream_is_numeric (stream))
- result = number_parse (stream, app, error);
-
else if (token_stream_peek (stream, '\'') ||
token_stream_peek (stream, '"'))
result = string_parse (stream, app, error);
+ else if (token_stream_peek2 (stream, 'b', '\'') ||
+ token_stream_peek2 (stream, 'b', '"'))
+ result = bytestring_parse (stream, app, error);
+
else
{
- token_stream_set_error (stream, error, FALSE, "expected value");
+ token_stream_set_error (stream, error, FALSE,
+ G_VARIANT_PARSE_ERROR_VALUE_EXPECTED,
+ "expected value");
return NULL;
}
/**
* g_variant_parse:
- * @type: a #GVariantType, or %NULL
+ * @type: (allow-none): a #GVariantType, or %NULL
* @text: a string containing a GVariant in text form
- * @limit: a pointer to the end of @text, or %NULL
- * @endptr: a location to store the end pointer, or %NULL
- * @error: a pointer to a %NULL #GError pointer, or %NULL
- * @Returns: a reference to a #GVariant, or %NULL
+ * @limit: (allow-none): a pointer to the end of @text, or %NULL
+ * @endptr: (allow-none): a location to store the end pointer, or %NULL
+ * @error: (allow-none): a pointer to a %NULL #GError pointer, or %NULL
*
* Parses a #GVariant from a text representation.
*
* A single #GVariant is parsed from the content of @text.
*
+ * The format is described <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.
* is returned.
*
* In case of any error, %NULL will be returned. If @error is non-%NULL
- * then it will be set to reflect the error that occured.
+ * then it will be set to reflect the error that occurred.
*
* Officially, the language understood by the parser is "any string
* produced by g_variant_print()".
+ *
+ * Returns: a reference to a #GVariant, or %NULL
**/
GVariant *
g_variant_parse (const GVariantType *type,
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);
* 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.
*
* 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,
* g_variant_new_parsed:
* @format: a text format #GVariant
* @...: arguments as per @format
- * @returns: a new floating #GVariant instance
*
* Parses @format and returns the result.
*
- * @format must be a text format #GVariant with one extention: at any
+ * @format must be a text format #GVariant with one extension: at any
* point that a value may appear in the text, a '%' character followed
* by a GVariant format string (as per g_variant_new()) may appear. In
* that case, the same arguments are collected from the argument list as
* g_variant_new() would have collected.
*
* Consider this simple example:
- *
- * <informalexample><programlisting>
+ * |[<!-- language="C" -->
* g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
- * </programlisting></informalexample>
+ * ]|
*
* In the example, the variable argument parameters are collected and
* filled in as if they were part of the original string to produce the
- * result of <code>[('one', 1), ('two', 2), ('three', 3)]</code>.
+ * result of
+ * |[<!-- language="C" -->
+ * [('one', 1), ('two', 2), ('three', 3)]
+ * ]|
*
* This function is intended only to be used with @format as a string
* literal. Any parse error is fatal to the calling process. If you
*
* You may not use this function to return, unmodified, a single
* #GVariant pointer from the argument list. ie: @format may not solely
- * be anything along the lines of "%*", "%?", "%r", or anything starting
+ * be anything along the lines of "%*", "%?", "\%r", or anything starting
* with "%@".
+ *
+ * Returns: a new floating #GVariant instance
**/
GVariant *
g_variant_new_parsed (const gchar *format,
*
* This function might be used as follows:
*
- * <programlisting>
+ * |[<!-- language="C" -->
* GVariant *
* make_pointless_dictionary (void)
* {
- * GVariantBuilder *builder;
+ * GVariantBuilder builder;
* int i;
*
- * builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
- * g_variant_builder_add_parsed (builder, "{'width', <%i>}", 600);
- * g_variant_builder_add_parsed (builder, "{'title', <%s>}", "foo");
- * g_variant_builder_add_parsed (builder, "{'transparency', <0.5>}");
- * return g_variant_builder_end (builder);
+ * g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ * g_variant_builder_add_parsed (&builder, "{'width', <%i>}", 600);
+ * g_variant_builder_add_parsed (&builder, "{'title', <%s>}", "foo");
+ * g_variant_builder_add_parsed (&builder, "{'transparency', <0.5>}");
+ * return g_variant_builder_end (&builder);
* }
- * </programlisting>
+ * ]|
*
* Since: 2.26
- **/
+ */
void
g_variant_builder_add_parsed (GVariantBuilder *builder,
const gchar *format,
va_list ap;
va_start (ap, format);
- g_variant_builder_add_value (builder, g_variant_new_parsed_va (format, ap));
+ 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;
-#define __G_VARIANT_PARSER_C__
-#include "galiasdef.c"
+ /* 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);
+}