chickadee » procedural-macros

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?

This library provides the means for this to happen. Combining implicit renaming with destructuring, some macro-writing macros are defined, in particular, a 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 import 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 fender ...) .. xpr ....))syntax

This macro does not handle injected or keyword symbols. For this use macro-rules.

The implementation of these macros depends on the bind-case macro of the bindings package which does the pattern matching of macro-rules. Since the former can handle wildcards, non-symbol literals and fenders, so does the latter.

The module procedural-macros

procedural-macros

procedural-macros sym ..procedure

documentation procedure. Shows the exported symbols and the syntax of such an exported symbol, respectively.

macro-rules

(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 imported for-syntax if used in the preprocessing phase of a macro evaluation.

define-macro

(define-macro (name . args) (where fender ...) .. xpr ....))syntax

generates a hygienic implicit-renaming macro, name.

macro-let

(macro-let (((name . args) (where fender ...) .. xpr ...) ...) body ....)syntax

evaluates body ... in the context of parallel hygienic macros name ....

macro-letrec

(macro-letrec (((name . args) (where fender ...) .. xpr ...) ...) body ....)syntax

evaluates body ... 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.

define-ir-macro-transformer

(define-er-macro-transformer (name form inject compare?)syntax

wrapper around ir-macro-transformer.

define-er-macro-transformer

(define-er-macro-transformer (name form rename compare?)syntax

wrapper around er-macro-transformer.

with-mapped-symbols

(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

(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 .....

Requirements

bindings, basic-sequences

Usage

(use procedural-macros)

(import-for-syntax
 (only procedural-macros macro-rules once-only
                         with-mapped-symbols with-gensyms)

(import-for-syntax (only procedural-macros macro-rules with-gensyms once-only))

Examples

(use procedural-macros)

(import-for-syntax (only procedural-macros macro-rules once-only)
                   (only data-structures list-of?))

(use procedural-macros)
(import-for-syntax
  (only procedural-macros macro-rules with-gensyms once-only))

;; 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)
  (inject self)
  `(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"

Last update

Jul 28, 2016

Author

Juergen Lorenz

License

Copyright (c) 2015-2016, 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

2.0
New implementation based on bind-case from the bindings egg
1.1
fenders now writen in the form (var ok? ...) accepting zero or many predicates
1.0.3
wildcards ommitted, out of hygiene reasons
1.0.2
documentation procedure exported
1.0.1
bug fix in literals
1.0
initial import

Contents »