geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
mod inspect;
mod slice;
mod unicode;

pub(super) use self::inspect::do_inspect;
pub(super) use self::slice::{erl_split, erl_trim, grapheme_slice, unsafe_byte_slice};
pub(super) use self::unicode::{
    from_utf_codepoints, pop_grapheme, unsafe_int_to_utf_codepoint, utf_codepoint_to_int,
};

use crate::gleam_stdlib::{StringTreeExternalStorage, StringTreeSchema};
use crate::{HostExternalBinding, HostProvider};
use ecow::EcoString;
use num_bigint::BigInt;
use std::marker::PhantomData;

pub(super) struct StringProvider<Profile>(PhantomData<Profile>);

impl<Profile> HostProvider<Profile> for StringProvider<Profile>
where
    Profile: super::super::GleamStdlibHostProfile,
{
    type State = Profile::RunState;

    fn project(state: &mut Profile::RunState) -> &mut Self::State {
        state
    }
}

impl<Profile> HostExternalBinding<Profile, StringTreeSchema> for StringProvider<Profile>
where
    Profile: super::super::GleamStdlibHostProfile,
{
    type Storage = StringTreeExternalStorage;
}

pub(super) fn length(string: EcoString) -> BigInt {
    BigInt::from(
        unicode_segmentation::UnicodeSegmentation::graphemes(string.as_str(), true).count(),
    )
}

pub(super) fn lowercase(string: EcoString) -> EcoString {
    string.to_lowercase()
}

pub(super) fn uppercase(string: EcoString) -> EcoString {
    string.to_uppercase()
}

pub(super) fn less_than(left: EcoString, right: EcoString) -> bool {
    left < right
}

pub(super) fn crop(string: EcoString, substring: EcoString) -> EcoString {
    match string.find(substring.as_str()) {
        Some(index) => string[index..].into(),
        None => string,
    }
}

pub(super) fn contains(haystack: EcoString, needle: EcoString) -> bool {
    haystack.contains(needle.as_str())
}

pub(super) fn starts_with(string: EcoString, prefix: EcoString) -> bool {
    string.starts_with(prefix.as_str())
}

pub(super) fn ends_with(string: EcoString, suffix: EcoString) -> bool {
    string.ends_with(suffix.as_str())
}

pub(super) fn byte_size(string: EcoString) -> BigInt {
    BigInt::from(string.len())
}

pub(super) fn remove_prefix(string: EcoString, prefix: EcoString) -> EcoString {
    string
        .strip_prefix(prefix.as_str())
        .map(EcoString::from)
        .unwrap_or(string)
}

pub(super) fn remove_suffix(string: EcoString, suffix: EcoString) -> EcoString {
    string
        .strip_suffix(suffix.as_str())
        .map(EcoString::from)
        .unwrap_or(string)
}

#[cfg(test)]
mod tests {
    use super::{
        StringProvider, byte_size, contains, crop, ends_with, length, less_than, lowercase,
        remove_prefix, remove_suffix, starts_with, uppercase,
    };
    use crate::HostProvider;
    use crate::gleam_stdlib::{GleamStdlibProfile, GleamStdlibRunState};
    use num_bigint::BigInt;

    #[test]
    fn projects_the_stdlib_run_state() {
        let mut state = GleamStdlibRunState::from_seed([0; 32]);
        let original = std::ptr::from_ref(&state);
        let projected =
            <StringProvider<GleamStdlibProfile> as HostProvider<GleamStdlibProfile>>::project(
                &mut state,
            );

        assert_eq!(std::ptr::from_mut(projected).cast_const(), original);
    }

    #[test]
    fn applies_scalar_string_semantics() {
        assert_eq!(length("A👍🏽e\u{301}".into()), BigInt::from(3));
        assert_eq!(lowercase("Gleam İ".into()), "gleam i\u{307}");
        assert_eq!(uppercase("Gleam ß".into()), "GLEAM SS");
        assert!(less_than("A".into(), "B".into()));
        assert!(!less_than("B".into(), "A".into()));
        assert_eq!(crop("The Lone Gunmen".into(), "Lone".into()), "Lone Gunmen");
        assert_eq!(
            crop("The Lone Gunmen".into(), "Fox".into()),
            "The Lone Gunmen"
        );
        assert!(contains("theory".into(), "ory".into()));
        assert!(!contains("theory".into(), "THE".into()));
        assert!(starts_with("theory".into(), "the".into()));
        assert!(ends_with("theory".into(), "ory".into()));
        assert_eq!(byte_size("👍".into()), BigInt::from(4));
        assert_eq!(remove_prefix("@lpil".into(), "@".into()), "lpil");
        assert_eq!(remove_prefix("hello!".into(), "@".into()), "hello!");
        assert_eq!(remove_suffix("Hello!".into(), "!".into()), "Hello");
        assert_eq!(remove_suffix("Hello!?".into(), "!".into()), "Hello!?");
    }
}