0

Racket マクロ システムを学習する演習として、 C++ の catch フレームワークに基づいて単体テスト フレームワークを実装してきました。そのフレームワークの機能の 1 つは、次のようなチェックを作成する場合です。

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

CHECK_EQUALS や CHECK_GREATER などのマクロを使用する必要がある他のテスト フレームワークとは異なり、使用されているマクロが完全に汎用的であっても、チェックに違反すると、エラー メッセージに x と y の値が出力されます。これはハッカーによって可能です。式テンプレートと演算子のオーバーロードが含まれます。

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 に答える