knave 0.12.1

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use rustc_hash::FxHashSet;

use crate::core::BindingId;

#[derive(Default)]
pub struct Refusals {
    refused: FxHashSet<BindingId>,
    followings: Vec<Following>,
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Following {
    leader: BindingId,
    follower: BindingId,
}

impl Refusals {
    pub fn refuse(&mut self, binding: BindingId) {
        self.refused.insert(binding);
    }

    pub fn follow(&mut self, follower: BindingId, leader: BindingId) {
        self.followings.push(Following { leader, follower });
    }

    pub fn spread(mut self) -> FxHashSet<BindingId> {
        self.followings.sort_unstable();
        let mut pending: Vec<BindingId> = self.refused.iter().copied().collect();
        while let Some(leader) = pending.pop() {
            for follower in Self::followers(&self.followings, leader) {
                if self.refused.insert(follower) {
                    pending.push(follower);
                }
            }
        }
        self.refused
    }

    fn followers(sorted: &[Following], leader: BindingId) -> impl Iterator<Item = BindingId> + '_ {
        let first = sorted.partition_point(|following| following.leader < leader);
        sorted[first..]
            .iter()
            .take_while(move |following| following.leader == leader)
            .map(|following| following.follower)
    }
}

#[cfg(test)]
mod tests {
    use rustc_hash::FxHashSet;

    use super::Refusals;
    use crate::core::{BindingArena, BindingId, BindingInfo, BindingKind};

    struct RefusalFixture {
        bindings: BindingArena,
        refusals: Refusals,
    }

    impl RefusalFixture {
        fn new() -> Self {
            Self {
                bindings: BindingArena::default(),
                refusals: Refusals::default(),
            }
        }

        fn parameter(&mut self, name: &str) -> BindingId {
            self.bindings
                .add(BindingInfo::new(name.into(), BindingKind::Local, None))
        }

        fn spread(self) -> FxHashSet<BindingId> {
            self.refusals.spread()
        }
    }

    #[test]
    fn a_refusal_reaches_every_follower_down_a_chain() {
        let mut fixture = RefusalFixture::new();
        let inner = fixture.parameter("inner");
        let middle = fixture.parameter("middle");
        let outer = fixture.parameter("outer");
        fixture.refusals.follow(outer, middle);
        fixture.refusals.follow(middle, inner);
        fixture.refusals.refuse(inner);
        assert_eq!(
            fixture.spread(),
            FxHashSet::from_iter([inner, middle, outer])
        );
    }

    #[test]
    fn a_cycle_that_nothing_refuses_keeps_every_member() {
        let mut fixture = RefusalFixture::new();
        let even = fixture.parameter("even");
        let odd = fixture.parameter("odd");
        fixture.refusals.follow(even, odd);
        fixture.refusals.follow(odd, even);
        assert_eq!(fixture.spread(), FxHashSet::default());
    }

    #[test]
    fn a_refusal_that_enters_a_cycle_refuses_every_member() {
        let mut fixture = RefusalFixture::new();
        let pad = fixture.parameter("pad");
        let fill = fixture.parameter("fill");
        let push = fixture.parameter("push");
        fixture.refusals.follow(pad, fill);
        fixture.refusals.follow(fill, pad);
        fixture.refusals.follow(pad, push);
        fixture.refusals.refuse(push);
        assert_eq!(fixture.spread(), FxHashSet::from_iter([pad, fill, push]));
    }

    #[test]
    fn a_leader_that_is_not_refused_keeps_its_follower() {
        let mut fixture = RefusalFixture::new();
        let refused = fixture.parameter("refused");
        let leader = fixture.parameter("leader");
        let follower = fixture.parameter("follower");
        fixture.refusals.follow(follower, leader);
        fixture.refusals.refuse(refused);
        assert_eq!(fixture.spread(), FxHashSet::from_iter([refused]));
    }
}