/**
* \file parse.c
*
* \brief The PennMUSH function/expression parser
*
* The most important function in this file is process_expression.
*
*/
#include "copyrite.h"
#include "config.h"
#include <math.h>
#include <limits.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include "conf.h"
#include "externs.h"
#include "ansi.h"
#include "dbdefs.h"
#include "function.h"
#include "case.h"
#include "match.h"
#include "mushdb.h"
#include "parse.h"
#include "attrib.h"
#include "pcre.h"
#include "flags.h"
#include "log.h"
#include "mymalloc.h"
#include "confmagic.h"
extern char *absp[], *obj[], *poss[], *subj[]; /* fundb.c */
extern int inum, inum_limit;
int global_fun_invocations;
int global_fun_recursions;
extern int re_subpatterns;
extern int *re_offsets;
extern char *re_from;
extern sig_atomic_t cpu_time_limit_hit;
extern int cpu_limit_warning_sent;
/** Structure for storing DEBUG output in a linked list */
struct debug_info {
char *string; /**< A DEBUG string */
Debug_Info *prev; /**< Previous node in the linked list */
Debug_Info *next; /**< Next node in the linked list */
};
FUNCTION_PROTO(fun_gfun);
#ifndef DOXYGEN_SHOULD_SKIP_THIS
/* Common error messages */
char e_int[] = "#-1 ARGUMENT MUST BE INTEGER";
char e_ints[] = "#-1 ARGUMENTS MUST BE INTEGERS";
char e_uint[] = "#-1 ARGUMENT MUST BE POSITIVE INTEGER";
char e_uints[] = "#-1 ARGUMENTS MUST BE POSITIVE INTEGERS";
char e_num[] = "#-1 ARGUMENT MUST BE NUMBER";
char e_nums[] = "#-1 ARGUMENTS MUST BE NUMBERS";
char e_invoke[] = "#-1 FUNCTION INVOCATION LIMIT EXCEEDED";
char e_call[] = "#-1 CALL LIMIT EXCEEDED";
char e_perm[] = "#-1 PERMISSION DENIED";
char e_atrperm[] = "#-1 NO PERMISSION TO GET ATTRIBUTE";
char e_match[] = "#-1 NO MATCH";
char e_notvis[] = "#-1 NO SUCH OBJECT VISIBLE";
char e_disabled[] = "#-1 FUNCTION DISABLED";
char e_range[] = "#-1 OUT OF RANGE";
#endif
#if 0
static void dummy_errors(void);
static void
dummy_errors()
{
/* Just to make sure the error messages are in the translation
tables. */
char *temp;
temp = T("#-1 ARGUMENT MUST BE INTEGER");
temp = T("#-1 ARGUMENTS MUST BE INTEGERS");
temp = T("#-1 ARGUMENT MUST BE POSITIVE INTEGER");
temp = T("#-1 ARGUMENTS MUST BE POSITIVE INTEGERS");
temp = T("#-1 ARGUMENT MUST BE NUMBER");
temp = T("#-1 ARGUMENTS MUST BE NUMBERS");
temp = T("#-1 FUNCTION INVOCATION LIMIT EXCEEDED");
temp = T("#-1 CALL LIMIT EXCEEDED");
temp = T("#-1 PERMISSION DENIED");
temp = T("#-1 NO PERMISSION TO GET ATTRIBUTE");
temp = T("#-1 NO MATCH");
temp = T("#-1 NO SUCH OBJECT VISIBLE");
temp = T("#-1 FUNCTION DISABLED");
temp = T("#-1 OUT OF RANGE");
}
#endif
/** Given a string, parse out a dbref.
* \param str string to parse.
* \return dbref contained in the string, or NOTHING if not a valid dbref.
*/
dbref
parse_dbref(char const *str)
{
/* Make sure string is strictly in format "#nnn".
* Otherwise, possesives will be fouled up.
*/
char const *p;
dbref num;
if (!str || (*str != NUMBER_TOKEN) || !*(str + 1))
return NOTHING;
for (p = str + 1; isdigit((unsigned char) *p); p++) {
}
if (*p)
return NOTHING;
num = atoi(str + 1);
if (!GoodObject(num))
return NOTHING;
return num;
}
/** Version of parse_dbref() that doesn't do GoodObject checks */
dbref
qparse_dbref(const char *s)
{
if (!s || (*s != NUMBER_TOKEN) || !*(s + 1))
return NOTHING;
return parse_integer(s + 1);
}
/** Given a string, parse out an object id or dbref.
* \param str string to parse.
* \return dbref of object referenced by string, or NOTHING if not a valid
* string or not an existing dbref.
*/
dbref
parse_objid(char const *str)
{
char *p;
if ((p = strchr(str, ':'))) {
char tbuf1[BUFFER_LEN];
dbref it;
/* A unique id, probably */
strncpy(tbuf1, str, (p - str));
tbuf1[p - str] = '\0';
it = parse_dbref(tbuf1);
if (GoodObject(it)) {
time_t matchtime;
p++;
if (!is_strict_integer(p))
return NOTHING;
matchtime = parse_integer(p);
return (CreTime(it) == matchtime) ? it : NOTHING;
} else
return NOTHING;
} else
return parse_dbref(str);
}
/** Given a string, parse out a boolean value.
* The meaning of boolean is fuzzy. To TinyMUSH, any string that begins with
* a non-zero number is true, and everything else is false.
* To PennMUSH, negative dbrefs are false, non-negative dbrefs are true,
* 0 is false, all other numbers are true, empty or blank strings are false,
* and all other strings are true.
* \param str string to parse.
* \retval 1 str represents a true value.
* \retval 0 str represents a false value.
*/
int
parse_boolean(char const *str)
{
if (TINY_BOOLEANS) {
return (atoi(str) ? 1 : 0);
} else {
/* Turn a string into a boolean value.
* All negative dbrefs are false, all non-negative dbrefs are true.
* Zero is false, all other numbers are true.
* Empty (or space only) strings are false, all other strings are true.
*/
/* Null strings are false */
if (!str || !*str)
return 0;
/* Negative dbrefs are false - actually anything starting #-,
* which will also cover our error messages. */
if (*str == '#' && *(str + 1) && (*(str + 1) == '-'))
return 0;
/* Non-zero numbers are true, zero is false */
if (is_strict_number(str))
return parse_number(str) != 0; /* avoid rounding problems */
/* Skip blanks */
while (*str == ' ')
str++;
/* If there's any non-blanks left, it's true */
return *str != '\0'; /* force to 1 or 0 */
}
}
/** Is a string a boolean value?
* To TinyMUSH, any integer is a boolean value. To PennMUSH, any
* string at all is boolean.
* \param str string to check.
* \retval 1 string is a valid boolean.
* \retval 0 string is not a valid boolean.
*/
int
is_boolean(char const *str)
{
if (TINY_BOOLEANS)
return is_integer(str);
else
return 1;
}
/** Is a string a dbref?
* A dbref is a string starting with a #, optionally followed by a -,
* and then followed by at least one digit, and nothing else.
* \param str string to check.
* \retval 1 string is a dbref.
* \retval 0 string is not a dbref.
*/
int
is_dbref(char const *str)
{
if (!str || (*str != NUMBER_TOKEN) || !*(str + 1))
return 0;
if (*(str + 1) == '-') {
str++;
}
for (str++; isdigit((unsigned char) *str); str++) {
}
return !*str;
}
/** Is a string an objid?
* An objid is a string starting with a #, optionally followed by a -,
* and then followed by at least one digit, then optionally followed
* by a : and at least one digit, and nothing else.
* In regex: ^#-?\d+(:\d+)?$
* \param str string to check.
* \retval 1 string is a dbref.
* \retval 0 string is not a dbref.
*/
int
is_objid(char const *str)
{
static pcre *re = NULL;
const char *errptr;
int erroffset;
if (!str)
return 0;
if (!re)
re = pcre_compile("^#-?\\d+(?::\\d+)?$", 0, &errptr, &erroffset, NULL);
return (pcre_exec(re, NULL, str, strlen(str), 0, 0, NULL, 0) >= 0);
}
/** Is string an integer?
* To TinyMUSH, any string is an integer. To PennMUSH, a string that
* passes strtol is an integer, and a blank string is an integer
* if NULL_EQ_ZERO is turned on.
* \param str string to check.
* \retval 1 string is an integer.
* \retval 0 string is not an integer.
*/
int
is_integer(char const *str)
{
char *end;
/* If we're emulating Tiny, anything is an integer */
if (TINY_MATH)
return 1;
if (!str)
return 0;
while (isspace((unsigned char) *str))
str++;
if (*str == '\0')
return NULL_EQ_ZERO;
errno = 0;
strtol(str, &end, 10);
if (errno == ERANGE || *end != '\0')
return 0;
return 1;
}
/** Is string an uinteger?
* To TinyMUSH, any string is an uinteger. To PennMUSH, a string that
* passes strtoul is an uinteger, and a blank string is an uinteger
* if NULL_EQ_ZERO is turned on.
* \param str string to check.
* \retval 1 string is an uinteger.
* \retval 0 string is not an uinteger.
*/
int
is_uinteger(char const *str)
{
char *end;
/* If we're emulating Tiny, anything is an integer */
if (TINY_MATH)
return 1;
if (!str)
return 0;
/* strtoul() accepts negative numbers, so we still have to do this check */
while (isspace((unsigned char) *str))
str++;
if (*str == '\0')
return NULL_EQ_ZERO;
if (!(isdigit((unsigned char) *str) || *str == '+'))
return 0;
errno = 0;
strtoul(str, &end, 10);
if (errno == ERANGE || *end != '\0')
return 0;
return 1;
}
/** Is string a number by the strict definition?
* A strict number is a non-null string that passes strtod.
* \param str string to check.
* \retval 1 string is a strict number.
* \retval 0 string is not a strict number.
*/
int
is_strict_number(char const *str)
{
char *end;
NVAL val;
if (!str)
return 0;
errno = 0;
val = strtod(str, &end);
if (errno == ERANGE || *end != '\0')
return 0;
return end > str;
}
/** Is string an integer by the strict definition?
* A strict integer is a non-null string that passes strtol.
* \param str string to check.
* \retval 1 string is a strict integer.
* \retval 0 string is not a strict integer.
*/
int
is_strict_integer(char const *str)
{
char *end;
int val;
if (!str)
return 0;
errno = 0;
val = strtol(str, &end, 10);
if (errno == ERANGE || *end != '\0')
return 0;
return end > str;
}
/** Is string a number?
* To TinyMUSH, any string is a number. To PennMUSH, a strict number is
* a number, and a blank string is a number if NULL_EQ_ZERO is turned on.
* \param str string to check.
* \retval 1 string is a number.
* \retval 0 string is not a number.
*/
int
is_number(char const *str)
{
/* If we're emulating Tiny, anything is a number */
if (TINY_MATH)
return 1;
while (isspace((unsigned char) *str))
str++;
if (*str == '\0')
return NULL_EQ_ZERO;
return is_strict_number(str);
}
/* Table of interesting characters for process_expression() */
extern char active_table[UCHAR_MAX + 1];
/* Indexes of valid q-regs into the renv array. -1 is error. */
extern signed char qreg_indexes[UCHAR_MAX + 1];
#ifdef WIN32
#pragma warning( disable : 4761) /* NJG: disable warning re conversion */
#endif
/** Function and other substitution evaluation.
* This is the PennMUSH function/expression parser. Big stuff.
*
* All results are returned in buff, at the point *bp. bp is likely
* not equal to buff, so make no assumptions about writing at the
* start of the buffer. *bp must be updated to point at the next
* place to be filled (ala safe_str() and safe_chr()). Be very
* careful about not overflowing buff; use of safe_str() and safe_chr()
* for all writes into buff is highly recommended.
*
* nargs is the count of the number of arguments passed to the function,
* and args is an array of pointers to them. args will have at least
* nargs elements, or 10 elements, whichever is greater. The first ten
* elements are initialized to NULL for ease of porting functions from
* the old style, but relying on such is considered bad form.
* The argument strings are stored in BUFFER_LEN buffers, but reliance
* on that size is also considered bad form. The argument strings may
* be modified, but modifying the pointers to the argument strings will
* cause crashes.
*
* executor corresponds to %!, the object invoking the function.
* caller corresponds to %@, the last object to do a U() or similar.
* enactor corresponds to %#, the object that started the whole mess.
* Note that fun_ufun() and similar must swap around these parameters
* in calling process_expression(); no checks are made in the parser
* itself to maintain these values.
*
* called_as contains a pointer to the name of the function called
* (taken from the function table). This may be used to distinguish
* multiple functions which use the same C function for implementation.
*
* pe_info holds context information used by the parser. It should
* be passed untouched to process_expression(), if it is called.
* pe_info should be treated as a black box; its structure and contents
* may change without notice.
*
* Normally, p_e() returns 0. It returns 1 upon hitting the CPU time limit.
*
* \param buff buffer to store returns of parsing.
* \param bp pointer to pointer into buff marking insert position.
* \param str string to parse.
* \param executor dbref of the object invoking the function.
* \param caller dbref of the last object to use u()
* \param enactor dbref of the enactor.
* \param eflags flags to control what is evaluated.
* \param tflags flags to control what terminates an expression.
* \param pe_info pointer to parser context data.
* \retval 0 success.
* \retval 1 CPU time limit exceeded.
*/
int
process_expression(char *buff, char **bp, char const **str,
dbref executor, dbref caller, dbref enactor,
int eflags, int tflags, PE_Info * pe_info)
{
int debugging = 0, made_info = 0;
char *debugstr = NULL, *sourcestr = NULL;
char *realbuff = NULL, *realbp = NULL;
int gender = -1;
char *startpos = *bp;
int had_space = 0;
char temp[3];
int temp_eflags;
int old_iter_limit;
int qindex;
int e_len;
int retval = 0;
const char *e_msg;
if (!buff || !bp || !str || !*str)
return 0;
if (cpu_time_limit_hit) {
if (!cpu_limit_warning_sent) {
cpu_limit_warning_sent = 1;
/* Can't just put #-1 CPU USAGE EXCEEDED in buff here, because
* it might never get displayed.
*/
if (!Quiet(enactor))
notify(enactor, T("CPU usage exceeded."));
do_rawlog(LT_TRACE,
T
("CPU time limit exceeded. enactor=#%d executor=#%d caller=#%d code=%s"),
enactor, executor, caller, *str);
}
return 1;
}
if (Halted(executor))
eflags = PE_NOTHING;
if (eflags & PE_COMPRESS_SPACES)
while (**str == ' ')
(*str)++;
if (!*str)
return 0;
if (!pe_info) {
old_iter_limit = inum_limit;
inum_limit = inum;
made_info = 1;
pe_info = (PE_Info *) mush_malloc(sizeof(PE_Info),
"process_expression.pe_info");
pe_info->fun_invocations = 0;
pe_info->fun_depth = 0;
pe_info->nest_depth = 0;
pe_info->call_depth = 0;
pe_info->debug_strings = NULL;
} else {
old_iter_limit = -1;
}
/* If we've been asked to evaluate, log the expression if:
* (a) the last thing we logged wasn't an expression, and
* (b) this expression isn't a substring of the last thing we logged
*/
if ((eflags & PE_EVALUATE) &&
((last_activity_type() != LA_PE) || !strstr(last_activity(), *str))) {
log_activity(LA_PE, executor, *str);
}
if (eflags != PE_NOTHING) {
if (((*bp) - buff) > (BUFFER_LEN - SBUF_LEN)) {
realbuff = buff;
realbp = *bp;
buff = (char *) mush_malloc(BUFFER_LEN,
"process_expression.buffer_extension");
*bp = buff;
startpos = buff;
}
}
if (CALL_LIMIT && (pe_info->call_depth++ > CALL_LIMIT)) {
e_msg = T(e_call);
e_len = strlen(e_msg);
if ((buff + e_len > *bp) || strcmp(e_msg, *bp - e_len))
safe_str(e_msg, buff, bp);
goto exit_sequence;
}
if (eflags != PE_NOTHING) {
debugging = (Debug(executor) || (eflags & PE_DEBUG))
&& (Connected(Owner(executor)) || atr_get(executor, "DEBUGFORWARDLIST"));
if (debugging) {
int j;
char *debugp;
char const *mark;
Debug_Info *node;
debugstr = (char *) mush_malloc(BUFFER_LEN,
"process_expression.debug_source");
debugp = debugstr;
safe_dbref(executor, debugstr, &debugp);
safe_chr('!', debugstr, &debugp);
for (j = 0; j <= pe_info->nest_depth; j++)
safe_chr(' ', debugstr, &debugp);
sourcestr = debugp;
mark = *str;
process_expression(debugstr, &debugp, str,
executor, caller, enactor,
PE_NOTHING, tflags, pe_info);
*str = mark;
if (eflags & PE_COMPRESS_SPACES)
while ((debugp > sourcestr) && (debugp[-1] == ' '))
debugp--;
*debugp = '\0';
node = (Debug_Info *) mush_malloc(sizeof(Debug_Info),
"process_expression.debug_node");
node->string = debugstr;
node->prev = pe_info->debug_strings;
node->next = NULL;
if (node->prev)
node->prev->next = node;
pe_info->debug_strings = node;
pe_info->nest_depth++;
}
}
/* Only strip command braces if the first character is a brace. */
if (**str != '{')
eflags &= ~PE_COMMAND_BRACES;
for (;;) {
/* Find the first "interesting" character */
{
char const *pos;
int len, len2;
/* Inlined strcspn() equivalent, to save on overhead and portability */
pos = *str;
while (!active_table[*(unsigned char const *) *str])
(*str)++;
/* Inlined safe_str(), since the source string
* may not be null terminated */
len = *str - pos;
len2 = BUFFER_LEN - 1 - (*bp - buff);
if (len > len2)
len = len2;
if (len >= 0) {
memcpy(*bp, pos, len);
*bp += len;
}
}
switch (**str) {
/* Possible terminators */
case '}':
if (tflags & PT_BRACE)
goto exit_sequence;
break;
case ']':
if (tflags & PT_BRACKET)
goto exit_sequence;
break;
case ')':
if (tflags & PT_PAREN)
goto exit_sequence;
break;
case ',':
if (tflags & PT_COMMA)
goto exit_sequence;
break;
case ';':
if (tflags & PT_SEMI)
goto exit_sequence;
break;
case '=':
if (tflags & PT_EQUALS)
goto exit_sequence;
break;
case ' ':
if (tflags & PT_SPACE)
goto exit_sequence;
break;
case '\0':
goto exit_sequence;
}
switch (**str) {
case 0x1B: /* ANSI escapes. */
/* Skip over until the 'm' that matches the end. */
for (; *str && **str && **str != 'm'; (*str)++)
safe_chr(**str, buff, bp);
if (*str && **str) {
safe_chr(**str, buff, bp);
(*str)++;
}
break;
case '$': /* Dollar subs for regedit() */
if ((eflags & (PE_DOLLAR | PE_EVALUATE)) != (PE_DOLLAR | PE_EVALUATE)) {
safe_chr('$', buff, bp);
(*str)++;
} else {
char obuf[BUFFER_LEN];
int p = 0;
(*str)++;
/* Check the first two characters after the $ for a number */
if (isdigit((unsigned char) **str)) {
p = **str - '0';
(*str)++;
if (isdigit((unsigned char) **str)) {
p *= 10;
p += **str - '0';
(*str)++;
}
} else
break;
if (p >= re_subpatterns || re_offsets == NULL || re_from == NULL)
break;
pcre_copy_substring(re_from, re_offsets, re_subpatterns, p, obuf,
BUFFER_LEN);
safe_str(obuf, buff, bp);
}
break;
case '%': /* Percent substitutions */
if (!(eflags & PE_EVALUATE) || (*bp - buff >= BUFFER_LEN - 1)) {
/* peak -- % escapes (at least) one character */
char savec;
safe_chr('%', buff, bp);
(*str)++;
savec = **str;
if (!savec)
goto exit_sequence;
safe_chr(savec, buff, bp);
(*str)++;
switch (savec) {
case 'Q':
case 'q':
case 'V':
case 'v':
case 'W':
case 'w':
case 'X':
case 'x':
/* These sequences escape two characters */
savec = **str;
if (!savec)
goto exit_sequence;
safe_chr(savec, buff, bp);
(*str)++;
}
break;
} else {
char savec, nextc;
char *savepos;
ATTR *attrib;
(*str)++;
savec = **str;
if (!savec)
goto exit_sequence;
savepos = *bp;
(*str)++;
switch (savec) {
case '%': /* %% - a real % */
safe_chr('%', buff, bp);
break;
case '!': /* executor dbref */
safe_dbref(executor, buff, bp);
break;
case '@': /* caller dbref */
safe_dbref(caller, buff, bp);
break;
case '#': /* enactor dbref */
safe_dbref(enactor, buff, bp);
break;
case ':': /* enactor unique id */
safe_dbref(enactor, buff, bp);
safe_chr(':', buff, bp);
safe_integer(CreTime(enactor), buff, bp);
break;
case '?': /* function limits */
if (pe_info) {
safe_integer(pe_info->fun_invocations, buff, bp);
safe_chr(' ', buff, bp);
safe_integer(pe_info->fun_depth, buff, bp);
} else {
safe_str("0 0", buff, bp);
}
break;
case '~': /* enactor accented name */
safe_str(accented_name(enactor), buff, bp);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': /* positional argument */
if (global_eval_context.wenv[savec - '0'])
safe_str(global_eval_context.wenv[savec - '0'], buff, bp);
break;
case 'A':
case 'a': /* enactor absolute possessive pronoun */
if (gender < 0)
gender = get_gender(enactor);
safe_str(absp[gender], buff, bp);
break;
case 'B':
case 'b': /* blank space */
safe_chr(' ', buff, bp);
break;
case 'C':
case 'c': /* command line */
safe_str(global_eval_context.ccom, buff, bp);
break;
case 'L':
case 'l': /* enactor location dbref */
/* The security implications of this have
* already been talked to death. Deal. */
safe_dbref(Location(enactor), buff, bp);
break;
case 'N':
case 'n': /* enactor name */
safe_str(Name(enactor), buff, bp);
break;
case 'O':
case 'o': /* enactor objective pronoun */
if (gender < 0)
gender = get_gender(enactor);
safe_str(obj[gender], buff, bp);
break;
case 'P':
case 'p': /* enactor possessive pronoun */
if (gender < 0)
gender = get_gender(enactor);
safe_str(poss[gender], buff, bp);
break;
case 'Q':
case 'q': /* temporary storage */
nextc = **str;
if (!nextc)
goto exit_sequence;
(*str)++;
if ((qindex = qreg_indexes[(unsigned char) nextc]) == -1)
break;
if (global_eval_context.renv[qindex])
safe_str(global_eval_context.renv[qindex], buff, bp);
break;
case 'R':
case 'r': /* newline */
if (NEWLINE_ONE_CHAR)
safe_chr('\n', buff, bp);
else
safe_str("\r\n", buff, bp);
break;
case 'S':
case 's': /* enactor subjective pronoun */
if (gender < 0)
gender = get_gender(enactor);
safe_str(subj[gender], buff, bp);
break;
case 'T':
case 't': /* tab */
safe_chr('\t', buff, bp);
break;
case 'V':
case 'v':
case 'W':
case 'w':
case 'X':
case 'x': /* attribute substitution */
nextc = **str;
if (!nextc)
goto exit_sequence;
(*str)++;
temp[0] = UPCASE(savec);
temp[1] = UPCASE(nextc);
temp[2] = '\0';
attrib = atr_get(executor, temp);
if (attrib)
safe_str(atr_value(attrib), buff, bp);
break;
default: /* just copy */
safe_chr(savec, buff, bp);
}
if (isupper((unsigned char) savec))
*savepos = UPCASE(*savepos);
}
break;
case '{': /* "{}" parse group; recurse with no function check */
if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
(*str)++;
break;
}
if (eflags & PE_LITERAL) {
safe_chr('{', buff, bp);
(*str)++;
break;
}
if (!(eflags & (PE_STRIP_BRACES | PE_COMMAND_BRACES)))
safe_chr('{', buff, bp);
(*str)++;
if (process_expression(buff, bp, str,
executor, caller, enactor,
eflags & PE_COMMAND_BRACES
? (eflags & ~PE_COMMAND_BRACES)
: (eflags &
~(PE_STRIP_BRACES | PE_FUNCTION_CHECK)),
PT_BRACE, pe_info)) {
retval = 1;
break;
}
if (**str == '}') {
if (!(eflags & (PE_STRIP_BRACES | PE_COMMAND_BRACES)))
safe_chr('}', buff, bp);
(*str)++;
}
/* Only strip one set of braces for commands */
eflags &= ~PE_COMMAND_BRACES;
break;
case '[': /* "[]" parse group; recurse with mandatory function check */
if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
(*str)++;
break;
}
if (eflags & PE_LITERAL) {
safe_chr('[', buff, bp);
(*str)++;
break;
}
if (!(eflags & PE_EVALUATE)) {
safe_chr('[', buff, bp);
temp_eflags = eflags & ~PE_STRIP_BRACES;
} else
temp_eflags = eflags | PE_FUNCTION_CHECK | PE_FUNCTION_MANDATORY;
(*str)++;
if (process_expression(buff, bp, str,
executor, caller, enactor,
temp_eflags, PT_BRACKET, pe_info)) {
retval = 1;
break;
}
if (**str == ']') {
if (!(eflags & PE_EVALUATE))
safe_chr(']', buff, bp);
(*str)++;
}
break;
case '(': /* Function call */
if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
(*str)++;
break;
}
(*str)++;
if (!(eflags & PE_EVALUATE) || !(eflags & PE_FUNCTION_CHECK)) {
safe_chr('(', buff, bp);
if (**str == ' ') {
safe_chr(**str, buff, bp);
(*str)++;
}
if (process_expression(buff, bp, str,
executor, caller, enactor,
eflags & ~PE_STRIP_BRACES, PT_PAREN, pe_info))
retval = 1;
if (**str == ')') {
if (eflags & PE_COMPRESS_SPACES && (*str)[-1] == ' ')
safe_chr(' ', buff, bp);
safe_chr(')', buff, bp);
(*str)++;
}
break;
} else {
char *sargs[10];
char **fargs;
int sarglens[10];
int *arglens;
int args_alloced;
int nfargs;
int j;
static char name[BUFFER_LEN];
char *sp, *tp;
FUN *fp;
int temp_tflags;
int denied;
fargs = sargs;
arglens = sarglens;
for (j = 0; j < 10; j++) {
fargs[j] = NULL;
arglens[j] = 0;
}
args_alloced = 10;
eflags &= ~PE_FUNCTION_CHECK;
/* Get the function name */
for (sp = startpos, tp = name; sp < *bp; sp++)
safe_chr(UPCASE(*sp), name, &tp);
*tp = '\0';
fp = func_hash_lookup(name);
if (!fp) {
if (eflags & PE_FUNCTION_MANDATORY) {
*bp = startpos;
safe_str(T("#-1 FUNCTION ("), buff, bp);
safe_str(name, buff, bp);
safe_str(") NOT FOUND", buff, bp);
if (process_expression(name, &tp, str,
executor, caller, enactor,
PE_NOTHING, PT_PAREN, pe_info))
retval = 1;
if (**str == ')')
(*str)++;
break;
}
safe_chr('(', buff, bp);
if (**str == ' ') {
safe_chr(**str, buff, bp);
(*str)++;
}
if (process_expression(buff, bp, str, executor, caller, enactor,
eflags, PT_PAREN, pe_info)) {
retval = 1;
break;
}
if (**str == ')') {
if (eflags & PE_COMPRESS_SPACES && (*str)[-1] == ' ')
safe_chr(' ', buff, bp);
safe_chr(')', buff, bp);
(*str)++;
}
break;
}
*bp = startpos;
/* Check for the invocation limit */
if ((pe_info->fun_invocations >= FUNCTION_LIMIT) ||
(global_fun_invocations >= FUNCTION_LIMIT * 5)) {
e_msg = T(e_invoke);
e_len = strlen(e_msg);
if ((buff + e_len > *bp) || strcmp(e_msg, *bp - e_len))
safe_str(e_msg, buff, bp);
if (process_expression(name, &tp, str,
executor, caller, enactor,
PE_NOTHING, PT_PAREN, pe_info))
retval = 1;
if (**str == ')')
(*str)++;
break;
}
/* Check for the recursion limit */
if ((pe_info->fun_depth + 1 >= RECURSION_LIMIT) ||
(global_fun_recursions + 1 >= RECURSION_LIMIT * 5)) {
safe_str(T("#-1 FUNCTION RECURSION LIMIT EXCEEDED"), buff, bp);
if (process_expression(name, &tp, str,
executor, caller, enactor,
PE_NOTHING, PT_PAREN, pe_info))
retval = 1;
if (**str == ')')
(*str)++;
break;
}
/* Get the arguments */
temp_eflags = (eflags & ~PE_FUNCTION_MANDATORY)
| PE_COMPRESS_SPACES | PE_EVALUATE | PE_FUNCTION_CHECK;
switch (fp->flags & FN_ARG_MASK) {
case FN_LITERAL:
temp_eflags |= PE_LITERAL;
/* FALL THROUGH */
case FN_NOPARSE:
temp_eflags &= ~(PE_COMPRESS_SPACES | PE_EVALUATE |
PE_FUNCTION_CHECK);
break;
}
denied = !check_func(executor, fp);
if (denied)
temp_eflags &=
~(PE_COMPRESS_SPACES | PE_EVALUATE | PE_FUNCTION_CHECK);
temp_tflags = PT_COMMA | PT_PAREN;
nfargs = 0;
do {
char *argp;
if ((fp->maxargs < 0) && ((nfargs + 1) >= -fp->maxargs))
temp_tflags = PT_PAREN;
if (nfargs >= args_alloced) {
char **nargs;
int *narglens;
nargs = (char **) mush_malloc((nfargs + 10) * sizeof(char *),
"process_expression.function_arglist");
narglens = (int *) mush_malloc((nfargs + 10) * sizeof(int),
"process_expression.function_arglens");
for (j = 0; j < nfargs; j++) {
nargs[j] = fargs[j];
narglens[j] = arglens[j];
}
if (fargs != sargs)
mush_free((Malloc_t) fargs,
"process_expression.function_arglist");
if (arglens != sarglens)
mush_free((Malloc_t) arglens,
"process_expression.function_arglens");
fargs = nargs;
arglens = narglens;
args_alloced += 10;
}
fargs[nfargs] = (char *) mush_malloc(BUFFER_LEN,
"process_expression.function_argument");
argp = fargs[nfargs];
if (process_expression(fargs[nfargs], &argp, str,
executor, caller, enactor,
temp_eflags, temp_tflags, pe_info)) {
retval = 1;
nfargs++;
goto free_func_args;
}
*argp = '\0';
arglens[nfargs] = argp - fargs[nfargs];
(*str)++;
nfargs++;
} while ((*str)[-1] == ',');
if ((*str)[-1] != ')')
(*str)--;
/* See if this function is enabled */
/* Can't do this check earlier, because of possible side effects
* from the functions. Bah. */
if (denied) {
if (fp->flags & FN_DISABLED)
safe_str(T(e_disabled), buff, bp);
else
safe_str(T(e_perm), buff, bp);
goto free_func_args;
} else {
/* If we have the right number of args, eval the function.
* Otherwise, return an error message.
* Special case: zero args is recognized as one null arg.
*/
if ((fp->minargs == 0) && (nfargs == 1) && !*fargs[0]) {
mush_free((Malloc_t) fargs[0],
"process_expression.function_argument");
fargs[0] = NULL;
arglens[0] = 0;
nfargs = 0;
}
if ((nfargs < fp->minargs) || (nfargs > abs(fp->maxargs))) {
safe_str(T("#-1 FUNCTION ("), buff, bp);
safe_str(fp->name, buff, bp);
safe_str(") EXPECTS ", buff, bp);
if (fp->minargs == abs(fp->maxargs)) {
safe_integer(fp->minargs, buff, bp);
} else if ((fp->minargs + 1) == abs(fp->maxargs)) {
safe_integer(fp->minargs, buff, bp);
safe_str(" OR ", buff, bp);
safe_integer(abs(fp->maxargs), buff, bp);
} else if (fp->maxargs == INT_MAX) {
safe_str("AT LEAST ", buff, bp);
safe_integer(fp->minargs, buff, bp);
} else {
safe_str("BETWEEN ", buff, bp);
safe_integer(fp->minargs, buff, bp);
safe_str(" AND ", buff, bp);
safe_integer(abs(fp->maxargs), buff, bp);
}
safe_str(" ARGUMENTS BUT GOT ", buff, bp);
safe_integer(nfargs, buff, bp);
} else {
global_fun_recursions++;
pe_info->fun_depth++;
if (fp->flags & FN_BUILTIN) {
global_fun_invocations++;
pe_info->fun_invocations++;
fp->where.fun(fp, buff, bp, nfargs, fargs, arglens, executor,
caller, enactor, fp->name, pe_info);
if (fp->flags & FN_LOGARGS) {
char logstr[BUFFER_LEN];
char *logp;
int logi;
logp = logstr;
safe_str(fp->name, logstr, &logp);
safe_chr('(', logstr, &logp);
for (logi = 0; logi < nfargs; logi++) {
safe_str(fargs[logi], logstr, &logp);
if (logi + 1 < nfargs)
safe_chr(',', logstr, &logp);
}
safe_chr(')', logstr, &logp);
*logp = '\0';
do_log(LT_CMD, executor, caller, "%s", logstr);
} else if (fp->flags & FN_LOGNAME)
do_log(LT_CMD, executor, caller, "%s()", fp->name);
} else {
dbref thing;
ATTR *attrib;
global_fun_invocations++;
pe_info->fun_invocations++;
thing = userfn_tab[fp->where.offset].thing;
attrib = atr_get(thing, userfn_tab[fp->where.offset].name);
if (!attrib) {
do_rawlog(LT_ERR,
T("ERROR: @function (%s) without attribute (#%d/%s)"),
fp->name, thing, userfn_tab[fp->where.offset].name);
safe_str("#-1 @FUNCTION (", buff, bp);
safe_str(fp->name, buff, bp);
safe_str(") MISSING ATTRIBUTE (", buff, bp);
safe_dbref(thing, buff, bp);
safe_chr('/', buff, bp);
safe_str(userfn_tab[fp->where.offset].name, buff, bp);
safe_chr(')', buff, bp);
} else
do_userfn(buff, bp, thing, attrib, nfargs, fargs,
executor, caller, enactor, pe_info);
}
pe_info->fun_depth--;
global_fun_recursions--;
}
}
/* Free up the space allocated for the args */
free_func_args:
for (j = 0; j < nfargs; j++)
if (fargs[j])
mush_free((Malloc_t) fargs[j],
"process_expression.function_argument");
if (fargs != sargs)
mush_free((Malloc_t) fargs, "process_expression.function_arglist");
if (arglens != sarglens)
mush_free((Malloc_t) arglens, "process_expression.function_arglens");
}
break;
/* Space compression */
case ' ':
had_space = 1;
safe_chr(' ', buff, bp);
(*str)++;
if (eflags & PE_COMPRESS_SPACES) {
while (**str == ' ')
(*str)++;
} else
while (**str == ' ') {
safe_chr(' ', buff, bp);
(*str)++;
}
break;
/* Escape charater */
case '\\':
if (!(eflags & PE_EVALUATE))
safe_chr('\\', buff, bp);
(*str)++;
if (!**str)
goto exit_sequence;
/* FALL THROUGH */
/* Basic character */
default:
safe_chr(**str, buff, bp);
(*str)++;
break;
}
}
exit_sequence:
if (eflags != PE_NOTHING) {
if ((eflags & PE_COMPRESS_SPACES) && had_space &&
((*str)[-1] == ' ') && ((*bp)[-1] == ' '))
(*bp)--;
if (debugging) {
pe_info->nest_depth--;
**bp = '\0';
if (strcmp(sourcestr, startpos)) {
static char dbuf[BUFFER_LEN];
char *dbp;
if (pe_info->debug_strings) {
while (pe_info->debug_strings->prev)
pe_info->debug_strings = pe_info->debug_strings->prev;
while (pe_info->debug_strings->next) {
dbp = dbuf;
dbuf[0] = '\0';
safe_format(dbuf, &dbp, "%s :", pe_info->debug_strings->string);
*dbp = '\0';
if (Connected(Owner(executor)))
raw_notify(Owner(executor), dbuf);
notify_list(executor, executor, "DEBUGFORWARDLIST", dbuf,
NA_NOLISTEN | NA_NOPREFIX);
pe_info->debug_strings = pe_info->debug_strings->next;
mush_free((Malloc_t) pe_info->debug_strings->prev,
"process_expression.debug_node");
}
mush_free((Malloc_t) pe_info->debug_strings,
"process_expression.debug_node");
pe_info->debug_strings = NULL;
}
dbp = dbuf;
dbuf[0] = '\0';
safe_format(dbuf, &dbp, "%s => %s", debugstr, startpos);
*dbp = '\0';
if (Connected(Owner(executor)))
raw_notify(Owner(executor), dbuf);
notify_list(executor, executor, "DEBUGFORWARDLIST", dbuf,
NA_NOLISTEN | NA_NOPREFIX);
} else {
Debug_Info *node;
node = pe_info->debug_strings;
if (node) {
pe_info->debug_strings = node->prev;
if (node->prev)
node->prev->next = NULL;
mush_free((Malloc_t) node, "process_expression.debug_node");
}
}
mush_free((Malloc_t) debugstr, "process_expression.debug_source");
}
if (realbuff) {
**bp = '\0';
*bp = realbp;
safe_str(buff, realbuff, bp);
mush_free((Malloc_t) buff, "process_expression.buffer_extension");
}
}
/* Once we cross call limit, we stay in error */
if (pe_info && CALL_LIMIT && pe_info->call_depth <= CALL_LIMIT)
pe_info->call_depth--;
if (made_info)
mush_free((Malloc_t) pe_info, "process_expression.pe_info");
if (old_iter_limit != -1) {
inum_limit = old_iter_limit;
}
return retval;
}
#ifdef WIN32
#pragma warning( default : 4761) /* NJG: enable warning re conversion */
#endif