knave 0.9.20

A small Lisp for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use crate::diagnostic::DiagnosticCode;
use crate::reader::Reader;
use crate::source::SourceMap;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OpenPart {
    List,
    String,
}

pub struct OpenInput;

impl OpenInput {
    const CONTINUING_CODES: [DiagnosticCode; 2] = [
        DiagnosticCode::UNCLOSED_LIST,
        DiagnosticCode::UNTERMINATED_STRING,
    ];

    pub fn awaits_more(text: &str) -> bool {
        Self::open_part(text).is_some()
    }

    pub fn open_part(text: &str) -> Option<OpenPart> {
        let mut scratch = SourceMap::default();
        let source = scratch.add("", text);
        let diagnostics = Reader::new(scratch.file(source)).read().err()?;
        let ordered = diagnostics.in_source_order();
        let codes: Vec<&DiagnosticCode> = ordered
            .iter()
            .map(|diagnostic| diagnostic.code())
            .collect::<Option<_>>()?;
        codes
            .iter()
            .all(|code| Self::CONTINUING_CODES.contains(code))
            .then(|| Self::part_left_open(&codes))
    }

    fn part_left_open(codes: &[&DiagnosticCode]) -> OpenPart {
        if codes.contains(&&DiagnosticCode::UNTERMINATED_STRING) {
            OpenPart::String
        } else {
            OpenPart::List
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{OpenInput, OpenPart};

    #[test]
    fn an_unclosed_list_leaves_a_list_open() {
        assert_eq!(OpenInput::open_part("(defn f (x)"), Some(OpenPart::List));
    }

    #[test]
    fn an_unclosed_string_inside_a_list_leaves_the_string_open() {
        assert_eq!(OpenInput::open_part("(println \"a"), Some(OpenPart::String));
    }

    #[test]
    fn a_closed_text_or_another_reader_error_leaves_nothing_open() {
        assert_eq!(OpenInput::open_part("(+ 1 2)"), None);
        assert_eq!(OpenInput::open_part(") ("), None);
    }
}