geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::super::local;
use crate::plan::{
    FunctionTemplate, FunctionTemplateId, FunctionTemplateSignature, HostFunctionTemplate,
    HostedFunctionTemplate,
};
use std::collections::HashMap;

pub(super) struct HostTemplateCatalog {
    templates: Vec<Vec<HostLoweringTemplate>>,
}

pub(super) enum HostLoweringTemplate {
    Gleam(Box<FunctionTemplate>),
    Host(Box<HostFunctionTemplate>),
}

impl HostTemplateCatalog {
    pub(super) fn new() -> Self {
        Self {
            templates: Vec::new(),
        }
    }

    pub(super) fn push_module(
        &mut self,
        functions: Vec<HostedFunctionTemplate>,
        anonymous_functions: Vec<FunctionTemplate>,
    ) {
        let mut templates = functions
            .into_iter()
            .map(|template| match template {
                HostedFunctionTemplate::GleamBody(template) => {
                    HostLoweringTemplate::Gleam(template)
                }
                HostedFunctionTemplate::HostTemplate(template) => {
                    HostLoweringTemplate::Host(template)
                }
            })
            .collect::<Vec<_>>();
        templates.extend(
            anonymous_functions
                .into_iter()
                .map(|template| HostLoweringTemplate::Gleam(Box::new(template))),
        );
        templates.sort_by_key(HostLoweringTemplate::index);
        self.templates.push(templates);
    }

    pub(super) fn get(&self, id: FunctionTemplateId) -> &HostLoweringTemplate {
        &self.templates[id.module().index()][id.index()]
    }

    pub(super) fn entry_templates(
        &self,
    ) -> HashMap<FunctionTemplateId, local::FunctionEntryTemplate> {
        self.templates
            .iter()
            .flatten()
            .map(|template| {
                (
                    template.id(),
                    match template {
                        HostLoweringTemplate::Gleam(template) => {
                            local::FunctionEntryTemplate::new(template)
                        }
                        HostLoweringTemplate::Host(template) => {
                            local::FunctionEntryTemplate::from_shapes(
                                template.signature().shape().argument_shapes().to_vec(),
                            )
                        }
                    },
                )
            })
            .collect()
    }
}

impl HostLoweringTemplate {
    pub(super) fn signature(&self) -> &FunctionTemplateSignature {
        match self {
            Self::Gleam(template) => template.signature(),
            Self::Host(template) => template.signature(),
        }
    }

    fn id(&self) -> FunctionTemplateId {
        self.signature().id()
    }

    fn index(&self) -> usize {
        self.id().index()
    }
}

#[cfg(test)]
mod tests {
    use super::{HostLoweringTemplate, HostTemplateCatalog};
    use crate::frontend::{ModuleSource, PackageSource, compile_typed_host_program};
    use crate::host::{HostModule, HostProviderSet};
    use crate::plan::FunctionTemplateId;
    use crate::planner::plan_host_program;

    #[test]
    fn catalogs_host_named_and_anonymous_templates_by_module_local_id() {
        let hosts = HostProviderSet::new([HostModule::new("host_support", "host/predicate")
            .expect("host module should be valid")
            .with_function("negate", <bool as std::ops::Not>::not)
            .expect("host function should be valid")])
        .expect("host modules should be unique");
        let source = r#"
import host/predicate

fn apply(function: fn(Bool) -> Bool, value: Bool) {
  function(value)
}

pub fn main() {
  apply(fn(value) { predicate.negate(value) }, True)
}
"#;
        let typed = compile_typed_host_program(
            "application",
            "main",
            [PackageSource::new(
                "application",
                ["host_support"],
                [ModuleSource::new("main", "main.gleam", source)],
            )],
            hosts,
        )
        .expect("host source should compile");
        let plan = plan_host_program(typed).expect("host source should plan");
        let parts = plan.into_parts();
        let root = parts.root;
        let host = parts
            .modules
            .iter()
            .find(|module| module.package() == "host_support")
            .expect("host package should be planned")
            .id();
        let mut catalog = HostTemplateCatalog::new();

        for module in parts.modules {
            let parts = module.into_parts();
            catalog.push_module(parts.functions, parts.anonymous_functions);
        }

        assert_eq!(
            [
                FunctionTemplateId::in_module(host, 0),
                FunctionTemplateId::in_module(root, 0),
                FunctionTemplateId::in_module(root, 1),
                FunctionTemplateId::in_module(root, 2),
            ]
            .map(|id| match catalog.get(id) {
                HostLoweringTemplate::Gleam(function) => ("gleam", function.name().as_str()),
                HostLoweringTemplate::Host(function) => ("host", function.name().as_str()),
            }),
            [
                ("host", "negate"),
                ("gleam", "main"),
                ("gleam", "apply"),
                ("gleam", "<anonymous:0>"),
            ],
        );
        assert_eq!(catalog.entry_templates().len(), 4);
    }
}