diff options
author | Emmanuele Bassi <ebassi@openedhand.com> | 2007-09-21 11:54:40 +0100 |
---|---|---|
committer | Emmanuele Bassi <ebassi@openedhand.com> | 2007-09-21 11:54:40 +0100 |
commit | d98d8c3d245192abe6ec5a531c9d0d678b07d061 (patch) | |
tree | 8e30d2694dc9f9ac94c3973fd9fd7276199445c1 /json-glib/json-parser.c | |
parent | cd1040e2fb6f6da50aaf72017746b33234c39704 (diff) | |
download | json-glib-d98d8c3d245192abe6ec5a531c9d0d678b07d061.tar.gz |
Add stubs to JsonParser for actually parsing a JSON stream
Initial commit for getting JsonParser to work. Because GScanner API
is old and mostly sucks, we need to do some magic with signals.
If json_parser_load_from_data() fails, the passed GError will be set
with a JSON_PARSER_ERROR code and message; unfortunately, we can't get
the nice error message out of GScanner. We can, however, override the
default message handler and make it emit a signal on the JsonParser
object.
So, to make a long story short: the GError passed to the load_from_data()
method is filled with a short error message; the *real* error message
is passed to the ::error signal handlers so they can actually use it.
GScanner should really get a way to retrieve the last error message.
Diffstat (limited to 'json-glib/json-parser.c')
-rw-r--r-- | json-glib/json-parser.c | 266 |
1 files changed, 265 insertions, 1 deletions
diff --git a/json-glib/json-parser.c b/json-glib/json-parser.c index f44451f..dbdefd3 100644 --- a/json-glib/json-parser.c +++ b/json-glib/json-parser.c @@ -2,6 +2,7 @@ #include <string.h> +#include "json-marshal.h" #include "json-parser.h" #include "json-private.h" @@ -19,6 +20,71 @@ struct _JsonParserPrivate GList *top_levels; }; +static const GScannerConfig json_scanner_config = +{ + ( " \t\r\n" ) /* cset_skip_characters */, + ( + "_" + G_CSET_a_2_z + G_CSET_A_2_Z + ) /* cset_identifier_first */, + ( + G_CSET_DIGITS + "-_" + G_CSET_a_2_z + G_CSET_A_2_Z + ) /* cset_identifier_nth */, + ( "//\n" ) /* cpair_comment_single */, + TRUE /* case_sensitive */, + TRUE /* skip_comment_multi */, + TRUE /* skip_comment_single */, + TRUE /* scan_comment_multi */, + TRUE /* scan_identifier */, + FALSE /* scan_identifier_1char */, + FALSE /* scan_identifier_NULL */, + TRUE /* scan_symbols */, + TRUE /* scan_binary */, + TRUE /* scan_octal */, + TRUE /* scan_float */, + TRUE /* scan_hex */, + TRUE /* scan_hex_dollar */, + TRUE /* scan_string_sq */, + TRUE /* scan_string_dq */, + TRUE /* numbers_2_int */, + FALSE /* int_2_float */, + FALSE /* identifier_2_string */, + TRUE /* char_2_token */, + TRUE /* symbol_2_token */, + FALSE /* scope_0_fallback */, +}; + + +static const gchar symbol_names[] = + "true\0" + "false\0" + "null\0"; + +static const struct +{ + guint name_offset; + guint token; +} symbols[] = { + { 0, JSON_TOKEN_TRUE }, + { 5, JSON_TOKEN_FALSE }, + { 11, JSON_TOKEN_NULL } +}; + +static const guint n_symbols = G_N_ELEMENTS (symbols); + +enum +{ + ERROR, + + LAST_SIGNAL +}; + +static guint parser_signals[LAST_SIGNAL] = { 0, }; + G_DEFINE_TYPE (JsonParser, json_parser, G_TYPE_OBJECT); static void @@ -35,6 +101,24 @@ json_parser_class_init (JsonParserClass *klass) g_type_class_add_private (klass, sizeof (JsonParserPrivate)); gobject_class->dispose = json_parser_dispose; + + /** + * JsonParser::error: + * @parser: the parser instance that received the signal + * @error: a pointer to the #GError + * + * The ::error signal is emitted each time a #JsonParser encounters + * an error in a JSON stream. + */ + parser_signals[ERROR] = + g_signal_new ("error", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, error), + NULL, NULL, + _json_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); } static void @@ -47,6 +131,104 @@ json_parser_init (JsonParser *parser) priv->top_levels = NULL; } +static guint +json_parse_array (JsonParser *parser, + GScanner *scanner) +{ + return G_TOKEN_NONE; +} + +static guint +json_parse_pair (JsonParser *parse, + GScanner *scanner) +{ + return G_TOKEN_NONE; +} + +static guint +json_parse_object (JsonParser *parser, + GScanner *scanner) +{ + guint token; + guint result = G_TOKEN_NONE; + + token = g_scanner_get_next_token (scanner); + if (token != G_TOKEN_LEFT_CURLY) + return G_TOKEN_LEFT_CURLY; + + token = g_scanner_get_next_token (scanner); + if (token == G_TOKEN_RIGHT_CURLY) + return G_TOKEN_NONE; + else + { + } + + return result; +} + +static guint +json_parse_statement (JsonParser *parser, + GScanner *scanner) +{ + guint token; + + token = g_scanner_peek_next_token (scanner); + switch (token) + { + case G_TOKEN_LEFT_CURLY: + return json_parse_object (parser, scanner); + + case G_TOKEN_LEFT_BRACE: + return json_parse_array (parser, scanner); + + case G_TOKEN_STRING: + return json_parse_pair (parser, scanner); + + default: + g_scanner_get_next_token (scanner); + return G_TOKEN_SYMBOL; + } +} + +static void +json_scanner_msg_handler (GScanner *scanner, + gchar *message, + gboolean error) +{ + JsonParser *parser = scanner->user_data; + + if (error) + { + GError *error = NULL; + + g_set_error (&error, JSON_PARSER_ERROR, + JSON_PARSER_ERROR_PARSE, + "Parse error on line %d: %s", + scanner->line, + message); + + g_signal_emit (parser, parser_signals[ERROR], 0, error); + + g_error_free (error); + } + else + { + g_warning ("Line %d: %s", scanner->line, message); + } +} + +static GScanner * +json_scanner_new (JsonParser *parser) +{ + GScanner *scanner; + + scanner = g_scanner_new (&json_scanner_config); + scanner->msg_handler = json_scanner_msg_handler; + scanner->user_data = parser; + + return scanner; +} + /** * json_parser_new: * @@ -123,13 +305,95 @@ json_parser_load_from_data (JsonParser *parser, gsize length, GError **error) { + GScanner *scanner; + gboolean done = FALSE; + gboolean retval = TRUE; + gint i; + g_return_val_if_fail (JSON_IS_PARSER (parser), FALSE); g_return_val_if_fail (data != NULL, FALSE); if (length < 0) length = strlen (data); - return TRUE; + scanner = json_scanner_new (parser); + g_scanner_input_text (scanner, data, length); + + for (i = 0; i < n_symbols; i++) + { + g_scanner_scope_add_symbol (scanner, 0, + symbol_names + symbols[i].name_offset, + GINT_TO_POINTER (symbols[i].token)); + } + + while (!done) + { + if (g_scanner_peek_next_token (scanner) == G_TOKEN_EOF) + done = TRUE; + else + { + guint expected_token; + + expected_token = json_parse_statement (parser, scanner); + if (expected_token != G_TOKEN_NONE) + { + const gchar *symbol_name; + gchar *msg; + + msg = NULL; + symbol_name = NULL; + if (scanner->scope_id == 0) + { + if (expected_token > JSON_TOKEN_INVALID && + expected_token < JSON_TOKEN_LAST) + { + for (i = 0; i < n_symbols; i++) + if (symbols[i].token == expected_token) + msg = (gchar *) symbol_names + symbols[i].name_offset; + + if (msg) + msg = g_strconcat ("e.g. `", msg, "'", NULL); + } + + if (scanner->token > JSON_TOKEN_INVALID && + scanner->token < JSON_TOKEN_LAST) + { + symbol_name = "???"; + + for (i = 0; i < n_symbols; i++) + if (symbols[i].token == scanner->token) + symbol_name = symbol_names + symbols[i].name_offset; + } + } + + /* this will emit the ::error signal via the custom + * message handler we install + */ + g_scanner_unexp_token (scanner, expected_token, + NULL, "keyword", + symbol_name, msg, + TRUE); + + /* we set a generic error here; the message from + * GScanner is relayed in the ::error signal + */ + g_set_error (error, JSON_PARSER_ERROR, + JSON_PARSER_ERROR_PARSE, + "Invalid token `%s' found: expecting %s", + symbol_name, + msg); + + retval = FALSE; + + g_free (msg); + done = TRUE; + } + } + } + + g_scanner_destroy (scanner); + + return retval; } /** |