geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use crate::plan::{
    ConstantTemplateId, ConstantTemplateSignature, CustomTypeDefinition, CustomTypeName,
    ExternalTypeDefinition, ExternalTypeName, ModuleId, ValueShape,
};
use crate::planner::context::FunctionInfo;
use crate::planner::module::constant::ConstantSignatures;
use ecow::EcoString;
use std::collections::{HashMap, HashSet};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::planner) enum ModuleConstantResolutionError {
    MissingConstant,
    Instantiation,
}

pub(in crate::planner) struct ProgramRegistry {
    by_name: HashMap<EcoString, ModuleId>,
    modules: Vec<ModuleRegistry>,
}

pub(in crate::planner) struct ModuleRegistry {
    name: EcoString,
    custom_types: Vec<CustomTypeDefinition>,
    external_types: Vec<ExternalTypeDefinition>,
    functions: HashMap<EcoString, FunctionInfo>,
    executable_externals: HashSet<EcoString>,
    constants: ConstantSignatures,
}

impl ProgramRegistry {
    pub(in crate::planner) fn new(modules: Vec<ModuleRegistry>) -> Self {
        let by_name = modules
            .iter()
            .enumerate()
            .map(|(index, module)| (module.name.clone(), ModuleId::new(index)))
            .collect();
        Self { by_name, modules }
    }

    pub(in crate::planner) fn module_name(&self, module: ModuleId) -> &EcoString {
        &self.modules[module.index()].name
    }

    pub(in crate::planner) fn module_id(&self, module: &EcoString) -> Option<ModuleId> {
        self.by_name.get(module).copied()
    }

    pub(in crate::planner) fn function(
        &self,
        module: ModuleId,
        name: &EcoString,
    ) -> Option<FunctionInfo> {
        self.modules[module.index()].functions.get(name).cloned()
    }

    pub(in crate::planner) fn executable_external(
        &self,
        module: &EcoString,
        name: &EcoString,
    ) -> bool {
        self.module_id(module).is_some_and(|module| {
            self.modules[module.index()]
                .executable_externals
                .contains(name)
        })
    }

    pub(in crate::planner) fn constant_instantiation(
        &self,
        module: ModuleId,
        name: &EcoString,
        shape: &ValueShape,
    ) -> Result<crate::plan::ConstantInstantiation, ModuleConstantResolutionError> {
        let signature = self.modules[module.index()]
            .constants
            .signature(name)
            .ok_or(ModuleConstantResolutionError::MissingConstant)?;
        crate::planner::type_parameter::instantiate_constant(signature, shape)
            .ok_or(ModuleConstantResolutionError::Instantiation)
    }

    pub(in crate::planner) fn constant_signature(
        &self,
        id: ConstantTemplateId,
    ) -> &ConstantTemplateSignature {
        self.modules[id.module().index()].constants.get(id)
    }

    pub(in crate::planner) fn custom_type(
        &self,
        name: &CustomTypeName,
    ) -> Option<&CustomTypeDefinition> {
        self.modules
            .iter()
            .flat_map(|module| &module.custom_types)
            .find(|definition| definition.name() == name)
    }

    pub(in crate::planner) fn custom_types(&self, module: ModuleId) -> &[CustomTypeDefinition] {
        &self.modules[module.index()].custom_types
    }

    pub(in crate::planner) fn external_type(
        &self,
        name: &ExternalTypeName,
    ) -> Option<&ExternalTypeDefinition> {
        self.modules
            .iter()
            .flat_map(|module| &module.external_types)
            .find(|definition| definition.name() == name)
    }
}

impl ModuleRegistry {
    pub(in crate::planner) fn new(
        name: EcoString,
        custom_types: Vec<CustomTypeDefinition>,
        external_types: Vec<ExternalTypeDefinition>,
        functions: HashMap<EcoString, FunctionInfo>,
        constants: ConstantSignatures,
    ) -> Self {
        Self {
            name,
            custom_types,
            external_types,
            functions,
            executable_externals: HashSet::new(),
            constants,
        }
    }

    pub(in crate::planner) fn with_executable_externals(
        mut self,
        functions: HashSet<EcoString>,
    ) -> Self {
        self.executable_externals = functions;
        self
    }
}

#[cfg(test)]
mod tests {
    use super::{ModuleConstantResolutionError, ModuleRegistry, ProgramRegistry};
    use crate::plan::{
        CustomTypeDefinition, CustomTypeName, CustomTypePublicity, FunctionShape,
        FunctionTemplateId, FunctionTemplateSignature, ModuleId, TypeScheme, ValueShape,
    };
    use crate::planner::context::FunctionInfo;
    use crate::planner::module::constant::{ConstantSignatures, reserve_constants};
    use crate::planner::support::compile;
    use crate::planner::type_parameter::TypeParameterScope;
    use std::collections::HashMap;

    #[test]
    fn registry_qualifies_same_named_declarations_by_module() {
        let alpha = ModuleId::new(0);
        let root = ModuleId::new(1);
        let alpha_type = custom_type("alpha", "Box");
        let root_type = custom_type("root", "Box");
        let module = compile("const values = []\npub fn main() { Nil }");
        let declarations = reserve_constants(root, module.definitions.constants)
            .expect("generic constant declaration should reserve");
        let (root_constants, _) = declarations.into_parts();
        let registry = ProgramRegistry::new(vec![
            ModuleRegistry::new(
                "alpha".into(),
                vec![alpha_type.clone()],
                Vec::new(),
                HashMap::from([("same".into(), function_info(alpha))]),
                ConstantSignatures::default(),
            ),
            ModuleRegistry::new(
                "root".into(),
                vec![root_type.clone()],
                Vec::new(),
                HashMap::from([("same".into(), function_info(root))]),
                root_constants,
            ),
        ]);

        assert_eq!(registry.module_name(alpha), "alpha");
        assert_eq!(registry.module_name(root), "root");
        assert_eq!(registry.module_id(&"alpha".into()), Some(alpha));
        assert_eq!(registry.module_id(&"root".into()), Some(root));
        assert_eq!(registry.module_id(&"missing".into()), None);
        assert_eq!(
            registry
                .function(alpha, &"same".into())
                .map(function_template_id),
            Some(FunctionTemplateId::in_module(alpha, 0)),
        );
        assert_eq!(
            registry
                .function(root, &"same".into())
                .map(function_template_id),
            Some(FunctionTemplateId::in_module(root, 0)),
        );
        assert_eq!(
            registry
                .function(root, &"missing".into())
                .map(function_template_id),
            None,
        );
        assert_eq!(registry.custom_type(alpha_type.name()), Some(&alpha_type),);
        assert_eq!(registry.custom_type(root_type.name()), Some(&root_type));
        assert_eq!(
            registry.custom_type(&CustomTypeName::new(
                "geam".into(),
                "missing".into(),
                "Box".into(),
            )),
            None,
        );
        assert_eq!(
            registry.constant_instantiation(root, &"missing".into(), &ValueShape::Int),
            Err(ModuleConstantResolutionError::MissingConstant),
        );
        assert_eq!(
            registry
                .constant_instantiation(
                    root,
                    &"values".into(),
                    &ValueShape::List(Box::new(ValueShape::Int)),
                )
                .map(crate::plan::ConstantValue::reference)
                .map(|value| value.shape()),
            Ok(ValueShape::List(Box::new(ValueShape::Int))),
        );
        assert_eq!(
            registry.constant_instantiation(root, &"values".into(), &ValueShape::Int),
            Err(ModuleConstantResolutionError::Instantiation),
        );
    }

    fn function_template_id(info: FunctionInfo) -> FunctionTemplateId {
        info.signature.id()
    }

    fn function_info(module: ModuleId) -> FunctionInfo {
        FunctionInfo {
            signature: FunctionTemplateSignature::new(
                FunctionTemplateId::in_module(module, 0),
                TypeScheme::new(0),
                FunctionShape::new(Vec::new(), ValueShape::Int),
            ),
            type_parameters: TypeParameterScope::default(),
            return_shape: ValueShape::Int,
            params: Vec::new(),
            definition_span: crate::plan::SourceSpan::new(0, 0),
        }
    }

    fn custom_type(module: &str, name: &str) -> CustomTypeDefinition {
        CustomTypeDefinition::new(
            CustomTypeName::new("geam".into(), module.into(), name.into()),
            CustomTypePublicity::Public,
            false,
            Vec::new(),
            Vec::new(),
        )
    }
}