首页 文章

用于记录Racket评估步骤和中间值的宏?

提问于
浏览
0

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

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 . 这看起来很棘手 .

  • 跟踪库几乎看起来像我想要的那样,但你必须先给它一个函数列表,它似乎不能让你控制输出的位置(我只想打印任何东西,如果检查失败,如果成功则失败,因此我需要在执行过程中将中间值保存到一边) .

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

2 回答

  • 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
    
  • 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
    

相关问题