定义解析器:debug 与测试

作为解析器的最后一部分,我们需要编写一些测试。为此,我们将创建一个数据库,设置输入源文本,运行解析器并检查结果。

然而,在我们能够做到这一点之前,必须先解决一个问题:如何查看像 Expression 这样的被 interned 的类型的值呢?

DebugWithDb trait

因为像 Expression 这样的 interned 类型只存储一个整数,所以传统的 Debug trait 并不是很有用。

要正确打印 Expression,你需要访问 Salsa 数据库以找出它的值。

为了解决这个问题,Salsa 提供了一个 DebugWithDb trait,它的行为类似于常规的 Debug,但以数据库作为参数。

对于实现该 trait 的类型,可以调用 debug 方法,该方法将返回一个实现了普通 Debug trait 的值,从而你可以编写如下内容:

eprintln!("Expression = {:?}", expr.debug(db));

然后得到你想要的结果。

对于所有 #[input]#[interned]#[tracked] 结构体,都会自动派生 DeugWithDb trait。

转发到普通的 Debug trait

为了保持一致性,有时实现 DebugWithDb 是很有用的,即使对于 Op 这样只是普通枚举的类型也是如此。你可以这样做:


编写单元测试

我们既然已经准备好了 DebugWithDb 实现,就可以编写一个简单的单元测试工具了。

下面的 parse_string 函数创建一个数据库,设置源文本,然后调用了解析器:

/// 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::debug::DebugWithDb;

    // Create the database
    let db = crate::db::Database::default();

    // 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::<Diagnostics>(&db, source_program);

    // Format the result as a string and return it
    format!("{:#?}", (statements.debug_all(&db), accumulated))
}

结合 expect-test crate,我们就可以编写这样的单元测试:

#[test]
fn parse_print() {
    let actual = parse_string("print 1 + 2");
    let expected = expect_test::expect![[r#"
        (
            Program {
                [salsa id]: 0,
                statements: [
                    Statement {
                        span: Span(
                            Id {
                                value: 5,
                            },
                        ),
                        data: Print(
                            Expression {
                                span: Span(
                                    Id {
                                        value: 4,
                                    },
                                ),
                                data: Op(
                                    Expression {
                                        span: Span(
                                            Id {
                                                value: 1,
                                            },
                                        ),
                                        data: Number(
                                            OrderedFloat(
                                                1.0,
                                            ),
                                        ),
                                    },
                                    Add,
                                    Expression {
                                        span: Span(
                                            Id {
                                                value: 3,
                                            },
                                        ),
                                        data: Number(
                                            OrderedFloat(
                                                2.0,
                                            ),
                                        ),
                                    },
                                ),
                            },
                        ),
                    },
                ],
            },
            [],
        )"#]];
    expected.assert_eq(&actual);
}