]> git.tuebingen.mpg.de Git - paraslash.git/blob - mp.c
mood: Fix compute_score().
[paraslash.git] / mp.c
1 /* Copyright (C) 2017 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
2
3 /**
4  * \file mp.c Mood parser helper functions.
5  *
6  * This file contains the public and the private API of the flex/bison based
7  * mood parser.
8  *
9  * The public API (at the bottom of the file) allows parsing the same mood
10  * definition many times in an efficient manner.
11  *
12  * The first function to call is \ref mp_init(), which analyzes the given mood
13  * definition syntactically. It returns the abstract syntax tree of the mood
14  * definition and pre-compiles all regular expression patterns to make later
15  * pattern matching efficient.
16  *
17  * Semantic analysis is performed in \ref mp_eval_row(). This function is
18  * called from \ref mood.c once for each file in the audio file table. It
19  * utilizes the abstract syntax tree and the pre-compiled regular expressions
20  * to determine the set of admissible audio files.
21  *
22  * If the mood is no longer needed, \ref mp_shutdown() should be called to free
23  * the resources.
24  *
25  * The internal API is described in \ref mp.h.
26  */
27
28 #include "para.h"
29
30 #include <regex.h>
31 #include <fnmatch.h>
32 #include <osl.h>
33 #include <lopsub.h>
34
35 #include "string.h"
36 #include "error.h"
37 #include "afh.h"
38 #include "afs.h"
39 #include "mp.h"
40 #include "mp.bison.h"
41
42 struct mp_context {
43         /* global context */
44         char *errmsg;
45         struct mp_ast_node *ast;
46         /* per audio file context */
47         const struct osl_row *aft_row;
48         char *path;
49         bool have_afsi;
50         struct afs_info afsi;
51         bool have_afhi;
52         struct afh_info afhi;
53 };
54
55 /**
56  * Parse a (generalized) string literal.
57  *
58  * \param src The string to parse.
59  * \param quote_chars Opening and closing quote characters.
60  * \param result The corresponding C string is returned here.
61  *
62  * This function turns a generalized C99 string literal like "xyz\n" into a C
63  * string (containing the three characters 'x', 'y' and 'z', followed by a
64  * newline character and the terminating zero byte). The function receives
65  * quote characters as an argument so that, for example, regular expression
66  * patterns enclosed in '/' can be parsed as well. To parse a proper string
67  * literal, one has to pass two double quotes as the second argument.
68  *
69  * The function strips off the opening and leading quote characters, replaces
70  * double backslashes by single backslashes and handles the usual escapes like
71  * \n and \".
72  *
73  * The caller must make sure that the input is well-formed. The function simply
74  * aborts if the input is not a valid C99 string literal (modulo the quote
75  * characters).
76  *
77  * \return Offset of the first character after the closing quote. For proper
78  * string literals this will be the terminating zero byte of the input string,
79  * for regular expression patterns it is the beginning of the flags which
80  * modify the matching behaviour.
81  *
82  * \sa \ref mp_parse_regex_pattern(), \ref mp_parse_wildcard_pattern().
83  */
84 unsigned parse_quoted_string(const char *src, const char quote_chars[2],
85                 char **result)
86 {
87         size_t n, len = strlen(src);
88         char *dst, *p;
89         bool backslash;
90
91         assert(len >= 2);
92         assert(src[0] == quote_chars[0]);
93         p = dst = alloc(len - 1);
94         backslash = false;
95         for (n = 1;; n++) {
96                 char c;
97                 assert(n < len);
98                 c = src[n];
99                 if (!backslash) {
100                         if (c == '\\') {
101                                 backslash = true;
102                                 continue;
103                         }
104                         if (c == quote_chars[1])
105                                 break;
106                         *p++ = c;
107                         continue;
108                 }
109                 if (c == quote_chars[1])
110                         *p++ = quote_chars[1];
111                 else switch (c) {
112                         case '\\': *p++ = '\\'; break;
113                         case 'a': *p++ = '\a'; break;
114                         case 'b': *p++ = '\b'; break;
115                         case 'f': *p++ = '\f'; break;
116                         case 'n': *p++ = '\n'; break;
117                         case 'r': *p++ = '\r'; break;
118                         case 't': *p++ = '\t'; break;
119                         case 'v': *p++ = '\v'; break;
120                         default: assert(false);
121                 }
122                 backslash = false;
123         }
124         assert(src[n] == quote_chars[1]);
125         *p = '\0';
126         *result = dst;
127         return n + 1;
128 }
129
130 /**
131  * Parse and compile an extended regular expression pattern, including flags.
132  *
133  * \param src The pattern to parse.
134  * \param result C-string and flags are returned here.
135  *
136  * A regex pattern is identical to a C99 string literal except (a) it is
137  * enclosed in '/' characters rather than double quotes, (b) double quote
138  * characters which are part of the pattern do not need to be quoted with
139  * backslashes, but slashes must be quoted in this way, and (c) the closing
140  * slash may be followed by one or more flag characters which modify the
141  * matching behaviour.
142  *
143  * The only flags which are currently supported are 'i' to ignore case in match
144  * (REG_ICASE) and 'n' to change the handling of newline characters
145  * (REG_NEWLINE).
146  *
147  * \return Standard. This function calls \ref parse_quoted_string(), hence it
148  * aborts if the input string is malformed. However, errors from \ref
149  * para_regcomp are returned without aborting the process. The rationale behind
150  * this difference is that passing a malformed string must be considered an
151  * implementation bug because malformed strings should be rejected earlier by
152  * the lexer.
153  *
154  * \sa \ref mp_parse_wildcard_pattern(), \ref parse_quoted_string(),
155  * \ref para_regcomp(), regex(3).
156  */
157 int mp_parse_regex_pattern(const char *src, struct mp_re_pattern *result)
158 {
159         int ret;
160         char *pat;
161         unsigned n = parse_quoted_string(src, "//", &pat);
162
163         result->flags = 0;
164         for (; src[n]; n++) {
165                 switch (src[n]) {
166                 case 'i': result->flags |= REG_ICASE; break;
167                 case 'n': result->flags |= REG_NEWLINE; break;
168                 default: assert(false);
169                 }
170         }
171         ret = para_regcomp(&result->preg, pat, result->flags);
172         free(pat);
173         return ret;
174 }
175
176 /**
177  * Parse a wildcard pattern, including flags.
178  *
179  * \param src The pattern to parse.
180  * \param result C-string and flags are returned here.
181  *
182  * This function parses a shell wildcard pattern. It is similar to \ref
183  * mp_parse_regex_pattern(), so the remarks mentioned there apply to this
184  * function as well.
185  *
186  * Wildcard patterns differ from regular expression patterns in that (a) they
187  * must be enclosed in '|' characters, (b) they support different flags for
188  * modifying matching behaviour, and (c) there is no cache for them.
189  *
190  * The following flags, whose meaning is explained in fnmatch(3), are currently
191  * supported: 'n' (FNM_NOESCAPE), 'p' (FNM_PATHNAME), 'P' (FNM_PERIOD), 'l'
192  * (FNM_LEADING_DIR), 'i' (FNM_CASEFOLD), 'e' (FNM_EXTMATCH). The last flag is
193  * a GNU extension. It is silently ignored on non GNU systems.
194  *
195  * \sa \ref parse_quoted_string(), \ref mp_parse_regex_pattern(), fnmatch(3).
196  */
197 void mp_parse_wildcard_pattern(const char *src, struct mp_wc_pattern *result)
198 {
199         unsigned n = parse_quoted_string(src, "||", &result->pat);
200
201         result->flags = 0;
202         for (; src[n]; n++) {
203                 switch (src[n]) {
204                 case 'n': result->flags |= FNM_NOESCAPE; break;
205                 case 'p': result->flags |= FNM_PATHNAME; break;
206                 case 'P': result->flags |= FNM_PERIOD; break;
207                 /* not POSIX, but both FreeBSD and NetBSD have it */
208                 case 'l': result->flags |= FNM_LEADING_DIR; break;
209                 case 'i': result->flags |= FNM_CASEFOLD; break;
210                 /* GNU only */
211 #ifdef HAVE_FNM_EXTMATCH
212                 case 'e': result->flags |= FNM_EXTMATCH; break;
213 #else /* silently ignore extglob flag */
214                 case 'e': break;
215 #endif
216                 default: assert(false);
217                 }
218         }
219 }
220
221 /**
222  * Set the error bit in the parser context and log a message.
223  *
224  * \param line The number of the input line which caused the error.
225  * \param ctx Contains the error bit.
226  * \param fmt Usual format string.
227  *
228  * This is called if the lexer or the parser detect an error in the mood
229  * definition. Only the first error is logged (with a severity of "warn").
230  */
231 __printf_3_4 void mp_parse_error(int line, struct mp_context *ctx,
232                 const char *fmt, ...)
233 {
234         va_list ap;
235         char *tmp;
236
237         if (ctx->errmsg) /* we already printed an error message */
238                 return;
239         va_start(ap, fmt);
240         xvasprintf(&tmp, fmt, ap);
241         va_end(ap);
242         xasprintf(&ctx->errmsg, "line %d: %s", line, tmp);
243         free(tmp);
244         PARA_WARNING_LOG("%s\n", ctx->errmsg);
245 }
246
247 static int get_afsi(struct mp_context *ctx)
248 {
249         int ret;
250
251         if (ctx->have_afsi)
252                 return 0;
253         ret = get_afsi_of_row(ctx->aft_row, &ctx->afsi);
254         if (ret < 0)
255                 return ret;
256         ctx->have_afsi = true;
257         return 1;
258 }
259
260 static int get_afhi(struct mp_context *ctx)
261 {
262         int ret;
263
264         if (ctx->have_afhi)
265                 return 0;
266         ret = get_afhi_of_row(ctx->aft_row, &ctx->afhi);
267         if (ret < 0)
268                 return ret;
269         ctx->have_afhi = true;
270         return 1;
271 }
272
273 /**
274  * Return the full path to the audio file.
275  *
276  * \param ctx Contains a reference to the row of the audio file table which
277  * corresponds to the current audio file. The path of the audio file, the
278  * afs_info and the afh_info structures (which contain the tag information) can
279  * be retrieved through this reference.
280  *
281  * \return A reference to the path. Must not be freed by the caller.
282  *
283  * \sa \ref get_audio_file_path_of_row().
284  */
285 char *mp_path(struct mp_context *ctx)
286 {
287         if (!ctx->path)
288                 get_audio_file_path_of_row(ctx->aft_row, &ctx->path);
289         return ctx->path;
290 }
291
292 /**
293  * Check whether the given attribute is set for the current audio file.
294  *
295  * \param attr The string to look up in the attribute table.
296  * \param ctx See \ref mp_path().
297  *
298  * First, determine the bit number which corresponds to the attribute, then
299  * check if this bit is set in the ->attributes field of the afs_info structure
300  * of the audio file.
301  *
302  * \return True if the attribute is set, false if it is not. On errors, for
303  * example if the given string is no attribute, the function returns false.
304  *
305  * \sa \ref get_attribute_bitnum_by_name().
306  */
307 bool mp_is_set(const char *attr, struct mp_context *ctx)
308 {
309         int ret;
310         unsigned char bitnum;
311         const uint64_t one = 1;
312
313         ret = get_attribute_bitnum_by_name(attr, &bitnum);
314         if (ret < 0) /* treat invalid attributes as not set */
315                 return false;
316         ret = get_afsi(ctx);
317         if (ret < 0)
318                 return false;
319         return (one << bitnum) & ctx->afsi.attributes;
320 }
321
322 /**
323  * Count the number of attributes set.
324  *
325  * \param ctx See \ref mp_path().
326  *
327  * \return The number of bits which are set in the ->attributes field of the
328  * afs_info structure of the current audio file.
329  */
330 int64_t mp_num_attributes_set(struct mp_context *ctx)
331 {
332         const uint64_t m = ~(uint64_t)0;
333         int ret;
334         uint64_t v;
335
336         ret = get_afsi(ctx);
337         if (ret < 0)
338                 return 0;
339
340         v = ctx->afsi.attributes;
341         /* taken from https://graphics.stanford.edu/~seander/bithacks.html */
342         v = v - ((v >> 1) & m / 3);
343         v = (v & m / 15 * 3) + ((v >> 2) & m / 15 * 3);
344         v = (v + (v >> 4)) & m / 255 * 15;
345         v = (v * (m / 255)) >> 56;
346         assert(v <= 64);
347         return v;
348 }
349
350 /**
351  * Define a function which returns a field of the afs_info structure.
352  *
353  * \param _name The name of the field.
354  *
355  * The defined function casts the value to int64_t. On errors, zero is returned.
356  */
357 #define MP_AFSI(_name) \
358         int64_t mp_ ## _name(struct mp_context *ctx) \
359         { \
360                 int ret = get_afsi(ctx); \
361                 if (ret < 0) \
362                         return 0; \
363                 return ctx->afsi._name; \
364         }
365 /** \cond MP_AFSI */
366 MP_AFSI(num_played)
367 MP_AFSI(image_id)
368 MP_AFSI(lyrics_id)
369 /** \endcond */
370
371 /**
372  * Define a function which returns a field of the afh_info structure.
373  *
374  * \param _name The name of the field.
375  *
376  * The defined function casts the value to int64_t. On errors, zero is returned.
377  */
378 #define MP_AFHI(_name) \
379         int64_t mp_ ## _name(struct mp_context *ctx) \
380         { \
381                 int ret = get_afhi(ctx); \
382                 if (ret < 0) \
383                         return 0; \
384                 return ctx->afhi._name; \
385         }
386 /** \cond MP_AFHI */
387 MP_AFHI(bitrate)
388 MP_AFHI(frequency)
389 MP_AFHI(channels)
390 /** \endcond */
391
392 /**
393  * Return the duration of the audio file from the afh info structure.
394  *
395  * \param ctx See \ref mp_path().
396  *
397  * The duration is computed by multiplying the number of chunks and the
398  * duration of one chunk.
399  *
400  * \return The approximate number of milliseconds.
401  */
402 int64_t mp_duration(struct mp_context *ctx)
403 {
404         struct timeval tmp;
405         int ret = get_afhi(ctx);
406
407         if (ret < 0)
408                 return 0;
409         tv_scale(ctx->afhi.chunks_total, &ctx->afhi.chunk_tv, &tmp);
410         return tv2ms(&tmp);
411 }
412
413 /**
414  * Define a function which extracts and returns the value of a meta tag.
415  *
416  * \param _name The name of the tag (artist, title, ...).
417  *
418  * The function will return a pointer to memory owned by the audio file
419  * selector. On errors, or if the current audio file has no tag of the given
420  * name, the function returns the empty string. The caller must not attempt to
421  * free the returned string.
422  */
423 #define MP_TAG(_name) \
424         char *mp_ ## _name (struct mp_context *ctx) \
425         { \
426                 int ret = get_afhi(ctx); \
427                 if (ret < 0) \
428                         return ""; \
429                 return ctx->afhi.tags._name; \
430         }
431 /** \cond MP_TAG */
432 MP_TAG(artist)
433 MP_TAG(title)
434 MP_TAG(album)
435 MP_TAG(comment)
436 /** \endcond */
437
438 /**
439  * Parse and return the value of the year tag.
440  *
441  * \param ctx See \ref mp_path().
442  *
443  * \return If the year tag is not present, can not be parsed, or its value is
444  * less than zero, the function returns 0. If the value is less than 100, we
445  * add 1900.
446  */
447 int64_t mp_year(struct mp_context *ctx)
448 {
449         int64_t year;
450         int ret = get_afhi(ctx);
451
452         if (ret < 0)
453                 return 0;
454         assert(ctx->afhi.tags.year);
455         ret = para_atoi64(ctx->afhi.tags.year, &year);
456         if (ret < 0)
457                 return 0;
458         if (year < 0)
459                 return 0;
460         if (year < 100)
461                 year += 1900;
462         return year;
463 }
464
465 /*
466  * Ideally, these functions should be declared in a header file which is
467  * created by flex with the --header-file option. However, for flex-2.6.x
468  * (2017) this option is borken: if --reentrant is also given, the generated
469  * header file contains syntax errors. As a workaround we declare the functions
470  * here.
471  */
472 /** \cond flex_workaround */
473 int mp_yylex_init(mp_yyscan_t *yyscanner);
474 struct yy_buffer_state *mp_yy_scan_bytes(const char *buf, int len,
475         mp_yyscan_t yyscanner);
476 void mp_yy_delete_buffer(struct yy_buffer_state *bs, mp_yyscan_t yyscanner);
477 int mp_yylex_destroy(mp_yyscan_t yyscanner);
478 void mp_yyset_lineno(int lineno, mp_yyscan_t scanner);
479 /** \endcond */
480
481 /* Public API */
482
483 /**
484  * Initialize the mood parser.
485  *
486  * This allocates and sets up the internal structures of the mood parser
487  * and creates an abstract syntax tree from the given mood definition.
488  * It must be called before \ref mp_eval_row() can be called.
489  *
490  * The context pointer returned by this function may be passed to \ref
491  * mp_eval_row() to determine whether an audio file is admissible.
492  *
493  * \param definition A reference to the mood definition.
494  * \param nbytes The size of the mood definition.
495  * \param result Opaque context pointer is returned here.
496  * \param errmsg Optional error message is returned here.
497  *
498  * It's OK to pass a NULL pointer or a zero sized buffer as the mood
499  * definition. This corresponds to the "dummy" mood for which all audio files
500  * are admissible.
501  *
502  * The error message pointer may also be NULL in which case no error message
503  * is returned. Otherwise, the caller must free the returned string.
504  *
505  * \return Standard. On success *errmsg is set to NULL.
506  */
507 int mp_init(const char *definition, int nbytes, struct mp_context **result,
508                  char **errmsg)
509 {
510         int ret;
511         mp_yyscan_t scanner;
512         struct mp_context *ctx;
513         struct yy_buffer_state *buffer_state;
514
515         *result = NULL;
516         if (!definition || nbytes == 0) { /* dummy mood */
517                 if (errmsg)
518                         *errmsg = NULL;
519                 return 0;
520         }
521         ctx = zalloc(sizeof(*ctx));
522         ctx->errmsg = NULL;
523         ctx->ast = NULL;
524
525         ret = mp_yylex_init(&scanner);
526         assert(ret == 0);
527         buffer_state = mp_yy_scan_bytes(definition, nbytes, scanner);
528         mp_yyset_lineno(1, scanner);
529         PARA_NOTICE_LOG("creating abstract syntax tree\n");
530         ret = mp_yyparse(ctx, &ctx->ast, scanner);
531         mp_yy_delete_buffer(buffer_state, scanner);
532         mp_yylex_destroy(scanner);
533         if (ctx->errmsg) { /* parse error */
534                 mp_free_ast(ctx->ast);
535                 if (errmsg)
536                         *errmsg = ctx->errmsg;
537                 else
538                         free(ctx->errmsg);
539                 free(ctx);
540                 return -E_MOOD_PARSE;
541         }
542         if (errmsg)
543                 *errmsg = NULL;
544         *result = ctx;
545         return 1;
546 }
547
548 /**
549  * Determine whether the given audio file is admissible.
550  *
551  * \param aft_row The audio file to check for admissibility.
552  * \param ctx As returned from \ref mp_init().
553  *
554  * \return Whether the audio file is admissible.
555  *
556  * If the mood parser was set up without an input buffer (dummy mood), this
557  * function returns true (without looking at the audio file metadata) to
558  * indicate that the given audio file should be considered admissible.
559  *
560  * \sa \ref change_current_mood(), \ref mp_eval_ast().
561  */
562 bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx)
563 {
564         if (!ctx) /* dummy mood */
565                 return true;
566         if (!ctx->ast) /* empty mood */
567                 return true;
568         assert(aft_row);
569         ctx->aft_row = aft_row;
570         ctx->have_afsi = false;
571         ctx->have_afhi = false;
572         ctx->path = NULL;
573         return mp_eval_ast(ctx->ast, ctx);
574 }
575
576 /**
577  * Deallocate the resources of a mood parser.
578  *
579  * This function frees the abstract syntax tree which was created by \ref
580  * mp_init().
581  *
582  * \param ctx As returned from \ref mp_init().
583  *
584  * It's OK to pass a NULL pointer, in which case the function does nothing.
585  */
586 void mp_shutdown(struct mp_context *ctx)
587 {
588         if (!ctx)
589                 return;
590         mp_free_ast(ctx->ast);
591         free(ctx);
592 }