/* GStreamer * Copyright (C) <2007> Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "asmrules.h" #define MAX_RULE_LENGTH 2048 /* define to enable some more debug */ #undef DEBUG static GstASMNode * gst_asm_node_new (void) { GstASMNode *node; node = g_new0 (GstASMNode, 1); node->type = GST_ASM_NODE_UNKNOWN; return node; } static void gst_asm_node_free (GstASMNode * node) { if (node->left) gst_asm_node_free (node->left); if (node->right) gst_asm_node_free (node->right); if (node->type == GST_ASM_NODE_VARIABLE && node->data.varname) g_free (node->data.varname); g_free (node); } static gfloat gst_asm_operator_eval (GstASMOp optype, gfloat left, gfloat right) { gfloat result = 0.0; switch (optype) { case GST_ASM_OP_GREATER: result = (gfloat) (left > right); break; case GST_ASM_OP_LESS: result = (gfloat) (left < right); break; case GST_ASM_OP_GREATEREQUAL: result = (gfloat) (left >= right); break; case GST_ASM_OP_LESSEQUAL: result = (gfloat) (left <= right); break; case GST_ASM_OP_EQUAL: result = (gfloat) (left == right); break; case GST_ASM_OP_NOTEQUAL: result = (gfloat) (left != right); break; case GST_ASM_OP_AND: result = (gfloat) (left && right); break; case GST_ASM_OP_OR: result = (gfloat) (left || right); break; default: break; } return result; } static gfloat gst_asm_node_evaluate (GstASMNode * node, GHashTable * vars) { gfloat result = 0.0; if (node == NULL) return 0.0; switch (node->type) { case GST_ASM_NODE_VARIABLE: { gchar *val; val = g_hash_table_lookup (vars, node->data.varname); if (val) result = (gfloat) atof (val); break; } case GST_ASM_NODE_INTEGER: result = (gfloat) node->data.intval; break; case GST_ASM_NODE_FLOAT: result = node->data.floatval; break; case GST_ASM_NODE_OPERATOR: { gfloat left, right; left = gst_asm_node_evaluate (node->left, vars); right = gst_asm_node_evaluate (node->right, vars); result = gst_asm_operator_eval (node->data.optype, left, right); break; } default: break; } return result; } #define IS_SPACE(p) (((p) == ' ') || ((p) == '\n') || \ ((p) == '\r') || ((p) == '\t')) #define IS_RULE_DELIM(p) (((p) == ',') || ((p) == ';') || ((p) == ')')) #define IS_OPERATOR(p) (((p) == '>') || ((p) == '<') || \ ((p) == '=') || ((p) == '!') || \ ((p) == '&') || ((p) == '|')) #define IS_NUMBER(p) ((((p) >= '0') && ((p) <= '9')) || ((p) == '.')) #define IS_CHAR(p) (!IS_OPERATOR(ch) && !IS_RULE_DELIM(ch) && (ch != '\0')) #define IS_OP_TOKEN(t) (((t) == GST_ASM_TOKEN_AND) || ((t) == GST_ASM_TOKEN_OR)) #define IS_COND_TOKEN(t) (((t) == GST_ASM_TOKEN_LESS) || ((t) == GST_ASM_TOKEN_LESSEQUAL) || \ ((t) == GST_ASM_TOKEN_GREATER) || ((t) == GST_ASM_TOKEN_GREATEREQUAL) || \ ((t) == GST_ASM_TOKEN_EQUAL) || ((t) == GST_ASM_TOKEN_NOTEQUAL)) typedef struct { const gchar *buffer; gint pos; gchar ch; GstASMToken token; gchar val[MAX_RULE_LENGTH]; } GstASMScan; #define NEXT_CHAR(scan) ((scan)->ch = (scan)->buffer[(scan)->pos++]) #define THIS_CHAR(scan) ((scan)->ch) static GstASMScan * gst_asm_scan_new (const gchar * buffer) { GstASMScan *scan; scan = g_new0 (GstASMScan, 1); scan->buffer = buffer; NEXT_CHAR (scan); return scan; } static void gst_asm_scan_free (GstASMScan * scan) { g_free (scan); } static void gst_asm_scan_string (GstASMScan * scan, gchar delim) { gchar ch; gint i = 0; ch = THIS_CHAR (scan); while ((ch != delim) && (ch != '\0')) { if (i < MAX_RULE_LENGTH - 1) scan->val[i++] = ch; ch = NEXT_CHAR (scan); if (ch == '\\') ch = NEXT_CHAR (scan); } scan->val[i] = '\0'; if (ch == delim) NEXT_CHAR (scan); scan->token = GST_ASM_TOKEN_STRING; } static void gst_asm_scan_number (GstASMScan * scan) { gchar ch; gint i = 0; gboolean have_float = FALSE; ch = THIS_CHAR (scan); /* real strips all spaces that are not inside quotes for numbers */ while ((IS_NUMBER (ch) || IS_SPACE (ch))) { if (i < (MAX_RULE_LENGTH - 1) && !IS_SPACE (ch)) scan->val[i++] = ch; if (ch == '.') have_float = TRUE; ch = NEXT_CHAR (scan); } scan->val[i] = '\0'; if (have_float) scan->token = GST_ASM_TOKEN_FLOAT; else scan->token = GST_ASM_TOKEN_INT; } static void gst_asm_scan_identifier (GstASMScan * scan) { gchar ch; gint i = 0; ch = THIS_CHAR (scan); /* real strips all spaces that are not inside quotes for identifiers */ while ((IS_CHAR (ch) || IS_SPACE (ch))) { if (i < (MAX_RULE_LENGTH - 1) && !IS_SPACE (ch)) scan->val[i++] = ch; ch = NEXT_CHAR (scan); } scan->val[i] = '\0'; scan->token = GST_ASM_TOKEN_IDENTIFIER; } static void gst_asm_scan_print_token (GstASMScan * scan) { #ifdef DEBUG switch (scan->token) { case GST_ASM_TOKEN_NONE: g_print ("none\n"); break; case GST_ASM_TOKEN_EOF: g_print ("EOF\n"); break; case GST_ASM_TOKEN_INT: g_print ("INT %d\n", atoi (scan->val)); break; case GST_ASM_TOKEN_FLOAT: g_print ("FLOAT %f\n", atof (scan->val)); break; case GST_ASM_TOKEN_IDENTIFIER: g_print ("ID %s\n", scan->val); break; case GST_ASM_TOKEN_STRING: g_print ("STRING %s\n", scan->val); break; case GST_ASM_TOKEN_HASH: g_print ("HASH\n"); break; case GST_ASM_TOKEN_SEMICOLON: g_print ("SEMICOLON\n"); break; case GST_ASM_TOKEN_COMMA: g_print ("COMMA\n"); break; case GST_ASM_TOKEN_EQUAL: g_print ("==\n"); break; case GST_ASM_TOKEN_NOTEQUAL: g_print ("!=\n"); break; case GST_ASM_TOKEN_AND: g_print ("&&\n"); break; case GST_ASM_TOKEN_OR: g_print ("||\n"); break; case GST_ASM_TOKEN_LESS: g_print ("<\n"); break; case GST_ASM_TOKEN_LESSEQUAL: g_print ("<=\n"); break; case GST_ASM_TOKEN_GREATER: g_print (">\n"); break; case GST_ASM_TOKEN_GREATEREQUAL: g_print (">=\n"); break; case GST_ASM_TOKEN_DOLLAR: g_print ("$\n"); break; case GST_ASM_TOKEN_LPAREN: g_print ("(\n"); break; case GST_ASM_TOKEN_RPAREN: g_print (")\n"); break; default: break; } #endif } static GstASMToken gst_asm_scan_next_token (GstASMScan * scan) { gchar ch; ch = THIS_CHAR (scan); /* skip spaces */ while (IS_SPACE (ch)) ch = NEXT_CHAR (scan); /* remove \ which is common in front of " */ while (ch == '\\') ch = NEXT_CHAR (scan); switch (ch) { case '#': scan->token = GST_ASM_TOKEN_HASH; NEXT_CHAR (scan); break; case ';': scan->token = GST_ASM_TOKEN_SEMICOLON; NEXT_CHAR (scan); break; case ',': scan->token = GST_ASM_TOKEN_COMMA; NEXT_CHAR (scan); break; case '=': scan->token = GST_ASM_TOKEN_EQUAL; if (NEXT_CHAR (scan) == '=') NEXT_CHAR (scan); break; case '!': if (NEXT_CHAR (scan) == '=') { scan->token = GST_ASM_TOKEN_NOTEQUAL; NEXT_CHAR (scan); } break; case '&': scan->token = GST_ASM_TOKEN_AND; if (NEXT_CHAR (scan) == '&') NEXT_CHAR (scan); break; case '|': scan->token = GST_ASM_TOKEN_OR; if (NEXT_CHAR (scan) == '|') NEXT_CHAR (scan); break; case '<': scan->token = GST_ASM_TOKEN_LESS; if (NEXT_CHAR (scan) == '=') { scan->token = GST_ASM_TOKEN_LESSEQUAL; NEXT_CHAR (scan); } break; case '>': scan->token = GST_ASM_TOKEN_GREATER; if (NEXT_CHAR (scan) == '=') { scan->token = GST_ASM_TOKEN_GREATEREQUAL; NEXT_CHAR (scan); } break; case '$': scan->token = GST_ASM_TOKEN_DOLLAR; NEXT_CHAR (scan); break; case '(': scan->token = GST_ASM_TOKEN_LPAREN; NEXT_CHAR (scan); break; case ')': scan->token = GST_ASM_TOKEN_RPAREN; NEXT_CHAR (scan); break; case '"': NEXT_CHAR (scan); gst_asm_scan_string (scan, '"'); break; case '\'': NEXT_CHAR (scan); gst_asm_scan_string (scan, '\''); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': gst_asm_scan_number (scan); break; case '\0': scan->token = GST_ASM_TOKEN_EOF; break; default: gst_asm_scan_identifier (scan); break; } gst_asm_scan_print_token (scan); return scan->token; } static GstASMRule * gst_asm_rule_new (void) { GstASMRule *rule; rule = g_new (GstASMRule, 1); rule->root = NULL; rule->props = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); return rule; } static void gst_asm_rule_free (GstASMRule * rule) { g_hash_table_destroy (rule->props); if (rule->root) gst_asm_node_free (rule->root); g_free (rule); } static void gst_asm_rule_add_property (GstASMRule * rule, gchar * key, gchar * val) { g_hash_table_insert (rule->props, key, val); } static GstASMNode *gst_asm_scan_parse_condition (GstASMScan * scan); static GstASMNode * gst_asm_scan_parse_operand (GstASMScan * scan) { GstASMNode *node; switch (scan->token) { case GST_ASM_TOKEN_DOLLAR: gst_asm_scan_next_token (scan); if (scan->token != GST_ASM_TOKEN_IDENTIFIER) g_warning ("identifier expected"); node = gst_asm_node_new (); node->type = GST_ASM_NODE_VARIABLE; node->data.varname = g_strdup (scan->val); break; case GST_ASM_TOKEN_INT: node = gst_asm_node_new (); node->type = GST_ASM_NODE_INTEGER; node->data.intval = (gfloat) atof (scan->val); break; case GST_ASM_TOKEN_FLOAT: node = gst_asm_node_new (); node->type = GST_ASM_NODE_FLOAT; node->data.floatval = atoi (scan->val); break; case GST_ASM_TOKEN_LPAREN: gst_asm_scan_next_token (scan); node = gst_asm_scan_parse_condition (scan); if (scan->token != GST_ASM_TOKEN_RPAREN) g_warning (") expected"); break; default: g_warning ("$ or ) expected"); node = NULL; break; } gst_asm_scan_next_token (scan); return node; } static GstASMNode * gst_asm_scan_parse_expression (GstASMScan * scan) { GstASMNode *node, *left; node = gst_asm_scan_parse_operand (scan); while (IS_COND_TOKEN (scan->token)) { left = node; node = gst_asm_node_new (); node->type = GST_ASM_NODE_OPERATOR; node->data.optype = (GstASMOp) scan->token; gst_asm_scan_next_token (scan); node->right = gst_asm_scan_parse_operand (scan); node->left = left; } return node; } static GstASMNode * gst_asm_scan_parse_condition (GstASMScan * scan) { GstASMNode *node, *left; node = gst_asm_scan_parse_expression (scan); while (IS_OP_TOKEN (scan->token)) { left = node; node = gst_asm_node_new (); node->type = GST_ASM_NODE_OPERATOR; node->data.optype = (GstASMOp) scan->token; gst_asm_scan_next_token (scan); node->right = gst_asm_scan_parse_expression (scan); node->left = left; } return node; } static void gst_asm_scan_parse_property (GstASMRule * rule, GstASMScan * scan) { gchar *key, *val; if (scan->token != GST_ASM_TOKEN_IDENTIFIER) { g_warning ("identifier expected"); return; } key = g_strdup (scan->val); gst_asm_scan_next_token (scan); if (scan->token != GST_ASM_TOKEN_EQUAL) { g_warning ("= expected"); g_free (key); return; } gst_asm_scan_next_token (scan); val = g_strdup (scan->val); gst_asm_rule_add_property (rule, key, val); gst_asm_scan_next_token (scan); } static GstASMRule * gst_asm_scan_parse_rule (GstASMScan * scan) { GstASMRule *rule; rule = gst_asm_rule_new (); if (scan->token == GST_ASM_TOKEN_HASH) { gst_asm_scan_next_token (scan); rule->root = gst_asm_scan_parse_condition (scan); if (scan->token == GST_ASM_TOKEN_COMMA) gst_asm_scan_next_token (scan); } if (scan->token != GST_ASM_TOKEN_SEMICOLON) { gst_asm_scan_parse_property (rule, scan); while (scan->token == GST_ASM_TOKEN_COMMA) { gst_asm_scan_next_token (scan); gst_asm_scan_parse_property (rule, scan); } gst_asm_scan_next_token (scan); } return rule; } static gboolean gst_asm_rule_evaluate (GstASMRule * rule, GHashTable * vars) { gboolean res; if (rule->root) { res = (gboolean) gst_asm_node_evaluate (rule->root, vars); } else res = TRUE; return res; } GstASMRuleBook * gst_asm_rule_book_new (const gchar * rulebook) { GstASMRuleBook *book; GstASMRule *rule = NULL; GstASMScan *scan; GstASMToken token; book = g_new0 (GstASMRuleBook, 1); book->rulebook = rulebook; scan = gst_asm_scan_new (book->rulebook); gst_asm_scan_next_token (scan); do { rule = gst_asm_scan_parse_rule (scan); if (rule) { book->rules = g_list_append (book->rules, rule); book->n_rules++; } token = scan->token; } while (token != GST_ASM_TOKEN_EOF); gst_asm_scan_free (scan); return book; } void gst_asm_rule_book_free (GstASMRuleBook * book) { GList *walk; for (walk = book->rules; walk; walk = g_list_next (walk)) { GstASMRule *rule = (GstASMRule *) walk->data; gst_asm_rule_free (rule); } g_list_free (book->rules); g_free (book); } gint gst_asm_rule_book_match (GstASMRuleBook * book, GHashTable * vars, gint * rulematches) { GList *walk; gint i, n = 0; for (walk = book->rules, i = 0; walk; walk = g_list_next (walk), i++) { GstASMRule *rule = (GstASMRule *) walk->data; if (gst_asm_rule_evaluate (rule, vars)) { rulematches[n++] = i; } } return n; } #ifdef TEST gint main (gint argc, gchar * argv[]) { GstASMRuleBook *book; gint rulematch[MAX_RULEMATCHES]; GHashTable *vars; gint i, n; static const gchar rules1[] = "#($Bandwidth < 67959),TimestampDelivery=T,DropByN=T," "priority=9;#($Bandwidth >= 67959) && ($Bandwidth < 167959)," "AverageBandwidth=67959,Priority=9;#($Bandwidth >= 67959) && ($Bandwidth" " < 167959),AverageBandwidth=0,Priority=5,OnDepend=\\\"1\\\";#($Bandwidth >= 167959)" " && ($Bandwidth < 267959),AverageBandwidth=167959,Priority=9;#($Bandwidth >= 167959)" " && ($Bandwidth < 267959),AverageBandwidth=0,Priority=5,OnDepend=\\\"3\\\";" "#($Bandwidth >= 267959),AverageBandwidth=267959,Priority=9;#($Bandwidth >= 267959)" ",AverageBandwidth=0,Priority=5,OnDepend=\\\"5\\\";"; static const gchar rules2[] = "AverageBandwidth=32041,Priority=5;AverageBandwidth=0," "Priority=5,OnDepend=\\\"0\\\", OffDepend=\\\"0\\\";"; static const gchar rules3[] = "#(($Bandwidth >= 27500) && ($OldPNMPlayer)),AverageBandwidth=27500,priority=9,PNMKeyframeRule=T;#(($Bandwidth >= 27500) && ($OldPNMPlayer)),AverageBandwidth=0,priority=5,PNMNonKeyframeRule=T;#(($Bandwidth < 27500) && ($OldPNMPlayer)),TimestampDelivery=T,DropByN=T,priority=9,PNMThinningRule=T;#($Bandwidth < 13899),TimestampDelivery=T,DropByN=T,priority=9;#($Bandwidth >= 13899) && ($Bandwidth < 19000),AverageBandwidth=13899,Priority=9;#($Bandwidth >= 13899) && ($Bandwidth < 19000),AverageBandwidth=0,Priority=5,OnDepend=\\\"4\\\";#($Bandwidth >= 19000) && ($Bandwidth < 27500),AverageBandwidth=19000,Priority=9;#($Bandwidth >= 19000) && ($Bandwidth < 27500),AverageBandwidth=0,Priority=5,OnDepend=\\\"6\\\";#($Bandwidth >= 27500) && ($Bandwidth < 132958),AverageBandwidth=27500,Priority=9;#($Bandwidth >= 27500) && ($Bandwidth < 132958),AverageBandwidth=0,Priority=5,OnDepend=\\\"8\\\";#($Bandwidth >= 132958) && ($Bandwidth < 187958),AverageBandwidth=132958,Priority=9;#($Bandwidth >= 132958) && ($Bandwidth < 187958),AverageBandwidth=0,Priority=5,OnDepend=\\\"10\\\";#($Bandwidth >= 187958),AverageBandwidth=187958,Priority=9;#($Bandwidth >= 187958),AverageBandwidth=0,Priority=5,OnDepend=\\\"12\\\";"; vars = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (vars, (gchar *) "Bandwidth", (gchar *) "300000"); book = gst_asm_rule_book_new (rules1); n = gst_asm_rule_book_match (book, vars, rulematch); gst_asm_rule_book_free (book); g_print ("%d rules matched\n", n); for (i = 0; i < n; i++) { g_print ("rule %d matched\n", rulematch[i]); } book = gst_asm_rule_book_new (rules2); n = gst_asm_rule_book_match (book, vars, rulematch); gst_asm_rule_book_free (book); g_print ("%d rules matched\n", n); for (i = 0; i < n; i++) { g_print ("rule %d matched\n", rulematch[i]); } book = gst_asm_rule_book_new (rules3); n = gst_asm_rule_book_match (book, vars, rulematch); gst_asm_rule_book_free (book); g_print ("%d rules matched\n", n); for (i = 0; i < n; i++) { g_print ("rule %d matched\n", rulematch[i]); } g_hash_table_destroy (vars); return 0; } #endif