swiftlet 0.2.0

swiftlet is a high-performance text-parsing library for Rust, inspired by Python’s Lark.
Documentation
use std::sync::Arc;
use swiftlet::error::{ParseError, SwiftletError};
use swiftlet::grammar::Algorithm;
use swiftlet::{ParserConfig, Swiftlet};

fn clr_option(start: &str) -> Arc<ParserConfig> {
    Arc::new(ParserConfig {
        algorithm: Algorithm::CLR,
        start: start.to_string(),
        ..Default::default()
    })
}

#[test]
fn clr_returns_conflict_for_ambiguous_grammar() {
    let grammar = r#"
    start: expr
    expr: expr "+" expr
        | INT
    %import (WS, INT)
    %ignore WS
    "#;

    let parser = Swiftlet::from_str(grammar)
        .expect("ambiguous grammar should still build")
        .parser(clr_option("start"));

    let err = parser
        .parse("1 + 2 + 3")
        .expect_err("CLR parse should report a conflict");

    assert!(matches!(
        err,
        SwiftletError::Parse(ParseError::Conflict { .. })
    ));
}

#[test]
fn clr_returns_rule_not_found_for_unknown_start_token() {
    let grammar = r#"
    start: expr
    expr: INT
    %import (WS, INT)
    %ignore WS
    "#;

    let parser = Swiftlet::from_str(grammar)
        .expect("parser should build")
        .parser(clr_option("start"));

    let err = parser
        .parse("abc")
        .expect_err("non-numeric input should be rejected");

    assert!(matches!(
        err,
        SwiftletError::Parse(ParseError::RuleNotFound(word)) if word.is_empty()
    ));
}

#[test]
fn clr_returns_rule_not_found_for_incomplete_expression() {
    let grammar = r#"
    start: expr
    expr: INT "+" INT
    %import (WS, INT)
    %ignore WS
    "#;

    let parser = Swiftlet::from_str(grammar)
        .expect("parser should build")
        .parser(clr_option("start"));

    let err = parser
        .parse("42 +")
        .expect_err("missing right-hand token should be rejected");

    assert!(matches!(
        err,
        SwiftletError::Parse(ParseError::RuleNotFound(word)) if word.is_empty()
    ));
}

#[test]
fn new_returns_file_read_error_for_missing_grammar_file() {
    let missing_path = std::env::temp_dir().join("swiftlet_missing_grammar_file.lark");
    let path = missing_path.to_string_lossy().into_owned();

    let err = match Swiftlet::from_file(path.as_str()) {
        Ok(_) => panic!("missing grammar file should return an error"),
        Err(err) => err,
    };

    assert!(matches!(
        err,
        SwiftletError::GrammarFileReadError { path: err_path, .. } if err_path == path
    ));
}