knave-expand 0.13.2

The reading, scoping and expansion stages of knave
Documentation
use std::rc::Rc;

use crate::core::Namespace;
use crate::source::{SourceId, Span};

#[derive(Default)]
pub(super) struct RecordedInputs {
    inputs: Vec<Span>,
    bound: Vec<BoundName>,
    loaded: Vec<LoadedModule>,
    registering: Option<Span>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BoundName {
    pub name: Rc<str>,
    pub namespace: Namespace,
    pub span: Span,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LoadedModule {
    pub source: SourceId,
    pub input: Span,
}

impl BoundName {
    pub fn names_the_same(&self, other: &BoundName) -> bool {
        self.name == other.name && self.namespace == other.namespace
    }
}

impl RecordedInputs {
    pub(super) fn new(inputs: Vec<Span>) -> Self {
        Self {
            inputs,
            ..Self::default()
        }
    }

    pub(super) fn note_bound(&mut self, span: Span, name: impl FnOnce() -> Option<BoundName>) {
        if self.surrounds(span) {
            self.bound.extend(name());
        }
    }

    pub(super) fn enter(&mut self, written: Span) -> Option<Span> {
        let outer = self.registering;
        if self.inputs.contains(&written) {
            self.registering = Some(written);
        }
        outer
    }

    pub(super) fn leave(&mut self, outer: Option<Span>) {
        self.registering = outer;
    }

    pub(super) fn note_loaded(&mut self, source: SourceId) {
        if let Some(input) = self.registering {
            self.loaded.push(LoadedModule { source, input });
        }
    }

    pub(super) fn bound_by(&self, span: Span) -> Vec<BoundName> {
        self.bound
            .iter()
            .filter(|name| span.contains(name.span))
            .cloned()
            .collect()
    }

    pub(super) fn loaded(&self) -> &[LoadedModule] {
        &self.loaded
    }

    fn surrounds(&self, span: Span) -> bool {
        self.inputs.iter().any(|input| input.contains(span))
    }
}

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

    struct RecordFixture;

    impl RecordFixture {
        const INPUT: usize = 0;
        const OUTSIDE: usize = 20;

        fn span(start: usize) -> Span {
            Span::new(SourceId::new(0), start..start + 1)
        }

        fn name(span: Span) -> Option<BoundName> {
            Some(BoundName {
                name: "bound".into(),
                namespace: Namespace::Value,
                span,
            })
        }

        fn recording() -> RecordedInputs {
            RecordedInputs::new(vec![Self::span(Self::INPUT)])
        }
    }

    #[test]
    fn a_name_bound_outside_every_input_is_not_recorded() {
        let mut recorded = RecordFixture::recording();
        let outside = RecordFixture::span(RecordFixture::OUTSIDE);
        recorded.note_bound(outside, || RecordFixture::name(outside));
        let covering = Span::new(SourceId::new(0), 0..RecordFixture::OUTSIDE + 1);
        assert!(recorded.bound_by(covering).is_empty());
    }

    #[test]
    fn a_module_loaded_while_an_input_registers_belongs_to_the_input() {
        let mut recorded = RecordFixture::recording();
        let input = RecordFixture::span(RecordFixture::INPUT);
        let outer = recorded.enter(input);
        let nested = recorded.enter(RecordFixture::span(RecordFixture::OUTSIDE));
        recorded.note_loaded(SourceId::new(1));
        recorded.leave(nested);
        recorded.leave(outer);
        recorded.note_loaded(SourceId::new(2));
        assert_eq!(
            recorded.loaded(),
            [LoadedModule {
                source: SourceId::new(1),
                input
            }]
        );
    }
}