/*--------------------------------------------------------------------------- * 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 errorf()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: * * VEC_SIZE(v): Return the number of elements in v. * * VEC_HEAD(size): Expand to the initializers of a vector with * 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 with 1(2) * elements of type (and ). Both elements are * initialised to 0, and the actual vector can be accessed * as '.v'. * * This module contains both low-level and efun-level functions. * The latter are collected in the lower half of the source. *--------------------------------------------------------------------------- */ #include "driver.h" #include "typedefs.h" #include "my-alloca.h" #include #include "array.h" #include "backend.h" #include "closure.h" /* closure_cmp(), closure_eq() */ #include "interpret.h" #include "main.h" #include "mapping.h" #include "mempools.h" #include "mstrings.h" #include "object.h" #include "stdstrings.h" #include "simulate.h" #include "svalue.h" #include "swap.h" #include "wiz_list.h" #include "xalloc.h" #include "i-svalue_cmp.h" /*-------------------------------------------------------------------------*/ #define ALLOC_VECTOR(nelem) \ ((size_t)nelem >= (SSIZE_MAX - sizeof(vector_t)) / sizeof(svalue_t)) \ ? NULL \ : (vector_t *)xalloc_pass(sizeof(vector_t) + \ sizeof(svalue_t) * (nelem - 1)) /* ALLOC_VECTOR(size,file,line): Allocate dynamically the memory for * a vector of elements. * TODO: Use SIZET_MAX instead of SSIZE_MAX, see port.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) (const char *, ...) = errorf; /* from simulate.c */ /* This handler is called if an allocation fails. * Usually it points to simulate::errorf(), but the swapper * replaces it temporarily with its own dummy handler when * swapping in an object. */ /*-------------------------------------------------------------------------*/ vector_t * _allocate_array(mp_int n MTRACE_DECL) /* Allocate an array for 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 errorf() 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 and <...line> */ { mp_int i; vector_t *p; svalue_t *svp; if (n < 0 || (max_array_size && (size_t)n > max_array_size)) errorf("Illegal array size: %"PRIdMPINT".\n", n); if (n == 0) { p = ref_array(&null_vector); return p; } num_arrays++; p = ALLOC_VECTOR(n); if (!p) { #ifndef MALLOC_TRACE (*allocate_array_error_handler) ("Out of memory: array[%"PRIdMPINT"]\n", n); #else (*allocate_array_error_handler) ("(%s:%d) Out of memory: array[%"PRIdMPINT"]\n" MTRACE_PASS, 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; } /*-------------------------------------------------------------------------*/ vector_t * _allocate_array_unlimited(mp_int n MTRACE_DECL) /* Allocate an array for elements and return the pointer. * The elements are initialised to the svalue 0. * * If the allocations fails (and errorf() 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 and <...line> */ { mp_int i; vector_t *p; svalue_t *svp; if (n < 0) errorf("Illegal array size: %"PRIdMPINT".\n", n); if (n == 0) { p = ref_array(&null_vector); return p; } num_arrays++; p = ALLOC_VECTOR(n); if (!p) { #ifndef MALLOC_TRACE (*allocate_array_error_handler) ("Out of memory: unlimited array[%"PRIdMPINT"]\n", n); #else (*allocate_array_error_handler) ("(%s:%d) Out of memory: unlimited array[%"PRIdMPINT"]\n" MTRACE_PASS, 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; } /*-------------------------------------------------------------------------*/ vector_t * _allocate_uninit_array (mp_int n MTRACE_DECL) /* Allocate an array for elements (but no more than the current * maximum) and return the pointer. * The elements are not initialised. * If the allocations fails (and errorf() 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 and <...line> */ { vector_t *p; if (n < 0 || (max_array_size && (size_t)n > max_array_size)) errorf("Illegal array size: %"PRIdMPINT".\n", n); if (n == 0) { p = ref_array(&null_vector); return p; } num_arrays++; p = ALLOC_VECTOR(n); if (!p) { #ifndef MALLOC_TRACE (*allocate_array_error_handler) ("Out of memory: uninited array[%"PRIdMPINT"]\n", n); #else (*allocate_array_error_handler) ("(%s:%d) Out of memory: uninited array[%"PRIdMPINT"]\n" MTRACE_PASS, 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

, 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 %"PRIdPINT" 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

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 INLINE vector_t * i_shrink_array (vector_t *p, mp_int n) /* Create and return a new array containing just the first elements * of

.

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; } vector_t * shrink_array (vector_t *p, mp_int n) { return i_shrink_array(p, n); } #define shrink_array(p,n) i_shrink_array(p,n) /*-------------------------------------------------------------------------*/ void set_vector_user (vector_t *p, object_t *owner) /* Wizlist statistics: take vector

from its former owner and account it * under its new . */ { 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 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 to the statistics. */ { num_arrays++; vec->user->size_array += VEC_SIZE(vec); } /* count_array_size(void) */ #endif /* GC_SUPPORT */ /*-------------------------------------------------------------------------*/ vector_t * explode_string (string_t *str, string_t *del) /* Explode the string by delimiter string 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 assumed. * 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, left; vector_t *ret; string_t *buff; len = (long)mstrsize(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)mstrsize(str); ret = allocate_array(len); for ( svp = ret->item, p = get_txt(str) ; --len >= 0 ; svp++, p++ ) { buff = new_n_mstring(p, 1); if (!buff) { free_array(ret); outofmem(1, "explode() on a string"); } put_string(svp, buff); } return ret; } /* Delimiter is one-char string: speedy implementation which uses * direct character comparisons instead of calls to memcmp(). */ else { char c; char * txt; svalue_t *svp; txt = get_txt(str); len = (long)mstrsize(str); c = get_txt(del)[0]; /* TODO: Remember positions here */ /* Determine the number of delimiters in the string. */ for (num = 1, p = txt ; p < txt + len && NULL != (p = memchr(p, c, len - (p - txt))) ; p++, num++) NOOP; ret = allocate_array(num); for ( svp = ret->item, left = len ; NULL != (p = memchr(txt, c, left)) ; left -= (p + 1 - txt), txt = p + 1, svp++) { len = p - txt; buff = new_n_mstring(txt, (size_t)len); if (!buff) { free_array(ret); outofmem(len, "explode() on a string"); } put_string(svp, buff); } /* txt now points to the (possibly empty) remains after * the last delimiter. */ len = get_txt(str) + mstrsize(str) - txt; buff = new_n_mstring(txt, (size_t)len); if (!buff) { free_array(ret); outofmem(len, "explode() on a string"); } put_string(svp, buff); 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 = get_txt(str), left = mstrsize(str), num=1 ; left > 0; ) { if (left >= len && memcmp(p, get_txt(del), (size_t)len) == 0) { p += len; left -= len; num++; } else { p += 1; left -= 1; } } ret = allocate_array(num); /* Extract the strings into the result array . * serves as temporary buffer for the copying. */ for (p = get_txt(str), beg = get_txt(str), num = 0, left = mstrsize(str) ; left > 0; ) { if (left >= len && memcmp(p, get_txt(del), (size_t)len) == 0) { ptrdiff_t bufflen; bufflen = p - beg; buff = new_n_mstring(beg, (size_t)bufflen); if (!buff) { free_array(ret); outofmem(bufflen, "buffer for explode()"); } put_string(ret->item+num, buff); num++; beg = p + len; p = beg; left -= len; } else { p += 1; left -= 1; } } /* Copy the last occurence (may be empty). */ len = get_txt(str) + mstrsize(str) - beg; buff = new_n_mstring(beg, (size_t)len); if (!buff) { free_array(ret); outofmem(len, "last fragment in explode()"); } put_string(ret->item + num, buff); return ret; } /* explode_string() */ /*-------------------------------------------------------------------------*/ string_t * arr_implode_string (vector_t *arr, string_t *del MTRACE_DECL) /* Implode the string vector by , i.e. all strings from * with 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 and */ { mp_int size, i, arr_size; size_t del_len; char *deltxt; char *p; string_t *result; svalue_t *svp; del_len = mstrsize(del); deltxt = get_txt(del); /* Compute the of the final string */ size = -(mp_int)del_len; for (i = (arr_size = (mp_int)VEC_SIZE(arr)), svp = arr->item; --i >= 0; svp++) { if (svp->type == T_STRING) { size += (mp_int)del_len + mstrsize(svp->u.str); } else if (destructed_object_ref(svp)) { /* While we're here anyway... */ assign_svalue(svp, &const0); } } /* Allocate the string; cop out if there's nothing to implode. */ if (size <= 0) return ref_mstring(STR_EMPTY); result = mstring_alloc_string(size MTRACE_PASS); if (!result) { /* caller raises the errorf() */ return NULL; } p = get_txt(result); /* Concatenate the result string. * * is the number of elements left to check, * is the next element to check, *

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++; } memcpy(p, get_txt(svp->u.str), mstrsize(svp->u.str)); p += mstrsize(svp->u.str); /* Copy the others if any */ while (--i > 0) { svp++; if (svp->type == T_STRING) { memcpy(p, deltxt, del_len); p += del_len; memcpy(p, get_txt(svp->u.str), mstrsize(svp->u.str)); p += mstrsize(svp->u.str); } } return result; } /* implode_array() */ /*-------------------------------------------------------------------------*/ vector_t * slice_array (vector_t *p, mp_int from, mp_int to) /* Create a vector slice from vector

, range to inclusive, * and return it. * * is guaranteed to not exceed the size of

. * If is greater than , 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

and and return the resulting vector. *

and 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; } /* add_array() */ /*-------------------------------------------------------------------------*/ static INLINE void sanitize_array (vector_t * vec) /* In the given array, make all strings tabled, and replace destructed * object references by svalue 0s. * Used for example in preparation for ordering the array. */ { size_t j, keynum; svalue_t * inpnt; keynum = VEC_SIZE(vec); for ( j = 0, inpnt = vec->item; j < keynum; j++, inpnt++) { if (inpnt->type == T_STRING) { if (!mstr_tabled(inpnt->u.str)) { inpnt->u.str = make_tabled(inpnt->u.str); } } else if (destructed_object_ref(inpnt)) { free_svalue(inpnt); put_number(inpnt, 0); } } } /* sanitize_array() */ /*-------------------------------------------------------------------------*/ ptrdiff_t * get_array_order (vector_t * vec ) /* Determine the order of the elements in vector and return the * sorted indices (actually svalue_t* pointer diffs). The order is * determined by svalue_cmp() (which happens to be high-to-low). * * As a side effect, strings in the vector are made shared, and * destructed objects in the vector are replaced by svalue 0s. */ { 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 elements long. * sorted[] is created from root[] after sorting. */ svalue_t **root; /* Auxiliary array with the sorted keys as svalue* into vec. * This way the sorting is given by the order of the pointers, while * the original position is given by (pointer - vec->item). * The very first element is a dummy (heapsort uses array indexing * starting with index 1), the next elements are scratch * area, the final 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 j; keynum = (mp_int)VEC_SIZE(vec); xallocate(sorted, keynum * sizeof(ptrdiff_t) + sizeof(ptrdiff_t) , "sorted index array"); /* The extra sizeof(ptrdiff_t) is just to have something in * case keynum is 0. */ sanitize_array(vec); /* For small arrays, use something else but Heapsort - trading * less overhead for worse complexity. * TODO: The limit of '6' is arbitrary (it was the transition point * TODO:: on my machine) - a better way would be to test the system * TODO:: speed at startup. */ if (keynum <= 6) { switch (keynum) { case 0: /* Do nothing */ break; case 1: sorted[0] = 0; break; case 2: if (svalue_cmp(vec->item, vec->item + 1) > 0) { sorted[0] = 0; sorted[1] = 1; } else { sorted[0] = 1; sorted[1] = 0; } break; case 3: { int d; sorted[0] = 0; sorted[1] = 1; sorted[2] = 2; d = svalue_cmp(vec->item, vec->item + 1); if (d < 0) { sorted[1] = 0; sorted[0] = 1; } d = svalue_cmp(vec->item + sorted[0], vec->item + 2); if (d < 0) { ptrdiff_t tmp = sorted[2]; sorted[2] = sorted[0]; sorted[0] = tmp; } d = svalue_cmp(vec->item + sorted[1], vec->item + sorted[2]); if (d < 0) { ptrdiff_t tmp = sorted[2]; sorted[2] = sorted[1]; sorted[1] = tmp; } break; } /* case 3 */ default: { size_t start; /* Index of the next position to set */ /* Initialise the sorted[] array */ for (start = 0; (mp_int)start < keynum; start++) sorted[start] = (ptrdiff_t)start; /* Outer loop: walk start through the array, being the position * where the next highest element has to go. */ for (start = 0; (mp_int)start < keynum-1; start++) { size_t max_idx; /* Index (in sorted[]) of the current max */ svalue_t *max; /* Pointer to the current max svalue */ size_t test_idx; /* Index of element to test */ /* Find the highest element in the remaining vector */ max_idx = start; max = vec->item + sorted[start]; for (test_idx = start+1; (mp_int)test_idx < keynum; test_idx++) { svalue_t *test = vec->item + sorted[test_idx]; if (svalue_cmp(max, test) < 0) { max_idx = test_idx; max = test; } } /* Put the found maximum at position start */ if (max_idx != start) { ptrdiff_t tmp = sorted[max_idx]; sorted[max_idx] = sorted[start]; sorted[start] = tmp; } } break; } /* case default */ } /* switch(keynum) */ return sorted; } /* Allocate the auxiliary array. */ root = (svalue_t **)alloca(keynum * sizeof(svalue_t *[2]) + sizeof(svalue_t) ); if (!root) { errorf("Stack overflow in get_array_order()"); /* NOTREACHED */ return NULL; } /* Heapsort vec into *root. */ /* Heapify the keys into the first half of root */ for ( j = 1, inpnt = vec->item ; j <= keynum ; j++, inpnt++) { int curix, parix; /* propagate the new element up in the heap as much as necessary */ for (curix = j; 0 != (parix = curix>>1); ) { if ( svalue_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] || svalue_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] - vec->item; return sorted; } /* get_array_order() */ /*-------------------------------------------------------------------------*/ vector_t * order_array (vector_t *vec) /* Order the array and return a new vector with the sorted data. * The sorting order is the internal order defined by svalue_cmp() (which * happens to be high-to-low). * * This function and lookup_key() are used in several places for internal * lookup functions (e.g. in say()). * * As a side effect, strings in the vector are made shared, and * destructed objects in the vector are replaced by svalue 0s. */ { vector_t * out; /* The result vector of vectors */ svalue_t * outpnt; /* Next result value element to fill in */ ptrdiff_t * sorted; /* The vector elements in sorted order */ long keynum; /* Number of keys */ long j; keynum = (long)VEC_SIZE(vec); sorted = get_array_order(vec); /* Copy the elements from the in-vector to the result vector. */ out = allocate_array(VEC_SIZE(vec)); outpnt = out->item; for (j = keynum; --j >= 0; ) { assign_svalue_no_free(outpnt++, vec->item + sorted[j]); } xfree(sorted); return out; } /* order_array() */ /*-------------------------------------------------------------------------*/ long lookup_key (svalue_t *key, vector_t *vec) /* Lookup up value in ordered vector and return it's position. * If not found, return as negative number the position at which the * key would have to be inserted, incremented by 1. That is: * -1 -> key should be at position 0, * -2 -> key should be at position 1, * -len(vec)-1 -> key should be appended to the vector. * * must be sorted according to svalue_cmp(), else the result will be * interesting, but useless. * * The function is used by object.c and pkg-alists.c . */ { mp_int i, o, d, keynum; svalue_t shared_string_key; /* The svalue used to shared search key during the search. * It does not count as reference! */ /* If key is a non-shared string, lookup and use the shared copy. */ if (key->type == T_STRING && !mstr_tabled(key->u.str)) { shared_string_key.type = T_STRING; if ( !(shared_string_key.u.str = find_tabled(key->u.str)) ) { return -1; } key = &shared_string_key; } if ( !(keynum = (mp_int)VEC_SIZE(vec)) ) return -1; /* Simple binary search */ i = keynum >> 1; o = (i+2) >> 1; for (;;) { d = svalue_cmp(key, &vec->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 { /* Found! */ return i; } if (o <= 1) { /* Last element to try */ d = svalue_cmp(key, &vec->item[i]); if (d == 0) return i; if (d > 0) return -(i+1)-1; return -i-1; } o = (o+1) >> 1; } /* NOTREACHED */ return -1; } /* lookup_key() */ /*-------------------------------------------------------------------------*/ static Bool * match_arrays (vector_t *vec1, vector_t *vec2) /* Compare the contents of the two (unordered) vectors and * and return a boolean vector describing for each vector * which elements are in both. * * The resulting bool vector has len(vec1)+len(vec2) flags (but * at least 1); the first describing the elements of vec1, the last * describing those of vec2. Each flag is FALSE if the vector entry * is unique, and TRUE if the same value appears in the other vector. * * When out of memory, an errorf() is thrown. */ { size_t len1, len2, len; /* Length of vec1, vec2, and both summed */ Bool *flags; /* The resulting flag vector */ len1 = VEC_SIZE(vec1); len2 = VEC_SIZE(vec2); /* Get the flag vector, default it to 'non matching'. */ len = len1 + len2; if (!len) len = 1; xallocate(flags, len * sizeof(Bool), "flag vector"); memset(flags, 0, len * sizeof(Bool)); /* Test some special cases */ /* Special case: if one of the vectors is empty, no elements match */ if (len1 == 0 || len2 == 0) return flags; /* Special case: if one of the vectors has only one element, * a simple linear comparison is sufficient. */ if (len1 == 1 || len2 == 1) { svalue_t * rover; /* Pointer to the long vector elements */ size_t rlen; /* Length (remaining) in the long vector */ svalue_t * elem; /* Pointer to the single-elem vector elements */ Bool * rflag; /* Pointer to the long vector flags */ Bool * eflag; /* Pointer to the single-elem vector flag */ sanitize_array(vec1); sanitize_array(vec2); /* Sort out which vector is which */ if (len1 == 1) { /* Even more special case: both vectors have just one elem */ if (len2 == 1) { if (!svalue_eq(vec1->item, vec2->item)) { flags[0] = flags[1] = MY_TRUE; } return flags; } /* vec1 is the short one */ rover = vec2->item; rlen = len2; rflag = flags + len1; elem = vec1->item; eflag = flags; } else /* len2 == 1 */ { /* vec2 is the short one */ rover = vec1->item; rlen = len1; rflag = flags; elem = vec2->item; eflag = flags + len1; } /* Now loop over all elements in the long vector and compare * them to the one in the short vector. */ for ( ; rlen != 0; rlen--, rover++, rflag++) { if (!svalue_eq(rover, elem)) *rflag = *eflag = MY_TRUE; } /* Done */ return flags; } /* if (one vector has only one element */ /* The generic matching routine: first both arrays are ordered, * then compared side by side. */ { ptrdiff_t *sorted1, *sorted2; /* Ordered indices to the vectors */ ptrdiff_t *index1, *index2; /* Current elements to compare */ Bool *flag1, *flag2; /* flags base pointers */ sorted1 = get_array_order(vec1); sorted2 = get_array_order(vec2); /* Set up the comparison */ index1 = sorted1; index2 = sorted2; flag1 = flags; flag2 = flags + len1; /* Compare side by side. Any element left uncompared at * the end is automatically non-matching. */ while (len1 != 0 && len2 != 0) { int d; d = svalue_cmp(vec1->item + *index1, vec2->item + *index2); if (d == 0) { /* Elements match */ svalue_t *test_val = vec1->item+*index1; /* Important here is to remember that there might * be several elements of the same value in a row. * The side-by-side comparison itself is not able * to handle it, so we have to check here manually * for it. * The loops will leave index1/index2 point to the * first element after the sequence of matching ones. */ do { flag1[*index1] = MY_TRUE; index1++; len1--; if (len1 != 0) d = svalue_eq(test_val, vec1->item + *index1); } while (len1 != 0 && d == 0); do { flag2[*index2] = MY_TRUE; index2++; len2--; if (len2 != 0) d = svalue_eq(test_val, vec2->item + *index2); } while (len2 != 0 && d == 0); continue; /* Next iteration of the main loop */ } /* Else advance in array(s) */ if (d > 0) { index1++; len1--; } if (d < 0) { index2++; len2--; } } /* while (in both vectors) */ /* Cleanup */ xfree(sorted1); xfree(sorted2); /* Done */ return flags; } /* NOTREACHED */ return flags; } /* match_array() */ /*-------------------------------------------------------------------------*/ vector_t * subtract_array (vector_t *minuend, vector_t *subtrahend) /* Subtract all elements in from the vector * and return the resulting difference vector. * and are freed. */ { Bool *flags; /* The result from match_arrays() */ size_t result_size; /* Size of the result array */ vector_t *result; /* Result array */ svalue_t *dest; /* Pointer for storing the result elements */ size_t i; size_t minuend_size = VEC_SIZE(minuend); size_t subtrahend_size = VEC_SIZE(subtrahend); /* Handle empty vectors quickly */ if (minuend_size == 0 || subtrahend_size == 0) { free_array(subtrahend); return minuend; } /* Non-trivial arrays: match them up */ flags = match_arrays(minuend, subtrahend); /* Count how many elements would be left in minuend * and allocate the result array. */ for (i = result_size = 0; i < minuend_size; i++) { if (!flags[i]) result_size++; } if (result_size == minuend_size) { /* No elements to remove */ xfree(flags); free_array(subtrahend); return minuend; } if (max_array_size && result_size > max_array_size) { xfree(flags); free_array(minuend); free_array(subtrahend); errorf("Illegal array size: %lu.\n", (unsigned long)result_size); } result = allocate_array(result_size); /* Copy the elements to keep from minuend into result. * We count down result_size to be able to stop as early * as possible. */ for ( dest = result->item, i = 0 ; i < minuend_size && result_size != 0 ; i++ ) { if (!flags[i]) { assign_svalue_no_free(dest, minuend->item+i); dest++; result_size--; } } /* Cleanup and return */ xfree(flags); free_array(minuend); free_array(subtrahend); return result; } /* subtract_array() */ /*-------------------------------------------------------------------------*/ vector_t * intersect_array (vector_t *vec1, vector_t *vec2) /* OPERATOR & (array intersection) * * Perform an intersection of the two vectors and . * The result is a new vector with all elements which are present in both * input vectors. * * Both and are freed. */ { Bool *flags; /* The result from match_arrays() */ size_t result_size; /* Size of the result array */ vector_t *result; /* Result array */ svalue_t *dest; /* Pointer for storing the result elements */ size_t i; size_t vec1_size = VEC_SIZE(vec1); size_t vec2_size = VEC_SIZE(vec2); /* Handle empty arrays quickly */ if (vec1_size == 0 || vec2_size == 0) { free_array(vec2); return shrink_array(vec1, 0); /* Fancy way of creating an empty array */ } /* Non-trivial arrays: match them up */ flags = match_arrays(vec1, vec2); /* Count how many elements have to be copied from vec1 * and allocate the result array. */ for (i = result_size = 0; i < vec1_size; i++) { if (flags[i]) result_size++; } if (result_size == vec1_size) { /* No elements to remove */ xfree(flags); free_array(vec2); return vec1; } if (max_array_size && result_size > max_array_size) { xfree(flags); free_array(vec1); free_array(vec2); errorf("Illegal array size: %lu.\n", (unsigned long)result_size); } result = allocate_array(result_size); /* Copy the elements to keep from vec1 into result. * We count down result_size to be able to stop as early * as possible. */ for ( dest = result->item, i = 0 ; i < vec1_size && result_size != 0 ; i++ ) { if (flags[i]) { assign_svalue_no_free(dest, vec1->item+i); dest++; result_size--; } } /* Cleanup and return */ xfree(flags); free_array(vec1); free_array(vec2); return result; } /* intersect_array() */ /*-------------------------------------------------------------------------*/ vector_t * join_array (vector_t *vec1, vector_t *vec2) /* OPERATOR | (array union) * * Perform a join of the two vectors and . * The result is a new vector with all elements and those elements * from which are not present in . * * Both and are freed. */ { Bool *flags; /* The result from match_arrays() */ size_t result_size; /* Size of the result array */ vector_t *result; /* Result array */ svalue_t *src; /* Pointer for getting the result elements */ svalue_t *dest; /* Pointer for storing the result elements */ size_t i; size_t vec1_size = VEC_SIZE(vec1); size_t vec2_size = VEC_SIZE(vec2); size_t sum_size = vec1_size + vec2_size; /* Handle empty arrays quickly */ if (vec1_size == 0) { free_array(vec1); return vec2; } if (vec2_size == 0) { free_array(vec2); return vec1; } /* Non-trivial arrays: match them up */ flags = match_arrays(vec1, vec2); /* Count how many elements have to be copied from vec2 * (we have to get all from vec1 anyway) and allocate the result array. */ result_size = 0; for (i = vec1_size; i < sum_size; i++) { if (!flags[i]) result_size++; } if (result_size == 0) { /* No elements to copy */ xfree(flags); free_array(vec2); return vec1; } if (max_array_size && result_size+vec1_size > max_array_size) { xfree(flags); errorf("Illegal array size: %lu.\n", (unsigned long)(result_size+vec1_size)); } result = allocate_array(vec1_size+result_size); /* Copy the elements to keep from vec1 into result. */ for (dest = result->item, i = 0 ; i < vec1_size ; i++) { assign_svalue_no_free(dest, vec1->item+i); dest++; } /* Copy the elements to keep from vec1 into result. * We count down result_size to be able to stop as early * as possible. */ for ( src = vec2->item, dest = result->item + vec1_size, i = vec1_size ; i < sum_size && result_size != 0 ; i++, src++ ) { if (!flags[i]) { assign_svalue_no_free(dest, src); dest++; result_size--; } } /* Cleanup and return */ xfree(flags); free_array(vec1); free_array(vec2); return result; } /* join_array() */ /*-------------------------------------------------------------------------*/ vector_t * symmetric_diff_array (vector_t *vec1, vector_t *vec2) /* OPERATOR ^ (symmetric array difference) * * Compute the symmetric difference of the two vectors and . * The result is a new vector with all elements which are present in only * one of the input vectors. * * Both and are freed. */ { Bool *flags; /* The result from match_arrays() */ size_t result_size; /* Size of the result array */ vector_t *result; /* Result array */ svalue_t *src; /* Pointer for getting the result elements */ svalue_t *dest; /* Pointer for storing the result elements */ size_t i; size_t vec1_size = VEC_SIZE(vec1); size_t vec2_size = VEC_SIZE(vec2); size_t sum_size = vec1_size + vec2_size; /* Handle empty arrays quickly */ if (vec1_size == 0) { free_array(vec1); return vec2; } if (vec2_size == 0) { free_array(vec2); return vec1; } /* Non-trivial arrays: match them up */ flags = match_arrays(vec1, vec2); /* Count how many elements have to be copied * and allocate the result array. */ for (i = result_size = 0; i < sum_size; i++) { if (!flags[i]) result_size++; } if (max_array_size && result_size > max_array_size) { xfree(flags); errorf("Illegal array size: %lu.\n", (unsigned long)result_size); } result = allocate_array(result_size); /* Copy the elements to keep from vec1 into result. * We count down result_size to be able to stop as early * as possible. */ dest = result->item; for ( src = vec1->item, i = 0 ; i < vec1_size && result_size != 0 ; i++, src++ ) { if (!flags[i]) { assign_svalue_no_free(dest, src); dest++; result_size--; } } /* Copy the elements to keep from vec2 into result, starting * at the current position . * We count down result_size to be able to stop as early * as possible. */ for ( src = vec2->item, i = vec1_size ; i < sum_size && result_size != 0 ; i++, src++ ) { if (!flags[i]) { assign_svalue_no_free(dest, src); dest++; result_size--; } } /* Cleanup and return */ xfree(flags); free_array(vec1); free_array(vec2); return result; } /* symmetric_diff_array() */ /*-------------------------------------------------------------------------*/ Bool is_ordered (vector_t *v) /* Determine if satisfies the conditions for being an ordered vector. * Return true if yes, false if not. * * The conditions are: * - every string is shared * - all elements are sorted according to svalue_cmp(). * * This predicate is currently used just by the swapper, historically * 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 && !mstr_tabled(svp->u.str)) return MY_FALSE; if (svalue_cmp(svp, svp+1) > 0) return MY_FALSE; } if (svp->type == T_STRING && !mstr_tabled(svp->u.str)) return MY_FALSE; return MY_TRUE; } /* is_ordered() */ /*=========================================================================*/ /* EFUNS */ /*-------------------------------------------------------------------------*/ svalue_t * v_allocate (svalue_t *sp, int num_arg) /* EFUN allocate() * * mixed *allocate(int|int* size) * mixed *allocate(int|int* size, mixed init_value) * * Allocate an array of elements (if is an array, the result * will be a multidimensional array), either empty or all * elements initialized with . If is a * mapping or array, allocate will create shallow copies of them. */ { vector_t *v; svalue_t *argp; size_t new_size; argp = sp - num_arg + 1; if (argp->type == T_NUMBER) { new_size = (size_t)argp->u.number; if (num_arg == 1 || (sp->type == T_NUMBER && !sp->u.number)) v = allocate_array(new_size); else { size_t i; svalue_t *svp; /* If the initialisation value is a mapping, remove all * destructed elements so that we can use copy_mapping() * later on. */ if (sp->type == T_MAPPING) check_map_for_destr(sp->u.map); v = allocate_uninit_array(new_size); for (svp = v->item, i = 0; i < new_size; i++, svp++) copy_svalue_no_free(svp, sp); } } else if (argp->type == T_POINTER && ( VEC_SIZE(argp->u.vec) == 0 || ( VEC_SIZE(argp->u.vec) == 1 && argp->u.vec->item->type == T_NUMBER && argp->u.vec->item->u.number == 0) ) ) { /* Special case: result is the empty array. * The condition catches ( ({}) ) as well as ( ({0}) ) * (the generic code below can't handle either of them). */ v = allocate_array(0); } else if (argp->type == T_POINTER) { svalue_t *svp; size_t dim, num_dim; size_t count; Bool hasInitValue = MY_FALSE; size_t * curpos = alloca(VEC_SIZE(argp->u.vec) * sizeof(*curpos)); size_t * sizes = alloca(VEC_SIZE(argp->u.vec) * sizeof(*sizes)); vector_t ** curvec = alloca(VEC_SIZE(argp->u.vec) * sizeof(*curvec)); num_dim = VEC_SIZE(argp->u.vec); if (!curpos || !curvec || !sizes) { errorf("Out of stack memory.\n"); /* NOTREACHED */ } if (num_arg == 2 && (sp->type != T_NUMBER || sp->u.number != 0)) { hasInitValue = MY_TRUE; /* If the initialisation value is a mapping, remove all * destructed elements so that we can use copy_mapping() * later on. */ if (sp->type == T_MAPPING) check_map_for_destr(sp->u.map); } /* Check the size array for consistency, and also count how many * elements we're going to allocate. */ for ( dim = 0, count = 0, svp = argp->u.vec->item ; dim < num_dim ; dim++, svp++ ) { p_int size; if (svp->type != T_NUMBER) { errorf("Bad argument to allocate(): size[%d] is a '%s', " "expected 'int'.\n" , (int)dim, typename(svp->type)); /* NOTREACHED */ } size = svp->u.number; if (size < 0 || (max_array_size && (size_t)size > max_array_size)) errorf("Illegal array size: %"PRIdPINT"\n", size); if (size == 0 && dim < num_dim-1) errorf("Only the last dimension can have empty arrays.\n"); count *= (size_t)size; if (max_array_size && count > max_array_size) errorf("Illegal total array size: %lu\n", (unsigned long)count); sizes[dim] = (size_t)size; curvec[dim] = NULL; } /* Now loop over the dimensions, creating the array structure */ dim = 0; curpos[0] = 0; while (dim > 0 || curpos[0] < sizes[0]) { if (!curvec[dim]) { /* We just entered this dimension. * Create a new array and initialise the loop. */ if (hasInitValue || dim+1 < num_dim) { curvec[dim] = allocate_uninit_array(sizes[dim]); } else { curvec[dim] = allocate_array(sizes[dim]); /* This is the last dimension, and there is nothing * to initialize: return immediately to the higher level */ curpos[dim] = sizes[dim]; /* In case dim == 0 */ if (dim > 0) dim--; continue; } curpos[dim] = 0; } /* curvec[dim] is valid, and we have to put the next * element in at index curpos[dim]. */ if (dim == num_dim-1) { /* Last dimension: assign the init value */ if (hasInitValue && curpos[dim] < sizes[dim]) copy_svalue_no_free(curvec[dim]->item+curpos[dim], sp); } else if (!curvec[dim+1]) { /* We need a vector from a lower dimension, but it doesn't * exist yet: setup the loop parameters to go into * that lower level. */ dim++; continue; } else if (curpos[dim] < sizes[dim]) { /* We got a vector from a lower lever */ put_array(curvec[dim]->item+curpos[dim], curvec[dim+1]); curvec[dim+1] = NULL; } /* Continue to the next element. If we are at the end * of this dimension, return to the next higher one. */ curpos[dim]++; if (curpos[dim] >= sizes[dim] && dim > 0) { dim--; } } /* while() */ /* The final vector is now in curvec[0] */ v = curvec[0]; } else { /* The type checker should prevent this case */ fatal("Illegal arg 1 to allocate(): got '%s', expected 'int|int*'.\n" , typename(argp->type)); } /* if (argp->type) */ if (num_arg == 2) free_svalue(sp--); free_svalue(sp); put_array(sp, v); return sp; } /* v_allocate() */ /*-------------------------------------------------------------------------*/ svalue_t * x_filter_array (svalue_t *sp, int num_arg) /* EFUN: 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 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: * * ->(elem, ...) * * or a mapping query: * * [elem] * * 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 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

* (in reverse order) */ 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; p = arg->u.vec; p_size = (mp_int)VEC_SIZE(p); flags = alloca((size_t)p_size+1); if (!flags) { errorf("Stack overflow in filter()"); /* NOTREACHED */ return sp; } /* 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; errorf("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) { vefun_bad_arg(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; errorf("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() */ /*-------------------------------------------------------------------------*/ svalue_t * x_map_array (svalue_t *sp, int num_arg) /* EFUN 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 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: * * ->(elem, ...) * * or a mapping query: * * [elem] * * In the mapping case, if [elem] does not exist, the original * value is returned in the result. * * can both be an object reference or a filename. If is * omitted, or neither an object nor a string, then this_object() is used. * * As a bonus, all references to destructed objects in are replaced * by proper 0es. */ { vector_t *arr; vector_t *res; svalue_t *arg; svalue_t *v, *w, *x; mp_int cnt; inter_sp = sp; arg = sp - num_arg + 1; 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; errorf("Too many arguments to map(array)\n"); } m = arg[1].u.map; res = allocate_array(cnt); if (!res) errorf("(map_array) Out of memory: array[%"PRIdMPINT "] for result\n", cnt); push_array(inter_sp, 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) { vefun_bad_arg(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) errorf("(map_array) Out of memory: array[%"PRIdMPINT "] for result\n", cnt); push_array(inter_sp, 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)) errorf("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; /* Keep svalue type: T_POINTER */ return arg; } /* x_map_array () */ /*-------------------------------------------------------------------------*/ svalue_t * v_sort_array (svalue_t * sp, int num_arg) /* EFUN 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 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 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 * 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; error_index = setup_efun_callback(&cb, arg+1, num_arg-1); if (error_index >= 0) { vefun_bad_arg(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) { errorf("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) end1 = size; end2 = i + step; if (end2 > size) end2 = size; while (index1 < end1 && index2 < end2) { svalue_t *d; if (!callback_object(&cb)) errorf("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; } /* v_sort_array() */ /*-------------------------------------------------------------------------*/ svalue_t * v_filter_objects (svalue_t *sp, int num_arg) /* EFUN filter_objects() * * object *filter_objects (object *arr, string fun, mixed extra, ...) * * Filter the objects in by calling the lfun obj->(...) * 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 is ignored and thus not included in the result. * * As a bonus, all references to destructed objects in are replaced * by proper 0es. */ { vector_t *p; /* The argument */ string_t *func; /* The 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

*/ int res; /* Count of objects to return */ object_t *ob; /* Object to call */ mp_int p_size; /* Size of

*/ 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; p = arguments[-2].u.vec; func = arguments[-1].u.str; num_arg -= 2; p_size = (mp_int)VEC_SIZE(p); /* Call 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 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; func = find_tabled(func); if (NULL != func) { flags = alloca((p_size+1)*sizeof(*flags)); if (!flags) { errorf("Stack overflow in filter_objects()"); /* NOTREACHED */ return NULL; } for (cnt = 0; cnt < p_size; cnt++) { flags[cnt] = MY_FALSE; v = &p->item[cnt]; /* Coerce 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.str)) ) 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() */ } /* if() */ /* 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) { (void)ref_mstring(sv.u.str); } 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; } /* v_filter_objects() */ /*-------------------------------------------------------------------------*/ svalue_t * v_map_objects (svalue_t *sp, int num_arg) /* EFUN map_objects() * * mixed *map_objects (object *arr, string fun, mixed extra, ...) * * Map the objects in by calling the lfun obj->(...) * 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 is ignored and a 0 is returned in its place. * * As a bonus, all references to destructed objects in are replaced * by proper 0es. */ { vector_t *p; /* The argument */ string_t *func; /* The 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

*/ 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; p = arguments[-2].u.vec; func = arguments[-1].u.str; num_arg -= 2; r = allocate_array(size = (mp_int)VEC_SIZE(p)); arguments[-2].u.vec = r; push_array(inter_sp, p); /* Ref it from the stack in case of errors */ /* Call in every object, storing the result in r. * * Checking if 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. */ func = find_tabled(func); if (NULL != func) { for (cnt = size, v = p->item, x = r->item; --cnt >= 0; v++, x++) { /* Coerce 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.str)) ) 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() */ } /* if() */ /* Clean up and return */ do { free_svalue(sp--); } while(--num_arg >= 0); free_array(p); return sp; } /* v_map_objects() */ /*-------------------------------------------------------------------------*/ svalue_t * f_transpose_array (svalue_t *sp) /* EFUN 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 */ mp_int b; /* size of [ix] for all ix */ mp_int i, j; int no_copy; /* 1 if 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 */ 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) { errorf("Bad arg 1 to transpose_array(): not an array of arrays.\n"); /* NOTREACHED */ return 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) < 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() */ /*=========================================================================*/ /* EFUN unique_array() * * mixed *unique_array (object *obarr, string seperator, mixed skip = 0) * * Group all those objects from together for which the * function (which is called in every object) returns the * same value. Objects for which the function returns the 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 () (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 is identical to . * For arrays, this function only compares if and 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 mstreq(arg1->u.str, arg2->u.str); } else if (arg1->type == T_OBJECT && arg2->type == T_OBJECT) { return arg1->u.ob == arg2->u.ob; } else return 0; } /* sameval() */ /*-------------------------------------------------------------------------*/ static int put_in (Mempool pool, struct unique **ulist , svalue_t *marker, svalue_t *elem) /* Insert the object according to its value into the comb * of unique structures. 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: 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 into. */ while (llink) { if (!fixed && sameval(marker, &(llink->mark))) { /* Insert the new 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) { errorf("(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) { errorf("(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; } /* put_in() */ /*-------------------------------------------------------------------------*/ /* To facilitate automatic cleanup of the temporary structures in case * of an error, the following structure will be pushed onto the VM stack * as T_ERROR_HANDLER. */ struct unique_cleanup_s { svalue_t head; /* The link to the error handler function */ Mempool pool; /* Pool for the unique structures */ vector_t * arr; /* Protective reference to the array */ }; static void make_unique_cleanup (svalue_t * arg) { struct unique_cleanup_s * data = (struct unique_cleanup_s *)arg; if (data->pool) mempool_delete(data->pool); if (data->arr) deref_array(data->arr); xfree(arg); } /* make_unique_cleanup() */ /*-------------------------------------------------------------------------*/ static vector_t * make_unique (vector_t *arr, callback_t *cb, svalue_t *skipnum) /* The actual implementation of efun unique_array(); * * The caller made sure that 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 ay */ mp_int ant; /* Number of distinct markers */ mp_int cnt, cnt2; struct unique_cleanup_s * ucp; 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. */ pool = new_mempool(size_mempool(sizeof(*head))); if (!pool) errorf("(unique_array) Out of memory: (%lu bytes) for mempool\n" , (unsigned long)arr_size * sizeof(*head)); /* Create the automatic cleanup structure */ ucp = xalloc(sizeof(*ucp)); if (!ucp) { mempool_delete(pool); errorf("(unique_array) Out of memory: (%lu bytes) for cleanup structure\n" , (unsigned long)sizeof(*ucp)); } ucp->pool = pool; ucp->arr = ref_array(arr); /* Prevent apply from freeing this */ push_error_handler(make_unique_cleanup, &(ucp->head)); /* Build the comb structure. */ ant = 0; for (cnt = 0; cnt < arr_size; cnt++) { if (current_object->flags & O_DESTRUCTED) break; /* Don't call the filters anymore */ if (arr->item[cnt].type == T_OBJECT && !destructed_object_ref(&(arr->item[cnt])) ) { /* It's usually done the other way around, but not here: if * it's a closure, we pass the object analyzed; otherwise we * change the object the callback is bound to to call the * discriminator function in it. */ if (!cb->is_lambda) callback_change_object(cb, arr->item[cnt].u.ob); else push_ref_object(inter_sp, arr->item[cnt].u.ob, "unique_array"); v = apply_callback(cb, cb->is_lambda ? 1 : 0); if (v && !sameval(v, skipnum)) ant = put_in(pool, &head, v, &(arr->item[cnt])); } } 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 */ } /* Cleanup using the cleanup structure */ free_svalue(inter_sp--); return ret; } /* make_unique() */ /*-------------------------------------------------------------------------*/ svalue_t * v_unique_array (svalue_t *sp, int num_arg) /* EFUN unique_array() * * mixed unique_array(object *obarr, string|closure fun) * mixed unique_array(object *obarr, string|closure fun, mixed skip) * mixed unique_array(object *obarr, string|closure fun, mixed extra..., mixed skip) * * Groups objects together for which the separator function * returns the same value. obarr should be an array of objects, * other types are ignored. * * If the separator function is defined by name, it is searched and called * in the objects from . If arguments are given, they are * passed to the function as arguments. * * If the separator function is defined as a closure, it will be passed * the objects from as first argument, with the arguments * (if any) passed following. * * If the argument is given (it is required when arguments * are to be used), and the return value from the separator function call * matches this value, the object in question will _not_ be included in the * returned array. Default value for is the number 0. */ { vector_t *res; svalue_t *argp = sp - num_arg + 1; callback_t cb; /* must persist until the end of the function */ check_for_destr(argp->u.vec); /* Sort out the arguments */ if (num_arg == 2) { /* Just the callback function name on the stack: add the default * 'skip' value */ sp++; put_number(sp, 0); } { /* Extract the callback information from the stack */ int error_index; assign_eval_cost(); inter_sp = sp; error_index = setup_efun_callback_noobj(&cb, argp+1, num_arg-2); if (error_index >= 0) { /* The callback values have already been removed, now * make sure that the 'skip' value isn't left out either */ transfer_svalue_no_free(argp+1, sp); inter_sp = sp = argp+1; vefun_bad_arg(error_index+2, argp+1); /* NOTREACHED */ return argp; } /* Callback creation successful, now setup the stack */ put_callback(argp+1, &cb); transfer_svalue_no_free(argp+2, sp); inter_sp = sp = argp+2; } /* At this point: argp[0]: the vector * argp[1]: the callback structure * sp -> argp[2]: the skip value */ res = make_unique(argp->u.vec, argp[1].u.cb, argp+2); /* Clean up the stack and push the result */ free_svalue(sp--); free_svalue(sp--); free_svalue(sp); if (res) put_array(sp, res); else put_number(sp, 0); return sp; } /* v_unique_array() */ /***************************************************************************/