首页 > 解决方案 > 方案 - 重新定义辅助关键字后语法错误

问题描述

请看下面的宏用法。

(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
  )

我注意到宏from中涉及的辅助语法有一些意想不到的地方。minus

在我看来,我们from可以在任何地方重新定义。但是看起来案例d不起作用。

这可以很容易地用Racket.

知道为什么语法不好吗?

谢谢,


编辑:

请注意,如果我稍微修改定义,它将再次有效。

(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
  )

由于free-identifier=?用于辅助关键字的隐式保护,因此不将fromin caseb视为关键字,则不会匹配第一个 case。

标签: schemeracket

解决方案


正如文档所建议的那样,syntax-rules扩展为syntax-case. 确实,syntax-case有同样的问题:

#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)

但是,syntax-casenamed有一个变体syntax-case*。其文档指出:

与 syntax-case 类似,但 id-compare-expr 必须生成一个接受两个参数的过程。模式中的literal-id 匹配一个标识符,当给定要匹配的标识符(作为第一个参数)和模式中的标识符(作为第二个参数)时,过程返回true。

换句话说,syntax-case 就像带有 id-compare-expr 的 syntax-case*,它产生 free-identifier=?。

所以我们可以试试这个:

#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)

如您所见,问题在于在后一种情况下free-identifier=?发现两者from都不相等。

因此,要完成这项工作,只需提供您自己的不使用的比较器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

如果不知何故你不能使用syntax-case*,你也可以这样做:

(define-syntax (minus stx)
  (syntax-case stx () 
    [(_ e1 from e2)
     (eq? (syntax->datum #'from) 'from) ; a guard
     #'(- e2 e1)]))

尽管当您有多个文字 ID 时会变得乏味。


推荐阅读