]> git.tuebingen.mpg.de Git - paraslash.git/blobdiff - yy/mp.y
Version 2 moods.
[paraslash.git] / yy / mp.y
diff --git a/yy/mp.y b/yy/mp.y
new file mode 100644 (file)
index 0000000..82ef514
--- /dev/null
+++ b/yy/mp.y
@@ -0,0 +1,419 @@
+/*
+ * 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);
+}