panfix 0.6.2

Panfix parsing: linear time parsing of multifix operators.
Documentation
use panfix::{pattern, Grammar, GrammarError, Parser, Source};

#[track_caller]
fn assert_parse(parser: &Parser<&'static str>, src: &str, expected: &str) {
    let source = Source::new("testcase", src.to_owned());
    let tree = parser.parse(&source).unwrap();
    assert_eq!(format!("{}", tree.visitor()), expected);
}

#[track_caller]
fn assert_error(parser: &Parser<&'static str>, src: &str, expected: &str) {
    let source = Source::new("testcase", src.to_owned());
    let err = parser.parse(&source).unwrap_err();
    assert_eq!(
        format!("{}", err.display_with_color_override(false)),
        expected
    );
}

#[test]
fn test_lexing_error() {
    fn make_parser() -> Result<Parser<&'static str>, GrammarError> {
        let mut grammar = Grammar::new_with_unicode_whitespace()?;
        grammar.regex("num", "[0-9]+")?;
        grammar.left_assoc();
        grammar.op("plus", pattern!(_ "+" _))?;
        grammar.finish()
    }
    let parser = make_parser().unwrap();

    assert_error(
        &parser,
        "%!",
        r#"Parse Error: Unrecognized token.
 --> testcase:1:1
  |
1 |%!
  |^^ unrecognized token
"#,
    );

    assert_error(
        &parser,
        "5\n + %! + 8",
        r#"Parse Error: Unrecognized token.
 --> testcase:2:4
  |
2 | + %! + 8
  |   ^^ unrecognized token
"#,
    );
}

#[test]
fn test_parsing_assoc() {
    fn make_parser() -> Result<Parser<&'static str>, GrammarError> {
        let mut grammar = Grammar::new_with_unicode_whitespace()?;
        grammar.regex("num", "[0-9]+")?;
        grammar.right_assoc();
        grammar.op("exp", pattern!(_ "^" _))?;
        grammar.left_assoc();
        grammar.op("plus", pattern!(_ "+" _))?;
        grammar.finish()
    }
    let parser = make_parser().unwrap();

    assert_parse(&parser, "1^2^3", "(exp 1 (exp 2 3))");
    assert_parse(&parser, "1 + 2 + 3", "(plus (plus 1 2) 3)");
}

#[test]
fn test_parsing_minus() {
    fn make_parser() -> Result<Parser<&'static str>, GrammarError> {
        let mut grammar = Grammar::new_with_unicode_whitespace()?;
        grammar.regex("num", "[0-9]+")?;
        grammar.left_assoc();
        grammar.op("neg", pattern!("-" _))?;
        grammar.left_assoc();
        grammar.op("minus", pattern!(_ "-" _))?;
        grammar.finish()
    }
    let parser = make_parser().unwrap();

    assert_parse(&parser, "- 2", "(neg 2)");
    assert_parse(&parser, "--2", "(neg (neg 2))");
    assert_parse(&parser, "1 - 2 - 3", "(minus (minus 1 2) 3)");
    assert_parse(&parser, "1 - -2 - 3", "(minus (minus 1 (neg 2)) 3)");
}

#[test]
fn test_parsing_blank() {
    fn make_parser() -> Result<Parser<&'static str>, GrammarError> {
        let mut grammar = Grammar::new_with_unicode_whitespace()?;
        grammar.regex("num", "[0-9]+")?;
        grammar.op("parens", pattern!("(" ")"))?;
        grammar.left_assoc();
        grammar.op("neg", pattern!("-" _))?;
        grammar.left_assoc();
        grammar.op("minus", pattern!(_ "-" _))?;
        grammar.op("plus", pattern!(_ "+" _))?;
        grammar.finish()
    }
    let parser = make_parser().unwrap();

    assert_parse(&parser, "+2", "(plus _ 2)");
    assert_parse(&parser, "+", "(plus _ _)");
    assert_parse(&parser, "3++", "(plus (plus 3 _) _)");
    assert_parse(&parser, "-", "(neg _)");
    assert_parse(&parser, "+--+", "(plus (plus _ (neg (neg _))) _)");
    assert_parse(&parser, "+--+", "(plus (plus _ (neg (neg _))) _)");
    assert_parse(&parser, "()", "(parens _)");
}

#[test]
fn test_parsing_juxtapose() {
    fn make_parser_1() -> Result<Parser<&'static str>, GrammarError> {
        let mut grammar = Grammar::new_with_unicode_whitespace()?;
        grammar.regex("num", "[0-9]+")?;
        grammar.left_assoc();
        grammar.op("plus", pattern!(_ "+" _))?;
        grammar.finish()
    }
    let parser = make_parser_1().unwrap();

    assert_parse(&parser, "1 2", "(_ 1 2)");
    assert_parse(&parser, "1 2 3", "(_ (_ 1 2) 3)");
    assert_parse(&parser, "1 2 + 3 4", "(plus (_ 1 2) (_ 3 4))");

    fn make_parser_2() -> Result<Parser<&'static str>, GrammarError> {
        let mut grammar = Grammar::new_with_unicode_whitespace()?;
        grammar.regex("num", "[0-9]+")?;
        grammar.left_assoc();
        grammar.op("plus", pattern!(_ "+" _))?;
        grammar.right_assoc();
        grammar.juxtapose()?;
        grammar.finish()
    }
    let parser = make_parser_2().unwrap();

    assert_parse(&parser, "1 2", "(_ 1 2)");
    assert_parse(&parser, "1 2 3", "(_ 1 (_ 2 3))");
    assert_parse(&parser, "1 2 + 3 4", "(_ 1 (_ (plus 2 3) 4))");
}