--- /dev/null
+/*
+ * Copyright (C) 2017 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/*
+ * Provide more verbose and specific error messages instead of just "syntax
+ * error".
+ */
+%define parse.error verbose
+
+/*
+ * Verbose error messages may contain incorrect information if LAC (Lookahead
+ * Correction) is not enabled.
+ */
+%define parse.lac full
+
+/* Avoid symbol clashes (lopsub might also expose yy* symbols). */
+%define api.prefix {mp_yy}
+
+/*
+ * Although locations are automatically enabled as soon as the grammar uses the
+ * special @N tokens, specifying %locations explicitly allows for more accurate
+ * syntax error messages.
+ */
+%locations
+
+/*
+ * Generate a pure (reentrant) parser. With this option enabled, yylval and
+ * yylloc become local variables in yyparse(), and a different calling
+ * convention is used for yylex().
+ */
+%define api.pure full
+
+/* Additional arguments to yylex(), yyparse() and yyerror() */
+%param {struct mp_context *ctx}
+%param {struct mp_ast_node **ast}
+%param {mp_yyscan_t yyscanner} /* reentrant lexers */
+
+%{
+#include <fnmatch.h>
+#include <regex.h>
+
+#include "para.h"
+#include "string.h"
+#include "mp.h"
+#include "mp.bison.h"
+#include "error.h"
+
+int yylex(MP_YYSTYPE *lvalp, MP_YYLTYPE *llocp, struct mp_context *ctx,
+ struct mp_ast_node **ast, mp_yyscan_t yyscanner);
+static void yyerror(YYLTYPE *llocp, struct mp_context *ctx,
+ struct mp_ast_node **ast, mp_yyscan_t yyscanner, const char *msg);
+
+enum semantic_types {
+ ST_STRVAL,
+ ST_INTVAL,
+ ST_BOOLVAL,
+ ST_REGEX_PATTERN,
+ ST_WC_PATTERN
+};
+
+static struct mp_ast_node *ast_node_raw(int id)
+{
+ struct mp_ast_node *node = para_malloc(sizeof(struct mp_ast_node));
+ node->id = id;
+ return node;
+}
+
+/* This is non-static because it is also called from the lexer. */
+struct mp_ast_node *mp_new_ast_leaf_node(int id)
+{
+ struct mp_ast_node *node = ast_node_raw(id);
+ node->num_children = 0;
+ return node;
+}
+
+static struct mp_ast_node *ast_node_new_unary(int id, struct mp_ast_node *child)
+{
+ struct mp_ast_node *node = ast_node_raw(id);
+ node->num_children = 1;
+ node->children = para_malloc(sizeof(struct mp_ast_node *));
+ node->children[0] = child;
+ return node;
+}
+
+static struct mp_ast_node *ast_node_new_binary(int id, struct mp_ast_node *left,
+ struct mp_ast_node *right)
+{
+ struct mp_ast_node *node = ast_node_raw(id);
+ node->num_children = 2;
+ node->children = para_malloc(2 * sizeof(struct mp_ast_node *));
+ node->children[0] = left;
+ node->children[1] = right;
+ return node;
+}
+
+void mp_free_ast(struct mp_ast_node *root)
+{
+ if (!root)
+ return;
+ if (root->num_children > 0) {
+ int i;
+ for (i = 0; i < root->num_children; i++)
+ mp_free_ast(root->children[i]);
+ free(root->children);
+ } else {
+ union mp_semantic_value *sv = &root->sv;
+ switch (root->id) {
+ case STRING_LITERAL:
+ free(sv->strval);
+ break;
+ case REGEX_PATTERN:
+ regfree(&sv->re_pattern.preg);
+ break;
+ case WILDCARD_PATTERN:
+ free(sv->wc_pattern.pat);
+ break;
+ }
+ }
+ free(root);
+}
+
+static int eval_node(struct mp_ast_node *node, struct mp_context *ctx,
+ union mp_semantic_value *result);
+
+static void eval_binary_op(struct mp_ast_node *node, struct mp_context *ctx,
+ union mp_semantic_value *v1, union mp_semantic_value *v2)
+{
+ eval_node(node->children[0], ctx, v1);
+ eval_node(node->children[1], ctx, v2);
+}
+
+static int eval_node(struct mp_ast_node *node, struct mp_context *ctx,
+ union mp_semantic_value *result)
+{
+ int ret;
+ char *arg;
+ union mp_semantic_value v1, v2;
+
+ switch (node->id) {
+ /* strings */
+ case STRING_LITERAL:
+ result->strval = node->sv.strval;
+ return ST_STRVAL;
+ case PATH:
+ result->strval = mp_path(ctx);
+ return ST_STRVAL;
+ case ARTIST:
+ result->strval = mp_artist(ctx);
+ return ST_STRVAL;
+ case TITLE:
+ result->strval = mp_title(ctx);
+ return ST_STRVAL;
+ case ALBUM:
+ result->strval = mp_album(ctx);
+ return ST_STRVAL;
+ case COMMENT:
+ result->strval = mp_comment(ctx);
+ return ST_STRVAL;
+ /* integers */
+ case NUM:
+ result->intval = node->sv.intval;
+ return ST_INTVAL;
+ case '+':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->intval = v1.intval + v2.intval;
+ return ST_INTVAL;
+ case '-':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->intval = v1.intval - v2.intval;
+ return ST_INTVAL;
+ case '*':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->intval = v1.intval * v2.intval;
+ return ST_INTVAL;
+ case '/':
+ eval_binary_op(node, ctx, &v1, &v2);
+ if (v2.intval == 0) {
+ static bool warned;
+ if (!warned)
+ PARA_ERROR_LOG("division by zero\n");
+ warned = true;
+ result->intval = 0;
+ } else
+ result->intval = v1.intval / v2.intval;
+ return ST_INTVAL;
+ case NEG:
+ eval_node(node->children[0], ctx, &v1);
+ result->intval = -v1.intval;
+ return ST_INTVAL;
+ case YEAR:
+ result->intval = mp_year(ctx);
+ return ST_INTVAL;
+ case NUM_ATTRIBUTES_SET:
+ result->intval = mp_num_attributes_set(ctx);
+ return ST_INTVAL;
+ case NUM_PLAYED:
+ result->intval = mp_num_played(ctx);
+ return ST_INTVAL;
+ case IMAGE_ID:
+ result->intval = mp_image_id(ctx);
+ return ST_INTVAL;
+ case LYRICS_ID:
+ result->intval = mp_lyrics_id(ctx);
+ return ST_INTVAL;
+ case BITRATE:
+ result->intval = mp_bitrate(ctx);
+ return ST_INTVAL;
+ case FREQUENCY:
+ result->intval = mp_frequency(ctx);
+ return ST_INTVAL;
+ case CHANNELS:
+ result->intval= mp_channels(ctx);
+ return ST_INTVAL;
+ /* bools */
+ case IS_SET:
+ arg = node->children[0]->sv.strval;
+ result->boolval = mp_is_set(arg, ctx);
+ return ST_BOOLVAL;
+ case TRUE:
+ result->boolval = true;
+ return ST_BOOLVAL;
+ case FALSE:
+ result->boolval = false;
+ return ST_BOOLVAL;
+ case OR:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.boolval || v2.boolval;
+ return ST_BOOLVAL;
+ case AND:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.boolval && v2.boolval;
+ return ST_BOOLVAL;
+ case NOT:
+ eval_node(node->children[0], ctx, &v1);
+ result->boolval = !v1.boolval;
+ return ST_BOOLVAL;
+ case EQUAL:
+ ret = eval_node(node->children[0], ctx, &v1);
+ eval_node(node->children[1], ctx, &v2);
+ if (ret == ST_STRVAL)
+ result->boolval = !strcmp(v1.strval, v2.strval);
+ else
+ result->boolval = v1.intval == v2.intval;
+ return ST_BOOLVAL;
+ case NOT_EQUAL:
+ ret = eval_node(node->children[0], ctx, &v1);
+ eval_node(node->children[1], ctx, &v2);
+ if (ret == ST_STRVAL)
+ result->boolval = strcmp(v1.strval, v2.strval);
+ else
+ result->boolval = v1.intval != v2.intval;
+ return ST_BOOLVAL;
+ case '<':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.intval < v2.intval;
+ return ST_BOOLVAL;
+ case '>':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.intval > v2.intval;
+ return ST_BOOLVAL;
+ case LESS_OR_EQUAL:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.intval <= v2.intval;
+ return ST_BOOLVAL;
+ case GREATER_OR_EQUAL:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.intval >= v2.intval;
+ return ST_BOOLVAL;
+ case FILENAME_MATCH:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = fnmatch(v2.wc_pattern.pat, v1.strval,
+ v2.wc_pattern.flags) == 0;
+ return ST_BOOLVAL;
+ case REGEX_MATCH:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = regexec(&v2.re_pattern.preg, v1.strval,
+ 0, NULL, 0) == 0;
+ return ST_BOOLVAL;
+ case REGEX_PATTERN:
+ result->re_pattern = node->sv.re_pattern;
+ return ST_REGEX_PATTERN;
+ case WILDCARD_PATTERN:
+ result->wc_pattern = node->sv.wc_pattern;
+ return ST_WC_PATTERN;
+ default:
+ PARA_EMERG_LOG("bug: invalid node id %d\n", node->id);
+ exit(EXIT_FAILURE);
+ }
+}
+
+bool mp_eval_ast(struct mp_ast_node *root, struct mp_context *ctx)
+{
+ union mp_semantic_value v;
+ int ret = eval_node(root, ctx, &v);
+
+ if (ret == ST_INTVAL)
+ return v.intval != 0;
+ if (ret == ST_STRVAL)
+ return v.strval[0] != 0;
+ if (ret == ST_BOOLVAL)
+ return v.boolval;
+ assert(false);
+}
+
+%}
+
+%union {
+ struct mp_ast_node *node;
+}
+
+/* terminals */
+%token <node> NUM
+%token <node> STRING_LITERAL
+%token <node> REGEX_PATTERN
+%token <node> WILDCARD_PATTERN
+
+/* keywords with semantic value */
+%token <node> PATH
+%token <node> ARTIST
+%token <node> TITLE
+%token <node> ALBUM
+%token <node> COMMENT
+%token <node> YEAR
+%token <node> NUM_ATTRIBUTES_SET
+%token <node> NUM_PLAYED
+%token <node> IMAGE_ID
+%token <node> LYRICS_ID
+%token <node> BITRATE
+%token <node> FREQUENCY
+%token <node> CHANNELS
+%token <node> FALSE TRUE
+
+/* keywords without semantic value */
+%token IS_SET
+
+/* operators, ordered by precendence */
+%left OR
+%left AND
+%left EQUAL NOT_EQUAL
+%left LESS_THAN LESS_OR_EQUAL GREATER_OR_EQUAL REGEX_MATCH FILENAME_MATCH
+%left '-' '+'
+%left '*' '/'
+%right NOT NEG /* negation (unary minus) */
+
+/* nonterminals */
+%type <node> string
+%type <node> exp
+%type <node> boolexp
+
+%%
+
+program:
+ /* empty */ {*ast = NULL; return 0;}
+ | string {*ast = $1; return 0;}
+ | exp {*ast = $1; return 0;}
+ | boolexp {*ast = $1; return 0;}
+
+string: STRING_LITERAL {$$ = $1;}
+ | PATH {$$ = mp_new_ast_leaf_node(PATH);}
+ | ARTIST {$$ = mp_new_ast_leaf_node(ARTIST);}
+ | TITLE {$$ = mp_new_ast_leaf_node(TITLE);}
+ | ALBUM {$$ = mp_new_ast_leaf_node(ALBUM);}
+ | COMMENT {$$ = mp_new_ast_leaf_node(COMMENT);}
+;
+
+exp: NUM {$$ = $1;}
+ | exp '+' exp {$$ = ast_node_new_binary('+', $1, $3);}
+ | exp '-' exp {$$ = ast_node_new_binary('-', $1, $3);}
+ | exp '*' exp {$$ = ast_node_new_binary('*', $1, $3);}
+ | exp '/' exp {$$ = ast_node_new_binary('/', $1, $3);}
+ | '-' exp %prec NEG {$$ = ast_node_new_unary(NEG, $2);}
+ | '(' exp ')' {$$ = $2;}
+ | YEAR {$$ = mp_new_ast_leaf_node(YEAR);}
+ | NUM_ATTRIBUTES_SET {$$ = mp_new_ast_leaf_node(NUM_ATTRIBUTES_SET);}
+ | NUM_PLAYED {$$ = mp_new_ast_leaf_node(NUM_PLAYED);}
+ | IMAGE_ID {$$ = mp_new_ast_leaf_node(IMAGE_ID);}
+ | LYRICS_ID {$$ = mp_new_ast_leaf_node(LYRICS_ID);}
+ | BITRATE {$$ = mp_new_ast_leaf_node(BITRATE);}
+ | FREQUENCY {$$ = mp_new_ast_leaf_node(FREQUENCY);}
+ | CHANNELS {$$ = mp_new_ast_leaf_node(CHANNELS);}
+;
+
+boolexp: IS_SET '(' STRING_LITERAL ')' {$$ = ast_node_new_unary(IS_SET, $3);}
+ | TRUE {$$ = mp_new_ast_leaf_node(TRUE);}
+ | FALSE {$$ = mp_new_ast_leaf_node(FALSE);}
+ | '(' boolexp ')' {$$ = $2;}
+ | boolexp OR boolexp {$$ = ast_node_new_binary(OR, $1, $3);}
+ | boolexp AND boolexp {$$ = ast_node_new_binary(AND, $1, $3);}
+ | NOT boolexp {$$ = ast_node_new_unary(NOT, $2);}
+ | exp EQUAL exp {$$ = ast_node_new_binary(EQUAL, $1, $3);}
+ | exp NOT_EQUAL exp {$$ = ast_node_new_binary(NOT_EQUAL, $1, $3);}
+ | exp '<' exp {$$ = ast_node_new_binary('<', $1, $3);}
+ | exp '>' exp {$$ = ast_node_new_binary('>', $1, $3);}
+ | exp LESS_OR_EQUAL exp {
+ $$ = ast_node_new_binary(LESS_OR_EQUAL, $1, $3);
+ }
+ | exp GREATER_OR_EQUAL exp {
+ $$ = ast_node_new_binary(GREATER_OR_EQUAL, $1, $3);
+ }
+ | string REGEX_MATCH REGEX_PATTERN {
+ $$ = ast_node_new_binary(REGEX_MATCH, $1, $3);
+ }
+ | string FILENAME_MATCH WILDCARD_PATTERN {
+ $$ = ast_node_new_binary(FILENAME_MATCH, $1, $3);
+ }
+ | string EQUAL string {$$ = ast_node_new_binary(EQUAL, $1, $3);}
+ | string NOT_EQUAL string {$$ = ast_node_new_binary(NOT_EQUAL, $1, $3);}
+;
+%%
+
+/* Called by yyparse() on error */
+static void yyerror(YYLTYPE *llocp, struct mp_context *ctx,
+ struct mp_ast_node **ast, mp_yyscan_t yyscanner, const char *msg)
+{
+ mp_parse_error(llocp->first_line, ctx, "%s", msg);
+}