TOC »
Procedural macros made easy
The Scheme standard, R5RS, only provides declarative macros based on syntax-rules. They are easy to use, but rather limited. For example, you can only create hygienic macros, you have no control over the expansion process, in particular, you can't use local procedures to be evaluated at compile time. To overcome this limitations, R6RS offers syntax-case macros, but that's a mouthfull ...
Fortunately, Chicken offers two versions of procedural macros, explicit and implicit renaming macros. They offer full flexibility without any limitations but are tedious to use.
First, you must care to avoid variable capture with renaming if you want hygienic macros, or you must decide which variables should be captured on purpose. Implicit renaming here helps a lot: You simply inject names which you want to be captured, the others are renamed automatically by the runtime system.
Second, you must do the destructuring of the macro code by hand. Wouldn't it be nice, if this could be done automatically behind the scene as well? Well, two macros of the bindings egg, bind and bind-case, will help here.
This library provides the means for this to happen. In particular, you'll find variants of good old define-macro. And macro-rules are implemented, which looks much like syntax-rules but doesn't have its limitations.
Module procedural-macros
define-er-macro
- (define-er-macro (name . args) (where . fenders) prefix xpr . xprs)syntax
- (define-er-macro (name . args) prefix xpr . xprs)syntax
- (define-er-macro name (pat (where . fenders) prefix xpr . xprs) . others)syntax
- (define-er-macro name (pat prefix xpr . xprs) . others)syntax
where fenders are of the form (key? sym) most of the time, to check for keywords. A version of good old define-macro, where symbols prefixed with prefix are automatically renamed.
define-ir-macro
- (define-ir-macro (name . args) (where . fenders) prefix xpr . xprs)syntax
- (define-ir-macro (name . args) prefix xpr . xprs)syntax
- (define-ir-macro name (pat (where . fenders) prefix xpr . xprs) . others)syntax
- (define-ir-macro name (pat prefix xpr . xprs) . others)syntax
where fenders are of the form (key? sym) most of the time, to check for keywords. A version of good old define-macro, where symbols prefixed with prefix are automatically injected.
define-macro
- (define-macro (name . args) body )syntax
where body is either
- (with-explicit-renaming (c? %x ...) xpr ....)
- (with-implicit-renaming (c? %x ...) xpr ....) or simply
- xpr ....
defines an explicit- or implicit-renaming macro with body xpr .... c? is a compare-routine to handle keys and %x ... are renamed or injected symbols to be used in the body.
The last form is implicit-renaming without injections and keys.
macro-rules
- (macro-rules sym ... (key ...) (pat tpl) ....)syntax
like syntax-rules, but the templates are usually quasiquote-expressions. Moreover, the symbols sym ... are injected, if there are any.
Note, that non-symbol literals are accepted in each pat and considered a match if they are equal to the corresponding expression in the macro-code. The keys are transformed to keyword literals behind the scene.
macro-rules must be imported for syntax if used in the preprocessing phase of a macro evaluation.
macro-let
- (macro-let (((name . args) xpr ...) ...) xpr ....)syntax
evaluates xpr .... in the context of parallel hygienic macros name ...
macro-letrec
- (macro-letrec (((name . args) xpr ...) ...) xpr ....)syntax
evaluates xpr .... in the context of recursive hygienic macros name ...
once-only
- (once-only (x ...) body ....)syntax
to be used in a macro-body to avoid side-effects. The arguments x ... are only evaluated once.
once-only must be imported for-syntax.
with-renamed-symbols
- (with-renamed-symbols (renamer %x ....) xpr ....)syntax
binds a series of prefixed names, %x .... to the images of the original names, x ...., under renamer and evaluates xpr .... in this context.
The prefix is arbitrary, but must be only one letter. The macro must be imported for syntax.
with-gensyms
- (with-gensyms (x ...) xpr ....)syntax
to be used in a macro body and hence to be imported for-syntax. Generates a list of gensyms x ... which can be used in xpr .....
The macro must be imported for syntax.
procedural-macros
- procedural-macros sym ..procedure
documentation procedure. Shows the exported symbols and the syntax of such an exported symbol, respectively.
Requirements
bindings
Usage
(import procedural-macros) (import-for-syntax (only procedural-macros macro-rules once-only with-renamed-symbols with-gensyms)
Examples
(import procedural-macros) (import-for-syntax (only checks <<) (only bindings bind bind-case) (only procedural-macros macro-rules with-renamed-symbols once-only)) ;; NUMERIC AND VERBOSE IF AS ER-MACRO (define-er-macro (er-nif xpr pos zer neg) % `(,%let ((,%result ,xpr)) (,%cond ((,%positive? ,%result) ,pos) ((,%negative? ,%result) ,neg) (,%else ,zer)))) (define-er-macro er-vif ((_ test (then . xprs) (else . yprs)) (where (key? then) (key? else)) % `(,%if ,test (,%begin ,@xprs) (,%begin ,@yprs)))) ;; COND AND CASE AS ER- OR IR-MACRO (define-er-macro er-cond ((_ (else xpr . xprs)) (where (key? else)) % `(,%begin ,xpr ,@xprs)) ((_ (test => xpr)) (where (key? =>)) % `(,%let ((,%tmp ,test)) (,%if ,%tmp (,xpr ,%tmp)))) ((_ (test => xpr) . clauses) (where (key? =>)) % `(,%let ((,%tmp ,test)) (,%if ,%tmp (,xpr ,%tmp) (,%er-cond ,@clauses)))) ((_ (test)) % ;`(if #f #f)) test) ((_ (test) . clauses) % `(,%let ((,%tmp ,test)) (,%if ,%tmp ,%tmp (,%er-cond ,@clauses)))) ((_ (test xpr . xprs)) % `(,%if ,test (,%begin ,xpr ,@xprs))) ((_ (test xpr . xprs) . clauses) % `(,%if ,test (,%begin ,xpr ,@xprs) (,%er-cond ,@clauses))) ) (define-ir-macro ir-case* ; helper ((_ key (else result . results)) (where (key? else)) % `(begin ,result ,@results)) ((_ key (keys result . results)) % `(if (memv ,key ',keys) (begin ,result ,@results))) ((_ key (keys result . results) clause . clauses) % `(if (memv ,key ',keys) (begin ,result ,@results) (ir-case* ,key ,clause ,@clauses))) ) (define-ir-macro (ir-case key clause . clauses) % ;`(let ((tmp ,key)) ; ok ; (ir-case* tmp ,clause ,@clauses))) (let ((tmp key)) ; ok `(ir-case* ,tmp ,clause ,@clauses))) ;; ALAMBDA AS ER- AND IR-MACRO (define-ir-macro (ir-alambda args xpr . xprs) % `(letrec ((,%self (lambda ,args ,xpr ,@xprs))) ,%self)) (define-er-macro (er-alambda args xpr . xprs) % `(,%letrec ((self (,%lambda ,args ,xpr ,@xprs))) self)) ;; TWO ANAPHORIC MACROS (define-syntax aif (macro-rules it () ((_ test consequent) `(let ((,it ,test)) (if ,it ,consequent))) ((_ test consequent alternative) `(let ((,it ,test)) (if ,it ,consequent ,alternative))))) (define-syntax alambda (macro-rules self () ((_ args xpr . xprs) `(letrec ((,self (lambda ,args ,xpr ,@xprs))) ,self)))) ;; VERBOSE IF (define-er-macro % (vvif test (then . xprs) (else . yprs)) (lambda (compare?) (if (and (compare? then %then) (compare? else %else)) `(,%if ,test (,%begin ,@xprs) (,%begin ,@yprs)) `(,%error 'vvif "wrong keys" then else)))) ;; EFFICIENT MEMBERSHIP TESTING (define-macro (in what equ? . choices) (let ((insym 'in)) `(let ((,insym ,what)) (or ,@(map (lambda (choice) `(,equ? ,insym ,choice)) choices))))) ;; FOR WITH ONCE-ONLY (define-macro (for (var start end) xpr . xprs) (once-only (start end) `(do ((,var ,start (add1 ,var))) ((= ,var ,end)) ,xpr ,@xprs))) ;; VERBOSE IF (define-syntax vif (macro-rules (then else) ((_ test (then xpr . xprs)) `(if ,test (begin ,xpr ,@xprs))) ((_ test (else xpr . xprs)) `(if ,(not test) (begin ,xpr ,@xprs))) ((_ test (then xpr . xprs) (else ypr . yprs)) `(if ,test (begin ,xpr ,@xprs) (begin ,ypr ,@yprs))))) ;; PROCEDURAL VERSION OF COND (define-syntax my-cond (macro-rules (else =>) ((_ (else xpr . xprs)) `(begin ,xpr ,@xprs)) ((_ (test => xpr)) `(let ((tmp ,test)) (if tmp (,xpr tmp)))) ((_ (test => xpr) . clauses) `(let ((tmp ,test)) (if tmp (,xpr tmp) (my-cond ,@clauses)))) ((_ (test)) ;`(if #f #f)) test) ((_ (test) . clauses) `(let ((tmp ,test)) (if tmp tmp (my-cond ,@clauses)))) ((_ (test xpr . xprs)) `(if ,test (begin ,xpr ,@xprs))) ((_ (test xpr . xprs) . clauses) `(if ,test (begin ,xpr ,@xprs) (my-cond ,@clauses))) )) ;; PROCEDURAL VERSION OF LETREC (define-macro (my-letrec pairs xpr . xprs) (<< pairs (list-of? pair?)) (let ((vars (map car pairs)) (vals (map cadr pairs)) (aux (map (lambda (x) (gensym)) pairs))) `(let ,(map (lambda (var) `(,var #f)) vars) (let ,(map (lambda (a v) `(,a ,v)) aux vals) ,@(map (lambda (v e) `(set! ,v ,e)) vars vals) ,xpr ,@xprs)))) ;; NON-SYMBOLIC LITERALS (define-syntax foo (macro-rules () ((_ "foo" x) x) ((_ #f x) `(list 'false)) ((_ #f x) 'false) ((_ a b) (<< a string?) `(list ,a ,b)) ((_ a b) (<< a odd?) `(list ,a ,b)) ((_ a b) a))) ;; LOCAL MACROS (macro-let ( ((first lst) `(car (<< ,lst list?))) ((rest lst) `(cdr (<< ,lst list?))) ) (first (rest '(1 2 3)))) (macro-letrec ( ((second lst) `(car (rest ,lst))) ((rest lst) `(cdr ,lst)) ) (second '(1 2 3)))
Last update
May 28, 2020
Author
Repository
This egg is hosted on the CHICKEN Subversion repository:
https://anonymous@code.call-cc.org/svn/chicken-eggs/release/5/procedural-macros
If you want to check out the source code repository of this egg and you are not familiar with Subversion, see this page.
License
Copyright (c) 2015-2020, Juergen Lorenz All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Version History
- 3.0.1
- some code simplified
- 3.0
- define-er-macro and define-ir-macro added
- 2.1
- bug in macro-let and macro-letrec fixed
- 2.0
- simplyfied and streamlined rewrite. Only one module remains.
- 1.1
- fixed some bugs reported by Diego. I thank him.
- 1.0.1
- port from chicken-4 procedural- and basic-macros