chickadee » chicken » syntax

Module (chicken syntax)

This module has support for syntax- and module handling. This module is used by default, unless a program is compiled with the -explicit-use option.

Macro transformers

Macro transformers are procedures you can use in a define-syntax context to register a procedure that can transform s-expressions into other s-expressions. Only use these when you need to break hygiene in a controlled way; for many use cases syntax-rules is more appropriate, as it offers stronger guarantees of hygiene, is more high-level and is standard R5RS Scheme.

For those situations where you need more control, however, CHICKEN supports two kinds of low-level macros: so-called explicit renaming and implicit renaming macros.

Explicit renaming macros

The low-level macro facility that CHICKEN provides is called "explicit renaming" and allows writing hygienic or non-hygienic macros procedurally. When given the return value of one of the procedures er-macro-transformer or ir-macro-transformer instead of a syntax-rules form, define-syntax evaluates the procedure in a distinct expansion environment (initially having access to the exported identifiers of the scheme module). The procedure takes an expression and two other arguments and returns a transformed expression.

For example, the transformation procedure for a call macro such that (call proc arg ...) expands into (proc arg ...) can be written as

 (er-macro-transformer
   (lambda (exp rename compare)
     (cdr exp)))

Expressions are represented as lists in the traditional manner, except that identifiers are represented as special uninterned symbols.

The second argument to a transformation procedure is a renaming procedure that takes the representation of an identifier as its argument and returns the representation of a fresh identifier that occurs nowhere else in the program. For example, the transformation procedure for a simplified version of the let macro might be written as

 (er-macro-transformer
   (lambda (exp rename compare)
     (let ((vars (map car (cadr exp)))
           (inits (map cadr (cadr exp)))
           (body (cddr exp)))
       `((lambda ,vars ,@body)
         ,@inits))))

This would not be hygienic, however. A hygienic let macro must rename the identifier lambda to protect it from being captured by a local binding. The renaming effectively creates a fresh alias for lambda, one that cannot be captured by any subsequent binding:

 (er-macro-transformer
   (lambda (exp rename compare)
     (let ((vars (map car (cadr exp)))
           (inits (map cadr (cadr exp)))
           (body (cddr exp)))
       `((,(rename 'lambda) ,vars ,@body)
         ,@inits))))

The expression returned by the transformation procedure will be expanded in the syntactic environment obtained from the syntactic environment of the macro application by binding any fresh identifiers generated by the renaming procedure to the denotations of the original identifiers in the syntactic environment in which the macro was defined. This means that a renamed identifier will denote the same thing as the original identifier unless the transformation procedure that renamed the identifier placed an occurrence of it in a binding position.

Identifiers obtained from any two calls to the renaming procedure with the same argument will necessarily be the same, but will denote the same syntactical binding. It is an error if the renaming procedure is called after the transformation procedure has returned.

The third argument to a transformation procedure is a comparison predicate that takes the representations of two identifiers as its arguments and returns true if and only if they denote the same thing in the syntactic environment that will be used to expand the transformed macro application. For example, the transformation procedure for a simplified version of the cond macro can be written as

 (er-macro-transformer
   (lambda (exp rename compare)
     (let ((clauses (cdr exp)))
       (if (null? clauses)
           `(,(rename 'quote) unspecified)
           (let* ((first (car clauses))
                  (rest (cdr clauses))
                  (test (car first)))
             (cond ((and (symbol? test)
                         (compare test (rename 'else)))
                    `(,(rename 'begin) ,@(cdr first)))
                   (else `(,(rename 'if)
                           ,test
                            (,(rename 'begin) ,@(cdr first))
                            (,(rename 'cond) ,@rest)))))))))

In this example the identifier else is renamed before being passed to the comparison predicate, so the comparison will be true if and only if the test expression is an identifier that denotes the same thing in the syntactic environment of the expression being transformed as else denotes in the syntactic environment in which the cond macro was defined. If else were not renamed before being passed to the comparison predicate, then it would match a local variable that happened to be named else, and the macro would not be hygienic. The final recursive call to cond also needs to be renamed because someone might create an alias for this macro and use it in a let where cond is an ordinary variable.

Some macros are non-hygienic by design. For example, the following defines a loop macro that implicitly binds exit to an escape procedure. The binding of exit is intended to capture free references to exit in the body of the loop, so exit is not renamed.

 (define-syntax loop
   (er-macro-transformer
     (lambda (x r c)
       (let ((body (cdr x)))
         `(,(r 'call-with-current-continuation)
           (,(r 'lambda) (exit)
            (,(r 'let) ,(r 'f) () ,@body (,(r 'f)))))))))

Suppose a while macro is implemented using loop, with the intent that exit may be used to escape from the while loop. The while macro cannot be written as

 (define-syntax while
   (syntax-rules ()
     ((while test body ...)
      (loop (if (not test) (exit #f))
            body ...))))

because the reference to exit that is inserted by the while macro is intended to be captured by the binding of exit that will be inserted by the loop macro. In other words, this while macro is not hygienic. Like loop, it must be written using procedurally:

 (define-syntax while
   (er-macro-transformer
     (lambda (x r c)
       (let ((test (cadr x))
             (body (cddr x)))
         `(,(r 'loop)
           (,(r 'if) (,(r 'not) ,test) (exit #f))
           ,@body)))))

Think about it: If we did rename exit, it would refer to an exit procedure existing in the context of the macro's definition. That one actually exists; it is the procedure that exits the Scheme interpreter. Definitely not the one we want :) So now we make it refer to an exit that's locally bound in the environment where the macro is expanded.

Note: this implementation of explicit-renaming macros allows passing arbitrary expressions to the renaming and comparison procedures. When being renamed, a fresh copy of the expression will be produced, with all identifiers renamed appropriately. Comparison also supports arbitrary expressions as arguments.

er-macro-transformer
er-macro-transformer TRANSFORMERprocedure

Returns an explicit-renaming macro transformer procedure created from the procedural macro body TRANSFORMER, which is a procedure of three arguments.

This procedure will be called on expansion with the complete s-expression of the macro invocation, a rename procedure that hygienically renames identifiers and a comparison procedure that compares (possibly renamed) identifiers (see the section "Explicit renaming macros" below for a detailed explanation on non-R5RS macros).

Implementation note: this procedure currently just returns its argument unchanged and is available for writing low-level macros in a more portable fashion, without hard-coding the signature of a transformer procedure.

Implicit renaming macros

Explicit renaming macros generally require the user to perform quite a few renames, because most identifiers that aren't taken from the input expression should generally be inserted hygienically. It would make more sense to give the output expression as-is, and only explicitly convert those identifiers that you want to treat as unhygienic.

This can be done with implicit renaming macros. They just swap the default insertion "mode" from unhygienic to hygienic, so to speak. Here's the cond example from the previous section as an ir-macro:

 (ir-macro-transformer
   (lambda (exp inject compare)
     (let ((clauses (cdr exp)))
       (if (null? clauses)
           `(quote unspecified)
           (let* ((first (car clauses))
                  (rest (cdr clauses))
                  (test (car first)))
             (cond ((and (symbol? test)
                         (compare test 'else))
                    `(begin ,@(cdr first)))
                   (else `(if ,test
                              (begin ,@(cdr first))
                              (cond ,@rest)))))))))

In this example the identifier else does not need to be renamed before being passed to the comparison predicate because it is already implicitly renamed. This comparison will also be true if and only if the test expression is an identifier that denotes the same thing in the syntactic environment of the expression being transformed as else denotes in the syntactic environment in which the cond macro was defined. If else were not renamed before being passed to the comparison predicate, then it would match a local variable that happened to be named else, and the macro would not be hygienic.

As you can see, the code is a lot clearer because it isn't obscured by excessive renaming.

Here's the loop macro so you can see how hygiene can be broken with implicit renaming macros:

 (define-syntax loop
   (ir-macro-transformer
     (lambda (expr inject compare)
       (let ((body (cdr expr)))
         `(call-with-current-continuation
           (lambda (,(inject 'exit))
            (let f () ,@body (f))))))))

The while macro is a little trickier: do we inject the call to exit or not? Just like the explicit renaming macro version did not rename it, we must inject it to allow it to be captured by the loop macro:

 (define-syntax while
   (ir-macro-transformer
     (lambda (expr inject compare)
       (let ((test (cadr expr))
             (body (cddr expr)))
         `(loop
           (if (not ,test) (,(inject 'exit) #f))
           ,@body)))))

Note: Just like explicit renaming macros, this implementation of implicit renaming macros allow passing arbitrary expressions to the injection and comparison procedures. The injection procedure also return fresh copies of its input.

ir-macro-transformer
ir-macro-transformer TRANSFORMERprocedure

This procedure accepts a reverse syntax transformer, also known as an implicit renaming macro transformer. This is a transformer which works almost like er-macro-transformer, except the rename and compare procedures it receives work a little differently.

The rename procedure is now called inject and instead of renaming the identifier to be resolved in the macro's definition environment, it will explicitly inject the identifier to be resolved in the expansion environment. Any non-injected identifiers in the output expression produced by the transformer will be implicitly renamed to refer to the macro's environment instead. All identifiers in the input expression are of course implicitly injected just like with explicit renaming macros. See the section above for a more complete explanation.

To compare an input identifier you can generally compare to the bare symbol and only free identifiers will match. In practice, this means that when you would call e.g. (compare (cadr expression) (rename 'x)) in an ER macro, you simply call (compare (cadr expression) 'x) in the IR macro. Likewise, an unhygienic ER macro's comparison (compare sym 'abc) should be written as (compare sym (inject 'abc)) in an IR macro.

Expanding macros

expand

expand Xprocedure

If X is a macro-form, expand the macro (and repeat expansion until expression is a non-macro form). Returns the resulting expression.

Macro helper procedures

begin-for-syntax

(begin-for-syntax EXP ...)syntax

Equivalent to (begin EXP ...), but performs the evaluation of the expression during macro-expansion time, using the macro environment rather than the interaction environment.

You can use this to define your own helper procedures that you can call from a syntax transformer.

define-for-syntax

(define-for-syntax (NAME VAR ...) EXP1 ...)syntax
(define-for-syntax (NAME VAR1 ... VARn . VARn+1) EXP1 ...)syntax
(define-for-syntax NAME [VALUE])syntax

Defines the toplevel variable NAME at macro-expansion time. This can be helpful when you want to define support procedures for use in macro-transformers, for example.

Essentially, this is a shorthand for (begin-for-syntax (define ...)).

Note that define-for-syntax definitions within a module are implicitly added to that module's import library. Refer to the documentation on import libraries for more information.

syntax

syntax EXPRESSIONprocedure

This will quote the EXPRESSION for use in a syntax expansion. Any syntactic information will be stripped from the EXPRESSION.

strip-syntax

strip-syntax EXPRESSIONprocedure

Strips all syntactical information from EXPRESSION, returning a new expression where symbols have all context-information removed.

You should use this procedure whenever you want to manually construct new identifiers, which an unhygienic macro can insert. In some cases it does not appear to be necessary to strip context information when you use the macro, but you still should do it. Sometimes identifiers will not have been renamed (most often at toplevel), but there may be other contexts in which identifiers will have been renamed.

read-with-source-info

read-with-source-info #!optional portprocedure

Exactly like {read} from the {scheme} module, except it registers the expression it read into the line number database, so that if {(read-with-source-info)} returns {OBJ}, {(get-line-number OBJ)} will return the line number in {port}.

The port argument may be omitted, in which case it defaults to the value returned by current-input-port. It is an error to read from a closed port.

get-line-number

get-line-number EXPRprocedure

If EXPR is a pair with the car being a symbol, and line-number information is available for this expression, then this procedure returns the associated source file and line number as a string. If line-number information is not available, then #f is returned.

syntax-error

(syntax-error [LOCATION] MESSAGE ARGUMENT ...)procedure

Signals an exception of the kind (exn syntax). Otherwise identical to error.

Compiler macros

define-compiler-syntax

(define-compiler-syntax NAME)syntax
(define-compiler-syntax NAME TRANSFORMER)syntax

Defines what is usually called a compiler macro in Lisp: NAME should be the name of a globally or locally bound procedure. Any direct call to this procedure will be transformed before compilation, which allows arbitrary rewritings of function calls.

TRANSFORMER can be a syntax-rules expression or a transformer procedure (as returned by er-macro-transformer or ir-macro-transformer). Returning the original form in an explicit/implicit-renaming macro or simply "falling trough" all patterns in a syntax-rules form will keep the original expression and compile it normally.

In the interpreter this form does nothing and returns an unspecified value.

Compiler-syntax is always local to the current compilation unit and can not be exported. Compiler-syntax defined inside a module is not visible outside of that module.

define-compiler-syntax should only be used at top-level. Local compiler-syntax can be defined with let-compiler-syntax.

(define-compiler-syntax +
  (syntax-rules ()
    ((_) 1)
    ((_ x 0) x) ) )

If no transformer is given, then (define-compiler-syntax NAME) removes any compiler-syntax definitions for NAME.

let-compiler-syntax

(let-compiler-syntax ((NAME [TRANSFORMER]) ...) BODY ...)syntax

Allows definition local compiler macros, which are only applicable inside BODY .... By not providing a TRANSFORMER expression, compiler-syntax for specific identifiers can be temporarily disabled.


Previous: Module (chicken string)

Next: Module (chicken tcp)

Contents »