plotnik-compiler 0.3.2

Compiler for Plotnik query language (parser, analyzer, bytecode emitter)
Documentation
use crate::Query;
use indoc::indoc;

#[test]
fn single_definition() {
    let input = "Expr = (expression)";
    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @"Expr");
}

#[test]
fn multiple_definitions() {
    let input = indoc! {r#"
    Expr = (expression)
    Stmt = (statement)
    Decl = (declaration)
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    Expr
    Stmt
    Decl
    ");
}

#[test]
fn valid_reference() {
    let input = indoc! {r#"
    Expr = (expression)
    Call = (call_expression function: (Expr))
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    Expr
    Call
      Expr
    ");
}

#[test]
fn undefined_reference() {
    let input = "Call = (call_expression function: (Undefined))";

    let res = Query::expect_invalid(input);

    insta::assert_snapshot!(res, @r"
    error: `Undefined` is not defined
      |
    1 | Call = (call_expression function: (Undefined))
      |                                    ^^^^^^^^^
    ");
}

#[test]
fn self_reference() {
    let input = "Expr = [(identifier) (call (Expr))]";

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    Expr
      Expr (cycle)
    ");
}

#[test]
fn mutual_recursion() {
    let input = indoc! {r#"
    A = (foo (B))
    B = (bar (A))
    "#};

    let res = Query::expect_invalid(input);

    insta::assert_snapshot!(res, @r"
    error: infinite recursion: no escape path
      |
    1 | A = (foo (B))
      |           - references B (completing cycle)
    2 | B = (bar (A))
      | -         ^
      | |         |
      | |         references A
      | B is defined here
      |
    help: add a non-recursive branch to terminate: `[Base: ... Rec: (Self)]`
    ");
}

#[test]
fn duplicate_definition() {
    let input = indoc! {r#"
    Expr = (expression)
    Expr = (other)
    "#};

    let res = Query::expect_invalid(input);

    insta::assert_snapshot!(res, @r"
    error: `Expr` is already defined
      |
    2 | Expr = (other)
      | ^^^^
    ");
}

#[test]
fn reference_in_alternation() {
    let input = indoc! {r#"
    Expr = (expression)
    Value = [(Expr) (literal)]
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    Expr
    Value
      Expr
    ");
}

#[test]
fn reference_in_sequence() {
    let input = indoc! {r#"
    Expr = (expression)
    Pair = {(Expr) (Expr)}
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    Expr
    Pair
      Expr
    ");
}

#[test]
fn reference_in_quantifier() {
    let input = indoc! {r#"
    Expr = (expression)
    List = (Expr)*
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    Expr
    List
      Expr
    ");
}

#[test]
fn reference_in_capture() {
    let input = indoc! {r#"
    Expr = (expression)
    Named = (Expr) @e
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    Expr
    Named
      Expr
    ");
}

#[test]
fn entry_point_reference() {
    let input = indoc! {r#"
    Expr = (expression)
    Q = (call function: (Expr))
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    Expr
    Q
      Expr
    ");
}

#[test]
fn entry_point_undefined_reference() {
    let input = "Q = (call function: (Unknown))";

    let res = Query::expect_invalid(input);

    insta::assert_snapshot!(res, @r"
    error: `Unknown` is not defined
      |
    1 | Q = (call function: (Unknown))
      |                      ^^^^^^^
    ");
}

#[test]
fn no_definitions() {
    let input = "Q = (identifier)";
    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @"Q");
}

#[test]
fn nested_references() {
    let input = indoc! {r#"
    A = (a)
    B = (b (A))
    C = (c (B))
    D = (d (C) (A))
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    A
    B
      A
    C
      B
        A
    D
      A
      C
        B
          A
    ");
}

#[test]
fn multiple_undefined() {
    let input = "Q = (foo (X) (Y) (Z))";

    let res = Query::expect_invalid(input);

    insta::assert_snapshot!(res, @r"
    error: `X` is not defined
      |
    1 | Q = (foo (X) (Y) (Z))
      |           ^

    error: `Y` is not defined
      |
    1 | Q = (foo (X) (Y) (Z))
      |               ^

    error: `Z` is not defined
      |
    1 | Q = (foo (X) (Y) (Z))
      |                   ^
    ");
}

#[test]
fn reference_inside_tree_child() {
    let input = indoc! {r#"
        A = (a)
        B = (b (A))
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    A
    B
      A
    ");
}

#[test]
fn reference_inside_capture() {
    let input = indoc! {r#"
        A = (a)
        B = (A)@x
    "#};

    let res = Query::expect_valid_symbols(input);

    insta::assert_snapshot!(res, @r"
    A
    B
      A
    ");
}