use lispexp::{parse, parse_form_at, DatumKind, Delim, ErrorKind, Options};
fn only_error(src: &str) -> ErrorKind {
let parsed = parse(src, &Options::scheme());
assert_eq!(parsed.errors.len(), 1, "errors: {:?}", parsed.errors);
parsed.errors[0].kind.clone()
}
#[test]
fn unclosed_list_kind() {
assert!(matches!(
only_error("(a b"),
ErrorKind::UnclosedList {
open: Delim::Round,
..
}
));
}
#[test]
fn unexpected_delimiter_kind() {
assert!(matches!(
only_error(")"),
ErrorKind::UnexpectedDelimiter {
found: Delim::Round,
..
}
));
}
#[test]
fn mismatched_delimiter_carries_expected_and_found() {
assert!(matches!(
only_error("[a)"),
ErrorKind::MismatchedDelimiter {
expected: Delim::Square,
found: Delim::Round,
..
}
));
}
#[test]
fn malformed_token_carries_text() {
let parsed = parse("\"oops", &Options::scheme());
assert!(parsed.errors.iter().any(
|e| matches!(&e.kind, ErrorKind::MalformedToken { text, .. } if text.contains("oops"))
));
}
#[test]
fn kind_is_shift_stable_and_hashable() {
let a = parse("(a", &Options::scheme()).errors[0].kind.clone();
let b = parse(" (a", &Options::scheme()).errors[0].kind.clone();
assert_eq!(a, b);
use std::collections::HashSet;
let set: HashSet<ErrorKind> = [a, b].into_iter().collect();
assert_eq!(set.len(), 1);
}
#[test]
fn display_renders_human_message() {
let msg = format!("{}", only_error("(a b"));
assert!(msg.contains("unclosed"), "{msg}");
}
#[test]
fn parse_form_at_reads_one_form_with_absolute_spans() {
let src = "(a b) (c d) (e f)";
let first = parse_form_at(src, 0, &Options::scheme()).unwrap();
assert_eq!(first.form.span.text(src), "(a b)");
assert!(first.errors.is_empty());
let second = parse_form_at(src, first.end, &Options::scheme()).unwrap();
assert_eq!(second.form.span.text(src), "(c d)");
assert_eq!(second.form.span.start, 6);
}
#[test]
fn parse_form_at_offset_in_leading_whitespace() {
let src = "(a b) (c d)";
let f = parse_form_at(src, 5, &Options::scheme()).unwrap();
assert_eq!(f.form.span.text(src), "(c d)");
}
#[test]
fn parse_form_at_reports_local_errors() {
let src = "(a b) (c";
let f = parse_form_at(src, 6, &Options::scheme()).unwrap();
assert!(matches!(f.form.kind, DatumKind::List { .. }));
assert_eq!(f.errors.len(), 1);
assert!(matches!(
f.errors[0].kind,
ErrorKind::UnclosedList {
open: Delim::Round,
..
}
));
}
#[test]
fn parse_form_at_past_end_is_none() {
let src = "(a b) ";
assert!(parse_form_at(src, 5, &Options::scheme()).is_none());
}
#[test]
fn item_after_dotted_tail_is_reported() {
let parsed = parse("(a . b c)", &Options::scheme());
assert!(
parsed
.errors
.iter()
.any(|e| matches!(e.kind, ErrorKind::ItemAfterDottedTail)),
"expected ItemAfterDottedTail, got {:?}",
parsed.errors
);
}
#[test]
fn second_dot_is_reported_in_scheme() {
let parsed = parse("(a . b . c)", &Options::scheme());
assert!(
parsed
.errors
.iter()
.any(|e| matches!(e.kind, ErrorKind::ItemAfterDottedTail)),
"expected ItemAfterDottedTail for a second dot, got {:?}",
parsed.errors
);
}
#[test]
fn racket_infix_dot_is_not_an_error() {
let parsed = parse("(dom . -> . rng)", &Options::racket());
assert!(
parsed.errors.is_empty(),
"Racket infix dot must parse cleanly: {:?}",
parsed.errors
);
}
#[test]
fn depth_limit_exceeded_kind() {
let src = "(".repeat(2000);
let parsed = parse(&src, &Options::scheme());
assert!(
parsed
.errors
.iter()
.any(|e| matches!(e.kind, ErrorKind::DepthLimitExceeded)),
"expected DepthLimitExceeded, got {:?}",
parsed.errors
);
}