簡體   English   中英

宏來記錄球拍中的評估步驟和中間值嗎?

[英]Macro to record evaluation steps and intermediate values in Racket?

作為學習Racket宏系統的練習,我一直在基於C ++ catch框架實現一個單元測試框架 該框架的功能之一是,如果我編寫如下檢查:

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

違反檢查時,即使所使用的宏是完全通用的,錯誤消息也會打印出x和y的值,這與其他測試框架不同,其他測試框架要求您使用諸如CHECK_EQUALS,CHECK_GREATER等宏。這可能是通過某些黑客手段實現的涉及表達式模板和運算符重載。

在我看來,在球拍中您應該能夠做得更好。 在C ++版本中,宏看不到內部子表達式,因此,如果您編寫類似以下內容:

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

違反支票時,您只會找出等號左右兩側的值,而不是x,y或g(y)的值。 在球拍中,我希望應該可以遞歸到子表達式並打印出顯示評估步驟的樹。

問題是我不知道執行此操作的最佳方法是:

  • 我對語法解析已經相當熟悉,但這似乎超出了它的能力范圍。
  • 我讀過有關自定義#%app的內容,這幾乎看起來像我想要的,但是例如,如果f是宏,則我不想打印出擴展中表達式的每個求值,而只是打印那些當用戶調用檢查宏時可見。 也不確定是否可以在不定義語言的情況下使用它。
  • 我可以使用語法參數化劫持基本運算符的含義,但這對g(y)之類的函數調用無濟於事。
  • 我可以使用語法->數據並手動遍歷AST,自己對子表達式調用eval。 這似乎很棘手。
  • 跟蹤庫幾乎看起來像它要執行的操作,但是您必須預先給它一個函數列表,並且它似乎沒有給您任何控制輸出的位置(我只想打印任何內容,如果檢查失敗,而不是是否成功,所以我需要在執行過程中將中間值保存到一邊。

最好的或至少是慣用的方法是什么?

這是一些讓您入門的東西。

#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

打印所有內容的一種替代方法是為應該顯示的內容添加一個標記。 這是一個簡單的草圖:

#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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM