如何在外部宏之前展开内部宏?

时间:2013-10-05 19:57:30

标签: macros lisp scheme racket

说我有以下宏:

(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本身视为非终端,而不是将其扩展为真正的非终端(numbers和{{1} })。

3 个答案:

答案 0 :(得分:2)

你可以试试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)))

答案 1 :(得分:1)

您需要有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))

答案 2 :(得分:1)

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

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

示例的结果相同,除了last现在给出语法错误:

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