396 lines
12 KiB
Plaintext
396 lines
12 KiB
Plaintext
;;;; "mbe.scm" "Macro by Example" (Eugene Kohlbecker, R4RS)
|
|
;;; From: Dorai Sitaram, dorai@cs.rice.edu, 1991, 1997
|
|
;
|
|
;Permission to copy this software, to redistribute it, and to use it
|
|
;for any purpose is granted, subject to the following restrictions and
|
|
;understandings.
|
|
;
|
|
;1. Any copy made of this software must include this copyright notice
|
|
;in full.
|
|
;
|
|
;2. I have made no warrantee or representation that the operation of
|
|
;this software will be error-free, and I am under no obligation to
|
|
;provide any services, by way of maintenance, update, or otherwise.
|
|
;
|
|
;3. In conjunction with products arising from the use of this
|
|
;material, there shall be no use of my name in any advertising,
|
|
;promotional, or sales literature without prior written consent in
|
|
;each case.
|
|
|
|
;;; revised Dec. 6, 1993 to R4RS syntax (if not semantics).
|
|
;;; revised Mar. 2 1994 for SLIB (jaffer@ai.mit.edu).
|
|
;;; corrections, Apr. 24, 1997.
|
|
;;; revised Aug. 30 1999 for STk (eg@unice.fr)
|
|
;;;
|
|
;;; Last file update: 31-Aug-1999 15:56 (eg)
|
|
;;;
|
|
;;; A vanilla implementation of hygienic macro-by-example as described
|
|
;;; by Eugene Kohlbecker and in R4RS Appendix.
|
|
|
|
|
|
(define-module MBE
|
|
|
|
;;;
|
|
;;; Scheme utilities
|
|
;;;
|
|
(define (reverse! l)
|
|
(let loop ((l l) (r '()))
|
|
(if (null? l)
|
|
r
|
|
(let ((d (cdr l)))
|
|
(set-cdr! l r)
|
|
(loop d l)))))
|
|
|
|
;=============================================================================
|
|
|
|
(define hyg:rassq
|
|
(lambda (k al)
|
|
(let loop ((al al))
|
|
(if (null? al) #f
|
|
(let ((c (car al)))
|
|
(if (eq? (cdr c) k) c
|
|
(loop (cdr al))))))))
|
|
|
|
(define hyg:tag
|
|
(lambda (e kk al)
|
|
(cond ((pair? e)
|
|
(let* ((a-te-al (hyg:tag (car e) kk al))
|
|
(d-te-al (hyg:tag (cdr e) kk (cdr a-te-al))))
|
|
(cons (cons (car a-te-al) (car d-te-al))
|
|
(cdr d-te-al))))
|
|
((vector? e)
|
|
(list->vector
|
|
(hyg:tag (vector->list e) kk al)))
|
|
((symbol? e)
|
|
(cond ((eq? e '...) (cons '... al))
|
|
((memq e kk) (cons e al))
|
|
((hyg:rassq e al) =>
|
|
(lambda (c)
|
|
(cons (car c) al)))
|
|
(else
|
|
(let ((te (gensym)))
|
|
(cons te (cons (cons te e) al))))))
|
|
(else (cons e al)))))
|
|
|
|
;;untagging
|
|
|
|
(define hyg:untag
|
|
(lambda (e al tmps)
|
|
(if (pair? e)
|
|
(let ((a (hyg:untag (car e) al tmps)))
|
|
(if (list? e)
|
|
(case a
|
|
((quote) (hyg:untag-no-tags e al))
|
|
((if begin)
|
|
`(,a ,@(map (lambda (e1)
|
|
(hyg:untag e1 al tmps)) (cdr e))))
|
|
((set! define)
|
|
`(,a ,(hyg:untag-vanilla (cadr e) al tmps)
|
|
,@(map (lambda (e1)
|
|
(hyg:untag e1 al tmps)) (cddr e))))
|
|
((lambda) (hyg:untag-lambda (cadr e) (cddr e) al tmps))
|
|
((letrec) (hyg:untag-letrec (cadr e) (cddr e) al tmps))
|
|
((let)
|
|
(let ((e2 (cadr e)))
|
|
(if (symbol? e2)
|
|
(hyg:untag-named-let e2 (caddr e) (cdddr e) al tmps)
|
|
(hyg:untag-let e2 (cddr e) al tmps))))
|
|
((let*) (hyg:untag-let* (cadr e) (cddr e) al tmps))
|
|
((do) (hyg:untag-do (cadr e) (caddr e) (cdddr e) al tmps))
|
|
((case)
|
|
`(case ,(hyg:untag-vanilla (cadr e) al tmps)
|
|
,@(map
|
|
(lambda (c)
|
|
`(,(hyg:untag-vanilla (car c) al tmps)
|
|
,@(hyg:untag-list (cdr c) al tmps)))
|
|
(cddr e))))
|
|
((cond)
|
|
`(cond ,@(map
|
|
(lambda (c)
|
|
(hyg:untag-list c al tmps))
|
|
(cdr e))))
|
|
(else (cons a (hyg:untag-list (cdr e) al tmps))))
|
|
(cons a (hyg:untag-list* (cdr e) al tmps))))
|
|
(hyg:untag-vanilla e al tmps))))
|
|
|
|
(define hyg:untag-list
|
|
(lambda (ee al tmps)
|
|
(map (lambda (e)
|
|
(hyg:untag e al tmps)) ee)))
|
|
|
|
(define hyg:untag-list*
|
|
(lambda (ee al tmps)
|
|
(let loop ((ee ee))
|
|
(if (pair? ee)
|
|
(cons (hyg:untag (car ee) al tmps)
|
|
(loop (cdr ee)))
|
|
(hyg:untag ee al tmps)))))
|
|
|
|
(define hyg:untag-no-tags
|
|
(lambda (e al)
|
|
(cond ((pair? e)
|
|
(cons (hyg:untag-no-tags (car e) al)
|
|
(hyg:untag-no-tags (cdr e) al)))
|
|
((vector? e)
|
|
(list->vector
|
|
(hyg:untag-no-tags (vector->list e) al)))
|
|
((not (symbol? e)) e)
|
|
((assq e al) => cdr)
|
|
(else e))))
|
|
|
|
(define hyg:untag-lambda
|
|
(lambda (bvv body al tmps)
|
|
(let ((tmps2 (append! (hyg:flatten bvv) tmps)))
|
|
`(lambda ,bvv
|
|
,@(hyg:untag-list body al tmps2)))))
|
|
|
|
(define hyg:untag-letrec
|
|
(lambda (varvals body al tmps)
|
|
(let ((tmps (append! (map car varvals) tmps)))
|
|
`(letrec
|
|
,(map
|
|
(lambda (varval)
|
|
`(,(car varval)
|
|
,(hyg:untag (cadr varval) al tmps)))
|
|
varvals)
|
|
,@(hyg:untag-list body al tmps)))))
|
|
|
|
(define hyg:untag-let
|
|
(lambda (varvals body al tmps)
|
|
(let ((tmps2 (append! (map car varvals) tmps)))
|
|
`(let
|
|
,(map
|
|
(lambda (varval)
|
|
`(,(car varval)
|
|
,(hyg:untag (cadr varval) al tmps)))
|
|
varvals)
|
|
,@(hyg:untag-list body al tmps2)))))
|
|
|
|
(define hyg:untag-named-let
|
|
(lambda (lname varvals body al tmps)
|
|
(let ((tmps2 (cons lname (append! (map car varvals) tmps))))
|
|
`(let ,lname
|
|
,(map
|
|
(lambda (varval)
|
|
`(,(car varval)
|
|
,(hyg:untag (cadr varval) al tmps)))
|
|
varvals)
|
|
,@(hyg:untag-list body al tmps2)))))
|
|
|
|
(define hyg:untag-let*
|
|
(lambda (varvals body al tmps)
|
|
(let ((tmps2 (append! (reverse! (map car varvals)) tmps)))
|
|
`(let*
|
|
,(let loop ((varvals varvals)
|
|
(i (length varvals)))
|
|
(if (null? varvals) '()
|
|
(let ((varval (car varvals)))
|
|
(cons `(,(car varval)
|
|
,(hyg:untag (cadr varval)
|
|
al (list-tail tmps2 i)))
|
|
(loop (cdr varvals) (- i 1))))))
|
|
,@(hyg:untag-list body al tmps2)))))
|
|
|
|
(define hyg:untag-do
|
|
(lambda (varinistps exit-test body al tmps)
|
|
(let ((tmps2 (append! (map car varinistps) tmps)))
|
|
`(do
|
|
,(map
|
|
(lambda (varinistp)
|
|
(let ((var (car varinistp)))
|
|
`(,var ,@(hyg:untag-list (cdr varinistp) al
|
|
(cons var tmps)))))
|
|
varinistps)
|
|
,(hyg:untag-list exit-test al tmps2)
|
|
,@(hyg:untag-list body al tmps2)))))
|
|
|
|
(define hyg:untag-vanilla
|
|
(lambda (e al tmps)
|
|
(cond ((pair? e)
|
|
(cons (hyg:untag-vanilla (car e) al tmps)
|
|
(hyg:untag-vanilla (cdr e) al tmps)))
|
|
((vector? e)
|
|
(list->vector
|
|
(hyg:untag-vanilla (vector->list e) al tmps)))
|
|
((not (symbol? e)) e)
|
|
((memq e tmps) e)
|
|
((assq e al) => cdr)
|
|
(else e))))
|
|
|
|
(define hyg:flatten
|
|
(lambda (e)
|
|
(let loop ((e e) (r '()))
|
|
(cond ((pair? e) (loop (car e)
|
|
(loop (cdr e) r)))
|
|
((null? e) r)
|
|
(else (cons e r))))))
|
|
|
|
;;;; End of hygiene filter.
|
|
|
|
;;; finds the leftmost index of list l where something equal to x
|
|
;;; occurs
|
|
(define mbe:position
|
|
(lambda (x l)
|
|
(let loop ((l l) (i 0))
|
|
(cond ((not (pair? l)) #f)
|
|
((equal? (car l) x) i)
|
|
(else (loop (cdr l) (+ i 1)))))))
|
|
|
|
;;; tests if expression e matches pattern p where k is the list of
|
|
;;; keywords
|
|
(define mbe:matches-pattern?
|
|
(lambda (p e k)
|
|
(cond ((mbe:ellipsis? p)
|
|
(and (or (null? e) (pair? e))
|
|
(let* ((p-head (car p))
|
|
(p-tail (cddr p))
|
|
(e-head=e-tail (mbe:split-at-ellipsis e p-tail)))
|
|
(and e-head=e-tail
|
|
(let ((e-head (car e-head=e-tail))
|
|
(e-tail (cdr e-head=e-tail)))
|
|
(and (every
|
|
(lambda (x) (mbe:matches-pattern? p-head x k))
|
|
e-head)
|
|
(mbe:matches-pattern? p-tail e-tail k)))))))
|
|
((pair? p)
|
|
(and (pair? e)
|
|
(mbe:matches-pattern? (car p) (car e) k)
|
|
(mbe:matches-pattern? (cdr p) (cdr e) k)))
|
|
((symbol? p) (if (memq p k) (eq? p e) #t))
|
|
(else (equal? p e)))))
|
|
|
|
;;; gets the bindings of pattern variables of pattern p for
|
|
;;; expression e;
|
|
;;; k is the list of keywords
|
|
(define mbe:get-bindings
|
|
(lambda (p e k)
|
|
(cond ((mbe:ellipsis? p)
|
|
(let* ((p-head (car p))
|
|
(p-tail (cddr p))
|
|
(e-head=e-tail (mbe:split-at-ellipsis e p-tail))
|
|
(e-head (car e-head=e-tail))
|
|
(e-tail (cdr e-head=e-tail)))
|
|
(cons (cons (mbe:get-ellipsis-nestings p-head k)
|
|
(map (lambda (x) (mbe:get-bindings p-head x k))
|
|
e-head))
|
|
(mbe:get-bindings p-tail e-tail k))))
|
|
((pair? p)
|
|
(append (mbe:get-bindings (car p) (car e) k)
|
|
(mbe:get-bindings (cdr p) (cdr e) k)))
|
|
((symbol? p)
|
|
(if (memq p k) '() (list (cons p e))))
|
|
(else '()))))
|
|
|
|
;;; expands pattern p using environment r;
|
|
;;; k is the list of keywords
|
|
(define mbe:expand-pattern
|
|
(lambda (p r k)
|
|
(cond ((mbe:ellipsis? p)
|
|
(append (let* ((p-head (car p))
|
|
(nestings (mbe:get-ellipsis-nestings p-head k))
|
|
(rr (mbe:ellipsis-sub-envs nestings r)))
|
|
(map (lambda (r1)
|
|
(mbe:expand-pattern p-head (append r1 r) k))
|
|
rr))
|
|
(mbe:expand-pattern (cddr p) r k)))
|
|
((pair? p)
|
|
(cons (mbe:expand-pattern (car p) r k)
|
|
(mbe:expand-pattern (cdr p) r k)))
|
|
((symbol? p)
|
|
(if (memq p k) p
|
|
(let ((x (assq p r)))
|
|
(if x (cdr x) p))))
|
|
(else p))))
|
|
|
|
;;; returns a list that nests a pattern variable as deeply as it
|
|
;;; is ellipsed
|
|
(define mbe:get-ellipsis-nestings
|
|
(lambda (p k)
|
|
(let sub ((p p))
|
|
(cond ((mbe:ellipsis? p) (cons (sub (car p)) (sub (cddr p))))
|
|
((pair? p) (append (sub (car p)) (sub (cdr p))))
|
|
((symbol? p) (if (memq p k) '() (list p)))
|
|
(else '())))))
|
|
|
|
;;; finds the subenvironments in r corresponding to the ellipsed
|
|
;;; variables in nestings
|
|
(define mbe:ellipsis-sub-envs
|
|
(lambda (nestings r)
|
|
(some (lambda (c)
|
|
(if (mbe:contained-in? nestings (car c)) (cdr c) #f))
|
|
r)))
|
|
|
|
;;; checks if nestings v and y have an intersection
|
|
(define mbe:contained-in?
|
|
(lambda (v y)
|
|
(if (or (symbol? v) (symbol? y)) (eq? v y)
|
|
(some (lambda (v_i)
|
|
(some (lambda (y_j)
|
|
(mbe:contained-in? v_i y_j))
|
|
y))
|
|
v))))
|
|
|
|
;;; split expression e so that its second half matches with
|
|
;;; pattern p-tail
|
|
(define mbe:split-at-ellipsis
|
|
(lambda (e p-tail)
|
|
(if (null? p-tail) (cons e '())
|
|
(let ((i (mbe:position (car p-tail) e)))
|
|
(if i (cons (butlast e (- (length e) i))
|
|
(list-tail e i))
|
|
(error "mbe:split-at-ellipsis : bad-arg"))))))
|
|
|
|
;;; tests if x is an ellipsing pattern, i.e., of the form
|
|
;;; (blah ... . blah2)
|
|
(define mbe:ellipsis?
|
|
(lambda (x)
|
|
(and (pair? x) (pair? (cdr x)) (eq? (cadr x) '...))))
|
|
|
|
;define-syntax
|
|
|
|
(define-macro (define-syntax macro-name syn-rules)
|
|
(if (or (not (pair? syn-rules))
|
|
(not (eq? (car syn-rules) 'syntax-rules)))
|
|
(error "define-syntax: in ~S, bad syntax-rules" macro-name syn-rules)
|
|
(let ((keywords (cons macro-name (cadr syn-rules)))
|
|
(clauses (cddr syn-rules)))
|
|
`(define-macro (,macro-name . macro-arg)
|
|
(let ((macro-arg (cons ',macro-name macro-arg))
|
|
(keywords ',keywords)
|
|
(mbe:matches-pattern? (with-module mbe mbe:matches-pattern?))
|
|
(mbe:expand-pattern (with-module mbe mbe:expand-pattern))
|
|
(mbe:get-bindings (with-module mbe mbe:get-bindings))
|
|
(hyg:tag (with-module mbe hyg:tag))
|
|
(hyg:untag (with-module mbe hyg:untag))
|
|
(hyg:flatten (with-module mbe hyg:flatten)))
|
|
|
|
(cond ,@(map
|
|
(lambda (clause)
|
|
(let ((in-pattern (car clause))
|
|
(out-pattern (cadr clause)))
|
|
`((mbe:matches-pattern? ',in-pattern macro-arg
|
|
keywords)
|
|
(let ((tagged-out-pattern+alist
|
|
(hyg:tag
|
|
',out-pattern
|
|
(append! (hyg:flatten ',in-pattern) keywords)
|
|
'())))
|
|
(hyg:untag
|
|
(mbe:expand-pattern
|
|
(car tagged-out-pattern+alist)
|
|
(mbe:get-bindings ',in-pattern macro-arg keywords)
|
|
keywords)
|
|
(cdr tagged-out-pattern+alist)
|
|
'())))))
|
|
clauses)
|
|
(else (error "~S: no matching clause: ~S"
|
|
',macro-name ',clauses))))))))
|
|
|
|
)
|
|
|
|
;; Make only define-syntax visible from MBE
|
|
(with-module Scheme
|
|
(define define-syntax (with-module MBE define-syntax)))
|
|
|
|
(provide "defsyntax")
|