git.fiddlerwoaroof.com
Raw Blame History
;;; walk-ast.scm -- general-purpose walkers for AST structures.
;;;
;;; author :  Sandra & John
;;; date   :  30 Jan 1992
;;;
;;;

;;;=====================================================================
;;; Basic support, macros
;;;=====================================================================


;;; Here is a macro for accessing the walker function for a particular
;;; type.
;;; The walk-type names the walker.
;;; If an accessor argument is provided, it must name a SETF'able function
;;; or macro that takes a type descriptor as an argument.  This is used to
;;; do the lookup of the walker function for the given type.
;;; If no explicit accessor is provided, one will be created.  It will
;;; use a hash table keyed off the type names to store the walker functions.
;;; In either case, the mapping between the walker name and accessor is
;;; stored in the hash table ast-walker-table.

(define ast-walker-table (make-table))

(define-syntax (define-walker walk-type . maybe-accessor)
  (let ((accessor-name  (if (null? maybe-accessor)
			    (symbol-append walk-type '-walker)
			    (car maybe-accessor))))
    (setf (table-entry ast-walker-table walk-type) accessor-name)
	`(begin
	   ,@(if (null? maybe-accessor)
		 (let ((accessor-table (symbol-append '* walk-type '-table*)))
		   `((define ,accessor-table (make-table))
		     (define-syntax (,accessor-name td)
		       (list 'table-entry
			     ',accessor-table
			     (list 'td-name td)))))
		 '())
	   (setf (table-entry ast-walker-table ',walk-type)
		 ',accessor-name)
	   ',walk-type)))

(define-syntax (ast-walker walk-type td)
  (let ((accessor  (table-entry ast-walker-table walk-type)))
    `(,accessor ,td)))


;;; This macro dispatches a walker on an object of type ast-node.

(define-syntax (call-walker walk-type object . args)
  (let ((temp (gensym "OBJ")))
    `(let ((,temp ,object))
       (funcall (or (ast-walker ,walk-type (struct-type-descriptor ,temp))
		    (walker-not-found-error ',walk-type ,temp))
		,temp
		,@args))
    ))

(define (walker-not-found-error walk-type object)
  (error "There is no ~a walker for structure ~A defined."
	 walk-type (td-name (struct-type-descriptor object))))



;;; Define an individual walker for a particular type.  The body should
;;; return either the original object or a replacement for it.

(define-syntax (define-walker-method walk-type type args . body)
  (let ((function-name  (symbol-append walk-type '- type)))
    `(begin
       (define (,function-name ,@args) ,@body)
       (setf (ast-walker ,walk-type (lookup-type-descriptor ',type))
	     (function ,function-name))
       ',function-name)))



;;;=====================================================================
;;; Support for default walker methods
;;;=====================================================================

;;; Two kinds of walkers are supported: a collecting walker, which
;;; walks over a tree collecting some sort of returned result while
;;; not changing the tree itself, and a rewriting walker which maps
;;; ast to ast.

;;; The basic template for a collecting walk is:
;;; (define-walker-method walk-type type (object accum)
;;;   (sf1 (sf2 object ... (sfn accum)))
;;; where sfi = slot function for the ith slot.
;;;
;;; The slot-processor should be the name of a macro that is called with four
;;; arguments:  a slot descriptor, the object type name, a form 
;;; representing the object being traversed, and a form representing the 
;;; accumulated value.
;;; If the slot does not participate in the walk, this last argument should
;;; be returned unchanged as the expansion of the macro.

(define-syntax (define-collecting-walker-methods walk-type types
		 slot-processor)
  `(begin
     ,@(map (lambda (type)
	      (make-collecting-walker-method walk-type type slot-processor))
	    types)))

(define (make-collecting-walker-method walk-type type slot-processor)
  `(define-walker-method ,walk-type ,type (object accum)
     object   ; prevent possible unreferenced variable warning
     ,(make-collecting-walker-method-body
       'accum
       type
       (td-slots (lookup-type-descriptor type))
       slot-processor)))

(define (make-collecting-walker-method-body base type slots slot-processor)
  (if (null? slots)
      base
      `(,slot-processor ,(car slots) ,type object 
		 ,(make-collecting-walker-method-body
		     base type (cdr slots) slot-processor))))



;;; A rewriting walker traverses the ast modifying various subtrees.
;;; The basic template here is:
;;; (define-walker-method walker type (object . args)
;;;   (setf (slot1 object) (walk (slot1 object)))
;;;   (setf (slot2 object) (walk (slot2 object)))
;;;   ...
;;;   object)

;;; The basic macro to generate default walkers is as above except
;;; that the slot-processor macro is called with only 
;;; two arguments, the slot and object type.
;;; The `args' is the actual lambda-list for the methods, and bindings
;;; can be referenced inside the code returned by the macro.
;;; If a slot participates in the walk, the macro should return code
;;; to SETF the slot, as in the template above.  Otherwise, the macro
;;; should just return #f.

(define-syntax (define-modify-walker-methods walk-type types args
		 slot-processor)
  `(begin
     ,@(map (lambda (type)
	      (make-modify-walker-method walk-type type args
					 slot-processor))
	    types)))

(define (make-modify-walker-method walk-type type args slot-processor)
  `(define-walker-method ,walk-type ,type ,args
     ,@(cdr args)  ; prevent possible unreferenced variable warnings
     ,@(map (lambda (slot)
	      `(,slot-processor ,slot ,type))
	    (td-slots (lookup-type-descriptor type)))
     ,(car args)))