;;; A simple table utility ;;; ---------------------------------------------------------------------- ;;; - File: tables.lisp ;;; - Author: Chris Riesbeck ;;; - Most recent update: 7/27/94 ;;; ---------------------------------------------------------------------- ;;; Defining a table function ;;; ---------------------------------------------------------------------- ;;; (DEFTABLE name) => name ;;; ;;; DEFTABLE defines name to be a table function such that ;;; ;;; - (name key) retrieves a value for key, if any ;;; - (SETF (name key) value) stores a value for key ;;; - (name) returns the internal table associated with name; ;;; this is useful when manipulating tables (see below). ;;; ;;; The table is empty when name is defined (or redefined). ;;; ;;; Examples: ;;; ;;; > (deftable AGE-of) ;;; AGE-OF ;;; > (age-of 'john) ;;; NIL ;;; > (setf (age-of 'john) 22) ;;; 22 ;;; > (age-of 'john) ;;; 22 ;;; ;;; Note: DEFTABLE is a top-level form, like DEFUN. It is not for ;;; creating local table functions. The following is wrong: ;;; ;;; (defun foo (...) ;;; (deftable baz) ;;; ...) ;;; ;;; If you want a local table, use MAKE-HASH-TABLE and GETHASH. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Packages ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (eval-when (load eval compile) (unless (find-package :tables) (make-package :tables))) (in-package :tables) (export '(deftable in-table-p remove-key clear-table map-table)) ;;; ---------------------------------------------------------------------- ;;; Implementation notes: ;;; ;;; - I avoided (DEFUN (SETF fn) ...) so as not to require CL 2 ;;; - I used PROGN to make the DEFSETF top-level for MacIntosh ;;; Common Lisp. (defmacro deftable (fn &optional test) (let ((set-fn (gensym))) `(progn (let* ((fn ',fn) (table (get-table fn ,test))) (defun ,fn (&optional (key nil key-given-p)) (if key-given-p (gethash key table) table)) (defun ,set-fn (arg1 &optional (arg2 nil arg2-p)) (cond (arg2-p (setf (gethash arg1 table) arg2)) (t (set-table fn arg1))))) (defsetf ,fn ,set-fn) ',fn))) (defvar *tables* (make-hash-table) "Table of DEFTABLE functions.") (defun get-table (name test) (set-table name (make-hash-table :test (or test #'eql)))) (defun set-table (name table) (if (hash-table-p table) (setf (gethash name *tables*) table) (error "~S not a table" table))) ;;; ---------------------------------------------------------------------- ;;; Manipulating tables ;;; ---------------------------------------------------------------------- ;;; Certain functions need explicit access to the internal table. To ;;; get this table, call the table function with no arguments, e.g., ;;; (AGE-OF). This returns the internal table for AGE-OF, which ;;; can then be passed to a table manipulation function. ;;; ;;; Example: The following clears the AGE-OF table. ;;; ;;; > (clear-table (age-of)) ;;; ;;; The nature of the internal table is implementation-dependent. ;;; (IN-TABLE-P key table) => T or NIL ;;; Returns true if key has a value in the table. ;;; (REMOVE-KEY key table) => T or NIL ;;; Removes any entry for key in the table, and returns true ;;; if there was one. ;;; (CLEAR-TABLE table) => table ;;; Removes all entries from the table. ;;; (MAP-TABLE function table) => NIL ;;; Calls (function key value) for every key and value in the table. ;;; The order in which keys are found is implementation-dependent. ;;; ---------------------------------------------------------------------- ;;; Implementation notes: ;;; ;;; - I avoided MULTIPLE-VALUE-BIND for Xlisp compatibility. (let ((flag (list nil))) (defun in-table-p (key table) (not (eq flag (gethash key table flag))))) (defun remove-key (key table) (remhash key table)) (defun clear-table (table) (clrhash table)) (defun map-table (fn table) (maphash fn table)) (provide "tables") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Change log ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #| 7/27/94 [CKR] Problem: If name is a function, (DEFTABLE name) would cause an error. Cause: Calling (name) doesn't do the right thing. Change: Store name->table associations in the table *TABLES*. 12/1/93 [CKR] Problem: If several packages used TABLES, they each loaded separate copies of TABLES. Cause: No TABLES package (because all functions were exported) that they could use. Change: Set up TABLES package. 11/4/92 [CKR] Problem: In some Lisps, e.g., MCL, the DEFSETF in DEFTABLE wasn't happening at the right time in compiled code. Cause: DEFSETF, a top-level form, was inside the LET. Change: Put DEFSETF outside the LET, in a PROGN. 9/30/92 [CKR] Problem: IN-TABLE-P returned multiple values instead of just T or NIL Cause: IN-TABLE-P defined as a simple call to GETHASH Change: Use (NOT (EQ flag (GETHASH ... flag))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |#