geam 0.1.2

Experimental Rust-embedded execution runtime for typed Gleam programs
use crate::plan::{
    BitArrayExpr, BoolExpr, CustomExpr, Expr, ExprKind, ExternalExpr, FloatExpr, FunctionExpr,
    GenericExpr, IntExpr, ListExpr, NilExpr, StringExpr, TupleExpr, UtfCodepointExpr, ValueShape,
    ValueType,
};
use crate::planner::error::{InvalidExpressionType, InvalidTypedAstReason, PlanError};
use gleam_core::type_::Type;

pub(in crate::planner) fn expect_expression<Family>(expression: Expr) -> Result<Family, PlanError>
where
    Family: PlannedExpressionFamily,
{
    let actual = InvalidExpressionType::from_value_type(expression.value_type());
    Family::from_kind(expression.into_kind()).ok_or(PlanError::InvalidTypedAst {
        reason: InvalidTypedAstReason::ExpressionType {
            expected: Family::TYPE,
            actual,
        },
    })
}

pub(in crate::planner) fn refine_expression_shape(
    expression: Expr,
    shape: ValueShape,
) -> Result<Expr, PlanError> {
    refine_shape(expression, shape)
}

pub(in crate::planner) fn refine_value_shape(
    expression: ValueShape,
    shape: ValueShape,
) -> Result<ValueShape, PlanError> {
    refine_shape(expression, shape)
}

pub(in crate::planner) fn validate_expression_value_type(
    expected: &ValueType,
    actual: &ValueType,
) -> Result<(), PlanError> {
    expect_value_type_result(
        (expected == actual)
            .then_some(())
            .ok_or_else(|| (expected.clone(), actual.clone())),
        |types| types,
    )
}

pub(in crate::planner) fn validate_expression_shape_flow(
    source: &ValueShape,
    target: &ValueShape,
) -> Result<(), PlanError> {
    expect_shape_result(
        target.value_type(),
        source.value_type(),
        source.can_flow_to(target).then_some(()),
    )
}

pub(in crate::planner) fn value_type_from_gleam(
    type_: &Type,
    expected: InvalidExpressionType,
) -> Result<ValueType, PlanError> {
    ValueType::from_gleam(type_).ok_or(PlanError::InvalidTypedAst {
        reason: InvalidTypedAstReason::UnsupportedExpressionType { expected },
    })
}

pub(in crate::planner) fn expect_list_spread(
    elements: Result<crate::plan::ListSpreadElements, crate::plan::ListSpreadConstructionError>,
) -> Result<crate::plan::ListSpreadElements, PlanError> {
    match elements {
        Ok(elements) => Ok(elements),
        Err(crate::plan::ListSpreadConstructionError::ElementTypeMismatch(error)) => {
            expect_value_type_result(Err(error), |error| (error.expected, error.actual))
        }
        Err(crate::plan::ListSpreadConstructionError::EmptyPrefix) => {
            Err(PlanError::InvalidTypedAst {
                reason: InvalidTypedAstReason::ExpressionShape {
                    kind: crate::planner::InvalidExpressionShapeKind::ListSpreadEmptyPrefix,
                },
            })
        }
    }
}

pub(in crate::planner) fn expect_value_type_result<Value, Error>(
    result: Result<Value, Error>,
    types: impl FnOnce(Error) -> (ValueType, ValueType),
) -> Result<Value, PlanError> {
    result.map_err(|error| {
        let (expected, actual) = types(error);
        PlanError::InvalidTypedAst {
            reason: InvalidTypedAstReason::ExpressionValueTypeMismatch { expected, actual },
        }
    })
}

fn refine_shape<Value>(value: Value, shape: ValueShape) -> Result<Value::Output, PlanError>
where
    Value: PlannedShapeRefinement,
{
    let actual = value.value_type();
    let expected = shape.value_type();
    expect_shape_result(expected, actual, value.refine(shape))
}

fn expect_shape_result<Value>(
    expected: ValueType,
    actual: ValueType,
    value: Option<Value>,
) -> Result<Value, PlanError> {
    value.ok_or(PlanError::InvalidTypedAst {
        reason: InvalidTypedAstReason::ExpressionShapeRefinement { expected, actual },
    })
}

mod sealed {
    pub trait ExpressionFamily {}
    pub trait ShapeRefinement {}
}

pub(in crate::planner) trait PlannedExpressionFamily:
    sealed::ExpressionFamily + Sized
{
    const TYPE: InvalidExpressionType;

    fn from_kind(kind: ExprKind) -> Option<Self>;
}

macro_rules! expression_family {
    ($type_:ty, $family:ident, $kind:ident) => {
        impl sealed::ExpressionFamily for $type_ {}

        impl PlannedExpressionFamily for $type_ {
            const TYPE: InvalidExpressionType = InvalidExpressionType::$family;

            fn from_kind(kind: ExprKind) -> Option<Self> {
                match kind {
                    ExprKind::$kind(expression) => Some(expression),
                    _ => None,
                }
            }
        }
    };
}

expression_family!(GenericExpr, TypeParameter, Generic);
expression_family!(IntExpr, Int, Int);
expression_family!(StringExpr, String, String);
expression_family!(BitArrayExpr, BitArray, BitArray);
expression_family!(UtfCodepointExpr, UtfCodepoint, UtfCodepoint);
expression_family!(CustomExpr, Custom, Custom);
expression_family!(ExternalExpr, External, External);
expression_family!(FloatExpr, Float, Float);
expression_family!(BoolExpr, Bool, Bool);
expression_family!(NilExpr, Nil, Nil);
expression_family!(TupleExpr, Tuple, Tuple);
expression_family!(ListExpr, List, List);
expression_family!(FunctionExpr, Function, Function);

trait PlannedShapeRefinement: sealed::ShapeRefinement + Sized {
    type Output;

    fn value_type(&self) -> ValueType;

    fn refine(self, shape: ValueShape) -> Option<Self::Output>;
}

impl sealed::ShapeRefinement for Expr {}

impl PlannedShapeRefinement for Expr {
    type Output = Self;

    fn value_type(&self) -> ValueType {
        self.value_type()
    }

    fn refine(self, shape: ValueShape) -> Option<Self::Output> {
        self.with_shape(shape)
    }
}

impl sealed::ShapeRefinement for ValueShape {}

impl PlannedShapeRefinement for ValueShape {
    type Output = Self;

    fn value_type(&self) -> ValueType {
        self.value_type()
    }

    fn refine(self, shape: ValueShape) -> Option<Self::Output> {
        ValueShape::refine(&self, &shape)
    }
}

#[cfg(test)]
mod tests {
    use super::{expect_expression, refine_expression_shape};
    use crate::plan::{
        BitArrayExpr, BitArrayLocalId, BoolExpr, CustomExpr, CustomLocal, CustomLocalId,
        CustomType, CustomTypeName, CustomValueShape, Expr, ExternalExpr, ExternalLocal,
        ExternalLocalId, ExternalType, ExternalTypeName, ExternalValueShape, FloatExpr,
        FunctionExpr, GenericExpr, GenericLocal, GenericLocalId, IntExpr, ListExpr, NilExpr,
        StringExpr, TupleExpr, TypeParameterId, UtfCodepointExpr, UtfCodepointLocalId, ValueShape,
        ValueType,
    };
    use crate::planner::error::{InvalidExpressionType, InvalidTypedAstReason, PlanError};

    fn mismatch(expected: InvalidExpressionType, actual: InvalidExpressionType) -> PlanError {
        PlanError::InvalidTypedAst {
            reason: InvalidTypedAstReason::ExpressionType { expected, actual },
        }
    }

    #[test]
    fn converts_every_planned_expression_family() {
        let parameter = TypeParameterId(0);
        let custom = CustomType::new(
            CustomTypeName::new("app".into(), "main".into(), "Boxed".into()),
            Vec::new(),
        );
        let custom_shape = CustomValueShape::any(custom);
        let external = ExternalType::new(
            ExternalTypeName::new("app".into(), "main".into(), "Token".into()),
            Vec::new(),
        );
        let external_shape = ExternalValueShape::any(external);
        let function: Expr =
            crate::planner::dsl::int_function_ref(0, Vec::<crate::plan::ParamLocal>::new()).into();

        assert!(
            expect_expression::<GenericExpr>(Expr::generic(GenericExpr::local_get(
                GenericLocal::new(GenericLocalId(0), parameter),
                "value".into(),
            )))
            .is_ok()
        );
        assert!(expect_expression::<IntExpr>(Expr::int(IntExpr::value(1.into()))).is_ok());
        assert!(
            expect_expression::<StringExpr>(Expr::string(StringExpr::value("a".into()))).is_ok()
        );
        assert!(
            expect_expression::<BitArrayExpr>(Expr::bit_array(BitArrayExpr::local_get(
                BitArrayLocalId(0),
                "bits".into(),
            )))
            .is_ok()
        );
        assert!(
            expect_expression::<UtfCodepointExpr>(Expr::utf_codepoint(
                UtfCodepointExpr::local_get(UtfCodepointLocalId(0), "codepoint".into()),
            ))
            .is_ok()
        );
        assert!(
            expect_expression::<CustomExpr>(Expr::custom(CustomExpr::local_get(
                CustomLocal::from_shape(CustomLocalId(0), custom_shape),
                "boxed".into(),
            )))
            .is_ok()
        );
        assert!(
            expect_expression::<ExternalExpr>(Expr::external(ExternalExpr::local_get(
                ExternalLocal::from_shape(ExternalLocalId(0), external_shape),
                "token".into(),
            )))
            .is_ok()
        );
        assert!(expect_expression::<FloatExpr>(Expr::float(FloatExpr::value(1.0))).is_ok());
        assert!(expect_expression::<BoolExpr>(Expr::bool(BoolExpr::value(true))).is_ok());
        assert!(expect_expression::<NilExpr>(Expr::nil(NilExpr::value())).is_ok());
        assert!(
            expect_expression::<TupleExpr>(
                crate::planner::dsl::tuple([crate::planner::dsl::int(1)]).into(),
            )
            .is_ok()
        );
        assert!(
            expect_expression::<ListExpr>(
                crate::planner::dsl::list([crate::planner::dsl::int(1)], ValueType::Int).into(),
            )
            .is_ok()
        );
        assert!(expect_expression::<FunctionExpr>(function).is_ok());
    }

    #[test]
    fn reports_family_and_shape_refinement_failures() {
        assert_eq!(
            expect_expression::<StringExpr>(Expr::int(IntExpr::value(1.into()))),
            Err(mismatch(
                InvalidExpressionType::String,
                InvalidExpressionType::Int
            )),
        );

        let expression: Expr =
            crate::planner::dsl::list([crate::planner::dsl::int(1)], ValueType::Int).into();
        assert_eq!(
            refine_expression_shape(expression, ValueShape::List(Box::new(ValueShape::String)),),
            Err(PlanError::InvalidTypedAst {
                reason: InvalidTypedAstReason::ExpressionShapeRefinement {
                    expected: ValueType::List(Box::new(ValueType::String)),
                    actual: ValueType::List(Box::new(ValueType::Int)),
                },
            }),
        );
    }
}