use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher}
};
use crate::{
Parser,
parser::{NomErrorKind, ParseError},
support::on_small_stack,
tests::ast::{DEPTH, LEAF, Nesting, nest_parsable}
};
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_group() { parse_deep(Nesting::Group) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_binding() { parse_deep(Nesting::Binding) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_range_start() { parse_deep(Nesting::RangeStart) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_range_end() { parse_deep(Nesting::RangeEnd) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_negation() { parse_deep(Nesting::Negation) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_exponent() { parse_deep(Nesting::Exponent) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_addition() { parse_deep(Nesting::Addition) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_subtraction() { parse_deep(Nesting::Subtraction) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_multiplication() { parse_deep(Nesting::Multiplication) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_division() { parse_deep(Nesting::Division) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_modulo() { parse_deep(Nesting::Modulo) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_dice_count() { parse_deep(Nesting::DiceCount) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_dice_faces() { parse_deep(Nesting::DiceFaces) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_custom_count() { parse_deep(Nesting::CustomCount) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_drop_expression() { parse_deep(Nesting::DropExpression) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_drop_clauses() { parse_deep(Nesting::DropClauses) }
#[test]
#[ignore = "stress: run with just stress"]
fn test_parse_deep_mixed() { parse_deep(Nesting::Mixed) }
fn parse_deep(nesting: Nesting)
{
on_small_stack(|| {
let source = nest_parsable(nesting, DEPTH, LEAF).to_string();
let function = Parser::parse(&source).expect("deep source failed");
assert!(function.parameters.is_none(), "{:?}", nesting);
assert!(
function.body.to_string() == source,
"{:?}: parsed expression renders differently from source",
nesting
);
drop(function);
let leaf = LEAF.to_string();
assert_eq!(source.matches(&leaf).count(), 1, "{:?}", nesting);
let failing = source.replacen(&leaf, "@", 1);
let error = Parser::parse(&failing).expect_err("failing source parsed");
let offset = source.find(&leaf).unwrap();
assert_eq!(
error.errors[0].0.location_offset(),
offset,
"{:?}",
nesting
);
assert!(
synthetic_nesting(&error) <= SYNTHETIC_NESTING_BOUND,
"{:?}: synthetic errors nest too deeply",
nesting
);
let leading = error
.errors
.iter()
.take_while(|(span, _)| span.location_offset() == offset)
.count();
assert!(
error.errors.len() <= leading + 1,
"{:?}: error keeps {} entries, of which {} are leading",
nesting,
error.errors.len(),
leading
);
assert!(error.to_string().starts_with("Parse error @ 1:"));
let copy = error.clone();
assert!(copy == error, "{:?}: clone compared unequal", nesting);
assert_eq!(hash_of(©), hash_of(&error), "{:?}", nesting);
});
}
const SYNTHETIC_NESTING_BOUND: usize = 11;
#[test]
fn test_synthetic_nesting_is_bounded()
{
let leaf = LEAF.to_string();
for nesting in Nesting::ROTATION.into_iter().chain([Nesting::Mixed])
{
for depth in 1..=40
{
let source = nest_parsable(nesting, depth, LEAF).to_string();
let failing = source.replacen(&leaf, "@", 1);
for input in source
.char_indices()
.map(|(i, _)| &source[..i])
.chain([failing.as_str()])
{
if let Err(error) = Parser::parse(input)
{
let copy = error.clone();
assert!(
copy == error,
"clone compared unequal: {:?}",
input
);
assert_eq!(hash_of(©), hash_of(&error), "{:?}", input);
assert!(
synthetic_nesting(&error) <= SYNTHETIC_NESTING_BOUND,
"{:?} at depth {}: synthetic errors nest too deeply \
for {:?}",
nesting,
depth,
input
);
}
}
}
}
}
fn synthetic_nesting(error: &ParseError<'_>) -> usize
{
let mut greatest = 0;
let mut pending = vec![(error, 0)];
while let Some((error, depth)) = pending.pop()
{
for (_, kind) in &error.errors
{
if let NomErrorKind::Synthetic(merged) = kind
{
greatest = greatest.max(depth + 1);
pending.extend(merged.iter().map(|error| (error, depth + 1)));
}
}
}
greatest
}
fn hash_of<T: Hash>(value: &T) -> u64
{
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}