knave-expand 0.14.22

The reading, scoping and expansion stages of knave
Documentation
use crate::reader::Form;

pub(super) struct BinderForm<'f> {
    pub name: &'f Form,
    pub annotation: Option<&'f Form>,
}

impl<'f> BinderForm<'f> {
    pub(super) fn of(binder: &'f Form) -> Self {
        match binder.as_list() {
            Some([name, annotation]) if name.as_symbol().is_some() => Self {
                name,
                annotation: Some(annotation),
            },
            _ => Self {
                name: binder,
                annotation: None,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::reader::Reader;
    use crate::source::SourceMap;

    struct BinderFixture {
        sources: SourceMap,
    }

    impl BinderFixture {
        fn new() -> Self {
            Self {
                sources: SourceMap::default(),
            }
        }

        fn form(&mut self, text: &str) -> Form {
            let source = self.sources.add("binder", text);
            let forms = Reader::new(self.sources.file(source)).read();
            let mut forms = forms.unwrap_or_else(|_| panic!("`{text}` reads"));
            forms.remove(0)
        }

        fn parts(form: &Form) -> (String, Option<String>) {
            let binder = BinderForm::of(form);
            (
                binder.name.to_string(),
                binder.annotation.map(ToString::to_string),
            )
        }
    }

    #[test]
    fn a_bare_name_is_the_whole_binder_without_an_annotation() {
        let form = BinderFixture::new().form("x");
        assert_eq!(BinderFixture::parts(&form), ("x".to_string(), None));
    }

    #[test]
    fn a_name_and_a_type_split_into_the_name_and_its_annotation() {
        let form = BinderFixture::new().form("(x i64)");
        assert_eq!(
            BinderFixture::parts(&form),
            ("x".to_string(), Some("i64".to_string()))
        );
    }

    #[test]
    fn a_pair_that_does_not_start_with_a_name_is_kept_whole() {
        let form = BinderFixture::new().form("((x) i64)");
        assert_eq!(BinderFixture::parts(&form), ("((x) i64)".to_string(), None));
    }
}