use crate::{
error::{CompileError, Error, ErrorEmitted, Handler},
expr::{evaluate::Evaluator, BinaryOp, GeneratorKind, Immediate},
predicate::{Contract, Expr, ExprKey, VisitorKind},
span::{empty_span, Spanned},
types::{PrimitiveKind, Type},
};
use fxhash::FxHashMap;
use itertools::Itertools;
use std::collections::HashSet;
fn unroll_generator(
handler: &Handler,
contract: &mut Contract,
generator: Expr,
) -> Result<ExprKey, ErrorEmitted> {
let Expr::Generator {
kind,
gen_ranges,
conditions,
body,
span,
} = generator
else {
unreachable!("only Expr::Generator is passed here")
};
let mut indices = HashSet::new();
for range in &gen_ranges {
if !indices.insert(&range.0.name) {
let first_occurance = gen_ranges
.iter()
.find(|range_j| range.0.name == range_j.0.name)
.expect("Index guaranteed to be in gen_ranges");
return Err(handler.emit_err(Error::Compile {
error: CompileError::DuplicateGeneratorIndex {
name: range.0.name.clone(),
gen_kind: kind.to_string(),
span: range.0.span.clone(),
prev_span: first_occurance.0.span.clone(),
},
}));
}
}
let product_of_ranges = gen_ranges
.iter()
.map(|range| {
match range.1.get(contract) {
Expr::Range { lb, ub, .. } => {
let lb = lb.get(contract);
let ub = ub.get(contract);
match (lb, ub) {
(
Expr::Immediate {
value: Immediate::Int(lb),
..
},
Expr::Immediate {
value: Immediate::Int(ub),
..
},
) => Ok(*lb..=*ub),
_ if !matches!(
lb,
Expr::Immediate {
value: Immediate::Int(_),
..
}
) =>
{
Err(handler.emit_err(Error::Compile {
error: CompileError::InvalidGeneratorIndexBound {
name: range.0.name.clone(),
gen_kind: kind.to_string(),
span: lb.span().clone(),
},
}))
}
_ => {
Err(handler.emit_err(Error::Compile {
error: CompileError::InvalidGeneratorIndexBound {
name: range.0.name.clone(),
gen_kind: kind.to_string(),
span: ub.span().clone(),
},
}))
}
}
}
_ => panic!("guaranteed by the parser"),
}
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.multi_cartesian_product();
let indices = gen_ranges
.into_iter()
.map(|(index, _)| index)
.collect::<Vec<_>>();
let bool_ty = Type::Primitive {
kind: PrimitiveKind::Bool,
span: span.clone(),
};
let mut unrolled = contract.exprs.insert(
Expr::Immediate {
value: Immediate::Bool(match kind {
GeneratorKind::ForAll => true,
GeneratorKind::Exists => false,
}),
span: span.clone(),
},
bool_ty.clone(),
);
for values in product_of_ranges {
let values_map = indices
.iter()
.zip(values.iter())
.map(|(index, int_index)| ("::".to_owned() + &index.name, Immediate::Int(*int_index)))
.collect::<FxHashMap<_, _>>();
let mut satisfied = true;
{
let evaluator = Evaluator::from_values(contract, values_map.clone());
for condition in &conditions {
match evaluator.evaluate(*condition, handler, contract)? {
Immediate::Bool(false) => {
satisfied = false;
break;
}
Immediate::Bool(true) => {}
_ => {
return Err(handler.emit_internal_err(
"type error: boolean expression expected",
empty_span(),
))
}
}
}
}
if satisfied {
let rhs = body.plug_in(contract, &values_map);
unrolled = contract.exprs.insert(
Expr::BinaryOp {
op: match kind {
GeneratorKind::ForAll => BinaryOp::LogicalAnd,
GeneratorKind::Exists => BinaryOp::LogicalOr,
},
lhs: unrolled,
rhs,
span: span.clone(),
},
bool_ty.clone(),
);
}
}
Ok(unrolled)
}
pub(crate) fn unroll_generators(
handler: &Handler,
contract: &mut Contract,
) -> Result<(), ErrorEmitted> {
for pred_key in contract.preds.keys().collect::<Vec<_>>() {
let mut generators = Vec::new();
contract.visitor(
pred_key,
VisitorKind::DepthFirstChildrenBeforeParents,
|expr_key: ExprKey, expr: &Expr| {
if matches!(expr, Expr::Generator { .. }) {
generators.push(expr_key);
}
},
);
for old_generator_key in generators {
let generator = old_generator_key.get(contract).clone();
if let Ok(unrolled_generator_key) = unroll_generator(handler, contract, generator) {
contract.replace_exprs(Some(pred_key), old_generator_key, unrolled_generator_key);
}
}
if handler.has_errors() {
return Err(handler.cancel());
}
}
Ok(())
}