Skip to content

Commit

Permalink
Merge pull request #362 from mlwilkerson/iolist-bifs
Browse files Browse the repository at this point in the history
Implement IOList BIFs
  • Loading branch information
KronicDeth committed Jan 14, 2020
2 parents 10802de + 8d34892 commit 305e844
Show file tree
Hide file tree
Showing 31 changed files with 989 additions and 103 deletions.
2 changes: 1 addition & 1 deletion liblumen_alloc/src/erts/term/binary/aligned_binary.rs
Expand Up @@ -216,7 +216,7 @@ impl_aligned_try_into!(BinaryLiteral);
/// Displays a binary using Erlang-style formatting
pub(super) fn display(bytes: &[u8], f: &mut fmt::Formatter) -> fmt::Result {
match str::from_utf8(bytes) {
Ok(s) => write!(f, "{}", s),
Ok(s) => write!(f, "\"{}\"", s.escape_default().to_string()),
Err(_) => {
f.write_str("<<")?;

Expand Down
@@ -0,0 +1 @@
cc 120136f010840952852eb52f87bfba1a91a73bdb62c7d2bdd97dd3f4ce94a709
6 changes: 6 additions & 0 deletions lumen_runtime/src/context.rs
@@ -1,3 +1,5 @@
pub mod r#type;

use std::convert::TryInto;

use anyhow::*;
Expand All @@ -8,6 +10,10 @@ use liblumen_alloc::erts::term::prelude::*;

use crate::time;

pub fn string(name: &'static str, quote: char, value: &str) -> String {
format!("{} ({}{}{})", name, quote, value.escape_default(), quote)
}

pub fn term_is_not_type(name: &str, value: Term, r#type: &str) -> String {
format!("{} ({}) is not {}", name, value, r#type)
}
Expand Down
2 changes: 2 additions & 0 deletions lumen_runtime/src/context/type.rs
@@ -0,0 +1,2 @@
pub const IOLIST: &str =
"a maybe improper list with byte, binary, or iolist elements and binary or empty list tail";
3 changes: 3 additions & 0 deletions lumen_runtime/src/macros/exception.rs
Expand Up @@ -258,6 +258,9 @@ macro_rules! prop_assert_is_not_tuple {

#[cfg(test)]
macro_rules! prop_assert_is_not_type {
($actual:expr, $name:ident, $type:expr) => {
prop_assert_is_not_type!($actual, stringify!($name), $name, $type)
};
($actual:expr, $name:expr, $value:expr, $type:expr) => {
prop_assert_badarg!($actual, format!("{} ({}) is not {}", $name, $value, $type))
};
Expand Down
4 changes: 4 additions & 0 deletions lumen_runtime/src/otp/erlang.rs
Expand Up @@ -72,6 +72,10 @@ pub mod integer_to_binary_2;
pub mod integer_to_list_1;
pub mod integer_to_list_2;
mod integer_to_string;
mod iolist_or_binary;
pub mod iolist_size_1;
pub mod iolist_to_binary_1;
pub mod iolist_to_iovec_1;
pub mod is_alive_0;
pub mod is_atom_1;
pub mod is_binary_1;
Expand Down
2 changes: 1 addition & 1 deletion lumen_runtime/src/otp/erlang/binary_to_float_1.rs
Expand Up @@ -18,5 +18,5 @@ use crate::otp::erlang::string_to_float::string_to_float;
pub fn native(process: &Process, binary: Term) -> exception::Result<Term> {
let string = binary_to_string(binary)?;

string_to_float(process, &string).map_err(From::from)
string_to_float(process, "binary", &string, '"').map_err(From::from)
}
2 changes: 1 addition & 1 deletion lumen_runtime/src/otp/erlang/binary_to_float_1/test.rs
Expand Up @@ -27,7 +27,7 @@ fn with_binary_with_integer_errors_badarg() {
|(arc_process, binary)| {
prop_assert_badarg!(
native(&arc_process, binary),
format!("float string ({}) does not contain decimal point", binary)
format!("binary ({}) does not contain decimal point", binary)
);

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion lumen_runtime/src/otp/erlang/binary_to_integer_1.rs
Expand Up @@ -18,5 +18,5 @@ use crate::otp::erlang::string_to_integer::decimal_string_to_integer;
pub fn native(process: &Process, binary: Term) -> exception::Result<Term> {
let string: String = binary_to_string(binary)?;

decimal_string_to_integer(process, &string).map_err(From::from)
decimal_string_to_integer(process, "binary", '"', &string).map_err(From::from)
}
Expand Up @@ -91,7 +91,7 @@ fn with_non_decimal_errors_badarg() {
|(arc_process, binary)| {
prop_assert_badarg!(
native(&arc_process, binary),
format!("string ({}) is not base 10", binary)
format!("binary ({}) is not base 10", binary)
);

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion lumen_runtime/src/otp/erlang/binary_to_integer_2.rs
Expand Up @@ -18,5 +18,5 @@ use crate::otp::erlang::string_to_integer::base_string_to_integer;
pub fn native(process: &Process, binary: Term, base: Term) -> exception::Result<Term> {
let string: String = binary_to_string(binary)?;

base_string_to_integer(process, base, &string).map_err(From::from)
base_string_to_integer(process, base, "binary", '"', &string).map_err(From::from)
}
2 changes: 1 addition & 1 deletion lumen_runtime/src/otp/erlang/binary_to_integer_2/test.rs
Expand Up @@ -112,7 +112,7 @@ fn with_binary_without_integer_in_base_errors_badarg() {
|(arc_process, binary, base)| {
prop_assert_badarg!(
native(&arc_process, binary, base),
format!("string ({}) is not in base ({})", binary, base)
format!("binary ({}) is not in base ({})", binary, base)
);

Ok(())
Expand Down
121 changes: 121 additions & 0 deletions lumen_runtime/src/otp/erlang/iolist_or_binary.rs
@@ -0,0 +1,121 @@
use std::convert::TryInto;

use anyhow::*;

use liblumen_alloc::erts::exception;
use liblumen_alloc::erts::process::Process;
use liblumen_alloc::erts::term::prelude::*;

use crate::context::{r#type, term_is_not_type};

pub fn element_not_a_binary_context(iolist_or_binary: Term, element: Term) -> String {
format!(
"iolist_or_binary ({}) element ({}) is a bitstring, but not a binary",
iolist_or_binary, element
)
}

pub fn element_type_context(iolist_or_binary: Term, element: Term) -> String {
format!(
"iolist_or_binary ({}) element ({}) is not a byte, binary, or nested iolist ({})",
iolist_or_binary,
element,
r#type::IOLIST
)
}

pub fn native(
process: &Process,
iolist_or_binary: Term,
try_into: fn(&Process, Term) -> exception::Result<Term>,
) -> exception::Result<Term> {
match iolist_or_binary.decode()? {
TypedTerm::Nil
| TypedTerm::List(_)
| TypedTerm::BinaryLiteral(_)
| TypedTerm::HeapBinary(_)
| TypedTerm::MatchContext(_)
| TypedTerm::ProcBin(_)
| TypedTerm::SubBinary(_) => try_into(process, iolist_or_binary),
_ => Err(TypeError)
.context(term_is_not_type(
"iolist_or_binary",
iolist_or_binary,
&format!("an iolist ({}) or binary", r#type::IOLIST),
))
.map_err(From::from),
}
}

pub fn to_binary(process: &Process, name: &'static str, value: Term) -> exception::Result<Term> {
let mut byte_vec: Vec<u8> = Vec::new();
let mut stack: Vec<Term> = vec![value];

while let Some(top) = stack.pop() {
match top.decode()? {
TypedTerm::SmallInteger(small_integer) => {
let top_byte = small_integer
.try_into()
.with_context(|| element_context(name, value, top))?;

byte_vec.push(top_byte);
}
TypedTerm::Nil => (),
TypedTerm::List(boxed_cons) => {
// @type iolist :: maybe_improper_list(byte() | binary() | iolist(),
// binary() | []) means that `byte()` isn't allowed
// for `tail`s unlike `head`.

let tail = boxed_cons.tail;
let result_u8: Result<u8, _> = tail.try_into();

match result_u8 {
Ok(_) => {
return Err(TypeError)
.context(format!(
"{} ({}) tail ({}) cannot be a byte",
name, value, tail
))
.map_err(From::from)
}
Err(_) => stack.push(tail),
};

stack.push(boxed_cons.head);
}
TypedTerm::HeapBinary(heap_binary) => {
byte_vec.extend_from_slice(heap_binary.as_bytes());
}
TypedTerm::SubBinary(subbinary) => {
if subbinary.is_binary() {
if subbinary.is_aligned() {
byte_vec.extend(unsafe { subbinary.as_bytes_unchecked() });
} else {
byte_vec.extend(subbinary.full_byte_iter());
}
} else {
return Err(NotABinary)
.context(element_context(name, value, top))
.map_err(From::from);
}
}
TypedTerm::ProcBin(procbin) => {
byte_vec.extend_from_slice(procbin.as_bytes());
}
_ => {
return Err(TypeError)
.context(element_context(name, value, top))
.map_err(From::from)
}
}
}

Ok(process.binary_from_bytes(byte_vec.as_slice()).unwrap())
}

fn element_context(name: &'static str, value: Term, element: Term) -> String {
format!(
"{} ({}) element ({}) is not a byte, binary, or nested iolist",
name, value, element
)
}
91 changes: 91 additions & 0 deletions lumen_runtime/src/otp/erlang/iolist_size_1.rs
@@ -0,0 +1,91 @@
// wasm32 proptest cannot be compiled at the same time as non-wasm32 proptest, so disable tests that
// use proptest completely for wasm32
//
// See https://github.com/rust-lang/cargo/issues/4866
#[cfg(all(not(target_arch = "wasm32"), test))]
mod test;

use std::convert::TryInto;

use anyhow::*;

use liblumen_alloc::erts::exception;
use liblumen_alloc::erts::process::Process;
use liblumen_alloc::erts::term::prelude::*;

use lumen_runtime_macros::native_implemented_function;

use crate::otp::erlang::iolist_or_binary::{self, *};

/// Returns the size, in bytes, of the binary that would be result from iolist_to_binary/1
#[native_implemented_function(iolist_size/1)]
pub fn native(process: &Process, iolist_or_binary: Term) -> exception::Result<Term> {
iolist_or_binary::native(process, iolist_or_binary, iolist_or_binary_size)
}

fn iolist_or_binary_size(process: &Process, iolist_or_binary: Term) -> exception::Result<Term> {
let mut size: usize = 0;
let mut stack: Vec<Term> = vec![iolist_or_binary];

while let Some(top) = stack.pop() {
match top.decode()? {
TypedTerm::SmallInteger(small_integer) => {
let _: u8 = small_integer
.try_into()
.with_context(|| element_type_context(iolist_or_binary, top))?;

size += 1;
}
TypedTerm::Nil => (),
TypedTerm::List(boxed_cons) => {
// @type iolist :: maybe_improper_list(byte() | binary() | iolist(),
// binary() | []) means that `byte()` isn't allowed
// for `tail`s unlike `head`.

let tail = boxed_cons.tail;
let result_u8: Result<u8, _> = tail.try_into();

match result_u8 {
Ok(_) => {
return Err(TypeError)
.context(format!(
"iolist_or_binary ({}) tail ({}) cannot be a byte",
iolist_or_binary, tail
))
.map_err(From::from)
}
Err(_) => stack.push(tail),
};

stack.push(boxed_cons.head);
}
TypedTerm::HeapBinary(heap_binary) => size += heap_binary.full_byte_len(),
TypedTerm::MatchContext(match_context) => {
if match_context.is_binary() {
size += match_context.full_byte_len();
} else {
return Err(NotABinary)
.context(element_not_a_binary_context(iolist_or_binary, top))
.map_err(From::from);
}
}
TypedTerm::ProcBin(proc_bin) => size += proc_bin.total_byte_len(),
TypedTerm::SubBinary(subbinary) => {
if subbinary.is_binary() {
size += subbinary.full_byte_len();
} else {
return Err(NotABinary)
.context(element_not_a_binary_context(iolist_or_binary, top))
.map_err(From::from);
}
}
_ => {
return Err(TypeError)
.context(element_type_context(iolist_or_binary, top))
.map_err(From::from);
}
}
}

process.integer(size).map_err(From::from)
}

0 comments on commit 305e844

Please sign in to comment.