use crate::ast::{Expr, FnDef, Pattern, Spanned, VerifyLaw};
use crate::codegen::CodegenContext;
use crate::codegen::common::expr_to_dotted_name;
use std::collections::{BTreeMap, BTreeSet};
pub(crate) const HEAD: &str = "__cite_h";
pub(crate) const TAIL: &str = "__cite_t";
const UNFOLD_FUEL: usize = 8;
const POOL_CLOSURE_FUEL: usize = 4;
const POOL_SIZE_CAP: usize = 256;
pub(crate) fn law_rewrites_to_self(law: &VerifyLaw) -> bool {
let wildcards: BTreeSet<String> = law.givens.iter().map(|g| g.name.clone()).collect();
if wildcards.is_empty() {
return false;
}
let mut binds: BTreeMap<String, Spanned<Expr>> = BTreeMap::new();
!expr_eq(&law.lhs, &law.rhs) && match_expr(&law.lhs, &law.rhs, &wildcards, &mut binds)
}
pub(crate) struct Instantiation {
pub law_index: usize,
pub args: Vec<Spanned<Expr>>,
}
pub(crate) fn compute_instantiations(
law: &VerifyLaw,
ind_param: &str,
cited: &[&VerifyLaw],
ctx: &CodegenContext,
) -> Vec<Instantiation> {
if cited.is_empty() {
return Vec::new();
}
let list_param = ind_param;
let mod_scope = ctx.active_module_scope();
let mut cone: BTreeMap<String, &FnDef> = BTreeMap::new();
let mut seed = BTreeSet::new();
collect_calls(&law.lhs, &mut seed);
collect_calls(&law.rhs, &mut seed);
let mut frontier: Vec<String> = seed.into_iter().collect();
while let Some(name) = frontier.pop() {
if cone.contains_key(&name) {
continue;
}
if let Some(fd) = ctx.fn_def_by_name(&name, mod_scope.as_deref()) {
cone.insert(name.clone(), fd);
if let Some(body) = fd.body.tail_expr() {
let mut inner = BTreeSet::new();
collect_calls(body, &mut inner);
frontier.extend(inner);
}
}
}
let head = ident(HEAD);
let tail = ident(TAIL);
let consed = cons(head.clone(), tail.clone());
let lhs0 = unfold_fix(&subst(&law.lhs, list_param, &consed), &cone);
let rhs0 = unfold_fix(&subst(&law.rhs, list_param, &consed), &cone);
let mut rules: Vec<RewriteRule> = Vec::new();
rules.push(rule_at_tail(law, list_param));
for c in cited {
if !law_rewrites_to_self(c) {
rules.push(rewrite_rule(c));
}
}
let mut pool: Vec<Spanned<Expr>> = vec![lhs0.clone(), rhs0.clone()];
let mut frontier: Vec<Spanned<Expr>> = pool.clone();
'closure: for _ in 0..POOL_CLOSURE_FUEL {
let mut next: Vec<Spanned<Expr>> = Vec::new();
for base in &frontier {
for r in &rules {
let rewritten = unfold_fix(&apply_rule_all(base, r), &cone);
if !expr_eq(&rewritten, base)
&& !pool.iter().any(|p| expr_eq(p, &rewritten))
&& !next.iter().any(|p| expr_eq(p, &rewritten))
{
next.push(rewritten);
if pool.len() + next.len() >= POOL_SIZE_CAP {
pool.extend(next);
break 'closure;
}
}
}
}
if next.is_empty() {
break;
}
pool.extend(next.iter().cloned());
frontier = next;
}
let mut subterms: Vec<Spanned<Expr>> = Vec::new();
for t in &pool {
collect_subterms(t, &mut subterms);
}
let mut emitted: BTreeSet<String> = BTreeSet::new();
let mut out: Vec<Instantiation> = Vec::new();
for (law_index, c) in cited.iter().enumerate() {
let wildcards: BTreeSet<String> = c.givens.iter().map(|g| g.name.clone()).collect();
for sub in &subterms {
let mut binds: BTreeMap<String, Spanned<Expr>> = BTreeMap::new();
if match_expr(&c.lhs, sub, &wildcards, &mut binds) {
let mut args: Vec<Spanned<Expr>> = Vec::new();
let mut ok = true;
for g in &c.givens {
match binds.get(&g.name) {
Some(e) => args.push(e.clone()),
None => {
ok = false;
break;
}
}
}
if !ok {
continue;
}
let key = format!(
"{law_index}:{:?}",
args.iter().map(|a| &a.node).collect::<Vec<_>>()
);
if emitted.insert(key) {
out.push(Instantiation { law_index, args });
}
}
}
}
out
}
fn ident(name: &str) -> Spanned<Expr> {
Spanned::bare(Expr::Ident(name.to_string()))
}
pub(crate) fn ident_name(e: &Spanned<Expr>) -> Option<&str> {
match &e.node {
Expr::Ident(n) => Some(n),
Expr::Resolved { name, .. } => Some(name),
_ => None,
}
}
fn cons(head: Spanned<Expr>, tail: Spanned<Expr>) -> Spanned<Expr> {
Spanned::bare(Expr::FnCall(
Box::new(ident("List.concat")),
vec![Spanned::bare(Expr::List(vec![head])), tail],
))
}
fn as_cons(e: &Spanned<Expr>) -> Option<(&Spanned<Expr>, &Spanned<Expr>)> {
if let Expr::FnCall(callee, args) = &e.node
&& expr_to_dotted_name(&callee.node).as_deref() == Some("List.concat")
&& args.len() == 2
&& let Expr::List(items) = &args[0].node
&& items.len() == 1
{
return Some((&items[0], &args[1]));
}
None
}
fn collect_calls(e: &Spanned<Expr>, out: &mut BTreeSet<String>) {
if let Expr::FnCall(callee, _) = &e.node
&& let Some(n) = expr_to_dotted_name(&callee.node)
{
out.insert(n);
}
if let Expr::TailCall(data) = &e.node {
out.insert(data.target.clone());
}
for c in children(e) {
collect_calls(c, out);
}
}
fn children(e: &Spanned<Expr>) -> Vec<&Spanned<Expr>> {
match &e.node {
Expr::FnCall(callee, args) => {
let mut v = vec![callee.as_ref()];
v.extend(args.iter());
v
}
Expr::TailCall(data) => data.args.iter().collect(),
Expr::List(items) | Expr::Tuple(items) => items.iter().collect(),
Expr::Constructor(_, Some(inner)) => vec![inner.as_ref()],
Expr::Attr(inner, _) => vec![inner.as_ref()],
Expr::BinOp(_, l, r) => vec![l.as_ref(), r.as_ref()],
Expr::Neg(inner) | Expr::ErrorProp(inner) => vec![inner.as_ref()],
_ => Vec::new(),
}
}
fn map_children(
e: &Spanned<Expr>,
f: &mut impl FnMut(&Spanned<Expr>) -> Spanned<Expr>,
) -> Spanned<Expr> {
let node = match &e.node {
Expr::FnCall(callee, args) => {
Expr::FnCall(Box::new(f(callee)), args.iter().map(&mut *f).collect())
}
Expr::TailCall(data) => Expr::FnCall(
Box::new(ident(&data.target)),
data.args.iter().map(&mut *f).collect(),
),
Expr::List(items) => Expr::List(items.iter().map(&mut *f).collect()),
Expr::Tuple(items) => Expr::Tuple(items.iter().map(&mut *f).collect()),
Expr::Constructor(name, Some(inner)) => {
Expr::Constructor(name.clone(), Some(Box::new(f(inner))))
}
Expr::Attr(inner, field) => Expr::Attr(Box::new(f(inner)), field.clone()),
Expr::BinOp(op, l, r) => Expr::BinOp(*op, Box::new(f(l)), Box::new(f(r))),
Expr::Neg(inner) => Expr::Neg(Box::new(f(inner))),
Expr::ErrorProp(inner) => Expr::ErrorProp(Box::new(f(inner))),
other => other.clone(),
};
Spanned::bare(node)
}
fn collect_subterms(e: &Spanned<Expr>, out: &mut Vec<Spanned<Expr>>) {
out.push(e.clone());
for c in children(e) {
collect_subterms(c, out);
}
}
fn subst(e: &Spanned<Expr>, name: &str, replacement: &Spanned<Expr>) -> Spanned<Expr> {
if ident_name(e) == Some(name) {
return replacement.clone();
}
let mut f = |c: &Spanned<Expr>| subst(c, name, replacement);
map_children(e, &mut f)
}
fn subst_many(e: &Spanned<Expr>, map: &BTreeMap<String, Spanned<Expr>>) -> Spanned<Expr> {
if let Some(n) = ident_name(e)
&& let Some(rep) = map.get(n)
{
return rep.clone();
}
let mut f = |c: &Spanned<Expr>| subst_many(c, map);
map_children(e, &mut f)
}
fn unfold_fix(e: &Spanned<Expr>, cone: &BTreeMap<String, &FnDef>) -> Spanned<Expr> {
let mut cur = simplify_concat_nil(e);
for _ in 0..UNFOLD_FUEL {
let (next, changed) = unfold_once(&cur, cone);
cur = simplify_concat_nil(&next);
if !changed {
break;
}
}
cur
}
fn simplify_concat_nil(e: &Spanned<Expr>) -> Spanned<Expr> {
let mut f = |c: &Spanned<Expr>| simplify_concat_nil(c);
let mapped = map_children(e, &mut f);
if let Expr::FnCall(callee, args) = &mapped.node
&& expr_to_dotted_name(&callee.node).as_deref() == Some("List.concat")
&& args.len() == 2
{
let is_nil = |x: &Spanned<Expr>| matches!(&x.node, Expr::List(items) if items.is_empty());
if is_nil(&args[0]) {
return args[1].clone();
}
if is_nil(&args[1]) {
return args[0].clone();
}
}
mapped
}
fn unfold_once(e: &Spanned<Expr>, cone: &BTreeMap<String, &FnDef>) -> (Spanned<Expr>, bool) {
if let Expr::FnCall(callee, args) = &e.node
&& let Some(name) = expr_to_dotted_name(&callee.node)
&& let Some(fd) = cone.get(&name)
&& !args.is_empty()
&& let Some((h, t)) = as_cons(&args[0])
&& let Some(unfolded) = unfold_cons_arm(fd, h, t, &args[1..])
{
return (unfolded, true);
}
let mut changed = false;
let mut f = |c: &Spanned<Expr>| {
let (next, ch) = unfold_once(c, cone);
changed |= ch;
next
};
let mapped = map_children(e, &mut f);
(mapped, changed)
}
fn unfold_cons_arm(
fd: &FnDef,
h: &Spanned<Expr>,
t: &Spanned<Expr>,
rest: &[Spanned<Expr>],
) -> Option<Spanned<Expr>> {
let body = fd.body.tail_expr()?;
let Expr::Match { subject, arms } = &body.node else {
return None;
};
let first_param = fd.params.first()?.0.clone();
if ident_name(subject) != Some(first_param.as_str()) {
return None;
}
let arm = arms
.iter()
.find(|a| matches!(a.pattern, Pattern::Cons(_, _)))?;
let Pattern::Cons(hname, tname) = &arm.pattern else {
return None;
};
let mut map: BTreeMap<String, Spanned<Expr>> = BTreeMap::new();
map.insert(hname.clone(), h.clone());
map.insert(tname.clone(), t.clone());
for (param, arg) in fd.params.iter().skip(1).zip(rest.iter()) {
map.insert(param.0.clone(), arg.clone());
}
Some(subst_many(&arm.body, &map))
}
struct RewriteRule {
lhs: Spanned<Expr>,
rhs: Spanned<Expr>,
wildcards: BTreeSet<String>,
}
fn rule_at_tail(law: &VerifyLaw, list_param: &str) -> RewriteRule {
let tail = ident(TAIL);
let lhs = subst(&law.lhs, list_param, &tail);
let rhs = subst(&law.rhs, list_param, &tail);
let wildcards = law
.givens
.iter()
.map(|g| g.name.clone())
.filter(|n| n != list_param)
.collect();
RewriteRule {
lhs,
rhs,
wildcards,
}
}
fn rewrite_rule(law: &VerifyLaw) -> RewriteRule {
RewriteRule {
lhs: law.lhs.clone(),
rhs: law.rhs.clone(),
wildcards: law.givens.iter().map(|g| g.name.clone()).collect(),
}
}
fn apply_rule_all(e: &Spanned<Expr>, rule: &RewriteRule) -> Spanned<Expr> {
let mut binds: BTreeMap<String, Spanned<Expr>> = BTreeMap::new();
if match_expr(&rule.lhs, e, &rule.wildcards, &mut binds) {
return instantiate(&rule.rhs, &binds);
}
let mut f = |c: &Spanned<Expr>| apply_rule_all(c, rule);
map_children(e, &mut f)
}
fn instantiate(e: &Spanned<Expr>, binds: &BTreeMap<String, Spanned<Expr>>) -> Spanned<Expr> {
if let Some(n) = ident_name(e)
&& let Some(rep) = binds.get(n)
{
return rep.clone();
}
let mut f = |c: &Spanned<Expr>| instantiate(c, binds);
map_children(e, &mut f)
}
fn match_expr(
pattern: &Spanned<Expr>,
target: &Spanned<Expr>,
wildcards: &BTreeSet<String>,
binds: &mut BTreeMap<String, Spanned<Expr>>,
) -> bool {
if let Some(pn) = ident_name(pattern) {
if wildcards.contains(pn) {
return match binds.get(pn) {
Some(prev) => expr_eq(prev, target),
None => {
binds.insert(pn.to_string(), target.clone());
true
}
};
}
return ident_name(target) == Some(pn);
}
match (&pattern.node, &target.node) {
(Expr::Literal(a), Expr::Literal(b)) => a == b,
(Expr::FnCall(pc, pa), Expr::FnCall(tc, ta)) => {
expr_to_dotted_name(&pc.node) == expr_to_dotted_name(&tc.node)
&& pa.len() == ta.len()
&& pa
.iter()
.zip(ta.iter())
.all(|(p, t)| match_expr(p, t, wildcards, binds))
}
(Expr::List(pa), Expr::List(ta)) => {
pa.len() == ta.len()
&& pa
.iter()
.zip(ta.iter())
.all(|(p, t)| match_expr(p, t, wildcards, binds))
}
(Expr::Tuple(pa), Expr::Tuple(ta)) => {
pa.len() == ta.len()
&& pa
.iter()
.zip(ta.iter())
.all(|(p, t)| match_expr(p, t, wildcards, binds))
}
(Expr::Constructor(pn, pi), Expr::Constructor(tn, ti)) => {
pn == tn
&& match (pi, ti) {
(None, None) => true,
(Some(p), Some(t)) => match_expr(p, t, wildcards, binds),
_ => false,
}
}
(Expr::Attr(pi, pf), Expr::Attr(ti, tf)) => {
pf == tf && match_expr(pi, ti, wildcards, binds)
}
(Expr::BinOp(po, pl, pr), Expr::BinOp(to, tl, tr)) => {
po == to && match_expr(pl, tl, wildcards, binds) && match_expr(pr, tr, wildcards, binds)
}
_ => false,
}
}
fn expr_eq(a: &Spanned<Expr>, b: &Spanned<Expr>) -> bool {
let empty = BTreeSet::new();
let mut binds = BTreeMap::new();
match_expr(a, b, &empty, &mut binds)
}