use rustledger_parser::cst::ast::{AstNode as _, Directive};
use rustledger_parser::parse;
fn violation(src: &str) -> Option<String> {
let parsed = parse(src);
let mut prev: Option<u32> = None;
for n in parsed.syntax_node().children() {
if !Directive::can_cast(n.kind()) {
continue;
}
let range = n.text_range();
let start: u32 = range.start().into();
if u32::from(range.len()) == 0 {
return Some(format!("zero-width {:?} at {start} in {src:?}", n.kind()));
}
if let Some(p) = prev
&& start <= p
{
return Some(format!("start {start} not after {p} in {src:?}"));
}
prev = Some(start);
}
None
}
struct Lcg(u64);
impl Lcg {
const fn next(&mut self) -> usize {
self.0 = self
.0
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(self.0 >> 33) as usize
}
}
#[test]
fn directive_siblings_never_share_a_start() {
const ATOMS: &[&str] = &[
"pushtag",
"poptag",
"pushmeta",
"popmeta",
"2024-01-01",
"open",
"close",
"*",
"\"",
"{",
"}",
"{{",
"}}",
"^a",
"#a",
"\n",
" ",
"\r\n",
"\u{feff}",
"",
";x",
"custom",
"Assets:B",
"1 USD",
":",
",",
"balance",
"pad",
"price",
"@",
"@@",
];
for src in [
"",
"\n",
"\n\n\n",
"pushtag",
"poptag\n",
"pushmeta",
"popmeta\n",
"2024-01-01",
"2024-01-01\n",
"2024-01-01 ",
"2024-01-01 open",
"2024-01-01 * \"",
"\u{feff}",
"\u{feff}\n\n",
"\r\n\r\n",
"*\n*\n*\n",
"{{{\n",
"pushtagpoptag\n",
"2024-01-012024-01-02\n",
"pushtag #a\npoptag #a\npushtag #b\npoptag #b\n",
] {
assert!(violation(src).is_none(), "{}", violation(src).unwrap());
}
let mut rng = Lcg(0x2545_F491_4F6C_DD1D);
for _ in 0..20_000 {
let len = rng.next() % 12 + 1;
let src: String = (0..len).map(|_| ATOMS[rng.next() % ATOMS.len()]).collect();
assert!(violation(&src).is_none(), "{}", violation(&src).unwrap());
}
}
#[test]
fn the_violation_detector_can_actually_fail() {
let starts = [0u32, 10, 10, 20];
let mut prev: Option<u32> = None;
let mut flagged = false;
for start in starts {
if let Some(p) = prev
&& start <= p
{
flagged = true;
}
prev = Some(start);
}
assert!(
flagged,
"the ascending check must reject a duplicate start; if it does not, \
`directive_siblings_never_share_a_start` proves nothing",
);
}