;;; -*- Mode:LISP; Package:SYSTEM-INTERNALS; Base:10; Readtable:CL -*- ;;; ;;; ;;; DEFSTRUCT.LISP ;;; ;;; ;;; The DEFSTRUCT macro expands into a PROGN form which contains a whole bunch ;;; of forms (herein called "tasks") that do the dirty work of the DEFSTRUCT. ;;; The macro itself calls several parsers to parse the DEFSTRUCT body, creates ;;; a procedure to incrementally build up a list of tasks, calls MAKE-DEFSTRUCT- ;;; TASKS to make the tasks, and constructs the PROGN form. Most of the gory ;;; work is done by MAKE-DEFSTRUCT-TASKS and the functions it calls. (defvar *defined-structures* NIL "Table of all structure types defined by DEFSTRUCT") (defmacro new-defstruct (name-and-options &body documentation-and-slot-descriptions) (let ((tasks NIL)) (flet ((push-task (task) (setq tasks (cons task tasks)))) (multiple-value-bind (name options doc-string slots) (parse-defstruct name-and-options documentation-and-slot-descriptions) (push-task `(SETF (DOCUMENTATION ',name :STRUCTURE) ,doc-string)) (let ((parsed-slots (mapcar #'parse-slot slots))) (make-defstruct-tasks name options parsed-slots #'push-task)) `(PROGN ,@(reverse tasks) ',name))))) (defun parse-defstruct (name-and-options documentation-and-slot-descriptions) (let (name options doc-string slots) (etypecase name-and-options (symbol (setq name name-and-options) (setq options NIL)) (list (setq name (first name-and-options)) (setq options (rest name-and-options)))) (if (stringp (car documentation-and-slot-descriptions)) (progn (setq doc-string (car documentation-and-slot-descriptions)) (setq slots (cdr documentation-and-slot-descriptions))) (progn (setq doc-string NIL) (setq slots documentation-and-slot-descriptions))) (values name options doc-string slots))) (defun make-defstruct-tasks (name options slots push-task-proc) (flet ((push-task (task) (funcall push-task-proc task))) (let ( (conc-name (concatenate 'string (symbol-name name) "-")) (constructor (intern (concatenate 'string "MAKE-" (symbol-name name)) *package*)) (copier (intern (concatenate 'string "COPY-" (symbol-name name)) *package*)) (predicate (intern (concatenate 'string (symbol-name name) "-P") *package*)) (BOA-constructors NIL) (inclusions NIL) (print-function NIL) ;default? (type NIL) (namedp :IMPLIED) (predicatep :IMPLIED) (initial-offset-p NIL) (initial-offset 0) (net-offset NIL) ) (dolist (option options) (let (option-type option-args first-arg) (etypecase option (symbol (setq option-type option) (setq option-args NIL) (setq first-arg NIL)) (list (setq option-type (first option)) (setq option-args (rest option)) (setq first-arg (car option-args)))) (case option-type (:conc-name (if first-arg (setq conc-name (symbol-name first-arg)) (setq conc-name ""))) (:constructor (case (length option-args) (1 (if (symbolp first-arg) (setq constructor first-arg) (ferror "~S is not a valid name for a constructor function." first-arg))) (2 (push option-args BOA-constructors)) (otherwise (ferror ":CONSTRUCTOR must have 1 or 2 arguments.")))) (:copier (when option-args (if first-arg (setq copier first-arg) (setq copier NIL)))) (:predicate (when option-args (if first-arg (progn (setq predicate first-arg) (setq predicatep T)) (progn (setq predicate NIL) (setq predicatep NIL))))) (:include (if inclusions (ferror "More than 1 :INCLUDE argument in DEFSTRUCT.") (setq inclusions option-args))) (:print-function (setq print-function first-arg)) (:type (if (or (eq first-arg 'VECTOR) (eq first-arg 'LIST) (and (listp first-arg) (= (length first-arg) 2) (eq (car first-arg) 'VECTOR))) (setq type first-arg) (ferror "Don't recognize :TYPE ~S." first-arg))) (:named (setq namedp T)) (:initial-offset (unless (and (numberp first-arg) (>= first-arg 0)) (ferror "Initial-offset can't be ~S." first-arg)) (setq initial-offset first-arg) (setq initial-offset-p T)) ))) ;; Decide, once and for all, if the structure is named or not. (when (eq namedp :IMPLIED) (if type (setq namedp NIL) (setq namedp T))) ;; Augment the slots with :INCLUDEd slots, :INITIAL-OFFSET nils, and the name, where appropriate. (let ((addenda (append (when inclusions (include (car inclusions) (cdr inclusions) type)) (make-list initial-offset) (when namedp (list name))))) (setq net-offset (length addenda)) (setq slots (append addenda slots))) ;; Make sure all the rules are being followed (when (and predicatep type (not namedp)) (cond ((eq predicatep T) (ferror "Predicate cannot be defined for unnamed structure type ~S." type)) ((eq predicatep :IMPLIED) (setq predicatep NIL)))) (when (and initial-offset-p (not type)) (ferror ":TYPE must be specified for :INITIAL-OFFSET.")) ;; Make the constructors (when (and constructor (not BOA-constructors)) (push-task (make-defun-constructor constructor NIL type slots))) (dolist (BOA-constructor BOA-constructors) (let ((constructor-name (first BOA-constructor)) (lambda-list (second BOA-constructor))) (push-task (make-defun-constructor constructor-name lambda-list type slots)))) ;; Make the copier (when copier (push-task (make-defun-copier copier name type))) ;; Make the predicate (when predicatep (push-task (make-defun-predicate predicate name type net-offset))) ;; Define the print function (if type (when print-function (ferror ":PRINT-FUNCTION may not be specified for type ~S." type)) (when print-function (push-task `(SET-STRUCTURE-OBJECT-PRINT-FUNCTION ',name ',print-function)))) ;; Make the slot accessors and SETF methods (let ((index 0)) (dolist (slot slots) (when (actual-slot-record-p slot) (push-task (make-defun-accessor conc-name slot name type index)) (unless (cadr (member :READ-ONLY (slot-record-plist slot))) (push-task (make-defsetf-accessor conc-name slot name type index)))) (incf index))) ;; (push-task `(RECORD-NEW-DEFSTRUCT ',name :TYPE ',type :SLOT-RECORDS ',slots))))) ;;; Task Generators ;;; ;;; These functions generate the myriad DEFUN, DEFSUBST, and DEFSETF tasks that appear ;;; in the expansion of DEFSTRUCT. (defun make-defun-constructor (constructor-name lambda-list type slots) (flet ( (value-for-slot (slot) (cond ((null slot) NIL) ((symbolp slot) (list 'QUOTE slot)) ((listp slot) (slot-record-name slot)))) ) (let ( (lambda-list (make-constructor-lambda-list (extract-actual-slot-records slots) lambda-list)) (slot-names (mapcar #'value-for-slot slots)) ) (let* ((rest-arg (cadr (member '&REST lambda-list))) (make-it-safe (if rest-arg `((SETQ ,rest-arg (COPY-LIST ,rest-arg))) NIL))) (case (internal-type-representation type) (:DEFAULT `(DEFUN ,constructor-name ,lambda-list ,@make-it-safe (MAKE-STRUCTURE-OBJECT ,@slot-names))) (:VECTOR `(DEFUN ,constructor-name ,lambda-list ,@make-it-safe (VECTOR ,@slot-names))) (:LIST `(DEFUN ,constructor-name ,lambda-list ,@make-it-safe (LIST ,@slot-names)))))))) (defun make-defun-copier (copier-name defstruct-name type) (case (internal-type-representation type) (:DEFAULT `(DEFUN ,copier-name (,defstruct-name) (COPY-STRUCTURE-OBJECT ,defstruct-name))) (:VECTOR `(DEFUN ,copier-name (,defstruct-name) (COPY-SEQ ,defstruct-name))) (:LIST `(DEFUN ,copier-name (,defstruct-name) (COPY-LIST ,defstruct-name))))) (defun make-defun-predicate (predicate-name defstruct-name type net-offset) (case (internal-type-representation type) (:DEFAULT `(DEFUN ,predicate-name (THING) (AND (STRUCTURE-OBJECT-P THING) (>= (STRUCTURE-OBJECT-LENGTH THING) ,net-offset) (EQ (STRUCTURE-OBJECT-REF THING ,(1- net-offset)) ',defstruct-name)))) (:VECTOR `(DEFUN ,predicate-name (THING) (AND (VECTORP THING) (>= (LENGTH THING) ,net-offset) (EQ (AREF THING ,(1- net-offset)) ',defstruct-name)))) (:LIST `(DEFUN ,predicate-name (THING) (AND (LISTP THING) (>= (LENGTH THING) ,net-offset) (EQ (NTH ,(1- net-offset) THING) ',defstruct-name)))))) ; we can't require length because of subtypes. (defun make-defun-accessor (conc-name slot defstruct-name type index) (let ((accessor-name (concatenate-and-intern conc-name (symbol-name (slot-record-name slot))))) (case (internal-type-representation type) (:DEFAULT `(DEFUN ,accessor-name (,defstruct-name) (STRUCTURE-OBJECT-REF ,defstruct-name ,index))) (:VECTOR `(DEFUN ,accessor-name (,defstruct-name) (AREF ,defstruct-name ,index))) (:LIST `(DEFUN ,accessor-name (,defstruct-name) (NTH ,index ,defstruct-name)))))) (defun make-defsetf-accessor (conc-name slot defstruct-name type index) (let ((accessor-name (concatenate-and-intern conc-name (symbol-name (slot-record-name slot)))) (value-temp (gensym))) (case (internal-type-representation type) (:DEFAULT `(DEFSETF ,accessor-name (,defstruct-name) (,value-temp) `(SETF (STRUCTURE-OBJECT-REF ,,defstruct-name ,,index) ,,value-temp))) (:VECTOR `(DEFSETF ,accessor-name (,defstruct-name) (,value-temp) `(SETF (AREF ,,defstruct-name ,,index) ,,value-temp))) (:LIST `(DEFSETF ,accessor-name (,defstruct-name) (,value-temp) `(SETF (NTH ,,index ,,defstruct-name) ,,value-temp)))))) (defun concatenate-and-intern (string1 string2) "Concatenate STRING1 and STRING2, and intern the resulting string in the current package." (intern (concatenate 'string string1 string2) *package*)) (defun internal-type-representation (type-spec) (cond ((eq type-spec NIL) :DEFAULT) ((or (eq type-spec 'VECTOR) (and (listp type-spec) (eq (car type-spec) 'VECTOR))) :VECTOR) ((eq type-spec 'LIST) :LIST) (t (ferror "Internal: unrecognized type ~S" type-spec)))) ;;; Incredible Hair ("Cheveux Incroyable") ;;; ;;; The following atrocity takes care of lambda lists for :CONSTRUCTOR functions. ;;; SLOTS is a list only of actual-slot-records. (defun make-constructor-lambda-list (slots supplied-lambda-list) (if supplied-lambda-list ;; Parse the lambda list into its parameters (multiple-value-bind (required optional rest aux) (USER::PARSE-LAMBDA-LIST supplied-lambda-list '(:REQUIRED :OPTIONAL :REST :AUX)) (let ((lambda-list-variables (append required (mapcar #'USER::PARSE-OPTIONAL-PARAMETER optional) (if rest (list rest)) (mapcar #'USER::PARSE-AUX-PARAMETER aux))) (redefaulted-optionals NIL) (redefaulted-auxs NIL) (unmentioned-slots NIL)) ;; Require each parameter to name a slot. (mapcar #'(lambda (llvar) (unless (find-slot llvar slots) (ferror "~S does not name a slot." llvar))) lambda-list-variables) ;; Use the defstruct's defaults (rather than NIL) for the &OPTIONAL and &AUX parameters. ;; Arbitrarily forbid supplied-p variables. (dolist (parameter optional) (multiple-value-bind (variable initform) (USER::PARSE-OPTIONAL-PARAMETER parameter) (if (and (listp parameter) (> (length parameter) 1)) (push (list variable initform) redefaulted-optionals) (push (list variable (slot-record-default (find-slot variable slots))) redefaulted-optionals)))) (setq redefaulted-optionals (reverse redefaulted-optionals)) (dolist (parameter aux) (multiple-value-bind (variable value) (USER::PARSE-AUX-PARAMETER parameter) (if (and (listp parameter) (> (length parameter) 1)) (push (list variable value) redefaulted-auxs) (push (list variable (slot-record-default (find-slot variable slots))) redefaulted-auxs)))) (setq redefaulted-auxs (reverse redefaulted-auxs)) ;; Add an &AUX parameter for each unmentioned slot. (mapcar #'(lambda (slot) (unless (member (slot-record-name slot) lambda-list-variables) (push `(,(slot-record-name slot) ,(slot-record-default slot)) unmentioned-slots))) slots) (setq unmentioned-slots (reverse unmentioned-slots)) ;; Reassemble the lambda list. `(,@required ,@(when redefaulted-optionals (cons '&OPTIONAL redefaulted-optionals)) ,@(when rest (list '&REST rest)) &AUX ,@redefaulted-auxs ,@unmentioned-slots))) ;; If no lambda list was supplied, construct one with &KEY parameters. `(&KEY ,@(mapcar #'(lambda (slot) `(,(slot-record-name slot) ,(slot-record-default slot))) slots)))) ;;; Inheritance ;;; ;;; Returns a list of slot-records to include (defun include (parent new-slot-descriptions new-type) (let ((parent-plist (lookup-defstruct parent))) (when (eq parent-plist :UNDEFINED) (ferror "Attempt to include structure ~S, which hasn't been defined." parent)) (let ((parent-type (getf parent-plist :TYPE)) (included-slots (getf parent-plist :SLOTS))) (unless (equal parent-type new-type) (ferror "Included structure ~S has :TYPE ~S, which does not match type ~S." parent parent-type new-type)) (dolist (foo new-slot-descriptions) (let* ((new-slot (parse-slot foo)) (slot-name (slot-record-name new-slot)) (new-plist (slot-record-plist new-slot)) (old-slot (find-slot slot-name included-slots)) (old-plist (slot-record-plist old-slot))) (unless old-slot (ferror "Slot ~S is not defined in the included structure ~S" slot-name parent)) (when (and (getf old-plist :read-only) (not (getf new-plist :read-only))) (ferror "Slot ~S is :READ-ONLY in ~S; it must also be :READ-ONLY when included." slot-name parent)) (let ((old-type (getf old-plist :type)) (new-type (getf new-plist :type))) (when (and new-type old-type (not (subtypep new-type old-type))) (ferror ":TYPE of slot ~S must be a subtype of included type ~S." slot-name old-type))) ;; maybe an error if old-type & ~new-type (setq included-slots (substitute-if new-slot #'(lambda (u) (and (actual-slot-record-p u) (eq (slot-record-name u) slot-name))) included-slots)))) included-slots))) ;;; Slots, Slot-Descriptions, and Slot-Records ;;; ;;; SLOT-DESCRIPTIONS make up the body of DEFSTRUCT forms. In Common Lisp, ;;; the syntax of a SLOT-DESCRIPTION is ;;; slot-name | (slot-name [default-init . slot-plist]) ;;; If no default-init is provided, then :UNDEFINED is used. ;;; ;;; SLOT-RECORDS are the elements used to represent a structure. If a SLOT-RECORD ;;; represents an actual slot, its syntax is ;;; (slot-name default-init . slot-plist), ;;; which is also valid syntax for an equivalent SLOT-DESCRIPTION. If a SLOT-RECORD ;;; represents a place to contain the name of a structure, its syntax is ;;; structure-name, ;;; a symbol. If a SLOT-RECORD represents an empty slot being reserved by a ;;; :INITIAL-OFFSET, its syntax is ;;; NIL. ;;; ;;; The function PARSE-SLOT-DESCRIPTION translates a SLOT-DESCRIPTION into the ;;; first kind of SLOT-RECORD. If a default-init is not given, :UNDEFINED is used. ;;; If a slot-plist is not given, NIL is used. ;;; ;;; SLOT-RECORDS of the first kind may be neatly constructed and dissected with ;;; the MAKE-SLOT-RECORD constructor and the SLOT-RECORD-NAME, SLOT-RECORD-DEFAULT, ;;; and SLOT-RECORD-PLIST selectors. ;;; ;;; The predicate ACTUAL-SLOT-RECORD-P returns T if a slot-record is of the first kind, ;;; and NIL otherwise. The function EXTRACT-ACTUAL-SLOT-RECORDS takes a list of ;;; slot-records and returns a list of all the actual slot-records in that list. ;;; The function FIND-SLOT finds an actual slot-record in a list of slot-records with ;;; a given name. (defun make-slot-record (slot-name default-value plist) (list* slot-name default-value plist)) (defun slot-record-name (slot-record) (car slot-record)) (defun slot-record-default (slot-record) (cadr slot-record)) (defun slot-record-plist (slot-record) (cddr slot-record)) (defun actual-slot-record-p (slot-record) (consp slot-record)) (defun parse-slot (slot-description) (etypecase slot-description (symbol (make-slot-record slot-description :unbound NIL)) (list (let ((slot-name (car slot-description)) (default (if (< (length slot-description) 2) :UNBOUND (second slot-description))) (plist (cddr slot-description))) (if (symbolp slot-name) (make-slot-record slot-name default plist) (ferror "Slot name ~S must be a symbol." slot-name)))))) (defun find-slot (name slot-records) "Return the first actual slot-record in the list SLOT-RECORDS with name NAME, or NIL if there is none." (cond ((null slot-records) NIL) ((and (actual-slot-record-p (first slot-records)) (eq name (slot-record-name (first slot-records)))) (first slot-records)) (t (find-slot name (rest slot-records))))) (defun extract-actual-slot-records (slot-records) (cond ((null slot-records) NIL) ((actual-slot-record-p (car slot-records)) (cons (car slot-records) (extract-actual-slot-records (cdr slot-records)))) (t (extract-actual-slot-records (cdr slot-records))))) ;;; Bookkeeping ;;; ;;; The global variable *defined-structures* keeps a record of each defstruct ;;; form. This information is needed for :INCLUDE options in subsequent defstructs. ;;; The procedures RECORD-NEW-DEFSTRUCT and LOOKUP-DEFSTRUCT make up the interface ;;; to this variable. ;;; ;;; *defined-structures* is a table that associates a symbol, the name of a ;;; defstruct, with a plist. Items on this plist include: ;;; :TYPE - The defstruct's type, which may be LIST, VECTOR, (VECTOR foo), or NIL. ;;; :SLOTS - A list of slot-records. ;;; ;;; *defined-structures* is currently being maintained as an a-list; sooner or later ;;; it should become a hash table. (defun record-new-defstruct (name &key type slot-records) (setq *defined-structures* (cons (cons name (list :TYPE type :SLOTS slot-records)) *defined-structures*))) (defun lookup-defstruct (name) "Return the plist associated with structure NAME, or :UNDEFINED." (let ((lookup (assoc name *defined-structures*))) (if lookup (cdr lookup) :UNDEFINED))) ;;; Structure objects (the default type for representing defstructs) ;;; (defun make-structure-object (name &rest elements) (SI::MAKE-ARRAY-INTO-NAMED-STRUCTURE (apply #'vector name elements))) (defun structure-object-ref (structure-object slot-number) (aref structure-object slot-number)) (defsetf structure-object-ref (structure-object slot-number) (x) `(SETF (AREF ,structure-object ,slot-number) ,x)) (defun structure-object-length (structure-object) (length structure-object)) (defun structure-object-p (thing) (named-structure-p thing)) (defun copy-structure-object (structure-object) (SI::MAKE-ARRAY-INTO-NAMED-STRUCTURE (copy-seq structure-object))) (defun set-structure-object-print-function (name print-function) (declare (ignore name print-function)) NIL)