Skip to content

Commit

Permalink
add reverse map iteration
Browse files Browse the repository at this point in the history
  • Loading branch information
devsnek committed Mar 5, 2024
1 parent 63ca3fe commit e0f1609
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 15 deletions.
38 changes: 30 additions & 8 deletions rustler/src/types/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,22 @@ impl<'a> Term<'a> {
pub struct MapIterator<'a> {
env: Env<'a>,
iter: map::ErlNifMapIterator,
start: map::MapIteratorEntry,
}

impl<'a> MapIterator<'a> {
pub fn new(map: Term<'a>) -> Option<MapIterator<'a>> {
let env = map.get_env();
unsafe { map::map_iterator_create(env.as_c_arg(), map.as_c_arg()) }
.map(|iter| MapIterator { env, iter })
let start = map::MapIteratorEntry::First;
unsafe { map::map_iterator_create(env.as_c_arg(), map.as_c_arg(), start) }
.map(|iter| MapIterator { env, iter, start })
}

pub fn new_from_last(map: Term<'a>) -> Option<MapIterator<'a>> {
let env = map.get_env();
let start = map::MapIteratorEntry::Last;
unsafe { map::map_iterator_create(env.as_c_arg(), map.as_c_arg(), start) }
.map(|iter| MapIterator { env, iter, start })
}
}

Expand All @@ -217,13 +226,26 @@ impl<'a> Drop for MapIterator<'a> {
impl<'a> Iterator for MapIterator<'a> {
type Item = (Term<'a>, Term<'a>);

fn next(&mut self) -> Option<(Term<'a>, Term<'a>)> {
unsafe {
map::map_iterator_get_pair(self.env.as_c_arg(), &mut self.iter).map(|(key, value)| {
map::map_iterator_next(self.env.as_c_arg(), &mut self.iter);
(Term::new(self.env, key), Term::new(self.env, value))
})
fn next(&mut self) -> Option<Self::Item> {
let pair = unsafe {
map::map_iterator_get_pair(self.env.as_c_arg(), &mut self.iter)
.map(|(key, value)| (Term::new(self.env, key), Term::new(self.env, value)))
};

if pair.is_some() {
unsafe {
match self.start {
map::MapIteratorEntry::First => {
map::map_iterator_next(self.env.as_c_arg(), &mut self.iter);
}
map::MapIteratorEntry::Last => {
map::map_iterator_prev(self.env.as_c_arg(), &mut self.iter);
}
}
}
}

pair
}
}

Expand Down
21 changes: 19 additions & 2 deletions rustler/src/wrapper/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,26 @@ pub unsafe fn map_update(
Some(result.assume_init())
}

pub unsafe fn map_iterator_create(env: NIF_ENV, map: NIF_TERM) -> Option<ErlNifMapIterator> {
#[derive(Clone, Copy, Debug)]
pub enum MapIteratorEntry {
First,
Last,
}

pub unsafe fn map_iterator_create(
env: NIF_ENV,
map: NIF_TERM,
entry: MapIteratorEntry,
) -> Option<ErlNifMapIterator> {
let mut iter = MaybeUninit::uninit();
let success = rustler_sys::enif_map_iterator_create(
env,
map,
iter.as_mut_ptr(),
ErlNifMapIteratorEntry::ERL_NIF_MAP_ITERATOR_HEAD,
match entry {
MapIteratorEntry::First => ErlNifMapIteratorEntry::ERL_NIF_MAP_ITERATOR_HEAD,
MapIteratorEntry::Last => ErlNifMapIteratorEntry::ERL_NIF_MAP_ITERATOR_TAIL,
},
);
if success == 0 {
None
Expand Down Expand Up @@ -103,6 +116,10 @@ pub unsafe fn map_iterator_next(env: NIF_ENV, iter: &mut ErlNifMapIterator) {
rustler_sys::enif_map_iterator_next(env, iter);
}

pub unsafe fn map_iterator_prev(env: NIF_ENV, iter: &mut ErlNifMapIterator) {
rustler_sys::enif_map_iterator_prev(env, iter);
}

pub unsafe fn make_map_from_arrays(
env: NIF_ENV,
keys: &[NIF_TERM],
Expand Down
3 changes: 2 additions & 1 deletion rustler_tests/lib/rustler_test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ defmodule RustlerTest do
def term_type(_term), do: err()

def sum_map_values(_), do: err()
def map_entries_sorted(_), do: err()
def map_entries(_), do: err()
def map_entries_reversed(_), do: err()
def map_from_arrays(_keys, _values), do: err()
def map_from_pairs(_pairs), do: err()
def map_generic(_), do: err()
Expand Down
3 changes: 2 additions & 1 deletion rustler_tests/native/rustler_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ rustler::init!(
test_term::term_phash2_hash,
test_term::term_type,
test_map::sum_map_values,
test_map::map_entries_sorted,
test_map::map_entries,
test_map::map_entries_reversed,
test_map::map_from_arrays,
test_map::map_from_pairs,
test_map::map_generic,
Expand Down
20 changes: 18 additions & 2 deletions rustler_tests/native/rustler_test/src/test_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,30 @@ pub fn sum_map_values(iter: MapIterator) -> NifResult<i64> {
}

#[rustler::nif]
pub fn map_entries_sorted<'a>(env: Env<'a>, iter: MapIterator<'a>) -> NifResult<Vec<Term<'a>>> {
pub fn map_entries<'a>(env: Env<'a>, iter: MapIterator<'a>) -> NifResult<Vec<Term<'a>>> {
let mut vec = vec![];
for (key, value) in iter {
let key_string = key.decode::<String>()?;
vec.push((key_string, value));
}

let erlang_pairs: Vec<Term> = vec
.into_iter()
.map(|(key, value)| make_tuple(env, &[key.encode(env), value]))
.collect();
Ok(erlang_pairs)
}

#[rustler::nif]
pub fn map_entries_reversed<'a>(env: Env<'a>, map: Term<'a>) -> NifResult<Vec<Term<'a>>> {
let iter = MapIterator::new_from_last(map).unwrap();

let mut vec = vec![];
for (key, value) in iter {
let key_string = key.decode::<String>()?;
vec.push((key_string, value));
}

vec.sort_by_key(|pair| pair.0.clone());
let erlang_pairs: Vec<Term> = vec
.into_iter()
.map(|(key, value)| make_tuple(env, &[key.encode(env), value]))
Expand Down
7 changes: 6 additions & 1 deletion rustler_tests/test/map_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ defmodule RustlerTest.MapTest do
end

test "map iteration with keys" do
entries = RustlerTest.map_entries(%{"d" => 0, "a" => 1, "b" => 7, "e" => 4, "c" => 6})

assert [{"a", 1}, {"b", 7}, {"c", 6}, {"d", 0}, {"e", 4}] ==
RustlerTest.map_entries_sorted(%{"d" => 0, "a" => 1, "b" => 7, "e" => 4, "c" => 6})
Enum.sort_by(entries, &elem(&1, 0))

assert Enum.reverse(entries) ==
RustlerTest.map_entries_reversed(%{"d" => 0, "a" => 1, "b" => 7, "e" => 4, "c" => 6})
end

test "map from arrays" do
Expand Down

0 comments on commit e0f1609

Please sign in to comment.