petr-parse 0.1.0

parser for the petr language
Documentation
use expect_test::expect;
use petr_utils::{render_error, PrettyPrint};

use super::Parser;

fn check<T: Into<String>>(
    sources: Vec<T>,
    expected: expect_test::Expect,
) {
    let parser = Parser::new(sources.into_iter().map(|s| ("test", s)));
    let (ast, errs, interner, source_map) = parser.into_result();

    let pretty_printed_ast = ast.pretty_print(&interner, 0);
    let errs = errs
        .into_iter()
        .map(|err| format!("{:?}", render_error(&source_map, err)))
        .collect::<Vec<_>>()
        .join("\n");
    let errors_str = if errs.is_empty() {
        String::new()
    } else {
        format!("\n\nErrors\n____\n{errs}")
    };

    expected.assert_eq(&format!("AST\n____\n{pretty_printed_ast}\n{errors_str}"));
}

#[test]
fn prefix_operator_expression() {
    check(
        vec!["fn addToFive() returns 'Integer + 4 1"],
        expect![[r#"
            AST
            ____
            module test =
            Func addToFive() -> 'Integer +(4 1)

        "#]],
    )
}

#[test]
fn parse_parameters() {
    check(
        vec!["fn addTwoNums(a ∈ 'Integer, b ∈ 'Integer) returns 'Integer + a b"],
        expect![[r#"
            AST
            ____
            module test =
            Func addTwoNums(
              a ∈ 'Integer,
              b ∈ 'Integer
            ) -> 'Integer +(var(a) var(b))

        "#]],
    )
}

#[test]
fn parse_parameters_in_keyword_identical_to_symbol() {
    let parser_one = Parser::new(vec![("test", "fn addTwoNums(a ∈ 'Integer, b ∈ 'Integer) returns 'Integer + a b")]);
    let parser_two = Parser::new(vec![("test", "fn addTwoNums(a in 'Integer, b in 'Integer) returns 'Integer + a b")]);
    let (ast_one, errs_one, interner_one, _) = parser_one.into_result();

    let (ast_two, errs_two, interner_two, _) = parser_two.into_result();

    let pretty_one = ast_one.pretty_print(&interner_one, 0);
    let pretty_two = ast_two.pretty_print(&interner_two, 0);

    assert_eq!(pretty_one, pretty_two);
    assert_eq!(errs_one, errs_two)
}

#[test]
fn commented_function() {
    check(
        vec![r#"{- this is a comment -} fn addTwoNums(a in 'A, b in 'B) returns 'B + a b    "#],
        expect![[r#"
            AST
            ____
            module test =
            {- this is a comment -}
            Func addTwoNums(
              a ∈ 'A,
              b ∈ 'B
            ) -> 'B +(var(a) var(b))

        "#]],
    )
}

#[test]
fn multi_commented_function() {
    check(
        vec![r#" {- comment one -} {- comment two -} fn addTwoNums(a in 'A, b in 'B) returns 'B + a b    "#],
        expect![[r#"
            AST
            ____
            module test =
            {- comment one -}
            {- comment two -}
            Func addTwoNums(
              a ∈ 'A,
              b ∈ 'B
            ) -> 'B +(var(a) var(b))

        "#]],
    )
}

#[test]
fn list_expr() {
    check(
        vec!["fn list_to_three() returns 'intlist [1, 2, 3]"],
        expect![[r#"
            AST
            ____
            module test =
            Func list_to_three() -> 'intlist [1, 2, 3]

        "#]],
    )
}

#[test]
fn list_with_nested_exprs() {
    check(
        vec!["fn list_to_three() returns 'intlist [+ 1 2 , 2, + 1 + 2 3]"],
        expect![[r#"
            AST
            ____
            module test =
            Func list_to_three() -> 'intlist [+(1 2), 2, +(1 +(2 3))]

        "#]],
    )
}

#[test]
fn nested_list() {
    check(
        vec!["fn list_to_three() returns 'intlist [[1, 2], [3, 4, + 1 2]]"],
        expect![[r#"
            AST
            ____
            module test =
            Func list_to_three() -> 'intlist [[1, 2], [3, 4, +(1 2)]]

        "#]],
    )
}

#[test]
fn fn_call() {
    check(
        vec!["fn makes_function_call() returns 'unit ~fn_call a, b, c"],
        expect![[r#"
            AST
            ____
            module test =
            Func makes_function_call() -> 'unit call fn_call(var(a), var(b), var(c))

        "#]],
    )
}

#[test]
fn let_bindings() {
    check(
        vec![
            "fn makes_function_call(c in 'int) returns 'int
                        let a = 1;
                            b = 20
                        ~fn_call a, b, c

            fn fn_call(a in 'int, b in 'int, c in 'int) returns 'int + a + b c
                     ",
        ],
        expect![[r#"
            AST
            ____
            module test =
            Func makes_function_call(
              c ∈ 'int
            ) -> 'int 
              let a = 1,
                  b = 20
              call fn_call(var(a), var(b), var(c))


            Func fn_call(
              a ∈ 'int,
              b ∈ 'int,
              c ∈ 'int
            ) -> 'int +(var(a) +(var(b) var(c)))

        "#]],
    )
}

#[test]
fn let_bindings_trailing_comma() {
    check(
        vec![
            "fn makes_function_call(c in 'int) returns 'int
                        let a = 1;
                            b = 20;
                        ~fn_call a, b, c

            fn fn_call(a in 'int, b in 'int, c in 'int) returns 'int + a + b c
                     ",
        ],
        expect![[r#"
            AST
            ____
            module test =
            Func makes_function_call(
              c ∈ 'int
            ) -> 'int 
              let a = 1,
                  b = 20
              call fn_call(var(a), var(b), var(c))


            Func fn_call(
              a ∈ 'int,
              b ∈ 'int,
              c ∈ 'int
            ) -> 'int +(var(a) +(var(b) var(c)))

        "#]],
    )
}

#[test]
fn imports_and_exports() {
    check(
        vec![
            "import moduleA.itemA

             fn someFunction(a in 'int) returns 'int + a 1

             import something_else.foo as bar
            ",
        ],
        expect![[r#"
            AST
            ____
            module test =
            import moduleA.itemA
            Func someFunction(
              a ∈ 'int
            ) -> 'int +(var(a) 1)
            import something_else.foo as bar

        "#]],
    )
}