0

作为学习 Racket 宏系统的练习,我一直在实现一个基于C++ catch 框架的单元测试框架。该框架的一个特点是,如果我写这样的支票:

CHECK(x == y); // (check x y)

当检查被违反时,错误消息将打印出 x 和 y 的值,即使使用的宏是完全通用的,不像其他测试框架需要您使用 CHECK_EQUALS、CHECK_GREATER 等宏。这可以通过一些hackery涉及表达式模板和运算符重载。

在我看来,在 Racket 中你应该能够做得更好。在 C++ 版本中,宏看不到子表达式内部,因此如果您编写如下内容:

CHECK(f(x, g(y)) == z); // (check (= (f x (g y)) z))

当检查被违反时,您只能找到等号左右两侧的值,而不是 x、y 或 g(y) 的值。在球拍中,我希望应该可以递归到子表达式并打印一棵树来显示评估的每个步骤。

问题是我不知道最好的方法是什么:

  • 我已经相当熟悉语法解析,但这似乎超出了它的能力。
  • 我读到了关于自定义 #%app 的内容,这几乎就像我想要的那样,但是如果例如 f 是一个宏,我不想打印出扩展中表达式的每个求值,而只是打印出表达式的求值当用户调用检查宏时可见。也不确定我是否可以在不定义语言的情况下使用它。
  • 我可以使用 syntax-parameterize 来劫持基本运算符的含义,但这对 g(y) 之类的函数调用没有帮助。
  • 我可以使用 syntax->datum 并手动遍历 AST,自己在子表达式上调用 eval。这似乎很棘手。
  • 跟踪库几乎看起来像我想要的那样,但你必须预先给它一个函数列表,而且它似乎不能让你控制输出的去向(我只想打印任何东西,如果检查失败,而不是如果它成功,所以我需要在执行过程中将中间值保存到一边)。

实现这一点的最佳或至少是惯用的方法是什么?

4

2 回答 2

2

打印所有内容的替代方法是为应该显示的内容添加标记。这是一个粗略的简单草图:

#lang racket

(require racket/stxparam)

(define-syntax-parameter ?
  (λ(stx) (raise-syntax-error '? "can only be used in a `test' context")))

(define-syntax-rule (test expr)
  (let ([log '()])
    (define (log! stuff) (set! log (cons stuff log)))
    (syntax-parameterize ([? (syntax-rules ()
                               [(_ E) (let ([r E]) (log! `(E => ,r)) r)])])
      (unless expr
        (printf "Test failure: ~s\n" 'expr)
        (for ([l (in-list (reverse log))])
          (for-each display
                    `("  " ,@(add-between (map ~s l) " ") "\n")))))))

(define x 11)
(define y 22)
(test (equal? (? (* (? x) 2)) (? y)))
(test (equal? (? (* (? x) 3)) (? y)))

这导致了这个输出:

Test failure: (equal? (? (* (? x) 3)) (? y))
  x => 11
  (* (? x) 3) => 33
  y => 22
于 2016-09-28T21:25:47.607 回答
2

这里有一些东西可以帮助您入门。

#lang racket

(require (for-syntax syntax/parse racket/list))

(begin-for-syntax
  (define (expression->subexpressions stx)
    (define expansion (local-expand stx 'expression '()))
    (syntax-parse expansion
      #:datum-literals (#%app quote)
      [x:id      (list #'x)]
      [b:boolean (list #'b)]
      [n:number  (list #'n)]
      ; insert other atoms here
      [(quote literal) (list #'literal)]
      [(#%app e ...)
       (cons stx
             (append-map expression->subexpressions (syntax->list #'(e ...))))]
      ; other forms in fully expanded syntax goes here
      [else
       (raise-syntax-error 'expression->subexpressions
                           "implement this construct"
                           stx)])))

(define-syntax (echo-and-eval stx)
  (syntax-parse stx
    [(_ expr)
     #'(begin
         (display "] ") (displayln (syntax->datum #'expr))
         (displayln expr))]))

(define-syntax (echo-and-eval-subexpressions stx)
  (syntax-parse stx
    [(_ expr)
     (define subs (expression->subexpressions #'expr))
     (with-syntax ([(sub ...) subs])
       #'(begin
           ; sub expressions
           (echo-and-eval sub)
           ...
           ; original expression
           (echo-and-eval expr)))]))


(echo-and-eval-subexpressions (+ 1 2 (* 4 5)))

输出:

] (+ 1 2 (* 4 5))
23
] +
#<procedure:+>
] 1
1
] 2
2
] (#%app * '4 '5)
20
] *
#<procedure:*>
] 4
4
] 5
5
] (+ 1 2 (* 4 5))
23
于 2016-09-28T09:48:51.397 回答