/*---------------------------------------------------------------------------
* Various Efuns.
*
*---------------------------------------------------------------------------
* This file acts as a repository for various old and new efuns. Over the time
* it will probably grow large enough to justify a split into several files.
*
* The implemented efuns, sorted by topic, are:
*
* Strings:
* vefun: copy_bits()
* tefun: make_shared_string()
* tefun: md5()
#ifdef USE_PROCESS_STRING
* tefun: process_string()
#endif
* efun: sscanf()
* efun: terminal_colour()
* tefun: trim()
* tefun: upper_case()
*
* Objects:
* xefun: all_environment()
* tefun: blueprint()
* vefun: clones()
* tefun: object_info()
* tefun: present_clone() (preliminary)
* tefun: present()
#ifdef USE_SET_IS_WIZARD
* tefun: set_is_wizard()
#endif
* tefun: set_modify_command()
* tefun: set_prompt()
* tefun: transfer() (optional)
* efun: say()
* efun: tell_room()
*
* Values:
* tefun: copy()
* tefun: deep_copy()
* vefun: filter()
* vefun: map()
* vefun: min()
* vefun: max()
*
* Others:
* tefun: debug_info()
* tefun: gmtime()
* tefun: localtime()
* tefun: shutdown()
*
*---------------------------------------------------------------------------
*/
#include "driver.h"
#include "typedefs.h"
#include "my-alloca.h"
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <time.h>
#define USES_SVALUE_STRLEN
#include "efuns.h"
#include "actions.h"
#include "array.h"
#include "backend.h"
#include "call_out.h"
#include "closure.h"
#include "comm.h"
#include "dumpstat.h"
#include "heartbeat.h"
#include "instrs.h" /* F_TRANSFER, F_UNDEF */
#include "interpret.h"
#include "main.h"
#include "mapping.h"
#include "md5.h"
#include "object.h"
#include "otable.h"
#include "ptrtable.h"
#include "rxcache.h"
#include "simulate.h"
#include "smalloc.h"
#include "stdstrings.h"
#include "stralloc.h"
#include "strfuns.h"
#include "swap.h"
#include "svalue.h"
#include "wiz_list.h"
#include "xalloc.h"
#include "../mudlib/sys/debug_info.h"
#include "../mudlib/sys/objectinfo.h"
#include "../mudlib/sys/strings.h"
#include "../mudlib/sys/time.h"
/* Forward declarations */
static void copy_svalue (svalue_t *dest, svalue_t *, struct pointer_table *, int);
/* Macros */
/* Typetests for xefuns/tefuns */
#define TYPE_TEST1(arg1,type1) \
if ((arg1)->type != type1) \
bad_xefun_arg(1, sp);
#define TYPE_TEST2(arg1,type1) \
if ((arg1)->type != type1) \
bad_xefun_arg(2, sp);
#define TYPE_TEST3(arg1,type1) \
if ((arg1)->type != type1) \
bad_xefun_arg(3, sp);
/* Typetests for vararg xefuns/vefuns */
#define TYPE_TESTV1(arg1,type1) \
if ((arg1)->type != type1) \
bad_xefun_vararg(1, sp);
#define TYPE_TESTV2(arg1,type1) \
if ((arg1)->type != type1) \
bad_xefun_vararg(2, sp);
#define TYPE_TESTV3(arg1,type1) \
if ((arg1)->type != type1) \
bad_xefun_vararg(3, sp);
#define TYPE_TESTV4(arg1,type1) \
if ((arg1)->type != type1) \
bad_xefun_vararg(4, sp);
#define TYPE_TESTV5(arg1,type1) \
if ((arg1)->type != type1) \
bad_xefun_vararg(5, sp);
/* Typetests for efuns */
#define E_TYPE_TESTV1(arg1,type1) \
if ((arg1)->type != type1) \
bad_efun_arg(1, -2, sp);
#define E_TYPE_TESTV2(arg1,type1) \
if ((arg1)->type != type1) \
bad_efun_arg(2, -2, sp);
/*-------------------------------------------------------------------------*/
#ifdef USE_SET_IS_WIZARD
Bool is_wizard_used = MY_FALSE;
/* TODO: This flag can go when the special commands are gone. */
#endif
/*=========================================================================*/
/* STRINGS */
/*-------------------------------------------------------------------------*/
static INLINE p_int
last_bit (const char *str)
/* Return the number of the last set bit in bitstring <str>.
*/
{
mp_int pos;
long len;
int c;
pos = -1;
/* Get the arguments */
len = (long)strlen(str);
/* First, find the last non-zero character */
c = 0;
while (len-- > 0 && (c = str[len]) == ' ') NOOP;
if (len >= 0)
{
int pattern;
/* Found a character, now determine the bit position */
c -= ' ';
pos = 6 * len + 5;
for ( pattern = 1 << 5
; pattern && !(c & pattern)
; pattern >>= 1, pos--)
NOOP;
}
return pos;
} /* last_bit() */
/*-------------------------------------------------------------------------*/
static INLINE void
copy_bits ( char * dest, p_int deststart
, char * src, p_int srcstart
, p_int len
)
/* Copy the bitrange <src>[<srcstart>..<srcstart>+<len>[ into the
* <dest> string starting at <deststart>.
* <dest> is supposed to be allocated big enough.
*/
{
char * pDest, * pSrc;
if (len < 1)
return;
pDest = dest + deststart / 6;
pSrc = src + srcstart / 6;
/* Special case: both source and target bit range start on the same bit.
*/
if (deststart % 6 == srcstart % 6)
{
/* Copy the first few bits until the next byte boundary.
* Make sure not to overwrite preexisting bytes
*/
if (deststart % 6 != 0)
{
int getmask, storemask;
storemask = (1 << deststart % 6) - 1;
/* storemask is now 00011111 through 00000001 */
getmask = ~storemask;
/* getmask is now 11100000 through 11111110 */
if (len < 6 - deststart % 6)
{
/* A really small copy: we don't even need all bits from
* the src byte.
*/
int len2 = 6 - deststart % 6 - len;
int lenmask;
lenmask = (1 << len2) - 1;
lenmask <<= 6 - len2;
/* lenmask has now 1-bits for the bits we need not to copy */
storemask &= ~lenmask;
}
*pDest = ' ' + ( ((*pDest - ' ') & storemask)
| ((*pSrc - ' ') & getmask)
);
len -= 6 - deststart % 6; /* might become negative here */
pDest++;
pSrc++;
}
/* Do a fast byte copy */
if (len >= 6)
{
memcpy(pDest, pSrc, len / 6);
}
/* Copy the remaining bytes, if any */
if (len > 0 && len % 6 != 0)
{
int mask;
mask = (1 << (len % 6)) - 1;
/* mask is now 00011111 through 00000001 */
pDest[len / 6] = ((pSrc[len / 6] - ' ') & mask) + ' ';
}
}
else
{
/* Copy enough bits so that deststart points to a byte boundary */
if (deststart % 6 != 0)
{
/* Since these are going to be at max five bits, we copy
* them in a loop bit by bit.
*/
int getmask, getbit;
int storemask, storebit;
char src_bits, dest_bits;
getbit = srcstart % 6;
getmask = 1 << getbit;
storebit = deststart % 6;
storemask = 1 << storebit;
src_bits = *pSrc - ' ';
dest_bits = *pDest - ' ';
while (len > 0 && storebit != 6)
{
if (0 != (src_bits & getmask))
dest_bits = dest_bits | storemask;
else
dest_bits = dest_bits & (~storemask);
srcstart++;
getbit++; getmask <<= 1;
if (getbit == 6)
{
getbit = 0;
getmask = 0x01;
pSrc++;
src_bits = *pSrc - ' ';
}
deststart++;
storebit++; storemask <<= 1;
len--;
}
*pDest = dest_bits + ' ';
pDest++;
} /* if need to align deststart */
/* deststart now points to byte boundary, that means we can
* now copy the bits by blockwise selection from the src string.
*/
if (len >= 6)
{
int getmask1, getmask2, len2;
int getshift1, getshift2;
len2 = 6 - srcstart % 6; /* Number of bits in first byte */
getmask1 = (1 << len2) - 1;
getmask1 <<= 6 - len2;
/* getmask1 has now the mask for the high order bits of the
* first src byte
*/
getshift1 = 6 - len2;
getshift2 = len2;
len2 = 6 - len2; /* Number of bits in second byte */
getmask2 = (1 << len2) - 1;
/* getmask2 has now the mask for the low order bits of the
* second src byte
*/
while (len >= 6)
{
*pDest = ' ' + ( ((pSrc[0]-' ') & getmask1) >> getshift1
|((pSrc[1]-' ') & getmask2) << getshift2
);
len -= 6;
pDest++;
pSrc++;
srcstart += 6;
deststart += 6;
}
} /* if >= 6 bits left */
/* deststart still points to byte boundary, but there are
* less than 6 bits left to copy.
*/
if (len > 0)
{
/* Since these are going to be at max five bits, we copy
* them in a loop bit by bit.
*/
int getmask, getbit;
int storemask, storebit;
char src_bits, dest_bits;
getbit = srcstart % 6;
getmask = 1 << getbit;
storebit = 0;
storemask = 0x01;
src_bits = *pSrc - ' ';
dest_bits = 0;
while (len > 0)
{
if (0 != (src_bits & getmask))
dest_bits |= storemask;
srcstart++;
getbit++; getmask <<= 1;
if (getbit == 6)
{
getbit = 0;
getmask = 0x01;
pSrc++;
src_bits = *pSrc - ' ';
}
deststart++;
storebit++; storemask <<= 1;
len--;
}
*pDest = dest_bits + ' ';
} /* if (1..5 bits left) */
} /* case selection */
} /* copy_bits() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_copy_bits (svalue_t *sp, int num_arg)
/* EFUN copy_bits()
*
* string copy_bits(string src, string dest
* [, int srcstart [, int deststart [, int copylen ]]]
* )
*
* Copy the bitrange [<srcstart>..<srcstart>+<copylen>[ from bitstring <src>
* and copy it into the bitstring <dest> starting at <deststart>, overwriting
* the original bits at those positions.
* The resulting combined string is returned, the input strings remain
* unaffected.
*
* If <srcstart> is not given, <src> is copied from the start.
* If <srcstart> is negative, it is counted from one past the last set
* bit in the string (ie. '-1' will index the last set bit).
* If <deststart> is not given, <dest> will be overwritten from the start.
* If <deststart> is negative, it is counted from one past the last set
* bit in the string (ie. '-1' will index the last set bit).
* If <copylen> is not given, it is assumed to be infinite, ie. the result
* will consist of <dest> up to position <deststart>, followed by
* the data copied from <src>.
* If <copylen> is negative, the function will copy the abs(<copylen>)
* bits _before_ <srcstart> in to the result.
*
* None of the range limits can become negative.
*/
{
p_int srcstart, deststart, copylen;
p_int srclen, destlen, resultlen;
Bool copyall;
char *src, *dest, *result;
int result_string_type;
/* Get the optional command arguments */
srcstart = deststart = copylen = 0;
copyall = MY_TRUE;
switch (num_arg)
{
case 5:
TYPE_TESTV5(sp, T_NUMBER);
copyall = MY_FALSE;
copylen = sp->u.number;
sp--;
num_arg--;
/* FALL THROUGH */
case 4:
TYPE_TESTV4(sp, T_NUMBER);
deststart = sp->u.number;
sp--;
num_arg--;
/* FALL THROUGH */
case 3:
TYPE_TESTV3(sp, T_NUMBER);
srcstart = sp->u.number;
sp--;
num_arg--;
/* FALL THROUGH */
}
inter_sp = sp;
/* Get the fixed command arguments and check for consistency */
TYPE_TESTV2(sp, T_STRING);
TYPE_TESTV1(sp-1, T_STRING);
dest = sp->u.string;
src = sp[-1].u.string;
sp++; /* We might need to save a precautionary reference to the result */
srclen = last_bit(src)+1;
destlen = last_bit(dest)+1;
if (srcstart < 0 && srcstart + srclen < 0)
error("Bad argument 3 to copy_bits(): Index %ld is out of range "
"(last bit: %ld).\n"
, (long)srcstart, (long)srclen);
if (srcstart < 0)
srcstart += srclen;
if (deststart < 0 && deststart + destlen < 0)
error("Bad argument 4 to copy_bits(): Index %ld is out of range "
"(last bit: %ld).\n"
, (long)deststart, (long)destlen);
if (deststart < 0)
deststart += destlen;
if (!copyall && copylen < 0)
{
if (srcstart + copylen < 0)
error("Bad argument 5 to copy_bits(): Length %ld out of range "
"(start index: %ld).\n"
, (long)copylen, (long)srcstart);
srcstart += copylen;
copylen = -copylen;
}
/* Test the input strings for sanity */
{
char *cp;
long len;
len = (long)svalue_strlen(sp-2);
cp = src;
for ( ; len > 0; len--, cp++)
{
int c = *cp - ' ';
if (c < 0 || c > 0x3f)
error("Bad argument 1 to copy_bits(): String contains "
"illegal character %d\n", c + ' ');
}
len = (long)svalue_strlen(sp-1);
cp = dest;
for ( ; len > 0; len--, cp++)
{
int c = *cp - ' ';
if (c < 0 || c > 0x3f)
error("Bad argument 2 to copy_bits(): String contains "
"illegal character %d\n", c + ' ');
}
}
/* Do the copying - some constellations are really simple */
if (copyall)
{
if (srcstart == 0 && deststart == 0)
{
if (sp[-2].x.string_type == STRING_SHARED)
{
result_string_type = STRING_SHARED;
result = ref_string(src);
}
else
{
result_string_type = STRING_MALLOC;
result = string_copy(src);
}
}
else
{
if (srclen > srcstart)
copylen = srclen - srcstart;
else
copylen = 0;
if (PINT_MAX - copylen < deststart)
error("copy_bits: result length exceeds numerical limit: "
"%ld + %ld\n"
, (long)deststart, (long)copylen
);
resultlen = deststart + copylen;
if (resultlen > MAX_BITS || resultlen < 0)
error("copy_bits: Result too big: %lu bits\n"
, (unsigned long)resultlen);
/* Get the result string and store the reference on the stack
* for error cleanups.
*/
result_string_type = STRING_MALLOC;
result = xalloc((resultlen + 5) / 6 + 1);
if (result == NULL)
{
error("Out of memory.\n");
/* NOTREACHED */
}
memset(result, ' ', (resultlen + 5) / 6);
result[(resultlen + 5) / 6] = '\0';
inter_sp = sp; put_malloced_string(sp, result);
/* Copy the first part of dest into the result */
if (deststart > 0)
{
if (deststart > destlen)
copy_bits(result, 0, dest, 0, destlen);
else
copy_bits(result, 0, dest, 0, deststart);
}
/* Now append the src */
copy_bits(result, deststart, src, srcstart, copylen);
}
}
else if (copylen == 0)
{
if (sp[-1].x.string_type == STRING_SHARED)
{
result_string_type = STRING_SHARED;
result = ref_string(dest);
}
else
{
result_string_type = STRING_MALLOC;
result = string_copy(dest);
}
}
else if (srcstart == 0 && deststart == 0
&& copylen >= destlen && copylen >= srclen)
{
if (sp[-2].x.string_type == STRING_SHARED)
{
result_string_type = STRING_SHARED;
result = ref_string(src);
}
else
{
result_string_type = STRING_MALLOC;
result = string_copy(src);
}
}
else
{
p_int destendlen; /* Length of the end part of dest to copy */
p_int srccopylen; /* Actual number of bits to copy from src */
/* Get the length to copy and the length of the result */
srccopylen = copylen;
if (srcstart >= srclen - copylen)
srccopylen = srclen - srcstart;
if (PINT_MAX - copylen < deststart)
error("copy_bits: result length exceeds numerical limit: %ld + %ld\n"
, (long)deststart, (long)copylen
);
resultlen = deststart + copylen;
if (resultlen < destlen)
resultlen = destlen;
if (resultlen > MAX_BITS || resultlen < 0)
error("copy_bits: Result too big: %lu bits\n"
, (unsigned long)resultlen);
destendlen = destlen - (deststart + copylen);
/* Get the result string and store the reference on the stack
* for error cleanups.
*/
result_string_type = STRING_MALLOC;
result = xalloc((resultlen + 5) / 6 + 1);
if (result == NULL)
{
error("Out of memory.\n");
/* NOTREACHED */
}
memset(result, ' ', (resultlen + 5) / 6);
result[(resultlen + 5) / 6] = '\0';
inter_sp = sp; put_malloced_string(sp, result);
/* Copy the first part of dest into the result */
if (deststart > 0)
{
if (deststart > destlen)
copy_bits(result, 0, dest, 0, destlen);
else
copy_bits(result, 0, dest, 0, deststart);
}
/* Copy the source part into the result */
copy_bits(result, deststart, src, srcstart, srccopylen);
/* Copy the remainder of dest into the result */
if (destendlen > 0)
{
copy_bits( result, deststart + copylen
, dest, deststart + copylen
, destendlen);
}
}
/* Clean up the stack and push the result */
sp--; /* The result reference for error cleanup */
free_svalue(sp--);
free_svalue(sp);
put_string(sp, result); sp->x.string_type = result_string_type;
return sp;
} /* f_copy_bits() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_make_shared_string (svalue_t *sp)
/* TEFUN make_shared_string()
*
* string make_shared_string(string s)
*
* If the passed string <s> is not shared, the efun enters it into
* the shared string table and returns the shared version. Else the
* passed string is returned.
*
* TODO: Improve the string handling of the driver so that this efun
* TODO:: becomes unnecessary.
*/
{
TYPE_TEST1(sp, T_STRING)
if (sp->x.string_type != STRING_SHARED)
{
char *s = make_shared_string(sp->u.string);
if (sp->x.string_type == STRING_MALLOC)
xfree(sp->u.string);
sp->u.string = s;
sp->x.string_type = STRING_SHARED;
}
return sp;
} /* f_make_shared_string() */
/*--------------------------------------------------------------------*/
svalue_t *
f_md5(svalue_t *sp)
/* TEFUN: md5()
*
* string md5(string arg)
*
* Create and return a MD5 message digest from the string <arg>.
*/
{
MD5_CTX context;
unsigned char *digest, d[17];
int i;
TYPE_TEST1(sp, T_STRING);
xallocate(digest, 33, "md5 result");
MD5Init(&context);
MD5Update(&context, (unsigned char *)sp->u.string, svalue_strlen(sp));
MD5Final(&context, d);
d[16]='\0';
for (i = 0; i < 16; i++)
sprintf((char *)digest+2*i, "%02x", d[i]);
digest[32] = '\0';
free_string_svalue(sp);
put_malloced_string(sp, (char *)digest);
return sp;
} /* f_md5() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_trim (svalue_t *sp, int num_arg)
/* VEFUN trim()
*
* string trim (string s [, int where [, string|int ch]])
*
* Remove all leading/trailing characters <ch> from the string <s>
* and return the new string. <ch> may be a single character, or a string
* of characters to be trimmed. If <ch> is not given or 0, it defaults
* to " \t". <where> determines where to remove the characters:
* TRIM_LEFT: remove the leading characters
* TRIM_RIGHT: remove the trailing characters
* TRIM_BOTH: remove both leading and trailing characters.
*
* TODO: Expand this to remove interim characters as well?
* TODO: Expand this to fold runs of embedded chs into just one?
*/
{
svalue_t * argp;
char *str, *end; /* Pointer to string begin and end */
char *left, *right; /* Pointer to the strings left and right end */
char def_ch[3] /* Buffer for single characters to strip */
= { '\t', ' ', '\0' };
char *strip; /* String of characters to strip */
size_t strip_l; /* Length of *strip */
int where;
/* Get and test the arguments */
if (num_arg > 3)
error("Bad number of arguments to trim()\n");
argp = sp - num_arg + 1;
TYPE_TESTV1(argp, T_STRING)
str = argp->u.string;
if (num_arg > 1)
{
TYPE_TESTV2(argp+1, T_NUMBER)
where = argp[1].u.number;
if (!where)
where = TRIM_LEFT|TRIM_RIGHT;
if (where & ~(TRIM_LEFT|TRIM_RIGHT))
error("Bad argument 2 to trim(): illegal value %ld\n", (long)where);
}
else
where = TRIM_LEFT|TRIM_RIGHT;
if (num_arg > 2)
{
if (argp[2].type == T_NUMBER)
{
if (argp[2].u.number <= 0 || argp[2].u.number >= 1 << CHAR_BIT)
error("Bad argument 3 to trim(): %ld is not a character\n"
, argp[2].u.number);
def_ch[0] = (char)argp[2].u.number;
def_ch[1] = '\0';
strip = def_ch;
strip_l = 1;
}
else if (argp[2].type == T_STRING)
{
strip = argp[2].u.string;
strip_l = svalue_strlen(argp+2);
}
else
bad_xefun_vararg(3, sp);
}
else
{
strip = def_ch;
strip_l = 2;
}
/* Get the string limits */
end = str + strlen(str);
if (where & TRIM_LEFT)
{
for (left = str
; *left != '\0' && strchr(strip, *left) != NULL
; left++
) NOOP;
}
else
left = str;
if (where & TRIM_RIGHT && end != left)
{
for (right = end; right != left && NULL != strchr(strip, right[-1])
; right--) NOOP;
}
else
right = end;
/* If there are things to strip, create a new string and put it
* into the place of the old one.
*/
if (left != str || right != end)
{
char * trimmed;
size_t newlen;
newlen = (size_t)(right - left);
xallocate(trimmed, newlen+1, "trimmed result");
memcpy(trimmed, left, newlen);
trimmed[newlen] = '\0';
free_string_svalue(argp);
put_malloced_string(argp, trimmed);
}
/* argp+2 might need to be freed, but argp+1 is always just a number.
* And the result argp is fine as it is.
*/
if (num_arg > 2 && argp[2].type == T_STRING)
free_svalue(argp+2);
return argp;
} /* f_trim() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_upper_case (svalue_t *sp)
/* TEFUN upper_case()
*
* string upper_case (string s)
*
* Convert all characters in <s> to upper case and return the new string.
*/
{
char *str, *s, *d, c;
ptrdiff_t initial_len;
TYPE_TEST1(sp, T_STRING)
/* Find the first non-uppercase character in the string */
for (s = sp->u.string; '\0' != (c = *s) && !islower((unsigned char)c); s++)
NOOP;
if ('\0' != *s) /* there are lowercase characters */
{
if (STRING_MALLOC == sp->x.string_type)
{
/* MALLOCed strings can be changed in-place */
for ( ; '\0' != (c = *s); s++)
{
if (islower((unsigned char)c))
*s = (char)toupper((unsigned char)c);
}
}
else
{
/* Other strings must be duplicated and then changed */
xallocate(str, svalue_strlen(sp)+1, "uppercase result");
initial_len = s - sp->u.string;
/* Copy the initial part */
if (initial_len)
memcpy(str, sp->u.string, (size_t)initial_len);
/* Copy and change the rest */
for (d = str + initial_len; '\0' != (c = *s++) ; )
{
if (islower((unsigned char)c))
c = (char)toupper((unsigned char)c);
*d++ = c;
}
*d = '\0';
free_string_svalue(sp);
put_malloced_string(sp, str);
}
}
/* That's it */
return sp;
} /* f_upper_case() */
/*-------------------------------------------------------------------------*/
static Bool
at_end (int i, int imax, int z, int *lens)
/* Auxilary function for e_terminal_colour().
*
* <z> is the position within string number <i>. <lens> is an array
* with the lengths of all <imax> strings.
*
* The function returns true if there are no more characters to process
* after <i>:<z> in all strings, else it returns false.
*/
{
if (z + 1 < lens[i])
return MY_FALSE;
for (i++; i < imax; i++) {
if (lens[i] > 0)
return MY_FALSE;
}
return MY_TRUE;
}
/*-------------------------------------------------------------------------*/
char *
e_terminal_colour ( char * text, mapping_t * map, svalue_t *cl
, int indent, int wrap
)
/* EFUN terminal_colour()
*
* string terminal_colour (string text, mapping|closure map
* , int wrap, int indent )
*
* Expands all colour-defines from the input-string and replaces them by the
* apropriate values found for the color-key inside the given mapping. The
* mapping has the format "KEY" : "value", non-string contents are ignored
* with one exception: the entry (0 : value) is used for otherwise
* unrecognized tags, if existing; <value> may be a string or a closure (see
* below).
*
* If <map> is given as 0, no keyword detection or replacement will be
* performed and the efun acts just as a text wrapper and indenter (assuming
* that <wrap> and <indent> are given).
*
* If <map> is given as a closure, it is called for each KEY with the key
* as argument, and it has to return the replacement string.
*
* The parameters wrap and indent are both optional, if only wrap is given
* then the str will be linewrapped at the column given with wrap. If indent
* is given too, then all wrapped lines will be indented with the number of
* blanks specified with indent.
*
* The wrapper itself ignores the length of the color macros and that what
* they contain, it wraps the string based on the length of the other chars
* inside. Therefor it is color-aware.
*
* This function is called from the evaluator and provided with the
* proper arguments.
*
* Result is a pointer to the final string. If no changes were necessary,
* this is <text> again; otherwise it is a pointer to memory allocated
* by the function.
* TODO: Instead of computing the wrapping twice, the first pass
* TODO:: should record what to break where.
*/
{
#define CALLOCATE(num, type) ((type *)xalloc(sizeof(type[1]) * (num) ))
/* Allocate a block of <num> elements of <type>
*/
#define RESIZE(ptr, num, type) ((type *)rexalloc((void *)ptr, sizeof(type) * (num)))
/* Resize the block <ptr> to hold <num> elements of <type>.
*/
#define NSTRSEGS 32
/* Allocation increment. */
#define TC_FIRST_CHAR '%'
#define TC_SECOND_CHAR '^'
/* The two magic characters.
*/
#define MAX_STRING_LENGTH 200000
/* The maximum length of the result.
*/
char *cp; /* Workpointer */
char *savestr = NULL; /* Allocated auxiliary string */
char *instr;
/* The input string. This may be <text> itself, or a working copy. */
char *deststr; /* Result string */
char **parts;
/* The <num> delimited parts from <instr>. This are mostly
* pointers into *<instr>, but can also be (uncounted) pointers to
* the string data in <map>.
*/
int num; /* Number of delimited parts in <instr> */
int *lens;
/* Length of the <num> parts. This value is negative for strings
* 'retrieved' from the <map>ping when wrapping is required. This
* is necessary to determine which parts[] to exempt from the
* wrapping calculation.
*/
svalue_t * mdata_save = NULL;
/* Pointer into an array on the stack, pointing to the next
* free entry.
* The array is used to keep copies of the replacement string
* svalues to make sure that the strings exist as long as we
* need them.
* By keeping the array itself on the stack, cleanup is automatic.
*/
int num_tmp; /* Number of temporary svalues on the stack */
int k; /* Index within a string */
int col; /* Current print column */
int j; /* Accumulated total length of result */
int j_extra; /* Temporary extra length of result before fmt'ing */
int start; /* Col of first non-blank character */
int space; /* Col of last space char */
int i;
Bool maybe_at_end; /* TRUE if the next text might start a new line */
Bool no_keys; /* TRUE if no delimiter in the string */
Bool indent_overflows;
/* Used to catch this boundary condition:
* t_c("\\/ "*32, 0, indent > MAX_STRING_LENGTH - 40, 40)
* In this case, the last indent is followed by no data, which the
* data copying part notices, but not the previous length calculation
* part.
* Set to TRUE in the length calculation when the possibility arises.
*/
if (wrap && indent > wrap)
{
error("(terminal_colour) indent %ld > wrap %ld\n"
, (long)indent, (long)wrap);
/* NOTREACHED */
return NULL;
}
instr = text;
num_tmp = 0;
/* Find the first occurance of the magic character pair.
* If found, duplicate the input string into instr and
* let cp point into that copy at the delimiter.
* If not found (or no mapping/closure given), cp will be NULL.
*/
if (map != NULL || cl != NULL)
{
cp = text;
do {
cp = strchr(cp, TC_FIRST_CHAR);
if (cp)
{
if (cp[1] == TC_SECOND_CHAR)
{
savestr = string_copy(text);
cp = savestr + (cp - text);
instr = savestr;
/* Check for the special escape '%%^^'.
* If found, modify it to '%^%^, and let cp
* point to it.
*/
if (cp > savestr
&& cp[-1] == TC_FIRST_CHAR
&& cp[2] == TC_SECOND_CHAR
)
{
cp--;
cp[1] = TC_SECOND_CHAR;
cp[2] = TC_FIRST_CHAR;
}
break;
}
cp++;
}
} while (cp);
}
else
cp = NULL;
/* If the delimiter was found, split up the instr into the
* parts and store them. If not found, just return.
*/
no_keys = MY_FALSE;
if (cp == NULL)
{
/* No delimiter found - but maybe we need to wrap */
if (wrap)
{
/* Yup, just fake one delimited part which just happens
* to not match anything in the mapping.
*/
num = 1;
parts = CALLOCATE(1, char *);
parts[0] = instr;
savestr = NULL; /* should be NULL anyway */
no_keys = MY_TRUE;
}
else
{
/* no delimiter in string and no wrapping, so return the original.
*/
return text;
}
}
else
{
/* There are delimiters in the string. Find them all, let the
* pointers in *<parts> point to the strings delimited by
* them, and let those parts end with a '\0'.
* This means modifying the *<instr>, but it is already
* a copy.
*/
/* If we got a mapping, do a one-time lookup for the default
* entry and store it in <cl>.
*/
if (map != NULL)
{
cl = get_map_value(map, &const0);
if (cl->type == T_NUMBER && cl->u.number == 0)
cl = NULL; /* No default entry */
if (cl && cl->type != T_STRING && cl->type != T_CLOSURE)
{
error("(terminal_colour) Illegal type for default entry.\n");
/* NOTREACHED */
return text;
}
}
/* cp here points to the first delimiter found */
parts = CALLOCATE( NSTRSEGS, char * );
if (!parts)
{
error("(terminal_colour) Out of memory (%lu bytes) "
"for %d parts.\n"
, (unsigned long) NSTRSEGS * sizeof(char*), NSTRSEGS);
/* NOTREACHED */
return NULL;
}
/* The string by definition starts with a non-keyword,
* which might be empty.
* Initialize our variables accordingly.
*/
num = 1;
parts[0] = instr;
*cp = '\0';
/* Search and find the other delimited segments.
* Loop variant: cp points to the last delimiter found,
* its first character replaced by \0, or cp points to NULL (exit
* condition)
* Loop invariant: instr points to the begin of the last delimited
* segment.
*/
while (cp)
{
/* Skip the delimiter found last and search the next */
cp += 2;
instr = cp;
do
{
cp = strchr(cp,TC_FIRST_CHAR);
if (cp) {
if (cp[1] == TC_SECOND_CHAR)
{
/* Check for the special escape '%%^^'.
* If found, modify it to '%^%^, and let cp
* point to it.
*/
if (cp > savestr
&& cp[-1] == TC_FIRST_CHAR
&& cp[2] == TC_SECOND_CHAR
)
{
cp--;
cp[1] = TC_SECOND_CHAR;
cp[2] = TC_FIRST_CHAR;
}
break;
}
cp++;
}
} while (cp);
if (cp)
{
/* Another delimiter found: put it into the parts array.
*/
*cp = '\0';
parts[num] = instr;
num++;
if (num % NSTRSEGS == 0)
parts = RESIZE(parts, num + NSTRSEGS, char * );
}
}
/* Trailing part, or maybe just a delimiter */
if (*instr)
parts[num++] = instr;
} /* if (delimiter found or not) */
/* Prepare the lens[] array */
if ( num )
lens = CALLOCATE(num, int);
else
lens = NULL;
/* If required, allocate the mdata save array on the stack */
if (!no_keys)
{
vector_t *vec;
vec = allocate_array_unlimited(num/2 + 1);
/* Slightly bigger than required */
mdata_save = vec->item;
inter_sp++; put_array(inter_sp, vec);
num_tmp++;
}
/* Do the the keyword replacement and calculate the lengths.
* The lengths are collected in the lens[] array to save the
* need for repeated strlens().
*/
for (i = 0; i < num; i++)
{
long len;
char * str;
svalue_t * mdata;
/* If parts[i] is a valid colour key, there must exist a shared
* string for it. Is that the case, look up parts[i] in the
* mapping and set the result in mdata, otherwise save that effort.
* However, if i is even, parts[i] is by definition not a colour
* key.
*/
mdata = NULL;
if (i % 2 && !no_keys)
{
if (parts[i][0] == '\0') /* Empty key - already handled */
str = NULL;
else
str = findstring(parts[i]);
if (str != NULL && map != NULL)
{
svalue_t mkey;
put_string(&mkey, str);
/* The only use of mkey is to index a mapping - an
* operation which will not decrement the refcount
* for <str>. This makes it safe to not count the
* ref by mkey here, and saves a bit time.
*/
/* now look for mapping data */
mdata = get_map_value(map, &mkey);
if (mdata->type == T_NUMBER && mdata->u.number == 0)
mdata = NULL; /* No entry */
}
/* If the map lookup didn't find anything, try the
* <cl>osure (which might be the default entry)
*/
if (mdata == NULL && cl != NULL && parts[i][0] != '\0')
{
if (cl->type == T_STRING)
{
mdata = cl;
}
else
{
/* It's a closure.
* We keep the result in the array on the stack
* to make sure it lives until we are done processing it.
*/
push_volatile_string(parts[i]);
call_lambda(cl, 1);
*mdata_save = *inter_sp;
inter_sp--;
mdata = mdata_save++;
if (mdata->type != T_STRING)
{
error("(terminal_colour) Closure did not return a string.\n");
/* NOTREACHED */
return NULL;
}
}
}
}
else if (!(i % 2) && !no_keys
&& i < num -1 && parts[i+1][0] == '\0')
{
/* Special case: the following colour key is the empty "%^%^".
* We interpret it as literal "%^" and add it to this part.
* Both part[i] and part[i+1] will end with the same '\0'.
*/
parts[i+1][-2] = TC_FIRST_CHAR;
}
/* If mdata found a string, use it instead of the old parts[i].
* Note its length, making it negative where necessary.
*/
if ( mdata && mdata->type == T_STRING )
{
parts[i] = mdata->u.string;
len = (long)svalue_strlen( mdata );
if (wrap)
len = -len;
}
else
len = (long)strlen(parts[i]);
lens[i] = len;
} /* for (i = 0..num) for length gathering */
/* Do the wrapping analysis.
* In order to do this, we need to have all lengths already
* available.
*/
col = 0;
start = -1;
space = 0;
maybe_at_end = MY_FALSE;
indent_overflows = MY_FALSE;
j = 0; /* gathers the total length of the final string */
j_extra = 0; /* gathers the extra length needed during fmt'ing */
for (i = 0; i < num; i++)
{
long len;
len = lens[i];
if (len > 0)
{
/* This part must be considered for wrapping/indentation */
if (maybe_at_end)
{
/* This part may start a new line, so count in the indent */
if (j + indent > MAX_STRING_LENGTH)
{
/* This string no longer counts, so we are still in a
* maybe_at_end condition. This means we will end up
* truncating the rest of the fragments too, since the
* indent will never fit.
*/
lens[i] = 0;
len = 0;
}
else
{
j += indent;
col += indent;
maybe_at_end = MY_FALSE;
}
}
/* Add the new string to the total length */
j += len;
if (j > MAX_STRING_LENGTH)
{
/* Overflow: shorten this fragment to fit (and all
* the following ones will be shortened to 0 length).
*/
lens[i] -= j - MAX_STRING_LENGTH;
j = MAX_STRING_LENGTH;
}
/* If wrapping is requested, perform the analysis */
if (wrap)
{
int z; /* Index into the current string */
char *p = parts[i]; /* Pointer into the current string */
for (z = 0; z < lens[i]; z++)
{
char c = p[z]; /* current character */
if (c == '\n')
{
/* Hard line break: start a new line */
col = 0;
start = -1;
}
else
{
/* All space characters in columns before col <start>
* do not count.
*/
if (col > start || c != ' ')
col++;
else
{
j--;
j_extra++;
}
/* If space, remember the position */
if (c == ' ')
space = col;
if (col == wrap+1)
{
/* Wrapping necessary */
if (space)
{
/* Break the line at the last space. */
int next_word_len = 0;
if (col - space > 2)
{
/* Check if the current word is too
* long to be put on one line. If it
* is, don't bother breaking at the last
* space.
*/
int test_z = z;
int test_i = i;
Bool done = MY_FALSE;
next_word_len = col - space;
for ( ; !done && test_i < num; test_i++)
{
if (lens[test_i] < 0)
continue;
for ( ; !done && test_z < lens[test_i]
; test_z++)
{
char testc = parts[test_i][test_z];
if (testc == ' ' || testc == '\n')
{
done = MY_TRUE;
break;
}
next_word_len++;
}
test_z = 0;
}
}
if (next_word_len+indent > wrap)
{
/* Word is too long, just treat it
* as if there is no space within range.
*/
space = 0;
j++;
col = 1;
}
else
{
/* It makes sense to break properly */
col -= space;
space = 0;
}
}
else
{
/* No space within range: simply let this
* one extent over the wrap margin and
* restart counting.
*/
j++;
col = 1;
}
/* Reset the start column. */
start = indent;
}
else
continue; /* the for(z) */
}
/* If we get here, we ended a line */
if (col || z + 1 != lens[i])
{
/* Not at the end of the fragment: count in
* the indent from the new line.
*/
j += indent;
col += indent;
}
else
{
maybe_at_end = MY_TRUE;
}
/* Guard against overflow */
if (j > MAX_STRING_LENGTH)
{
/* Reduce this part to fit; all the following
* parts will be reduced to shreds.
*/
indent_overflows = MY_TRUE;
lens[i] -= (j - MAX_STRING_LENGTH);
j = MAX_STRING_LENGTH;
if (lens[i] < z)
{
/* must have been ok or we wouldn't be here */
lens[i] = z;
break;
}
}
} /* for (z = 0..lens[i]) */
} /* if (wrap) */
}
else
{
/* This replacement does not need to be wrapped. */
indent_overflows = MY_FALSE;
j += -len;
if (j > MAX_STRING_LENGTH)
{
/* Max length exceeded: shrink the working length
* to something usable. All following fragments
* will be shrunk to length 0.
*/
lens[i] = -(-(lens[i]) - (j - MAX_STRING_LENGTH));
j = MAX_STRING_LENGTH;
}
} /* if (len > 0) */
} /* for (i = 0..num) for wrapping analysis */
/* Now we have the final string in parts and length in j.
* let's compose the result, wrapping it where necessary.
*/
xallocate(deststr, (size_t)(j+1), "result string");
cp = deststr; /* destination pointer */
if (wrap)
{
/* Catenate and wrap the parts together. This will look similar
* to the length computation above.
*/
int space_garbage = 0;
/* Number of characters to be ignored since the last space,
* most of them are control codes and other junk.
*/
size_t tmpmem_size;
char *tmpmem;
/* Temporary buffer for the current line */
char *pt;
/* Pointer into tmpmem */
tmpmem_size = (size_t)j+j_extra+1;
/* Actually, the allocated '+j_extra' size is never used, but
* it makes the sanity check below simpler.
*/
xallocate(tmpmem, tmpmem_size, "temporary string");
col = 0;
start = -1;
space = 0;
pt = tmpmem;
/* Loop over all parts */
for (i = 0; i < num; i++)
{
int kind; /* The kind of a line break */
int len; /* Actual length of the line */
int l = lens[i]; /* Length of current part */
char *p = parts[i]; /* Current part */
if (pt - tmpmem + ((l < 0) ? -l : l) >= tmpmem_size)
{
error("Partial string '%s' too long (%ld+%ld >= %ld).\n"
, p
, (long)(pt - tmpmem), (long)((l < 0) ? -l : l)
, (long)tmpmem_size);
/* NOTREACHED */
return NULL;
}
if (l < 0)
{
/* String retrieved from the mapping: not to be counted */
memcpy(pt, p, (size_t)-l);
pt += -l;
space_garbage += -l;
continue;
}
/* Loop over the current part, copying and wrapping */
for (k = 0; k < lens[i]; k++)
{
int n;
char c = p[k]; /* Current character */
/* Copy the character into tmpmem */
*pt++ = c;
if (c == '\n')
{
/* Start a new line */
col = 0;
kind = 0;
start = -1;
}
else
{
/* All space characters in columns before col <start>
* do not count.
*/
if (col > start || c != ' ')
col++;
else
pt--;
/* If space, remember the position */
if (c == ' ')
{
space = col;
space_garbage = 0;
}
/* Wrapping necessary? */
if (col == wrap+1)
{
if (space)
{
/* Break at last space */
int next_word_len = 0;
if (col - space > 2)
{
/* Check if the following word is too
* long to be put on one line. If it
* is, don't bother breaking at the last
* space.
*/
int test_k = k;
int test_i = i;
Bool done = MY_FALSE;
next_word_len = col - space;
for ( ; !done && test_i < num; test_i++)
{
if (lens[test_i] < 0)
continue;
for ( ; !done && test_k < lens[test_i]
; test_k++)
{
char testc = parts[test_i][test_k];
if (testc == ' ' || testc == '\n')
{
done = MY_TRUE;
break;
}
next_word_len++;
}
test_k = 0;
}
}
if (next_word_len + indent > wrap)
{
/* Word is too long: treat it as if there
* is no space within range.
*/
space = 0;
col = 1;
kind = 2;
}
else
{
col -= space;
space = 0;
kind = 1;
}
}
else
{
/* No space within range: simply let this
* one extent over the wrap margin and
* restart counting.
*/
col = 1;
kind = 2;
}
/* Reset the start column */
start = indent;
}
else
continue;
} /* if (type of c) */
/* If we get here, we ended a line, and kind tells us why:
* kind == 0: hard line break
* 1: line wrapped at suitable space
* 2: line extended over the limit with no space
*/
len = (kind == 1 ? col + space_garbage : col);
/* Determine the length of the _previous_ (and therefore
* wrapped) line and copy it from tmpmem into deststr.
*/
n = (pt - tmpmem) - len;
memcpy(cp, tmpmem, (size_t)n);
cp += n;
if (kind == 1)
{
/* replace the space with the newline */
cp[-1] = '\n';
}
if (kind == 2)
{
/* need to insert a newline */
*cp++ = '\n';
}
/* Remove the previous line from tmpmem */
move_memory(tmpmem, tmpmem + n, (size_t)len);
pt = tmpmem + len;
/* If we are indenting, check if we have to add the
* indentation space.
* Note: if kind == 2, it's the current character which
* will go onto the next line, otherwise it's the next
* character will. The difference is important in the
* call to at_end().
*/
if (indent != 0
&& ( len > space_garbage
|| !at_end(i, num, (kind == 2) ? k-1 : k, lens))
)
{
/* There will be data coming next: insert the
* indentation.
*/
memset(cp, ' ', (size_t)indent);
cp += indent;
col += indent;
}
/* Since we're in a new line, all the 'garbage' is gone. */
space_garbage = 0;
} /* for(k = 0.. lens[i] */
} /* for(i = 0..num) */
/* Append the last fragment from the tmpmem to the result */
memcpy(cp, tmpmem, (size_t)(pt - tmpmem));
cp += pt - tmpmem;
xfree(tmpmem);
}
else
{
/* No wrapping: just catenate the parts (and all lens[] entries
* are positive here)
*/
for (i = 0; i < num; i++)
{
memcpy(cp, parts[i], (size_t)lens[i]);
cp += lens[i];
}
}
/* Terminate the string */
*cp = '\0';
if ( lens )
xfree(lens);
if ( parts )
xfree(parts);
if (savestr)
xfree(savestr);
while (num_tmp > 0)
{
free_svalue(inter_sp);
inter_sp--;
num_tmp--;
}
/* now we have what we want */
#ifdef DEBUG
if (cp - deststr != j
&& (!indent_overflows || cp - deststr != wrap)
) {
fatal("Length miscalculated in terminal_colour()\n"
" Expected: %i (or %i) Was: %ld\n"
" In string: %s\n"
" Out string: %s\n"
" Indent: %i Wrap: %i, indent overflow: %s\n"
, j, wrap
, (long)(cp - deststr), text, deststr, indent, wrap
, indent_overflows ? "true" : "false");
}
#endif
return deststr;
#undef CALLOCATE
#undef RESIZE
#undef NSTRSEGS
#undef TC_FIRST_CHAR
#undef TC_SECOND_CHAR
} /* e_terminal_colour() */
#ifdef USE_PROCESS_STRING
/*-------------------------------------------------------------------------*/
static char *
process_value (char *str)
/* Helper function for process_string(): take a function call in <str>
* in the form "function[:objectname]{|arg}" and try to call it.
* If the function exists and returns a string, the result is a pointer
* to the string, which must be copied immediately.
* If the function can't be called, or does not return a string, the
* result is NULL.
*/
{
svalue_t *ret; /* Return value from the function call */
char *func; /* Copy of the <str> string for local modifications */
char *func2; /* Shared string with the function name from <func> */
char *obj; /* NULL or points to the object part in <func> */
char *arg; /* NULL or points to the first arg in <func> */
char *narg; /* Next argument while pushing them */
int numargs; /* Number of arguments to the call */
object_t *ob;
/* Simple check if the argument is valid */
if (strlen(str) < 1 || !isalpha((unsigned char)(str[0])))
return NULL;
/* Copy the argument so that we can separate the various
* parts with \0 characters.
*/
func = string_copy(str);
/* Find the object and the argument part */
arg = strchr(func,'|'); if (arg) { *arg='\0'; arg++; }
obj = strchr(func,':'); if (obj) { *obj='\0'; obj++; }
/* Check if the function exists at all. apply() will be delighted
* over the shared string anyway.
*/
if ( NULL == (func2 = findstring(func)) )
{
xfree(func);
return NULL;
}
/* Get the object */
if (!obj)
ob = current_object;
else
ob = find_object(obj);
if (!ob)
{
xfree(func);
return NULL;
}
/* Push all arguments as strings to the stack
*/
for (numargs = 0; arg; arg = narg)
{
narg = strchr(arg,'|');
if (narg)
*narg = '\0';
push_string_malloced(arg);
numargs++;
if (narg)
{
*narg = '|';
narg++;
}
}
/* We no longer need this */
xfree(func);
/* Apply the function and see if adequate answer is returned.
*/
ret = apply(func2, ob, numargs);
if (ret && ret->type == T_STRING)
return ret->u.string;
/* The svalue is stored statically in apply_return_value */
return NULL;
} /* process_value() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_process_string(svalue_t *sp)
/* EFUN process_string()
*
* string process_string(string str)
*
* Searches string str for occurences of a "value by function
* call", which is @@ followed by an implicit function call. See
* "value_by_function_call" in the principles section.
*
* The value should contain a string like this:
* @@function[:filename][|arg|arg]@@
*
* function must return a string or else the string which should be
* processed will be returned unchanged.
*
* Note that process_string() does not recurse over returned
* replacement values. If a function returns another function
* description, that description will not be replaced.
*
* Both filename and args are optional.
*
* TODO: OSB has a bugfix for this function to handle spaces in
* TODO:: arguments. Basically using the new explode solves the problem.
* TODO:: public string process_string(string str)
* TODO:: {
* TODO:: int il;
* TODO:: string * parts;
* TODO:: string tmp;
* TODO:: parts = explode(str, "@@");
* TODO:: for ( il = 1; il < sizeof(parts); il += 2) {
* TODO:: tmp = process_value(parts[il]);
* TODO:: if (stringp(tmp))
* TODO:: parts[il] = tmp;
* TODO:: }
* TODO:: return implode(parts, "");
* TODO:: }
*/
{
vector_t *vec; /* Arg string exploded by '@@' */
object_t *old_cur; /* Old current object */
wiz_list_t *old_eff_user; /* Old euid */
int pr_start; /* Index of the first pr-spec in vec */
int il; /* Index in vec */
Bool changed; /* True if there was a replacement */
Bool ch_last; /* True if the last vec-entry was replaced */
char *p0, *p1, *p2;
char *buf; /* Result string(s) */
char *str; /* The argument string */
TYPE_TEST1(sp, T_STRING);
str = sp->u.string;
if (!str || !(p1 = strchr(str,'@')))
return sp; /* Nothing to do */
old_eff_user = NULL;
old_cur = current_object;
if (!current_object)
{
/* This means we are called from notify_ in comm1
* We must temporary set eff_user to backbone uid for
* security reasons.
*/
svalue_t *ret;
current_object = command_giver;
ret = apply_master(STR_GET_BB_UID,0);
if (!ret)
return sp;
if (ret->type != T_STRING
&& (strict_euids || ret->type != T_NUMBER || ret->u.number))
return sp;
if (current_object->eff_user)
{
old_eff_user = current_object->eff_user;
if (ret->type == T_STRING)
current_object->eff_user = add_name(ret->u.string);
else
current_object->eff_user = NULL;
}
}
/* Explode the argument by the '@@' */
/* TODO: Rewrite to use new explode_string() */
vec = old_explode_string(str,"@@");
if (!vec)
return sp;
push_referenced_vector(vec); /* automatic free in case of errors */
pr_start = ((str[0]=='@') && (str[1]=='@')) ? 0 : 1;
for ( ch_last = MY_FALSE, changed = MY_FALSE, il = pr_start
; (size_t)il < VEC_SIZE(vec)
; il++)
{
p0 = vec->item[il].u.string;
/* Try to interpret the entry as function call.
* If that succeeds, hold the result (freshly allocated) in p2.
*/
p1 = strchr(p0, ' ');
if (!p1)
{
/* No space, the whole entry might be a function call */
p2 = process_value(p0);
if (p2)
{
/* Yup, it is: copy the result */
p2 = string_copy(p2);
ch_last = MY_TRUE;
}
}
else
{
/* There is a space: just interpret the characters before
* as possible function call.
*/
size_t len;
len = (size_t)(p1 - p0);
buf = xalloc(len + 1);
strncpy(buf, p0, len);
buf[len] = '\0';
p2 = process_value(buf);
if (p2)
{
/* We got a result: join it with the remains after the
* space and put it into p2.
*/
char * tmp;
len = strlen(p2);
tmp = xalloc(len + strlen(p1) + 1);
strcpy(tmp,p2);
strcpy(tmp+len,p1);
p2 = tmp;
}
xfree(buf);
}
if (!p2)
{
/* No replacement by function call */
if (!ch_last)
{
/* ...but we have to recreate the '@@' from the original */
p2 = xalloc(3+strlen(p0));
strcpy(p2,"@@");
strcpy(p2+2,p0);
}
else
{
ch_last = MY_FALSE;
}
}
else
{
/* Mark that we have a true replacement */
changed = MY_TRUE;
}
/* If we have a replacement string, put it into place. */
if (p2)
{
xfree(p0);
vec->item[il].u.string = p2;
}
} /* for() */
/* If there were changes, implode the vector again */
if (changed)
buf = implode_string(vec, "");
else
buf = NULL;
/* Clean up */
inter_sp--;
free_array(vec);
if (old_eff_user)
{
current_object->eff_user = old_eff_user;
}
current_object = old_cur;
/* Return the result */
if (buf)
{
free_string_svalue(sp);
put_malloced_string(sp, buf);
}
return sp;
} /* f_process_string() */
#endif /* USE_PROCESS_STRING */
/*-------------------------------------------------------------------------*/
/* Structures for sscanf() */
/* Flags for every argument whether to assign and/or count it
*/
struct sscanf_flags {
int do_assign: 16;
int count_match: 16;
};
/* Packet of information passed between the scan functions:
*/
struct sscanf_info
{
svalue_t *arg_start; /* first argument for the current %-spec */
svalue_t *arg_current; /* current argument to consider */
svalue_t *arg_end; /* the last argument */
char *fmt_end;
/* After the match: the next character in the fmt-string to match.
*/
char *match_end;
/* After the match: the next character in the in-string to match.
* NULL for 'no match'.
*/
mp_uint field; /* Numbers: parsed fieldwidth */
mp_uint min; /* Numbers: parsed precision */
mp_uint string_max; /* Strings: parsed fieldwidth */
mp_uint string_min; /* Strings: parsed 'precision' */
struct sscanf_flags flags;
int sign; /* -1 for '%-d', 0 for '%d', '%+d' or '%u' */
mp_int number_of_matches; /* Number of matches so far */
};
/*-------------------------------------------------------------------------*/
static void
sscanf_decimal (char *str, struct sscanf_info *info)
/* Parse a number from <str> according the .field and .min given in <info>,
* and, if successfull, store it in <info>->arg_current, which is then
* incremented.
*
* <info>.match_end and .fmt_end are set properly on return.
*/
{
static svalue_t tmp_svalue = { T_NUMBER };
mp_int i, num;
char c;
num = 0;
i = (mp_int)info->min;
if (i > 0)
{
/* The number must have at least i digits */
info->field -= i;
do
{
if (!lexdigit(c = *str))
{
if (info->fmt_end[-1] != 'd')
{
info->match_end = NULL;
}
else
{
info->match_end = str;
info->fmt_end = "d"+1;
}
return;
}
str++;
num = num * 10 + c - '0';
} while (--i);
}
/* There can be info->field more digits */
i = (mp_int)info->field;
while (--i >= 0)
{
if (!lexdigit(c = *str))
break;
str++;
num = num * 10 + c - '0';
}
info->match_end = str;
if (info->flags.do_assign)
{
/* Assign the parsed number */
if (info->arg_current >= info->arg_end)
return;
tmp_svalue.u.number = (p_int)((num ^ info->sign) - info->sign);
transfer_svalue((info->arg_current++)->u.lvalue, &tmp_svalue);
}
info->number_of_matches += info->flags.count_match;
return;
}
/*-------------------------------------------------------------------------*/
static char *
sscanf_match_percent (char *str, char *fmt, struct sscanf_info *info)
/* Match a %-specification, called from sscanf_match().
*
* <fmt> points to the first character after the '%'.
* <str> points to the first character to match.
*
* Return new value for <str> if matching is to be continued, else
* return NULL and write in info->match_end the match end if a match was
* found, NULL otherwise.
*
* If a match was found, also write info->fmt_end with a pointer to the
* conversion character, and info->flags, info->field, info->min.
*/
{
char c;
mp_uint *nump; /* Pointer to parsed fieldwidth resp. precision */
/* Initialize field with a large value that will become
* zero when doubled. Because 10 is divisible by 2, the multiply
* will zero it. Note that it is negative before we decrement it
* the first time.
*/
*(nump = &info->field) = (((mp_uint)-1 / 2)) + 1;
info->min = 1;
info->flags.do_assign = 1;
info->flags.count_match = 1;
for (;;)
{
switch(c = *fmt++)
{
case '!':
info->flags.count_match ^= 1;
info->flags.do_assign ^= 1;
continue;
case '~':
info->flags.do_assign ^= 1;
continue;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
*nump = *nump * 10 + c - '0';
continue;
case '*':
if (info->arg_current >= info->arg_end
|| info->arg_current->u.lvalue->type != T_NUMBER)
{
info->match_end = NULL;
return NULL;
}
*nump = (mp_uint)((info->arg_current++)->u.lvalue->u.number);
continue;
case '.':
*(nump = &info->min) = 0;
continue;
case 'd':
/* Skip leading whitespace */
while(isspace((unsigned char)*str))
str++;
/* FALLTHROUGH */
case 'D':
/* Match a signed number */
if (*str == '-')
{
info->sign = -1;
str++;
}
else
{
if (*str == '+')
str++;
info->sign = 0;
}
info->fmt_end = fmt;
sscanf_decimal(str, info);
return NULL;
case 'U':
/* Match an unsigned number */
info->sign = 0;
info->fmt_end = fmt;
sscanf_decimal(str, info);
return NULL;
case 's':
/* Match a string */
/* min = (min was explicitly given) ? min : 0; */
info->string_max = info->field;
info->field = 0;
info->string_min = *nump;
info->fmt_end = fmt;
info->match_end = str;
return NULL;
default:
error("Bad type : '%%%c' in sscanf fmt string.\n", fmt[-1]);
return 0;
case 't':
{
/* Skip whitespaces */
mp_int i;
info->field -= (i = (mp_int)info->min);
/* Required whitespace */
while (--i >= 0)
{
if (!isspace((unsigned char)*str))
{
info->match_end = NULL;
return NULL;
}
str++;
}
/* Optional whitespace */
i = (mp_int)info->field;
while (--i >= 0)
{
if (!isspace((unsigned char)*str))
break;
str++;
}
info->fmt_end = fmt;
return str;
}
} /* switch(*fmt) */
} /* forever */
} /* sscanf_match_percent() */
/*-------------------------------------------------------------------------*/
static void
sscanf_match (char *str, char *fmt, struct sscanf_info *info)
/* Find position in <str> after matching text from <fmt>, and place it in
* info->match_end.
* Set info->match_end to NULL for no match.
* Set info->fmt_end to a guaranteed static '\0' when the fmt string ends.
*/
{
char c;
/* (Re)set the current argument */
info->arg_current = info->arg_start;
/* Loop over the format string, matching characters */
for (;;)
{
if ( !(c = *fmt) )
{
info->match_end = str;
info->fmt_end = "d"+1;
return;
}
fmt++;
if (c == '%')
{
c = *fmt;
if (c != '%')
{
/* We have a format specifier! */
char *new_str;
new_str = sscanf_match_percent(str, fmt, info);
if (!new_str)
return; /* Failure or string specifier */
str = new_str;
fmt = info->fmt_end;
continue;
}
fmt++;
}
if (c == *str++)
{
continue;
}
else
{
info->match_end = NULL;
return;
}
}
} /* sscanf_match() */
/*-------------------------------------------------------------------------*/
static char *
sscanf_search (char *str, char *fmt, struct sscanf_info *info)
/* sscanf() found a possible '%s' match. This function finds the start
* of the next match in <str> and returns a pointer to it.
* If none can be found, NULL is returned.
*/
{
char a, b, c;
mp_int n;
a = *fmt;
if (!a)
{
/* End of format: match all */
info->fmt_end = "d"+1;
info->arg_current = info->arg_start;
return info->match_end = str + strlen(str);
}
fmt++;
b = *fmt++;
if (a == '%')
{
if (b != '%')
{
/* It's another %-spec: try to find its match within the
* <str> by attempting the match at one character after the
* other.
*/
for (fmt -= 2; *str; str++)
{
sscanf_match(str, fmt, info);
/* If the sequence was '%s%d', the '%d' has to match
* on the first try, otherwise all will be assigned to
* the '%s'.
*/
if (b == 'd' && info->match_end == str)
return str + strlen(str);
/* If we found a match at the current position of str,
* the '%s' ends here and the next match starts.
*/
if (info->match_end)
return str;
}
return NULL;
}
else
{
/* Double '%' stands for '%' itself */
b = *fmt++;
}
}
/* a and b are now the 'next two' characters from fmt, and they
* don't start a %-spec.
*/
if (b == a)
{
/* A run of identical characters: set n to the length */
n = 0;
do {
n++;
b = *fmt++;
} while (b == a);
if (a == '%')
{
/* n fmt-'%' represent (n/2) real '%'s */
if (n & 1)
{
n >>= 1;
fmt--;
goto a_na_search;
}
n >>= 1;
}
if (b == '\0')
{
fmt--;
goto a_na_search;
}
if (b == '%')
{
/* Since a is not '%' here, this may be the next %-spec */
b = *fmt++;
if (b != '%')
{
fmt -= 2;
goto a_na_search;
}
}
/* Search in <str> for the sequence <a>, (<n>+?)*<a>, <b>.
* <b> is a character which starts a successfull new match.
* To find this, the function tries a match at every possible <b>
* it finds.
*
* If the <b> is found, all the characters before belong to
* the previous %s match, if not found, the whole string
* belongs to the match.
*/
{
char ch;
mp_int i;
a_na_b_search:
if ( !(ch = *str++) )
return NULL;
/* First <a> ? */
if (ch != a)
goto a_na_b_search;
/* Followed by <n> <a>s? */
i = n;
do {
if ( !(ch = *str++) )
return NULL;
if (ch != a)
goto a_na_b_search;
} while (--i);
/* There may be more <a>s */
do {
if ( !(ch = *str++) )
return NULL;
} while (ch == a);
/* If followed by <b>, we may have found the next match */
if (ch == b)
{
sscanf_match(str, fmt, info);
if (info->match_end)
return str - n - 2;
}
/* Not found: start all over */
goto a_na_b_search;
}
/* NOTREACHED */
}
if (!b)
{
/* Special case: the sequence is just <a> */
n = 0;
fmt--;
/* Search in <str> for the sequence <a>, (<n>+?)*<a>, 'x'.
* 'x' is a character which starts a successfull new match.
* To find this, the function tries a match at every possible 'x'
* it finds.
*
* If the 'x' is found, all the characters before belong to
* the previous %s match, if not found, the whole string
* belongs to the match.
*/
{
char ch;
mp_int i;
a_na_search:
if ( !(ch = *str++) )
return NULL;
/* First <a>? */
if (ch != a)
goto a_na_search;
/* Followed by <n> <a>s? */
if ( 0 != (i = n)) do {
if ( !(ch = *str++) )
return NULL;
if (ch != a)
goto a_na_search;
} while (--i);
/* For every other character, test if the next match starts here */
do {
sscanf_match(str, fmt, info);
if (info->match_end)
return str - n - 1;
if ( !(ch = *str++) )
return NULL;
} while (ch == a);
/* Not found: start all over */
goto a_na_search;
}
/* NOTREACHED */
}
if (b == '%')
{
/* Special case: <a>, (<n>+?)*<a>, which we know will
* be successfull.
*/
b = *fmt++;
if (b != '%')
{
fmt -= 2;
n = 0;
goto a_na_search;
/* "goto, goto, goto - this is sooo ugly" says Tune */
}
}
/* a != b && b != '%' here */
c = *fmt;
if (!c)
{
/* Special case: <a>, (0+?)*<b>, '\0' which we know will
* be successfull because the fmt ends.
*/
n = 0;
goto ab_nab_search;
}
if (c == '%')
{
c = *++fmt;
if (c != '%')
{
/* Special case: <a>, (0+?)*<b>, '%-spec', which we know will
* be successfull because of the format spec.
*/
fmt--;
n = 0;
goto ab_nab_search;
}
/* just a literal '%' */
}
fmt++;
if (c == a)
{
c = *fmt++;
if (c == '%')
{
c = *fmt;
if (c != '%')
{
/* <a> (0+?)*<b> <a> '%-spec' */
fmt -= 2 + (a == '%');
n = 0;
goto ab_nab_search;
}
fmt++;
/* just a literal '%' */
}
if (c != b)
{
if (!c)
{
/* <a> (0+?)*<b> <a> '\0' */
fmt -= 2 + (a == '%');
n = 0;
goto ab_nab_search;
}
/* Search in <str> for <a> ?*{<b> <a>} <a> <c>.
* <c> is a character which starts a successfull new match.
* To find this, the function tries a match at every possible <c>
* it finds.
*
* If the <c> is found, all the characters before belong to
* the previous %s match, if not found, the whole string
* belongs to the match.
*/
for (;;)
{
char ch;
ch = *str++;
a_b_a_c_check_a:
if (!ch)
return NULL;
/* First <a>? */
if (ch != a)
continue;
ch = *str++;
a_b_a_c_check_b:
/* Check for <b> <a> */
if (ch != b)
goto a_b_a_c_check_a;
ch = *str++;
if (ch != a)
continue;
ch = *str++;
if (ch != c)
goto a_b_a_c_check_b;
sscanf_match(str, fmt, info);
if (info->match_end)
return str - 4;
goto a_b_a_c_check_a;
}
/* NOTREACHED */
}
/* c == b */
n = 2;
/* Search in <str> for <a> <b> n*{<a> <b>} ?*<b> 'x'.
* 'x' is a character which starts a successfull new match.
* To find this, the function tries a match at every possible
* 'x' it finds.
*
* If the 'x' is found, all the characters before belong to
* the previous %s match, if not found, the whole string
* belongs to the match.
*/
{
char ch;
int i;
goto ab_nab_search;
ab_nab_check_0:
if (!ch)
return NULL;
ab_nab_search:
ch = *str++;
ab_nab_check_a:
/* First <a> */
if (ch != a)
goto ab_nab_check_0;
/* A <b> should follow, introducing the repetition */
ch = *str++;
if (ch != b)
goto ab_nab_check_a;
/* <n> times the couple <a> <b> should follow */
if (0 != (i = n)) do
{
ch = *str++;
if (ch != a)
goto ab_nab_check_0;
ch = *str++;
if (ch != b)
goto ab_nab_check_a;
} while (i -= 2);
do {
sscanf_match(str, fmt, info);
if (info->match_end)
return str - n - 2;
ch = *str++;
if (ch != a)
goto ab_nab_check_0;
ch = *str++;
} while (ch == b);
goto ab_nab_check_0;
}
/* NOREACHED */
}
/* c != a */
/* Search in <str> for <a> <b> <c> 'x'.
* 'x' is a character which starts a successfull new match.
* To find this, the function tries a match at every possible
* 'x' it finds.
*
* If the 'x' is found, all the characters before belong to
* the previous %s match, if not found, the whole string
* belongs to the match.
*/
for (;;) {
char ch;
ch = *str++;
a_b_c_check_a:
if (!ch)
return 0;
if (ch != a)
continue;
ch = *str++;
if (ch != b)
goto a_b_c_check_a;
ch = *str++;
if (ch != c)
goto a_b_c_check_a;
sscanf_match(str, fmt, info);
if (info->match_end)
return str - 3;
}
/* NOTREACHED */
} /* sscanf_search() */
/*-------------------------------------------------------------------------*/
int
e_sscanf (int num_arg, svalue_t *sp)
/* EFUN sscanf()
*
* int sscanf(string str, string fmt, mixed var1, mixed var2, ...)
*
* Execute the sscanf() function if <num_arg> arguments on the stack <sp>,
* and return the number of matches.
*
* Parse a string str using the format fmt. fmt can contain strings seperated
* by %d and %s. Every %d and %s corresponds to one of var1, var2, ... .
*
* The match operators in the format string have one of these formats:
* %[!|~][<size>[.<minmatch>]]<type>
*
* <type> may be:
* d: matches any number.
* D: matches any number.
* U: matches any unsigned number.
* s: matches any string.
* %: matches the % character.
* t: matches whitespace (spaces and tab characters), but does
* not store them (the simple ' ' matches just spaces and
* can't be given a size specification).
*
* <size> is the expected field size, <minmatch> the demanded minimal match
* length (defaults are 0 for strings and 1 for numbers). Each of these both
* may be specified numerically, or as '*' - then the value of the variable at
* the current place in the argument list is used.
*
* Specifying ! will perform the match, but neither store the result nor count
* the match.
* Specifying ~ will perform and count the match, but not store the result.
*
* (You can think of '!' as negating on a wholesale basis, while '~'
* negates only individual bits. Thus, '%!' negates both do_assign
* and count_match, while '%~' only negates do_assign.)
*
* The difference between %d and %D/%U is that the latter will abort an
* immediately preceeding %s as soon as possible, whereas the former will
* attempt to make largest match to %s first. %D/%U will still not skip
* whitespace, use %.0t%D to skip optional whitespace.
*
* The number of matched arguments will be returned.
*
* The function sscanf is special, in that arguments are passed by reference
* automatically.
*/
{
char *fmt; /* Format description */
char *in_string; /* The string to be parsed. */
svalue_t sv_tmp;
svalue_t *arg0; /* The first argument */
struct sscanf_flags flags; /* local copy of info.flags */
struct sscanf_info info; /* scan information packet */
inter_sp = sp; /* we can have an error() deep inside */
arg0 = sp - num_arg + 1;
/* First get the string to be parsed.
*/
E_TYPE_TESTV1(arg0, T_STRING);
in_string = arg0[0].u.string;
/* Now get the format description.
*/
E_TYPE_TESTV2((arg0+1), T_STRING);
fmt = arg0[1].u.string;
info.arg_end = arg0 + num_arg;
info.arg_current = arg0 + 2;
/* Loop for every % or substring in the format. Update the
* arg pointer continuosly. Assigning is done manually, for speed.
*/
for (info.number_of_matches = 0; info.arg_current <= info.arg_end; )
{
info.arg_start = info.arg_current;
sscanf_match(in_string, fmt, &info);
in_string = info.match_end;
if (!in_string) /* End of input? */
break;
/* Either fmt is out, or we found a string match */
match_skipped:
fmt = info.fmt_end;
if (fmt[-1] == 's')
{
mp_uint max;
mp_int num;
char *match;
svalue_t *arg;
flags = info.flags;
/* Set match to the first possible end character of the string
* to match.
*/
num = (mp_int)info.string_min;
if (num > 0)
{
if (num > (mp_int)strlen(in_string))
break;
match = in_string + num;
}
else
{
/* num = 0 */
match = in_string;
}
max = info.string_max;
arg = info.arg_current;
info.arg_start = arg + flags.do_assign;
if (info.arg_start > info.arg_end)
{
break;
}
/* Search the real end of the string to match and set match
* to it.
*/
if (NULL != (match = sscanf_search(match, fmt, &info))
&& (mp_uint)(num = match - in_string) <= max)
{
/* Got the string: assign resp. skip it */
if (flags.do_assign)
{
xallocate(match, (size_t)num+1, "matchstring");
strncpy(match, in_string, (size_t)num);
match[num] = '\0';
put_malloced_string(&sv_tmp, match);
transfer_svalue(arg->u.lvalue, &sv_tmp);
}
in_string = info.match_end;
info.number_of_matches += flags.count_match;
info.arg_start = info.arg_current;
goto match_skipped;
}
/* no match found */
break;
}
if (!fmt[0]) /* End of format */
break;
}
return info.number_of_matches;
}
/*=========================================================================*/
/* OBJECTS */
/*-------------------------------------------------------------------------*/
svalue_t *
x_all_environment (svalue_t *sp, int numarg)
/* XEFUN all_environment()
*
* object * all_environment(object o)
*
* Return an array with all environments of object <o> in 'outgoing'
* order. If <o> has no environment, 0 is returned.
*
* The caller checked the correctness of the arguments.
*/
{
object_t *o;
/* Get the arg from the stack, if any */
if (numarg)
{
o = ref_object(sp->u.ob, "all_environment");
free_object_svalue(sp);
}
else
{
o = current_object;
sp++;
}
/* Default return value: 0 */
put_number(sp, 0);
if (!(o->flags & O_DESTRUCTED))
{
mp_int num;
object_t *env;
vector_t *v;
svalue_t *svp;
/* Count the number of environments */
for ( num = 0, env = o->super
; NULL != env
; num++, env = env->super)
NOOP;
if (num)
{
/* Get the array and fill it */
v = allocate_uninit_array(num);
for ( svp = v->item, env = o->super
; NULL != env
; svp++, env = env->super)
{
put_ref_object(svp, env, "all_environment");
}
/* Put the result on the stack and return */
put_array(sp, v);
}
}
if (numarg)
free_object(o, "all_environment");
return sp;
} /* x_all_environment() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_blueprint (svalue_t *sp)
/* EFUN blueprint()
*
* object blueprint ()
* object blueprint (string|object ob)
*
* The efuns returns the blueprint for the given object <ob>, or for
* the current object if <ob> is not specified.
*
* If the blueprint is destructed, the efun returns 0.
* For objects with replaced programs, the efun returns the blueprint
* for the replacement program.
*/
{
object_t * obj, * blueprint;
if (sp->type == T_OBJECT)
obj = sp->u.ob;
else if (sp->type == T_STRING)
{
obj = get_object(sp->u.string);
if (!obj)
{
error("Object not found: %s\n", sp->u.string);
/* NOTREACHED */
return sp;
}
}
else
{
bad_xefun_arg(1, sp);
/* NOTREACHED */
return sp;
}
if ((obj->flags & O_SWAPPED) && load_ob_from_swap(obj) < 0)
error("Out of memory: unswap object '%s'.\n", obj->name);
blueprint = NULL;
if (obj->prog != NULL
&& obj->prog->blueprint != NULL
&& !(obj->prog->blueprint->flags & O_DESTRUCTED)
)
blueprint = ref_object(obj->prog->blueprint, "blueprint()");
free_svalue(sp);
if (blueprint != NULL)
put_object(sp, blueprint);
else
put_number(sp, 0);
return sp;
} /* f_blueprint() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_clones (svalue_t *sp, int num_arg)
/* VEFUN clones()
*
* object* clones ()
* object* clones (int what)
* object* clones (string|object obj [, int what])
*
* The efuns returns an array with all clones of a certain blueprint.
*
* If <obj> is given, all clones of the blueprint of <obj> (which
* may be <obj> itself) are returned, otherwise all clones of the
* current object resp. of the current object's blueprint. If <obj>
* is given as string, it must name an existing object.
*
* <what> selects how to treat clones made from earlier versions
* of the blueprint:
* == 0: (default) return the clones of the current blueprint only.
* == 1: return the clones of the previous blueprints only.
* == 2: return all clones of the blueprint.
*
* If the driver is compiled with DYNAMIC_COSTS, the cost of this
* efun is proportional to the number of objects in the game.
*/
{
char *name; /* The load-name to search */
mp_int mintime; /* 0 or lowest load_time for an object to qualify */
mp_int maxtime; /* 0 or highest load_time for an object to qualify */
mp_int load_id; /* The load_id of the reference */
object_t **ores; /* Table pointing to the found objects */
size_t found; /* Number of objects found */
size_t checked; /* Number of objects checked */
size_t osize; /* Size of ores[] */
vector_t *res; /* Result vector */
svalue_t *svp;
object_t *ob;
mintime = 0;
maxtime = 0;
load_id = 0;
/* Evaluate the arguments */
{
int what;
object_t * reference;
/* Defaults */
reference = current_object;
what = 0;
if (num_arg == 1)
{
if (sp->type == T_OBJECT)
reference = sp->u.ob;
else if (sp->type == T_STRING) {
reference = get_object(sp->u.string);
if (!reference) {
error("Object not found: %s\n", sp->u.string);
/* NOTREACHED */
return sp;
}
}
else if (sp->type == T_NUMBER) {
what = sp->u.number;
if (what < 0 || what > 2) {
bad_xefun_vararg(1, sp);
/* NOTREACHED */
return sp;
}
}
else {
bad_xefun_vararg(1, sp);
/* NOTREACHED */
return sp;
}
}
else if (num_arg == 2)
{
if (sp->type == T_NUMBER) {
what = sp->u.number;
if (what < 0 || what > 2)
{
bad_xefun_vararg(2, sp);
/* NOTREACHED */
return sp;
}
}
else {
bad_xefun_vararg(2, sp);
/* NOTREACHED */
return sp;
}
free_svalue(sp--); inter_sp = sp;
if (sp->type == T_OBJECT)
reference = sp->u.ob;
else if (sp->type == T_STRING) {
reference = get_object(sp->u.string);
if (!reference)
{
error("Object not found: %s\n", sp->u.string);
/* NOTREACHED */
return sp;
}
}
else {
bad_xefun_vararg(1, sp);
/* NOTREACHED */
return sp;
}
}
name = reference->load_name;
/* If we received a clone as reference, we have
* to find the blueprint.
*/
if (reference->flags & O_CLONE)
reference = get_object(reference->load_name);
/* Encode the 'what' parameter into the two
* time bounds: during the search we just have to
* compare the load_times against these bounds.
*/
if (!reference)
{
if (!what)
{
/* We know that there is nothing to find,
* therefore return immediately.
*/
res = allocate_array(0);
if (!num_arg)
sp++;
else
free_svalue(sp);
put_array(sp, res);
return sp;
}
/* otherwise we can return all we find */
}
else if (!what)
{
/* Just the new objects */
mintime = reference->load_time;
load_id = reference->load_id;
}
else if (what == 1)
{
/* Just the old objects */
maxtime = reference->load_time;
load_id = reference->load_id;
}
} /* evaluation of arguments */
/* Prepare the table with the object pointers */
osize = 256;
found = 0;
checked = 0;
xallocate(ores, sizeof(*ores) * osize, "initial object table");
/* Loop through the object list */
for (ob = obj_list; ob; ob = ob->next_all)
{
checked++;
if ((ob->flags & (O_DESTRUCTED|O_CLONE)) == O_CLONE
&& ob->load_name == name
&& (!mintime || ob->load_time > mintime
|| (ob->load_time == mintime && ob->load_id >= load_id)
)
&& (!maxtime || ob->load_time < maxtime
|| (ob->load_time == maxtime && ob->load_id < load_id)
)
)
{
/* Got one */
if (found == osize)
{
/* Need to extend the array */
osize += 256;
ores = rexalloc(ores, sizeof(*ores) * osize);
if (!ores)
{
error("(clones) Out of memory (%lu bytes) for increased "
"object table.\n"
, (unsigned long) sizeof(*ores)*osize);
/* NOTREACHED */
return sp;
}
}
ores[found++] = ob;
}
}
#if defined(DYNAMIC_COSTS)
eval_cost += checked / 100 + found / 256;
#endif /* DYNAMIC_COSTS */
/* Create the result and put it onto the stack */
if (max_array_size && found > max_array_size)
{
xfree(ores);
error("Illegal array size: %ld\n", (long)found);
/* NOTREACHED */
return sp;
}
res = allocate_uninit_array(found);
if (!res)
{
xfree(ores);
error("(clones) Out of memory: array[%lu] for result.\n"
,(unsigned long) found);
/* NOTREACHED */
return sp;
}
osize = found;
for (found = 0, svp = res->item; found < osize; found++, svp++)
{
put_ref_object(svp, ores[found], "clones");
}
if (!num_arg)
sp++;
else
free_svalue(sp);
put_array(sp, res);
xfree(ores);
return sp;
} /* f_clones() */
/*-------------------------------------------------------------------------*/
static object_t *
object_present_in (char *str, object_t *ob)
/* <ob> is the first object in an environment: test all the objects there
* if they match the id <str>.
* <str> may be of the form "<id> <num>" - then the <num>th object with
* this <id> is returned, it it is found.
*/
{
svalue_t *ret;
char *p;
int count = 0; /* >0: return the <count>th object */
int length;
char *item;
length = strlen(str);
xallocate(item, (size_t)length + 1, "work string");
strcpy(item, str);
push_malloced_string(item); /* free on error */
/* Check if there is a number in the string */
p = item + length - 1;
if (*p >= '0' && *p <= '9')
{
while(p > item && *p >= '0' && *p <= '9')
p--;
if (p > item && *p == ' ')
{
count = atoi(p+1) - 1;
*p = '\0';
}
}
/* Now look for the object */
for (; ob; ob = ob->next_inv)
{
push_volatile_string(item);
ret = sapply(STR_ID, ob, 1);
if (ob->flags & O_DESTRUCTED)
{
xfree(item);
inter_sp--;
return NULL;
}
if (ret == NULL || (ret->type == T_NUMBER && ret->u.number == 0))
continue;
if (count-- > 0)
continue;
xfree(item);
inter_sp--;
return ob;
}
xfree(item);
inter_sp--;
/* Not found */
return NULL;
} /* object_present_in() */
/*-------------------------------------------------------------------------*/
object_t *
e_object_present (svalue_t *v, object_t *ob)
/* EFUN present()
*
* object present(mixed str)
* object present(mixed str, object ob)
*
* If an object that identifies (*) to the name ``str'' is present
* in the inventory or environment of this_object (), then return
* it. If "str" has the form "<id> <n>" the <n>-th object matching
* <id> will be returned.
*
* "str" can also be an object, in which case the test is much faster
* and easier.
*
* A second optional argument ob is the enviroment where the search
* for str takes place. Normally this_player() is a good choice.
* Only the inventory of ob is searched, not its environment.
* TODO: Make this a nice efuns.c-Efun and also implement
* TODO:: deep_present() and present_clone() (see bugs/f-something)
*/
{
svalue_t *ret;
object_t *ret_ob;
Bool specific = MY_FALSE;
/* Search where? */
if (!ob)
ob = current_object;
else
specific = MY_TRUE;
if (ob->flags & O_DESTRUCTED)
return NULL;
if (v->type == T_OBJECT)
{
/* Oooh, that's easy. */
if (specific)
{
if (v->u.ob->super == ob)
return v->u.ob;
else
return NULL;
}
if (v->u.ob->super == ob
|| (v->u.ob->super == ob->super && ob->super != 0))
return v->u.ob;
return NULL;
}
/* Always search in the object's inventory */
ret_ob = object_present_in(v->u.string, ob->contains);
if (ret_ob)
return ret_ob;
if (specific)
return NULL;
/* Search in the environment of <ob> if it was not specified */
if (!specific && ob->super)
{
/* Is it _the_ environment? */
push_volatile_string(v->u.string);
ret = sapply(STR_ID, ob->super, 1);
if (ob->super->flags & O_DESTRUCTED)
return NULL;
if (ret && !(ret->type == T_NUMBER && ret->u.number == 0))
return ob->super;
/* No, search the other objects here. */
return object_present_in(v->u.string, ob->super->contains);
}
/* Not found */
return NULL;
} /* e_object_present() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_object_info (svalue_t *sp, int num_args)
/* TEFUN object_info()
*
* mixed * object_info(object o, int type)
* mixed * object_info(object o, int type, int which)
*
* Return an array with information about the object <o>. The
* type of information returned is determined by <type>.
*
* If <which> is specified, the function does not return the full array, but
* just the single value from index <which>.
*/
{
vector_t *v;
object_t *o, *o2;
program_t *prog;
svalue_t *svp, *argp;
mp_int v0, v1, v2;
int flags, pos, value;
svalue_t result;
/* Test and get the arguments from the stack */
argp = sp - num_args + 1;
TYPE_TESTV1(argp, T_OBJECT)
TYPE_TESTV2(argp+1, T_NUMBER)
if (num_args == 3)
{
TYPE_TESTV3(argp+2, T_NUMBER)
value = argp[2].u.number;
assign_svalue_no_free(&result, &const0);
}
else
value = -1;
o = argp->u.ob;
/* Depending on the <type> argument, determine the
* data to return.
*/
switch(argp[1].u.number)
{
#define PREP(max) \
if (num_args == 2) { \
v = allocate_array(max); \
if (!v) \
error("Out of memory: array[%d] for result.\n" \
, max); \
svp = v->item; \
} else { \
v = NULL; \
if (value < 0 || value >= max) \
error("Illegal index for object_info(): %d, " \
"expected 0..%d\n", value, max-1); \
svp = &result; \
}
#define ST_NUMBER(which,code) \
if (value == -1) svp[which].u.number = code; \
else if (value == which) svp->u.number = code; \
else {}
#define ST_DOUBLE(which,code) \
if (value == -1) { \
svp[which].type = T_FLOAT; \
STORE_DOUBLE(svp+which, code); \
} else if (value == which) { \
svp->type = T_FLOAT; \
STORE_DOUBLE(svp, code); \
} else {}
#define ST_STRING(which,code) \
if (value == -1) { \
put_malloced_string(svp+which, code); \
} else if (value == which) { \
put_malloced_string(svp, code); \
} else {}
#define ST_RSTRING(which,code) \
if (value == -1) { \
put_ref_string(svp+which, code); \
} else if (value == which) { \
put_ref_string(svp, code); \
} else {}
#define ST_OBJECT(which,code,tag) \
if (value == -1) { \
put_ref_object(svp+which, code, tag); \
} else if (value == which) { \
put_ref_object(svp, code, tag); \
} else {}
default:
error("Illegal value %ld for object_info().\n", sp->u.number);
/* NOTREACHED */
return sp;
/* --- The basic information from the object structure */
case OINFO_BASIC:
PREP(OIB_MAX);
flags = o->flags;
ST_NUMBER(OIB_HEART_BEAT, (flags & O_HEART_BEAT) ? 1 : 0);
#ifdef O_IS_WIZARD
ST_NUMBER(OIB_IS_WIZARD, (flags & O_IS_WIZARD) ? 1 : 0);
#else
ST_NUMBER(OIB_IS_WIZARD, 0);
#endif
ST_NUMBER(OIB_ENABLE_COMMANDS, (flags & O_ENABLE_COMMANDS) ? 1 : 0);
ST_NUMBER(OIB_CLONE, (flags & O_CLONE) ? 1 : 0);
ST_NUMBER(OIB_DESTRUCTED, (flags & O_DESTRUCTED) ? 1 : 0);
ST_NUMBER(OIB_SWAPPED, (flags & O_SWAPPED) ? 1 : 0);
ST_NUMBER(OIB_ONCE_INTERACTIVE, (flags & O_ONCE_INTERACTIVE) ? 1 : 0);
ST_NUMBER(OIB_RESET_STATE, (flags & O_RESET_STATE) ? 1 : 0);
ST_NUMBER(OIB_WILL_CLEAN_UP, (flags & O_WILL_CLEAN_UP) ? 1 : 0);
ST_NUMBER(OIB_LAMBDA_REFERENCED, (flags & O_LAMBDA_REFERENCED) ? 1 : 0);
ST_NUMBER(OIB_SHADOW, (flags & O_SHADOW) ? 1 : 0);
ST_NUMBER(OIB_REPLACED, (flags & O_REPLACED) ? 1 : 0);
#ifdef USE_SET_LIGHT
ST_NUMBER(OIB_TOTAL_LIGHT, o->total_light);
#else
ST_NUMBER(OIB_TOTAL_LIGHT, 0);
#endif
ST_NUMBER(OIB_NEXT_RESET, o->time_reset);
ST_NUMBER(OIB_TIME_OF_REF, o->time_of_ref);
ST_NUMBER(OIB_REF, o->ref);
ST_NUMBER(OIB_GIGATICKS, (p_int)o->gigaticks);
ST_NUMBER(OIB_TICKS, (p_int)o->ticks);
ST_NUMBER(OIB_SWAP_NUM, O_SWAP_NUM(o));
ST_NUMBER(OIB_PROG_SWAPPED, O_PROG_SWAPPED(o) ? 1 : 0);
ST_NUMBER(OIB_VAR_SWAPPED, O_VAR_SWAPPED(o) ? 1 : 0);
if (compat_mode)
{
ST_STRING(OIB_NAME, string_copy(o->name));
}
else
{
ST_STRING(OIB_NAME, add_slash(o->name));
}
ST_RSTRING(OIB_LOAD_NAME, o->load_name);
o2 = o->next_all;
if (o2)
{
ST_OBJECT(OIB_NEXT_ALL, o2, "object_info(0)");
} /* else the element was already allocated as 0 */
o2 = o->prev_all;
if (o2)
{
ST_OBJECT(OIB_PREV_ALL, o2, "object_info(0)");
} /* else the element was already allocated as 0 */
break;
/* --- Position in the object list */
case OINFO_POSITION:
PREP(OIP_MAX);
o2 = o->next_all;
if (o2)
{
ST_OBJECT(OIP_NEXT, o2, "object_info(1) next");
} /* else the element was already allocated as 0 */
o2 = o->prev_all;
if (o2)
{
ST_OBJECT(OIP_PREV, o2, "object_info(1) next");
} /* else the element was already allocated as 0 */
if (value == -1 || value == OIP_POS)
{
/* Find the non-destructed predecessor of the object */
if (obj_list == o)
{
pos = 0;
}
else
for (o2 = obj_list, pos = 0; o2; o2 = o2->next_all)
{
pos++;
if (o2->next_all == o)
break;
}
if (!o2) /* Not found in the list (this shouldn't happen) */
pos = -1;
ST_NUMBER(OIP_POS, pos);
}
break;
/* --- Memory and program information */
case OINFO_MEMORY:
PREP(OIM_MAX);
if ((o->flags & O_SWAPPED) && load_ob_from_swap(o) < 0)
error("Out of memory: unswap object '%s'.\n", o->name);
prog = o->prog;
ST_NUMBER(OIM_REF, prog->ref);
ST_STRING(OIM_NAME, string_copy(prog->name));
ST_NUMBER(OIM_PROG_SIZE, (long)(PROGRAM_END(*prog) - prog->program));
/* Program size */
ST_NUMBER(OIM_NUM_FUNCTIONS, prog->num_functions);
ST_NUMBER(OIM_SIZE_FUNCTIONS
, (p_int)(prog->num_functions * sizeof(uint32)
+ prog->num_function_names * sizeof(short)));
/* Number of function names and the memory usage */
ST_NUMBER(OIM_NUM_VARIABLES, prog->num_variables);
ST_NUMBER(OIM_SIZE_VARIABLES
, (p_int)(prog->num_variables * sizeof(variable_t)));
/* Number of variables and the memory usage */
v1 = program_string_size(prog, &v0, &v2);
ST_NUMBER(OIM_NUM_STRINGS, prog->num_strings);
ST_NUMBER(OIM_SIZE_STRINGS, (p_int)v0);
ST_NUMBER(OIM_SIZE_STRINGS_DATA, v1);
ST_NUMBER(OIM_SIZE_STRINGS_TOTAL, v2);
/* Number of strings and the memory usage */
ST_NUMBER(OIM_NUM_INCLUDES, prog->num_includes);
{
int i = prog->num_inherited;
int cnt = 0;
inherit_t *inheritp;
for (inheritp = prog->inherit; i--; inheritp++)
{
if (inheritp->inherit_type == INHERIT_TYPE_NORMAL
|| inheritp->inherit_type == INHERIT_TYPE_VIRTUAL
)
cnt++;
}
ST_NUMBER(OIM_NUM_INHERITED, cnt);
}
ST_NUMBER(OIM_SIZE_INHERITED
, (p_int)(prog->num_inherited * sizeof(inherit_t)));
/* Number of inherites and the memory usage */
ST_NUMBER(OIM_TOTAL_SIZE, prog->total_size);
{
mp_int totalsize;
ST_NUMBER(OIM_DATA_SIZE, data_size(o, &totalsize));
ST_NUMBER(OIM_TOTAL_DATA_SIZE, totalsize);
}
ST_NUMBER(OIM_NO_INHERIT, (prog->flags & P_NO_INHERIT) ? 1 : 0);
ST_NUMBER(OIM_NO_CLONE, (prog->flags & P_NO_CLONE) ? 1 : 0);
ST_NUMBER(OIM_NO_SHADOW, (prog->flags & P_NO_SHADOW) ? 1 : 0);
break;
#undef PREP
#undef ST_NUMBER
#undef ST_DOUBLE
#undef ST_STRING
#undef ST_RSTRING
#undef ST_OBJECT
}
free_svalue(sp);
sp--;
free_svalue(sp);
if (num_args == 3)
{
sp--;
free_svalue(sp);
}
/* Assign the result */
if (num_args == 2)
put_array(sp, v);
else
transfer_svalue_no_free(sp, &result);
return sp;
}
/*-------------------------------------------------------------------------*/
svalue_t *
f_present_clone (svalue_t *sp)
/* TEFUN present_clone()
*
* object present_clone(string str, object env)
* object present_clone(object obj, object env)
*
* Search in the inventory of <env> for the first object with the
* same blueprint as object <obj>, resp. for the first object with
* the loadname <str>, and return that object.
*
* If not found, 0 is returned.
*/
{
char * name; /* the shared loadname to look for */
object_t *obj; /* the object under scrutiny */
/* Test and get the arguments from the stack */
TYPE_TEST2(sp, T_OBJECT)
if (sp[-1].type == T_STRING)
{
size_t len;
long i;
char * end;
char * name0; /* Intermediate name */
name0 = sp[-1].u.string;
/* Normalize the given string and check if it is
* in the shared string table. If not, we know that
* there is no blueprint with that name
*/
/* First, slash of a trailing '#<num>' */
len = svalue_strlen(sp-1);
i = (long)len;
end = name0 + len;
while (--i > 0)
{
char c;
c = *--end;
if (c < '0' || c > '9' )
{
/* Not a digit: maybe a '#' */
if ('#' == c && len - i > 1)
{
name0 = alloca((size_t)i + 1);
if (!name0)
error("Out of stack memory.\n");
strncpy(name0, sp[-1].u.string, (size_t)i);
name0[i] = '\0';
}
break; /* in any case */
}
}
/* Now make the name sane */
name = (char *)make_name_sane(name0, !compat_mode);
if (name)
name = findstring(name);
else
name = findstring(name0);
}
else if (sp[-1].type == T_OBJECT)
{
name = sp[-1].u.ob->load_name;
}
else
bad_xefun_arg(1, sp);
obj = NULL;
if (name)
{
/* We have a name, now look for the object */
for (obj = sp->u.ob->contains; obj != NULL; obj = obj->next_inv)
{
if (!(obj->flags & O_DESTRUCTED) && name == obj->load_name)
break;
}
}
/* Assign the result */
sp = pop_n_elems(2, sp) + 1;
if (obj != NULL)
put_ref_object(sp, obj, "present_clone");
else
put_number(sp, 0);
return sp;
} /* f_present_clone() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_to_object (svalue_t *sp)
/* TEFUN to_object()
*
* object to_object(string arg)
* object to_object(closure arg)
* object to_object(object arg)
*
* The argument is converted into an object, if possible. For strings, the
* object with a matching file_name() is returned, or 0 if there is none, as
* find_object() does. For (bound!) closures, the object holding the closure
* is returned.
* Objects and the number 0 return themselves.
*/
{
int n;
object_t *o;
switch(sp->type)
{
case T_NUMBER:
if (!sp->u.number)
return sp;
/* FALLTHROUGH */
default:
bad_xefun_arg(1, sp);
case T_CLOSURE:
n = sp->x.closure_type;
o = sp->u.ob;
if (n == CLOSURE_EFUN + F_UNDEF)
o = NULL;
else if (CLOSURE_MALLOCED(n))
{
if (n == CLOSURE_UNBOUND_LAMBDA)
bad_xefun_arg(1, sp);
o = sp->u.lambda->ob;
}
if (o && o->flags & O_DESTRUCTED)
o = NULL;
free_closure(sp);
break;
case T_OBJECT:
return sp;
case T_STRING:
o = find_object(sp->u.string);
free_svalue(sp);
break;
}
if (o)
put_ref_object(sp, o, "to_object");
else
put_number(sp, 0);
return sp;
} /* f_to_object() */
/*-------------------------------------------------------------------------*/
#ifdef F_TRANSFER
svalue_t *
f_transfer (svalue_t *sp)
/* TEFUN transfer()
*
* int transfer(object item, object dest)
*
* This efun is for backward compatibility only. It is only
* available in compat mode.
*
* Move the object "item" to the object "dest". All kinds of
* tests are done, and a number is returned specifying the
* result:
*
* 0: Success.
* 1: To heavy for destination.
* 2: Can't be dropped.
* 3: Can't take it out of it's container.
* 4: The object can't be inserted into bags etc.
* 5: The destination doesn't allow insertions of objects.
* 6: The object can't be picked up.
*
* If an object is transfered to a newly created object, make
* sure that the new object first is transfered to it's
* destination.
*
* The efun calls add_weight(), drop(), get(), prevent_insert(),
* add_weight(), and can_put_and_get() where needed.
*/
{
object_t *ob, *to;
svalue_t *v_weight, *ret;
int weight;
object_t *from;
int result;
/* Get and test the arguments */
if (sp[-1].type != T_OBJECT)
{
bad_xefun_arg(1, sp);
}
ob = sp[-1].u.ob;
if (sp->type == T_OBJECT)
to = sp->u.ob;
else if (sp->type == T_STRING)
{
to = get_object(sp->u.string);
if (!to)
error("Object %s not found.\n", sp->u.string);
free_string_svalue(sp);
put_ref_object(sp, to, "transfer"); /* for move_object() below */
}
else
bad_xefun_arg(2, sp);
from = ob->super;
result = 0; /* Default: success result */
/* Perform the transfer step by step */
switch(0){default:
/* Get the weight of the object
*/
weight = 0;
v_weight = sapply(STR_QUERY_WEIGHT, ob, 0);
if (v_weight && v_weight->type == T_NUMBER)
weight = v_weight->u.number;
if (ob->flags & O_DESTRUCTED)
{
result = 3;
break;
}
/* If the original place of the object is a living object,
* then we must call drop() to check that the object can be dropped.
*/
if (from && (from->flags & O_ENABLE_COMMANDS))
{
ret = sapply(STR_DROP, ob, 0);
if (ret && (ret->type != T_NUMBER || ret->u.number != 0))
{
result = 2;
break;
}
/* This should not happen, but we can not trust LPC hackers. :-) */
if (ob->flags & O_DESTRUCTED)
{
result = 2;
break;
}
}
/* If 'from' is not a room and not a player, check that we may
* remove things out of it.
*/
if (from && from->super && !(from->flags & O_ENABLE_COMMANDS))
{
ret = sapply(STR_CANPUTGET, from, 0);
if (!ret
|| (ret->type == T_NUMBER && ret->u.number == 0)
|| (from->flags & O_DESTRUCTED))
{
result = 3;
break;
}
}
/* If the destination is not a room, and not a player,
* Then we must test 'prevent_insert', and 'can_put_and_get'.
*/
if (to->super && !(to->flags & O_ENABLE_COMMANDS))
{
ret = sapply(STR_PREVENT_INSERT, ob, 0);
if (ret && (ret->type != T_NUMBER || ret->u.number != 0))
{
result = 4;
break;
}
ret = sapply(STR_CANPUTGET, to, 0);
if (!ret
|| (ret->type == T_NUMBER && ret->u.number == 0)
|| (to->flags & O_DESTRUCTED)
|| (ob->flags & O_DESTRUCTED))
{
result = 5;
break;
}
}
/* If the destination is a player, check that he can pick it up.
*/
if (to->flags & O_ENABLE_COMMANDS)
{
ret = sapply(STR_GET, ob, 0);
if (!ret
|| (ret->type == T_NUMBER && ret->u.number == 0)
|| (ob->flags & O_DESTRUCTED))
{
result = 6;
break;
}
}
/* If it is not a room, correct the total weight in
* the destination.
*/
if (to->super && weight)
{
/* Check if the destination can carry that much.
*/
push_number(weight);
ret = sapply(STR_ADD_WEIGHT, to, 1);
if (ret && ret->type == T_NUMBER && ret->u.number == 0)
{
result = 1;
break;
}
if (to->flags & O_DESTRUCTED)
{
result = 1;
break;
}
}
/* If it is not a room, correct the weight in
* the 'from' object.
*/
if (from && from->super && weight)
{
push_number(-weight);
(void)sapply(STR_ADD_WEIGHT, from, 1);
}
/* When we come here, the move is ok */
} /* pseudo-switch() */
if (result)
{
/* All the applys might have changed these */
free_svalue(sp);
free_svalue(sp-1);
}
else
{
/* The move is ok: do it (and use up both arguments) */
inter_sp = sp;
move_object();
}
put_number(sp-1, result);
return sp-1;
} /* f_transfer() */
#endif /* F_TRANSFER */
/*-------------------------------------------------------------------------*/
void
e_say (svalue_t *v, vector_t *avoid)
/* Implementation of the EFUN say() (see interpret.c for the
* full manpage).
* <v> is the value to say, <avoid> the array of objects to exclude.
* If the first element of <avoid> is not an object, the function
* will store its command_giver object into it.
*/
{
static svalue_t ltmp = { T_POINTER };
static svalue_t stmp = { T_OBJECT };
object_t *ob;
object_t *save_command_giver = command_giver;
object_t *origin;
char buff[256];
char *message;
#define INITIAL_MAX_RECIPIENTS 48
int max_recipients = INITIAL_MAX_RECIPIENTS;
/* Current size of the recipients table.
*/
object_t *first_recipients[INITIAL_MAX_RECIPIENTS];
/* Initial table of recipients.
*/
object_t **recipients = first_recipients;
/* Pointer to the current table of recipients.
* The end is marked with a NULL entry.
*/
object_t **curr_recipient = first_recipients;
/* Next recipient to enter.
*/
object_t **last_recipients =
&first_recipients[INITIAL_MAX_RECIPIENTS-1];
/* Last entry in the current table.
*/
object_t *save_again;
/* Determine the command_giver to use */
if (current_object->flags & O_ENABLE_COMMANDS)
{
command_giver = current_object;
}
else if (current_object->flags & O_SHADOW
&& O_GET_SHADOW(current_object)->shadowing)
{
command_giver = O_GET_SHADOW(current_object)->shadowing;
}
/* Determine the originating object */
if (command_giver)
{
interactive_t *ip;
if (O_SET_INTERACTIVE(ip, command_giver))
{
trace_level |= ip->trace_level;
}
origin = command_giver;
/* Save the commandgiver to avoid, if needed */
if (avoid->item[0].type == T_NUMBER)
{
put_ref_object(avoid->item, command_giver, "say");
}
}
else
origin = current_object;
/* Sort the avoid vector for fast lookups
*/
ltmp.u.vec = avoid;
avoid = order_alist(<mp, 1, 1);
push_referenced_vector(avoid); /* in case of errors... */
avoid = avoid->item[0].u.vec;
/* Collect the list of propable recipients.
* First, look in the environment.
*/
if ( NULL != (ob = origin->super) )
{
interactive_t *ip;
/* The environment itself? */
if (ob->flags & O_ENABLE_COMMANDS
|| O_SET_INTERACTIVE(ip, ob))
{
*curr_recipient++ = ob;
}
for (ob = ob->contains; ob; ob = ob->next_inv)
{
if (ob->flags & O_ENABLE_COMMANDS
|| O_SET_INTERACTIVE(ip,ob))
{
if (curr_recipient >= last_recipients)
{
/* Increase the table */
max_recipients *= 2;
curr_recipient = alloca(max_recipients * sizeof(object_t *));
memcpy( curr_recipient, recipients
, max_recipients * sizeof(object_t *) / 2);
recipients = curr_recipient;
last_recipients = &recipients[max_recipients-1];
curr_recipient += (max_recipients / 2) - 1;
}
*curr_recipient++ = ob;
}
} /* for() */
} /* if(environment) */
/* Now check this environment */
for (ob = origin->contains; ob; ob = ob->next_inv)
{
interactive_t *ip;
if (ob->flags & O_ENABLE_COMMANDS
|| O_SET_INTERACTIVE(ip, ob))
{
if (curr_recipient >= last_recipients)
{
/* Increase the table */
max_recipients *= 2;
curr_recipient = alloca(max_recipients * sizeof(object_t *));
memcpy( curr_recipient, recipients
, max_recipients * sizeof(object_t *) / 2);
recipients = curr_recipient;
last_recipients = &recipients[max_recipients-1];
curr_recipient += (max_recipients / 2) - 1;
}
*curr_recipient++ = ob;
}
}
*curr_recipient = NULL; /* Mark the end of the list */
/* Construct the message. */
switch(v->type)
{
case T_STRING:
message = v->u.string;
break;
case T_OBJECT:
xstrncpy(buff, v->u.ob->name, sizeof buff);
buff[sizeof buff - 1] = '\0';
message = buff;
break;
case T_NUMBER:
sprintf(buff, "%ld", v->u.number);
message = buff;
break;
case T_POINTER:
/* say()'s evil twin: send <v> to all recipients' catch_msg() lfun */
for (curr_recipient = recipients; NULL != (ob = *curr_recipient++) ; )
{
if (ob->flags & O_DESTRUCTED)
continue;
stmp.u.ob = ob;
if (assoc(&stmp, avoid) >= 0)
continue;
push_vector(v->u.vec);
push_object(origin);
sapply(STR_CATCH_MSG, ob, 2);
}
pop_stack(); /* free avoid alist */
command_giver = check_object(save_command_giver);
return;
default:
error("Invalid argument %d to say()\n", v->type);
}
/* Now send the message to all recipients */
for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
{
interactive_t *ip;
if (ob->flags & O_DESTRUCTED)
continue;
stmp.u.ob = ob;
if (assoc(&stmp, avoid) >= 0)
continue;
if (!(O_SET_INTERACTIVE(ip, ob)))
{
tell_npc(ob, message);
continue;
}
save_again = command_giver;
command_giver = ob;
add_message("%s", message);
command_giver = save_again;
}
pop_stack(); /* free avoid alist */
command_giver = check_object(save_command_giver);
} /* e_say() */
/*-------------------------------------------------------------------------*/
void
e_tell_room (object_t *room, svalue_t *v, vector_t *avoid)
/* Implementation of the EFUN tell_room() (see interpret.c for
* the full manpage).
*
* Value <v> is sent to all living objects in <room>, except those
* in <avoid>. <avoid> has to be in order_alist() order.
*/
{
object_t *ob;
object_t *save_command_giver;
int num_recipients = 0;
object_t *some_recipients[20];
object_t **recipients;
object_t **curr_recipient;
char buff[256], *message;
static svalue_t stmp = { T_OBJECT, } ;
/* Like in say(), collect the possible recipients.
* First count how many there are.
*/
for (ob = room->contains; ob; ob = ob->next_inv)
{
interactive_t *ip;
if ( ob->flags & O_ENABLE_COMMANDS
|| O_SET_INTERACTIVE(ip, ob))
{
num_recipients++;
}
}
/* Allocate the table */
if (num_recipients < 20)
recipients = some_recipients;
else
recipients =
alloca( (num_recipients+1) * sizeof(object_t *) );
/* Now fill the table */
curr_recipient = recipients;
for (ob = room->contains; ob; ob = ob->next_inv)
{
interactive_t *ip;
if ( ob->flags & O_ENABLE_COMMANDS
|| O_SET_INTERACTIVE(ip, ob))
{
*curr_recipient++ = ob;
}
}
*curr_recipient = NULL; /* Mark the end of the table */
/* Construct the message */
switch(v->type)
{
case T_STRING:
message = v->u.string;
break;
case T_OBJECT:
xstrncpy(buff, v->u.ob->name, sizeof buff);
buff[sizeof buff - 1] = '\0';
message = buff;
break;
case T_NUMBER:
sprintf(buff, "%ld", v->u.number);
message = buff;
break;
case T_POINTER:
{
/* say()s evil brother: send <v> to all recipients'
* catch_msg() lfun
*/
object_t *origin = command_giver;
if (!origin)
origin = current_object;
for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
{
if (ob->flags & O_DESTRUCTED)
continue;
stmp.u.ob = ob;
if (assoc(&stmp, avoid) >= 0)
continue;
push_vector(v->u.vec);
push_object(origin);
sapply(STR_CATCH_MSG, ob, 2);
}
return;
}
default:
error("Invalid argument %d to tell_room()\n", v->type);
}
/* Now send the message to all recipients */
for (curr_recipient = recipients; NULL != (ob = *curr_recipient++); )
{
interactive_t *ip;
if (ob->flags & O_DESTRUCTED) continue;
stmp.u.ob = ob;
if (assoc(&stmp, avoid) >= 0) continue;
if (!(O_SET_INTERACTIVE(ip, ob)))
{
tell_npc(ob, message);
continue;
}
save_command_giver = command_giver;
command_giver = ob;
add_message("%s", message);
command_giver = save_command_giver;
}
} /* e_tell_room() */
/*-------------------------------------------------------------------------*/
#ifdef USE_SET_IS_WIZARD
svalue_t *
f_set_is_wizard (svalue_t *sp)
/* TEFUN set_is_wizard()
*
* int set_is_wizard(object ob, int n)
*
* Change object ob's wizardhood flag. If n is 0, it is cleared, if n is, it
* is set, if n is -1 the current status is reported. The return value is
* always the old value of the flag. Using this function sets a flag in the
* parser, that affects permissions for dumpallobj etc, which are by default
* free for every user.
*/
{
int i;
unsigned short *flagp;
TYPE_TEST1(sp-1, T_OBJECT)
TYPE_TEST2(sp, T_NUMBER)
flagp = &sp[-1].u.ob->flags;
i = (*flagp & O_IS_WIZARD) != 0;
switch (sp->u.number)
{
default: bad_xefun_arg(2, sp);
case 0: *flagp &= ~O_IS_WIZARD; is_wizard_used = MY_TRUE; break;
case 1: *flagp |= O_IS_WIZARD; is_wizard_used = MY_TRUE; break;
case -1: break; /* only report status */
}
sp--;
free_object_svalue(sp);
put_number(sp, i);
return sp;
} /* f_set_is_wizard() */
#endif /* USE_SET_IS_WIZARD */
/*-------------------------------------------------------------------------*/
svalue_t *
f_set_modify_command (svalue_t *sp)
/* TEFUN set_modify_command()
*
* object set_modify_command(object)
* object set_modify_command(string)
* object set_modify_command(int)
*
* All commands for the current object (that must obviously be interactive)
* will be passed to ob->modify_command() before actually being executed. The
* argument can be passed an object or a file_name.
*
* When set_modify_command() was called, the parser won't expand the standard
* abbreviations n,e,s,w,nw,sw,ne,se for that user anymore, nor use any hook
* set for this.
*
* 0 as argument will stop the command modification and reinstall
* the standard abbreviations.
* -1 as argument will just return the object previously set.
*
* The return value is the object that was previously set with
* set_modify_command(), if any.
*/
{
object_t *old, *new;
interactive_t *ip;
inter_sp = sp;
/* Make sure the current_object is interactive */
if (!(O_SET_INTERACTIVE(ip, current_object))
|| ip->closing)
{
error("set_modify_command in non-interactive object\n");
}
/* Get the old setting */
old = ip->modify_command;
if (old && old->flags & O_DESTRUCTED)
{
free_object(old, "set_modify_command");
old = NULL;
ip->modify_command = NULL;
}
/* Set the new setting */
new = sp->u.ob;
switch(sp->type)
{
default:
bad_arg_1:
bad_xefun_arg(1, sp);
case T_STRING:
new = get_object(sp->u.string);
if (!new) goto bad_arg_1;
case T_OBJECT:
ip->modify_command = ref_object(new, "set_modify_command");
break;
case T_NUMBER:
if (sp->u.number == 0 )
{
/* ref count of old is reused below, so don't free now */
ip->modify_command = NULL;
}
else
{
if (sp->u.number != -1) goto bad_arg_1;
if (old) ref_object(old, "set_modify_command");
}
}
free_svalue(sp);
/* Return the old setting */
if (old)
put_object(sp, old); /* reuse ref count */
else
put_number(sp, 0);
return sp;
} /* f_set_modify_command() */
/*=========================================================================*/
/* VALUES */
/*-------------------------------------------------------------------------*/
svalue_t *
f_copy (svalue_t *sp)
/* TEFUN copy()
*
* mixed copy(mixed data)
*
* Make a copy of <data> and return it. For everything but arrays and
* mappings this is obviously a noop, but for arrays and mappings this
* efuns returns a shallow value copy.
*/
{
switch (sp->type)
{
default:
NOOP
break;
case T_QUOTED_ARRAY:
case T_POINTER:
{
vector_t *old, *new;
size_t size, i;
old = sp->u.vec;
size = VEC_SIZE(old);
if (old->ref != 1 && old != &null_vector)
{
DYN_ARRAY_COST(size);
new = allocate_uninit_array((int)size);
if (!new)
error("(copy) Out of memory: array[%lu] for copy.\n"
, (unsigned long) size);
for (i = 0; i < size; i++)
assign_svalue_no_free(&new->item[i], &old->item[i]);
free_array(old);
sp->u.vec = new;
}
break;
}
case T_MAPPING:
{
mapping_t *old, *new;
old = sp->u.map;
if (old->ref != 1)
{
check_map_for_destr(old);
DYN_MAPPING_COST(MAP_SIZE(old));
new = copy_mapping(old);
if (!new)
error("(copy) Out of memory: mapping[%lu] for copy.\n"
, MAP_SIZE(old));
free_mapping(old);
sp->u.map = new;
}
break;
}
}
return sp;
} /* f_copy() */
/*-------------------------------------------------------------------------*/
/* Data packet passed to deep_copy_mapping() during a mapping walk.
*/
struct csv_info {
int depth; /* Depth of the copy procedure */
int width; /* width of the mapping */
mapping_t * dest; /* the mapping to copy into */
struct pointer_table *ptable; /* the pointer table to use */
};
/*-------------------------------------------------------------------------*/
static void
deep_copy_mapping (svalue_t *key, svalue_t *val, void *extra)
/* Called from copy_svalue() as part of the mapping walk to deeply copy
* a mapping. <extra> is a (struct csv_info *).
*/
{
struct csv_info *info = (struct csv_info *)extra;
svalue_t newkey;
svalue_t *newdata;
int i;
copy_svalue(&newkey, key, info->ptable, info->depth);
newdata = get_map_lvalue_unchecked(info->dest, &newkey);
if (!newdata)
{
error("Out of memory.\n");
/* NOTREACHED */
return;
}
for (i = info->width; i-- > 0; newdata++, val++)
copy_svalue(newdata, val, info->ptable, info->depth);
free_svalue(&newkey); /* no longer needed */
}
/*-------------------------------------------------------------------------*/
static void
copy_svalue ( svalue_t *dest, svalue_t *src
, struct pointer_table *ptable
, int depth)
/* Copy the svalue <src> into the yet uninitialised svalue <dest>.
* If <src> is an array or mapping, recurse to achieve a deep copy, using
* <ptable> to keep track of the arrays and mappings encountered.
* <depth> is the nesting depth of this value.
*
* The records in the pointer table store the svalue* of the created
* copy for each registered array and mapping in the .data member.
*/
{
assert_stack_gap();
if (EVALUATION_TOO_LONG())
{
put_number(dest, 0); /* Need to store something! */
return;
}
switch (src->type)
{
default:
assign_svalue_no_free(dest, src);
break;
case T_QUOTED_ARRAY:
case T_POINTER:
{
struct pointer_record *rec;
vector_t *old, *new;
mp_int size, i;
old = src->u.vec;
/* No need to copy the null vector */
if (old == &null_vector)
{
assign_svalue_no_free(dest, src);
break;
}
/* Lookup/add this array to the pointer table */
rec = find_add_pointer(ptable, old, MY_TRUE);
if (rec->ref_count++ < 0) /* New array */
{
size = (mp_int)VEC_SIZE(old);
DYN_ARRAY_COST(size);
#if defined(DYNAMIC_COSTS)
eval_cost += (depth+1) / 10;
#endif
/* Create a new array, assign it to dest, and store
* it in the table, too.
*/
new = allocate_uninit_array(size);
put_array(dest, new);
if (src->type == T_QUOTED_ARRAY)
{
dest->type = T_QUOTED_ARRAY;
dest->x.quotes = src->x.quotes;
}
rec->data = dest;
/* Copy the values */
for (i = 0; i < size; i++)
{
svalue_t * svp = &old->item[i];
if (svp->type == T_MAPPING
|| svp->type == T_POINTER
|| svp->type == T_QUOTED_ARRAY
)
copy_svalue(&new->item[i], svp, ptable, depth+1);
else
assign_svalue_no_free(&new->item[i], svp);
}
}
else /* shared array we already encountered */
{
assign_svalue_no_free(dest, (svalue_t *)rec->data);
}
break;
}
case T_MAPPING:
{
mapping_t *old, *new;
struct pointer_record *rec;
old = src->u.map;
/* Lookup/add this mapping to the pointer table */
rec = find_add_pointer(ptable, old, MY_TRUE);
if (rec->ref_count++ < 0) /* New mapping */
{
mp_int size;
struct csv_info info;
/* Create a new array, assign it to dest, and store it
* in the table, too.
*/
check_map_for_destr(old);
size = (mp_int)MAP_SIZE(old);
DYN_MAPPING_COST(size);
#if defined(DYNAMIC_COSTS)
eval_cost += (depth+1) / 10;
#endif
info.depth = depth+1;
info.width = old->num_values;
new = allocate_mapping(size, info.width);
if (!new)
error("(copy) Out of memory: new mapping[%lu, %u].\n"
, size, info.width);
put_mapping(dest, new);
rec->data = dest;
/* It is tempting to use copy_mapping() and then just
* replacing all array/mapping references, but since this
* can mess up the sorting order and needs a walk of the
* mapping anyway, we do all the copying in the walk.
*/
info.ptable = ptable;
info.dest = new;
walk_mapping(old, deep_copy_mapping, &info);
}
else /* shared mapping we already encountered */
{
assign_svalue_no_free(dest, (svalue_t *)rec->data);
}
break;
}
} /* switch(src->type) */
} /* copy_svalue() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_deep_copy (svalue_t *sp)
/* TEFUN deep_copy()
*
* mixed deep_copy(mixed data)
*
* Make a copy of <data> and return it. For everything but arrays and
* mappings this is obviously a noop, but for arrays and mappings this
* efuns returns a deep value copy.
*
* Note: checking the ref-count of the array/mapping passed is of no use
* here as it doesn't tell anything about the contained arrays/mappings.
*/
{
struct pointer_table *ptable;
switch (sp->type)
{
default:
NOOP
break;
case T_QUOTED_ARRAY:
case T_POINTER:
{
vector_t *old;
old = sp->u.vec;
if (old != &null_vector)
{
svalue_t new;
ptable = new_pointer_table();
if (!ptable)
error("(deep_copy) Out of memory for pointer table.\n");
copy_svalue(&new, sp, ptable, 0);
if (sp->type == T_QUOTED_ARRAY)
new.x.quotes = sp->x.quotes;
transfer_svalue(sp, &new);
free_pointer_table(ptable);
}
break;
}
case T_MAPPING:
{
mapping_t *old;
svalue_t new;
old = sp->u.map;
ptable = new_pointer_table();
if (!ptable)
error("(deep_copy) Out of memory for pointer table.\n");
copy_svalue(&new, sp, ptable, 0);
transfer_svalue(sp, &new);
free_pointer_table(ptable);
break;
}
}
return sp;
} /* f_deep_copy() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_filter (svalue_t *sp, int num_arg)
/* VEFUN filter()
*
* mixed * filter (mixed *arg, string fun, string|object ob, mixed extra...)
* mixed * filter (mixed *arg, closure cl, mixed extra...)
* mixed * filter (mixed *arg, mapping map, mixed extra...)
*
* mapping filter (mapping arg, string fun, string|object ob, mixed extra...)
* mapping filter (mapping arg, closure cl, mixed extra...)
*
* Call the function <ob>-><fun>() resp. the closure <cl> for
* every element of the array or mapping <arg>, and return
* a result made from those elements for which the function
* call returns TRUE.
*
* If <ob> is omitted, or neither an object nor a string, then
* this_object() is used.
*/
{
if (sp[-num_arg+1].type == T_MAPPING)
return x_filter_mapping(sp, num_arg, MY_TRUE);
else
return x_filter_array(sp, num_arg);
} /* f_filter() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_map (svalue_t *sp, int num_arg)
/* VEFUN map()
*
* mixed * map(mixed *arg, string func, string|object ob, mixed extra...)
* mixed * map(mixed *arg, closure cl, mixed extra...)
*
* mapping map(mapping arg, string func, string|object ob, mixed extra...)
* mapping map(mapping arg, closure cl, mixed extra...)
*
* Call the function <ob>-><func>() resp. the closure <cl> for
* every element of the array or mapping <arg>, and return a result
* made up from the returned values.
*
* If <ob> is omitted, or neither an object nor a string, then
* this_object() is used.
*/
{
if (sp[-num_arg+1].type == T_MAPPING)
return x_map_mapping(sp, num_arg, MY_TRUE);
else
return x_map_array(sp, num_arg);
} /* f_map() */
/*-------------------------------------------------------------------------*/
static svalue_t *
x_min_max (svalue_t *sp, int num_arg, Bool bMax)
/* Implementation of VEFUNs max() and min().
* <bMax> is true if the maximum is to be returned, false for the minimum.
*/
{
char * fname = bMax ? "max" : "min";
svalue_t *argp = sp-num_arg+1;
svalue_t *valuep = argp;
int left = num_arg;
Bool gotArray = MY_FALSE;
svalue_t *result = NULL;
if (argp->type == T_POINTER)
{
if (num_arg > 1)
{
error("Bad arguments to %s: only one array accepted.\n", fname);
/* NOTREACHED */
}
valuep = argp->u.vec->item;
left = (int)VEC_SIZE(argp->u.vec);
gotArray = MY_TRUE;
if (left < 1)
{
error("Bad argument 1 to %s: array must not be empty.\n", fname);
/* NOTREACHED */
}
}
if (valuep->type == T_STRING)
{
result = valuep;
for (valuep++, left--; left > 0; valuep++, left--)
{
int cmp;
if (valuep->type != T_STRING)
{
if (gotArray)
error("Bad argument to %s: array[%d] is not a string.\n"
, fname, (int)VEC_SIZE(argp->u.vec) - left + 1);
else
error("Bad argument %d to %s: not a string.\n"
, num_arg - left + 1, fname);
/* TODO: Give type and value */
/* NOTREACHED */
}
cmp = strcmp(valuep->u.string, result->u.string);
if (bMax ? (cmp > 0) : (cmp < 0))
result = valuep;
}
}
else if (valuep->type == T_NUMBER || valuep->type == T_FLOAT)
{
result = valuep;
for (valuep++, left--; left > 0; valuep++, left--)
{
if (valuep->type != T_FLOAT && valuep->type != T_NUMBER)
{
if (gotArray)
error("Bad argument to %s: array[%d] is not a number.\n"
, fname, (int)VEC_SIZE(argp->u.vec) - left + 1);
else
error("Bad argument %d to %s: not a number.\n"
, num_arg - left + 1, fname);
/* TODO: Give type and value */
/* NOTREACHED */
}
if (valuep->type == T_NUMBER && result->type == T_NUMBER)
{
if (bMax ? (valuep->u.number > result->u.number)
: (valuep->u.number < result->u.number))
result = valuep;
}
else
{
double v, r;
if (valuep->type == T_FLOAT)
v = READ_DOUBLE(valuep);
else
v = (double)(valuep->u.number);
if (result->type == T_FLOAT)
r = READ_DOUBLE(result);
else
r = (double)(result->u.number);
if (bMax ? (v > r)
: (v < r))
result = valuep;
}
} /* for (values) */
}
else
{
if (gotArray)
error("Bad argument to %s: array[0] is not a string or number.\n"
, fname);
else
error("Bad argument 1 to %s: not a string or number.\n"
, fname);
/* TODO: Give type and value */
/* NOTREACHED */
}
/* Assign the result.
* We need to make a local copy, otherwise we might lose it in the pop.
*/
{
svalue_t resvalue;
assign_svalue_no_free(&resvalue, result);
sp = pop_n_elems(num_arg, sp) + 1;
transfer_svalue_no_free(sp, &resvalue);
}
return sp;
} /* x_min_max() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_max (svalue_t *sp, int num_arg)
/* VEFUN max()
*
* string max (string arg, ...)
* string max (string * arg_array)
*
* int|float max (int|float arg, ...)
* int|float max (int|float * arg_array)
*
* Determine the maximum value of the <arg>uments and return it.
* If max() is called with an array (which must not be empty) as only
* argument, it returns the maximum value of the array contents.
*/
{
return x_min_max(sp, num_arg, MY_TRUE);
} /* f_max() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_min (svalue_t *sp, int num_arg)
/* VEFUN min()
*
* string min (string arg, ...)
* string min (string * arg_array)
*
* int|float min (int|float arg, ...)
* int|float min (int|float * arg_array)
*
* Determine the minimum value of the <arg>uments and return it.
* If min() is called with an array (which must not be empty) as only
* argument, it returns the minimum value of the array contents.
*/
{
return x_min_max(sp, num_arg, MY_FALSE);
} /* f_min() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_sgn (svalue_t *sp)
/* VEFUN sgn()
*
* int sgn (int|float arg)
*
* Return the sign of the argument: -1 if it's < 0, +1 if it's > 0, and
* 0 if it is 0.
*/
{
if (sp->type == T_NUMBER)
{
if (sp->u.number > 0)
sp->u.number = 1;
else if (sp->u.number < 0)
sp->u.number = -1;
else
sp->u.number = 0;
}
else if (sp->type == T_FLOAT)
{
double d = READ_DOUBLE(sp);
sp->type = T_NUMBER;
if (d > 0.0)
sp->u.number = 1;
else if (d < 0.0)
sp->u.number = -1;
else
sp->u.number = 0;
}
else
error("Bad argument 1 to sgn(): not a number or float.\n");
return sp;
} /* f_sgn() */
/*=========================================================================*/
/* OTHER */
/*-------------------------------------------------------------------------*/
svalue_t *
f_debug_info (svalue_t *sp, int num_arg)
/* VEFUN debug_info()
*
* mixed debug_info(int flag)
* mixed debug_info(int flag, object obj)
* mixed debug_info(int flag, int arg2)
* mixed debug_info(int flag, int arg2, int arg3)
*
* Print out some driver internal debug information.
*
* DINFO_OBJECT (0): Information like heart_beat, enable_commands etc. of the
* specified object will be printed, and 0 returned.
*
* DINFO_MEMORY (1): Memory usage information like how many strings,
* variables, inherited files, object size etc. will be printed about the
* specified object, and 0 returned.
*
* DINFO_OBJLIST (2): Objects from the global object list are returned. If
* the optional second arg is omitted, the first element (numbered 0)
* is returned. If the second arg is a number n, the n'th element of the
* object list returned. If the second arg is an object, it's successor
* in the object list is returned.
*
* DINFO_MALLOC: Equivalent to typing ``malloc'' at the command line.
* No second arg must be given. Returns 0.
*
* DINFO_STATUS (4): Collect the status information of the driver. The
* optional second arg can be 0, "tables", "swap", "malloc" or any other
* argument accepted by the actual driver. The result is a printable
* string with the status information, or 0 if an invalid argument was
* given.
*
* DINFO_DUMP (5): Dump the information specified by <arg2> into the
* filename specified by <arg3>. If <arg3> is omitted, a default file
* name is used. The function calls master->valid_write() to check that
* it can write the files. The file in question is always written anew.
* Result is 1 on success, or 0 if an error occured.
*
* <arg2> == "objects": dump information about all live objects. Default
* filename is '/OBJ_DUMP', the valid_write() will read 'objdump' for
* the function.
*
* <arg2> == "destructed": dump information about all destructed objects.
* Default filename is '/DEST_OBJ_DUMP', the valid_write() will read
* 'objdump' for the function.
*
* <arg2> == "opcodes": dump the usage statistics of the opcodes. Default
* filename is '/OPC_DUMP', the valid_write() will read 'opcdump' for
* the function. If the driver is compiled without OPCPROF, this call
* will always return 0.
*
* DINFO_DATA (6): Return raw information about an aspect of
* the driver specified by <arg2>. The result of the function
* is an array with the information, or 0 for unsupported values
* of <arg2>. If <arg3> is given and in the range of array indices for
* the given <arg2>, the result will be just the indexed array entry,
* but not the full array.
*
* Allowed values for <arg2> are: DID_STATUS, DID_SWAP, DID_MALLOC.
*
* <arg2> == DID_STATUS (0): Returns the "status" and "status tables"
* information:
*
* int DID_ST_ACTIONS
* int DID_ST_ACTIONS_SIZE
* Number and size of allocated actions.
*
* int DID_ST_SHADOWS
* int DID_ST_SHADOWS_SIZE
* Number and size of allocated shadows.
*
* int DID_ST_OBJECTS
* Total number and size of objects.
*
* int DID_ST_OBJECTS_SWAPPED
* int DID_ST_OBJECTS_SWAP_SIZE
* Number and size of swapped-out object variable blocks.
*
* int DID_ST_OBJECTS_LIST
* Number of objects in the object list.
*
* int DID_ST_OBJECTS_NEWLY_DEST
* Number of newly destructed objects (ie. objects destructed
* in this execution thread).
*
* int DID_ST_OBJECTS_DESTRUCTED
* Number of destructed but still referenced objects, not
* counting the DID_ST_OBJECTS_NEWLY_DEST.
*
* int DID_ST_OBJECTS_PROCESSED
* Number of listed objects processed in the last backend
* cycle.
*
* float DID_ST_OBJECTS_AVG_PROC
* Average number of objects processed each cycle, expressed
* as fraction (0..1.0).
*
* int DID_ST_OTABLE
* Number of objects listed in the object table.
*
* int DID_ST_OTABLE_SLOTS
* Number of hash slots provided by the object table.
*
* int DID_ST_OTABLE_SIZE
* Size occupied by the object table.
*
* int DID_ST_HBEAT_OBJS
* Number of objects with a heartbeat.
*
* int DID_ST_HBEAT_CALLS
* Number of heart_beats executed so far.
*
* int DID_ST_HBEAT_CALLS_TOTAL
* Number of heart_beats calls so far. The difference to
* ST_HBEAT_CALLS is that the latter only counts heart beat
* calls during which at least one heart beat was actually executed.
*
* int DID_ST_HBEAT_SLOTS
* int DID_ST_HBEAT_SIZE
* Number of allocated entries in the heart_beat table
* and its size.
*
* int DID_ST_HBEAT_PROCESSED
* Number of heart_beats called in the last backend cycle.
*
* float DID_ST_HBEAT_AVG_PROC
* Average number of heart_beats called each cycle, expressed
* as fraction (0..1.0).
*
* int DID_ST_CALLOUTS
* Number of pending call_outs.
*
* int DID_ST_CALLOUT_SLOTS
* int DID_ST_CALLOUT_SIZE
* Number of allocated entries in the call_out table
* and its size.
*
* int DID_ST_ARRAYS
* int DID_ST_ARRAYS_SIZE
* Number and size of all arrays.
*
* int DID_ST_MAPPINGS
* int DID_ST_MAPPINGS_SIZE
* Number and size of all mappings.
*
* int DID_ST_PROGS
* int DID_ST_PROGS_SIZE
* Number and size of all programs.
*
* int DID_ST_PROGS_SWAPPED
* int DID_ST_PROGS_SWAP_SIZE
* Number and size of swapped-out programs.
*
* int DID_ST_USER_RESERVE
* int DID_ST_MASTER_RESERVE
* int DID_ST_SYSTEM_RESERVE
* Current sizes of the three memory reserves.
*
* int DID_ST_ADD_MESSAGE
* int DID_ST_PACKETS
* int DID_ST_PACKET_SIZE
* Number of calls to add_message(), number and total size
* of sent packets.
* If the driver is not compiled with COMM_STAT, all three
* values are returned as -1.
*
* int DID_ST_APPLY
* int DID_ST_APPLY_HITS
* Number of calls to apply_low(), and how many of these
* were cache hits.
* If the driver is not compiled with APPLY_CACHE_STAT, all two
* values are returned as -1.
*
* int DID_ST_STRINGS
* int DID_ST_STRING_SIZE
* Number of distinct strings in the string table, and
* their size.
*
* int DID_ST_STR_TABLE_SIZE
* Size of the string table.
*
* int DID_ST_STR_REQ
* int DID_ST_STR_REQ_SIZE
* Total number of string allocations, and their size.
*
* int DID_ST_STR_SEARCHES
* int DID_ST_STR_SEARCH_LEN
* Number of searches in the string table, and the
* accumulated search length.
*
* int DID_ST_STR_FOUND
* Number successful searches.
*
* int DID_ST_STR_ENTRIES
* Number of entries (hash chains) in the string table.
*
* int DID_ST_STR_ADDED
* Number of strings added to the table so far.
*
* int DID_ST_STR_DELETED
* Number of strings delete from the table so far.
*
* int DID_ST_STR_COLLISIONS
* Number of strings added to an existing hash chain.
*
* int DID_ST_RX_CACHED
* Number of regular expressions cached.
*
* int DID_ST_RX_TABLE
* int DID_ST_RX_TABLE_SIZE
* Number of slots in the regexp cache table, and size of the
* memory currently held by it and the cached expressions.
*
* int DID_ST_RX_REQUESTS
* Number of requests for new regexps.
*
* int DID_ST_RX_REQ_FOUND
* Number of requested regexps found in the table.
*
* int DID_ST_RX_REQ_COLL
* Number of requested new regexps which collided with
* a cached one.
*
*
* <arg2> == DID_SWAP (1): Returns the "status swap" information:
*
* int DID_SW_PROGS
* int DID_SW_PROG_SIZE
* Number and size of swapped-out program blocks.
*
* int DID_SW_PROG_UNSWAPPED
* int DID_SW_PROG_U_SIZE
* Number and size of unswapped program blocks.
*
* int DID_SW_VARS
* int DID_SW_VAR_SIZE
* Number and size of swapped-out variable blocks.
*
* int DID_SW_FREE
* int DID_SW_FREE_SIZE
* Number and size of free blocks in the swap file.
*
* int DID_SW_FILE_SIZE
* Size of the swap file.
*
* int DID_SW_REUSED
* Total reused space in the swap file.
*
* int DID_SW_SEARCHES
* int DID_SW_SEARCH_LEN
* Number and total length of searches for block to reuse
* in the swap file.
*
* int DID_SW_F_SEARCHES
* int DID_SW_F_SEARCH_LEN
* Number and total length of searches for a block to free.
*
* int DID_SW_COMPACT
* TRUE if the swapper is running in compact mode.
*
* int DID_SW_RECYCLE_FREE
* TRUE if the swapper is currently recycling free block.
*
*
* <arg2> == DID_MEMORY (2): Returns the "status malloc" information:
*
* string DID_MEM_NAME
* The name of the allocator: "sysmalloc" or "smalloc".
*
* int DID_MEM_SBRK
* int DID_MEM_SBRK_SIZE
* Number and size of memory blocks requested from the
* operating system (smalloc only).
*
* int DID_MEM_LARGE
* int DID_MEM_LARGE_SIZE
* int DID_MEM_LFREE
* int DID_MEM_LFREE_SIZE
* Number and size of large allocated resp. free blocks.
* (smalloc only)
*
* int DID_MEM_LWASTED
* int DID_MEM_LWASTED_SIZE
* Number and size of unusable large memory fragments.
* (smalloc only).
*
* int DID_MEM_CHUNK
* int DID_MEM_CHUNK_SIZE
* Number and size of small chunk blocks (smalloc only).
*
* int DID_MEM_UNUSED
* Unused space in the current small chunk block
* (smalloc only).
*
* int DID_MEM_SMALL
* int DID_MEM_SMALL_SIZE
* int DID_MEM_SFREE
* int DID_MEM_SFREE_SIZE
* Number and size of small allocated resp. free blocks
* (smalloc only).
*
* int DID_MEM_SWASTED
* int DID_MEM_SWASTED_SIZE
* Number and size of unusably small memory fragments.
* (smalloc only).
*
* int DID_MEM_MINC_CALLS
* int DID_MEM_MINC_SUCCESS
* int DID_MEM_MINC_SIZE
* Number of calls to malloc_increment(), the number
* of successes and the size of memory allocated this
* way (smalloc only).
*
* int DID_MEM_PERM
* int DID_MEM_PERM_SIZE
* Number and size of permanent (non-GCable) allocations
* (smalloc only).
*
* int DID_MEM_CLIB
* int DID_MEM_CLIB_SIZE
* Number and size of allocations done through the
* clib functions (smalloc only with SBRK_OK).
*
* int DID_MEM_OVERHEAD
* Overhead for every allocation (smalloc only).
*
* int DID_MEM_ALLOCATED
* The amount of memory currently allocated from the
* allocator, including the overhead for the allocator
* (smalloc only).
*
* int DID_MEM_USED
* The amount of memory currently used for driver data,
* excluding the overhead from the allocator (smalloc only).
*
* int DID_MEM_TOTAL_UNUSED
* The amount of memory allocated from the system, but
* not used by the driver.
*
* DINFO_TRACE (7): Return the call stack 'trace' information as specified
* by <arg2>. The result of the function is either an array (format
* explained below), or a printable string. Omitting <arg2> defaults
* to DIT_CURRENT.
*
* <arg2> == DIT_CURRENT (0): Current call trace
* == DIT_ERROR (1): Most recent error call trace (caught or
* uncaught)
* == DIT_UNCAUGHT_ERROR (2): Most recent uncaught-error call trace
* Return the information in array form.
*
* The error traces are changed only when an appropriate error
* occurs; in addition a GC deletes them. After an uncaught
* error, both error traces point to the same array (so the '=='
* operator holds true).
*
* If the array has just one entry, the trace information is not
* available and the one entry is string with the reason.
*
* If the array has more than one entries, the first entry is 0 or the
* name of the object with the heartbeat which started the current
* thread; all following entries describe the call stack starting with
* the topmost function called.
*
* All call entries are arrays themselves with the following elements:
*
* int[TRACE_TYPE]: The type of the call frame:
* TRACE_TYPE_SYMBOL (0): a function symbol (shouldn't happen).
* TRACE_TYPE_SEFUN (1): a simul-efun.
* TRACE_TYPE_EFUN (2): an efun closure.
* TRACE_TYPE_LAMBDA (3): a lambda closure.
* TRACE_TYPE_LFUN (4): a normal lfun.
*
* mixed[TRACE_NAME]: The 'name' of the called frame:
* _TYPE_EFUN: either the name of the efun, or the code of
* the instruction for operator closures
* _TYPE_LAMBDA: the numeric lambda identifier.
* _TYPE_LFUN: the name of the lfun.
*
* string[TRACE_PROGRAM]: The (file)name of the program holding the
* code.
* string[TRACE_OBJECT]: The name of the object for which the code
* was executed.
* int[TRACE_LOC]:
* _TYPE_LAMBDA: current program offset from the start of the
* closure code.
* _TYPE_LFUN: the line number.
*
* <arg2> == DIT_STR_CURRENT (3): Return the information about the current
* call trace as printable string.
*
* TODO: debug_info() and all associated routines are almost big enough
* TODO:: to justify a file on their own.
*/
{
svalue_t *arg;
svalue_t res;
object_t *ob;
arg = sp-num_arg+1;
inter_sp = sp;
TYPE_TESTV1(arg, T_NUMBER)
assign_svalue_no_free(&res, &const0);
assign_eval_cost();
switch ( arg[0].u.number )
{
case DINFO_OBJECT: /* --- DINFO_OBJECT --- */
{
/* Give information about an object, deciphering it's flags, nameing
* it's position in the list of all objects, total light and all the
* stuff that is of interest with respect to look_for_objects_to_swap.
*/
int flags;
object_t *prev, *obj2;
if (num_arg != 2)
error("bad number of arguments to debug_info\n");
TYPE_TESTV2(arg+1, T_OBJECT)
ob = arg[1].u.ob;
flags = ob->flags;
add_message("O_HEART_BEAT : %s\n",
flags&O_HEART_BEAT ?"TRUE":"FALSE");
#ifdef O_IS_WIZARD
add_message("O_IS_WIZARD : %s\n",
flags&O_IS_WIZARD ?"TRUE":"FALSE");
#endif
add_message("O_ENABLE_COMMANDS : %s\n",
flags&O_ENABLE_COMMANDS ?"TRUE":"FALSE");
add_message("O_CLONE : %s\n",
flags&O_CLONE ?"TRUE":"FALSE");
add_message("O_DESTRUCTED : %s\n",
flags&O_DESTRUCTED ?"TRUE":"FALSE");
add_message("O_SWAPPED : %s\n",
flags&O_SWAPPED ?"TRUE":"FALSE");
add_message("O_ONCE_INTERACTIVE: %s\n",
flags&O_ONCE_INTERACTIVE?"TRUE":"FALSE");
add_message("O_RESET_STATE : %s\n",
flags&O_RESET_STATE ?"TRUE":"FALSE");
add_message("O_WILL_CLEAN_UP : %s\n",
flags&O_WILL_CLEAN_UP ?"TRUE":"FALSE");
add_message("O_REPLACED : %s\n",
flags&O_REPLACED ?"TRUE":"FALSE");
#ifdef USE_SET_LIGHT
add_message("total light : %d\n", ob->total_light);
#endif
add_message("time_reset : %ld\n", (long)ob->time_reset);
add_message("time_of_ref : %ld\n", (long)ob->time_of_ref);
add_message("ref : %ld\n", ob->ref);
#ifdef DEBUG
add_message("extra_ref : %ld\n", ob->extra_ref);
#endif
if (ob->gigaticks)
add_message("evalcost : %lu%09lu\n", ob->gigaticks, ob->ticks);
else
add_message("evalcost : %lu\n", ob->ticks);
add_message("swap_num : %ld\n", O_SWAP_NUM(ob));
add_message("name : '%s'\n", ob->name);
add_message("load_name : '%s'\n", ob->load_name);
obj2 = ob->next_all;
if (obj2)
add_message("next_all : OBJ(%s)\n",
obj2->next_all?obj2->name:"NULL");
prev = ob->prev_all;
if (prev) {
add_message("Previous object in object list: OBJ(%s)\n"
, prev->name);
} else
add_message("This object is the head of the object list.\n");
break;
}
case DINFO_MEMORY: /* --- DINFO_MEMORY --- */
{
/* Give information about an object's program with regard to memory
* usage. This is meant to point out where memory can be saved in
* program structs.
*/
program_t *pg;
mp_int v0, v1, v2;
if (num_arg != 2)
error("bad number of arguments to debug_info\n");
TYPE_TESTV2(arg+1, T_OBJECT)
if ((sp->u.ob->flags & O_SWAPPED) && load_ob_from_swap(sp->u.ob) < 0)
error("Out of memory: unswap object '%s'\n", sp->u.ob->name);
pg = sp->u.ob->prog;
add_message("program ref's %3ld\n", pg->ref);
add_message("Name: '%s'\n", pg->name);
add_message("program size %6ld\n"
,(long)(PROGRAM_END(*pg) - pg->program));
add_message("num func's: %3d (%4ld)\n", pg->num_functions
, (long)(pg->num_functions * sizeof(uint32) +
pg->num_function_names * sizeof(short)));
add_message("num vars: %3d (%4ld)\n", pg->num_variables
, (long)(pg->num_variables * sizeof(variable_t)));
v1 = program_string_size(pg, &v0, &v2);
add_message("num strings: %3d (%4ld) : overhead %ld + data %ld (%ld)\n"
, pg->num_strings
, (long)(v0 + v1)
, (long)v0
, (long)v1
, (long)v2
);
{
int i = pg->num_inherited;
int cnt = 0;
inherit_t *inheritp;
for (inheritp = pg->inherit; i--; inheritp++)
{
if (inheritp->inherit_type == INHERIT_TYPE_NORMAL
|| inheritp->inherit_type == INHERIT_TYPE_VIRTUAL
)
cnt++;
}
add_message("num inherits %3d (%4ld)\n", cnt
, (long)(pg->num_inherited * sizeof(inherit_t)));
}
add_message("total size %6ld\n"
,pg->total_size);
v1 = data_size(sp->u.ob, &v2);
add_message("data size %6ld (%6ld)\n", v1, v2);
break;
}
case DINFO_OBJLIST: /* --- DINFO_OBJLIST --- */
{
/* Get the first/next object in the object list */
int i;
ob = obj_list;
i = 0;
if (num_arg > 1)
{
if (num_arg > 2)
error("bad number of arguments to debug_info\n");
if (sp->type == T_NUMBER)
{
i = sp->u.number;
}
else
{
TYPE_TESTV2(sp, T_OBJECT)
ob = sp->u.ob;
i = 1;
}
}
while (ob && --i >= 0) ob = ob->next_all;
if (ob)
put_ref_object(&res, ob, "debug_info");
break;
}
case DINFO_MALLOC: /* --- DINFO_MALLOC --- */
{
/* Print the malloc data */
/* TODO: This case can go, DINFO_STATUS "malloc" is sufficient */
strbuf_t sbuf;
status_parse(&sbuf, "malloc");
strbuf_send(&sbuf);
break;
}
case DINFO_STATUS: /* --- DINFO_STATUS --- */
{
/* Execute the 'status' command */
strbuf_t sbuf;
if (num_arg != 1 && num_arg != 2)
error("bad number of arguments to debug_info\n");
if (num_arg == 1
|| (sp->type == T_NUMBER && sp->u.number == 0)) {
sp->u.string = "";
} else {
TYPE_TESTV2(arg+1, T_STRING)
}
if (status_parse(&sbuf, sp->u.string))
strbuf_store(&sbuf, &res);
else
strbuf_free(&sbuf);
break;
}
case DINFO_DUMP: /* --- DINFO_DUMP --- */
{
/* Dump information into files */
char * fname;
if (num_arg != 2 && num_arg != 3)
error("bad number of arguments to debug_info\n");
TYPE_TESTV2(arg+1, T_STRING);
if (num_arg == 2
|| (sp->type == T_NUMBER && sp->u.number == 0)) {
fname = NULL;
} else {
TYPE_TESTV3(arg+2, T_STRING)
fname = sp->u.string;
}
if (!strcmp(arg[1].u.string, "objects"))
{
res.u.number = dumpstat(fname ? fname : "/OBJ_DUMP") ? 1 : 0;
break;
}
if (!strcmp(arg[1].u.string, "destructed"))
{
res.u.number = dumpstat_dest(fname ? fname : "/DEST_OBJ_DUMP") ? 1 : 0;
break;
}
if (!strcmp(arg[1].u.string, "opcodes"))
{
#ifdef OPCPROF
res.u.number = opcdump(fname ? fname : "/OPC_DUMP") ? 1 : 0;
#endif
break;
}
error("Bad argument '%s' to debug_info(DINFO_DUMP).\n", arg[1].u.string);
break;
}
case DINFO_DATA: /* --- DINFO_DATA --- */
{
/* Return information about the one or other driver interna.
* This is basically the same information returned by DINFO_STATUS,
* just not pre-processed into nice strings.
*/
vector_t *v;
svalue_t *dinfo_arg;
int value = -1;
if (num_arg != 2 && num_arg != 3)
error("bad number of arguments to debug_info\n");
TYPE_TESTV2(arg+1, T_NUMBER)
if (num_arg == 3)
{
TYPE_TESTV3(arg+2, T_NUMBER)
value = arg[2].u.number;
}
switch(arg[1].u.number)
{
#define PREP(which) \
if (value == -1) { \
v = allocate_array(which); \
if (!v) \
error("Out of memory: array[%d] for result.\n" \
, which); \
dinfo_arg = v->item; \
} else { \
v = NULL; \
if (value < 0 || value >= which) \
error("Illegal index for debug_info(): %d, " \
"expected 0..%d\n", value, which-1); \
dinfo_arg = &res; \
}
case DID_STATUS:
PREP(DID_STATUS_MAX)
dinfo_data_status(dinfo_arg, value);
otable_dinfo_status(dinfo_arg, value);
hbeat_dinfo_status(dinfo_arg, value);
callout_dinfo_status(dinfo_arg, value);
string_dinfo_status(dinfo_arg, value);
#ifdef RXCACHE_TABLE
rxcache_dinfo_status(dinfo_arg, value);
#endif
if (value == -1)
put_array(&res, v);
break;
case DID_SWAP:
PREP(DID_SWAP_MAX)
swap_dinfo_data(dinfo_arg, value);
if (value == -1)
put_array(&res, v);
break;
case DID_MEMORY:
PREP(DID_MEMORY_MAX)
#if defined(MALLOC_smalloc)
smalloc_dinfo_data(dinfo_arg, value);
#endif
#if defined(MALLOC_sysmalloc)
if (value == -1)
put_volatile_string(v->item+DID_MEM_NAME, "system malloc");
else if (value == DID_MEM_NAME)
put_volatile_string(dinfo_arg, "system malloc");
#endif
if (value == -1)
put_array(&res, v);
break;
#undef PREP
}
break;
}
case DINFO_TRACE: /* --- DINFO_TRACE --- */
{
/* Return the trace information */
if (num_arg != 1 && num_arg != 2)
error("bad number of arguments to debug_info\n");
if (num_arg == 2 && sp->type != T_NUMBER)
error("bad arg 2 to debug_info(): not a number.\n");
if (num_arg == 1 || sp->u.number == DIT_CURRENT)
{
vector_t * vec;
(void)collect_trace(NULL, &vec);
put_array(&res, vec);
}
else if (sp->u.number == DIT_ERROR)
{
if (current_error_trace)
put_ref_array(&res, current_error_trace);
else
{
vector_t *vec;
vec = allocate_uninit_array(1);
put_ref_string(vec->item, STR_NO_TRACE);
put_array(&res, vec);
}
}
else if (sp->u.number == DIT_UNCAUGHT_ERROR)
{
if (uncaught_error_trace)
put_ref_array(&res, uncaught_error_trace);
else
{
vector_t *vec;
vec = allocate_uninit_array(1);
put_ref_string(vec->item, STR_NO_TRACE);
put_array(&res, vec);
}
}
else if (sp->u.number == DIT_STR_CURRENT)
{
strbuf_t sbuf;
strbuf_zero(&sbuf);
(void)collect_trace(&sbuf, NULL);
put_malloced_string(&res, string_copy(sbuf.buf));
strbuf_free(&sbuf);
}
else
error("bad arg 2 to debug_info(): %ld, expected 0..2\n"
, sp->u.number);
break;
}
default:
error("Bad debug_info() request value: %ld\n", arg[0].u.number);
/* NOTREACHED */
break;
}
/* Clean up the stack and return the result */
sp = pop_n_elems(num_arg, sp);
sp++;
*sp = res;
return sp;
} /* f_debug_info() */
/*-------------------------------------------------------------------------*/
static INLINE svalue_t *
x_gm_localtime (svalue_t *sp, Bool localTime)
/* Implementation of the efuns gmtime() and localtime()
* localTime = TRUE: return localtime(), otherwise gmtime()
*/
{
time_t clk;
struct tm * pTm;
vector_t * v;
if (sp->type != T_NUMBER)
{
TYPE_TEST1(sp, T_POINTER)
if (VEC_SIZE(sp->u.vec) != 2)
error("Invalid array size for argument 1: %ld, expected 2\n"
, (long)VEC_SIZE(sp->u.vec));
if (sp->u.vec->item[0].type != T_NUMBER
|| sp->u.vec->item[1].type != T_NUMBER)
error("Invalid array for argument 1\n");
clk = sp->u.vec->item[0].u.number;
}
else
{
clk = sp->u.number;
}
pTm = (localTime ? localtime : gmtime)(&clk);
v = allocate_array(TM_MAX);
if (!v)
error("Out of memory: array[%d] for result.\n", TM_MAX);
v->item[TM_SEC].u.number = pTm->tm_sec;
v->item[TM_MIN].u.number = pTm->tm_min;
v->item[TM_HOUR].u.number = pTm->tm_hour;
v->item[TM_MDAY].u.number = pTm->tm_mday;
v->item[TM_MON].u.number = pTm->tm_mon;
v->item[TM_YEAR].u.number = pTm->tm_year + 1900;
v->item[TM_WDAY].u.number = pTm->tm_wday;
v->item[TM_YDAY].u.number = pTm->tm_yday;
v->item[TM_ISDST].u.number = pTm->tm_isdst ? 1 : 0;
free_svalue(sp);
put_array(sp, v); /* Adopt the ref */
return sp;
} /* x_gm_localtime() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_gmtime (svalue_t *sp)
/* TEFUN gmtime()
*
* int * gmtime(int clock = time())
* int * gmtime(int* uclock)
*
* Interpret the argument clock as number of seconds since Jan,
* 1st, 1970, 0:00, and return the time in UTC in a nice structure.
*
* Alternatively, accept an array of two ints: the first is <clock>
* value as in the first form, the second int is the number of
* microseconds elapsed in the current second.
*
* The result is an array of integers:
*
* int TM_SEC (0) : Seconds (0..59)
* int TM_MIN (1) : Minutes (0..59)
* int TM_HOUR (2) : Hours (0..23)
* int TM_MDAY (3) : Day of the month (1..31)
* int TM_MON (4) : Month of the year (0..11)
* int TM_YEAR (5) : Year (e.g. 2001)
* int TM_WDAY (6) : Day of the week (Sunday = 0)
* int TM_YDAY (7) : Day of the year (0..365)
* int TM_ISDST (8) : TRUE: Daylight saving time
*/
{
return x_gm_localtime(sp, MY_FALSE);
} /* f_gmtime() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_localtime (svalue_t *sp)
/* TEFUN localtime()
*
* int * localtime(int clock = time())
* int * localtime(int* uclock)
*
* Interpret the argument clock as number of seconds since Jan,
* 1st, 1970, 0:00, and return the time in local time in a nice structure.
*
* Alternatively, accept an array of two ints: the first is <clock>
* value as in the first form, the second int is the number of
* microseconds elapsed in the current second.
*
* The result is an array of integers:
*
* int TM_SEC (0) : Seconds (0..59)
* int TM_MIN (1) : Minutes (0..59)
* int TM_HOUR (2) : Hours (0..23)
* int TM_MDAY (3) : Day of the month (1..31)
* int TM_MON (4) : Month of the year (0..11)
* int TM_YEAR (5) : Year (e.g. 2001)
* int TM_WDAY (6) : Day of the week (Sunday = 0)
* int TM_YDAY (7) : Day of the year (0..365)
* int TM_ISDST (8) : TRUE: Daylight saving time
*/
{
return x_gm_localtime(sp, MY_TRUE);
} /* f_localtime() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_shutdown (svalue_t *sp)
/* TEFUN shutdown()
*
* void shutdown()
*
* Shutdown the mud. Never use this efun. Instead if you have a
* need to shutdown the mud use the shutdown command.
* You may be asking yourself, if you're not supposed
* to use it why is it here? Sorry, I cannot answer
* that. Its top secret.
*/
{
extra_jobs_to_do = MY_TRUE;
game_is_being_shut_down = MY_TRUE;
return sp;
} /* f_shutdown() */
/***************************************************************************/