Update of /project/mcclim/cvsroot/mcclim/Drei
In directory clnet:/tmp/cvs-serv6550
Modified Files:
lisp-syntax.lisp
Log Message:
Made Lisp syntax `form-to-object' handle label reader macros.
--- /project/mcclim/cvsroot/mcclim/Drei/lisp-syntax.lisp 2007/01/10 20:54:13 1.16
+++ /project/mcclim/cvsroot/mcclim/Drei/lisp-syntax.lisp 2007/01/15 22:13:16 1.17
@@ -2664,10 +2664,30 @@
(list* (car result) item (cdr result)))
(t (list op item result))))
-(defun token-to-symbol (syntax token &optional (case (readtable-case *readtable*)))
- "Return the symbol `token' represents. If the symbol cannot be
-found in a package, an uninterned symbol will be returned."
- (form-to-object syntax token :case case :no-error t))
+(define-condition reader-invoked (condition)
+ ((%end-mark :reader end-mark :initarg :end-mark
+ :initform (error "You must provide an ending-mark for
+the condition")
+ :documentation "The position at which the reader
+stopped reading, form-to-object conversion should be resumed
+from this point.")
+ (%object :reader object :initarg :object
+ :initform (error "You must provide the object that
+was returned by the reader")
+ :documentation "The object that was returned by the reader."))
+ (:documentation "Signal that the reader has been directly
+invoked on the buffer contents, that the object of this condition
+should be assumed as the result of the form-conversion."))
+
+(defun invoke-reader (syntax form)
+ "Use the system reader to handle `form' and signal a
+`reader-invoked' condition with the resulting data."
+ (let* ((start-mark (clone-mark (high-mark (buffer syntax)))))
+ (setf (offset start-mark) (start-offset form))
+ (let* ((stream (make-buffer-stream :buffer (buffer syntax)
+ :start-mark start-mark))
+ (object (read-preserving-whitespace stream)))
+ (signal 'reader-invoked :end-mark (point stream) :object object))))
(define-condition form-conversion-error (simple-error user-condition-mixin)
((syntax :reader syntax :initarg :syntax
@@ -2698,6 +2718,153 @@
(setf (offset (point drei))
(start-offset (form condition))))
+;;; Handling labels (#n= and #n#) takes a fair bit of machinery, most
+;;; of which is located here. We follow an approach similar to that
+;;; found in the SBCL reader, where we replace instances of #n# with a
+;;; special unique marker symbol that we replace before returning the
+;;; final object. We maintain two tables, one that maps labels to
+;;; placerholder symbols and one that maps placeholder symbols to the
+;;; concrete objects.
+
+(defvar *labels->placeholders* nil
+ "This variable holds an alist mapping labels (as integers) to a
+placeholder symbol. It is used for implementing the label reader
+macros (#n=foo #n#).")
+
+(defvar *label-placeholders->object* nil
+ "This variable holds an alist mapping placeholder symbols to
+the object. It is used for implementing the label reader
+macros (#n=foo #n#).")
+
+(defgeneric extract-label (syntax form)
+ (:documentation "Get the label of `form' as an integer."))
+
+(defmethod extract-label ((syntax lisp-syntax) (form sharpsign-equals-form))
+ (let ((string (form-string syntax (first (children form)))))
+ (parse-integer string :start 1 :end (1- (length string)) :radix 10)))
+
+(defmethod extract-label ((syntax lisp-syntax) (form sharpsign-sharpsign-lexeme))
+ (let ((string (form-string syntax form)))
+ (parse-integer string :start 1 :end (1- (length string)) :radix 10)))
+
+(defun register-form-label (syntax form &rest args)
+ "Register the label of `form' and the corresponding placeholder
+symbol. `Form' must be a sharpsign-equals form (#n=), and if the
+label has already been registered, an error of type
+`form-conversion-error' will be signalled. Args will be passed to
+`form-to-object' for the creation of the object referred to by
+the label. Returns `form' converted to an object."
+ (let* ((label (extract-label syntax form))
+ (placeholder-symbol (gensym)))
+ (when (assoc label *labels->placeholders*)
+ (form-conversion-error syntax form "multiply defined label: ~A" label))
+ (push (list label placeholder-symbol) *labels->placeholders*)
+ (let ((object (apply #'form-to-object syntax
+ (second (children form)) args)))
+ (push (list placeholder-symbol object) *label-placeholders->object*)
+ object)))
+
+(defgeneric find-and-register-label (syntax form label limit &rest args)
+ (:documentation "Find the object referred to by the integer
+value `label' in children of `form' or `form' itself. `Args' will
+be passed to `form-to-object' for the creation of the
+object. `Limit' is a buffer offset delimiting where not to search
+past."))
+
+(defmethod find-and-register-label ((syntax lisp-syntax) (form form)
+ (label integer) (limit integer) &rest args)
+ (find-if #'(lambda (child)
+ (when (and (formp child)
+ (< (start-offset form) limit))
+ (apply #'find-and-register-label syntax child label limit args)))
+ (children form)))
+
+(defmethod find-and-register-label ((syntax lisp-syntax) (form sharpsign-equals-form)
+ (label integer) (limit integer) &rest args)
+ (when (and (= (extract-label syntax form) label)
+ (< (start-offset form) limit))
+ (apply #'register-form-label syntax form args)))
+
+(defun ensure-label (syntax form label &rest args)
+ "Ensure as best as possible that `label' exist. `Form' is the
+form that needs the value of the label, limiting where to end the
+search. `Args' will be passed to `form-to-object' if it is
+necessary to create a new object for the label."
+ (unless (assoc label *labels->placeholders*)
+ (apply #'find-and-register-label syntax (form-toplevel form syntax) label (start-offset form) args)))
+
+(defun label-placeholder (syntax form label &optional search-whole-form &rest args)
+ "Get the placeholder for `label' (which must be an integer). If
+the placeholder symbol cannot be found, the label is undefined,
+and an error of type `form-conversion-error' will be
+signalled. If `search-whole-form' is true, the entire
+top-level-form will be searched for the label reference if it has
+not already been seen, upwards from `form', but not past
+`form'. `Args' will be passed as arguments to `form-to-object' to
+create the labelled object."
+ (when search-whole-form
+ (apply #'ensure-label syntax form label args))
+ (let ((pair (assoc label *labels->placeholders*)))
+ (second pair)))
+
+;;; The `circle-subst' function is cribbed from SBCL.
+
+(defvar *sharp-equal-circle-table* nil
+ "Objects already seen by `circle-subst'.")
+
+(defun circle-subst (old-new-alist tree)
+ "This function is kind of like NSUBLIS, but checks for
+circularities and substitutes in arrays and structures as well as
+lists. The first arg is an alist of the things to be replaced
+assoc'd with the things to replace them."
+ (cond ((not (typep tree
+ '(or cons (array t) structure-object standard-object)))
+ (let ((entry (find tree old-new-alist :key #'first)))
+ (if entry (second entry) tree)))
+ ((null (gethash tree *sharp-equal-circle-table*))
+ (setf (gethash tree *sharp-equal-circle-table*) t)
+ (cond ((typep tree '(or structure-object standard-object))
+ ;; I am time and again saved by the MOP as I code
+ ;; myself into a corner.
+ (let ((class (class-of tree)))
+ (dolist (slotd (clim-mop:class-slots class))
+ (when (clim-mop:slot-boundp-using-class class tree slotd)
+ (let* ((old (clim-mop:slot-value-using-class class tree slotd))
+ (new (circle-subst old-new-alist old)))
+ (unless (eq old new)
+ (setf (clim-mop:slot-value-using-class
+ class tree slotd)
+ new)))))))
+ ((arrayp tree)
+ (loop for i from 0 below (length tree) do
+ (let* ((old (aref tree i))
+ (new (circle-subst old-new-alist old)))
+ (unless (eq old new)
+ (setf (aref tree i) new)))))
+ (t
+ (let ((a (circle-subst old-new-alist (car tree)))
+ (d (circle-subst old-new-alist (cdr tree))))
+ (unless (eq a (car tree))
+ (rplaca tree a))
+ (unless (eq d (cdr tree))
+ (rplacd tree d)))))
+ tree)
+ (t tree)))
+
+(defun replace-placeholders (&rest values)
+ "Replace the placeholder symbols in `values' with the real
+objects as determined by `*label-placeholders->objects*' and
+return the modified `values' as multiple return values."
+ (values-list
+ (mapcar #'(lambda (value)
+ (let ((*sharp-equal-circle-table* (make-hash-table :test #'eq :size 20)))
+ (circle-subst *label-placeholders->object* value)))
+ values)))
+
+(defvar *form-to-object-depth* 0
+ "This variable is used to keep track of how deeply nested calls
+to `form-to-object' are.")
+
(defgeneric form-to-object (syntax form &key no-error package read backquote-level case)
(:documentation "Return the Lisp object `form' would become if
read. An attempt will be made to construct objects from
@@ -2710,17 +2877,25 @@
will be signalled for incomplete forms.")
(:method :around ((syntax lisp-syntax) (form form) &key package no-error &allow-other-keys)
;; Ensure that every symbol that is READ will be looked up
- ;; in the correct package. Also handle quoting.
+ ;; in the correct package.
(flet ((act ()
- (let ((*package* (or package
- (package-at-mark
- syntax (start-offset form)))))
-
- (call-next-method))))
- (if no-error
- (handler-case (act)
- (form-conversion-error ()))
- (act))))
+ (handler-case
+ (multiple-value-call #'replace-placeholders (call-next-method))
+ (reader-invoked (c)
+ (if (> (offset (end-mark c)) (end-offset form))
+ (signal c)
+ (object c)))
+ (form-conversion-error (e)
+ (unless no-error
+ (error e))))))
+ (let ((*form-to-object-depth* (1+ *form-to-object-depth*))
+ (*package* (or package (package-at-mark
+ syntax (start-offset form)))))
+ (if (= *form-to-object-depth* 1)
+ (let ((*labels->placeholders* nil)
+ (*label-placeholders->object* nil))
+ (act))
+ (act)))))
(:method ((syntax lisp-syntax) (form t) &rest args
&key no-error &allow-other-keys)
(unless no-error
@@ -2738,9 +2913,20 @@
(defmethod form-to-object ((syntax lisp-syntax) (form list-form) &rest args
&key &allow-other-keys)
- (mapcan #'(lambda (child)
- (multiple-value-list (apply #'form-to-object syntax child args)))
- (remove-if-not #'formp (children form))))
+ (labels ((recurse (elements)
+ (unless (null elements)
+ (handler-case
+ (nconc (multiple-value-list
+ (apply #'form-to-object syntax (first elements) args))
+ (recurse (rest elements)))
+ (reader-invoked (c)
+ (let ((remaining-elements (remove (offset (end-mark c)) elements
+ :key #'start-offset :test #'>)))
+ (if (and (not (null (rest elements)))
+ (null remaining-elements))
+ (signal c)
+ (cons (object c) (recurse remaining-elements)))))))))
+ (recurse (remove-if-not #'formp (children form)))))
(defmethod form-to-object ((syntax lisp-syntax) (form complete-quote-form) &rest args
&key (backquote-level 0) &allow-other-keys)
@@ -2825,7 +3011,7 @@
(defmethod form-to-object ((syntax lisp-syntax) (form number-lexeme)
&key &allow-other-keys)
(let ((*read-base* (base syntax)))
- (values (read-from-string (form-string syntax form)))))
+ (invoke-reader syntax form)))
(defmethod form-to-object ((syntax lisp-syntax) (form simple-vector-form)
&key &allow-other-keys)
@@ -2837,7 +3023,7 @@
(defmethod form-to-object ((syntax lisp-syntax) (form complete-string-form)
&key &allow-other-keys)
- (values (read-from-string (form-string syntax form))))
+ (invoke-reader syntax form))
(defmethod form-to-object ((syntax lisp-syntax) (form function-form) &rest args)
(list 'cl:function (apply #'form-to-object syntax (second (children form)) args)))
@@ -2875,9 +3061,10 @@
(defmethod form-to-object ((syntax lisp-syntax) (form undefined-reader-macro-form)
&key read &allow-other-keys)
- ;; ???
+ ;; This is likely to malfunction for some really evil reader macros,
+ ;; in that case, you need to extend the parser to understand them.
(when read
- (read-from-string (form-string syntax form))))
+ (invoke-reader syntax form)))
(defmethod form-to-object ((syntax lisp-syntax) (form literal-object-form) &key &allow-other-keys)
(object-after (start-mark form)))
@@ -2910,6 +3097,14 @@
(when read
(values (eval (apply #'form-to-object syntax (first-form (children form)) args)))))
+(defmethod form-to-object ((syntax lisp-syntax) (form sharpsign-equals-form)
+ &rest args)
+ (apply #'register-form-label syntax form args))
+
+(defmethod form-to-object ((syntax lisp-syntax) (form sharpsign-sharpsign-lexeme)
+ &rest args)
+ (apply #'label-placeholder syntax form (extract-label syntax form) t args))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Arglist fetching.