use crate::propform::Propform;
use crate::temex_error::TemexError;
use crate::temex_match::TemexMatch;
use crate::temex_matches::TemexMatches;
use crate::temex_trace::TemexTrace;
use regex::Captures;
use regex::Regex;
use std::str;
#[derive(Clone, Debug)]
pub struct Temex {
labels: Vec<String>,
width: usize,
re: regex::bytes::Regex,
te: String,
}
impl Temex {
pub fn new(te: &str) -> Result<Temex, TemexError> {
let labels = get_all_boolean_variables(te)?;
let raw_pattern = pattern_expansion(te, &labels)?;
let re = regex::bytes::Regex::new(raw_pattern.as_ref())?;
let width = labels.len() + 1;
Ok(Temex {
labels,
width,
re,
te: te.to_owned(),
})
}
pub fn is_match(&self, trace: &TemexTrace) -> Result<bool, TemexError> {
self.is_match_at(trace, 0)
}
pub fn is_match_at(&self, trace: &TemexTrace, start: usize) -> Result<bool, TemexError> {
let tnf = trace;
self.labels_match(tnf)?;
Ok(self
.re
.is_match_at(&tnf.data, self.denormalize_index(start)))
}
pub fn find(&self, trace: &TemexTrace) -> Result<Option<TemexMatch>, TemexError> {
self.find_at(trace, 0)
}
pub fn find_at(
&self,
trace: &TemexTrace,
start: usize,
) -> Result<Option<TemexMatch>, TemexError> {
let tnf = trace;
self.labels_match(tnf)?;
match self.re.find_at(&tnf.data, self.denormalize_index(start)) {
Some(m) => Ok(Some(self.normalize_match(m))),
None => Ok(None),
}
}
pub fn find_iter<'r, 't>(&'r self, trace: &'t TemexTrace) -> TemexMatches<'r, 't> {
let re_matches = self.re.find_iter(&trace.data);
TemexMatches {
re_matches,
width: self.width,
}
}
pub fn as_str(&self) -> &str {
&self.te
}
fn labels_match(&self, trace: &TemexTrace) -> Result<(), TemexError> {
if self.labels != trace.labels {
Err(TemexError::MismatchedLabels {
temex_labels: self.labels.clone(),
trace_labels: trace.labels.clone(),
})
} else {
Ok(())
}
}
fn normalize_match(&self, re: regex::bytes::Match) -> TemexMatch {
TemexMatch::new(re.start() / self.width, re.end() / self.width)
}
fn denormalize_index(&self, idx: usize) -> usize {
idx * self.width
}
}
fn pattern_expansion(raw_pattern: &str, labels: &Vec<String>) -> Result<String, TemexError> {
let propform_capture = Regex::new(r"\[([^\]]*)\]").unwrap();
let mut err: Option<TemexError> = None;
let pattern = propform_capture.replace_all(raw_pattern, |caps: &Captures| {
let result = (|| {
let raw_propform = &caps[1].to_owned();
let propform = raw_propform.parse::<Propform>()?;
let expanded = expand_propform(propform, labels)?;
Ok(expanded)
})();
result.map_err(|e| err = Some(e)).unwrap_or_default()
});
match err {
Some(e) => Err(e),
None => Ok(pattern.to_string()),
}
}
fn get_all_boolean_variables(raw_pattern: &str) -> Result<Vec<String>, TemexError> {
let propform_capture = Regex::new(r"\[(?P<inner>[^\]]*)\]").unwrap();
let mut boolean_variables: Vec<String> = vec![];
for cap in propform_capture.captures_iter(raw_pattern) {
let mut local_vars = crate::propform::get_boolean_vars(&cap["inner"])?;
boolean_variables.append(&mut local_vars);
}
boolean_variables.sort_unstable();
boolean_variables.dedup();
Ok(boolean_variables)
}
fn find_label(s: &str, labels: &[String]) -> usize {
for (i, label) in labels.iter().enumerate() {
if label == s {
return i;
}
}
unreachable!()
}
fn propform_eval(prop: &Propform, labels: &Vec<String>, interp: &Vec<u8>) -> bool {
match *prop {
Propform::True => true,
Propform::False => false,
Propform::BooleanVar(ref s) => {
let i = find_label(s, labels);
interp[i] == b'1'
}
Propform::Not(ref subform) => !(propform_eval(subform, labels, interp)),
Propform::And(ref lhs, ref rhs) => {
propform_eval(lhs, labels, interp) && propform_eval(rhs, labels, interp)
}
Propform::Or(ref lhs, ref rhs) => {
propform_eval(lhs, labels, interp) || propform_eval(rhs, labels, interp)
}
Propform::Nand(ref lhs, ref rhs) => {
!(propform_eval(lhs, labels, interp) && propform_eval(rhs, labels, interp))
}
Propform::Nor(ref lhs, ref rhs) => {
!(propform_eval(lhs, labels, interp) || propform_eval(rhs, labels, interp))
}
Propform::Xor(ref lhs, ref rhs) => {
propform_eval(lhs, labels, interp) && !(propform_eval(rhs, labels, interp))
|| (!propform_eval(lhs, labels, interp) && propform_eval(rhs, labels, interp))
}
}
}
fn propform_labels_valid(prop: &Propform, labels: &Vec<String>) -> Result<(), TemexError> {
match *prop {
Propform::True => Ok(()),
Propform::False => Ok(()),
Propform::BooleanVar(ref s) => {
if labels.contains(s) {
Ok(())
} else {
Err(TemexError::UnexpectedLabelError(s.to_owned()))
}
}
Propform::Not(ref subform) => propform_labels_valid(subform, labels),
Propform::And(ref lhs, ref rhs) => {
propform_labels_valid(lhs, labels)?;
propform_labels_valid(rhs, labels)
}
Propform::Or(ref lhs, ref rhs) => {
propform_labels_valid(lhs, labels)?;
propform_labels_valid(rhs, labels)
}
Propform::Nand(ref lhs, ref rhs) => {
propform_labels_valid(lhs, labels)?;
propform_labels_valid(rhs, labels)
}
Propform::Nor(ref lhs, ref rhs) => {
propform_labels_valid(lhs, labels)?;
propform_labels_valid(rhs, labels)
}
Propform::Xor(ref lhs, ref rhs) => {
propform_labels_valid(lhs, labels)?;
propform_labels_valid(rhs, labels)
}
}
}
fn int_to_charvec(i: u32, len: usize) -> Vec<u8> {
format!("{:0len$b}", i).as_bytes().to_owned()
}
fn propform_to_satisfying_interpretations(
prop: Propform,
labels: &Vec<String>,
) -> Result<Vec<String>, TemexError> {
propform_labels_valid(&prop, labels)?;
let len = labels.len();
let interps: Vec<String> = (0..2_u32.pow(len as u32))
.map(|x| int_to_charvec(x, len))
.filter(|x| propform_eval(&prop, labels, x))
.map(|x| String::from_utf8(x).unwrap())
.collect();
Ok(interps)
}
fn expand_propform(prop: Propform, labels: &Vec<String>) -> Result<String, TemexError> {
let interps: Vec<String> = propform_to_satisfying_interpretations(prop, labels)?
.into_iter()
.collect();
if interps.is_empty() {
return Ok(vec!['F'; labels.len() + 1].into_iter().collect());
}
let mut result = "(?:".to_string();
result.push_str(&interps.join("\n|"));
result.push_str("\n)");
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
const ONE_VAR: &str = "p1";
const ONE_VAR_NEG: &str = "not p1";
const SIMPLE_CONJUNCT: &str = "p1 and p2";
const SIMPLE_DISJUNCT: &str = "p1 or p2";
#[test]
fn expand_propform_works() {
let prop_1var = ONE_VAR.to_string().parse::<Propform>().unwrap();
let prop_1var_neg = ONE_VAR_NEG.to_string().parse::<Propform>().unwrap();
let prop_simple_conjunct = SIMPLE_CONJUNCT.to_string().parse::<Propform>().unwrap();
let prop_simple_disjunct = SIMPLE_DISJUNCT.to_string().parse::<Propform>().unwrap();
let labels = vec!["p1".to_owned(), "p2".to_owned(), "p3".to_owned()];
assert_eq!(
expand_propform(prop_1var, &labels).unwrap(),
"(?:100\n|101\n|110\n|111\n)".to_string()
);
assert_eq!(
expand_propform(prop_1var_neg, &labels).unwrap(),
"(?:000\n|001\n|010\n|011\n)".to_string()
);
assert_eq!(
expand_propform(prop_simple_conjunct, &labels).unwrap(),
"(?:110\n|111\n)".to_string()
);
assert_eq!(
expand_propform(prop_simple_disjunct, &labels).unwrap(),
"(?:010\n|011\n|100\n|101\n|110\n|111\n)"
);
}
#[test]
fn propform_eval_works() {
let prop_1var = ONE_VAR.to_string().parse::<Propform>().unwrap();
let prop_1var_neg = ONE_VAR_NEG.to_string().parse::<Propform>().unwrap();
let prop_simple_conjunct = SIMPLE_CONJUNCT.to_string().parse::<Propform>().unwrap();
let prop_simple_disjunct = SIMPLE_DISJUNCT.to_string().parse::<Propform>().unwrap();
let labels = vec!["p1".to_owned(), "p2".to_owned(), "p3".to_owned()];
let p_true_interp = [b'1', b'1', b'1'];
let p_false_interp = [b'0', b'1', b'0'];
let simple_disjunct_false = [b'0', b'0', b'0'];
assert!(propform_eval(&prop_1var, &labels, &p_true_interp.to_vec()));
assert!(!propform_eval(
&prop_1var,
&labels,
&p_false_interp.to_vec()
));
assert!(!propform_eval(
&prop_1var_neg,
&labels,
&p_true_interp.to_vec()
));
assert!(propform_eval(
&prop_1var_neg,
&labels,
&p_false_interp.to_vec()
));
assert!(propform_eval(
&prop_simple_conjunct,
&labels,
&p_true_interp.to_vec()
));
assert!(!propform_eval(
&prop_simple_conjunct,
&labels,
&p_false_interp.to_vec()
));
assert!(propform_eval(
&prop_simple_disjunct,
&labels,
&p_false_interp.to_vec()
));
assert!(!propform_eval(
&prop_simple_disjunct,
&labels,
&simple_disjunct_false.to_vec()
));
}
#[test]
fn pattern_expansion_works() {
let labels = vec!["p1".to_owned()];
let p1_star = "[p1]*".to_string();
let p1_paren_star = "([p1])*".to_string();
let p1_concat = "[p1][p1][p1]".to_string();
assert_eq!(pattern_expansion(&p1_star, &labels).unwrap(), "(?:1\n)*");
assert_eq!(
pattern_expansion(&p1_paren_star, &labels).unwrap(),
"((?:1\n))*"
);
assert_eq!(
pattern_expansion(&p1_concat, &labels).unwrap(),
"(?:1\n)(?:1\n)(?:1\n)"
);
}
}