use super::types::{BinderInfo, Expr, FVarId, Literal, Node, REBUILD_COST_MAX};
use crate::expr_util::{has_any_fvar, has_level_mvar, has_level_param, has_loose_bvars};
use crate::{Level, LevelMVarId, LevelView, Name};
fn lcg(state: &mut u64) -> u64 {
*state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
*state >> 33
}
fn random_level(state: &mut u64, depth: u32) -> Level {
if depth == 0 || lcg(state).is_multiple_of(3) {
return match lcg(state) % 4 {
0 => Level::param(Name::str("u")),
1 => Level::mvar(LevelMVarId(lcg(state) % 3)),
_ => Level::zero(),
};
}
match lcg(state) % 3 {
0 => Level::succ(random_level(state, depth - 1)),
1 => Level::max(
random_level(state, depth - 1),
random_level(state, depth - 1),
),
_ => Level::imax(
random_level(state, depth - 1),
random_level(state, depth - 1),
),
}
}
fn random_dag(state: &mut u64, steps: usize) -> Expr {
fn leaf(state: &mut u64) -> Expr {
match lcg(state) % 7 {
0 => Expr::BVar((lcg(state) % 3) as u32),
1 => Expr::Sort(random_level(state, 2)),
2 => Expr::FVar(FVarId(lcg(state) % 3)),
3 => Expr::Lit(Literal::nat(lcg(state) % 3)),
4 => Expr::Const(
Name::str("d"),
vec![random_level(state, 2), random_level(state, 1)],
),
_ => Expr::Const(Name::str("c"), Vec::new()),
}
}
let mut pool: Vec<Node> = (0..3).map(|_| Node::new(leaf(state))).collect();
for _ in 0..steps {
let pick = |state: &mut u64, pool: &Vec<Node>| -> Node {
if lcg(state).is_multiple_of(3) {
Node::new(leaf(state))
} else {
pool[(lcg(state) as usize) % pool.len()].clone()
}
};
let node = match lcg(state) % 6 {
0 | 1 => Expr::App(pick(state, &pool), pick(state, &pool)),
2 => Expr::Lam(
BinderInfo::Default,
Name::str("x"),
pick(state, &pool),
pick(state, &pool),
),
3 => Expr::Pi(
BinderInfo::Default,
Name::str("x"),
pick(state, &pool),
pick(state, &pool),
),
4 => Expr::Let(
Name::str("x"),
pick(state, &pool),
pick(state, &pool),
pick(state, &pool),
),
_ => Expr::Proj(Name::str("S"), 0, pick(state, &pool)),
};
pool.push(Node::new(node));
}
let last = pool.len() - 1;
(*pool[last]).clone()
}
fn level_has(level: &Level, param: bool) -> bool {
match level.view() {
LevelView::Zero => false,
LevelView::Param(_) => param,
LevelView::MVar(_) => !param,
LevelView::Succ(l) => level_has(l, param),
LevelView::Max(a, b) | LevelView::IMax(a, b) => level_has(a, param) || level_has(b, param),
}
}
fn any_leaf(e: &Expr, leaf: &dyn Fn(&Expr) -> bool) -> bool {
match e {
Expr::App(f, a) => any_leaf(f, leaf) || any_leaf(a, leaf),
Expr::Lam(_, _, ty, b) | Expr::Pi(_, _, ty, b) => any_leaf(ty, leaf) || any_leaf(b, leaf),
Expr::Let(_, ty, v, b) => any_leaf(ty, leaf) || any_leaf(v, leaf) || any_leaf(b, leaf),
Expr::Proj(_, _, s) => any_leaf(s, leaf),
other => leaf(other),
}
}
fn walk_has_fvar(e: &Expr) -> bool {
any_leaf(e, &|leaf| matches!(leaf, Expr::FVar(_)))
}
fn walk_has_level(e: &Expr, param: bool) -> bool {
any_leaf(e, &|leaf| match leaf {
Expr::Sort(l) => level_has(l, param),
Expr::Const(_, ls) => ls.iter().any(|l| level_has(l, param)),
_ => false,
})
}
fn walk_has_loose_bvar(e: &Expr, depth: u32) -> bool {
match e {
Expr::BVar(n) => *n >= depth,
Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => false,
Expr::App(f, a) => walk_has_loose_bvar(f, depth) || walk_has_loose_bvar(a, depth),
Expr::Lam(_, _, ty, b) | Expr::Pi(_, _, ty, b) => {
walk_has_loose_bvar(ty, depth) || walk_has_loose_bvar(b, depth + 1)
}
Expr::Let(_, ty, v, b) => {
walk_has_loose_bvar(ty, depth)
|| walk_has_loose_bvar(v, depth)
|| walk_has_loose_bvar(b, depth + 1)
}
Expr::Proj(_, _, s) => walk_has_loose_bvar(s, depth),
}
}
#[test]
fn the_flags_say_what_a_walk_of_the_term_finds() {
let mut state = 0x0fac_7500_0000_0001_u64;
let mut seen = [0usize; 8];
for i in 0..3000 {
let term = loop {
let term = random_dag(&mut state, 9);
if term.rebuild_cost() <= 20_000 {
break term;
}
};
let fvar = walk_has_fvar(&term);
let param = walk_has_level(&term, true);
let mvar = walk_has_level(&term, false);
assert_eq!(term.has_fvar(), fvar, "term {i}: {term:?}");
assert_eq!(term.has_level_param(), param, "term {i}: {term:?}");
assert_eq!(term.has_level_mvar(), mvar, "term {i}: {term:?}");
assert_eq!(has_any_fvar(&term), fvar);
assert_eq!(has_level_param(&term), param);
assert_eq!(has_level_mvar(&term), mvar);
assert_eq!(
has_loose_bvars(&term),
walk_has_loose_bvar(&term, 0),
"term {i}: {term:?}"
);
let node = Node::new(term.clone());
assert_eq!(node.has_fvar(), fvar);
assert_eq!(node.has_level_param(), param);
assert_eq!(node.rebuild_cost(), term.rebuild_cost());
seen[usize::from(fvar) | usize::from(param) << 1 | usize::from(mvar) << 2] += 1;
}
assert!(seen.iter().all(|&count| count > 0), "{seen:?}");
}
fn tower(leaf: Expr, height: u32) -> Expr {
let mut term = leaf;
for _ in 0..height {
let shared = Node::new(term);
term = Expr::App(shared.clone(), shared);
}
term
}
#[test]
fn a_saturated_size_leaves_the_flags_alone_and_the_flags_leave_the_size_alone() {
let u = Level::param(Name::str("u"));
let cases: [(Expr, [bool; 3]); 5] = [
(Expr::Const(Name::str("c"), vec![]), [false, false, false]),
(Expr::FVar(FVarId(3)), [true, false, false]),
(Expr::Sort(u.clone()), [false, true, false]),
(
Expr::Sort(Level::mvar(LevelMVarId(1))),
[false, false, true],
),
(
Expr::App(
Node::new(Expr::FVar(FVarId(0))),
Node::new(Expr::Const(
Name::str("d"),
vec![Level::max(u, Level::mvar(LevelMVarId(2)))],
)),
),
[true, true, true],
),
];
for (leaf, [fvar, param, mvar]) in cases {
let small = tower(leaf.clone(), 3);
assert_eq!(small.rebuild_cost(), 8 * leaf.rebuild_cost() + 7);
let big = tower(leaf.clone(), 60);
for term in [&small, &big] {
assert_eq!(term.has_fvar(), fvar, "{leaf:?}");
assert_eq!(term.has_level_param(), param, "{leaf:?}");
assert_eq!(term.has_level_mvar(), mvar, "{leaf:?}");
}
assert_eq!(big.rebuild_cost(), REBUILD_COST_MAX);
let node = Node::new(big);
assert_eq!(node.rebuild_cost(), REBUILD_COST_MAX);
assert_eq!(node.has_fvar(), fvar);
assert_eq!(node.has_level_param(), param);
}
}
#[cfg(feature = "term-depth")]
fn walk_depth(e: &Expr) -> u32 {
1 + match e {
Expr::App(f, a) => walk_depth(f).max(walk_depth(a)),
Expr::Lam(_, _, ty, b) | Expr::Pi(_, _, ty, b) => walk_depth(ty).max(walk_depth(b)),
Expr::Let(_, ty, v, b) => walk_depth(ty).max(walk_depth(v)).max(walk_depth(b)),
Expr::Proj(_, _, s) => walk_depth(s),
_ => 0,
}
}
#[cfg(feature = "term-depth")]
#[test]
fn the_cached_depth_is_the_longest_path_to_a_leaf() {
let mut state = 0x0dee_9000_0000_0001_u64;
let mut deepest = 0;
for i in 0..3000 {
let term = loop {
let term = random_dag(&mut state, 9);
if term.rebuild_cost() <= 20_000 {
break term;
}
};
let depth = walk_depth(&term);
assert_eq!(term.depth(), depth, "term {i}: {term:?}");
assert_eq!(Node::new(term).depth(), depth);
deepest = deepest.max(depth);
}
assert!(deepest >= 6, "the terms were not all shallow: {deepest}");
assert_eq!(Expr::BVar(7).depth(), 1);
assert_eq!(
Expr::Sort(Level::succ(Level::succ(Level::zero()))).depth(),
1
);
let big = tower(Expr::BVar(0), 60);
assert_eq!(big.depth(), 61);
assert_eq!(big.rebuild_cost(), REBUILD_COST_MAX);
let mut chain = Expr::Const(Name::str("c"), vec![]);
for k in 0..1000u32 {
chain = match k % 3 {
0 => Expr::Proj(Name::str("S"), 0, Node::new(chain)),
1 => Expr::Lam(
BinderInfo::Default,
Name::str("x"),
Node::new(Expr::Const(Name::str("c"), vec![])),
Node::new(chain),
),
_ => Expr::Let(
Name::str("x"),
Node::new(Expr::Const(Name::str("c"), vec![])),
Node::new(chain),
Node::new(Expr::BVar(0)),
),
};
}
assert_eq!(chain.depth(), 1001);
}
#[cfg(feature = "term-depth")]
#[test]
fn building_a_node_over_the_depth_limit_spends_the_budget() {
use crate::fuel;
fuel::set_budget(Some(1_000_000));
fuel::set_term_depth_limit(Some(40));
let mut term = Expr::Const(Name::str("c"), vec![]);
for _ in 0..39 {
term = Expr::App(
Node::new(Expr::Const(Name::str("g"), vec![])),
Node::new(term),
);
}
assert_eq!(term.depth(), 40);
let node = Node::new(term);
assert!(!fuel::term_depth_limit_hit() && !fuel::is_exhausted());
let over = Expr::App(Node::new(Expr::Const(Name::str("g"), vec![])), node);
assert_eq!(over.depth(), 41);
assert!(
!fuel::term_depth_limit_hit(),
"a term held by value is not a node yet"
);
let _held = Node::new(over);
assert!(fuel::term_depth_limit_hit() && fuel::is_exhausted());
fuel::set_budget(None);
fuel::set_term_depth_limit(None);
}
#[test]
fn is_shared_counts_the_owners_of_an_allocation() {
let node = Node::new(Expr::BVar(0));
assert!(!node.is_shared(), "one owner");
let second = node.clone();
assert!(node.is_shared() && second.is_shared(), "two owners");
drop(second);
assert!(!node.is_shared(), "one owner again");
let term = Expr::App(node.clone(), node.clone());
let Expr::App(f, a) = &term else {
panic!("an application");
};
assert!(f.is_shared() && a.is_shared());
let other = Node::new(Expr::BVar(0));
assert!(!other.is_shared());
assert!(!Node::ptr_eq(&other, &node));
}