knave-expand 0.14.21

The reading, scoping and expansion stages of knave
Documentation
use super::built_value::BuiltValue;
use super::builtin_site::BuiltinSite;
use crate::diagnostic::Diagnostic;
use crate::source::Span;

pub struct BuildLimit {
    site: BuiltinSite,
}

impl BuildLimit {
    pub fn of(function: &'static str, span: Span) -> Self {
        Self {
            site: BuiltinSite { function, span },
        }
    }

    pub fn span(&self) -> Span {
        self.site.span
    }

    #[inline]
    pub fn check<V: BuiltValue>(&self, value: V) -> Result<V, Diagnostic> {
        let size = value.built_size();
        if size <= V::LIMIT {
            Ok(value)
        } else {
            Err(V::too_large(self.site, size))
        }
    }
}

#[cfg(test)]
mod tests {
    use std::fmt::Write;

    use super::BuildLimit;
    use crate::diagnostic::DiagnosticCode;
    use crate::interpret::limited_text::LimitedText;
    use crate::interpret::planned_list::PlannedList;
    use crate::interpret::value_limits::ValueLimits;
    use crate::source::{SourceId, Span};

    struct BuildLimitFixture;

    impl BuildLimitFixture {
        fn limit() -> BuildLimit {
            BuildLimit::of("string", Span::new(SourceId::new(0), 0..1))
        }

        fn text_of(bytes: usize) -> String {
            "x".repeat(bytes)
        }
    }

    #[test]
    fn text_at_the_limit_is_built() {
        let text = BuildLimitFixture::text_of(ValueLimits::TEXT_BYTES);
        assert!(BuildLimitFixture::limit().check(text).is_ok());
    }

    #[test]
    fn text_past_the_limit_is_refused() {
        let text = BuildLimitFixture::text_of(ValueLimits::TEXT_BYTES + 1);
        let refusal = BuildLimitFixture::limit()
            .check(text)
            .expect_err("too long");
        assert_eq!(refusal.code(), Some(&DiagnosticCode::MACRO_VALUE_SIZE));
    }

    #[test]
    fn limited_text_keeps_what_fits() {
        let mut text = LimitedText::default();
        text.write_str("ab").expect("it fits");
        let built = BuildLimitFixture::limit().check(text).expect("it fits");
        assert_eq!(built.into_text(), "ab");
    }

    #[test]
    fn limited_text_stops_writing_past_the_limit() {
        let mut text = LimitedText::default();
        text.write_str(&BuildLimitFixture::text_of(ValueLimits::TEXT_BYTES))
            .expect("it fits");
        assert!(text.write_str("c").is_err());
        assert!(BuildLimitFixture::limit().check(text).is_err());
    }

    #[test]
    fn a_planned_list_past_the_limit_is_refused() {
        let limit = BuildLimitFixture::limit();
        assert!(limit.check(PlannedList(ValueLimits::LIST_VALUES)).is_ok());
        assert!(
            limit
                .check(PlannedList(ValueLimits::LIST_VALUES + 1))
                .is_err()
        );
    }
}