sise 0.8.0

SISE (Simple S-expression) parser, serializer and utilities library.
Documentation
use crate::{ParseError, ParsedItem, Parser};

struct ParserPassTest<'a> {
    src_data: &'a str,
    expected_items: &'a [ParsedItem<'a>],
}

impl<'a> ParserPassTest<'a> {
    #[track_caller]
    fn run(&self) {
        let mut parser = Parser::new(self.src_data);
        for parsed_item in self.expected_items.iter() {
            assert_eq!(parser.next_item().unwrap(), *parsed_item);
        }
        parser.finish().unwrap();
    }
}

struct ParserFailTest<'a> {
    src_data: &'a str,
    expected_items: &'a [ParsedItem<'a>],
    error_at_finish: bool,
    expected_error: ParseError,
}

impl<'a> ParserFailTest<'a> {
    #[track_caller]
    fn run(&self) {
        let mut parser = Parser::new(self.src_data);
        for parsed_item in self.expected_items.iter() {
            assert_eq!(parser.next_item().unwrap(), *parsed_item);
        }
        if self.error_at_finish {
            assert_eq!(parser.finish().unwrap_err(), self.expected_error);
        } else {
            assert_eq!(parser.next_item().unwrap_err(), self.expected_error);
        }
    }
}

#[test]
fn test_empty_list() {
    ParserPassTest {
        src_data: "()",
        expected_items: &[ParsedItem::ListStart(0), ParsedItem::ListEnd(1)],
    }
    .run();
}

#[test]
fn test_single_atom() {
    ParserPassTest {
        src_data: "atom",
        expected_items: &[ParsedItem::Atom("atom", 0)],
    }
    .run();
}

#[test]
fn test_simple_list_1() {
    ParserPassTest {
        src_data: "(atom-1)",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::Atom("atom-1", 1),
            ParsedItem::ListEnd(7),
        ],
    }
    .run();
}

#[test]
fn test_simple_list_2() {
    ParserPassTest {
        src_data: "(atom-1 atom-2)",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::Atom("atom-1", 1),
            ParsedItem::Atom("atom-2", 8),
            ParsedItem::ListEnd(14),
        ],
    }
    .run();
}

#[test]
fn test_nested_list_1() {
    ParserPassTest {
        src_data: "(())",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::ListStart(1),
            ParsedItem::ListEnd(2),
            ParsedItem::ListEnd(3),
        ],
    }
    .run();
}

#[test]
fn test_nested_list_2() {
    ParserPassTest {
        src_data: "(() ())",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::ListStart(1),
            ParsedItem::ListEnd(2),
            ParsedItem::ListStart(4),
            ParsedItem::ListEnd(5),
            ParsedItem::ListEnd(6),
        ],
    }
    .run();
}

#[test]
fn test_nested_list_3() {
    ParserPassTest {
        src_data: "((atom-1) (atom-2 atom-3))",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::ListStart(1),
            ParsedItem::Atom("atom-1", 2),
            ParsedItem::ListEnd(8),
            ParsedItem::ListStart(10),
            ParsedItem::Atom("atom-2", 11),
            ParsedItem::Atom("atom-3", 18),
            ParsedItem::ListEnd(24),
            ParsedItem::ListEnd(25),
        ],
    }
    .run();
}

#[test]
fn test_nested_lists() {
    ParserPassTest {
        src_data: "(((((((((())))))))))",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::ListStart(1),
            ParsedItem::ListStart(2),
            ParsedItem::ListStart(3),
            ParsedItem::ListStart(4),
            ParsedItem::ListStart(5),
            ParsedItem::ListStart(6),
            ParsedItem::ListStart(7),
            ParsedItem::ListStart(8),
            ParsedItem::ListStart(9),
            ParsedItem::ListEnd(10),
            ParsedItem::ListEnd(11),
            ParsedItem::ListEnd(12),
            ParsedItem::ListEnd(13),
            ParsedItem::ListEnd(14),
            ParsedItem::ListEnd(15),
            ParsedItem::ListEnd(16),
            ParsedItem::ListEnd(17),
            ParsedItem::ListEnd(18),
            ParsedItem::ListEnd(19),
        ],
    }
    .run();
}

#[test]
fn test_mixed() {
    ParserPassTest {
        src_data: "(atom-1 (atom-2) (atom-3 (atom-4) atom-5) atom-6)",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::Atom("atom-1", 1),
            ParsedItem::ListStart(8),
            ParsedItem::Atom("atom-2", 9),
            ParsedItem::ListEnd(15),
            ParsedItem::ListStart(17),
            ParsedItem::Atom("atom-3", 18),
            ParsedItem::ListStart(25),
            ParsedItem::Atom("atom-4", 26),
            ParsedItem::ListEnd(32),
            ParsedItem::Atom("atom-5", 34),
            ParsedItem::ListEnd(40),
            ParsedItem::Atom("atom-6", 42),
            ParsedItem::ListEnd(48),
        ],
    }
    .run();
}

#[test]
fn test_atom_chars() {
    ParserPassTest {
        src_data: "!#$%&*+-./:<=>?@_~",
        expected_items: &[ParsedItem::Atom("!#$%&*+-./:<=>?@_~", 0)],
    }
    .run();
}

#[test]
fn test_string_1() {
    ParserPassTest {
        src_data: "\"atom-1\"",
        expected_items: &[ParsedItem::Atom("\"atom-1\"", 0)],
    }
    .run();
}

#[test]
fn test_string_2() {
    ParserPassTest {
        src_data: "prefix\"atom-1\"suffix",
        expected_items: &[ParsedItem::Atom("prefix\"atom-1\"suffix", 0)],
    }
    .run();
}

#[test]
fn test_string_3() {
    ParserPassTest {
        src_data: "\" \\\\ \\\" \"",
        expected_items: &[ParsedItem::Atom("\" \\\\ \\\" \"", 0)],
    }
    .run();
}

#[test]
fn test_multiline() {
    ParserPassTest {
        src_data: "\n(1 2\r3\r\n4 5)\n",
        expected_items: &[
            ParsedItem::ListStart(1),
            ParsedItem::Atom("1", 2),
            ParsedItem::Atom("2", 4),
            ParsedItem::Atom("3", 6),
            ParsedItem::Atom("4", 9),
            ParsedItem::Atom("5", 11),
            ParsedItem::ListEnd(12),
        ],
    }
    .run();
}

#[test]
fn test_comment_1_lf() {
    ParserPassTest {
        src_data: "; comment\n(1 2)",
        expected_items: &[
            ParsedItem::ListStart(10),
            ParsedItem::Atom("1", 11),
            ParsedItem::Atom("2", 13),
            ParsedItem::ListEnd(14),
        ],
    }
    .run();
}

#[test]
fn test_comment_1_cr() {
    ParserPassTest {
        src_data: "; comment\r(1 2)",
        expected_items: &[
            ParsedItem::ListStart(10),
            ParsedItem::Atom("1", 11),
            ParsedItem::Atom("2", 13),
            ParsedItem::ListEnd(14),
        ],
    }
    .run();
}

#[test]
fn test_comment_2() {
    ParserPassTest {
        src_data: "(1 2); comment",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::Atom("1", 1),
            ParsedItem::Atom("2", 3),
            ParsedItem::ListEnd(4),
        ],
    }
    .run();
}

#[test]
fn test_comment_3() {
    ParserPassTest {
        src_data: "(1; comment\n2)",
        expected_items: &[
            ParsedItem::ListStart(0),
            ParsedItem::Atom("1", 1),
            ParsedItem::Atom("2", 12),
            ParsedItem::ListEnd(13),
        ],
    }
    .run();
}

#[test]
fn test_fail_empty() {
    ParserFailTest {
        src_data: "",
        expected_items: &[],
        error_at_finish: false,
        expected_error: ParseError::UnexpectedEof { pos: 0 },
    }
    .run();
}

#[test]
fn test_fail_expected_eof() {
    ParserFailTest {
        src_data: "() ()",
        expected_items: &[ParsedItem::ListStart(0), ParsedItem::ListEnd(1)],
        error_at_finish: true,
        expected_error: ParseError::ExpectedEof { pos: 3 },
    }
    .run();
}

#[test]
fn test_fail_unfinished_string() {
    ParserFailTest {
        src_data: "\"atom-1",
        expected_items: &[],
        error_at_finish: false,
        expected_error: ParseError::UnfinishedString { pos: 7 },
    }
    .run();
}

#[test]
fn test_fail_unclosed_list() {
    ParserFailTest {
        src_data: "(atom-1",
        expected_items: &[ParsedItem::ListStart(0), ParsedItem::Atom("atom-1", 1)],
        error_at_finish: false,
        expected_error: ParseError::UnexpectedEof { pos: 7 },
    }
    .run();
}

#[test]
fn test_fail_unexpected_closing() {
    ParserFailTest {
        src_data: ")",
        expected_items: &[],
        error_at_finish: false,
        expected_error: ParseError::UnexpectedRightParen { pos: 0 },
    }
    .run();
}

#[test]
fn test_fail_unclosed_list_with_comment() {
    ParserFailTest {
        src_data: "(atom-1 ; comment)",
        expected_items: &[ParsedItem::ListStart(0), ParsedItem::Atom("atom-1", 1)],
        error_at_finish: false,
        expected_error: ParseError::UnexpectedEof { pos: 18 },
    }
    .run();
}

#[test]
fn test_fail_illegal_chr() {
    ParserFailTest {
        src_data: "\0",
        expected_items: &[],
        error_at_finish: false,
        expected_error: ParseError::IllegalChr { pos: 0, chr: '\0' },
    }
    .run();
}

#[test]
fn test_fail_illegal_chr_in_string() {
    ParserFailTest {
        src_data: "\"\0",
        expected_items: &[],
        error_at_finish: false,
        expected_error: ParseError::IllegalChrInString { pos: 1, chr: '\0' },
    }
    .run();
}

#[test]
fn test_fail_illegal_chr_in_comment() {
    ParserFailTest {
        src_data: "() ; \0",
        expected_items: &[ParsedItem::ListStart(0), ParsedItem::ListEnd(1)],
        error_at_finish: true,
        expected_error: ParseError::IllegalChrInComment { pos: 5, chr: '\0' },
    }
    .run();
}