You are looking at historical revision 30155 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-macros.

Recall that low-level 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 third parameter allows to handle additional keywords.

Each of these transformer arguments does a special job, each of which is tedious and error-prone. In this module, we'll automate each of these jobs.

Let's start with destructuring the macro-code. It can be done by a tool, bind from the bindings module, for example. Renaming or injecting can be done by supplying a special prefix, % in most cases. Symbols with this prefix can be extracted from the macro-body and transformed into a let which defines the necessary symbols. Last but not least, additional keywords can be extracted from the macro body as well and transformed into a where clause of the bind macro. This way, the transformer disappears completely on the surface, but ,of course, happens to do the job behind the scene.

There is a helper module, symbols, which provides some routines which are used in low-level-macros, but might be of separate interest.

Programming interface

The symbols module

symbols

[procedure] (symbols)

shows which symbols are exported.

prefixed-with?

[procedure] (prefixed-with? pre)

returns a predicate, which checks, if pre is a prefix of its argument.

strip-prefix

[procedure] (strip-prefix pre id)

strips the prefix pre from the identifier id.

strip-suffix

[procedure] (strip-suffix suf id)

strips the suffix suf from the identifier id.

extract

[procedure] (extract ok? tree)

returns a flat list of all the symbols in a tree which pass the ok? test.

remove-duplicates

[procedure] (remove-duplicates lst)

returns a sublist of lst with dups removed.

adjoin

[procedure] (adjoin obj lst)

conses obj to lst provided it isn't already there.

memp

[procedure] (memp ok? lst)

returns the tail of lst, whose car passes ok?, or #f otherwise.

filter

[procedure] (filter ok? lst)

returns a sublist of lst where each item passes ok?

flatten

[procedure] (flatten tree)

returns a flat list with all the items of tree.

The module low-level-macros

low-level-macros

[procedure] (low-level-macros)

returns a list of all the exported symbols of the module.

bind

[syntax] (bind pat seq xpr . xprs)
[syntax] (bind pat seq (where . fenders) xpr . xprs)

binds pattern variables of pat to subexpressions of seq and executes xpr . xprs in this context, provided all fenders return #t, if supplied.

bind-case

[syntax] (bind-case seq clause ....)

where seq is a sequence expression and each clause is of one of two forms

(pat (where . fenders) xpr . xprs)
(pat xpr . xprs)

Matches seq against a series of patterns and executes the body of the first matching pattern satisfying fenders (if given).

define-macro

[syntax] (define-macro (name . args) xpr . xprs))
[syntax] (define-macro (name . args) (with-rename-prefix % xpr . xprs))
[syntax] (define-macro (name . args) (with-inject-prefix % xpr . xprs))
[syntax] (define-macro (name . args) (with-keywords (x . xs) xpr . xprs))
[syntax] (define-macro (name . args) (with-rename-prefix % (with-keywords (x . xs) xpr . xprs)))
[syntax] (define-macro (name . args) (with-inject-prefix % (with-keywords (x . xs) xpr . xprs)))

generates a macro name, which is of type explicit-renaming if with-rename-prefix is supplied or implicit-renaming otherwise. Keywords and prefixed symbols are extracted from the macro body transformed into appropriate subexpressions of the macro-transformer.

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

let-macro

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

where (code0 clause0), (code1 clause0), ... are as in define-macro.

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

letrec-macro

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

where (code0 clause0), (code1 clause0), ... are as in define-macro.

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

macro-rules

[syntax] (macro-rules sym ... (keyword ...) (pat0 tpl0) (pat1 tpl1) ...)

like syntax-rules, but the templates are usually quasiquote-expressions. Moreover, the symbols sym ... are injected, if there are any.

once-only

[syntax] (once-only (x ...) . body)

to be used in a macro-body to avoid side-effects. The arguments x ... are only evaluated once.

with-gensyms

[syntax] (with-gensyms (x ...) . body)

to be used in a macro body. Generates a list of gensyms x ...

define-syntax-rule

[syntax] (define-syntax-rule (name . args) tpl)

the only high-level macro. To be used instead of syntax-rules in case there is only one rule and no additional keywords

Requires

nothing

Usage

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

Examples

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

;; destructuring
(bind a 1 a) ; -> 1
(bind (x y z w) '(1 2 3 4) (list x y z w) ; -> '(1 2 3 4)
(bind (x (y (z))) '(1 (2 (3))) (where (odd? y)) (list x y z))
  ; -> error
(bind (x (y (z))) '(1 (2 (3))) (list x y z))
  ; -> '(1 2 3)

(letrec (
  (my-map
    (lambda (fn lst)
      (bind-case lst
        (() '())
        ((x . xs) (cons (fn x) (my-map fn xs))))))
  )
  (my-map add1 '(1 2 3)))
; -> '(2 3 4)
(bind-case '(1 2 3 4 5)
	 ((a (b . C) . d) (list a b C d))
	 ((e . f) (where (zero? e)) e)
	 ((e . f) (list e f)))
; -> '(1 (2 3 4 5)))

;; 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)
	(with-inject-prefix %
		`(letrec ((,%self (lambda ,args ,xpr ,@xprs)))
			 ,%self)))

;; two versions of a verbose if
(define-macro (verbose-if test (then . xprs) (else . yprs))
	(with-rename-prefix %
		(with-keywords (then else)
			`(,%if ,test
				 (,%begin ,@xprs)
				 (,%begin ,@yprs)))))

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

;; low-level 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)) test)
		((_ (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)))))

Author

Juergen Lorenz

License

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

Nov 27, 2013

Version History

2.1
dependency on bindings removed, simplified versions of bind and bind-case added
2.0
complete rewrite
1.2
renamed macro-define to define-macro
1.1
fixed low-level-macros.meta and low-level-macros.setup
1.0
initial import, merging er-macros and ir-macros