use core::{fmt, ops::Index};
#[cfg(feature = "mem_dbg")]
use mem_dbg::{MemDbg, MemSize};
use nonmax::NonMaxU64;
use typed_index_collections::TiSlice;
use crate::{
items::*, parsers::z3::VersionInfo, FxHashMap, IString, NonMaxU32, StringTable, TiVec,
};
use super::{
cdcl::Literals,
egraph::EGraph,
inst::{InstData, Insts},
inter_line::InterLine,
smt2::EventLog,
stack::{HasFrame, Stack},
synthetic::{AnyTerm, SynthIdx, SynthTerms},
terms::Terms,
};
#[cfg_attr(feature = "mem_dbg", derive(MemSize, MemDbg))]
#[derive(Debug)]
pub struct Z3Parser {
pub(crate) version_info: VersionInfo,
pub(crate) push_count: u32,
pub(crate) terms: Terms,
pub(crate) synth_terms: SynthTerms,
pub(crate) quantifiers: TiVec<QuantIdx, Quantifier>,
pub(crate) insts: Insts,
pub(crate) egraph: EGraph,
pub(crate) stack: Stack,
pub lits: Literals,
pub strings: StringTable,
pub events: EventLog,
pub(super) comm: InterLine,
}
pub type AstSize = core::result::Result<Option<NonMaxU64>, TermIdx>;
#[derive(Default)]
pub struct ParseErrors {
pub match_error_count: usize,
pub match_count: usize,
pub trans_error_count: usize,
pub trans_count: usize,
}
impl fmt::Display for ParseErrors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.match_error_count == 0 && self.trans_error_count == 0 {
return Ok(());
}
write!(f, " Errors:")?;
if self.match_error_count != 0 {
write!(
f,
" {} match ({:.1}%)",
self.match_error_count,
100. * self.match_error_count as f64 / self.match_count as f64
)?;
}
if self.trans_error_count != 0 {
write!(
f,
" {} eq ({:.1}%)",
self.trans_error_count,
100. * self.trans_error_count as f64 / self.trans_count as f64
)?;
}
Ok(())
}
}
impl Z3Parser {
pub fn errors(&self) -> ParseErrors {
let mut errors = ParseErrors::default();
for match_ in self.insts.matches.iter() {
if match_.kind.pattern().is_none() {
continue;
}
debug_assert!(!match_.blamed.is_empty());
errors.match_count += 1;
if match_.blamed.iter().any(|b| !b.is_complete()) {
errors.match_error_count += 1;
}
}
errors.trans_count = self.egraph.equalities.transitive.len();
errors.trans_error_count = self
.egraph
.equalities
.transitive
.iter()
.filter(|eq| eq.error_from().is_some())
.count();
errors
}
pub fn meaning(&self, tidx: TermIdx) -> Option<&Meaning> {
self.terms.meaning(tidx)
}
pub fn from_to(&self, eq: EqTransIdx) -> (ENodeIdx, ENodeIdx) {
self.egraph.equalities.from_to(eq)
}
pub fn quant_count_incl_theory_solving(&self) -> (usize, bool) {
(self.quantifiers.len(), self.insts.has_theory_solving_inst())
}
pub fn quantifiers(&self) -> &TiSlice<QuantIdx, Quantifier> {
&self.quantifiers
}
pub fn instantiations(&self) -> &TiSlice<InstIdx, Instantiation> {
&self.insts.insts
}
pub fn instantiations_data(&self) -> impl Iterator<Item = InstData<'_>> + '_ {
self.insts.instantiations()
}
pub fn terms(&self) -> &TiSlice<TermIdx, Term> {
self.terms.app_terms.terms()
}
pub fn proofs(&self) -> &TiSlice<ProofIdx, ProofStep> {
self.terms.proof_terms.terms()
}
pub fn cdcls(&self) -> &TiSlice<CdclIdx, Cdcl> {
self.lits.cdcl.cdcls()
}
pub fn quantifier_body(&self, qidx: QuantIdx) -> Option<TermIdx> {
let children = &self[self[qidx].term].child_ids;
children.last().copied()
}
pub fn patterns(&self, q: QuantIdx) -> Option<&TiSlice<PatternIdx, TermIdx>> {
let child_ids = &self[self[q].term].child_ids;
child_ids
.len()
.checked_sub(1)
.map(|len| &child_ids[..len])
.map(TiSlice::from_ref)
}
pub fn get_inst(&self, iidx: InstIdx) -> InstData<'_> {
self.insts.get_inst(iidx)
}
pub fn get_instantiation_body(&self, iidx: InstIdx) -> Option<TermIdx> {
self.terms.get_instantiation_body(self[iidx].kind)
}
pub fn as_tidx(&self, sidx: SynthIdx) -> Option<TermIdx> {
self.synth_terms.as_tidx(sidx)
}
pub fn get_pattern(&self, qpat: QuantPat) -> Option<TermIdx> {
qpat.pat.map(|pat| self.patterns(qpat.quant).unwrap()[pat])
}
pub fn get_pattern_term(&self, qpat: QuantPat) -> Option<&Term> {
self.get_pattern(qpat).map(|tidx| &self[tidx])
}
pub fn get_frame(&self, idx: impl HasFrame) -> &StackFrame {
&self.stack[idx.frame(self)]
}
pub fn proves_false(&self, pidx: ProofIdx) -> bool {
let result = &self[self[pidx].result];
result.child_ids.is_empty() && result.app_name().is_some_and(|name| &self[name] == "false")
}
pub fn ast_size(&self, tidx: TermIdx, cached: &mut FxHashMap<TermIdx, AstSize>) -> AstSize {
fn children(tidx: TermIdx, term: &Term) -> core::result::Result<&[TermIdx], TermIdx> {
match term.kind() {
TermKind::Var(_) => Err(tidx),
TermKind::App(_) => Ok(&*term.child_ids),
TermKind::Quant(_) => {
let body = term.child_ids.last().map(core::slice::from_ref);
Ok(body.unwrap_or_default())
}
}
}
*self.terms.app_terms.ast_walk_cached(
tidx,
(),
cached,
|tidx, term, ()| (children(tidx, term).ok().unwrap_or_default(), ()),
|tidx, term, cached, ()| {
let children = children(tidx, term)?;
let mut size = NonMaxU64::ONE;
for child in children {
let Some(child) = cached[child]? else {
return Ok(None);
};
let child = NonMaxU64::new(size.get().saturating_add(child.get()));
let Some(child) = child else {
return Ok(None);
};
size = child;
}
Ok(Some(size))
},
)
}
pub fn inst_ast_size(
&self,
iidx: InstIdx,
cached: &mut FxHashMap<TermIdx, AstSize>,
) -> Vec<std::option::Option<NonMaxU64>> {
let bound_terms = self[self[iidx].match_]
.kind
.bound_terms(|e| self[e].owner, |t| t);
IntoIterator::into_iter(bound_terms)
.map(|bt| self.ast_size(bt, cached).unwrap())
.collect()
}
pub fn new_quant_pat_vec<T>(&self, f: impl Fn(QuantPat) -> T) -> QuantPatVec<T> {
QuantPatVec(
self.quantifiers()
.keys()
.map(|quant| {
let mbqi = f(QuantPat { quant, pat: None });
let pats = self.patterns(quant).into_iter().flat_map(|p| p.keys());
let pats = pats
.map(|pat| {
f(QuantPat {
quant,
pat: Some(pat),
})
})
.collect();
PatVec { mbqi, pats }
})
.collect(),
)
}
pub(super) fn bound(&self, match_: &MatchKind, qvar: NonMaxU32) -> Option<TermIdx> {
match_.bound_term(|e| &self[e], qvar)
}
fn named_proofs(&self) -> &FxHashMap<ProofIdx, Option<ProofIdx>> {
&self.terms.named_asserts.named
}
pub fn named_assert_to_variable(&self, pidx: ProofIdx) -> Option<ProofIdx> {
self.named_proofs().get(&pidx).and_then(|opt| *opt)
}
pub fn is_named_variable(&self, pidx: ProofIdx) -> bool {
self.named_proofs()
.get(&pidx)
.is_some_and(|opt| opt.is_none())
}
}
impl Index<TermIdx> for Z3Parser {
type Output = Term;
fn index(&self, idx: TermIdx) -> &Self::Output {
&self.terms[idx]
}
}
impl Index<SynthIdx> for Z3Parser {
type Output = AnyTerm;
fn index(&self, idx: SynthIdx) -> &Self::Output {
self.synth_terms.index(&self.terms, idx)
}
}
impl Index<ProofIdx> for Z3Parser {
type Output = ProofStep;
fn index(&self, idx: ProofIdx) -> &Self::Output {
&self.terms[idx]
}
}
impl Index<QuantIdx> for Z3Parser {
type Output = Quantifier;
fn index(&self, idx: QuantIdx) -> &Self::Output {
&self.quantifiers[idx]
}
}
impl Index<ENodeIdx> for Z3Parser {
type Output = ENode;
fn index(&self, idx: ENodeIdx) -> &Self::Output {
&self.egraph[idx]
}
}
impl Index<InstIdx> for Z3Parser {
type Output = Instantiation;
fn index(&self, idx: InstIdx) -> &Self::Output {
&self.insts[idx]
}
}
impl Index<MatchIdx> for Z3Parser {
type Output = Match;
fn index(&self, idx: MatchIdx) -> &Self::Output {
&self.insts[idx]
}
}
impl Index<EqGivenIdx> for Z3Parser {
type Output = EqualityExpl;
fn index(&self, idx: EqGivenIdx) -> &Self::Output {
&self.egraph.equalities.given[idx]
}
}
impl Index<EqTransIdx> for Z3Parser {
type Output = TransitiveExpl;
fn index(&self, idx: EqTransIdx) -> &Self::Output {
&self.egraph.equalities.transitive[idx]
}
}
impl Index<StackIdx> for Z3Parser {
type Output = StackFrame;
fn index(&self, idx: StackIdx) -> &Self::Output {
&self.stack[idx]
}
}
impl Index<LitIdx> for Z3Parser {
type Output = Literal;
fn index(&self, idx: LitIdx) -> &Self::Output {
&self.lits[idx]
}
}
impl Index<CdclIdx> for Z3Parser {
type Output = Cdcl;
fn index(&self, idx: CdclIdx) -> &Self::Output {
&self.lits.cdcl[idx]
}
}
impl Index<IString> for Z3Parser {
type Output = str;
fn index(&self, idx: IString) -> &Self::Output {
&self.strings[*idx]
}
}
#[test]
fn ast_size() {
use rand::seq::SliceRandom;
use rand::SeedableRng;
let correct: TiVec<TermIdx, AstSize> =
serde_json::from_str(include_str!("../../../tests/data/ast_size.json")).unwrap();
let parser = crate::test_parser();
let mut idxs: Vec<_> = parser.terms().keys().collect();
for i in 0..10 {
let mut rng = rand::rngs::StdRng::seed_from_u64(i);
idxs.shuffle(&mut rng);
let mut cached = FxHashMap::default();
for &idx in &idxs {
let size = parser.ast_size(idx, &mut cached);
assert_eq!(size, correct[idx], "idx {idx}");
}
}
}