use crate::dslx_tactics::obligations::{ObligationPayload, ProverObligation, Side};
use crate::dslx_tactics::tactics::IsTactic;
use crate::dslx_tactics::tactics::utils::is_valid_ident;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FocusPair {
pub lhs: String,
pub rhs: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FocusTactic {
pub pairs: Vec<FocusPair>,
}
impl IsTactic for FocusTactic {
fn name(&self) -> &'static str {
"focus"
}
fn apply(&self, base: &ProverObligation) -> Result<Vec<ProverObligation>, String> {
for (idx, p) in self.pairs.iter().enumerate() {
if !is_valid_ident(&p.lhs) {
return Err(format!("lhs pair {} invalid function name", idx + 1));
}
if !is_valid_ident(&p.rhs) {
return Err(format!("rhs pair {} invalid function name", idx + 1));
}
}
let base_lec = match &base.payload {
ObligationPayload::Lec(lec) => lec,
_ => return Err("focus tactic requires a Lec obligation".to_string()),
};
let mut obligations: Vec<ProverObligation> = Vec::new();
for (idx, p) in self.pairs.iter().enumerate() {
let mut ob = ProverObligation {
selector_segment: format!("pair_{}", idx + 1),
description: None,
payload: ObligationPayload::Lec(base_lec.clone()),
};
if let ObligationPayload::Lec(lec) = &mut ob.payload {
lec.set_top(Side::Lhs, &p.lhs);
lec.set_top(Side::Rhs, &p.rhs);
}
obligations.push(ob);
}
{
let mut ob = ProverObligation {
selector_segment: "skeleton".to_string(),
description: None,
payload: ObligationPayload::Lec(base_lec.clone()),
};
if let ObligationPayload::Lec(lec) = &mut ob.payload {
for (idx, p) in self.pairs.iter().enumerate() {
let uf = format!("__uf_focus_{}", idx + 1);
lec.set_uf_alias(&p.lhs, &p.rhs, &uf);
}
}
obligations.push(ob);
}
Ok(obligations)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dslx_tactics::obligations::{
FileWithHistory, LecObligation, LecSide, ObligationPayload, ProverObligation, SourceFile,
};
fn base_obligation_with(sel: &str) -> ProverObligation {
let lhs = LecSide {
top_func: "f1".to_string(),
uf_map: std::collections::BTreeMap::new(),
file: FileWithHistory {
base_source: SourceFile::Text(String::new()),
edits: Vec::new(),
text: String::new(),
},
};
let rhs = LecSide {
top_func: "f2".to_string(),
uf_map: std::collections::BTreeMap::new(),
file: FileWithHistory {
base_source: SourceFile::Text(String::new()),
edits: Vec::new(),
text: String::new(),
},
};
ProverObligation {
selector_segment: sel.to_string(),
description: None,
payload: ObligationPayload::Lec(LecObligation { lhs, rhs }),
}
}
#[test]
fn builds_expected_focus_obligations() {
let base = base_obligation_with("root");
let t = FocusTactic {
pairs: vec![
FocusPair {
lhs: "a".to_string(),
rhs: "b".to_string(),
},
FocusPair {
lhs: "c".to_string(),
rhs: "d".to_string(),
},
],
};
let obs = t.apply(&base).expect("ok");
assert_eq!(obs.len(), 3);
let p1 = obs.iter().find(|o| o.selector_segment == "pair_1").unwrap();
let p1_lec = match &p1.payload {
ObligationPayload::Lec(lec) => lec,
_ => panic!("expected Lec payload"),
};
assert_eq!(p1_lec.lhs.top_func, "a");
assert_eq!(p1_lec.rhs.top_func, "b");
assert_eq!(p1_lec.lhs.file.edits.len(), 0);
assert_eq!(p1_lec.rhs.file.edits.len(), 0);
let p2 = obs.iter().find(|o| o.selector_segment == "pair_2").unwrap();
let p2_lec = match &p2.payload {
ObligationPayload::Lec(lec) => lec,
_ => panic!("expected Lec payload"),
};
assert_eq!(p2_lec.lhs.top_func, "c");
assert_eq!(p2_lec.rhs.top_func, "d");
let skel = obs
.iter()
.find(|o| o.selector_segment == "skeleton")
.unwrap();
let skel_lec = match &skel.payload {
ObligationPayload::Lec(lec) => lec,
_ => panic!("expected Lec payload"),
};
assert_eq!(skel_lec.lhs.top_func, "f1");
assert_eq!(skel_lec.rhs.top_func, "f2");
assert_eq!(
skel_lec.lhs.uf_map.get("a").map(|s| s.as_str()),
Some("__uf_focus_1")
);
assert_eq!(
skel_lec.rhs.uf_map.get("b").map(|s| s.as_str()),
Some("__uf_focus_1")
);
assert_eq!(
skel_lec.lhs.uf_map.get("c").map(|s| s.as_str()),
Some("__uf_focus_2")
);
assert_eq!(
skel_lec.rhs.uf_map.get("d").map(|s| s.as_str()),
Some("__uf_focus_2")
);
}
}