blob: af2f8a0a3fdef4b8e8980bd422712e875532277e [file] [log] [blame]
/* GStreamer
* Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
*
* 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 <string.h>
#include <stdlib.h>
#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 ("$ <number> 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