#[cfg(test)]
mod tests {
use super::*;
use crate::parser::combinators::{
primitive,
combinator::ParserCombinator,
scheme,
types::*,
};
use primitive::{char, tag, digit, satisfy};
use scheme::{scheme_number, scheme_string, scheme_character, scheme_symbol, scheme_sexp};
use combinator::{whitespace0, whitespace1};
#[test]
fn test_char_parsing() {
let parser = char('a');
match parser.parse("abc") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "bc");
assert_eq!(parsed, 'a');
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("xyz") {
Ok(_) => panic!("Expected parse error for wrong character"),
Err(e) => {
assert_eq!(e.actual, "x".to_string());
assert!(e.expected.contains(&"a".to_string()));
}
}
match parser.parse("") {
Ok(_) => panic!("Expected parse error for empty input"),
Err(e) => {
assert_eq!(e.actual, "EOF".to_string());
assert!(e.expected.contains(&"a".to_string()));
}
}
}
#[test]
fn test_tag_parsing() {
let parser = tag("hello");
match parser.parse("hello world") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " world");
assert_eq!(parsed, "hello");
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("help") {
Ok(_) => panic!("Expected parse error for partial match"),
Err(e) => {
assert_eq!(e.actual, "help".to_string());
assert!(e.expected.contains(&"hello".to_string()));
}
}
}
#[test]
fn test_digit_parsing() {
let parser = digit();
match parser.parse("5abc") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "abc");
assert_eq!(parsed, '5');
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("abc") {
Ok(_) => panic!("Expected parse error for non-digit"),
Err(e) => {
assert_eq!(e.actual, "a".to_string());
assert!(e.expected.contains(&"digit".to_string()));
}
}
}
#[test]
fn test_satisfy_parsing() {
let parser = satisfy(|c: &char| c.is_alphabetic());
match parser.parse("abc123") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "bc123");
assert_eq!(parsed, 'a');
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("123abc") {
Ok(_) => panic!("Expected parse error when predicate fails"),
Err(e) => {
assert_eq!(e.actual, "1".to_string());
assert!(e.message.contains("predicate"));
}
}
}
#[test]
fn test_map_combinator() {
let parser = digit().map(|c| c.to_digit(10).unwrap() as i32);
match parser.parse("7xyz") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "xyz");
assert_eq!(parsed, 7);
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
}
#[test]
fn test_or_combinator() {
let parser = char('a').or(char('b'));
match parser.parse("abc") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "bc");
assert_eq!(parsed, 'a');
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("bac") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "ac");
assert_eq!(parsed, 'b');
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("xyz") {
Ok(_) => panic!("Expected parse error when both alternatives fail"),
Err(e) => {
assert_eq!(e.actual, "x".to_string());
assert!(e.expected.len() >= 1); }
}
}
#[test]
fn test_many_combinator() {
let parser = digit().many();
match parser.parse("123abc") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "abc");
assert_eq!(parsed, vec!['1', '2', '3']);
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("abc123") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "abc123");
assert_eq!(parsed, Vec::<char>::new());
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
}
#[test]
fn test_many1_combinator() {
let parser = digit().many1();
match parser.parse("123abc") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "abc");
assert_eq!(parsed, vec!['1', '2', '3']);
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("abc123") {
Ok(_) => panic!("Expected parse error when no matches for many1"),
Err(e) => {
assert!(e.message.contains("at least one"));
}
}
}
#[test]
fn test_optional_combinator() {
let parser = char('a').optional();
match parser.parse("abc") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "bc");
assert_eq!(parsed, Some('a'));
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
match parser.parse("xyz") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "xyz");
assert_eq!(parsed, None);
}
Err(e) => panic!("Expected successful parse, got error: {:?}", e),
}
}
#[test]
fn test_scheme_number_parsing() {
let parser = scheme_number();
match parser.parse("42 ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
}
Err(e) => panic!("Expected successful integer parse, got error: {:?}", e),
}
match parser.parse("-123 ") {
Ok((remaining, _parsed)) => {
assert_eq!(remaining, " ");
}
Err(e) => panic!("Expected successful negative integer parse, got error: {:?}", e),
}
match parser.parse("3.14159 ") {
Ok((remaining, _parsed)) => {
assert_eq!(remaining, " ");
}
Err(e) => panic!("Expected successful float parse, got error: {:?}", e),
}
match parser.parse("22/7 ") {
Ok((remaining, _parsed)) => {
assert_eq!(remaining, " ");
}
Err(e) => panic!("Expected successful rational parse, got error: {:?}", e),
}
}
#[test]
fn test_scheme_string_parsing() {
let parser = scheme_string();
match parser.parse("\"hello world\" ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, "hello world");
}
Err(e) => panic!("Expected successful string parse, got error: {:?}", e),
}
match parser.parse("\"line1\\nline2\\t\\\"quoted\\\"\" ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, "line1\nline2\t\"quoted\"");
}
Err(e) => panic!("Expected successful escaped string parse, got error: {:?}", e),
}
match parser.parse("\"\" ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, "");
}
Err(e) => panic!("Expected successful empty string parse, got error: {:?}", e),
}
match parser.parse("\"unterminated") {
Ok(_) => panic!("Expected parse error for unterminated string"),
Err(e) => {
assert!(e.message.contains("unterminated") || e.message.contains("expected"));
}
}
}
#[test]
fn test_scheme_character_parsing() {
let parser = scheme_character();
match parser.parse("#\\a ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, 'a');
}
Err(e) => panic!("Expected successful character parse, got error: {:?}", e),
}
match parser.parse("#\\newline ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, '\n');
}
Err(e) => panic!("Expected successful newline parse, got error: {:?}", e),
}
match parser.parse("#\\space ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, ' ');
}
Err(e) => panic!("Expected successful space parse, got error: {:?}", e),
}
match parser.parse("#\\tab ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, '\t');
}
Err(e) => panic!("Expected successful tab parse, got error: {:?}", e),
}
}
#[test]
fn test_scheme_symbol_parsing() {
let parser = scheme_symbol();
match parser.parse("hello ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, "hello");
}
Err(e) => panic!("Expected successful symbol parse, got error: {:?}", e),
}
match parser.parse("my-var? ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, "my-var?");
}
Err(e) => panic!("Expected successful symbol with special chars parse, got error: {:?}", e),
}
match parser.parse("var123 ") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, " ");
assert_eq!(parsed, "var123");
}
Err(e) => panic!("Expected successful symbol with numbers parse, got error: {:?}", e),
}
match parser.parse("123var") {
Ok(_) => panic!("Expected parse error for symbol starting with number"),
Err(_e) => {
}
}
}
#[test]
fn test_comment_parsing() {
let parser = CommentSkipper::new();
match parser.parse("; this is a comment\nrest") {
Ok((remaining, _)) => {
assert_eq!(remaining, "rest");
}
Err(e) => panic!("Expected successful comment skip, got error: {:?}", e),
}
match parser.parse("#| block comment |# rest") {
Ok((remaining, _)) => {
assert_eq!(remaining, " rest");
}
Err(e) => panic!("Expected successful block comment skip, got error: {:?}", e),
}
}
#[test]
fn test_whitespace_parsing() {
let parser = whitespace1();
match parser.parse(" \t\n abc") {
Ok((remaining, parsed)) => {
assert_eq!(remaining, "abc");
assert_eq!(parsed.len(), 7); }
Err(e) => panic!("Expected successful whitespace parse, got error: {:?}", e),
}
match parser.parse("abc") {
Ok(_) => panic!("Expected parse error when no whitespace for whitespace1"),
Err(_e) => {
}
}
}
#[test]
fn test_error_span_information() {
let parser = tag("hello");
match parser.parse("help") {
Ok(_) => panic!("Expected parse error"),
Err(e) => {
assert_eq!(e.span.start, 0);
assert_eq!(e.span.end, 4); assert!(e.message.len() > 0);
assert!(e.expected.len() > 0);
assert_eq!(e.actual, "help");
}
}
}
#[test]
fn test_simple_sexp_integration() {
let parser = whitespace0().and_then(|_| scheme_sexp());
match parser.parse("(+ 1 2)") {
Ok((remaining, _parsed)) => {
assert_eq!(remaining.trim(), "");
}
Err(e) => panic!("Expected successful S-exp parse, got error: {:?}", e),
}
match parser.parse("(if (> x 0) x (- x))") {
Ok((remaining, _parsed)) => {
assert_eq!(remaining.trim(), "");
}
Err(e) => panic!("Expected successful nested S-exp parse, got error: {:?}", e),
}
}
#[test]
fn test_performance_large_input() {
let parser = digit().many();
let large_input = "1".repeat(10000);
let start_time = std::time::Instant::now();
match parser.parse(&large_input) {
Ok((remaining, parsed)) => {
let duration = start_time.elapsed();
assert_eq!(remaining, "");
assert_eq!(parsed.len(), 10000);
assert!(duration.as_millis() < 100, "Parse took too long: {:?}", duration);
}
Err(e) => panic!("Expected successful large input parse, got error: {:?}", e),
}
}
}