summaryrefslogtreecommitdiff
path: root/mp.c
blob: a37d8c52e713d80a446d895af1ad3b8e3d7a2cfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
/* SPDX-License-Identifier: GPL-2.0 */

/**
 * \file mp.c Mood parser helper functions.
 *
 * This file contains the public and the private API of the flex/bison based
 * mood parser.
 *
 * The public API (at the bottom of the file) allows parsing the same mood
 * definition many times in an efficient manner.
 *
 * The first function to call is \ref mp_init(), which analyzes the given mood
 * definition syntactically. It returns the abstract syntax tree of the mood
 * definition and pre-compiles all regular expression patterns to make later
 * pattern matching efficient.
 *
 * Semantic analysis is performed in \ref mp_eval_row(). This function is
 * called from \ref mood.c once for each file in the audio file table. It
 * utilizes the abstract syntax tree and the pre-compiled regular expressions
 * to determine the set of admissible audio files.
 *
 * If the mood is no longer needed, \ref mp_shutdown() should be called to free
 * the resources.
 *
 * The internal API is described in \ref mp.h.
 */

#include "para.h"

#include <fnmatch.h>
#include <osl.h>
#include <lopsub.h>

#include "string.h"
#include "error.h"
#include "afh.h"
#include "afs.h"
#include "mp.h"
#include "mp.bison.h"

struct mp_context {
	/* global context */
	char *errmsg;
	struct mp_ast_node *ast;
	/* per audio file context */
	const struct osl_row *aft_row;
	char *path;
	bool have_afsi;
	struct afs_info afsi;
	bool have_afhi;
	struct afh_info afhi;
};

/**
 * Parse a (generalized) string literal.
 *
 * \param src The string to parse.
 * \param quote_chars Opening and closing quote characters.
 * \param result The corresponding C string is returned here.
 *
 * This function turns a generalized C99 string literal like "xyz\n" into a C
 * string (containing the three characters 'x', 'y' and 'z', followed by a
 * newline character and the terminating zero byte). The function receives
 * quote characters as an argument so that, for example, regular expression
 * patterns enclosed in '/' can be parsed as well. To parse a proper string
 * literal, one has to pass two double quotes as the second argument.
 *
 * The function strips off the opening and leading quote characters, replaces
 * double backslashes by single backslashes and handles the usual escapes like
 * \n and \".
 *
 * The caller must make sure that the input is well-formed. The function simply
 * aborts if the input is not a valid C99 string literal (modulo the quote
 * characters).
 *
 * \return Offset of the first character after the closing quote. For proper
 * string literals this will be the terminating zero byte of the input string,
 * for regular expression patterns it is the beginning of the flags which
 * modify the matching behaviour.
 *
 * \sa \ref mp_parse_regex_pattern(), \ref mp_parse_wildcard_pattern().
 */
unsigned parse_quoted_string(const char *src, const char quote_chars[2],
		char **result)
{
	size_t n, len = strlen(src);
	char *dst, *p;
	bool backslash;

	assert(len >= 2);
	assert(src[0] == quote_chars[0]);
	p = dst = alloc(len - 1);
	backslash = false;
	for (n = 1;; n++) {
		char c;
		assert(n < len);
		c = src[n];
		if (!backslash) {
			if (c == '\\') {
				backslash = true;
				continue;
			}
			if (c == quote_chars[1])
				break;
			*p++ = c;
			continue;
		}
		if (c == quote_chars[1])
			*p++ = quote_chars[1];
		else switch (c) {
			case '\\': *p++ = '\\'; break;
			case 'a': *p++ = '\a'; break;
			case 'b': *p++ = '\b'; break;
			case 'f': *p++ = '\f'; break;
			case 'n': *p++ = '\n'; break;
			case 'r': *p++ = '\r'; break;
			case 't': *p++ = '\t'; break;
			case 'v': *p++ = '\v'; break;
			default: assert(false);
		}
		backslash = false;
	}
	assert(src[n] == quote_chars[1]);
	*p = '\0';
	*result = dst;
	return n + 1;
}

/**
 * Parse and compile an extended regular expression pattern, including flags.
 *
 * \param src The pattern to parse.
 * \param result C-string and flags are returned here.
 *
 * A regex pattern is identical to a C99 string literal except (a) it is
 * enclosed in '/' characters rather than double quotes, (b) double quote
 * characters which are part of the pattern do not need to be quoted with
 * backslashes, but slashes must be quoted in this way, and (c) the closing
 * slash may be followed by one or more flag characters which modify the
 * matching behaviour.
 *
 * The only flags which are currently supported are 'i' to ignore case in match
 * (REG_ICASE) and 'n' to change the handling of newline characters
 * (REG_NEWLINE).
 *
 * \return Standard. This function calls \ref parse_quoted_string(), hence it
 * aborts if the input string is malformed. However, errors from \ref
 * para_regcomp are returned without aborting the process. The rationale behind
 * this difference is that passing a malformed string must be considered an
 * implementation bug because malformed strings should be rejected earlier by
 * the lexer.
 *
 * \sa \ref mp_parse_wildcard_pattern(), \ref parse_quoted_string(),
 * \ref para_regcomp(), regex(3).
 */
int mp_parse_regex_pattern(const char *src, struct mp_re_pattern *result)
{
	int ret;
	char *pat;
	unsigned n = parse_quoted_string(src, "//", &pat);

	result->flags = 0;
	for (; src[n]; n++) {
		switch (src[n]) {
		case 'i': result->flags |= REG_ICASE; break;
		case 'n': result->flags |= REG_NEWLINE; break;
		default: assert(false);
		}
	}
	ret = para_regcomp(&result->preg, pat, result->flags);
	free(pat);
	return ret;
}

/**
 * Parse a wildcard pattern, including flags.
 *
 * \param src The pattern to parse.
 * \param result C-string and flags are returned here.
 *
 * This function parses a shell wildcard pattern. It is similar to \ref
 * mp_parse_regex_pattern(), so the remarks mentioned there apply to this
 * function as well.
 *
 * Wildcard patterns differ from regular expression patterns in that (a) they
 * must be enclosed in '|' characters, (b) they support different flags for
 * modifying matching behaviour, and (c) there is no cache for them.
 *
 * The following flags, whose meaning is explained in fnmatch(3), are currently
 * supported: 'n' (FNM_NOESCAPE), 'p' (FNM_PATHNAME), 'P' (FNM_PERIOD), 'l'
 * (FNM_LEADING_DIR), 'i' (FNM_CASEFOLD), 'e' (FNM_EXTMATCH). The last flag is
 * a GNU extension. It is silently ignored on non GNU systems.
 *
 * \sa \ref parse_quoted_string(), \ref mp_parse_regex_pattern(), fnmatch(3).
 */
void mp_parse_wildcard_pattern(const char *src, struct mp_wc_pattern *result)
{
	unsigned n = parse_quoted_string(src, "||", &result->pat);

	result->flags = 0;
	for (; src[n]; n++) {
		switch (src[n]) {
		case 'n': result->flags |= FNM_NOESCAPE; break;
		case 'p': result->flags |= FNM_PATHNAME; break;
		case 'P': result->flags |= FNM_PERIOD; break;
		/* not POSIX, but both FreeBSD and NetBSD have it */
		case 'l': result->flags |= FNM_LEADING_DIR; break;
		case 'i': result->flags |= FNM_CASEFOLD; break;
		/* GNU only */
#ifdef HAVE_FNM_EXTMATCH
		case 'e': result->flags |= FNM_EXTMATCH; break;
#else /* silently ignore extglob flag */
		case 'e': break;
#endif
		default: assert(false);
		}
	}
}

/**
 * Set the error bit in the parser context and log a message.
 *
 * \param line The number of the input line which caused the error.
 * \param ctx Contains the error bit.
 * \param fmt Usual format string.
 *
 * This is called if the lexer or the parser detect an error in the mood
 * definition. Only the first error is logged (with a severity of "warn").
 */
__printf_3_4 void mp_parse_error(int line, struct mp_context *ctx,
		const char *fmt, ...)
{
	va_list ap;
	char *tmp;

	if (ctx->errmsg) /* we already printed an error message */
		return;
	va_start(ap, fmt);
	xvasprintf(&tmp, fmt, ap);
	va_end(ap);
	xasprintf(&ctx->errmsg, "line %d: %s", line, tmp);
	free(tmp);
	PARA_NOTICE_LOG("%s\n", ctx->errmsg);
}

static int get_afsi(struct mp_context *ctx)
{
	int ret;

	if (ctx->have_afsi)
		return 0;
	ret = get_afsi_of_row(ctx->aft_row, &ctx->afsi);
	if (ret < 0)
		return ret;
	ctx->have_afsi = true;
	return 1;
}

static int get_afhi(struct mp_context *ctx)
{
	int ret;

	if (ctx->have_afhi)
		return 0;
	ret = get_afhi_of_row(ctx->aft_row, &ctx->afhi);
	if (ret < 0)
		return ret;
	ctx->have_afhi = true;
	return 1;
}

/**
 * Return the full path to the audio file.
 *
 * \param ctx Contains a reference to the row of the audio file table which
 * corresponds to the current audio file. The path of the audio file, the
 * afs_info and the afh_info structures (which contain the tag information) can
 * be retrieved through this reference.
 *
 * \return A reference to the path. Must not be freed by the caller.
 *
 * \sa \ref get_audio_file_path_of_row().
 */
char *mp_path(struct mp_context *ctx)
{
	if (!ctx->path)
		get_audio_file_path_of_row(ctx->aft_row, &ctx->path);
	return ctx->path;
}

/**
 * Check whether the given attribute is set for the current audio file.
 *
 * \param attr The string to look up in the attribute table.
 * \param ctx See \ref mp_path().
 *
 * First, determine the bit number which corresponds to the attribute, then
 * check if this bit is set in the ->attributes field of the afs_info structure
 * of the audio file.
 *
 * \return True if the attribute is set, false if it is not. On errors, for
 * example if the given string is no attribute, the function returns false.
 *
 * \sa \ref attr_name_to_bitnum().
 */
bool mp_is_set(const char *attr, struct mp_context *ctx)
{
	int ret;
	uint8_t bitnum;
	const uint64_t one = 1;

	ret = attr_name_to_bitnum(attr);
	if (ret < 0) /* treat invalid attributes as not set */
		return false;
	bitnum = ret;
	ret = get_afsi(ctx);
	if (ret < 0)
		return false;
	return (one << bitnum) & ctx->afsi.attributes;
}

/**
 * Count the number of attributes set.
 *
 * \param ctx See \ref mp_path().
 *
 * \return The number of bits which are set in the ->attributes field of the
 * afs_info structure of the current audio file.
 */
int64_t mp_num_attributes_set(struct mp_context *ctx)
{
	const uint64_t m = ~(uint64_t)0;
	int ret;
	uint64_t v;

	ret = get_afsi(ctx);
	if (ret < 0)
		return 0;

	v = ctx->afsi.attributes;
	/* taken from https://graphics.stanford.edu/~seander/bithacks.html */
	v = v - ((v >> 1) & m / 3);
	v = (v & m / 15 * 3) + ((v >> 2) & m / 15 * 3);
	v = (v + (v >> 4)) & m / 255 * 15;
	v = (v * (m / 255)) >> 56;
	assert(v <= 64);
	return v;
}

/**
 * Define a function which returns a field of the afs_info structure.
 *
 * \param _name The name of the field.
 *
 * The defined function casts the value to int64_t. On errors, zero is returned.
 */
#define MP_AFSI(_name) \
	int64_t mp_ ## _name(struct mp_context *ctx) \
	{ \
		int ret = get_afsi(ctx); \
		if (ret < 0) \
			return 0; \
		return ctx->afsi._name; \
	}
/** \cond MP_AFSI */
MP_AFSI(num_played)
MP_AFSI(image_id)
MP_AFSI(lyrics_id)
/** \endcond */

/**
 * Define a function which returns a field of the afh_info structure.
 *
 * \param _name The name of the field.
 *
 * The defined function casts the value to int64_t. On errors, zero is returned.
 */
#define MP_AFHI(_name) \
	int64_t mp_ ## _name(struct mp_context *ctx) \
	{ \
		int ret = get_afhi(ctx); \
		if (ret < 0) \
			return 0; \
		return ctx->afhi._name; \
	}
/** \cond MP_AFHI */
MP_AFHI(bitrate)
MP_AFHI(frequency)
MP_AFHI(channels)
/** \endcond */

/**
 * Return the duration of the audio file from the afh info structure.
 *
 * \param ctx See \ref mp_path().
 *
 * The duration is computed by multiplying the number of chunks and the
 * duration of one chunk.
 *
 * \return The approximate number of milliseconds.
 */
int64_t mp_duration(struct mp_context *ctx)
{
	struct timeval tmp;
	int ret = get_afhi(ctx);

	if (ret < 0)
		return 0;
	tv_scale(ctx->afhi.chunks_total, &ctx->afhi.chunk_tv, &tmp);
	return tv2ms(&tmp);
}

/**
 * Define a function which extracts and returns the value of a meta tag.
 *
 * \param _name The name of the tag (artist, title, ...).
 *
 * The function will return a pointer to memory owned by the audio file
 * selector. On errors, or if the current audio file has no tag of the given
 * name, the function returns the empty string. The caller must not attempt to
 * free the returned string.
 */
#define MP_TAG(_name) \
	char *mp_ ## _name (struct mp_context *ctx) \
	{ \
		int ret = get_afhi(ctx); \
		if (ret < 0) \
			return ""; \
		return ctx->afhi.tags._name; \
	}
/** \cond MP_TAG */
MP_TAG(artist)
MP_TAG(title)
MP_TAG(album)
MP_TAG(comment)
/** \endcond */

/**
 * Parse and return the value of the year tag.
 *
 * \param ctx See \ref mp_path().
 *
 * \return If the year tag is not present, can not be parsed, or its value is
 * less than zero, the function returns 0. If the value is less than 100, we
 * add 1900.
 */
int64_t mp_year(struct mp_context *ctx)
{
	int64_t year;
	int ret = get_afhi(ctx);

	if (ret < 0)
		return 0;
	assert(ctx->afhi.tags.year);
	ret = para_atoi64(ctx->afhi.tags.year, &year);
	if (ret < 0)
		return 0;
	if (year < 0)
		return 0;
	if (year < 100)
		year += 1900;
	return year;
}

/*
 * Ideally, these functions should be declared in a header file which is
 * created by flex with the --header-file option. However, for flex-2.6.x
 * (2017) this option is borken: if --reentrant is also given, the generated
 * header file contains syntax errors. As a workaround we declare the functions
 * here.
 */
/** \cond flex_workaround */
int mp_yylex_init(mp_yyscan_t *yyscanner);
struct yy_buffer_state *mp_yy_scan_bytes(const char *buf, int len,
	mp_yyscan_t yyscanner);
void mp_yy_delete_buffer(struct yy_buffer_state *bs, mp_yyscan_t yyscanner);
int mp_yylex_destroy(mp_yyscan_t yyscanner);
void mp_yyset_lineno(int lineno, mp_yyscan_t scanner);
/** \endcond */

/* Public API */

/**
 * Initialize the mood parser.
 *
 * This allocates and sets up the internal structures of the mood parser
 * and creates an abstract syntax tree from the given mood definition.
 * It must be called before \ref mp_eval_row() can be called.
 *
 * The context pointer returned by this function may be passed to \ref
 * mp_eval_row() to determine whether an audio file is admissible.
 *
 * \param definition A reference to the mood definition.
 * \param nbytes The size of the mood definition.
 * \param result Opaque context pointer is returned here.
 * \param errmsg Optional error message is returned here.
 *
 * It's OK to pass a NULL pointer or a zero sized buffer as the mood
 * definition. This corresponds to the "dummy" mood for which all audio files
 * are admissible.
 *
 * The error message pointer may also be NULL in which case no error message
 * is returned. Otherwise, the caller must free the returned string.
 *
 * \return Standard. On success *errmsg is set to NULL.
 */
int mp_init(const char *definition, int nbytes, struct mp_context **result,
		 char **errmsg)
{
	mp_yyscan_t scanner;
	struct mp_context *ctx;
	struct yy_buffer_state *buffer_state;

	*result = NULL;
	if (!definition || nbytes == 0) { /* dummy mood */
		if (errmsg)
			*errmsg = NULL;
		return 0;
	}
	ctx = zalloc(sizeof(*ctx));
	ctx->errmsg = NULL;
	ctx->ast = NULL;

	assert(mp_yylex_init(&scanner) == 0);
	buffer_state = mp_yy_scan_bytes(definition, nbytes, scanner);
	mp_yyset_lineno(1, scanner);
	PARA_NOTICE_LOG("creating abstract syntax tree\n");
	mp_yyparse(ctx, &ctx->ast, scanner);
	mp_yy_delete_buffer(buffer_state, scanner);
	mp_yylex_destroy(scanner);
	if (ctx->errmsg) { /* parse error */
		PARA_NOTICE_LOG("%s\n", ctx->errmsg);
		mp_free_ast(ctx->ast);
		if (errmsg)
			*errmsg = make_message("%s\n", ctx->errmsg);
		free(ctx->errmsg);
		free(ctx);
		return -E_MOOD_PARSE;
	}
	if (errmsg)
		*errmsg = NULL;
	*result = ctx;
	return 1;
}

/**
 * Determine whether the given audio file is admissible.
 *
 * \param aft_row The audio file to check for admissibility.
 * \param ctx As returned from \ref mp_init().
 *
 * \return Whether the audio file is admissible.
 *
 * If the mood parser was set up without an input buffer (dummy mood), this
 * function returns true (without looking at the audio file metadata) to
 * indicate that the given audio file should be considered admissible.
 *
 * \sa \ref mp_eval_ast().
 */
bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx)
{
	if (!ctx) /* dummy mood */
		return true;
	if (!ctx->ast) /* empty mood */
		return true;
	assert(aft_row);
	ctx->aft_row = aft_row;
	ctx->have_afsi = false;
	ctx->have_afhi = false;
	ctx->path = NULL;
	return mp_eval_ast(ctx->ast, ctx);
}

/**
 * Deallocate the resources of a mood parser.
 *
 * This function frees the abstract syntax tree which was created by \ref
 * mp_init().
 *
 * \param ctx As returned from \ref mp_init().
 *
 * It's OK to pass a NULL pointer, in which case the function does nothing.
 */
void mp_shutdown(struct mp_context *ctx)
{
	if (!ctx)
		return;
	mp_free_ast(ctx->ast);
	free(ctx);
}