From: Tin Gherdanarra
Subject: 60 Lines of generator code -- did I get it right?
Date: 
Message-ID: <4cek1kF15p36jU1@individual.net>
Dear cll,

Here is a brief code-sample inspired by Chris Riesbeck's piece on
"generators", to be found here:
http://www.cs.northwestern.edu/academics/courses/325/readings/graham/generators.html
(Tip of the propellor-hat to Zach Beane for the quality link)

I tried to get everything right, but how can I know that I suck?
Comments welcome, especially negative ones. Thanks for any support.

------8<----------- CUT HERE ------------------>8------------


;;; The "Generator Exhausted" token
;;; returned by a generator as soon as
;;; it has hit on the end of the
;;; series.
(defun dumy-end-of-gen nil)

;;; The generator base class
(defclass generator nil
   ; stores what the generator "is made of"
   ; i.e. its parameter(s)
   ((params :accessor generator-params 	
	   :initarg :params		
	   :initform nil)

    ; cache for the current, i.e. last object fetched.
    (this-obj :accessor generator-this-obj 	
	     :initarg :this-obj			
	     :initform 0)))


;;; The three generator methods:
;;; along the lines of
;;; *i == EOF? --> returns t or nil
;;; *i --> returns two values: the
;;; current item and t or nil upon end or not
;;; *i++ --> returns the current item and t or nil upon end or not
;;; gen-get and gen-next return #'dumy-end-of-gen for the
;;; current item if the generator has been exhausted

;;; have we hit the end yet? Override for you generator
(defgeneric gen-endp (gen))

;;; get the current item in the series, overridable
(defgeneric gen-get (gen))

;;; advance the generator to the next item
(defgeneric gen-next (gen))

;;; overridable for fleshing out your generator
(defgeneric gen-next-core (gen))
				

;;; The one-size-fits-all gen-next. Do not override
(defmethod gen-next ((gen generator))
   (let ((x (gen-get gen)))	; remember what we will return
     (if (gen-endp gen)		; report if we are done
       (values #'dumy-end-of-gen t)
       (progn
         (gen-next-core gen)		; advance generator
         (values x (gen-endp gen))))))	; return value, end status

;;;
;;; Let's make a simple generator for numbers
;;; (make-generator-num 100 120) gives you the numbers 100 thru 119
;;; (make-generator-num 100 nil) gives you the nubmers 100 thru infinity
;;;

(defclass generator-num (generator) nil)

;;; A convenience wrapper for setting up a generator-num
;;; /params/ stores what the generator-num is made of
(defun make-generator-num (m &optional n)
   (make-instance 'generator-num :params (list m n)))


;;; little helper function doing the actual work
;;; of checking if we've hit the end of the rope
;;; yet
(defun gen-endp-equal (m n)
   (and (numberp n) (>= m n)))

;;; our flesh for checking for the generator-num end
(defmethod gen-endp ((gen generator-num))
   (gen-endp-equal
     (car (generator-params gen))
     (cadr (generator-params gen))))

;;; give us the current integer
(defmethod gen-get ((gen generator-num))
   (car (generator-params gen)))

;;; Advancing a generator-num, called by
;;; the generic gen-get method
(defmethod gen-next-core ((gen generator-num))
   (incf (car (generator-params gen))))


;;;
;;; Let's make a simple generator for lines in
;;; a file. Pass a stream from which to fetch
;;; the lines. This is where the /this-obj/ comes
;;; into play. It stores the line fetched last
;;; for an arbitrary number of calls to
;;; gen-get, without actually accessing the
;;; stream.
;;;

(defclass generator-line (generator) nil)

;;; Convenience wrapper for setting up a generator-line
;;; /params/ stores the stream for the generator-line
(defun make-generator-line (st)
   (make-instance 'generator-line :this-obj
		 (read-line st nil #'dumy-end-of-gen)
		 :params st))

;;; Our gen-get specialization for the generator-line
(defmethod gen-get ((gen generator-line))
   (generator-this-obj gen))

;;; Get us another line, called by the
;;; generic gen-next method. Note that
;;; /read-line/ is instructed to return
;;; a #'dumy-end-of-gen token upon hitting :EOF
(defmethod gen-next-core ((gen generator-line))
   (setf (generator-this-obj gen)
	(read-line (generator-params gen)
		   nil
		   #'dumy-end-of-gen)))

;;; Have we hit the end yet? We know if /read-line/
;;; has handed us an end-of-gen token
(defmethod gen-endp ((gen generator-line))
   (or (null (generator-this-obj gen))
       (eql #'dumy-end-of-gen
	   (generator-this-obj gen))))


;;; Go ahead, try it
(defparameter *gn* (make-generator-num 3 7))
(defparameter *gl* (make-generator-line
                        (open "gens.lisp" :direction :input)))



------8<----------- CUT HERE ------------------>8------------

-- 
Lisp kann nicht kratzen, denn Lisp ist fluessig