Description

A syntax-rules macro-expander

Author

Al Petrofsky, ported to CHICKEN by felix winkelmann

Version

Usage

(require-extension alexpander)

Download

alexpander.egg

Documentation

This extension provides a syntax-rules macro system for compiled or interpreted code. It is less featureful than the syntax-case egg and only supports pure R5RS Scheme (plus a few extensions) but may be useful if you need a leightweight syntax-rules macro expander at run-time.

To use it at runtime only, evaluate (require 'alexpander). To use it in the interpreter or for compiled code, add (require-extension alexpander) or add the -R alexpander option.

Implementations of the special forms declare and require-extension are available, otherwise this expander only knows about R5RS syntax.

EXTENSIONS:

The expander supports all the features of the r5rs macro system,
plus several extensions in the way syntaxes can be specified and
used, which are best summarized in BNF:

Modified r5rs productions:
  <expression> ---> <variable> | <literal> | <procedure call>
                  | <lambda expression> | <conditional> | <assignment>
                  | <derived expression> | <macro use> | <macro block>
                  | <keyword>
  <syntax definition> ---> (define-syntax <keyword> <syntax or expression>)
                         | (begin <syntax definition>*)
                         | <macro use>
  <syntax spec> --> (<keyword> <syntax or expression>)
  <syntax or expression> --> <syntax> | <expression>
  <macro use> ---> (<syntax> <datum>*)
  <definition> ---> (define <variable> <expression>)
                  | (define (<variable> <def formals>) <body>)
                  | (define <expression>)
                  | (begin <definition>*)
                  | <macro use>
                  | <syntax definition>
  <command or definition> ---> <command> | <definition>
                             | (begin <command or definition>*)
                             | <top-level macro block>
                             | <macro use>

New productions:
  <syntax> --> <transformer spec>
             | <keyword>
             | <macro use>
             | <syntax macro block>
  <syntax macro block> --> (<syntax-only block stuff> <syntax>)
  <top-level macro block>
      --> (<syntax-only block stuff> <command or definition>)
  <syntax-only block stuff>
     ---> <let-or-letrec-syntax> (<syntax spec>*) <syntax definition>*
  <let-or-letrec-syntax> ---> let-syntax | letrec-syntax

These extensions all have the obvious meaning.

Okay, I'll elaborate on that a little bit.  Consider the intializer
position of a syntax definition and the head position of a
list-format expression:

  (define-syntax <keyword> <xxx>)

  (<yyy> <foo>*)

In r5rs, <xxx> must be a transformer.  <Yyy> may be an expression,
in which case the enclosing expression is taken to be a procedure
call and the <foo>s are the expressions for the operands, or <yyy>
may be a keyword bound to a syntax (a builtin or transformer), in
which case the <foo>s are processed according to that syntax.

The core generalization in our system is that both <xxx> and <yyy>
may be any type of expression or syntax.  The four forms of syntax
allowed are: a transformer (as allowed in the <xxx> position in
r5rs), a keyword (as allowed in the <yyy> position in r5rs), a
macro use that expands into a syntax, and a macro block (let-syntax
or letrec-syntax) whose body is a syntax.

Some examples:

 ;; a macro with a local macro
 (let-syntax ((foo (let-syntax ((bar (syntax-rules () ((bar x) (- x)))))
                     (syntax-rules () ((foo) (bar 2))))))
   (foo))
 => -2

 ;; an anonymous let transformer, used directly in a macro call.
 ((syntax-rules ()
    ((_ ((var init) ...) . body)
     ((lambda (var ...) . body) init ...)))
  ((x 1) (y 2))
  (+ x y))
 => 3

 ;; a keyword used to initialize a keyword
 (let-syntax ((q quote)) (q x)) => x

 ;; Binding a keyword to an expression (which could also be thought
 ;; of as creating a macro that is called without arguments).
 (let ((n 0))
   (let-syntax ((x (set! n (+ n 1))))
     (begin x x x n)))
 => 3

 (let-syntax ((x append)) ((x x))) => ()


Top-level macro blocks.

At top level, if a macro block (a let-syntax or letrec-syntax form)
has only one body element, that element need not be an expression
(as would be required in r5rs).  Instead, it may be anything
allowed at top level: an expression, a definition, a begin sequence
of top-level forms, or another macro block containing a top-level
form.

(let-syntax ((- quote))
  (define x (- 1)))
(list x (- 1)) => (1 -1)

Note that, unlike the similar extension in Chez scheme 6.0, this is
still r5rs-compatible, because we only treat definitions within the
last body element as top-level definitions (and r5rs does not allow
internal definitions within a body's last element, even if it is a
begin form):

(begin
  (define x 1)
  (let-syntax ()
    (define x 2)
    'blah)
  x)
=> 1, in r5rs and alexpander, but 2 in Chez scheme

(begin
  (define x 1)
  (let-syntax ()
    (begin (define x 2)
           'blah))
  x)
=> 2, in alexpander and in Chez scheme, but an error in r5rs.


Expressions among internal definitions.

A definition of the form (define <expression>) causes the
expression to be evaluated at the conclusion of any enclosing set
of internal definitons.  That is, at top level, (define
<expression>) is equivalent to just plain <expression>.  As for
internal definitions, the following are equivalent:

(let ()
  (define v1 <init1>)
  (define <expr1>)
  (define <expr2>)
  (define v2 <init2>)
  (define <expr3>)
  (begin
    <expr4>
    <expr5>))

(let ()
  (define v1 <init1>)
  (define v2 <init2>)
  (begin
    <expr1>
    <expr2>
    <expr3>
    <expr4>
    <expr5>))

(Yes, it would probably be better to have a separate builtin for
this rather than to overload define.)

This feature makes it possible to implement a define-values that
works properly both at top-level and among internal definitions:

(define define-values-temp #f)

(define-syntax define-values
  (syntax-rules ()
    ((define-values (var ...) init)
     (begin
       (define define-values-temp (call-with-values (lambda () init) list))
       (define var #f) ...
       (define
         (set!-values (var ...) (apply values define-values-temp)))))))

(Set!-values is implementable using just r5rs features and is left
as an exercise.)

When used among internal definitions, the definition of
define-values-temp in define-values's output creates a local
binding, and thus the top-level binding of define-values-temp is
irrelevant.  When used at top-level, the definition of
define-values-temp in the output does not create a binding, it
mutates the top-level binding of define-values-temp.  Thus, all
top-level uses of define-values share a single temp variable.  For
internal-definition-level uses of define-values, a single shared
temp would not be sufficient, but things work out okay because
hygienic renaming causes each such use to create a distinct temp
variable.

The version below works the same way, but hides from the top-level
environment the temp that is shared by top-level uses of
define-values.  For a bit of tutorial and rationale about this
technique, see usenet article
<8765tos2y9.fsf@radish.petrofsky.org>:

(define-syntax define-values
  (let-syntax ((temp (syntax-rules ())))
    (syntax-rules ()
      ((define-values (var ...) init)
       (begin
         (define temp (call-with-values (lambda () init) list))
         (define var #f) ...
         (define (set!-values (var ...) (apply values temp))))))))


Internal syntax definitions.

Internal syntax definitions are supported wherever they would make
sense (see the BNF) and have the letrec-syntax semantics you would
expect.  It is legal for the initializer of an internal variable
definition to use one of the internal syntax definitions in the
same body:

(let ()
  (define x (y))
  (define-syntax y (syntax-rules () ((y) 1)))
  x)
=> 1

It's also legal for internal syntax definitions to be mutually
recursive transformers, but it is an error for the expansion of a
syntax definition's initializer to require the result of another
initializer:

(let ()
  (define-syntax m1 (syntax-rules () ((m1) #f) ((m1 . args) (m2 . args))))
  (define-syntax m2 (syntax-rules () ((m2 arg . args) (m1 . args))))
  (m1 foo bar baz))
=> #f

(let ()
  (define-syntax simple-transformer
    (syntax-rules ()
      ((simple-transformer pattern template)
       (syntax-rules () (pattern template)))))
  (define-syntax m (simple-transformer (m x) (- x)))
  (m 1))
=> error ("Premature use of keyword bound by an internal define-syntax")

(let ()
  (define-syntax simple-transformer
    (syntax-rules ()
      ((simple-transformer pattern template)
       (syntax-rules () (pattern template)))))
  (let ()
    (define-syntax m (simple-transformer (m x) (- x)))
    (m 1)))
=> -1


Syntax-rules ellipsis

Per draft SRFI-46, syntax-rules transformers can specify the
identifier to be used as the ellipsis (such a specification is
treated as a hygienic binding), and a list pattern may contain
subpatterns after an ellipsis as well as before it:

  <transformer spec> ---> (syntax-rules (<identifier>*) <syntax rule>*)
             | (syntax-rules <ellipsis> (<identifier>*) <syntax rule>*)
  
  <syntax rule> ---> (<pattern> <template>)
  
  <pattern> ---> <pattern identifier>
               | (<pattern>*)
               | (<pattern>+ . <pattern>)
               | (<pattern>* <pattern> <ellipsis> <pattern>*)
               | #(<pattern>*)
               | #(<pattern>* <pattern> <ellipsis> <pattern>*)
               | <pattern datum>
  
  <pattern identifier> ---> <identifier>
  
  <ellipsis> ---> <identifier>


Improved nested unquote-splicing.  

Quasiquote is extended to make commas and comma-ats distributive
over a nested comma-at, as in Common Lisp's backquote.  See my
2004-09-03 usenet article <87pt53f9f2.fsf@radish.petrofsky.org>,
Bawden's 1999 quasiquotation paper, and Appendix C of Steele's
"Common Lisp the Language 2nd edition".

  <splicing unquotation 1> ---> ,@<qq template 0>
                              | (unquote-splicing <qq template 0>)

  <splicing unquotation D> ---> ,@<qq template D-1>
                              | ,<splicing unquotaion D-1>
                              | ,@<splicing unquotaion D-1>
                              | (unquote-splicing <qq template D-1>)
                              | (unquote <splicing unquotaion D-1>)
                              | (unquote-splicing <splicing unquotaion D-1>)

When a comma at-sign and the expression that follows it are being
replaced by the elements of the list that resulted from the
expression's evaluation, any sequence of commas and comma at-signs
that immediately preceeded the comma at-sign is also removed and is
added to the front of each of the replacements.

 (let ((x '(a b c))) ``(,,x ,@,x ,,@x ,@,@x))
 => `(,(a b c) ,@(a b c) ,a ,b ,c ,@a ,@b ,@c)

 ``(,,@'() ,@,@(list))
 => `()

 `````(a ,(b c ,@,,@,@(list 'a 'b 'c)))
 => ````(a ,(b c ,@,,@a ,@,,@b ,@,,@c))
 
(let ((vars '(x y)))
  (eval `(let ((x '(1 2)) (y '(3 4)))
           `(foo ,@,@vars))
        (null-environment 5)))
=> (foo 1 2 3 4)


Non-standard syntax

The following non-standard macros are provided:

receive
time
assert
ensure
case-lambda
and-let*
when, unless
define-values , set!-values
let-values, let*-values
switch
optional, let-optionals, let-optionals*
define-inline, define-constant
nth-value
define-record-printer
handle-exceptions, condition-case
require-extension, use
cut, cute
rec
define-record-type
cond-expand
fluid-let

`include' is not supported. `cond-expand' recognizes the following
features:

chicken
srfi-0
alexpander
highlevel-macros
syntax-rules

License

Copyright 2002-2004,2006,2007 Al Petrofsky

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.