~ chicken-core (chicken-5) 806c211b443a33bee673b50a46193257c9521775
commit 806c211b443a33bee673b50a46193257c9521775
Author: felix <felix@call-with-current-continuation.org>
AuthorDate: Sun Sep 26 22:05:03 2010 +0200
Commit: felix <felix@call-with-current-continuation.org>
CommitDate: Sun Sep 26 22:05:03 2010 +0200
ir-macro-transformer docs by sjamaan
diff --git a/manual/Macros b/manual/Macros
index 5c40447c..796e189a 100644
--- a/manual/Macros
+++ b/manual/Macros
@@ -149,7 +149,7 @@ as
(else `(,(rename 'if)
,test
(,(rename 'begin) ,@(cdr first))
- (cond ,@rest))))))))
+ (,(r 'cond) ,@rest))))))))
In this example the identifier {{else}} is renamed before being passed
to the comparison predicate, so the comparison will be true if and
@@ -159,6 +159,9 @@ as {{else}} denotes in the syntactic environment in which the {{cond}}
macro was defined. If {{else}} were not renamed before being passed to
the comparison predicate, then it would match a local variable that
happened to be named {{else}}, and the macro would not be hygienic.
+The final recursive call to {{cond}} also needs to be renamed because
+someone might create an alias for this macro and use it in a {{let}}
+where {{cond}} is an ordinary variable.
Some macros are non-hygienic by design. For example, the
following defines a {{loop}} macro that implicitly binds {{exit}} to an
@@ -196,12 +199,89 @@ not hygienic. Like {{loop}}, it must be written using procedurally:
(,(r 'if) (,(r 'not) ,test) (exit #f))
,@body))))
+Think about it: If we ''did'' rename {{exit}}, it would refer to an
+{{exit}} procedure existing in the context of the macro's definition.
+That one [[Unit library#exit|actually exists]]; it is the procedure
+that exits the Scheme interpreter. Definitely ''not'' the one we want :)
+So now we make it refer to an {{exit}} that's locally bound in the
+environment where the macro is expanded.
+
Note: this implementation of explicit-renaming macros allows passing
arbitrary expressions to the renaming and comparison procedures. When
being renamed, a fresh copy of the expression will be produced, with all
identifiers renamed appropriately. Comparison also supports arbitrary
expressions as arguments.
+=== Implicit renaming macros
+
+Explicit renaming macros generally require the user to perform quite a
+few renames, because most identifiers that aren't taken from the input
+expression should generally be inserted hygienically. It would make
+more sense to give the output expression as-is, and only explicitly
+convert those identifiers that you want to treat as ''unhygienic''.
+
+This can be done with implicit renaming macros. They just swap the
+default insertion "mode" from unhygienic to hygienic, so to speak.
+Here's the {{cond}} example from the previous section as an ir-macro:
+
+
+ (ir-macro-transformer
+ (lambda (exp inject compare)
+ (let ((clauses (cdr exp)))
+ (if (null? clauses)
+ `(quote unspecified)
+ (let* ((first (car clauses))
+ (rest (cdr clauses))
+ (test (car first)))
+ (cond ((and (symbol? test)
+ (compare test 'else))
+ `(begin ,@(cdr first)))
+ (else `(if ,test
+ (begin ,@(cdr first))
+ (cond ,@rest)))))))))
+
+In this example the identifier {{else}} does ''not'' need to be renamed
+before being passed to the comparison predicate because it is already
+''implicitly'' renamed. This comparison will also be true if and
+only if the test expression is an identifier that denotes the same
+thing in the syntactic environment of the expression being transformed
+as {{else}} denotes in the syntactic environment in which the {{cond}}
+macro was defined. If {{else}} were not renamed before being passed to
+the comparison predicate, then it would match a local variable that
+happened to be named {{else}}, and the macro would not be hygienic.
+
+As you can see, the code is a lot clearer because it isn't obscured
+by excessive renaming.
+
+Here's the {{loop}} macro so you can see how hygiene can be broken
+with implicit renaming macros:
+
+ (define-syntax loop
+ (ir-macro-transformer
+ (lambda (expr inject compare)
+ (let ((body (cdr expr)))
+ `(call-with-current-continuation
+ (lambda (,(inject 'exit))
+ (let f () ,@body (f))))))))
+
+The {{while}} macro is a little trickier: do we inject the call to
+{{exit}} or not? Just like the explicit renaming macro version
+did ''not'' rename it, we must inject it to allow it to be captured
+by the {{loop}} macro:
+
+ (define-syntax while
+ (ir-macro-transformer
+ (lambda (expr inject compare)
+ (let ((test (cadr expr))
+ (body (cddr expr)))
+ `(loop
+ (if (not ,test) (,(inject 'exit) #f))
+ ,@body)))))
+
+Note: Just like explicit renaming macros, this implementation of
+implicit renaming macros allow passing arbitrary expressions to
+the injection and comparison procedures. The injection procedure
+also return fresh copies of its input.
---
Previous: [[Non-standard macros and special forms]]
diff --git a/manual/Unit expand b/manual/Unit expand
index 61fbceba..b9b6f41b 100644
--- a/manual/Unit expand
+++ b/manual/Unit expand
@@ -43,6 +43,31 @@ This procedure does nothing and is available for writing low-level
macros in a more portable fashion, without hard-coding the signature
of a transformer procedure.
+==== ir-macro-transformer
+
+<procedure>(ir-macro-transformer TRANSFORMER)</procedure>
+
+This procedure accepts a ''reverse'' syntax transformer, also known as
+an ''implicit renaming macro transformer''. This is a transformer which
+works almost like er-macro-transformer, except the rename and compare
+procedures it receives work a little differently.
+
+The rename procedure is now called {{inject}} and instead of renaming
+the identifier to be resolved in the macro's definition environment,
+it will explicitly ''inject'' the identifier to be resolved in the
+expansion environment. Any non-injected identifiers in the output
+expression produced by the transformer will be implicitly renamed to
+refer to the macro's environment instead. All identifiers in the
+input expression are of course implicitly injected just like with
+explicit renaming macros.
+
+To compare an input identifier you can generally compare to the bare
+symbol and only free identifiers will match. In practice, this means
+that when you would call e.g. {{(compare (cadr expression) (rename 'x))}}
+in an ER macro, you simply call {{(compare (cadr expression) 'x)}} in the
+IR macro. Likewise, an ''unhygienic'' ER macro's comparison
+{{(compare sym 'abc)}} should be written as {{(compare sym (inject 'abc))}}
+in an IR macro.
---
Previous: [[Unit library]]
Trap