【问题标题】:How do I expand inner macros before the outer ones?如何在外部宏之前扩展内部宏?
【发布时间】:2013-10-05 19:57:30
【问题描述】:

假设我有以下宏:

(define-syntax-rule (qq x) '(1 x))

我可以将看起来像 (qq (qq 2)) 的东西扩展为 '(1 (1 2)) 而不是 (1 (qq 2) 吗?

我说“看起来像”的原因是因为到目前为止唯一的hint I found 表明由内而外的宏扩展很棘手,所以我想知道是否有更明智的方法来获得我想要的最终结果。

我最初的动机与 Racket 的 parser generator library 有关:为了创建语法,库提供了一个如下所示的 parser 宏:

(define my-parser (parser
  (start start) (end EOF)
  (tokens value-tokens op-tokens)
  (error (lambda (a b c) (void)))

  (grammar
   (start [(numbers) $1])

   (numbers [(numberx) (reverse $1)])
   (numberx [() empty]
            [(numberx NUM) (cons $2 $1)])
  )
))

我的语法有很多我想抽象出来的样板。例如,我希望能够定义某种list-rules 抽象,让我可以写出类似于

(define my-parser (parser
  (start start) (end EOF)
  (tokens value-tokens op-tokens)
  (error (lambda (a b c) (void)))

  (grammar
   (start [(numbers) $1])

   (list-rules NUM numbers numberx)
  )
))

但是,如果 parser 先扩展,它将把 list-rules 本身视为非终端,而不是将其扩展为真正的非终端(numbersnumberx)。

【问题讨论】:

标签: macros lisp scheme racket


【解决方案1】:

你可以试试local-expand,不过我承认我没有花时间理解你问题的第二部分,不足以知道它是否适合使用。

#lang racket
(define-syntax (qq stx)
  (syntax-case stx ()
    [(_ x) 
     (with-syntax ([y (local-expand #'x 'expression '())])
       #'`(1 ,y))]))
(qq 2)
(qq (qq 2))
(qq (qq (qq 2)))

=>

'(1 2)
'(1 (1 2))
'(1 (1 (1 2)))

【讨论】:

  • 本地扩展不起作用。我尝试制作一个"expanding-parser" macro,它在“语法”参数中本地扩展了所有产品,但是 1)它会尝试扩展不应该扩展的东西(错误可能会抱怨“开始”未绑定)和 2)我的“列表规则”需要扩展为两个列表项,我认为您不能通过宏扩展来做到这一点......
  • 现在我想起来了,如果这只是生成具有我想要的结构的列表或语法对象的问题,我可以使用准引号(,@#,@ 用于“ list-rules") 但我不知道如何从中定义my-parser
  • local-expand 的第三个参数是停止标识符列表,因此您可以控制发生多少扩展。将start 添加到此列表是否可以修复错误?
  • #f 作为第三个参数传递给 local-expand 解决了第一个问题,但现在当我尝试使用 list-rules 宏时出现 let-values: cannot bind identifier tainted by macro expansion in: numberx 错误。此外,还有一个问题是我最终需要将列表规则扩展到 2 个非终端,而不仅仅是一个,I don't think is possible
【解决方案2】:

您需要有 2 条语法规则,首先是更通用的规则;也没有使用 quasiquote 和 unquote 运算符,以便可以评估内部形式:

(define-syntax qq
   (syntax-rules ()
     ((_ (x ...) ...)  `(1 ,(x ...) ...))
     ((_ x)            `(1 x))))

比如

-> (qq x)
'(1 x)
-> (qq (qq x))
'(1 (1 x))
-> (qq (qq (qq x)))
'(1 (1 (1 x)))
-> (qq (qq (qq (qq y))))
'(1 (1 (1 (1 y))))

这也是可组合的:

(define-syntax qq
   (syntax-rules ()
     ((_ (x ...) ...)  `(1 ,(x ...) ...))
     ((_ x)            `(1 x))))

(define-syntax hh
   (syntax-rules ()
     ((_ (x ...) ...)  `(2 ,(x ...) ...))
     ((_ x)            `(2 x))))

比如

-> (qq (hh x))
'(1 (2 x))
-> (hh (qq x))
'(2 (1 x))

【讨论】:

  • 看起来这解决了递归宏的简化问题,但我似乎无法理解如何使用它来扩展库中现有宏的参数。
  • @missingo 我的理解是,这取决于库宏的设计方式。
  • 当然。如果库宏能帮到你,我该怎么办?
【解决方案3】:

好吧,您可以对 shape 进行宏检查:

(define-syntax qq
  (syntax-rules ()
    [(qq (qq x)) (list 1 (qq x))]
    [(qq x)      (list 1 x)]))

好消息是它“递归”地工作:

(qq 2)           ; => '(1 2)
(qq (qq 2))      ; => '(1 (1 2))
(qq (qq (qq 2))) ; => '(1 (1 (1 2)))

但即使提供了 qq 以外的其他内容,它也可以工作:

(qq (+ 1))       ; => '(1 2)

如果这对您的预期用途来说是一个问题——如果你希望将其标记为错误——你可以这样做:

(define-syntax (rr stx)
  (syntax-case stx ()
    [(_ (rr x))
     (cond [(equal? 'rr (syntax->datum #'rr))
            #'(list 1 (rr x))]
           [else (raise-syntax-error #f "Expected rr" stx #'rr)])]
    [(_ x)
     #'(list 1 x)]))

示例的结果相同,但最后一个现在给出了语法错误:

(rr 2)           ; => '(1 2)
(rr (rr 2))      ; => '(1 (1 2))
(rr (rr (rr 2))) ; => '(1 (1 (1 2)))
(rr (+ 1))       ; =>
; src/scheme/misc/so-syntax.rkt:27:5: rr: Expected rr
;  at: +
;  in: (rr (+ 1))

更新:第二个版本可以用syntax-parse写得更干净一点:

(require (for-syntax syntax/parse))
(define-syntax (rr stx)
  (syntax-parse stx
    [(_ ((~literal rr) x)) #'(list 1 (rr x))]
    [(_ (_ x)) (raise-syntax-error #f "Expected rr" stx #'rr)]
    [(_ x) #'(list 1 x)]))

【讨论】:

  • 看起来这解决了简化的递归宏问题,但我看不出这将如何应用于我原来的问题:(
猜你喜欢
  • 1970-01-01
  • 2015-01-27
  • 1970-01-01
  • 2011-06-26
  • 1970-01-01
  • 1970-01-01
  • 2020-11-21
  • 2019-11-07
  • 2015-09-26
相关资源
最近更新 更多