/*
 * ratExp.c --
 *
 *	This file handles the expressions.
 *
 * TkRat software and its included text is Copyright 1996-2002 by
 * Martin Forssén
 *
 * The full text of my legal notices is contained in the file called
 * COPYRIGHT, included with this distribution.
 */

#include "ratFolder.h"

/*
 * Private types
 */
typedef enum {TT_Field, TT_Operator, TT_Boolean,TT_Grouping,TT_Spec} TokenType;
typedef enum {T_To, T_From, T_Subject, T_Sender, T_Cc, T_Reply_To, T_Size,
	      T_Has, T_Is, T_Gt, T_Lt,
	      T_And, T_Or, T_Not,
	      T_Lparen, T_Rparen} Token;

typedef struct {
    Token		token;
    TokenType		type;
    char               *string;
    RatFolderInfoType	info;
} TokenList;

typedef struct Expression {
    int		negate;
    Token	op;
    union {
	struct Expression      *expPtr;
	RatFolderInfoType	info;
    } arg1;
    union {
	struct Expression      *expPtr;
	char                   *string;
    } arg2;
} Expression;

typedef struct ExpList {
    int		    id;
    Expression     *expPtr;
    struct ExpList *next;
} ExpList;

/*
 * Static data
 */
static int expCounter = 0;
static ExpList *expListPtr = NULL;
static TokenList tokenList[] = {
    {T_To,	TT_Field,	"to",		RAT_FOLDER_TO},
    {T_From,	TT_Field,	"from",		RAT_FOLDER_FROM},
    {T_Subject,	TT_Field,	"subject",	RAT_FOLDER_SUBJECT},
    {T_Sender,	TT_Field,	"sender",	RAT_FOLDER_SENDER},
    {T_Cc,	TT_Field,	"cc",		RAT_FOLDER_CC},
    {T_Reply_To,TT_Field,	"reply-to",	RAT_FOLDER_REPLY_TO},
    {T_Size,	TT_Field,	"size",		RAT_FOLDER_SIZE},
    {T_Has,	TT_Operator,	"has",		0},
    {T_Is,	TT_Operator,	"is",		0},
    {T_Gt,	TT_Operator,	">",		0},
    {T_Lt,	TT_Operator,	"<",		0},
    {T_And,	TT_Boolean,	"and",		0},
    {T_Or,	TT_Boolean,	"or",		0},
    {T_Not,	TT_Spec,	"not",		0},
    {T_Lparen,	TT_Grouping,	"(",		0},
    {T_Rparen,	TT_Grouping,	")",		0},
    {0,		0,		NULL,		0}
};

/*
 * Local functions
 */
static TokenList   *GetToken(char **sPtr);
static char 	   *GetString(char **sPtr);
static void	    FreeExp(Expression *expPtr);
static Expression  *ParseExpression(char **sPtr, char **errPtr, int inParen);
static void	    GetExpression(Tcl_Interp *interp, Tcl_Obj *ePtr,
				  Expression *expPtr);
static int	    RatExpMatchDo(Tcl_Interp *interp, Expression *expPtr,
		    RatInfoProc *infoProc, ClientData clientData, int index);


/*
 *----------------------------------------------------------------------
 *
 * GetToken --
 *
 *      Extract the next token from the string.
 *	to the interpreter.
 *
 * Results:
 *	A pointer to a TokenList entity or nULL if no valid token is
 *	found
 *
 * Side effects:
 *	*sPtr will most probably be modified.
 *
 *
 *----------------------------------------------------------------------
 */

static TokenList*
GetToken(char **sPtr)
{
    char *cPtr = *sPtr;
    int i;

    while (isspace((unsigned char)*cPtr)) {
	cPtr++;
    }
    *sPtr = cPtr;
    if (!*cPtr) {
	return NULL;
    }
    for (i=0; tokenList[i].string; i++) {
	if (!strncasecmp(cPtr, tokenList[i].string,
			 strlen(tokenList[i].string))) {
	    cPtr += strlen(tokenList[i].string);
	    *sPtr = cPtr;
	    return &tokenList[i];
	}
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * GetString --
 *
 *      Extract the next string.
 *
 * Results:
 *	A pointer to a copy of the found string. It is the callers
 *	resposibility to eventually free this area.
 *
 * Side effects:
 *	*sPtr will most probably be modified.
 *
 *
 *----------------------------------------------------------------------
 */

static char*
GetString(char **sPtr)
{
    char quote = '\0', *cPtr = *sPtr, *result;
    int i;


    while (isspace((unsigned char)*cPtr)) {
	cPtr++;
    }
    if ('\'' == *cPtr || '"' == *cPtr || '{' == *cPtr) {
	quote = *cPtr++;
    }
    *sPtr = cPtr;
    if ('{' == quote) {
	quote = '}';
    }
    result = (char*)ckalloc(strlen(cPtr)+1);
    i=0;
    while (*cPtr && !(quote == *cPtr
		      || (!quote && isspace((unsigned char)*cPtr)))) {
	if ('\\' == *cPtr && cPtr[1]) {
	    cPtr++;
	}
	if (isupper((unsigned char)*cPtr)) {
	    result[i] = tolower((unsigned char)*cPtr);
	} else {
	    result[i] = *cPtr;
	}
	i++;
	cPtr++;
    }
    result[i] = '\0';
    if (quote && quote == *cPtr) {
	cPtr++;
    }
    *sPtr = cPtr;
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * FreeExp --
 *
 *      Free an expression
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The given expression will be free'ed.
 *
 *
 *----------------------------------------------------------------------
 */

static void
FreeExp(Expression *expPtr)
{
    if (!expPtr) {
	return;
    }
    if (expPtr->op == T_And || expPtr->op == T_Or) {
	FreeExp(expPtr->arg1.expPtr);
	FreeExp(expPtr->arg2.expPtr);
    } else {
	ckfree(expPtr->arg2.string);
    }
    ckfree(expPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * ParseExpression --
 *
 *      Parse a given search expression
 *
 * Results:
 *	A pointer to an expression, or NULL if an error was encountered.
 *
 * Side effects:
 *	*sPtr will most probably be modified.
 *
 *
 *----------------------------------------------------------------------
 */

static Expression*
ParseExpression(char **sPtr, char **errPtr, int inParen)
{
    TokenList *tokPtr;
    Expression *expPtr = NULL, *exp2Ptr;
    char *newString;
    int negated, l;

    while (**sPtr) {
	negated = 0;
	while (tokPtr = GetToken(sPtr), (tokPtr && tokPtr->token == T_Not)) {
	    negated = negated ? 0 : 1;
	}
	if (!tokPtr) {
	    if (**sPtr) {
		*errPtr = "Unparseable text";
	    }
	    return expPtr;
	}
	switch (tokPtr->type) {
	case TT_Field:
		if (expPtr && expPtr->op != T_And && expPtr->op != T_Or) {
		    *errPtr = "Expected boolean or ')'";
		    return expPtr;
		}
		exp2Ptr = (Expression*)ckalloc(sizeof(Expression));
		exp2Ptr->negate = negated;
		exp2Ptr->arg1.info = tokPtr->info;
		tokPtr = GetToken(sPtr);
		if (!tokPtr || tokPtr->type != TT_Operator) {
		    *errPtr = "Expected operator";
		    ckfree(exp2Ptr);
		    return expPtr;
		}
		exp2Ptr->op = tokPtr->token;
		exp2Ptr->arg2.string = GetString(sPtr);
		if (!exp2Ptr->arg2.string) {
		    *errPtr = "String expected";
		    ckfree(exp2Ptr);
		    return expPtr;
		} else if (T_Is == exp2Ptr->op) {
		    exp2Ptr->op = T_Has;
		    l = strlen(exp2Ptr->arg2.string)+3;
		    newString = (char*)ckalloc(l);
		    strlcpy(newString, "^", l);
		    strlcat(newString, exp2Ptr->arg2.string, l);
		    strlcat(newString, "$", l);
		    ckfree(exp2Ptr->arg2.string);
		    exp2Ptr->arg2.string = newString;
		}
		if (expPtr) {
		    expPtr->arg2.expPtr = exp2Ptr;
		} else {
		    expPtr = exp2Ptr;
		}
		break;
	case TT_Boolean:
		if (!expPtr) {
		    *errPtr = "Must have a valid expression before a boolean";
		    return expPtr;
		}
		exp2Ptr = (Expression*)ckalloc(sizeof(Expression));
		exp2Ptr->negate = negated;
		exp2Ptr->op = tokPtr->token;
		exp2Ptr->arg1.expPtr = expPtr;
		exp2Ptr->arg2.expPtr = NULL;
		expPtr = exp2Ptr;
		break;
	case TT_Grouping:
		if (T_Lparen == tokPtr->token) {
		    if (expPtr && expPtr->op != T_And && expPtr->op != T_Or) {
			*errPtr = "Expected boolean, field or ')'";
			return expPtr;
		    }
		    exp2Ptr = ParseExpression(sPtr, errPtr, 1);
		    if (expPtr) {
			expPtr->arg2.expPtr = exp2Ptr;
		    } else {
			expPtr = exp2Ptr;
		    }
		    if (*errPtr) {
			return expPtr;
		    }
		} else {
		    if (!inParen) {
			*errPtr = "Unexpected ')'.";
			return expPtr;
		    }
		    return expPtr;
		}
		break;
	case TT_Operator:	/* fallthrough */
	case TT_Spec:
		*errPtr = "Expected field or (";
		return expPtr;
	}
    }
    if (!expPtr) {
	*errPtr = "Empty expression";
	return NULL;
    }
    return expPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * RatParseExp --
 *
 *      See ../doc/interface
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	Will probably add an expression to the local list.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatParseExpCmd(ClientData clientData, Tcl_Interp *interp, int objc,
	       Tcl_Obj *const objv[])
{
    char *error = NULL, *cPtr, *exp;
    Expression *expPtr;
    ExpList *elemPtr;

    if (objc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
			 Tcl_GetString(objv[0]), " expression\"",
			 (char *) NULL);
	return TCL_ERROR;
    }

    exp = cPtr = Tcl_GetString(objv[1]);
    expPtr = ParseExpression(&cPtr, &error, 0);
    if (error) {
	char buf[32];

	FreeExp(expPtr);
	sprintf(buf, "%d", cPtr-exp);
	Tcl_AppendElement(interp, buf);
	Tcl_AppendElement(interp, error);
	return TCL_ERROR;
    }

    elemPtr = (ExpList*)ckalloc(sizeof(ExpList));
    elemPtr->id = expCounter;
    elemPtr->expPtr = expPtr;
    elemPtr->next = expListPtr;
    expListPtr = elemPtr;
    Tcl_SetObjResult(interp, Tcl_NewIntObj(expCounter++));
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * GetExp --
 *
 *      Print one expression into the given DString.
 *
 * Results:
 *	The given DString will be modified.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

static void
GetExpression(Tcl_Interp *interp, Tcl_Obj *ePtr, Expression *expPtr)
{
    int opIndex, fIndex;
    Tcl_Obj *oPtr;

    for (opIndex=0; tokenList[opIndex].token != expPtr->op; opIndex++);

    if (expPtr->negate) {
	Tcl_ListObjAppendElement(interp, ePtr, Tcl_NewStringObj("not", 3));
    }
    if (tokenList[opIndex].type == TT_Boolean) {
	oPtr = Tcl_NewObj();
	GetExpression(interp, oPtr, expPtr->arg1.expPtr);
	Tcl_ListObjAppendElement(interp, ePtr, oPtr);
	Tcl_ListObjAppendElement(interp, ePtr,
			      Tcl_NewStringObj(tokenList[opIndex].string, -1));
	oPtr = Tcl_NewObj();
	GetExpression(interp, oPtr, expPtr->arg2.expPtr);
	Tcl_ListObjAppendElement(interp, ePtr, oPtr);
    } else {
	for (fIndex=0; tokenList[fIndex].info != expPtr->arg1.info; fIndex++);
	Tcl_ListObjAppendElement(interp, ePtr,
			      Tcl_NewStringObj(tokenList[fIndex].string, -1));
	Tcl_ListObjAppendElement(interp, ePtr,
			      Tcl_NewStringObj(tokenList[opIndex].string, -1));
	Tcl_ListObjAppendElement(interp, ePtr,
			      Tcl_NewStringObj(expPtr->arg2.string, -1));
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RatGetExp --
 *
 *      See ../doc/interface
 *
 * Results:
 *	The identified expression is returned as a string in the result area.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatGetExpCmd(ClientData clientData, Tcl_Interp *interp, int objc,
	     Tcl_Obj *const objv[])
{
    ExpList *elemPtr;
    Tcl_Obj *rPtr;
    int id;

    if (objc < 2
	|| TCL_OK != Tcl_GetIntFromObj(interp, objv[1], &id)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
			 Tcl_GetString(objv[0]), " id\"", (char *) NULL);
	return TCL_ERROR;
    }

    for (elemPtr = expListPtr; elemPtr; elemPtr = elemPtr->next) {
	if (elemPtr->id == id) {
	    rPtr = Tcl_NewObj();
	    GetExpression(interp, rPtr, elemPtr->expPtr);
	    Tcl_SetObjResult(interp, rPtr);
	    return TCL_OK;
	}
    }
    Tcl_AppendResult(interp, "No expression with id \"",
		     Tcl_GetString(objv[1]), "\"", (char *) NULL);
    return TCL_ERROR;
}


/*
 *----------------------------------------------------------------------
 *
 * RatFreeExp --
 *
 *      See ../doc/interface
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	Will probably remove an expression from the local list.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatFreeExpCmd(ClientData clientData, Tcl_Interp *interp, int objc,
	      Tcl_Obj *const objv[])
{
    ExpList **elemPtrPtr, *elemPtr;
    int id;

    if (objc < 2
	|| TCL_OK != Tcl_GetIntFromObj(interp, objv[1], &id)) {
	Tcl_AppendResult(interp, "Illegal usage: should be \"",
			 Tcl_GetString(objv[0]), " id\"", (char *) NULL);
	return TCL_ERROR;
    }
    for (elemPtrPtr = &expListPtr; *elemPtrPtr;
	 elemPtrPtr=&(*elemPtrPtr)->next){
	if ((*elemPtrPtr)->id == id) {
	    elemPtr = *elemPtrPtr;
	    FreeExp(elemPtr->expPtr);
	    *elemPtrPtr = elemPtr->next;
	    ckfree(elemPtr);
	    return TCL_OK;
	}
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * RatExpMatch --
 *
 *      Checks if a given expression matches to given message.
 *
 * Results:
 *	True if it did match.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatExpMatch(Tcl_Interp *interp, int expId, RatInfoProc *infoProc,
	ClientData clientData, int index)
{
    ExpList *elemPtr;

    for (elemPtr = expListPtr; elemPtr && elemPtr->id != expId;
	elemPtr = elemPtr->next);
    if (!elemPtr) {
	return 0;
    }
    return RatExpMatchDo(interp, elemPtr->expPtr, infoProc, clientData, index);
}


/*
 *----------------------------------------------------------------------
 *
 * RatExpMatchDo --
 *
 *      Checks if a given expression matches to given message.
 *	This routine actually does the checking and may call itself
 *	recursively.
 *
 * Results:
 *	True if it did match.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatExpMatchDo(Tcl_Interp *interp, Expression *expPtr, RatInfoProc *infoProc,
	ClientData clientData, int index)
{
    char *sLower, *cPtr;
    int opIndex, val;
    static Tcl_Obj *sPtr = NULL;
    Tcl_Obj *oPtr;

    for (opIndex=0; tokenList[opIndex].token != expPtr->op; opIndex++);

    if (TT_Boolean == tokenList[opIndex].type) {
	val = RatExpMatchDo(interp, expPtr->arg1.expPtr, infoProc, clientData,
		index);
	if (!((T_Or == tokenList[opIndex].token && val) ||
		(T_And == tokenList[opIndex].token && !val))) {
	    val = RatExpMatchDo(interp, expPtr->arg2.expPtr, infoProc,
		    clientData, index);
	}
	if (expPtr->negate) {
	    val = val ? 0 : 1;
	}
	return val;
    } else {
	oPtr = (*infoProc)(interp, clientData, expPtr->arg1.info, index);
	if (!oPtr) {
	    if (!sPtr) {
		sPtr = Tcl_NewObj();
		Tcl_IncrRefCount(sPtr);
	    }
	    oPtr = sPtr;
	}
	if (T_Has == tokenList[opIndex].token
		|| T_Is == tokenList[opIndex].token) {
	    sLower = cpystr(Tcl_GetString(oPtr));
	    for (cPtr = sLower; *cPtr; cPtr++) {
		if (isupper((unsigned char)*cPtr)) {
		    *cPtr = tolower((unsigned char)*cPtr);
		}
	    }
	    val = Tcl_RegExpMatch(interp, sLower, expPtr->arg2.string);
	    ckfree(sLower);
	    return val;
	} else if (expPtr->arg1.info == RAT_FOLDER_SIZE) {
	    Tcl_GetIntFromObj(interp, oPtr, &val);
	    if (T_Gt == tokenList[opIndex].token) {
		return val > atoi(expPtr->arg2.string);
	    } else {
		return val < atoi(expPtr->arg2.string);
	    }
	}
    }
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1