Lin
Lin

Reputation: 1595

scheme - bad syntax after redefining the auxiliary keyword

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

Answers (1)

Sorawee Porncharoenwase
Sorawee Porncharoenwase

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

Related Questions