From b877bc3026e9bf9dabd053a0a999a441cbd8f54d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 10 Jun 2009 09:52:38 +0200 Subject: [PATCH] parser: added a response parser library This sub-library consumes response lines obtained by mpd_async_recv_line() and parses them. --- Makefile.am | 2 + include/mpd/parser.h | 134 ++++++++++++++++++++++++++++ libmpdclient.ld | 11 +++ src/parser.c | 208 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 355 insertions(+) create mode 100644 include/mpd/parser.h create mode 100644 src/parser.c diff --git a/Makefile.am b/Makefile.am index e8023cf..10620ac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,6 +14,7 @@ mpdinclude_HEADERS = \ include/mpd/entity.h \ include/mpd/error.h \ include/mpd/idle.h \ + include/mpd/parser.h \ include/mpd/protocol.h \ include/mpd/send.h \ include/mpd/status.h \ @@ -43,6 +44,7 @@ src_libmpdclient_la_SOURCES = \ src/pair.c \ src/entity.c \ src/idle.c \ + src/parser.c \ src/quote.c src/quote.h \ src/search.c \ src/send.c \ diff --git a/include/mpd/parser.h b/include/mpd/parser.h new file mode 100644 index 0000000..9c4292f --- /dev/null +++ b/include/mpd/parser.h @@ -0,0 +1,134 @@ +/* libmpdclient + (c) 2003-2009 The Music Player Daemon Project + This project's homepage is: http://www.musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Music Player Daemon nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef LIBMPDCLIENT_PARSER_H +#define LIBMPDCLIENT_PARSER_H + +#include + +#include + +enum mpd_parser_result { + /** + * Response line was not understood. + */ + MPD_PARSER_MALFORMED, + + /** + * MPD has returned "OK" or "list_OK" (check with + * mpd_parser_is_partial()). + */ + MPD_PARSER_SUCCESS, + + /** + * MPD has returned "ACK" with an error code. Call + * mpd_parser_get_reason() to get the error code. + */ + MPD_PARSER_ERROR, + + /** + * MPD has returned a name-value pair. Call + * mpd_parser_get_name() and mpd_parser_get_value(). + */ + MPD_PARSER_PAIR, +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Allocates a new mpd_parser object. Returns NULL on error (out of + * memory). + */ +struct mpd_parser * +mpd_parser_new(void); + +/** + * Frees a mpd_parser object. + */ +void +mpd_parser_free(struct mpd_parser *parser); + +/** + * Feeds a line (without the trailing newline character) received from + * MPD / mpd_async_recv_line() into the parser. + */ +enum mpd_parser_result +mpd_parser_feed(struct mpd_parser *parser, char *line); + +/** + * Call this when mpd_parser_feed() has returned #MPD_PARSER_SUCCESS + * to find out whether this is an "OK" (false) or a "list_OK" (true) + * response. + */ +bool +mpd_parser_is_partial(const struct mpd_parser *parser); + +/** + * Call this when mpd_parser_feed() has returned #MPD_PARSER_ERROR to + * obtain the reason for the error. + */ +enum mpd_ack +mpd_parser_get_ack(const struct mpd_parser *parser); + +/** + * On #MPD_PARSER_ERROR, this returns the number of the list command + * which failed, or -1 if that information is not available. + */ +int +mpd_parser_get_at(const struct mpd_parser *parser); + +/** + * On #MPD_PARSER_ERROR, this returns the human readable error message + * returned by MPD (UTF-8). + */ +const char * +mpd_parser_get_message(const struct mpd_parser *parser); + +/** + * On #MPD_PARSER_PAIR, this returns the name. + */ +const char * +mpd_parser_get_name(const struct mpd_parser *parser); + +/** + * On #MPD_PARSER_PAIR, this returns the value. + */ +const char * +mpd_parser_get_value(const struct mpd_parser *parser); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libmpdclient.ld b/libmpdclient.ld index 918454d..96c1da8 100644 --- a/libmpdclient.ld +++ b/libmpdclient.ld @@ -13,6 +13,17 @@ global: mpd_async_send_command; mpd_async_recv_line; + /* mpd/parser.h */ + mpd_parser_new; + mpd_parser_free; + mpd_parser_feed; + mpd_parser_is_partial; + mpd_parser_get_ack; + mpd_parser_get_at; + mpd_parser_get_message; + mpd_parser_get_name; + mpd_parser_get_value; + /* mpd/send.h */ mpd_send_command; diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..9aa10dc --- /dev/null +++ b/src/parser.c @@ -0,0 +1,208 @@ +/* libmpdclient + (c) 2003-2009 The Music Player Daemon Project + This project's homepage is: http://www.musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Music Player Daemon nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include +#include +#include + +struct mpd_parser { +#ifndef NDEBUG + enum mpd_parser_result result; +#endif + + union { + bool partial; + + struct { + enum mpd_ack ack; + int at; + const char *message; + } error; + + struct { + const char *name, *value; + } pair; + } u; +}; + +struct mpd_parser * +mpd_parser_new(void) +{ + struct mpd_parser *parser = malloc(sizeof(*parser)); + if (parser == NULL) + return NULL; + +#ifndef NDEBUG + parser->result = MPD_PARSER_MALFORMED; +#endif + + return parser; +} + +void +mpd_parser_free(struct mpd_parser *parser) +{ + free(parser); +} + +static inline enum mpd_parser_result +set_result(struct mpd_parser *parser, enum mpd_parser_result result) +{ +#ifndef NDEBUG + /* this value exists only in the debug build, and is used by + assertions in the "get" functions below */ + parser->result = result; +#else + /* suppress "unused" warning */ + (void)parser; +#endif + + return result; +} + +enum mpd_parser_result +mpd_parser_feed(struct mpd_parser *parser, char *line) +{ + if (strcmp(line, "OK") == 0) { + parser->u.partial = false; + return set_result(parser, MPD_PARSER_SUCCESS); + } else if (strcmp(line, "list_OK") == 0) { + parser->u.partial = true; + return set_result(parser, MPD_PARSER_SUCCESS); + } else if (memcmp(line, "ACK", 3) == 0) { + char *p, *q; + + parser->u.error.ack = MPD_ACK_ERROR_UNK; + parser->u.error.at = -1; + parser->u.error.message = NULL; + + /* parse [ACK@AT] */ + + p = strchr(line + 3, '['); + if (p == NULL) + return set_result(parser, MPD_PARSER_ERROR); + + parser->u.error.ack = strtol(p + 1, &p, 10); + if (*p == '@') + parser->u.error.at = strtol(p + 1, &p, 10); + + q = strchr(p, ']'); + if (q == NULL) + return set_result(parser, MPD_PARSER_MALFORMED); + + /* skip the {COMMAND} */ + + p = q + 1; + q = strchr(p, '{'); + if (q != NULL) { + q = strchr(p, '}'); + if (q != NULL) + p = q + 1; + } + + /* obtain error message */ + + while (*p == ' ') + ++p; + + if (*p != 0) + parser->u.error.message = p; + + return set_result(parser, MPD_PARSER_ERROR); + } else { + /* so this must be a name-value pair */ + + char *p; + + p = strchr(line, ':'); + if (p == NULL || p[1] != ' ') + return set_result(parser, MPD_PARSER_MALFORMED); + + *p = 0; + + parser->u.pair.name = line; + parser->u.pair.value = p + 2; + + return set_result(parser, MPD_PARSER_PAIR); + } +} + +bool +mpd_parser_is_partial(const struct mpd_parser *parser) +{ + assert(parser->result == MPD_PARSER_SUCCESS); + + return parser->u.partial; +} + +enum mpd_ack +mpd_parser_get_ack(const struct mpd_parser *parser) +{ + assert(parser->result == MPD_PARSER_ERROR); + + return parser->u.error.ack; +} + +int +mpd_parser_get_at(const struct mpd_parser *parser) +{ + assert(parser->result == MPD_PARSER_ERROR); + + return parser->u.error.at; +} + +const char * +mpd_parser_get_message(const struct mpd_parser *parser) +{ + assert(parser->result == MPD_PARSER_ERROR); + + return parser->u.error.message; +} + +const char * +mpd_parser_get_name(const struct mpd_parser *parser) +{ + assert(parser->result == MPD_PARSER_PAIR); + + return parser->u.pair.name; +} + +const char * +mpd_parser_get_value(const struct mpd_parser *parser) +{ + assert(parser->result == MPD_PARSER_PAIR); + + return parser->u.pair.value; +}