utterance 0.1.4

A parser library for creating readable, natural-language-inspired domain-specific languages.
Documentation
use utterance::empty_children;
use utterance::lexer::{Lexer, SpecialChar};
use utterance::lexer::{Number, Punctuation, Symbol};
use utterance::parser::expectation::{NumberKind, StatementKind};
use utterance::parser::tree::{ParseTree, ParseTreeFactory, noop_fn};
use utterance::parser::{CustomParseError, Parser};

macro_rules! assert_parse {
    ($parser:expr, $input:expr) => {{
        $parser.update_input($input);

        match $parser.parse() {
            Ok(_) => {}
            Err(err) => {
                panic!("\nParse failed\nInput: {:?}\nError: {:#?}\n", $input, err);
            }
        }
    }};
}

macro_rules! assert_parse_err {
    ($parser:expr, $input:expr) => {{
        $parser.update_input($input);

        match $parser.parse() {
            Ok(_) => {
                panic!(
                    "\nExpected parse failure but succeeded\nInput: {:?}\n",
                    $input
                );
            }
            Err(err) => {
                println!("\nExpected error\nInput: {:?}\nError: {:#?}\n", $input, err);
            }
        }
    }};
}

macro_rules! end_tree {
    ($factory:expr) => {{
        ParseTree::new(
            $factory.clone(),
            StatementKind::NewLineOrEnd,
            empty_children!(),
            noop_fn,
        )
    }};
}

macro_rules! statement_tree {
    ($factory:expr, $statement:expr) => {{
        ParseTree::new(
            $factory.clone(),
            $statement,
            vec![end_tree!($factory)],
            noop_fn,
        )
    }};
}

macro_rules! parser_for {
    ($tree:expr) => {{ Parser::<'static, NoopContext, CustomParseError, 1, 1>::new(Lexer::<1>::new(""), $tree) }};
}

#[derive(Clone, Default, Debug)]
struct NoopContext;

#[test]
fn test_noop_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let consume = statement_tree!(factory.clone(), StatementKind::Word);
    let tree = ParseTree::new(factory, StatementKind::Noop, vec![consume], noop_fn);

    let mut parser = parser_for!(tree);
    assert_parse!(parser, "a");
}

#[test]
fn test_capitalize_word_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let consume = statement_tree!(factory.clone(), StatementKind::Word);
    let tree = ParseTree::new(
        factory,
        StatementKind::CapitalizeWord,
        vec![consume],
        noop_fn,
    );

    let mut parser = parser_for!(tree);
    for input in ["Hello", "World", "Rust"] {
        assert_parse!(parser, input);
    }
}

#[test]
fn test_word_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let tree = statement_tree!(factory, StatementKind::Word);

    let mut parser = parser_for!(tree);
    for input in ["hello", "abc123", "rust"] {
        assert_parse!(parser, input);
    }
}

#[test]
fn test_exact_word_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let tree = statement_tree!(factory, StatementKind::ExactWord("hello"));

    let mut parser = parser_for!(tree);
    assert_parse!(parser, "hello");
    assert_parse_err!(parser, "world");
}

#[test]
fn test_one_of_exact_words_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let tree = statement_tree!(
        factory,
        StatementKind::OneOfExactWords(&["yes", "no", "maybe",])
    );

    let mut parser = parser_for!(tree);
    for input in ["yes", "no", "maybe"] {
        assert_parse!(parser, input);
    }

    assert_parse_err!(parser, "unknown");
}

#[test]
fn test_symbol_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let tree = statement_tree!(
        factory,
        StatementKind::Symbol(Symbol::Punctuation(Punctuation::Comma))
    );

    let mut parser = parser_for!(tree);
    assert_parse!(parser, ",");
    assert_parse_err!(parser, ".");
}

#[test]
fn test_double_symbol_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let tree = statement_tree!(
        factory,
        StatementKind::DoubleSymbol(Symbol::Punctuation(Punctuation::Comma))
    );

    let mut parser = parser_for!(tree);
    assert_parse!(parser, ",,");
    assert_parse_err!(parser, ",");
}

#[test]
fn test_new_line_or_end_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let tree = ParseTree::new(
        factory,
        StatementKind::NewLineOrEnd,
        empty_children!(),
        noop_fn,
    );

    let mut parser = parser_for!(tree);
    assert_parse!(parser, "");
    assert_parse!(parser, "\n");
}

#[test]
fn test_number_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();
    let tree = statement_tree!(factory, StatementKind::Number(NumberKind::Float));

    let mut parser = parser_for!(tree);
    for input in ["1", "1.5", "-5", "-3.14"] {
        assert_parse!(parser, input);
    }
}

#[test]
fn test_exact_number_statement() {
    let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

    let tree = statement_tree!(factory, StatementKind::ExactNumber(Number::Integer(42)));

    let mut parser = parser_for!(tree);
    assert_parse!(parser, "42");
    assert_parse_err!(parser, "43");
}

#[test]
fn test_comment_statement() {
    let comments = [
        StatementKind::DoubleSymbol(Symbol::Punctuation(Punctuation::SemiColon)),
        StatementKind::Symbol(Symbol::Punctuation(Punctuation::SemiColon)),
        StatementKind::DoubleSymbol(Symbol::Punctuation(Punctuation::Comma)),
        StatementKind::Symbol(Symbol::Punctuation(Punctuation::Comma)),
        StatementKind::DoubleSymbol(Symbol::SpecialChar(SpecialChar::Slash)),
        StatementKind::Symbol(Symbol::SpecialChar(SpecialChar::Slash)),
    ];

    for comment_type in comments {
        let factory = ParseTreeFactory::<NoopContext, CustomParseError>::new();

        let (inputs, invalid_inputs) = match comment_type {
            StatementKind::DoubleSymbol(symbol) => (
                vec![
                    format!("{}{} hello", symbol.as_str(), symbol.as_str()),
                    format!("{}{}", symbol.as_str(), symbol.as_str()),
                ],
                vec![format!("{}{}hello", symbol.as_str(), symbol.as_str())],
            ),

            StatementKind::Symbol(symbol) => (
                vec![
                    format!("{} hello", symbol.as_str()),
                    symbol.as_str().to_string(),
                ],
                vec![format!("{}hello", symbol.as_str())],
            ),

            _ => unreachable!(),
        };

        let tree = statement_tree!(factory.clone(), StatementKind::Comment);

        let mut parser = Parser::<NoopContext, CustomParseError, 1, 1>::new(
            Lexer::<1>::new("").set_comment_symbol(comment_type),
            tree,
        );

        for input in &inputs {
            assert_parse!(parser, input);
        }

        for input in &invalid_inputs {
            assert_parse_err!(parser, input);
        }
    }
}