geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::HostFunctionTemplate;
use crate::plan::{
    ConstantTemplates, CustomTypeDefinition, ExternalTypeDefinition, FunctionTemplate, ModuleId,
    SourceContext,
};
use ecow::EcoString;

pub struct HostedPlannedModule {
    id: ModuleId,
    package: EcoString,
    module: EcoString,
    source_context: Option<SourceContext>,
    custom_types: Vec<CustomTypeDefinition>,
    external_types: Vec<ExternalTypeDefinition>,
    constants: ConstantTemplates,
    functions: Vec<HostedFunctionTemplate>,
    anonymous_functions: Vec<FunctionTemplate>,
}

pub enum HostedFunctionTemplate {
    GleamBody(Box<FunctionTemplate>),
    HostTemplate(Box<HostFunctionTemplate>),
}

pub(crate) struct HostedPlannedModuleParts {
    pub(crate) id: ModuleId,
    pub(crate) package: EcoString,
    pub(crate) module: EcoString,
    pub(crate) source_context: Option<SourceContext>,
    pub(crate) custom_types: Vec<CustomTypeDefinition>,
    pub(crate) external_types: Vec<ExternalTypeDefinition>,
    pub(crate) constants: ConstantTemplates,
    pub(crate) functions: Vec<HostedFunctionTemplate>,
    pub(crate) anonymous_functions: Vec<FunctionTemplate>,
}

impl HostedPlannedModule {
    pub(crate) fn new(parts: HostedPlannedModuleParts) -> Self {
        Self {
            id: parts.id,
            package: parts.package,
            module: parts.module,
            source_context: parts.source_context,
            custom_types: parts.custom_types,
            external_types: parts.external_types,
            constants: parts.constants,
            functions: parts.functions,
            anonymous_functions: parts.anonymous_functions,
        }
    }

    pub fn id(&self) -> ModuleId {
        self.id
    }

    pub fn package(&self) -> &EcoString {
        &self.package
    }

    pub fn module(&self) -> &EcoString {
        &self.module
    }

    pub fn source_context(&self) -> Option<&SourceContext> {
        self.source_context.as_ref()
    }

    pub fn functions(&self) -> &[HostedFunctionTemplate] {
        &self.functions
    }

    pub fn external_types(&self) -> &[ExternalTypeDefinition] {
        &self.external_types
    }

    pub(crate) fn into_parts(self) -> HostedPlannedModuleParts {
        HostedPlannedModuleParts {
            id: self.id,
            package: self.package,
            module: self.module,
            source_context: self.source_context,
            custom_types: self.custom_types,
            external_types: self.external_types,
            constants: self.constants,
            functions: self.functions,
            anonymous_functions: self.anonymous_functions,
        }
    }
}

impl HostedFunctionTemplate {
    pub fn gleam_body(&self) -> Option<&FunctionTemplate> {
        match self {
            Self::GleamBody(function) => Some(function.as_ref()),
            Self::HostTemplate(_) => None,
        }
    }

    pub fn host_template(&self) -> Option<&HostFunctionTemplate> {
        match self {
            Self::GleamBody(_) => None,
            Self::HostTemplate(function) => Some(function.as_ref()),
        }
    }
}

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

    #[test]
    fn function_templates_expose_exactly_one_body_owner() {
        let hosts = HostProviderSet::new([HostModule::new("host_support", "host/math")
            .expect("host module should be valid")
            .with_function("identity", |value: BigInt| value)
            .expect("host function should be valid")])
        .expect("host modules should be unique");
        let source = r#"
import host/math

pub fn main() {
  math.identity(1)
}
"#;
        let typed = compile_typed_host_program(
            "application",
            "main",
            [PackageSource::new(
                "application",
                ["host_support"],
                [ModuleSource::new("main", "main.gleam", source)],
            )],
            hosts,
        )
        .expect("host program should compile");
        let plan = plan_host_program(typed).expect("host program should plan");
        let host = &plan.modules()[0].functions()[0];
        let source = &plan.modules()[1].functions()[0];

        assert_eq!(
            host.host_template()
                .expect("source-less function should own a host template")
                .name(),
            "identity",
        );
        assert!(host.gleam_body().is_none());
        assert_eq!(
            source
                .gleam_body()
                .expect("source function should own a Gleam body")
                .name(),
            "main",
        );
        assert!(source.host_template().is_none());
    }
}