mod function;
use super::{Component, GleamStdlibHostProfile};
use crate::{BitArrayValue, HostProviderModule, HostRegistrationError};
use bitvec::order::Msb0;
use bitvec::vec::BitVec;
use ecow::EcoString;
use geam_core::provider::HostResult;
use geam_core::provider_support::{bit_array_bits, bit_array_from_bits};
use num_bigint::BigInt;
#[geam_macros::module(
path = "gleam/bit_array",
crate_path = geam_core,
profile = crate::GleamStdlibHostProfile,
component = crate::Component<Profile::Io>,
)]
mod provider {
use super::{
BigInt, BitArrayValue, BitVec, EcoString, HostResult, Msb0, bit_array_bits,
bit_array_from_bits, function,
};
#[geam_macros::function]
fn from_string(value: EcoString) -> BitArrayValue {
function::from_string(value)
}
#[geam_macros::function]
fn bit_size(value: BitArrayValue) -> BigInt {
function::bit_size(value)
}
#[geam_macros::function]
fn byte_size(value: BitArrayValue) -> BigInt {
function::byte_size(value)
}
#[geam_macros::function]
fn pad_to_bytes(value: BitArrayValue) -> BitArrayValue {
function::pad_to_bytes(value)
}
#[geam_macros::function]
fn slice(value: BitArrayValue, position: BigInt, length: BigInt) -> Result<BitArrayValue, ()> {
function::slice(value, position, length)
}
#[geam_macros::function]
fn unsafe_to_string(value: BitArrayValue) -> HostResult<EcoString> {
function::unsafe_to_string(value).map_err(Into::into)
}
#[geam_macros::function]
fn concat(values: geam_core::provider::List<BitArrayValue>) -> BitArrayValue {
let mut bits = BitVec::<u8, Msb0>::new();
let mut index = 0;
while let Some(value) = values.get(index) {
bits.extend_from_bitslice(bit_array_bits(&value));
index += 1;
}
bit_array_from_bits(bits)
}
#[geam_macros::function]
fn base64_encode(value: BitArrayValue, padding: bool) -> EcoString {
function::base64_encode(value, padding)
}
#[geam_macros::function]
fn decode64(value: EcoString) -> Result<BitArrayValue, ()> {
function::decode64(value)
}
#[geam_macros::function]
fn base16_encode(value: BitArrayValue) -> EcoString {
function::base16_encode(value)
}
#[geam_macros::function]
fn base16_decode(value: EcoString) -> Result<BitArrayValue, ()> {
function::base16_decode(value)
}
#[geam_macros::function]
fn bit_array_to_int_and_size(value: BitArrayValue) -> (BigInt, BigInt) {
function::bit_array_to_int_and_size(value)
}
}
pub(super) fn host_provider<Profile>() -> Result<HostProviderModule<Profile>, HostRegistrationError>
where
Profile: GleamStdlibHostProfile,
{
provider::__geam_module::<Profile>()
}
#[cfg(test)]
mod tests {
use super::host_provider;
use crate::{
ExecutionError, HostModule, HostProviderSet, HostedExecution, ModuleSource, PackageSource,
ValueType, compile_typed_host_program, plan_host_program,
};
use crate::{GleamStdlibProfile, GleamStdlibRunState};
use ecow::EcoString;
use geam_core::{HostError, InvariantError};
const BIT_ARRAY_DECLARATIONS: &str = r#"
@external(erlang, "host", "from_string")
fn from_string(value: String) -> BitArray
@external(erlang, "host", "bit_size")
fn bit_size(value: BitArray) -> Int
@external(erlang, "host", "byte_size")
fn byte_size(value: BitArray) -> Int
@external(erlang, "host", "pad_to_bytes")
fn pad_to_bytes(value: BitArray) -> BitArray
@external(erlang, "host", "slice")
fn slice(value: BitArray, position: Int, length: Int) -> Result(BitArray, Nil)
@external(erlang, "host", "unsafe_to_string")
fn unsafe_to_string(value: BitArray) -> String
@external(erlang, "host", "concat")
fn concat(values: List(BitArray)) -> BitArray
@external(erlang, "host", "base64_encode")
fn base64_encode(value: BitArray, padding: Bool) -> String
@external(erlang, "host", "decode64")
fn decode64(value: String) -> Result(BitArray, Nil)
@external(erlang, "host", "base16_encode")
fn base16_encode(value: BitArray) -> String
@external(erlang, "host", "base16_decode")
fn base16_decode(value: String) -> Result(BitArray, Nil)
@external(erlang, "host", "bit_array_to_int_and_size")
fn bit_array_to_int_and_size(value: BitArray) -> #(Int, Int)
"#;
fn execution(source: &str) -> HostedExecution<GleamStdlibProfile> {
let source = format!("{BIT_ARRAY_DECLARATIONS}\n{source}");
let provider = host_provider::<GleamStdlibProfile>()
.expect("official bit array provider should register");
let hosts = HostProviderSet::with_providers(
Vec::<HostModule<GleamStdlibProfile>>::new(),
[provider],
)
.expect("bit array provider module should be unique");
let typed = compile_typed_host_program(
"gleam_stdlib",
"gleam/bit_array",
[PackageSource::new(
"gleam_stdlib",
Vec::<EcoString>::new(),
[ModuleSource::new(
"gleam/bit_array",
"src/gleam/bit_array.gleam",
source,
)],
)],
hosts,
)
.expect("synthetic bit array source should compile");
let plan = plan_host_program(typed).expect("synthetic bit array source should plan");
HostedExecution::try_from_module_plan(plan).expect("bit array execution should seal")
}
#[test]
fn registers_the_exact_official_bit_array_provider_inventory() {
let provider = host_provider::<GleamStdlibProfile>()
.expect("official bit array provider should register");
assert_eq!(provider.package(), "gleam_stdlib");
assert_eq!(provider.module(), "gleam/bit_array");
assert_eq!(provider.external_types().count(), 0);
assert_eq!(
provider
.functions()
.map(|function| function.name().as_str())
.collect::<Vec<_>>(),
[
"from_string",
"bit_size",
"byte_size",
"pad_to_bytes",
"slice",
"unsafe_to_string",
"concat",
"base64_encode",
"decode64",
"base16_encode",
"base16_decode",
"bit_array_to_int_and_size",
],
);
}
#[test]
fn executes_every_bit_array_provider_through_the_hosted_pipeline() {
let execution = execution(
r#"
pub fn main() {
assert from_string("AB") == <<65, 66>>
assert bit_size(<<5:size(3)>>) == 3
assert byte_size(<<5:size(3)>>) == 1
assert pad_to_bytes(<<5:size(3)>>) == <<5:size(3), 0:size(5)>>
assert slice(<<1, 2, 3>>, 1, 1) == Ok(<<2>>)
assert slice(<<1, 2, 3>>, 2, -1) == Ok(<<2>>)
assert slice(<<1>>, 0, -1) == Error(Nil)
assert slice(<<1, 2, 3>>, 4, 1) == Error(Nil)
assert slice(<<1:size(2)>>, 0, 0) == Error(Nil)
assert unsafe_to_string(<<65, 66>>) == "AB"
assert concat([<<1>>, <<2:size(2)>>]) == <<1, 2:size(2)>>
assert base64_encode(<<5:size(3)>>, True) == "oA=="
assert base64_encode(<<5:size(3)>>, False) == "oA"
assert decode64("AQ==") == Ok(<<1>>)
assert decode64("AB==") == Ok(<<0>>)
assert byte_size(from_string("aG \t\nVsbG8=")) == 12
assert decode64("aG \t\nVsbG8=") == Ok(<<"hello":utf8>>)
assert decode64("***=") == Error(Nil)
assert base16_encode(<<5:size(3)>>) == "A0"
assert base16_decode("01") == Ok(<<1>>)
assert base16_decode("GG") == Error(Nil)
bit_array_to_int_and_size(<<5:size(3)>>)
}
"#,
);
let value = execution
.run_main(
&mut GleamStdlibRunState::from_seed([0; 32]),
&mut Vec::new(),
)
.expect("bit array providers should run");
assert_eq!(value.inspect().to_string(), "#(5, 3)");
}
#[test]
fn preserves_invalid_utf8_through_the_bit_array_host_adapter() {
let execution = execution("pub fn main() { unsafe_to_string(<<255>>) }");
let error = execution
.run_main(
&mut GleamStdlibRunState::from_seed([0; 32]),
&mut Vec::new(),
)
.expect_err("invalid UTF-8 should fail");
let error = expect_bit_array_host_error(error);
assert_eq!(error.package(), "gleam_stdlib");
assert_eq!(error.module(), "gleam/bit_array");
assert_eq!(error.function(), "unsafe_to_string");
assert_eq!(error.failure().message(), "bit array is not valid UTF-8");
}
#[test]
#[should_panic(expected = "invalid UTF-8 should remain a host failure")]
fn bit_array_host_failure_assertion_rejects_other_execution_errors() {
let _ = expect_bit_array_host_error(ExecutionError::Invariant(
InvariantError::ListIndexOutOfBounds {
item_type: ValueType::BitArray,
index: 1,
length: 0,
},
));
}
fn expect_bit_array_host_error(error: ExecutionError) -> Box<HostError> {
let ExecutionError::Host(error) = error else {
panic!("invalid UTF-8 should remain a host failure");
};
error
}
}