geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::{call_args, expr, function, generic, list, panic_expr, tuple};
use crate::plan::execution::lowering::graph::{DraftCursor, DraftCustom, DraftFlow, DraftGraph};
use crate::plan::execution::lowering::specialization::{
    CompoundInhabitation, Representability, SpecializedCustomValueShape, StoredValueShape,
};
use crate::plan::{execution, module};

pub(in crate::plan::execution::lowering) fn custom_expr(
    expression: &module::CustomExpr,
    cursor: DraftCursor,
    graph: &mut DraftGraph,
    context: &mut super::super::LoweringContext,
) -> Representability<DraftFlow<DraftCustom>> {
    let shape = context.concrete_custom_value_shape(expression.shape());
    if let CompoundInhabitation::Uninhabited(proof) =
        context.representations.custom_inhabitation(&shape)
    {
        return generic::custom_never_expr(expression, &proof, cursor, graph, context)
            .map(|_| DraftFlow::Diverged);
    }

    custom_expr_kind(expression.kind(), &shape, cursor, graph, context)
}

pub(in crate::plan::execution::lowering) fn custom_expr_kind(
    kind: &module::CustomExprKind,
    shape: &SpecializedCustomValueShape,
    cursor: DraftCursor,
    graph: &mut DraftGraph,
    context: &mut super::super::LoweringContext,
) -> Representability<DraftFlow<DraftCustom>> {
    use super::super::instruction::DraftCustomInstruction as I;
    use module::CustomExprKind as E;

    let stored = StoredValueShape::Custom(shape.clone());
    match kind {
        E::Constructor(construction) => lower_fields(construction.fields(), cursor, graph, context)
            .map(|flow| match flow {
                DraftFlow::Diverged => DraftFlow::Diverged,
                DraftFlow::Value {
                    mut cursor,
                    value: fields,
                } => {
                    let constructor =
                        context.custom_constructor(construction.constructor().clone());
                    let value = graph.custom_instruction(
                        &mut cursor,
                        shape.clone(),
                        I::Construct {
                            constructor,
                            fields,
                        },
                    );
                    DraftFlow::value(cursor, value)
                }
            }),
        E::Constant(reference) => context.custom_constant(reference).map(|id| {
            let mut cursor = cursor;
            let value = graph.custom_instruction(
                &mut cursor,
                shape.clone(),
                I::Constant(execution::constant::ConstantId::new(id.index())),
            );
            DraftFlow::value(cursor, value)
        }),
        E::LocalGet { local, name: _ } => {
            let value = cursor.scope().custom(super::super::local::LocalKey::new(
                super::super::local::LocalKind::Custom,
                local.id().0,
            ));
            Representability::Inhabited(DraftFlow::value(cursor, value))
        }
        E::Call {
            function,
            args,
            site,
        } => call_args(args, cursor, graph, context).and_then(|flow| match flow {
            DraftFlow::Diverged => Representability::Inhabited(DraftFlow::Diverged),
            DraftFlow::Value {
                mut cursor,
                value: args,
            } => context.custom_function_id(function, shape).map(|function| {
                let value = graph.custom_instruction(
                    &mut cursor,
                    shape.clone(),
                    I::Call {
                        function,
                        args,
                        site: site.clone(),
                    },
                );
                DraftFlow::value(cursor, value)
            }),
        }),
        E::FunctionCall(call) => {
            function::custom_function_expr(call.function(), cursor, graph, context).and_then(
                |flow| match flow {
                    DraftFlow::Diverged => Representability::Inhabited(DraftFlow::Diverged),
                    DraftFlow::Value {
                        cursor,
                        value: function,
                    } => {
                        call_args(call.arguments(), cursor, graph, context).map(|flow| match flow {
                            DraftFlow::Diverged => DraftFlow::Diverged,
                            DraftFlow::Value {
                                mut cursor,
                                value: args,
                            } => {
                                let value = graph.custom_instruction(
                                    &mut cursor,
                                    shape.clone(),
                                    I::FunctionCall {
                                        function: function.value().clone(),
                                        args,
                                        site: call.site().clone(),
                                    },
                                );
                                DraftFlow::value(cursor, value)
                            }
                        })
                    }
                },
            )
        }
        E::TupleIndex {
            tuple: source,
            index,
        } => tuple::tuple_expr(source, cursor, graph, context).map(|flow| match flow {
            DraftFlow::Diverged => DraftFlow::Diverged,
            DraftFlow::Value {
                mut cursor,
                value: tuple,
            } => {
                let value = graph.custom_instruction(
                    &mut cursor,
                    shape.clone(),
                    I::TupleIndex {
                        tuple,
                        index: *index,
                    },
                );
                DraftFlow::value(cursor, value)
            }
        }),
        E::CustomField(access) => {
            custom_expr(access.source(), cursor, graph, context).map(|flow| match flow {
                DraftFlow::Diverged => DraftFlow::Diverged,
                DraftFlow::Value {
                    mut cursor,
                    value: source,
                } => {
                    let value = graph.custom_instruction(
                        &mut cursor,
                        shape.clone(),
                        I::CustomField {
                            source,
                            index: access.index(),
                        },
                    );
                    DraftFlow::value(cursor, value)
                }
            })
        }
        E::ListIndex {
            list: source,
            index,
        } => list::custom_list_expr(source, cursor, graph, context).map(|flow| match flow {
            DraftFlow::Diverged => DraftFlow::Diverged,
            DraftFlow::Value {
                mut cursor,
                value: list,
            } => {
                let value = graph.custom_instruction(
                    &mut cursor,
                    shape.clone(),
                    I::ListIndex {
                        list: list.value().clone(),
                        index: *index,
                    },
                );
                DraftFlow::value(cursor, value)
            }
        }),
        E::Panic(value) => panic_expr(value, cursor, graph, context).map(|_| DraftFlow::Diverged),
        E::BoolCase {
            subject,
            true_,
            false_,
        } => super::bool_case(
            subject,
            cursor,
            super::case_lowering(graph, context, stored),
            |cursor, graph, context| custom_expr_kind(true_, shape, cursor, graph, context),
            |cursor, graph, context| custom_expr_kind(false_, shape, cursor, graph, context),
            DraftCustom::from_ref,
        ),
        E::IntCase {
            subject,
            clauses,
            fallback,
        } => super::int_case(
            subject,
            clauses,
            fallback,
            cursor,
            super::case_lowering(graph, context, stored),
            |branch, cursor, graph, context| {
                custom_expr_kind(branch, shape, cursor, graph, context)
            },
            DraftCustom::from_ref,
        ),
        E::StringCase {
            subject,
            clauses,
            fallback,
        } => super::string_case(
            subject,
            clauses,
            fallback,
            cursor,
            super::case_lowering(graph, context, stored),
            |branch, cursor, graph, context| {
                custom_expr_kind(branch, shape, cursor, graph, context)
            },
            DraftCustom::from_ref,
        ),
        E::FloatCase {
            subject,
            clauses,
            fallback,
        } => super::float_case(
            subject,
            clauses,
            fallback,
            cursor,
            super::case_lowering(graph, context, stored),
            |branch, cursor, graph, context| {
                custom_expr_kind(branch, shape, cursor, graph, context)
            },
            DraftCustom::from_ref,
        ),
        E::Block { steps, return_ } => super::super::step::steps(steps, cursor, graph, context)
            .and_then(|flow| match flow {
                DraftFlow::Diverged => Representability::Inhabited(DraftFlow::Diverged),
                DraftFlow::Value { cursor, value: () } => {
                    custom_expr_kind(return_, shape, cursor, graph, context)
                }
            }),
    }
}

fn lower_fields(
    fields: &[module::Expr],
    mut cursor: DraftCursor,
    graph: &mut DraftGraph,
    context: &mut super::super::LoweringContext,
) -> Representability<DraftFlow<Vec<super::super::DraftValueRef>>> {
    let mut values = Vec::with_capacity(fields.len());
    for field in fields {
        match expr(field, cursor, graph, context) {
            Representability::Uninhabited => return Representability::Uninhabited,
            Representability::Inhabited(DraftFlow::Diverged) => {
                return Representability::Inhabited(DraftFlow::Diverged);
            }
            Representability::Inhabited(DraftFlow::Value {
                cursor: next,
                value,
            }) => {
                cursor = next;
                values.push(value);
            }
        }
    }
    Representability::Inhabited(DraftFlow::value(cursor, values))
}

#[cfg(test)]
mod tests {
    use super::custom_expr;
    use crate::plan::execution::lowering::graph::draft::DraftGraphBuilder;
    use crate::plan::execution::lowering::graph::{DraftFlow, DraftValueRef};
    use crate::plan::execution::lowering::specialization::{Representability, SpecializationKey};
    use crate::plan::{
        CustomConstructor, CustomConstructorDefinition, CustomConstructorField,
        CustomFieldDefinition, CustomType, CustomTypeDefinition, CustomTypeName,
        CustomTypePublicity, CustomTypeTemplate, Expr, FunctionShape, FunctionTemplateId, IntExpr,
        PanicExpr, PanicSite, ValueShape, ValueType, monomorphic_function_instantiation,
    };

    #[derive(Debug, PartialEq, Eq)]
    enum FlowOutcome {
        Uninhabited,
        Diverged,
        Value,
    }

    fn flow_outcome<T>(flow: Representability<DraftFlow<T>>) -> FlowOutcome {
        match flow {
            Representability::Uninhabited => FlowOutcome::Uninhabited,
            Representability::Inhabited(DraftFlow::Diverged) => FlowOutcome::Diverged,
            Representability::Inhabited(DraftFlow::Value { .. }) => FlowOutcome::Value,
        }
    }

    #[test]
    fn custom_construction_propagates_an_erased_field_specialization() {
        let name = CustomTypeName::new("geam".into(), "main".into(), "Boxed".into());
        let definition = CustomTypeDefinition::new(
            name.clone(),
            CustomTypePublicity::Private,
            false,
            Vec::new(),
            vec![CustomConstructorDefinition::new(
                "Boxed".into(),
                0,
                vec![CustomFieldDefinition::new(None, CustomTypeTemplate::Int)],
            )],
        );
        let function =
            monomorphic_function_instantiation(0, FunctionShape::new(Vec::new(), ValueShape::Int));
        let constructor = CustomConstructor::new(
            CustomType::new(name, Vec::new()),
            "Boxed".into(),
            0,
            vec![CustomConstructorField::new(None, ValueType::Int)],
        );
        let expressions = [
            (IntExpr::value(1.into()), FlowOutcome::Value),
            (
                IntExpr::panic(PanicExpr::panic_at(None, PanicSite::unknown())),
                FlowOutcome::Diverged,
            ),
            (
                IntExpr::call(function, Vec::new()),
                FlowOutcome::Uninhabited,
            ),
        ];
        let mut context =
            crate::plan::execution::lowering::test_support::lowering_context(vec![definition]);
        context
            .erased_specializations
            .insert(SpecializationKey::monomorphic(FunctionTemplateId::new(0)));
        let (mut graph, cursor) =
            DraftGraphBuilder::<DraftValueRef, ()>::new(Vec::new(), Vec::new());

        for (field, expected) in expressions {
            let expression = crate::plan::CustomExpr::try_constructor(
                constructor.clone(),
                vec![Expr::int(field)],
            )
            .expect("one Int field should construct Boxed");
            let cursor = graph.empty_block(cursor.scope().clone());
            assert_eq!(
                flow_outcome(custom_expr(&expression, cursor, &mut graph, &mut context)),
                expected,
            );
        }
    }

    #[test]
    fn custom_fields_preserve_left_to_right_source_stops() {
        for (expression, expected) in [
            (
                "Pair(failed(\"first\"), failed(\"second\"))",
                "panic: first",
            ),
            ("Pair(1, failed(\"second\"))", "panic: second"),
        ] {
            assert_eq!(run(expression), expected);
        }
    }

    fn run(expression: &str) -> String {
        let source = format!(
            r#"
pub type Pair {{ Pair(Int, Int) }}

fn failed(message: String) -> Int {{ panic as message }}

pub fn main() {{ {expression} }}
"#,
        );
        let typed = crate::compile_typed_module("main", "main.gleam", source.as_str())
            .expect("source should compile");
        let module = crate::plan_module(typed).expect("source should plan");
        crate::run_main(
            &crate::ExecutionPlan::from_module_plan(module),
            &mut Vec::new(),
        )
        .unwrap_err()
        .to_string()
    }
}