use links_notation::parser::parse_document_with_max_depth;
use links_notation::{
format_links, parse_lino_to_links, parse_lino_to_links_with_config, ParseError, ParserConfig,
};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
const BUDGET: Duration = Duration::from_secs(5);
const DEEP: usize = 32;
const VERY_DEEP: usize = 4096;
fn within_budget<T: Send + 'static>(what: &str, work: impl FnOnce() -> T + Send + 'static) -> T {
let (sender, receiver) = mpsc::channel();
thread::Builder::new()
.stack_size(512 * 1024 * 1024)
.spawn(move || {
let _ = sender.send(work());
})
.unwrap();
receiver
.recv_timeout(BUDGET)
.unwrap_or_else(|_| panic!("{what} did not finish within {BUDGET:?}"))
}
fn parse_very_deep(source: &str) -> Result<Vec<links_notation::LiNo<String>>, ParseError> {
parse_lino_to_links_with_config(source, &ParserConfig::new().with_max_depth(usize::MAX))
}
fn closed(depth: usize) -> String {
format!("{}a{}", "(".repeat(depth), ")".repeat(depth))
}
fn value_after(depth: usize) -> String {
format!("{}a{}", "(".repeat(depth), ") b".repeat(depth))
}
fn unclosed(depth: usize) -> String {
format!("{}a", "(".repeat(depth))
}
fn indented_unclosed(depth: usize) -> String {
(0..depth)
.map(|level| format!("{}(a", " ".repeat(level)))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn closed_groups_read_in_linear_time() {
let source = closed(DEEP);
let expected = source.clone();
let links = within_budget("closed groups", move || parse_lino_to_links(&source));
assert_eq!(format_links(&links.unwrap()), expected);
}
#[test]
fn values_after_groups_read_in_linear_time() {
let source = value_after(DEEP);
let expected = format!("({source})");
let links = within_budget("values after groups", move || parse_lino_to_links(&source));
assert_eq!(format_links(&links.unwrap()), expected);
}
#[test]
fn unclosed_groups_fail_in_linear_time() {
let source = unclosed(DEEP);
let result = within_budget("unclosed groups", move || parse_lino_to_links(&source));
assert!(result.is_err());
}
#[test]
fn unclosed_groups_on_indented_lines_fail_in_linear_time() {
let source = indented_unclosed(DEEP);
let result = within_budget("unclosed indented groups", move || {
parse_lino_to_links(&source)
});
assert!(result.is_err());
}
#[test]
fn parse_lino_to_links_does_not_copy_each_level() {
let source = closed(VERY_DEEP);
let expected = source.clone();
let formatted = within_budget("very deep closed groups", move || {
format_links(&parse_very_deep(&source).unwrap())
});
assert_eq!(formatted, expected);
}
#[test]
fn values_after_very_deep_groups_read_in_linear_time() {
let source = value_after(VERY_DEEP);
let length = source.len();
let parsed = within_budget("very deep values after groups", move || {
parse_document_with_max_depth(&source, usize::MAX).is_ok()
});
assert!(parsed, "all {length} bytes should be read");
}
#[test]
fn group_followed_by_values_keeps_its_structure() {
let cases = [
("(a) b", "((a) b)"),
("(a) (b) c", "((a) (b) c)"),
("((a) b) c", "(((a) b) c)"),
("(a: b) c", "((a: b) c)"),
("(a)\n(b) c", "(a)\n((b) c)"),
("x\n (a) b\n (c)", "(x)\n((x) ((a) b))\n((x) (c))"),
];
for (source, expected) in cases {
let links = parse_lino_to_links(source).unwrap();
assert_eq!(format_links(&links), expected, "{source:?}");
}
}