hashtables.scm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. ;;; Hoot hashtables
  2. ;;; Copyright (C) 2023 David Thompson <dave@spritely.institute>
  3. ;;; Copyright (C) 2023 Robin Templeton <robin@spritely.institute>
  4. ;;;
  5. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  6. ;;; you may not use this file except in compliance with the License.
  7. ;;; You may obtain a copy of the License at
  8. ;;;
  9. ;;; http://www.apache.org/licenses/LICENSE-2.0
  10. ;;;
  11. ;;; Unless required by applicable law or agreed to in writing, software
  12. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  13. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. ;;; See the License for the specific language governing permissions and
  15. ;;; limitations under the License.
  16. ;;; Commentary:
  17. ;;;
  18. ;;; R6RS-inspired hashtables.
  19. ;;;
  20. ;;; Code:
  21. (library (hoot hashtables)
  22. (export make-eq-hashtable
  23. make-eqv-hashtable
  24. make-hashtable
  25. hashtable?
  26. hashtable-size
  27. hashtable-ref
  28. hashtable-set!
  29. hashtable-delete!
  30. hashtable-contains?
  31. hashtable-update!
  32. hashtable-copy
  33. hashtable-clear!
  34. hashtable-keys
  35. hashtable-entries
  36. hashtable-for-each
  37. make-weak-key-hashtable
  38. weak-key-hashtable?
  39. weak-key-hashtable-ref
  40. weak-key-hashtable-set!
  41. weak-key-hashtable-delete!)
  42. (import (hoot primitives)
  43. (hoot pairs)
  44. (hoot numbers)
  45. (hoot eq)
  46. (hoot procedures)
  47. (hoot values)
  48. (hoot vectors)
  49. (hoot lists)
  50. (hoot errors))
  51. (define* (make-eq-hashtable)
  52. (%inline-wasm
  53. '(func (result (ref eq))
  54. (call $make-hash-table))))
  55. (define* (make-eqv-hashtable)
  56. (raise (make-unimplemented-error 'make-eqv-hashtable)))
  57. (define* (make-hashtable hash-function equiv)
  58. (raise (make-unimplemented-error 'make-hashtable)))
  59. (define (hashtable? hashtable)
  60. (%inline-wasm
  61. '(func (param $obj (ref eq)) (result (ref eq))
  62. (if (ref eq)
  63. (ref.test $hash-table (local.get $obj))
  64. (then (ref.i31 (i32.const 17)))
  65. (else (ref.i31 (i32.const 1)))))
  66. hashtable))
  67. (define (hashtable-size hashtable)
  68. (check-type hashtable hashtable? 'hashtable-size)
  69. (%inline-wasm
  70. '(func (param $table (ref $hash-table))
  71. (result (ref eq))
  72. (call $i32->fixnum
  73. (struct.get $hash-table $size (local.get $table))))
  74. hashtable))
  75. (define* (hashtable-ref hashtable key #:optional default)
  76. (check-type hashtable hashtable? 'hashtable-ref)
  77. (%inline-wasm
  78. '(func (param $table (ref $hash-table))
  79. (param $key (ref eq))
  80. (param $default (ref eq))
  81. (result (ref eq))
  82. (call $hashq-ref
  83. (local.get $table)
  84. (local.get $key)
  85. (local.get $default)))
  86. hashtable key default))
  87. (define (hashtable-set! hashtable key obj)
  88. (check-type hashtable hashtable? 'hashtable-set!)
  89. (%inline-wasm
  90. '(func (param $table (ref $hash-table))
  91. (param $key (ref eq))
  92. (param $val (ref eq))
  93. (result (ref eq))
  94. (call $hashq-update
  95. (local.get $table)
  96. (local.get $key)
  97. (local.get $val)
  98. (local.get $val)))
  99. hashtable key obj)
  100. (values))
  101. (define (hashtable-delete! hashtable key)
  102. (check-type hashtable hashtable? 'hashtable-delete!)
  103. (%inline-wasm
  104. '(func (param $table (ref $hash-table))
  105. (param $key (ref eq))
  106. (call $hashq-delete! (local.get $table) (local.get $key)))
  107. hashtable key)
  108. (values))
  109. (define (hashtable-contains? hashtable key)
  110. (check-type hashtable hashtable? 'hashtable-contains?)
  111. (pair? (%hashq-get-handle hashtable key)))
  112. (define (hashtable-update! hashtable key proc default)
  113. (check-type hashtable hashtable? 'hashtable-update!)
  114. (check-type proc procedure? 'hashtable-update!)
  115. (let ((handle (%hashq-get-handle hashtable key)))
  116. (if (pair? handle)
  117. (set-cdr! handle (proc (cdr handle)))
  118. (hashtable-set! hashtable key (proc default))))
  119. (values))
  120. (define* (hashtable-copy hashtable)
  121. (check-type hashtable hashtable? 'hashtable-copy)
  122. (let ((hashtable* (make-eq-hashtable)))
  123. (hashtable-for-each (lambda (k v)
  124. (hashtable-set! hashtable* k v))
  125. hashtable)
  126. hashtable*))
  127. (define* (hashtable-clear! hashtable)
  128. (check-type hashtable hashtable? 'hashtable-clear!)
  129. (%inline-wasm
  130. '(func (param $table (ref $hash-table))
  131. (struct.set $hash-table
  132. $size
  133. (local.get $table)
  134. (i32.const 0))
  135. (array.fill $raw-scmvector
  136. (struct.get $hash-table $buckets
  137. (local.get $table))
  138. (i32.const 0)
  139. (ref.i31 (i32.const 13))
  140. (array.len (struct.get $hash-table $buckets
  141. (local.get $table)))))
  142. hashtable)
  143. (values))
  144. (define (hashtable-keys hashtable)
  145. (check-type hashtable hashtable? 'hashtable-keys)
  146. (list->vector
  147. (%hash-fold (lambda (k v seed) (cons k seed))
  148. '()
  149. hashtable)))
  150. (define (hashtable-entries hashtable)
  151. (check-type hashtable hashtable? 'hashtable-entries)
  152. (list->vector
  153. (%hash-fold (lambda (k v seed) (cons v seed))
  154. '()
  155. hashtable)))
  156. ;; TODO: non-eq hashtables
  157. (define (hashtable-equivalence-function hashtable)
  158. (check-type hashtable hashtable? 'hashtable-equivalence-function)
  159. eq?)
  160. ;; TODO: non-eq hashtables
  161. (define (hashtable-hash-function hashtable)
  162. (check-type hashtable hashtable? 'hashtable-hash-function)
  163. %hashq)
  164. (define (equal-hash obj)
  165. (raise (make-unimplemented-error 'equal-hash)))
  166. (define (string-hash string)
  167. (raise (make-unimplemented-error 'string-hash)))
  168. (define (string-ci-hash string)
  169. (raise (make-unimplemented-error 'string-ci-hash)))
  170. (define (symbol-hash symbol)
  171. (raise (make-unimplemented-error 'symbol-hash)))
  172. (define (hashtable-for-each proc hashtable)
  173. (check-type proc procedure? 'hashtable-for-each)
  174. (check-type hashtable hashtable? 'hashtable-for-each)
  175. (let ((len (%buckets-length hashtable)))
  176. (do ((i 0 (1+ i)))
  177. ((= i len) (values))
  178. (for-each (lambda (handle)
  179. (proc (car handle) (cdr handle)))
  180. (%bucket-ref hashtable i)))))
  181. (define (%hashq-get-handle table key)
  182. (%inline-wasm
  183. '(func (param $table (ref $hash-table))
  184. (param $key (ref eq))
  185. (result (ref eq))
  186. (call $hashq-lookup/default
  187. (local.get $table)
  188. (local.get $key)
  189. (ref.i31 (i32.const 1))))
  190. table key))
  191. (define (%hashq key size)
  192. (%inline-wasm
  193. '(func (param $v (ref eq))
  194. (result (ref eq))
  195. (call $i32->fixnum (call $hashq (local.get $v))))
  196. key))
  197. (define (%buckets-length table)
  198. (%inline-wasm
  199. '(func (param $table (ref $hash-table))
  200. (result (ref eq))
  201. (call $i32->fixnum
  202. (array.len (struct.get $hash-table
  203. $buckets
  204. (local.get $table)))))
  205. table))
  206. (define (%bucket-ref table i)
  207. (%inline-wasm
  208. '(func (param $table (ref $hash-table))
  209. (param $i i32)
  210. (result (ref eq))
  211. (array.get $raw-scmvector
  212. (struct.get $hash-table
  213. $buckets
  214. (local.get $table))
  215. (local.get $i)))
  216. table i))
  217. (define (%hash-fold-handles proc init table)
  218. (let ((len (%buckets-length table)))
  219. (let loop ((i 0)
  220. (seed init))
  221. (if (= i len)
  222. seed
  223. (loop (1+ i)
  224. (fold proc seed (%bucket-ref table i)))))))
  225. (define (%hash-fold proc init table)
  226. (%hash-fold-handles (lambda (h seed) (proc (car h) (cdr h) seed))
  227. init
  228. table))
  229. ;; Weak key hashtables
  230. (define (make-weak-key-hashtable)
  231. (%inline-wasm
  232. '(func (result (ref eq))
  233. (struct.new $weak-table
  234. (i32.const 0)
  235. (call $make-weak-map)))))
  236. (define (weak-key-hashtable? obj)
  237. (%inline-wasm
  238. '(func (param $obj (ref eq)) (result (ref eq))
  239. (if (ref eq)
  240. (ref.test $weak-table (local.get $obj))
  241. (then (ref.i31 (i32.const 17)))
  242. (else (ref.i31 (i32.const 1)))))
  243. obj))
  244. (define* (weak-key-hashtable-ref table key #:optional default)
  245. (check-type table weak-key-hashtable? 'weak-key-hashtable-ref)
  246. (%inline-wasm
  247. '(func (param $table (ref eq)) (param $key (ref eq))
  248. (param $default (ref eq)) (result (ref eq))
  249. (call $weak-map-get
  250. (struct.get $weak-table $val
  251. (ref.cast $weak-table (local.get $table)))
  252. (local.get $key)
  253. (local.get $default)))
  254. table key default))
  255. (define (weak-key-hashtable-set! table key value)
  256. (check-type table weak-key-hashtable? 'weak-key-hashtable-set!)
  257. (%inline-wasm
  258. '(func (param $table (ref eq)) (param $key (ref eq)) (param $val (ref eq))
  259. (call $weak-map-set
  260. (struct.get $weak-table $val
  261. (ref.cast $weak-table (local.get $table)))
  262. (local.get $key)
  263. (local.get $val)))
  264. table key value))
  265. (define (weak-key-hashtable-delete! table key)
  266. (check-type table weak-key-hashtable? 'weak-key-hashtable-delete!)
  267. (%inline-wasm
  268. '(func (param $table (ref eq)) (param $key (ref eq))
  269. (call $weak-map-delete
  270. (struct.get $weak-table $val
  271. (ref.cast $weak-table (local.get $table)))
  272. (local.get $key))
  273. (drop))
  274. table key)))