diff --git a/native_implemented/otp/src/binary.rs b/native_implemented/otp/src/binary.rs index fdfcfbb1b..c2e9cb3f7 100644 --- a/native_implemented/otp/src/binary.rs +++ b/native_implemented/otp/src/binary.rs @@ -1,3 +1,4 @@ +pub mod encode_unsigned_1; pub mod to_term; use std::backtrace::Backtrace; @@ -11,6 +12,14 @@ use liblumen_alloc::erts::exception::{self, ArcError, Exception, InternalExcepti use liblumen_alloc::erts::term::prelude::*; use liblumen_alloc::Process; +pub fn module() -> Atom { + Atom::from_str("binary") +} + +pub fn module_id() -> usize { + module().id() +} + pub struct PartRange { pub byte_offset: usize, pub byte_len: usize, diff --git a/native_implemented/otp/src/binary/encode_unsigned_1.rs b/native_implemented/otp/src/binary/encode_unsigned_1.rs new file mode 100644 index 000000000..4082da7c6 --- /dev/null +++ b/native_implemented/otp/src/binary/encode_unsigned_1.rs @@ -0,0 +1,47 @@ +#[cfg(all(not(target_arch = "wasm32"), test))] +mod test; + +use crate::runtime::context::{term_is_not_integer, term_is_not_non_negative_integer}; +use anyhow::*; +use liblumen_alloc::erts::exception; +use liblumen_alloc::erts::process::Process; +use liblumen_alloc::erts::term::prelude::*; +use num_bigint::Sign; + +/// Returns the smallest possible representation in a binary digit representation for the given big +/// endian unsigned integer. +#[native_implemented::function(binary:encode_unsigned/1)] +pub fn result(process: &Process, term: Term) -> exception::Result { + match term.decode().unwrap() { + TypedTerm::SmallInteger(small_integer) => { + let signed: isize = small_integer.into(); + if signed < 0 { + return Err(TryIntoIntegerError::Type) + .context(term_is_not_non_negative_integer("encoded_unsigned", term)) + .map_err(From::from); + } + let mut bytes: Vec = small_integer.to_le_bytes(); + bytes.reverse(); + Ok(process.binary_from_bytes(without_leading_zeros(&bytes))) + } + TypedTerm::BigInteger(big_integer) => { + if Sign::Minus == big_integer.sign() { + return Err(TryIntoIntegerError::Type) + .context(term_is_not_non_negative_integer("encoded_unsigned", term)) + .map_err(From::from); + } + + let bytes: Vec = big_integer.to_signed_bytes_be(); + Ok(process.binary_from_bytes(without_leading_zeros(&bytes))) + } + _ => Err(TryIntoIntegerError::Type) + .context(term_is_not_integer("encoded_unsigned", term)) + .map_err(From::from), + } +} + +#[inline] +fn without_leading_zeros(bytes: &Vec) -> &[u8] { + let first_nonzero_index = bytes.iter().position(|&b| b != 0).unwrap_or(0); + &bytes[first_nonzero_index..] +} diff --git a/native_implemented/otp/src/binary/encode_unsigned_1/test.rs b/native_implemented/otp/src/binary/encode_unsigned_1/test.rs new file mode 100644 index 000000000..86484b8f4 --- /dev/null +++ b/native_implemented/otp/src/binary/encode_unsigned_1/test.rs @@ -0,0 +1,111 @@ +use crate::binary::encode_unsigned_1::result; +use crate::test::with_process; +use crate::test::*; +use liblumen_alloc::erts::term::prelude::*; +use num_bigint::{BigInt, ToBigInt}; +use proptest::strategy::Just; + +// 1> binary:encode_unsigned(11111111). +// <<169,138,199>> +#[test] +fn otp_doctest() { + with_process(|process| { + assert_eq!( + result(process, process.integer(11111111)), + Ok(process.binary_from_bytes(&[169, 138, 199])) + ) + }); +} + +#[test] +fn smallest_big_int() { + let largest_small_int_as_big_int: BigInt = SmallInteger::MAX_VALUE.into(); + let smallest_big_int: BigInt = largest_small_int_as_big_int + 1; + + // 1> binary:encode_unsigned(70368744177664). + // <<64,0,0,0,0,0>> + + with_process(|process| { + assert_eq!( + result(process, process.integer(smallest_big_int)), + Ok(process.binary_from_bytes(&[64, 0, 0, 0, 0, 0])) + ) + }); +} + +#[test] +fn big_int_with_middle_zeros() { + let largest_small_int_as_big_int: BigInt = SmallInteger::MAX_VALUE.into(); + let big_int_with_middle_zeros: BigInt = largest_small_int_as_big_int + 2; + + // 1> binary:encode_unsigned(70368744177665). + // <<64,0,0,0,0,1>> + with_process(|process| { + assert_eq!( + result(process, process.integer(big_int_with_middle_zeros)), + Ok(process.binary_from_bytes(&[64, 0, 0, 0, 0, 1])) + ) + }); +} + +#[test] +fn small_int_with_middle_zeros() { + // 1> binary:encode_unsigned(11075783). + // <<169,0,199>> + let largest_small_int_as_big_int: BigInt = SmallInteger::MAX_VALUE.into(); + assert!(11075783.to_bigint().unwrap() < largest_small_int_as_big_int); + + with_process(|process| { + assert_eq!( + result(process, process.integer(11075783)), + Ok(process.binary_from_bytes(&[169, 0, 199])) + ) + }); +} + +#[test] +fn small_int_with_trailing_zeros() { + // 1> binary:encode_unsigned(16777216). + // <<1,0,0,0>> + let largest_small_int_as_big_int: BigInt = SmallInteger::MAX_VALUE.into(); + assert!(16777216.to_bigint().unwrap() < largest_small_int_as_big_int); + + with_process(|process| { + assert_eq!( + result(process, process.integer(16777216)), + Ok(process.binary_from_bytes(&[1, 0, 0, 0])) + ) + }); +} + +#[test] +fn negative_integer() { + run!( + |arc_process| { + ( + Just(arc_process.clone()), + strategy::term::integer::negative(arc_process.clone()), + ) + }, + |(arc_process, non_int)| { + prop_assert_badarg!(result(&arc_process, non_int), "invalid integer conversion"); + Ok(()) + }, + ); +} + +#[test] +fn not_integer() { + run!( + |arc_process| { + ( + Just(arc_process.clone()), + strategy::term::is_not_integer(arc_process.clone()), + ) + }, + |(arc_process, non_int)| { + prop_assert_badarg!(result(&arc_process, non_int), "invalid integer conversion"); + Ok(()) + }, + ); +} diff --git a/native_implemented/otp/tests/internal/lib.rs b/native_implemented/otp/tests/internal/lib.rs index 8ada4ba80..e54e05371 100644 --- a/native_implemented/otp/tests/internal/lib.rs +++ b/native_implemented/otp/tests/internal/lib.rs @@ -1,3 +1,5 @@ +#[path = "lib/binary.rs"] +pub mod binary; #[path = "lib/erlang.rs"] pub mod erlang; #[path = "lib/maps.rs"] diff --git a/native_implemented/otp/tests/internal/lib/binary.rs b/native_implemented/otp/tests/internal/lib/binary.rs new file mode 100644 index 000000000..d26048ae3 --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary.rs @@ -0,0 +1,2 @@ +#[path = "binary/encode_unsigned_1.rs"] +pub mod encode_unsigned_1; diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1.rs b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1.rs new file mode 100644 index 000000000..6b7538965 --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1.rs @@ -0,0 +1,26 @@ +test_stdout!(doctest, "<<169,138,199>>\n"); +test_stdout!(with_smallest_big_int, "<<64,0,0,0,0,0>>\n"); +test_stdout!( + with_non_integer, + "{caught, error, badarg}\n{caught, error, badarg}\n{caught, error, badarg}\n" +); +test_stdout!( + with_negative_integer, + "{caught, error, badarg}\n{caught, error, badarg}\n" +); +test_stdout!( + when_big_int_encoded_bytes_have_significant_trailing_zeros, + "<<64,0,0,0,0,0>>\n" +); +test_stdout!( + when_small_int_encoded_bytes_have_significant_trailing_zeros, + "<<1,0,0,0>>\n" +); +test_stdout!( + when_small_int_encoded_bytes_have_zeros_in_the_middle, + "<<169,0,199>>\n" +); +test_stdout!( + when_big_int_encoded_bytes_have_zeros_in_the_middle, + "<<64,0,0,0,0,1>>\n" +); diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/doctest/init.erl b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/doctest/init.erl new file mode 100644 index 000000000..3117c05b3 --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/doctest/init.erl @@ -0,0 +1,6 @@ +-module(init). +-export([start/0]). +-import(erlang, [display/1]). + +start() -> + display(binary:encode_unsigned(11111111)). diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_big_int_encoded_bytes_have_significant_trailing_zeros/init.erl b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_big_int_encoded_bytes_have_significant_trailing_zeros/init.erl new file mode 100644 index 000000000..0b4f10f34 --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_big_int_encoded_bytes_have_significant_trailing_zeros/init.erl @@ -0,0 +1,6 @@ +-module(init). +-export([start/0]). +-import(erlang, [display/1]). + +start() -> + display(binary:encode_unsigned(70368744177665)). diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_big_int_encoded_bytes_have_zeros_in_the_middle/init.erl b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_big_int_encoded_bytes_have_zeros_in_the_middle/init.erl new file mode 100644 index 000000000..0b4f10f34 --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_big_int_encoded_bytes_have_zeros_in_the_middle/init.erl @@ -0,0 +1,6 @@ +-module(init). +-export([start/0]). +-import(erlang, [display/1]). + +start() -> + display(binary:encode_unsigned(70368744177665)). diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_small_int_encoded_bytes_have_significant_trailing_zeros/init.erl b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_small_int_encoded_bytes_have_significant_trailing_zeros/init.erl new file mode 100644 index 000000000..65d6a8035 --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_small_int_encoded_bytes_have_significant_trailing_zeros/init.erl @@ -0,0 +1,6 @@ +-module(init). +-export([start/0]). +-import(erlang, [display/1]). + +start() -> + display(binary:encode_unsigned(16777216)). diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_small_int_encoded_bytes_have_zeros_in_the_middle/init.erl b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_small_int_encoded_bytes_have_zeros_in_the_middle/init.erl new file mode 100644 index 000000000..26a486d6e --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/when_small_int_encoded_bytes_have_zeros_in_the_middle/init.erl @@ -0,0 +1,6 @@ +-module(init). +-export([start/0]). +-import(erlang, [display/1]). + +start() -> + display(binary:encode_unsigned(11075783)). diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_negative_integer/init.erl b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_negative_integer/init.erl new file mode 100644 index 000000000..36e5b1e1c --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_negative_integer/init.erl @@ -0,0 +1,11 @@ +-module(init). +-export([start/0]). +-import(erlang, [display/1]). + +start() -> + test:caught(fun () -> + display(binary:encode_unsigned(-1)) + end), + test:caught(fun () -> + display(binary:encode_unsigned(-70368744177664)) + end). \ No newline at end of file diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_non_integer/init.erl b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_non_integer/init.erl new file mode 100644 index 000000000..f060d0e56 --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_non_integer/init.erl @@ -0,0 +1,14 @@ +-module(init). +-export([start/0]). +-import(erlang, [display/1]). + +start() -> + test:caught(fun () -> + display(binary:encode_unsigned(nil)) + end), + test:caught(fun () -> + display(binary:encode_unsigned(foo)) + end), + test:caught(fun () -> + display(binary:encode_unsigned("foo")) + end). \ No newline at end of file diff --git a/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_smallest_big_int/init.erl b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_smallest_big_int/init.erl new file mode 100644 index 000000000..d7d9349e4 --- /dev/null +++ b/native_implemented/otp/tests/internal/lib/binary/encode_unsigned_1/with_smallest_big_int/init.erl @@ -0,0 +1,6 @@ +-module(init). +-export([start/0]). +-import(erlang, [display/1]). + +start() -> + display(binary:encode_unsigned(70368744177664)).