定义解析器:Debug 实现与测试
作为解析器部分的最后一步,我们需要编写一些测试。
为此,我们会创建一个数据库,设置输入源文本,运行解析器并检查结果。
不过在这之前,还有一个问题必须先解决:
我们要如何查看像 Expression 这种 interned 类型所代表的值?
DebugWithDb trait
像 Expression 这种 interned 类型本质上只存了一个整数,
所以传统的 Debug trait 并不怎么有用。
要正确打印一个 Expression,
你需要访问 Salsa 数据库,才能知道这个整数实际对应的值是什么。
为了解决这个问题,salsa 提供了 DebugWithDb trait。
它的行为类似普通的 Debug,
但额外接收一个数据库参数。
对实现了这个 trait 的类型,你可以调用 debug 方法。
它会返回一个临时值,
这个临时值实现了普通的 Debug trait,
于是你就可以写出下面这样的代码:
#![allow(unused)] fn main() { eprintln!("Expression = {:?}", expr.debug(db)); }
并得到你真正想看到的输出。
DebugWithDb 会自动为所有 #[input]、#[interned] 和 #[tracked] 结构体派生。
转发到普通 Debug trait
为了保持一致性,
即使对于像 Op 这种普通枚举,
有时也希望它实现 DebugWithDb。
你可以像下面这样做:
#![allow(unused)] fn main() { }
编写单元测试
现在 DebugWithDb 的实现已经准备好了,
我们就可以写一个简单的单元测试辅助函数。
下面的 parse_string 会创建一个数据库、设置源文本,然后调用解析器:
#![allow(unused)] fn main() { /// Create a new database with the given source text and parse the result. /// Returns the statements and the diagnostics generated. #[cfg(test)] fn parse_string(source_text: &str) -> String { use salsa::Database; use crate::db::CalcDatabaseImpl; CalcDatabaseImpl::default().attach(|db| { // Create the source program let source_program = SourceProgram::new(db, source_text.to_string()); // Invoke the parser let statements = parse_statements(db, source_program); // Read out any diagnostics let accumulated = parse_statements::accumulated::<Diagnostic>(db, source_program); // Format the result as a string and return it format!("{:#?}", (statements, accumulated)) }) } }
再配合 expect-test crate,
我们就能写出这样的单元测试:
#![allow(unused)] fn main() { #[test] fn parse_print() { let actual = parse_string("print 1 + 2"); let expected = expect_test::expect![[r#" ( Program { [salsa id]: Id(800), statements: [ Statement { span: Span { [salsa id]: Id(404), start: 0, end: 11, }, data: Print( Expression { span: Span { [salsa id]: Id(403), start: 6, end: 11, }, data: Op( Expression { span: Span { [salsa id]: Id(400), start: 6, end: 7, }, data: Number( 1.0, ), }, Add, Expression { span: Span { [salsa id]: Id(402), start: 10, end: 11, }, data: Number( 2.0, ), }, ), }, ), }, ], }, [], )"#]]; expected.assert_eq(&actual); } }