/* * $Id: expr2.c,v 1.1.1.1 2003/04/11 01:09:07 dan Exp $ * math.c - mathematical expression evaluation * This file is based on 'math.c', which is part of zsh, the Z shell. * * Copyright (c) 1992-1997 Paul Falstad, All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright notice, * this list of conditions and the two following paragraphs. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimers in the * documentation and/or other materials provided with the distribution * 3. The names of the author(s) may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * In no event shall Paul Falstad or the Zsh Development Group be liable * to any party for direct, indirect, special, incidental, or consequential * damages arising out of the use of this software and its documentation, * even if Paul Falstad and the Zsh Development Group have been advised of * the possibility of such damage. * * Paul Falstad and the Zsh Development Group specifically disclaim any * warranties, including, but not limited to, the implied warranties of * merchantability and fitness for a particular purpose. The software * provided hereunder is on an "as is" basis, and Paul Falstad and the * Zsh Development Group have no obligation to provide maintenance, * support, updates, enhancements, or modifications. * */ /* * Significant modifications by Jeremy Nelson * Coypright 1998 EPIC Software Labs, All rights reserved. * * You may distribute this file under the same terms as the above, by * including the parties "Jeremy Nelson" and "EPIC Software Labs" to the * limitations of liability and the express disclaimer of all warranties. * This software is provided "AS IS". * * $Id: expr2.c,v 1.1.1.1 2003/04/11 01:09:07 dan Exp $ */ #include "irc.h" #include #include "debug.h" /* XXX in expr.c */ #if 0 static char *alias_special_char(char **, char *, const char *, char *, int *); #endif #define STACKSZ 100 #define TOKENCOUNT 256 #define MAGIC_TOKEN -14 /* * One thing of note is that while the original did only ints, we really * only do strings. We convert to and from ints as neccesary. Icky, * but given the semantics we require its the only way. */ /* * All the information for each expression is stored in a struct. This * is done so that there are no global variables in use (theyre all collected * making them easier to handle), and makes re-entrancy much easier since * i dont have to ask myself "have i accounted for the old state of all the * global variables?" */ /* * When we want to refer symbolically to a token, we just sling around * the integer index to the token table. This serves two purposes: We * dont have to worry about whether something is malloced or not, or who * is resopnsible to free, or anything like that. If you want to keep * something around, you tokenize() it and that returns a "handle" to the * token and then you pass that handle around. So the pair (context,handle) * refers to a unique, usable token. */ typedef int TOKEN; /* * This sets up whether we do floating point math or integer math */ #ifdef FLOATING_POINT_MATH /* XXXX This doesnt work yet */ typedef double NUMBER; typedef long BooL; # define STON atof # define NTOS ftoa #else typedef unsigned long NUMBER; typedef long BooL; # define STON atol # define NTOS ltoa #endif typedef struct { /* POSITION AND STATE INFORMATION */ /* * This is the current position in the lexing. */ char *ptr; /* * When set, the expression is lexed, but nothing that may have a side * effect (function calls, assignments, etc) are actually performed. * Dummy values are instead substituted. */ int noeval; /* * When set, this means the next token may either be a prefix operator * or an operand. When clear, it means the next operator must be a * non-prefix operator. */ int operand; /* TOKEN TABLE */ /* * Each registered 'token' is given a TOKEN id. The idea is * that we want TOKEN to be an opaque type to be used to refer * to a token in a generic way, but in practice its just an integer * offset into a char ** table. We register all tokens sequentially, * so this just gets incremented when we want to register a new token. */ TOKEN token; /* * This is the list of operand (string) tokens we have extracted * so far from the expression. Offsets into this array are stored * into the parsing stack. */ char * tokens[TOKENCOUNT + 1]; /* OPERAND STACK */ /* * This is the operand shift stack. These are the operands that * are currently awaiting reduction. Note that rather than keeping * track of the lvals and rvals here, we simply keep track of offsets * to the 'tokens' table that actually stores all the relevant data. * Then we can just call the token-class functions to get that data. * This is more efficient because it allows us to recycle tokens * more reasonably without wasteful malloc-copies. */ TOKEN stack[STACKSZ + 1]; /* Current index to the operand stack */ int sp; /* This is the last token that was lexed. */ TOKEN mtok; /* This is set when an error happens */ int errflag; TOKEN last_token; const char *args; int *args_flag; } expr_info; __inline static TOKEN tokenize (expr_info *c, const char *t); static char * after_expando_special (expr_info *c); void setup_expr_info (expr_info *c) { int i; c->ptr = NULL; c->noeval = 0; c->operand = 1; c->token = 0; for (i = 0; i <= TOKENCOUNT; i++) c->tokens[i] = NULL; for (i = 0; i <= STACKSZ; i++) c->stack[i] = 0; c->sp = -1; c->mtok = 0; c->errflag = 0; c->last_token = 0; tokenize(c, empty_string); /* Always token 0 */ } void destroy_expr_info (expr_info *c) { int i; c->ptr = NULL; c->noeval = -1; c->operand = -1; for (i = 0; i < c->token; i++) new_free(&c->tokens[i]); c->token = -1; for (i = 0; i <= STACKSZ; i++) c->stack[i] = -1; c->sp = -1; c->mtok = -1; c->errflag = -1; c->last_token = -1; } /* * LR = left-to-right associativity * RL = right-to-left associativity * BOO = short-circuiting boolean */ #define LR 0 #define RL 1 #define BOOL 2 /* * These are all the token-types. Each of the operators is represented, * as is the generic operand type */ enum LEX { M_INPAR, NOT, COMP, PREMINUS, PREPLUS, UPLUS, UMINUS, STRLEN, WORDC, DEREF, POWER, MUL, DIV, MOD, PLUS, MINUS, STRCAT, SHLEFT, SHRIGHT, LES, LEQ, GRE, GEQ, MATCH, NOMATCH, DEQ, NEQ, AND, XOR, OR, DAND, DXOR, DOR, QUEST, COLON, EQ, PLUSEQ, MINUSEQ, MULEQ, DIVEQ, MODEQ, ANDEQ, XOREQ, OREQ, SHLEFTEQ, SHRIGHTEQ, DANDEQ, DOREQ, DXOREQ, POWEREQ, STRCATEQ, STRPREEQ, SWAP, COMMA, POSTMINUS, POSTPLUS, ID, M_OUTPAR, EERROR, EOI, TOKCOUNT }; /* * Precedence table: Operators with a lower precedence VALUE have a higher * precedence. The theory behind infix notation (algebraic notation) is that * you have a sequence of operands seperated by (typically binary) operators. * The problem of precedence is that each operand is surrounded by two * operators, so it is ambiguous which operator the operand "binds" to. This * is resolved by "precedence rules" which state that given two operators, * which one is allowed to "reduce" (operate on) the operand. For a simple * explanation, take the expression (3+4*5). Now the middle operand is a * '4', but we dont know if it should be reduced via the plus, or via the * multiply. If we look up both operators in the prec table, we see that * multiply has the lower value -- therefore the 4 is reduced via the multiply * and then the result of the multiply is reduced by the addition. */ static int prec[TOKCOUNT] = { 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13, 14, 15, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 2, 2, 0, 137, 156, 200 }; #define TOPPREC 21 /* * Associativity table: But precedence isnt enough. What happens when you * have two identical operations to determine between? Well, the easy way * is to say that the first operation is always done first. But some * operators dont work that way (like the assignment operator) and always * reduce the LAST (or rightmost) operation first. For example: * (3+4+5) ((4+3)+5) (7+5) (12) * (v1=v2=3) (v1=(v2=3)) (v1=3) (3) * So for each operator we need to specify how to determine the precedence * of the same operator against itself. This is called "associativity". * Left-to-right associativity means that the operations occur left-to-right, * or first-operator, first-reduced. Right-to-left associativity means * that the operations occur right-to-left, or last-operator, first-reduced. * * We have a special case of associativity called BOOL, which is just a * special type of left-to-right associtivity whereby the right hand side * of the operand is not automatically parsed. (not really, but its the * theory that counts.) */ static int assoc[TOKCOUNT] = { LR, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, LR, BOOL, BOOL, BOOL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, RL, LR, RL, RL, LR, LR, LR, LR }; /* ********************* OPERAND TOKEN REPOSITORY ********************** */ /* * This is where we store our lvalues, kind of. What we really store here * are all of the string operands. Actually, we store all of the operands * here. When an operand is parsed, its converted to a string and put in * here and the index into the 'token' table is returned. */ /* THIS FUNCTION MAKES A NEW COPY OF 'T'. YOU MUST DISPOSE OF 'T' YOURSELF */ __inline static TOKEN tokenize (expr_info *c, const char *t) { if (c->token >= TOKENCOUNT) { error("Too many tokens for this expression"); return -1; } c->tokens[c->token] = m_strdup(t); return c->token++; } /* get_token gets the ``lvalue'', or original text, of a token */ /* YOU MUST _NOT_ FREE THE RETURN VALUE FROM THIS FUNCTION! */ __inline static const char * get_token (expr_info *c, TOKEN v) { if (v == MAGIC_TOKEN) /* Magic token */ return c->args; if (v < 0 || v >= c->token) { error("Token index [%d] is out of range", v); return get_token(c, 0); /* The empty token */ } return c->tokens[v]; } /* * These three functions ``getsval'', ``getival'', and ``getbval'' take * as arguments token-indexes, and return the appropriate rvalue for those * tokens. For literal text strings, they are simply expanded and returned. * For function calls, the function is called, and the return value is * returned. For variable references, the value is looked up and returned. * Furthermore, ``getival'' takes this value and converts it into a long * int and returns that. ``getbval'' takes the value and checks it for its * truth or falseness (as determined by check_val()). */ /* YOU MUST FREE THE RETURN VALUE FROM THIS FUNCTION! */ static char * getsval2 (expr_info *c, TOKEN s); static char * getsval (expr_info *c, TOKEN s) { char * retval; const char * t; t = get_token(c, s); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell(">>> Expanding token [%d]: [%s]", s, t); retval = getsval2(c, s); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("<<< Expanded token [%d]: [%s] to: [%s]", s, t, retval); return retval; } /* XXX Ick. :-D */ static char * getsval2 (expr_info *c, TOKEN s) { const char *t; if (c->noeval || s == 0) return m_strdup(get_token(c, 0)); /* XXXX Bleh */ if (s == MAGIC_TOKEN) { *c->args_flag = 1; return m_strdup(c->args); } t = get_token(c, s); /* * Handle [string] token types. */ if (*t == '[') { t++; /* * Attempt to handle $0 and friends here. Also handle * Things like $1-, and also $*. */ if (*t == '$') { if (t[1] == '*' && !t[2]) return m_strdup(c->args); else { char * end = NULL; long j = strtol(t + 1, &end, 10); /* Handle [$X] */ if (end && !*end) { *c->args_flag = 1; if (j < 0) return extract2(c->args, SOS, -j); else return extract2(c->args, j, j); } /* Gracefully handle [$X-] as well */ else if (*end == '-' && !end[1]) { *c->args_flag = 1; return extract2(c->args, j, EOS); } /* Anything else we dont grok */ else return expand_alias(t, c->args, c->args_flag, NULL); } } /* Handle [plain text] */ else if (!strchr(t, '$') && !strchr(t, '\\')) return m_strdup(t); /* Everything else gets expanded per normal */ else return expand_alias(t, c->args, c->args_flag, NULL); } /* Do this first, its cheap */ else if (is_number(t)) return m_strdup(t); /* Figure out if its a variable reference or a function call */ else { char *after, *ptr, *w, saver = 0; int func = 0; w = LOCAL_COPY(t); after = after_expando(w, 0, &func); if (after) { saver = *after; *after = 0; } if (func) ptr = call_function(w, c->args, c->args_flag); else ptr = get_variable_with_args(w, c->args, c->args_flag); if (!ptr) return m_strdup(empty_string); if (after) *after = saver; return ptr; } return NULL /* */; } __inline static NUMBER getnval (expr_info *c, TOKEN s) { char *t; NUMBER retval; if (c->noeval) return 0; if (!(t = getsval(c, s))) return 0; retval = STON(t); new_free(&t); return retval; } #ifdef notused __inline static BooL getbval (expr_info *c, TOKEN s) { char *t; long retval; if (c->noeval) return 0; if (!(t = getsval(c, s))) return 0; retval = check_val(t); new_free(&t); return retval; } #endif /* ******************** ASSIGNING TO VARIABLES ************************** */ /* * When you have an lvalue (left hand side of an assignment) that needs to * be assigned to, then you can call these functions to assign to it the * appropriate type. The basic operation is to assign and rvalue token * to an lvalue token. But some times you dont always have a tokenized * rvalue, so you can just pass in a raw value and we will tokenize it for * you and go from there. Note that the "result" of an assignment is the * rvalue token. This is then pushed back onto the stack. */ __inline static TOKEN setvar (expr_info *c, TOKEN l, TOKEN r) { char *t = expand_alias(get_token(c, l), c->args, c->args_flag, NULL); char *u = getsval(c, r); char *s; if (!c->noeval) { int old_window_display = window_display; window_display = 0; add_var_alias(t, u); window_display = old_window_display; } s = alloca(strlen(u) + 3); s[0] = '['; strcpy(s+1, u); new_free(&t); new_free(&u); return tokenize(c, s); } __inline static TOKEN setnvar (expr_info *c, TOKEN l, NUMBER v) { return setvar(c, l, tokenize(c, NTOS(v))); } __inline static TOKEN setsvar (expr_info *c, TOKEN l, char *v) { char *s; s = alloca(strlen(v) + 3); s[0] = '['; strcpy(s+1, v); return setvar(c, l, tokenize(c, s)); } /* *************************** OPERAND STACK ************************** */ /* * Adding (shifting) and Removing (reducing) operands from the stack is a * fairly straightforward process. The general way to add an token to * the stack is to pass in its TOKEN index. However, there are some times * when you want to shift a value that has not been tokenized. So you call * one of the other functions that will do this for you. */ __inline static TOKEN pusht (expr_info *c, TOKEN t) { if (c->sp == STACKSZ - 1) { error("Expressions may not have more than 99 operands"); return -1; } else c->sp++; if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("Pushing token [%d] [%s]", t, get_token(c, t)); return ((c->stack[c->sp] = t)); } __inline static TOKEN pushn (expr_info *c, NUMBER val) { return pusht(c, tokenize(c, NTOS(val))); } __inline static TOKEN pushs (expr_info *c, char *val) { char *blah; blah = alloca(strlen(val) + 2); sprintf(blah, "[%s", val); return pusht(c, tokenize(c, blah)); } __inline static TOKEN top (expr_info *c) { if (c->sp < 0) { error("No operands."); return -1; } else return c->stack[c->sp]; } __inline static TOKEN pop (expr_info *c) { if (c->sp < 0) { /* * Attempting to pop more operands than are available * Yeilds empty values. Thats probably the most reasonable * course of action. */ error("Cannot pop operand: no more operands"); return 0; } else return c->stack[c->sp--]; } __inline static double popn (expr_info *c) { char * x = getsval(c, pop(c)); NUMBER i = atof(x); new_free(&x); return i; } /* YOU MUST FREE THE RETURN VALUE FROM THIS FUNCTION */ __inline static char * pops (expr_info *c) { return getsval(c, pop(c)); } __inline static BooL popb (expr_info *c) { char * x = getsval(c, pop(c)); BooL i = check_val(x); new_free(&x); return i; } __inline static void pop2 (expr_info *c, TOKEN *t1, TOKEN *t2) { *t2 = pop(c); *t1 = pop(c); } __inline static void pop2n (expr_info *c, NUMBER *a, NUMBER *b) { TOKEN t1, t2; char *x, *y; pop2(c, &t1, &t2); x = getsval(c, t1); y = getsval(c, t2); *a = STON(x); *b = STON(y); new_free(&x); new_free(&y); } __inline static void pop2s (expr_info *c, char **s, char **t) { TOKEN t1, t2; char *x, *y; pop2(c, &t1, &t2); x = getsval(c, t1); y = getsval(c, t2); *s = x; *t = y; } __inline static void pop2b (expr_info *c, BooL *a, BooL *b) { TOKEN t1, t2; char *x, *y; pop2(c, &t1, &t2); x = getsval(c, t1); y = getsval(c, t2); *a = check_val(x); *b = check_val(y); new_free(&x); new_free(&y); } __inline static void pop2n_a (expr_info *c, NUMBER *a, NUMBER *b, TOKEN *v) { TOKEN t1, t2; char *x, *y; pop2(c, &t1, &t2); x = getsval(c, t1); y = getsval(c, t2); *a = STON(x); *b = STON(y); *v = t1; new_free(&x); new_free(&y); } __inline static void pop2s_a (expr_info *c, char **s, char **t, TOKEN *v) { TOKEN t1, t2; char *x, *y; pop2(c, &t1, &t2); x = getsval(c, t1); y = getsval(c, t2); *s = x; *t = y; *v = t1; } #if notused __inline static void pop2b_a (expr_info *c, BooL *a, BooL *b, TOKEN *v) { TOKEN t1, t2; char *x, *y; pop2(c, &t1, &t2); x = getsval(c, t1); y = getsval(c, t2); *a = check_val(x); *b = check_val(y); *v = t1; new_free(&x); new_free(&y); } #endif __inline static void pop3 (expr_info *c, NUMBER *a, TOKEN *v, TOKEN *w) { TOKEN t1, t2, t3; char *x; t3 = pop(c); t2 = pop(c); t1 = pop(c); x = getsval(c, t1); *a = STON(x); *v = t2; *w = t3; new_free(&x); } /* * This is the reducer. It takes the relevant arguments off the argument * stack and then performs the neccesary operation on them. */ void op (expr_info *cx, int what) { NUMBER a, b; BooL c, d; char *s, *t; TOKEN v, w; if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("Reducing last operation..."); if (cx->sp < 0) { error("An operator is missing a required operand"); return; } if (cx->errflag) return; /* Dont parse on an error */ #define BINARY(x) \ { \ pop2n(cx, &a, &b); \ pushn(cx, (x)); \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s (%ld %ld) -> %ld", #x, a, b, (long)x); \ break; \ } #define BINARY_BOOLEAN(x) \ { \ pop2b(cx, &c, &d); \ pushn(cx, (x)); \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s (%ld %ld) -> %ld", #x, c, d, (long)x); \ break; \ } #define BINARY_NOZERO(x) \ { \ pop2n(cx, &a, &b); \ if (b == 0) { \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s (%ld %ld) -> 0", #x, a, b); \ error("Division by zero"); \ pushn(cx, 0); \ } \ else \ { \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s (%ld %ld) -> %ld", #x, a, b, (long)x); \ pushn(cx, (x)); \ } \ break; \ } #define IMPLIED(x) \ { \ pop2n_a(cx, &a, &b, &v); \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s = %s (%ld %ld) -> %ld", \ get_token(cx, v), #x, a, b, x); \ pushn(cx, setnvar(cx, v, (x))); \ break; \ } #define IMPLIED_NOZERO(x) \ { \ pop2n_a(cx, &a, &b, &v); \ if (b == 0) { \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s = %s (%ld %ld) -> 0", \ get_token(cx, v), #x, a, b); \ error("Division by zero"); \ pushn(cx, setnvar(cx, v, 0)); \ } \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s = %s (%ld %ld) -> %ld", \ get_token(cx, v), #x, a, b, x); \ pushn(cx, setnvar(cx, v, (x))); \ break; \ } #define AUTO_UNARY(x, y) \ { \ v = pop(cx); \ b = getnval(cx, v); \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s (%s %ld) -> %ld", \ #x, get_token(cx, v), b, (x)); \ setnvar(cx, v, (x)); \ pushn(cx, (y)); \ break; \ } #define dpushn(x1,x2,y1) \ { \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ { \ debugyell("O: COMPARE"); \ debugyell("O: %s -> %d", #x2, (x2)); \ } \ pushn(x1,y1); \ } #define COMPARE(x, y) \ { \ pop2s(cx, &s, &t); \ if ((a = STON(s)) && (b = STON(t))) \ { \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s (%ld %ld) -> %d", #x, a, b, (x)); \ if ((x)) dpushn(cx, x, 1) \ else dpushn(cx, x, 0) \ } \ else \ { \ if (x_debug & DEBUG_NEW_MATH_DEBUG) \ debugyell("O: %s (%s %s) -> %d", #x, s, t, (y)); \ if ((y)) dpushn(cx, y, 1) \ else dpushn(cx, y, 0) \ } \ new_free(&s); \ new_free(&t); \ break; \ } switch (what) { /* Simple unary prefix operators */ case NOT: c = popb(cx); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: !%ld -> %d", c, !c); pushn(cx, !c); break; case COMP: a = popn(cx); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell(": ~%ld -> %ld", a, ~a); pushn(cx, ~a); break; case UPLUS: a = popn(cx); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: +%ld -> %ld", a, a); pushn(cx, a); break; case UMINUS: a = popn(cx); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: -%ld -> %ld", a, -a); pushn(cx, -a); break; case STRLEN: s = pops(cx); a = strlen(s); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: @(%s) -> %ld", s, a); pushn(cx, a); new_free(&s); break; case WORDC: s = pops(cx); a = word_count(s); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: #(%s) -> %ld", s, a); pushn(cx, a); new_free(&s); break; case DEREF: { char *buffer = NULL, *tmp; if (top(cx) == MAGIC_TOKEN) break; /* Dont do anything */ s = pops(cx); tmp = expand_alias(s, cx->args, cx->args_flag, NULL); alias_special_char(&buffer, tmp, cx->args, NULL, cx->args_flag); if (buffer == NULL) buffer = m_strdup(empty_string); *cx->args_flag = 1; pushs(cx, buffer); new_free(&buffer); new_free(&tmp); break; } /* (pre|post)(in|de)crement operators. */ case PREPLUS: AUTO_UNARY(b + 1, b + 1) case PREMINUS: AUTO_UNARY(b - 1, b - 1) case POSTPLUS: AUTO_UNARY(b + 1, b) case POSTMINUS: AUTO_UNARY(b - 1, b) /* Simple binary operators */ case AND: BINARY(a & b) case XOR: BINARY(a ^ b) case OR: BINARY(a | b) case PLUS: BINARY(a + b) case MINUS: BINARY(a - b) case MUL: BINARY(a * b) case POWER: BINARY(pow(a, b)) case SHLEFT: BINARY(a << b) case SHRIGHT: BINARY(a >> b) case DIV: BINARY_NOZERO(a / b) case MOD: BINARY_NOZERO(a % b) case DAND: BINARY_BOOLEAN((long)(c && d)) case DOR: BINARY_BOOLEAN((long)(c || d)) case DXOR: BINARY_BOOLEAN((long)((c && !d) || (!c && d))) case STRCAT: pop2s(cx, &s, &t); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: (%s) ## (%s) -> %s%s", s, t, s, t); malloc_strcat(&s, t); pushs(cx, s); new_free(&s); new_free(&t); break; /* Assignment operators */ case PLUSEQ: IMPLIED(a + b) case MINUSEQ: IMPLIED(a - b) case MULEQ: IMPLIED(a * b) case POWEREQ: IMPLIED((long)pow(a, b)) case DIVEQ: IMPLIED_NOZERO(a / b) case MODEQ: IMPLIED_NOZERO(a % b) case ANDEQ: IMPLIED(a & b) case XOREQ: IMPLIED(a ^ b) case OREQ: IMPLIED(a | b) case SHLEFTEQ: IMPLIED(a << b) case SHRIGHTEQ: IMPLIED(a >> b) case DANDEQ: IMPLIED((long)(c && d)) case DOREQ: IMPLIED((long)(c || d)) case DXOREQ: IMPLIED((long)((c && !d) || (!c && d))) case STRCATEQ: pop2s_a(cx, &s, &t, &v); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s = (%s ## %s) -> %s%s", get_token(cx, v), s, t, s, t); malloc_strcat(&s, t); pusht(cx, setsvar(cx, v, s)); new_free(&s); new_free(&t); break; case STRPREEQ: pop2s_a(cx, &s, &t, &v); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s = (%s ## %s) -> %s%s", get_token(cx, v), t, s, t, s); malloc_strcat(&t, s); pusht(cx, setsvar(cx, v, t)); new_free(&s); new_free(&t); break; case EQ: pop2(cx, &v, &w); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s = (%s)", get_token(cx, v), get_token(cx, w)); pusht(cx, setvar(cx, v, w)); break; case SWAP: { char *vval, *wval; pop2(cx, &v, &w); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s <=> %s", get_token(cx, v), get_token(cx, w)); vval = getsval(cx, v); /* lhs variable */ wval = getsval(cx, w); /* rhs variable */ setsvar(cx, w, vval); pusht(cx, setsvar(cx, v, wval)); new_free(&vval); new_free(&wval); break; } /* Comparison operators */ case DEQ: pop2s(cx, &s, &t); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s == %s -> %d", s, t, !!my_stricmp(s, t)); if (my_stricmp(s, t) == 0) pushn(cx, 1); else pushn(cx, 0); new_free(&s); new_free(&t); break; case NEQ: pop2s(cx, &s, &t); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s != %s -> %d", s, t, !my_stricmp(s, t)); if (my_stricmp(s, t) != 0) pushn(cx, 1); else pushn(cx, 0); new_free(&s); new_free(&t); break; case MATCH: pop2s(cx, &s, &t); a = !!wild_match(t, s); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s =~ %s -> %ld", s, t, a); pushn(cx, a); new_free(&s); new_free(&t); break; case NOMATCH: pop2s(cx, &s, &t); a = !wild_match(t, s); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s !~ %s -> %ld", s, t, a); pushn(cx, a); new_free(&s); new_free(&t); break; case LES: COMPARE(a < b, my_stricmp(s, t) < 0) case LEQ: COMPARE(a <= b, my_stricmp(s, t) <= 0) case GRE: COMPARE(a > b, my_stricmp(s, t) > 0) case GEQ: COMPARE(a >= b, my_stricmp(s, t) >= 0) /* Miscelaneous operators */ case QUEST: pop3(cx, &a, &v, &w); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %ld ? %s : %s -> %s", a, get_token(cx, v), get_token(cx, w), (a) ? get_token(cx, v) : get_token(cx, w)); pusht(cx, (a) ? v : w); break; case COLON: break; case COMMA: v = pop(cx); w = pop(cx); if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("O: %s , %s -> %s", get_token(cx, w), get_token(cx, v), get_token(cx, v)); pusht(cx, v); break; default: error("Unknown operator or out of operators"); return; } } /***************************************************************************/ /* * THE LEXER */ static int dummy = 1; int lexerr (expr_info *c, char *format, ...) { char buffer[BIG_BUFFER_SIZE + 1]; va_list a; va_start(a, format); vsnprintf(buffer, BIG_BUFFER_SIZE, format, a); va_end(a); error("%s", buffer); c->errflag = 1; return EOI; } /* * 'operand' is state information that tells us about what the next token * is expected to be. When a binary operator is lexed, then the next token * is expected to be either a unary operator or an operand. So in this * case 'operand' is set to 1. When an operand is lexed, then the next token * is expected to be a binary operator, so 'operand' is set to 0. */ __inline int check_implied_arg (expr_info *c) { if (c->operand == 2) { pusht(c, MAGIC_TOKEN); /* XXXX Bleh */ c->operand = 0; *c->args_flag = 1; return 0; } return c->operand; } __inline TOKEN operator (expr_info *c, char *x, int y, TOKEN z) { check_implied_arg(c); if (c->operand) return lexerr(c, "A binary operator (%s) was found " "where an operand was expected", x); c->ptr += y; c->operand = 1; return z; } __inline TOKEN unary (expr_info *c, char *x, int y, TOKEN z) { if (!c->operand) return lexerr(c, "An operator (%s) was found where " "an operand was expected", x); c->ptr += y; c->operand = dummy; return z; } /* * This finds and extracts the next token in the expression */ static int zzlex (expr_info *c) { char *start = c->ptr; #define OPERATOR(x, y, z) return operator(c, x, y, z); #define UNARY(x, y, z) return unary(c, x, y, z); dummy = 1; if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("Parsing next token from: [%s]", c->ptr); for (;;) { switch (*(c->ptr++)) { case '(': c->operand = 1; return M_INPAR; case ')': /* * If we get a close paren and the lexer is expecting * an operand, then obviously thats a syntax error. * But we gently just insert the empty value as the * rhs for the last operand and hope it all works out. */ if (check_implied_arg(c)) pusht(c, 0); c->operand = 0; return M_OUTPAR; case '+': { /* * Note: In general, any operand that depends on * whether it is a unary or binary operator based * upon the context is required to call the func * 'check_implied_arg' to solidify the context. * That is because some operators are ambiguous, * And if you see (# + 4), it can only be determined * on the fly how to lex that. */ check_implied_arg(c); if (*c->ptr == '+' && (c->operand || !isalnum((unsigned char)*c->ptr))) { c->ptr++; return c->operand ? PREPLUS : POSTPLUS; } else if (*c->ptr == '=') OPERATOR("+=", 1, PLUSEQ) else if (c->operand) UNARY("+", 0, UPLUS) else OPERATOR("+", 0, PLUS) } case '-': { check_implied_arg(c); if (*c->ptr == '-' && (c->operand || !isalnum((unsigned char)*c->ptr))) { c->ptr++; return (c->operand) ? PREMINUS : POSTMINUS; } else if (*c->ptr == '=') OPERATOR("-=", 1, MINUSEQ) else if (c->operand) UNARY("-", 0, UMINUS) else OPERATOR("-", 0, MINUS) } case '*': { if (*c->ptr == '*') { c->ptr++; if (*c->ptr == '=') OPERATOR("**=", 1, POWEREQ) else OPERATOR("**", 0, POWER) } else if (*c->ptr == '=') OPERATOR("*=", 1, MULEQ) else if (c->operand) { dummy = 2; UNARY("*", 0, DEREF) } else OPERATOR("*", 0, MUL) } case '/': { if (*c->ptr == '=') OPERATOR("/=", 1, DIVEQ) else OPERATOR("/", 0, DIV) } case '%': { if (*c->ptr == '=') OPERATOR("%=", 1, MODEQ) else OPERATOR("%", 0, MOD) } case '!': { if (*c->ptr == '=') OPERATOR("!=", 1, NEQ) else if (*c->ptr == '~') OPERATOR("!~", 1, NOMATCH) else UNARY("!", 0, NOT) } case '~': UNARY("~", 0, COMP) case '&': { if (*c->ptr == '&') { c->ptr++; if (*c->ptr == '=') OPERATOR("&&=", 1, DANDEQ) else OPERATOR("&&", 0, DAND) } else if (*c->ptr == '=') OPERATOR("&=", 1, ANDEQ) else OPERATOR("&", 0, AND) } case '|': { if (*c->ptr == '|') { c->ptr++; if (*c->ptr == '=') OPERATOR("||=", 1, DOREQ) else OPERATOR("||", 0, DOR) } else if (*c->ptr == '=') OPERATOR("|=", 1, OREQ) else OPERATOR("|", 0, OR) } case '^': { if (*c->ptr == '^') { c->ptr++; if (*c->ptr == '=') OPERATOR("^^=", 1, DXOREQ) else OPERATOR("^^", 0, DXOR) } else if (*c->ptr == '=') OPERATOR("^=", 1, XOREQ) else OPERATOR("^", 0, XOR) } case '#': { check_implied_arg(c); if (*c->ptr == '#') { c->ptr++; if (*c->ptr == '=') OPERATOR("##=", 1, STRCATEQ) else OPERATOR("##", 0, STRCAT) } else if (*c->ptr == '=') OPERATOR("#=", 1, STRCATEQ) else if (*c->ptr == '~') OPERATOR("#~", 1, STRPREEQ) else if (c->operand) { dummy = 2; UNARY("#", 0, WORDC) } else OPERATOR("#", 0, STRCAT) } case '@': dummy = 2; UNARY("@", 0, STRLEN) case '<': { if (*c->ptr == '<') { c->ptr++; if (*c->ptr == '=') OPERATOR("<<=", 1, SHLEFTEQ) else OPERATOR("<<", 0, SHLEFT) } else if (*c->ptr == '=') { c->ptr++; if (*c->ptr == '>') OPERATOR("<=>", 1, SWAP) else OPERATOR("<=", 0, LEQ) } else OPERATOR("<", 0, LES) } case '>': { if (*c->ptr == '>') { c->ptr++; if (*c->ptr == '=') OPERATOR(">>=", 1, SHRIGHTEQ) else OPERATOR(">>", 0, SHRIGHT) } else if (*c->ptr == '=') OPERATOR(">=", 1, GEQ) else OPERATOR(">", 0, GRE) } case '=': if (*c->ptr == '=') OPERATOR("==", 1, DEQ) else if (*c->ptr == '~') OPERATOR("=~", 1, MATCH) else OPERATOR("=", 0, EQ) case '?': c->operand = 1; return QUEST; case ':': /* * I dont want to hear anything from you anti-goto * bigots out there. ;-) If you can't figure out * what this does, you ought to give up programming. * And a big old :p to everyone who insisted that * i support this horrid hack. */ if (c->operand) goto handle_expando; c->operand = 1; return COLON; case ',': /* Same song, second verse. */ if (c->operand) goto handle_expando; c->operand = 1; return COMMA; case '\0': check_implied_arg(c); c->operand = 1; c->ptr--; return EOI; case '[': { char *p = c->ptr - 1; char oc = 0; if (!c->operand) return lexerr(c, "Misplaced [ token"); if ((c->ptr = MatchingBracket(p + 1, '[', ']'))) { oc = *c->ptr; *c->ptr = 0; } else c->ptr = empty_string; c->last_token = tokenize(c, p); if (oc) *c->ptr++ = oc; c->operand = 0; return ID; } case ' ': case '\t': case '\n': start++; break; /* * Handle literal numbers */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { char *end; char endc; c->operand = 0; c->ptr--; strtod(c->ptr, &end); endc = *end; *end = 0; c->last_token = tokenize(c, c->ptr); *end = endc; c->ptr = end; return ID; } /* * Handle those weirdo $-values */ case '$': continue; /* * Handle generic lvalue operands */ default: handle_expando: { char *end; char endc; c->operand = 0; c->ptr--; if ((end = after_expando_special(c))) { endc = *end; *end = 0; c->last_token = tokenize(c, start); *end = endc; c->ptr = end; } else { c->last_token = 0; /* Empty token */ c->ptr = empty_string; } if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("After token: [%s]", c->ptr); return ID; } } } } /* * mathparse -- this is the state machine that actually parses the * expression. The parsing is done through a shift-reduce mechanism, * and all the precedence levels lower than 'pc' are evaluated. */ static void mathparse (expr_info *c, int pc) { int otok, onoeval; /* * Drop out of parsing if an error has occured */ if (c->errflag) return; /* * Get the next token in the expression */ c->mtok = zzlex(c); /* * For as long as the next operator indicates a shift operation... */ while (prec[c->mtok] <= pc) { /* Drop out if an error has occured */ if (c->errflag) return; /* * Figure out what to do with this token that needs * to be shifted. */ switch (c->mtok) { case ID: if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("Parsed identifier token [%s]", get_token(c, c->last_token)); if (c->noeval) pusht(c, 0); else pusht(c, c->last_token); break; /* * An open-parenthesis indicates that we should * recursively evaluate the inside of the paren-set. */ case M_INPAR: { if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("Parsed open paren"); mathparse(c, TOPPREC); /* * Of course if the expression ends without * a matching rparen, then we whine about it. */ if (c->mtok != M_OUTPAR) { if (!c->errflag) error("')' expected"); return; } break; } /* * A question mark requires that we check for short * circuiting. We check the lhs, and if it is true, * then we evaluate the lhs of the colon. If it is * false then we just parse the lhs of the colon and * evaluate the rhs of the colon. */ case QUEST: { long u = popb(c); pushn(c, u); if (!u) c->noeval++; mathparse(c, prec[QUEST] - 1); if (!u) c->noeval--; else c->noeval++; mathparse(c, prec[QUEST]); if (u) c->noeval--; op(c, QUEST); continue; } /* * All other operators handle normally */ default: { /* Save state */ otok = c->mtok; onoeval = c->noeval; /* * Check for short circuiting. */ if (assoc[otok] == BOOL) { if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("Parsed short circuit operator"); switch (otok) { case DAND: case DANDEQ: { long u = popb(c); pushn(c, u); if (!u) c->noeval++; break; } case DOR: case DOREQ: { long u = popb(c); pushn(c, u); if (u) c->noeval++; break; } } } if (x_debug & DEBUG_NEW_MATH_DEBUG) debugyell("Parsed operator of type [%d]", otok); /* * Parse the right hand side through * recursion if we're doing things R->L. */ mathparse(c, prec[otok] - (assoc[otok] != RL)); /* * Then reduce this operation. */ c->noeval = onoeval; op(c, otok); continue; } } /* * Grab the next token */ c->mtok = zzlex(c); } } /* * This is the new math parser. It sets up an execution context, which * contains sundry information like all the extracted tokens, intermediate * tokens, shifted tokens, and the like. The expression context is passed * around from function to function, each function is totaly independant * of state information stored in global variables. Therefore, this math * parser is re-entrant safe. */ static char * matheval (char *s, const char *args, int *args_flag) { expr_info context; char * ret; /* Sanity check */ if (!s || !*s) return m_strdup(empty_string); /* Create new state */ setup_expr_info(&context); context.ptr = s; context.args = args; context.args_flag = args_flag; /* Actually do the parsing */ mathparse(&context, TOPPREC); /* Check for error */ if (context.errflag) { ret = m_strdup(empty_string); goto cleanup; } /* Check for leftover operands */ if (context.sp) error("The expression has too many operands"); if (x_debug & DEBUG_NEW_MATH_DEBUG) { int i; debugyell("Terms left: %d", context.sp); for (i = 0; i <= context.sp; i++) debugyell("Term [%d]: [%s]", i, get_token(&context, context.stack[i])); } /* Get the return value */ ret = getsval(&context, pop(&context)); cleanup: /* Clean up and restore order */ destroy_expr_info(&context); if (internal_debug & DEBUG_EXPANSIONS && !in_debug_yell) debugyell("Returning [%s]", ret); /* Return the result */ return ret; } /* * after_expando_special: This is a special version of after_expando that * can handle parsing out lvalues in expressions. Due to the eclectic nature * of lvalues in expressions, this is quite a bit different than the normal * after_expando, requiring a different function. Ugh. * * This replaces some much more complicated logic strewn * here and there that attempted to figure out just how long an expando * name was supposed to be. Well, now this changes that. This will slurp * up everything in 'start' that could possibly be put after a $ that could * result in a syntactically valid expando. All you need to do is tell it * if the expando is an rvalue or an lvalue (it *does* make a difference) */ static char * after_expando_special (expr_info *c) { char *start; char *rest; int call; if (!(start = c->ptr)) return c->ptr; for (;;) { rest = after_expando(start, 0, &call); if (*rest != '$') break; start = rest + 1; } /* * All done! */ return rest; }