changeset 3409:86dafe2300f7 trunk

Added Tuplez compiler (not used yet, though) and some related changes in tuple code.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 30 Aug 2007 23:41:33 +0300
parents aea3349e2c62
children e251841884fc
files src/audacious/Makefile src/audacious/tuple.c src/audacious/tuple.h src/audacious/tuple_compiler.c src/audacious/tuple_compiler.h src/audacious/tuple_formatter.c
diffstat 6 files changed, 888 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/src/audacious/Makefile	Thu Aug 30 17:41:40 2007 +0200
+++ b/src/audacious/Makefile	Thu Aug 30 23:41:33 2007 +0300
@@ -56,6 +56,7 @@
 	strings.h \
 	tuple.h \
 	tuple_formatter.h \
+	tuple_compiler.h \
 	ui_fileinfopopup.h \
 	ui_lastfm.h\
 	ui_plugin_menu.h \
@@ -103,6 +104,7 @@
 	strings.c \
 	tuple.c \
 	tuple_formatter.c \
+	tuple_compiler.c \
 	skin.c \
 	ui_about.c \
 	ui_albumart.c \
--- a/src/audacious/tuple.c	Thu Aug 30 17:41:40 2007 +0200
+++ b/src/audacious/tuple.c	Thu Aug 30 23:41:33 2007 +0300
@@ -23,19 +23,6 @@
 
 #include "tuple.h"
 
-struct _Tuple {
-    mowgli_object_t parent;
-    mowgli_dictionary_t *dict;
-};
-
-typedef struct {
-    TupleValueType type;
-    union {
-        gchar *string;
-        gint integer;
-    } value;
-} TupleValue;
-
 static mowgli_heap_t *tuple_heap = NULL;
 static mowgli_heap_t *tuple_value_heap = NULL;
 static mowgli_object_class_t tuple_klass;
@@ -124,8 +111,8 @@
     g_return_val_if_fail(tuple != NULL, FALSE);
     g_return_val_if_fail(field != NULL, FALSE);
 
-    if (mowgli_dictionary_find(tuple->dict, field))
-        tuple_disassociate(tuple, field);
+    if ((value = mowgli_dictionary_delete(tuple->dict, field)))
+        tuple_disassociate_now(value);
 
     if (string == NULL)
         return TRUE;
@@ -147,8 +134,8 @@
     g_return_val_if_fail(tuple != NULL, FALSE);
     g_return_val_if_fail(field != NULL, FALSE);
 
-    if (mowgli_dictionary_find(tuple->dict, field))
-        tuple_disassociate(tuple, field);
+    if ((value = mowgli_dictionary_delete(tuple->dict, field)))
+        tuple_disassociate_now(value);
 
     value = mowgli_heap_alloc(tuple_value_heap);
     value->type = TUPLE_INT;
@@ -160,6 +147,15 @@
 }
 
 void
+tuple_disassociate_now(TupleValue *value)
+{
+    if (value->type == TUPLE_STRING)
+        g_free(value->value.string);
+
+    mowgli_heap_free(tuple_value_heap, value);
+}
+
+void
 tuple_disassociate(Tuple *tuple, const gchar *field)
 {
     TupleValue *value;
@@ -170,11 +166,8 @@
     /* why _delete()? because _delete() returns the dictnode's data on success */
     if ((value = mowgli_dictionary_delete(tuple->dict, field)) == NULL)
         return;
-
-    if (value->type == TUPLE_STRING)
-        g_free(value->value.string);
-
-    mowgli_heap_free(tuple_value_heap, value);
+    
+    tuple_disassociate_now(value);
 }
 
 TupleValueType
--- a/src/audacious/tuple.h	Thu Aug 30 17:41:40 2007 +0200
+++ b/src/audacious/tuple.h	Thu Aug 30 23:41:33 2007 +0300
@@ -24,8 +24,11 @@
 #include <glib.h>
 #include <mowgli.h>
 
-struct _Tuple;
-typedef struct _Tuple Tuple;
+typedef struct _Tuple {
+    mowgli_object_t parent;
+    mowgli_dictionary_t *dict;
+} Tuple;
+
 
 typedef enum {
     TUPLE_STRING,
@@ -33,11 +36,20 @@
     TUPLE_UNKNOWN
 } TupleValueType;
 
+typedef struct {
+    TupleValueType type;
+    union {
+        gchar *string;
+        gint integer;
+    } value;
+} TupleValue;
+
 Tuple *tuple_new(void);
 Tuple *tuple_new_from_filename(const gchar *filename);
 gboolean tuple_associate_string(Tuple *tuple, const gchar *field, const gchar *string);
 gboolean tuple_associate_int(Tuple *tuple, const gchar *field, gint integer);
 void tuple_disassociate(Tuple *tuple, const gchar *field);
+void tuple_disassociate_now(TupleValue *value);
 TupleValueType tuple_get_value_type(Tuple *tuple, const gchar *field);
 const gchar *tuple_get_string(Tuple *tuple, const gchar *field);
 int tuple_get_int(Tuple *tuple, const gchar *field);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/audacious/tuple_compiler.c	Thu Aug 30 23:41:33 2007 +0300
@@ -0,0 +1,748 @@
+/*
+ * Audacious - Tuplez compiler
+ * Copyright (c) 2007 Matti 'ccr' Hämäläinen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 3 of the License.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ *
+ * The Audacious team does not consider modular code linking to
+ * Audacious or using our public API to be a derived work.
+ */
+
+/*
+ * What's this?
+ * ------------
+ * Nothing really. A prototype / pseudo-C for an improved Tuple formatting
+ * system, where the format string is "compiled" into a tree structure,
+ * which can then be traversed fast while "evaluating". This file does
+ * not represent anything but some of my (ccr) ideas for the concept.
+ *
+ * The basic ideas are: 
+ * 1) compiled structure for faster traversing
+ * 2) sub-expression removal / constant elimination
+ * 3) indexes and/or hashes for tuple entries for faster access
+ * 4) avoid expensive memory re-allocation
+ *
+ * and possibly 5) caching of certain things
+ *
+ *
+ * TODO:
+ * - implement definitions (${=foo,"baz"} ${=foo,1234})
+ * - implement functions
+ * - implement handling of external expressions
+ * - error handling issues?
+ * - evaluation context: how local variables should REALLY work?
+ */
+
+#include "config.h"
+#include <assert.h>
+#include <stdarg.h>
+#include "tuple_compiler.h"
+
+
+void tuple_error(const char *fmt, ...)
+{
+  va_list ap;
+  fprintf(stderr, "compiler: ");
+  va_start(ap, fmt);
+  vfprintf(stderr, fmt, ap);
+  va_end(ap);
+  exit(5);
+}
+
+
+static void tuple_evalctx_free_var(TupleEvalVar *var)
+{
+  assert(var != NULL);
+  
+  g_free(var->defval);
+  g_free(var->name);
+  g_free(var);
+}
+
+
+static void tuple_evalctx_free_function(TupleEvalFunc *func)
+{
+  assert(func != NULL);
+  
+  g_free(func->name);
+  g_free(func);
+}
+
+
+/* Initialize an evaluation context
+ */
+TupleEvalContext * tuple_evalctx_new(void)
+{
+  return g_new0(TupleEvalContext, 1);
+}
+
+
+/* "Reset" the evaluation context, clean up locally set variables,
+ * but leave globals.
+ */
+void tuple_evalctx_reset(TupleEvalContext *ctx)
+{
+  gint i;
+  
+  /* Free local variables */
+  for (i = 0; i < ctx->nvariables; i++)
+    if (ctx->variables[i]) {
+      ctx->variables[i]->dictref = NULL;
+
+      if (ctx->variables[i]->islocal)
+        tuple_evalctx_free_var(ctx->variables[i]);
+    }
+  
+}
+
+
+/* Free an evaluation context and associated data
+ */
+void tuple_evalctx_free(TupleEvalContext *ctx)
+{
+  gint i;
+  
+  if (!ctx) return;
+  
+  /* Deallocate variables */
+  for (i = 0; i < ctx->nvariables; i++)
+    if (ctx->variables[i])
+      tuple_evalctx_free_var(ctx->variables[i]);
+  
+  g_free(ctx->variables);
+  
+  /* Deallocate functions */
+  for (i = 0; i < ctx->nfunctions; i++)
+    if (ctx->functions[i])
+      tuple_evalctx_free_function(ctx->functions[i]);
+  
+  g_free(ctx->functions);
+  
+  /* Zero context */
+  memset(ctx, 0, sizeof(TupleEvalContext));
+}
+
+
+gint tuple_evalctx_add_var(TupleEvalContext *ctx, gchar *name, gboolean islocal, gint type)
+{
+  gint i;
+  TupleEvalVar * tmp = g_new0(TupleEvalVar, 1);
+
+  tmp->name = g_strdup(name);
+  tmp->islocal = islocal;
+  tmp->type = type;
+  
+  /* Find a free slot */
+  for (i = 0; i < ctx->nvariables; i++)
+  if (!ctx->variables[i]) {
+    ctx->variables[i] = tmp;
+    return i;
+  }
+  
+  i = ctx->nvariables;
+  ctx->variables = g_renew(TupleEvalVar *, ctx->variables, ctx->nvariables + MIN_ALLOC_NODES);
+  memset(&(ctx->variables[ctx->nvariables]), 0, MIN_ALLOC_NODES * sizeof(TupleEvalVar *));
+  ctx->nvariables += MIN_ALLOC_NODES;
+  ctx->variables[i] = tmp;
+
+  return i;
+}
+
+
+gint tuple_evalctx_add_function(TupleEvalContext *ctx, gchar *name)
+{
+  assert(ctx != NULL);
+  assert(name != NULL);
+  
+  return -1;
+}
+
+
+static void tuple_evalnode_insert(TupleEvalNode **nodes, TupleEvalNode *node)
+{
+  assert(nodes != NULL);
+  assert(node != NULL);
+
+  if (*nodes) {
+    node->prev = (*nodes)->prev;
+    (*nodes)->prev->next = node;
+    (*nodes)->prev = node;
+    node->next = NULL;
+  } else {
+    *nodes = node;
+    node->prev = node;
+    node->next = NULL;
+  }
+}
+
+
+static TupleEvalNode *tuple_evalnode_new(void)
+{
+  return g_new0(TupleEvalNode, 1);
+}
+
+
+void tuple_evalnode_free(TupleEvalNode *expr)
+{
+  TupleEvalNode *curr = expr, *next;
+  
+  while (curr) {
+    next = curr->next;
+
+    g_free(curr->text);
+    
+    if (curr->children)
+      tuple_evalnode_free(curr->children);
+
+    g_free(curr);
+    
+    curr = next;
+  }
+}
+
+
+static TupleEvalNode *tuple_compiler_pass1(gint *level, TupleEvalContext *ctx, gchar **expression);
+
+
+static gboolean tc_get_item(gchar **str, gchar *buf, size_t max, gchar endch, gboolean *literal, gchar *errstr, gchar *item)
+{
+  size_t i = 0;
+  gchar *s = *str, tmpendch;
+  assert(str != NULL);
+  assert(buf != NULL);
+  
+  if (*s == '"') {
+    if (*literal == FALSE) {
+      tuple_error("Literal string value not allowed in '%s'.\n", item);
+      return FALSE;
+    }
+    s++;
+    *literal = TRUE;
+    tmpendch = '"';
+  } else {
+    *literal = FALSE;
+    tmpendch = endch;
+  }
+  
+  if (*literal == FALSE) {
+    while (*s != '\0' && *s != tmpendch && (isalnum(*s) || *s == '-') && i < (max - 1)) {
+      buf[i++] = *(s++);
+    }
+    
+    if (*s != tmpendch && *s != '}' && !isalnum(*s) && *s != '-') {
+      tuple_error("Invalid field '%s' in '%s'.\n", *str, item);
+      return FALSE;
+    } else if (*s != tmpendch) {
+      tuple_error("Expected '%c' in '%s'.\n", tmpendch, item);
+      return FALSE;
+    }
+  } else {
+    while (*s != '\0' && *s != tmpendch && i < (max - 1)) {
+      if (*s == '\\') s++;
+      buf[i++] = *(s++);
+    }
+  }
+  buf[i] = '\0';
+  
+  if (*literal) {
+    if (*s == tmpendch)
+      s++;
+    else {
+      tuple_error("Expected literal string end ('%c') in '%s'.\n", tmpendch, item);
+      return FALSE;
+    }
+  }
+
+  if (*s != endch) {
+    tuple_error("Expected '%c' after %s in '%s'\n", endch, errstr, item);
+    return FALSE;
+  } else {
+    *str = s;
+    return TRUE;
+  }
+}
+
+
+static gint tc_get_variable(TupleEvalContext *ctx, gchar *name, gint type)
+{
+  gint i;
+  
+  if (*name == '\0') return -1;
+
+  for (i = 0; i < ctx->nvariables; i++)
+    if (ctx->variables[i] && !strcmp(ctx->variables[i]->name, name))
+      return i;
+  
+  return tuple_evalctx_add_var(ctx, name, FALSE, type);
+}
+
+
+static gboolean tc_parse_construct1(TupleEvalContext *ctx, TupleEvalNode **res, gchar *item, gchar **c, gint *level, gint opcode)
+{
+  gchar tmps1[MAX_STR], tmps2[MAX_STR];
+  gboolean literal1 = TRUE, literal2 = TRUE;
+
+  (*c)++;
+  if (tc_get_item(c, tmps1, MAX_STR, ',', &literal1, "tag1", item)) {
+    (*c)++;
+    if (tc_get_item(c, tmps2, MAX_STR, ':', &literal2, "tag2", item)) {
+      TupleEvalNode *tmp = tuple_evalnode_new();
+      (*c)++;
+
+      tmp->opcode = opcode;
+      if ((tmp->var[0] = tc_get_variable(ctx, tmps1, literal1 ? VAR_CONST : VAR_FIELD)) < 0) {
+        tuple_evalnode_free(tmp);
+        tuple_error("Invalid variable '%s' in '%s'.\n", tmps1, item);
+        return FALSE;
+      }
+      if ((tmp->var[1] = tc_get_variable(ctx, tmps2, literal2 ? VAR_CONST : VAR_FIELD)) < 0) {
+        tuple_evalnode_free(tmp);
+        tuple_error("Invalid variable '%s' in '%s'.\n", tmps2, item);
+        return FALSE;
+      }
+      tmp->children = tuple_compiler_pass1(level, ctx, c);
+      tuple_evalnode_insert(res, tmp);
+    } else
+      return FALSE;
+  } else
+    return FALSE;
+  
+  return TRUE;
+}
+
+
+/* Compile format expression into TupleEvalNode tree.
+ * A "simple" straight compilation is sufficient in first pass, later
+ * passes can perform subexpression removal and other optimizations.
+ */
+static TupleEvalNode *tuple_compiler_pass1(gint *level, TupleEvalContext *ctx, gchar **expression)
+{
+  TupleEvalNode *res = NULL, *tmp = NULL;
+  gchar *c = *expression, *item, tmps1[MAX_STR];
+  gboolean literal, end = FALSE;
+  assert(ctx != NULL);
+  assert(expression != NULL);
+  
+  (*level)++;
+  
+  while (*c != '\0' && !end) {
+    tmp = NULL;
+    if (*c == '}') {
+      c++;
+      (*level)--;
+      end = TRUE;
+    } else if (*c == '$') {
+      /* Expression? */
+      item = c++;
+      if (*c == '{') {
+        gint opcode;
+        gchar *expr = ++c;
+        
+        switch (*c) {
+          case '?': c++;
+            /* Exists? */
+            literal = FALSE;
+            if (tc_get_item(&c, tmps1, MAX_STR, ':', &literal, "tag", item)) {
+              c++;
+              tmp = tuple_evalnode_new();
+              tmp->opcode = OP_EXISTS;
+              if ((tmp->var[0] = tc_get_variable(ctx, tmps1, VAR_FIELD)) < 0) {
+                tuple_error("Invalid variable '%s' in '%s'.\n", tmps1, expr);
+                goto ret_error;
+              }
+              tmp->children = tuple_compiler_pass1(level, ctx, &c);
+              tuple_evalnode_insert(&res, tmp);
+            } else
+              goto ret_error;
+            break;
+          
+          case '=': c++;
+            if (*c != '=') {
+              /* Definition */
+              literal = FALSE;
+              if (tc_get_item(&c, tmps1, MAX_STR, ',', &literal, "variable", item)) {
+                c++;
+                if (*c == '"') {
+                  /* String */
+                  c++;
+                } else if (isdigit(*c)) {
+                  /* Integer */
+                }
+                
+                tuple_error("definitions not yet supported!\n");
+                goto ret_error;
+              } else
+                goto ret_error;
+            } else {
+              /* Equals? */
+              if (!tc_parse_construct1(ctx, &res, item, &c, level, OP_EQUALS))
+                goto ret_error;
+            }
+            break;
+          
+          case '!': c++;
+            if (*c != '=') goto ext_expression;
+            if (!tc_parse_construct1(ctx, &res, item, &c, level, OP_NOT_EQUALS))
+              goto ret_error;
+            break;
+          
+          case '<': c++;
+            if (*c == '=') {
+              opcode = OP_LTEQ;
+              c++;
+            } else
+              opcode = OP_LT;
+            
+            if (!tc_parse_construct1(ctx, &res, item, &c, level, opcode))
+              goto ret_error;
+            break;
+          
+          case '>': c++;
+            if (*c == '=') {
+              opcode = OP_GTEQ;
+              c++;
+            } else
+              opcode = OP_GT;
+            
+            if (!tc_parse_construct1(ctx, &res, item, &c, level, opcode))
+              goto ret_error;
+            break;
+          
+          case '(': c++;
+            if (!strncmp(c, "empty)?", 7)) {
+              c += 7;
+            } else
+              goto ext_expression;
+            break;
+            
+          default:
+          ext_expression:
+            /* Get expression content */
+            c = expr;
+            literal = FALSE;
+            if (tc_get_item(&c, tmps1, MAX_STR, '}', &literal, "field", item)) {
+              /* FIXME!! FIX ME! Check for external expressions */
+              
+              /* I HAS A FIELD - A field. You has it. */
+              tmp = tuple_evalnode_new();
+              tmp->opcode = OP_FIELD;
+              if ((tmp->var[0] = tc_get_variable(ctx, tmps1, VAR_FIELD)) < 0) {
+                tuple_error("Invalid variable '%s' in '%s'.\n", tmps1, expr);
+                goto ret_error;
+              }
+              tuple_evalnode_insert(&res, tmp);
+              c++;
+              
+            } else
+              goto ret_error;
+        }        
+      } else {
+        tuple_error("Expected '{', got '%c' in '%s'.\n", *c, c);
+        goto ret_error;
+      }
+       
+    } else if (*c == '%') {
+      /* Function? */
+      item = c++;
+      if (*c == '{') {
+        size_t i = 0;
+        c++;
+        
+        while (*c != '\0' && (isalnum(*c) || *c == '-') && *c != '}' && *c != ':' && i < (MAX_STR - 1))
+          tmps1[i++] = *(c++);
+        tmps1[i] = '\0';
+        
+        if (*c == ':') {
+          c++;
+        } else if (*c == '}') {
+          c++;
+        } else if (*c == '\0') {
+          tuple_error("Expected '}' or function arguments in '%s'\n", item);
+          goto ret_error;
+        }
+      } else {
+        tuple_error("Expected '{', got '%c' in '%s'.\n", *c, c);
+        goto ret_error;
+      }
+    } else {
+      /* Parse raw/literal text */
+      size_t i = 0;
+      while (*c != '\0' && *c != '$' && *c != '%' && *c != '}' && i < (MAX_STR - 1)) {
+        if (*c == '\\') c++;
+        tmps1[i++] = *(c++);
+      }
+      tmps1[i] = '\0';
+      
+      tmp = tuple_evalnode_new();
+      tmp->opcode = OP_RAW;
+      tmp->text = g_strdup(tmps1);
+      tuple_evalnode_insert(&res, tmp);
+    }
+  }
+
+  if (*level <= 0) {
+    tuple_error("Syntax error! Uneven/unmatched nesting of elements in '%s'!\n", c);
+    goto ret_error;
+  }
+  
+  *expression = c;
+  return res;
+
+ret_error:
+  tuple_evalnode_free(tmp);
+  tuple_evalnode_free(res);
+  return NULL;
+}
+
+
+static TupleEvalNode *tuple_compiler_pass2(gboolean *changed, TupleEvalContext *ctx, TupleEvalNode *expr)
+{
+  TupleEvalNode *curr = expr, *res = NULL;
+  *changed = FALSE;
+  assert(ctx != NULL);
+  assert(expr != NULL);
+  
+  return res;
+}
+
+
+TupleEvalNode *tuple_formatter_compile(TupleEvalContext *ctx, gchar *expr)
+{
+  gint level = 0;
+  gboolean changed = FALSE;
+  gchar *tmpexpr = expr;
+  TupleEvalNode *res1, *res2;
+  
+  res1 = tuple_compiler_pass1(&level, ctx, &tmpexpr);
+
+  if (level != 1) {
+    tuple_error("Syntax error! Uneven/unmatched nesting of elements! (%d)\n", level);
+    tuple_evalnode_free(res1);
+    return NULL;
+  }
+  
+  res2 = tuple_compiler_pass2(&changed, ctx, res1);
+
+  if (changed) {
+    tuple_evalnode_free(res1);
+    return res2;
+  } else {
+    tuple_evalnode_free(res2);
+    return res1;
+  }
+}
+
+
+/* Evaluate tuple in given TupleEval expression in given
+ * context and return resulting string.
+ */
+static TupleValue * tf_get_dictref(TupleEvalVar *var, Tuple *tuple)
+{
+  if (var->type == VAR_FIELD && var->dictref == NULL)
+    var->dictref = mowgli_dictionary_retrieve(tuple->dict, var->name);
+
+  return var->dictref;
+}
+
+static TupleValueType tf_get_var(gchar **tmps, gint *tmpi, TupleEvalVar *var, Tuple *tuple)
+{
+  TupleValueType type = TUPLE_UNKNOWN;
+  
+  switch (var->type) {
+    case VAR_DEF: *tmps = var->defval; type = TUPLE_STRING; break;
+    case VAR_CONST: *tmps = var->name; type = TUPLE_STRING; break;
+    case VAR_FIELD:
+      if (tf_get_dictref(var, tuple)) {
+        if (var->dictref->type == TUPLE_STRING)
+          *tmps = var->dictref->value.string;
+        else
+          *tmpi = var->dictref->value.integer;
+        type = var->dictref->type;
+      }
+      break;
+    default:
+      tmps = NULL;
+      tmpi = 0;
+  }
+  
+  return type;
+}
+
+
+static gboolean tuple_formatter_eval_do(TupleEvalContext *ctx, TupleEvalNode *expr, Tuple *tuple, gchar **res, size_t *resmax, size_t *reslen)
+{
+  TupleEvalNode *curr = expr;
+  gchar tmps[MAX_STR], *tmps2;
+  gboolean result;
+  gint resulti;
+  TupleEvalVar *var0, *var1;
+  gint tmpi0, tmpi1;
+  gchar *tmps0, *tmps1;
+  TupleValueType type0, type1;
+  
+  if (!expr) return FALSE;
+  
+  while (curr) {
+    const gchar *str = NULL;
+
+    switch (curr->opcode) {
+      case OP_RAW:
+        str = curr->text;
+        break;
+      
+      case OP_FIELD:
+        var0 = ctx->variables[curr->var[0]];
+        
+        switch (var0->type) {
+          case VAR_DEF:
+            str = var0->defval;
+            break;
+          
+          case VAR_FIELD:
+            if (tf_get_dictref(var0, tuple)) {
+              switch (var0->dictref->type) {
+                case TUPLE_STRING:
+                  str = var0->dictref->value.string;
+                  break;
+          
+                case TUPLE_INT:
+                  snprintf(tmps, sizeof(tmps), "%d", var0->dictref->value.integer);
+                  str = tmps;
+                  break;
+                
+                default:
+                  str = NULL;
+              }
+            }
+            break;
+        }
+        break;
+      
+      case OP_EXISTS:
+        if (mowgli_dictionary_retrieve(tuple->dict, ctx->variables[curr->var[0]]->name)) {
+          if (!tuple_formatter_eval_do(ctx, curr->children, tuple, res, resmax, reslen))
+            return FALSE;
+        }
+        break;
+      
+      case OP_EQUALS:
+      case OP_NOT_EQUALS:
+      case OP_LT: case OP_LTEQ:
+      case OP_GT: case OP_GTEQ:
+        var0 = ctx->variables[curr->var[0]];
+        var1 = ctx->variables[curr->var[1]];
+        
+        type0 = tf_get_var(&tmps0, &tmpi0, var0, tuple);
+        type1 = tf_get_var(&tmps1, &tmpi1, var1, tuple);
+        
+        if (type0 == type1) {
+          if (type0 == TUPLE_STRING)
+            resulti = strcmp(tmps0, tmps1);
+          else
+            resulti = tmpi0 - tmpi1;
+
+          switch (curr->opcode) {
+            case OP_EQUALS:     result = (resulti == 0); break;
+            case OP_NOT_EQUALS: result = (resulti != 0); break;
+            case OP_LT:         result = (resulti <  0); break;
+            case OP_LTEQ:       result = (resulti <= 0); break;
+            case OP_GT:         result = (resulti >  0); break;
+            case OP_GTEQ:       result = (resulti >= 0); break;
+            default: result = FALSE;
+          }
+            
+          if (result && !tuple_formatter_eval_do(ctx, curr->children, tuple, res, resmax, reslen))
+            return FALSE;
+        }
+        break;
+      
+      case OP_IS_EMPTY:
+        var0 = ctx->variables[curr->var[0]];
+
+        if (var0->dictref == NULL)
+          var0->dictref = mowgli_dictionary_retrieve(tuple->dict, var0->name);
+
+        switch (var0->dictref->type) {
+          case TUPLE_INT:
+            result = (var0->dictref->value.integer == 0);
+            break;
+        
+          case TUPLE_STRING:
+            result = TRUE;
+            tmps2 = var0->dictref->value.string;
+          
+            while (result && *tmps2 != '\0') {
+              if (*tmps2 == ' ')
+                tmps2++;
+              else
+                result = FALSE;
+            }
+            break;
+
+          default:
+            result = TRUE;
+        }
+        
+        if (result) {
+          if (!tuple_formatter_eval_do(ctx, curr->children, tuple, res, resmax, reslen))
+            return FALSE;
+        }
+        break;
+      
+      default:
+        tuple_error("Unimplemented opcode %d!\n", curr->opcode);
+        return FALSE;
+        break;
+    }
+    
+    if (str) {
+      /* (Re)allocate res for more space, if needed. */
+      *reslen += strlen(str);
+      if (*res) {
+        if (*reslen >= *resmax) {
+          *resmax += MIN_ALLOC_BUF;
+          *res = g_realloc(*res, *resmax);
+        }
+        
+        strcat(*res, str);
+      } else {
+        *resmax = *reslen + MIN_ALLOC_BUF;
+        *res = g_malloc(*resmax);
+        
+        strcpy(*res, str);
+      }
+    }
+    
+    curr = curr->next;
+  }
+  
+  return TRUE;
+}
+
+
+gchar *tuple_formatter_eval(TupleEvalContext *ctx, TupleEvalNode *expr, Tuple *tuple)
+{
+  gchar *res = g_strdup("");
+  size_t resmax = 0, reslen = 0;
+  assert(ctx != NULL);
+  assert(tuple != NULL);
+  
+  if (!expr) return NULL;
+  
+  if (!tuple_formatter_eval_do(ctx, expr, tuple, &res, &resmax, &reslen)) {
+    g_free(res);
+    return NULL;
+  }
+  
+  return res;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/audacious/tuple_compiler.h	Thu Aug 30 23:41:33 2007 +0300
@@ -0,0 +1,108 @@
+/*
+ * Audacious - Tuplez compiler
+ * Copyright (c) 2007 Matti 'ccr' Hämäläinen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 3 of the License.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses>.
+ *
+ * The Audacious team does not consider modular code linking to
+ * Audacious or using our public API to be a derived work.
+ */
+#ifndef __AUDACIOUS_TUPLE_COMPILER_H__
+#define __AUDACIOUS_TUPLE_COMPILER_H__
+
+#include <glib.h>
+#include <mowgli.h>
+#include "tuple.h"
+
+
+#define MAX_VAR		(4)
+#define MAX_STR		(256)
+#define MIN_ALLOC_NODES (8)
+#define MIN_ALLOC_BUF	(64)
+
+
+enum {
+    OP_RAW = 0,		/* plain text */
+    OP_FIELD,		/* a field/variable */
+    OP_EXISTS,
+    OP_DEF_STRING,
+    OP_DEF_INT,
+    OP_EQUALS,
+    OP_NOT_EQUALS,
+    OP_GT,
+    OP_GTEQ,
+    OP_LT,
+    OP_LTEQ,
+    OP_IS_EMPTY,
+
+    OP_FUNCTION,	/* function */
+    OP_EXPRESSION	/* additional registered expressions */
+};
+
+
+enum {
+    VAR_FIELD = 0,
+    VAR_CONST,
+    VAR_DEF
+};
+    
+
+/* Caching structure for deterministic functions
+ */
+typedef struct {
+    gchar *name;
+    gboolean isdeterministic;
+    gchar *(*func)(Tuple *tuple, gchar **argument);
+} TupleEvalFunc;
+
+
+typedef struct {
+    gchar *name;
+    gboolean islocal;		/* Local? true = will be cleaned with tuple_evalctx_reset() */
+    gint type;			/* Type of this "variable", see VAR_* */
+    gchar *defval;
+    TupleValue *dictref;	/* Cached tuple value ref */
+} TupleEvalVar;
+
+
+typedef struct _TupleEvalNode {
+    gint opcode;		/* operator, see OP_ enums */
+    gint var[MAX_VAR];		/* tuple / global variable references (perhaps hashes, or just indexes to a list?) */
+    gboolean global[MAX_VAR];
+    gchar *text;		/* raw text, if any (OP_RAW) */
+    gint function, expression;	/* for OP_FUNCTION and OP_EXPRESSION */
+    struct _TupleEvalNode *children, *next, *prev; /* children of this struct, and pointer to next node. */
+} TupleEvalNode;
+
+
+typedef struct {
+    gint nvariables, nfunctions, nexpressions;
+    TupleEvalVar **variables;
+    TupleEvalFunc **functions;
+} TupleEvalContext;
+
+
+TupleEvalContext * tuple_evalctx_new(void);
+void tuple_evalctx_reset(TupleEvalContext *ctx);
+void tuple_evalctx_free(TupleEvalContext *ctx);
+gint tuple_evalctx_add_var(TupleEvalContext *ctx, gchar *name, gboolean islocal, gint type);
+
+void tuple_evalnode_free(TupleEvalNode *expr);
+
+TupleEvalNode *tuple_formatter_compile(TupleEvalContext *ctx, gchar *expr);
+gchar *tuple_formatter_eval(TupleEvalContext *ctx, TupleEvalNode *expr, Tuple *tuple);
+/*
+gchar *tuple_formatter_make_title_string(Tuple *tuple, const gchar *string);
+*/
+
+#endif
--- a/src/audacious/tuple_formatter.c	Thu Aug 30 17:41:40 2007 +0200
+++ b/src/audacious/tuple_formatter.c	Thu Aug 30 23:41:33 2007 +0300
@@ -25,7 +25,7 @@
 #include "tuple.h"
 #include "tuple_formatter.h"
 
-#define _DEBUG
+//#define _DEBUG
 
 #ifdef _DEBUG
 # define _TRACE(fmt, ...) g_print("[tuple-fmt] %s(%d) " fmt "\n", __FILE__, __LINE__, __VA_ARGS__);