386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Sun, 19 Apr 1992 19:45:30 +0000 (11:45 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Sun, 19 Apr 1992 19:45:30 +0000 (11:45 -0800)
Work on file usr/othersrc/public/ghostscript-2.4.1/idict.c

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/othersrc/public/ghostscript-2.4.1/idict.c [new file with mode: 0644]

diff --git a/usr/othersrc/public/ghostscript-2.4.1/idict.c b/usr/othersrc/public/ghostscript-2.4.1/idict.c
new file mode 100644 (file)
index 0000000..067eb6d
--- /dev/null
@@ -0,0 +1,609 @@
+/* Copyright (C) 1989, 1992 Aladdin Enterprises.  All rights reserved.
+   Distributed by Free Software Foundation, Inc.
+
+This file is part of Ghostscript.
+
+Ghostscript is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
+to anyone for the consequences of using it or for whether it serves any
+particular purpose or works at all, unless he says so in writing.  Refer
+to the Ghostscript General Public License for full details.
+
+Everyone is granted permission to copy, modify and redistribute
+Ghostscript, but only under the conditions described in the Ghostscript
+General Public License.  A copy of this license is supposed to have been
+given to you along with Ghostscript so you can know your rights and
+responsibilities.  It should be in a file named COPYING.  Among other
+things, the copyright notice and this notice must be preserved on all
+copies.  */
+
+/* idict.c */
+/* Dictionaries for Ghostscript */
+#include "ghost.h"
+#include "alloc.h"
+#include "errors.h"
+#include "name.h"
+#include "packed.h"
+#include "save.h"                      /* for value cache in names */
+#include "store.h"
+#include "iutil.h"                     /* for obj_eq */
+#include "dict.h"                      /* interface definition */
+#include "dstack.h"                    /* ditto */
+
+/*
+ * A dictionary is a structure of three elements (refs):
+ *
+ *     count - a t_integer whose value says how many entries are
+ *     occupied (N), and whose size says how many elements the client
+ *     thinks the dictionary can hold (C).  C may be less than M (see below).
+ *
+ *     keys - a t_shortarray or t_array of M+1 elements, containing
+ *     the keys.
+ *
+ *     values - a t_array of M+1 elements, containing the values.
+ *
+ * C < M is possible because on 32-bit systems, we round up M so that
+ * M is a power of 2; this allows us to use masking rather than division
+ * for computing the initial hash probe.  However, C is always the
+ * maxlength specified by the client, so clients get a consistent story.
+ */
+#define dict_round_size (!arch_ints_are_short)
+#if dict_round_size
+#  define hash_mod(hash, size) ((hash) & ((size) - 1))
+#else
+#  define hash_mod(hash, size) ((hash) % (size))
+#endif
+/*
+ * In the packed form, unused or deleted entries contain packed_key_empty
+ * or packed_key_deleted respectively; in the unpacked form, unused
+ * or deleted entries contain a literal or executable null respectively.
+ * The first entry is always marked as a deleted entry, to avoid a
+ * special wrap-around check.
+ *
+ * Note that if the keys slot in the dictionary is new,
+ * all the key slots are new (more recent than the last save).
+ * We use this fact to avoid saving stores into packed keys
+ * for newly created dictionaries.
+ */
+#define dict_is_packed(dct) r_has_type(&(dct)->keys, t_shortarray)
+#define packed_key_empty (pt_tag(pt_integer) + 0)
+#define packed_key_deleted (pt_tag(pt_integer) + 1)
+#define packed_key_impossible pt_tag(pt_full_ref)      /* never matches */
+#define packed_name_key(nidx)\
+  ((nidx) <= packed_max_name_index ? pt_tag(pt_literal_name) + (nidx) :\
+   packed_key_impossible)
+/*
+ * Using a special mark for deleted entries causes lookup time to degrade
+ * as entries are inserted and deleted.  This is not a problem, because
+ * entries are almost never deleted.
+ */
+#define d_maxlength(dct) r_size(&(dct)->count)
+#define d_set_maxlength(dct,siz) r_set_size(&(dct)->count,siz)
+#define nslots(dct) r_size(&(dct)->values)
+#define npairs(dct) (nslots(dct) - 1)
+#define d_length(dct) ((uint)((dct)->count.value.intval))
+
+/* Define the size of the largest valid dictionary. */
+/* This is limited by the size field of the keys and values refs, */
+/* and by the enumeration interface, which requires the size to */
+/* fit in an int. */
+const uint dict_max_size = max_ushort / 2 - 2;
+
+/* Define whether dictionaries expand automatically when full. */
+int dict_auto_expand = 1;
+
+/* Define the hashing function for names. */
+/* We don't have to scramble the index, because */
+/* indices are assigned in a scattered order (see name_ref in iname.c). */
+#define dict_name_index_hash(nidx) (nidx)
+
+/* Define whether dictionaries are packed by default. */
+#define default_pack 1
+
+/* Forward references */
+private int dict_create_contents(P3(uint size, dict *pdict, int pack));
+
+/* Create a dictionary. */
+int
+dict_create(uint size, ref *pref)
+{      dict *pdict =
+         (dict *)alloc_refs(sizeof(dict) / sizeof(ref), "dict_create");
+       int code;
+       if ( pdict == 0 ) return e_VMerror;
+       code = dict_create_contents(size, pdict, default_pack);
+       if ( code < 0 ) return code;
+       make_tav_new(pref, t_dictionary, a_all, pdict, pdict);
+       return 0;
+}
+private int
+dict_create_unpacked_keys(uint asize, dict *pdict)
+{      ref *kp = alloc_refs(asize, "dict_create(keys)");
+       ref *zp;
+       register uint i;
+       if ( kp == 0 ) return e_VMerror;
+       make_tasv_new(&pdict->keys, t_array, a_all, asize,
+                     refs, kp);
+       for ( zp = kp, i = asize; i; zp++, i-- )
+               make_null_new(zp);
+       r_set_attrs(kp, a_executable);  /* wraparound entry */
+       return 0;
+}
+private int
+dict_create_contents(uint size, dict *pdict, int pack)
+{      uint csize = (size == 0 ? 1 : size);    /* client-specified size */
+       uint asize = csize;
+       ref *vp;
+       register uint i;
+       ref *zp;
+#if dict_round_size
+       /* Round up the actual allocated size to the next higher */
+       /* power of 2, so we can use & instead of %. */
+       while ( asize & (asize - 1) ) asize = (asize | (asize >> 1)) + 1;
+#endif
+       asize++;                /* allow room for wraparound entry */
+       vp = alloc_refs(asize, "dict_create(values)");
+       if ( vp == 0 ) return e_VMerror;
+       make_tasv_new(&pdict->values, t_array, a_all, asize, refs, vp);
+       for ( zp = vp, i = asize; i; zp++, i-- )
+               make_null_new(zp);
+       if ( pack )
+          {    uint ksize = (asize + packed_per_ref - 1) / packed_per_ref;
+               ref_packed *pkp = (ref_packed *)alloc_refs(ksize, "dict_create(packed keys)");
+               ref_packed *pzp;
+               make_tasv_new(&pdict->keys, t_shortarray, a_all, asize,
+                             packed, pkp);
+               for ( pzp = pkp, i = 0; i < asize || i % packed_per_ref; pzp++, i++ )
+                       *pzp = packed_key_empty;
+               *pkp = packed_key_deleted;      /* wraparound entry */
+          }
+       else                            /* not packed */
+          {    int code = dict_create_unpacked_keys(asize, pdict);
+               if ( code < 0 ) return code;
+          }
+       make_tv_new(&pdict->count, t_integer, intval, 0);
+       d_set_maxlength(pdict, csize);
+       return 0;
+}
+
+/*
+ * Define a macro for searching a packed dictionary.  Free variables:
+ *     ref_packed kpack - holds the packed key.
+ *     uint hash - holds the hash of the name.
+ *     dict *pdict - points to the dictionary.
+ *     uint size - holds npairs(pdict).
+ *     int wrap - counts wraparounds, initialized to 0.
+ * Note that the macro is *not* enclosed in {}, so that we can access
+ * the values of kbot and kp after leaving the loop.
+ */
+#define packed_search(del,pre,post)\
+   int wrap = 0;\
+   ref_packed *kbot = pdict->keys.value.packed;\
+   register ref_packed *kp;\
+   for ( kp = kbot + hash_mod(hash, size) + 2; ; )\
+    { if ( *--kp == kpack )\
+       { pre (pdict->values.value.refs + (kp - kbot));\
+        post;\
+       }\
+      else if ( !packed_ref_is_name(kp) )\
+       { /* Empty, deleted, or wraparound. Figure out which. */\
+        if ( *kp == packed_key_empty ) break;\
+        if ( kp == kbot )      /* wrap */\
+         { if ( wrap++ ) break;        /* 2 wraps */\
+           kp += size + 1;\
+         }\
+        else { del; }\
+       }\
+    }
+
+/*
+ * Look up in a stack of dictionaries.  Store a pointer to the value slot
+ * where found, or to the (value) slot for inserting.
+ * Return 1 if found, 0 if not and there is room for a new entry in
+ * the top dictionary on the stack, or e_dictfull if the top dictionary
+ * is full and the key is missing.
+ * Note that pdbot <= pdtop, and the search starts at pdtop.
+ */
+int
+dict_lookup(const ref *pdbot, const ref *pdtop, const ref *pkey,
+  ref **ppvalue /* result is stored here */)
+{      const ref *pdref = pdtop;
+       name *kpname;
+       uint nidx;
+       ref_packed kpack;
+       uint hash;
+       int ktype;
+       int full = 1;                   /* gets set to 0 or e_dictfull */
+       /* Compute hash.  The only types we bother with are strings, */
+       /* names, and (unlikely, but worth checking for) integers. */
+       switch ( r_type(pkey) )
+          {
+       case t_name:
+               kpname = pkey->value.pname;
+nh:            nidx = kpname->index;
+               hash = dict_name_index_hash(nidx);
+               kpack = packed_name_key(nidx);
+               ktype = t_name;
+               break;
+       case t_string:                  /* convert to a name first */
+          {    ref nref;
+               int code = name_ref(pkey->value.bytes,
+                                   r_size(pkey), &nref, 1);
+               if ( code < 0 ) return code;
+               kpname = nref.value.pname;
+          }    goto nh;
+       case t_integer:
+               hash = (uint)pkey->value.intval * 30503;
+               kpack = packed_key_impossible;
+               ktype = -1;
+               break;
+       default:
+               hash = r_btype(pkey) * 99;      /* yech */
+               kpack = packed_key_impossible;
+               ktype = -1;
+          }
+       do
+          {    dict *pdict = pdref->value.pdict;
+               uint size = npairs(pdict);
+               int wrap = 0;
+               register int etype;
+               /* Search the dictionary */
+               if ( dict_is_packed(pdict) )
+                  {    ref_packed *pslot = 0;
+                       packed_search(if ( pslot == 0 ) pslot = kp,
+                                     *ppvalue =, return 1);
+                       if ( full > 0 ) /* first dictionary */
+                          {    if ( pslot == 0 )
+                                { if ( wrap == 2 )
+                                    full = e_dictfull;
+                                  else
+                                    *ppvalue = pdict->values.value.refs +
+                                      (kp - kbot),
+                                    full = 0;
+                                }
+                               else
+                                 *ppvalue = pdict->values.value.refs +
+                                   (pslot - kbot),
+                                 full = 0;
+                          }
+                  }
+               else
+                  {    ref *kbot = pdict->keys.value.refs;
+                       register ref *kp;
+                       ref *pslot = 0;
+                       for ( kp = kbot + hash_mod(hash, size) + 2; ; )
+                          {    --kp;
+                               if ( (etype = r_type(kp)) == ktype )
+                                  {    /* Fast comparison if both keys are names */
+                                       if ( kp->value.pname == kpname )
+                                          {    *ppvalue = pdict->values.value.refs + (kp - kbot);
+                                               return 1;
+                                          }
+                                  }
+                               else if ( etype == t_null )
+                                  {    /* Empty, deleted, or wraparound. */
+                                       /* Figure out which. */
+                                       if ( kp == kbot )       /* wrap */
+                                          {    if ( wrap++ )   /* wrapped twice */
+                                                  {    if ( full > 0 )
+                                                          {    if ( pslot != 0 )
+                                                                       break;
+                                                               full = e_dictfull;
+                                                          }
+                                                       goto next_dict;
+                                                  }
+                                               kp += size + 1;
+                                          }
+                                       else if ( r_has_attr(kp, a_executable) )
+                                          {    /* Deleted entry, save the slot. */
+                                               if ( pslot == 0 ) pslot = kp;
+                                          }
+                                       else    /* key not found */
+                                               break;
+                                  }
+                               else
+                                  {    if ( obj_eq(kp, pkey) )
+                                          {    *ppvalue = pdict->values.value.refs + (kp - kbot);
+                                               return 1;
+                                          }
+                                  }
+                          }
+                       if ( full > 0 )
+                          {    *ppvalue = pdict->values.value.refs +
+                                 ((pslot != 0 ? pslot : kp) - kbot);
+                               full = 0;
+                          }
+                  }
+next_dict: ;
+          }
+       while ( --pdref >= pdbot );
+       return full;
+}
+
+/*
+ * Look up a name on the dictionary stack.
+ * Return the pointer to the value if found, 0 if not.
+ * This is just an optimization of dict_lookup with a different interface.
+ */
+ref *
+dict_find_name(ref *pname)
+{      ds_ptr pdref = dsp;
+       name *kpname = pname->value.pname;
+       uint nidx = kpname->index;
+       uint hash = dict_name_index_hash(nidx);
+       ref_packed kpack = packed_name_key(nidx);
+       int wrap = 0;
+       do
+          {    dict *pdict = pdref->value.pdict;
+               uint size = npairs(pdict);
+               if ( dict_is_packed(pdict) )
+                  {    packed_search(0, return, 0);
+                  }
+               else
+                  {    ref *kbot = pdict->keys.value.refs;
+                       register ref *kp;
+                       /* Search the dictionary */
+                       for ( kp = kbot + hash_mod(hash, size) + 2; ; )
+                          {    --kp;
+                               if ( r_has_type(kp, t_name) )
+                                  {    if ( kp->value.pname == kpname )
+                                         return pdict->values.value.refs +
+                                           (kp - kbot);
+                                  }
+                               else if ( r_has_type(kp, t_null) )
+                                  {    /* Empty, deleted, or wraparound. */
+                                       /* Figure out which. */
+                                       if ( !r_has_attr(kp, a_executable) )
+                                               break;
+                                       if ( kp == kbot )       /* wrap */
+                                          {    if ( wrap++ )
+                                                       break;  /* 2 wraps */
+                                               kp += size + 1;
+                                          }
+                                  }
+                          }
+                  }
+          }
+       while ( --pdref >= dstack );
+       return (ref *)0;
+}
+
+/*
+ * Enter a key-value pair in a dictionary.
+ * The caller is responsible for ensuring key is not a null.
+ * Return 0, e_dictfull, or e_VMerror if the key was a string
+ * and a VMerror occurred when converting it to a name.
+ */
+int
+dict_put(ref *pdref /* t_dictionary */, const ref *pkey, const ref *pvalue)
+{      ref *pvslot;
+top:   if ( dict_find(pdref, pkey, &pvslot) <= 0 )     /* not found */
+          {    /* Check for overflow */
+               dict *pdict = pdref->value.pdict;
+               ref kname;
+               uint index = pvslot - pdict->values.value.refs;
+               if ( d_length(pdict) == npairs(pdict) )
+                  {    int code;
+                       ulong new_size;
+                       if ( !dict_auto_expand )
+                               return e_dictfull;
+                       new_size = (ulong)npairs(pdict) * 3 / 2 + 2;
+                       if ( new_size > dict_max_size )
+                          {    if ( npairs(pdict) >= dict_max_size )
+                                       return e_dictfull;
+                               new_size = dict_max_size;
+                          }
+                       code = dict_resize(pdref, (uint)new_size);
+                       if ( code < 0 ) return code;
+                       goto top;       /* keep things simple */
+                  }
+               /* If the key is a string, convert it to a name. */
+               if ( r_has_type(pkey, t_string) )
+                  {    int code = name_from_string(pkey, &kname);
+                       if ( code < 0 ) return code;
+                       pkey = &kname;
+                  }
+               if ( dict_is_packed(pdict) )
+                  {    ref_packed *kp;
+                       if ( !r_has_type(pkey, t_name) ||
+                            name_index(pkey) > packed_max_name_index
+                          )
+                          {    /* Change to unpacked representation. */
+                               /* We can't just use dict_resize, */
+                               /* because the values slots mustn't move. */
+                               uint count = nslots(pdict);
+                               ref_packed *old_keys = pdict->keys.value.packed;
+                               int code;
+                               ref *nkp;
+                               if ( alloc_save_new_mask )
+                                       alloc_save_change(&pdict->keys, "dict_unpack(keys)");
+                               code = dict_create_unpacked_keys(count, pdict);
+                               if ( code < 0 ) return code;
+                               for ( kp = old_keys, nkp = pdict->keys.value.refs; count--; kp++, nkp++ )
+                                 if ( packed_ref_is_name(kp) )
+                                   packed_get(kp, nkp);
+                               alloc_free_refs((ref *)old_keys,
+                                               (count + packed_per_ref - 1) / packed_per_ref,
+                                               "dict_unpack(old keys)");
+                               return dict_put(pdref, pkey, pvalue);
+                          }
+                       kp = pdict->keys.value.packed + index;
+                       if ( alloc_save_new_mask &&
+                            !r_has_attr(&pdict->keys, l_new)
+                          )
+                          {    /* See initial comment for why it is safe */
+                               /* not to save the change if the keys */
+                               /* array itself is new. */
+                               alloc_save_change(pdict->keys.value.refs + (index / packed_per_ref), "dict_put(key)");
+                          }
+                       *kp = pt_tag(pt_literal_name) + name_index(pkey);
+                  }
+               else
+                  {    ref *kp = pdict->keys.value.refs + index;
+                       if_debug2('d', "[d]%lx fill key %lx\n",
+                                 (ulong)pdict, (ulong)kp);
+                       ref_assign_old(kp, pkey, "dict_put(key)");      /* set key of pair */
+                  }
+               ref_save(&pdict->count, "dict_put(count)");
+               pdict->count.value.intval++;
+               /* If the key is a name, update its 1-element cache. */
+               if ( r_has_type(pkey, t_name) )
+                  {    name *pname = pkey->value.pname;
+                       if ( pname->pvalue == pv_no_defn &&
+                               (pdict == systemdict.value.pdict ||
+                                pdict == userdict.value.pdict) &&
+                               /* Only set the cache if we aren't inside */
+                               /* a save.  This way, we never have to */
+                               /* undo setting the cache. */
+                               alloc_save_level() == 0
+                          )
+                          {    /* Set the cache */
+                               pname->pvalue = pvslot;
+                          }
+                       else    /* The cache is worthless */
+                               pname->pvalue = pv_other;
+                  }
+          }
+       if_debug6('d', "[d]in %lx put %lx: %lx %lx -> %lx %lx\n",
+                 (ulong)pdref->value.pdict, (ulong)pvslot,
+                 ((ulong *)pvslot)[0], ((ulong *)pvslot)[1],
+                 ((ulong *)pvalue)[0], ((ulong *)pvalue)[1]);
+       ref_assign_old(pvslot, pvalue, "dict_put(value)");
+       return 0;
+}
+
+/* Remove an element from a dictionary. */
+int
+dict_undef(ref *pdref, const ref *pkey)
+{      ref *pvslot;
+       dict *pdict;
+       uint index;
+       if ( dict_find(pdref, pkey, &pvslot) <= 0 )
+               return e_undefined;
+       /* Remove the entry from the dictionary. */
+       pdict = pdref->value.pdict;
+       index = pvslot - pdict->values.value.refs;
+       if ( dict_is_packed(pdict) )
+          {    ref_packed *pkp = pdict->keys.value.packed + index;
+               /* Since packed arrays don't have room for a saved bit, */
+               /* always save the entire ref containing this key. */
+               /* This wastes a little space, but undef is rare. */
+               /* See the initial comment for why it is safe not to save */
+               /* the change if the keys array itself is new. */
+               if ( alloc_save_new_mask && !r_has_attr(&pdict->keys, l_new) )
+                       alloc_save_change(pdict->keys.value.refs + (index / packed_per_ref), "dict_undef(key)");
+               /* Accumulating deleted entries slows down lookup. */
+               /* Detect the easy case where we can use an empty entry */
+               /* rather than a deleted one, namely, when the next entry */
+               /* in the probe order is empty. */
+               if ( pkp[-1] == packed_key_empty )
+                       *pkp = packed_key_empty;
+               else
+                       *pkp = packed_key_deleted;
+          }
+       else                            /* not packed */
+          {    ref *kp = pdict->keys.value.refs + index;
+               make_null_old(kp, "dict_undef(key)");
+               /* Accumulating deleted entries slows down lookup. */
+               /* Detect the easy case where we can use an empty entry */
+               /* rather than a deleted one, namely, when the next entry */
+               /* in the probe order is empty. */
+               if ( !r_has_type(kp - 1, t_null) ||     /* full entry */
+                    r_has_attr(kp - 1, a_executable)   /* deleted or wraparound */
+                   )
+                       r_set_attrs(kp, a_executable);  /* mark as deleted */
+          }
+       ref_save(&pdict->count, "dict_undef(count)");
+       pdict->count.value.intval--;
+       /* If the key is a name, update its 1-element cache. */
+       if ( r_has_type(pkey, t_name) )
+          {    name *pname = pkey->value.pname;
+               if ( pv_valid(pname->pvalue) &&
+                       (pdict == systemdict.value.pdict ||
+                        pdict == userdict.value.pdict) )
+                  {    /* Clear the cache */
+                       pname->pvalue = pv_no_defn;
+                  }
+          }
+       make_null_old(pvslot, "dict_undef(value)");
+       return 0;
+}
+
+/* Return the number of elements in a dictionary. */
+uint
+dict_length(const ref *pdref /* t_dictionary */)
+{      return d_length(pdref->value.pdict);
+}
+
+/* Return the capacity of a dictionary. */
+uint
+dict_maxlength(const ref *pdref /* t_dictionary */)
+{      return d_maxlength(pdref->value.pdict);
+}
+
+/* Copy one dictionary into another. */
+int
+dict_copy(const ref *pdrfrom /* t_dictionary */, ref *pdrto /* t_dictionary */)
+{      int index = dict_first(pdrfrom);
+       ref elt[2];
+       int code;
+       while ( (index = dict_next(pdrfrom, index, elt)) >= 0 )
+         if ( (code = dict_put(pdrto, &elt[0], &elt[1])) < 0 )
+           return code;
+       return 0;
+}
+
+/* Resize a dictionary. */
+int
+dict_resize(ref *pdrfrom, uint new_size)
+{      dict *pdict = pdrfrom->value.pdict;
+       uint count = nslots(pdict);
+       dict dnew;
+       ref drto;
+       int code;
+       if ( new_size < d_length(pdict) )
+          {    if ( !dict_auto_expand )
+                       return e_dictfull;
+               new_size = d_length(pdict);
+          }
+       if ( (code = dict_create_contents(new_size, &dnew, dict_is_packed(pdict))) < 0 )
+               return code;
+       make_tav_new(&drto, t_dictionary, a_all, pdict, &dnew);
+       dict_copy(pdrfrom, &drto);      /* can't fail */
+       /* Free the old dictionary */
+       alloc_free_refs(pdict->values.value.refs, count,
+                       "dict_resize(old values)");
+       alloc_free_refs(pdict->keys.value.refs,
+                       (dict_is_packed(pdict) ?
+                        (count + packed_per_ref - 1) / packed_per_ref :
+                        count),
+                       "dict_resize(old keys)");
+       ref_assign_old(&pdict->keys, &dnew.keys, "dict_resize(keys)");
+       ref_assign_old(&pdict->values, &dnew.values, "dict_resize(values)");
+       ref_save(&pdict->count, "dict_resize(size)");
+       d_set_maxlength(pdict, new_size);
+       return 0;
+}
+
+/* Prepare to enumerate a dictionary. */
+int
+dict_first(const ref *pdref)
+{      return (int)nslots(pdref->value.pdict);
+}
+
+/* Enumerate the next element of a dictionary. */
+int
+dict_next(const ref *pdref, int index, ref *eltp /* ref eltp[2] */)
+{      dict *pdict = pdref->value.pdict;
+       ref *vp = pdict->values.value.refs + index;
+       while ( vp--, --index >= 0 )
+          {    array_get(&pdict->keys, (long)index, eltp);
+               /* Make sure this is a valid entry. */
+               if ( r_has_type(eltp, t_name) ||
+                    (!dict_is_packed(pdict) && !r_has_type(eltp, t_null))
+                  )
+                  {    eltp[1] = *vp;
+                       return index;
+                  }
+          }
+       return -1;                      /* no more elements */
+}