/* Copyright (C) 2017 Andre Noll , see file 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 #include #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 NUM %token STRING_LITERAL %token REGEX_PATTERN %token WILDCARD_PATTERN /* keywords with semantic value */ %token PATH %token ARTIST %token TITLE %token ALBUM %token COMMENT %token YEAR %token NUM_ATTRIBUTES_SET %token NUM_PLAYED %token IMAGE_ID %token LYRICS_ID %token BITRATE %token FREQUENCY %token CHANNELS %token 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 string %type exp %type 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); }