knave 0.14.22

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use super::dropped_definitions::DroppedDefinitions;
use super::dropped_documents::DroppedDocuments;
use super::set_aside_drop::SetAsideDrop;
use super::set_aside_source::SetAsideSource;
use crate::expand::{BoundName, InputTerms};
use crate::source::{SourceId, Span};

#[derive(Clone)]
pub struct BufferedInput {
    name: String,
    span: Span,
    terms: InputTerms,
}

#[derive(Clone, Default)]
pub struct DefinitionBuffer {
    inputs: Vec<BufferedInput>,
}

#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub struct Replacement {
    replaced: Vec<usize>,
}

impl BufferedInput {
    pub fn new(name: String, span: Span, terms: InputTerms) -> Self {
        Self { name, span, terms }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn names(&self) -> &[BoundName] {
        self.terms.bound()
    }

    pub fn source(&self) -> SourceId {
        self.span.source()
    }

    pub fn binds(&self, name: &BoundName) -> bool {
        self.terms.binds(name)
    }
}

impl DefinitionBuffer {
    pub fn inputs(&self) -> &[BufferedInput] {
        &self.inputs
    }

    pub fn sources(&self) -> Vec<SourceId> {
        self.sources_kept_by(&Replacement::default())
    }

    pub fn spans(&self) -> Vec<Span> {
        self.inputs.iter().map(|input| input.span).collect()
    }

    pub fn bound_names(&self) -> impl Iterator<Item = &BoundName> {
        self.inputs.iter().flat_map(|input| input.names())
    }

    pub fn sources_kept_by(&self, replacement: &Replacement) -> Vec<SourceId> {
        self.inputs
            .iter()
            .enumerate()
            .filter(|(position, _)| !replacement.replaces(*position))
            .map(|(_, input)| input.source())
            .collect()
    }

    pub fn widened(&self, entered: &InputTerms, replacement: &Replacement) -> Option<Replacement> {
        let sharing: Vec<usize> = self
            .inputs
            .iter()
            .enumerate()
            .filter(|(position, input)| {
                !replacement.replaces(*position) && input.terms.shares_a_term_with(entered)
            })
            .map(|(position, _)| position)
            .collect();
        if sharing.is_empty() {
            return None;
        }
        let mut replaced = replacement.replaced.clone();
        replaced.extend(sharing);
        Some(Replacement { replaced })
    }

    pub fn dropped_by(
        &self,
        replacement: &Replacement,
        entered: &BufferedInput,
    ) -> Vec<Box<dyn SetAsideDrop>> {
        replacement
            .replaced
            .iter()
            .filter_map(|position| Self::drop_of(&self.inputs[*position], entered))
            .collect()
    }

    pub fn replaced(&self, replacement: &Replacement, input: BufferedInput) -> Self {
        let place = replacement.first();
        let mut entered = Some(input);
        let mut inputs = Vec::with_capacity(self.inputs.len() + 1);
        for (position, earlier) in self.inputs.iter().enumerate() {
            if Some(position) == place {
                inputs.extend(entered.take());
            }
            if !replacement.replaces(position) {
                inputs.push(earlier.clone());
            }
        }
        inputs.extend(entered);
        Self { inputs }
    }

    pub fn renamed(&self, names: Vec<Vec<BoundName>>) -> Self {
        let inputs = self
            .inputs
            .iter()
            .zip(names)
            .map(|(input, names)| BufferedInput {
                terms: input.terms.rebound(names),
                ..input.clone()
            })
            .collect();
        Self { inputs }
    }

    pub fn without(&self, set_aside: &[SourceId]) -> Self {
        let inputs = self
            .inputs
            .iter()
            .filter(|input| !set_aside.contains(&input.source()))
            .cloned()
            .collect();
        Self { inputs }
    }

    pub fn input(&self, source: SourceId) -> Option<&BufferedInput> {
        self.inputs.iter().find(|input| input.source() == source)
    }

    fn drop_of(earlier: &BufferedInput, entered: &BufferedInput) -> Option<Box<dyn SetAsideDrop>> {
        let source = SetAsideSource {
            name: earlier.name().to_string(),
            span: earlier.span,
        };
        let documents =
            DroppedDocuments::left_behind(source.clone(), &earlier.terms, &entered.terms);
        Self::boxed(documents).or_else(|| {
            Self::boxed(DroppedDefinitions::left_behind(
                source,
                &earlier.terms,
                &entered.terms,
            ))
        })
    }

    fn boxed(drop: Option<impl SetAsideDrop + 'static>) -> Option<Box<dyn SetAsideDrop>> {
        drop.map(|drop| Box::new(drop) as Box<dyn SetAsideDrop>)
    }
}

impl Replacement {
    fn replaces(&self, position: usize) -> bool {
        self.replaced.contains(&position)
    }

    fn first(&self) -> Option<usize> {
        self.replaced.iter().min().copied()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::Namespace;

    struct BufferFixture;

    impl BufferFixture {
        const SETTER: &str = "<input 1>";

        fn input(source: usize, names: &[&str]) -> BufferedInput {
            let source = SourceId::new(source);
            let names = names
                .iter()
                .map(|name| BoundName {
                    name: (*name).into(),
                    namespace: Namespace::Value,
                    span: Span::new(source, 0..0),
                })
                .collect();
            Self::entered(source, InputTerms::binding(names))
        }

        fn document(source: usize, term: &str) -> BufferedInput {
            Self::entered(SourceId::new(source), Self::documenting(term))
        }

        fn entered(source: SourceId, terms: InputTerms) -> BufferedInput {
            BufferedInput::new(
                format!("<input {}>", source.index()),
                Span::new(source, 0..1),
                terms,
            )
        }

        fn documenting(term: &str) -> InputTerms {
            InputTerms::documenting(Vec::new(), vec![term.into()])
        }

        fn terms(names: &[BoundName]) -> InputTerms {
            InputTerms::binding(names.to_vec())
        }

        fn documenting_sources(buffer: &DefinitionBuffer, term: &str) -> Vec<usize> {
            let documenting = Self::documenting(term);
            buffer
                .inputs
                .iter()
                .filter(|input| input.terms.shares_a_term_with(&documenting))
                .map(|input| input.source().index())
                .collect()
        }

        fn buffer(inputs: &[(usize, &[&str])]) -> DefinitionBuffer {
            DefinitionBuffer {
                inputs: inputs
                    .iter()
                    .map(|(source, names)| Self::input(*source, names))
                    .collect(),
            }
        }

        fn sources(buffer: &DefinitionBuffer) -> Vec<usize> {
            buffer.sources().into_iter().map(SourceId::index).collect()
        }
    }

    #[test]
    fn a_replacing_input_takes_the_place_of_the_first_input_it_replaces() {
        let buffer = BufferFixture::buffer(&[(0, &["a"]), (1, &["b"]), (2, &["c"])]);
        let entered = BufferFixture::input(3, &["b"]);
        let replacement = buffer
            .widened(
                &BufferFixture::terms(entered.names()),
                &Replacement::default(),
            )
            .expect("`b` is defined earlier");
        let replaced = buffer.replaced(&replacement, entered);
        assert_eq!(BufferFixture::sources(&replaced), [0, 3, 2]);
    }

    #[test]
    fn an_input_that_replaces_two_inputs_takes_the_place_of_the_earlier() {
        let buffer = BufferFixture::buffer(&[(0, &["a"]), (1, &["b"]), (2, &["c"])]);
        let entered = BufferFixture::input(3, &["a", "c"]);
        let first = buffer
            .widened(
                &BufferFixture::terms(&entered.names()[..1]),
                &Replacement::default(),
            )
            .expect("`a` is defined earlier");
        let both = buffer
            .widened(&BufferFixture::terms(entered.names()), &first)
            .expect("`c` is defined earlier");
        let replaced = buffer.replaced(&both, entered);
        assert_eq!(BufferFixture::sources(&replaced), [3, 1]);
    }

    #[test]
    fn a_new_input_joins_the_end() {
        let buffer = BufferFixture::buffer(&[(0, &["a"]), (1, &["b"])]);
        let replaced = buffer.replaced(&Replacement::default(), BufferFixture::input(2, &["c"]));
        assert_eq!(BufferFixture::sources(&replaced), [0, 1, 2]);
    }

    #[test]
    fn a_replacement_widens_only_by_inputs_it_does_not_hold() {
        let buffer = BufferFixture::buffer(&[(0, &["a"]), (1, &["b"])]);
        let names = BufferFixture::input(2, &["a", "b"]).names().to_vec();
        let first = buffer
            .widened(&BufferFixture::terms(&names[..1]), &Replacement::default())
            .expect("`a` is defined earlier");
        let second = buffer
            .widened(&BufferFixture::terms(&names), &first)
            .expect("`b` is defined earlier");
        assert_eq!(buffer.widened(&BufferFixture::terms(&names), &second), None);
        assert_eq!(buffer.sources_kept_by(&second), []);
    }

    #[test]
    fn a_replacement_that_keeps_every_name_drops_nothing() {
        let buffer = BufferFixture::buffer(&[(0, &["a", "b"])]);
        let entered = BufferFixture::input(1, &["b", "a"]);
        let replacement = buffer
            .widened(
                &BufferFixture::terms(entered.names()),
                &Replacement::default(),
            )
            .expect("`a` is defined earlier");
        assert!(buffer.dropped_by(&replacement, &entered).is_empty());
    }

    #[test]
    fn a_replacement_that_loses_a_name_drops_it() {
        let buffer = BufferFixture::buffer(&[(0, &["a", "b"])]);
        let entered = BufferFixture::input(1, &["b"]);
        let replacement = buffer
            .widened(
                &BufferFixture::terms(entered.names()),
                &Replacement::default(),
            )
            .expect("`b` is defined earlier");
        let dropped = buffer.dropped_by(&replacement, &entered);
        let warning = dropped[0].warning(BufferFixture::SETTER);
        assert_eq!(warning.title(), "set aside `a` after `<input 1>`");
        assert_eq!(warning.notes(), ["`b` replaced the input that defined `a`"]);
    }

    #[test]
    fn a_document_that_leaves_a_term_behind_drops_it() {
        let earlier = BufferFixture::entered(
            SourceId::new(0),
            InputTerms::documenting(Vec::new(), vec!["g".into(), "k".into()]),
        );
        let buffer = DefinitionBuffer {
            inputs: vec![earlier],
        };
        let entered = BufferFixture::document(1, "g");
        let replacement = buffer
            .widened(&BufferFixture::documenting("g"), &Replacement::default())
            .expect("`g` is documented earlier");
        let dropped = buffer.dropped_by(&replacement, &entered);
        let warning = dropped[0].warning(BufferFixture::SETTER);
        assert_eq!(warning.title(), "set aside `k` after `<input 1>`");
    }

    #[test]
    fn a_renamed_buffer_takes_each_inputs_new_names() {
        let buffer = BufferFixture::buffer(&[(0, &["a"]), (1, &["b"])]);
        let renamed = buffer.renamed(vec![
            Vec::new(),
            BufferFixture::input(1, &["c"]).names().to_vec(),
        ]);
        let names: Vec<&str> = renamed.bound_names().map(|name| &*name.name).collect();
        assert_eq!(names, ["c"]);
    }

    #[test]
    fn a_buffer_without_an_input_keeps_the_others_in_order() {
        let buffer = BufferFixture::buffer(&[(0, &["a"]), (1, &["b"]), (2, &["c"])]);
        let kept = buffer.without(&[SourceId::new(1)]);
        assert_eq!(BufferFixture::sources(&kept), [0, 2]);
        assert!(kept.input(SourceId::new(1)).is_none());
        assert!(kept.input(SourceId::new(2)).is_some());
    }

    #[test]
    fn a_document_replaces_an_earlier_document_of_the_same_term() {
        let buffer = DefinitionBuffer {
            inputs: vec![
                BufferFixture::input(0, &["g"]),
                BufferFixture::document(1, "g"),
            ],
        };
        let later = BufferFixture::document(2, "g");
        let replacement = buffer
            .widened(&BufferFixture::documenting("g"), &Replacement::default())
            .expect("`g` is documented earlier");
        let replaced = buffer.replaced(&replacement, later);
        assert_eq!(BufferFixture::sources(&replaced), [0, 2]);
        assert_eq!(BufferFixture::documenting_sources(&replaced, "g"), [2]);
    }

    #[test]
    fn a_document_never_replaces_the_input_that_defines_its_term() {
        let buffer = BufferFixture::buffer(&[(0, &["g"])]);
        let terms = BufferFixture::documenting("g");
        assert_eq!(buffer.widened(&terms, &Replacement::default()), None);
    }
}