#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
use rexact_core::{Language, Limits, Mode};
use rexact_syntax::{Dialect, Flags, Pattern};
use std::collections::HashMap;
fn set(name: &str) -> (Vec<&'static str>, usize) {
match name {
"gen" => (
vec!["a", "b", "c", "\u{e9}", "\n", "1", " ", "\u{1F600}"],
4,
),
"edge" => (
vec![
"0", "9", "/", ":", "_", "A", "@", "[", "z", "`", "\u{a0}", "\r", "\u{2028}",
"\u{2029}", "\u{85}", "\u{feff}",
],
3,
),
"crlf" => (vec!["a", "\r", "\n", "\u{85}"], 4),
other => panic!("unknown set {other}"),
}
}
fn strings(symbols: &[&str], maxlen: usize) -> Vec<String> {
let mut out = vec![String::new()];
let mut layer = vec![String::new()];
for _ in 0..maxlen {
let mut next = Vec::new();
for s in &layer {
for sym in symbols {
next.push(format!("{s}{sym}"));
}
}
out.extend(next.iter().cloned());
layer = next;
}
out
}
fn decode(hex: &str) -> String {
hex.split_whitespace()
.map(|h| char::from_u32(u32::from_str_radix(h, 16).unwrap()).unwrap())
.collect()
}
#[test]
fn java_matching_agrees_with_java_util_regex() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../corpus/match_java.tsv");
let text = std::fs::read_to_string(path).expect("corpus readable");
let mut all: HashMap<&str, Vec<String>> = HashMap::new();
for name in ["gen", "edge", "crlf"] {
let (syms, n) = set(name);
all.insert(name, strings(&syms, n));
}
let limits = Limits::default();
let (mut compared, mut skipped_u, mut rejected_by_java, mut checks) = (0, 0, 0, 0u64);
let mut failures = Vec::new();
for line in text
.lines()
.filter(|l| !l.starts_with('#') && !l.is_empty())
{
let c: Vec<&str> = line.split('\t').collect();
let strs = &all[c[0]];
let flags = Flags::from_letters(Dialect::Java, c[2]).unwrap().0;
let pattern = decode(c[3]);
let label = format!("{}/{}/{:?}", c[0], c[2], pattern);
if c[4] == "-" {
rejected_by_java += 1;
if let Ok(p) = Pattern::parse(&pattern, Dialect::Java, flags) {
if Language::compile(&p, None, Mode::Full, &limits).is_ok() {
failures.push(format!("{label}: Java rejects, we accept"));
}
}
continue;
}
let bytes: Vec<u8> = (0..c[4].len() / 2)
.map(|i| u8::from_str_radix(&c[4][2 * i..2 * i + 2], 16).unwrap())
.collect();
let parsed = match Pattern::parse(&pattern, Dialect::Java, flags) {
Ok(p) => p,
Err(e) if e.code() == "RX-U007" || e.code() == "RX-U001" || e.code() == "RX-U004" => {
skipped_u += 1;
continue;
}
Err(e) => {
failures.push(format!(
"{label}: Java accepts, parser rejects: {}",
e.code()
));
continue;
}
};
let mut lang = match Language::compile(&parsed, None, Mode::Full, &limits) {
Ok(l) => l,
Err(e) if e.code() == "RX-U007" || e.code() == "RX-U005" => {
skipped_u += 1;
continue;
}
Err(e) => {
failures.push(format!("{label}: compile failed: {e}"));
continue;
}
};
compared += 1;
for (i, s) in strs.iter().enumerate() {
let expected = bytes[i / 8] >> (i % 8) & 1 == 1;
let got = lang.matches(s).expect("match");
checks += 1;
if got != expected {
failures.push(format!(
"{label}: input {s:?}: java={expected} rexact={got}"
));
break;
}
}
}
eprintln!(
"compared {compared} rows ({checks} checks); skipped {skipped_u} unmodelled; {rejected_by_java} rejected by Java"
);
assert!(
failures.is_empty(),
"{} disagreements:\n{}",
failures.len(),
failures
.iter()
.take(60)
.cloned()
.collect::<Vec<_>>()
.join("\n")
);
assert!(compared > 500, "too few rows compared: {compared}");
}