libmpdserver

Parser combinator library for MPD client commands

git clone https://git.8pit.net/libmpdserver.git

  1#include <assert.h>
  2#include <stddef.h>
  3#include <string.h>
  4#include <strings.h>
  5
  6#include "fns.h"
  7#include "mpc.h"
  8
  9static char *mpd_tag_names[] = {
 10    "any",
 11    "artist",
 12    "artistsort",
 13    "album",
 14    "albumsort",
 15    "albumartist",
 16    "albumartistsort",
 17    "title",
 18    "track",
 19    "name",
 20    "genre",
 21    "date",
 22    "composer",
 23    "performer",
 24    "grouping",
 25    "comment",
 26    "disc",
 27    "label",
 28    "musicbrainz_artistid",
 29    "musicbrainz_albumid",
 30    "musicbrainz_albumartistid",
 31    "musicbrainz_trackid",
 32    "musicbrainz_releasetrackid",
 33    "musicbrainz_workid",
 34};
 35
 36mpc_parser_t *
 37mpd_tag_name(void)
 38{
 39	mpc_parser_t *str, *lstr;
 40	static mpd_string_array_t a = MPD_STRING_ARY(mpd_tag_names);
 41
 42	str = mpc_many1(mpcf_strfold,
 43	                mpc_or(2, mpc_range('a', 'z'), mpc_range('A', 'Z')));
 44	lstr = mpc_apply(str, mpdf_lowercase);
 45
 46	return mpc_check_with(lstr, free, mpd_check_array, &a, "invalid tag");
 47}
 48
 49static mpc_parser_t *
 50mpd_expr_string(void)
 51{
 52	mpc_parser_t *strch, *par;
 53
 54	strch = mpc_or(2, mpc_escape(), mpc_noneof("\'"));
 55	par = mpc_between(mpc_many(mpcf_strfold, strch), free, "'", "'");
 56
 57	return mpc_apply(mpc_or(2, par, mpc_string_lit()), mpdf_unescape);
 58}
 59
 60static mpc_val_t *
 61mpdf_fold_seperator(int n, mpc_val_t **xs)
 62{
 63	if (!n)
 64		return xstrdup("");
 65
 66	return mpcf_fst_free(n, xs);
 67}
 68
 69static mpc_parser_t *
 70mpd_seperator(void)
 71{
 72	return mpc_many(mpdf_fold_seperator, mpd_whitespace());
 73}
 74
 75static mpc_parser_t *
 76mpd_op(mpc_parser_t *par)
 77{
 78	return mpc_and(3, mpcf_snd_free, mpd_seperator(), par, mpd_seperator(),
 79	               free, free);
 80}
 81
 82static mpd_operation_t
 83mpd_str2op(char *str)
 84{
 85	if (!strcmp(str, "=="))
 86		return MPD_OP_EQUAL;
 87	else if (!strcmp(str, "!="))
 88		return MPD_OP_NEQUAL;
 89	else if (!strcmp(str, "contains"))
 90		return MPD_OP_CONTAINS;
 91	else if (!strcmp(str, "=~"))
 92		return MPD_OP_MATCH;
 93	else if (!strcmp(str, "!~"))
 94		return MPD_OP_NMATCH;
 95	else
 96		return MPD_OP_NONE;
 97}
 98
 99static mpc_val_t *
100mpdf_fold_expression(int n, mpc_val_t **xs)
101{
102	mpd_expression_t *expr;
103
104	assert(n == 3);
105
106	expr = xmalloc(sizeof(*expr));
107	expr->name = (char *)xs[0];
108	expr->op = mpd_str2op((char *)xs[1]);
109	expr->o1.str = (char *)xs[2];
110	expr->next = NULL;
111
112	free(xs[1]);
113	return expr;
114}
115
116static mpc_val_t *
117mpdf_fold_expressions(int n, mpc_val_t **xs)
118{
119	mpd_expression_t *expr;
120
121	expr = xmalloc(sizeof(*expr));
122	expr->op = MPD_OP_NONE;
123
124	if (n == 3) {
125		expr->name = (char *)xs[1];
126		assert(!strcmp(expr->name, "AND"));
127		expr->o1.expr = (mpd_expression_t *)xs[0];
128		expr->next = (mpd_expression_t *)xs[2];
129	} else if (n == 2) {
130		expr->name = xstrdup("NOT");
131		assert(!strcmp((char *)xs[0], "!"));
132		expr->o1.expr = (mpd_expression_t *)xs[1];
133		expr->next = NULL;
134		free(xs[0]);
135	} else {
136		assert(0);
137	}
138
139	return expr;
140}
141
142static mpc_parser_t *
143mpd_tag(void)
144{
145	mpc_parser_t *op;
146
147	op = mpc_or(5, mpc_string("=="), mpc_string("!="),
148	            mpc_string("contains"), mpc_string("=~"), mpc_string("!~"));
149	return mpc_and(3, mpdf_fold_expression, mpd_tag_name(), mpd_op(op),
150	               mpd_expr_string(), free, free);
151}
152
153static mpc_parser_t *
154mpd_file(void)
155{
156	return mpc_and(3, mpdf_fold_expression, mpc_string("file"),
157	               mpd_op(mpc_string("==")), mpd_expr_string(), free, free);
158}
159
160static mpc_parser_t *
161mpd_base(void)
162{
163	return mpc_and(3, mpdf_fold_expression, mpc_string("base"),
164	               mpd_seperator(), mpd_expr_string(), free, free);
165}
166
167static mpc_parser_t *
168mpd_modified_since(void)
169{
170	return mpc_and(3, mpdf_fold_expression, mpc_string("modified-since"),
171	               mpd_seperator(), mpd_expr_string(), free, free);
172}
173
174static mpc_parser_t *
175mpd_audio_format(void)
176{
177	mpc_parser_t *ops;
178
179	ops = mpc_or(2, mpc_string("=="), mpc_string("=~"));
180	return mpc_and(3, mpdf_fold_expression, mpc_string("AudioFormat"),
181	               mpd_op(ops), mpd_expr_string(), free, free);
182}
183
184static mpc_parser_t *
185mpd_base_exprs(void)
186{
187	return mpc_or(5, mpd_file(), mpd_base(), mpd_modified_since(),
188	              mpd_audio_format(), mpd_tag());
189}
190
191void
192mpd_free_expression(void *ptr)
193{
194	mpd_expression_t *expr;
195
196	expr = (mpd_expression_t *)ptr;
197	if (!strcmp(expr->name, "AND") || !strcmp(expr->name, "NOT"))
198		mpd_free_expression(expr->o1.expr);
199	else
200		free(expr->o1.str);
201
202	if (expr->next)
203		mpd_free_expression(expr->next);
204
205	free(expr->name);
206	free(expr);
207}
208
209mpd_expression_t *
210mpd_expression(char *str)
211{
212	mpc_result_t r;
213	mpc_parser_t *expr;
214	mpd_expression_t *ret;
215
216	expr = mpc_new("expr");
217	mpc_define(expr,
218	           mpc_between(mpc_or(3, mpd_base_exprs(),
219	                              mpc_and(3, mpdf_fold_expressions, expr,
220	                                      mpd_op(mpc_string("AND")), expr,
221	                                      mpd_free_expression, free),
222	                              mpc_and(2, mpdf_fold_expressions,
223	                                      mpc_char('!'), expr, free)),
224	                       mpd_free_expression, "(", ")"));
225
226	if (mpc_parse("", str, expr, &r)) {
227		ret = (mpd_expression_t *)r.output;
228	} else {
229		mpc_err_delete(r.error);
230		ret = NULL;
231	}
232
233	mpc_cleanup(1, expr);
234	return ret;
235}