Kawa supports most of the syntax-case feature.
Syntax definitions are valid wherever definitions are. They have the following form:
Syntax: define-syntax keyword transformer-spec
The
keywordis a identifier, andtransformer-specis a function that maps syntax forms to syntax forms, usually an instance ofsyntax-rules. If thedefine-syntaxoccurs at the top level, then the top-level syntactic environment is extended by binding thekeywordto to the specified transformer, but existing references to any top-level binding forkeywordremain unchanged. Otherwise, it is a internal syntax definition, and is local to thebodyin which it is defined.(let ((x 1) (y 2)) (define-syntax swap! (syntax-rules () ((swap! a b) (let ((tmp a)) (set! a b) (set! b tmp))))) (swap! x y) (list x y)) ⇒ (2 1)Macros can expand into definitions in any context that permits them. However, it is an error for a definition to define an identifier whose binding has to be known in order to determine the meaning of the definitoion itself, or of any predecing definiton that belongs to the same group of internal definitions.
Syntax: define-syntax-case name (literals) (pattern expr) ...
A convenience macro to make it easy to define
syntax-case-style macros. Defines a macro with the givennameand list ofliterals. Eachpatternhas the form of asyntax-rules-style pattern, and it is matched against the macro invocation syntax form. When a match is found, the correspondingexpris evaluated. It must evaluate to a syntax form, which replaces the macro invocation.(define-syntax-case macro-name (literals) (pat1 result1) (pat2 result2))is equivalent to:
(define-syntax macro-name (lambda (form) (syntax-case form (literals) (pat1 result1) (pat2 result2))))
Syntax: define-macro (name lambda-list) form ...
This form is deprecated. Functionally equivalent to
defmacro.
Syntax: defmacro name lambda-list form ...
This form is deprecated. Instead of
(defmacro (name...) (let ... `(... ,exp...)))you should probably do:
(define-syntax-casename() ((_ ...) (let #`(... #,exp...))))and instead of
(defmacro (name...var...) `(...var...))you should probably do:
(define-syntax-casename() ((_ ...var...) #`(...var...))Defines an old-style macro a la Common Lisp, and installs
(lambdaas the expansion function forlambda-listform...)name. When the translator sees an application ofname, the expansion function is called with the rest of the application as the actual arguments. The resulting object must be a Scheme source form that is futher processed (it may be repeatedly macro-expanded).
Returns a new (interned) symbol each time it is called. The symbol names are implementation-dependent. (This is not directly macro-related, but is often used in conjunction with
defmacroto get a fresh unique identifier.)
The result of evaluating
formis treated as a Scheme expression, syntax-expanded to internal form, and then converted back to (roughly) the equivalent expanded Scheme form.This can be useful for debugging macros.
To access this function, you must first
(require 'syntax-utils).(require 'syntax-utils) (expand '(cond ((> x y) 0) (else 1))) ⇒ (if (> x y) 0 1)
Return
#tifobjis an identifier, i.e., a syntax object representing an identifier, and#fotherwise.The
identifier?procedure is often used within a fender to verify that certain subforms of an input form are identifiers, as in the definition ofrec, which creates self–contained recursive objects, below.(define-syntax rec (lambda (x) (syntax-case x () ((_ x e) (identifier? #'x) #'(letrec ((x e)) x))))) (map (rec fact (lambda (n) (if (= n 0) 1 (* n (fact (- n 1)))))) '(1 2 3 4 5)) ⇒ (1 2 6 24 120) (rec 5 (lambda (x) x)) ⇒ exception
The procedures bound-identifier=? and free-identifier=?
each take two identifier arguments and return #t if their arguments
are equivalent and #f otherwise. These predicates are used to
compare identifiers according to their intended use as free
references or bound identifiers in a given context.
Procedure: bound-identifier=? id1id2
id1andid2must be identifiers.The procedure
bound-identifier=?returns#tif a binding for one would capture a reference to the other in the output of the transformer, assuming that the reference appears within the scope of the binding, and#fotherwise.In general, two identifiers are
bound-identifier=?only if both are present in the original program or both are introduced by the same transformer application (perhaps implicitly, seedatum->syntax).The
bound-identifier=?procedure can be used for detecting duplicate identifiers in a binding construct or for other preprocessing of a binding construct that requires detecting instances of the bound identifiers.
Procedure: free-identifier=? id1id2
id1andid2must be identifiers.The
free-identifier=?procedure returns#tif and only if the two identifiers would resolve to the same binding if both were to appear in the output of a transformer outside of any bindings inserted by the transformer. (If neither of two like–named identifiers resolves to a binding, i.e., both are unbound, they are considered to resolve to the same binding.)Operationally, two identifiers are considered equivalent by
free-identifier=?if and only the topmost matching substitution for each maps to the same binding or the identifiers have the same name and no matching substitution.The
syntax-caseandsyntax-rulesforms internally usefree-identifier=?to compare identifiers listed in the literals list against input identifiers.(let ((fred 17)) (define-syntax a (lambda (x) (syntax-case x () ((_ id) #'(b id fred))))) (define-syntax b (lambda (x) (syntax-case x () ((_ id1 id2) #`(list #,(free-identifier=? #'id1 #'id2) #,(bound-identifier=? #'id1 #'id2)))))) (a fred)) ⇒ (#t #f)The following definition of unnamed
letusesbound-identifier=?to detect duplicate identifiers.(define-syntax let (lambda (x) (define unique-ids? (lambda (ls) (or (null? ls) (and (let notmem? ((x (car ls)) (ls (cdr ls))) (or (null? ls) (and (not (bound-identifier=? x (car ls))) (notmem? x (cdr ls))))) (unique-ids? (cdr ls)))))) (syntax-case x () ((_ ((i v) ...) e1 e2 ...) (unique-ids? #'(i ...)) #'((lambda (i ...) e1 e2 ...) v ...)))))The argument
#'(i ...)tounique-ids?is guaranteed to be a list by the rules given in the description ofsyntaxabove.With this definition of
let:(let ((a 3) (a 4)) (+ a a)) ⇒ syntax errorHowever,
(let-syntax ((dolet (lambda (x) (syntax-case x () ((_ b) #'(let ((a 3) (b 4)) (+ a b))))))) (dolet a)) ⇒ 7since the identifier
aintroduced bydoletand the identifieraextracted from the input form are notbound-identifier=?.Rather than including
elsein the literals list as before, this version ofcaseexplicitly tests forelseusingfree-identifier=?.(define-syntax case (lambda (x) (syntax-case x () ((_ e0 ((k ...) e1 e2 ...) ... (else-key else-e1 else-e2 ...)) (and (identifier? #'else-key) (free-identifier=? #'else-key #'else)) #'(let ((t e0)) (cond ((memv t '(k ...)) e1 e2 ...) ... (else else-e1 else-e2 ...)))) ((_ e0 ((ka ...) e1a e2a ...) ((kb ...) e1b e2b ...) ...) #'(let ((t e0)) (cond ((memv t '(ka ...)) e1a e2a ...) ((memv t '(kb ...)) e1b e2b ...) ...))))))With either definition of
case,elseis not recognized as an auxiliary keyword if an enclosing lexical binding forelseexists. For example,(let ((else#f)) (case 0 (else (write "oops")))) ⇒ syntax errorsince
elseis bound lexically and is therefore not the sameelsethat appears in the definition ofcase.
Procedure: syntax->datum syntax-object
Deprecated procedure: syntax-object->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 predicatesymbolic-identifier=?might be defined as follows.(define symbolic-identifier=? (lambda (x y) (eq? (syntax->datum x) (syntax->datum y))))
Procedure: datum->syntax template-iddatum
Deprecated procedure: datum->syntax-object datum
template-idmust be a template identifier anddatumshould be a datum value.The
datum->syntaxprocedure returns a syntax-object representation ofdatumthat contains the same contextual information astemplate-id, with the effect that the syntax object behaves as if it were introduced into the code whentemplate-idwas introduced.The
datum->syntaxprocedure 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. For example, the following defines aloopexpression that uses this controlled form of identifier capture to bind the variablebreakto an escape procedure within the loop body. (The derivedwith-syntaxform is likeletbut binds pattern variables.)(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 (if (= n 0) (break ls)) (set! ls (cons 'a ls)) (set! n (- n 1)))) ⇒ (a a a)Were
loopto be defined as:(define-syntax loop (lambda (x) (syntax-case x () ((_ e ...) #'(call-with-current-continuation (lambda (break) (let f () e ... (f))))))))the variable
breakwould not be visible ine ....The datum argument
datummay also represent an arbitrary Scheme form, as demonstrated by the following definition ofinclude.(define-syntax include (lambda (x) (define read-file (lambda (fn k) (let ((p (open-file-input-port fn))) (let f ((x (get-datum p))) (if (eof-object? x) (begin (close-port p) '()) (cons (datum->syntax k x) (f (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 abeginexpression containing the forms found in the file named by"filename". For example, if the fileflib.sscontains:(define f (lambda (x) (g (* x x))))and the file
glib.sscontains:(define g (lambda (x) (+ x x)))the expression:
(let () (include "flib.ss") (include "glib.ss") (f 5))evaluates to
50.The definition of
includeusesdatum->syntaxto 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 theincludeform appears.Using
datum->syntax, it is even possible to break hygiene entirely and write macros in the style of old Lisp macros. Thelisp-transformerprocedure defined below creates a transformer that converts its input into a datum, calls the programmer’s procedure on this datum, and converts the result back into a syntax object scoped where the original macro use appeared.(define lisp-transformer (lambda (p) (lambda (x) (syntax-case x () ((kwd . rest) (datum->syntax #'kwd (p (syntax->datum x))))))))
Syntax: with-syntax ((pattern) expression…) body
The
with-syntaxform is used to bind pattern variables, just asletis used to bind variables. This allows a transformer to construct its output in separate pieces, then put the pieces together.Each
patternis identical in form to asyntax-casepattern. The value of eachexpressionis computed and destructured according to the correspondingpattern, and pattern variables within thepatternare bound as withsyntax-caseto the corresponding portions of the value withinbody.The
with-syntaxform may be defined in terms ofsyntax-caseas follows.(define-syntax with-syntax (lambda (x) (syntax-case x () ((_ ((p e0) ...) e1 e2 ...) (syntax (syntax-case (list e0 ...) () ((p ...) (let () e1 e2 ...))))))))The following definition of
conddemonstrates the use ofwith-syntaxto support transformers that employ recursion internally to construct their output. It handles allcondclause variations and takes care to produce one-armedifexpressions where appropriate.(define-syntax cond (lambda (x) (syntax-case x () ((_ c1 c2 ...) (let f ((c1 #'c1) (c2* #'(c2 ...))) (syntax-case c2* () (() (syntax-case c1 (else =>) (((else e1 e2 ...) #'(begin e1 e2 ...)) ((e0) #'e0) ((e0 => e1) #'(let ((t e0)) (if t (e1 t)))) ((e0 e1 e2 ...) #'(if e0 (begin e1 e2 ...))))) ((c2 c3 ...) (with-syntax ((rest (f #'c2 #'(c3 ...)))) (syntax-case c1 (=>) ((e0) #'(let ((t e0)) (if t t rest))) ((e0 => e1) #'(let ((t e0)) (if t (e1 t) rest))) ((e0 e1 e2 ...) #'(if e0 (begin e1 e2 ...) rest)))))))))))
Auxiliary Syntax: unsyntax-splicing
The
quasisyntaxform is similar tosyntax, but it allows parts of the quoted text to be evaluated, in a manner similar to the operation ofquasiquote.Within a
quasisyntaxtemplate, subforms ofunsyntaxandunsyntax-splicingforms are evaluated, and everything else is treated as ordinary template material, as withsyntax.The value of each
unsyntaxsubform is inserted into the output in place of theunsyntaxform, while the value of eachunsyntax-splicingsubform is spliced into the surrounding list or vector structure. Uses ofunsyntaxandunsyntax-splicingare valid only withinquasisyntaxexpressions.A
quasisyntaxexpression may be nested, with eachquasisyntaxintroducing a new level of syntax quotation and eachunsyntaxorunsyntax-splicingtaking away a level of quotation. An expression nested within nquasisyntaxexpressions must be within n unsyntax orunsyntax-splicingexpressions to be evaluated.As noted in
abbreviation,#`is equivalent totemplate(quasisyntax,template)#,is equivalent totemplate(unsyntax, andtemplate)#,@is equivalent totemplate(unsyntax-splicing. Note that for backwards compatibility, you should only usetemplate)#,inside a literaltemplate#`form.templateThe
quasisyntaxkeyword can be used in place ofwith-syntaxin many cases. For example, the definition ofcaseshown under the description ofwith-syntaxabove can be rewritten usingquasisyntaxas follows.(define-syntax case (lambda (x) (syntax-case x () ((_ e c1 c2 ...) #`(let ((t e)) #,(let f ((c1 #'c1) (cmore #'(c2 ...))) (if (null? cmore) (syntax-case c1 (else) ((else e1 e2 ...) #'(begin e1 e2 ...)) (((k ...) e1 e2 ...) #'(if (memv t '(k ...)) (begin e1 e2 ...))]) (syntax-case c1 () (((k ...) e1 e2 ...) #`(if (memv t '(k ...)) (begin e1 e2 ...) #,(f (car cmore) (cdr cmore))))))))))))
Note: Any
syntax-rulesform can be expressed withsyntax-caseby making thelambdaexpression andsyntaxexpressions explicit, andsyntax-rulesmay be defined in terms ofsyntax-caseas follows.(define-syntax syntax-rules (lambda (x) (syntax-case x () ((_ (lit ...) ((k . p) t) ...) (for-all identifier? #'(lit ... k ...)) #'(lambda (x) (syntax-case x (lit ...) ((_ . p) #'t) ...))))))