/* * 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 #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 if little endian is ABCD and big endian is DCBA 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 $ or * $-, 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); }