Defining the parser: debug impls and testing

As the final part of the parser, we need to write some tests. To do so, we will create a database, set the input source text, run the parser, and check the result. Before we can do that, though, we have to address one question: how do we inspect Salsa structs whose fields live in the database?

Generated Debug implementations

The debug option on Salsa struct attributes generates an ordinary Debug implementation. Tracked functions attach the database automatically; other code can use salsa::Database::attach to include field values:


#![allow(unused)]
fn main() {
use salsa::Database as _;

db.attach(|db| {
    let function = FunctionId::new(db, "area_circle".to_string());
    eprintln!("Function = {function:?}");
});
}

Without an attached database, the generated formatter displays only the Salsa ID.

Debug for ordinary Rust types

Types such as Op that are not Salsa structs can use the ordinary Debug derive:


#![allow(unused)]
fn main() {
#[derive(Debug)]
pub enum Op {
    Add, Subtract, Multiply, Divide,
}
}

Writing the unit test

Now that we have our Debug implementations in place, we can write a simple unit test harness. The parse_string function below creates a database, sets the source text, and then invokes the parser:


#![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))
    })
}
}

Combined with the expect-test crate, we can then write unit tests like this one:


#![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);
}
}