Failed to match syntax-case for some literal names if using read-syntax

Hi. Can't understand why I can't match some literal-id's with syntax-case.

Here is a successful match example:

> (let ((stx #'(load 'hello)))
    (syntax-case stx (load)
      ((load expr) (syntax expr))))
#<syntax::73:19 'hello>

But if we use read-syntax then it fails for some unknown reason:

> (let ((stx (read-syntax "repl" (open-input-string "(load 'hello)"))))
    (syntax-case stx (load)
      ((load expr) (syntax expr))))
; repl::1: load: bad syntax
;   in: (load (quote hello))
;   Source locations:
;   repl:1:0

If we change literal-id to something which is not available in current namespace then it is working:

> (let ((stx (read-syntax "repl" (open-input-string "(xxx 'hello)"))))
    (syntax-case stx (xxx)
      ((xxx expr) (syntax expr))))
#<syntax:repl::6 'hello>

Could somebody please explain why this is not working and how could I fix this?
Thank you.

According to the doc of syntax-case:

An id that has the same binding as a literal-id matches a syntax object that is an identifier with the same binding in the sense of free-identifier=?. The match does not introduce any pattern variables.

When you use read-syntax, you get a syntax object with empty lexical context. However, load that you are trying to match against is load from #lang racket. Therefore, they don’t match according to free-identifier=?.

There are a couple of possibilities to make them match.

(1) Add the current lexical context to the syntax object. This can be done by using replace-context. E.g.

#lang racket

(require syntax/strip-context)

(let [(stx (replace-context
            #'here
            (read-syntax
             "repl"
             (open-input-string "(load 'hello)"))))]
  (syntax-case stx (load)
    [(load expr) (syntax expr)]))

(2) Don’t use literal-id in syntax-case; instead use a side-condition that checks textual equivalence.

#lang racket

(let [(stx (read-syntax
            "repl"
            (open-input-string "(load 'hello)")))]
  (syntax-case stx ()
    [(load expr)
     (and (identifier? #'load) (eq? 'load (syntax-e #'load)))
     (syntax expr)]))

(3) Use syntax-parse‘s ~datum.

#lang racket

(require syntax/parse)

(let [(stx (read-syntax
            "repl"
            (open-input-string "(load 'hello)")))]
  (syntax-parse stx
    [({~datum load} expr)
     (syntax expr)]))

Note that (2) and (3) are essentially equivalent (resulting expr has empty lexical context), but (1) is not (resulting expr has the same lexical context as #'here). Depending on what you want to do, one might be more appropriate than another.

1 Like