1841 lines
40 KiB
C
1841 lines
40 KiB
C
/*
|
|
* $Id: expr2.c 3 2008-02-25 09:49:14Z keaston $
|
|
* 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 3 2008-02-25 09:49:14Z keaston $
|
|
*/
|
|
|
|
#include "irc.h"
|
|
|
|
#include <math.h>
|
|
#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 /* <whatever> */;
|
|
}
|
|
|
|
__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;
|
|
}
|
|
|