tests.rs
raw
use super::*;
fn read_one(source: &str) -> Sexpr {
let doc = read(source).expect("reads");
assert_eq!(doc.values.len(), 1, "expected exactly one value");
doc.values.into_iter().next().unwrap()
}
#[test]
fn atoms() {
assert_eq!(
read_one("hello"),
Sexpr::Symbol("hello".into(), Span::new(0, 5))
);
assert_eq!(
read_one(":class"),
Sexpr::Keyword("class".into(), Span::new(0, 6))
);
assert_eq!(read_one("42"), Sexpr::Int(42, Span::new(0, 2)));
assert_eq!(read_one("-7"), Sexpr::Int(-7, Span::new(0, 2)));
assert_eq!(read_one("2.5"), Sexpr::Float(2.5, Span::new(0, 3)));
assert_eq!(read_one("\"hi\""), Sexpr::Str("hi".into(), Span::new(0, 4)));
// Operator and kebab symbols.
assert_eq!(read_one("+"), Sexpr::Symbol("+".into(), Span::new(0, 1)));
assert_eq!(
read_one("flex-start"),
Sexpr::Symbol("flex-start".into(), Span::new(0, 10))
);
}
#[test]
fn string_escapes() {
assert_eq!(
read_one(r#""a\nb""#),
Sexpr::Str("a\nb".into(), Span::new(0, 6))
);
assert_eq!(read_one(r#""say \"hi\"""#).as_symbol(), None);
let Sexpr::Str(s, _) = read_one(r#""say \"hi\"""#) else {
panic!()
};
assert_eq!(s, "say \"hi\"");
let Sexpr::Str(s, _) = read_one(r#""\{not-interp""#) else {
panic!()
};
assert_eq!(s, "{not-interp");
// Unicode passes through.
let Sexpr::Str(s, _) = read_one("\"Ω中\"") else {
panic!()
};
assert_eq!(s, "Ω中");
}
#[test]
fn nested_lists_and_spans() {
let value = read_one("(a (b 1) :k \"s\")");
let Sexpr::List(items, span) = &value else {
panic!()
};
assert_eq!(*span, Span::new(0, 16));
assert_eq!(items.len(), 4);
let Sexpr::List(inner, inner_span) = &items[1] else {
panic!()
};
assert_eq!(*inner_span, Span::new(3, 8));
assert_eq!(inner[1], Sexpr::Int(1, Span::new(6, 7)));
}
#[test]
fn comments_are_recorded_not_parsed() {
let doc = read("; header\n(a) ; trailing\n(b)").expect("reads");
assert_eq!(doc.values.len(), 2);
assert_eq!(doc.comments.len(), 2);
assert_eq!(doc.comments[0].text, "; header");
assert_eq!(doc.comments[0].span, Span::new(0, 8));
assert_eq!(doc.comments[1].text, "; trailing");
}
#[test]
fn keyword_adjacent_to_value() {
// `:gap 8` inside a list; also keywords terminate symbols.
let value = read_one("(box :gap 8)");
let items = value.as_list().unwrap();
assert_eq!(items[1], Sexpr::Keyword("gap".into(), Span::new(5, 9)));
assert_eq!(items[2], Sexpr::Int(8, Span::new(10, 11)));
}
#[test]
fn errors_have_positions() {
let err = read("(a (b)").unwrap_err();
assert!(err.message.contains("unclosed"));
assert_eq!(err.span, Span::new(0, 1), "points at the opening paren");
let err = read("\"oops").unwrap_err();
assert!(err.message.contains("unterminated string"));
let err = read("(a))").unwrap_err();
assert!(err.message.contains("unmatched"));
assert_eq!(err.span, Span::new(3, 4));
let err = read(r#""bad \q escape""#).unwrap_err();
assert!(err.message.contains("unknown escape"));
}
#[test]
fn negative_symbol_vs_number() {
// A lone `-` or `-x` is a symbol; `-3` is a number.
assert_eq!(read_one("-"), Sexpr::Symbol("-".into(), Span::new(0, 1)));
assert_eq!(read_one("-3"), Sexpr::Int(-3, Span::new(0, 2)));
}