- Procedural macros made easy
- Last update
- Version History
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?
This library provides the means for this to happen. Combining implicit renaming with destructuring, some macro-writing macros are defined, in particular, a (mostly) hygienic procedural define-macro and a procedural version of syntax-rules, named macro-rules. The latter is almost as easy to use as syntax-rules, but much more powerfull. Here is its syntax
- (macro-rules sym ... (key ...) (pat (where fender ...) .. tpl) ....)syntax
Note the special use of dots here and below: Three dots are ellipses, as usual, i.e. the pattern on the left is repeated zero or more times, two dots, zero or one time, 4 dots one ore several times.
This form can be used instead of syntax-rules in define-syntax, let-sytax and letrec-syntax, provided, you use it for-syntax. sym ... denote the injected symbols to break hygiene (if there is none, the constructed macro is hygienic). key ... and pat .... symbols are as in syntax-rules, fender ... are pairs of pattern variables and predicates, the latter applied to the former must be true for the pattern to match, and tpl .... are usually quasiquoted expressions.
And here is the syntax of define-macro
- (define-macro (name . args) (where (x . xs) ...) .. xpr ....)syntax
The implementation of these macros depends on the bind-case macro of the basic-macros package which does the pattern matching of macro-rules. Since the former can handle wildcards, non-symbol literals and fenders, so does the latter.
- procedural-macros sym ..procedure
documentation procedure. Shows the exported symbols and the syntax of such an exported symbol, respectively.
- (macro-rules sym ... (keyword ...) (pat (where fender ...) .. tpl) ....)syntax
like syntax-rules, but the templates are usually quasiquote-expressions. Moreover, the symbols sym ... are injected, if there are any. Here and in the sequel, fender is an expresseion of the form
(var ok? ...)
checking a pattern variable, var, against a sequence of predicates.
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 same applies to fenders: If they are not passed, the pattern is not matched.
macro-rules must be used for-syntax if used in the preprocessing phase of a macro evaluation.
- (define-macro (name . args) (where (x . xs) ...) .. xpr ....))syntax
where xs is either a list of predicates, providing fenders, or a singleton with one of the symbols keyword or injection, providing keyword parameters or unhygienic macros. Generates a hygienic implicit-renaming macro, name, if no injection parameter is given.
- (macro-let (((name . args) (where fender ...) .. xpr ...) ...) body ....)syntax
evaluates body ... in the context of parallel hygienic macros name ....
- (macro-letrec (((name . args) (where fender ...) .. xpr ...) ...) body ....)syntax
evaluates body ... in the context of recursive hygienic macros name ....
- (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.
- (define-er-macro-transformer (name form inject compare?)syntax
wrapper around ir-macro-transformer.
- (define-er-macro-transformer (name form rename compare?)syntax
wrapper around er-macro-transformer.
- (define-er-macro (name form rename-symbol compare?) xpr . xprs)syntax
defines an explicit-renaming-macro name with macro-code form renaming each symbol in the body xpr . xprs starting with rename-symbol automatically.
- (define-ir-macro (name form inject-symbol compare?) xpr . xprs)syntax
defines an implicit-renaming-macro name with macro-code form injecting each symbol in the body xpr . xprs starting with inject-symbol automatically.
- (with-mapped-symbols mapper prefix- (prefix-x ....) xpr ....)syntax
binds a series of prefixed names, prefix-x .... to the images of the original names, x ...., under mapper and evaluates xpr .... in this context
- (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 .....
(use procedural-macros) (use-for-syntax (only procedural-macros macro-rules once-only with-mapped-symbols with-gensyms)
(use procedural-macros) (use-for-syntax (only procedural-macros macro-rules)) (import-for-syntay (only data-structures list-of?)) ;; 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-macro (alambda args xpr . xprs) (self injection) `(letrec ((,self (lambda ,args ,xpr ,@xprs))) ,self)) ;; effective membership testing (define-macro (in? what equ? . choices) (let ((insym 'in)) `(let ((,insym ,what)) (or ,@(map (lambda (choice) `(,equ? ,insym ,choice)) choices))))) ;; 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 ((temp test)) `(if ,temp (,xpr ,temp)))) ((_ (test => xpr) . clauses) (let ((temp test)) `(if ,temp (,xpr ,temp) (my-cond ,@clauses)))) ((_ (test)) `(if #f #f)) ((_ (test) . clauses) (let ((temp test)) `(if ,temp ,temp (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 var-val-pairs . body) (where (var-val-pairs (list-of? pair?))) (let ((vars (map car var-val-pairs)) (vals (map cadr var-val-pairs)) (aux (map (lambda (x) (gensym)) var-val-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) ,@body)))) (my-letrec ((o? (lambda (m) (if (zero? m) #f (e? (- m 1))))) (e? (lambda (n) (if (zero? n) #t (o? (- n 1)))))) (list (o? 95) (e? 95))) ;; local macros (letrec-syntax ( (sec (macro-rules () ((_ lst) `(car (res ,lst))))) (res (macro-rules () ((_ lst) `(cdr ,lst)))) ) (sec '(1 2 3))) ;-> 2 (macro-letrec ( ((sec lst) `(car (res ,lst))) ((res lst) `(cdr ,lst)) ) (sec '(1 2 3))) ;-> 2 (macro-let ( ((fir lst) (where (lst list?)) `(car ,lst)) ((res lst) (where (lst list?)) `(cdr ,lst)) ) (fir (res '(1 2 3)))) ;-> 2 ;; non-symbolic literals (define-syntax foo (macro-rules () ((_ "foo" x) x) ((_ #f x) x) ((_ a b) (where (a string?)) `(list ,a ,b)) ((_ a b) (where (a odd?)) `(list ,a ,b)) ((_ a b) a))) (foo "foo" 1) ; -> 1 (foo "bar" 2) ; -> '("bar" 2) (foo #f 'blabla) ; -> 'blabla (foo 1 2) ; -> '(1 2) (foo 2 3) ; -> 2 (define-syntax add (macro-rules () ((_ x y) (where (x string?) (y string?)) `(string-append ,x ,y)) (( _ x y) (where (x integer?) (y integer?)) `(+ ,x ,y)))) (add 1 2) ;-> 3 (add "x" "y") ;-> "xy"
Nov 07, 2017
Copyright (c) 2015-2017, 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.
- dependency on basic-macros instead of bindings
- New implementation based on bind-case from the bindings egg
- fenders now writen in the form (var ok? ...) accepting zero or many predicates
- wildcards ommitted, out of hygiene reasons
- documentation procedure exported
- bug fix in literals
- initial import