Reputation: 1595
Please take a look at the following macro uses.
(let ()
(define-syntax minus
(syntax-rules (from)
[(_ e1 from e2) (- e2 e1)]))
; a
(minus 1 from 2)
;; => 1
; b
(define from 3)
(minus from from 2)
;; => -1
; c
(minus 1 from 2)
;; => 1
; d
#;
(let ([from 1])
(minus 1 from 2))
;; bad syntax
)
I have noticed something unexpected with the auxiliary syntax from
involved in the minus
macro.
It seems to me from
can be redefined wherever we want. However look like case d
will not work.
This can be easily reproduced with Racket
.
Any idea why there is a bad syntax?
Thanks,
Edit:
Note if I modify the definition a little, it will be valid again.
(let ()
(define-syntax minus
(syntax-rules (from)
[(_ e1 from e2) (- e2 e1)]
[(_ e1 e2 _) (- e2 e1)]))
; a
(minus 1 from 2)
;; => 1
; b
(let ([from 1])
(minus 1 from 2))
;; => 0
)
Since free-identifier=?
is used for the implicit guard for the auxiliary keywords, the from
in case b
is not treated as a keyword, then the first case will not be matched.
Upvotes: 1
Views: 140
Reputation: 6502
As the documentation suggests, syntax-rules
expands to syntax-case
. And indeed, syntax-case
has the same problem:
#lang racket
(define-syntax (minus stx)
(syntax-case stx (from)
[(_ e1 from e2) #'(- e2 e1)]))
(let ([from 1])
(minus 1 from 2))
;; => minus: bad syntax in: (minus 1 from 2)
However, there's a variant of syntax-case
named syntax-case*
. Its documentation states that:
Like syntax-case, but id-compare-expr must produce a procedure that accepts two arguments. A literal-id in a pattern matches an identifier for which the procedure returns true when given the identifier to match (as the first argument) and the identifier in the pattern (as the second argument).
In other words, syntax-case is like syntax-case* with an id-compare-expr that produces free-identifier=?.
So we can try this:
#lang racket
(define-for-syntax (my-comparator a b)
(println a)
(println b)
(println (free-identifier=? a b))
(free-identifier=? a b))
(define-syntax (minus stx)
(syntax-case* stx (from) my-comparator
[(_ e1 from e2) #'(- e2 e1)]))
(minus 1 from 2)
;; => .#<syntax:unsaved-editor:13:9 from>
;; => .#<syntax:unsaved-editor:11:11 from>
;; => #t
(let ([from 1])
(minus 1 from 2))
;; => .#<syntax:unsaved-editor:19:11 from>
;; => .#<syntax:unsaved-editor:11:11 from>
;; => #f
;; => minus: bad syntax in: (minus 1 from 2)
As you can see, the problem is that free-identifier=?
finds that both from
are not equal in the latter case.
So, to make this work, simply supply your own comparator that doesn't use free-identifier=?
:
#lang racket
(define-for-syntax (my-comparator a b)
(eq? (syntax->datum a) (syntax->datum b)))
(define-syntax (minus stx)
(syntax-case* stx (from) my-comparator
[(_ e1 from e2) #'(- e2 e1)]))
(minus 1 from 2)
;; => 1
(let ([from 1])
(minus 1 from 2))
;; => 1
If somehow you can't use syntax-case*
, you can also do this:
(define-syntax (minus stx)
(syntax-case stx ()
[(_ e1 from e2)
(eq? (syntax->datum #'from) 'from) ; a guard
#'(- e2 e1)]))
though it gets tedious when you have several literal ids.
Upvotes: 3