如何用模板宏重写这个宏?

时间:2016-05-31 07:22:12

标签: macros scheme racket

假设我想在Racket / Scheme中定义一个工具"定义"的宏。具体来说,它计算一个定义体中的数字文字,然后将这个数字添加到所有这些文字中(这应该在宏扩展阶段发生)。

这是以普通函数样式定义的宏(在语法树上作为列表运行):

#lang racket

(require
  (for-syntax racket/syntax)
  (rename-in racket [define define/racket]))

(define-for-syntax (count-and-transform e)
  (define tree (syntax->datum e))
  ; count number literals
  (define (count t)
    (if (list? t)
        (apply + (map count t))
        (if (number? t) 1 0)))
  (define n (count tree))
  ; transform number literals
  (define (transform t)
    (if (list? t)
        (map transform t)
        (if (number? t) (+ t n) t)))
  (values n (datum->syntax e (transform tree))))

; rewrite defines
(define-syntax (define stx)
  (syntax-case stx ()
    [(_ signature body)
     (let-values ([(n new-body)
                   (count-and-transform #'body)])
       #`(begin
           (display "number of literals in function ")
           (display 'signature) (display ": ") (displayln #,n)
           (define/racket signature #,new-body)))]))

(define (add-some x) (if (= x 0) (+ x 1) 2))

我想用普通的Racket / Scheme模板宏样式重写它。这是我的(不成功)尝试:

#lang racket

(require
  (for-syntax racket/syntax)
  (rename-in racket [define define/racket]))

(define-for-syntax n 0)

; rewrite defines
(define-syntax-rule
  (define signature body)
  (begin
    (display "number of literals in function ")
    (display 'signature) (display ": ") (display-counted-n)
    (define/racket signature (descent body))))

; descent the syntax tree and mark all nodes
(define-syntax descent
  (syntax-rules (f-node a-node)
    [(_ (f etc ...)) (mark (f (descent etc) ...))]
    [(_ a etc ...) (mark a (descent etc) ...)]))

; count number literals
(define-syntax (mark stx)
  (syntax-case stx ()
    [(_ node)
     (begin
       ;(display n) (display " : ") (displayln (syntax->datum #'node))
       (if (number? (syntax-e #'node))
           (begin
             (set! n (add1 n))
             #'(transform node))
           #'node))]))

; transform number literals
(define-syntax (transform stx)
  (syntax-case stx ()
    [(_ node)
     (let* ([i (syntax->datum #'node)]
            [i+n (+ i n)])
       (begin
         ;(display i) (display " -> ") (displayln i+n)
         (datum->syntax stx i+n)))]))

(define-syntax (display-counted-n stx)
  (syntax-case stx ()
    [(_) #`(displayln #,n)]))

(define (add-some x) (if (= x 0) (+ x 11) 13))

我们的想法是分阶段测试代码:首先在语法树中标记所有节点,然后计算文字,用" transform"替换标记。宏,如果需要......好吧,正如评论"显示"会显示,宏观"标记"在所有"下降之前开始扩张"已完成(因此它们仍然在宏代码中捕获)。甚至"显示计数n"过快地扩张,而" n"仍然是0。

有没有办法改变宏扩展的顺序?我希望Racket / Scheme分阶段进行扩展:首先" descent"然后"标记"然后"转换"然后"显示计数 - N'#34;

我已经阅读了How to control order of Scheme macro expansion?的答案 - 似乎用模板宏实现这样一项任务的唯一方法就是使用"秘密文字"并在一个大宏定义中定义所有内容。但是,我想这会使代码更难写入和读取。还有其他方式吗?

1 个答案:

答案 0 :(得分:2)

这是我的宏syntax-case版本的版本:

(define-syntax (lambda-fun stx)
  (define (count-numeric-literals stx2)
    (syntax-case stx2 ()
      (num (number? (syntax->datum #'num)) 1)
      ((first rest ...) (+ (count-numeric-literals #'first)
                           (count-numeric-literals #'(rest ...))))
      (_ 0)))

  (define (instrument-numeric-literals stx3 n)
    (syntax-case stx3 ()
      (num (number? (syntax->datum #'num))
           (datum->syntax #'num (+ (syntax->datum #'num) n)))
      ((first rest ...)
       (with-syntax ((a (instrument-numeric-literals #'first n))
                     ((b ...) (instrument-numeric-literals #'(rest ...) n)))
         #'(a b ...)))
      (x #'x)))

  (syntax-case stx ()
    ((_ params . body)
     (let ((count (count-numeric-literals #'body)))
       (with-syntax ((instrumented (instrument-numeric-literals #'body count)))
         #'(lambda params . instrumented))))))

(define-syntax define-fun
  (syntax-rules ()
    ((_ (f . params) . body)
     (define f (lambda-fun params . body)))
    ((_ . passthrough)
     (define . passthrough))))

这使用语法保护(也称为挡泥板)来确定语法数据是否为数字。对于更容易阅读的内容,您可以使用syntax-parse,它允许您指定语法类,如number,而不是使用语法保护:

(require (for-syntax syntax/parse))

(define-syntax (lambda-fun stx)
  (define (count-numeric-literals stx2)
    (syntax-parse stx2
      (num:number 1)
      ((first rest ...) (+ (count-numeric-literals #'first)
                           (count-numeric-literals #'(rest ...))))
      (_ 0)))

  (define (instrument-numeric-literals stx3 n)
    (syntax-parse stx3
      (num:number (datum->syntax #'num (+ (syntax->datum #'num) n)))
      ((first rest ...)
       (with-syntax ((a (instrument-numeric-literals #'first n))
                     ((b ...) (instrument-numeric-literals #'(rest ...) n)))
         #'(a b ...)))
      (x #'x)))

  (syntax-parse stx
    ((_ params . body)
     (let ((count (count-numeric-literals #'body)))
       (with-syntax ((instrumented (instrument-numeric-literals #'body count)))
         #'(lambda params . instrumented))))))

示例:

> (define-fun (fun) (+ 1 2 3 4))
> (fun)
26