geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::constant::ModuleToPlan;
use super::link::LinkedFunction;
use crate::host::{RegisteredHostConstructions, RegisteredHostImplementationId};
use crate::plan::{
    FunctionTemplateId, HostedFunctionTemplate, HostedPlannedModule, HostedPlannedModuleParts,
    ModuleId,
};
use crate::planner::context::PlanContext;
use crate::planner::error::PlanError;
use crate::planner::function::{plan_function, plan_selected_external_fallback};
use crate::planner::module::registry::ProgramRegistry;

pub(super) struct PlannedHostedProgram {
    pub(super) root: ModuleId,
    pub(super) entry: FunctionTemplateId,
    pub(super) modules: Vec<HostedPlannedModule>,
    pub(super) implementations: Vec<(
        FunctionTemplateId,
        RegisteredHostConstructions,
        RegisteredHostImplementationId,
    )>,
}

pub(super) fn plan_hosted_modules(
    root: ModuleId,
    registry: &ProgramRegistry,
    modules: Vec<ModuleToPlan>,
) -> Result<PlannedHostedProgram, PlanError> {
    let mut implementations = Vec::new();
    modules
        .into_iter()
        .map(|module| plan_hosted_module(module, registry, &mut implementations))
        .collect::<Result<Vec<_>, _>>()
        .map(|modules| PlannedHostedProgram {
            root,
            entry: FunctionTemplateId::in_module(root, 0),
            modules,
            implementations,
        })
}

fn plan_hosted_module(
    mut module: ModuleToPlan,
    registry: &ProgramRegistry,
    implementations: &mut Vec<(
        FunctionTemplateId,
        RegisteredHostConstructions,
        RegisteredHostImplementationId,
    )>,
) -> Result<HostedPlannedModule, PlanError> {
    module
        .functions
        .into_iter()
        .map(|function| {
            plan_hosted_function(
                module.id,
                function,
                registry,
                &mut module.anonymous_functions,
                implementations,
            )
        })
        .collect::<Result<Vec<_>, _>>()
        .map(|mut functions| {
            functions.sort_by_key(|function| match function {
                HostedFunctionTemplate::GleamBody(function) => function.id().index(),
                HostedFunctionTemplate::HostTemplate(function) => function.id().index(),
            });
            HostedPlannedModule::new(HostedPlannedModuleParts {
                id: module.id,
                package: module.package,
                module: registry.module_name(module.id).clone(),
                source_context: module.source_context,
                custom_types: module.custom_types,
                external_types: module.external_types,
                constants: module.constants,
                functions,
                anonymous_functions: module.anonymous_functions.into_functions(),
            })
        })
}

fn plan_hosted_function(
    module: ModuleId,
    function: LinkedFunction,
    registry: &ProgramRegistry,
    anonymous_functions: &mut super::super::AnonymousFunctions,
    implementations: &mut Vec<(
        FunctionTemplateId,
        RegisteredHostConstructions,
        RegisteredHostImplementationId,
    )>,
) -> Result<HostedFunctionTemplate, PlanError> {
    match function {
        LinkedFunction::Gleam { info, function } => plan_function(
            info,
            function,
            PlanContext::new_in_program(module, registry, anonymous_functions),
        )
        .map(|function| HostedFunctionTemplate::GleamBody(Box::new(function))),
        LinkedFunction::ExternalFallback {
            name,
            info,
            function,
        } => plan_selected_external_fallback(
            name,
            info,
            function,
            PlanContext::new_in_program(module, registry, anonymous_functions),
        )
        .map(|function| HostedFunctionTemplate::GleamBody(Box::new(function))),
        LinkedFunction::Host {
            template,
            constructions,
            implementation,
        } => {
            implementations.push((template.id(), constructions, implementation));
            Ok(HostedFunctionTemplate::HostTemplate(Box::new(template)))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::super::plan_host_program;
    use crate::frontend::{ModuleSource, PackageSource, compile_typed_host_program};
    use crate::host::{HostModule, HostProviderSet};
    use crate::planner::PlanError;
    use ecow::EcoString;

    #[test]
    fn selected_external_fallback_body_preserves_its_planning_error() {
        let source = r#"
@external(erlang, "host", "fallback")
fn fallback() -> BitArray {
  <<1:native>>
}

pub fn main() {
  1
}
"#;
        let typed = compile_typed_host_program(
            "application",
            "main",
            [PackageSource::new(
                "application",
                Vec::<EcoString>::new(),
                [ModuleSource::new("main", "main.gleam", source)],
            )],
            HostProviderSet::new(Vec::<HostModule>::new())
                .expect("empty host modules should be valid"),
        )
        .expect("external fallback should compile");

        assert_eq!(
            plan_host_program(typed).err(),
            Some(PlanError::UnsupportedBitArraySegment {
                reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
            }),
        );
    }
}