X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=glib%2Fgvariant-parser.c;h=3a8c63d3caf2347eadec532c1fae499e1cf57705;hb=ecf1359191b2f796a7d63288652dd1a93525417d;hp=2e35d05044c5e6ee2ce5fd333e4ddf545a6d7f11;hpb=bf4dbdbf0e1a3ac4349980942b9e91056ce7e448;p=platform%2Fupstream%2Fglib.git diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c index 2e35d05..3a8c63d 100644 --- a/glib/gvariant-parser.c +++ b/glib/gvariant-parser.c @@ -12,19 +12,26 @@ * 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 . * * Author: Ryan Lortie */ +#include "config.h" + #include #include #include -#include -#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 @@ -32,15 +39,49 @@ * 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) { - 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 @@ -48,10 +89,12 @@ typedef struct gint start, end; } SourceRef; +G_GNUC_PRINTF(5, 0) static void parser_set_error_va (GError **error, SourceRef *location, SourceRef *other, + gint code, const gchar *format, va_list ap) { @@ -70,21 +113,23 @@ parser_set_error_va (GError **error, g_string_append_c (msg, ':'); g_string_append_vprintf (msg, format, ap); - g_set_error_literal (error, G_VARIANT_PARSE_ERROR, 0, msg->str); + g_set_error_literal (error, G_VARIANT_PARSE_ERROR, code, msg->str); g_string_free (msg, TRUE); } +G_GNUC_PRINTF(5, 6) static void parser_set_error (GError **error, SourceRef *location, SourceRef *other, + gint code, const gchar *format, ...) { va_list ap; va_start (ap, format); - parser_set_error_va (error, location, other, format, ap); + parser_set_error_va (error, location, other, code, format, ap); va_end (ap); } @@ -98,10 +143,12 @@ typedef struct } TokenStream; +G_GNUC_PRINTF(5, 6) static void token_stream_set_error (TokenStream *stream, GError **error, gboolean this_token, + gint code, const gchar *format, ...) { @@ -116,18 +163,18 @@ token_stream_set_error (TokenStream *stream, ref.end = ref.start; va_start (ap, format); - parser_set_error_va (error, &ref, NULL, format, ap); + parser_set_error_va (error, &ref, NULL, code, format, ap); va_end (ap); } -static void +static gboolean token_stream_prepare (TokenStream *stream) { gint brackets = 0; const gchar *end; if (stream->this != NULL) - return; + return TRUE; while (stream->stream != stream->end && g_ascii_isspace (*stream->stream)) stream->stream++; @@ -135,7 +182,7 @@ token_stream_prepare (TokenStream *stream) if (stream->stream == stream->end || *stream->stream == '\0') { stream->this = stream->stream; - return; + return FALSE; } switch (stream->stream[0]) @@ -149,7 +196,22 @@ token_stream_prepare (TokenStream *stream) break; break; - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'b': + if (stream->stream[1] == '\'' || stream->stream[1] == '"') + { + for (end = stream->stream + 2; end != stream->end; end++) + if (*end == stream->stream[1] || *end == '\0' || + (*end == '\\' && (++end == stream->end || *end == '\0'))) + break; + + if (end != stream->end && *end) + end++; + break; + } + + else /* ↓↓↓ */; + + case 'a': /* 'b' */ case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': @@ -159,12 +221,24 @@ token_stream_prepare (TokenStream *stream) break; break; + case '\'': case '"': + for (end = stream->stream + 1; end != stream->end; end++) + if (*end == stream->stream[0] || *end == '\0' || + (*end == '\\' && (++end == stream->end || *end == '\0'))) + break; + + if (end != stream->end && *end) + end++; + break; + case '@': case '%': - /* stop at the first space, comma or unmatched bracket. - * deals nicely with cases like (%i, %i). + /* 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 != ',' && !g_ascii_isspace (*end); + end != stream->end && *end != ',' && + *end != ':' && *end != '>' && *end != ']' && !g_ascii_isspace (*end); end++) if (*end == '(' || *end == '{') @@ -175,16 +249,6 @@ token_stream_prepare (TokenStream *stream) break; - case '\'': case '"': - for (end = stream->stream + 1; end != stream->end; end++) - if (*end == stream->stream[0] || *end == '\0' || - (*end == '\\' && (++end == stream->end || *end == '\0'))) - break; - - if (end != stream->end && *end) - end++; - break; - default: end = stream->stream + 1; break; @@ -192,6 +256,8 @@ token_stream_prepare (TokenStream *stream) stream->this = stream->stream; stream->stream = end; + + return TRUE; } static void @@ -204,23 +270,39 @@ static gboolean token_stream_peek (TokenStream *stream, gchar first_char) { - token_stream_prepare (stream); + if (!token_stream_prepare (stream)) + return FALSE; return stream->this[0] == first_char; } static gboolean +token_stream_peek2 (TokenStream *stream, + gchar first_char, + gchar second_char) +{ + if (!token_stream_prepare (stream)) + return FALSE; + + return stream->this[0] == first_char && + stream->this[1] == second_char; +} + +static gboolean token_stream_is_keyword (TokenStream *stream) { - token_stream_prepare (stream); + if (!token_stream_prepare (stream)) + return FALSE; - return g_ascii_isalpha (stream->this[0]); + return g_ascii_isalpha (stream->this[0]) && + g_ascii_isalpha (stream->this[1]); } static gboolean token_stream_is_numeric (TokenStream *stream) { - token_stream_prepare (stream); + if (!token_stream_prepare (stream)) + return FALSE; return (g_ascii_isdigit (stream->this[0]) || stream->this[0] == '-' || @@ -229,21 +311,25 @@ token_stream_is_numeric (TokenStream *stream) } static gboolean -token_stream_consume (TokenStream *stream, - const gchar *token) +token_stream_peek_string (TokenStream *stream, + const gchar *token) { gint length = strlen (token); - token_stream_prepare (stream); + return token_stream_prepare (stream) && + stream->stream - stream->this == length && + memcmp (stream->this, token, length) == 0; +} - if (stream->stream - stream->this == length && - memcmp (stream->this, token, length) == 0) - { - token_stream_next (stream); - return TRUE; - } +static gboolean +token_stream_consume (TokenStream *stream, + const gchar *token) +{ + if (!token_stream_peek_string (stream, token)) + return FALSE; - return FALSE; + token_stream_next (stream); + return TRUE; } static gboolean @@ -256,7 +342,8 @@ token_stream_require (TokenStream *stream, if (!token_stream_consume (stream, token)) { token_stream_set_error (stream, error, FALSE, - "expected `%s'%s", token, purpose); + G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN, + "expected '%s'%s", token, purpose); return FALSE; } @@ -278,7 +365,8 @@ token_stream_get (TokenStream *stream) { gchar *result; - token_stream_prepare (stream); + if (!token_stream_prepare (stream)) + return NULL; result = g_strndup (stream->this, stream->stream - stream->this); @@ -300,7 +388,7 @@ token_stream_end_ref (TokenStream *stream, ref->end = stream->stream - stream->start; } -void +static void pattern_copy (gchar **out, const gchar **in) { @@ -452,10 +540,12 @@ ast_free (AST *ast) ast->class->free (ast); } +G_GNUC_PRINTF(5, 6) static void ast_set_error (AST *ast, GError **error, AST *other_ast, + gint code, const gchar *format, ...) { @@ -464,6 +554,7 @@ ast_set_error (AST *ast, va_start (ap, format); parser_set_error_va (error, &ast->source_ref, other_ast ? & other_ast->source_ref : NULL, + code, format, ap); va_end (ap); } @@ -477,7 +568,8 @@ ast_type_error (AST *ast, typestr = g_variant_type_dup_string (type); ast_set_error (ast, error, NULL, - "can not parse as value of type `%s'", + G_VARIANT_PARSE_ERROR_TYPE_ERROR, + "can not parse as value of type '%s'", typestr); g_free (typestr); @@ -507,7 +599,9 @@ ast_resolve (AST *ast, switch (pattern[i]) { case '*': - ast_set_error (ast, error, NULL, "unable to infer type"); + ast_set_error (ast, error, NULL, + G_VARIANT_PARSE_ERROR_CANNOT_INFER_TYPE, + "unable to infer type"); g_free (pattern); return NULL; @@ -620,6 +714,7 @@ ast_array_get_pattern (AST **array, * report the error. note: 'j' is first. */ ast_set_error (array[j], error, array[i], + G_VARIANT_PARSE_ERROR_NO_COMMON_TYPE, "unable to find a common type"); g_free (tmp); return NULL; @@ -627,7 +722,10 @@ ast_array_get_pattern (AST **array, j++; } + } + + g_free (tmp); } return pattern; @@ -724,7 +822,9 @@ maybe_parse (TokenStream *stream, else if (!token_stream_consume (stream, "nothing")) { - token_stream_set_error (stream, error, TRUE, "unknown keyword"); + token_stream_set_error (stream, error, TRUE, + G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD, + "unknown keyword"); return NULL; } @@ -855,7 +955,7 @@ array_parse (TokenStream *stream, if (need_comma && !token_stream_require (stream, ",", - " or `]' to follow array element", + " or ']' to follow array element", error)) goto error; @@ -934,6 +1034,12 @@ tuple_get_value (AST *ast, { GVariant *child; + if (childtype == NULL) + { + g_variant_builder_clear (&builder); + return ast_type_error (ast, type, error); + } + if (!(child = ast_get_value (tuple->children[i], childtype, error))) { g_variant_builder_clear (&builder); @@ -944,6 +1050,12 @@ tuple_get_value (AST *ast, childtype = g_variant_type_next (childtype); } + if (childtype != NULL) + { + g_variant_builder_clear (&builder); + return ast_type_error (ast, type, error); + } + return g_variant_builder_end (&builder); } @@ -982,7 +1094,7 @@ tuple_parse (TokenStream *stream, if (need_comma && !token_stream_require (stream, ",", - " or `)' to follow tuple element", + " or ')' to follow tuple element", error)) goto error; @@ -1042,7 +1154,9 @@ variant_get_value (AST *ast, Variant *variant = (Variant *) ast; GVariant *child; - g_assert (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT)); + if (!g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT)) + return ast_type_error (ast, type, error); + child = ast_resolve (variant->value, error); if (child == NULL) @@ -1135,6 +1249,7 @@ dictionary_get_pattern (AST *ast, 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; } @@ -1174,7 +1289,7 @@ dictionary_get_value (AST *ast, if (!(subvalue = ast_get_value (dict->keys[0], subtype, error))) { g_variant_builder_clear (&builder); - return FALSE; + return NULL; } g_variant_builder_add_value (&builder, subvalue); @@ -1182,7 +1297,7 @@ dictionary_get_value (AST *ast, if (!(subvalue = ast_get_value (dict->values[0], subtype, error))) { g_variant_builder_clear (&builder); - return FALSE; + return NULL; } g_variant_builder_add_value (&builder, subvalue); @@ -1212,14 +1327,14 @@ dictionary_get_value (AST *ast, if (!(subvalue = ast_get_value (dict->keys[i], key, error))) { g_variant_builder_clear (&builder); - return FALSE; + return NULL; } g_variant_builder_add_value (&builder, subvalue); if (!(subvalue = ast_get_value (dict->values[i], val, error))) { g_variant_builder_clear (&builder); - return FALSE; + return NULL; } g_variant_builder_add_value (&builder, subvalue); g_variant_builder_close (&builder); @@ -1282,7 +1397,7 @@ dictionary_parse (TokenStream *stream, only_one = token_stream_consume (stream, ","); if (!only_one && !token_stream_require (stream, ":", - " or `,' to follow dictionary entry key", + " or ',' to follow dictionary entry key", error)) goto error; @@ -1308,7 +1423,7 @@ dictionary_parse (TokenStream *stream, AST *child; if (!token_stream_require (stream, ",", - " or `}' to follow dictionary entry", error)) + " or '}' to follow dictionary entry", error)) goto error; child = parse (stream, app, error); @@ -1370,7 +1485,9 @@ string_get_value (AST *ast, { if (!g_variant_is_object_path (string->string)) { - ast_set_error (ast, error, NULL, "not a valid object path"); + ast_set_error (ast, error, NULL, + G_VARIANT_PARSE_ERROR_INVALID_OBJECT_PATH, + "not a valid object path"); return NULL; } @@ -1381,7 +1498,9 @@ string_get_value (AST *ast, { if (!g_variant_is_signature (string->string)) { - ast_set_error (ast, error, NULL, "not a valid signature"); + ast_set_error (ast, error, NULL, + G_VARIANT_PARSE_ERROR_INVALID_SIGNATURE, + "not a valid signature"); return NULL; } @@ -1401,6 +1520,43 @@ string_free (AST *ast) 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, @@ -1434,8 +1590,10 @@ string_parse (TokenStream *stream, { case '\0': parser_set_error (error, &ref, NULL, + G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT, "unterminated string constant"); g_free (token); + g_free (str); return NULL; case '\\': @@ -1443,6 +1601,135 @@ string_parse (TokenStream *stream, { case '\0': parser_set_error (error, &ref, NULL, + G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT, + "unterminated string constant"); + g_free (token); + g_free (str); + return NULL; + + case 'u': + if (!unicode_unescape (token, &i, str, &j, 4, &ref, error)) + { + g_free (token); + g_free (str); + return NULL; + } + continue; + + 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; @@ -1463,11 +1750,13 @@ string_parse (TokenStream *stream, } 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; } @@ -1477,8 +1766,8 @@ string_parse (TokenStream *stream, str[j++] = '\0'; g_free (token); - string = g_slice_new (String); - string->ast.class = &string_class; + string = g_slice_new (ByteString); + string->ast.class = &bytestring_class; string->string = str; token_stream_next (stream); @@ -1500,8 +1789,9 @@ number_get_pattern (AST *ast, 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"); @@ -1512,7 +1802,9 @@ number_overflow (AST *ast, const GVariantType *type, GError **error) { - ast_set_error (ast, error, NULL, "number out of range for type `%c'", + ast_set_error (ast, error, NULL, + G_VARIANT_PARSE_ERROR_NUMBER_OUT_OF_RANGE, + "number out of range for type '%c'", g_variant_type_peek_string (type)[0]); return NULL; } @@ -1540,9 +1832,15 @@ number_get_value (AST *ast, 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; } + + /* silence uninitialised warnings... */ + negative = FALSE; + abs_val = 0; } else { @@ -1555,12 +1853,17 @@ number_get_value (AST *ast, abs_val = g_ascii_strtoull (token, &end, 0); if (abs_val == G_MAXUINT64 && errno == ERANGE) { - ast_set_error (ast, error, NULL, "integer too big for any type"); + ast_set_error (ast, error, NULL, + G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG, + "integer too big for any type"); return NULL; } if (abs_val == 0) negative = FALSE; + + /* silence uninitialised warning... */ + dbl_val = 0.0; } if (*end != '\0') @@ -1572,6 +1875,7 @@ number_get_value (AST *ast, ref.end = ref.start + 1; parser_set_error (error, &ref, NULL, + G_VARIANT_PARSE_ERROR_INVALID_CHARACTER, "invalid character in number"); return NULL; } @@ -1594,7 +1898,7 @@ number_get_value (AST *ast, case 'q': if (negative || abs_val > G_MAXUINT16) return number_overflow (ast, type, error); - return g_variant_new_uint16 (negative ? -abs_val : abs_val); + return g_variant_new_uint16 (abs_val); case 'i': if (abs_val - negative > G_MAXINT32) @@ -1604,7 +1908,7 @@ number_get_value (AST *ast, case 'u': if (negative || abs_val > G_MAXUINT32) return number_overflow (ast, type, error); - return g_variant_new_uint32 (negative ? -abs_val : abs_val); + return g_variant_new_uint32 (abs_val); case 'x': if (abs_val - negative > G_MAXINT64) @@ -1614,7 +1918,7 @@ number_get_value (AST *ast, case 't': if (negative) return number_overflow (ast, type, error); - return g_variant_new_uint64 (negative ? -abs_val : abs_val); + return g_variant_new_uint64 (abs_val); case 'h': if (abs_val - negative > G_MAXINT32) @@ -1782,6 +2086,7 @@ positional_parse (TokenStream *stream, if (*endptr || positional->value == NULL) { token_stream_set_error (stream, error, TRUE, + G_VARIANT_PARSE_ERROR_INVALID_FORMAT_STRING, "invalid GVariant format string"); /* memory management doesn't matter in case of programmer error. */ return NULL; @@ -1853,6 +2158,7 @@ typedecl_parse (TokenStream *stream, if (!g_variant_type_string_is_valid (token + 1)) { token_stream_set_error (stream, error, TRUE, + G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING, "invalid type declaration"); g_free (token); @@ -1864,6 +2170,7 @@ typedecl_parse (TokenStream *stream, if (!g_variant_type_is_definite (type)) { token_stream_set_error (stream, error, TRUE, + G_VARIANT_PARSE_ERROR_DEFINITE_TYPE_EXPECTED, "type declarations must be definite"); g_variant_type_free (type); g_free (token); @@ -1917,7 +2224,9 @@ typedecl_parse (TokenStream *stream, else { - token_stream_set_error (stream, error, TRUE, "unknown keyword"); + token_stream_set_error (stream, error, TRUE, + G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD, + "unknown keyword"); return NULL; } } @@ -1968,6 +2277,11 @@ parse (TokenStream *stream, else if (token_stream_consume (stream, "false")) result = boolean_new (FALSE); + else if (token_stream_is_numeric (stream) || + token_stream_peek_string (stream, "inf") || + token_stream_peek_string (stream, "nan")) + result = number_parse (stream, app, error); + else if (token_stream_peek (stream, 'n') || token_stream_peek (stream, 'j')) result = maybe_parse (stream, app, error); @@ -1976,16 +2290,19 @@ parse (TokenStream *stream, token_stream_is_keyword (stream)) result = typedecl_parse (stream, app, error); - else if (token_stream_is_numeric (stream)) - result = number_parse (stream, app, error); - else if (token_stream_peek (stream, '\'') || token_stream_peek (stream, '"')) result = string_parse (stream, app, error); + else if (token_stream_peek2 (stream, 'b', '\'') || + token_stream_peek2 (stream, 'b', '"')) + result = bytestring_parse (stream, app, error); + else { - token_stream_set_error (stream, error, FALSE, "expected value"); + token_stream_set_error (stream, error, FALSE, + G_VARIANT_PARSE_ERROR_VALUE_EXPECTED, + "expected value"); return NULL; } @@ -2000,17 +2317,18 @@ parse (TokenStream *stream, /** * g_variant_parse: - * @type: a #GVariantType, or %NULL + * @type: (allow-none): a #GVariantType, or %NULL * @text: a string containing a GVariant in text form - * @limit: a pointer to the end of @text, or %NULL - * @endptr: a location to store the end pointer, or %NULL - * @error: a pointer to a %NULL #GError pointer, or %NULL - * @Returns: a reference to a #GVariant, or %NULL + * @limit: (allow-none): a pointer to the end of @text, or %NULL + * @endptr: (allow-none): a location to store the end pointer, or %NULL + * @error: (allow-none): a pointer to a %NULL #GError pointer, or %NULL * * Parses a #GVariant from a text representation. * * A single #GVariant is parsed from the content of @text. * + * The format is described [here][gvariant-text]. + * * The memory at @limit will never be accessed and the parser behaves as * if the character at @limit is the nul terminator. This has the * effect of bounding @text. @@ -2031,10 +2349,12 @@ parse (TokenStream *stream, * is returned. * * In case of any error, %NULL will be returned. If @error is non-%NULL - * then it will be set to reflect the error that occured. + * then it will be set to reflect the error that occurred. * * Officially, the language understood by the parser is "any string * produced by g_variant_print()". + * + * Returns: a reference to a #GVariant, or %NULL **/ GVariant * g_variant_parse (const GVariantType *type, @@ -2043,7 +2363,7 @@ g_variant_parse (const GVariantType *type, const gchar **endptr, GError **error) { - TokenStream stream = { }; + TokenStream stream = { 0, }; GVariant *result = NULL; AST *ast; @@ -2077,6 +2397,7 @@ g_variant_parse (const GVariantType *type, stream.stream - text }; parser_set_error (error, &ref, NULL, + G_VARIANT_PARSE_ERROR_INPUT_NOT_AT_END, "expected end of input"); g_variant_unref (result); @@ -2097,18 +2418,36 @@ g_variant_parse (const GVariantType *type, * g_variant_new_parsed_va: * @format: a text format #GVariant * @app: a pointer to a #va_list - * @returns: a new floating #GVariant instance * * 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 = { }; + TokenStream stream = { 0, }; GVariant *result = NULL; GError *error = NULL; AST *ast; @@ -2142,21 +2481,27 @@ g_variant_new_parsed_va (const gchar *format, * * Parses @format and returns the result. * - * @format must be a text format #GVariant with one extention: at any + * @format must be a text format #GVariant with one extension: at any * point that a value may appear in the text, a '%' character followed * by a GVariant format string (as per g_variant_new()) may appear. In * that case, the same arguments are collected from the argument list as * g_variant_new() would have collected. * - * Consider this simple example: + * Note that the arguments must be of the correct width for their types + * specified in @format. This can be achieved by casting them. See + * the [GVariant varargs documentation][gvariant-varargs]. * - * + * Consider this simple example: + * |[ * 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 [('one', 1), ('two', 2), ('three', 3)]. + * result of + * |[ + * [('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 @@ -2164,8 +2509,10 @@ g_variant_new_parsed_va (const gchar *format, * * You may not use this function to return, unmodified, a single * #GVariant pointer from the argument list. ie: @format may not solely - * be anything along the lines of "%*", "%?", "%r", or anything starting + * be anything along the lines of "%*", "%?", "\%r", or anything starting * with "%@". + * + * Returns: a new floating #GVariant instance **/ GVariant * g_variant_new_parsed (const gchar *format, @@ -2181,5 +2528,271 @@ g_variant_new_parsed (const gchar *format, return result; } -#define __G_VARIANT_PARSER_C__ -#include "galiasdef.c" +/** + * 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: + * + * |[ + * 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); +}