Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement IOList BIFs #362

Merged
merged 29 commits into from Jan 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cc45a0d
Initial implementation of iolist_to_binary/1
mlwilkerson Nov 11, 2019
043d5d9
Implement iolist_size/1
mlwilkerson Nov 11, 2019
628e502
Fix typo in test comments
mlwilkerson Nov 11, 2019
16c504e
Add doctest for iolist_size/1
mlwilkerson Nov 11, 2019
d218479
Auto-formatting
mlwilkerson Nov 11, 2019
227ae25
WIP: initial implementation of iolist_to_iovec_1
mlwilkerson Nov 12, 2019
23553ed
auto-formatting
mlwilkerson Nov 12, 2019
d93ab1d
Revise iolist_to_iovec/1 to process mixed iolists
mlwilkerson Nov 12, 2019
cdc8d7f
auto-formatting
mlwilkerson Nov 12, 2019
6dd6bfa
WIP: re-write iolist_size/1
mlwilkerson Nov 14, 2019
fb9b7c5
auto-formatting
mlwilkerson Nov 14, 2019
299dfb6
Add subbinary testing for iolist_to_binary/1
mlwilkerson Nov 18, 2019
ce34a59
Add tests for subbinary and smallint tail in improper list for iolist…
mlwilkerson Nov 18, 2019
41714c2
handle smallint in tail of improper list and error return in iolist_s…
mlwilkerson Nov 18, 2019
40dfd7b
Add test for non-binary, non-smallint in iolist
mlwilkerson Nov 18, 2019
d484109
WIP: add tests for subbinary, smallint tail in improper list and atom…
mlwilkerson Nov 18, 2019
64b3db1
WIP: fixing iolist_to_iovec/1 to handle errors when converting list e…
mlwilkerson Nov 18, 2019
8eb7281
Fix iteration in iolist_to_iovec/1 to handle errors
mlwilkerson Nov 18, 2019
357967e
auto-formatting
mlwilkerson Nov 18, 2019
e3f4930
Fix ups after rebasing on develop
mlwilkerson Dec 8, 2019
115efdc
Add test for ProcBin
mlwilkerson Dec 8, 2019
15addaf
Ensure the procbin test has a procbin to work with
mlwilkerson Dec 8, 2019
66f1011
Add test coverage for ProcBin
mlwilkerson Dec 8, 2019
1e0ce4e
Add test for iolist_to_binary with ProcBin
mlwilkerson Dec 8, 2019
7bf6919
Use context instead of badarg for errors
KronicDeth Jan 13, 2020
fdd7d4b
Use stack instead of recursion for iolist_size/1
KronicDeth Jan 14, 2020
7f6b3e3
Quote and escape UTF-8 binaries
KronicDeth Jan 14, 2020
eed4eb5
Combine common parts of iolist and iolist_or_binary functions
KronicDeth Jan 14, 2020
8d34892
Escape and quote strings for string_to_* functions
KronicDeth Jan 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}