Most Scheme programmers, including myself, do not like to use define-macro
, because it's totally unhygienic. I have no idea why you prefer to use them. With this in mind (that I would not write any define-macro
macros myself), I poked around Femtolisp (a Scheme-like implementation that also does not use hygienic macros) for its implementation of cond
:
(define-macro (cond . clauses)
(define (cond-clauses->if lst)
(if (atom? lst)
#f
(let ((clause (car lst)))
(if (or (eq? (car clause) 'else)
(eq? (car clause) #t))
(if (null? (cdr clause))
(car clause)
(cons 'begin (cdr clause)))
(if (null? (cdr clause))
; test by itself
(list 'or
(car clause)
(cond-clauses->if (cdr lst)))
; test => expression
(if (eq? (cadr clause) '=>)
(if (1arg-lambda? (caddr clause))
; test => (lambda (x) ...)
(let ((var (caadr (caddr clause))))
`(let ((,var ,(car clause)))
(if ,var ,(cons 'begin (cddr (caddr clause)))
,(cond-clauses->if (cdr lst)))))
; test => proc
(let ((b (gensym)))
`(let ((,b ,(car clause)))
(if ,b
(,(caddr clause) ,b)
,(cond-clauses->if (cdr lst))))))
(list 'if
(car clause)
(cons 'begin (cdr clause))
(cond-clauses->if (cdr lst)))))))))
(cond-clauses->if clauses))
Hope it works for you!
If what you prefer isn't old-style unhygienic macros, but simply a macro system that lets you play with the incoming form in the raw, many Scheme implementations provide an explicit renaming (ER) macro system, which allows you to manipulate the forms directly and still allow you to maintain hygiene by (as the name implies) explicitly renaming any identifiers that should be protected from shadowing by the macro call site. Here's Chibi Scheme's implementation of cond
:
(define-syntax cond
(er-macro-transformer
(lambda (expr rename compare)
(if (null? (cdr expr))
(if #f #f)
((lambda (cl)
(if (compare (rename 'else) (car cl))
(if (pair? (cddr expr))
(error "non-final else in cond" expr)
(cons (rename 'begin) (cdr cl)))
(if (if (null? (cdr cl)) #t (compare (rename '=>) (cadr cl)))
(list (list (rename 'lambda) (list (rename 'tmp))
(list (rename 'if) (rename 'tmp)
(if (null? (cdr cl))
(rename 'tmp)
(list (car (cddr cl)) (rename 'tmp)))
(cons (rename 'cond) (cddr expr))))
(car cl))
(list (rename 'if)
(car cl)
(cons (rename 'begin) (cdr cl))
(cons (rename 'cond) (cddr expr))))))
(cadr expr))))))
Major Scheme implementations are generally split into two camps in terms of what they use for low-level macros: syntax-case
and explicit renaming. Racket, Chez Scheme, Guile, etc. use syntax-case
. CHICKEN, MIT Scheme, Chibi Scheme, etc. use explicit renaming. So you won't be able to use the explicit renaming version above in Guile, because it's in the syntax-case
camp.