PageRenderTime 39ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/lisp/Hash.cs

http://github.com/toshok/shelisp
C# | 536 lines | 236 code | 65 blank | 235 comment | 51 complexity | 883174d34181aa902ff81b2dc2a92042 MD5 | raw file
Possible License(s): GPL-3.0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. namespace Shelisp {
  6. public class Hash : Object {
  7. public enum Weakness {
  8. None,
  9. Key,
  10. Value,
  11. KeyOrValue,
  12. KeyAndValue
  13. }
  14. public Hash (L l, Shelisp.Object test, Shelisp.Object weakness, Shelisp.Object size, Shelisp.Object rehash_size, Shelisp.Object rehash_threshold)
  15. {
  16. this.l = l;
  17. this.test = test;
  18. this.weakness = weakness;
  19. this.size = size;
  20. this.rehash_size = size;
  21. this.rehash_threshold = rehash_threshold;
  22. this.count = 0;
  23. // map weakness to our enum
  24. if (L.NILP (weakness)) {
  25. weakness_ = Weakness.None;
  26. }
  27. else if (weakness.LispEq (L.Qt)) {
  28. weakness_ = Weakness.KeyAndValue;
  29. }
  30. else if (weakness.LispEq (L.Qkey)) {
  31. weakness_ = Weakness.Key;
  32. }
  33. else if (weakness.LispEq (L.Qvalue)) {
  34. weakness_ = Weakness.Value;
  35. }
  36. else if (weakness.LispEq (L.Qkey_or_value)) {
  37. weakness_ = Weakness.KeyOrValue;
  38. }
  39. else if (weakness.LispEq (L.Qkey_and_value)) {
  40. weakness_ = Weakness.KeyAndValue;
  41. }
  42. else
  43. throw new Exception (string.Format ("invalid weakness {0}", weakness));
  44. compare = null;
  45. // use a builtin comparison function for the builtin test types
  46. if (test.LispEq (L.intern ("eq"))) {
  47. compare = compare_eq;
  48. }
  49. else if (test.LispEq (L.intern ("eql"))) {
  50. compare = compare_eql;
  51. }
  52. else if (test.LispEqual (L.intern ("equal"))) {
  53. compare = compare_equal;
  54. }
  55. table = new Tuple<Shelisp.Object,Shelisp.Object>[(int)((Number)size).boxed];
  56. }
  57. public override string ToString ()
  58. {
  59. return string.Format ("#<hash-table '{0} {1} {2}/{3} {4}>", test, weakness, count, size, -1/*XXX*/);
  60. }
  61. private L l;
  62. private int count;
  63. private Tuple<Shelisp.Object,Shelisp.Object>[] table;
  64. private Shelisp.Object test;
  65. private Shelisp.Object weakness;
  66. private Weakness weakness_;
  67. private Shelisp.Object size;
  68. private Shelisp.Object rehash_size;
  69. private Shelisp.Object rehash_threshold;
  70. private Func<Shelisp.Object, Shelisp.Object, long, long, bool> compare;
  71. private static bool compare_eq (Shelisp.Object obj1, Shelisp.Object obj2, long hash1, long hash2)
  72. {
  73. return obj1.LispEq (obj2);
  74. }
  75. private static bool compare_eql (Shelisp.Object obj1, Shelisp.Object obj2, long hash1, long hash2)
  76. {
  77. return obj1.LispEql (obj2);
  78. }
  79. private static bool compare_equal (Shelisp.Object obj1, Shelisp.Object obj2, long hash1, long hash2)
  80. {
  81. return hash1 == hash2 && obj1.LispEqual (obj2);
  82. }
  83. /*
  84. — Function: make-hash-table &rest keyword-args
  85. :test test
  86. This specifies the method of key lookup for this
  87. hash table. The default is eql; eq and equal are
  88. other alternatives:
  89. eql
  90. Keys which are numbers are “the same” if they are
  91. equal, that is, if they are equal in value and
  92. either both are integers or both are floating point
  93. numbers; otherwise, two distinct objects are never
  94. “the same.”
  95. eq
  96. Any two distinct Lisp objects are “different” as
  97. keys.
  98. equal
  99. Two Lisp objects are “the same,” as keys, if they
  100. are equal according to equal.
  101. You can use define-hash-table-test (see Defining
  102. Hash) to define additional possibilities for test.
  103. :weakness weak
  104. The weakness of a hash table specifies whether the
  105. presence of a key or value in the hash table
  106. preserves it from garbage collection. The value,
  107. weak, must be one of nil, key, value, key-or-value,
  108. key-and-value, or t which is an alias for
  109. key-and-value. If weak is key then the hash table
  110. does not prevent its keys from being collected as
  111. garbage (if they are not referenced anywhere else);
  112. if a particular key does get collected, the
  113. corresponding association is removed from the hash
  114. table.
  115. If weak is value, then the hash table does not
  116. prevent values from being collected as garbage (if
  117. they are not referenced anywhere else); if a
  118. particular value does get collected, the
  119. corresponding association is removed from the hash
  120. table.
  121. If weak is key-and-value or t, both the key and the
  122. value must be live in order to preserve the
  123. association. Thus, the hash table does not protect
  124. either keys or values from garbage collection; if
  125. either one is collected as garbage, that removes the
  126. association.
  127. If weak is key-or-value, either the key or the value
  128. can preserve the association. Thus, associations are
  129. removed from the hash table when both their key and
  130. value would be collected as garbage (if not for
  131. references from weak hash tables).
  132. The default for weak is nil, so that all keys and
  133. values referenced in the hash table are preserved
  134. from garbage collection.
  135. :size size
  136. This specifies a hint for how many associations you
  137. plan to store in the hash table. If you know the
  138. approximate number, you can make things a little
  139. more efficient by specifying it this way. If you
  140. specify too small a size, the hash table will grow
  141. automatically when necessary, but doing that takes
  142. some extra time. The default size is 65.
  143. :rehash-size rehash-size
  144. When you add an association to a hash table and the
  145. table is “full,” it grows automatically. This value
  146. specifies how to make the hash table larger, at that
  147. time. If rehash-size is an integer, it should be
  148. positive, and the hash table grows by adding that
  149. much to the nominal size. If rehash-size is a
  150. floating point number, it had better be greater than
  151. 1, and the hash table grows by multiplying the old
  152. size by that number.
  153. The default value is 1.5.
  154. :rehash-threshold threshold
  155. This specifies the criterion for when the hash table
  156. is “full” (so it should be made larger). The value,
  157. threshold, should be a positive floating point
  158. number, no greater than 1. The hash table is “full”
  159. whenever the actual number of entries exceeds this
  160. fraction of the nominal size. The default for
  161. threshold is 0.8.
  162. */
  163. [LispBuiltin]
  164. public static Shelisp.Object Fmake_hash_table (L l, params Shelisp.Object[] keyword_args)
  165. {
  166. Shelisp.Object test = L.intern ("eql"); // this is wrong, it's not just numbers, right?
  167. Shelisp.Object weakness = L.Qnil;
  168. Shelisp.Object size = new Number (65);
  169. Shelisp.Object rehash_size = new Number (1.5);
  170. Shelisp.Object rehash_threshold = new Number (0.8);
  171. for (int i = 0; i < keyword_args.Length; i += 2) {
  172. var keyword = keyword_args[i];
  173. Console.WriteLine (keyword);
  174. if (i == keyword_args.Length - 1)
  175. throw new Exception (string.Format ("keyword {0} has no value", keyword));
  176. var value = keyword_args[i+1];
  177. if (keyword.LispEq (L.Qtest))
  178. test = value;
  179. else if (keyword.LispEq (L.Qweakness))
  180. weakness = value;
  181. else if (keyword.LispEq (L.Qsize))
  182. size = value;
  183. else if (keyword.LispEq (L.Qrehash_size))
  184. rehash_size = value;
  185. else if (keyword.LispEq (L.Qrehash_threshold))
  186. rehash_threshold = value;
  187. }
  188. return new Hash (l, test, weakness, size, rehash_size, rehash_threshold);
  189. }
  190. public static int sxhash (Shelisp.Object obj)
  191. {
  192. return obj.GetHashCode();
  193. }
  194. /*
  195. — Function: gethash key table &optional default
  196. This function looks up key in table, and returns its associated value—or default, if key has no association in table.
  197. */
  198. [LispBuiltin]
  199. public static Shelisp.Object Fgethash (L l, Shelisp.Object key, Shelisp.Object table, [LispOptional] Shelisp.Object @default)
  200. {
  201. if (!(table is Hash))
  202. throw new WrongTypeArgumentException ("hashp", table);
  203. return ((Hash)table).Get (key, @default ?? L.Qnil);
  204. }
  205. private int GetIndex (Shelisp.Object key)
  206. {
  207. long key_hash = (long)(uint)sxhash (key);
  208. int start = (int)(key_hash % table.Length);
  209. int i = start;
  210. do {
  211. if (table[i] == null)
  212. return i;
  213. long item1_hash = (long)(uint)sxhash (table[i].Item1);
  214. if (compare (key, table[i].Item1, key_hash, item1_hash)) {
  215. return i;
  216. }
  217. i = (i + 1) % table.Length;
  218. } while (i != start);
  219. throw new Exception ("hash table is full and wasn't rehashed?");
  220. }
  221. public Shelisp.Object Get (Shelisp.Object key, Shelisp.Object @default)
  222. {
  223. int index = GetIndex (key);
  224. if (table[index] == null)
  225. return @default;
  226. return table[index].Item2;
  227. }
  228. /*
  229. — Function: puthash key value table
  230. This function enters an association for key in table, with value value. If key already has an association in table, value replaces the old associated value.
  231. */
  232. [LispBuiltin]
  233. public static Shelisp.Object Fputhash (L l, Shelisp.Object key, Shelisp.Object value, Shelisp.Object table)
  234. {
  235. if (!(table is Hash))
  236. throw new WrongTypeArgumentException ("hashp", table);
  237. ((Hash)table).Put (key, value);
  238. return value;
  239. }
  240. public void Put (Shelisp.Object key, Shelisp.Object value)
  241. {
  242. int index = GetIndex (key);
  243. bool adding = false;
  244. if (table[index] == null)
  245. adding = true;
  246. table[index] = Tuple.Create<Shelisp.Object,Shelisp.Object>(key, value);
  247. if (adding) {
  248. // up the count and maybe rehash
  249. count ++;
  250. }
  251. }
  252. /*
  253. — Function: remhash key table
  254. This function removes the association for key from
  255. table, if there is one. If key has no association,
  256. remhash does nothing.
  257. Common Lisp note: In Common Lisp, remhash returns
  258. non-nil if it actually removed an association and
  259. nil otherwise. In Emacs Lisp, remhash always returns
  260. nil.
  261. */
  262. [LispBuiltin]
  263. public static Shelisp.Object Fremhash (L l, Shelisp.Object key, Shelisp.Object table)
  264. {
  265. if (!(table is Hash))
  266. throw new WrongTypeArgumentException ("hashp", table);
  267. ((Hash)table).Remove (key);
  268. return L.Qnil;
  269. }
  270. public void Remove (Shelisp.Object key)
  271. {
  272. int index = GetIndex (key);
  273. if (table[index] == null)
  274. return;
  275. table[index] = null;
  276. count --;
  277. }
  278. /*
  279. — Function: clrhash table
  280. This function removes all the associations from hash
  281. table table, so that it becomes empty. This is also
  282. called clearing the hash table.
  283. Common Lisp note: In Common Lisp, clrhash returns
  284. the empty table. In Emacs Lisp, it returns nil.
  285. */
  286. [LispBuiltin]
  287. public static Shelisp.Object Fclrhash (L l, Shelisp.Object table)
  288. {
  289. if (!(table is Hash))
  290. throw new WrongTypeArgumentException ("hashp", table);
  291. ((Hash)table).Clear ();
  292. return L.Qnil;
  293. }
  294. public void Clear ()
  295. {
  296. table = new Tuple<Shelisp.Object,Shelisp.Object>[(int)((Number)size).boxed];
  297. count = 0;
  298. }
  299. /*
  300. — Function: maphash function table
  301. This function calls function once for each of the
  302. associations in table. The function function should
  303. accept two arguments—a key listed in table, and its
  304. associated value. maphash returns nil.
  305. */
  306. /*
  307. — Function: define-hash-table-test name test-fn hash-fn
  308. This function defines a new hash table test, named
  309. name.
  310. After defining name in this way, you can use it as
  311. the test argument in make-hash-table. When you do
  312. that, the hash table will use test-fn to compare key
  313. values, and hash-fn to compute a “hash code” from a
  314. key value.
  315. The function test-fn should accept two arguments,
  316. two keys, and return non-nil if they are considered
  317. “the same.”
  318. The function hash-fn should accept one argument, a
  319. key, and return an integer that is the “hash code”
  320. of that key. For good results, the function should
  321. use the whole range of integer values for hash
  322. codes, including negative integers.
  323. The specified functions are stored in the property
  324. list of name under the property hash-table-test; the
  325. property value's form is (test-fn hash-fn).
  326. */
  327. /*
  328. — Function: sxhash obj
  329. This function returns a hash code for Lisp object
  330. obj. This is an integer which reflects the contents
  331. of obj and the other Lisp objects it points to.
  332. If two objects obj1 and obj2 are equal, then (sxhash
  333. obj1) and (sxhash obj2) are the same integer.
  334. If the two objects are not equal, the values
  335. returned by sxhash are usually different, but not
  336. always; once in a rare while, by luck, you will
  337. encounter two distinct-looking objects that give the
  338. same result from sxhash.
  339. This example creates a hash table whose keys are
  340. strings that are compared case-insensitively.
  341. (defun case-fold-string= (a b)
  342. (compare-strings a nil nil b nil nil t))
  343. (defun case-fold-string-hash (a)
  344. (sxhash (upcase a)))
  345. (define-hash-table-test 'case-fold
  346. 'case-fold-string= 'case-fold-string-hash)
  347. (make-hash-table :test 'case-fold)
  348. Here is how you could define a hash table test
  349. equivalent to the predefined test value equal. The
  350. keys can be any Lisp object, and equal-looking
  351. objects are considered the same key.
  352. (define-hash-table-test 'contents-hash 'equal 'sxhash)
  353. (make-hash-table :test 'contents-hash)
  354. */
  355. [LispBuiltin]
  356. public static Shelisp.Object Fsxhash (L l, Shelisp.Object obj)
  357. {
  358. return new Number (sxhash (obj));
  359. }
  360. // Here are some other functions for working with hash tables.
  361. /*
  362. — Function: hash-table-p table
  363. This returns non-nil if table is a hash table object.
  364. */
  365. [LispBuiltin]
  366. public static Shelisp.Object Fhash_table_p (L l, Shelisp.Object table)
  367. {
  368. return (table is Hash) ? L.Qt : L.Qnil;
  369. }
  370. /*
  371. — Function: copy-hash-table table
  372. This function creates and returns a copy of table. Only the table itself is copied—the keys and values are shared.
  373. */
  374. /*
  375. — Function: hash-table-count table
  376. This function returns the actual number of entries in table.
  377. */
  378. /*
  379. — Function: hash-table-test table
  380. This returns the test value that was given when
  381. table was created, to specify how to hash and
  382. compare keys. See make-hash-table (see Creating
  383. Hash).
  384. */
  385. [LispBuiltin]
  386. public static Shelisp.Object Fhash_table_test (L l, Shelisp.Object table)
  387. {
  388. if (!(table is Hash))
  389. throw new WrongTypeArgumentException ("hashp", table);
  390. return ((Hash)table).test;
  391. }
  392. /*
  393. — Function: hash-table-weakness table
  394. This function returns the weak value that was specified for hash table table.
  395. */
  396. [LispBuiltin]
  397. public static Shelisp.Object Fhash_table_weakness (L l, Shelisp.Object table)
  398. {
  399. if (!(table is Hash))
  400. throw new WrongTypeArgumentException ("hashp", table);
  401. return ((Hash)table).weakness;
  402. }
  403. /*
  404. — Function: hash-table-rehash-size table
  405. This returns the rehash size of table.
  406. */
  407. [LispBuiltin]
  408. public static Shelisp.Object Fhash_table_rehash_size (L l, Shelisp.Object table)
  409. {
  410. if (!(table is Hash))
  411. throw new WrongTypeArgumentException ("hashp", table);
  412. return ((Hash)table).rehash_size;
  413. }
  414. /*
  415. — Function: hash-table-rehash-threshold table
  416. This returns the rehash threshold of table.
  417. */
  418. [LispBuiltin]
  419. public static Shelisp.Object Fhash_table_rehash_threshold (L l, Shelisp.Object table)
  420. {
  421. if (!(table is Hash))
  422. throw new WrongTypeArgumentException ("hashp", table);
  423. return ((Hash)table).rehash_threshold;
  424. }
  425. /*
  426. — Function: hash-table-size table
  427. This returns the current nominal size of table.
  428. */
  429. [LispBuiltin]
  430. public static Shelisp.Object Fhash_table_size (L l, Shelisp.Object table)
  431. {
  432. if (!(table is Hash))
  433. throw new WrongTypeArgumentException ("hashp", table);
  434. return ((Hash)table).size;
  435. }
  436. }
  437. }