geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::{HostFunctionId, HostNeverFunctionId, HostedNeverFunction, HostedValueFunction};
use crate::host::HostProfile;
use crate::plan::execution::function::ExecutionFunctionBody;

pub(crate) struct HostFunctionTables<Profile: HostProfile> {
    value_functions: Box<[HostedValueFunction<Profile>]>,
    never_functions: Box<[HostedNeverFunction<Profile>]>,
}

impl<Profile: HostProfile> HostFunctionTables<Profile> {
    pub(in crate::plan::execution) fn new(
        value_functions: Box<[HostedValueFunction<Profile>]>,
        never_functions: Box<[HostedNeverFunction<Profile>]>,
    ) -> Self {
        Self {
            value_functions,
            never_functions,
        }
    }

    pub(crate) fn value<Body: ExecutionFunctionBody>(
        &self,
        id: &HostFunctionId<Body>,
    ) -> &HostedValueFunction<Profile> {
        &self.value_functions[id.index()]
    }

    pub(crate) fn never(&self, id: HostNeverFunctionId) -> &HostedNeverFunction<Profile> {
        &self.never_functions[id.index()]
    }

    #[cfg(test)]
    pub(in crate::plan::execution) fn value_functions(&self) -> &[HostedValueFunction<Profile>] {
        &self.value_functions
    }

    #[cfg(test)]
    pub(in crate::plan::execution) fn never_functions(&self) -> &[HostedNeverFunction<Profile>] {
        &self.never_functions
    }
}

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

    #[test]
    fn preserves_first_use_function_metadata_and_removes_unused_implementations() {
        let math = HostModule::new("host_support", "host/math")
            .expect("host module should be valid")
            .with_function("subtract", <BigInt as std::ops::Sub>::sub)
            .expect("host function should be valid")
            .with_function("add", <BigInt as std::ops::Add>::add)
            .expect("host function should be valid")
            .with_function("unused", <BigInt as std::ops::Add>::add)
            .expect("host function should be valid")
            .with_function("positive", |value: BigInt| value > BigInt::from(0))
            .expect("host function should be valid")
            .with_function("unused_predicate", || false)
            .expect("host function should be valid");
        let hosts = HostProviderSet::new([math]).expect("host modules should be unique");
        let source = r#"
import host/math

pub fn main() {
  let added = math.add(1, 2)
  #(math.subtract(added, 1), math.positive(added))
}
"#;
        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 execution =
            HostedExecution::try_from_module_plan(plan).expect("hosted execution should seal");

        assert_eq!(execution.host_functions.value_functions().len(), 3);
        assert!(execution.host_functions.never_functions().is_empty());
        let add = &execution.host_functions.value_functions()[0];
        assert_eq!(add.package(), "host_support");
        assert_eq!(add.module(), "host/math");
        assert_eq!(add.name(), "add");
        let subtract = &execution.host_functions.value_functions()[1];
        assert_eq!(subtract.package(), "host_support");
        assert_eq!(subtract.module(), "host/math");
        assert_eq!(subtract.name(), "subtract");
        let positive = &execution.host_functions.value_functions()[2];
        assert_eq!(positive.package(), "host_support");
        assert_eq!(positive.module(), "host/math");
        assert_eq!(positive.name(), "positive");
        assert_eq!(
            execution.run_main(&mut (), &mut Vec::new()),
            Ok(Value::Tuple(vec![Value::Int(2.into()), Value::Bool(true)])),
        );
    }
}