Mercurial > hg > xemacs-beta
diff src/elhash.c @ 5191:71ee43b8a74d
Add #'equalp as a hash test by default; add #'define-hash-table-test, GNU API
tests/ChangeLog addition:
2010-04-05 Aidan Kehoe <kehoea@parhasard.net>
* automated/hash-table-tests.el:
Test the new built-in #'equalp hash table test. Test
#'define-hash-table-test.
* automated/lisp-tests.el:
When asserting that two objects are #'equalp, also assert that
their #'equalp-hash is identical.
man/ChangeLog addition:
2010-04-03 Aidan Kehoe <kehoea@parhasard.net>
* lispref/hash-tables.texi (Introduction to Hash Tables):
Document that we now support #'equalp as a hash table test by
default, and mention #'define-hash-table-test.
(Working With Hash Tables): Document #'define-hash-table-test.
src/ChangeLog addition:
2010-04-05 Aidan Kehoe <kehoea@parhasard.net>
* elhash.h:
* elhash.c (struct Hash_Table_Test, lisp_object_eql_equal)
(lisp_object_eql_hash, lisp_object_equal_equal)
(lisp_object_equal_hash, lisp_object_equalp_hash)
(lisp_object_equalp_equal, lisp_object_general_hash)
(lisp_object_general_equal, Feq_hash, Feql_hash, Fequal_hash)
(Fequalp_hash, define_hash_table_test, Fdefine_hash_table_test)
(init_elhash_once_early, mark_hash_table_tests, string_equalp_hash):
* glyphs.c (vars_of_glyphs):
Add a new hash table test in C, #'equalp.
Make it possible to specify new hash table tests with functions
define_hash_table_test, #'define-hash-table-test.
Use define_hash_table_test() in glyphs.c.
Expose the hash functions (besides that used for #'equal) to Lisp,
for people writing functions to be used with #'define-hash-table-test.
Call define_hash_table_test() very early in temacs, to create the
built-in hash table tests.
* ui-gtk.c (emacs_gtk_boxed_hash):
* specifier.h (struct specifier_methods):
* specifier.c (specifier_hash):
* rangetab.c (range_table_entry_hash, range_table_hash):
* number.c (bignum_hash, ratio_hash, bigfloat_hash):
* marker.c (marker_hash):
* lrecord.h (struct lrecord_implementation):
* keymap.c (keymap_hash):
* gui.c (gui_item_id_hash, gui_item_hash):
* glyphs.c (image_instance_hash, glyph_hash):
* glyphs-x.c (x_image_instance_hash):
* glyphs-msw.c (mswindows_image_instance_hash):
* glyphs-gtk.c (gtk_image_instance_hash):
* frame-msw.c (mswindows_set_title_from_ibyte):
* fontcolor.c (color_instance_hash, font_instance_hash):
* fontcolor-x.c (x_color_instance_hash):
* fontcolor-tty.c (tty_color_instance_hash):
* fontcolor-msw.c (mswindows_color_instance_hash):
* fontcolor-gtk.c (gtk_color_instance_hash):
* fns.c (bit_vector_hash):
* floatfns.c (float_hash):
* faces.c (face_hash):
* extents.c (extent_hash):
* events.c (event_hash):
* data.c (weak_list_hash, weak_box_hash):
* chartab.c (char_table_entry_hash, char_table_hash):
* bytecode.c (compiled_function_hash):
* alloc.c (vector_hash):
Change the various object hash methods to take a new EQUALP
parameter, hashing appropriately for #'equalp if it is true.
author | Aidan Kehoe <kehoea@parhasard.net> |
---|---|
date | Mon, 05 Apr 2010 13:03:35 +0100 |
parents | 6c6d78781d59 |
children | 41ac827cb71b |
line wrap: on
line diff
--- a/src/elhash.c Mon Apr 05 00:18:49 2010 -0500 +++ b/src/elhash.c Mon Apr 05 13:03:35 2010 +0100 @@ -83,18 +83,69 @@ #include "elhash.h" #include "gc.h" #include "opaque.h" +#include "buffer.h" Lisp_Object Qhash_tablep; +Lisp_Object Qeq, Qeql, Qequal, Qequalp; +Lisp_Object Qeq_hash, Qeql_hash, Qequal_hash, Qequalp_hash; + static Lisp_Object Qhashtable, Qhash_table, Qmake_hash_table; static Lisp_Object Qweakness, Qvalue, Qkey_or_value, Qkey_and_value; static Lisp_Object Vall_weak_hash_tables; static Lisp_Object Qrehash_size, Qrehash_threshold; static Lisp_Object Q_size, Q_test, Q_weakness, Q_rehash_size, Q_rehash_threshold; +static Lisp_Object Vhash_table_test_eq, Vhash_table_test_eql; +static Lisp_Object Vhash_table_test_weak_list; /* obsolete as of 19990901 in xemacs-21.2 */ static Lisp_Object Qweak, Qkey_weak, Qvalue_weak, Qkey_or_value_weak; static Lisp_Object Qnon_weak, Q_type, Q_data; +/* A hash table test, with its associated hash function. equal_function may + call lisp_equal_function, and hash_function similarly may call + lisp_hash_function. */ +struct Hash_Table_Test +{ + NORMAL_LISP_OBJECT_HEADER header; + Lisp_Object name; + hash_table_equal_function_t equal_function; + hash_table_hash_function_t hash_function; + Lisp_Object lisp_equal_function; + Lisp_Object lisp_hash_function; +}; + +static Lisp_Object +mark_hash_table_test (Lisp_Object obj) +{ + Hash_Table_Test *http = XHASH_TABLE_TEST (obj); + + mark_object (http->name); + mark_object (http->lisp_equal_function); + mark_object (http->lisp_hash_function); + + return Qnil; +} + +static const struct memory_description hash_table_test_description_1[] = + { + { XD_LISP_OBJECT, offsetof (struct Hash_Table_Test, name) }, + { XD_LISP_OBJECT, offsetof (struct Hash_Table_Test, lisp_equal_function) }, + { XD_LISP_OBJECT, offsetof (struct Hash_Table_Test, lisp_hash_function) }, + { XD_END } + }; + +static const struct sized_memory_description hash_table_test_description = + { + sizeof (struct Hash_Table_Test), + hash_table_test_description_1 + }; + +DEFINE_DUMPABLE_INTERNAL_LISP_OBJECT ("hash-table-test", hash_table_test, + mark_hash_table_test, + hash_table_test_description_1, + Hash_Table_Test); +/* A hash table. */ + struct Lisp_Hash_Table { NORMAL_LISP_OBJECT_HEADER header; @@ -104,9 +155,8 @@ double rehash_size; double rehash_threshold; Elemcount golden_ratio; - hash_table_hash_function_t hash_function; - hash_table_test_function_t test_function; htentry *hentries; + Lisp_Object test; enum hash_table_weakness weakness; Lisp_Object next_weak; /* Used to chain together all of the weak hash tables. Don't mark through this. */ @@ -119,16 +169,17 @@ #define HASH_TABLE_DEFAULT_SIZE 16 #define HASH_TABLE_DEFAULT_REHASH_SIZE 1.3 #define HASH_TABLE_MIN_SIZE 10 -#define HASH_TABLE_DEFAULT_REHASH_THRESHOLD(size, test_function) \ - (((size) > 4096 && NULL == (test_function)) ? 0.7 : 0.6) +#define HASH_TABLE_DEFAULT_REHASH_THRESHOLD(size, test) \ + (((size) > 4096 && EQ (Vhash_table_test_eq, test)) ? 0.7 : 0.6) -#define HASHCODE(key, ht) \ - ((((ht)->hash_function ? (ht)->hash_function (key) : LISP_HASH (key)) \ - * (ht)->golden_ratio) \ - % (ht)->size) +#define HASHCODE(key, ht, http) \ + ((((!EQ (Vhash_table_test_eq, ht->test)) ? \ + (http)->hash_function (http, key) : \ + LISP_HASH (key)) * (ht)->golden_ratio) % (ht)->size) -#define KEYS_EQUAL_P(key1, key2, testfun) \ - (EQ (key1, key2) || ((testfun) && (testfun) (key1, key2))) +#define KEYS_EQUAL_P(key1, key2, test, http) \ + (EQ (key1, key2) || ((!EQ (Vhash_table_test_eq, test) && \ + (http->equal_function) (http, key1, key2)))) #define LINEAR_PROBING_LOOP(probe, entries, size) \ for (; \ @@ -187,28 +238,92 @@ static int -lisp_object_eql_equal (Lisp_Object obj1, Lisp_Object obj2) +lisp_object_eql_equal (const Hash_Table_Test *UNUSED (http), Lisp_Object obj1, + Lisp_Object obj2) { return EQ (obj1, obj2) || (NON_FIXNUM_NUMBER_P (obj1) && internal_equal (obj1, obj2, 0)); } static Hashcode -lisp_object_eql_hash (Lisp_Object obj) +lisp_object_eql_hash (const Hash_Table_Test *UNUSED (http), Lisp_Object obj) { - return NON_FIXNUM_NUMBER_P (obj) ? internal_hash (obj, 0) : LISP_HASH (obj); + return NON_FIXNUM_NUMBER_P (obj) ? + internal_hash (obj, 0, 0) : LISP_HASH (obj); } static int -lisp_object_equal_equal (Lisp_Object obj1, Lisp_Object obj2) +lisp_object_equal_equal (const Hash_Table_Test *UNUSED (http), + Lisp_Object obj1, Lisp_Object obj2) { return internal_equal (obj1, obj2, 0); } static Hashcode -lisp_object_equal_hash (Lisp_Object obj) +lisp_object_equal_hash (const Hash_Table_Test *UNUSED (http), Lisp_Object obj) +{ + return internal_hash (obj, 0, 0); +} + +static Hashcode +lisp_object_equalp_hash (const Hash_Table_Test *UNUSED (http), Lisp_Object obj) +{ + return internal_hash (obj, 0, 1); +} + +static int +lisp_object_equalp_equal (const Hash_Table_Test *UNUSED (http), + Lisp_Object obj1, Lisp_Object obj2) +{ + return internal_equalp (obj1, obj2, 0); +} + +static Hashcode +lisp_object_general_hash (const Hash_Table_Test *http, Lisp_Object obj) { - return internal_hash (obj, 0); + struct gcpro gcpro1; + Lisp_Object args[2] = { http->lisp_hash_function, obj }, res; + + /* Make sure any weakly referenced objects don't get collected before the + funcall: */ + GCPRO1 (args[0]); + gcpro1.nvars = countof (args); + res = IGNORE_MULTIPLE_VALUES (Ffuncall (countof (args), args)); + UNGCPRO; + + if (INTP (res)) + { + return (Hashcode) (XINT (res)); + } + +#ifdef HAVE_BIGNUM + if (BIGNUMP (res)) + { + if (bignum_fits_emacs_int_p (XBIGNUM_DATA (res))) + { + return (Hashcode) bignum_to_emacs_int (XBIGNUM_DATA (res)); + } + + signal_error (Qrange_error, "Not a valid hash code", res); + } +#endif + + dead_wrong_type_argument (Qintegerp, res); +} + +static int +lisp_object_general_equal (const Hash_Table_Test *http, Lisp_Object obj1, + Lisp_Object obj2) +{ + struct gcpro gcpro1; + Lisp_Object args[] = { http->lisp_equal_function, obj1, obj2 }, res; + + GCPRO1 (args[0]); + gcpro1.nvars = countof (args); + res = IGNORE_MULTIPLE_VALUES (Ffuncall (countof (args), args)); + UNGCPRO; + + return !(NILP (res)); } @@ -231,6 +346,9 @@ mark_object (e->value); } } + + mark_object (ht->test); + return Qnil; } @@ -252,8 +370,8 @@ Lisp_Hash_Table *ht2 = XHASH_TABLE (hash_table2); htentry *e, *sentinel; - if ((ht1->test_function != ht2->test_function) || - (ht1->weakness != ht2->weakness) || + if (!(EQ (ht1->test, ht2->test)) || + (ht1->weakness != ht2->weakness) || (ht1->count != ht2->count)) return 0; @@ -276,7 +394,8 @@ Examining all entries is too expensive, and examining a random subset does not yield a correct hash function. */ static Hashcode -hash_table_hash (Lisp_Object hash_table, int UNUSED (depth)) +hash_table_hash (Lisp_Object hash_table, int UNUSED (depth), + int UNUSED (equalp)) { return XHASH_TABLE (hash_table)->count; } @@ -366,17 +485,11 @@ write_ascstring (printcharfun, print_readably ? "#s(hash-table" : "#<hash-table"); - /* These checks have a kludgy look to them, but they are safe. - Due to nature of hashing, you cannot use arbitrary - test functions anyway. */ - if (!ht->test_function) - write_ascstring (printcharfun, " :test eq"); - else if (ht->test_function == lisp_object_equal_equal) - write_ascstring (printcharfun, " :test equal"); - else if (ht->test_function == lisp_object_eql_equal) - DO_NOTHING; - else - ABORT (); + if (!(EQ (ht->test, Vhash_table_test_eql))) + { + write_fmt_string_lisp (printcharfun, " :test %S", + 1, XHASH_TABLE_TEST (ht->test)->name); + } if (ht->count || !print_readably) { @@ -405,8 +518,7 @@ } if (ht->rehash_threshold - != HASH_TABLE_DEFAULT_REHASH_THRESHOLD (ht->size, - ht->test_function)) + != HASH_TABLE_DEFAULT_REHASH_THRESHOLD (ht->size, ht->test)) { float_to_string (pigbuf, ht->rehash_threshold); write_fmt_string (printcharfun, " :rehash-threshold %s", pigbuf); @@ -507,6 +619,7 @@ { XD_UNION, offsetof (Lisp_Hash_Table, hentries), XD_INDIRECT (1, 0), { &htentry_union_description } }, { XD_LO_LINK, offsetof (Lisp_Hash_Table, next_weak) }, + { XD_LISP_OBJECT,offsetof (Lisp_Hash_Table, test) }, { XD_END } }; @@ -553,45 +666,10 @@ #endif /* not NEW_GC */ } -Lisp_Object -make_standard_lisp_hash_table (enum hash_table_test test, - Elemcount size, - double rehash_size, - double rehash_threshold, - enum hash_table_weakness weakness) -{ - hash_table_hash_function_t hash_function = 0; - hash_table_test_function_t test_function = 0; - - switch (test) - { - case HASH_TABLE_EQ: - test_function = 0; - hash_function = 0; - break; - - case HASH_TABLE_EQL: - test_function = lisp_object_eql_equal; - hash_function = lisp_object_eql_hash; - break; - - case HASH_TABLE_EQUAL: - test_function = lisp_object_equal_equal; - hash_function = lisp_object_equal_hash; - break; - - default: - ABORT (); - } - - return make_general_lisp_hash_table (hash_function, test_function, - size, rehash_size, rehash_threshold, - weakness); -} +static Lisp_Object decode_hash_table_test (Lisp_Object obj); Lisp_Object -make_general_lisp_hash_table (hash_table_hash_function_t hash_function, - hash_table_test_function_t test_function, +make_general_lisp_hash_table (Lisp_Object test, Elemcount size, double rehash_size, double rehash_threshold, @@ -600,8 +678,9 @@ Lisp_Object hash_table = ALLOC_NORMAL_LISP_OBJECT (hash_table); Lisp_Hash_Table *ht = XHASH_TABLE (hash_table); - ht->test_function = test_function; - ht->hash_function = hash_function; + assert (HASH_TABLE_TESTP (test)); + + ht->test = test; ht->weakness = weakness; ht->rehash_size = @@ -609,7 +688,7 @@ ht->rehash_threshold = rehash_threshold > 0.0 ? rehash_threshold : - HASH_TABLE_DEFAULT_REHASH_THRESHOLD (size, ht->test_function); + HASH_TABLE_DEFAULT_REHASH_THRESHOLD (size, ht->test); if (size < HASH_TABLE_MIN_SIZE) size = HASH_TABLE_MIN_SIZE; @@ -631,11 +710,11 @@ } Lisp_Object -make_lisp_hash_table (Elemcount size, - enum hash_table_weakness weakness, - enum hash_table_test test) +make_lisp_hash_table (Elemcount size, enum hash_table_weakness weakness, + Lisp_Object test) { - return make_standard_lisp_hash_table (test, size, -1.0, -1.0, weakness); + test = decode_hash_table_test (test); + return make_general_lisp_hash_table (test, size, -1.0, -1.0, weakness); } /* Pretty reading of hash tables. @@ -678,12 +757,14 @@ if (EQ (value, Qkey_or_value)) return 1; if (EQ (value, Qvalue)) return 1; +#ifndef NO_NEED_TO_HANDLE_21_4_CODE /* Following values are obsolete as of 19990901 in xemacs-21.2 */ if (EQ (value, Qnon_weak)) return 1; if (EQ (value, Qweak)) return 1; if (EQ (value, Qkey_weak)) return 1; if (EQ (value, Qkey_or_value_weak)) return 1; if (EQ (value, Qvalue_weak)) return 1; +#endif maybe_invalid_constant ("Invalid hash table weakness", value, Qhash_table, errb); @@ -700,12 +781,14 @@ if (EQ (obj, Qkey_or_value)) return HASH_TABLE_KEY_VALUE_WEAK; if (EQ (obj, Qvalue)) return HASH_TABLE_VALUE_WEAK; +#ifndef NO_NEED_TO_HANDLE_21_4_CODE /* Following values are obsolete as of 19990901 in xemacs-21.2 */ if (EQ (obj, Qnon_weak)) return HASH_TABLE_NON_WEAK; if (EQ (obj, Qweak)) return HASH_TABLE_WEAK; if (EQ (obj, Qkey_weak)) return HASH_TABLE_KEY_WEAK; if (EQ (obj, Qkey_or_value_weak)) return HASH_TABLE_KEY_VALUE_WEAK; if (EQ (obj, Qvalue_weak)) return HASH_TABLE_VALUE_WEAK; +#endif invalid_constant ("Invalid hash table weakness", obj); RETURN_NOT_REACHED (HASH_TABLE_NON_WEAK); @@ -715,26 +798,40 @@ hash_table_test_validate (Lisp_Object UNUSED (keyword), Lisp_Object value, Error_Behavior errb) { - if (EQ (value, Qnil)) return 1; - if (EQ (value, Qeq)) return 1; - if (EQ (value, Qequal)) return 1; - if (EQ (value, Qeql)) return 1; + Lisp_Object lookup; + + if (NILP (value)) + { + return 1; + } - maybe_invalid_constant ("Invalid hash table test", - value, Qhash_table, errb); - return 0; + lookup = Fassq (value, XWEAK_LIST_LIST (Vhash_table_test_weak_list)); + if (NILP (lookup)) + { + maybe_invalid_constant ("Invalid hash table test", + value, Qhash_table, errb); + } + + return 1; } -static enum hash_table_test +static Lisp_Object decode_hash_table_test (Lisp_Object obj) { - if (EQ (obj, Qnil)) return HASH_TABLE_EQL; - if (EQ (obj, Qeq)) return HASH_TABLE_EQ; - if (EQ (obj, Qequal)) return HASH_TABLE_EQUAL; - if (EQ (obj, Qeql)) return HASH_TABLE_EQL; + Lisp_Object result; + + if (NILP (obj)) + { + obj = Qeql; + } - invalid_constant ("Invalid hash table test", obj); - RETURN_NOT_REACHED (HASH_TABLE_EQ); + result = Fassq (obj, XWEAK_LIST_LIST (Vhash_table_test_weak_list)); + if (NILP (result)) + { + invalid_constant ("Invalid hash table test", obj); + } + + return XCDR (result); } static int @@ -865,7 +962,9 @@ else if (EQ (key, Qrehash_threshold)) rehash_threshold = value; else if (EQ (key, Qweakness)) weakness = value; else if (EQ (key, Qdata)) data = value; +#ifndef NO_NEED_TO_HANDLE_21_4_CODE else if (EQ (key, Qtype))/*obsolete*/ weakness = value; +#endif else if (KEYWORDP (key)) signal_error (Qinvalid_read_syntax, "can't mix keyword and non-keyword hash table syntax", @@ -875,14 +974,14 @@ } /* Create the hash table. */ - hash_table = make_standard_lisp_hash_table + hash_table = make_general_lisp_hash_table (decode_hash_table_test (test), decode_hash_table_size (size), decode_hash_table_rehash_size (rehash_size), decode_hash_table_rehash_threshold (rehash_threshold), decode_hash_table_weakness (weakness)); - /* I'm not sure whether this can GC, but better safe than sorry. */ + /* This can GC with a user-specified test. */ { struct gcpro gcpro1; GCPRO1 (hash_table); @@ -924,8 +1023,10 @@ define_structure_type_keyword (st, Qweakness, hash_table_weakness_validate); define_structure_type_keyword (st, Qdata, hash_table_data_validate); +#ifndef NO_NEED_TO_HANDLE_21_4_CODE /* obsolete as of 19990901 in xemacs-21.2 */ define_structure_type_keyword (st, Qtype, hash_table_weakness_validate); +#endif } /* Create a built-in Lisp structure type named `hash-table'. @@ -956,10 +1057,13 @@ Return a new empty hash table object. Use Common Lisp style keywords to specify hash table properties. -Keyword :test can be `eq', `eql' (default) or `equal'. -Comparison between keys is done using this function. -If speed is important, consider using `eq'. -When storing strings in the hash table, you will likely need to use `equal'. +Keyword :test can be `eq', `eql' (default), `equal' or `equalp'. +Comparison between keys is done using this function. If speed is important, +consider using `eq'. When storing strings in the hash table, you will +likely need to use `equal' or `equalp' (for case-insensitivity). With other +objects, consider using a test function defined with +`define-hash-table-test', an emacs extension to this Common Lisp hash table +API. Keyword :size specifies the number of keys likely to be inserted. This number of entries can be inserted without enlarging the hash table. @@ -1006,7 +1110,7 @@ #ifdef NO_NEED_TO_HANDLE_21_4_CODE PARSE_KEYWORDS (Qmake_hash_table, nargs, args, 0, 5, (test, size, rehash_size, rehash_threshold, weakness), - NULL, weakness = Qunbound), 0); + NULL, 0); #else PARSE_KEYWORDS (Qmake_hash_table, nargs, args, 0, 6, (test, size, rehash_size, rehash_threshold, weakness, @@ -1034,7 +1138,7 @@ VALIDATE_VAR (rehash_threshold); VALIDATE_VAR (weakness); - return make_standard_lisp_hash_table + return make_general_lisp_hash_table (decode_hash_table_test (test), decode_hash_table_size (size), decode_hash_table_rehash_size (rehash_size), @@ -1071,6 +1175,7 @@ { htentry *old_entries, *new_entries, *sentinel, *e; Elemcount old_size; + Hash_Table_Test *http = XHASH_TABLE_TEST (ht->test); old_size = ht->size; ht->size = new_size; @@ -1086,7 +1191,7 @@ for (e = old_entries, sentinel = e + old_size; e < sentinel; e++) if (!HTENTRY_CLEAR_P (e)) { - htentry *probe = new_entries + HASHCODE (e->key, ht); + htentry *probe = new_entries + HASHCODE (e->key, ht, http); LINEAR_PROBING_LOOP (probe, new_entries, new_size) ; *probe = *e; @@ -1107,11 +1212,12 @@ /* We leave room for one never-occupied sentinel htentry at the end. */ htentry *new_entries = allocate_hash_table_entries (ht->size + 1); htentry *e, *sentinel; + Hash_Table_Test *http = XHASH_TABLE_TEST (ht->test); for (e = ht->hentries, sentinel = e + ht->size; e < sentinel; e++) if (!HTENTRY_CLEAR_P (e)) { - htentry *probe = new_entries + HASHCODE (e->key, ht); + htentry *probe = new_entries + HASHCODE (e->key, ht, http); LINEAR_PROBING_LOOP (probe, new_entries, ht->size) ; *probe = *e; @@ -1135,19 +1241,21 @@ htentry * find_htentry (Lisp_Object key, const Lisp_Hash_Table *ht) { - hash_table_test_function_t test_function = ht->test_function; + Lisp_Object test = ht->test; + Hash_Table_Test *http = XHASH_TABLE_TEST (test); + htentry *entries = ht->hentries; - htentry *probe = entries + HASHCODE (key, ht); + htentry *probe = entries + HASHCODE (key, ht, http); LINEAR_PROBING_LOOP (probe, entries, ht->size) - if (KEYS_EQUAL_P (probe->key, key, test_function)) + if (KEYS_EQUAL_P (probe->key, key, test, http)) break; return probe; } /* A version of Fputhash() that increments the value by the specified - amount and dispenses will all error checks. Assumes that tables does + amount and dispenses with all error checks. Assumes that tables does comparison using EQ. Used by the profiling routines to avoid overhead -- profiling overhead was being recorded at up to 15% of the total time. */ @@ -1156,8 +1264,9 @@ inchash_eq (Lisp_Object key, Lisp_Object table, EMACS_INT offset) { Lisp_Hash_Table *ht = XHASH_TABLE (table); + Hash_Table_Test *http = XHASH_TABLE_TEST (ht->test); htentry *entries = ht->hentries; - htentry *probe = entries + HASHCODE (key, ht); + htentry *probe = entries + HASHCODE (key, ht, http); LINEAR_PROBING_LOOP (probe, entries, ht->size) if (EQ (probe->key, key)) @@ -1213,6 +1322,7 @@ static void remhash_1 (Lisp_Hash_Table *ht, htentry *entries, htentry *probe) { + Hash_Table_Test *http = XHASH_TABLE_TEST (ht->test); Elemcount size = ht->size; CLEAR_HTENTRY (probe); probe++; @@ -1221,7 +1331,7 @@ LINEAR_PROBING_LOOP (probe, entries, size) { Lisp_Object key = probe->key; - htentry *probe2 = entries + HASHCODE (key, ht); + htentry *probe2 = entries + HASHCODE (key, ht, http); LINEAR_PROBING_LOOP (probe2, entries, size) if (EQ (probe2->key, key)) /* htentry at probe doesn't need to move. */ @@ -1279,16 +1389,15 @@ } DEFUN ("hash-table-test", Fhash_table_test, 1, 1, 0, /* -Return the test function of HASH-TABLE. -This can be one of `eq', `eql' or `equal'. +Return HASH-TABLE's test. + +This can be one of `eq', `eql', `equal', `equalp', or some symbol supplied +as the NAME argument to `define-hash-table-test', which see. */ (hash_table)) { - hash_table_test_function_t fun = xhash_table (hash_table)->test_function; - - return (fun == lisp_object_eql_equal ? Qeql : - fun == lisp_object_equal_equal ? Qequal : - Qeq); + CHECK_HASH_TABLE (hash_table); + return XHASH_TABLE_TEST (XHASH_TABLE (hash_table)->test)->name; } DEFUN ("hash-table-size", Fhash_table_size, 1, 1, 0, /* @@ -1711,7 +1820,7 @@ /* Return a hash value for an array of Lisp_Objects of size SIZE. */ Hashcode -internal_array_hash (Lisp_Object *arr, int size, int depth) +internal_array_hash (Lisp_Object *arr, int size, int depth, Boolint equalp) { int i; Hashcode hash = 0; @@ -1720,7 +1829,7 @@ if (size <= 5) { for (i = 0; i < size; i++) - hash = HASH2 (hash, internal_hash (arr[i], depth)); + hash = HASH2 (hash, internal_hash (arr[i], depth, equalp)); return hash; } @@ -1728,11 +1837,78 @@ A slightly better approach would be to offset by some noise factor from the points chosen below. */ for (i = 0; i < 5; i++) - hash = HASH2 (hash, internal_hash (arr[i*size/5], depth)); + hash = HASH2 (hash, internal_hash (arr[i*size/5], depth, equalp)); return hash; } +/* This needs to be algorithmically the same as + internal_array_hash(). Unfortunately, for strings with non-ASCII content, + it has to be O(2N), I don't see a reasonable alternative to hashing + sequence relying on their length. It is O(1) for pure ASCII strings, + though. */ + +static Hashcode +string_equalp_hash (Lisp_Object string) +{ + Bytecount len = XSTRING_LENGTH (string), + ascii_begin = (Bytecount) XSTRING_ASCII_BEGIN (string); + const Ibyte *ptr = XSTRING_DATA (string), *pend = ptr + len; + Charcount clen; + Hashcode hash = 0; + + if (len == ascii_begin) + { + clen = len; + } + else + { + clen = string_char_length (string); + } + + if (clen <= 5) + { + while (ptr < pend) + { + hash = HASH2 (hash, + LISP_HASH (make_char (CANONCASE (NULL, + itext_ichar (ptr))))); + INC_IBYTEPTR (ptr); + } + } + else + { + int ii; + + if (clen == len) + { + for (ii = 0; ii < 5; ii++) + { + hash = HASH2 (hash, + LISP_HASH (make_char + (CANONCASE (NULL, + ptr[ii * clen / 5])))); + } + } + else + { + Charcount this_char = 0, last_char = 0; + for (ii = 0; ii < 5; ii++) + { + this_char = ii * clen / 5; + ptr = itext_n_addr (ptr, this_char - last_char); + last_char = this_char; + + hash = HASH2 (hash, + LISP_HASH (make_char + (CANONCASE (NULL, itext_ichar (ptr))))); + } + } + } + + return HASH2 (clen, hash); +} + /* Return a hash value for a Lisp_Object. This is for use when hashing objects with the comparison being `equal' (for `eq', you can just use the Lisp_Object itself as the hash value). You need to make a @@ -1746,7 +1922,7 @@ hash, but practically this won't ever happen. */ Hashcode -internal_hash (Lisp_Object obj, int depth) +internal_hash (Lisp_Object obj, int depth, Boolint equalp) { if (depth > 5) return 0; @@ -1761,18 +1937,18 @@ if (!CONSP(XCDR(obj))) { /* special case for '(a . b) conses */ - return HASH2(internal_hash(XCAR(obj), depth), - internal_hash(XCDR(obj), depth)); + return HASH2(internal_hash(XCAR(obj), depth, equalp), + internal_hash(XCDR(obj), depth, equalp)); } /* Don't simply tail recurse; we want to hash lists with the same contents in distinct orders differently. */ - hash = internal_hash(XCAR(obj), depth); + hash = internal_hash(XCAR(obj), depth, equalp); obj = XCDR(obj); for (s = 1; s < 6 && CONSP(obj); obj = XCDR(obj), s++) { - h = internal_hash(XCAR(obj), depth); + h = internal_hash(XCAR(obj), depth, equalp); hash = HASH3(hash, h, s); } @@ -1780,6 +1956,11 @@ } if (STRINGP (obj)) { + if (equalp) + { + return string_equalp_hash (obj); + } + return hash_string (XSTRING_DATA (obj), XSTRING_LENGTH (obj)); } if (LRECORDP (obj)) @@ -1787,34 +1968,247 @@ const struct lrecord_implementation *imp = XRECORD_LHEADER_IMPLEMENTATION (obj); if (imp->hash) - return imp->hash (obj, depth); + return imp->hash (obj, depth, equalp); + } + + if (equalp) + { + if (CHARP (obj)) + { + /* Characters and numbers of the same numeric value hash + differently, which is fine, they're not equalp. */ + return LISP_HASH (make_char (CANONCASE (NULL, XCHAR (obj)))); + } + + if (INTP (obj)) + { + return FLOAT_HASHCODE_FROM_DOUBLE ((double) (XINT (obj))); + } } return LISP_HASH (obj); } -DEFUN ("sxhash", Fsxhash, 1, 1, 0, /* -Return a hash value for OBJECT. -\(equal obj1 obj2) implies (= (sxhash obj1) (sxhash obj2)). +DEFUN ("eq-hash", Feq_hash, 1, 1, 0, /* +Return a hash value for OBJECT appropriate for use with `eq.' +*/ + (object)) +{ + return make_integer (XPNTRVAL (object)); +} + +DEFUN ("eql-hash", Feql_hash, 1, 1, 0, /* +Return a hash value for OBJECT appropriate for use with `eql.' +*/ + (object)) +{ + EMACS_INT hashed = lisp_object_eql_hash (NULL, object); + return make_integer (hashed); +} + +DEFUN ("equal-hash", Fequal_hash, 1, 1, 0, /* +Return a hash value for OBJECT appropriate for use with `equal.' +\(equal obj1 obj2) implies (= (equal-hash obj1) (equal-hash obj2)). +*/ + (object)) +{ + EMACS_INT hashed = internal_hash (object, 0, 0); + return make_integer (hashed); +} + +DEFUN ("equalp-hash", Fequalp_hash, 1, 1, 0, /* +Return a hash value for OBJECT appropriate for use with `equalp.' */ (object)) { - return make_int (internal_hash (object, 0)); + EMACS_INT hashed = internal_hash (object, 0, 1); + return make_integer (hashed); +} + +static Lisp_Object +make_hash_table_test (Lisp_Object name, + hash_table_equal_function_t equal_function, + hash_table_hash_function_t hash_function, + Lisp_Object lisp_equal_function, + Lisp_Object lisp_hash_function) +{ + Lisp_Object result = ALLOC_NORMAL_LISP_OBJECT (hash_table_test); + Hash_Table_Test *http = XHASH_TABLE_TEST (result); + + http->name = name; + http->equal_function = equal_function; + http->hash_function = hash_function; + http->lisp_equal_function = lisp_equal_function; + http->lisp_hash_function = lisp_hash_function; + + return result; +} + +Lisp_Object +define_hash_table_test (Lisp_Object name, + hash_table_equal_function_t equal_function, + hash_table_hash_function_t hash_function, + Lisp_Object lisp_equal_function, + Lisp_Object lisp_hash_function) +{ + Lisp_Object result = make_hash_table_test (name, equal_function, + hash_function, + lisp_equal_function, + lisp_hash_function); + XWEAK_LIST_LIST (Vhash_table_test_weak_list) + = Fcons (Fcons (name, result), + XWEAK_LIST_LIST (Vhash_table_test_weak_list)); + + return result; } -#if 0 -DEFUN ("internal-hash-value", Finternal_hash_value, 1, 1, 0, /* -Hash value of OBJECT. For debugging. -The value is returned as (HIGH . LOW). +DEFUN ("define-hash-table-test", Fdefine_hash_table_test, 3, 3, 0, /* +Define a new hash table test with name NAME, a symbol. + +In a hash table created with NAME as its test, use EQUAL-FUNCTION to compare +keys, and HASH-FUNCTION for computing hash codes of keys. + +EQUAL-FUNCTION must be a function taking two arguments and returning non-nil +if both arguments are the same. HASH-FUNCTION must be a function taking one +argument and returning an integer that is the hash code of the argument. + +Computation should use the whole value range of the underlying machine long +type. In XEmacs this will necessitate bignums for values above +`most-positive-fixnum' but below (1+ (* most-positive-fixnum 2)) and +analagous values below `most-negative-fixnum'. Relatively poor hashing +performance is guaranteed in a build without bignums. + +This function returns t if successful, and errors if NAME +cannot be defined as a hash table test. +*/ + (name, equal_function, hash_function)) +{ + Lisp_Object min, max, lookup; + + CHECK_SYMBOL (name); + + lookup = Fassq (name, XWEAK_LIST_LIST (Vhash_table_test_weak_list)); + + if (!NILP (lookup)) + { + invalid_change ("Cannot redefine existing hash table test", name); + } + + min = Ffunction_min_args (equal_function); + max = Ffunction_max_args (equal_function); + + if (!((XINT (min) <= 2) && (NILP (max) || 2 <= XINT (max)))) + { + signal_wrong_number_of_arguments_error (equal_function, 2); + } + + min = Ffunction_min_args (hash_function); + max = Ffunction_max_args (hash_function); + + if (!((XINT (min) <= 1) && (NILP (max) || 1 <= XINT (max)))) + { + signal_wrong_number_of_arguments_error (hash_function, 1); + } + + define_hash_table_test (name, lisp_object_general_equal, + lisp_object_general_hash, equal_function, + hash_function); + return Qt; +} + +DEFUN ("valid-hash-table-test-p", Fvalid_hash_table_test_p, 1, 1, 0, /* +Return t if OBJECT names a hash table test, nil otherwise. + +A valid hash table test is one of the symbols `eq', `eql', `equal', +`equalp', or some symbol passed as the NAME argument to +`define-hash-table-test'. As a special case, `nil' is regarded as +equivalent to `eql'. */ (object)) { - /* This function is pretty 32bit-centric. */ - Hashcode hash = internal_hash (object, 0); - return Fcons (hash >> 16, hash & 0xffff); + Lisp_Object lookup; + + if (NILP (object)) + { + return Qt; + } + + lookup = Fassq (object, XWEAK_LIST_LIST (Vhash_table_test_weak_list)); + + if (!NILP (lookup)) + { + return Qt; + } + + return Qnil; +} + +DEFUN ("hash-table-test-list", Fhash_table_test_list, 0, 0, 0, /* +Return a list of symbols naming valid hash table tests. +These can be passed as the value of the TEST keyword to `make-hash-table'. +This list does not include nil, regarded as equivalent to `eql' by +`make-hash-table'. +*/ + ()) +{ + Lisp_Object result = Qnil; + + LIST_LOOP_2 (test, XWEAK_LIST_LIST (Vhash_table_test_weak_list)) + { + if (!UNBOUNDP (XCAR (test))) + { + result = Fcons (XCAR (test), result); + } + } + + return result; } -#endif + +DEFUN ("hash-table-test-equal-function", + Fhash_table_test_equal_function, 1, 1, 0, /* +Return the comparison function used for hash table test TEST. +See `define-hash-table-test' and `make-hash-table'. +*/ + (test)) +{ + Lisp_Object lookup; + + if (NILP (test)) + { + test = Qeql; + } + + lookup = Fassq (test, XWEAK_LIST_LIST (Vhash_table_test_weak_list)); + if (NILP (lookup)) + { + invalid_argument ("Not a defined hash table test", test); + } + return XHASH_TABLE_TEST (XCDR (lookup))->lisp_equal_function; +} + +DEFUN ("hash-table-test-hash-function", + Fhash_table_test_hash_function, 1, 1, 0, /* +Return the hash function used for hash table test TEST. +See `define-hash-table-test' and `make-hash-table'. +*/ + (test)) +{ + Lisp_Object lookup; + + if (NILP (test)) + { + test = Qeql; + } + + lookup = Fassq (test, XWEAK_LIST_LIST (Vhash_table_test_weak_list)); + if (NILP (lookup)) + { + invalid_argument ("Not a defined hash table test", test); + } + + return XHASH_TABLE_TEST (XCDR (lookup))->lisp_hash_function; +} /************************************************************************/ /* initialization */ @@ -1846,12 +2240,21 @@ DEFSUBR (Fhash_table_rehash_threshold); DEFSUBR (Fhash_table_weakness); DEFSUBR (Fhash_table_type); /* obsolete */ - DEFSUBR (Fsxhash); -#if 0 - DEFSUBR (Finternal_hash_value); -#endif + + DEFSUBR (Feq_hash); + DEFSUBR (Feql_hash); + DEFSUBR (Fequal_hash); + Ffset (intern ("sxhash"), intern ("equal-hash")); + DEFSUBR (Fequalp_hash); + + DEFSUBR (Fdefine_hash_table_test); + DEFSUBR (Fvalid_hash_table_test_p); + DEFSUBR (Fhash_table_test_list); + DEFSUBR (Fhash_table_test_equal_function); + DEFSUBR (Fhash_table_test_hash_function); DEFSYMBOL_MULTIWORD_PREDICATE (Qhash_tablep); + DEFSYMBOL (Qhash_table); DEFSYMBOL (Qhashtable); DEFSYMBOL (Qmake_hash_table); @@ -1880,6 +2283,22 @@ void vars_of_elhash (void) { + Lisp_Object weak_list_list = XWEAK_LIST_LIST (Vhash_table_test_weak_list); + + /* This var was staticpro'd and initialised in + init_elhash_once_early, but its Vall_weak_lists isn't sane, since + that was done before vars_of_data() was called. Create a sane + weak list object now, set its list appropriately, assert that our + data haven't been garbage collected. */ + assert (!NILP (Fassq (Qeq, weak_list_list))); + assert (!NILP (Fassq (Qeql, weak_list_list))); + assert (!NILP (Fassq (Qequal, weak_list_list))); + assert (!NILP (Fassq (Qequalp, weak_list_list))); + assert (4 == XINT (Flength (weak_list_list))); + + Vhash_table_test_weak_list = make_weak_list (WEAK_LIST_KEY_ASSOC); + XWEAK_LIST_LIST (Vhash_table_test_weak_list) = weak_list_list; + #ifdef MEMORY_USAGE_STATS OBJECT_HAS_PROPERTY (hash_table, memusage_stats_list, list1 (intern ("hash-entries"))); @@ -1890,11 +2309,40 @@ init_elhash_once_early (void) { INIT_LISP_OBJECT (hash_table); + INIT_LISP_OBJECT (hash_table_test); + #ifdef NEW_GC INIT_LISP_OBJECT (hash_table_entry); #endif /* NEW_GC */ + /* init_elhash_once_early() is called very early, we can't have these + DEFSYMBOLs in syms_of_elhash(), unfortunately. */ + + DEFSYMBOL (Qeq); + DEFSYMBOL (Qeql); + DEFSYMBOL (Qequal); + DEFSYMBOL (Qequalp); + + DEFSYMBOL (Qeq_hash); + DEFSYMBOL (Qeql_hash); + DEFSYMBOL (Qequal_hash); + DEFSYMBOL (Qequalp_hash); + /* This must NOT be staticpro'd */ Vall_weak_hash_tables = Qnil; dump_add_weak_object_chain (&Vall_weak_hash_tables); + + staticpro (&Vhash_table_test_weak_list); + Vhash_table_test_weak_list = make_weak_list (WEAK_LIST_KEY_ASSOC); + + staticpro (&Vhash_table_test_eq); + Vhash_table_test_eq = define_hash_table_test (Qeq, NULL, NULL, Qeq, Qeq_hash); + staticpro (&Vhash_table_test_eql); + Vhash_table_test_eql + = define_hash_table_test (Qeql, lisp_object_eql_equal, + lisp_object_eql_hash, Qeql, Qeql_hash); + (void) define_hash_table_test (Qequal, lisp_object_equal_equal, + lisp_object_equal_hash, Qequal, Qequal_hash); + (void) define_hash_table_test (Qequalp, lisp_object_equalp_equal, + lisp_object_equalp_hash, Qequalp, Qequalp_hash); }