[racket] Macros and literal-id
Harry Spier wrote:
> And if not, would it be possible for someone to give a
> simple example of its use.
I will use R6 Scheme here. Scheme defines the IF syntax as:
(if ?test ?consequent ?alternate)
where the only "language keyword" is IF itself. Many other
languages use a version with "then" and "else", which are
also "reserved keywords" for those languages; Scheme has no
reserved keywords.
A Scheme IF with THEN and ELSE, let's call it IF*, would
look like:
(if ?test (then ?consequent) (else ?alternate))
we can define it with the following simple SYNTAX-RULES:
#!r6rs
(import (rnrs))
(define-syntax if*
(syntax-rules ()
((_ ?test (?then ?consequent) (?else ?alternate))
(if ?test ?consequent ?alternate))))
(let ((a 2))
(if* (< 1 a)
(then (display "yeah"))
(else (display "nay"))))
--| yeah
but notice that in this definition the pattern is:
(_ ?test (?then ?consequent) (?else ?alternate))
and the identifiers ?THEN and ?ELSE become pattern
variables, with no validation of their value; so the
following macro use also works with no error:
(let ((a 2))
(if* (< 1 a)
(123 (display "yeah"))
(456 (display "nay"))))
that is in the template of the first SYNTAX-RULES arm:
* the identifier ?THEN is a pattern variable bound to a
syntax object holding the number 123;
* the identifier ?ELSE is a pattern variable bound to a
syntax object holding the number 456;
we can verify it with the following modified program:
#!r6rs
(import (rnrs))
(define-syntax if*
(syntax-rules ()
((_ ?test (?then ?consequent) (?else ?alternate))
(display ?then))))
(let ((a 2))
(if* (< 1 a)
(123 (display "yeah"))
(456 (display "nay"))))
--| 123
This is not what we want: we would like the macro to
verify that the components of the input macro falling in the
positions of ?THEN and ?ELSE in the pattern, are identifiers
having as names the symbol THEN and the symbol ELSE.
Specifically we would like, in those positions,
identifiers being FREE-IDENTIFIER=? to "auxiliary keywords"
THEN and ELSE that we have defined. We could do such a test
using SYNTAX-CASE:
#!r6rs
(import (except (rnrs) else))
;; Define auxiliary keywords, they are just identifiers
;; bound to something.
(define-syntax then (syntax-rules ()))
(define-syntax else (syntax-rules ()))
(define-syntax if*
(lambda (stx)
(syntax-case stx ()
((_ ?test (?then ?consequent) (?else ?alternate))
(if (and (identifier? #'?then)
(free-identifier=? #'?then #'then))
(display 'good)
(display 'bad))))))
(let ((a 2))
(if* (< 1 a)
(then (display "yeah"))
(else (display "nay"))))
--| good
(let ((a 2))
(if* (< 1 a)
(123 (display "yeah"))
(456 (display "nay"))))
--| bad
We do not need to use SYNTAX-CASE for such a simple
validation, because SYNTAX-RULES offers the <LITERAL>
arguments exactly for this purpose. So the full version of
IF... THEN... ELSE... is:
#!r6rs
(import (except (rnrs) else))
(define-syntax if*
(syntax-rules (then else)
((_ ?test (then ?consequent) (else ?alternate))
(if ?test ?consequent ?alternate))))
(define-syntax then (syntax-rules ()))
(define-syntax else (syntax-rules ()))
(let ((a 2))
(if* (< 1 a)
(then (display "yeah"))
(else (display "nay"))))
--| yeah
(let ((a 2))
(if* (< 1 a)
(123 (display "yeah"))
(456 (display "nay"))))
---> syntax error
The language (rnrs) already defines an auxiliary syntax
ELSE and it is all right to use it for IF* so we can just
do:
#!r6rs
(import (rnrs))
(define-syntax if*
(syntax-rules (then else)
((_ ?test (then ?consequent) (else ?alternate))
(if ?test ?consequent ?alternate))))
(define-syntax then (syntax-rules ()))
Notice that with these definitions we have created actual
bindings for THEN and ELSE in the lexical context of the
definition for IF*, and IF* will recognise only those. So
when writing a library that exports IF* we have to export
THEN and ELSE along with it:
#!r6rs
(library (if-star)
(export if* then else)
(import (rnrs))
(define-syntax if*
(syntax-rules (then else)
((_ ?test (then ?consequent) (else ?alternate))
(if ?test ?consequent ?alternate))))
(define-syntax then (syntax-rules ())))
and use it as:
#!r6rs
(import (rnrs)
(if-star))
(let ((a 2))
(if* (< 1 a)
(then (display "yeah"))
(else (display "nay"))))
--| yeah
(let ((a 2))
(if* (< 1 a)
(123 (display "yeah"))
(456 (display "nay"))))
---> syntax error
HTH
--
Marco Maggi