Audacious $Id:Doxyfile42802007-03-2104:39:00Znenolod$
tuple_compiler.c
Go to the documentation of this file.
00001 /*
00002  * Audacious - Tuplez compiler
00003  * Copyright (c) 2007 Matti 'ccr' Hämäläinen
00004  * Copyright (c) 2011 John Lindgren
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; under version 3 of the License.
00009  *
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program.  If not, see <http://www.gnu.org/licenses>.
00017  *
00018  * The Audacious team does not consider modular code linking to
00019  * Audacious or using our public API to be a derived work.
00020  */
00021 
00022 /*
00023  * TODO:
00024  * - Unicode/UTF-8 support in format strings. using any non-ASCII
00025  *   characters in Tuplez format strings WILL cause things go boom
00026  *   at the moment!
00027  *
00028  * - implement definitions (${=foo,"baz"} ${=foo,1234})
00029  * - implement functions
00030  * - implement handling of external expressions
00031  * - evaluation context: how local variables should REALLY work?
00032  *   currently there is just a single context, is a "global" context needed?
00033  */
00034 
00035 #include <ctype.h>
00036 #include <stdarg.h>
00037 #include <stdlib.h>
00038 #include <stdio.h>
00039 #include <string.h>
00040 
00041 #include <glib.h>
00042 
00043 #include "tuple_compiler.h"
00044 
00045 #define MAX_STR         (256)
00046 #define MIN_ALLOC_NODES (8)
00047 #define MIN_ALLOC_BUF   (64)
00048 #define TUPLEZ_MAX_VARS (4)
00049 
00050 #define tuple_error(ctx, ...) fprintf (stderr, "Tuple compiler: " __VA_ARGS__)
00051 
00052 enum {
00053     OP_RAW = 0,         /* plain text */
00054     OP_FIELD,           /* a field/variable */
00055     OP_EXISTS,
00056     OP_EQUALS,
00057     OP_NOT_EQUALS,
00058     OP_GT,
00059     OP_GTEQ,
00060     OP_LT,
00061     OP_LTEQ,
00062     OP_IS_EMPTY
00063 };
00064 
00065 enum {
00066     TUPLE_VAR_FIELD = 0,
00067     TUPLE_VAR_CONST
00068 };
00069 
00070 struct _TupleEvalNode {
00071     int opcode;         /* operator, see OP_ enums */
00072     int var[TUPLEZ_MAX_VARS];   /* tuple variable references */
00073     char *text;         /* raw text, if any (OP_RAW) */
00074     struct _TupleEvalNode *children, *next, *prev; /* children of this struct, and pointer to next node. */
00075 };
00076 
00077 typedef struct {
00078     char *name;
00079     int type;                   /* Type of variable, see VAR_* */
00080     int defvali;
00081     TupleValueType ctype;       /* Type of constant/def value */
00082 
00083     int fieldidx;               /* if >= 0: Index # of "pre-defined" Tuple fields */
00084     bool_t fieldread, fieldvalid;
00085     char * fieldstr;
00086 } TupleEvalVar;
00087 
00088 struct _TupleEvalContext {
00089     int nvariables;
00090     TupleEvalVar **variables;
00091 };
00092 
00093 
00094 static void tuple_evalctx_free_var(TupleEvalVar *var)
00095 {
00096   g_free(var->name);
00097   str_unref (var->fieldstr);
00098   g_free(var);
00099 }
00100 
00101 
00102 /* Initialize an evaluation context
00103  */
00104 TupleEvalContext * tuple_evalctx_new(void)
00105 {
00106   return g_new0(TupleEvalContext, 1);
00107 }
00108 
00109 
00110 /* "Reset" the evaluation context
00111  */
00112 void tuple_evalctx_reset(TupleEvalContext *ctx)
00113 {
00114   int i;
00115 
00116   for (i = 0; i < ctx->nvariables; i++)
00117     if (ctx->variables[i]) {
00118       ctx->variables[i]->fieldread = FALSE;
00119       ctx->variables[i]->fieldvalid = FALSE;
00120       str_unref (ctx->variables[i]->fieldstr);
00121       ctx->variables[i]->fieldstr = NULL;
00122     }
00123 }
00124 
00125 
00126 /* Free an evaluation context and associated data
00127  */
00128 void tuple_evalctx_free(TupleEvalContext *ctx)
00129 {
00130   int i;
00131 
00132   if (!ctx) return;
00133 
00134   /* Deallocate variables */
00135   for (i = 0; i < ctx->nvariables; i++)
00136     if (ctx->variables[i])
00137       tuple_evalctx_free_var(ctx->variables[i]);
00138 
00139   g_free(ctx->variables);
00140   g_free(ctx);
00141 }
00142 
00143 
00144 static int tuple_evalctx_add_var (TupleEvalContext * ctx, const char * name,
00145  const int type, const TupleValueType ctype)
00146 {
00147   int i;
00148   TupleEvalVar *tmp = g_new0(TupleEvalVar, 1);
00149 
00150   tmp->name = g_strdup(name);
00151   tmp->type = type;
00152   tmp->fieldidx = -1;
00153   tmp->ctype = ctype;
00154 
00155   /* Find fieldidx, if any */
00156   switch (type) {
00157     case TUPLE_VAR_FIELD:
00158       tmp->fieldidx = tuple_field_by_name (name);
00159       tmp->ctype = tuple_field_get_type (tmp->fieldidx);
00160       break;
00161 
00162     case TUPLE_VAR_CONST:
00163       if (ctype == TUPLE_INT)
00164         tmp->defvali = atoi(name);
00165       break;
00166   }
00167 
00168   /* Find a free slot */
00169   for (i = 0; i < ctx->nvariables; i++)
00170   if (!ctx->variables[i]) {
00171     ctx->variables[i] = tmp;
00172     return i;
00173   }
00174 
00175   i = ctx->nvariables;
00176   ctx->variables = g_renew(TupleEvalVar *, ctx->variables, ctx->nvariables + MIN_ALLOC_NODES);
00177   memset(&(ctx->variables[ctx->nvariables]), 0, MIN_ALLOC_NODES * sizeof(TupleEvalVar *));
00178   ctx->nvariables += MIN_ALLOC_NODES;
00179   ctx->variables[i] = tmp;
00180 
00181   return i;
00182 }
00183 
00184 
00185 static void tuple_evalnode_insert(TupleEvalNode **nodes, TupleEvalNode *node)
00186 {
00187   if (*nodes) {
00188     node->prev = (*nodes)->prev;
00189     (*nodes)->prev->next = node;
00190     (*nodes)->prev = node;
00191     node->next = NULL;
00192   } else {
00193     *nodes = node;
00194     node->prev = node;
00195     node->next = NULL;
00196   }
00197 }
00198 
00199 
00200 static TupleEvalNode *tuple_evalnode_new(void)
00201 {
00202   return g_new0(TupleEvalNode, 1);
00203 }
00204 
00205 
00206 void tuple_evalnode_free(TupleEvalNode *expr)
00207 {
00208   TupleEvalNode *curr = expr, *next;
00209 
00210   while (curr) {
00211     next = curr->next;
00212 
00213     g_free(curr->text);
00214 
00215     if (curr->children)
00216       tuple_evalnode_free(curr->children);
00217 
00218     g_free(curr);
00219 
00220     curr = next;
00221   }
00222 }
00223 
00224 
00225 static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, char **expression);
00226 
00227 
00228 static bool_t tc_get_item(TupleEvalContext *ctx,
00229     char **str, char *buf, gssize max,
00230     char endch, bool_t *literal, char *errstr, char *item)
00231 {
00232   gssize i = 0;
00233   char *s = *str, tmpendch;
00234 
00235   if (*s == '"') {
00236     if (*literal == FALSE) {
00237       tuple_error(ctx, "Literal string value not allowed in '%s'.\n", item);
00238       return FALSE;
00239     }
00240     s++;
00241     *literal = TRUE;
00242     tmpendch = '"';
00243   } else {
00244     *literal = FALSE;
00245     tmpendch = endch;
00246   }
00247 
00248   if (*literal == FALSE) {
00249     while (*s != '\0' && *s != tmpendch && (isalnum(*s) || *s == '-') && i < (max - 1)) {
00250       buf[i++] = *(s++);
00251     }
00252 
00253     if (*s != tmpendch && *s != '}' && !isalnum(*s) && *s != '-') {
00254       tuple_error(ctx, "Invalid field '%s' in '%s'.\n", *str, item);
00255       return FALSE;
00256     } else if (*s != tmpendch) {
00257       tuple_error(ctx, "Expected '%c' in '%s'.\n", tmpendch, item);
00258       return FALSE;
00259     }
00260   } else {
00261     while (*s != '\0' && *s != tmpendch && i < (max - 1)) {
00262       if (*s == '\\') s++;
00263       buf[i++] = *(s++);
00264     }
00265   }
00266   buf[i] = '\0';
00267 
00268   if (*literal) {
00269     if (*s == tmpendch)
00270       s++;
00271     else {
00272       tuple_error(ctx, "Expected literal string end ('%c') in '%s'.\n", tmpendch, item);
00273       return FALSE;
00274     }
00275   }
00276 
00277   if (*s != endch) {
00278     tuple_error(ctx, "Expected '%c' after %s in '%s'\n", endch, errstr, item);
00279     return FALSE;
00280   } else {
00281     *str = s;
00282     return TRUE;
00283   }
00284 }
00285 
00286 
00287 static int tc_get_variable(TupleEvalContext *ctx, char *name, int type)
00288 {
00289   int i;
00290   TupleValueType ctype = TUPLE_UNKNOWN;
00291 
00292   if (name == '\0') return -1;
00293 
00294   if (isdigit(name[0])) {
00295     ctype = TUPLE_INT;
00296     type = TUPLE_VAR_CONST;
00297   } else
00298     ctype = TUPLE_STRING;
00299 
00300   if (type != TUPLE_VAR_CONST) {
00301     for (i = 0; i < ctx->nvariables; i++)
00302       if (ctx->variables[i] && !strcmp(ctx->variables[i]->name, name))
00303         return i;
00304   }
00305 
00306   return tuple_evalctx_add_var(ctx, name, type, ctype);
00307 }
00308 
00309 
00310 static bool_t tc_parse_construct(TupleEvalContext *ctx, TupleEvalNode **res, char *item, char **c, int *level, int opcode)
00311 {
00312   char tmps1[MAX_STR], tmps2[MAX_STR];
00313   bool_t literal1 = TRUE, literal2 = TRUE;
00314 
00315   (*c)++;
00316   if (tc_get_item(ctx, c, tmps1, MAX_STR, ',', &literal1, "tag1", item)) {
00317     (*c)++;
00318     if (tc_get_item(ctx, c, tmps2, MAX_STR, ':', &literal2, "tag2", item)) {
00319       TupleEvalNode *tmp = tuple_evalnode_new();
00320       (*c)++;
00321 
00322       tmp->opcode = opcode;
00323       if ((tmp->var[0] = tc_get_variable(ctx, tmps1, literal1 ? TUPLE_VAR_CONST : TUPLE_VAR_FIELD)) < 0) {
00324         tuple_evalnode_free(tmp);
00325         tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, item);
00326         return FALSE;
00327       }
00328       if ((tmp->var[1] = tc_get_variable(ctx, tmps2, literal2 ? TUPLE_VAR_CONST : TUPLE_VAR_FIELD)) < 0) {
00329         tuple_evalnode_free(tmp);
00330         tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps2, item);
00331         return FALSE;
00332       }
00333       tmp->children = tuple_compiler_pass1(level, ctx, c);
00334       tuple_evalnode_insert(res, tmp);
00335     } else
00336       return FALSE;
00337   } else
00338     return FALSE;
00339 
00340   return TRUE;
00341 }
00342 
00343 
00344 /* Compile format expression into TupleEvalNode tree.
00345  * A "simple" straight compilation is sufficient in first pass, later
00346  * passes can perform subexpression removal and other optimizations.
00347  */
00348 static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, char **expression)
00349 {
00350   TupleEvalNode *res = NULL, *tmp = NULL;
00351   char *c = *expression, *item, tmps1[MAX_STR];
00352   bool_t literal, end = FALSE;
00353 
00354   (*level)++;
00355 
00356   while (*c != '\0' && !end) {
00357     tmp = NULL;
00358     if (*c == '}') {
00359       c++;
00360       (*level)--;
00361       end = TRUE;
00362     } else if (*c == '$') {
00363       /* Expression? */
00364       item = c++;
00365       if (*c == '{') {
00366         int opcode;
00367         char *expr = ++c;
00368 
00369         switch (*c) {
00370           case '?': c++;
00371             /* Exists? */
00372             literal = FALSE;
00373             if (tc_get_item(ctx, &c, tmps1, MAX_STR, ':', &literal, "tag", item)) {
00374               c++;
00375               tmp = tuple_evalnode_new();
00376               tmp->opcode = OP_EXISTS;
00377               if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) {
00378                 tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr);
00379                 goto ret_error;
00380               }
00381               tmp->children = tuple_compiler_pass1(level, ctx, &c);
00382               tuple_evalnode_insert(&res, tmp);
00383             } else
00384               goto ret_error;
00385             break;
00386 
00387           case '=': c++;
00388             if (*c != '=') {
00389               /* Definition */
00390               literal = FALSE;
00391               if (tc_get_item(ctx, &c, tmps1, MAX_STR, ',', &literal, "variable", item)) {
00392                 c++;
00393                 if (*c == '"') {
00394                   /* String */
00395                   c++;
00396                 } else if (isdigit(*c)) {
00397                   /* Integer */
00398                 }
00399 
00400                 tuple_error(ctx, "Definitions are not yet supported!\n");
00401                 goto ret_error;
00402               } else
00403                 goto ret_error;
00404             } else {
00405               /* Equals? */
00406               if (!tc_parse_construct(ctx, &res, item, &c, level, OP_EQUALS))
00407                 goto ret_error;
00408             }
00409             break;
00410 
00411           case '!': c++;
00412             if (*c != '=') goto ext_expression;
00413             if (!tc_parse_construct(ctx, &res, item, &c, level, OP_NOT_EQUALS))
00414               goto ret_error;
00415             break;
00416 
00417           case '<': c++;
00418             if (*c == '=') {
00419               opcode = OP_LTEQ;
00420               c++;
00421             } else
00422               opcode = OP_LT;
00423 
00424             if (!tc_parse_construct(ctx, &res, item, &c, level, opcode))
00425               goto ret_error;
00426             break;
00427 
00428           case '>': c++;
00429             if (*c == '=') {
00430               opcode = OP_GTEQ;
00431               c++;
00432             } else
00433               opcode = OP_GT;
00434 
00435             if (!tc_parse_construct(ctx, &res, item, &c, level, opcode))
00436               goto ret_error;
00437             break;
00438 
00439           case '(': c++;
00440             if (!strncmp(c, "empty)?", 7)) {
00441               c += 7;
00442               literal = FALSE;
00443               if (tc_get_item(ctx, &c, tmps1, MAX_STR, ':', &literal, "tag", item)) {
00444                 c++;
00445                 tmp = tuple_evalnode_new();
00446                 tmp->opcode = OP_IS_EMPTY;
00447                 if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) {
00448                   tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr);
00449                   goto ret_error;
00450                 }
00451                 tmp->children = tuple_compiler_pass1(level, ctx, &c);
00452                 tuple_evalnode_insert(&res, tmp);
00453               } else
00454                 goto ret_error;
00455             } else
00456               goto ext_expression;
00457             break;
00458 
00459           default:
00460           ext_expression:
00461             /* Get expression content */
00462             c = expr;
00463             literal = FALSE;
00464             if (tc_get_item(ctx, &c, tmps1, MAX_STR, '}', &literal, "field", item)) {
00465               /* FIXME!! FIX ME! Check for external expressions */
00466 
00467               /* I HAS A FIELD - A field. You has it. */
00468               tmp = tuple_evalnode_new();
00469               tmp->opcode = OP_FIELD;
00470               if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) {
00471                 tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr);
00472                 goto ret_error;
00473               }
00474               tuple_evalnode_insert(&res, tmp);
00475               c++;
00476 
00477             } else
00478               goto ret_error;
00479         }
00480       } else {
00481         tuple_error(ctx, "Expected '{', got '%c' in '%s'.\n", *c, c);
00482         goto ret_error;
00483       }
00484 
00485     } else if (*c == '%') {
00486       /* Function? */
00487       item = c++;
00488       if (*c == '{') {
00489         gssize i = 0;
00490         c++;
00491 
00492         while (*c != '\0' && (isalnum(*c) || *c == '-') && *c != '}' && *c != ':' && i < (MAX_STR - 1))
00493           tmps1[i++] = *(c++);
00494         tmps1[i] = '\0';
00495 
00496         if (*c == ':') {
00497           c++;
00498         } else if (*c == '}') {
00499           c++;
00500         } else if (*c == '\0') {
00501           tuple_error(ctx, "Expected '}' or function arguments in '%s'\n", item);
00502           goto ret_error;
00503         }
00504       } else {
00505         tuple_error(ctx, "Expected '{', got '%c' in '%s'.\n", *c, c);
00506         goto ret_error;
00507       }
00508     } else {
00509       /* Parse raw/literal text */
00510       gssize i = 0;
00511       while (*c != '\0' && *c != '$' && *c != '%' && *c != '}' && i < (MAX_STR - 1)) {
00512         if (*c == '\\') c++;
00513         tmps1[i++] = *(c++);
00514       }
00515       tmps1[i] = '\0';
00516 
00517       tmp = tuple_evalnode_new();
00518       tmp->opcode = OP_RAW;
00519       tmp->text = g_strdup(tmps1);
00520       tuple_evalnode_insert(&res, tmp);
00521     }
00522   }
00523 
00524   if (*level <= 0) {
00525     tuple_error(ctx, "Syntax error! Uneven/unmatched nesting of elements in '%s'!\n", c);
00526     goto ret_error;
00527   }
00528 
00529   *expression = c;
00530   return res;
00531 
00532 ret_error:
00533   tuple_evalnode_free(tmp);
00534   tuple_evalnode_free(res);
00535   return NULL;
00536 }
00537 
00538 
00539 TupleEvalNode *tuple_formatter_compile(TupleEvalContext *ctx, char *expr)
00540 {
00541   int level = 0;
00542   char *tmpexpr = expr;
00543   TupleEvalNode *res1;
00544 
00545   res1 = tuple_compiler_pass1(&level, ctx, &tmpexpr);
00546 
00547   if (level != 1) {
00548     tuple_error(ctx, "Syntax error! Uneven/unmatched nesting of elements! (%d)\n", level);
00549     tuple_evalnode_free(res1);
00550     return NULL;
00551   }
00552 
00553   return res1;
00554 }
00555 
00556 
00557 /* Fetch a tuple field value.  Return TRUE if found. */
00558 static bool_t tf_get_fieldval (TupleEvalVar * var, const Tuple * tuple)
00559 {
00560   if (var->type != TUPLE_VAR_FIELD || var->fieldidx < 0)
00561     return FALSE;
00562 
00563   if (var->fieldread)
00564     return var->fieldvalid;
00565 
00566   if (tuple_get_value_type (tuple, var->fieldidx, NULL) != var->ctype) {
00567     var->fieldread = TRUE;
00568     var->fieldvalid = FALSE;
00569     return FALSE;
00570   }
00571 
00572   if (var->ctype == TUPLE_INT)
00573     var->defvali = tuple_get_int (tuple, var->fieldidx, NULL);
00574   else if (var->ctype == TUPLE_STRING)
00575     var->fieldstr = tuple_get_str (tuple, var->fieldidx, NULL);
00576 
00577   var->fieldread = TRUE;
00578   var->fieldvalid = TRUE;
00579   return TRUE;
00580 }
00581 
00582 
00583 /* Fetch string or int value of given variable, whatever type it might be.
00584  * Return VAR_* type for the variable.
00585  */
00586 static TupleValueType tf_get_var (char * * tmps, int * tmpi, TupleEvalVar *
00587  var, const Tuple * tuple)
00588 {
00589   TupleValueType type = TUPLE_UNKNOWN;
00590   *tmps = NULL;
00591   *tmpi = 0;
00592 
00593   switch (var->type) {
00594     case TUPLE_VAR_CONST:
00595       switch (var->ctype) {
00596         case TUPLE_STRING: *tmps = var->name; break;
00597         case TUPLE_INT: *tmpi = var->defvali; break;
00598         default: /* Cannot happen */ break;
00599       }
00600       type = var->ctype;
00601       break;
00602 
00603     case TUPLE_VAR_FIELD:
00604       if (tf_get_fieldval (var, tuple)) {
00605         type = var->ctype;
00606         if (type == TUPLE_INT)
00607           * tmpi = var->defvali;
00608         else if (type == TUPLE_STRING)
00609           * tmps = var->fieldstr;
00610       }
00611       break;
00612   }
00613 
00614   return type;
00615 }
00616 
00617 
00618 /* Evaluate tuple in given TupleEval expression in given
00619  * context and return resulting string.
00620  */
00621 static bool_t tuple_formatter_eval_do (TupleEvalContext * ctx, TupleEvalNode *
00622  expr, const Tuple * tuple, GString * out)
00623 {
00624   TupleEvalNode *curr = expr;
00625   TupleEvalVar *var0, *var1;
00626   TupleValueType type0, type1;
00627   int tmpi0, tmpi1;
00628   char tmps[MAX_STR], *tmps0, *tmps1, *tmps2;
00629   bool_t result;
00630   int resulti;
00631 
00632   if (!expr) return FALSE;
00633 
00634   while (curr) {
00635     const char *str = NULL;
00636 
00637     switch (curr->opcode) {
00638       case OP_RAW:
00639         str = curr->text;
00640         break;
00641 
00642       case OP_FIELD:
00643         var0 = ctx->variables[curr->var[0]];
00644 
00645         switch (var0->type) {
00646           case TUPLE_VAR_FIELD:
00647             if (tf_get_fieldval (var0, tuple)) {
00648               switch (var0->ctype) {
00649                 case TUPLE_STRING:
00650                   str = var0->fieldstr;
00651                   break;
00652 
00653                 case TUPLE_INT:
00654                   g_snprintf (tmps, sizeof (tmps), "%d", var0->defvali);
00655                   str = tmps;
00656                   break;
00657 
00658                 default:
00659                   str = NULL;
00660               }
00661             }
00662             break;
00663         }
00664         break;
00665 
00666       case OP_EQUALS:
00667       case OP_NOT_EQUALS:
00668       case OP_LT: case OP_LTEQ:
00669       case OP_GT: case OP_GTEQ:
00670         var0 = ctx->variables[curr->var[0]];
00671         var1 = ctx->variables[curr->var[1]];
00672 
00673         type0 = tf_get_var(&tmps0, &tmpi0, var0, tuple);
00674         type1 = tf_get_var(&tmps1, &tmpi1, var1, tuple);
00675         result = FALSE;
00676 
00677         if (type0 != TUPLE_UNKNOWN && type1 != TUPLE_UNKNOWN) {
00678           if (type0 == type1) {
00679             if (type0 == TUPLE_STRING)
00680               resulti = strcmp(tmps0, tmps1);
00681             else
00682               resulti = tmpi0 - tmpi1;
00683           } else {
00684             if (type0 == TUPLE_INT)
00685               resulti = tmpi0 - atoi(tmps1);
00686             else
00687               resulti = atoi(tmps0) - tmpi1;
00688           }
00689 
00690           switch (curr->opcode) {
00691             case OP_EQUALS:     result = (resulti == 0); break;
00692             case OP_NOT_EQUALS: result = (resulti != 0); break;
00693             case OP_LT:         result = (resulti <  0); break;
00694             case OP_LTEQ:       result = (resulti <= 0); break;
00695             case OP_GT:         result = (resulti >  0); break;
00696             case OP_GTEQ:       result = (resulti >= 0); break;
00697           default:            result = FALSE;
00698           }
00699         }
00700 
00701         if (result && ! tuple_formatter_eval_do (ctx, curr->children, tuple, out))
00702           return FALSE;
00703         break;
00704 
00705       case OP_EXISTS:
00706         if (tf_get_fieldval (ctx->variables[curr->var[0]], tuple)) {
00707           if (! tuple_formatter_eval_do (ctx, curr->children, tuple, out))
00708             return FALSE;
00709         }
00710         break;
00711 
00712       case OP_IS_EMPTY:
00713         var0 = ctx->variables[curr->var[0]];
00714 
00715         if (tf_get_fieldval (var0, tuple)) {
00716           switch (var0->ctype) {
00717           case TUPLE_INT:
00718             result = (var0->defvali == 0);
00719             break;
00720 
00721           case TUPLE_STRING:
00722             result = TRUE;
00723             tmps2 = var0->fieldstr;
00724 
00725             while (result && tmps2 && *tmps2 != '\0') {
00726               gunichar uc = g_utf8_get_char(tmps2);
00727               if (g_unichar_isspace(uc))
00728                 tmps2 = g_utf8_next_char(tmps2);
00729               else
00730                 result = FALSE;
00731             }
00732             break;
00733 
00734           default:
00735             result = TRUE;
00736           }
00737         } else
00738           result = TRUE;
00739 
00740         if (result && ! tuple_formatter_eval_do (ctx, curr->children, tuple, out))
00741           return FALSE;
00742         break;
00743 
00744       default:
00745         tuple_error(ctx, "Unimplemented opcode %d!\n", curr->opcode);
00746         return FALSE;
00747         break;
00748     }
00749 
00750     if (str)
00751       g_string_append (out, str);
00752 
00753     curr = curr->next;
00754   }
00755 
00756   return TRUE;
00757 }
00758 
00759 void tuple_formatter_eval (TupleEvalContext * ctx, TupleEvalNode * expr,
00760  const Tuple * tuple, GString * out)
00761 {
00762     g_string_truncate (out, 0);
00763     tuple_formatter_eval_do (ctx, expr, tuple, out);
00764 }