geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
mod diagnostic;
mod host;
mod invariant;
mod panic;

use crate::plan::{PanicSite, SourceContext, SourceSpan};
use crate::runtime::Value;
use ecow::EcoString;

pub(crate) use self::host::HostCallOrigin;
pub use self::host::{HostError, HostLocation, HostOrigin};
pub use self::invariant::InvariantError;
pub use self::panic::{BitArraySegmentPanicReason, Panic, PanicDetails, PanicKind, PanicMessage};

#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ExecutionError {
    #[error("{0}")]
    Panic(Panic),
    #[error("{0}")]
    Invariant(InvariantError),
    #[error("{0}")]
    Host(Box<HostError>),
}

pub(crate) type ExecutionResult<T> = Result<T, ExecutionError>;

impl ExecutionError {
    pub(crate) fn from_host_call(
        function: &crate::plan::execution::host::HostedFunctionMetadata,
        site: crate::plan::HostCallSite,
        source_context: Option<&SourceContext>,
        failure: crate::HostFailure,
    ) -> Self {
        Self::Host(Box::new(HostError::new(
            function.package().clone(),
            function.module().clone(),
            function.name().clone(),
            function.signature().clone(),
            failure,
            site,
            source_context,
        )))
    }

    pub(crate) fn from_host_origin(
        function: &crate::plan::execution::host::HostedFunctionMetadata,
        caller: HostOrigin,
        failure: crate::HostFailure,
    ) -> Self {
        Self::Host(Box::new(HostError::new_from_host(
            function.package().clone(),
            function.module().clone(),
            function.name().clone(),
            function.signature().clone(),
            failure,
            caller,
        )))
    }

    pub(crate) fn source_panic(
        source_context: Option<&SourceContext>,
        kind: PanicKind,
        message: Option<EcoString>,
        site: PanicSite,
    ) -> Self {
        Self::Panic(Panic::new(
            kind,
            PanicMessage::from_optional_explicit(message),
            site,
            source_context,
            None,
        ))
    }

    pub(crate) fn let_assert_panic(
        source_context: Option<&SourceContext>,
        message: Option<EcoString>,
        site: PanicSite,
        value: Value,
        pattern_span: SourceSpan,
    ) -> Self {
        Self::Panic(Panic::new(
            PanicKind::LetAssert,
            PanicMessage::from_optional_explicit(message),
            site,
            source_context,
            Some(PanicDetails::LetAssert {
                value,
                pattern_span,
            }),
        ))
    }

    pub(crate) fn bit_array_segment_panic(
        source_context: Option<&SourceContext>,
        reason: BitArraySegmentPanicReason,
        site: PanicSite,
    ) -> Self {
        Self::Panic(Panic::new(
            PanicKind::BitArraySegment,
            PanicMessage::Default,
            site,
            source_context,
            Some(PanicDetails::BitArraySegment { reason }),
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::{ExecutionError, InvariantError};
    use crate::plan::execution::function::FunctionReturnFamily;

    #[test]
    fn invariant_display_delegates_to_invariant_error() {
        let invariant = InvariantError::FunctionReturnFamilyMismatch {
            expected: FunctionReturnFamily::Int,
            actual: FunctionReturnFamily::String,
        };
        let error = ExecutionError::Invariant(invariant);

        assert_eq!(
            error.to_string(),
            "function return family mismatch (expected Int, got String)",
        );
    }
}