geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::schema::DynamicSchema;
use crate::gleam_stdlib::GleamStdlibHostProfile;
use crate::host::HostStoredValueFamily;
use crate::{
    HostExternalEquality, HostExternalHashing, HostExternalInspection, HostExternalStorage,
    HostExternalStore, HostStoredDynamic,
};
use ecow::EcoString;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

#[derive(Default)]
pub(in crate::gleam_stdlib) struct Stores {
    values: HostExternalStore<DynamicPayload>,
}

pub(crate) enum DynamicPayload {
    Stored {
        representation: DynamicRepresentation,
        value: HostStoredDynamic,
    },
    Array {
        value: HostStoredDynamic,
        elements: Box<[HostStoredDynamic]>,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum DynamicRepresentation {
    Bool,
    String,
    Float,
    Int,
    BitArray,
    UtfCodepoint,
    List,
    Array,
    Dict,
    Nil,
    Function,
    Custom,
    External,
}

impl DynamicRepresentation {
    pub(super) fn from_value(value: &HostStoredDynamic) -> Self {
        Self::from_family(value.value_family(), value.value_type())
    }

    pub(super) fn name(self) -> &'static str {
        match self {
            Self::Bool => "Bool",
            Self::String => "String",
            Self::Float => "Float",
            Self::Int => "Int",
            Self::BitArray => "BitArray",
            Self::UtfCodepoint => "UtfCodepoint",
            Self::List => "List",
            Self::Array => "Array",
            Self::Dict => "Dict",
            Self::Nil => "Nil",
            Self::Function => "Function",
            Self::Custom => "Custom",
            Self::External => "External",
        }
    }

    fn from_family(family: HostStoredValueFamily, type_: &crate::ValueType) -> Self {
        if let crate::ValueType::External(type_) = type_
            && type_.type_name().package() == "gleam_stdlib"
            && type_.type_name().module() == "gleam/dict"
            && type_.type_name().name() == "Dict"
        {
            return Self::Dict;
        }

        match family {
            HostStoredValueFamily::Int => Self::Int,
            HostStoredValueFamily::Float => Self::Float,
            HostStoredValueFamily::String => Self::String,
            HostStoredValueFamily::BitArray => Self::BitArray,
            HostStoredValueFamily::UtfCodepoint => Self::UtfCodepoint,
            HostStoredValueFamily::Bool => Self::Bool,
            HostStoredValueFamily::Nil => Self::Nil,
            HostStoredValueFamily::List => Self::List,
            HostStoredValueFamily::Tuple => Self::Array,
            HostStoredValueFamily::Custom => Self::Custom,
            HostStoredValueFamily::External => Self::External,
            HostStoredValueFamily::Function => Self::Function,
        }
    }
}

impl DynamicPayload {
    pub(super) fn representation(&self) -> DynamicRepresentation {
        match self {
            Self::Stored { representation, .. } => *representation,
            Self::Array { .. } => DynamicRepresentation::Array,
        }
    }

    pub(super) fn value(&self) -> &HostStoredDynamic {
        match self {
            Self::Stored { value, .. } | Self::Array { value, .. } => value,
        }
    }
}

pub(crate) struct DynamicExternalStorage;

impl<Profile> HostExternalStorage<Profile, DynamicSchema> for DynamicExternalStorage
where
    Profile: GleamStdlibHostProfile,
{
    type Payload = DynamicPayload;

    fn store(stores: &Profile::ExternalStores) -> &HostExternalStore<Self::Payload> {
        &Profile::gleam_stdlib_stores(stores).dynamic.values
    }

    fn source_equal(
        context: &HostExternalEquality<'_>,
        left: &Self::Payload,
        right: &Self::Payload,
    ) -> bool {
        left.representation() == right.representation()
            && context.dynamic_values_equal(left.value(), right.value())
    }

    fn source_hash(context: &HostExternalHashing<'_>, value: &Self::Payload) -> u64 {
        let mut hasher = DefaultHasher::new();
        value.representation().hash(&mut hasher);
        context.dynamic_value_hash(value.value()).hash(&mut hasher);
        hasher.finish()
    }

    fn inspect(context: &HostExternalInspection<'_>, value: &Self::Payload) -> EcoString {
        match value {
            DynamicPayload::Stored { value, .. } => context.inspect_dynamic_value(value),
            DynamicPayload::Array { elements, .. } => {
                let mut output = String::from("#(");
                let mut separator = "";
                for item in elements {
                    output.push_str(separator);
                    output.push_str(&context.inspect_dynamic_value(item));
                    separator = ", ";
                }
                output.push(')');
                output.into()
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::DynamicRepresentation;
    use crate::host::HostStoredValueFamily;
    use crate::{
        CustomType, CustomTypeName, ExternalType, ExternalTypeName, FunctionType, ValueType,
    };

    #[test]
    fn classifies_every_runtime_value_family_and_nominal_dictionary() {
        let dict = ExternalType::new(
            ExternalTypeName::new("gleam_stdlib".into(), "gleam/dict".into(), "Dict".into()),
            vec![ValueType::Int, ValueType::String],
        );
        let resource = ExternalType::new(
            ExternalTypeName::new("domain".into(), "domain/resource".into(), "Resource".into()),
            Vec::new(),
        );
        let custom = CustomType::new(
            CustomTypeName::new("domain".into(), "domain/item".into(), "Item".into()),
            Vec::new(),
        );
        let cases = [
            (HostStoredValueFamily::Int, ValueType::Int, "Int"),
            (HostStoredValueFamily::Float, ValueType::Float, "Float"),
            (HostStoredValueFamily::String, ValueType::String, "String"),
            (
                HostStoredValueFamily::BitArray,
                ValueType::BitArray,
                "BitArray",
            ),
            (
                HostStoredValueFamily::UtfCodepoint,
                ValueType::UtfCodepoint,
                "UtfCodepoint",
            ),
            (HostStoredValueFamily::Bool, ValueType::Bool, "Bool"),
            (HostStoredValueFamily::Nil, ValueType::Nil, "Nil"),
            (
                HostStoredValueFamily::Tuple,
                ValueType::Tuple(vec![ValueType::Int]),
                "Array",
            ),
            (
                HostStoredValueFamily::List,
                ValueType::List(Box::new(ValueType::Int)),
                "List",
            ),
            (
                HostStoredValueFamily::Function,
                ValueType::Function(Box::new(FunctionType::new(
                    vec![ValueType::Int],
                    ValueType::Bool,
                ))),
                "Function",
            ),
            (
                HostStoredValueFamily::Custom,
                ValueType::Custom(custom),
                "Custom",
            ),
            (
                HostStoredValueFamily::External,
                ValueType::External(dict),
                "Dict",
            ),
            (
                HostStoredValueFamily::External,
                ValueType::External(resource),
                "External",
            ),
        ];

        for (family, type_, expected) in cases {
            assert_eq!(
                DynamicRepresentation::from_family(family, &type_).name(),
                expected,
            );
        }
    }
}