Previous: , Up: The Language   [Contents][Index]

4.9 Syntax

SCM provides a native implementation of defmacro. See Defmacro in SLIB.

When built with ‘-F macro’ build option (see Build Options) and ‘*syntax-rules*’ is non-false, SCM also supports [R5RS] syntax-rules macros. See Macros in Revised(5) Scheme.

Other Scheme Syntax Extension Packages from SLIB can be employed through the use of ‘macro:eval’ and ‘macro:load’; Or by using the SLIB read-eval-print-loop:

(require 'repl)
(repl:top-level macro:eval)

With the appropriate catalog entries (see Library Catalogs in SLIB), files using macro packages will automatically use the correct macro loader when ‘require’d.


Next: , Previous: , Up: Syntax   [Contents][Index]

4.9.1 Define and Set

Special Form: defined? symbol

Equivalent to #t if symbol is a syntactic keyword (such as if) or a symbol with a value in the top level environment (see Variables and regions in Revised(5) Scheme). Otherwise equivalent to #f.

Special Form: defvar identifier initial-value

If identifier is unbound in the top level environment, then identifier is defined to the result of evaluating the form initial-value as if the defvar form were instead the form (define identifier initial-value) . If identifier already has a value, then initial-value is not evaluated and identifier’s value is not changed. defvar is valid only when used at top-level.

Special Form: defconst identifier value

If identifier is unbound in the top level environment, then identifier is defined to the result of evaluating the form value as if the defconst form were instead the form (define identifier value) . If identifier already has a value, then value is not evaluated, identifier’s value is not changed, and an error is signaled. defconst is valid only when used at top-level.

Special Form: set! (variable1 variable2 …) <expression>

The identifiers variable1, variable2, … must be bound either in some region enclosing the ‘set!’ expression or at top level.

<Expression> is evaluated, and the elements of the resulting list are stored in the locations to which each corresponding variable is bound. The result of the ‘set!’ expression is unspecified.

(define x 2)
(define y 3)
(+ x y)                              ⇒ 5
(set! (x y) (list 4 5))              ⇒ unspecified
(+ x y)                              ⇒ 9
Special Form: qase key clause1 clause2 …

qase is an extension of standard Scheme case: Each clause of a qase statement must have as first element a list containing elements which are:

  • literal datums, or
  • a comma followed by the name of a symbolic constant, or
  • a comma followed by an at-sign (@) followed by the name of a symbolic constant whose value is a list.

A qase statement is equivalent to a case statement in which these symbolic constants preceded by commas have been replaced by the values of the constants, and all symbolic constants preceded by comma-at-signs have been replaced by the elements of the list values of the constants. This use of comma, (or, equivalently, unquote) is similar to that of quasiquote except that the unquoted expressions must be symbolic constants.

Symbolic constants are defined using defconst, their values are substituted in the head of each qase clause during macro expansion. defconst constants should be defined before use. qase can be substituted for any correct use of case.

(defconst unit '1)
(defconst semivowels '(w y))
(qase (* 2 3)
  ((2 3 5 7) 'prime)
  ((,unit 4 6 8 9) 'composite))        ==>  composite
(qase (car '(c d))
  ((a) 'a)
  ((b) 'b))                            ==>  unspecified
(qase (car '(c d))
  ((a e i o u) 'vowel)
  ((,@semivowels) 'semivowel)
  (else 'consonant))                   ==>  consonant


Next: , Previous: , Up: Syntax   [Contents][Index]

4.9.2 Defmacro

SCM supports the following constructs from Common Lisp: defmacro, macroexpand, macroexpand-1, and gentemp. See Defmacro in SLIB.

SCM defmacro is extended over that described for SLIB:

(defmacro (macro-name . arguments) body)

is equivalent to

(defmacro macro-name arguments body)

As in Common Lisp, an element of the formal argument list for defmacro may be a possibly nested list, in which case the corresponding actual argument must be a list with as many members as the formal argument. Rest arguments are indicated by improper lists, as in Scheme. It is an error if the actual argument list does not have the tree structure required by the formal argument list.

For example:

(defmacro (let1 ((name value)) . body)
    `((lambda (,name) ,@body) ,value))

(let1 ((x (foo))) (print x) x) ≡ ((lambda (x) (print x) x) (foo))

(let1 not legal syntax) error→ not "does not match" ((name value))

Next: , Previous: , Up: Syntax   [Contents][Index]

4.9.3 Syntax-Rules

SCM supports [R5RS] syntax-rules macros See Macros in Revised(5) Scheme.

The pattern language is extended by the syntax (... <obj>), which is identical to <obj> except that ellipses in <obj> are treated as ordinary identifiers in a template, or as literals in a pattern. In particular, (... ...) quotes the ellipsis token ... in a pattern or template.

For example:

(define-syntax check-tree
  (syntax-rules ()
    ((_ (?pattern (... ...)) ?obj)
     (let loop ((obj ?obj))
       (or (null? obj)
           (and (pair? obj)
                (check-tree ?pattern (car obj))
                (loop (cdr obj))))))
    ((_ (?first . ?rest) ?obj)
     (let ((obj ?obj))
       (and (pair? obj)
            (check-tree ?first (car obj))
            (check-tree ?rest (cdr obj)))))
    ((_ ?atom ?obj) #t)))

(check-tree ((a b) ...) '((1 2) (3 4) (5 6))) ⇒ #t

(check-tree ((a b) ...) '((1 2) (3 4) not-a-2list) ⇒ #f

Note that although the ellipsis is matched as a literal token in the defined macro it is not included in the literals list for syntax-rules.

The pattern language is also extended to support identifier macros. A reference to an identifier macro keyword that is not the first identifier in a form may expand into Scheme code, rather than raising a “keyword as variable” error. The pattern for expansion of such a bare macro keyword is a single identifier, as in other syntax rules the identifier is ignored.

For example:

(define-syntax eight
    (syntax-rules ()
      (_ 8)))

(+ 3 eight) ⇒ 11
(eight) ⇒ ERROR
(set! eight 9) ⇒ ERROR

Next: , Previous: , Up: Syntax   [Contents][Index]

4.9.4 Macro Primitives

Function: procedure->syntax proc

Returns a macro which, when a symbol defined to this value appears as the first symbol in an expression, returns the result of applying proc to the expression and the environment.

Function: procedure->macro proc
Function: procedure->memoizing-macro proc
Function: procedure->identifier-macro

Returns a macro which, when a symbol defined to this value appears as the first symbol in an expression, evaluates the result of applying proc to the expression and the environment. The value returned from proc which has been passed to PROCEDURE->MEMOIZING-MACRO replaces the form passed to proc. For example:

(defsyntax trace
  (procedure->macro
   (lambda (x env) `(set! ,(cadr x) (tracef ,(cadr x) ',(cadr x))))))

(trace foo) ≡ (set! foo (tracef foo 'foo)).

PROCEDURE->IDENTIFIER-MACRO is similar to PROCEDURE->MEMOIZING-MACRO except that proc is also called in case the symbol bound to the macro appears in an expression but not as the first symbol, that is, when it looks like a variable reference. In that case, the form passed to proc is a single identifier.

Special Form: defsyntax name expr

Defines name as a macro keyword bound to the result of evaluating expr, which should be a macro. Using define for this purpose may not result in name being interpreted as a macro keyword.


4.9.5 Environment Frames

An environment is a list of frames representing lexical bindings. Only the names and scope of the bindings are included in environments passed to macro expanders – run-time values are not included.

There are several types of environment frames:

((lambda (variable1 …) …) value1 …)
(let ((variable1 value1) (variable2 value2) …) …)
(letrec ((variable1 value1) …) …)

result in a single enviroment frame:


(variable1 variable2 …)

(let ((variable1 value1)) …)
(let* ((variable1 value1) …) …)

result in an environment frame for each variable:


variable1 variable2 …

(let-syntax ((key1 macro1) (key2 macro2)) …)
(letrec-syntax ((key1 value1) (key2 value2)) …)

Lexically bound macros result in environment frames consisting of a marker and an alist of keywords and macro objects:


(<env-syntax-marker> (key1 . value1) (key2 . value2))

Currently <env-syntax-marker> is the integer 6.

line numbers

Line numbers (see Line Numbers) may be included in the environment as frame entries to indicate the line number on which a function is defined. They are ignored for variable lookup.


#<line 8> 

miscellaneous

Debugging information is stored in environments in a plist format: Any exact integer stored as an environment frame may be followed by any value. The two frame entries are ignored when doing variable lookup. Load file names, procedure names, and closure documentation strings are stored in this format.


<env-filename-marker> "foo.scm" <env-procedure-name-marker> foo …

Currently <env-filename-marker> is the integer 1 and <env-procedure-name-marker> the integer 2.

Special Form: @apply procedure argument-list

Returns the result of applying procedure to argument-list. @apply differs from apply when the identifiers bound by the closure being applied are set!; setting affects argument-list.

(define lst (list 'a 'b 'c))
(@apply (lambda (v1 v2 v3) (set! v1 (cons v2 v3))) lst)
lst           ⇒ ((b . c) b c)

Thus a mutable environment can be treated as both a list and local bindings.


Previous: , Up: Syntax   [Contents][Index]

4.9.6 Syntactic Hooks for Hygienic Macros

SCM provides a synthetic identifier type for efficient implementation of hygienic macros (for example, syntax-rules see Macros in Revised(5) Scheme) A synthetic identifier may be inserted in Scheme code by a macro expander in any context where a symbol would normally be used. Collectively, symbols and synthetic identifiers are identifiers.

Function: identifier? obj

Returns #t if obj is a symbol or a synthetic identifier, and #f otherwise.

If it is necessary to distinguish between symbols and synthetic identifiers, use the predicate symbol?.

A synthetic identifier includes two data: a parent, which is an identifier, and an environment, which is either #f or a lexical environment which has been passed to a macro expander (a procedure passed as an argument to procedure->macro, procedure->memoizing-macro, or procedure->syntax).

Function: renamed-identifier parent env

Returns a synthetic identifier. parent must be an identifier, and env must either be #f or a lexical environment passed to a macro expander. renamed-identifier returns a distinct object for each call, even if passed identical arguments.

There is no direct way to access all of the data internal to a synthetic identifier, those data are used during variable lookup. If a synthetic identifier is inserted as quoted data then during macro expansion it will be repeatedly replaced by its parent, until a symbol is obtained.

Function: identifier->symbol id

Returns the symbol obtained by recursively extracting the parent of id, which must be an identifier.

4.9.7 Use of Synthetic Identifiers

renamed-identifier may be used as a replacement for gentemp:

(define gentemp
  (let ((name (string->symbol "An unlikely variable")))
    (lambda ()
      (renamed-identifier name #f))))

If an identifier returned by this version of gentemp is inserted in a binding position as the name of a variable then it is guaranteed that no other identifier (except one produced by passing the first to renamed-identifier) may denote that variable. If an identifier returned by gentemp is inserted free, then it will denote the top-level value bound to its parent, the symbol named “An unlikely variable”. This behavior, of course, is meant to be put to good use:

(define top-level-foo
  (procedure->memoizing-macro
   (lambda (exp env)
     (renamed-identifier 'foo #f))))

Defines a macro which may always be used to refer to the top-level binding of foo.

(define foo 'top-level)
(let ((foo 'local))
  (top-level-foo))  ⇒ top-level

In other words, we can avoid capturing foo.

If a lexical environment is passed as the second argument to renamed-identifier then if the identifier is inserted free its parent will be looked up in that environment, rather than in the top-level environment. The use of such an identifier must be restricted to the lexical scope of its environment.

There is another restriction imposed for implementation convenience: Macros passing their lexical environments to renamed-identifier may be lexically bound only by the special forms let-syntax or letrec-syntax. No error is signaled if this restriction is not met, but synthetic identifier lookup will not work properly.

In order to maintain referential transparency it is necessary to determine whether two identifiers have the same denotation. With synthetic identifiers it is not necessary that two identifiers be eq? in order to denote the same binding.

Function: identifier-equal? id1 id2 env

Returns #t if identifiers id1 and id2 denote the same binding in lexical environment env, and #f otherwise. env must either be a lexical environment passed to a macro transformer during macro expansion or the empty list.

For example,

(define top-level-foo?
  (procedure->memoizing-macro
   (let ((foo-name (renamed-identifier 'foo #f)))
     (lambda (exp env)
       (identifier-equal? (cadr exp) foo-name env)))))

(top-level-foo? foo)  ⇒ #t

(let ((foo 'local))
  (top-level-foo? foo))  ⇒ #f
Function: @macroexpand1 expr env

If the car of expr denotes a macro in env, then if that macro is a primitive, expr will be returned, if the macro was defined in Scheme, then a macro expansion will be returned. If the car of expr does not denote a macro, the #f is returned.

Function: extended-environment names values env

Returns a new environment object, equivalent to env, which must either be an environment object or null, extended by one frame. names must be an identifier, or an improper list of identifiers, usable as a formals list in a lambda expression. values must be a list of objects long enough to provide a binding for each of the identifiers in names. If names is an identifier or an improper list then vals may be, respectively, any object or an improper list of objects.

Special Form: syntax-quote obj

Synthetic identifiers are converted to their parent symbols by quote and quasiquote so that literal data in macro definitions will be properly transcribed. syntax-quote behaves like quote, but preserves synthetic identifier intact.

Special Form: the-macro mac

the-macro is the simplest of all possible macro transformers: mac may be a syntactic keyword (macro name) or an expression evaluating to a macro, otherwise an error is signaled. mac is evaluated and returned once only, after which the same memoizied value is returned.

the-macro may be used to protect local copies of macros against redefinition, for example:

(@let-syntax ((let (the-macro let)))
   ;; code that will continue to work even if LET is redefined.
        …)
Special Form: renaming-transformer proc

A low-level “explicit renaming” macro facility very similar to that proposed by W. Clinger [Exrename] is supported. Syntax may be defined in define-syntax, let-syntax, and letrec-syntax using renaming-transformer instead of syntax-rules. proc should evaluate to a procedure accepting three arguments: expr, rename, and compare. expr is a representation of Scheme code to be expanded, as list structure. rename is a procedure accepting an identifier and returning an identifier renamed in the definition environment of the new syntax. compare accepts two identifiers and returns true if and only if both denote the same binding in the usage environment of the new syntax.


Previous: , Up: The Language   [Contents][Index]