use regex_automata::Input;
use regex_automata::dfa::Automaton;
use regex_automata::dfa::dense;
use regex_automata::nfa::thompson;
use regex_automata::util::syntax;
use sheng::Quotient;
mod common;
const SLATE: &[&str] = &[
r"(?-u)WalletService",
r"(?-u)(alpha|beta|gamma)",
r"(?-u)[A-Z][a-z]+Service",
r"(?-u)[0-9]{3}-[0-9]{4}",
r"(?-u)a[^\n]*b",
r"(?-u)<[^>]*>",
r"(?-u)#[0-9a-fA-F]{6}",
r"(?-u)panic!\(",
];
fn main() {
let docs = common::corpus_bytes(8 << 20);
println!(
"{:<26} {:>3} {:>6} {:>7} {:>9} {:>6} {:>9} {:>9} {:>7}",
"pattern", "#q", "escape", "resident", "skippable", "engine", "compose", "skip", "ratio"
);
let (mut audited, mut agreed) = (0usize, 0usize);
for pattern in SLATE {
let dfa = dense::Builder::new()
.syntax(syntax::Config::new().utf8(false))
.thompson(thompson::Config::new().utf8(false))
.build(pattern)
.expect("pattern builds");
let engine = dfa
.start_state_forward(&Input::new(b""))
.ok()
.map_or(0, |s| dfa.accelerator(s).len());
let core = sheng::Projection::of(&dfa).expect("projects");
let planned = sheng::Sieve::ungated(pattern).map(|s| s.skipping()).ok();
let mut faster = 0usize;
for (i, q) in sheng::harvest(&core).iter().enumerate() {
let escape = escapes(q, q.start);
let (resident, runs) = residency(q, &docs);
let skippable = if resident > 0 {
(resident - runs.min(resident)) as f64 / total(&docs) as f64
} else {
0.0
};
let compose = per_byte(&docs, |hay| {
std::hint::black_box(sheng::shuffle::refutes(q, hay));
});
let (skip_ns, ratio) = match sheng::Skip::of(&q.rows, q.start) {
Some(s) if q.start < q.threshold => {
let ns = per_byte(&docs, |hay| {
std::hint::black_box(sheng::shuffle::refutes_skipping(q, &s, hay));
});
for doc in &docs {
assert_eq!(
sheng::shuffle::refutes_skipping(q, &s, doc),
sheng::shuffle::scalar(q, doc),
"skip disagreed with the reference on {pattern}"
);
}
faster += usize::from(ns < compose);
(format!("{ns:.4}"), format!("{:.2}x", compose / ns))
},
_ => ("-".into(), "-".into()),
};
println!(
"{:<26} {:>3} {:>6} {:>7.1}% {:>8.1}% {:>6} {:>9.4} {:>9} {:>7}",
if i == 0 { pattern } else { "" },
i,
escape.len(),
100.0 * resident as f64 / total(&docs) as f64,
100.0 * skippable,
engine,
compose,
skip_ns,
ratio,
);
}
if let Some(planned) = planned {
audited += 1;
agreed += usize::from(planned == faster);
if planned != faster {
println!(
"{:<26} planner took {planned} skip lane(s), measurement wanted {faster}",
""
);
}
}
}
println!(
"\nplanner agreed with the measurement on {agreed}/{audited} patterns \
— a disagreement is a mispriced coefficient, not an unsound sieve"
);
}
fn escapes(q: &Quotient, block: u8) -> Vec<u8> {
(0..=255u8)
.filter(|&b| q.rows[usize::from(b)][usize::from(block)] != block)
.collect()
}
fn residency(q: &Quotient, docs: &[Vec<u8>]) -> (usize, usize) {
let (mut resident, mut runs) = (0usize, 0usize);
for doc in docs {
let mut state = q.start;
let mut inside = false;
for &byte in doc {
if state == q.start {
resident += 1;
if !inside {
runs += 1;
inside = true;
}
} else {
inside = false;
}
state = q.rows[usize::from(byte)][usize::from(state)];
}
}
(resident, runs)
}
fn total(docs: &[Vec<u8>]) -> usize {
docs.iter().map(Vec::len).sum()
}
fn per_byte(docs: &[Vec<u8>], mut run: impl FnMut(&[u8])) -> f64 {
let mut best = f64::MAX;
for _ in 0..5 {
let t = std::time::Instant::now();
for doc in docs {
run(doc);
}
best = best.min(t.elapsed().as_secs_f64());
}
best * 1e9 / total(docs) as f64
}