2016-09-28 96 views
0

作爲學習Racket宏觀系統的練習,我一直在實現一個基於C++ catch framework的單元測試框架。其中的該框架的特點是,如果我寫這樣的檢查:在球拍中記錄評估步驟和中間值的宏?

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

當檢查違反錯誤信息會打印出x和y的值,即使使用的宏是完全通用的,而不像其他測試框架那樣需要使用像CHECK_EQUALS,CHECK_GREATER等宏。這可能通過一些涉及表達式模板和運算符重載的hackery來實現。

在我看來,在球拍中你應該能夠做得更好。在C++版本的宏無法看到裏面的子表達式,所以如果你寫的東西,如:

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

當檢查侵犯您只找出等號的左邊和右邊的值,而不是x,y或g(y)的值。在球拍中,我認爲應該可以遞歸到子表達式中並打印顯示評估每一步的樹。

問題是我不知道該怎麼做到這一點的最好辦法是:

  • 我已經得到相當熟悉語法解析,但這似乎超出了自己的能力。
  • 我閱讀了關於自定義#%應用程序,這幾乎看起來像我想要的,但如果例如f是一個宏,我不想打印每個擴展表達式的評估,只是評估當用戶調用檢查宏時可見的表達式。也不確定我是否可以在不定義語言的情況下使用它。
  • 我可以使用syntax-parameterize來劫持基本操作符的含義,但是這對g(y)等函數調用沒有幫助。
  • 我可以使用語法 - >數據並手動走AST,自己調用子表達式的eval。這似乎很棘手。
  • 跟蹤庫幾乎看起來像它做我想要的,但你必須先給它一個函數列表,它似乎不給你任何控制輸出的地方(我只想打印任何如果檢查失敗,不成功,所以我需要在執行過程中將中間值保存到一邊)。

什麼是最好的或至少是慣用的方式來實現這個?

+0

將誰投票決定關閉心靈解釋爲什麼?這個問題非常具體,我不確定它如何被解釋爲「廣泛」 –

回答

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