libmpdserver

Parser combinator library for MPD client commands

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

  1#include <errno.h>
  2#include <stdlib.h>
  3#include <string.h>
  4#include <strings.h>
  5
  6#include <sys/types.h>
  7
  8#include "fns.h"
  9#include "mpc.h"
 10#include "mpdserver.h"
 11
 12static mpc_parser_t *
 13mpd_command(void)
 14{
 15	return mpc_or(2, mpd_list_cmds(), mpd_command_primitive());
 16}
 17
 18static int
 19mpd_check_quote(mpc_val_t **val)
 20{
 21	char *ptr;
 22
 23	ptr = (char *)*val;
 24	return *ptr != '"';
 25}
 26
 27int
 28mpd_check_array(mpc_val_t **val, void *ptr)
 29{
 30	size_t i;
 31	int inset;
 32	char *str;
 33	mpd_string_array_t *ary;
 34
 35	str = *(char **)val;
 36	ary = (mpd_string_array_t *)ptr;
 37
 38	inset = 0;
 39	for (i = 0; i < ary->len; i++) {
 40		if (!strcasecmp(str, ary->ptr[i]))
 41			inset = 1;
 42	}
 43
 44	return inset;
 45}
 46
 47mpc_parser_t *
 48mpd_whitespace(void)
 49{
 50	return mpc_expect(mpc_oneof(" \t"), "whitespace");
 51}
 52
 53mpc_val_t *
 54mpdf_lowercase(mpc_val_t *val)
 55{
 56	return lowercase((char *)val);
 57}
 58
 59mpc_val_t *
 60mpdf_unescape(mpc_val_t *val)
 61{
 62	unsigned short esc;
 63	size_t i, oldlen, newlen;
 64	char *oldstr, *newstr;
 65
 66	oldstr = val;
 67	oldlen = strlen(oldstr);
 68
 69	newlen = 0;
 70	newstr = xmalloc(sizeof(char) * oldlen);
 71
 72	for (esc = 0, i = 0; i < oldlen; i++) {
 73		if (oldstr[i] == '\\' && !esc) {
 74			esc = 1;
 75			continue;
 76		}
 77
 78		newstr[newlen++] = oldstr[i];
 79		esc = 0;
 80	}
 81
 82	free(val);
 83	newstr = xrealloc(newstr, newlen + 1);
 84	newstr[newlen] = '\0';
 85	return newstr;
 86}
 87
 88static mpc_val_t *
 89mpdf_command_noarg(mpc_val_t *val)
 90{
 91	mpd_command_t *cmd;
 92	char *str;
 93
 94	str = (char *)val;
 95	cmd = mpd_new_command(str, 0);
 96
 97	return cmd;
 98}
 99
100static mpc_val_t *
101mpdf_fold_range(int n, mpc_val_t **xs)
102{
103	int i;
104	size_t start;
105	ssize_t end;
106
107	assert(n == 3);
108	assert(*(char *)xs[1] == ':');
109
110	/* TODO: We can't signal an error error condition from an apply
111	 * function and strtoull(3) might potentially overflow. */
112	start = (size_t)strtoull((char *)xs[0], NULL, 10);
113	end = (xs[2]) ? (ssize_t)strtoull((char *)xs[2], NULL, 10) : -1;
114
115	for (i = 0; i < n; i++)
116		free(xs[i]);
117
118	return mpd_new_range(start, end);
119}
120
121static mpc_val_t *
122mpdf_range(mpc_val_t *val)
123{
124	unsigned int pos;
125
126	pos = *(unsigned int *)val;
127	free(val);
128
129	return mpd_new_range((size_t)pos, (ssize_t)pos);
130}
131
132static mpc_val_t *
133mpdf_uint(mpc_val_t *val)
134{
135	unsigned int *uval;
136
137	uval = xmalloc(sizeof(*uval));
138	/* TODO: We can't signal an error error condition from an apply
139	 * function and strtoul(3) might potentially overflow. */
140	*uval = (unsigned int)strtoul((char *)val, NULL, 10);
141
142	free(val);
143	return uval;
144}
145
146mpc_parser_t *
147mpd_cmd_noarg(char *cmdstr)
148{
149	return mpc_apply(mpc_string(cmdstr), mpdf_command_noarg);
150}
151
152mpc_parser_t *
153mpd_argument(mpc_parser_t *a)
154{
155	mpc_parser_t *sep, *spaces, *quoted;
156
157	spaces = mpc_many1(mpcf_freefold, mpd_whitespace());
158	sep = mpc_expect(spaces, "argument");
159
160	/* All MPD arguments can be enclosed in quotes. However, some
161	 * argument parsers, e.g. mpd_string, handle quotes explicitly,
162	 * thus they may have already been parsed at this point and are
163	 * only optional. */
164
165	/* TODO: unescape all arguments before doing further parsing */
166
167	quoted = mpc_between(mpc_copy(a), free, "\"", "\"");
168	return mpc_and(2, mpcf_snd_free, sep, mpc_or(2, a, quoted), free);
169}
170
171mpc_parser_t *
172mpd_int(void)
173{
174	mpc_parser_t *neg, *val;
175
176	neg = mpc_and(2, mpcf_strfold, mpc_char('-'), mpc_digits(), free);
177	val = mpc_or(2, neg, mpc_digits());
178
179	return mpc_expect(mpc_apply(val, mpcf_int), "signed integer");
180}
181
182mpc_parser_t *
183mpd_uint(void)
184{
185	return mpc_expect(mpc_apply(mpc_digits(), mpdf_uint),
186	                  "unsigned integer");
187}
188
189mpc_parser_t *
190mpd_binary(void)
191{
192	return mpc_expect(mpc_apply(mpc_oneof("01"), mpdf_uint), "binary");
193}
194
195mpc_parser_t *
196mpd_float_digits(void)
197{
198	mpc_parser_t *pre, *sep, *fra;
199
200	pre = mpc_maybe_lift(mpc_digits(), mpcf_ctor_str);
201	sep = mpc_char('.');
202
203	fra = mpc_and(3, mpcf_strfold, pre, sep, mpc_digits(), free, free);
204	return mpc_or(2, fra, mpc_digits(), free);
205}
206
207mpc_parser_t *
208mpd_float(void)
209{
210	return mpc_expect(mpc_apply(mpd_float_digits(), mpcf_float), "float");
211}
212
213mpc_parser_t *
214mpd_range(void)
215{
216	return mpc_and(3, mpdf_fold_range, mpc_digits(), mpc_char(':'),
217	               mpc_maybe(mpc_digits()), free, free);
218}
219
220mpc_parser_t *
221mpd_range_with_single(void)
222{
223	mpc_parser_t *single;
224
225	single = mpc_apply(mpd_uint(), mpdf_range);
226	return mpc_or(2, mpd_range(), single);
227}
228
229mpc_parser_t *
230mpd_string(void)
231{
232	mpc_parser_t *str, *strcheck;
233
234	str = mpc_many(mpcf_strfold, mpc_noneof(" \t\n"));
235	strcheck =
236	    mpc_check(str, free, mpd_check_quote, "missing closing '\"'");
237
238	return mpc_or(2, mpc_apply(mpc_string_lit(), mpdf_unescape), strcheck);
239}
240
241mpc_parser_t *
242mpd_string_case(void)
243{
244	return mpc_apply(mpd_string(), mpdf_lowercase);
245}
246
247mpc_parser_t *
248mpd_uri(void)
249{
250	/* Unfortunately, a URI argument is either a URI or a file name.
251	 * While a URI is properly defined in RFC 3986 a file name can
252	 * be pretty much anything though mpd itself doesn't seem to
253	 * allow `.` and `..` as path elements in file names.
254	 * Nonetheless, it doesn't make sense to validate URIs properly
255	 * since every invalid URI is likely a valid file name. */
256	return mpd_string();
257}
258
259mpc_parser_t *
260mpd_command_primitive(void)
261{
262	mpc_parser_t *cmd;
263
264	/* TODO: mpc_or all parsers */
265	cmd = mpc_or(6, mpd_playback_cmds(), mpd_status_cmds(),
266	             mpd_queue_cmds(), mpd_control_cmds(), mpd_database_cmds(),
267	             mpd_connection_cmds());
268
269	return mpc_and(2, mpcf_fst_free, cmd, mpc_newline(), mpd_free_command);
270}
271
272mpd_command_t *
273mpd_parse(char *input)
274{
275	size_t i;
276	mpd_expression_t *expr;
277	mpd_argument_t *c;
278	mpd_command_t *cmd;
279	mpc_parser_t *par;
280	mpc_result_t r;
281
282	cmd = NULL;
283	par = mpd_command();
284
285	if (mpc_parse("", input, par, &r)) {
286		cmd = (mpd_command_t *)r.output;
287	} else {
288#ifdef MPC_PARSER_DEBUG
289		mpc_err_print(r.error);
290#endif
291		mpc_err_delete(r.error);
292		goto ret;
293	}
294
295	/* TODO: There is now easy way to unescape the input string
296	 * before processing it further. Woraround: call mpc_parse twice. */
297	for (i = 0; i < cmd->argc; i++) {
298		c = cmd->argv[i];
299		if (c->type != MPD_VAL_EXPR_STR)
300			continue;
301
302		if (!(expr = mpd_expression(c->v.sval))) {
303			mpd_free_command(cmd);
304			cmd = NULL;
305			goto ret;
306		}
307
308		free(c->v.sval);
309		c->v.eval = expr;
310		c->type = MPD_VAL_EXPR;
311	}
312
313ret:
314	mpc_cleanup(1, par);
315	return cmd;
316}