knave-expand 0.14.22

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

use crate::core::{BindingArena, BindingId, ModuleId, Namespace};
use crate::reader::Symbol;
use crate::scope::{BindingTable, Phase, Resolution};

#[derive(Clone, Copy)]
pub(super) struct Bindings<'t> {
    table: &'t BindingTable,
    bindings: &'t BindingArena,
    phase: Phase,
}

impl<'t> Bindings<'t> {
    pub(super) fn new(table: &'t BindingTable, bindings: &'t BindingArena, phase: Phase) -> Self {
        Self {
            table,
            bindings,
            phase,
        }
    }

    pub(super) fn resolve(&self, symbol: &Symbol, namespace: Namespace) -> Option<BindingId> {
        match self.table.resolve(symbol, namespace, self.phase) {
            Resolution::Bound(binding) => Some(binding),
            Resolution::Unbound | Resolution::Ambiguous(_) => None,
        }
    }

    pub(super) fn definition_module(&self, binding: BindingId) -> Option<ModuleId> {
        self.bindings.get(binding).kind().defining_module()
    }

    pub(super) fn visible_named_like(
        &self,
        symbol: &Symbol,
        namespace: Namespace,
    ) -> Vec<(Rc<str>, BindingId)> {
        self.table
            .visible_name_bindings(symbol, namespace, self.phase)
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use crate::core::{ModuleId, ModulePath, Namespace};
    use crate::expand::Expander;
    use crate::modules::ModuleSources;
    use crate::reader::{Form, FormKind, Symbol};
    use crate::source::{SourceId, SourceMap, Span};

    struct BindingsFixture {
        sources: SourceMap,
        entry: SourceId,
    }

    impl BindingsFixture {
        const MODULE: &str = "names";
        const PROGRAM: &str = "(defn twice (x) (* 2 x))\n";
        const DEFINED: &str = "twice";
        const UNBOUND: &str = "thrice";
        const MISSPELLED: &str = "twise";

        fn new() -> Self {
            let mut sources = SourceMap::default();
            let entry = sources.add(Self::MODULE, Self::PROGRAM);
            Self { sources, entry }
        }

        fn with_bindings<T>(
            &mut self,
            name: &str,
            read: impl FnOnce(&super::Bindings<'_>, &Symbol, ModuleId) -> T,
        ) -> T {
            let entry = self.entry;
            let mut expander = Expander::new(
                &mut self.sources,
                ModuleSources::new(PathBuf::new(), PathBuf::new()),
            );
            let module = expander.load_source_module(ModulePath::new(Self::MODULE), &[entry]);
            let mut form = Form::new(FormKind::Symbol(Symbol::new(name)), Span::new(entry, 0..0));
            let scopes = expander.modules.get(module).scopes();
            form.add_scope(scopes.base());
            form.add_scope(scopes.body());
            let symbol = form.as_symbol().expect("the form is a symbol").clone();
            read(&expander.bindings(), &symbol, module)
        }
    }

    #[test]
    fn a_name_the_module_defines_resolves_to_a_binding_of_the_module() {
        let mut fixture = BindingsFixture::new();
        let defining =
            fixture.with_bindings(BindingsFixture::DEFINED, |bindings, symbol, module| {
                let binding = bindings.resolve(symbol, Namespace::Value);
                (
                    binding.and_then(|binding| bindings.definition_module(binding)),
                    module,
                )
            });
        assert_eq!(defining.0, Some(defining.1));
    }

    #[test]
    fn an_unbound_name_resolves_to_nothing() {
        let mut fixture = BindingsFixture::new();
        let binding = fixture.with_bindings(BindingsFixture::UNBOUND, |bindings, symbol, _| {
            bindings.resolve(symbol, Namespace::Value)
        });
        assert_eq!(binding, None);
    }

    #[test]
    fn the_names_visible_from_a_symbol_include_the_module_definitions() {
        let mut fixture = BindingsFixture::new();
        let names = fixture.with_bindings(BindingsFixture::MISSPELLED, |bindings, symbol, _| {
            bindings
                .visible_named_like(symbol, Namespace::Value)
                .into_iter()
                .map(|(name, _)| name.to_string())
                .collect::<Vec<_>>()
        });
        assert!(
            names.iter().any(|name| name == BindingsFixture::DEFINED),
            "{names:?}"
        );
    }
}