Ian Price <ianpric...@googlemail.com> writes: > The problem is one that occurs when hygienic and non-hygienic macros are > mixed. Here, and-let* is the non-hygienic one. Basically, in a hygienic > macro, you carry around a bunch of context to allow you to refer to the > right variable names. Then defmacro comes along, strips all that away, > and uses the raw symbol names. > > We can fix and-let* to be hygienic, that's pretty easy, but I'm not sure > what you can do about this in general. It's not like you can pass in the > gensymed name, because that will break the way people who for some > reason still right defmacros expect them to work. > > Dunno, maybe I'm just missing some insight here.
This discussion came up on #scheme yesterday, and Eli Barzilay mentioned that Racket tries to be more clever with its legacy defmacro by keeping a hash associating input sexps with their syntax objects. This is in no way a silver bullet, but for many macros, this is liable to work out really nicely. An example implementation is below, though I have a few notes indicating things needing fixed. (define-syntax define-macro (lambda (x) "Define a defmacro." (syntax-case x () ((_ (macro . args) doc body1 body ...) (string? (syntax->datum #'doc)) #'(define-macro macro doc (lambda args body1 body ...))) ((_ (macro . args) body ...) #'(define-macro macro #f (lambda args body ...))) ((_ macro transformer) #'(define-macro macro #f transformer)) ((_ macro doc transformer) (or (string? (syntax->datum #'doc)) (not (syntax->datum #'doc))) #'(define-syntax macro (lambda (y) (define (recontextualize form context default) (define (walk x) ;; is there any possibility of a circular syntax object? (cond ((hashv-ref context x) => (lambda (x) x)) ((pair? x) (cons (walk (car x)) (walk (cdr x)))) ((vector? x) (vector-map walk x)) ((symbol? x) (datum->syntax default x)) (else x))) (walk form)) (define (build-context form stx-form) (define ctx (make-hash-table)) (define (walk x y) (hashv-set! ctx x y) ;; is there any possibility of a circular syntax object? (cond ((pair? x) (walk (car x) (car (syntax-e y))) (walk (cdr x) (cdr (syntax-e y)))) ((vector? x) (vector-for-each2 walk x (syntax-e y))) ;; Any other types needing handled? )) (walk form stx-form) ctx) (define (vector-for-each2 f v1 v2) (define len (vector-length v1)) (define v* (make-vector len)) (let loop ((i 0)) (unless (= i len) (vector-set! v* i (f (vector-ref v1 i) (vector-ref v2 i))) (loop (+ i 1)))) v*) (define (vector-map f v) (define len (vector-length v)) (define v* (make-vector len)) (let loop ((i 0)) (unless (= i len) (vector-set! v* i (f (vector-ref v i))) (loop (+ i 1)))) v*) (define (syntax-e obj) (syntax-case obj () [(first . rest) (cons #'first #'rest)] [#(value (... ...)) (apply vector #'(value (... ...)))] [a (syntax->datum #'a)])) doc ;; FIXME: may not be a docstring, and so would fail above #((macro-type . defmacro) (defmacro-args args)) (syntax-case y () ((_ . args) (let* ((v (syntax->datum #'args)) (ctx (build-context v #'args))) (recontextualize (apply transformer v) ctx y)))))))))) This version of define-macro still fails on the original macros as posted by Panicz Maciej Godek, but gives the "right" result using stis's ck macro version. At 2:30am, I'm not liable to get to the bottom of why till tomorrow, but I think doing something like this is a positive step. -- Ian Price -- shift-reset.com "Programming is like pinball. The reward for doing it well is the opportunity to do it again" - from "The Wizardy Compiled"