Next: , Previous: , Up: stdlib syntax-case   [Index]


5.12.7 Syntax–object and datum conversions

Procedure: syntax->datum syntax-object

Strip all syntactic information from a syntax object and returns the corresponding Scheme datum.

Identifiers stripped in this manner are converted to their symbolic names, which can then be compared with eq?. Thus, a predicate symbolic-identifier=? might be defined as follows.

(define (symbolic-identifier=? x y)
  (eq? (syntax->datum x)
       (syntax->datum y)))
Procedure: datum->syntax template-id datum

template-id must be a template identifier and datum should be a datum value.

The datum->syntax procedure returns a syntax-object representation of datum that contains the same contextual information as template-id, with the effect that the syntax object behaves as if it were introduced into the code when template-id was introduced.

The datum->syntax procedure allows a transformer to “bend” lexical scoping rules by creating implicit identifiers that behave as if they were present in the input form, thus permitting the definition of macros that introduce visible bindings for, or references to, identifiers that do not appear explicitly in the input form.

In the following example, green and red have the role of template-id in the alpha and beta macro uses at the end of the (test) library:

(library (lib)
  (export beta red)
  (import (rnrs))

  (define red  #t)
  (define data 'in-lib)

  (define-syntax beta
    (lambda (stx)
      (syntax-case stx ()
        ((_ ?context)
         #`(begin #,(datum->syntax #'?context 'data)))))))

(library (test)
  (export)
  (import (rnrs) (lib))

  (define green #t)
  (define data  'in-test)

  (define-syntax alpha
    (lambda (stx)
      (syntax-case stx ()
        ((_ ?context)
         #`(begin #,(datum->syntax #'?context 'data))))))

  (display (alpha green))       -| in-test
  (display (alpha red))         -| in-test
  (display (beta  green))       -| in-test
  (display (beta  red)))        -| in-test

in both the macro uses: the identifier syntax objects representing ‘green’ and ‘red’ are created in the (test) library, so both the macros reach for data in the context of the (test) library.

The following example demonstrates why, when macro uses are nested, we cannot rely on the macro keyword syntax object to carry the context of the “outer” macro use:

(library (sublib)
  (export beta)
  (import (rnrs))
  (define data 33)
  (define-syntax beta
    (lambda (stx)
      (syntax-case stx ()
        ((?context)
         #`(begin #,(datum->syntax #'?context 'data)))))))

(library (lib)
  (export alpha)
  (import (rnrs) (sublib))
  (define data 22)
  (define-syntax alpha
    (lambda (stx)
      (syntax-case stx ()
        ((_)
         #'(beta))))))

(library (test)
  (export)
  (import (rnrs) (lib))
  (define data 11)
  (display (alpha))) -| 22

in the body of the beta macro: ?context is bound to a syntax object carrying reference to the lexical context of the body of alpha; from the body of beta it is impossible to reach the lexical context of the (test) library, where alpha is used.

The following example defines a loop expression that uses this controlled form of identifier capture to bind the variable ‘break’ to an escape procedure within the loop body. (The derived with-syntax form is like let but binds pattern variables.)

(import (rnrs))

(define-syntax loop
  (lambda (x)
    (syntax-case x ()
      ((k e ...)
       (with-syntax ((break (datum->syntax #'k 'break)))
         #'(call-with-current-continuation
             (lambda (break)
               (let f () e ... (f)))))))))

(let ((n 3) (ls '()))
  (loop
    (when (= n 0)
      (break ls))
    (set! ls (cons 'a ls))
    (set! n (- n 1))))
⇒ (a a a)

Were loop to be defined as:

(define-syntax loop
  (lambda (x)
    (syntax-case x ()
      ((_ e ...)
       #'(call-with-current-continuation
           (lambda (break)
             (let f () e ... (f))))))))

the variable ‘break’ would not be visible in ‘e ...’.

The datum argument datum may also represent an arbitrary Scheme form, as demonstrated by the following definition of include.

(define-syntax include
  (lambda (x)

    (define (read-file fn k)
      (let ((p (open-file-input-port fn)))
        (let loop ((x (get-datum p)))
          (if (eof-object? x)
              (begin
                (close-port p)
                '())
            (cons (datum->syntax k x)
                  (loop (get-datum p)))))))

    (syntax-case x ()
      ((k filename)
       (let ((fn (syntax->datum #'filename)))
         (with-syntax (((exp ...) (read-file fn #'k)))
           #'(begin exp ...)))))))

(include "filename") expands into a begin expression containing the forms found in the file named by ‘"filename"’. For example, if the file flib.ss contains:

(define f (lambda (x) (g (* x x))))

and the file glib.ss contains:

(define g (lambda (x) (+ x x)))

the expression:

(let ()
  (include "flib.ss")
  (include "glib.ss")
  (f 5))

evaluates to 50.

The definition of include uses datum->syntax to convert the objects read from the file into syntax objects in the proper lexical context, so that identifier references and definitions within those expressions are scoped where the include form appears.


Next: , Previous: , Up: stdlib syntax-case   [Index]