git-svn-id: svn://svn.code.sf.net/p/bitchx/code/tags/ircii-pana-1.1-final@1 13b04d17-f746-0410-82c6-800466cd88b0
1902 lines
42 KiB
C
1902 lines
42 KiB
C
/*
|
||
* expr.c -- The expression mode parser and the textual mode parser
|
||
* #included by alias.c -- DO NOT DELETE
|
||
*
|
||
* Copyright 1990 Michael Sandrof
|
||
* Copyright 1997 EPIC Software Labs
|
||
* See the COPYRIGHT file for more info
|
||
*
|
||
* $Id: expr.c,v 1.1.1.2 2003/07/28 07:00:36 root Exp $
|
||
*/
|
||
|
||
#include "irc.h"
|
||
#include <math.h>
|
||
|
||
#undef PANA_EXP
|
||
#undef PANA_EXP1
|
||
|
||
/* Function decls */
|
||
static void TruncateAndQuote(char **, const char *, int, const char *, char);
|
||
static void do_alias_string (char *, char *);
|
||
|
||
char *alias_string = NULL;
|
||
|
||
/************************** EXPRESSION MODE PARSER ***********************/
|
||
/* canon_number: canonicalizes number to something relevant */
|
||
/* If FLOATING_POINT_MATH isnt set, it truncates it to an integer */
|
||
static char *canon_number (char *input)
|
||
{
|
||
int end = strlen(input);
|
||
|
||
if (end)
|
||
end--;
|
||
else
|
||
return input; /* nothing to do */
|
||
|
||
if (get_int_var(FLOATING_POINT_MATH_VAR))
|
||
{
|
||
/* remove any trailing zeros */
|
||
while (input[end] == '0')
|
||
end--;
|
||
|
||
/* If we removed all the zeros and all that is
|
||
left is the decimal point, remove it too */
|
||
if (input[end] == '.')
|
||
end--;
|
||
|
||
input[end+1] = 0;
|
||
}
|
||
else
|
||
{
|
||
char *dot = strchr(input, '.');
|
||
if (dot)
|
||
*dot = 0;
|
||
}
|
||
|
||
return input;
|
||
}
|
||
|
||
|
||
/* Given a pointer to an operator, find the last operator in the string */
|
||
static char *lastop (char *ptr)
|
||
{
|
||
/* dont ask why i put the space in there. */
|
||
while (ptr[1] && strchr("!=<>&^|#+/%,-* ", ptr[1]))
|
||
ptr++;
|
||
return ptr;
|
||
}
|
||
|
||
|
||
|
||
|
||
#define NU_EXPR 0
|
||
#define NU_ASSN 1
|
||
#define NU_TERT 2
|
||
#define NU_CONJ 3
|
||
#define NU_BITW 4
|
||
#define NU_COMP 5
|
||
#define NU_ADD 6
|
||
#define NU_MULT 7
|
||
#define NU_UNIT 8
|
||
|
||
/*
|
||
* Cleaned up/documented by Jeremy Nelson, Feb 1996.
|
||
*
|
||
* What types of operators does ircII (EPIC) support?
|
||
*
|
||
* The client handles as 'special cases" the () and []
|
||
* operators which have ircII-specific meanings.
|
||
*
|
||
* The general-purpose operators are:
|
||
*
|
||
* additive class: +, -, ++, --, +=, -=
|
||
* multiplicative class: *, /, %, *=, /=, %=
|
||
* string class: ##, #=
|
||
* and-class: &, &&, &=
|
||
* or-class: |, ||, |=
|
||
* xor-class: ^, ^^, ^=
|
||
* equality-class: ==, !=
|
||
* relative-class: <, <=, >, >=
|
||
* negation-class: ~ (1s comp), ! (2s comp)
|
||
* selector-class: ?: (tertiary operator)
|
||
* list-class: , (comma)
|
||
*
|
||
* Backslash quotes any character not within [] or (),
|
||
* which have their own quoting rules (*sigh*)
|
||
*/
|
||
char *BX_next_unit (char *str, const char *args, int *arg_flag, int stage)
|
||
{
|
||
register char *ptr; /* pointer to the current op */
|
||
|
||
char *ptr2, /* used to point matching brackets */
|
||
*right, /* used to denote end of bracket-set */
|
||
*lastc, /* used to denote end of token-set */
|
||
*tmp = NULL,
|
||
op; /* the op were working on */
|
||
int got_sloshed = 0, /* If the last char was a slash */
|
||
display;
|
||
|
||
char *result1 = NULL, /* raw lefthand-side of operator */
|
||
*result2 = NULL, /* raw righthand-side of operator */
|
||
*varname = NULL; /* Where we store varnames */
|
||
long value1 = 0, /* integer value of lhs */
|
||
value2 = 0, /* integer value of rhs */
|
||
value3 = 0; /* integer value of operation */
|
||
double dvalue1 = 0.0, /* floating value of lhs */
|
||
dvalue2 = 0.0, /* floating value of rhs */
|
||
dvalue3 = 0.0; /* floating value of operation */
|
||
#ifdef PANA_EXP
|
||
union
|
||
{
|
||
char s[4];
|
||
unsigned long t;
|
||
} strlong;
|
||
#endif
|
||
|
||
/*
|
||
* These macros make my life so much easier, its not funny.
|
||
*/
|
||
|
||
/*
|
||
* An implied operation is one where the left-hand argument is
|
||
* "implied" because it is both an lvalue and an rvalue. We use
|
||
* the rvalue of the left argument when we do the actual operation
|
||
* then we do an assignment to the lvalue of the left argument.
|
||
*/
|
||
#define SETUP_IMPLIED(var1, var2, func) \
|
||
{ \
|
||
/* Save the type of op, skip over the X=. */ \
|
||
op = *ptr; \
|
||
*ptr++ = '\0'; \
|
||
ptr++; \
|
||
\
|
||
/* Figure out the variable name were working on */ \
|
||
varname = expand_alias(str, args, arg_flag, NULL); \
|
||
lastc = varname + strlen(varname) - 1; \
|
||
while (lastc > varname && *lastc == ' ') \
|
||
*lastc-- = '\0'; \
|
||
while (my_isspace(*varname)) \
|
||
varname++; \
|
||
\
|
||
/* Get the value of the implied argument */ \
|
||
result1 = get_variable(varname); \
|
||
var1 = (result1 && *result1) ? func (result1) : 0; \
|
||
new_free(&result1); \
|
||
\
|
||
/* Get the value of the explicit argument */ \
|
||
result2 = next_unit(ptr, args, arg_flag, stage); \
|
||
var2 = func (result2); \
|
||
new_free(&result2); \
|
||
}
|
||
|
||
/*
|
||
* This make sure the calculated value gets back into the lvalue of
|
||
* the left operator, and turns the display back on.
|
||
*/
|
||
#define CLEANUP_IMPLIED() \
|
||
{ \
|
||
/* Make sure were really an implied case */ \
|
||
if (ptr[-1] == '=' && stage == NU_ASSN) \
|
||
{ \
|
||
/* Turn off the display */ \
|
||
display = window_display; \
|
||
window_display = 0; \
|
||
\
|
||
/* Make sure theres an lvalue */ \
|
||
if (*varname) \
|
||
add_var_alias(varname, tmp); \
|
||
else \
|
||
debugyell("Invalid assignment: No lvalue"); \
|
||
\
|
||
/* Turn the display back on */ \
|
||
window_display = display; \
|
||
} \
|
||
new_free(&varname); \
|
||
}
|
||
|
||
/*
|
||
* This sets up an ordinary explicit binary operation, where both
|
||
* arguments are used as rvalues. We just recurse and get their
|
||
* values and then do the operation on them.
|
||
*/
|
||
#define SETUP_BINARY(var1, var2, func) \
|
||
{ \
|
||
/* Save the op were working on cause we clobber it. */ \
|
||
op = *ptr; \
|
||
*ptr++ = '\0'; \
|
||
\
|
||
/* Get the two explicit operands */ \
|
||
result1 = next_unit(str, args, arg_flag, stage); \
|
||
result2 = next_unit(ptr, args, arg_flag, stage); \
|
||
\
|
||
/* Convert them with the specified function */ \
|
||
var1 = func (result1); \
|
||
var2 = func (result2); \
|
||
\
|
||
/* Clean up the mess we created. */ \
|
||
new_free(&result1); \
|
||
new_free(&result2); \
|
||
}
|
||
|
||
|
||
/*
|
||
* This sets up a type-independant section of code for doing an
|
||
* operation when the X and X= forms are both valid.
|
||
*/
|
||
#define SETUP(var1, var2, func, STAGE) \
|
||
{ \
|
||
/* If its an X= op, do an implied operation */ \
|
||
if (ptr[1] == '=' && stage == NU_ASSN) \
|
||
SETUP_IMPLIED(var1, var2, func) \
|
||
\
|
||
/* Else if its just an X op, do a binary operation */ \
|
||
else if (ptr[1] != '=' && stage == STAGE) \
|
||
SETUP_BINARY(var1, var2, func) \
|
||
\
|
||
/* Its not our turn to do this operation, just punt. */ \
|
||
else \
|
||
{ \
|
||
ptr = lastop(ptr); \
|
||
break; \
|
||
} \
|
||
}
|
||
|
||
/* This does a setup for a floating-point operation. */
|
||
#define SETUP_FLOAT_OPERATION(STAGE) \
|
||
SETUP(dvalue1, dvalue2, atof, STAGE)
|
||
|
||
/* This does a setup for an integer operation. */
|
||
#define SETUP_INTEGER_OPERATION(STAGE) \
|
||
SETUP(value1, value2, my_atol, STAGE)
|
||
|
||
/* remove leading spaces */
|
||
while (my_isspace(*str))
|
||
++str;
|
||
|
||
/* If there's nothing there, return it */
|
||
if (!*str)
|
||
return m_strdup(empty_string);
|
||
|
||
|
||
/* find the end of the rest of the expression */
|
||
if ((lastc = str+strlen(str)) > str)
|
||
lastc--;
|
||
|
||
/* and remove trailing spaces */
|
||
while (my_isspace(*lastc))
|
||
*lastc-- = '\0';
|
||
|
||
if (!args)
|
||
args = empty_string;
|
||
|
||
/*
|
||
* If we're in the last parsing level, and this token is in parens,
|
||
* strip the parens and parse the insides immediately.
|
||
*/
|
||
if (stage == NU_UNIT && *lastc == ')' && *str == '(')
|
||
{
|
||
str++, *lastc-- = '\0';
|
||
return next_unit(str, args, arg_flag, NU_EXPR);
|
||
}
|
||
|
||
|
||
/*
|
||
* Ok. now lets get some work done.
|
||
*
|
||
* Starting at the beginning of the string, look for something
|
||
* resembling an operator. This divides the expression into two
|
||
* parts, a lhs and an rhs. The rhs starts at "str", and the
|
||
* operator is at "ptr". So if you do ptr = lastop(ptr), youll
|
||
* end up at the beginning of the rhs, which is the rest of the
|
||
* expression. You can then parse it at the same level as the
|
||
* current level and it will recursively trickle back an rvalue
|
||
* which you can then apply to the lvalue give the operator.
|
||
*
|
||
* PROS: This is a very simplistic setup and not (terribly) confusing.
|
||
* CONS: Every operator is evaluated right-to-left which is *WRONG*.
|
||
*/
|
||
|
||
for (ptr = str; *ptr; ptr++)
|
||
{
|
||
if (got_sloshed)
|
||
{
|
||
got_sloshed = 0;
|
||
continue;
|
||
}
|
||
|
||
switch(*ptr)
|
||
{
|
||
case '\\':
|
||
{
|
||
got_sloshed = 1;
|
||
continue;
|
||
}
|
||
|
||
/*
|
||
* Parentheses have two contexts:
|
||
* 1) (text) is a unary precedence operator. It is nonassoc,
|
||
* and simply parses the insides immediately.
|
||
* 2) text(text) is the function operator. It calls the
|
||
* specified function/alias passing it the given args.
|
||
*/
|
||
case '(':
|
||
{
|
||
int savec = 0;
|
||
/*
|
||
* If we're not in NU_UNIT, then we have a paren-set
|
||
* that (probably) is still an left-operand for some
|
||
* binary op. Anyhow, we just immediately parse the
|
||
* paren-set, as thats the general idea of parens.
|
||
*/
|
||
if (stage != NU_UNIT || ptr == str)
|
||
{
|
||
/*
|
||
* If there is no matching ), gobble up the
|
||
* entire expression.
|
||
*/
|
||
if (!(ptr2 = MatchingBracket(ptr+1, '(', ')')))
|
||
ptr = ptr + strlen(ptr) - 1;
|
||
else
|
||
{
|
||
#ifdef PANA_EXP1
|
||
if ((ptr+1) == ptr2 && stage > NU_ASSN)
|
||
stage = NU_UNIT-1;
|
||
#endif
|
||
ptr = ptr2;
|
||
}
|
||
break;
|
||
}
|
||
|
||
/*
|
||
* and if that token is a left-paren, then we have a
|
||
* function call. We gobble up the arguments and
|
||
* ship it all off to call_function.
|
||
*/
|
||
if ((ptr2 = MatchingBracket(ptr + 1, '(', ')')))
|
||
{
|
||
ptr2++;
|
||
savec = *ptr2;
|
||
*ptr2 = 0;
|
||
}
|
||
|
||
|
||
result1 = call_function(str, args, arg_flag);
|
||
|
||
if (savec)
|
||
*ptr2 = savec;
|
||
/*
|
||
* and what do we do with this value? Why we prepend
|
||
* it to the next token! This is actually a hack
|
||
* that if you have a NON-operator as the next token,
|
||
* it has an interesting side effect:
|
||
* ie:
|
||
* /eval echo ${foobar()4 + 3}
|
||
* where
|
||
* alias foobar {@ function_return = 2}
|
||
*
|
||
* you get '27' as a return value, "as-if" you had done
|
||
*
|
||
* /eval echo ${foobar() ## 4 + 3}
|
||
*
|
||
* Dont depend on this behavior.
|
||
*/
|
||
if (ptr && *ptr)
|
||
{
|
||
malloc_strcat(&result1, ptr);
|
||
result2 = next_unit(result1, args, arg_flag, stage);
|
||
new_free(&result1);
|
||
result1 = result2;
|
||
}
|
||
|
||
return result1;
|
||
}
|
||
|
||
/*
|
||
* Braces are used for anonymous functions:
|
||
* @ condition : {some code} : {some more code}
|
||
*
|
||
* Dont yell at me if you dont think its useful. Im just
|
||
* supporting it because it makes sense. And it saves you
|
||
* from having to declare aliases to do the parts.
|
||
*/
|
||
case '{':
|
||
{
|
||
display = window_display;
|
||
|
||
ptr2 = MatchingBracket(ptr + 1, LEFT_BRACE, RIGHT_BRACE);
|
||
if (!ptr2)
|
||
ptr2 = ptr + strlen(ptr) - 1;
|
||
|
||
if (stage != NU_UNIT)
|
||
{
|
||
ptr = ptr2;
|
||
break;
|
||
}
|
||
|
||
*ptr2++ = 0;
|
||
*ptr++ = 0;
|
||
|
||
/* Taken more or less from call_user_function */
|
||
make_local_stack(NULL/*"anonymous"*/);
|
||
window_display = 0;
|
||
add_local_alias("FUNCTION_RETURN", empty_string);
|
||
window_display = display;
|
||
|
||
will_catch_return_exceptions++;
|
||
parse_line(NULL, ptr, args, 0, 1, 1);
|
||
will_catch_return_exceptions--;
|
||
return_exception = 0;
|
||
|
||
result1 = get_variable("FUNCTION_RETURN");
|
||
destroy_local_stack();
|
||
if (!result1)
|
||
result1 = m_strdup(empty_string);
|
||
|
||
return result1;
|
||
}
|
||
|
||
/*
|
||
* Brackets have two contexts:
|
||
* [text] is the literal-text operator. The contents are
|
||
* not parsed as an lvalue, but as literal text.
|
||
* This also covers the case of the array operator,
|
||
* since it just appends whats in the [] set with what
|
||
* came before it.
|
||
*
|
||
* The literal text operator applies not only to entire
|
||
* tokens, but also to the insides of array qualifiers.
|
||
*/
|
||
case '[':
|
||
{
|
||
#ifdef PANA_EXP
|
||
int got_it = 0;
|
||
#endif
|
||
if (stage != NU_UNIT)
|
||
{
|
||
if (!(ptr2 = MatchingBracket(ptr+1, LEFT_BRACKET, RIGHT_BRACKET)))
|
||
ptr = ptr+strlen(ptr)-1;
|
||
else
|
||
{
|
||
#ifdef PANA_EXP1
|
||
if ((ptr+1) == ptr2 && stage > NU_ASSN)
|
||
stage = NU_UNIT-1;
|
||
#endif
|
||
ptr = ptr2;
|
||
}
|
||
break;
|
||
}
|
||
#ifdef PANA_EXP
|
||
strlong.t = 0;
|
||
{
|
||
#if 0
|
||
<hop> if little endian is ABCD and big endian is DCBA
|
||
<hop> ive heard of middle enddian which is CDAB
|
||
#endif
|
||
memcpy(&strlong.t, ptr, 4);
|
||
if ((strlong.t & 0xfff0ffff) == 0x5d30245b)
|
||
{
|
||
unsigned int k;
|
||
if (!(k = ((strlong.t & 0x000f0000) >> 16) & 0x07))
|
||
{
|
||
result1 = extract2(args, k, k);
|
||
got_it = *arg_flag = 1;
|
||
/* *str = 0;*/
|
||
*ptr = 0;
|
||
ptr = ptr + 4;
|
||
|
||
}
|
||
}
|
||
}
|
||
if (!got_it)
|
||
#endif
|
||
{
|
||
/* ptr points right after the [] set */
|
||
*ptr++ = '\0';
|
||
right = ptr;
|
||
|
||
/*
|
||
* At this point, we check to see if it really is a
|
||
* '[', and if it is, we skip over it.
|
||
*/
|
||
if ((ptr = MatchingBracket(right, LEFT_BRACKET, RIGHT_BRACKET)))
|
||
*ptr++ = '\0';
|
||
|
||
/*
|
||
* Here we expand what is inside the [] set, as
|
||
* literal text.
|
||
*/
|
||
#ifndef NO_CHEATING
|
||
/*
|
||
* Very simple heuristic... If its $<num> or
|
||
* $-<num>, handle it here. Otherwise, if its
|
||
* got no $'s, its a literal, otherwise, do the
|
||
* normal thing.
|
||
*/
|
||
if (*right == '$')
|
||
{
|
||
char *end = NULL;
|
||
long j = strtol(right + 1, &end, 10);
|
||
if (end && !*end)
|
||
{
|
||
if (j < 0)
|
||
result1 = extract2(args, SOS, -j);
|
||
else
|
||
result1 = extract2(args, j, j);
|
||
*arg_flag = 1;
|
||
}
|
||
/*
|
||
* Gracefully handle $X- here, too.
|
||
*/
|
||
else if (*end == '-' && !end[1])
|
||
{
|
||
result1 = extract2(args, j, EOS);
|
||
*arg_flag = 1;
|
||
}
|
||
else
|
||
result1 = expand_alias(right, args, arg_flag, NULL);
|
||
}
|
||
else if (!strchr(right, '$') && !strchr(right, '\\'))
|
||
result1 = m_strdup(right);
|
||
else
|
||
#endif
|
||
result1 = expand_alias(right, args, arg_flag, NULL);
|
||
|
||
}
|
||
/*
|
||
* You need to look closely at this, as this test
|
||
* is actually testing to see if (ptr != str) at the
|
||
* top of this case, which would imply that the []
|
||
* set was an array qualifier to some other variable.
|
||
*
|
||
* Before you ask "how do you know that?" Remember
|
||
* that if (ptr == str) at the beginning of the case,
|
||
* then when we *ptr++ = 0, we would then be setting
|
||
* *str to 0; so testing to see if *str is not zero
|
||
* tells us if (ptr == str) was true or not...
|
||
*/
|
||
if (*str)
|
||
{
|
||
/* array qualifier */
|
||
int size = strlen(str) + (result1 ? strlen(result1) : 0) + (ptr ? strlen(ptr) : 0) + 2;
|
||
|
||
result2 = alloca(size);
|
||
strcpy(result2, str);
|
||
strcat(result2, ".");
|
||
strcat(result2, result1);
|
||
new_free(&result1);
|
||
|
||
/*
|
||
* Notice of unexpected behavior:
|
||
*
|
||
* If $foobar.onetwo is "999"
|
||
* then ${foobar[one]two + 3} is "1002"
|
||
* Dont depend on this behavior.
|
||
*/
|
||
if (ptr && *ptr)
|
||
{
|
||
strcat(result2, ptr);
|
||
result1 = next_unit(result2, args, arg_flag, stage);
|
||
}
|
||
else
|
||
{
|
||
if (!(result1 = get_variable(result2)))
|
||
malloc_strcpy(&result1, empty_string);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Notice of unexpected behavior:
|
||
*
|
||
* If $onetwo is "testing",
|
||
* /eval echo ${[one]two} returns "testing".
|
||
* Dont depend on this behavior.
|
||
*/
|
||
else if (ptr && *ptr)
|
||
{
|
||
malloc_strcat(&result1, ptr);
|
||
result2 = next_unit(result1, args, arg_flag, stage);
|
||
new_free(&result1);
|
||
result1 = result2;
|
||
}
|
||
|
||
/*
|
||
* result1 shouldnt ever be pointing at an empty
|
||
* string here, but if it is, we just malloc_strcpy
|
||
* a new empty_string into it. This fixes an icky
|
||
* memory hog bug my making sure that a (long) string
|
||
* with a leading null gets replaced by a (small)
|
||
* string of size one. Capish?
|
||
*/
|
||
if (!*result1)
|
||
malloc_strcpy(&result1, empty_string);
|
||
|
||
return result1;
|
||
}
|
||
|
||
/*
|
||
* The addition and subtraction operators have four contexts:
|
||
* 1) + is a binary additive operator if there is an rvalue
|
||
* as the token to the left (ignored)
|
||
* 2) + is a unary magnitudinal operator if there is no
|
||
* rvalue to the left.
|
||
* 3) ++text or text++ is a unary pre/post in/decrement
|
||
* operator.
|
||
* 4) += is the binary implied additive operator.
|
||
*/
|
||
case '-':
|
||
#if 0
|
||
if (ptr[1] && (ptr[1] == '>'))
|
||
{
|
||
char *ptr3;
|
||
char savec;
|
||
varname = str, *ptr = 0;
|
||
ptr++; ptr++;
|
||
if (!*ptr)
|
||
break;
|
||
if ((ptr3 = MatchingBracket(varname + 1, '(', ')')))
|
||
{
|
||
ptr3++;
|
||
savec = *ptr3;
|
||
*ptr3 = 0;
|
||
}
|
||
|
||
/* now pointing to the member check if valid */
|
||
if (!(ptr2 = lookup_member(varname, ptr3, ptr, args)))
|
||
break;
|
||
|
||
}
|
||
#endif
|
||
case '+':
|
||
{
|
||
if (ptr[1] == ptr[0])
|
||
{
|
||
int prefix;
|
||
long r;
|
||
|
||
if (stage != NU_UNIT)
|
||
{
|
||
/*
|
||
* only one 'ptr++' because a 2nd
|
||
* one is done at the top of the
|
||
* loop after the 'break'.
|
||
*/
|
||
ptr++;
|
||
break;
|
||
}
|
||
|
||
if (ptr == str) /* prefix */
|
||
prefix = 1, ptr2 = ptr + 2;
|
||
else /* postfix */
|
||
prefix = 0, ptr2 = str, *ptr++ = 0;
|
||
|
||
varname = expand_alias(ptr2, args, arg_flag, NULL);
|
||
upper(varname);
|
||
|
||
if (!(result1 = get_variable(varname)))
|
||
malloc_strcpy(&result1,zero);
|
||
|
||
r = my_atol(result1);
|
||
if (*ptr == '+')
|
||
r++;
|
||
else
|
||
r--;
|
||
|
||
display = window_display;
|
||
window_display = 0;
|
||
add_var_alias(varname,ltoa(r));
|
||
window_display = display;
|
||
|
||
if (!prefix)
|
||
r--;
|
||
|
||
new_free(&result1);
|
||
new_free(&varname);
|
||
return m_strdup(ltoa(r));
|
||
}
|
||
|
||
/* Unary op is ignored */
|
||
else if (ptr == str)
|
||
break;
|
||
#if 0
|
||
if (get_int_var(FLOATING_POINT_MATH_VAR))
|
||
#endif
|
||
{
|
||
SETUP_FLOAT_OPERATION(NU_ADD)
|
||
|
||
if (op == '-')
|
||
dvalue3 = dvalue1 - dvalue2;
|
||
else
|
||
dvalue3 = dvalue1 + dvalue2;
|
||
|
||
tmp = m_sprintf("%f", dvalue3);
|
||
canon_number(tmp);
|
||
|
||
}
|
||
#if 0
|
||
else
|
||
{
|
||
SETUP_INTEGER_OPERATION(NU_ADD)
|
||
|
||
if (op == '-')
|
||
value3 = value1 - value2;
|
||
else
|
||
value3 = value1 + value2;
|
||
|
||
tmp = m_strdup(ltoa(value3));
|
||
}
|
||
#endif
|
||
CLEANUP_IMPLIED()
|
||
return tmp;
|
||
}
|
||
|
||
|
||
/*
|
||
* The Multiplication operators have two contexts:
|
||
* 1) * is a binary multiplicative op
|
||
* 2) *= is the implied binary multiplicative op
|
||
*/
|
||
case '/':
|
||
case '*':
|
||
case '%':
|
||
{
|
||
/* Unary op is ignored */
|
||
if (ptr == str)
|
||
break;
|
||
|
||
/* default value on error */
|
||
dvalue3 = 0.0;
|
||
|
||
/*
|
||
* XXXX This is an awful hack to support
|
||
* the ** (pow) operator. Sorry.
|
||
*/
|
||
if (ptr[0] == '*' && ptr[1] == '*' && stage == NU_MULT)
|
||
{
|
||
*ptr++ = '\0';
|
||
SETUP_BINARY(dvalue1, dvalue2, atof)
|
||
return m_sprintf("%f", pow(dvalue1, dvalue2));
|
||
}
|
||
|
||
|
||
SETUP_FLOAT_OPERATION(NU_MULT)
|
||
|
||
if (op == '*')
|
||
dvalue3 = dvalue1 * dvalue2;
|
||
else
|
||
{
|
||
if (dvalue2 == 0.0)
|
||
debugyell("Division by zero!");
|
||
|
||
else if (op == '/')
|
||
dvalue3 = dvalue1 / dvalue2;
|
||
else
|
||
dvalue3 = (int)dvalue1 % (int)dvalue2;
|
||
}
|
||
|
||
tmp = m_sprintf("%f", dvalue3);
|
||
canon_number(tmp);
|
||
CLEANUP_IMPLIED()
|
||
return tmp;
|
||
}
|
||
|
||
|
||
/*
|
||
* The # operator has three contexts:
|
||
* 1) ## is a binary string catenation operator
|
||
* 2) #= is an implied string catenation operator
|
||
* 3) #~ is an implied string prepend operator
|
||
*/
|
||
case '#':
|
||
{
|
||
if (ptr[1] == '#' && stage == NU_ADD)
|
||
{
|
||
*ptr++ = '\0';
|
||
ptr++;
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
malloc_strcat(&result1, result2);
|
||
new_free(&result2);
|
||
return result1;
|
||
}
|
||
else if (ptr[1] == '~' && stage == NU_ASSN)
|
||
{
|
||
char *sval1, *sval2;
|
||
|
||
ptr[1] = '=';
|
||
SETUP_IMPLIED(sval1, sval2, m_strdup);
|
||
malloc_strcat(&sval2, sval1);
|
||
new_free(&sval1);
|
||
tmp = sval2;
|
||
CLEANUP_IMPLIED()
|
||
return sval2;
|
||
}
|
||
else if (ptr[1] == '=' && stage == NU_ASSN)
|
||
{
|
||
char *sval1, *sval2;
|
||
|
||
SETUP_IMPLIED(sval1, sval2, m_strdup)
|
||
malloc_strcat(&sval1, sval2);
|
||
new_free(&sval2);
|
||
tmp = sval1;
|
||
CLEANUP_IMPLIED()
|
||
return sval1;
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
* Reworked - Jeremy Nelson, Feb 1994
|
||
* Reworked again, Feb 1996 (jfn)
|
||
*
|
||
* X, XX, and X= are all supported, where X is one of "&" (and),
|
||
* "|" (or) and "^" (xor). The XX forms short-circuit, as they
|
||
* do in C and perl. X and X= forms are bitwise, XX is logical.
|
||
*/
|
||
case '&':
|
||
{
|
||
/* && is binary short-circuit logical and */
|
||
if (ptr[0] == ptr[1] && stage == NU_CONJ)
|
||
{
|
||
*ptr++ = '\0';
|
||
ptr++;
|
||
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
if (check_val(result1))
|
||
{
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
value3 = check_val(result2);
|
||
}
|
||
else
|
||
value3 = 0;
|
||
|
||
new_free(&result1);
|
||
new_free(&result2);
|
||
return m_strdup(value3 ? one : zero);
|
||
}
|
||
|
||
/* &= is implied binary bitwise and */
|
||
else if (ptr[1] == '=' && stage == NU_ASSN)
|
||
{
|
||
SETUP_IMPLIED(value1, value2, my_atol)
|
||
value1 &= value2;
|
||
tmp = m_strdup(ltoa(value1));
|
||
CLEANUP_IMPLIED();
|
||
return tmp;
|
||
}
|
||
|
||
/* & is binary bitwise and */
|
||
else if (ptr[1] != ptr[0] && ptr[1] != '=' && stage == NU_BITW)
|
||
{
|
||
SETUP_BINARY(value1, value2, my_atol)
|
||
return m_strdup(ltoa(value1 & value2));
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
case '|':
|
||
{
|
||
/* || is binary short-circuiting logical or */
|
||
if (ptr[0] == ptr[1] && stage == NU_CONJ)
|
||
{
|
||
*ptr++ = '\0';
|
||
ptr++;
|
||
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
if (!check_val(result1))
|
||
{
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
value3 = check_val(result2);
|
||
}
|
||
else
|
||
value3 = 1;
|
||
|
||
new_free(&result1);
|
||
new_free(&result2);
|
||
return m_strdup(value3 ? one : zero);
|
||
}
|
||
|
||
/* |= is implied binary bitwise or */
|
||
else if (ptr[1] == '=' && stage == NU_ASSN)
|
||
{
|
||
SETUP_IMPLIED(value1, value2, my_atol)
|
||
value1 |= value2;
|
||
tmp = m_strdup(ltoa(value1));
|
||
CLEANUP_IMPLIED();
|
||
return tmp;
|
||
}
|
||
|
||
/* | is binary bitwise or */
|
||
else if (ptr[1] != ptr[0] && ptr[1] != '=' && stage != NU_BITW)
|
||
{
|
||
SETUP_BINARY(value1, value2, my_atol)
|
||
return m_strdup(ltoa(value1 | value2));
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
case '^':
|
||
{
|
||
/* ^^ is binary logical xor */
|
||
if (ptr[0] == ptr[1] && stage == NU_CONJ)
|
||
{
|
||
*ptr++ = '\0';
|
||
ptr++;
|
||
|
||
value1 = check_val((result1 = next_unit(str, args, arg_flag, stage)));
|
||
value2 = check_val((result2 = next_unit(ptr, args, arg_flag, stage)));
|
||
new_free(&result1);
|
||
new_free(&result2);
|
||
|
||
return m_strdup((value1 ^ value2) ? one : zero);
|
||
}
|
||
|
||
/* ^= is implied binary bitwise xor */
|
||
else if (ptr[1] == '=' && stage == NU_ASSN) /* ^= op */
|
||
{
|
||
|
||
SETUP_IMPLIED(value1, value2, my_atol)
|
||
value1 ^= value2;
|
||
tmp = m_strdup(ltoa(value1));
|
||
CLEANUP_IMPLIED();
|
||
return tmp;
|
||
}
|
||
|
||
/* ^ is binary bitwise xor */
|
||
else if (ptr[1] != ptr[0] && ptr[1] != '=' && stage == NU_BITW)
|
||
{
|
||
SETUP_BINARY(value1, value2, my_atol)
|
||
return m_strdup(ltoa(value1 ^ value2));
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ?: is the tertiary operator. Confusing.
|
||
*/
|
||
case '?':
|
||
{
|
||
if (stage == NU_TERT)
|
||
{
|
||
*ptr++ = '\0';
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
ptr2 = MatchingBracket(ptr, '?', ':');
|
||
|
||
/* Unbalanced :, or possibly missing */
|
||
if (!ptr2) /* ? but no :, ignore */
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
*ptr2++ = '\0';
|
||
if ( check_val(result1) )
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
else
|
||
result2 = next_unit(ptr2, args, arg_flag, stage);
|
||
|
||
/* XXXX - needed? */
|
||
ptr2[-1] = ':';
|
||
new_free(&result1);
|
||
return result2;
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* = is the binary assignment operator
|
||
* == is the binary equality operator
|
||
* =~ is the binary matching operator
|
||
*/
|
||
case '=':
|
||
{
|
||
if (ptr[1] == '~' && stage == NU_COMP)
|
||
{
|
||
*ptr++ = 0;
|
||
ptr++;
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
if (wild_match(result2, result1))
|
||
malloc_strcpy(&result1, one);
|
||
else
|
||
malloc_strcpy(&result1, zero);
|
||
new_free(&result2);
|
||
return result1;
|
||
}
|
||
|
||
if (ptr[1] != '=' && ptr[1] != '~' && stage == NU_ASSN)
|
||
{
|
||
*ptr++ = '\0';
|
||
upper(str);
|
||
result1 = expand_alias(str, args, arg_flag, NULL);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
|
||
lastc = result1 + strlen(result1) - 1;
|
||
while (lastc > result1 && *lastc == ' ')
|
||
*lastc-- = '\0';
|
||
for (varname = result1; my_isspace(*varname);)
|
||
varname++;
|
||
|
||
display = window_display;
|
||
window_display = 0;
|
||
upper(varname);
|
||
|
||
if (*varname)
|
||
add_var_alias(varname, result2);
|
||
else
|
||
debugyell("Invalid assignment: no lvalue");
|
||
|
||
window_display = display;
|
||
new_free(&result1);
|
||
return result2;
|
||
}
|
||
|
||
else if (ptr[1] == '=' && stage == NU_COMP)
|
||
{
|
||
*ptr++ = '\0';
|
||
ptr++;
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
if (!my_stricmp(result1, result2))
|
||
malloc_strcpy(&result1, one);
|
||
else
|
||
malloc_strcpy(&result1, zero);
|
||
new_free(&result2);
|
||
return result1;
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* < is the binary relative operator
|
||
* << is the binary bitwise shift operator (not supported)
|
||
*/
|
||
case '>':
|
||
case '<':
|
||
{
|
||
if (ptr[1] == ptr[0] && stage == NU_BITW) /* << or >> op */
|
||
{
|
||
op = *ptr;
|
||
*ptr++ = 0;
|
||
ptr++;
|
||
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
|
||
value1 = my_atol(result1);
|
||
value2 = my_atol(result2);
|
||
if (op == '>')
|
||
value3 = value1 >> value2;
|
||
else
|
||
value3 = value1 << value2;
|
||
new_free(&result1);
|
||
new_free(&result2);
|
||
return m_strdup(ltoa(value3));
|
||
break;
|
||
}
|
||
|
||
else if (ptr[1] != ptr[0] && stage == NU_COMP)
|
||
{
|
||
op = *ptr;
|
||
if (ptr[1] == '=')
|
||
value3 = 1, *ptr++ = '\0';
|
||
else
|
||
value3 = 0;
|
||
|
||
*ptr++ = '\0';
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
|
||
if ((my_isdigit(result1)) && (my_isdigit(result2)))
|
||
{
|
||
dvalue1 = atof(result1);
|
||
dvalue2 = atof(result2);
|
||
value1 = (dvalue1 == dvalue2) ? 0 : ((dvalue1 < dvalue2) ? -1 : 1);
|
||
}
|
||
else
|
||
value1 = my_stricmp(result1, result2);
|
||
|
||
if (value1)
|
||
{
|
||
value2 = (value1 > 0) ? 1 : 0;
|
||
if (op == '<')
|
||
value2 = 1 - value2;
|
||
}
|
||
else
|
||
value2 = value3;
|
||
|
||
new_free(&result1);
|
||
new_free(&result2);
|
||
return m_strdup(ltoa(value2));
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ~ is the 1s complement (bitwise negation) operator
|
||
*/
|
||
case '~':
|
||
{
|
||
if (ptr == str && stage == NU_UNIT)
|
||
{
|
||
result1 = next_unit(str+1, args, arg_flag, stage);
|
||
if (isdigit((unsigned char)*result1))
|
||
value1 = ~my_atol(result1);
|
||
else
|
||
value1 = 0;
|
||
|
||
return m_strdup(ltoa(value1));
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ! is the 2s complement (logical negation) operator
|
||
* != is the inequality operator
|
||
*/
|
||
case '!':
|
||
{
|
||
if (ptr == str && stage == NU_UNIT)
|
||
{
|
||
result1 = next_unit(str+1, args, arg_flag, stage);
|
||
|
||
if (my_isdigit(result1))
|
||
{
|
||
value1 = my_atol(result1);
|
||
value2 = value1 ? 0 : 1;
|
||
}
|
||
else
|
||
value2 = ((*result1)?0:1);
|
||
|
||
new_free(&result1);
|
||
return m_strdup(ltoa(value2));
|
||
}
|
||
|
||
else if (ptr != str && ptr[1] == '~' && stage == NU_COMP)
|
||
{
|
||
*ptr++ = 0;
|
||
ptr++;
|
||
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
|
||
if (!wild_match(result2, result1))
|
||
malloc_strcpy(&result1, one);
|
||
else
|
||
malloc_strcpy(&result1, zero);
|
||
|
||
new_free(&result2);
|
||
return result1;
|
||
}
|
||
|
||
else if (ptr != str && ptr[1] == '=' && stage == NU_COMP)
|
||
{
|
||
*ptr++ = '\0';
|
||
ptr++;
|
||
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
|
||
if (!my_stricmp(result1, result2))
|
||
malloc_strcpy(&result1, zero);
|
||
else
|
||
malloc_strcpy(&result1, one);
|
||
|
||
new_free(&result2);
|
||
return result1;
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
* , is the binary right-hand operator
|
||
*/
|
||
case ',':
|
||
{
|
||
if (stage == NU_EXPR)
|
||
{
|
||
*ptr++ = '\0';
|
||
result1 = next_unit(str, args, arg_flag, stage);
|
||
result2 = next_unit(ptr, args, arg_flag, stage);
|
||
new_free(&result1);
|
||
return result2;
|
||
}
|
||
|
||
else
|
||
{
|
||
ptr = lastop(ptr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
} /* end of switch */
|
||
}
|
||
|
||
/*
|
||
* If were not done parsing, parse it again.
|
||
*/
|
||
if (stage != NU_UNIT)
|
||
return next_unit(str, args, arg_flag, stage + 1);
|
||
|
||
/*
|
||
* If the result is a number, return it.
|
||
*/
|
||
if (my_isdigit(str))
|
||
return m_strdup(str);
|
||
|
||
|
||
/*
|
||
* If the result starts with a #, or a @, its a special op
|
||
*/
|
||
if (*str == '#' || *str == '@')
|
||
op = *str++;
|
||
else
|
||
op = '\0';
|
||
|
||
/*
|
||
* Its not a number, so its a variable, look it up.
|
||
*/
|
||
if (!*str)
|
||
result1 = m_strdup(args);
|
||
else if (!(result1 = get_variable(str)))
|
||
return m_strdup(empty_string);
|
||
|
||
/*
|
||
* See if we have to take strlen or word_count on the variable.
|
||
*/
|
||
if (op)
|
||
{
|
||
if (op == '#')
|
||
value1 = word_count(result1);
|
||
else if (op == '@')
|
||
value1 = strlen(result1);
|
||
new_free(&result1);
|
||
return m_strdup(ltoa(value1));
|
||
}
|
||
|
||
/*
|
||
* Nope. Just return the variable.
|
||
*/
|
||
return result1;
|
||
}
|
||
|
||
/*
|
||
* parse_inline: This evaluates user-variable expression. I'll talk more
|
||
* about this at some future date. The ^ function and some fixes by
|
||
* troy@cbme.unsw.EDU.AU (Troy Rollo)
|
||
*/
|
||
char *BX_parse_inline(char *str, const char *args, int *args_flag)
|
||
{
|
||
#ifndef WINNT
|
||
if (x_debug & DEBUG_NEW_MATH)
|
||
return matheval(str, args, args_flag);
|
||
else
|
||
#endif
|
||
return next_unit(str, args, args_flag, NU_EXPR);
|
||
}
|
||
|
||
|
||
|
||
/**************************** TEXT MODE PARSER *****************************/
|
||
/*
|
||
* expand_alias: Expands inline variables in the given string and returns the
|
||
* expanded string in a new string which is malloced by expand_alias().
|
||
*
|
||
* Also unescapes anything that was quoted with a backslash
|
||
*
|
||
* Behaviour is modified by the following:
|
||
* Anything between brackets (...) {...} is left unmodified.
|
||
* If more_text is supplied, the text is broken up at
|
||
* semi-colons and returned one at a time. The unprocessed
|
||
* portion is written back into more_text.
|
||
* Backslash escapes are unescaped.
|
||
*/
|
||
char *BX_expand_alias (const char *string, const char *args, int *args_flag, char **more_text)
|
||
{
|
||
char *buffer = NULL,
|
||
*ptr,
|
||
*stuff = NULL,
|
||
*free_stuff,
|
||
*quote_str = NULL;
|
||
char quote_temp[2];
|
||
char ch;
|
||
int is_quote = 0;
|
||
int unescape = 1;
|
||
|
||
if (!string || !*string)
|
||
return m_strdup(empty_string);
|
||
|
||
if (*string == '@' && more_text)
|
||
{
|
||
unescape = 0;
|
||
*args_flag = 1; /* Stop the @ command from auto appending */
|
||
}
|
||
quote_temp[1] = 0;
|
||
|
||
ptr = free_stuff = stuff = LOCAL_COPY(string);
|
||
|
||
if (more_text)
|
||
*more_text = NULL;
|
||
|
||
while (ptr && *ptr)
|
||
{
|
||
if (is_quote)
|
||
{
|
||
is_quote = 0;
|
||
++ptr;
|
||
continue;
|
||
}
|
||
switch(*ptr)
|
||
{
|
||
case '$':
|
||
{
|
||
/*
|
||
* The test here ensures that if we are in the
|
||
* expression evaluation command, we don't expand $.
|
||
* In this case we are only coming here to do command
|
||
* separation at ';'s. If more_text is not defined,
|
||
* and the first character is '@', we have come here
|
||
* from [] in an expression.
|
||
*/
|
||
if (more_text && *string == '@')
|
||
{
|
||
ptr++;
|
||
break;
|
||
}
|
||
*ptr++ = 0;
|
||
if (!*ptr)
|
||
break;
|
||
m_strcat_ues(&buffer, stuff, unescape);
|
||
|
||
for (; *ptr == '^'; ptr++)
|
||
{
|
||
ptr++;
|
||
if (!*ptr)
|
||
break;
|
||
quote_temp[0] = *ptr;
|
||
malloc_strcat("e_str, quote_temp);
|
||
}
|
||
stuff = alias_special_char(&buffer, ptr, args, quote_str, args_flag);
|
||
if (quote_str)
|
||
new_free("e_str);
|
||
ptr = stuff;
|
||
break;
|
||
}
|
||
case ';':
|
||
{
|
||
if (!more_text)
|
||
{
|
||
ptr++;
|
||
break;
|
||
}
|
||
*more_text = (char *)(string + (ptr - free_stuff) + 1);
|
||
*ptr = '\0'; /* To terminate the loop */
|
||
break;
|
||
}
|
||
case LEFT_PAREN:
|
||
case LEFT_BRACE:
|
||
{
|
||
ch = *ptr;
|
||
*ptr = '\0';
|
||
m_strcat_ues(&buffer, stuff, unescape);
|
||
stuff = ptr;
|
||
*args_flag = 1;
|
||
if (!(ptr = MatchingBracket(stuff + 1, ch,
|
||
(ch == LEFT_PAREN) ?
|
||
RIGHT_PAREN : RIGHT_BRACE)))
|
||
{
|
||
debugyell("Unmatched %c", ch);
|
||
ptr = stuff + strlen(stuff+1)+1;
|
||
}
|
||
else
|
||
ptr++;
|
||
|
||
*stuff = ch;
|
||
ch = *ptr;
|
||
*ptr = '\0';
|
||
malloc_strcat(&buffer, stuff);
|
||
stuff = ptr;
|
||
*ptr = ch;
|
||
break;
|
||
}
|
||
case '\\':
|
||
{
|
||
is_quote = 1;
|
||
ptr++;
|
||
break;
|
||
}
|
||
default:
|
||
ptr++;
|
||
break;
|
||
}
|
||
}
|
||
if (stuff)
|
||
m_strcat_ues(&buffer, stuff, unescape);
|
||
|
||
if (internal_debug & DEBUG_EXPANSIONS && !in_debug_yell)
|
||
debugyell("Expanded [%s] to [%s]", string, buffer);
|
||
#if 0
|
||
if ((internal_debug & DEBUG_CMDALIAS) && alias_debug)
|
||
debugyell("%d %s", debug_count++, string);
|
||
#endif
|
||
return buffer;
|
||
}
|
||
|
||
|
||
extern char *call_structure_internal(char *, const char *, char *, char *);
|
||
|
||
char *call_structure(char *name, const char *args, int *args_flag, char *rest, char *rest1)
|
||
{
|
||
char *ret = NULL, *tmp = NULL;
|
||
char *lparen, *rparen;
|
||
|
||
if ((lparen = strchr(name, '(')))
|
||
{
|
||
if ((rparen = MatchingBracket(lparen + 1, '(', ')')))
|
||
*rparen++ = 0;
|
||
else
|
||
debugyell("Unmatched lparen in function call [%s]", name);
|
||
|
||
*lparen++ = 0;
|
||
}
|
||
else
|
||
lparen = empty_string;
|
||
|
||
tmp = expand_alias(lparen, args, args_flag, NULL);
|
||
|
||
if ((internal_debug & DEBUG_STRUCTURES) && !in_debug_yell)
|
||
debugyell("%s->%s %d", name, rest, *args_flag);
|
||
ret = call_structure_internal(name, tmp ? tmp : "0", rest, rest1);
|
||
new_free(&tmp);
|
||
return m_strdup(ret ? ret : empty_string);
|
||
}
|
||
|
||
|
||
/*
|
||
* alias_special_char: Here we determine what to do with the character after
|
||
* the $ in a line of text. The special characters are described more fully
|
||
* in the help/ALIAS file. But they are all handled here. Parameters are the
|
||
* return char ** pointer to which things are placed,
|
||
* a ptr to the string (the first character of which is the special
|
||
* character), the args to the alias, and a character indication what
|
||
* characters in the string should be quoted with a backslash. It returns a
|
||
* pointer to the character right after the converted alias.
|
||
*
|
||
* The args_flag is set to 1 if any of the $n, $n-, $n-m, $-m, $*, or $()
|
||
* is used in the alias. Otherwise it is left unchanged.
|
||
*/
|
||
char *BX_alias_special_char(char **buffer, char *ptr, const char *args, char *quote_em, int *args_flag)
|
||
{
|
||
char *tmp,
|
||
*tmp2,
|
||
pad_char = 0;
|
||
register unsigned char c;
|
||
|
||
int upper,
|
||
lower,
|
||
length;
|
||
|
||
length = 0;
|
||
if ((c = *ptr) == LEFT_BRACKET)
|
||
{
|
||
ptr++;
|
||
if ((tmp = MatchingBracket(ptr, '[', ']')))
|
||
{
|
||
*tmp = 0;
|
||
if (*ptr == '$')
|
||
{
|
||
char *str;
|
||
size_t slen;
|
||
|
||
str = expand_alias(ptr, args, args_flag, NULL);
|
||
|
||
slen = strlen(str);
|
||
if (slen &&
|
||
!isdigit((unsigned char)str[slen - 1]))
|
||
pad_char = str[slen - 1];
|
||
|
||
length = my_atol(str);
|
||
new_free(&str);
|
||
}
|
||
else
|
||
{
|
||
if (!isdigit((unsigned char)*(tmp - 1)))
|
||
pad_char = *(tmp - 1);
|
||
length = my_atol(ptr);
|
||
}
|
||
ptr = ++tmp;
|
||
c = *ptr;
|
||
|
||
}
|
||
else
|
||
{
|
||
say("Missing %c", RIGHT_BRACKET);
|
||
return (ptr);
|
||
}
|
||
}
|
||
tmp = ptr+1;
|
||
switch (c)
|
||
{
|
||
/*
|
||
* $(...) is the "dereference" case. It allows you to
|
||
* expand whats inside of the parens and use *that* as a
|
||
* variable name. The idea can be used for pointers of
|
||
* sorts. Specifically, if $foo == [bar], then $($foo) is
|
||
* actually the same as $(bar), which is actually $bar.
|
||
* Got it?
|
||
*
|
||
* epic4pre1.049 -- I changed this somewhat. I dont know if
|
||
* itll get me in trouble. It will continue to expand the
|
||
* inside of the parens until the first character isnt a $.
|
||
* since by all accounts the result of the expansion is
|
||
* SUPPOSED to be an rvalue, obviously a leading $ precludes
|
||
* this. However, there are definitely some cases where you
|
||
* might want two or even three levels of indirection. Im
|
||
* not sure i have any immediate ideas why, but Kanan probably
|
||
* does since he's the one that needed this. (esl)
|
||
*/
|
||
case LEFT_PAREN:
|
||
{
|
||
char *sub_buffer = NULL,
|
||
*tmp2 = NULL,
|
||
*tmpsav = NULL,
|
||
*ph = ptr + 1;
|
||
|
||
if ((ptr = MatchingBracket(ph, '(', ')')) ||
|
||
(ptr = strchr(ph, ')')))
|
||
*ptr++ = 0;
|
||
else
|
||
debugyell("Unmatched ( (continuing anyways)");
|
||
|
||
/*
|
||
* Keep expanding as long as neccesary.
|
||
*/
|
||
do
|
||
{
|
||
tmp2 = expand_alias(tmp, args, args_flag, NULL);
|
||
if (tmpsav)
|
||
new_free(&tmpsav);
|
||
tmpsav = tmp = tmp2;
|
||
}
|
||
while (*tmp == '$');
|
||
alias_special_char(&sub_buffer, tmp, args,
|
||
quote_em, args_flag);
|
||
if (sub_buffer == NULL)
|
||
sub_buffer = m_strdup(empty_string);
|
||
|
||
TruncateAndQuote(buffer, sub_buffer, length, quote_em, pad_char);
|
||
new_free(&sub_buffer);
|
||
new_free(&tmpsav);
|
||
*args_flag = 1;
|
||
return (ptr);
|
||
|
||
}
|
||
case '!':
|
||
{
|
||
if ((ptr = (char *) strchr(tmp, '!')) != NULL)
|
||
*(ptr++) = (char) 0;
|
||
if ((tmp = do_history(tmp, empty_string)) != NULL)
|
||
{
|
||
TruncateAndQuote(buffer, tmp, length, quote_em, pad_char);
|
||
new_free(&tmp);
|
||
}
|
||
return (ptr);
|
||
}
|
||
case LEFT_BRACE:
|
||
{
|
||
char *ph = ptr + 1;
|
||
/*
|
||
* BLAH. This didnt allow for nesting before.
|
||
* How lame.
|
||
*/
|
||
if ((ptr = MatchingBracket(ph, '{', '}')) ||
|
||
(ptr = strchr(ph, '}')))
|
||
*ptr++ = 0;
|
||
else
|
||
debugyell("Unmatched { (continuing anyways)");
|
||
|
||
if ((tmp = parse_inline(tmp, args, args_flag)) != NULL)
|
||
{
|
||
TruncateAndQuote(buffer, tmp, length, quote_em, pad_char);
|
||
new_free(&tmp);
|
||
}
|
||
return (ptr);
|
||
}
|
||
case DOUBLE_QUOTE:
|
||
case '\'':
|
||
{
|
||
if ((ptr = strchr(tmp, c)))
|
||
*ptr++ = 0;
|
||
|
||
alias_string = NULL;
|
||
add_wait_prompt(tmp, do_alias_string, NULL,
|
||
(c == DOUBLE_QUOTE) ? WAIT_PROMPT_LINE
|
||
: WAIT_PROMPT_KEY, 1);
|
||
while (!alias_string)
|
||
io("Input Prompt");
|
||
|
||
TruncateAndQuote(buffer, alias_string, length,quote_em, pad_char);
|
||
new_free(&alias_string);
|
||
return (ptr);
|
||
}
|
||
case '*':
|
||
{
|
||
TruncateAndQuote(buffer, args, length, quote_em, pad_char);
|
||
*args_flag = 1;
|
||
return (ptr + 1);
|
||
}
|
||
|
||
/* ok, ok. so i did forget something. */
|
||
case '#':
|
||
case '@':
|
||
{
|
||
char c2 = 0;
|
||
char *sub_buffer = NULL;
|
||
char *rest = NULL, *val;
|
||
int dummy;
|
||
|
||
rest = after_expando(ptr + 1, 0, &dummy);
|
||
if (rest == ptr + 1)
|
||
{
|
||
sub_buffer = m_strdup(args);
|
||
*args_flag = 1;
|
||
}
|
||
else
|
||
{
|
||
c2 = *rest;
|
||
*rest = 0;
|
||
alias_special_char(&sub_buffer, ptr + 1,
|
||
args, quote_em, args_flag);
|
||
|
||
*rest = c2;
|
||
}
|
||
|
||
if (c == '#')
|
||
val = m_strdup(ltoa(word_count(sub_buffer)));
|
||
else
|
||
val = m_strdup(ltoa(sub_buffer?strlen(sub_buffer):0));
|
||
|
||
TruncateAndQuote(buffer, val, length, quote_em, pad_char);
|
||
new_free(&val);
|
||
new_free(&sub_buffer);
|
||
if (rest)
|
||
*rest = c2;
|
||
return rest;
|
||
}
|
||
|
||
case '$':
|
||
{
|
||
TruncateAndQuote(buffer, "$", length, quote_em, pad_char);
|
||
return ptr + 1;
|
||
}
|
||
default:
|
||
{
|
||
/*
|
||
* Is it a numeric expando? This includes the
|
||
* "special" expando $~.
|
||
*/
|
||
if (isdigit(c) || (c == '-') || c == '~')
|
||
{
|
||
*args_flag = 1;
|
||
|
||
/*
|
||
* Handle $~. EOS especially handles this
|
||
* condition.
|
||
*/
|
||
if (c == '~')
|
||
{
|
||
lower = upper = EOS;
|
||
ptr++;
|
||
}
|
||
|
||
/*
|
||
* Handle $-X where X is some number. Note that
|
||
* any leading spaces in the args list are
|
||
* retained in the result, even if X is 0. The
|
||
* stock client stripped spaces out on $-0, but
|
||
* not for any other case, which was judged to be
|
||
* in error. We always retain the spaces.
|
||
*
|
||
* If there is no number after the -, then the
|
||
* hyphen is slurped and expanded to nothing.
|
||
*/
|
||
else if (c == '-')
|
||
{
|
||
lower = SOS;
|
||
ptr++;
|
||
upper = parse_number(&ptr);
|
||
if (upper == -1)
|
||
return empty_string; /* error */
|
||
}
|
||
|
||
/*
|
||
* Handle $N, $N-, and $N-M, where N and M are
|
||
* numbers.
|
||
*/
|
||
else
|
||
{
|
||
lower = parse_number(&ptr);
|
||
if (*ptr == '-')
|
||
{
|
||
ptr++;
|
||
upper = parse_number(&ptr);
|
||
if (upper == -1)
|
||
upper = EOS;
|
||
}
|
||
else
|
||
upper = lower;
|
||
}
|
||
|
||
/*
|
||
* Protect against a crash. There
|
||
* are some gross syntactic errors
|
||
* that can be made that will result
|
||
* in ''args'' being NULL here. That
|
||
* will crash the client, so we have
|
||
* to protect against that by simply
|
||
* chewing the expando.
|
||
*/
|
||
if (!args)
|
||
tmp2 = m_strdup(empty_string);
|
||
else
|
||
tmp2 = extract2(args, lower, upper);
|
||
|
||
TruncateAndQuote(buffer, tmp2, length, quote_em, pad_char);
|
||
new_free(&tmp2);
|
||
return (ptr ? ptr : empty_string);
|
||
}
|
||
|
||
/*
|
||
* Ok. So we know we're doing a normal rvalue
|
||
* expando. Slurp it up.
|
||
*/
|
||
else
|
||
{
|
||
char *rest, d = 0;
|
||
char *rest1 = NULL;
|
||
int function_call = 0;
|
||
int struct_call = 0;
|
||
|
||
rest = after_expando(ptr, 0, &function_call);
|
||
if (*rest)
|
||
{
|
||
d = *rest;
|
||
*rest = 0;
|
||
}
|
||
if ((d == '-') && *(rest + 1) == '>')
|
||
{
|
||
struct_call = 1;
|
||
rest = rest + 2;
|
||
function_call = 0;
|
||
d = 0;
|
||
}
|
||
|
||
if (function_call)
|
||
tmp = call_function(ptr, args, args_flag);
|
||
else if (struct_call)
|
||
{
|
||
rest1 = after_expando(rest, 0, &function_call);
|
||
if (*rest1)
|
||
{
|
||
d = *rest1;
|
||
if (*rest1)
|
||
{
|
||
*rest1 = 0;
|
||
rest1++;
|
||
}
|
||
}
|
||
tmp = call_structure(ptr, args, args_flag, rest, rest1);
|
||
if ((d == '-') && (*rest1 == '>'))
|
||
rest1 = strchr(rest1, ' '), d = 0;
|
||
}
|
||
else
|
||
tmp = get_variable_with_args(ptr, args, args_flag);
|
||
|
||
if (!tmp)
|
||
tmp = m_strdup(empty_string);
|
||
TruncateAndQuote(buffer, tmp, length, quote_em, pad_char);
|
||
new_free(&tmp);
|
||
|
||
if (struct_call)
|
||
{
|
||
if (d)
|
||
rest = rest1 - 1;
|
||
else
|
||
rest = rest1;
|
||
}
|
||
if (d)
|
||
*rest = d;
|
||
return(rest);
|
||
}
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* TruncateAndQuote: This handles string width formatting and quoting for irc
|
||
* variables when [] or ^x is specified.
|
||
*/
|
||
static void TruncateAndQuote(char **buff, const char *add, int length, const char *quote_em, char pad_char)
|
||
{
|
||
/*
|
||
* Semantics:
|
||
* If length is nonzero, then "add" will be truncated
|
||
* to "length" characters
|
||
* If length is zero, nothing is done to "add"
|
||
* If quote_em is not NULL, then the resulting string
|
||
* will be quoted and appended to "buff"
|
||
* If quote_em is NULL, then the value of "add" is
|
||
* appended to "buff"
|
||
*/
|
||
if (length)
|
||
{
|
||
char *buffer = NULL;
|
||
buffer = alloca(abs(length)+1);
|
||
strformat(buffer, add, length, pad_char ? pad_char:get_int_var(PAD_CHAR_VAR));
|
||
add = buffer;
|
||
}
|
||
if (quote_em && add)
|
||
{
|
||
char *ptr = alloca(strlen(add) * 2 + 2);
|
||
add = double_quote(add, quote_em, ptr);
|
||
}
|
||
|
||
if (buff)
|
||
malloc_strcat(buff, add);
|
||
|
||
return;
|
||
}
|
||
|
||
static void do_alias_string (char *unused, char *input)
|
||
{
|
||
malloc_strcpy(&alias_string, input);
|
||
}
|
||
|