/*---------------------------------------------------------------------------
* Array handling functions.
*
*---------------------------------------------------------------------------
* TODO: Rewrite the low-level functions (like allocate_array()) to return
* TODO:: failure codes (errno like) instead of throwing errors. In addition,
* TODO:: provide wrapper functions which do throw error()s, so that every
* TODO:: caller can handle the errors himself (like the swapper).
* The structure of an array ("vector") is defined in datatypes.h as this:
*
* vector_t_s {
* p_int size;
* p_int ref;
* p_int extra_ref; (ifdef DEBUG)
* wiz_list_t *user;
* svalue_t item[1...];
* };
*
* .size is the number of elements in the vector.
*
* .ref is the number of references to the vector. If this number
* reaches 0, the vector can (and should) be deallocated. This scheme
* breaks down with circular references, but those are caught by
* the garbage collector.
*
* .extra_ref exists when the driver is compiled for DEBUGging, and
* is used to countercheck the the .ref count.
*
* .user records which wizard's object created the vector, and is used
* to keep the wizlist statistics (array usage) up to date.
*
* .item[] is the array of elements in indexing order. The structure
* itself declares just an array of one element, it is task of the user
* to allocated a big enough memory block.
*
*
* Some macros help with the use of vector variables:
*
* ALLOC_VECTOR(size,file,line): Allocate dynamically the memory for
* a vector of <size> elements.
*
* VEC_SIZE(v): Return the number of elements in v.
*
* VEC_HEAD(size): Expand to the initializers of a vector with
* <size> elements and 1 ref. This does not include the
* element initialisers.
*
* LOCAL_VEC1(name, type1)
* LOCAL_VEC2(name, type1, type2)
* Construct a local vector instance named <name> with 1(2)
* elements of type <type1> (and <type2>). Both elements are
* initialised to 0, and the actual vector can be accessed
* as '<name>.v'.
*
* This module contains both low-level and efun-level functions.
* The latter are collected in the lower half of the source.
*---------------------------------------------------------------------------
* One special application of arrays are alists: associative lists.
* Alists allow the association of data (single values or tuples) with
* a key value, which is then used to locate the data in the alist structure.
*
* Nowadays the same functionality is offered by mappings in a much more
* efficient manner, so this usage of alists is deprecated. However, for
* reasons explained below, alists can be used as an efficient way to
* construct lookup arrays.
*
* It might be historically interesting to know that the very first
* implementations of mappings were mere syntactic sugar for alists.
* Furthermore, the LPMud variant of alists offers only a part of the
* functionality of 'real' alists.
*
* Alists are implemented by a vector of vectors. A typical alist
* for (key:data1,...,dataN) tuples looks like this:
*
* alist = ({ ({ key values })
* , ({ data1 values })
* , ...
* , ({ dataN values })
* })
*
* All subarrays are of the same length, and all the values for one tuple
* is found at the same index. For example, if the key for a tuple
* is found in alist[0][3], the data values are found in alist[1..N][3].
*
* The key value array is sorted to allow fast lookups, the sorting order
* uses the internal representation of the key values (which usually has
* nothing in common with the meaning of the key values). Three things
* however can be guaranteed:
*
* - integer key values appear in rising order in the key array, though
* not necessarily consecutive.
* - removing one or more keys does not break the order of the
* other keys.
* - all strings used as key values are made shared strings.
*
* TODO: order_alist() should be generalized into a sort_array() function
* TODO:: since it is used for more than just alists (similar assoc() into
* TODO:: a lookup function). Alists themselves are pretty outdated by now.
*---------------------------------------------------------------------------
*/
#include "driver.h"
#include "typedefs.h"
#include "my-alloca.h"
#include <stddef.h>
#include "array.h"
#include "backend.h"
#include "closure.h" /* closure_cmp(), closure_eq() */
#include "instrs.h" /* F_FILTER_ARRAY, F_MAP_ARRAY, F_INSER_ALIST */
#include "interpret.h" /* for the efuns */
#include "main.h"
#include "mapping.h"
#include "mempools.h"
#include "object.h"
#include "regexp.h"
#include "rxcache.h"
#include "simulate.h"
#include "svalue.h"
#include "stralloc.h"
#include "swap.h"
#include "wiz_list.h"
#include "xalloc.h"
#include "smalloc.h" /* TODO: DEBUG: as long as vec_size() is used */
#include "../mudlib/sys/functionlist.h"
#include "../mudlib/sys/include_list.h"
#include "../mudlib/sys/inherit_list.h"
/*-------------------------------------------------------------------------*/
int num_arrays;
/* Total number of allocated arrays */
vector_t null_vector = { VEC_HEAD(0), { { T_INVALID } } };
/* The global empty array ({}).
* Reusing it is cheaper than repeated allocations/deallocations.
*/
void (*allocate_array_error_handler) (char *, ...)
= error; /* from simulate.c */
/* This handler is called if an allocation fails.
* Usually it points to simulate::error(), but the swapper
* replaces it temporarily with its own dummy handler when
* swapping in an object.
*/
char *last_insert_alist_shared_string = NULL; /* TODO: Remove me */
/* The last key string inserted into an alist.
* gcollect needs to know this.
* At the moment this value is not used and could as well be
* avoided immediately in insert_alist().
*/
svalue_t assoc_shared_string_key; /* TODO: Remove me */
/* The svalue assoc() uses to pass the shared search key to
* search_alist(). It is initialised by main() on startup,
* probably in order to save a few cycles (assoc() was once
* heavily used). This should be done on every call (static
* initialisation is not possible as it would confuse the
* garbage collector).
*/
#if defined(DEBUG) && defined(MALLOC_smalloc)
vector_t * static_vector1 = NULL;
vector_t * static_vector2 = NULL;
/* Filled in by interpret.c at runtime, these are the other two
* arrays not allocated from the heap.
* TODO: When vec_size() is no longer needed, these can go, too.
*/
/*-------------------------------------------------------------------------*/
p_int
vec_size (vector_t *vec)
/* TODO: Remove this function if nobody complains.
* Return the size of vector <vec>.
* This function compares the size stored in the vector with the
* size of the memory block in case the driver forgets to update
* the stored size.
*/
{
p_int memsize;
if (vec == &null_vector
|| vec == static_vector1
|| vec == static_vector2
)
return vec->size;
memsize = ( malloced_size(vec)
- ( SMALLOC_OVERHEAD +
( sizeof(vector_t) - sizeof(svalue_t) ) / SIZEOF_CHAR_P
)
) / (sizeof(svalue_t)/SIZEOF_CHAR_P);
if (vec->size != memsize)
fatal("Size %ld of vector %p doesn't match memsize %ld\n"
, vec->size, vec, memsize);
return vec->size;
} /* vec_size() */
#endif
/*-------------------------------------------------------------------------*/
#ifndef allocate_array
vector_t *
allocate_array (mp_int n)
#else
vector_t *
_allocate_array(mp_int n, char * file, int line)
#endif
/* Allocate an array for <n> elements (but not more than the current
* maximum) and return the pointer.
* The elements are initialised to the svalue 0.
*
* If the allocations fails (and error() does return), a 0 pointer
* may be returned. This is usually only possible when arrays
* are allocated from the swapper.
*
* Allocating an array of size 0 will return a reference to the
* globally shared empty array.
*
* If possible, annotate the allocations with <file> and <line>
*/
{
mp_int i;
vector_t *p;
svalue_t *svp;
if (n < 0 || (max_array_size && (size_t)n > max_array_size))
error("Illegal array size: %ld.\n", n);
if (n == 0) {
p = ref_array(&null_vector);
return p;
}
num_arrays++;
p = ALLOC_VECTOR(n, file, line);
if (!p) {
#ifndef allocate_array
(*allocate_array_error_handler)("Out of memory: array[%ld]\n", n);
#else
(*allocate_array_error_handler)
("(%s:%d) Out of memory: array[%ld]\n", file, line, n);
#endif
return 0;
}
p->ref = 1;
p->size = n;
if (current_object)
(p->user = current_object->user)->size_array += n;
else
(p->user = &default_wizlist_entry)->size_array += n;
svp = p->item;
for (i = n; --i >= 0; )
*svp++ = const0;
return p;
}
/*-------------------------------------------------------------------------*/
#ifndef allocate_array_unlimited
vector_t *
allocate_array_unlimited (mp_int n)
#else
vector_t *
_allocate_array_unlimited(mp_int n, char * file, int line)
#endif
/* Allocate an array for <n> elements and return the pointer.
* The elements are initialised to the svalue 0.
*
* If the allocations fails (and error() does return), a 0 pointer
* may be returned. This is usually only possible when arrays
* are allocated from the swapper.
*
* Allocating an array of size 0 will return a reference to the
* globally shared empty array.
*
* If possible, annotate the allocations with <file> and <line>
*/
{
mp_int i;
vector_t *p;
svalue_t *svp;
if (n < 0)
error("Illegal array size: %ld.\n", n);
if (n == 0) {
p = ref_array(&null_vector);
return p;
}
num_arrays++;
p = ALLOC_VECTOR(n, file, line);
if (!p) {
#ifndef allocate_array_unlimited
(*allocate_array_error_handler)
("Out of memory: unlimited array[%ld]\n", n);
#else
(*allocate_array_error_handler)
("(%s:%d) Out of memory: unlimited array[%ld]\n", file, line, n);
#endif
return 0;
}
p->ref = 1;
p->size = n;
if (current_object)
(p->user = current_object->user)->size_array += n;
else
(p->user = &default_wizlist_entry)->size_array += n;
svp = p->item;
for (i = n; --i >= 0; )
*svp++ = const0;
return p;
}
/*-------------------------------------------------------------------------*/
#ifndef allocate_uninit_array
vector_t *
allocate_uninit_array (mp_int n)
#else
vector_t *
_allocate_uninit_array (mp_int n, char *file, int line)
#endif
/* Allocate an array for <n> elements (but no more than the current
* maximum) and return the pointer.
* The elements are not initialised.
* If the allocations fails (and error() does return), a 0 pointer
* may be returned.
*
* Allocating an array of size 0 will return a reference to the
* globally shared empty array.
*
* If possible, annotate the allocations with <file> and <line>
*/
{
vector_t *p;
if (n < 0 || (max_array_size && (size_t)n > max_array_size))
error("Illegal array size: %ld.\n", n);
if (n == 0) {
p = ref_array(&null_vector);
return p;
}
num_arrays++;
p = ALLOC_VECTOR(n, file, line);
if (!p) {
#ifndef allocate_uninit_array
(*allocate_array_error_handler)
("Out of memory: uninited array[%ld]\n", n);
#else
(*allocate_array_error_handler)
("(%s:%d) Out of memory: uninited array[%ld]\n", file, line, n);
#endif
return 0;
}
p->ref = 1;
p->size = n;
if (current_object)
(p->user = current_object->user)->size_array += n;
else
(p->user = &default_wizlist_entry)->size_array += n;
return p;
}
/*-------------------------------------------------------------------------*/
void
_free_vector (vector_t *p)
/* Deallocate the vector <p>, properly freeing the contained elements.
* The refcount is supposed to be zero at the time of call.
*/
{
mp_uint i;
svalue_t *svp;
#ifdef DEBUG
if (p->ref > 0)
fatal("Vector with %ld refs passed to _free_vector()\n", p->ref);
if (p == &null_vector)
fatal("Tried to free the zero-size shared vector.\n");
#endif
i = VEC_SIZE(p);
num_arrays--;
p->user->size_array -= i;
svp = p->item;
do {
free_svalue(svp++);
} while (--i);
xfree(p);
} /* _free_vector() */
/*-------------------------------------------------------------------------*/
void
free_empty_vector (vector_t *p)
/* Deallocate the vector <p> without regard of refcount or contained
* elements. Just the statistics are cared for.
*/
{
mp_uint i;
i = VEC_SIZE(p);
p->user->size_array -= i;
num_arrays--;
xfree((char *)p);
}
/*-------------------------------------------------------------------------*/
static vector_t *
shrink_array (vector_t *p, mp_int n)
/* Create and return a new array containing just the first <n> elements
* of <p>. <p> itself is freed (and thus possibly deallocated).
*/
{
vector_t *res;
if (p->ref == 1 && VEC_SIZE(p) == n)
return p;
/* This case seems to happen often enough to justify
* the shortcut
*/
if (n)
{
res = slice_array(p, 0, n-1);
}
else
{
res = ref_array(&null_vector);
}
free_array(p);
return res;
}
/*-------------------------------------------------------------------------*/
void
set_vector_user (vector_t *p, object_t *owner)
/* Wizlist statistics: take vector <p> from its former owner and account it
* under its new <owner>.
*/
{
svalue_t *svp;
mp_int i;
i = (mp_int)VEC_SIZE(p);
if (p->user)
p->user->size_array -= i;
if ( NULL != (p->user = owner->user) )
p->user->size_array += i;
svp = p->item;
for (; --i >= 0; svp++) {
set_svalue_user(svp, owner);
}
}
/*-------------------------------------------------------------------------*/
void
check_for_destr (vector_t *v)
/* Check the vector <v> for destructed objects and closures on destructed
* objects and replace them with svalue 0s. Subvectors are not checked, though.
*
* This function is used by certain efuns (parse_command(), unique_array(),
* map_array()) to make sure that the data passed to the efuns is valid,
* avoiding game crashes (though this won't happen on simple operations
* like assign_svalue).
* TODO: The better way is to make the affected efuns resistant against
* TODO:: destructed objects, and keeping this only as a safeguard and
* TODO:: to save memory.
*/
{
mp_int i;
svalue_t *p;
for (p = v->item, i = (mp_int)VEC_SIZE(v); --i >= 0 ; p++ )
{
if (destructed_object_ref(p))
assign_svalue(p, &const0);
}
} /* check_for_destr() */
/*-------------------------------------------------------------------------*/
long
total_array_size (void)
/* Statistics for the command 'status [tables]'.
* Return the total memory used for all vectors in the game.
*/
{
wiz_list_t *wl;
long total;
total = default_wizlist_entry.size_array;
for (wl = all_wiz; wl; wl = wl->next)
total += wl->size_array;
total *= sizeof(svalue_t);
total += num_arrays * (sizeof(vector_t) - sizeof(svalue_t));
return total;
}
/*-------------------------------------------------------------------------*/
#if defined(GC_SUPPORT)
void
clear_array_size (void)
/* Clear the statistics about the number and memory usage of all vectors
* in the game.
*/
{
wiz_list_t *wl;
num_arrays = 0;
default_wizlist_entry.size_array = 0;
for (wl = all_wiz; wl; wl = wl->next)
wl->size_array = 0;
} /* clear_array_size(void) */
/*-------------------------------------------------------------------------*/
void
count_array_size (vector_t *vec)
/* Add the vector <vec> to the statistics.
*/
{
num_arrays++;
vec->user->size_array += VEC_SIZE(vec);
} /* count_array_size(void) */
#endif /* GC_SUPPORT */
/*-------------------------------------------------------------------------*/
vector_t *
explode_string (char *str, char *del)
/* Explode the string <str> by delimiter string <del> and return an array
* of the (unshared) strings found between the delimiters.
* They are unshared because they are most likely short-lived.
*
* TODO: At some later point in the execution thread, all the longlived
* unshared strings should maybe be converted into shared strings.
*
* This is the new, logical behaviour: nothing is occured.
* The relation implode(explode(x,y),y) == x holds.
*
* explode("xyz", "") -> { "x", "y", "z" }
* explode("###", "##") -> { "", "#" }
* explode(" the fox ", " ") -> { "", "the", "", "", "fox", ""}
*/
{
char *p, *beg;
long num;
long len;
vector_t *ret;
char *buff;
len = (long)strlen(del);
/* --- Special case: Delimiter is an empty or one-char string --- */
if (len <= 1) {
/* Delimiter is empty: return an array which holds all characters as
* single-character strings.
*/
if (len < 1) {
svalue_t *svp;
len = (long)strlen(str);
ret = allocate_array(len);
for( svp = ret->item; --len >= 0; svp++, str++ ) {
buff = xalloc(2);
if (!buff) {
free_array(ret);
error("(explode_string) Out of memory (2 bytes)\n");
}
buff[0] = *str;
buff[1] = '\0';
put_malloced_string(svp, buff);
}
return ret;
}
/* Delimiter is one-char string: speedy implementation which uses
* direct character comparisons instead of calls to strncmp().
*/
else {
char c;
svalue_t *svp;
c = *del;
/* TODO: Remember positions here */
/* Determine the number of delimiters in the string. */
for (num = 1, p = str; NULL != (p = strchr(p, c)); p++, num++) NOOP;
ret = allocate_array(num);
for (svp = ret->item; NULL != (p = strchr(str, c)); str = p + 1, svp++) {
len = p - str;
buff = xalloc((size_t)(len + 1));
if (!buff) {
free_array(ret);
error("(explode_string) Out of memory (%ld bytes)\n"
, len+1);
}
memcpy(buff, str, (size_t)len);
buff[len] = '\0';
put_malloced_string(svp, buff);
}
/* str now points to the (possibly empty) remains after
* the last delimiter.
*/
put_malloced_string(svp, string_copy(str));
if ( !svp->u.string ) {
free_array(ret);
error("(explode_string) Out of memory (%lu bytes) for result.\n"
, (unsigned long)strlen(str));
}
return ret;
}
/* NOTREACHED */
} /* --- End of special case --- */
/* Find the number of occurences of the delimiter 'del' by doing
* a first scan of the string.
*
* The number of array items is then one more than the number of
* delimiters, hence the 'num=1'.
* TODO: Implement a strncmp() which returns the number of matching
* characters in case of a mismatch.
* TODO: Remember the found positions so that we don't have to
* do the comparisons again.
*/
for (p=str, num=1; *p;) {
if (strncmp(p, del, (size_t)len) == 0) {
p += len;
num++;
} else
p += 1;
}
ret = allocate_array(num);
/* Extract the <num> strings into the result array <ret>.
* <buff> serves as temporary buffer for the copying.
*/
for (p=str, beg = str, num=0; *p; ) {
if (strncmp(p, del, (size_t)len) == 0) {
long bufflen;
bufflen = p - beg;
buff = xalloc((size_t)bufflen + 1);
if (!buff) {
free_array(ret);
error("(explode_string) Out of memory (%ld bytes) for buffer\n"
, bufflen+1);
}
memcpy(buff, beg, (size_t)bufflen);
buff[bufflen] = '\0';
put_malloced_string(ret->item+num, buff);
num++;
beg = p + len;
p = beg;
} else {
p += 1;
}
}
/* Copy the last occurence (may be empty). */
put_malloced_string(ret->item + num, string_copy(beg));
if ( !ret->item[num].u.string) {
free_array(ret);
error("(explode_string) Out of memory (%lu bytes) for last fragment\n"
, (unsigned long)strlen(beg));
}
return ret;
}
/*-------------------------------------------------------------------------*/
vector_t *
old_explode_string (char *str, char *del)
/* Explode the string <str> by delimiter string <del> and return an array
* of the (unshared) strings found between the delimiters.
*
* This is the old behaviour: leading and trailing occurences of <del>
* in <str> are ignored.
*
* explode("xyz", "") -> { "xyz" }
* explode("###", "##") -> { "", "#" }
* explode(" the fox ", " ") -> { "the", "", "fox" }
*
* This function used to implement the explode() efun. Now the parse_command
* parser and the efun process_string() are the only parts still using it.
*/
{
char *p, *beg;
size_t num, len;
vector_t *ret;
char *buff;
len = strlen(del);
/* Take care of the case where the delimiter is an
* empty string. Then, return an array with only one element,
* which is the original string.
*/
if (len == 0) {
ret = allocate_array(1);
put_malloced_string(ret->item, string_copy(str));
return ret;
}
/* Skip leading 'del' strings, if any.
*/
while(strncmp(str, del, len) == 0) {
str += len;
if (str[0] == '\0')
return allocate_array(0);
}
/* Find number of occurences of the delimiter 'del' by doing a first
* scan of the string.
*
* The found number + 1 is then the number of needed array elements.
* Remember that explode("###","##") -> { "","#" }.
* TODO: Implement a strncmp() which returns the number of matching
* characters in case of a mismatch.
* TODO: Remember the found positions so that we don't have to
* do the comparisons again.
*/
for (p=str, num=1; *p;) {
if (strncmp(p, del, len) == 0) {
p += len;
if (*p)
num++;
} else
p += 1;
}
ret = allocate_array(num);
/* Extract the <num> strings into the result array <ret>.
* <buff> serves as temporary buffer for the copying.
*/
buff = xalloc(strlen(str) + 1);
if (!buff)
{
free_array(ret);
error("(old_explode) Out of memory (%lu bytes) for result.\n"
, (unsigned long)strlen(str)+1);
/* NOTREACHED */
return NULL;
}
for (p=str, beg = str, num=0; *p; ) {
if (strncmp(p, del, len) == 0) {
strncpy(buff, beg, p - beg);
buff[p-beg] = '\0';
put_malloced_string(ret->item + num, string_copy(buff));
/* TODO: implement a string_copy_n(beg, n) */
num++;
beg = p + len;
p = beg;
} else {
p += 1;
}
}
/* Copy last occurence, if there was not a 'del' at the end.
*/
if (*beg != '\0') {
#if defined(DEBUG) || 1
if (num >= VEC_SIZE(ret))
fatal("Index out of bounds in old explode(): estimated %ld, got %ld.\n", (long)num, VEC_SIZE(ret));
#endif
put_malloced_string(ret->item + num, string_copy(beg));
}
xfree(buff);
return ret;
} /* old_explode_string() */
/*-------------------------------------------------------------------------*/
#ifndef implode_string
char *
implode_string (vector_t *arr, char *del)
#else
char *
_implode_string (vector_t *arr, char *del, char *file, int line)
#endif
/* Implode the string vector <arr> by <del>, i.e. all strings from <arr>
* with <del> interspersed are contatenated into one string. The
* resulting string is returned. The function will return at least
* the empty string "".
*
* Non-string elements are ignore; elements referencing destructed
* objects are replaced by the svalue number 0.
*
* implode({"The", "fox", ""}, " ") -> "The fox "
*
* If possible, annotate the allocations with <file> and <line>
*/
{
mp_int size, i, arr_size;
mp_int del_len;
char *p, *q;
svalue_t *svp;
del_len = (mp_int)strlen(del);
/* Compute the <size> of the final string
*/
size = -del_len;
for (i = (arr_size = (mp_int)VEC_SIZE(arr)), svp = arr->item; --i >= 0; svp++)
{
if (svp->type == T_STRING) {
size += del_len + strlen(svp->u.string);
} else if (destructed_object_ref(svp)) {
assign_svalue(svp, &const0);
}
}
/* Allocate the string; cop out if there's nothing to implode.
*/
#ifndef implode_string
if (size <= 0)
return string_copy("");
p = xalloc((size_t)size + 1);
#else
if (size <= 0)
return string_copy_traced("", file, line);
p = xalloc_traced((size_t)size + 1, file, line);
#endif
if (!p) {
/* caller raises the error() */
return NULL;
}
q = p; /* Remember the start of the allocated string */
/* Concatenate the result string.
*
* <i> is the number of elements left to check,
* <svp> is the next element to check,
* <p> points to the current end of the result string.
*/
svp = arr->item;
/* Look for the first element to add (there is at least one!) */
for (i = arr_size; svp->type != T_STRING; ) {
--i;
svp++;
}
strcpy(p, svp->u.string);
p += strlen(svp->u.string);
/* Copy the others if any */
while (--i > 0) {
svp++;
if (svp->type == T_STRING) {
strcpy(p, del);
p += del_len;
strcpy(p, svp->u.string);
p += strlen(svp->u.string);
}
}
return q;
}
/*-------------------------------------------------------------------------*/
vector_t *
slice_array (vector_t *p, mp_int from, mp_int to)
/* Create a vector slice from vector <p>, range <from> to <to> inclusive,
* and return it.
*
* <to> is guaranteed to not exceed the size of <p>.
* If <from> is greater than <to>, the empty array is returned.
*/
{
vector_t *d;
int cnt;
if (from < 0)
from = 0;
if (to < from)
return allocate_array(0);
d = allocate_array(to-from+1);
for (cnt = from; cnt <= to; cnt++)
assign_svalue_no_free (&d->item[cnt-from], &p->item[cnt]);
return d;
}
/*-------------------------------------------------------------------------*/
vector_t *
add_array (vector_t *p, vector_t *q)
/* Concatenate the vectors <p> and <q> and return the resulting vector.
* <p> and <q> are not modified.
*/
{
mp_int cnt;
svalue_t *s, *d;
mp_int q_size;
s = p->item;
p = allocate_array((cnt = (mp_int)VEC_SIZE(p)) + (q_size = (mp_int)VEC_SIZE(q)));
d = p->item;
for ( ; --cnt >= 0; ) {
assign_svalue_no_free (d++, s++);
}
s = q->item;
for (cnt = q_size; --cnt >= 0; ) {
assign_svalue_no_free (d++, s++);
}
return p;
}
/*-------------------------------------------------------------------------*/
static int
compare_single (svalue_t *svp, vector_t *v)
/* Compare *svp and v->item[0], return 0 if equal, and -1 if not.
*
* The function is used by subtract_array() and must match the signature
* of assoc().
*/
{
svalue_t *p2 = &v->item[0];
if (svp->type != p2->type)
return -1;
if (svp->type == T_STRING)
{
if (svp->u.string == p2->u.string)
return 0;
return strcmp(svp->u.string, p2->u.string) ? -1 : 0;
}
if (svp->type == T_CLOSURE)
{
return closure_cmp(svp, p2);
}
if (svp->u.number != p2->u.number)
return -1;
switch (svp->type)
{
case T_FLOAT:
case T_SYMBOL:
case T_QUOTED_ARRAY:
return svp->x.generic != p2->x.generic ? -1 : 0;
default:
return 0;
}
/* NOTREACHED */
return 0;
}
/*-------------------------------------------------------------------------*/
vector_t *
subtract_array (vector_t *minuend, vector_t *subtrahend)
/* Subtract all elements in <subtrahend> from the vector <minuend>
* and return the resulting difference vector.
* <subtrahend> and <minuend> are freed.
*
* The function uses order_alist()/assoc()/compare_single() on
* <subtrahend> for faster operation, and recognizes subtrahends with
* only one element and/or one reference.
*/
{
static svalue_t ltmp = { T_POINTER };
/* Temporary svalue to pass vectors to order_alist().
* The static initialisation saves a few cycles.
*/
vector_t *difference; /* Resulting difference vector,
with extra zeroes at the end */
vector_t *vtmpp; /* {( Ordered <subtrahend> }) */
svalue_t *source, *dest; /* Pointers into minuend
and difference vector */
mp_int i;
mp_int minuend_size = (mp_int)VEC_SIZE(minuend);
mp_int subtrahend_size = (mp_int)VEC_SIZE(subtrahend);
int (*assoc_function)(svalue_t *, vector_t *);
/* Function to find an svalue in a sorted vector.
* Use of this indirection allows to replace assoc() with
* faster functions for special cases.
*/
/* Handle empty vectors quickly */
if (minuend_size == 0)
{
free_array(subtrahend);
return minuend;
}
if (subtrahend_size == 0)
{
free_array(subtrahend);
return shrink_array(minuend, minuend_size);
}
/* Order the subtrahend */
if (subtrahend_size == 1)
{
if (destructed_object_ref(&subtrahend->item[0]))
{
assign_svalue(&subtrahend->item[0], &const0);
}
assoc_function = &compare_single;
vtmpp = subtrahend;
}
else
{
ltmp.u.vec = subtrahend;
vtmpp = order_alist(<mp, 1, 1);
free_array(ltmp.u.vec);
assoc_function = &assoc;
subtrahend = vtmpp->item[0].u.vec;
}
/* Scan minuend and look up every element in the ordered subtrahend.
* If it's not there, add the element to the difference vector.
* If minuend is referenced only once, reuse its memory.
*/
if (minuend->ref == 1)
{
for (source = minuend->item, i = minuend_size ; i-- ; source++)
{
if (destructed_object_ref(source))
assign_svalue(source, &const0);
if ( (*assoc_function)(source, subtrahend) >-1 ) break;
}
for (dest = source++; i-- > 0 ; source++)
{
if (destructed_object_ref(source))
assign_svalue(source, &const0);
if ( (*assoc_function)(source, subtrahend) < 0 )
assign_svalue(dest++, source);
}
free_array(vtmpp);
return shrink_array(minuend, dest - minuend->item);
}
/* The difference can be equal to minuend in the worst case */
difference = allocate_array(minuend_size);
for (source = minuend->item, dest = difference->item, i = minuend_size
; i--
; source++) {
if (destructed_object_ref(source))
assign_svalue(source, &const0);
if ( (*assoc_function)(source, subtrahend) < 0 )
assign_svalue_no_free(dest++, source);
}
free_array(vtmpp);
free_array(minuend);
/* Shrink the difference vector to the needed size and return it. */
return shrink_array(difference, dest-difference->item);
}
/*-------------------------------------------------------------------------*/
vector_t *
all_inventory (object_t *ob)
/* Return a vector with all objects contained in <ob>.
* TODO: Make this a proper f_all_inventory(sp, num_arg) efun?
*/
{
vector_t *d; /* The result vector */
object_t *cur; /* Current inventory object */
int cnt, res;
/* Count how many inventory objects there are. */
cnt=0;
for (cur=ob->contains; cur; cur = cur->next_inv)
cnt++;
if (!cnt)
return allocate_array(0);
d = allocate_array(cnt);
/* Copy the object references */
cur=ob->contains;
for (res=0; res < cnt; res++) {
d->item[res].type=T_OBJECT;
d->item[res].u.ob = ref_object(cur, "all_inventory");
cur=cur->next_inv;
}
return d;
}
/*-------------------------------------------------------------------------*/
static int
deep_inventory_size (object_t *ob)
/* Helper function for deep_inventory()
*
* Count the size of <ob>'s inventory by counting the contained objects,
* invoking this function for every object and then returning the sum
* of all numbers.
*/
{
int n;
n = 0;
do {
if (ob->contains)
n += deep_inventory_size(ob->contains);
n++;
} while ( NULL != (ob = ob->next_inv) );
return n;
}
/*-------------------------------------------------------------------------*/
static svalue_t *
write_deep_inventory (object_t *first, svalue_t *svp)
/* Helper function for deep_inventory()
*
* Copy into <svp> and following a reference to all objects in the
* inventory chain starting with <first>; then invoke this function
* for every inventory chain in the found objects.
*
* <svp> has to point into a suitably big area of svalue elements, like
* a vector.
*
* Result is the updated <svp>, pointing to the next free svalue element
* in the storage area.
*/
{
object_t *ob;
ob = first;
do {
put_ref_object(svp, ob, "deep_inventory");
svp++;
} while ( NULL != (ob = ob->next_inv) );
ob = first;
do {
if (ob->contains)
svp = write_deep_inventory(ob->contains, svp);
} while ( NULL != (ob = ob->next_inv) );
return svp;
}
/*-------------------------------------------------------------------------*/
vector_t *
deep_inventory (object_t *ob, Bool take_top)
/* Return a vector with the full inventory of <ob>, i.e. all objects contained
* by <ob> and all deep inventories of those objects, too. The resulting
* vector is created by a recursive breadth search.
*
* If <take_top> is true, <ob> itself is included as first element in the
* result vector.
*
* The function is used for the efuns deep_inventory() and parse_command().
*/
{
vector_t *dinv; /* The resulting inventory vector */
svalue_t *svp; /* Next element to fill in dinv */
int n; /* Number of elements in dinv */
/* Count the contained objects */
n = take_top ? 1 : 0;
if (ob->contains) {
n += deep_inventory_size(ob->contains);
}
/* Get the array */
dinv = allocate_array(n);
svp = dinv->item;
/* Fill in <ob> if desired */
if (take_top) {
put_ref_object(svp, ob, "deep_inventory");
svp++;
}
/* Fill in the deep inventory */
if (ob->contains) {
write_deep_inventory(ob->contains, svp);
}
return dinv;
}
/*-------------------------------------------------------------------------*/
static INLINE int
alist_cmp (svalue_t *p1, svalue_t *p2)
/* Alist comparison function.
*
* Compare the svalues <p1> and <p2> and return an integer with the
* following meaning:
*
* > 0: <p1> 'is greater than' <p2>
* = 0: <p1> 'is equal to' <p2>
* < 0: <p1> 'is less than' <p2>
*
* The relation need not make sense with the actual interpretation
* of <p1>/<p2>, as long as it defines a deterministic order relation.
*
* TODO: Is the assumption '.number is big enough to hold everything
* TODO:: in the svalue' true for future hardware?
* TODO: Reinterpreting the pointers as 'integer' may not be portable
* TODO:: enough.
*/
{
register int d;
/* Avoid a numeric overflow by first comparing the values halfed. */
if ( 0 != (d = p1->type - p2->type) ) return d;
if (p1->type == T_CLOSURE)
return closure_cmp(p1, p2);
if ( 0 != (d = (p1->u.number >> 1) - (p2->u.number >> 1)) ) return d;
if ( 0 != (d = p1->u.number - p2->u.number) ) return d;
switch (p1->type) {
case T_FLOAT:
case T_SYMBOL:
case T_QUOTED_ARRAY:
if ( 0 != (d = p1->x.generic - p2->x.generic) ) return d;
break;
}
return 0;
}
/*-------------------------------------------------------------------------*/
vector_t *
order_alist (svalue_t *inlists, int listnum, Bool reuse)
/* Order the alist <inlists> and return a new vector with it. The sorting
* order is the internal order defined by alist_cmp().
*
* <inlists> is a vector of <listnum> vectors:
* <inlists> = ({ ({ keys }), ({ data1 }), ..., ({ data<listnum-1> }) })
*
* If <reuse> is true, the vectors of <inlists> are reused for the
* vectors of the result when possible, and their entries in <inlists> are
* set to T_INVALID.
*
* This function and assoc() are used in several places for internal
* lookup functions (e.g. in sort_array()).
*
* As a side effect, strings in the key vector are made shared, and
* destructed objects in key and data vectors are replaced by svalue 0s.
*/
{
vector_t *outlist; /* The result vector of vectors */
vector_t *v; /* Aux vector pointer */
svalue_t *outlists; /* Next element in outlist to fill in */
ptrdiff_t * sorted;
/* The vector elements in sorted order, given as the offsets of the array
* element in question to the start of the vector. This way,
* sorted[] needs only to be <keynum> elements long.
* sorted[] is created from root[] after sorting.
*/
svalue_t **root;
/* Auxiliary array with the sorted keys as svalue* into inlists[0].vec.
* This way the sorting is given by the order of the pointers, while
* the original position is given by (pointer-inlists[0].vec->item).
* The very first element is a dummy (heapsort uses array indexing
* starting with index 1), the next <keynum> elements are scratch
* area, the final <keynum> elements hold the sorted keys in reverse
* order.
*/
svalue_t **root2; /* Aux pointer into *root. */
svalue_t *inpnt; /* Pointer to the value to copy into the result */
mp_int keynum; /* Number of keys */
int i, j;
keynum = (mp_int)VEC_SIZE(inlists[0].u.vec);
/* Allocate the auxiliary array. */
root = (svalue_t **)alloca(keynum * sizeof(svalue_t *[2])
+ sizeof(svalue_t)
);
sorted = alloca(keynum * sizeof(ptrdiff_t) + sizeof(ptrdiff_t));
/* TODO: keynum may be 0, so the c-alloca() would return NULL without
* the extra sizeof(ptrdiff_t) :-(
*/
if (!root || !sorted)
{
error("Stack overflow in order_alist()");
/* NOTREACHED */
return NULL;
}
/*
* Heapsort inlists[0].vec into *root.
* TODO: For small arrays a simpler sort like linear insertion or
* TODO:: even bubblesort might be faster (less overhead). Best solution
* TODO:: would be to offer both algorithms and determine the threshhold
* TODO:: at startup.
*/
/* Heapify the keys into the first half of root */
for ( j = 1, inpnt = inlists->u.vec->item
; j <= keynum
; j++, inpnt++)
{
int curix, parix;
/* make sure that strings can be compared by their pointer */
if (inpnt->type == T_STRING) {
if (inpnt->x.string_type != STRING_SHARED) {
char *str = make_shared_string(inpnt->u.string);
free_string_svalue(inpnt);
inpnt->x.string_type = STRING_SHARED;
inpnt->u.string = str;
}
} else if (destructed_object_ref(inpnt)) {
free_svalue(inpnt);
put_number(inpnt, 0);
}
/* propagate the new element up in the heap as much as necessary */
for(curix = j; 0 != (parix = curix>>1); ) {
if ( alist_cmp(root[parix], inpnt) > 0 ) {
root[curix] = root[parix];
curix = parix;
} else {
break;
}
}
root[curix] = inpnt;
}
root++; /* Adjust root to ignore the heapsort-dummy element */
/* Sort the heaped keys from the first into the second half of root. */
root2 = &root[keynum];
for(j = keynum; --j >= 0; ) {
int curix;
*root2++ = *root;
for (curix=0; ; ) {
int child, child2;
child = curix+curix+1;
child2 = child+1;
if (child2 >= keynum) {
if (child2 == keynum && root[child]) {
root[curix] = root[child];
curix = child;
}
break;
}
if (root[child2]) {
if (!root[child] || alist_cmp(root[child], root[child2]) > 0)
{
root[curix] = root[child2];
curix = child2;
continue;
}
} else if (!root[child]) {
break;
}
root[curix] = root[child];
curix = child;
}
root[curix] = 0;
}
/* Compute the sorted offsets from root[] into sorted[].
* Note that root[] is in reverse order.
*/
for (root = &root[keynum], j = 0; j < keynum; j++)
sorted[j] = root[keynum-j-1] - inlists[0].u.vec->item;
/*
* Generate the result vectors from the sorted keys in root.
*/
outlist = allocate_array(listnum);
outlists = outlist->item;
/* Copy the elements from all inlist vectors into the outlist
* vectors.
*
* At the beginning of every loop v points to the vector to
* use as the next 'out' vector. It may be a re-used 'in' vector
* from the previous run.
*/
v = allocate_array(keynum);
for (i = listnum; --i >= 0; ) {
svalue_t *outpnt; /* Next result value element to fill in */
/* Set the new array v as the next 'out' vector, and init outpnt
* and offs.
*/
put_array(outlists + i, v);
outpnt = v->item;
v = inlists[i].u.vec; /* Next vector to fill if reusable */
/* Copy the elements.
* For a reusable 'in' vector, a simple memory copy is sufficient.
* For a new vector, a full assignment is due to keep the refcounters
* happy.
*/
if (reuse && inlists[i].u.vec->ref == 1) {
if (i)/* not the last iteration */
inlists[i].type = T_INVALID;
for (j = keynum; --j >= 0; ) {
inpnt = inlists[i].u.vec->item + sorted[j];
if (destructed_object_ref(inpnt))
{
free_svalue(inpnt);
put_number(outpnt, 0);
outpnt++;
} else {
*outpnt++ = *inpnt;
}
inpnt->type = T_INVALID;
}
} else {
if (i) /* not the last iteration */
v = allocate_array(keynum);
for (j = keynum; --j >= 0; ) {
inpnt = inlists[i].u.vec->item + sorted[j];
if (destructed_object_ref(inpnt))
{
put_number(outpnt, 0);
outpnt++;
} else {
assign_svalue_no_free(outpnt++, inpnt);
}
}
} /* if (reuse) */
} /* for (listnum) */
return outlist;
}
/*-------------------------------------------------------------------------*/
Bool
is_alist (vector_t *v)
/* Determine if <v> satisfies the conditions for being an alist key vector.
* Return true if yes, false if not.
*
* The conditions are:
* - every string is shared
* - all elements are sorted according to alist_cmp().
*
* Note that an ordinary array can do this by chance.
*
* This predicate is currently used just by the swapper to avoid swapping
* out alist values. This is because the internal order is based on
* pointer values and thus unreproducible.
*/
{
svalue_t *svp;
mp_int i;
for (svp = v->item, i = (mp_int)VEC_SIZE(v); --i > 0; svp++) {
if (svp->type == T_STRING && svp->x.string_type != STRING_SHARED)
return 0;
if (alist_cmp(svp, svp+1) > 0)
return 0;
}
if (svp->type == T_STRING && svp->x.string_type != STRING_SHARED)
return 0;
return 1;
}
/*=========================================================================*/
/* EFUNS */
/*-------------------------------------------------------------------------*/
svalue_t *
x_filter_array (svalue_t *sp, int num_arg)
/* VEFUN: filter() for arrays.
*
* mixed *filter(mixed *arr, string fun)
* mixed *filter(mixed *arr, string fun, string|object obj, mixed extra, ...)
* mixed *filter(mixed *arr, closure cl, mixed extra, ...)
* mixed *filter(mixed *arr, mapping map)
*
* Filter the elements of <arr> through a filter defined by the other
* arguments, and return an array of those elements, for which the
* filter yields non-zero.
*
* The filter can be a function call:
*
* <obj>-><fun>(elem, <extra>...)
*
* or a mapping query:
*
* <map>[elem]
*
* <obj> can both be an object reference or a filename. If omitted,
* this_object() is used (this also works if the third argument is
* neither a string nor an object).
*
* As a bonus, all references to destructed objects in <arr> are replaced
* by proper 0es.
*
* TODO: Autodoc-Feature to create doc/efun/filter_array automatically.
*/
{
svalue_t *arg; /* First argument the vm stack */
vector_t *p; /* The filtered vector */
mp_int p_size; /* sizeof(*p) */
vector_t *vec;
svalue_t *v, *w;
char *flags; /* Flag array, one flag for each element of <p> */
int res; /* Number of surviving elements */
int cnt;
res = 0;
/* Locate the args on the stack, extract the vector to filter
* and allocate the flags vector.
*/
arg = sp - num_arg + 1;
if (arg->type != T_POINTER)
bad_xefun_vararg(1, sp);
p = arg->u.vec;
p_size = (mp_int)VEC_SIZE(p);
flags = alloca((size_t)p_size+1);
if (!flags)
{
error("Stack overflow in filter_array()");
/* NOTREACHED */
return NULL;
}
/* Every element in flags is associated by index number with an
* element in the vector to filter. The filter function is evaluated
* for every vector element, and the associated flag is set to 0
* or 1 according to the result.
* At the end, all 1-flagged elements are gathered and copied
* into the result vector.
*/
if (arg[1].type == T_MAPPING) {
/* --- Filter by mapping query --- */
mapping_t *m;
if (num_arg > 2) {
inter_sp = sp;
error("Too many arguments to filter_array()\n");
}
m = arg[1].u.map;
for (w = p->item, cnt = p_size; --cnt >= 0; )
{
if (destructed_object_ref(w))
assign_svalue(w, &const0);
if (get_map_value(m, w++) == &const0) {
flags[cnt] = 0;
continue;
}
flags[cnt] = 1;
res++;
}
free_svalue(arg+1);
sp = arg;
} else {
/* --- Filter by function call --- */
int error_index;
callback_t cb;
assign_eval_cost();
inter_sp = sp;
error_index = setup_efun_callback(&cb, arg+1, num_arg-1);
if (error_index >= 0)
{
bad_xefun_vararg(error_index+2, arg);
/* NOTREACHED */
return arg;
}
inter_sp = sp = arg+1;
put_callback(sp, &cb);
/* Loop over all elements in p and call the filter.
* w is the current element filtered.
*/
for (w = p->item, cnt = p_size; --cnt >= 0; )
{
flags[cnt] = 0;
if (current_object->flags & O_DESTRUCTED)
continue;
/* Don't call the filter anymore, but fill the
* flags array with 0es.
*/
if (destructed_object_ref(w))
assign_svalue(w, &const0);
if (!callback_object(&cb))
{
inter_sp = sp;
error("object used by filter_array destructed");
}
push_svalue(w++);
v = apply_callback(&cb, 1);
if (!v || (v->type == T_NUMBER && !v->u.number) )
continue;
flags[cnt] = 1;
res++;
}
free_callback(&cb);
}
/* flags[] holds the filter results, res is the number of
* elements to keep. Now create the result vector.
*/
vec = allocate_array(res);
if (res) {
for(v = p->item, w = vec->item, flags = &flags[p_size]; ; v++) {
if (*--flags) {
assign_svalue_no_free (w++, v);
if (--res <= 0) break;
}
}
}
/* Cleanup (everything but the array has been removed already) */
free_array(p);
arg->u.vec = vec;
return arg;
} /* x_filter_array() */
#ifdef F_FILTER_ARRAY
svalue_t * f_filter_array (svalue_t *sp, int num_arg)
{ return x_filter_array (sp, num_arg); }
#endif
/*-------------------------------------------------------------------------*/
svalue_t *
x_map_array (svalue_t *sp, int num_arg)
/* VEFUN map() on arrays
*
* mixed * map(mixed *arg, string func, string|object ob, mixed extra...)
* mixed * map(mixed *arg, closure cl, mixed extra...)
* mixed * map(mixed *arr, mapping map)
*
* Map the elements of <arr> through a filter defined by the other
* arguments, and return an array of the elements returned by the filter.
*
* The filter can be a function call:
*
* <obj>-><fun>(elem, <extra>...)
*
* or a mapping query:
*
* <map>[elem]
*
* In the mapping case, if <map>[elem] does not exist, the original
* value is returned in the result.
*
* <obj> can both be an object reference or a filename. If <ob> is
* omitted, or neither an object nor a string, then this_object() is used.
*
* As a bonus, all references to destructed objects in <arr> are replaced
* by proper 0es.
*/
{
vector_t *arr;
vector_t *res;
svalue_t *arg;
svalue_t *v, *w, *x;
mp_int cnt;
arg = sp - num_arg + 1;
if (arg[0].type != T_POINTER)
{
bad_xefun_vararg(1, sp);
/* NOTREACHED */
return sp;
}
arr = arg->u.vec;
cnt = (mp_int)VEC_SIZE(arr);
if (arg[1].type == T_MAPPING)
{
/* --- Map through mapping --- */
mapping_t *m;
if (num_arg > 2) {
inter_sp = sp;
error("Too many arguments to map_array()\n");
}
m = arg[1].u.map;
res = allocate_array(cnt);
if (!res)
error("(map_array) Out of memory: array[%ld] for result\n", cnt);
push_referenced_vector(res); /* In case of errors */
for (w = arr->item, x = res->item; --cnt >= 0; w++, x++)
{
if (destructed_object_ref(w))
assign_svalue(w, &const0);
v = get_map_value(m, w);
if (v == &const0)
assign_svalue_no_free(x, w);
else
assign_svalue_no_free(x, v);
}
free_svalue(arg+1); /* the mapping */
sp = arg;
}
else
{
/* --- Map through function call --- */
callback_t cb;
int error_index;
error_index = setup_efun_callback(&cb, arg+1, num_arg-1);
if (error_index >= 0)
{
bad_xefun_vararg(error_index+2, arg);
/* NOTREACHED */
return arg;
}
inter_sp = sp = arg+1;
put_callback(sp, &cb);
num_arg = 2;
res = allocate_array(cnt);
if (!res)
error("(map_array) Out of memory: array[%ld] for result\n", cnt);
push_referenced_vector(res); /* In case of errors */
/* Loop through arr and res, mapping the values from arr */
for (w = arr->item, x = res->item; --cnt >= 0; w++, x++)
{
if (current_object->flags & O_DESTRUCTED)
continue;
if (destructed_object_ref(w))
assign_svalue(w, &const0);
if (!callback_object(&cb))
{
error("object used by map_array destructed");
}
push_svalue(w);
v = apply_callback(&cb, 1);
if (v)
{
transfer_svalue_no_free(x, v);
v->type = T_INVALID;
}
}
free_callback(&cb);
}
/* The arguments have been removed already, now just replace
* the arr on the stack with the result.
*/
free_array(arr);
arg->u.vec = res;
return arg;
} /* x_map_array () */
#ifdef F_MAP_ARRAY
svalue_t * f_map_array (svalue_t *sp, int num_arg)
{ return x_map_array(sp, num_arg); }
#endif
/*-------------------------------------------------------------------------*/
svalue_t *
f_sort_array (svalue_t * sp, int num_arg)
/* VEFUN sort_array()
*
* mixed *sort_array(mixed *arr, string wrong_order
* , object|string ob, mixed extra...)
* mixed *sort_array(mixed *arr, closure cl, mixed extra...)
*
* Create a shallow copy of array <arr> and sort that copy by the ordering
* function ob->wrong_order(a, b), or by the closure expression 'cl'.
* The sorted copy is returned as result.
*
* If the 'arr' argument equals 0, the result is also 0.
* 'ob' is the object in which the ordering function is called
* and may be given as object or by its filename.
* If <ob> is omitted, or neither an object nor a string, then
* this_object() is used.
*
* The elements from the array to be sorted are passed in pairs to
* the function 'wrong_order' as arguments, followed by any <extra>
* arguments.
*
* The function should return a positive number if the elements
* are in the wrong order. It should return 0 or a negative
* number if the elements are in the correct order.
*
* The sorting is implemented using Mergesort, which gives us a O(N*logN)
* worst case behaviour and provides a stable sort.
*/
{
vector_t *data;
svalue_t *arg;
callback_t cb;
int error_index;
mp_int step, halfstep, size;
int i, j, index1, index2, end1, end2;
svalue_t *source, *dest, *temp;
arg = sp - num_arg + 1;
if (arg[0].type != T_POINTER)
{
bad_xefun_vararg(1, sp);
/* NOTREACHED */
return sp;
}
error_index = setup_efun_callback(&cb, arg+1, num_arg-1);
if (error_index >= 0)
{
bad_xefun_vararg(error_index+2, arg);
/* NOTREACHED */
return arg;
}
inter_sp = sp = arg+1;
put_callback(sp, &cb);
num_arg = 2;
/* Get the array. Since the sort sorts in-place, we have
* to make a shallow copy of arrays with more than one
* ref.
*/
data = arg->u.vec;
check_for_destr(data);
if (data->ref != 1)
{
vector_t *vcopy;
vcopy = slice_array(data, 0, VEC_SIZE(data)-1);
free_array(data);
data = vcopy;
arg->u.vec = data;
}
size = (mp_int)VEC_SIZE(data);
/* Easiest case: nothing to sort */
if (size <= 1)
{
free_callback(&cb);
return arg;
}
/* In order to provide clean error recovery, data must always hold
* exactly one copy of each original content svalue when an error is
* possible. Thus, it would be not a good idea to use it as scrap
* space.
*/
temp = data->item;
source = alloca(size*sizeof(svalue_t));
dest = alloca(size*sizeof(svalue_t));
if (!source || !dest)
{
error("Stack overflow in sort_array()");
/* NOTREACHED */
return arg;
}
for (i = 0; i < size; i++)
source[i] = temp[i];
step = 2;
halfstep = 1;
while (halfstep<size)
{
for (i = j = 0; i < size; i += step)
{
index1 = i;
index2 = i + halfstep;
end1 = index2;
if (end1 > size)
end1 = size;
end2 = i + step;
if (end2 > size)
end2 = size;
while (index1 < end1 && index2 < end2)
{
svalue_t *d;
if (!callback_object(&cb))
error("object used by sort_array destructed");
push_svalue(source+index1);
push_svalue(source+index2);
d = apply_callback(&cb, 2);
if (d && (d->type != T_NUMBER || d->u.number > 0))
dest[j++] = source[index2++];
else
dest[j++] = source[index1++];
}
if (index1 == end1)
{
while (index2 < end2)
dest[j++] = source[index2++];
}
else
{
while (index1 < end1)
dest[j++] = source[index1++];
}
}
halfstep = step;
step += step;
temp = source;
source = dest;
dest = temp;
}
temp = data->item;
for (i = size; --i >= 0; )
temp[i] = source[i];
free_callback(&cb);
return arg;
} /* f_sort_array() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_filter_objects (svalue_t *sp, int num_arg)
/* VEFUN filter_objects()
*
* object *filter_objects (object *arr, string fun, mixed extra, ...)
*
* Filter the objects in <arr> by calling the lfun obj-><fun>(<extra>...)
* and return an array of those objects for which the lfun call yields
* non-zero.
*
* The objects can be true objects or filenames. In the latter case, the
* function tries to load the object before calling the lfun. Any non-object
* element in <arr> is ignored and thus not included in the result.
*
* As a bonus, all references to destructed objects in <arr> are replaced
* by proper 0es.
*/
{
vector_t *p; /* The <arr> argument */
char *func; /* The <fun> argument */
svalue_t *arguments; /* Beginning of 'extra' arguments on vm stack */
vector_t *w; /* Result vector */
CBool *flags = NULL; /* Flag array, one flag for each element of <p> */
int res; /* Count of objects to return */
object_t *ob; /* Object to call */
mp_int p_size; /* Size of <p> */
int cnt = 0;
svalue_t *v;
assign_eval_cost();
inter_sp = sp; /* needed for errors in allocate_array(), apply() */
/* Locate the arguments on the stack and extract them */
arguments = sp-num_arg+3;
if (arguments[-2].type != T_POINTER)
bad_xefun_vararg(1, sp);
if (arguments[-1].type != T_STRING)
bad_xefun_vararg(2, sp);
p = arguments[-2].u.vec;
func = arguments[-1].u.string;
num_arg -= 2;
p_size = (mp_int)VEC_SIZE(p);
/* Call <func> in every object, recording the result in flags.
*
* Every element in flags is associated by index number with an
* element in the vector to filter. The filter function is evaluated
* for every vector element, and the associated flag is set to 0
* or 1 according to the result.
* At the end, all 1-flagged elements are gathered and copied
* into the result vector.
*
* Checking if <func> exists as shared string takes advantage of
* the fact that every existing lfun name is stored as shared string.
* If it's not shared, no object implements it and we can skip
* the whole function call loop.
*/
res = 0;
switch(arguments[-1].x.string_type) {
default:
if ( !(func = findstring(func)) )
break;
/* FALLTHROUGH */
case STRING_SHARED:
flags = alloca((p_size+1)*sizeof(*flags));
if (!flags)
{
error("Stack overflow in filter_objects()");
/* NOTREACHED */
return NULL;
}
for (cnt = 0; cnt < p_size; cnt++) {
flags[cnt] = MY_FALSE;
v = &p->item[cnt];
/* Coerce <v> into a (non-destructed) object ob (if necessary
* by loading it). If that doesn't work, simply continue
* with the next element.
*/
if (v->type != T_OBJECT) {
if (v->type != T_STRING)
continue;
if ( !(ob = get_object(v->u.string)) )
continue;
} else {
ob = v->u.ob;
if (ob->flags & O_DESTRUCTED) {
assign_svalue(v, &const0);
continue;
}
}
/* Abort the efun if this_object is destructed (slightly
* strange place to check for it).
*/
if (current_object->flags & O_DESTRUCTED)
continue;
/* Call the filter lfun and record the result. */
push_svalue_block(num_arg, arguments);
v = sapply (func, ob, num_arg);
if ((v) && (v->type!=T_NUMBER || v->u.number) ) {
flags[cnt] = MY_TRUE;
res++;
}
} /* for() */
} /* switch() */
/* Now: cnt == p_size, res == number of 'true' flags */
/* Create the result vector and fill it with all objects for which
* true flag was recorded.
*/
w = allocate_array(res); /* might be a 0-elements array */
if (res) {
/* Walk through flags/w->item from the end, copying all
* positively flagged elements from p.
*/
v = &w->item[res];
for (;;) {
if (flags[--cnt]) {
svalue_t sv;
/* Copy the element and update the ref-count */
*--v = sv = p->item[cnt];
if (sv.type == T_STRING) {
if (sv.x.string_type == STRING_MALLOC) {
if ( !(v->u.string = string_copy(sv.u.string)) ) {
v->type = T_INVALID;
free_array(w);
error("(map_array) Out of memory (%lu bytes) "
"for string\n"
, (unsigned long)strlen(sv.u.string));
}
} else {
ref_string(sv.u.string);
}
} else {
(void)ref_object(sv.u.ob, "filter");
}
/* Loop termination check moved in here to save cycles */
if (v == w->item)
break;
}
} /* for () */
} /* if (res) */
/* Cleanup and return */
free_array(p);
do {
free_svalue(sp--);
} while(--num_arg >= 0);
put_array(sp, w);
return sp;
}
/*-------------------------------------------------------------------------*/
svalue_t *
f_map_objects (svalue_t *sp, int num_arg)
/* VEFUN map_objects()
*
* mixed *map_objects (object *arr, string fun, mixed extra, ...)
*
* Map the objects in <arr> by calling the lfun obj-><fun>(<extra>...)
* and return an array of the function call results.
*
* The objects can be true objects or filenames. In the latter case, the
* function tries to load the object before calling the lfun. Any non-object
* element in <arr> is ignored and a 0 is returned in its place.
*
* As a bonus, all references to destructed objects in <arr> are replaced
* by proper 0es.
*/
{
vector_t *p; /* The <arr> argument */
char *func; /* The <fun> argument */
svalue_t *arguments; /* Beginning of 'extra' arguments on vm stack */
vector_t *r; /* Result vector */
object_t *ob; /* Object to call */
mp_int size; /* Size of <p> */
int cnt;
svalue_t *w, *v, *x;
assign_eval_cost();
inter_sp = sp; /* In case of errors leave a clean stack behind */
arguments = sp-num_arg+3;
if (arguments[-2].type != T_POINTER)
bad_xefun_vararg(1, sp);
if (arguments[-1].type != T_STRING)
bad_xefun_vararg(2, sp);
p = arguments[-2].u.vec;
func = arguments[-1].u.string;
num_arg -= 2;
r = allocate_array(size = (mp_int)VEC_SIZE(p));
arguments[-2].u.vec = r;
push_referenced_vector(p); /* Ref it from the stack in case of errors */
/* Call <func> in every object, storing the result in r.
*
* Checking if <func> exists as shared string takes advantage of
* the fact that every existing lfun name is stored as shared string.
* If it's not shared, no object implements it and we can skip
* the whole function call loop.
*/
switch(arguments[-1].x.string_type) {
default:
if ( !(func = findstring(func)) )
break;
/* FALLTHROUGH */
case STRING_SHARED:
for (cnt = size, v = p->item, x = r->item; --cnt >= 0; v++, x++) {
/* Coerce <v> into a (non-destructed) object ob (if necessary
* by loading it). If that doesn't work, simply continue
* with the next element.
*/
if (v->type != T_OBJECT) {
if (v->type != T_STRING)
continue;
if ( !(ob = get_object(v->u.string)) )
continue;
} else {
ob = v->u.ob;
if (ob->flags & O_DESTRUCTED) {
assign_svalue(v, &const0);
continue;
}
}
/* Abort the efun if this_object is destructed (slightly
* strange place to check for it).
*/
if (current_object->flags & O_DESTRUCTED)
continue;
/* Call the lfun and record the result */
push_svalue_block(num_arg, arguments);
w = sapply (func, ob, num_arg);
if (w) {
*x = *w;
w->type = T_INVALID;
}
} /* for() */
} /* switch() */
/* Clean up and return */
do {
free_svalue(sp--);
} while(--num_arg >= 0);
free_array(p);
return sp;
} /* f_map_objects() */
/*-------------------------------------------------------------------------*/
static int
search_alist (svalue_t *key, vector_t *keylist)
/* Helper for insert_alist() and assoc().
*
* Search for <key> in the alist key vector <keylist> and return its position.
* If <key> is not found, return the position at which the <key> would
* have to be inserted (this might be sizeof(<keylist>), ie. the element
* beyond the current end).
*
* The key vector must be sorted according to alist_cmd(), else the
* binary search will return surely interesting but useless results.
*/
{
mp_int i, o, d, keynum;
if ( !(keynum = (mp_int)VEC_SIZE(keylist)) )
return 0;
/* Simple binary search */
i = keynum >> 1;
o = (i+2) >> 1;
for (;;) {
d = alist_cmp(key, &keylist->item[i]);
if (d<0) {
i -= o;
if (i<0) {
i = 0;
}
} else if (d>0) {
i += o;
if (i >= keynum) {
i = keynum-1;
}
} else {
return i;
}
if (o<=1) {
if (alist_cmp(key, &keylist->item[i]) > 0) return i+1;
return i;
}
o = (o+1) >> 1;
}
return 0;
}
/*-------------------------------------------------------------------------*/
#ifdef F_INSERT_ALIST
svalue_t *
insert_alist (svalue_t *key, svalue_t * /* TODO: bool */ key_data, vector_t *list)
/* EFUN insert_alist()
*
* The function can be used in two ways:
*
* 1. Insert/replace a (new) <key>:<keydata> tuple into the alist <list>.
* <key> and <key_data> have to point to an array of svalues. The first
* element is the key value, the following values the associated
* data values. The function will read as many elements from the
* array as necessary to fill the alist <list>.
* Result is a fresh copy of the modified alist.
*
* 2. Lookup a <key> in the alist <list> and return its index+1. The
* result is 0 if the key is not found.
* <key_data> must be NULL, <key> points to the svalue to be looked
* up, and <list> points to an alist with at least the key vector.
*
* If <list> is no alist, the result can be wrong (case 2.) or not
* an alist either (case 1.).
*
* If the <key> is a string, it is made shared.
*
* TODO: Make the hidden flag 'key_data' a real flag.
*/
{
static svalue_t stmp; /* Result value */
mp_int i,j,ix;
mp_int keynum, list_size; /* Number of keys, number of alist vectors */
int new_member; /* Flag if a new tuple is given */
/* If key is a string, make it shared */
if (key->type == T_STRING && key->x.string_type != STRING_SHARED) {
char *tmpstr;
if (last_insert_alist_shared_string)
free_string(last_insert_alist_shared_string);
tmpstr = make_shared_string(key->u.string);
if (key->x.string_type == STRING_MALLOC)
xfree(key->u.string);
put_ref_string(key, tmpstr);
last_insert_alist_shared_string = tmpstr;
}
keynum = (mp_int)VEC_SIZE(list->item[0].u.vec);
/* Locate the key */
ix = search_alist(key, list->item[0].u.vec);
/* If its just a lookup: return the result.
*/
if (key_data == 0) {
put_number(&stmp, ix);
return &stmp;
}
/* Prepare the result alist vector */
put_array(&stmp, allocate_array(list_size = (mp_int)VEC_SIZE(list)));
new_member = ix == keynum || alist_cmp(key, &list->item[0].u.vec->item[ix]);
/* Loop over all key/data vectors in <list>, insert/replace the
* new value and put the new vector into <stmp>.
*/
for (i = 0; i < list_size; i++) {
vector_t *vtmp;
if (new_member) {
svalue_t *pstmp = list->item[i].u.vec->item;
vtmp = allocate_array(keynum+1);
for (j=0; j < ix; j++) {
assign_svalue_no_free(&vtmp->item[j], pstmp++);
}
assign_svalue_no_free(&vtmp->item[ix], i ? &key_data[i] : key );
for (j = ix+1; j <= keynum; j++) {
assign_svalue_no_free(&vtmp->item[j], pstmp++);
}
} else {
vtmp = slice_array(list->item[i].u.vec, 0, keynum-1);
if (i)
assign_svalue(&vtmp->item[ix], &key_data[i]);
/* No need to assign the key value: it's already there. */
}
stmp.u.vec->item[i].type=T_POINTER;
stmp.u.vec->item[i].u.vec=vtmp;
}
/* Done */
return &stmp;
}
#endif
/*-------------------------------------------------------------------------*/
int
assoc (svalue_t *key, vector_t *list)
/* EFUN assoc(), also used for internal vector lookups.
*
* Lookup <key> in the alist key vector <list> and return its position.
* If it is not found, return -1.
*
* The key vector must be sorted according to alist_cmd(), else the
* result will be interesting, but useless.
*/
{
int i;
/* If key is a non-shared string, lookup and use the shared copy.
*/
if (key->type == T_STRING && key->x.string_type != STRING_SHARED) {
if ( !(assoc_shared_string_key.u.string = findstring(key->u.string)) )
return -1;
key = &assoc_shared_string_key;
}
i = search_alist(key, list);
if (i == (int)VEC_SIZE(list) || alist_cmp(key, &list->item[i]))
i = -1;
return i;
}
/*-------------------------------------------------------------------------*/
vector_t *
intersect_alist (vector_t *a1, vector_t *a2)
/* EFUN intersect_alist(), also used by generic array intersection.
*
* Perform a fast intersection of the alist key vectors <a1> and <a2>.
* The result is a new sorted(!) vector with all elements, which are present
* in both input vectors.
*
* TODO: Maybe rename the efun.
*/
{
vector_t *a3;
mp_int d, l, i1, i2, a1s, a2s;
a1s = (mp_int)VEC_SIZE(a1);
a2s = (mp_int)VEC_SIZE(a2);
a3 = allocate_array( a1s < a2s ? a1s : a2s);
for (i1=i2=l=0; i1 < a1s && i2 < a2s; ) {
d = alist_cmp(&a1->item[i1], &a2->item[i2]);
if (d<0)
i1++;
else if (d>0)
i2++;
else {
assign_svalue_no_free(&a3->item[l++], &a2->item[(i1++,i2++)] );
}
}
return shrink_array(a3, l);
}
/*-------------------------------------------------------------------------*/
vector_t *
intersect_array (vector_t *a1, vector_t *a2)
/* OPERATOR & (array intersection)
*
* Perform an intersection of the two vectors <a1> and <a2>.
* The result is a new vector with all elements which are present in both
* input vectors.
*
* The result vector is also sorted according to alist_cmp(), but
* don't rely on it.
*/
{
vector_t *vtmpp1, *vtmpp2, *vtmpp3;
static svalue_t ltmp = { T_POINTER };
/* Order the two ingoing lists and then perform an alist intersection.
*/
ltmp.u.vec = a1;
vtmpp1 = order_alist(<mp, 1, 1);
free_array(ltmp.u.vec);
ltmp.u.vec = a2;
vtmpp2 = order_alist(<mp, 1, 1);
free_array(ltmp.u.vec);
vtmpp3 = intersect_alist(vtmpp1->item[0].u.vec, vtmpp2->item[0].u.vec);
free_array(vtmpp1);
free_array(vtmpp2);
return vtmpp3;
}
/*-------------------------------------------------------------------------*/
vector_t *
match_regexp (vector_t *v, char *pattern)
/* EFUN regexp()
*
* Match the content of <v> against the regexp <pattern>
* Return a new vector of all strings in <v> which match the pattern.
* Evalcost is sizeof(<v>).
*/
{
struct regexp *reg; /* compiled regexp */
CBool *res; /* res[i] true -> v[i] matches */
mp_int num_match, v_size; /* Number of matches, size of <v> */
vector_t *ret; /* The result vector */
mp_int i;
/* Simple case: empty input yields empty output */
if ((v_size = (mp_int)VEC_SIZE(v)) == 0)
return allocate_array(0);
/* Compile the regexp (or take it from the cache) */
reg = REGCOMP((unsigned char *)pattern, 0, MY_FALSE);
if (reg == NULL)
return NULL;
/* Check every string in <v> if it matches and set res[]
* accordingly.
*/
res = alloca(v_size * sizeof(*res));
if (!res)
{
REGFREE(reg);
error("Stack overflow in regexp()");
/* NOTREACHED */
return NULL;
}
for (num_match = i = 0; i < v_size; i++) {
char *line;
res[i] = MY_FALSE;
if (v->item[i].type != T_STRING)
continue;
eval_cost++;
line = v->item[i].u.string;
if (hs_regexec(reg, line, line) == 0)
continue;
res[i] = MY_TRUE;
num_match++;
}
/* Create the result vector and copy the matching lines */
ret = allocate_array(num_match);
for (num_match=i=0; i < v_size; i++) {
if (!res[i])
continue;
assign_svalue_no_free(&ret->item[num_match], &v->item[i]);
num_match++;
}
REGFREE(reg);
return ret;
}
/*-------------------------------------------------------------------------*/
svalue_t *
f_transpose_array (svalue_t *sp)
/* TEFUN transpose_array()
*
* mixed *transpose_array (mixed *arr);
*
* transpose_array ( ({ ({1,2,3}), ({a,b,c}) }) )
* => ({ ({1,a}), ({2,b)}, ({3,c}) })
*
* transpose_array() applied to an alist results in an array of
* ({ key, data }) pairs, useful if you want to use sort_array()
* or filter_array() on the alist.
*
* TODO: There should be something like this for mappings.
*/
{
vector_t *v; /* Input vector */
vector_t *w; /* Result vector */
mp_int a; /* size of <v> */
mp_int b; /* size of <v>[ix] for all ix */
mp_int i, j;
int no_copy;
/* 1 if <v> has only one ref, else 0. Not just a boolean, it
* is compared with the ref counts of the subvectors of v.
*/
svalue_t *x, *y, *z;
int o;
/* Get and test the arguments */
if (sp->type != T_POINTER)
bad_xefun_arg(1, sp);
v = sp->u.vec;
if ( !(a = (mp_int)VEC_SIZE(v)) )
return sp;
/* Find the widest subarray in the main array */
b = 0;
for (x = v->item, i = a; i > 0; i--, x++)
{
mp_int c;
if (x->type != T_POINTER)
bad_xefun_arg(1, sp);
c = (mp_int)VEC_SIZE(x->u.vec);
if (c > b)
b = c;
}
/* If all subarrays are empty, just return an empty array */
if (!b)
{
sp->u.vec = ref_array(v->item->u.vec);
free_array(v);
return sp;
}
no_copy = (v->ref == 1) ? 1 : 0;
/* Allocate and initialize the result vector */
w = allocate_uninit_array(b);
for (j = b, x = w->item; --j >= 0; x++)
{
put_array(x, allocate_array(a));
}
o = offsetof(vector_t, item);
for (i = a, y = v->item; --i >= 0; o += sizeof(svalue_t), y++)
{
mp_int c;
x = w->item;
if (y->type != T_POINTER)
break;
z = y->u.vec->item;
c = b;
if (VEC_SIZE(y->u.vec) < (size_t)b
&& !(c = (mp_int)VEC_SIZE(y->u.vec)) )
continue;
if (y->u.vec->ref == no_copy)
{
/* Move the values to the result vector */
j = c;
do {
transfer_svalue_no_free(
(svalue_t *)((char*)x->u.vec+o),
z
);
x++;
z++;
} while (--j > 0);
free_empty_vector(y->u.vec);
y->type = T_INVALID;
}
else
{
/* Assign the values to the result vector */
j = c;
do {
assign_svalue_no_free(
(svalue_t *)((char*)x->u.vec+o),
z
);
x++;
z++;
} while (--j > 0);
}
}
/* Clean up and return the result */
free_array(sp->u.vec);
sp->u.vec = w;
return sp;
} /* f_transpose_array() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_regexplode (svalue_t *sp)
/* TEFUN regexplode()
*
* string *regexplode (string text, string pattern)
*
* Explode the <text> by the delimiter <pattern>, returning a vector
* of the exploded text. Every second element in the result vector
* is the text that matched the delimiter.
* Evalcost: number of matches.
*/
{
/* The found delimiter matches are kept in a list of these
* structures which are allocated on the stack.
*/
struct regexplode_match {
char *start, *end; /* Start and end of the match in text */
struct regexplode_match *next; /* Next list element */
};
char *text; /* Input text from the vm stack */
char *pattern; /* Delimiter pattern from the vm stack */
struct regexp *reg; /* Compiled pattern */
struct regexplode_match *matches; /* List of matches */
struct regexplode_match **matchp; /* Pointer to previous_match.next */
struct regexplode_match *match; /* Current match structure */
vector_t *ret; /* Result vector */
svalue_t *svp; /* Next element in ret to fill in */
int num_match; /* Number of matches */
char *str;
/* Get the efun arguments */
if (sp[-1].type != T_STRING)
bad_xefun_arg(1, sp);
if (sp->type != T_STRING)
bad_xefun_arg(2, sp);
text = sp[-1].u.string;
pattern = sp->u.string;
reg = REGCOMP((unsigned char *)pattern, 0, MY_FALSE);
if (reg == 0) {
inter_sp = sp;
error("Unrecognized search pattern");
/* NOTREACHED */
return NULL;
}
/* Loop over <text>, repeatedly matching it against the pattern,
* until all matches have been found and recorded.
*/
str = text; /* Remaining <text> to analyse */
num_match = 0;
matchp = &matches;
while (hs_regexec(reg, str, text)) {
eval_cost++;
match = (struct regexplode_match *)alloca(sizeof *match);
if (!match)
{
REGFREE(reg);
error("Stack overflow in regexplode()");
/* NOTREACHED */
return NULL;
}
match->start = reg->startp[0];
str = reg->endp[0];
match->end = str;
*matchp = match;
matchp = &match->next;
num_match++;
if (!*str || (match->start == str && !*++str) )
break;
}
*matchp = 0; /* Terminate list properly */
/* Prepare the result vector */
if (max_array_size && num_match > ((max_array_size-1) >> 1) ) {
REGFREE(reg);
inter_sp = sp;
error("Illegal array size");
/* NOTREACHED */
return NULL;
}
ret = allocate_array((num_match << 1) + 1);
/* Walk down the list of matches, extracting the
* text parts and matched delimiters, copying them
* into ret.
*/
svp = ret->item;
for (match = matches; match; match = match->next) {
mp_int len;
/* Copy the text leading up to the current delimiter match. */
len = match->start - text;
xallocate(str, (size_t)len + 1, "text before delimiter");
strncpy(str, text, (size_t)len);
str[len] = 0;
text += len;
put_malloced_string(svp, str);
svp++;
/* Copy the matched delimiter */
len = match->end - text;
xallocate(str, (size_t)len + 1, "matched delimiter");
strncpy(str, text, (size_t)len);
str[len] = 0;
text += len;
put_malloced_string(svp, str);
svp++;
}
/* Copy the remaining text (maybe the empty string) */
put_malloced_string(svp, string_copy(text));
/* Cleanup */
REGFREE(reg);
free_string_svalue(sp);
sp--;
free_string_svalue(sp);
/* Return the result */
put_array(sp, ret);
return sp;
} /* f_regexplode() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_include_list (svalue_t *sp, int num_arg)
/* EFUN include_list()
*
* string* include_list ()
* string* include_list (object ob)
* string* include_list (object ob, int flags)
*
* Return a list with the names of all files included by the program
* of object <ob>, including <ob>'s program file itself.
*/
{
Mempool pool; /* The memory pool to allocate from */
object_t *ob; /* Analyzed object */
vector_t *vec; /* Result vector */
int count; /* Total number of includes */
svalue_t *argp; /* Arguments */
include_t *includes; /* Pointer to the include information */
p_int flags;
/* Get the arguments */
argp = sp - num_arg + 1;
if (num_arg >= 1)
{
if (argp->type != T_OBJECT)
bad_xefun_vararg(1, sp);
ob = argp[0].u.ob;
}
else
ob = current_object;
if (num_arg >= 2)
{
if (argp[1].type != T_NUMBER)
bad_xefun_vararg(2, sp);
flags = argp[1].u.number;
}
else
flags = 0;
if (O_PROG_SWAPPED(ob))
if (load_ob_from_swap(ob) < 0)
{
error("Out of memory: unswap object '%s'\n", ob->name);
/* NOTREACHED */
return NULL;
}
/* Create the result.
* Depending on the flags value, this can be a flat list or a tree.
*/
if (!(flags & INCLIST_TREE))
{
svalue_t *svp;
/* Get the result array */
vec = allocate_array((ob->prog->num_includes+1) * 3);
svp = vec->item;
/* Walk the includes information and copy it into the result vector
*/
for ( svp = vec->item+3
, count = ob->prog->num_includes
, includes = ob->prog->includes
; count > 0
; count--, includes++, svp += 3
)
{
int depth;
put_ref_string(svp, includes->name);
put_ref_string(svp+1, includes->filename);
depth = includes->depth;
if (depth > 0)
put_number(svp+2, depth);
else
put_number(svp+2, -depth);
}
}
else /* Tree-type result */
{
/* Local structure to hold the found programs */
struct iinfo {
struct iinfo * next; /* Next structure in flat list */
int depth; /* Include depth */
include_t * inc; /* The include information */
/* The following members are used to recreate the inherit tree */
int count; /* Number of direct includes */
struct iinfo * parent; /* Parent include, or NULL */
struct iinfo * child; /* First child include */
struct iinfo * sibling; /* Next include on same level */
/* These members are used to create the result tree */
size_t index; /* # of this include file in the parent
* vector */
vector_t * vec; /* Result vector for this include */
} *begin, *end; /* Flat list of all found includes */
struct iinfo * last; /* Last include found on this depth */
struct iinfo * next; /* Next include to work */
/* Get the memory pool */
pool = new_mempool(sizeof(*begin) * 64);
if (NULL == pool)
{
error("Out of memory: memory pool\n");
/* NOTREACHED */
return NULL;
}
/* Walk the list of included files and build the tree from it.
*/
begin = mempool_alloc(pool, sizeof(*begin));
if (NULL == begin)
{
mempool_delete(pool);
outofmem(sizeof(*begin), "allocation from mempool");
}
/* Root node for the object's program itself */
begin->next = NULL;
begin->child = NULL;
begin->sibling = NULL;
begin->inc = NULL;
begin->depth = 0;
begin->count = 0;
begin->parent = NULL;
begin->vec = NULL;
begin->index = 0;
end = begin;
last = begin;
includes = ob->prog->includes;
count = ob->prog->num_includes;
for ( ; count > 0; count--, includes++)
{
/* Get new node and put it into the flat list */
end->next = mempool_alloc(pool, sizeof(*end));
if (NULL == end->next)
{
mempool_delete(pool);
outofmem(sizeof(*end), "allocation from mempool");
}
end = end->next;
end->next = NULL;
end->inc = includes;
end->depth = includes->depth > 0 ? includes->depth : - includes->depth;
/* Handle the tree-based information */
end->child = NULL;
end->sibling = NULL;
if (last->depth > end->depth)
{
/* We reached a leaf with <last> - this new was included from
* some parent above.
*/
while (last->depth > end->depth)
last = last->parent;
/* Got back up to the right sibling level, no go to the end
* of the sibling list (just in case - we should already
* be there).
*/
while (last->sibling)
last = last->sibling;
}
/* Now the new file is either a sibling or a child of <last> */
if (last->depth == end->depth)
{
/* Sibling to <last> */
last->sibling = end;
end->parent = last->parent;
last = end;
end->parent->count++;
}
else /* last->depth < end->depth */
{
/* Included from <last> */
last->child = end;
last->count++;
end->parent = last;
last = end;
}
/* Init the rest */
end->count = 0;
end->index = end->parent->count;
end->vec = NULL;
}
/* Get the top result array and keep a reference to it on the
* stack so that it will be deallocated on an error.
*/
vec = allocate_array((begin->count+1) * 3);
begin->vec = vec;
sp++; put_array(sp, vec); inter_sp = sp;
/* Loop through all the include infos and copy them into
* their result vector. We create the subvectors when
* we encounter them.
* Invariant: <next> points to the next iinfo to work.
*/
for (next = begin->child; next != NULL; )
{
/* If this child has no includes, we just copy the
* name into its proper place in the parent vector.
*
* Otherwise we create a vector for this include
* and store the names in there.
*/
if (next->child == NULL)
{
svalue_t *svp;
svp = &next->parent->vec->item[next->index*3];
put_ref_string(svp, next->inc->name);
put_ref_string(svp+1, next->inc->filename);
put_number(svp+2, next->depth);
/* If we are in the last sibling, roll back up to
* the parents.
*/
while (next->sibling == NULL && next->parent != NULL)
next = next->parent;
/* Advance to the next sibling. If by */
next = next->sibling;
}
else
{
svalue_t *svp;
next->vec = allocate_array((next->count+1)*3);
svp = &next->parent->vec->item[next->index*3];
put_array(svp, next->vec);
/* svp[1] and svp[2] are already 0 */
svp = next->vec->item;
put_ref_string(svp, next->inc->name);
put_ref_string(svp+1, next->inc->filename);
put_number(svp+2, next->depth);
/* Descend into the first child */
next = next->child;
}
}
mempool_delete(pool);
sp--; /* Remove the temporary storage of vec on the stack */
}
/* Copy the information about the program file itself. */
{
char *str;
size_t slen; /* Also used for error reporting */
slen = strlen(ob->prog->name);
if (compat_mode)
str = string_copy(ob->prog->name);
else
str = add_slash(ob->prog->name);
if (!str)
{
free_array(vec);
error("(include_list) Out of memory: (%lu bytes) for filename\n"
, (unsigned long)slen);
}
put_malloced_string(vec->item, str);
/* vec->item[1] and vec->item[2] are already 0 */
}
/* Done */
sp = pop_n_elems(num_arg, sp);
sp++;
put_array(sp, vec);
return sp;
} /* f_include_list() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_inherit_list (svalue_t *sp, int num_arg)
/* EFUN inherit_list()
*
* string* inherit_list ()
* string* inherit_list (object ob)
* string* inherit_list (object ob, int flags)
*
* Return a list with the filenames of all programs inherited by <ob>, include
* <ob>'s program itself.
*/
{
/* Local structure to hold the found programs */
struct iinfo {
struct iinfo * next; /* Next structure in flat list */
SBool virtual; /* TRUE: Virtual inherit */
program_t * prog; /* Program found */
/* The following members are used to recreate the inherit tree */
int count; /* Number of direct inherits */
struct iinfo * parent; /* Parent program, or NULL */
/* These members are used to create the result tree */
size_t index; /* # of this inherited program */
vector_t * vec; /* Result vector for this program */
} *begin, *end; /* Flat list of all found inherits */
struct iinfo * next; /* Next program to analyze */
Mempool pool; /* The memory pool to allocate from */
object_t *ob; /* Analyzed object */
vector_t *vec; /* Result vector */
svalue_t *svp; /* Pointer to next vec entry to fill in */
int count; /* Total number of inherits found */
svalue_t *argp; /* Arguments */
p_int flags;
/* Get the arguments */
argp = sp - num_arg + 1;
if (num_arg >= 1)
{
if (argp->type != T_OBJECT)
bad_xefun_vararg(1, sp);
ob = argp[0].u.ob;
}
else
ob = current_object;
if (num_arg >= 2)
{
if (argp[1].type != T_NUMBER)
bad_xefun_vararg(2, sp);
flags = argp[1].u.number;
}
else
flags = 0;
if (O_PROG_SWAPPED(ob))
if (load_ob_from_swap(ob) < 0) {
error("Out of memory: unswap object '%s'\n", ob->name);
/* NOTREACHED */
return NULL;
}
/* Get the memory pool */
pool = new_mempool(sizeof(*begin) * 64);
if (NULL == pool)
{
error("Out of memory: memory pool\n");
/* NOTREACHED */
return NULL;
}
/* Perform a breadth search on ob's inherit tree and append the
* found programs to the iinfo list while counting them.
*/
begin = mempool_alloc(pool, sizeof(*begin));
if (NULL == begin)
{
mempool_delete(pool);
error("Out of memory: allocation from memory pool\n");
/* NOTREACHED */
return NULL;
}
begin->next = NULL;
begin->prog = ob->prog;
begin->virtual = MY_FALSE;
begin->count = 0;
begin->parent = NULL;
begin->vec = NULL;
begin->index = 0;
end = begin;
count = 1;
for (next = begin; next != NULL; next = next->next)
{
int cnt;
inherit_t *inheritp;
cnt = next->prog->num_inherited;
/* Store the inherited programs in the list.
*/
for (inheritp = &next->prog->inherit[0]; cnt--; inheritp++)
{
if (inheritp->inherit_type == INHERIT_TYPE_NORMAL
|| inheritp->inherit_type == INHERIT_TYPE_VIRTUAL
)
{
count++;
next->count++;
end->next = mempool_alloc(pool, sizeof(*end));
if (NULL == end->next)
{
mempool_delete(pool);
error("Out of memory: allocation from memory pool\n");
/* NOTREACHED */
return NULL;
}
end = end->next;
end->next = NULL;
end->prog = inheritp->prog;
end->virtual = inheritp->inherit_type == INHERIT_TYPE_VIRTUAL;
/* Handle the tree-based information */
end->parent = next;
end->count = 0;
end->index = next->count;
end->vec = NULL;
}
}
}
/* Create the result.
* Depending on the flags value, this can be a flat list or a tree.
*/
if (!(flags & INHLIST_TREE))
{
/* Get the result array */
vec = allocate_array(count);
/* Take the filenames of the programs and copy them into
* the result vector.
*/
for (svp = vec->item, next = begin; next != NULL; svp++, next = next->next)
{
char *str;
size_t slen; /* Also used for error reporting */
slen = strlen(next->prog->name);
if (compat_mode)
str = string_copy(next->prog->name);
else
str = add_slash(next->prog->name);
if (str && (flags & INHLIST_TAG_VIRTUAL))
{
char * str2;
slen = strlen(str) + 3;
str2 = xalloc(slen);
if (str2)
{
if (next->virtual)
strcpy(str2, "v ");
else
strcpy(str2, " ");
strcpy(str2+2, str);
}
xfree(str);
str = str2;
}
if (!str)
{
free_array(vec);
mempool_delete(pool);
error("(inherit_list) Out of memory: (%lu bytes) for filename\n"
, (unsigned long)slen);
}
put_malloced_string(svp, str);
}
}
else
{
/* Get the top result array and keep a reference to it on the
* stack so that it will be deallocated on an error.
*/
vec = allocate_array(begin->count+1);
begin->vec = vec;
sp++; put_array(sp, vec); inter_sp = sp;
/* Loop through all filenames and copy them into their result
* vector. Since the list in breadth-order, we can create the
* sub-vectors when we encounter them.
*/
for (next = begin; next != NULL; next = next->next)
{
char *str;
size_t slen; /* Also used for error reporting */
slen = strlen(next->prog->name);
if (compat_mode)
str = string_copy(next->prog->name);
else
str = add_slash(next->prog->name);
if (str && (flags & INHLIST_TAG_VIRTUAL))
{
char * str2;
slen = strlen(str) + 3;
str2 = xalloc(slen);
if (str2)
{
if (next->virtual)
strcpy(str2, "v ");
else
strcpy(str2, " ");
strcpy(str2+2, str);
}
xfree(str);
str = str2;
}
if (!str)
{
free_array(vec);
mempool_delete(pool);
error("(inherit_list) Out of memory: (%lu bytes) for filename\n"
, (unsigned long)slen);
}
/* If this child has no inherits, we just copy the
* name into its proper place in the parent vector.
* Same for the name of the top program.
*
* Otherwise we create a vector for this program
* and store the name in there.
*/
if (begin == next)
{
put_malloced_string(next->vec->item, str);
}
else if (next->count == 0)
{
put_malloced_string(&next->parent->vec->item[next->index], str);
}
else
{
next->vec = allocate_array(next->count+1);
put_array(&next->parent->vec->item[next->index], next->vec);
put_malloced_string(next->vec->item, str);
}
}
sp--; /* Remove the temporary storage of vec on the stack */
}
mempool_delete(pool);
sp = pop_n_elems(num_arg, sp);
sp++;
put_array(sp, vec);
return sp;
} /* f_inherit_list() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_functionlist (svalue_t *sp)
/* TEFUN functionlist()
*
* mixed *functionlist (object ob, int flags = RETURN_FUNCTION_NAME)
*
* Return an array with information about <ob>s lfunctions. For every
* function, 1 to 4 values (depending on <flags>) are stored in
* the result array conveying in this order:
* - the name of the function
* - the function flags (see below)
* - the return type (listed in mudlib/sys/lpctypes.h)
* - the number of accepted argumens
*
* <ob> may be given as true object or as a filename. In the latter
* case, the efun does not try to load the object before proceeding.
*
* <flags> determines both which information is returned for every
* function, and which functions should be considered at all.
* Its value is created by bin-or'ing together following flags from
* mudlib/sys/functionlist.h:
*
* Control of returned information:
* RETURN_FUNCTION_NAME include the function name
* RETURN_FUNCTION_FLAGS include the function flags
* RETURN_FUNCTION_TYPE include the return type
* RETURN_FUNCTION_NUMARG include the number of arguments.
*
* The name RETURN_FUNCTION_ARGTYPE is defined but not implemented.
*
* Control of listed functions:
* NAME_INHERITED don't list if defined by inheritance
* TYPE_MOD_STATIC don't list if static function
* TYPE_MOD_PRIVATE don't list if private
* TYPE_MOD_PROTECTED don't list if protected
* NAME_HIDDEN don't list if not visible through inheritance
*
* The 'flags' information consists of the bin-or of the list control
* flags given above, plus the following:
*
* TYPE_MOD_VARARGS function takes varargs
* NAME_UNDEFINED function not defined yet, but referenced.
* NAME_CROSS_DEFINED function is defined to be in a different program
* TYPE_MOD_NO_MASK function is nomask
* TYPE_MOD_PUBLIC function is public
*
* All these flags are defined in mudlib/sys/functionlist.h, which
* should be copied into an accessible place in the mudlib. The
* return types are defined in mudlib/sys/lpctypes.h which also
* should be copied into the mudlib.
*
* TODO: All these defs are in mudlib/sys/functionlist.h and mudlib/sys/lpctypes.h
* TODO:: as well as in exec.h and this file. This should be centralized.
* TODO:: Maybe write the files on mud startup?
* TODO:: Include mudlib/sys/functionlist.h doesn't help because then
* TODO:: mkdepend stumbles over the embedded include <sys/lpctypes.h>.
*/
{
#define RETURN_FUNCTION_NAME 0x01
#define RETURN_FUNCTION_FLAGS 0x02
#define RETURN_FUNCTION_TYPE 0x04
#define RETURN_FUNCTION_NUMARG 0x08
#define RETURN_FUNCTION_MASK 0x0f /* union of all RETURN_FUNCTION_ defs */
#define RETURN_FUNCTION_ARGTYPE 0x10 /* not implemented */
object_t *ob; /* <ob> argument to list */
mp_int mode_flags; /* <flags> argument */
program_t *prog; /* <ob>'s program */
unsigned short num_functions; /* Number of functions to list */
char *vis_tags;
/* Bitflag array describing the visibility of every function in prog
* in relation to the passed <flags>: */
#define VISTAG_INVIS '\0' /* Function should not be listed */
#define VISTAG_VIS '\1' /* Function matches the <flags> list criterium */
#define VISTAG_ALL '\2' /* Function should be listed, no list restrictions */
#define VISTAG_NAMED '\4' /* Function is neither hidden nor private */
vector_t *list; /* Result vector */
svalue_t *svp; /* Last element in list which was filled in. */
uint32 *fun; /* Current function under examination */
uint32 active_flags; /* A functions definition status flags */
program_t *defprog; /* Program which actually defines *fun */
uint32 flags;
unsigned short *ixp;
long i, j;
inter_sp = sp; /* In case of errors leave a clean stack */
/* Extract the arguments from the vm stack.
*/
if (sp[-1].type != T_OBJECT)
{
if (sp[-1].type != T_STRING)
bad_xefun_arg(1, sp);
if (!(ob = find_object(sp[-1].u.string)))
error("Object '%s' not found.\n", sp[-1].u.string);
}
else
ob = sp[-1].u.ob;
if (sp->type != T_NUMBER)
bad_xefun_arg(2, sp);
mode_flags = sp->u.number;
if (O_PROG_SWAPPED(ob))
if (load_ob_from_swap(ob) < 0)
{
error("Out of memory: unswap object '%s'\n", ob->name);
/* NOTREACHED */
return NULL;
}
prog = ob->prog;
/* Initialize the vistag[] flag array.
*/
num_functions = prog->num_functions;
vis_tags = alloca(num_functions);
if (!vis_tags)
{
error("Stack overflow in functionlist()");
/* NOTREACHED */
return NULL;
}
/* Preset the visibility. By default, if there is any listing
* modifier, the functions are not visible. If there is none, the functions
* are visible.
*/
memset(
vis_tags,
mode_flags &
(NAME_HIDDEN|TYPE_MOD_PRIVATE|TYPE_MOD_STATIC|TYPE_MOD_PROTECTED|
NAME_INHERITED) ?
VISTAG_INVIS :
VISTAG_ALL ,
num_functions
);
/* Count how many named functions need to be listed in the result.
* Flag every function to list in vistag[].
*/
num_functions = 0;
/* First, check all functions for which we have a name */
flags = mode_flags &
(TYPE_MOD_PRIVATE|TYPE_MOD_STATIC|TYPE_MOD_PROTECTED|NAME_INHERITED);
fun = prog->functions;
j = prog->num_function_names;
for (ixp = prog->function_names + j; --j >= 0; ) {
i = *--ixp;
if (!(fun[i] & flags) )
{
vis_tags[i] = VISTAG_NAMED|VISTAG_VIS;
num_functions++;
}
else
{
vis_tags[i] |= VISTAG_NAMED;
}
}
/* If the user wants to see the hidden or private functions, we loop
* through the full function table and check all functions not yet
* touched by the previous 'named' scan.
*/
if ((mode_flags & (TYPE_MOD_PRIVATE|NAME_HIDDEN)) == 0)
{
fun = prog->functions;
for (i = prog->num_functions; --i >= 0; )
{
if (!(vis_tags[i] & VISTAG_NAMED)
&& !(fun[i] & flags)
)
{
vis_tags[i] = VISTAG_VIS;
num_functions++;
}
}
}
/* If <flags> accepts all functions, use the total number of functions
* instead of the count computed above.
* TODO: Due to the dedicated 'find hidden name' loop, this shouldn't
* TODO:: be necessary, nor the VISTAG_ALL at all.
*/
if ( !(mode_flags &
(NAME_HIDDEN|TYPE_MOD_PRIVATE|TYPE_MOD_STATIC|TYPE_MOD_PROTECTED|
NAME_INHERITED) ) )
{
num_functions = prog->num_functions;
}
/* Compute the size of the result vector to
* 2**(number of RETURN_FUNCTION_ bits set)
*/
for (i = mode_flags & RETURN_FUNCTION_MASK, j = 0; i; i >>= 1) {
if (i & 1)
j += num_functions;
}
/* Allocate the result vector and set svp to its end
*/
list = allocate_array(j);
svp = list->item + j;
/* Loop backwards through all functions, check their flags if
* they are to be listed and store the requested data in
* the result vector.
*/
for(i = prog->num_functions, fun += i; --i >= 0; ) {
fun_hdr_p funstart; /* Pointer to function in the executable */
fun--;
if ((vis_tags[i] & (VISTAG_ALL|VISTAG_VIS)) == VISTAG_INVIS)
continue; /* Don't list this one */
flags = *fun;
active_flags = (flags & ~INHERIT_MASK);
if (!(vis_tags[i] & VISTAG_NAMED))
active_flags |= NAME_HIDDEN;
defprog = prog;
/* If its a cross-defined function, get the flags from
* real definition and let j point to it.
*/
if ( !~(flags | ~(NAME_INHERITED|NAME_CROSS_DEFINED) ) ) {
active_flags |= NAME_CROSS_DEFINED;
j = (long)CROSSDEF_NAME_OFFSET(flags);
flags = fun[j];
j += i;
} else {
j = i;
}
/* If the function is inherited, find the original definition.
*/
while (flags & NAME_INHERITED) {
inherit_t *ip = &defprog->inherit[flags & INHERIT_MASK];
defprog = ip->prog;
j -= ip->function_index_offset;
flags = defprog->functions[j];
}
/* defprog now points to the program which really defines
* the function fun.
*/
funstart = defprog->program + (flags & FUNSTART_MASK);
/* Add the data to the result vector as <flags> determines.
*/
if (mode_flags & RETURN_FUNCTION_NUMARG) {
svp--;
svp->u.number = FUNCTION_NUM_ARGS(funstart) & 0x7f;
}
if (mode_flags & RETURN_FUNCTION_TYPE) {
svp--;
svp->u.number = FUNCTION_TYPE(funstart); /* return type */
}
if (mode_flags & RETURN_FUNCTION_FLAGS) {
/* If the function starts with the bytecodes F_ESCAPE F_UNDEF,
* it referenced but undefined. But you know that.
*/
if (FUNCTION_CODE(funstart)[0] == F_ESCAPE
&& FUNCTION_CODE(funstart)[1] == F_UNDEF-0x100)
{
active_flags |= NAME_UNDEFINED;
}
svp--;
svp->u.number = (p_int)active_flags;
}
if (mode_flags & RETURN_FUNCTION_NAME) {
svp--;
svp->type = T_STRING;
svp->x.string_type = STRING_SHARED;
memcpy( &svp->u.string, FUNCTION_NAMEP(funstart)
, sizeof svp->u.string);
ref_string(svp->u.string);
}
} /* for() */
/* Cleanup and return */
free_svalue(sp);
sp--;
free_svalue(sp);
put_array(sp, list);
return sp;
#undef VISTAG_INVIS
#undef VISTAG_VIS
#undef VISTAG_ALL
#undef RETURN_FUNCTION_NAME
#undef RETURN_FUNCTION_FLAGS
#undef RETURN_FUNCTION_TYPE
#undef RETURN_FUNCTION_NUMARG
#undef RETURN_FUNCTION_ARGTYPE
#undef RETURN_FUNCTION_MASK
}
/*=========================================================================*/
/* EFUN unique_array()
*
* mixed *unique_array (object *obarr, string seperator, mixed skip = 0)
*
* Group all those objects from <obarr> together for which the
* <separator> function (which is called in every object) returns the
* same value. Objects for which the function returns the <skip> value
* and all non-object elements are omitted fully from the result.
*
* The returned array is an array of arrays of objects in the form:
*
* ({ ({ Same1:1, Same1:2, ... Same1:N }),
* ({ Same2:1, Same2:2, ... Same2:N }),
* ....
* ({ SameM:1, SameM:2, ... SameM:N })
* })
*
* The result of <separator>() (the 'marker value') must be a number,
* a string, an object or an array.
*
* Basic purpose of this efun is to speed up the preparation of an
* inventory description - e.g. it allows to to fold all objects with
* identical descriptions into one textline.
*
* Other applications are possible, for example:
*
* mixed *arr;
* arr=unique_array(users(), "_query_level", -1);
*
* This will return an array of arrays holding all user objects
* grouped together by their user levels. Wizards have a user
* level of -1 so they will not appear in the the returned array.
*
* TODO: Expand unique_array(), e.g. by taking a closure as function
* TODO:: or provide a simulation.
* TODO: Allow unique_array() to tag the returned groups with the
* TODO:: value returned by the separator().
* TODO: unique_array() is almost big enough for a file on its own.
*/
/*-------------------------------------------------------------------------*/
/* The function builds a comb of unique structures: every tooth lists
* all objects with the same marker value, with the first structure
* of every tooth linked together to form the spine:
*
* -> Marker1:1 -> Marker1:2 -> ...
* |
* V
* Marker2:1 -> Marker2:2 -> ...
* |
* V
* ...
*/
struct unique
{
int count; /* Number of structures in this tooth */
svalue_t *val; /* The object itself */
svalue_t mark; /* The marker value for this object */
struct unique *same; /* Next structure in this tooth */
struct unique *next; /* Next tooth head */
};
/*-------------------------------------------------------------------------*/
static int
sameval (svalue_t *arg1, svalue_t *arg2)
/* Return true if <arg1> is identical to <arg2>.
* For arrays, this function only compares if <arg1> and <arg2> refer
* to the same array, not the values.
*/
{
if (!arg1 || !arg2) return 0;
if (arg1->type == T_NUMBER && arg2->type == T_NUMBER) {
return arg1->u.number == arg2->u.number;
} else if (arg1->type == T_POINTER && arg2->type == T_POINTER) {
return arg1->u.vec == arg2->u.vec;
} else if (arg1->type == T_STRING && arg2->type == T_STRING) {
return !strcmp(arg1->u.string, arg2->u.string);
} else if (arg1->type == T_OBJECT && arg2->type == T_OBJECT) {
return arg1->u.ob == arg2->u.ob;
} else
return 0;
}
/*-------------------------------------------------------------------------*/
static int
put_in (Mempool pool, struct unique **ulist
, svalue_t *marker, svalue_t *elem)
/* Insert the object <elem> according to its <marker> value into the comb
* of unique structures. <ulist> points to the root pointer of this comb.
* Return the (new) number of distinct markers.
*/
{
struct unique *llink, *slink, *tlink;
int cnt; /* Number of distinct markers */
Bool fixed; /* True: <elem> was inserted */
llink = *ulist;
cnt = 0;
fixed = 0;
/* Loop through the comb's top level, counting the distinct marker
* and searching for the right teeth to insert <elem> into.
*/
while (llink) {
if (!fixed && sameval(marker, &(llink->mark))) {
/* Insert the new <elem> here
*/
for (tlink = llink; tlink->same; tlink = tlink->same) tlink->count++;
tlink->count++;
/* TODO: Is the above really necessary?
* slink = new unique; llink->same = slink; llink->count++;
* should be sufficient.
*/
slink = mempool_alloc(pool, sizeof(struct unique));
if (!slink)
{
error("(unique_array) Out of memory (%lu bytes pooled) "
"for comb.\n", (unsigned long)sizeof(struct unique));
/* NOTREACHED */
return 0;
}
slink->count = 1;
assign_svalue_no_free(&slink->mark,marker);
slink->val = elem;
slink->same = NULL;
slink->next = NULL;
tlink->same = slink;
fixed = 1; /* ...just continue to count now */
/* TODO: Do not recount the comb size all the time! */
}
llink=llink->next;
cnt++;
}
if (fixed)
return cnt;
/* It's a really new marker -> start a new tooth in the comb.
*/
llink = mempool_alloc(pool, sizeof(struct unique));
if (!llink)
{
error("(unique_array) Out of memory (%lu bytes pooled) "
"for comb.\n", (unsigned long)sizeof(struct unique));
/* NOTREACHED */
return 0;
}
llink->count = 1;
assign_svalue_no_free(&llink->mark,marker);
llink->val = elem;
llink->same = NULL;
llink->next = *ulist;
*ulist = llink;
return cnt+1;
}
/*-------------------------------------------------------------------------*/
vector_t *
make_unique (vector_t *arr, char *func, svalue_t *skipnum)
/* EFUN unique_array()
*
* See above for the commentary :-)
*
* The caller made sure that <arr> contains no destructed objects.
*/
{
Mempool pool; /* Pool for the unique structures */
svalue_t *v;
vector_t *ret; /* Result vector */
vector_t *res; /* Current sub vector in ret */
struct unique *head; /* Head of the unique comb */
struct unique *nxt;
mp_int arr_size; /* Size of the incoming <arr>ay */
mp_int ant; /* Number of distinct markers */
mp_int cnt, cnt2;
head = NULL;
arr_size = (mp_int)VEC_SIZE(arr);
/* Special case: unifying an empty array */
if (!arr_size)
return allocate_array(0);
/* Get the memory for the arr_size unique-structures we're going
* to need.
* TODO: Implement an automatic memory-cleanup in case of errors,
* TODO:: e.g. by adding a dedicated structure on the runtime stack.
*/
pool = new_mempool(arr_size * sizeof(*head));
if (!pool)
error("(unique_array) Out of memory: (%lu bytes) for mempool\n"
, arr_size * sizeof(*head));
ref_array(arr); /* Prevent apply from freeing this */
/* Build the comb structure.
*/
ant = 0;
for (cnt = 0; cnt < arr_size; cnt++)
if (arr->item[cnt].type == T_OBJECT) {
v = apply(func,arr->item[cnt].u.ob, 0);
if (v && !sameval(v, skipnum))
ant = put_in(pool, &head, v, &(arr->item[cnt]));
}
deref_array(arr); /* Undo the protection from above */
ret = allocate_array(ant);
/* Copy the objects from the comb structure into the result vector,
* deallocating the structure by this.
* The elements are stored in reverse to compensate put_in(),
* but TODO: does someone really care?
*/
for (cnt = ant-1; cnt >= 0; cnt--) {
res = allocate_array(head->count);
put_array(ret->item+cnt, res);
nxt = head;
head = head->next;
cnt2 = 0;
while (nxt) {
assign_svalue_no_free (&res->item[cnt2++], nxt->val);
free_svalue(&nxt->mark);
nxt = nxt->same;
}
if (!head)
break; /* It shouldn't but, to avoid skydive just in case */
}
mempool_delete(pool);
return ret;
} /* make_unique() */
/***************************************************************************/