geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::{write_call, write_function_call, write_projection};
use crate::plan::execution::explain::{Explain, ExplainContext};
use crate::plan::execution::function::ExternalFunctionId;
use crate::plan::execution::graph::{
    CustomLocal, ExternalFunctionLocal, ExternalListLocalId, ParamLocal, TupleLocalId,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ExternalInstruction {
    Call {
        function: ExternalFunctionId,
        args: Box<[ParamLocal]>,
        site: crate::plan::HostCallSite,
    },
    FunctionCall {
        function: ExternalFunctionLocal,
        args: Box<[ParamLocal]>,
        site: crate::plan::HostCallSite,
    },
    TupleIndex {
        tuple: TupleLocalId,
        index: usize,
    },
    CustomField {
        source: CustomLocal,
        index: usize,
    },
    ListIndex {
        list: ExternalListLocalId,
        index: usize,
    },
}

pub(crate) trait ExternalInstructionView {
    type Function;

    fn instruction_ref(&self) -> ExternalInstructionRef<'_, Self::Function>;
}

#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ExternalInstructionRef<'instruction, Function> {
    Call {
        function: &'instruction Function,
        args: &'instruction [ParamLocal],
        site: &'instruction crate::plan::HostCallSite,
    },
    FunctionCall {
        function: &'instruction ExternalFunctionLocal,
        args: &'instruction [ParamLocal],
        site: &'instruction crate::plan::HostCallSite,
    },
    TupleIndex {
        tuple: TupleLocalId,
        index: usize,
    },
    CustomField {
        source: &'instruction CustomLocal,
        index: usize,
    },
    ListIndex {
        list: ExternalListLocalId,
        index: usize,
    },
}

impl ExternalInstructionView for ExternalInstruction {
    type Function = ExternalFunctionId;

    fn instruction_ref(&self) -> ExternalInstructionRef<'_, Self::Function> {
        match self {
            Self::Call {
                function,
                args,
                site,
            } => ExternalInstructionRef::Call {
                function,
                args,
                site,
            },
            Self::FunctionCall {
                function,
                args,
                site,
            } => ExternalInstructionRef::FunctionCall {
                function,
                args,
                site,
            },
            Self::TupleIndex { tuple, index } => ExternalInstructionRef::TupleIndex {
                tuple: *tuple,
                index: *index,
            },
            Self::CustomField { source, index } => ExternalInstructionRef::CustomField {
                source,
                index: *index,
            },
            Self::ListIndex { list, index } => ExternalInstructionRef::ListIndex {
                list: *list,
                index: *index,
            },
        }
    }
}

impl ExternalInstructionView for std::convert::Infallible {
    type Function = std::convert::Infallible;

    fn instruction_ref(&self) -> ExternalInstructionRef<'_, Self::Function> {
        match *self {}
    }
}

impl Explain for ExternalInstruction {
    fn write_explanation(&self, context: &mut ExplainContext<'_, '_>) {
        let output = context.output();
        match self {
            Self::Call { function, args, .. } => {
                write_call(output, "external.call", function, args);
            }
            Self::FunctionCall { function, args, .. } => {
                write_function_call(output, "external.function_call", function, args);
            }
            Self::TupleIndex { tuple, index } => {
                write_projection(output, "external.tuple_index", tuple, *index);
            }
            Self::CustomField { source, index } => {
                write_projection(output, "external.custom_field", source, *index);
            }
            Self::ListIndex { list, index } => {
                write_projection(output, "external.list_index", list, *index);
            }
        }
    }
}

#[cfg(test)]
mod view_tests {
    use super::{ExternalInstruction, ExternalInstructionRef, ExternalInstructionView};
    use crate::plan::execution::function::ExternalFunctionId;
    use crate::plan::execution::graph::{
        CustomLocal, CustomLocalId, ExternalFunctionLocal, ExternalFunctionLocalId,
        ExternalListLocalId, IntLocalId, ParamLocal, TupleLocalId,
    };
    use crate::plan::execution::type_::{
        CustomTypeId, CustomValueShape, CustomValueShapeId, ExternalFunctionType, ExternalTypeId,
        FunctionType, ValueType,
    };

    #[test]
    fn exposes_every_external_instruction_variant() {
        let external_type = ExternalTypeId::new(0);
        let call_function = ExternalFunctionId::new(1, external_type);
        let call_args = vec![ParamLocal::Int(IntLocalId(0))].into_boxed_slice();
        let call_site = crate::plan::HostCallSite::unknown();
        let call = ExternalInstruction::Call {
            function: call_function,
            args: call_args.clone(),
            site: call_site.clone(),
        };
        assert_eq!(
            call.instruction_ref(),
            ExternalInstructionRef::Call {
                function: &call_function,
                args: &call_args,
                site: &call_site,
            },
        );

        let function_local = ExternalFunctionLocal::new(
            ExternalFunctionLocalId(2),
            ExternalFunctionType::from_shapes(
                FunctionType::new(Vec::new(), ValueType::External(external_type)),
                Vec::new(),
                external_type,
            ),
        );
        let function_args: Box<[ParamLocal]> = Box::new([]);
        let function_site = crate::plan::HostCallSite::unknown();
        let function_call = ExternalInstruction::FunctionCall {
            function: function_local.clone(),
            args: function_args.clone(),
            site: function_site.clone(),
        };
        assert_eq!(
            function_call.instruction_ref(),
            ExternalInstructionRef::FunctionCall {
                function: &function_local,
                args: &function_args,
                site: &function_site,
            },
        );

        let tuple_index = ExternalInstruction::TupleIndex {
            tuple: TupleLocalId(3),
            index: 4,
        };
        assert_eq!(
            tuple_index.instruction_ref(),
            ExternalInstructionRef::TupleIndex {
                tuple: TupleLocalId(3),
                index: 4,
            },
        );

        let custom_source = CustomLocal::new(
            CustomLocalId(5),
            CustomValueShape::new(CustomTypeId::new(0), CustomValueShapeId::new(0)),
        );
        let custom_field = ExternalInstruction::CustomField {
            source: custom_source,
            index: 6,
        };
        assert_eq!(
            custom_field.instruction_ref(),
            ExternalInstructionRef::CustomField {
                source: &custom_source,
                index: 6,
            },
        );

        let list_index = ExternalInstruction::ListIndex {
            list: ExternalListLocalId(7),
            index: 8,
        };
        assert_eq!(
            list_index.instruction_ref(),
            ExternalInstructionRef::ListIndex {
                list: ExternalListLocalId(7),
                index: 8,
            },
        );
    }

    #[test]
    fn plain_external_instruction_view_is_uninhabited() {
        fn assert_view<View>()
        where
            View: ExternalInstructionView<Function = std::convert::Infallible>,
        {
        }

        assert_view::<std::convert::Infallible>();
    }
}

#[cfg(test)]
mod explain_tests {
    use super::ExternalInstruction;
    use crate::plan::execution::explain;
    use crate::plan::execution::function::ExternalFunctionId;
    use crate::plan::execution::graph::{
        CustomLocal, CustomLocalId, ExternalFunctionLocal, ExternalFunctionLocalId,
        ExternalListLocalId, IntLocalId, ParamLocal, TupleLocalId,
    };
    use crate::plan::execution::type_::{
        CustomTypeId, CustomValueShape, CustomValueShapeId, ExternalFunctionType, ExternalTypeId,
        FunctionType, ValueType,
    };

    #[test]
    fn writes_external_call() {
        let external_type = ExternalTypeId::new(0);
        let instruction = ExternalInstruction::Call {
            function: ExternalFunctionId::new(1, external_type),
            args: vec![ParamLocal::Int(IntLocalId(0))].into_boxed_slice(),
            site: crate::plan::HostCallSite::unknown(),
        };
        let expected = "external.call external#1 args=[%int#0]";

        assert_explanation(&instruction, expected);
    }

    #[test]
    fn writes_external_function_call() {
        let external_type = ExternalTypeId::new(0);
        let function_local = ExternalFunctionLocal::new(
            ExternalFunctionLocalId(2),
            ExternalFunctionType::from_shapes(
                FunctionType::new(Vec::new(), ValueType::External(external_type)),
                Vec::new(),
                external_type,
            ),
        );
        let instruction = ExternalInstruction::FunctionCall {
            function: function_local,
            args: Box::new([]),
            site: crate::plan::HostCallSite::unknown(),
        };
        let expected = "external.function_call %function.external#2 args=[]";

        assert_explanation(&instruction, expected);
    }

    #[test]
    fn writes_external_tuple_index() {
        let instruction = ExternalInstruction::TupleIndex {
            tuple: TupleLocalId(3),
            index: 4,
        };
        let expected = "external.tuple_index %tuple#3 index=4";

        assert_explanation(&instruction, expected);
    }

    #[test]
    fn writes_external_custom_field() {
        let custom = CustomLocal::new(
            CustomLocalId(5),
            CustomValueShape::new(CustomTypeId::new(0), CustomValueShapeId::new(0)),
        );
        let instruction = ExternalInstruction::CustomField {
            source: custom,
            index: 6,
        };
        let expected = "external.custom_field %custom#5 index=6";

        assert_explanation(&instruction, expected);
    }

    #[test]
    fn writes_external_list_index() {
        let instruction = ExternalInstruction::ListIndex {
            list: ExternalListLocalId(7),
            index: 8,
        };
        let expected = "external.list_index %list.external#7 index=8";

        assert_explanation(&instruction, expected);
    }

    fn assert_explanation(instruction: &ExternalInstruction, expected: &str) {
        explain::with_execution_plan("pub fn main() { 1 }", |plan| {
            let mut actual = String::new();
            let mut context = explain::ExplainContext::new(plan, &mut actual);
            context.write(instruction);
            assert_eq!(actual, expected);
        });
    }
}