geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::{
    BoolExpr, CallArg, CustomFieldAccess, ExternalFunctionExpr, ExternalListExpr, FloatExpr,
    IntExpr, PanicExpr, StringExpr, TupleExpr,
};
use crate::plan::{ExternalLocal, ExternalValueShape, FunctionInstantiation, Step};
use ecow::EcoString;
use num_bigint::BigInt;

#[derive(Debug, Clone, PartialEq)]
pub struct ExternalExpr {
    shape: ExternalValueShape,
    kind: ExternalExprKind,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ExternalArgumentCountMismatch {
    pub(crate) expected: usize,
    pub(crate) actual: usize,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ExternalFunctionCall {
    function: Box<ExternalFunctionExpr>,
    arguments: Box<[CallArg]>,
    site: crate::plan::HostCallSite,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ExternalExprKind {
    LocalGet {
        local: ExternalLocal,
        name: EcoString,
    },
    Call {
        function: FunctionInstantiation,
        args: Vec<CallArg>,
        site: crate::plan::HostCallSite,
    },
    FunctionCall(ExternalFunctionCall),
    TupleIndex {
        tuple: Box<TupleExpr>,
        index: usize,
    },
    CustomField(CustomFieldAccess),
    ListIndex {
        list: Box<ExternalListExpr>,
        index: usize,
    },
    Panic(PanicExpr),
    BoolCase {
        subject: Box<BoolExpr>,
        true_: Box<ExternalExpr>,
        false_: Box<ExternalExpr>,
    },
    IntCase {
        subject: Box<IntExpr>,
        clauses: Vec<(BigInt, ExternalExpr)>,
        fallback: Box<ExternalExpr>,
    },
    StringCase {
        subject: Box<StringExpr>,
        clauses: Vec<(EcoString, ExternalExpr)>,
        fallback: Box<ExternalExpr>,
    },
    FloatCase {
        subject: Box<FloatExpr>,
        clauses: Vec<(f64, ExternalExpr)>,
        fallback: Box<ExternalExpr>,
    },
    Block {
        steps: Vec<Step>,
        return_: Box<ExternalExpr>,
    },
}

impl ExternalExpr {
    pub(crate) fn local_get(local: ExternalLocal, name: EcoString) -> Self {
        Self::new(
            local.shape().clone(),
            ExternalExprKind::LocalGet { local, name },
        )
    }

    pub(crate) fn call_at(
        function: FunctionInstantiation,
        args: Vec<CallArg>,
        shape: ExternalValueShape,
        site: crate::plan::HostCallSite,
    ) -> Self {
        Self::new(
            shape,
            ExternalExprKind::Call {
                function,
                args,
                site,
            },
        )
    }

    pub(crate) fn try_function_call_at(
        function: ExternalFunctionExpr,
        args: Vec<CallArg>,
        site: crate::plan::HostCallSite,
    ) -> Result<Self, ExternalArgumentCountMismatch> {
        let expected = function.external_function_type().argument_shapes().len();
        if expected != args.len() {
            return Err(ExternalArgumentCountMismatch {
                expected,
                actual: args.len(),
            });
        }
        let shape = function.external_function_type().return_().clone();
        Ok(Self::new(
            shape,
            ExternalExprKind::FunctionCall(ExternalFunctionCall {
                function: Box::new(function),
                arguments: args.into_boxed_slice(),
                site,
            }),
        ))
    }

    pub(crate) fn tuple_index_shape(
        tuple: TupleExpr,
        index: usize,
        shape: ExternalValueShape,
    ) -> Self {
        Self::new(
            shape,
            ExternalExprKind::TupleIndex {
                tuple: Box::new(tuple),
                index,
            },
        )
    }

    pub(crate) fn custom_field_shape(access: CustomFieldAccess, shape: ExternalValueShape) -> Self {
        Self::new(shape, ExternalExprKind::CustomField(access))
    }

    pub(crate) fn list_index_shape(
        list: ExternalListExpr,
        index: usize,
        shape: ExternalValueShape,
    ) -> Self {
        Self::new(
            shape,
            ExternalExprKind::ListIndex {
                list: Box::new(list),
                index,
            },
        )
    }

    pub(crate) fn panic_shape(panic: PanicExpr, shape: ExternalValueShape) -> Self {
        Self::new(shape, ExternalExprKind::Panic(panic))
    }

    pub(crate) fn bool_case(subject: BoolExpr, true_: Self, false_: Self) -> Self {
        Self::new(
            true_.shape.clone(),
            ExternalExprKind::BoolCase {
                subject: Box::new(subject),
                true_: Box::new(true_),
                false_: Box::new(false_),
            },
        )
    }

    pub(crate) fn int_case(subject: IntExpr, clauses: Vec<(BigInt, Self)>, fallback: Self) -> Self {
        Self::new(
            fallback.shape.clone(),
            ExternalExprKind::IntCase {
                subject: Box::new(subject),
                clauses,
                fallback: Box::new(fallback),
            },
        )
    }

    pub(crate) fn string_case(
        subject: StringExpr,
        clauses: Vec<(EcoString, Self)>,
        fallback: Self,
    ) -> Self {
        Self::new(
            fallback.shape.clone(),
            ExternalExprKind::StringCase {
                subject: Box::new(subject),
                clauses,
                fallback: Box::new(fallback),
            },
        )
    }

    pub(crate) fn float_case(
        subject: FloatExpr,
        clauses: Vec<(f64, Self)>,
        fallback: Self,
    ) -> Self {
        Self::new(
            fallback.shape.clone(),
            ExternalExprKind::FloatCase {
                subject: Box::new(subject),
                clauses,
                fallback: Box::new(fallback),
            },
        )
    }

    pub(crate) fn block(steps: Vec<Step>, return_: Self) -> Self {
        Self::new(
            return_.shape.clone(),
            ExternalExprKind::Block {
                steps,
                return_: Box::new(return_),
            },
        )
    }

    pub fn type_(&self) -> &crate::plan::ExternalType {
        self.shape.type_()
    }

    pub(crate) fn shape(&self) -> &ExternalValueShape {
        &self.shape
    }

    pub(super) fn with_shape(mut self, shape: ExternalValueShape) -> Self {
        self.shape = shape;
        self
    }

    pub(crate) fn kind(&self) -> &ExternalExprKind {
        &self.kind
    }

    pub(crate) fn into_parts(self) -> (ExternalValueShape, ExternalExprKind) {
        (self.shape, self.kind)
    }

    fn new(shape: ExternalValueShape, kind: ExternalExprKind) -> Self {
        Self { shape, kind }
    }
}

impl ExternalFunctionCall {
    pub(crate) fn function(&self) -> &ExternalFunctionExpr {
        &self.function
    }

    pub(crate) fn arguments(&self) -> &[CallArg] {
        &self.arguments
    }

    pub(crate) fn site(&self) -> &crate::plan::HostCallSite {
        &self.site
    }
}

#[cfg(test)]
mod tests {
    use super::{ExternalArgumentCountMismatch, ExternalExpr};
    use crate::plan::{
        CallArg, Expr, ExternalFunctionExpr, ExternalFunctionReference, ExternalTypeName,
        ExternalValueShape, FunctionShape, IntExpr, ValueShape, monomorphic_function_instantiation,
    };

    #[test]
    fn external_function_call_derives_its_return_shape_and_checks_argument_count() {
        let return_ = ExternalValueShape::new(
            ExternalTypeName::new("geam".into(), "main".into(), "Resource".into()),
            Vec::new(),
        );
        let function = ExternalFunctionExpr::reference(
            ExternalFunctionReference::new(monomorphic_function_instantiation(
                0,
                FunctionShape::new(vec![ValueShape::Int], ValueShape::External(return_.clone())),
            )),
            return_.clone(),
        );
        let argument = CallArg::new(Expr::int(IntExpr::value(1.into())));

        let expression = ExternalExpr::try_function_call_at(
            function.clone(),
            vec![argument],
            crate::plan::HostCallSite::unknown(),
        )
        .expect("one argument should match the external function");

        assert_eq!(expression.shape(), &return_);
        assert_eq!(
            ExternalExpr::try_function_call_at(
                function,
                Vec::new(),
                crate::plan::HostCallSite::unknown(),
            ),
            Err(ExternalArgumentCountMismatch {
                expected: 1,
                actual: 0,
            }),
        );
    }
}