You are looking at historical revision 24981 of this page. It may differ significantly from its current revision.

Low-level macros made easy

This module contains some macros to make the use of low-level macros easier. It replaces the now obsolete modules er-macros and ir-maceos.

Recall that those macros are implemented as transformer routines, which are three-parameter procedures enclosed in er-macro-transformer or ir-macro-transformer respectively

(er-macro-transformer
  (lambda (form rename compare?) ...))
(ir-macro-transformer
  (lambda (form inject compare?) ...))

The programmer's job is to destructure the macro-code, form, and to do the renaming of all symbols which should appear in the macro-expansion by hand in the explicit-renaming case or to inject those symbols, which should not be renamed in the implicit-renaming case. In any case, symbols which are not renamed are unhygienic.

The job of destructuring the macro-code is tedious and error prone, it can be done by a tool, bind from the contracts module, for example. It is reproduced here but not exported to make the module self-contained. Based on that we will implement macros er-macro-rules and ir-macro-rules here, which mimic syntax-rules by design.

The other job, compare? literal symbols supplied by the macro's client with renamed ones, something which is needed to use symbols like else and => in the cond macro, remains to be done. Hence er-macro-rules and ir-macros are unhygienic by design. They export compare? to their local scopes. Moreover, ir-macro-rules has to export inject to its local scope in case it is to produce new symbols - think about structure definitions, for example - which must not be renamed, off course.

Important note

All macros in this module except with-aliases are unhygienic by design. They pollute the local (!) namespace with the symbol compare? (and inject in the implicit renaming case). But the macros implemented with those macros can be - and are in most cases - hygienic.

Programming interface

low-level-macros

[procedure] (low-level-macros sym)

where sym is optional.

This is the documentation dispatcher. Without a symbol, it lists all exported symbols of the module, with one of these symbols it prints the documentation of that symbol.

er-macro-rules

[syntax] (er-macro-rules (%sym ...) (code0 xpr0) (code1 xpr1) ...)

references a renamed version of sym ... under the name %sym ... and pairs the differnt macro-codes code0 code1 ... with expressions xpr0 xpr1 ..., which usually evalute to backquoted templates.

This macro is unhygienic by design, it introduces the symbol compare? into its scope.

ir-macro-rules

[syntax] (ir-macro-rules (sym ...) (code0 xpr0) (code1 xpr1) ...)

pairs the differnt macro-codes code0 code1 ... with expressions xpr0 xpr1 ..., which usually evalute to backquoted templates in the scope of injected symbols sym ....

This macro is unhygienic by design, it introduces the two symbols inject and compare? into its scope.

Unhygienic macros can not be implemented with syntax-rules, so we must use one of the low-level transformers for the implementation.

Based on er-macro-rules and ir-macro-rules, it's easy to implement three macros, macro-define, macro-let and macro-letrec, which facilitate the implementation of low-level macros even more: We simply match one pattern, the macro code, against a list of the form

 (with-renamed (%sym ...) xpr . xprs)

where %sym ... are aliases of sym ... in the explicit-renaming case, or

 (with-injected (sym ...) xpr . xprs)

where sym ... are unhygienic symbols in the implicit-renaming case and the sequence xpr . xprs produce the macro-expansion. Since the list of injected symbols is empty in most cases, the wrapper (with-injected () ...) kann be omitted. If that is the case, implicit-renaming is used as default.

macro-define

[syntax] (macro-define code (with-renamed (%sym ...) xpr . xprs))
[syntax] (macro-define code (with-injected (sym ...) xpr . xprs))
[syntax] (macro-define code xpr . xprs))

where code is the complete macro-code (name . args), i.e. the pattern of a macro call, and the rest is explained above.

macro-let and macro-letrec are local versions of macro-define, where the local macros are evaluated in parallel or recursively.

macro-let

[syntax] (macro-let ((code0 clause0) (code1 clause1)...) xpr . xprs)

where code0, code1 are as in macro-define and each clause is one of

  (with-renamed (%sym ...) xpr . xprs)

  (with-injected (sym ...) xpr . xprs)

  xpr . xprs

This is a local version of macro-define, allowing a list of (code clause) lists to be processed in xpr . xprs in parallel.

macro-letrec

[syntax] (macro-letrec ((code0 clause0) (code1 clause1)...) xpr . xprs)

where code0, code1 are as in macro-define and each clause is one of

  (with-renamed (%sym ...) xpr . xprs)

  (with-injected (sym ...) xpr . xprs)

  xpr . xprs

This is a local version of macro-define, allowing a list of (code clause) lists to be processed in xpr . xprs recursively.

with-aliases

[syntax] (with-aliases (op %sym0 %sym1 ...) xpr . xprs)

binds each %sym0 %sym1 ... to (op sym0) (op sym1) ... and evaluates xpr . xprs in this context. Usually used in explicit-renaming macros where op is rename.

Requires

Nothing

Usage

(use low-level-macros)
(import-for-syntax (only low-level-macros ir-macro-rules er-macro-rules))

Examples

(use low-level-macros)
(import-for-syntax (only low-level-macros ir-macro-rules er-macro-rules))

(define-syntax aif
  (er-macro-rules (%let %if)
    ((_ test then)
     `(,%let ((it ,test))
        (,%if it ,then)))
    ((_ test then else)
     `(,%let ((it ,test))
        (,%if it ,then ,else)))))

(define-syntax acond
  (ir-macro-rules (it)
    ((_ (test . xprs))
     (if (compare? test 'else)
       `(begin ,@xprs)
       `(let ((,it ,test))
          (if ,it
            (begin ,@xprs)
            (error 'acond "no test succeeds")))))
    ((_ (test . xprs) (test1 . xprs1) . clauses)
     `(let ((,it ,test))
        (if ,it
          (begin ,@xprs)
          (acond (,test1 ,@xprs1) ,@clauses))))))

;; a variant of or
(macro-define (my-or . args)
  (with-renamed (%if %my-or)
    (if (null? args)
      #f
      (let ((tmp (car args)))
        `(,%if ,tmp ,tmp (,%my-or ,@(cdr args)))))))

;; anaphoric when, can refer to it
(macro-define (awhen test xpr . xprs)
  (with-injected (it)
    `(let ((,it ,test))
       (if ,it (begin ,xpr ,@xprs)))))

;; anaphoric and, each arg can refer to its precursor via it
(macro-define (aand . args)
  (with-injected (it)
   (let loop ((args args))
     (cond 
       ((null? args) #t)
       ((null? (cdr args)) (car args))
       (else 
         `(let ((,it ,(car args)))
            (if ,it
              ,(loop (cdr args)))))))))


;; anaphoric while, which can reference the result of each ok? with it
(macro-define (awhile ok? xpr . xprs)
  (with-renamed (%let %loop)
   `(,%let ,%loop ((it ,ok?))
      (when it
        ,xpr ,@xprs
        (,%loop ,ok?)))))

;; anaphoric lambda, can refer to itself via self
(macro-define (alambda args xpr . xprs)
  (with-injected (self)
   `(letrec ((,self (lambda ,args ,xpr ,@xprs)))
      ,self)))

(let ((f (lambda (n) (+ n 10))))
	(macro-let (
		((f n) (with-renamed ()  n))
		((g n) (with-renamed (%f) `(,%f ,n)))
		)
		(list (f 1) (g 1)))) ; -> (1 11)

(let ((f (lambda (n) (+ n 10))))
	(macro-letrec (
		((f n) n)
		((g n) `(f ,n)))
		)
		(list (f 1) (g 1)))) ; -> (1 1)

(macro-letrec (
	((aif test then)
	 (with-renamed (%let %if) `(,%let ((it ,test)) (,%if it ,then))))
	)
	(aif (memv 2 '(1 2 3)) it)) ; -> '(2 3)

(macro-let (
	((aif test then) `(let ((it ,test)) (if it ,then)))
	)
	(aif (memv 2 '(1 2 3)) it)) ; -> '(2 3)

(map (alambda (n) (if (zero? n) 1 (* n (self (- n 1)))))
		 '(1 2 3 4 5)) ; -> '(1 2 6 24 120)

(let ((lst '(0 1 2 3))) (aand lst (cdr it) (cdr it))) ; -> '(2 3)

(let ((lst '(0 1 2 3)))
	(acond ((memv 5 lst) it) ((memv 2 lst) it) (else it))) ; -> '(2 3)

(let ((lst '(0 1 2 3))) (aif (memv 2 lst) it #f)) ; -> '(2 3)

(let ((lst '(0 1 2 3))) (awhen (memv 2 lst) (reverse it))) ; -> '(3 2)

(let ((lst '(0 1 2 3)) (acc '()))
	(awhile lst
		(if (null? lst)
			(set! lst #f)
			(begin 
				(set!  acc (cons (car lst) acc))
				(set! lst (cdr lst)))))
	acc) ; -> '(3 2 1 0)

Author

Juergen Lorenz

License

Copyright (c) 2011, 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.

Last update

Sep 07, 2011

Version History

1.0
initial import, merging er-macros and ir-macros