- explicit renaming
- syntax-case
- syntactic closure
R6RS has syntax-case and rumour says R7RS would have explicit renaming. According to this article, if implementations have one of them, then the rest can be implemented atop it. I'm wondering is it really true? Very lame conclusion is true. Because Sagittarius uses kind of syntactic closure and implements both explicit renaming and syntax-case. Now, can it be done in a portable way?
So I've wrote this. It seems this can work most of R6RS implementations except Racket. Though I haven't tested on Larceny, Guile and Vicare yet, they are using either Psyntax or van Tonder expander such as Mosh and IronScheme (Psyntax) or NMosh (van Tonder). So should work.
The basic idea of the implementation is very simple. rename procedure is a simple wrapper of
datum->syntax
. compare is free-identifier=?
. Then wrap the returning form with datum->syntax*
.The initial revision of the Gist used mere
datum->syntax
. This wasn't good enough because the procedure should only accept datum not syntax object. The error was raised by Psyntax implementations and Racket. Then I've been suggested to walk thought the returning form.
@tk_riple (1)renameはマクロ定義環境を参照すると理解してます。で、最初の例でもマクロ定義環境のfooはローカルのfooですよね? (2)erマクロ展開手続きから帰ってきたフォームをwalkして生シンボルをマクロ使用環境のsyntaxに置換するんじゃだめですか?
— Kilo Kawai (@anohana) October 24, 2015
I first thought this won't work because traversing and constructing a new form would return a list not a syntax object. However I was just didn't consider thoroughly. If I use
syntax-case
and quasisyntax
(with-syntax
could also be), then I can construct syntax object containing syntax object renamed by er-macro-transformer
. So I've rewrite the code. Then most of the R6RS implementation seem working. Even Racket seems working if the macro is very simple like the one on the comment.Now, my question is 'Is this R6RS portable?'. R6RS standard libraries 12.2 says:
The distinction between the terms “syntax object” and “wrapped syntax object” is important. For example, when invoked by the expander, a transformer (section 12.3) must accept a wrapped syntax object but may return any syntax object, including an unwrapped syntax object.So transformer *MAY* return unwrapped syntax object. Though what I'm doing is returning wrapped syntax object so should be fine. What I couldn't read is whether or not a syntax object can contain syntax object(s) inserted by other transformer. If this is required feature, then this might be a bug of Racket. Otherwise this is not portable.
I hope it's a bug of Racket, then you (not me) can write an explicit renaming SRFI with sample implementation.
For convenience, embedded source.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2015-10-24 | |
- Changed comment | |
- Walk though returning form to wrap. Racket still doesn't work. | |
- Fixed incorrect example on definition of macro and usage environment. | |
Comment of: https://twitter.com/anohana/status/657865512370634753 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(library (er-macro-transformer) | |
(export er-macro-transformer) | |
(import (for (rnrs) run expand)) | |
#| | |
Definition of usage or macro env | |
These are the envrionments which refer when a macro is being expanded | |
or a macro is bound. For example, if the rename uses usage environment | |
then the following refers local binding (#t) | |
(define foo 'foo) | |
(define-syntax bar | |
(er-macro-transformer | |
(lambda (f r c) (r 'foo)))) | |
(let ((foo #t)) | |
(bar)) | |
If the rename uses macro environment then the above returns 'foo. | |
I'm not sure which is *proper*. | |
|# | |
#| | |
Accoding to the rumour, syntax-case, er macro and syntactic closure have | |
the same power and if implementations support one of them then the rest | |
can be implemented on top of it. I'm starting wondering is it really | |
true? Espesially er or syntactic closure atop syntax-case. | |
The following is *NOT* R6RS portable implementation. More precisely, | |
this doesn't work on Racket (plt-r6rs). | |
Let's seem how this works. `er-macro-transformer` takes a procedure | |
which takes 3 arguments, form, rename and compare. The procedure | |
returns a form which may or may not contain syntax object renamed | |
by given rename procedure. The following is an example: | |
(define-syntax foo | |
(er-macro-transformer | |
(lambda (f r c) | |
`(,(r 'begin) | |
(,(r 'display) ',f) | |
(,(r 'newline)))))) | |
(foo) | |
;; (foo) will be | |
;; (#<syntax begin> (#<syntax display> '(foo)) (#<syntax newline>)) | |
Then walk through the returning form and wrap with datum->syntax* if | |
there's symbol(s). datum->syntax* does the wrapping trick. Which is | |
very similar with datum->syntax but it can accept a datum which contains | |
syntax object. | |
|# | |
(define (datum->syntax* k form) | |
(define (vector-copy vec len) | |
(let ((v (make-vector len))) | |
(do ((i 0 (+ i 1))) | |
((= i len) v) | |
(vector-set! v i (vector-ref vec i))))) | |
(syntax-case form () | |
((a . d) | |
#`(#,(datum->syntax* k #'a) . #,(datum->syntax* k #'d))) | |
(v | |
(vector? #'v) | |
(let ((len (vector-length form))) | |
(let loop ((i 0) (vec #f)) | |
(if (= i len) | |
(or vec form) | |
(let ((e (datum->syntax* k (vector-ref form i)))) | |
(if (eq? e (vector-ref form i)) | |
(loop (+ i 1) vec) | |
(let ((v (or vec (vector-copy form len)))) | |
(vector-set! v i e) | |
(loop (+ i 1) v)))))))) | |
(s (symbol? #'s) (datum->syntax k #'s)) | |
(_ form))) | |
#| | |
;; this uses macro env for rename | |
(define-syntax er-macro-transformer | |
(lambda (x) | |
(syntax-case x () | |
((k proc) | |
#'(let ((rename (lambda (form) (datum->syntax #'k form))) | |
(compare (lambda (a b) (free-identifier=? a b))) | |
(transformer proc)) | |
(lambda (stx) | |
(syntax-case stx () | |
((kk args (... ...)) | |
(datum->syntax* #'kk | |
(transformer (syntax->datum #'(kk args (... ...))) | |
rename compare)))))))))) | |
|# | |
;; This uses usage env for rename | |
(define (er-macro-transformer transformer) | |
(lambda (x) | |
(syntax-case x () | |
((k args ...) | |
(let ((rename (lambda (form) (datum->syntax #'k form))) | |
(compare (lambda (a b) (free-identifier=? a b)))) | |
(datum->syntax* #'k | |
(transformer #'(k args ...) rename compare))))))) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(import (rnrs) (for (er-macro-transformer) expand)) | |
(define-syntax foo | |
(er-macro-transformer | |
(lambda (f r c) | |
`(,(r 'begin) | |
(,(r 'display) ',f) | |
(,(r 'newline)))))) | |
(foo) | |
#| | |
OK: Sagittarius, NMosh, Ypsilon, Mosh, Chez, IronScheme (I believe Guile, Larceny, Vicare as well) | |
NG: Racket | |
Is 9 out of 10 portable? | |
|# |