作为学习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 回答
这是让你入门的东西 .
输出:
打印所有内容的替代方法是为应显示的内容添加标记 . 这是一个粗略简单的草图:
这导致了这个输出: