lru-cache.scm
(module lru-cache mzscheme
	(provide lru-cache
		 lru-cache?
		 empty-lru-cache!
		 lru-cache-empty?
		 lru-cache+
		 lru-cache-find)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;=head1 LRU Cache
;;;
;;;This module gives an implementation of a thread safe
;;;Least Recently Used Cache. It is setup using a cyclic
;;;vector, i.e. a vector that is assigned in a cyclic way.
;;;Each new element in the cache is considered the least
;;;recently used element. If the full cache of elements is
;;;in use, each new element will overwrite the oldest (i.e.
;;;least used) element.
;;;
;;;The LRU Cache is searched lineairly for elements in the
;;;cache. Older elements will be reached slower, because
;;;the search is larger. A full search on a fully used
;;;cache vector, will go exactly two times through the
;;;vector of cached elements. The search is done recusivly.
;;;If an element is found, it will bubble up to the front
;;;of the cache. If it is needed again soon, next time it
;;;is needed, it will be in one of the first positions
;;;of the cache.
;;;
;;;=head2 Supportive macros and functions
;;;
;;;This section provides some simple macros. No explanation
;;;is given.
;;;
;;;=verbatim scm,8
(define-struct %lru-cache (type size sem vect index inuse))

(define-syntax lru-cache-type
  (syntax-rules ()
    ((_ cache)
     (%lru-cache-type cache))))

(define-syntax lru-cache-eq-type?
  (syntax-rules ()
    ((_ cache)
     (eq? (lru-cache-type cache) 'eq))))

(define-syntax lru-cache-size
  (syntax-rules ()
    ((_ cache)
     (%lru-cache-size cache))))

(define-syntax protect
  (syntax-rules ()
    ((_ cache body)
     (begin
       (semaphore-wait (%lru-cache-sem cache))
       (let ((r body))
	 (semaphore-post (%lru-cache-sem cache))
	 r)))))
;;;=verbatim
;;;
;;;=head2 Interface
;;;
;;;=head3 C<(lru-cache size ['equal]) : lru-cache>
;;;
;;;Returns a new lru-cache of size C<size> elements.
;;;If C<'equal> is provided as an arguments, comparisions
;;;on the keys of the cache will be done using C<equal?> instead
;;;of C<eq?>.
;;;
;;;=verbatim scm,8
(define (lru-cache size . args)
  (let ((type (if (null? args)
		  'eq
		  (if (eq? (car args) 'eq)
		      'eq
		      (if (eq? (car args) 'equal)
			  'equal
			  (error "lru-cache: invalid argument"))))))
    (make-%lru-cache type size (make-semaphore 1) (make-vector size) (- size 1) 0)))
;;;=verbatim
;;;
;;;=head3 C<(lru-cache? obj) : boolean>
;;;
;;;Returns #t, if obj is a lru cache.
;;;
;;;=verbatim scm,8
(define (lru-cache? obj)
  (%lru-cache? obj))
;;;=verbatim
;;;
;;;=head3 C<(empty-lru-cache! cache) : lru-cache>
;;;
;;;Clears cache and returns cache. This function is
;;;thread safe.
;;;
;;;=verbatim scm,8
(define (empty-lru-cache! cache)
  (protect cache
	   (let ((sem (%lru-cache-sem cache)))  ;;;(vector-ref cache 2)))
	     (let ((c (lru-cache (lru-cache-size cache) (lru-cache-type cache))))
	       (set-%lru-cache-vect! cache (%lru-cache-vect c))
	       (set-%lru-cache-index! cache (%lru-cache-index c))
	       (set-%lru-cache-inuse! cache (%lru-cache-inuse c))
	       cache))))
;;;=verbatim
;;;
;;;=head3 C<(lru-cache-empty? cache) : boolean>
;;;
;;;Returns #t, if the cache has no elements, #f otherwise.
;;;This function is not thread safe. This is however not
;;;important, as it returns a snapshot in time of the
;;;state of the lru-cache, that will only be of value
;;;in a context where concurrent access is not of importance.
;;;
;;;=verbatim scm,8
(define (lru-cache-empty? cache)
  (= (%lru-cache-inuse cache) 0))
;;;=verbatim
;;;
;;;=head3 C<(lru-cache+ cache key element) : cache>
;;;
;;;Adds element to the cache under key C<key>. This
;;;function is thread safe. The cache is protected
;;;using a critical section.
;;;
;;;=verbatim scm,8
(define (lru-cache+ cache key element)
  (protect cache
   (let ((index (%lru-cache-index cache))
	 (vect (%lru-cache-vect cache))
	 (size (lru-cache-size cache))
	 (inuse (%lru-cache-inuse cache)))
     (set! index (if (= index 0)
		     (- size 1)
		     (- index 1)))
     (set! inuse (if (= inuse size)
		     size
		     (+ inuse 1)))
     (vector-set! vect index (cons key element))
     (set-%lru-cache-index! cache index)
     (set-%lru-cache-inuse! cache inuse)
     cache)))
;;;=verbatim
;;;
;;;=head3 C<(lru-cache-find cache key) : #f | found element>
;;;
;;;This function will search key in cache. If it
;;;finds key, it will return the associated element.
;;;If it doesn't find key, it will return #f.
;;;
;;;=verbatim scm,8
(define (lru-cache-find cache key)
  (protect cache
   (let ((index (%lru-cache-index cache))
	 (inuse (%lru-cache-inuse cache))
	 (size (- (lru-cache-size cache) 1))
	 (vect (%lru-cache-vect cache))
	 (found #f)
	 (cmp (if (lru-cache-eq-type? cache)
		  eq?
		  equal?)))
     
     (define (find-and-bubble i previous-i N)
       (if (<= N 0)
	   #f
	   (if (cmp (car (vector-ref vect i)) key)
	       (let ((elem (vector-ref vect i)))
		 (if (= previous-i -1) 
		     elem
		     (begin
		       (vector-set! vect i (vector-ref vect previous-i))
		       elem)))
	       (let ((r (find-and-bubble (if (= i size) 
					     0
					     (+ i 1))
					 i
					 (- N 1))))
		 (if (not (eq? r #f))
		     (if (= previous-i -1)
			 (vector-set! vect i r)
			 (vector-set! vect i (vector-ref vect previous-i))))
		 r))))

     (let ((r (find-and-bubble index -1 inuse)))
       (if (eq? r #f)
	   #f
	   (cdr r))))))
;;;=verbatim
;;;
;;;=head2 Info
;;;
;;;S<C<Author(s):>> Hans Oesterholt-Dijkema (hansatelementalprogrammingdotorgextension).E<lb>
;;;S<C<Copyright:>> (c) 2005.E<lb>
;;;S<C<License  :>> L<Elemental Programming License|http://www.elemental-programming.org/epwiki/ep_license.html>.E<lb>
;;;S<C<File     :>> fifo.scm $Revision: 1.2 $
;;;
;;;=cut

		  

)